MaxGUI Overview


Introduction

The MaxGui module provides commands to build user interfaces for applications programmed in BlitzMax.

The command set originates from the original BlitzPlus "Gadget" based names providing a healthy amount of backward compatability for users wishing to port code developed in BlitzPlus.

Getting Started

The following program opens a small window on the desktop that ends when the window is closed.

The code beginning at "While True" is the main event loop of the program, it repeats forever waiting for an Event to happen, printing the event's details to the output window and ending if the event has the ID of EVENT_WINDOWCLOSE.

' createwindow.bmx

Strict 

Local window:TGadget

window=CreateWindow("My Window",40,40,320,240)

While True
	WaitEvent 
	Print CurrentEvent.ToString()
	Select EventID()
		Case EVENT_WINDOWCLOSE
			End
	End Select
Wend
Try moving and sizing the window while the program is running and you will see the details of the events that occur printed in the debug window of BlitzMax.

Try also modifying the "window=" line in the program with:

window=CreateWindow("My Window",40,40,320,240,Null,WINDOW_TITLEBAR)
which will create a Window with only a title bar and no status or menu bars. See the documentation for CreateWindow for more details of the Window styles supported by MaxGui.

The MaxGui documentation contains working examples for each of commands that create gadgets including CreateButton, CreateCanvas, CreateComboBox, CreateHTMLView, CreateLabel, CreateListBox, CreateMenu, CreatePanel, CreateProgBar, CreateSlider, CreateTabber, CreateTextField, CreateToolBar and CreateTreeView.

Gadget Layout

A Gadget in MaxGui is given pixel coordinates when it is created that positions it in it's parent's client area. The client area of a Window gadget is inside the window's borders not including the space used by any Status or Menu bars.

If a Window is resized MaxGui uses any children's layout settings to reposition them within the new size of it's client area.

The SetGadgetLayout is used to control how the Gadget is repositioned when it's parent is resized.

By considering each edge of the Gadget independently, the MaxGui layout engine repositions each gadget according to each edge's rule where the rule is

EDGE_CENTERED - The edge remains a fixed distance from the center of the parent
EDGE_ALIGNED - The edge is a locked distance away from it's parent's corresponding edge
EDGE_RELATIVE - The edge remains a proportional distance from the parent's 2 edges

The following code demonstrates a common case of creating a panel in a window the size of the window's client area and locking the distance of each edge to the correspong edge of the parent window (EDGE_ALIGNED rule). The result is that the panel is automatically sized by MaxGui when the window is resized filling the entire client area.

Strict 

Local window:TGadget
Local panel:TGadget

window=CreateWindow("My Window",40,40,320,240)

panel=CreatePanel(0,0,ClientWidth(window),ClientHeight(window),window)
SetGadgetColor panel,200,100,100
SetGadgetLayout panel,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED

While True
	WaitEvent 
	Print CurrentEvent.ToString()
	Select EventID()
		Case EVENT_WINDOWCLOSE
			End
	End Select
Wend
By removing the line of code with the SetGadetLayout command you can view the default EDGE_CENTERED rule in effect on all edges with the panel retaining it's size due to all it's edges being kept a fixed distance from the center of the window.

To view rule EDGE_RELATIVE in effect modify the SetGadgetLayout command to read "SetGadgetLayout panel,EDGE_RELATIVE,EDGE_RELATIVE,EDGE_RELATIVE,EDGE_RELATIVE".

But wait, there is no discernable change in the resizing behavior between rule EDGE_ALIGNED and rule EDGE_RELATIVE???

Unless the panel is created inset from the window client such as the result of the following modification:

panel=CreatePanel(10,10,ClientWidth(window)-20,ClientHeight(window)-20,window)
you will note the edges are infact being kept a proportional distance from the parent's 2 edges when the window is resized.

The fact is that EDGE_ALIGNED and EDGE_RELATIVE have the same effect on Gadget edges when the edges are created on the borders of their parent's client area.

Crossplatform Issues

In general the MaxGUI module should perform identically on Windows, Apple and Linux machines. However some care and attention is required when testing in regards to how your application will look on each platform.

The following are a few issues that if considered should hopefully enable you to publish programs that look and function well on a wide variety of systems.

Recomended gadget dimensions.

Use the WINDOW_CLIENTCOORDS flag when dependent on client size.

If you are using a fixed size window specify it's size based on the required client area using the WINDOW_CLIENTCOORDS style.

Specifying a window's size in the default frame coordinates will result in a client area that may be different on different machines due to a number of factors including both the version of the computer's operating system and certain user preference outside your control.

Using an EventHook

As an alternative to developing applications that have a single main loop that waits then processes events from an eventqueue the AddHook command allows a callback function to be used to process events as they happen.

