Code archives/BlitzPlus Gui/MaxGUI Layout

This code has been declared by its author to be Public Domain code.

Download source code

MaxGUI Layout by Otus2009
SetGadgetLayout only works for very basic layout needs. Here is more advanced (but still simple) layout management for MaxGUI.

Three layout types are implemented:
- Grid Layout, which makes each gadget fill a cell in a grid
- Box Layout, which stacks gadgets next to each other either vertically or horizontally
- Grid Intersection Layout, which positions gadgets in a grid, but keeps their original size if possible.

Where resizing must be supported, layouts should be associated with either a parent window, or a parent gadget that is laid out (has been added to another layout). Any other gadgets won't produce the necessary events when resized.

sample1.bmx:
SuperStrict

Framework MaxGUI.Drivers

Import "layout.bmx"

' Main window
Local flags:Int = WINDOW_CLIENTCOORDS | WINDOW_TITLEBAR | WINDOW_CENTER | WINDOW_RESIZABLE
Local win:TGadget = CreateWindow("Layout Sample 1", 0,0, 400,200, Null, flags)
SetMinWindowSize win, 100,100

' Grid layout for window with two cells and a gap
Local winlay:TGridLayout = TGridLayout.Create(win, 1, 2, 0, 25)

' Upper panel
Local panel1:TGadget = CreatePanel(0,0,0,0,win)
SetGadgetColor panel1, 255,0,0
winlay.AddGadget panel1, 0,0

' Box layout for upper panel: horizontal, centered and a gap
Local pan1lay:TBoxLayout = TBoxLayout.Create(panel1, TBoxLayout.X_AXIS, TBoxLayout.ALIGN_CENTER, 5)

' Buttons for upper panel
For Local i% = 1 To 3
	Local b:TGadget = CreateButton(i, 0,0, i*30, i*20, panel1)
	pan1lay.AddGadget b
Next

' Lower panel
Local panel2:TGadget = CreatePanel(0,0,0,0,win)
SetGadgetColor panel2, 0,0,255
winlay.AddGadget panel2, 0,1

' Grid intersection layout for lower panel: centers the gadget and restricts its size
Local pan2lay:TGridIntLayout = TGridIntLayout.Create(panel2, 1, 1)

' Button for lower panel
Local ok:TGadget = CreateButton("OK", 0,0,150,25, panel2, BUTTON_OK)
pan2lay.AddGadget ok, 0,0


' Quit after closing window
Global quit:Int
Function Hook:Object(id:Int, data:Object, context:Object)
	Local e:TEvent = TEvent(data)
	If e.id=EVENT_WINDOWCLOSE Or e.id=EVENT_APPTERMINATE Then quit=True
	Return data
End Function
AddHook EmitEventHook, Hook

' Main loop
Repeat
	WaitSystem
Until quit


sample2.bmx:
SuperStrict

Framework MaxGUI.Drivers

Import "layout.bmx"

' Main window
Local flags:Int = WINDOW_CLIENTCOORDS | WINDOW_TITLEBAR | WINDOW_CENTER | WINDOW_RESIZABLE
Local win:TGadget = CreateWindow("Layout Sample 2", 0,0, 200,300, Null, flags)
SetMinWindowSize win, 150,150

' Vertical layout
Local vlay:TGridLayout = TGridLayout.Create(win, 1, 4, 10, 10, 1)

' Panels
Local panel:TGadget[4]
For Local i% = 0 To 3
	panel[i] = CreatePanel(0,0,0,0, win)
	vlay.AddGadget panel[i], 0, i
Next

' Form fields:

' Box layouts
Local blay:TBoxLayout[3]
For Local i% = 0 To 2
	blay[i] = TBoxLayout.Create(panel[i], TBoxLayout.X_AXIS, TBoxLayout.ALIGN_CENTER, 15)
Next

' Labels
Local names:String[] = ["Name:", "Email:", "Password:"]
For Local i% = 0 To 2
	blay[i].AddGadget CreateLabel(names[i], 0,0, 25, 24, panel[i])
Next

