-
Notifications
You must be signed in to change notification settings - Fork 0
Tripleplay UI Layout
Layouts of any UI framework can be confusing, depending on your expectation, given other systems you may have used. Use the BoundaryDrawer.java tool (haven't yet posted it, it draws nested groups in different colours), to demonstrate how your code displays the Roots, Groups and different types of layers.
Tripleplay uses the Layout class to define a set of constraints (layout policy). It then has a LayoutData which as far as I can tell is kind of an instantiation of a particular Layout which is responsible for computing the size of that element/group given it's context.
Here's Michael's explanation of the mechanics, which can be supplemented by these notes. Especially the stacktrace logging further down on the page.
####Example part 1. Let's start with an example.
//in the init()
GroupLayer layer = graphics().createGroupLayer();
iface = new Interface(); //used to take in delegate as argument
Stylesheet sheet = SimpleStyles.newSheet();
Root root = iface.createRoot(AxisLayout.vertical().gap(5), sheet);
root.addStyles(Style.HALIGN.left);
layer.add(root.layer);
The important things to notice here is we create an Interface, use it to create a Root with a vertical layout. We also add a style to the root (which is essentially a constraint.. the demarcation between styles and constraints is not crystal clear).
Finally we add the Root's layer to a GroupLayer which we add to the scene.
The most complicated code that is called so far is the root.addStyles(...), but this only really creates a new style bindings object the element in question (in this case the Root).
Nothing so far has actually really happened, except for a PlayN layer that has been created on behalf of the root.
//continuing in init()
Group group = new Group(new AxisLayout.Horizontal());
root.add(group);
group.add(new Button("A"));
group.add(new Button("B"));
group.add(new Button("C"));
This creates a Group and a series of buttons. Much like the code above, very little has actually happened yet. The button's text is specified, but not yet created. See part 2 below for an explanation of how the wiring of the button works.
//finally in init()
PlayN.graphics().rootLayer().add(layer);
root.pack();
####part 2
/** Creates a button with the supplied text and icon. */
public Button (String text, Icon icon) {
this.text.update(text);
this.icon.update(icon);
this.text.connect(textDidChange());
this.icon.connect(iconDidChange());
}
What exactly is happening here? We do know that the layerings/styles etc of the button have not been manifested yet (except for the icon which would have been created as a layer).
If this stuff here looks freaky apon inspection... it's because it is! You'll see lots of UnitSlots in the tripleplay ui code which hark from the react library. Here is an overview guide and there are also some youtube vids if you search for them. Functional-reactive-programming does indeed seem an interesting technology.
From a cheat sheet perspective, always remember that there are types of objects that emit events: Signals emit one time events. Values emit events when their value gets changed.
Then there are Slots which listen for events and let you run some specific code (a handler).
I've done some very light searching for more complex examples, but can't find any. I imagine the most interesting examples are in the games that threerings ships ;-)
####Part 3
So I had initially been exploring the code, stepping into things, trying to keep the whole story in my head. It was too hard and error prone and the documentation I wrote at that point was flawed. So instead with the help of this helper script I wrote to indent the stack-trace and a whole lot of breakpoints, I avoided the tediousness and came out with something more meaningful at a glance.
Game.Default.init()
Element Constructor [class tripleplay.ui.Root]
Element.addStyles(..) [class tripleplay.ui.Root]
Element.invalidate() [class tripleplay.ui.Root]
Element Constructor [class tripleplay.ui.Group]
Elements.add(..) [class tripleplay.ui.Root]
Element.invalidate() [class tripleplay.ui.Group]
Element.invalidate() [class tripleplay.ui.Root]
Element Constructor [class tripleplay.ui.Button]
Elements.add(..) [class tripleplay.ui.Group]
Element.invalidate() [class tripleplay.ui.Button]
Element.invalidate() [class tripleplay.ui.Group]
Element Constructor [class tripleplay.ui.Button]
Elements.add(..) [class tripleplay.ui.Group]
Element.invalidate() [class tripleplay.ui.Button]
Element.invalidate() [class tripleplay.ui.Group]
Element Constructor [class tripleplay.ui.Button]
Elements.add(..) [class tripleplay.ui.Group]
Element.invalidate() [class tripleplay.ui.Button]
Element.invalidate() [class tripleplay.ui.Group]
Root.pack()
Element.preferredSize (start) currently has a null value
Element.computeSize() [class tripleplay.ui.Root] Insets:0.0,0.0
ElementsLayoutData.computeSize()
Vertical.computerSize()
AxisLayout.computeMetrics()
Element.preferredSize (start) currently has a null value
Element.computeSize() [class tripleplay.ui.Group] Insets:0.0,0.0
ElementsLayoutData.computeSize()
Horizontal.computeSize()
AxisLayout.computeMetrics() //<----- sizes are actually computed
Element.preferredSize (start) currently has a null value
new TextLayoutData() [creates text for button]
Element.computeSize() [class tripleplay.ui.Button] Insets:12.0,7.0
TextWidget.computeSize() [class tripleplay.ui.TextWidget$TextLayoutData]
TextWidget.addTextSize()
end of TextWidget.computeSize() Hint:-12.0,-7.0 Size:12.765625,21.6015625
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 0.0, 0.0 Size:25.0,29.0
Element.preferredSize (start) currently has a null value
new TextLayoutData() [creates text for button]
Element.computeSize() [class tripleplay.ui.Button] Insets:12.0,7.0
TextWidget.computeSize() [class tripleplay.ui.TextWidget$TextLayoutData]
TextWidget.addTextSize()
end of TextWidget.computeSize() Hint:-12.0,-7.0 Size:11.828125,21.6015625
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 0.0, 0.0 Size:24.0,29.0
Element.preferredSize (start) currently has a null value
new TextLayoutData() [creates text for button]
Element.computeSize() [class tripleplay.ui.Button] Insets:12.0,7.0
TextWidget.computeSize() [class tripleplay.ui.TextWidget$TextLayoutData]
TextWidget.addTextSize()
end of TextWidget.computeSize() Hint:-12.0,-7.0 Size:12.921875,21.6015625
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 0.0, 0.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Group] Hint: 0.0, 0.0 Size:84.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Root] Hint: 0.0, 0.0 Size:84.0,29.0
Element.invalidate() [class tripleplay.ui.Root]
Note that although sizes have been calculated, and even the playn.core.TextLayout has been created when the TextLayoutData was constructed, no actual playn layers have so far been created.
Then when the paint method is called, the actual layout begins.
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
Element.layout() [class tripleplay.ui.Root]
Element.layout() instantiate a background
ElementsLayoutData.layout() [class tripleplay.ui.Elements$ElementsLayoutData] args :0.0, 0.0, 84.0, 29.0
Vertical.layout()
AxisLayout.computeMetrics()
Element.preferredSize() (end) [class tripleplay.ui.Group] Hint: 84.0, 29.0 Size:84.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Group] Hint: 84.0, 29.0 Size:84.0,29.0
Layout.setBounds()
Element.invalidate() [class tripleplay.ui.Group]
Element.validate() [class tripleplay.ui.Group]
Element.layout() [class tripleplay.ui.Group]
Element.layout() instantiate a background
ElementsLayoutData.layout() [class tripleplay.ui.Elements$ElementsLayoutData] args :0.0, 0.0, 84.0, 29.0
Horizontal.layout()
AxisLayout.computeMetrics()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Layout.setBounds()
Element.invalidate() [class tripleplay.ui.Button]
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Layout.setBounds()
Element.invalidate() [class tripleplay.ui.Button]
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Layout.setBounds()
Element.invalidate() [class tripleplay.ui.Button]
Element.validate() [class tripleplay.ui.Button]
Element.layout() [class tripleplay.ui.Button]
Element.layout() instantiate a background //<----- layers for button A
TextWidget.updateTextGlyph()
Element.validate() [class tripleplay.ui.Button]
Element.layout() [class tripleplay.ui.Button]
Element.layout() instantiate a background //<----- layers for button B
TextWidget.updateTextGlyph()
Element.validate() [class tripleplay.ui.Button]
Element.layout() [class tripleplay.ui.Button]
Element.layout() instantiate a background //<----- layers for button C
TextWidget.updateTextGlyph()
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
Here all the layout is done, and at the end the actual ui playn elements are actually created.
After that it's merely painting, checking for validation with nothing to do.
If you press down on a button the following happens. Remember that calling invalidate on an element only removes it's _preferredSize. The layoutdata _ldata associated with the element is only removed after a successful layout or in some other outlier cases.
JavaPointer.onMouseDown()
Button.onPress() SELECTED
Element.invalidate() [class tripleplay.ui.Button]
Element.invalidate() [class tripleplay.ui.Group]
Element.invalidate() [class tripleplay.ui.Root]
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
Element.layout() [class tripleplay.ui.Root]
ElementsLayoutData.layout() [class tripleplay.ui.Elements$ElementsLayoutData] args :0.0, 0.0, 84.0, 29.0
Vertical.layout()
AxisLayout.computeMetrics()
Element.preferredSize (start) currently has a null value
Element.computeSize() [class tripleplay.ui.Group] Insets:0.0,0.0
ElementsLayoutData.computeSize()
Horizontal.computeSize()
AxisLayout.computeMetrics()
Element.preferredSize (start) currently has a null value
new TextLayoutData() [creates text for button]
Element.computeSize() [class tripleplay.ui.Button] Insets:12.0,7.0
TextWidget.computeSize() [class tripleplay.ui.TextWidget$TextLayoutData]
TextWidget.addTextSize()
end of TextWidget.computeSize() Hint:72.0,22.0 Size:12.765625,21.6015625
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize (start) currently has a null value
new TextLayoutData() [creates text for button]
Element.computeSize() [class tripleplay.ui.Button] Insets:12.0,7.0
TextWidget.computeSize() [class tripleplay.ui.TextWidget$TextLayoutData]
TextWidget.addTextSize()
end of TextWidget.computeSize() Hint:72.0,22.0 Size:11.828125,21.6015625
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Element.preferredSize (start) currently has a null value
new TextLayoutData() [creates text for button]
Element.computeSize() [class tripleplay.ui.Button] Insets:12.0,7.0
TextWidget.computeSize() [class tripleplay.ui.TextWidget$TextLayoutData]
TextWidget.addTextSize()
end of TextWidget.computeSize() Hint:72.0,22.0 Size:12.921875,21.6015625
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Group] Hint: 84.0, 29.0 Size:84.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Group] Hint: 84.0, 29.0 Size:84.0,29.0
Layout.setBounds()
Element.validate() [class tripleplay.ui.Group]
Element.layout() [class tripleplay.ui.Group]
ElementsLayoutData.layout() [class tripleplay.ui.Elements$ElementsLayoutData] args :0.0, 0.0, 84.0, 29.0
Horizontal.layout()
AxisLayout.computeMetrics()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Layout.setBounds()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Layout.setBounds()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Layout.setBounds()
Element.validate() [class tripleplay.ui.Button]
Element.validate() [class tripleplay.ui.Button]
Element.validate() [class tripleplay.ui.Button]
Element.layout() [class tripleplay.ui.Button]
Element.layout() instantiate a background <-------- visual effect of click
TextWidget.updateTextGlyph()
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
and when you release the mouse button
JavaPointer.onMouseUp
Element.invalidate() [class tripleplay.ui.Button]
Element.invalidate() [class tripleplay.ui.Group]
Element.invalidate() [class tripleplay.ui.Root]
Button.onClick()
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
Element.layout() [class tripleplay.ui.Root]
ElementsLayoutData.layout() [class tripleplay.ui.Elements$ElementsLayoutData] args :0.0, 0.0, 84.0, 29.0
Vertical.layout()
AxisLayout.computeMetrics()
Element.preferredSize (start) currently has a null value
Element.computeSize() [class tripleplay.ui.Group] Insets:0.0,0.0
ElementsLayoutData.computeSize()
Horizontal.computeSize()
AxisLayout.computeMetrics()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Element.preferredSize (start) currently has a null value
new TextLayoutData() [creates text for button]
Element.computeSize() [class tripleplay.ui.Button] Insets:12.0,7.0
TextWidget.computeSize() [class tripleplay.ui.TextWidget$TextLayoutData]
TextWidget.addTextSize()
end of TextWidget.computeSize() Hint:72.0,22.0 Size:12.921875,21.6015625
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Group] Hint: 84.0, 29.0 Size:84.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Group] Hint: 84.0, 29.0 Size:84.0,29.0
Layout.setBounds()
Element.validate() [class tripleplay.ui.Group]
Element.layout() [class tripleplay.ui.Group]
ElementsLayoutData.layout() [class tripleplay.ui.Elements$ElementsLayoutData] args :0.0, 0.0, 84.0, 29.0
Horizontal.layout()
AxisLayout.computeMetrics()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Layout.setBounds()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:24.0,29.0
Layout.setBounds()
Element.preferredSize() (end) [class tripleplay.ui.Button] Hint: 84.0, 29.0 Size:25.0,29.0
Layout.setBounds()
Element.validate() [class tripleplay.ui.Button]
Element.validate() [class tripleplay.ui.Button]
Element.validate() [class tripleplay.ui.Button]
Element.layout() [class tripleplay.ui.Button]
Element.layout() instantiate a background //<------- and button back to normal.
TextWidget.updateTextGlyph()
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
Game.Default.paint()
Element.validate() [class tripleplay.ui.Root]
###Extra notes