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
impl Frame
sourcepub fn start(&mut self, theme: &str) -> WidgetBuilder<'_>
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
fn party_members_panel(ui: &mut Frame, party: &mut Party) {
for (index, member) in party.members.iter_mut().enumerate() {
let clicked = ui.start("filled_slot_button")
.text(&member.name)
.active(Some(index) == party.editing_index)
.finish().clicked;
if clicked {
set_active_character(ui, member);
party.editing_index = Some(index);
}
}
if ui.start("add_character_button").finish().clicked {
let new_member = Character::generate(party.members.len());
set_active_character(ui, &new_member);
party.members.push(new_member);
party.editing_index = Some(party.members.len() - 1);
}
}
fn set_active_character(ui: &mut Frame, character: &Character) {
ui.open("character_window");
ui.modify("name_input", |state| {
state.text = Some(character.name.clone());
});
ui.close("item_picker");
}
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
fn item_picker(ui: &mut Frame, character: &mut Character) {
for item in ITEMS.iter() {
let clicked = ui.start("item_button")
.enabled(character.gp >= item.price)
.children(|ui| {
ui.label("name", item.name);
// TODO icon image
ui.child("icon");
ui.label("price", format!("{} Gold", item.price));
}).clicked;
if clicked {
character.gp -= item.price;
character.items.push(item.clone());
ui.close("item_picker");
}
}
}
fn inventory_panel(ui: &mut Frame, character: &mut Character) {
ui.start("top_panel")
.children(|ui| {
if ui.child("buy").clicked {
ui.open_modal("item_picker");
}
ui.label("gold", format!("{} Gold", character.gp));
});
ui.start("items_panel")
.scrollpane("items_content")
.show_vertical_scrollbar(ShowElement::Always)
.children(|ui| {
items_panel(ui, character);
});
}
sourcepub fn display_size(&self) -> Point
pub fn display_size(&self) -> Point
Returns the current window display size, in logical pixels.
Examples found in repository?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
sourcepub fn input_modifiers(&self) -> InputModifiers
pub fn input_modifiers(&self) -> InputModifiers
Returns the current state of the keyboard modifier keys
sourcepub fn mouse_rect(&self) -> Rect
pub fn mouse_rect(&self) -> Rect
Returns the current mouse position and size, in logical pixels
sourcepub fn wants_mouse(&self) -> bool
pub fn wants_mouse(&self) -> bool
Returns whether or not the Thyme UI wants the mouse this frame.
See Context.wants_mouse
sourcepub fn wants_keyboard(&self) -> bool
pub fn wants_keyboard(&self) -> bool
Returns whether or not the Thyme UI wants the keyboard this frame.
See Context.wants_keyboard
sourcepub fn mouse_time_in_current_widget(&self) -> u32
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.
sourcepub fn tooltip_ready(&mut self) -> Option<Point>
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
.
sourcepub fn set_mouse_cursor(&mut self, image: &str, align: Align)
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
sourcepub fn set_mouse_state(&mut self, state: AnimState)
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.
sourcepub fn gap(&mut self, gap: f32)
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?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme wgpu Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_wgpu()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
More examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme Glium Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_glium()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme Gl Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_gl()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
sourcepub fn set_cursor(&mut self, x: f32, y: f32)
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.
sourcepub fn cursor(&self) -> Point
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.
sourcepub fn focus_keyboard<T: Into<String>>(&mut self, id: T)
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");
}
sourcepub fn is_focus_keyboard(&self, id: &str) -> bool
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
.
sourcepub fn parent_bounds(&self) -> Rect
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.
sourcepub fn parent_max_child_bounds(&self) -> Rect
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.
sourcepub fn cur_time_millis(&self) -> u32
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?
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
sourcepub fn set_base_time_millis<T: Into<String>>(&mut self, id: T, time: u32)
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.
sourcepub fn set_base_time_now<T: Into<String>>(&mut self, id: T)
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?
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
sourcepub fn base_time_millis(&self, id: &str) -> u32
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?
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
sourcepub fn set_timer<T: Into<String>>(&mut self, id: T, time: u32)
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
.
sourcepub fn set_timer_to_now<T: Into<String>>(&mut self, id: T)
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
.
sourcepub fn timer(&self, id: &str) -> u32
pub fn timer(&self, id: &str) -> u32
Returns the current timer in millis of the PersistentState
for the
widget with the current id
.
sourcepub fn scroll(&self, id: &str) -> Point
pub fn scroll(&self, id: &str) -> Point
Sets the internal scroll
of the PersistentState
for
the widget with the specified id
. Useful for Scrollpanes
.
sourcepub fn change_scroll<T: Into<String>>(&mut self, id: T, x: f32, y: f32)
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
sourcepub fn text_for(&self, id: &str) -> Option<String>
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
.
sourcepub fn is_expanded(&self, id: &str) -> bool
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
sourcepub fn set_expanded<T: Into<String>>(&mut self, id: T, expanded: bool)
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
sourcepub fn is_open(&self, id: &str) -> bool
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.
sourcepub fn open_modal<T: Into<String>>(&mut self, id: T)
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?
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
fn inventory_panel(ui: &mut Frame, character: &mut Character) {
ui.start("top_panel")
.children(|ui| {
if ui.child("buy").clicked {
ui.open_modal("item_picker");
}
ui.label("gold", format!("{} Gold", character.gp));
});
ui.start("items_panel")
.scrollpane("items_content")
.show_vertical_scrollbar(ShowElement::Always)
.children(|ui| {
items_panel(ui, character);
});
}
sourcepub fn close_modal_on_click_outside(&mut self)
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.
sourcepub fn open<T: Into<String>>(&mut self, id: T)
pub fn open<T: Into<String>>(&mut self, id: T)
Opens the widget with the specified id
. This modifies the PersistentState
.
See is_open
sourcepub fn close<T: Into<String>>(&mut self, id: T)
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?
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
fn set_active_character(ui: &mut Frame, character: &Character) {
ui.open("character_window");
ui.modify("name_input", |state| {
state.text = Some(character.name.clone());
});
ui.close("item_picker");
}
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
fn item_picker(ui: &mut Frame, character: &mut Character) {
for item in ITEMS.iter() {
let clicked = ui.start("item_button")
.enabled(character.gp >= item.price)
.children(|ui| {
ui.label("name", item.name);
// TODO icon image
ui.child("icon");
ui.label("price", format!("{} Gold", item.price));
}).clicked;
if clicked {
character.gp -= item.price;
character.items.push(item.clone());
ui.close("item_picker");
}
}
}
sourcepub fn open_parent(&mut self)
pub fn open_parent(&mut self)
Opens the current parent widget. See open
.
sourcepub fn close_parent(&mut self)
pub fn close_parent(&mut self)
Closes the current parent widget. See close
.
sourcepub fn clear(&mut self, id: &str)
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.
sourcepub fn modify<T: Into<String>, Ret, F: FnOnce(&mut PersistentState) -> Ret>(
&mut self,
id: T,
f: F
) -> Ret
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.
sourcepub fn log<T: Into<String>>(&self, level: Level, message: T)
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.
sourcepub fn set_variable<T: Into<String>, U: Into<String>>(
&mut self,
key: T,
value: U
)
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
source§impl Frame
impl Frame
sourcepub fn child(&mut self, theme: &str) -> WidgetState
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
fn party_members_panel(ui: &mut Frame, party: &mut Party) {
for (index, member) in party.members.iter_mut().enumerate() {
let clicked = ui.start("filled_slot_button")
.text(&member.name)
.active(Some(index) == party.editing_index)
.finish().clicked;
if clicked {
set_active_character(ui, member);
party.editing_index = Some(index);
}
}
if ui.start("add_character_button").finish().clicked {
let new_member = Character::generate(party.members.len());
set_active_character(ui, &new_member);
party.members.push(new_member);
party.editing_index = Some(party.members.len() - 1);
}
}
fn set_active_character(ui: &mut Frame, character: &Character) {
ui.open("character_window");
ui.modify("name_input", |state| {
state.text = Some(character.name.clone());
});
ui.close("item_picker");
}
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
fn item_picker(ui: &mut Frame, character: &mut Character) {
for item in ITEMS.iter() {
let clicked = ui.start("item_button")
.enabled(character.gp >= item.price)
.children(|ui| {
ui.label("name", item.name);
// TODO icon image
ui.child("icon");
ui.label("price", format!("{} Gold", item.price));
}).clicked;
if clicked {
character.gp -= item.price;
character.items.push(item.clone());
ui.close("item_picker");
}
}
}
fn inventory_panel(ui: &mut Frame, character: &mut Character) {
ui.start("top_panel")
.children(|ui| {
if ui.child("buy").clicked {
ui.open_modal("item_picker");
}
ui.label("gold", format!("{} Gold", character.gp));
});
ui.start("items_panel")
.scrollpane("items_content")
.show_vertical_scrollbar(ShowElement::Always)
.children(|ui| {
items_panel(ui, character);
});
}
sourcepub fn label<T: Into<String>>(&mut self, theme: &str, text: T)
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
fn party_members_panel(ui: &mut Frame, party: &mut Party) {
for (index, member) in party.members.iter_mut().enumerate() {
let clicked = ui.start("filled_slot_button")
.text(&member.name)
.active(Some(index) == party.editing_index)
.finish().clicked;
if clicked {
set_active_character(ui, member);
party.editing_index = Some(index);
}
}
if ui.start("add_character_button").finish().clicked {
let new_member = Character::generate(party.members.len());
set_active_character(ui, &new_member);
party.members.push(new_member);
party.editing_index = Some(party.members.len() - 1);
}
}
fn set_active_character(ui: &mut Frame, character: &Character) {
ui.open("character_window");
ui.modify("name_input", |state| {
state.text = Some(character.name.clone());
});
ui.close("item_picker");
}
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
fn item_picker(ui: &mut Frame, character: &mut Character) {
for item in ITEMS.iter() {
let clicked = ui.start("item_button")
.enabled(character.gp >= item.price)
.children(|ui| {
ui.label("name", item.name);
// TODO icon image
ui.child("icon");
ui.label("price", format!("{} Gold", item.price));
}).clicked;
if clicked {
character.gp -= item.price;
character.items.push(item.clone());
ui.close("item_picker");
}
}
}
fn inventory_panel(ui: &mut Frame, character: &mut Character) {
ui.start("top_panel")
.children(|ui| {
if ui.child("buy").clicked {
ui.open_modal("item_picker");
}
ui.label("gold", format!("{} Gold", character.gp));
});
ui.start("items_panel")
.scrollpane("items_content")
.show_vertical_scrollbar(ShowElement::Always)
.children(|ui| {
items_panel(ui, character);
});
}
sourcepub fn multiline_label<T: Into<String>>(&mut self, theme: &str, text: T)
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.
*
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?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme wgpu Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_wgpu()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
More examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme Glium Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_glium()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
fn items_panel(ui: &mut Frame, character: &mut Character) {
let mut sell = None;
for (index, item) in character.items.iter().enumerate() {
let result = ui.button("item_button", item.name);
if result.clicked {
sell = Some(index);
}
if result.hovered {
// manually specify a tooltip
ui.tooltip_label("tooltip", "Remove Item");
}
}
if let Some(index) = sell {
let item = character.items.remove(index);
character.gp += item.price;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme Gl Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_gl()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
sourcepub fn vertical_slider(
&mut self,
theme: &str,
min: f32,
max: f32,
value: f32
) -> Option<f32>
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]
sourcepub fn horizontal_slider(
&mut self,
theme: &str,
min: f32,
max: f32,
value: f32
) -> Option<f32>
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
sourcepub fn spinner<T: PartialOrd + Display>(
&mut self,
theme: &str,
value: T,
min: T,
max: T
) -> i32
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?
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
sourcepub fn wrapping_spinner<T: Display>(&mut self, theme: &str, value: T) -> i32
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
.
sourcepub fn tree<F: FnOnce(&mut Frame), G: FnOnce(&mut Frame)>(
&mut self,
theme: &str,
id: &str,
initially_expanded: bool,
title: F,
children: G
)
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
fn party_members_panel(ui: &mut Frame, party: &mut Party) {
for (index, member) in party.members.iter_mut().enumerate() {
let clicked = ui.start("filled_slot_button")
.text(&member.name)
.active(Some(index) == party.editing_index)
.finish().clicked;
if clicked {
set_active_character(ui, member);
party.editing_index = Some(index);
}
}
if ui.start("add_character_button").finish().clicked {
let new_member = Character::generate(party.members.len());
set_active_character(ui, &new_member);
party.members.push(new_member);
party.editing_index = Some(party.members.len() - 1);
}
}
fn set_active_character(ui: &mut Frame, character: &Character) {
ui.open("character_window");
ui.modify("name_input", |state| {
state.text = Some(character.name.clone());
});
ui.close("item_picker");
}
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
sourcepub fn combo_box<'a, T: Display>(
&mut self,
theme: &str,
id: &str,
current: &T,
values: &'a [T]
) -> Option<&'a T>
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
A simple toggle button that can be toggle on or off, based on the passed in active
state.
See button
for a YAML example.
sourcepub fn input_field(
&mut self,
theme: &str,
id: &str,
initial_value: Option<String>
) -> Option<String>
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
sourcepub fn progress_bar(&mut self, theme: &str, frac: f32)
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?
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
fn stats_panel(ui: &mut Frame, character: &mut Character) {
let points_used: u32 = character.stats.values().sum();
let points_available: u32 = STAT_POINTS - points_used;
let frac = ((ui.cur_time_millis() - ui.base_time_millis("stat_roll")) as f32 / 1000.0).min(1.0);
let roll = ui.start("roll_button")
.enabled(frac > 0.99)
.children(|ui| {
ui.progress_bar("progress_bar", frac);
});
if roll.clicked {
ui.set_base_time_now("stat_roll");
}
for stat in Stat::iter() {
let value = character.stats.entry(stat).or_insert(10);
ui.tree(
"stat_panel",
&format!("stat_panel_{:?}", stat),
false,
|ui| {
ui.label("label", format!("{:?}", stat));
match ui.spinner("spinner", *value, MIN_STAT, if points_available == 0 { *value } else { MAX_STAT }) {
1 => *value += 1,
-1 => *value -= 1,
_ => (),
}
}, |ui| {
ui.child("description");
});
}
ui.label("points_available", format!("Points Remaining: {}", points_available));
}
sourcepub fn progress_bar_vert(&mut self, theme: &str, frac: f32)
pub fn progress_bar_vert(&mut self, theme: &str, frac: f32)
Creates a simple vertical progress bar. See progress_bar
*
sourcepub fn tooltip_label<T: Into<String>>(&mut self, theme: &str, label: T)
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?
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
fn items_panel(ui: &mut Frame, character: &mut Character) {
let mut sell = None;
for (index, item) in character.items.iter().enumerate() {
let result = ui.button("item_button", item.name);
if result.clicked {
sell = Some(index);
}
if result.hovered {
// manually specify a tooltip
ui.tooltip_label("tooltip", "Remove Item");
}
}
if let Some(index) = sell {
let item = character.items.remove(index);
character.gp += item.price;
}
}
sourcepub fn window<F: FnOnce(&mut Frame)>(&mut self, theme: &str, children: F)
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?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme wgpu Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_wgpu()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
More examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme Glium Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_glium()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = thyme::AppBuilder::new()
.with_logger()
.with_title("Thyme Gl Demo")
.with_window_size(1280.0, 720.0)
.with_base_dir("examples/data")
.with_theme_files(&["themes/base.yml", "themes/pixel.yml"])
.with_font_dir("fonts")
.with_image_dir("images")
.build_gl()?;
app.main_loop(|ui| {
ui.window("window", |ui| {
ui.gap(20.0);
ui.button("label", "Hello, World!");
});
});
}
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
sourcepub fn scrollpane<F: FnOnce(&mut Frame)>(
&mut self,
theme: &str,
content_id: &str,
children: F
)
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}
source§impl Frame
impl Frame
sourcepub fn text_area(&mut self, theme: &str)
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?
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
pub fn build_ui(ui: &mut Frame, party: &mut Party) {
match party.theme_choice {
ThemeChoice::Pixels | ThemeChoice::Fantasy | ThemeChoice::Transparent | ThemeChoice::Golden => {
// show a custom cursor. it automatically inherits mouse presses in its state
ui.set_mouse_cursor("gui/cursor", thyme::Align::TopLeft);
},
ThemeChoice::NoImage => {
// don't show a custom cursor
}
}
ui.label("bench", format!(
"{}\n{}\n{}",
bench::short_report("thyme"),
bench::short_report("frame"),
bench::short_report("draw"),
));
ui.start("theme_panel").children(|ui| {
if ui.start("live_reload").active(!party.live_reload_disabled).finish().clicked {
party.live_reload_disabled = !party.live_reload_disabled;
}
if let Some(choice) = ui.combo_box("theme_choice", "theme_choice", &party.theme_choice, &THEME_CHOICES) {
party.old_theme_choice = Some(party.theme_choice);
party.theme_choice = *choice;
party.reload_assets = true;
}
});
ui.start("party_window")
.window("party_window")
.with_close_button(false)
.moveable(false)
.resizable(false)
.children(|ui| {
ui.scrollpane("members_panel", "party_content", |ui| {
party_members_panel(ui, party);
});
});
if let Some(index) = party.editing_index {
let character = &mut party.members[index];
ui.window("character_window", |ui| {
ui.scrollpane("pane", "character_content", |ui| {
ui.start("name_panel")
.children(|ui| {
if let Some(new_name) = ui.input_field("name_input", "name_input", None) {
character.name = new_name;
}
});
ui.gap(10.0);
ui.label("age_label", format!("Age: {}", character.age.round() as u32));
if let Some(age) = ui.horizontal_slider("age_slider", MIN_AGE, MAX_AGE, character.age) {
character.age = age;
}
for stat in Stat::iter() {
let value = format!("{}", character.stats.get(&stat).unwrap_or(&10));
let key = format!("{:?}", stat);
ui.set_variable(key, value);
}
ui.scrollpane("description_panel", "description_pane", |ui| {
ui.text_area("description_box");
});
ui.gap(10.0);
if let Some(race) = ui.combo_box("race_selector", "race_selector", &character.race, Race::all()) {
character.race = *race;
}
ui.gap(10.0);
ui.tree("stats_panel", "stats_panel", true,
|ui| {
ui.child("title");
},|ui| {
stats_panel(ui, character);
});
ui.gap(10.0);
ui.tree("inventory_panel", "inventory_panel", true,
|ui| {
ui.child("title");
}, |ui| {
inventory_panel(ui, character);
});
});
});
ui.window("item_picker", |ui| {
let display_size = ui.display_size();
ui.start("greyed_out")
.unclip()
.unparent()
.size(display_size.x, display_size.y)
.screen_pos(0.0, 0.0).finish();
item_picker(ui, character);
});
}
}