Struct thyme::Frame

source ·
pub struct Frame { /* private fields */ }
Expand description

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

The frame is the main object that you pass along through your UI builder functions. It allows you to construct WidgetBuilders both with full control and convenient helper methods.

Frame also contains a number of methods for manipulating the internal PersistentState associated with a particular widget, with modify providing full control.

When building your UI, there will always be a current parent widget that widgets are currently being added to. This starts at the root widget which has defaults for all parameters. Each children closure you enter changes the associated parent widget.

Implementations§

source§

impl Frame

source

pub fn context(&self) -> &Context

Returns the overall Thyme Context.

source

pub fn start(&mut self, theme: &str) -> WidgetBuilder<'_>

Starts creating a new child widget within the current parent, using the specified theme. See the crate root for a discussion of the theme format. This method returns a WidgetBuilder which can be used for fully customizing the new widget.

Example
fn create_ui(ui: &mut Frame) {
    ui.start("cancel_button").finish();
}
Examples found in repository?
examples/demo.rs (line 207)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}

fn party_members_panel(ui: &mut Frame, party: &mut Party) {
    for (index, member) in party.members.iter_mut().enumerate() {
        let clicked = ui.start("filled_slot_button")
        .text(&member.name)
        .active(Some(index) == party.editing_index)
        .finish().clicked;

        if clicked {
            set_active_character(ui, member);
            party.editing_index = Some(index);
        }
    }

    if ui.start("add_character_button").finish().clicked {
        let new_member = Character::generate(party.members.len());
        set_active_character(ui, &new_member);
        party.members.push(new_member);
        party.editing_index = Some(party.members.len() - 1);
    }
}

fn set_active_character(ui: &mut Frame, character: &Character) {
    ui.open("character_window");
    ui.modify("name_input", |state| {
        state.text = Some(character.name.clone());
    });
    ui.close("item_picker");
}

fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}

fn item_picker(ui: &mut Frame, character: &mut Character) {
    for item in ITEMS.iter() {
        let clicked = ui.start("item_button")
        .enabled(character.gp >= item.price)
        .children(|ui| {
            ui.label("name", item.name);
            // TODO icon image
            ui.child("icon");
            ui.label("price", format!("{} Gold", item.price));
        }).clicked;

        if clicked {
            character.gp -= item.price;
            character.items.push(item.clone());
            ui.close("item_picker");
        }
    }
}

fn inventory_panel(ui: &mut Frame, character: &mut Character) {
    ui.start("top_panel")
    .children(|ui| {
        if ui.child("buy").clicked {
            ui.open_modal("item_picker");
        }

        ui.label("gold", format!("{} Gold", character.gp));
    });
    
    ui.start("items_panel")
    .scrollpane("items_content")
    .show_vertical_scrollbar(ShowElement::Always)
    .children(|ui| {
        items_panel(ui, character);
    });
}
source

pub fn display_size(&self) -> Point

Returns the current window display size, in logical pixels.

Examples found in repository?
examples/demo.rs (line 285)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn input_modifiers(&self) -> InputModifiers

Returns the current state of the keyboard modifier keys

source

pub fn mouse_rect(&self) -> Rect

Returns the current mouse position and size, in logical pixels

source

pub fn wants_mouse(&self) -> bool

Returns whether or not the Thyme UI wants the mouse this frame. See Context.wants_mouse

source

pub fn wants_keyboard(&self) -> bool

Returns whether or not the Thyme UI wants the keyboard this frame. See Context.wants_keyboard

source

pub fn mouse_time_in_current_widget(&self) -> u32

Returns the amount of time, in milliseconds, that the mouse has been hovering (inside) of the widget that it is currently inside. If hovered is true in a WidgetState, then the mouse has been hovering that widget for this amount of time.

source

pub fn tooltip_ready(&mut self) -> Option<Point>

If the mouse has been hovering over a widget at least as long as the tooltip time configured in the BuildOptions, returns the tooltip render position. Otherwise, returns None. See mouse_time_in_current_widget.

source

pub fn set_mouse_cursor(&mut self, image: &str, align: Align)

Sets the mouse cursor to the specified image with alignment. If you are hiding the default OS cursor, this should be called at least once every frame you want to show a cursor. If it is called multiple times, the last call will take effect. The image will automatically inherit Normal and Pressed animation states. See set_mouse_state to override this behavior.

Examples found in repository?
examples/demo.rs (line 193)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn set_mouse_state(&mut self, state: AnimState)