This is important for two type of events.

EVENT_GADGETACTION events produced by Slider gadgets with the original SLIDER_SCROLLBAR style and EVENT_WINDOWRESIZE events that occur when the user is resizing a Window both require a hooked type of event processing for best results. In both these situations most OS's go into a "modal" loop that means a WaitEvent command will only return once the user releases the mouse button but with Event hooks the code can be called directly from within the OS's own modal loop.

The other type of event is EVENT_GADGETPAINT which is generated whenever the operating system needs to redraw a Canvas gadget. When hooked, drawing code can be executed at the time of the event rather than later when the OS returns control to the main program, at which time it may have already drawn other content above the Canvas.

Using a Canvas

The following code is a simple example of using Max2D to render dynamically into a Canvas Gadget...

' createcanvas.bmx

Strict 

Global GAME_WIDTH=320
Global GAME_HEIGHT=240

' create a centered window with client size GAME_WIDTH,GAME_HEIGHT

Local wx=(GadgetWidth(Desktop())-GAME_WIDTH)/2
Local wy=(GadgetHeight(Desktop())-GAME_HEIGHT)/2

Local window:TGadget=CreateWindow("My Canvas",wx,wy,GAME_WIDTH,GAME_HEIGHT,0,WINDOW_TITLEBAR|WINDOW_CLIENTCOORDS)

' create a canvas for our game

Local canvas:TGadget=CreateCanvas(0,0,320,240,window)

' create an update timer

CreateTimer 60

While WaitEvent()
	Select EventID()
		Case EVENT_TIMERTICK
			RedrawGadget canvas

		Case EVENT_GADGETPAINT
			Local g=CanvasGraphics(canvas)
			SetGraphics g
			SetOrigin 160,120
			SetLineWidth 5
			Cls
			Local t=MilliSecs()
			DrawLine 0,0,120*Cos(t),120*Sin(t)
			DrawLine 0,0,80*Cos(t/60),80*Sin(t/60)
			Flip

		Case EVENT_WINDOWCLOSE
			FreeGadget canvas
			End

		Case EVENT_APPTERMINATE
			End
	End Select
Wend
The above code creates a timer that causes an event to occur at 60hz (ticks per second).

Each time an EVENT_TIMERTICK is received by the program's main event loop it calls RedrawGadget which tells the system to redraw our canvas gadget.

The code following the Case EVENT_GADGETPAINT above is where the actual drawing takes place.

- of interest -

If you remove two lines of the above code that contain the "RedrawGadget canvas" and "Case EVENT_GADGETPAINT" the program still appears to function normally.

This is because MaxGUI infact allows you to draw to a canvas when ever you want. Drawing in response to an EVENT_GADGETPAINT however is better behavior as no drawing code is executed if the canvas is obscured or the application has a low refresh rate in which case it will be still asked to redraw itself on the event an obscured canvas is revealed by window movement from above.

Event Hook based Rendering

As mentioned above the best way to manage redrawing a Canvas gadget is with an EventHook.

The following example uses an EventHook to handle events at the time of occurance rather than from the WaitEvent() queue. You will notice at the bottom of the program that infact the main loop does nothing but WaitEvent and all the event handling is done inside the TApplet Type.

' rendering a canvas using an EventHook based Applet Type

Strict

Type TApplet
	
	Field	window:TGadget
	Field	canvas:TGadget
	Field	timer:TTimer
	
	Method Render()
		Local x,y,width,height,t,i
		width=GraphicsWidth()
		height=GraphicsHeight()
		t=MilliSecs()
		SetBlend ALPHABLEND
		SetViewport 0,0,width,height
		SetClsColor 0,0,0
		SetOrigin width/2,height/2
		Cls
		For x=-width/2 To width/2 Step 2
			y=Sin(t*0.3+x)*height/2
			DrawLine 0,0,x,y
		Next
	End Method
	
	Method OnEvent(event:TEvent)
		Select event.id
		Case EVENT_WINDOWCLOSE
			End
		Case EVENT_TIMERTICK
			RedrawGadget canvas
		Case EVENT_GADGETPAINT
			SetGraphics CanvasGraphics(canvas)
			Render
			Flip
		End Select
	End Method

	Function eventhook:Object(id,data:Object,context:Object)
		Local	event:TEvent
		Local	app:TApplet
		event=TEvent(data)
		app=TApplet(context)
		app.OnEvent event	
	End Function
	
	Method Create:TApplet(name$)
		Local	a:TApplet
		Local	w,h
		window=CreateWindow(name,20,20,512,512)
		window.SetTarget Self
		w=ClientWidth(window)
		h=ClientHeight(window)
		canvas=CreateCanvas(0,0,w,h,window)
		canvas.SetLayout 1,1,1,1
		canvas.SetTarget Self		
		timer=CreateTimer(100)
		AddHook EmitEventHook,eventhook,Self
		Return Self		
	End Method
	
