From 984a56aae9eb489337708e8465b15311d8596949 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Jan 2019 10:55:38 +0100 Subject: [PATCH] Move widgets out of Region into own file --- emgui/src/layout.rs | 189 ++++++++------------------------ emgui/src/lib.rs | 1 + emgui/src/style.rs | 9 -- emgui/src/widgets.rs | 246 ++++++++++++++++++++++++++++++++++++++++++ emgui_wasm/src/app.rs | 62 ++++++----- emgui_wasm/src/lib.rs | 24 +++-- 6 files changed, 340 insertions(+), 191 deletions(-) create mode 100644 emgui/src/widgets.rs diff --git a/emgui/src/layout.rs b/emgui/src/layout.rs index 7dfe7f96..608579db 100644 --- a/emgui/src/layout.rs +++ b/emgui/src/layout.rs @@ -4,7 +4,12 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::{font::Font, math::*, types::*}; +use crate::{ + font::Font, + math::*, + types::*, + widgets::{label, Widget}, +}; // ---------------------------------------------------------------------------- @@ -72,7 +77,7 @@ impl GuiResponse { /// Show this text if the item was hovered pub fn tooltip_text>(&mut self, text: S) -> &mut Self { self.tooltip(|popup| { - popup.label(text); + popup.add(label(text)); }) } } @@ -90,7 +95,7 @@ pub struct Memory { // ---------------------------------------------------------------------------- -struct TextFragment { +pub struct TextFragment { /// The start of each character, starting at zero. x_offsets: Vec, /// 0 for the first line, n * line_spacing for the rest @@ -98,7 +103,7 @@ struct TextFragment { text: String, } -type TextFragments = Vec; +pub type TextFragments = Vec; // ---------------------------------------------------------------------------- @@ -116,7 +121,14 @@ impl Default for Direction { // ---------------------------------------------------------------------------- -type Id = u64; +pub type Id = u64; + +pub fn make_id(source: &H) -> Id { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + source.hash(&mut hasher); + hasher.finish() +} // ---------------------------------------------------------------------------- @@ -271,137 +283,6 @@ impl Region { self.cursor } - pub fn button>(&mut self, text: S) -> GuiResponse { - let text: String = text.into(); - let id = self.make_child_id(&text); - let (text, text_size) = self.layout_text(&text); - let text_cursor = self.cursor + self.options().button_padding; - let (rect, interact) = - self.reserve_space(text_size + 2.0 * self.options().button_padding, Some(id)); - self.add_graphic(GuiCmd::Button { interact, rect }); - self.add_text(text_cursor, text); - self.response(interact) - } - - pub fn checkbox>(&mut self, text: S, checked: &mut bool) -> GuiResponse { - let text: String = text.into(); - let id = self.make_child_id(&text); - let (text, text_size) = self.layout_text(&text); - let text_cursor = self.cursor - + self.options().button_padding - + vec2(self.options().start_icon_width, 0.0); - let (rect, interact) = self.reserve_space( - self.options().button_padding - + vec2(self.options().start_icon_width, 0.0) - + text_size - + self.options().button_padding, - Some(id), - ); - if interact.clicked { - *checked = !*checked; - } - self.add_graphic(GuiCmd::Checkbox { - checked: *checked, - interact, - rect, - }); - self.add_text(text_cursor, text); - self.response(interact) - } - - pub fn label>(&mut self, text: S) -> GuiResponse { - let text: String = text.into(); - let (text, text_size) = self.layout_text(&text); - self.add_text(self.cursor, text); - let (_, interact) = self.reserve_space(text_size, None); - self.response(interact) - } - - /// A radio button - pub fn radio>(&mut self, text: S, checked: bool) -> GuiResponse { - let text: String = text.into(); - let id = self.make_child_id(&text); - let (text, text_size) = self.layout_text(&text); - let text_cursor = self.cursor - + self.options().button_padding - + vec2(self.options().start_icon_width, 0.0); - let (rect, interact) = self.reserve_space( - self.options().button_padding - + vec2(self.options().start_icon_width, 0.0) - + text_size - + self.options().button_padding, - Some(id), - ); - self.add_graphic(GuiCmd::RadioButton { - checked, - interact, - rect, - }); - self.add_text(text_cursor, text); - self.response(interact) - } - - pub fn slider_f32>( - &mut self, - text: S, - value: &mut f32, - min: f32, - max: f32, - ) -> GuiResponse { - let text_string: String = text.into(); - if true { - // Text to the right of the slider - self.columns(2, |columns| { - columns[1].label(format!("{}: {:.3}", text_string, value)); - columns[0].naked_slider_f32(&text_string, value, min, max) - }) - } else { - // Text above slider - let (text, text_size) = self.layout_text(&format!("{}: {:.3}", text_string, value)); - self.add_text(self.cursor, text); - self.reserve_space_inner(text_size); - self.naked_slider_f32(&text_string, value, min, max) - } - } - - pub fn naked_slider_f32( - &mut self, - id: &H, - value: &mut f32, - min: f32, - max: f32, - ) -> GuiResponse { - debug_assert!(min <= max); - let id = self.make_child_id(id); - let (slider_rect, interact) = self.reserve_space( - Vec2 { - x: self.available_space.x, - y: self.data.font.line_spacing(), - }, - Some(id), - ); - - if interact.active { - *value = remap_clamp( - self.input().mouse_pos.x, - slider_rect.min().x, - slider_rect.max().x, - min, - max, - ); - } - - self.add_graphic(GuiCmd::Slider { - interact, - max, - min, - rect: slider_rect, - value: *value, - }); - - self.response(interact) - } - // ------------------------------------------------------------------------ // Sub-regions: @@ -507,11 +388,11 @@ impl Region { self.reserve_space_inner(size); } - /// Temporarily split split a vertical layout into two column regions. + /// Temporarily split split a vertical layout into several columns. /// /// gui.columns(2, |columns| { - /// columns[0].label("First column"); - /// columns[1].label("Second column"); + /// columns[0].add(label("First column")); + /// columns[1].add(label("Second column")); /// }); pub fn columns(&mut self, num_columns: usize, add_contents: F) -> R where @@ -547,6 +428,13 @@ impl Region { // ------------------------------------------------------------------------ + pub fn add(&mut self, widget: W) -> GuiResponse { + widget.add_to(self) + } + + // ------------------------------------------------------------------------ + + // TODO: Return a Rect pub fn reserve_space( &mut self, size: Vec2, @@ -577,8 +465,9 @@ impl Region { (rect, interact) } + // TODO: Return a Rect /// Reserve this much space and move the cursor. - fn reserve_space_inner(&mut self, size: Vec2) { + pub fn reserve_space_inner(&mut self, size: Vec2) { if self.dir == Direction::Horizontal { self.cursor.x += size.x; self.available_space.x -= size.x; @@ -592,7 +481,7 @@ impl Region { } } - fn make_child_id(&self, child_id: &H) -> Id { + pub fn make_child_id(&self, child_id: &H) -> Id { use std::hash::Hasher; let mut hasher = std::collections::hash_map::DefaultHasher::new(); hasher.write_u64(self.id); @@ -600,8 +489,18 @@ impl Region { hasher.finish() } - // TODO: move this function - fn layout_text(&self, text: &str) -> (TextFragments, Vec2) { + pub fn combined_id(&self, child_id: Option) -> Option { + child_id.map(|child_id| { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + hasher.write_u64(self.id); + child_id.hash(&mut hasher); + hasher.finish() + }) + } + + // TODO: move this function to Font + pub fn layout_text(&self, text: &str) -> (TextFragments, Vec2) { let line_spacing = self.data.font.line_spacing(); let mut cursor_y = 0.0; let mut max_width = 0.0; @@ -622,7 +521,7 @@ impl Region { (text_fragments, bounding_size) } - fn add_text(&mut self, pos: Vec2, text: Vec) { + pub fn add_text(&mut self, pos: Vec2, text: Vec) { for fragment in text { self.add_graphic(GuiCmd::Text { pos: pos + vec2(0.0, fragment.y_offset), @@ -633,7 +532,7 @@ impl Region { } } - fn response(&mut self, interact: InteractInfo) -> GuiResponse { + pub fn response(&mut self, interact: InteractInfo) -> GuiResponse { GuiResponse { hovered: interact.hovered, clicked: interact.clicked, diff --git a/emgui/src/lib.rs b/emgui/src/lib.rs index 71b882e3..eedb9676 100644 --- a/emgui/src/lib.rs +++ b/emgui/src/lib.rs @@ -13,6 +13,7 @@ pub mod math; mod painter; mod style; pub mod types; +pub mod widgets; pub use crate::{ emgui::Emgui, diff --git a/emgui/src/style.rs b/emgui/src/style.rs index 4b1fce7a..b81df847 100644 --- a/emgui/src/style.rs +++ b/emgui/src/style.rs @@ -7,11 +7,6 @@ pub struct Style { /// For stuff like check marks in check boxes. pub line_width: f32, - - pub font_name: String, - - /// Height in pixels of most text. - pub font_size: f32, } impl Default for Style { @@ -19,10 +14,6 @@ impl Default for Style { Style { debug_rects: false, line_width: 2.0, - // font_name: "Palatino".to_string(), - font_name: "Courier".to_string(), - // font_name: "Courier New".to_string(), - font_size: 12.0, } } } diff --git a/emgui/src/widgets.rs b/emgui/src/widgets.rs new file mode 100644 index 00000000..2d6e5ff8 --- /dev/null +++ b/emgui/src/widgets.rs @@ -0,0 +1,246 @@ +use crate::{ + layout::{make_id, GuiResponse, Id, Region}, + math::{remap_clamp, vec2, Vec2}, + types::GuiCmd, +}; + +// ---------------------------------------------------------------------------- + +/// Anything implementing Widget can be added to a Region with Region::add +pub trait Widget { + fn add_to(self, region: &mut Region) -> GuiResponse; +} + +// ---------------------------------------------------------------------------- + +pub struct Label { + text: String, +} + +impl Label { + pub fn new>(text: S) -> Self { + Label { text: text.into() } + } +} + +pub fn label>(text: S) -> Label { + Label::new(text) +} + +impl Widget for Label { + fn add_to(self, region: &mut Region) -> GuiResponse { + let (text, text_size) = region.layout_text(&self.text); + region.add_text(region.cursor(), text); + let (_, interact) = region.reserve_space(text_size, None); + region.response(interact) + } +} + +// ---------------------------------------------------------------------------- + +pub struct Button { + text: String, +} + +impl Button { + pub fn new>(text: S) -> Self { + Button { text: text.into() } + } +} + +impl Widget for Button { + fn add_to(self, region: &mut Region) -> GuiResponse { + let id = region.make_child_id(&self.text); + let (text, text_size) = region.layout_text(&self.text); + let text_cursor = region.cursor() + region.options().button_padding; + let (rect, interact) = + region.reserve_space(text_size + 2.0 * region.options().button_padding, Some(id)); + region.add_graphic(GuiCmd::Button { interact, rect }); + region.add_text(text_cursor, text); + region.response(interact) + } +} + +// ---------------------------------------------------------------------------- + +#[derive(Debug)] +pub struct Checkbox<'a> { + checked: &'a mut bool, + text: String, +} + +impl<'a> Checkbox<'a> { + pub fn new>(checked: &'a mut bool, text: S) -> Self { + Checkbox { + checked, + text: text.into(), + } + } +} + +impl<'a> Widget for Checkbox<'a> { + fn add_to(self, region: &mut Region) -> GuiResponse { + let id = region.make_child_id(&self.text); + let (text, text_size) = region.layout_text(&self.text); + let text_cursor = region.cursor() + + region.options().button_padding + + vec2(region.options().start_icon_width, 0.0); + let (rect, interact) = region.reserve_space( + region.options().button_padding + + vec2(region.options().start_icon_width, 0.0) + + text_size + + region.options().button_padding, + Some(id), + ); + if interact.clicked { + *self.checked = !*self.checked; + } + region.add_graphic(GuiCmd::Checkbox { + checked: *self.checked, + interact, + rect, + }); + region.add_text(text_cursor, text); + region.response(interact) + } +} + +// ---------------------------------------------------------------------------- + +#[derive(Debug)] +pub struct RadioButton { + checked: bool, + text: String, +} + +impl RadioButton { + pub fn new>(checked: bool, text: S) -> Self { + RadioButton { + checked, + text: text.into(), + } + } +} + +pub fn radio>(checked: bool, text: S) -> RadioButton { + RadioButton::new(checked, text) +} + +impl Widget for RadioButton { + fn add_to(self, region: &mut Region) -> GuiResponse { + let id = region.make_child_id(&self.text); + let (text, text_size) = region.layout_text(&self.text); + let text_cursor = region.cursor() + + region.options().button_padding + + vec2(region.options().start_icon_width, 0.0); + let (rect, interact) = region.reserve_space( + region.options().button_padding + + vec2(region.options().start_icon_width, 0.0) + + text_size + + region.options().button_padding, + Some(id), + ); + region.add_graphic(GuiCmd::RadioButton { + checked: self.checked, + interact, + rect, + }); + region.add_text(text_cursor, text); + region.response(interact) + } +} + +// ---------------------------------------------------------------------------- + +#[derive(Debug)] +pub struct Slider<'a> { + value: &'a mut f32, + min: f32, + max: f32, + id: Option, + text: Option, + text_on_top: Option, +} + +impl<'a> Slider<'a> { + pub fn new(value: &'a mut f32, min: f32, max: f32) -> Self { + Slider { + value, + min, + max, + id: None, + text: None, + text_on_top: None, + } + } + + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + + pub fn text>(mut self, text: S) -> Self { + self.text = Some(text.into()); + self + } +} + +impl<'a> Widget for Slider<'a> { + fn add_to(self, region: &mut Region) -> GuiResponse { + if let Some(text) = &self.text { + let text_on_top = self.text_on_top.unwrap_or_default(); + let full_text = format!("{}: {:.3}", text, self.value); + let id = Some(self.id.unwrap_or(make_id(text))); + let mut naked = self; + naked.id = id; + naked.text = None; + + if text_on_top { + let (text, text_size) = region.layout_text(&full_text); + region.add_text(region.cursor(), text); + region.reserve_space_inner(text_size); + naked.add_to(region) + } else { + region.columns(2, |columns| { + columns[1].add(label(full_text)); + naked.add_to(&mut columns[0]) + }) + } + } else { + let value = self.value; + let min = self.min; + let max = self.max; + debug_assert!(min <= max); + let id = region.combined_id(self.id); + let (slider_rect, interact) = region.reserve_space( + Vec2 { + x: region.available_space.x, + y: region.data.font.line_spacing(), + }, + id, + ); + + if interact.active { + *value = remap_clamp( + region.input().mouse_pos.x, + slider_rect.min().x, + slider_rect.max().x, + min, + max, + ); + } + + region.add_graphic(GuiCmd::Slider { + interact, + max, + min, + rect: slider_rect, + value: *value, + }); + + region.response(interact) + } + } +} + +// ---------------------------------------------------------------------------- diff --git a/emgui_wasm/src/app.rs b/emgui_wasm/src/app.rs index dca9f176..03ddca88 100644 --- a/emgui_wasm/src/app.rs +++ b/emgui_wasm/src/app.rs @@ -1,4 +1,4 @@ -use emgui::{math::*, types::*, Region}; +use emgui::{math::*, types::*, widgets::*, Region}; pub trait GuiSettings { fn show_gui(&mut self, gui: &mut Region); @@ -29,45 +29,54 @@ impl Default for App { impl GuiSettings for App { fn show_gui(&mut self, gui: &mut Region) { - gui.label(format!( + gui.add(label(format!( "Screen size: {} x {}", gui.input().screen_size.x, gui.input().screen_size.y, - )); + ))); - gui.label("Hover me").tooltip_text( + gui.add(label("Hover me")).tooltip_text( "This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.", ); - gui.checkbox("checkbox", &mut self.checked); + gui.add(Checkbox::new(&mut self.checked, "checkbox")); gui.horizontal(|gui| { - if gui.radio("First", self.selected_alternative == 0).clicked { + if gui + .add(radio(self.selected_alternative == 0, "First")) + .clicked + { self.selected_alternative = 0; } - if gui.radio("Second", self.selected_alternative == 1).clicked { + if gui + .add(radio(self.selected_alternative == 1, "Second")) + .clicked + { self.selected_alternative = 1; } - if gui.radio("Final", self.selected_alternative == 2).clicked { + if gui + .add(radio(self.selected_alternative == 2, "Final")) + .clicked + { self.selected_alternative = 2; } }); if gui - .button("Click me") + .add(Button::new("Click me")) .tooltip_text("This will just increase a counter.") .clicked { self.count += 1; } - gui.label(format!("This is a multiline label.\nThe button have been clicked {} times.\nBelow are more options.", self.count)); + gui.add(label(format!("This is a multiline label.\nThe button have been clicked {} times.\nBelow are more options.", self.count))); gui.foldable("Test box rendering", |gui| { - gui.slider_f32("width", &mut self.size.x, 0.0, 500.0); - gui.slider_f32("height", &mut self.size.y, 0.0, 500.0); - gui.slider_f32("corner_radius", &mut self.corner_radius, 0.0, 50.0); - gui.slider_f32("stroke_width", &mut self.stroke_width, 0.0, 10.0); + gui.add(Slider::new(&mut self.size.x, 0.0, 500.0).text("width")); + gui.add(Slider::new(&mut self.size.y, 0.0, 500.0).text("height")); + gui.add(Slider::new(&mut self.corner_radius, 0.0, 50.0).text("corner_radius")); + gui.add(Slider::new(&mut self.stroke_width, 0.0, 10.0).text("stroke_width")); let pos = gui.cursor(); gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Rect { @@ -87,27 +96,26 @@ impl GuiSettings for App { impl GuiSettings for emgui::LayoutOptions { fn show_gui(&mut self, gui: &mut Region) { - if gui.button("Reset LayoutOptions").clicked { + if gui.add(Button::new("Reset LayoutOptions")).clicked { *self = Default::default(); } - gui.slider_f32("item_spacing.x", &mut self.item_spacing.x, 0.0, 10.0); - gui.slider_f32("item_spacing.y", &mut self.item_spacing.y, 0.0, 10.0); - gui.slider_f32("window_padding.x", &mut self.window_padding.x, 0.0, 10.0); - gui.slider_f32("window_padding.y", &mut self.window_padding.y, 0.0, 10.0); - gui.slider_f32("indent", &mut self.indent, 0.0, 100.0); - gui.slider_f32("button_padding.x", &mut self.button_padding.x, 0.0, 20.0); - gui.slider_f32("button_padding.y", &mut self.button_padding.y, 0.0, 20.0); - gui.slider_f32("start_icon_width", &mut self.start_icon_width, 0.0, 60.0); + gui.add(Slider::new(&mut self.item_spacing.x, 0.0, 10.0).text("item_spacing.x")); + gui.add(Slider::new(&mut self.item_spacing.y, 0.0, 10.0).text("item_spacing.y")); + gui.add(Slider::new(&mut self.window_padding.x, 0.0, 10.0).text("window_padding.x")); + gui.add(Slider::new(&mut self.window_padding.y, 0.0, 10.0).text("window_padding.y")); + gui.add(Slider::new(&mut self.indent, 0.0, 100.0).text("indent")); + gui.add(Slider::new(&mut self.button_padding.x, 0.0, 20.0).text("button_padding.x")); + gui.add(Slider::new(&mut self.button_padding.y, 0.0, 20.0).text("button_padding.y")); + gui.add(Slider::new(&mut self.start_icon_width, 0.0, 60.0).text("start_icon_width")); } } impl GuiSettings for emgui::Style { fn show_gui(&mut self, gui: &mut Region) { - if gui.button("Reset Style").clicked { + if gui.add(Button::new("Reset Style")).clicked { *self = Default::default(); } - gui.checkbox("debug_rects", &mut self.debug_rects); - gui.slider_f32("line_width", &mut self.line_width, 0.0, 10.0); - gui.slider_f32("font_size", &mut self.font_size, 5.0, 32.0); + gui.add(Checkbox::new(&mut self.debug_rects, "debug_rects")); + gui.add(Slider::new(&mut self.line_width, 0.0, 10.0).text("line_width")); } } diff --git a/emgui_wasm/src/lib.rs b/emgui_wasm/src/lib.rs index d5996d9e..376cd684 100644 --- a/emgui_wasm/src/lib.rs +++ b/emgui_wasm/src/lib.rs @@ -7,7 +7,7 @@ extern crate emgui; use std::sync::Arc; -use emgui::{Emgui, Font, RawInput}; +use emgui::{widgets::label, Emgui, Font, RawInput}; use wasm_bindgen::prelude::*; @@ -73,21 +73,25 @@ impl State { style.show_gui(gui); }); - let stats = self.stats; // TODO: avoid - let webgl_info = self.webgl_painter.debug_info(); // TODO: avoid region.foldable("Stats", |gui| { - gui.label(format!("num_vertices: {}", stats.num_vertices)); - gui.label(format!("num_triangles: {}", stats.num_triangles)); + gui.add(label(format!("num_vertices: {}", self.stats.num_vertices))); + gui.add(label(format!( + "num_triangles: {}", + self.stats.num_triangles + ))); - gui.label("WebGl painter info:"); + gui.add(label("WebGl painter info:")); gui.indent(|gui| { - gui.label(webgl_info); + gui.add(label(self.webgl_painter.debug_info())); }); - gui.label("Timings:"); + gui.add(label("Timings:")); gui.indent(|gui| { - gui.label(format!("Everything: {:.1} ms", stats.everything_ms)); - gui.label(format!("WebGL: {:.1} ms", stats.webgl_ms)); + gui.add(label(format!( + "Everything: {:.1} ms", + self.stats.everything_ms + ))); + gui.add(label(format!("WebGL: {:.1} ms", self.stats.webgl_ms))); }); });