Manually set the Mouse cursor to the specified state. This is used when drawing the specified mouse cursor image. The mouse will automatically inherit Normal and Pressed states by default. This overrides that behavior.

source

pub fn gap(&mut self, gap: f32)

Adds a gap between the previous widget and the next to be specified, subject to the current parent’s layout requirement.

Examples found in repository?
examples/hello_wgpu.rs (line 14)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme wgpu Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_wgpu()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
More examples
Hide additional examples
examples/hello_glium.rs (line 14)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme Glium Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_glium()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
examples/hello_gl.rs (line 14)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme Gl Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_gl()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
examples/demo.rs (line 242)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn set_cursor(&mut self, x: f32, y: f32)

Sets the current cursor position of the current parent widget to the specified value. Normally, the cursor widget moves after each widget is placed based on the parent’s layout. This has nothing to do with the mouse cursor.

source

pub fn cursor(&self) -> Point

Returns the current cursor position of the parent widget. You can use this as a basis for relative changes with set_cursor. This has nothing to do with the mouse cursor.

source

pub fn focus_keyboard<T: Into<String>>(&mut self, id: T)

Causes Thyme to focus the keyboard on the widget with the specified id. Keyboard events will subsequently be sent to this widget, if it exists. Only one widget may have keyboard focus at a time.

Example
fn open_query_popup(ui: &mut Frame) {
    ui.open("query_popup");
    ui.focus_keyboard("query_popup_input_field");  
}
source

pub fn is_focus_keyboard(&self, id: &str) -> bool

Returns whether or not the widget with the specified id currently has keyboard focus. See focus_keyboard.

source

pub fn parent_bounds(&self) -> Rect

