So far we have only discussed how to implement the behavior of the application, but not how it looks. In this chapter, we will focus on this topic.
Similar to the workflow, the layout of your application is also defined in XML. Layouts consist of a LayoutPage
which defines a reusable frame for multiple screens and a LayoutModel
which is used to design the specifics of one particular screen. Additionally, we can define Style
to apply to multiple layout elements and PartTemplate
that make specific layout parts reusable.
All of these structural elements are defined in their separate files. Of course, they still have to be filled with content like text, buttons, or images. You can take a look at the list of UI elements to get an overview.
The layout page for our "Choice" component just includes the status bar, a title, and a content section using the <ContentPlaceHolder>
tag, which will be filled by the "LayoutModel".
<LayoutPage Name="DefaultMaster"> <Part Template="STATUS_BAR" Weight="0.1"/> <Text Name="Topic" Weight=".15" Style="TitleStyle" Content="Pick a fruit"/> <Panel Weight="0.75"> <ContentPlaceHolder Name="Content"/> </Panel> </LayoutPage>
The "LayoutModel" looks like this:
<LayoutModel Name="ChoiceScreen" Page="DefaultMaster" Orientation="Vertical"> <Content PlaceHolder="Content" Weight="1" Orientation="Horizontal"> <Button Name="Apple" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle"> <Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="Apple"/> <Events/> </Button> <Button Name="Pear" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle"> <Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="Pear"/> <Events/> </Button> </Content> </LayoutModel>
"LayoutModel" is the element we connect with the workflow. This is done when defining a step of the workflow: <step id="choose" descriptor="the user selects between two options" uitemplate="ChoiceScreen">
. As you can see above, the "LayoutModel" then refers to the Page="DefaultMaster
to get its outer frame. From the "LayoutPage", it then references the <ContentPlaceHolder Name="Content"/>
using <Content PlaceHolder="Content"...
.
Depending on the type of UI element, you can use a range of attributes to style the user interface to your liking. The two attributes you should first look at, are weight
and orientation
, since they help you design the general structure of your UI.
Weight
defines the size of an element in a range between 0 and 1, where 1 is 100% of the parent element's size.Orientation
is applied to a parent element and determines whether the child elements are horizontal
(next to each other) or vertical
(on top of each other) within the parent element.In our above "LayoutModel", the Content
element has Horizontal
orientation, so the buttons will be shown next to each other. They each get to use Weight="0.5"
(so 50%) of the size available to the Content
element. The text elements inside the buttons, on the other hand, use the full space available to their parent element (the button) with Weight="1"
.
Oftentimes, when setting up the size and orientation of your elements it helps to make use of Panel
elements. These elements are purely structural and can be used to easily change the orientation within a parent element or subdivide its space.
When working with buttons, one attribute is especially important: FocusOrder
. This attribute defines the order in which buttons will be focused if you flip through them using hardware buttons. It also defines which button will be pre-selected, namely the button with FocusOrder="0"
. Each button in your layout needs to have a different FocusOrder
. Even if some buttons are in a PartTemplate and thus in a different file, they still need to be in this overall order of your layout.
Tips & tricks: The focus order of your layout needs to start at 0 and be sequential without any gaps or duplicates. If this is not the case your application will likely crash upon trying to load the layout. If you ever experience sudden application crashes during development, verifying your FocusOrder should immediately come to your mind.
It is possible to format the texts that are displayed in the smart glasses with .html
tags. You can add the formatting to a UI template, in a layout file or to a component while creating a new text within the code.
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="<h1>Red Apples taste the best</h1> green Apples<br>are not so <sub>tasty</sub>"/>
The outcome of the formatting can vary from the Layout component it is assigned to and the particular component's configuration.
For more information on this, see Text Formatting under the Workflow Engine Reference section.
What is being shown to the user is seldom static. Any user interaction will typically also result in some change in the UI. Of course, we do not want to create a new layout and step every time the content of a text field changes. There are two ways to adapt the UI dynamically: creating a <mapping>
in the workflow and using the ui_update
action.
Mapping allows you to permanently associate a value or variable with an attribute of a UI element. If you map a variable, the attribute of the UI element will automatically be changed whenever the value of the variable changes.
<step ...> <states>...</states> <mapping> <ui_element name="LEFT_TEXT"> <param name="content">#{left_option}</param> <param name="text_color">#{option_color}</param> </ui_element> </mapping> </step>
The example above shows, that the mapping is added as a child of the step tag. You can add any number of ui_element tags here which would reference a UI element from your layout. In this case name="LEFT_TEXT
, the text of the button represents our first option. Whenever the variable #{left_option}
is changed in your workflow, the UI would show a different text on that button.
Another option to dynamically make changes to your layout is the ui_update
action. This action performs a one-time change on the layout:
<ui_update id="change_left_button_text"> <widget_params> <ui_element name="LEFT_TEXT"> <param name="content">#{left_option}</param> <param name="text_color">#{option_color}</param> </ui_element> </widget_params> </ui_update>
In most use cases, it is preferable to just set up a mapping instead of using this action. There is, however, one use case in which the mapping does not work and you are required to use the ui_update
action:
JavaScript inside a mapping is only executed once, so if you need to use JS and the value changes during the lifecycle of the step you will have to manually update the UI using the ui_update
action.
So far we have chosen between two textual options. Now we want to add images into the mix:
content
attribute of the image tag.Download Component (Pre-Assignment)
Download the two-item images.
Below is an exemplary solution:
<Button Name="Apple" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle"> <Image Name="LEFT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{apple.jpg}§" ScaleType="CenterCrop"/> <Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="Apple"/> <Events/> </Button> <Button Name="Pear" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle"> <Image Name="RIGHT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{pear.jpg}§" ScaleType="CenterCrop"/> <Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="Pear"/> <Events/> </Button>
Download Component (Post-Assignment)
In the next lesson, you will learn how to make the component configurable so that it becomes reusable.