From b73fbb33d81cf99ea60866bb0e9bd78ad28a3dcf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 1 May 2020 02:08:01 +0200 Subject: [PATCH] Improve size negotiation code. Better enfocred minimum sizes. You can now have windows that expand to fit their content. --- emigui/src/containers/resize.rs | 16 +- emigui/src/containers/scroll_area.rs | 8 +- emigui/src/containers/window.rs | 28 ++- emigui/src/context.rs | 8 + emigui/src/example_app.rs | 12 +- emigui/src/font.rs | 66 ++++--- emigui/src/region.rs | 268 +++++++++++++++++---------- emigui/src/style.rs | 33 ++-- emigui/src/widgets.rs | 20 +- example_glium/src/main.rs | 28 ++- example_wasm/src/lib.rs | 4 +- 11 files changed, 332 insertions(+), 159 deletions(-) diff --git a/emigui/src/containers/resize.rs b/emigui/src/containers/resize.rs index c4b10f68..6e6bb4a3 100644 --- a/emigui/src/containers/resize.rs +++ b/emigui/src/containers/resize.rs @@ -98,6 +98,20 @@ impl Resize { self } + /// true: prevent from resizing to smaller than contents. + /// false: allow shrinking to smaller than contents. + pub fn auto_expand_width(mut self, auto_expand: bool) -> Self { + self.expand_width_to_fit_content = auto_expand; + self + } + + /// true: prevent from resizing to smaller than contents. + /// false: allow shrinking to smaller than contents. + pub fn auto_expand_height(mut self, auto_expand: bool) -> Self { + self.expand_height_to_fit_content = auto_expand; + self + } + /// Offset the position of the resize handle by this much pub fn handle_offset(mut self, handle_offset: Vec2) -> Self { self.handle_offset = handle_offset; @@ -200,7 +214,7 @@ impl Resize { // state.size = state.size.clamp(self.min_size..=self.max_size); state.size = state.size.round(); // TODO: round to pixels - region.reserve_space_without_padding(state.size); + region.reserve_space(state.size, None); // ------------------------------ diff --git a/emigui/src/containers/scroll_area.rs b/emigui/src/containers/scroll_area.rs index d72c6292..1057ff0c 100644 --- a/emigui/src/containers/scroll_area.rs +++ b/emigui/src/containers/scroll_area.rs @@ -165,8 +165,12 @@ impl ScrollArea { }); } - let size = content_size.min(inner_rect.size()); - outer_region.reserve_space_without_padding(size); + // let size = content_size.min(inner_rect.size()); + let size = vec2( + content_size.x, // ignore inner_rect, i.e. try to expand horizontally if necessary + content_size.y.min(inner_rect.size().y), // respect vertical height. + ); + outer_region.reserve_space(size, None); state.offset.y = state.offset.y.min(content_size.y - inner_rect.height()); state.offset.y = state.offset.y.max(0.0); diff --git a/emigui/src/containers/window.rs b/emigui/src/containers/window.rs index 9ef12126..d3b3bb8a 100644 --- a/emigui/src/containers/window.rs +++ b/emigui/src/containers/window.rs @@ -7,11 +7,11 @@ use super::*; /// A wrapper around other containers for things you often want in a window #[derive(Clone, Debug)] pub struct Window { - title: String, - floating: Floating, - frame: Frame, - resize: Resize, - scroll: ScrollArea, + pub title: String, + pub floating: Floating, + pub frame: Frame, + pub resize: Resize, + pub scroll: ScrollArea, } impl Window { @@ -23,14 +23,30 @@ impl Window { frame: Frame::default(), resize: Resize::default() .handle_offset(Vec2::splat(4.0)) + .auto_shrink_width(true) + .auto_expand_width(true) .auto_shrink_height(false) - .auto_expand(false), + .auto_expand_height(false), scroll: ScrollArea::default() .always_show_scroll(false) .max_height(f32::INFINITY), // As large as we can be } } + /// This is quite a crap idea + /// Usage: `Winmdow::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))` + pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self { + mutate(&mut self); + self + } + + /// This is quite a crap idea + /// Usage: `Winmdow::new(...).resize(|r| r.auto_expand_width(true))` + pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self { + self.resize = mutate(self.resize); + self + } + pub fn default_pos(mut self, default_pos: Pos2) -> Self { self.floating = self.floating.default_pos(default_pos); self diff --git a/emigui/src/context.rs b/emigui/src/context.rs index 9ec2c404..0bf42f64 100644 --- a/emigui/src/context.rs +++ b/emigui/src/context.rs @@ -56,6 +56,14 @@ impl Context { (point * self.input.pixels_per_point).round() / self.input.pixels_per_point } + pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { + pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y)) + } + + pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 { + vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y)) + } + /// Raw input from last frame. Use `input()` instead. pub fn last_raw_input(&self) -> &RawInput { &self.last_raw_input diff --git a/emigui/src/example_app.rs b/emigui/src/example_app.rs index d738af36..37947e9c 100644 --- a/emigui/src/example_app.rs +++ b/emigui/src/example_app.rs @@ -1,7 +1,9 @@ +// #![allow(dead_code, unused_variables)] // should be commented out + use crate::{color::*, containers::*, widgets::*, *}; /// Showcase some region code -pub struct ExampleApp { +pub struct ExampleWindow { checked: bool, count: usize, radio: usize, @@ -19,9 +21,9 @@ pub struct ExampleApp { painting: Painting, } -impl Default for ExampleApp { - fn default() -> ExampleApp { - ExampleApp { +impl Default for ExampleWindow { + fn default() -> ExampleWindow { + ExampleWindow { checked: true, radio: 0, count: 0, @@ -41,7 +43,7 @@ impl Default for ExampleApp { } } -impl ExampleApp { +impl ExampleWindow { pub fn ui(&mut self, region: &mut Region) { region.collapsing("About Emigui", |region| { region.add(label!( diff --git a/emigui/src/font.rs b/emigui/src/font.rs index d393e264..e6188870 100644 --- a/emigui/src/font.rs +++ b/emigui/src/font.rs @@ -29,6 +29,14 @@ impl TextFragment { } } +// pub fn fn_text_width(fragmens: &[TextFragment]) -> f32 { +// if fragmens.is_empty() { +// 0.0 +// } else { +// fragmens.last().unwrap().max_x() - fragmens.first().unwrap().min_x() +// } +// } + // ---------------------------------------------------------------------------- #[derive(Clone, Copy, Debug)] @@ -93,6 +101,27 @@ impl Font { font } + pub fn round_to_pixel(&self, point: f32) -> f32 { + (point * self.pixels_per_point).round() / self.pixels_per_point + } + + /// Height of one line of text. In points + /// TODO: rename height ? + pub fn line_spacing(&self) -> f32 { + self.scale_in_pixels / self.pixels_per_point + } + pub fn height(&self) -> f32 { + self.scale_in_pixels / self.pixels_per_point + } + + pub fn uv_rect(&self, c: char) -> Option { + self.glyph_infos.get(&c).and_then(|gi| gi.uv_rect) + } + + fn glyph_info(&self, c: char) -> Option<&GlyphInfo> { + self.glyph_infos.get(&c) + } + fn add_char(&mut self, c: char) { let glyph = self.font.glyph(c); assert_ne!( @@ -154,26 +183,10 @@ impl Font { ); } - pub fn round_to_pixel(&self, point: f32) -> f32 { - (point * self.pixels_per_point).round() / self.pixels_per_point - } - - /// Height of one line of text. In points - /// TODO: rename height ? - pub fn line_spacing(&self) -> f32 { - self.scale_in_pixels / self.pixels_per_point - } - - pub fn uv_rect(&self, c: char) -> Option { - self.glyph_infos.get(&c).and_then(|gi| gi.uv_rect) - } - - fn glyph_info(&self, c: char) -> Option<&GlyphInfo> { - self.glyph_infos.get(&c) - } - /// Returns the a single line of characters separated into words - pub fn layout_single_line(&self, text: &str) -> Vec { + /// Always returns at least one frament. TODO: Vec1 + /// Returns total size. + pub fn layout_single_line(&self, text: &str) -> (Vec, Vec2) { let scale_in_pixels = Scale::uniform(self.scale_in_pixels); let mut current_fragment = TextFragment { @@ -220,7 +233,16 @@ impl Font { if !current_fragment.text.is_empty() { all_fragments.push(current_fragment) } - all_fragments + + let width = if all_fragments.is_empty() { + 0.0 + } else { + all_fragments.last().unwrap().max_x() + }; + + let size = vec2(width, self.height()); + + (all_fragments, size) } /// A paragraph is text with no line break character in it. @@ -229,8 +251,8 @@ impl Font { text: &str, max_width_in_points: f32, ) -> Vec { - let mut words = self.layout_single_line(text); - if words.is_empty() || words.last().unwrap().max_x() <= max_width_in_points { + let (mut words, size) = self.layout_single_line(text); + if words.is_empty() || size.x <= max_width_in_points { return words; // Early-out } diff --git a/emigui/src/region.rs b/emigui/src/region.rs index 351002f4..71eddaaf 100644 --- a/emigui/src/region.rs +++ b/emigui/src/region.rs @@ -7,7 +7,6 @@ use crate::{color::*, containers::*, font::TextFragment, layout::*, widgets::*, /// TODO: make Region a trait so we can have type-safe HorizontalRegion etc? pub struct Region { // TODO: remove pub(crate) from all members. - // /// How we access input, output and memory pub(crate) ctx: Arc, @@ -48,6 +47,8 @@ pub struct Region { /// Where the next widget will be put. /// Progresses along self.dir. /// Initially set to rect.min + /// If something has already been added, this will point ot style.item_spacing beyond the latest child. + /// The cursor can thus be style.item_spacing pixels outside of the child_bounds. pub(crate) cursor: Pos2, } @@ -55,6 +56,9 @@ pub struct Region { const CLIP_RECT_MARGIN: f32 = 3.0; impl Region { + // ------------------------------------------------------------------------ + // Creation: + pub fn new(ctx: Arc, layer: Layer, id: Id, rect: Rect) -> Self { let style = ctx.style(); Region { @@ -126,6 +130,14 @@ impl Region { self.ctx.round_to_pixel(point) } + pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 { + self.ctx.round_vec_to_pixels(vec) + } + + pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { + self.ctx.round_pos_to_pixels(pos) + } + /// Options for this region, and any child regions we may spawn. pub fn style(&self) -> &Style { &self.style @@ -158,15 +170,6 @@ impl Region { self.clip_rect } - /// This how much more space we can take up without overflowing our parent. - /// Shrinks as cursor increments. - pub fn available_space(&self) -> Vec2 { - // self.desired_rect.max - self.cursor - - // If a child doesn't fit in desired_rect, we have effectively expanded: - self.bottom_right() - self.cursor - } - pub fn bottom_right(&self) -> Pos2 { // If a child doesn't fit in desired_rect, we have effectively expanded: self.desired_rect.max.max(self.child_bounds.max) @@ -180,6 +183,15 @@ impl Region { self.available_space().y } + /// This how much more space we can take up without overflowing our parent. + /// Shrinks as cursor increments. + pub fn available_space(&self) -> Vec2 { + // self.desired_rect.max - self.cursor + + // If a child doesn't fit in desired_rect, we have effectively expanded: + self.bottom_right() - self.cursor + } + /// Size of content pub fn bounding_size(&self) -> Vec2 { self.child_bounds.max - self.desired_rect.min @@ -197,6 +209,60 @@ impl Region { self.align = align; } + // ------------------------------------------------------------------------ + + /// Will warn if the returned id is not guaranteed unique. + /// Use this to generate widget ids for widgets that have persistent state in Memory. + /// If the id_source is not unique within this region + /// then an error will be printed at the current cursor position. + pub fn make_unique_id(&self, id_source: &IdSource) -> Id + where + IdSource: Hash + std::fmt::Debug, + { + let id = self.id.with(id_source); + self.ctx.register_unique_id(id, id_source, self.cursor) + } + + /// Make an Id that is unique to this positon. + /// Can be used for widgets that do NOT persist state in Memory + /// but you still need to interact with (e.g. buttons, sliders). + pub fn make_position_id(&self) -> Id { + self.id.with(&Id::from_pos(self.cursor)) + } + + pub fn make_child_id(&self, id_seed: impl Hash) -> Id { + self.id.with(id_seed) + } + + // ------------------------------------------------------------------------ + // Interaction + + /// Check for clicks on this entire region (desired_rect) + pub fn interact_whole(&self) -> InteractInfo { + self.ctx.interact( + self.layer, + &self.clip_rect, + &self.desired_rect, + Some(self.id), + ) + } + + pub fn interact_rect(&self, rect: &Rect, id: Id) -> InteractInfo { + self.ctx + .interact(self.layer, &self.clip_rect, rect, Some(id)) + } + + pub fn response(&mut self, interact: InteractInfo) -> GuiResponse { + // TODO: unify GuiResponse and InteractInfo. They are the same thing! + GuiResponse { + hovered: interact.hovered, + clicked: interact.clicked, + active: interact.active, + rect: interact.rect, + ctx: self.ctx.clone(), + } + } + // ------------------------------------------------------------------------ // Sub-regions: @@ -214,7 +280,7 @@ impl Region { ..self.child_region(child_rect) }; add_contents(&mut child_region); - self.reserve_space_without_padding(child_region.bounding_size()); + self.reserve_space(child_region.bounding_size(), None); } /// Create a child region which is indented to the right @@ -243,7 +309,7 @@ impl Region { width: self.style.line_width, }); - self.reserve_space_without_padding(indent + size); + self.reserve_space(indent + size, None); } pub fn left_column(&mut self, width: f32) -> Region { @@ -271,6 +337,17 @@ impl Region { )) } + /// Start a region with horizontal layout + // TODO: remove first argument + pub fn horizontal(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) { + self.inner_layout(Direction::Horizontal, align, add_contents) + } + + /// Start a region with vertical layout + pub fn vertical(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) { + self.inner_layout(Direction::Vertical, align, add_contents) + } + pub fn inner_layout( &mut self, dir: Direction, @@ -288,16 +365,6 @@ impl Region { self.reserve_space(size, None); } - /// Start a region with horizontal layout - pub fn horizontal(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) { - self.inner_layout(Direction::Horizontal, align, add_contents) - } - - /// Start a region with vertical layout - pub fn vertical(&mut self, align: Align, add_contents: impl FnOnce(&mut Region)) { - self.inner_layout(Direction::Vertical, align, add_contents) - } - /// Temporarily split split a vertical layout into several columns. /// /// region.columns(2, |columns| { @@ -309,13 +376,13 @@ impl Region { F: FnOnce(&mut [Region]) -> R, { // TODO: ensure there is space - let padding = self.style.item_spacing.x; - let total_padding = padding * (num_columns as f32 - 1.0); - let column_width = (self.available_width() - total_padding) / (num_columns as f32); + let spacing = self.style.item_spacing.x; + let total_spacing = spacing * (num_columns as f32 - 1.0); + let column_width = (self.available_width() - total_spacing) / (num_columns as f32); let mut columns: Vec = (0..num_columns) .map(|col_idx| { - let pos = self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0); + let pos = self.cursor + vec2((col_idx as f32) * (column_width + spacing), 0.0); let child_rect = Rect::from_min_max(pos, pos2(pos.x + column_width, self.bottom_right().y)); @@ -329,33 +396,24 @@ impl Region { let result = add_contents(&mut columns[..]); + let mut sum_width = total_spacing; + for column in &columns { + sum_width += column.child_bounds.width(); + } + let mut max_height = 0.0; for region in columns { let size = region.bounding_size(); max_height = size.y.max(max_height); } - self.reserve_space_without_padding(vec2(self.available_width(), max_height)); + let size = vec2(self.available_width().max(sum_width), max_height); + self.reserve_space(size, None); result } // ------------------------------------------------------------------------ - /// Check for clicks on this entire region (desired_rect) - pub fn interact_whole(&self) -> InteractInfo { - self.ctx.interact( - self.layer, - &self.clip_rect, - &self.desired_rect, - Some(self.id), - ) - } - - pub fn interact_rect(&self, rect: &Rect, id: Id) -> InteractInfo { - self.ctx - .interact(self.layer, &self.clip_rect, rect, Some(id)) - } - pub fn contains_mouse(&self, rect: &Rect) -> bool { self.ctx.contains_mouse(self.layer, &self.clip_rect, rect) } @@ -393,65 +451,94 @@ impl Region { } // ------------------------------------------------------------------------ + // Stuff that moves the cursor, i.e. allocates space in this region! + + /// Reserve this much space and move the cursor. + /// Returns where to put the widget. + /// # How sizes are negotiated + /// Each widget should have a *minimum desired size* and a *desired size*. + /// When asking for space, ask AT LEAST for you minimum, and don't ask for more than you need. + /// If you want to fill the space, ask about available_space() and use that. + /// NOTE: we always get the size we ask for (at the moment). + pub fn reserve_space(&mut self, child_size: Vec2, interaction_id: Option) -> InteractInfo { + let child_size = self.round_vec_to_pixels(child_size); + self.cursor = self.round_pos_to_pixels(self.cursor); + + // For debug rendering + let too_wide = child_size.x > self.available_width(); + let too_high = child_size.x > self.available_height(); + + let child_pos = self.reserve_space_impl(child_size); + let rect = Rect::from_min_size(child_pos, child_size); + + if self.style().debug_regions { + self.add_paint_cmd(PaintCmd::Rect { + rect, + corner_radius: 0.0, + outline: Some(Outline::new(1.0, LIGHT_BLUE)), + fill_color: None, + }); + + let color = color::srgba(255, 0, 0, 128); + let width = 2.5; + + if too_wide { + self.add_paint_cmd(PaintCmd::line_segment( + (rect.left_top(), rect.left_bottom()), + color, + width, + )); + self.add_paint_cmd(PaintCmd::line_segment( + (rect.right_top(), rect.right_bottom()), + color, + width, + )); + } + + if too_high { + self.add_paint_cmd(PaintCmd::line_segment( + (rect.left_top(), rect.right_top()), + color, + width, + )); + self.add_paint_cmd(PaintCmd::line_segment( + (rect.left_bottom(), rect.right_bottom()), + color, + width, + )); + } + } - pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option) -> InteractInfo { - let padded_size = match self.dir { - Direction::Horizontal => vec2(size.x + self.style.item_spacing.x, size.y), - Direction::Vertical => vec2(size.x, size.y + self.style.item_spacing.y), - }; - let pos = self.reserve_space_without_padding(padded_size); - let rect = Rect::from_min_size(pos, size); self.ctx .interact(self.layer, &self.clip_rect, &rect, interaction_id) } /// Reserve this much space and move the cursor. /// Returns where to put the widget. - pub fn reserve_space_without_padding(&mut self, size: Vec2) -> Pos2 { - let mut pos = self.cursor; + fn reserve_space_impl(&mut self, child_size: Vec2) -> Pos2 { + let mut child_pos = self.cursor; if self.dir == Direction::Horizontal { - pos.y += match self.align { + child_pos.y += match self.align { Align::Min => 0.0, - Align::Center => 0.5 * (self.available_height() - size.y), - Align::Max => self.available_height() - size.y, + Align::Center => 0.5 * (self.available_height() - child_size.y), + Align::Max => self.available_height() - child_size.y, }; - self.child_bounds.extend_with(self.cursor + size); - self.cursor.x += size.x; + self.child_bounds.extend_with(self.cursor + child_size); + self.cursor.x += child_size.x; + self.cursor.x += self.style.item_spacing.x; // Where to put next thing, if there is a next thing } else { - pos.x += match self.align { + child_pos.x += match self.align { Align::Min => 0.0, - Align::Center => 0.5 * (self.available_width() - size.x), - Align::Max => self.available_width() - size.x, + Align::Center => 0.5 * (self.available_width() - child_size.x), + Align::Max => self.available_width() - child_size.x, }; - self.child_bounds.extend_with(self.cursor + size); - self.cursor.y += size.y; + self.child_bounds.extend_with(self.cursor + child_size); + self.cursor.y += child_size.y; + self.cursor.y += self.style.item_spacing.y; // Where to put next thing, if there is a next thing } - pos - } - /// Will warn if the returned id is not guaranteed unique. - /// Use this to generate widget ids for widgets that have persistent state in Memory. - /// If the id_source is not unique within this region - /// then an error will be printed at the current cursor position. - pub fn make_unique_id(&self, id_source: &IdSource) -> Id - where - IdSource: Hash + std::fmt::Debug, - { - let id = self.id.with(id_source); - self.ctx.register_unique_id(id, id_source, self.cursor) + child_pos } - - /// Make an Id that is unique to this positon. - /// Can be used for widgets that do NOT persist state in Memory - /// but you still need to interact with (e.g. buttons, sliders). - pub fn make_position_id(&self) -> Id { - self.id.with(&Id::from_pos(self.cursor)) - } - - pub fn make_child_id(&self, id_seed: impl Hash) -> Id { - self.id.with(id_seed) - } - // ------------------------------------------------ /// Paint some debug text at current cursor @@ -497,15 +584,4 @@ impl Region { }); } } - - pub fn response(&mut self, interact: InteractInfo) -> GuiResponse { - // TODO: unify GuiResponse and InteractInfo. They are the same thing! - GuiResponse { - hovered: interact.hovered, - clicked: interact.clicked, - active: interact.active, - rect: interact.rect, - ctx: self.ctx.clone(), - } - } } diff --git a/emigui/src/style.rs b/emigui/src/style.rs index 7e5ec704..484d0157 100644 --- a/emigui/src/style.rs +++ b/emigui/src/style.rs @@ -36,6 +36,10 @@ pub struct Style { pub animation_time: f32, pub window: Window, + + // ----------------------------------------------- + // Debug rendering: + pub debug_regions: bool, } #[derive(Clone, Copy, Debug, Serialize)] @@ -57,6 +61,7 @@ impl Default for Style { text_cursor_width: 2.0, animation_time: 1.0 / 20.0, window: Window::default(), + debug_regions: false, } } } @@ -154,22 +159,28 @@ impl Style { impl Style { #[rustfmt::skip] pub fn ui(&mut self, region: &mut crate::Region) { - use crate::widgets::{Button, Slider}; + use crate::{widgets::*, *}; if region.add(Button::new("Reset style")).clicked { *self = Default::default(); } - region.add(Slider::f32(&mut self.item_spacing.x, 0.0..=10.0).text("item_spacing.x").precision(0)); - region.add(Slider::f32(&mut self.item_spacing.y, 0.0..=10.0).text("item_spacing.y").precision(0)); - region.add(Slider::f32(&mut self.window_padding.x, 0.0..=10.0).text("window_padding.x").precision(0)); - region.add(Slider::f32(&mut self.window_padding.y, 0.0..=10.0).text("window_padding.y").precision(0)); - region.add(Slider::f32(&mut self.indent, 0.0..=100.0).text("indent").precision(0)); - region.add(Slider::f32(&mut self.button_padding.x, 0.0..=20.0).text("button_padding.x").precision(0)); - region.add(Slider::f32(&mut self.button_padding.y, 0.0..=20.0).text("button_padding.y").precision(0)); + region.add(Slider::f32(&mut self.item_spacing.x, 0.0..=10.0).text("item_spacing.x").precision(0)); + region.add(Slider::f32(&mut self.item_spacing.y, 0.0..=10.0).text("item_spacing.y").precision(0)); + region.add(Slider::f32(&mut self.window_padding.x, 0.0..=10.0).text("window_padding.x").precision(0)); + region.add(Slider::f32(&mut self.window_padding.y, 0.0..=10.0).text("window_padding.y").precision(0)); + region.add(Slider::f32(&mut self.indent, 0.0..=100.0).text("indent").precision(0)); + region.add(Slider::f32(&mut self.button_padding.x, 0.0..=20.0).text("button_padding.x").precision(0)); + region.add(Slider::f32(&mut self.button_padding.y, 0.0..=20.0).text("button_padding.y").precision(0)); region.add(Slider::f32(&mut self.clickable_diameter, 0.0..=60.0).text("clickable_diameter").precision(0)); - region.add(Slider::f32(&mut self.start_icon_width, 0.0..=60.0).text("start_icon_width").precision(0)); - region.add(Slider::f32(&mut self.line_width, 0.0..=10.0).text("line_width").precision(0)); - region.add(Slider::f32(&mut self.animation_time, 0.0..=1.0).text("animation_time").precision(2)); + region.add(Slider::f32(&mut self.start_icon_width, 0.0..=60.0).text("start_icon_width").precision(0)); + region.add(Slider::f32(&mut self.line_width, 0.0..=10.0).text("line_width").precision(0)); + region.add(Slider::f32(&mut self.animation_time, 0.0..=1.0).text("animation_time").precision(2)); + + + // TODO: region.section("Heading", |ui| ui.add(contents)) + region.add(Separator::new()); + region.add(label!("Debug:").text_style(TextStyle::Heading)); + region.add(Checkbox::new(&mut self.debug_regions, "debug_regions")); } } diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index 0f723795..ff1e4dd5 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -21,6 +21,7 @@ pub trait Widget { pub struct Label { text: String, + multiline: bool, text_style: TextStyle, // TODO: Option, where None means "use the default for the region" text_color: Option, } @@ -29,11 +30,17 @@ impl Label { pub fn new(text: impl Into) -> Self { Label { text: text.into(), + multiline: true, text_style: TextStyle::Body, text_color: None, } } + pub fn multiline(mut self, multiline: bool) -> Self { + self.multiline = multiline; + self + } + pub fn text_style(mut self, text_style: TextStyle) -> Self { self.text_style = text_style; self @@ -55,7 +62,11 @@ macro_rules! label { impl Widget for Label { fn ui(self, region: &mut Region) -> GuiResponse { let font = ®ion.fonts()[self.text_style]; - let (text, text_size) = font.layout_multiline(&self.text, region.available_width()); + let (text, text_size) = if self.multiline { + font.layout_multiline(&self.text, region.available_width()) + } else { + font.layout_single_line(&self.text) + }; let interact = region.reserve_space(text_size, None); region.add_text(interact.rect.min, self.text_style, text, self.text_color); region.response(interact) @@ -423,8 +434,9 @@ impl<'a> Widget for Slider<'a> { let slider_sans_text = Slider { text: None, ..self }; if text_on_top { - let (text, text_size) = font.layout_multiline(&full_text, region.available_width()); - let pos = region.reserve_space_without_padding(text_size); + // let (text, text_size) = font.layout_multiline(&full_text, region.available_width()); + let (text, text_size) = font.layout_single_line(&full_text); + let pos = region.reserve_space(text_size, None).rect.min; region.add_text(pos, text_style, text, text_color); slider_sans_text.ui(region) } else { @@ -437,7 +449,7 @@ impl<'a> Widget for Slider<'a> { .desired_rect .set_height(slider_response.rect.height()); columns[1].horizontal(Align::Center, |region| { - region.add(Label::new(full_text)); + region.add(Label::new(full_text).multiline(false)); }); slider_response diff --git a/example_glium/src/main.rs b/example_glium/src/main.rs index 79b378fb..bc2493f8 100644 --- a/example_glium/src/main.rs +++ b/example_glium/src/main.rs @@ -1,9 +1,12 @@ #![deny(warnings)] #[allow(clippy::single_match)] -use std::time::{Duration, Instant}; +use std::{ + collections::VecDeque, + time::{Duration, Instant}, +}; use { - emigui::{containers::*, example_app::ExampleApp, widgets::*, *}, + emigui::{containers::*, example_app::ExampleWindow, widgets::*, *}, glium::glutin, }; @@ -39,8 +42,8 @@ fn main() { let start_time = Instant::now(); let mut running = true; let mut frame_start = Instant::now(); - let mut frame_times = std::collections::VecDeque::new(); - let mut example_app = ExampleApp::default(); + let mut frame_times = VecDeque::new(); + let mut example_app = ExampleWindow::default(); let mut clipboard = emigui_glium::init_clipboard(); while running { @@ -74,15 +77,10 @@ fn main() { running = false; } - let mean_frame_time = if frame_times.is_empty() { - 0.0 - } else { - frame_times.iter().sum::() / (frame_times.len() as f64) - }; region.add( label!( "Frame time: {:.1} ms (excludes painting)", - 1e3 * mean_frame_time + 1e3 * mean_frame_time(&frame_times) ) .text_style(TextStyle::Monospace), ); @@ -92,6 +90,8 @@ fn main() { Window::new("Examples") .default_pos(pos2(50.0, 100.0)) .default_size(vec2(300.0, 600.0)) + // .mutate(|w| w.resize = w.resize.auto_expand_width(true)) + // .resize(|r| r.auto_expand_width(true)) .show(region.ctx(), |region| { example_app.ui(region); }); @@ -114,3 +114,11 @@ fn main() { emigui_glium::handle_output(output, &display, clipboard.as_mut()); } } + +pub fn mean_frame_time(frame_times: &VecDeque) -> f64 { + if frame_times.is_empty() { + 0.0 + } else { + frame_times.iter().sum::() / (frame_times.len() as f64) + } +} diff --git a/example_wasm/src/lib.rs b/example_wasm/src/lib.rs index 31e9f3e0..0e59c806 100644 --- a/example_wasm/src/lib.rs +++ b/example_wasm/src/lib.rs @@ -10,7 +10,7 @@ use { emigui::{ color::srgba, containers::*, - example_app::ExampleApp, + example_app::ExampleWindow, label, widgets::{Label, Separator}, Align, Emigui, RawInput, TextStyle, *, @@ -22,7 +22,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct State { - example_app: ExampleApp, + example_app: ExampleWindow, emigui: Emigui, webgl_painter: emigui_wasm::webgl::Painter,