Struct 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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
297
298fn party_members_panel(ui: &mut Frame, party: &mut Party) {
299    for (index, member) in party.members.iter_mut().enumerate() {
300        let clicked = ui.start("filled_slot_button")
301        .text(&member.name)
302        .active(Some(index) == party.editing_index)
303        .finish().clicked;
304
305        if clicked {
306            set_active_character(ui, member);
307            party.editing_index = Some(index);
308        }
309    }
310
311    if ui.start("add_character_button").finish().clicked {
312        let new_member = Character::generate(party.members.len());
313        set_active_character(ui, &new_member);
314        party.members.push(new_member);
315        party.editing_index = Some(party.members.len() - 1);
316    }
317}
318
319fn set_active_character(ui: &mut Frame, character: &Character) {
320    ui.open("character_window");
321    ui.modify("name_input", |state| {
322        state.text = Some(character.name.clone());
323    });
324    ui.close("item_picker");
325}
326
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
364
365fn item_picker(ui: &mut Frame, character: &mut Character) {
366    for item in ITEMS.iter() {
367        let clicked = ui.start("item_button")
368        .enabled(character.gp >= item.price)
369        .children(|ui| {
370            ui.label("name", item.name);
371            // TODO icon image
372            ui.child("icon");
373            ui.label("price", format!("{} Gold", item.price));
374        }).clicked;
375
376        if clicked {
377            character.gp -= item.price;
378            character.items.push(item.clone());
379            ui.close("item_picker");
380        }
381    }
382}
383
384fn inventory_panel(ui: &mut Frame, character: &mut Character) {
385    ui.start("top_panel")
386    .children(|ui| {
387        if ui.child("buy").clicked {
388            ui.open_modal("item_picker");
389        }
390
391        ui.label("gold", format!("{} Gold", character.gp));
392    });
393    
394    ui.start("items_panel")
395    .scrollpane("items_content")
396    .show_vertical_scrollbar(ShowElement::Always)
397    .children(|ui| {
398        items_panel(ui, character);
399    });
400}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme wgpu Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_wgpu()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
More examples
Hide additional examples
examples/hello_glium.rs (line 14)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme Glium Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_glium()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
examples/hello_gl.rs (line 14)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme Gl Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_gl()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
examples/demo.rs (line 242)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
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)
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
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)
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
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)
384fn inventory_panel(ui: &mut Frame, character: &mut Character) {
385    ui.start("top_panel")
386    .children(|ui| {
387        if ui.child("buy").clicked {
388            ui.open_modal("item_picker");
389        }
390
391        ui.label("gold", format!("{} Gold", character.gp));
392    });
393    
394    ui.start("items_panel")
395    .scrollpane("items_content")
396    .show_vertical_scrollbar(ShowElement::Always)
397    .children(|ui| {
398        items_panel(ui, character);
399    });
400}
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)
319fn set_active_character(ui: &mut Frame, character: &Character) {
320    ui.open("character_window");
321    ui.modify("name_input", |state| {
322        state.text = Some(character.name.clone());
323    });
324    ui.close("item_picker");
325}
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)
319fn set_active_character(ui: &mut Frame, character: &Character) {
320    ui.open("character_window");
321    ui.modify("name_input", |state| {
322        state.text = Some(character.name.clone());
323    });
324    ui.close("item_picker");
325}
326
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
364
365fn item_picker(ui: &mut Frame, character: &mut Character) {
366    for item in ITEMS.iter() {
367        let clicked = ui.start("item_button")
368        .enabled(character.gp >= item.price)
369        .children(|ui| {
370            ui.label("name", item.name);
371            // TODO icon image
372            ui.child("icon");
373            ui.label("price", format!("{} Gold", item.price));
374        }).clicked;
375
376        if clicked {
377            character.gp -= item.price;
378            character.items.push(item.clone());
379            ui.close("item_picker");
380        }
381    }
382}
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)
319fn set_active_character(ui: &mut Frame, character: &Character) {
320    ui.open("character_window");
321    ui.modify("name_input", |state| {
322        state.text = Some(character.name.clone());
323    });
324    ui.close("item_picker");
325}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
297
298fn party_members_panel(ui: &mut Frame, party: &mut Party) {
299    for (index, member) in party.members.iter_mut().enumerate() {
300        let clicked = ui.start("filled_slot_button")
301        .text(&member.name)
302        .active(Some(index) == party.editing_index)
303        .finish().clicked;
304
305        if clicked {
306            set_active_character(ui, member);
307            party.editing_index = Some(index);
308        }
309    }
310
311    if ui.start("add_character_button").finish().clicked {
312        let new_member = Character::generate(party.members.len());
313        set_active_character(ui, &new_member);
314        party.members.push(new_member);
315        party.editing_index = Some(party.members.len() - 1);
316    }
317}
318
319fn set_active_character(ui: &mut Frame, character: &Character) {
320    ui.open("character_window");
321    ui.modify("name_input", |state| {
322        state.text = Some(character.name.clone());
323    });
324    ui.close("item_picker");
325}
326
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
364
365fn item_picker(ui: &mut Frame, character: &mut Character) {
366    for item in ITEMS.iter() {
367        let clicked = ui.start("item_button")
368        .enabled(character.gp >= item.price)
369        .children(|ui| {
370            ui.label("name", item.name);
371            // TODO icon image
372            ui.child("icon");
373            ui.label("price", format!("{} Gold", item.price));
374        }).clicked;
375
376        if clicked {
377            character.gp -= item.price;
378            character.items.push(item.clone());
379            ui.close("item_picker");
380        }
381    }
382}
383
384fn inventory_panel(ui: &mut Frame, character: &mut Character) {
385    ui.start("top_panel")
386    .children(|ui| {
387        if ui.child("buy").clicked {
388            ui.open_modal("item_picker");
389        }
390
391        ui.label("gold", format!("{} Gold", character.gp));
392    });
393    
394    ui.start("items_panel")
395    .scrollpane("items_content")
396    .show_vertical_scrollbar(ShowElement::Always)
397    .children(|ui| {
398        items_panel(ui, character);
399    });
400}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
297
298fn party_members_panel(ui: &mut Frame, party: &mut Party) {
299    for (index, member) in party.members.iter_mut().enumerate() {
300        let clicked = ui.start("filled_slot_button")
301        .text(&member.name)
302        .active(Some(index) == party.editing_index)
303        .finish().clicked;
304
305        if clicked {
306            set_active_character(ui, member);
307            party.editing_index = Some(index);
308        }
309    }
310
311    if ui.start("add_character_button").finish().clicked {
312        let new_member = Character::generate(party.members.len());
313        set_active_character(ui, &new_member);
314        party.members.push(new_member);
315        party.editing_index = Some(party.members.len() - 1);
316    }
317}
318
319fn set_active_character(ui: &mut Frame, character: &Character) {
320    ui.open("character_window");
321    ui.modify("name_input", |state| {
322        state.text = Some(character.name.clone());
323    });
324    ui.close("item_picker");
325}
326
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
364
365fn item_picker(ui: &mut Frame, character: &mut Character) {
366    for item in ITEMS.iter() {
367        let clicked = ui.start("item_button")
368        .enabled(character.gp >= item.price)
369        .children(|ui| {
370            ui.label("name", item.name);
371            // TODO icon image
372            ui.child("icon");
373            ui.label("price", format!("{} Gold", item.price));
374        }).clicked;
375
376        if clicked {
377            character.gp -= item.price;
378            character.items.push(item.clone());
379            ui.close("item_picker");
380        }
381    }
382}
383
384fn inventory_panel(ui: &mut Frame, character: &mut Character) {
385    ui.start("top_panel")
386    .children(|ui| {
387        if ui.child("buy").clicked {
388            ui.open_modal("item_picker");
389        }
390
391        ui.label("gold", format!("{} Gold", character.gp));
392    });
393    
394    ui.start("items_panel")
395    .scrollpane("items_content")
396    .show_vertical_scrollbar(ShowElement::Always)
397    .children(|ui| {
398        items_panel(ui, character);
399    });
400}
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)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme wgpu Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_wgpu()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
More examples
Hide additional examples
examples/hello_glium.rs (line 16)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme Glium Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_glium()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
examples/demo.rs (line 405)
402fn items_panel(ui: &mut Frame, character: &mut Character) {
403    let mut sell = None;
404    for (index, item) in character.items.iter().enumerate() {
405        let result = ui.button("item_button", item.name);
406        if result.clicked {
407            sell = Some(index);
408        }
409        
410        if result.hovered {
411            // manually specify a tooltip
412            ui.tooltip_label("tooltip", "Remove Item");
413        }
414    }
415
416    if let Some(index) = sell {
417        let item = character.items.remove(index);
418        character.gp += item.price;
419    }
420}
examples/hello_gl.rs (line 16)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme Gl Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_gl()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
297
298fn party_members_panel(ui: &mut Frame, party: &mut Party) {
299    for (index, member) in party.members.iter_mut().enumerate() {
300        let clicked = ui.start("filled_slot_button")
301        .text(&member.name)
302        .active(Some(index) == party.editing_index)
303        .finish().clicked;
304
305        if clicked {
306            set_active_character(ui, member);
307            party.editing_index = Some(index);
308        }
309    }
310
311    if ui.start("add_character_button").finish().clicked {
312        let new_member = Character::generate(party.members.len());
313        set_active_character(ui, &new_member);
314        party.members.push(new_member);
315        party.editing_index = Some(party.members.len() - 1);
316    }
317}
318
319fn set_active_character(ui: &mut Frame, character: &Character) {
320    ui.open("character_window");
321    ui.modify("name_input", |state| {
322        state.text = Some(character.name.clone());
323    });
324    ui.close("item_picker");
325}
326
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
327fn stats_panel(ui: &mut Frame, character: &mut Character) {
328    let points_used: u32 = character.stats.values().sum();
329    let points_available: u32 = STAT_POINTS - points_used;
330    let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
331
332    let roll = ui.start("roll_button")
333    .enabled(frac > 0.99)
334    .children(|ui| {
335        ui.progress_bar("progress_bar", frac);
336    });
337
338    if roll.clicked {
339        ui.set_base_time_now("stat_roll");
340    }
341
342    for stat in Stat::iter() {
343        let value = character.stats.entry(stat).or_insert(10);
344
345        ui.tree(
346        "stat_panel",
347        &format!("stat_panel_{:?}", stat),
348        false,
349        |ui| {
350            ui.label("label", format!("{:?}", stat));
351
352            match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
353                1 => *value += 1,
354                -1 => *value -= 1,
355                _ => (),
356            }
357        }, |ui| {
358            ui.child("description");
359        });
360    }
361
362    ui.label("points_available", format!("Points Remaining: {}", points_available));
363}
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)
402fn items_panel(ui: &mut Frame, character: &mut Character) {
403    let mut sell = None;
404    for (index, item) in character.items.iter().enumerate() {
405        let result = ui.button("item_button", item.name);
406        if result.clicked {
407            sell = Some(index);
408        }
409        
410        if result.hovered {
411            // manually specify a tooltip
412            ui.tooltip_label("tooltip", "Remove Item");
413        }
414    }
415
416    if let Some(index) = sell {
417        let item = character.items.remove(index);
418        character.gp += item.price;
419    }
420}
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)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme wgpu Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_wgpu()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
More examples
Hide additional examples
examples/hello_glium.rs (lines 13-17)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme Glium Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_glium()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
examples/hello_gl.rs (lines 13-17)
1fn main() -> Result<(), Box<dyn std::error::Error>> {
2    let app = thyme::AppBuilder::new()
3        .with_logger()
4        .with_title("Thyme Gl Demo")
5        .with_window_size(1280.0, 720.0)
6        .with_base_dir("examples/data")
7        .with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
8        .with_font_dir("fonts")
9        .with_image_dir("images")
10        .build_gl()?;
11
12    app.main_loop(|ui| {
13        ui.window("window", |ui| {
14            ui.gap(20.0);
15    
16            ui.button("label", "Hello, World!");
17        });
18    });
19}
examples/demo.rs (lines 233-282)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}
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)
189pub fn build_ui(ui: &mut Frame, party: &mut Party) {
190    match party.theme_choice {
191        ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
192            // show a custom cursor.  it automatically inherits mouse presses in its state
193            ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
194        },
195        ThemeChoice::NoImage => {
196            // don't show a custom cursor
197        }
198    }
199
200    ui.label("bench", format!(
201        "{}\n{}\n{}",
202        bench::short_report("thyme"),
203        bench::short_report("frame"),
204        bench::short_report("draw"),
205    ));
206
207    ui.start("theme_panel").children(|ui| {
208        if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
209            party.live_reload_disabled = !party.live_reload_disabled;
210        }
211
212        if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
213            party.old_theme_choice = Some(party.theme_choice);
214            party.theme_choice = *choice;
215            party.reload_assets = true;
216        }
217    });
218
219    ui.start("party_window")
220    .window("party_window")
221    .with_close_button(false)
222    .moveable(false)
223    .resizable(false)
224    .children(|ui| {
225        ui.scrollpane("members_panel", "party_content", |ui| {
226            party_members_panel(ui, party);
227        });
228    });
229
230    if let Some(index) = party.editing_index {
231        let character = &mut party.members[index];
232
233        ui.window("character_window", |ui| {
234            ui.scrollpane("pane", "character_content", |ui| {
235                ui.start("name_panel")
236                .children(|ui| {
237                    if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
238                        character.name = new_name;
239                    }
240                });
241
242                ui.gap(10.0);
243                ui.label("age_label", format!("Age: {}", character.age.round() as u32));
244                if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
245                    character.age = age;
246                }
247
248                for stat in Stat::iter() {
249                    let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
250                    let key = format!("{:?}", stat);
251                    ui.set_variable(key, value);
252                }
253
254                ui.scrollpane("description_panel", "description_pane", |ui| {
255                    ui.text_area("description_box");
256                });
257
258                ui.gap(10.0);
259
260                if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
261                    character.race = *race;
262                }
263    
264                ui.gap(10.0);
265    
266                ui.tree("stats_panel", "stats_panel", true,
267                |ui| {
268                    ui.child("title");
269                },|ui| {
270                    stats_panel(ui, character);
271                });
272                
273                ui.gap(10.0);
274    
275                ui.tree("inventory_panel", "inventory_panel", true,
276                |ui| {
277                    ui.child("title");
278                }, |ui| {
279                    inventory_panel(ui, character);
280                });
281            });
282        });
283
284        ui.window("item_picker", |ui| {
285            let display_size = ui.display_size();
286
287            ui.start("greyed_out")
288            .unclip()
289            .unparent()
290            .size(display_size.x, display_size.y)
291            .screen_pos(0.0, 0.0).finish();
292
293            item_picker(ui, character);
294        });
295    }
296}

Auto Trait Implementations§

§

impl Freeze for Frame

§

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 T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast<T> for T

Source§

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 T
where 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 T
where U: Into<T>,

Source§

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 T
where U: TryFrom<T>,

Source§

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.
Source§

impl<T> Upcast<T> for T

Source§

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