Returns a `Rect with the current size and position of the current parent widget. (This is the widget that any currently created widgets will be added as a child of). Note that the size of the parent might change later depending on the layout choice.

source

pub fn parent_max_child_bounds(&self) -> Rect

Returns a Rect encompassing all children that have currently been added to the parent widget, recursively. This includes each widget’s actual final position and size.

source

pub fn cur_time_millis(&self) -> u32

Returns the current internal time being used by Thyme. This is useful if you want to set a timer to start running based on the current frame, using set_base_time_millis.

Example
fn set_animation_timer(ui: &mut Frame) {
    // widget will reach its zero animation time in 10 seconds
    let time = ui.cur_time_millis();
    ui.set_base_time_millis("my_timer_widget", time + 10_000);
}
Examples found in repository?
examples/demo.rs (line 330)
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
fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}
source

pub fn set_base_time_millis<T: Into<String>>(&mut self, id: T, time: u32)

Sets the base time of the PersistentState for the widget with the specified id to the specified time. This time should probably be based on something obtained from cur_time_millis or base_time_millis. The base time of a widget is used to specify the zero time of an Timed images associated with that widget.

source

pub fn set_base_time_now<T: Into<String>>(&mut self, id: T)

Sets the base time of the PersistentState for the widget with the specified id to the current internal time. See set_base_time_millis.

Examples found in repository?
examples/demo.rs (line 339)
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
fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}
source

pub fn base_time_millis(&self, id: &str) -> u32

Returns the current base time in millis of the PersistentState for the widget with the current id.

Examples found in repository?
examples/demo.rs (line 330)
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
fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}
source

pub fn set_timer<T: Into<String>>(&mut self, id: T, time: u32)

Sets the internal timer value of the PersistentState for the widget with the specified id to the specified time in milliseconds. This time should probably be based on something obtained from cur_time_millis or base_time_millis.

source

pub fn set_timer_to_now<T: Into<String>>(&mut self, id: T)

Sets the internal timer value of the PersistentState for the widget with the specified id to the current internal time. See set_timer.

source

pub fn timer(&self, id: &str) -> u32

Returns the current timer in millis of the PersistentState for the widget with the current id.

source

pub fn scroll(&self, id: &str) -> Point

Sets the internal scroll of the PersistentState for the widget with the specified id. Useful for Scrollpanes.

source

pub fn change_scroll<T: Into<String>>(&mut self, id: T, x: f32, y: f32)

Modifies the internal scroll of the widget with the specified id by the specified x and y amounts. See scroll

source

pub fn text_for(&self, id: &str) -> Option<String>

Returns the current text associated with the PersistentState of the widget with the specified id. Useful for input fields.

source

pub fn is_expanded(&self, id: &str) -> bool

Returns whether the widget with the specified id is expanded in its PersistentState. Trees and similar widgets will not show their entire content if not expanded

source

pub fn set_expanded<T: Into<String>>(&mut self, id: T, expanded: bool)

Sets the expanded value for the given widget to expanded. See is_expanded

source

pub fn is_open(&self, id: &str) -> bool

Returns whether the widget with the specified id is open in its PersistentState. If not open, widgets are not visible.

source

pub fn open_modal<T: Into<String>>(&mut self, id: T)

Opens the widget with the specified id as a modal. This modifies the PersistentState associated with that widget, as well as setting the overall Thyme modal to the specified widget. When a modal is open, only the modal and its children may receive input. There may be only one modal open at a time. If the specified id is closed, i.e. via close, the modal state ends.

Examples found in repository?
examples/demo.rs (line 388)
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
fn inventory_panel(ui: &mut Frame, character: &mut Character) {
    ui.start("top_panel")
    .children(|ui| {
        if ui.child("buy").clicked {
            ui.open_modal("item_picker");
        }

        ui.label("gold", format!("{} Gold", character.gp));
    });
    
    ui.start("items_panel")
    .scrollpane("items_content")
    .show_vertical_scrollbar(ShowElement::Always)
    .children(|ui| {
        items_panel(ui, character);
    });
}
source

pub fn close_modal_on_click_outside(&mut self)

Sets the currently open modal, if there is one, to close if the mouse is clicked outside of the modal’s area.

source

pub fn open<T: Into<String>>(&mut self, id: T)

Opens the widget with the specified id. This modifies the PersistentState. See is_open

Examples found in repository?
examples/demo.rs (line 320)
319
320
321
322
323
324
325
fn set_active_character(ui: &mut Frame, character: &Character) {
    ui.open("character_window");
    ui.modify("name_input", |state| {
        state.text = Some(character.name.clone());
    });
    ui.close("item_picker");
}
source

pub fn close<T: Into<String>>(&mut self, id: T)

Closes the widget with the specified id. This modifies the PersistentState. See is_open. If the widget was the current modal, resets Thyme so there is no longer a modal.

Examples found in repository?
examples/demo.rs (line 324)
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
fn set_active_character(ui: &mut Frame, character: &Character) {
    ui.open("character_window");
    ui.modify("name_input", |state| {
        state.text = Some(character.name.clone());
    });
    ui.close("item_picker");
}

fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}

fn item_picker(ui: &mut Frame, character: &mut Character) {
    for item in ITEMS.iter() {
        let clicked = ui.start("item_button")
        .enabled(character.gp >= item.price)
        .children(|ui| {
            ui.label("name", item.name);
            // TODO icon image
            ui.child("icon");
            ui.label("price", format!("{} Gold", item.price));
        }).clicked;

        if clicked {
            character.gp -= item.price;
            character.items.push(item.clone());
            ui.close("item_picker");
        }
    }
}
source

pub fn open_parent(&mut self)

Opens the current parent widget. See open.

source

pub fn close_parent(&mut self)

Closes the current parent widget. See close.

source

pub fn clear(&mut self, id: &str)

Completely clears all PersistentState associated with the specified id, resetting it to its default state. This includies clearing the modal state if the id is the current modal.

source

pub fn modify<T: Into<String>, Ret, F: FnOnce(&mut PersistentState) -> Ret>( &mut self, id: T, f: F ) -> Ret

Gets a mutable reference to the PersistentState associated with the id, and calls the passed in closure, f, allowing you to modify it in arbitrary ways. This is more efficient than calling several individual methods in a row, such as open, scroll, etc. The return value of the passed in function is passed through this method, allowing you to use it for queries as well.

Examples found in repository?
examples/demo.rs (lines 321-323)
319
320
321
322
323
324
325
fn set_active_character(ui: &mut Frame, character: &Character) {
    ui.open("character_window");
    ui.modify("name_input", |state| {
        state.text = Some(character.name.clone());
    });
    ui.close("item_picker");
}
source

pub fn log<T: Into<String>>(&self, level: Level, message: T)

Logs a message using the Thyme internal logger. Prevents a flood of the same message from appearing on each frame - the message will only appear once in the log output.

source

pub fn set_variable<T: Into<String>, U: Into<String>>( &mut self, key: T, value: U )

Sets an associated key value pair for a variable that can be used by various widgets. For example, text_area will subsitute the value in the output text whenever it finds a key inside curly braces {key}. The variable is set globally for the entire frame object. Variables are not persisted between frames, so this will need to be called on each frame before adding the widget(s) where the variable is used.

Examples found in repository?
examples/demo.rs (line 251)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn variables(&self) -> &HashMap<String, String>

Returns the current set of key value pairs of variables set on the frame. See set_variable.

source§

impl Frame

source

pub fn child(&mut self, theme: &str) -> WidgetState

The simplest way to construct a child widget. The widget has no special behavior in code at all. It is defined entirely based on its theme.

Example
fn create_window(ui: &mut Frame) {
    // the label can have its size, position, text, etc defined in-theme
    ui.child("title_label");
}
Examples found in repository?
examples/demo.rs (line 268)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}

fn party_members_panel(ui: &mut Frame, party: &mut Party) {
    for (index, member) in party.members.iter_mut().enumerate() {
        let clicked = ui.start("filled_slot_button")
        .text(&member.name)
        .active(Some(index) == party.editing_index)
        .finish().clicked;

        if clicked {
            set_active_character(ui, member);
            party.editing_index = Some(index);
        }
    }

    if ui.start("add_character_button").finish().clicked {
        let new_member = Character::generate(party.members.len());
        set_active_character(ui, &new_member);
        party.members.push(new_member);
        party.editing_index = Some(party.members.len() - 1);
    }
}

fn set_active_character(ui: &mut Frame, character: &Character) {
    ui.open("character_window");
    ui.modify("name_input", |state| {
        state.text = Some(character.name.clone());
    });
    ui.close("item_picker");
}

fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}

fn item_picker(ui: &mut Frame, character: &mut Character) {
    for item in ITEMS.iter() {
        let clicked = ui.start("item_button")
        .enabled(character.gp >= item.price)
        .children(|ui| {
            ui.label("name", item.name);
            // TODO icon image
            ui.child("icon");
            ui.label("price", format!("{} Gold", item.price));
        }).clicked;

        if clicked {
            character.gp -= item.price;
            character.items.push(item.clone());
            ui.close("item_picker");
        }
    }
}

fn inventory_panel(ui: &mut Frame, character: &mut Character) {
    ui.start("top_panel")
    .children(|ui| {
        if ui.child("buy").clicked {
            ui.open_modal("item_picker");
        }

        ui.label("gold", format!("{} Gold", character.gp));
    });
    
    ui.start("items_panel")
    .scrollpane("items_content")
    .show_vertical_scrollbar(ShowElement::Always)
    .children(|ui| {
        items_panel(ui, character);
    });
}
source

pub fn label<T: Into<String>>(&mut self, theme: &str, text: T)

A simple label displaying the specified text, with no user interactivity.

An example theme definition:

label:
  font: small
  border: { width: 5 }
  text_align: Center
  height_from: FontLine
Examples found in repository?
examples/demo.rs (lines 200-205)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}

fn party_members_panel(ui: &mut Frame, party: &mut Party) {
    for (index, member) in party.members.iter_mut().enumerate() {
        let clicked = ui.start("filled_slot_button")
        .text(&member.name)
        .active(Some(index) == party.editing_index)
        .finish().clicked;

        if clicked {
            set_active_character(ui, member);
            party.editing_index = Some(index);
        }
    }

    if ui.start("add_character_button").finish().clicked {
        let new_member = Character::generate(party.members.len());
        set_active_character(ui, &new_member);
        party.members.push(new_member);
        party.editing_index = Some(party.members.len() - 1);
    }
}

fn set_active_character(ui: &mut Frame, character: &Character) {
    ui.open("character_window");
    ui.modify("name_input", |state| {
        state.text = Some(character.name.clone());
    });
    ui.close("item_picker");
}

fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}

fn item_picker(ui: &mut Frame, character: &mut Character) {
    for item in ITEMS.iter() {
        let clicked = ui.start("item_button")
        .enabled(character.gp >= item.price)
        .children(|ui| {
            ui.label("name", item.name);
            // TODO icon image
            ui.child("icon");
            ui.label("price", format!("{} Gold", item.price));
        }).clicked;

        if clicked {
            character.gp -= item.price;
            character.items.push(item.clone());
            ui.close("item_picker");
        }
    }
}

fn inventory_panel(ui: &mut Frame, character: &mut Character) {
    ui.start("top_panel")
    .children(|ui| {
        if ui.child("buy").clicked {
            ui.open_modal("item_picker");
        }

        ui.label("gold", format!("{} Gold", character.gp));
    });
    
    ui.start("items_panel")
    .scrollpane("items_content")
    .show_vertical_scrollbar(ShowElement::Always)
    .children(|ui| {
        items_panel(ui, character);
    });
}
source

pub fn multiline_label<T: Into<String>>(&mut self, theme: &str, text: T)

A simple label, but specifically designed to extend over multiple lines. Generally, you should use height_from: Normal. Computes the widget height based on the theme width and number of lines of text. *

source

pub fn button<T: Into<String>>(&mut self, theme: &str, label: T) -> WidgetState

A simple button with a text label.

An example theme definition:

button:
  font: small
  wants_mouse: true
  background: gui/small_button
  text_align: Center
  size: [150, 24]
  border: { all: 5 }
Example
fn test_button(ui: &mut Frame) {
    if ui.button("button", "Click Me!").clicked {
        println!("Hello world!");
    }
}
Examples found in repository?
examples/hello_wgpu.rs (line 16)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme wgpu Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_wgpu()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
More examples
Hide additional examples
examples/hello_glium.rs (line 16)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme Glium Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_glium()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
examples/demo.rs (line 405)
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
fn items_panel(ui: &mut Frame, character: &mut Character) {
    let mut sell = None;
    for (index, item) in character.items.iter().enumerate() {
        let result = ui.button("item_button", item.name);
        if result.clicked {
            sell = Some(index);
        }
        
        if result.hovered {
            // manually specify a tooltip
            ui.tooltip_label("tooltip", "Remove Item");
        }
    }

    if let Some(index) = sell {
        let item = character.items.remove(index);
        character.gp += item.price;
    }
}
examples/hello_gl.rs (line 16)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme Gl Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_gl()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
source

pub fn vertical_slider( &mut self, theme: &str, min: f32, max: f32, value: f32 ) -> Option<f32>

A simple vertical slider. The slider button can be dragged by the user. The position of the button is based on the relative distance of value from min and max. Returns the new value if the user moved the slider on this frame, None, otherwise. Will always return a value within [min, max] inclusive. max must be greater than min.

An example theme definition:

vertical_slider:
  size: [15, 0]
  height_from: Parent
  border: { top: 6, bot: 5, left: 5, right: 5 }
  children:
    slider_bar:
      align: TopLeft
      width_from: Parent
      height_from: Parent
      background: gui/slider_vertical
    slider_button:
      from: button
      size: [15, 15]
source

pub fn horizontal_slider( &mut self, theme: &str, min: f32, max: f32, value: f32 ) -> Option<f32>

A simple horizontal slider. The slider button can be dragged by the user. The position of the button is based on the relative distance of value from min and max. Returns the new value if the user moved the slider on this frame, None, otherwise. Will always return a value within [min, max] inclusive. max must be greater than min.

An example theme definition:

horizontal_slider:
  size: [0, 15]
  width_from: Parent
  border: { top: 6, bot: 5, left: 5, right: 5 }
  children:
    slider_bar:
      align: TopLeft
      width_from: Parent
      height_from: Parent
      background: gui/slider_horizontal
    slider_button:
      from: button
      size: [15, 15]
Example
fn create_slider(ui: &mut Frame, value: &mut f32) {
    if let Some(new_value) = ui.horizontal_slider("slider", 0.0, 1.0, *value) {
        *value = new_value;
    }
}
Examples found in repository?
examples/demo.rs (line 244)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn spinner<T: PartialOrd + Display>( &mut self, theme: &str, value: T, min: T, max: T ) -> i32

A spinner, used to select a numeric value. The spinner includes a label, a button to increase the value, and a button to decrease the value. If the decrease button is clicked, returns -1, while if the increase button is clicked, returns 1. Otherwise, returns 0. The buttons will be enabled based on comparing the value with min and max to determine if the value can increase or decrease.

An example theme definition:

spinner:
  size: [80, 20]
  layout: Horizontal
  layout_spacing: [5, 5]
  child_align: Left
  children:
    decrease:
      from: button
      text: "-"
      background: gui/small_button
      size: [20, 20]
    value:
      from: label
      size: [30, 0]
      font: medium
      width_from: Normal
    increase:
      from: button
      text: "+"
      background: gui/small_button
      size: [20, 20]
Example
fn int_spinner(ui: &mut Frame, value: &mut i32) {
    *value = ui.spinner("spinner", *value, 0, 10);
}
Examples found in repository?
examples/demo.rs (line 352)
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
fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}
source

pub fn wrapping_spinner<T: Display>(&mut self, theme: &str, value: T) -> i32

A spinner, but without any restriction on maximum or minimum values. If there is a minimum or maximum, it is assumed that the user is allowed to wrap around from minimum to maximum or vice-versa.
See spinner.

source

pub fn tree<F: FnOnce(&mut Frame), G: FnOnce(&mut Frame)>( &mut self, theme: &str, id: &str, initially_expanded: bool, title: F, children: G )

A tree widget. Depending on its internal expanded state (see Frame.is_expanded, this widget will either show both its title and children widgets, or just its title widgets. It is intended that you use height_from with Children.

tree:
  size_from: [Parent, Children]
  border: { all: 5 }
  background: gui/window_bg
  children:
    expand:
      from: button
      align: TopLeft
      pos: [0, 0]
      text: "+"
      text_align: Center
      size: [24, 24]
    collapse:
      from: button
      align: TopLeft
      pos: [0, 0]
      text: "-"
      text_align: Center
      size: [24, 24]
Example
fn create_tree(ui: &mut Frame, name: &str, description: &str) {
    ui.tree("tree", "unique_id", |ui| {
      ui.label("label", name);
    }, |ui| {
      ui.label("label", description);
    });
}
Examples found in repository?
examples/demo.rs (lines 266-271)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}

fn party_members_panel(ui: &mut Frame, party: &mut Party) {
    for (index, member) in party.members.iter_mut().enumerate() {
        let clicked = ui.start("filled_slot_button")
        .text(&member.name)
        .active(Some(index) == party.editing_index)
        .finish().clicked;

        if clicked {
            set_active_character(ui, member);
            party.editing_index = Some(index);
        }
    }

    if ui.start("add_character_button").finish().clicked {
        let new_member = Character::generate(party.members.len());
        set_active_character(ui, &new_member);
        party.members.push(new_member);
        party.editing_index = Some(party.members.len() - 1);
    }
}

fn set_active_character(ui: &mut Frame, character: &Character) {
    ui.open("character_window");
    ui.modify("name_input", |state| {
        state.text = Some(character.name.clone());
    });
    ui.close("item_picker");
}

fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}
source

pub fn combo_box<'a, T: Display>( &mut self, theme: &str, id: &str, current: &T, values: &'a [T] ) -> Option<&'a T>

A drop down box. It displays its currently active selection (current), and opens a modal popup to select a new choice from the list of values when the user clicks on it. The specified id must be unique. The method will return a selected choice on the frame the user clicks on it, otherwise returning None.

An example theme definition; See ScrollpaneBuilder for the scrollpane example.

combo_box:
  from: button
  children:
    expand:
      size: [12, 12]
      align: Right
      foreground: gui/arrow_down
    combo_box_popup:
      from: scrollpane
      width_from: Parent
      height_from: Normal
      size: [10, 75]
      background: gui/small_button_normal
      children:
        content:
          size: [-18, -10]
          children:
            entry:
              from: button
              width_from: Parent
              size: [0, 25]
        scrollbar_vertical:
          size: [20, 20]
Examples found in repository?
examples/demo.rs (line 212)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn toggle_button<T: Into<String>>( &mut self, theme: &str, label: T, active: bool ) -> WidgetState

A simple toggle button that can be toggle on or off, based on the passed in active state.

See button for a YAML example.

source

pub fn input_field( &mut self, theme: &str, id: &str, initial_value: Option<String> ) -> Option<String>

Creates a simple text input field. The id that is passed in must be unique. The text input will grab keyboard focus when the user clicks on it, allowing the user to type text. The return value will be None if the text didn’t change this frame, or will contain the current text displayed by the textbox if it did change. Optionally, pass an initial_value which will set the field’s text if it is not already set.

An example YAML theme definition:

input_field:
  font: small
  border: { height: 4, width: 5 }
  background: gui/input_field
  text_align: Left
  wants_mouse: true
  size: [150, 24]
  child_align: TopLeft
  children:
    caret:
      size: [2, -2]
      height_from: Parent
      background: gui/caret
Example
fn select_name(ui: &mut Frame, name: &mut String) {
    if let Some(text) = ui.input_field("input_field", "unique_id", None) {
        *name = text;
    }
}
Examples found in repository?
examples/demo.rs (line 237)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn progress_bar(&mut self, theme: &str, frac: f32)

Creates a simple progress bar. The drawing will be clipped based on the size of the widget and the passed in frac.

An example YAML theme definition:

progress_bar:
  size: [100, 24]
  background: gui/button
  border: { width: 27 }
  child_align: TopLeft
  children:
    bar:
      background: gui/progress_bar
      width_from: Parent
      height_from: Parent
Examples found in repository?
examples/demo.rs (line 335)
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
fn stats_panel(ui: &mut Frame, character: &mut Character) {
    let points_used: u32 = character.stats.values().sum();
    let points_available: u32 = STAT_POINTS - points_used;
    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);

    let roll = ui.start("roll_button")
    .enabled(frac > 0.99)
    .children(|ui| {
        ui.progress_bar("progress_bar", frac);
    });

    if roll.clicked {
        ui.set_base_time_now("stat_roll");
    }

    for stat in Stat::iter() {
        let value = character.stats.entry(stat).or_insert(10);

        ui.tree(
        "stat_panel",
        &format!("stat_panel_{:?}", stat),
        false,
        |ui| {
            ui.label("label", format!("{:?}", stat));

            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
                1 => *value += 1,
                -1 => *value -= 1,
                _ => (),
            }
        }, |ui| {
            ui.child("description");
        });
    }

    ui.label("points_available", format!("Points Remaining: {}", points_available));
}
source

pub fn progress_bar_vert(&mut self, theme: &str, frac: f32)

Creates a simple vertical progress bar. See progress_bar *

source

pub fn tooltip_label<T: Into<String>>(&mut self, theme: &str, label: T)

Creates a simple tooltip with the specified text. The tooltip is placed based on the position of the mouse.

An example YAML theme definition:

tooltip:
  background: gui/button
  font: small
  text_align: Center
Examples found in repository?
examples/demo.rs (line 412)
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
fn items_panel(ui: &mut Frame, character: &mut Character) {
    let mut sell = None;
    for (index, item) in character.items.iter().enumerate() {
        let result = ui.button("item_button", item.name);
        if result.clicked {
            sell = Some(index);
        }
        
        if result.hovered {
            // manually specify a tooltip
            ui.tooltip_label("tooltip", "Remove Item");
        }
    }

    if let Some(index) = sell {
        let item = character.items.remove(index);
        character.gp += item.price;
    }
}
source

pub fn window<F: FnOnce(&mut Frame)>(&mut self, theme: &str, children: F)

A convenience method to create a window with the specified theme. The theme is also used for the window ID, which must be unique in your application. If this is not the case, you should use the full WindowBuilder form. The specified closure is called to add children to the window. The window will include a titlebar, close button, be moveable, and resizable. See WindowBuilder for more details and more flexible window creation.

Example
struct Person {
  name: String,
  age: u32,
}

fn create_person_window(ui: &mut Frame, person: &Person) {
    ui.window("person_window", |ui| {
        ui.label("name_label", &person.name);
        ui.label("age_label", person.age.to_string());
    });
}
Examples found in repository?
examples/hello_wgpu.rs (lines 13-17)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme wgpu Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_wgpu()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
More examples
Hide additional examples
examples/hello_glium.rs (lines 13-17)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme Glium Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_glium()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
examples/hello_gl.rs (lines 13-17)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = thyme::AppBuilder::new()
        .with_logger()
        .with_title("Thyme Gl Demo")
        .with_window_size(1280.0, 720.0)
        .with_base_dir("examples/data")
        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
        .with_font_dir("fonts")
        .with_image_dir("images")
        .build_gl()?;

    app.main_loop(|ui| {
        ui.window("window", |ui| {
            ui.gap(20.0);
    
            ui.button("label", "Hello, World!");
        });
    });
}
examples/demo.rs (lines 233-282)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source

pub fn scrollpane<F: FnOnce(&mut Frame)>( &mut self, theme: &str, content_id: &str, children: F )

A convenience method to create a scrollpane with the specified theme and content_id, which must be unique. See ScrollpaneBuilder for more details and more flexible scrollpane creation.

Examples found in repository?
examples/demo.rs (lines 225-227)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}
source§

impl Frame

source

pub fn text_area(&mut self, theme: &str)

A text area widget that parses markdown text. Child themes need to be defined for each font / size combination that you want to be able to render. This normally includes at least normal text, strong text, emphasis text, strong emphasis text, and a few heading levels. If, in your markdown, you make use of a combination that is not defined, the widget will log an error.

The widget can currently handle a subset of common Markdown, including headings, strong / emphasis text, unordered and ordered lists, and tables with column alignments.

Several parameters need to be specified for the widget to function properly, including tab_width, column_width, and a list_bullet character. See the example below. Note that the widget does not perform look-ahead to determine appropriate column widths - these are specified with the column_width parameter instead.

Variable Substitution

Using curly braces, i.e. {my_variable_id} you can substitute in values that are set dynamically in your code. See set_variable. Also available is an if statement with optional else that checks for the existance of a variable. If the variable is not set, nothing inside the if will be appear in the final output.

Example: {#if my_variable}My variable is set!{#else}My variable is not set.{/if}

Color

Text color is not specifiable in Markdown. For ease of use, the text area will parse a limited non-standard HTML tag, <e>. The tag accepts a color or c attribute. The value of the attribute may be any string that can be parsed by the Color struct. Quotes are not used in specifying the value. For example, <e c=#f00>Red text</e>. The extended tag may be nested.

An example theme definition:

text_area_item:
  font: small
  border: { width: 5 }
  text_align: TopLeft
  size_from: [Parent, FontLine]
text_area:
  border: { all: 5 }
  size_from: [Parent, Children]
  custom:
    tab_width: 6.0
    column_width: 70.0
    list_bullet: "* "
  children:
    paragraph_normal:
      from: text_area_item
      font: small
    paragraph_strong:
      from: text_area_item
      font: small_bold
    paragraph_emphasis:
      from: text_area_item
      font: small_italic
    paragraph_strong_emphasis:
      from: text_area_item
      font: small_bold_italic
    heading1_normal:
      from: text_area_item
      font: heading1
    heading2_normal:
      from: text_area_item
      font: heading2
  text: |
    This is multiline text in *YAML* with some
    basic markdown formatting.
Example
fn create_text_box(ui: &mut Frame) {
    ui.text_area("text_area");
}
Examples found in repository?
examples/demo.rs (line 255)
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
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
    match party.theme_choice {
        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
            // show a custom cursor.  it automatically inherits mouse presses in its state
            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
        },
        ThemeChoice::NoImage => {
            // don't show a custom cursor
        }
    }

    ui.label("bench", format!(
        "{}\n{}\n{}",
        bench::short_report("thyme"),
        bench::short_report("frame"),
        bench::short_report("draw"),
    ));

    ui.start("theme_panel").children(|ui| {
        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
            party.live_reload_disabled = !party.live_reload_disabled;
        }

        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
            party.old_theme_choice = Some(party.theme_choice);
            party.theme_choice = *choice;
            party.reload_assets = true;
        }
    });

    ui.start("party_window")
    .window("party_window")
    .with_close_button(false)
    .moveable(false)
    .resizable(false)
    .children(|ui| {
        ui.scrollpane("members_panel", "party_content", |ui| {
            party_members_panel(ui, party);
        });
    });

    if let Some(index) = party.editing_index {
        let character = &mut party.members[index];

        ui.window("character_window", |ui| {
            ui.scrollpane("pane", "character_content", |ui| {
                ui.start("name_panel")
                .children(|ui| {
                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
                        character.name = new_name;
                    }
                });

                ui.gap(10.0);
                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
                    character.age = age;
                }

                for stat in Stat::iter() {
                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
                    let key = format!("{:?}", stat);
                    ui.set_variable(key, value);
                }

                ui.scrollpane("description_panel", "description_pane", |ui| {
                    ui.text_area("description_box");
                });

                ui.gap(10.0);

                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
                    character.race = *race;
                }
    
                ui.gap(10.0);
    
                ui.tree("stats_panel", "stats_panel", true,
                |ui| {
                    ui.child("title");
                },|ui| {
                    stats_panel(ui, character);
                });
                
                ui.gap(10.0);
    
                ui.tree("inventory_panel", "inventory_panel", true,
                |ui| {
                    ui.child("title");
                }, |ui| {
                    inventory_panel(ui, character);
                });
            });
        });

        ui.window("item_picker", |ui| {
            let display_size = ui.display_size();

            ui.start("greyed_out")
            .unclip()
            .unparent()
            .size(display_size.x, display_size.y)
            .screen_pos(0.0, 0.0).finish();

            item_picker(ui, character);
        });
    }
}

Auto Trait Implementations§

§

impl !RefUnwindSafe for Frame

§

impl !Send for Frame

§

impl !Sync for Frame

§

impl Unpin for Frame

§

impl !UnwindSafe for Frame

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> Downcast<T> for T

§

fn downcast(&self) -> &T

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> Upcast<T> for T

§

fn upcast(&self) -> Option<&T>