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

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