Skip to content
Daniel Gerson edited this page Aug 29, 2013 · 33 revisions

TriplePlay uses PlayN’s API so make sure you’re familiar with the various rendering types before continuing. ( src: https://developers.google.com/playn/devguide/rendering )

The TriplePlay UI system is designed to provide standardized widgets and layouts for UI interaction, while preserving and utilizing PlayN’s game loop to achieve this goal. As such, the UI system will usually be kicked off as follows:

  • Create a tripleplay.ui.Interface instance.
  • Use this interface instance to create various root elements
  • Add a number of tripleplay.ui.Group instances to the root as a hierarchy.
  • Add various widgets as the lowest elements in the hierarchy.

##Understanding tripleplay.ui.Root The Root element represents the top of a discrete hierarchy and can be thought of similarly to the RootPanel in GWT. The Root acts simlarly to the Group below in that it also operates with a provided Layout policy.

The root will intercept all pointer events in its bounds, but it shouldn't mess with pointer events outside its bounds. Call root.pack() to pack the max bounds for interception.

##Understanding tripleplay.ui.Interface The interface object is needed to marshal all the time related “events” of all the root panels it’s created and for all of their children. As such, it provides an Animator instance (refer to above) that all the sub elements will use for their animations(see tripleplay.ui.Menu for an example), and also provides a similar style Task mechanism that elements can use to make use of the update-loop until such time as the Task completes.

Are you supposed to only create one interface? Ideally yes, but if you have separate parts of your PlayN game or have packaged a library to be used in other games that needed control of its own interface, then it's conceivable to have more.

##Understanding tripleplay.ui.Group : As is described in the documentation, the Group element is used for containing other elements and laying them out according to a layout-policy.

##Making sense of Style, Styles and Stylesheet : The styles mechanism in tripleplay is similar, but not exactly the same as CSS. Every property of an element that is considered a style of that element, is represented by an instance of the Style class. The sum collection of styles that happen to be set for an element is represented by an instance of Styles. A collection of Styles for various kinds of elements (or for various elements??) is represented by a Stylesheet.

##Understanding 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.

?? How does preferredSize affect things ??

?? How does validation? computeSize()? layout()? affect things ??

####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.

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

A thread on attempting to animate buttons

Clone this wiki locally