Unlocking the Power of CSS: Transforming Figuro with Seamless Theming

The Problem of Theming

After a couple of years of working on Figuro, a framework for building custom GUI apps with small self-contained binaries, I finally decided it was time to add theming to it. There’s something pleasing about being able to theme a GUI application while it’s running. It’s one of the great secrets behind HTML’s success. It also solves one of the bigger pain points in Figuro which was making it easier to develop both customizable and re-usable widgets.

Still it wasn’t clear how best to add theming. Would it be best to make a custom theming file format using Figuro’s properties and syntax? Or to build on something else? Perhaps it’d be possible to create a Nim based DSL for theming. These thoughts and questions lingered for a few months as I let my thoughts simmer on the topic.

Eventually I decided that creating a custom theming DSL would also require defining the semantics for the DSL as well. It’s hard enough developing the core of a GUI system, but creating a useful theming language on top would be even more work. Also building on Nim’s VM to borrow Figuro’s syntax for a DSL is great for development but would require embedding the Nim compiler as well which is less appealing – despite the compilers compact size.

Styling Widgets from the Outside

Let’s go back and check out some of the issues with making style-able widgets. Take this snippet of Figuro’s Button widget to see some of the limitations with styling widgets:

type
  Button*[T] = ref object of StatefulFiguro[T]
    label*: string
    disabled*: bool
    clickMode*: set[ButtonClicks] = {Single}

... 

proc draw*[T](self: Button[T]) {.slot.} =
  ## button widget!
  with self:
    clipContent true
  withOptional self:
    cornerRadius 10.0'ui

  if self.disabled:
    withOptional self:
      fill css"#F0F0F0"
  else:
    withOptional self:
      fill css"#2B9FEA"
    self.onHover:
      withOptional self:
        fill self, self.fill.lighten(0.14)
        # this changes the color on hover!

In this code you can see that styling a node is really easy in Figuro. However, making it so a user of the widget can style the Button required adding constructs such as withOptional self: which only changes the node’s color if the user hasn’t already set that particular field. For example:

          Button[int].new("btn"):
            let btn = node
            with node:
              size 100'ux, 100'ux
              fill css"red"
              connect(doHover, self, btnHover)
              connect(doClick, node, btnClicked)
            Text.new "text":
              with node:
                fill blackColor
                setText({font: $(btn.state)}, Center, Middle)

This requires widgets to be written differently than writing application code. That didn’t sit well with me. For various technical reasons a widget’s draw method (slot) executes after the user’s code. This allows the user to configure size and other important parameters. However, whether the user’s code is run before or after the widget’s you end up with an annoying ordering issue.

Briefly I experimented with pre and post blocks, but this didn’t feel ideal. It moves Figuro away from it’s “enlightened” mixed of both declarative and imperative style syntax. Writing applications requires more knowledge of when and where to properly add things.

          Button[int].new("btn"):
            size 100'ux, 100'ux
            node.connect(doHover, self, btnHover)
            node.connect(doClick, node, btnClicked)
            Text.new "text":
              node.setText({font: $(btn.state)}, Center, Middle)
              ...
          finally:
            node.size 100'ux, 100'ux
            node.fill css"red"

Enter CSS

Figuro takes it’s original design from Fidget and builds upon (and significantly improves) that original design. Fidget had some awesome ideas on code interacting with Figma. Still at it’s heart, Figuro is based on Nodes at its core.

Each node is simple with only a few properties. The API is less complex than the DOM at the heart of HTML. Despite being simpler than DOM nodes, Figuro Nodes still form a DAG of UI nodes each with a common set of properties.

That means Cascading Style Sheets (CSS) makes a good fit for styling UIs written with Figuro. Traditional GUI toolkits diverge significantly from HTML’s node tree as a UI. Of course they can still use CSS it’s just not quite as natural a fit in my view.

Being able to define a CSS rule for the Button widget becomes very simple in CSS:

Button {
  background: rgb(164, 158, 255);
  border-width: 5;
  border-color: #160075;
  border-radius: 10;
  color: rgb(78, 0, 90);
  box-shadow: none;
}

Button:hover {
  background: rgb(158, 223, 255);
  box-shadow: 5px 2px 4px rgba(47, 0, 75, 0.119);
}

Now our buttons go from this:

To this without any code changes:

0
Subscribe to my newsletter

Read articles from Jaremy Creechley directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jaremy Creechley
Jaremy Creechley