[][src]Crate thyme

Thyme is a highly customizable, themable immediate mode GUI toolkit for Rust.

It is designed to be performant and flexible enough for use both in prototyping and production games and applications. Requiring a theme and image sources adds some additional development cost compared to many other immediate mode toolkits, however the advantage is full flexibility and control over the ultimate appearance of your UI.

To use Thyme, you need the core library, a renderer (there are currently two - one using Glium and the other using wgpu), event handling support (one using winit is included), and a theme definition with associated images and fonts. Thyme logs errors using the log crate. A very simple logger that sends messages to stdout is included to help you get started.

All thyme widgets are drawn using images, with the image data registered with the renderer, and then individual widget components defined within that image within the theme file. Likewise, ttf fonts are registered with the renderer and then individual fonts for use in your UI are defined in the theme file. Widgets themselves can be defined fully in source code, with only some basic templates in the theme file, or you can largely leave only logic in the source, with layout, alignment, etc defined in the theme file.

Example

A quick snippet showing how the UI code looks:

// This method would be called in your main loop.  Each frame, you would call
// `create_frame` on your context and the typically pass it into a function
// like this one to construct the UI.
fn create_ui(ui: &mut Frame) {
    // all widgets need a theme, which is the first argument to widget builder methods
    ui.label("label", "My Title");

    // when a widget has children, the "ui" object is passed through a closure.
    // All of the widget types such as window, scrollpane, etc are built using
    // the Public API - meaning you can build your own custom versions if you wish.
    ui.window("data_window", |ui| {
      ui.label("label", "Data Points");

      // many widgets return state data.  Here, clicked will only
      // return true on the frame the button was clicked
      if ui.button("button", "Calculate").clicked {
        // do some expensive calculation
      }
    });

    // You can either specify layout and alignment in the theme, or directly in code.
    // If you specify your theme as a file read from disk (see the demo examples), you
    // can tweak these aspects live using Thyme's built in live-reload.

    // Here, we hardcode some layout
    ui.child("custom_widget")
    .align(Align::BotRight)
    .layout(Layout::Vertical)
    .children(|ui| {
      for i in 1..10 {
        ui.label("label", format!("Row #{}", i));
      }
    });
}

Overview

In general, you first create the ContextBuilder and register resources with it. Once done, you build the associated Context. At each frame of your app, you create a Thyme frame. The Frame is then passed along through your UI building routines, and is used to create WidgetBuilders and populate your Widget tree.

Theme Definition

When creating a ContextBuilder, you need to specify a theme. You can keep the theme fairly small with just a base set of widgets, defining most things in code, or go the other way around.

The theme can be defined from any serde compatible source, with the examples in this project using YAML. The theme has several sections: fonts, image_sets, and widgets.

Fonts

Defining fonts is very simple. The fonts section consists of a mapping, with IDs mapped to font data. The font IDs are used elsewhere in the widgets section and in code when specifying a font.

The data consists of a source, which is a string which must match one of the fonts registered with the ContextBuilder, and a size in logical pixels.

fonts:
  medium:
    source: roboto
    size: 20
  small:
    source: roboto
    size: 16

Image Sets

Images are defined as a series of image_sets. Each image_set has an id, used as the first part of the ID of each image in the set. The complete image ID is equal to image_set_id/image_id. Each image_set may be sourced from a different image file. Each image file must be registered with ContextBuilder, under an ID matching the source id.

image_sets:
  source: gui
  scale: 1
  images:
    ...

The image_set scale is used to pre-scale all images in that set by a given factor. With a scale of 1 (the default), all images will be drawn at 1 image pixel to 1 physical screen pixel when the display has a scale factor of 1, but 1 image pixel to 2 physical screen pixels on a hi-dpi display with a scale factor of 2. By setting the scale factor of the image set to 0.5, you can use the full resolution on hi-dpi displays, but you will need twice the image resolution to get the same UI size.

Images

Each image set can contain many images, which are defined as subsets of the overall image file in various ways. The type of image for each image within the set is determined based on the parameters specified.

Simple Images

