[bmx] MaxGUI Layout by Otus [ 1+ years ago ]

Started by BlitzBot, June 29, 2017, 00:28:43

Previous topic - Next topic

BlitzBot

Title : MaxGUI Layout
Author : Otus
Posted : 1+ years ago

Description : 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:
Code: BASIC
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:
Code: BASIC
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: [/i]

Code :
Code: blitzmax
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...