UI Components: Component List
Component groups and component lists are containers for other components. The difference between the two is that a component group is a known collection of components that can be dynamically sized; while a component list is a dynamic collection of components with more limited layout options.
Using component groups and lists, component UI system provides out of the box functionality for standard requirements like re-flowing / responsively positioned UI elements. Indigo's UI system is not as clever as using something like HTML, but should satisfy many common use cases.
Example Links
How to set up a component list
Imports
Before we do anything else, we'll need some additional imports:
import indigoextras.ui.*
import indigoextras.ui.syntax.*
And until issue #814 is resolved, we'll also need this import for convenience:
import indigo.shared.subsystems.SubSystemContext.*
Defining our component list
We're going to set up a component list of labels. It isn't necessary, but in this example our
list will have a dynamic number of elements based on a value in the game's model, passed across
as reference data in the UIContext
.
This dynamic layout ability is something ComponentGroup
cannot do, but reduces the available
layout options. So you need to choice the right component type for your needs.
object CustomComponents:
val listOfLabels: ComponentList[Int] =
ComponentList(Dimensions(200, 80)) { (labelCount: Int) =>
(1 to labelCount).toBatch.map { i =>
ComponentId("lbl" + i) -> Label[Int](
"Custom label " + i,
(_, label) => Bounds(0, 0, 250, 20)
) { case (offset, label, dimensions) =>
Outcome(
Layer(
TextBox(label)
.withColor(RGBA.White)
.moveTo(offset.unsafeToPoint)
.withSize(dimensions.unsafeToSize)
.withFontSize(20.pixels)
)
)
}
}
}
.withLayout(ComponentLayout.Vertical(Padding(10)))
Setting up the Model
The model contains the component list. Component lists define collections of components, and how they should be laid out. They have various options that affect their layout behaviour.
Here we initialise our model with the component list.
final case class Model(numOfLabels: Int, components: ComponentList[Int])
object Model:
val initial: Model =
Model(4, CustomComponents.listOfLabels)
Updating the Model
One thing to note is that we need to construct a UIContext
to pass to the component group.
This is important, if a little cumbersome.
The UIContext
holds any custom reference data we might like to propagate down through the
component hierarchy, but also information about the grid size the UI is operating on (normally
1x1, this feature is really for use cases like ASCII / terminal UIs) and the magnification of
the UI.
The UIContext
also provides the top level position of the component hierarchy, so to move
the group and so the button to a new position, we need to tell the UIContext
to move the
bounds by the desired amount using moveBoundsBy
.
In this example we need to update the context with the model value, and supply it along with the event to the component list's update method.
def updateModel(context: Context[Unit], model: Model): GlobalEvent => Outcome[Model] =
case e =>
val ctx = UIContext(context.forSubSystems, Size(1), 1)
.moveBoundsBy(Coords(50, 50))
.copy(reference = model.numOfLabels)
model.components.update(ctx)(e).map { cl =>
model.copy(components = cl)
}
Presenting the component list
The component list knows how to render everything, we just need to call the present
method
with, once again, and instance of UIContext, and provide the results to a SceneUpdateFragment.
def present(context: Context[Unit], model: Model): Outcome[SceneUpdateFragment] =
val ctx = UIContext(context.forSubSystems, Size(1), 1)
.moveBoundsBy(Coords(50, 50))
.copy(reference = model.numOfLabels)
model.components
.present(ctx)
.map(l => SceneUpdateFragment(l))