Simple images are defined by a position and size, in pixels, within the overall image. The fill field is optional, with valid values of None (default) - image is drawn at fixed size, Stretch - image is stretched to fill an area, Repeat - image repeats over an area.

  progress_bar:
    position: [100, 100]
    size: [16, 16]
    fill: Stretch

Collected Images

Collected images allow you to define an image that consists of one or more sub images, fairly arbitrarily. Each sub image includes the image it references, a position, and a size. Both position and size may be positive or negative. When drawing, the size of the sub image is calculated as the main size of the image being drawn plus the sub image size for a negative or zero component, while a positive component indicates to just use the sub image size directly. For the position, the calculation is similar except that for each x and y position component, a negative position means to add the main image position plus main image size plus sub image position. This allows you to offset sub-images with respect to any of the top, bottom, left, or right of the main image.

In this example, window_bg_base is a composed image. Assuming it is transparent in the center, the collected image window_bg will draw the window_bg_base frame around a repeating tile of the window_fill image.

  window_bg:
    sub_images:
      window_bg_base:
        position: [0, 0]
        size: [0, 0]
      window_fill:
        position: [5, 5]
        size: [-10, -10]
  window_bg_base:
    position: [0, 0]
    grid_size: [32, 32]
  window_fill:
    position: [128, 0]
    size: [128, 128]
    fill: Repeat

Composed Images

Composed images are a common special case of collected images., consisting of an even 3 by 3 grid. The corners are drawn at a fixed size, while the middle sections stretch along one axis. The center grid image stretches to fill in the inner area of the image. These images allow you to easily draw widgets with almost any size that maintain the same look. The grid_size specifies the size of one of the 9 cells, with each cell having the same size.

  button_normal:
    position: [100, 100]
    grid_size: [16, 16]

Composed Horizontal and Vertical

There are also composed horizontal and composed vertical images, that consist of a 3x1 and 1x3 grid, respectively. These are defined and used in the same manner as regular composed images, but use grid_size_horiz and grid_size_vert to differentiate the different types.

Timed Images

Timed images display one out of several frames, on a timer. Timed images can repeat continuously (the default), or only display once, based on the value of the optional once parameter. frame_time_millis is how long each frame is shown for, in milliseconds. Each frame is the id of an image within the current image set. It can be any of the other types of images in the current set.

In this example, each frame is displayed for 500 milliseconds in an endless cycle.

  button_flash:
    frame_time_millis: 500
    once: false
    frames:
      - button_normal
      - button_bright

Animated Images

Animated images display one of several sub images based on the AnimState. of the parent widget. The referenced images are specified by id, and can include Simple, Composed, or Collected images.

  button:
    states:
      Normal: button_normal
      Hover: button_hover
      Pressed: button_pressed
      Active: button_active
      Active + Hover: button_hover_active
      Active + Pressed: button_pressed_active

Images which contain references to other images are parsed in a particular order - Collected, then Animated, then Timed. This means an Animated image may reference a Collected image, but not the other way around. All of these image types may contain references to the basic image types - Simple, Composed, ComposedHorizontal, and ComposedVertical.

Aliases

For convenience, you can create an image ID which is an alias to another image. For example, you may want a particular type of button to be easily changable to its own unique image in the future.

  scroll_button:
    from: button

Widgets

The widgets section defines themes for all widgets you will use in your UI. Whenever you create a widget, such as through Frame.start, you specify a theme_id. This theme_id must match one of the keys defined in this section.

Recursive definition

Widget themes are defined recursively, and Thyme will first look for the exact recursive match, before falling back to the top level match. Each widget entry may have one or more children, with each child being a full widget definition in its own right. The ID of each widget in the tree is computed as {parent_id}/{child_id}, recursively.

For example, if you specified a button that is a child of a content that is in turn a child of window, the theme ID will be window/content/button. Thyme will first look for a theme at the full ID, i.e.

  window:
    children:
      content:
        children:
          button

If that is not found, it will look for button at the top level.

Widget from attribute

Each widget entry in the widgets section may optionally have a from attribute, which instructs Thyme to copy the specified widget theme into this theme. This is resolved fully recursively and will copy all children, merging where appropriate. from attributes may also be defined recursively. Specifically defined attributes within a widget theme will override the from theme. Thyme first looks for the from theme at the specified absolute path. If no theme is found there, it then looks in the path relative to the current widget.

