-
Notifications
You must be signed in to change notification settings - Fork 17
GUI Layout
Here we create two buttons and add a layout procedure:
import wNim/[wApp, wFrame, wPanel, wButton]
let app = App()
let frame = Frame(title="wNim Frame", size=(400, 300))
let panel = Panel(frame)
let button1 = Button(panel, label="Button1")
let button2 = Button(panel, label="Button2")
proc layout() =
button1.size = (panel.size.width div 2, panel.size.height)
button1.position = (0, 0)
button2.size = (panel.size.width div 2, panel.size.height)
button2.position = (panel.size.width div 2, 0)
layout()
frame.center()
frame.show()
app.mainLoop()
It seems good, but after resizing the frame:
The problem is that the layout don't change after resizing, so we add a event handler for that:
import wNim/[wApp, wFrame, wPanel, wButton]
let app = App()
let frame = Frame(title="wNim Frame", size=(400, 300))
let panel = Panel(frame)
let button1 = Button(panel, label="Button1")
let button2 = Button(panel, label="Button2")
proc layout() =
button1.size = (panel.size.width div 2, panel.size.height)
button1.position = (0, 0)
button2.size = (panel.size.width div 2, panel.size.height)
button2.position = (panel.size.width div 2, 0)
# frame.wEvent_Size already has it's default event handler to resize the panel.
# see https://github.com/khchen/wNim/wiki/Event-Handling#skip-event
panel.wEvent_Size do ():
layout()
layout()
frame.center()
frame.show()
app.mainLoop()
Now it works well.
If we create a lot of elements, counting the size and position for every elements is a bored thing. We can use layout DSL to count them automatically.
import wNim/[wApp, wFrame, wPanel, wButton]
let app = App()
let frame = Frame(title="wNim Frame", size=(400, 300))
let panel = Panel(frame)
let button1 = Button(panel, label="Button1")
let button2 = Button(panel, label="Button2")
proc layout() =
panel.layout:
button1:
top = panel.top
left = panel.left
bottom = panel.bottom
right = button2.left
button2:
top = panel.top
right = panel.right
bottom = panel.bottom
width = button1.width
panel.wEvent_Size do ():
layout()
layout()
frame.center()
frame.show()
app.mainLoop()
Layout DSL is easy to use. But sometimes it need more code than counting manually. We can use autolayout module to create the layout DSL at compiling time.
import wNim/[wApp, wFrame, wPanel, wButton]
let app = App()
let frame = Frame(title="wNim Frame", size=(400, 300))
let panel = Panel(frame)
let button1 = Button(panel, label="Button1")
let button2 = Button(panel, label="Button2")
proc layout() =
panel.autolayout """
H:|[button1][button2(button1)]|
V:|[button1,button2]|
"""
panel.wEvent_Size do ():
layout()
layout()
frame.center()
frame.show()
app.mainLoop()
With autolayout, it is easier to design the layout. For example, if we want to add some spacing:
import wNim/[wApp, wFrame, wPanel, wButton]
let app = App()
let frame = Frame(title="wNim Frame", size=(400, 300))
let panel = Panel(frame)
let button1 = Button(panel, label="Button1")
let button2 = Button(panel, label="Button2")
proc layout() =
panel.autolayout """
H:|-[button1]-[button2(button1)]-|
V:|-[button1,button2]-|
"""
panel.wEvent_Size do ():
layout()
layout()
frame.center()
frame.show()
app.mainLoop()
When we use autolayout, it will be translated to layout DSL, and then the layout DSL will be translated to nim code which create a wResizer object internally. All code translation is done at compiling time, and the wResizer uses Yuriy Glukhov's Kiwi constraint solving library to arrange the associated elements at running time.
We can see the output DSL by using autolayoutDump or autolayoutDebug macros:
panel.autolayoutDump """
H:|-[button1]-[button2(button1)]-|
V:|-[button1,button2]-|
"""
panel.layout:
button1:
left = panel.left + 10
top = panel.top + 10
bottom = panel.bottom - 10
button2:
left = button1.right + 10
width = button1.width
right = panel.right - 10
top = panel.top + 10
bottom = panel.bottom - 10
We can also see the output nim code by using layoutDump or layoutDebug macros:
panel.layoutDump:
button1:
left = panel.left + 10
top = panel.top + 10
bottom = panel.bottom - 10
button2:
left = button1.right + 10
width = button1.width
right = panel.right - 10
top = panel.top + 10
bottom = panel.bottom - 10
self = button1
resizer.addObject(button1)
resizer.addConstraint(self.left == panel.left + 10)
resizer.addConstraint(self.top == panel.top + 10)
resizer.addConstraint(self.bottom == panel.bottom - 10)
self = button2
resizer.addObject(button2)
resizer.addConstraint(self.left == button1.right + 10)
resizer.addConstraint(self.width == button1.width)
resizer.addConstraint(self.right == panel.right - 10)
resizer.addConstraint(self.top == panel.top + 10)
resizer.addConstraint(self.bottom == panel.bottom - 10)
Consider following example:
import random
import wNim/[wApp, wFrame, wPanel, wButton]
randomize()
let app = App()
let frame = Frame(title="wNim Frame", size=(400, 300))
let panel = Panel(frame)
let button1 = Button(panel, label="Button1")
let button2 = Button(panel, label="Button2")
proc layout() =
let gap = rand(50)
panel.layout:
button1:
top = panel.top
left = panel.left
bottom = panel.bottom
right + gap = button2.left
button2:
top = panel.top
right = panel.right
bottom = panel.bottom
width = button1.width
panel.wEvent_Size do ():
layout()
layout()
frame.center()
frame.show()
app.mainLoop()
Here we add a random gap between two buttons. the "gap" variable should be different every time when layout() is called. However, in this example, we need to restart the program to get a new gap. This is because layout DSL by default only initiate the constraint once for performance. If a new value is really needed every time, we can use relayout macro to replace the layout macro:
panel.relayout:
button1:
top = panel.top
left = panel.left
bottom = panel.bottom
right + gap = button2.left
button2:
top = panel.top
right = panel.right
bottom = panel.bottom
width = button1.width