An FP game engine for Scala.

UI Components: Label

This example shows how to set up a Label using Indigo's general UI system.

A word on appearance

Indigo's UI component system is a generic UI layout system, and as such does not come with any specific rendering / look-and-feel helpers. In this example, the component's visuals will be constructed using simple primitives like shapes and text boxes, but the look and feel are only limited by what you can get Indigo to render.

How to set up a custom label

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 a custom label

To keep the code nice and tidy, we'll define our custom label in a separate object.

To render the label we're going to use a TextBox. TextBox is not a great choice in practice, because it is relatively expensive, and to calculate the bounds for dynamic text we'll need to find a way to supply a Context.Services.Bounds instance. But for this demo, it'll do.

Text in labels can be static, or dynamic based on the 'reference' data in the UIContext, as in the example below.

object CustomComponents:

  val customLabel: Label[Int] =
    Label[Int](
      "Count: 0",
      (_, label) => Bounds(0, 0, 150, 20)
    ) { case (offset, label, dimensions) =>
      Outcome(
        Layer(
          TextBox(label)
            .withColor(RGBA.White)
            .moveTo(offset.unsafeToPoint)
            .withSize(dimensions.unsafeToSize)
            .withFontSize(20.pixels)
        )
      )
    }
      .withText((i: Int) => "Count: " + i)

Setting up the Model

Here we initialise our model with our label, and the 'count', which will be injected as the reference data, later.

final case class Model(count: Int, label: Label[Int])
object Model:

  val initial: Model =
    Model(
      42,
      CustomComponents.customLabel
    )

Updating the Model

We need to construct a UIContext to pass to the component group, and then we can update the label by supply it with the context and the event.

  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.count)

      model.label.update(ctx)(e).map { l =>
        model.copy(label = l)
      }

Presenting the Label

We 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.count)

    model.label
      .present(ctx)
      .map(l => SceneUpdateFragment(l))