' Text fields
For Local i% = 0 To 2
	blay[i].AddGadget CreateTextField(0,0, 75, 24, panel[i])
Next

' OK button:

' Grid layout for centering
Local glay:TGridIntLayout = TGridIntLayout.Create(panel[3], 1, 1)
glay.AddGadget CreateButton("OK", 0,0, 80,24, panel[3], BUTTON_OK), 0, 0

' Quit after closing window
Global quit:Int
Function Hook:Object(id:Int, data:Object, context:Object)
	Local e:TEvent = TEvent(data)
	If e.id=EVENT_WINDOWCLOSE Or e.id=EVENT_APPTERMINATE Then quit=True
	Return data
End Function
AddHook EmitEventHook, Hook

' Main loop
Repeat
	WaitSystem
Until quit


Layout.bmx:
SuperStrict

Import MaxGUI.MaxGUI

' Abstract type that handles resize events
Type TLayout Abstract
	
	Field parent:TGadget
	
	Method Update() Abstract
	
	Method SetParent(g:TGadget)
		If Not parent Then AddHook EmitEventHook, _Hook, Self
		parent = g
	End Method
	
	Global resize_event:Int = AllocUserEventId("Layout Resize")
	
	Function _Hook:Object(id:Int, data:Object, context:Object)
		Local event:TEvent = TEvent(data)
		If event.id=EVENT_WINDOWSIZE Or event.id=resize_event
			Local l:TLayout = TLayout(context)
			If l And event.source=l.parent Then l.Update()
		End If
		Return data
	End Function
	
End Type

' Grid layout makes gadget fill cells in a grid
Type TGridLayout Extends TLayout
	
	Field items:TList = New TList
	
	Field rows:Int, cols:Int, hgap:Int, vgap:Int, egap:Int
	
	' Add a gadget at position
	Method AddGadget(g:TGadget, x:Int, y:Int)
		Assert (0<=x) And (0<=y) And (x<cols) And (y<rows),..
			"Gadget position outside grid!"
		Local i:TGridItem = New TGridItem
		i.gadget = g
		i.x = x
		i.y = y
		items.AddLast i
		Update
	End Method
	
	Method Update()
		Local gw:Float = Float(ClientWidth(parent) - hgap*(cols-1+2*egap)) / cols
		Local gh:Float = Float(ClientHeight(parent) - vgap*(rows-1+2*egap)) / rows
		For Local i:TGridItem = EachIn items
			Local gx:Int = gw*i.x + hgap*(i.x+egap), gy:Int = gh*i.y + vgap*(i.y+egap)
			SetGadgetShape i.gadget, gx, gy, gw, gh
			EmitEvent TEvent.Create(TLayout.resize_event, i.gadget)
		Next
	End Method
	
	' Create a grid layout. Optional gaps between cells. Setting egap=True also adds gaps outside grid.
	Function Create:TGridLayout(g:TGadget, cols:Int, rows:Int, hgap:Int=0, vgap:Int=0, egap:Int=False)
		Local l:TGridLayout = New TGridLayout
		l.SetParent g
		l.rows = rows
		l.cols = cols
		l.hgap = hgap
		l.vgap = vgap
		l.egap = egap
		Return l
	End Function
	
End Type

Type TGridItem
	
	Field gadget:TGadget
	
	Field x:Int, y:Int
	
End Type

