1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::uninlined_format_args)]
/*!
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 to choose a renderer and event handling support.
There are currently three renderers built in - one using [Glium](https://github.com/glium/glium),
one using [wgpu](https://github.com/gfx-rs/wgpu-rs)), and one using raw OpenGL (https://github.com/brendanzab/gl-rs/)).
[winit](https://github.com/rust-windowing/winit) is currently supported for event handling.
You also need a theme definition  with associated images and fonts.  Thyme logs errors using the
[`log`](https://github.com/rust-lang/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.start("custom_widget")
    .align(Align::BotRight)
    .layout(Layout::Vertical)
    .children(|ui| {
      for i in 1..10 {
        ui.label("label", format!("Row #{}", i));
      }
    });
}
```

# Overview

For common use cases, the [`AppBuilder`](struct.AppBuilder.html) struct is available to allow you to create a simple
application with just a few lines of code.

In more general cases, you first create the [`ContextBuilder`](struct.ContextBuilder.html) and register resources
with it. Once done, you [`build`](struct.ContextBuilder.html#method.build) the associated [`Context`](struct.Context.html).
At each frame of your app, you [`create a Thyme frame`](struct.Context.html#method.create_frame).  The
[`Frame`](struct.Frame.html) is then passed along through your UI building routines, and is used to create
[`WidgetBuilders`](struct.WidgetBuilder.html) and populate your Widget tree.

See the examples for details on how to use both of the above methods.

# Theme Definition
When creating a [`ContextBuilder`](struct.ContextBuilder.html), 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`](https://serde.rs/)
compatible source, with the examples in this project using [`YAML`](https://yaml.org/).
The theme has several sections: `fonts`, `image_sets`, and `widgets`.

## Fonts
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`](struct.WidgetBuilder.html#method.font).

The data consists of a `source`, which is a string which must match one of the fonts registered
with the [`ContextBuilder`](struct.ContextBuilder.html#method.register_font_source), and a `size`
in logical pixels.  Fonts may optionally specify one or more (inclusive) ranges of characters to display,
subject to those characters being present in the actual font TTF data.  By default, printable
characters from U+0000 to U+00FF are added.  In the future, once this is supported by RustType,
 the default should change to automatically support all characters present in the source font data.
```yaml
fonts:
  medium:
    source: roboto
    size: 20
    # only support ASCII printable characters for this font
    characters:
      - lower: 0x0020
        upper: 0x007e
  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 `source`d from a different image file.  If you leave the `source` out of the image definition,
all images will be treated as sourced from a 1x1 pixel.  This can be useful to create simple, minimal themes
without requiring an image source.
Each image file must be registered with [`ContextBuilder`](struct.ContextBuilder.html#method.register_image),
under an ID matching the `source` id.
```yaml
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.

### Image Sampling
Building images as sub-images of a larger spritesheet is convenient, but you need to be aware of texture sampling issues.  Because of
floating point rounding, graphics cards will sometimes partially sample pixels just outside the defined area of your images.  To avoid
unsightly lines and other graphical glitches, it is safest to have a 1 pixel wide border around all images, so that none of them
are touching.  For images that need to seamlessly repeat or stretch many times (i.e. Simple Images below), the border pixels should
maintain the same color as the nearby sub-image.  Otherwise you may not always get a seamless effect.

### 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.  Each image may optionally have a `color`
attribute.  Color is specified via a `#` character followed by a hex code - See [`Color`](struct.Color.html).

#### Solid Images
Solid images are a single solid color, normally specified with the `color` field.  You will need to specify `solid: true`
to help the Deserializer parse these definitions.  These are especially useful when defining a theme without an image file source.
```yaml
  bg_grey:
    solid: true
    color: "#888888"
```

#### 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.
```yaml
  progress_bar:
    position: [100, 100]
    size: [16, 16]
    fill: Stretch
```

#### Image Groups
You can create an image group as a shorthand for multiple simple images.  You specify an overall scale factor and fill, then for each image,
x, y, width, and height.  These four values are multipled by the scale factor.  All simple images in a group are immediately expanded
as if they were specified as individual images for purposes of being referenced by other image types.
```yaml
  icons_set:
    fill: Stretch
    group_scale: [64, 64]
    images:
      up_arrow: [0, 0, 1, 1]
      down_arrow: [1, 0, 1, 1]
```

#### 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.
```yaml
  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.
```yaml
  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.
```yaml
  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`](struct.AnimState.html). of the parent widget.
The referenced images are specified by `id`, and can include Simple, Composed, or Collected images.
```yaml
  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 - `Solid`, `Simple`, `Composed`, `ComposedHorizontal`, and
`ComposedVertical`.  In addition, `Collected` images may refer to other `Collected` images.

### 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.
```yaml
  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`](struct.Frame.html#method.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.
```yaml
  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:
```yaml
  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:
```yaml
  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`](struct.WidgetBuilder.html) will take precedence over items defined in the theme file.  The
[`child_align`](struct.WidgetBuilder.html#method.child_align), [`layout`](struct.WidgetBuilder.html#method.layout), and
[`layout_spacing`](struct.WidgetBuilder.html#method.layout_spacing) fields deal specifically with how
the widget will layout its children.

```yaml
   complicated_button:
     text: Hello
     text_color: "#FFAA00"
     text_align: Center
     font: medium
     image_color: "#FFFFFF"
     background: gui/button
     foreground: gui/button_icon
     tooltip: "This is a button!"
     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` mapping of the theme.  This allows more specialized widgets to
obtain neccessary parameters from the theme itself, rather than relying on another external source.  Allowed data types
include floats, integers, and strings.

```yaml
  my_custom_widget:
    custom:
      min_width: 0.0
      min_height: 25.0
      secondary_font: "Bold"
```
!*/