End Type

AutoMidHandle True

Local	applet:TApplet

applet=New TApplet.Create("Render Applet")

While True
	WaitEvent
Wend

Unfortunately I have obfiscated (made obscure) the function of the event hook by wrapping it in an Applet object and I have started using big words in my sentences.

To be as concise as possible, the TApplet Type declares a Class that features an OnEvent Method that with some magic in it's constructor (the Create() Method) provides an object oriented way for our program to collect events.

Our TApplet implements the rendering sequence as featured in the previous example but because it does so based on a Hook responds in a more well behaved manner when the OS wishes to redraw the desktop when it is made visible by some user action or told to refresh with the RedrawGadget command.

Porting from BlitzPlus

The following are a few issues that you may encounter when porting code from BlitzPlus.

Event Codes

The codes returned by the WaitEvent and EventID commands are not the same as BlitzPlus. See the Event Objects docs for a list of the constants used in MaxGUI. Numerical values have not been documented in order to encourage users to use the correct EVENT_NAMES.

New events in MaxGUI include

EVENT_HOTKEYHIT - see SetHotKeyEvent

EVENT_WINDOWACCEPT - a file has been dragged onto the window

EVENT_GADGETPAINT - a canvas gadget needs redrawing

EVENT_GADGETSELECT - the selected state of a TextArea has changed

EVENT_GADGETMENU - the user has clicked the right button on this gadget

EVENT_GADGETOPEN - a node in a TreeViewNode has been expanded

EVENT_GADGETCLOSE - a node in a TreeViewNode has been collapsed

EVENT_GADGETDONE - an HTMLView has finished loading a page

TextArea filters

TextAreas no longer generate Key Up, Key Down or Key Stroke events.

Instead the new SetGadgetFilter command can be used to install a callback routine that is called each time the user presses a key with the callback able to accept or reject each key by returning either True or False respectively.

Popup Menus

There is a new PopupWindowMenu command that can be used for implementing context menus commonly activated by the right mouse button (reported as an EVENT_GADGETMENU for most gadgets).

The New Modules

The three platform specific modules that ship with MaxGui are Win32MaxGui.mod, CocoaMaxGui.mod and FLTKMaxGui.mod which provide native implementations of MaxGui for Windows, Apple MacOS and Linux platforms respectively.

The new Event.mod and EventQueue.mod modules are extensions to the BlitzMax system. BlitzMax events provide the necessary mechanics of communicating with a MaxGui based application as well as providing all BlitzMax applications with events such as EVENT_APPSUSPEND, EVENT_APPRESUME and EVENT_APPTERMINATE.

The commands WaitEvent, PeekEvent and PollEvent are the primary commands for receiving Events in BlitzMax programs and PostEvent can be used for generating Events.

Under the Hood

The Type TGadget is used in MaxGui to represent all 18 Gadget types including:

GADGET_DESKTOP GADGET_WINDOW GADGET_BUTTON GADGET_PANEL GADGET_TEXTFIELD GADGET_TEXTAREA GADGET_COMBOBOX GADGET_LISTBOX GADGET_TOOLBAR GADGET_TABBER GADGET_TREEVIEW GADGET_HTMLVIEW GADGET_LABEL GADGET_SLIDER GADGET_PROGBAR GADGET_MENUITEM GADGET_NODE GADGET_CANVAS

The TMaxGUIDriver Type defined in maxgui.driver represents the gadget factory which is created at startup to produce platform specific versions of TGadget on demand.

The type TGadget is actually an abstract class with TWin32Gadget, TCocoaGadget and TFLTKGadget being the actual types produced depending on the current TMaxGUIDriver.

The types TIconStrip, TGUIFont and TGadgetItem also provide an abstract interface to the platform specific implementations meaning that like TGadget itself, they are not intended to be extended by the user.

Crossplatform vs Flexibility

It is expected that most users wanting to create custom gadgets that work well across all platforms will find the Panel and Canvas gadgets provide adequate Event reporting and rendering functionality from which to produce their own uber-gadgets.

The cross platform nature of MaxGUI means there are implications for those wishing to extend functionality by taking advantage of platform specific features that have no logical equivalent on other platforms.

Those wishing to do so are encouraged to implement such features in separate modules rather than making modifications to the BRL modules which are intended only to support the core cross platform MaxGUI functionality.

This of course does not mean the the current specification of MaxGUI is set in stone, rather, that those wanting to interface at a lower level with the Operating System of their choice do so in the privacy of their own modules rather than becoming muddied in the source of the lowlevel platform specific BRL modules.