For example, this definition:

  button:
    background: gui/button
    size: [100, 25]
  titlebar:
    from: button
    children:
      label:
        font: medium
      close_button:
        from: button
        foreground: gui/close
        size: [25, 25]
  main_window_titlebar:
    from: titlebar
    children:
      label:
        text: "Main Window"

will interpret main_window_titlebar into the equivalent of this:

  main_window_titlebar:
    background: gui/button
    size: [100, 25]
    children:
      label:
        font: medium
        text: "Main Window"
      close_button:
        background: gui/button
        foregorund: gui/close
        size: [25, 25]

Overriding images

background and foreground image attributes may be overridden as normal. If you want to remove this attribute, you can use the special ID empty, which draws nothing.

Widget Attributes

Each widget theme has many optional attributes that may be defined in the theme file, UI building source code, or both. Source code methods on WidgetBuilder will take precedence over items defined in the theme file. The child_align, layout, and layout_spacing fields deal specifically with how the widget will layout its children.

   complicated_button:
     text: Hello
     text_color: "#FFAA00"
     text_align: Center
     font: medium
     background: gui/button
     foreground: gui/button_icon
     wants_mouse: true
     wants_scroll: false
     pos: [10, 10]
     size: [100, 0]
     width_from: Normal
     height_from: FontLine
     # OR size_from: [Normal, FontLine]
     border: { all: 5 }
     align: TopLeft
     child_align: Top
     layout: Vertical
     layout_spacing: 5

Custom fields

You may optionally specify custom values in the custom_floats mapping of the theme. This allows more specialized widgets to obtain neccessary parameters from the theme itself, rather than relying on another external source.

  my_custom_widget:
    custom_floats:
      min_width: 0.0
      min_height: 25.0

Modules

bench

Simple benchmarking functionality for supporting thyme.

log

A minimal logger for use with Thyme.

Structs

AnimState

An AnimState consists of one or more (currently up to four) state keys, with each key representing a different state.

Border

A struct representing a rectangular border around a Widget. In the theme file, border can be deserialzed as a standard mapping, or using all: {value} to specify all four values are the same, or width and height to specify left and right and top and bot, respectively.

BuildOptions

Global options that may be specified when building the Thyme context with ContextBuilder. These options cannot be changed afterwards.

Color

A Color with red, green, and blue components, with each component stored as a u8.

Context

The main Thyme Context that holds internal PersistentState and is responsible for creating Frames.

ContextBuilder

Structure to register resources and ultimately build the main Thyme Context.

Frame

A Frame, holding the widget tree to be drawn on a given frame, and a reference to the Thyme Context

GliumRenderer

A Thyme Renderer for Glium.

PersistentState

The internal state stored by Thyme for a given Widget that persists between frames.

Point

A two-dimensional point, with x and y coordinates.

Rect

A rectangular area, represented by a position and a size

ScrollpaneBuilder

A WidgetBuilder specifically for creating scrollpanes.

WgpuRenderer

A Thyme Renderer for wgpu.

WidgetBuilder

A WidgetBuilder is used to customize widgets within your UI tree, following a builder pattern.

WidgetState

The current state of a widget on this frame, this is returned when you finish most widgets, such as with a call to WidgetBuilder.finish.

WindowBuilder

A WidgetBuilder specifically for creating windows.

WinitIo

A Thyme Input/Output adapter for winit.

Enums

Align

Widget or text horizontal and vertical alignment.

AnimStateKey

One component of an AnimState

Error

A generic error that can come from a variety of internal sources.

HeightRelative

What to compute the height of widget relative to.

Layout

The Layout direction for a widget's children.

ShowElement

An enum to define when to show a particular UI element.

WidthRelative

What to compute the width of a widget relative to.

Traits

IO

A trait to be implemented on the type to be used for Event handling. See WinitIO for an example implementation. The IO handles events from an external source and passes them to the Thyme Context.

Renderer

A trait to be implemented on the type to be used for rendering the UI. See GliumRenderer for an example implementation. The Renderer takes a completed frame and renders the widget tree stored within it.