#![deny(missing_docs)]

pub mod bench;
pub mod log;

mod app_builder;
mod context;
mod context_builder;
mod font;
mod frame;
mod image;
mod theme;
mod recipes;
mod render;
mod resource;
mod theme_definition;
mod point;
mod scrollpane;
mod text_area;
mod widget;
mod window;
mod winit_io;

#[cfg(feature = "glium_backend")]
mod glium_backend;

#[cfg(feature = "glium_backend")]
pub use glium_backend::{GliumRenderer, GliumError};

#[cfg(feature = "wgpu_backend")]
mod wgpu_backend;

#[cfg(feature = "wgpu_backend")]
pub use wgpu_backend::WgpuRenderer;

pub use app_builder::AppBuilder;

#[cfg(feature="glium_backend")]
pub use app_builder::GliumApp;

#[cfg(feature = "gl_backend")]
mod gl_backend;

#[cfg(feature = "gl_backend")]
pub use gl_backend::{GLRenderer, GlError};

pub use frame::{Frame, MouseButton};
pub use point::{Rect, Point, Border};
pub use widget::{WidgetBuilder, WidgetState};
pub use context_builder::{BuildOptions, ContextBuilder};
pub use context::{Context, PersistentState, InputModifiers, SavedContext};
pub use scrollpane::{ScrollpaneBuilder, ShowElement};
pub use theme_definition::{AnimStateKey, AnimState, Align, Color, Layout, WidthRelative, HeightRelative};
pub use window::WindowBuilder;
pub use winit_io::WinitIo;

pub use render::{IO, Renderer};

/// A generic error that can come from a variety of internal sources.
#[derive(Debug)]
pub enum Error {
    /// An error originating from a passed in serde deserializer
    Serde(String),

    /// An error originating from an invalid theme reference or theme parsing
    Theme(String),

    /// An error originating from an invalid font source
    FontSource(String),

    /// An error that occurred attempting to use the filesystem
    IO(std::io::Error),

    /// An error creating the display
    DisplayCreation(String),

    /// An error originating from Winit
    Winit(crate::winit_io::WinitError),

    /// An error that occurred reading an image using the `image` crate.
    #[cfg(feature="image")]
    Image(::image::error::ImageError),

    /// An error originating from Glium
    #[cfg(feature="glium_backend")]
    Glium(crate::glium_backend::GliumError),

    /// An error originating from OpenGl
    #[cfg(feature="gl_backend")]
    Gl(crate::gl_backend::GlError),

    /// An error originating from Wgpu's create surface routine
    #[cfg(feature="wgpu_backend")]
    WgpuSurface(wgpu::CreateSurfaceError),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        use self::Error::*;
        match self {
            Serde(e) => write!(f, "Error deserializing theme: {}", e),
            Theme(msg) => write!(f, "Error creating theme from theme definition: {}", msg),
            FontSource(msg) => write!(f, "Error reading font source: {}", msg),
            IO(error) => write!(f, "IO Error: {}", error),
            DisplayCreation(msg) => write!(f, "Error creating display: {}", msg),
            Winit(error) => write!(f, "Winit error: {}", error),

            #[cfg(feature="image")]
            Image(error) => write!(f, "Image Error: {}", error),

            #[cfg(feature="glium_backend")]
            Glium(error) => write!(f, "Glium Error: {}", error),

            #[cfg(feature="gl_backend")]
            Gl(error) => write!(f, "OpenGL Error: {}", error),

            #[cfg(feature="wgpu_backend")]
            WgpuSurface(error) => write!(f, "Wgpu surface error: {}", error),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        use self::Error::*;
        match self {
            Serde(..) => None,
            Theme(..) => None,
            FontSource(..) => None,
            IO(error) => Some(error),
            DisplayCreation(..) => None,
            Winit(error) => Some(error),

            #[cfg(feature="image")]
            Image(error) => Some(error),

            #[cfg(feature="glium_backend")]
            Glium(error) => Some(error),

            #[cfg(feature="gl_backend")]
            Gl(error) => Some(error),

            #[cfg(feature="wgpu_backend")]
            WgpuSurface(error) => Some(error),
        }
    }
}