' Box layout stacks gadgets either vertically or horizontally
Type TBoxLayout Extends TLayout
	
	Const X_AXIS:Int = 0
	Const Y_AXIS:Int = 1
	
	Const ALIGN_LEFT:Int = 0
	Const ALIGN_CENTER:Int = 1
	Const ALIGN_RIGHT:Int = 2
	
	Const ALIGN_TOP:Int = 0
	Const ALIGN_BOTTOM:Int = 2
	
	Field items:TList = New TList
	
	Field axis:Int, align:Int, gap:Int
	
	Field wtot:Int, htot:Int, num:Int
	
	' Adds a gadget
	Method AddGadget(g:TGadget)
		Local i:TBoxItem = New TBoxItem
		i.gadget = g
		i.w = GadgetWidth(g)
		i.h = GadgetHeight(g)
		items.AddLast i
		UpdateTotals
		Update
	End Method
	
	Method UpdateTotals()
		wtot = 0
		htot = 0
		num = 0
		For Local i:TBoxItem = EachIn items
			wtot :+ i.w
			htot :+ i.h
			num :+ 1
		Next
	End Method
	
	Method Update()
		Local w:Int = ClientWidth(parent)
		Local h:Int = ClientHeight(parent)
		If axis = X_AXIS
			Local x:Int, n:Int
			Local factor:Float = Float(w-(num-1)*gap)/wtot
			For Local i:TBoxItem = EachIn items
				Local gx:Int = x*factor
				x :+ i.w
				Local gw:Int = x*factor - gx
				gx :+ n*gap
				n :+ 1
				Local gy:Int
				Local gh:Int = Min(i.h, h)
				If align=ALIGN_TOP
					gy = 0
				Else If align=ALIGN_CENTER
					gy = (h-gh)/2
				Else
					gy = h-gh
				End If
				SetGadgetShape i.gadget, gx, gy, gw, gh
			Next
		Else
			Local y:Int, n:Int
			Local factor:Float = Float(h-(num-1)*gap)/htot
			For Local i:TBoxItem = EachIn items
				Local gy:Int = y*factor
				y :+ i.h
				Local gh:Int = y*factor - gy
				gy :+ n*gap
				n :+ 1
				Local gx:Int
				Local gw:Int = Min(i.w, w)
				If align=ALIGN_LEFT
					gx = 0
				Else If align=ALIGN_CENTER
					gx = (w-gw)/2
				Else
					gx = w-gw
				End If
				SetGadgetShape i.gadget, gx, gy, gw, gh
			Next
		End If
	End Method
	
	' Creates a box layout. See constants above for valid axis and align values.
	Function Create:TBoxLayout(g:TGadget, axis:Int, align:Int, gap:Int=0)
		Local l:TBoxLayout = New TBoxLayout
		l.SetParent g
		l.axis = axis
		l.align = align
		l.gap = gap
		Return l
	End Function
	
End Type

Type TBoxItem
	
	Field gadget:TGadget
	
	Field w:Int, h:Int
	
End Type

' Grid intersection layout positions gadgets in a grid, but only resizes when necessary
Type TGridIntLayout Extends TLayout
	
	Field items:TList = New TList
	
	Field rows:Int, cols:Int
	
	' Adds a gadgt at position
	Method AddGadget(g:TGadget, x:Int, y:Int)
		Assert (0<=x) And (0<=y) And (x<cols) And (y<rows),..
			"Gadget position outside grid!"
		Local i:TGridIntItem = New TGridIntItem
		i.gadget = g
		i.x = x
		i.y = y
		i.w = GadgetWidth(g)
		i.h = GadgetHeight(g)
		items.AddLast i
		Update
	End Method
	
	Method Update()
		Local w:Int = ClientWidth(parent) / cols
		Local h:Int = ClientHeight(parent) / rows
		For Local i:TGridIntItem = EachIn items
			Local gw:Int = Min(i.w, w)
			Local gh:Int = Min(i.h, h)
			Local gx:Int = (i.x+0.5)*w- gw/2, gy:Int = (i.y+0.5)*h- gh/2
			SetGadgetShape i.gadget, gx, gy, gw, gh
			EmitEvent TEvent.Create(TLayout.resize_event, i.gadget)
		Next
	End Method
	
	' Creates a layout for gadget, allows optional gaps
	Function Create:TGridIntLayout(g:TGadget, cols:Int, rows:Int)
		Local l:TGridIntLayout = New TGridIntLayout
		l.SetParent g
		l.rows = rows
		l.cols = cols
		Return l
	End Function
	
End Type

Type TGridIntItem
	
	Field gadget:TGadget
	
	Field x:Int, y:Int, w:Int, h:Int
	
End Type

Comments

None.

Code Archives Forum