18Graphical User Interface

18.1Overview

wxWidgets

Tk

SDL

18.2wxWidgets

18.2.1About wxWidgets

Gura's wx module uses libraries of wxWidgets 3.0.0.

18.2.2Simple Example

The code below is the simplest example that shows an empty window.

import(wx)

MyApp = class(wx.App) {
    OnInit() = {
        frame = MyFrame('Button Test', size => wx.Size(200, 100))
        frame.Show()
        true
    }
}

MyFrame = class(wx.Frame) {
    __init__(title:string, pos:wx.Point => wx.DefaultPosition,
            size:wx.Size => wx.DefaultSize) = {|nil, wx.ID_ANY, title, pos, size|
        wx.Button(this, wx.ID_ANY, 'Push Me')
    }
}

wx.IMPLEMENT_APP(MyApp)

An application using wx module must create a class that derives from wx.App and implement OnInit() method in it. The method is responsible of initializing GUI-related resource and creating a main frame. It should return true at the end if no error occurs.

In the above example, the main frame is declared by a class MyFrame that derives from wx.Frame, which has a constructor function including an instance creation of wx.Button contol. You can create any necessary controls within the constructor.

An application class is realized by calling wx.IMPLEMENT_APP, which runs a main loop in it.

18.2.3Event Handling

There are several ways to address event handling. The first one is to call wx.Window#Bind method to the control instance like below.

import(wx)

MyApp = class(wx.App) {
    OnInit() = {
        frame = MyFrame('Button Test', size => wx.Size(200, 100))
        frame.Show()
        true
    }
}

MyFrame = class(wx.Frame) {
    __init__(title:string, pos:wx.Point => wx.DefaultPosition,
            size:wx.Size => wx.DefaultSize) = {|nil, wx.ID_ANY, title, pos, size|
        ctrl = wx.Button(this, wx.ID_ANY, 'Push Me')
        ctrl.Bind(wx.EVT_BUTTON) {|event|
            wx.MessageBox('Button was pushed', 'Button Test', wx.OK, this)
        }
    }
}

wx.IMPLEMENT_APP(MyApp)

You need to specify an event type like wx.EVT_BUTTON as an argument for wx.Window#Bind method and also describe a procedure that will be evaluated when the event occurs as its block. You may specify a block parameter event, which will take an instance of wx.CommandEvent class at the block's evaluation. Even though the button controls doesn't offer much information with the event instance, more complicated controls could include more data in it.

Another approach is to assign unique identifiers to controls and let the parent window to handle events that are sent from them. The example comes like this:

import(wx)

MyApp = class(wx.App) {
    OnInit() = {
        frame = MyFrame('Button Test', size => wx.Size(200, 100))
        frame.Show()
        true
    }
}

MyFrame = class(wx.Frame) {
    [
        ID_BTN_PushMe
    ] = wx.NewIds()
    __init__(title:string, pos:wx.Point => wx.DefaultPosition,
            size:wx.Size => wx.DefaultSize) = {|nil, wx.ID_ANY, title, pos, size|
        ctrl = wx.Button(this, ID_BTN_PushMe, 'Push Me')
        this.Bind(wx.EVT_BUTTON, ID_BTN_PushMe) {|event|
            wx.MessageBox('Button was pushed', 'Button Test', wx.OK, this)
        }
    }
}

wx.IMPLEMENT_APP(MyApp)

The function wx.NewIds generates as many unique identifers as you want. You can specify one of them to the second argument of a control constructor and also the second argument of window#Bind method. The identifier is necessary because the parent window must determine what control has issued the event.

18.2.4Layout Management

You can use classes derived from wx.Sizer to arrange controls' size and position.

import(wx)

MyApp = class(wx.App) {
    OnInit() = {
        frame = MyFrame('Button Test', size => wx.Size(200, 200))
        frame.Show()
        true
    }
}

MyFrame = class(wx.Frame) {
    __init__(title:string, pos:wx.Point => wx.DefaultPosition,
            size:wx.Size => wx.DefaultSize) = {|nil, wx.ID_ANY, title, pos, size|
        vbox = wx.BoxSizer(wx.VERTICAL)
        this.SetSizer(vbox)
        ctrl = wx.Button(this, wx.ID_ANY, 'First')
        vbox.Add(ctrl, wx.SizerFlags(1).Expand())
        ctrl = wx.Button(this, wx.ID_ANY, 'Second')
        vbox.Add(ctrl, wx.SizerFlags(1).Expand())
        ctrl = wx.Button(this, wx.ID_ANY, 'Third')
        vbox.Add(ctrl, wx.SizerFlags(1).Expand())
    }
}

wx.IMPLEMENT_APP(MyApp)

wx.BoxSizer is one the sizer classes that layouts controls in a direction, either vertical or horizontal. A top-level sizer must be associated to the window by window#SetSizer method. And then, you can put each control under the sizer's management by calling wx.Sizer#Add method. The method takes a wx.SizerFlags instance as its second argument, with which you can specify how the control's size is arranged.

18.2.5More Sample Scripts

You can find sample scripts using wxWidgets on GitHub repository.

18.3Tk

18.3.1About Tk

Gura provides modules named tcl and tk that use Tcl/Tk library for GUI programming.

18.3.2Simple Example

The following example creates a window that has one Button widget.

import(tk)

tk.mainwindow() {|mw|
    mw.Button(text => 'Push me') {|w|
        w.pack()
        w.bind(`command) {
            w.tk$MessageBox(title => 'event', message => 'hello')
        }
    }
}
tk.mainloop()

18.3.3Sample Script

The code below is a drawing program. I have ported it from a sample in TkDocs.

import(tk)

tk.mainwindow() {|mw|
    mw.Canvas(bg => 'white') {|c|
        c.pack(fill => 'both', expand => true)
        [lastx, lasty] = [0, 0]
        color = 'black'
        c.bind('<1>') {|x:number, y:number|
            [lastx, lasty] = [x, y]
        }
        c.bind('<B1-Motion>') {|x:number, y:number|
            addLine(x, y)
        }
        addLine(x:number, y:number) = {
            extern(lastx, lasty)
            c.Line(lastx, lasty, x, y, fill => color, width => 3)
            [lastx, lasty] = [x, y]
        }
        setColor(colorNew:string) = {
            color:extern = colorNew
        }
        function(color:string, y:number):map {
            c.Rectangle(10, y, 30, y + 20, fill => color) {|item|
                item.bind('<1>') { setColor(color) }
            }
        }(['red', 'blue', 'black'], 10 + (0..) * 25)
    }
}
tk.mainloop()

Sample result.

tk-demo

18.3.4More Sample Scripts

You can find sample scripts using Tk on GitHub repository.

18.4SDL

18.4.1About SDL

Gura provides a module named sdl that uses SDL library.

SDL, Simple DirectMedia Layer, is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.

18.4.2Simple Example

The following script only shows a blank window by using SDL.

import(sdl)

sdl.Init(sdl.INIT_EVERYTHING)
screen = sdl.SetVideoMode(640, 480, 16, sdl.SWSURFACE)
repeat {
    event = sdl.WaitEvent()
    (event.type == sdl.QUIT) && break
}

At first, you have to initialize SDL's status by calling sdl.Init. Then, calling sdl.SetVideoMode with screen size and depth in its arguments will show a window.

Unlike other GUI platform, SDL requires you to implement an event handling loop explicitly. The function sdl.WaitEvent would wait until some events come in and returns an instance of sdl.Event class that contains event type and related information.

18.4.3More Sample Scripts

You can find sample scripts using SDL on GitHub repository.