diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index 38c3e94d..91f1b8c8 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -49,9 +49,9 @@ impl Emigui { id: Id::background(), dir: layout::Direction::Vertical, align: layout::Align::Center, + rect: Rect::from_min_size(Default::default(), self.ctx.input.screen_size), cursor: Default::default(), bounding_size: Default::default(), - available_space: self.ctx.input.screen_size, } } diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index 579849ca..7dda7dc7 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -94,6 +94,7 @@ pub fn align_rect(rect: Rect, align: (Align, Align)) -> Rect { // ---------------------------------------------------------------------------- +// TODO: move show_popup, and expand its features (default size, autosize, etc) /// Show a pop-over window pub fn show_popup(ctx: &Arc, window_pos: Pos2, add_contents: F) where @@ -105,17 +106,9 @@ where let style = ctx.style(); let window_padding = style.window_padding; - let mut contents_region = Region { - ctx: ctx.clone(), - layer, - style, - id: Id::popup(), - dir: Direction::Vertical, - align: Align::Min, - cursor: window_pos + window_padding, - bounding_size: vec2(0.0, 0.0), - available_space: vec2(ctx.input.screen_size.x.min(350.0), std::f32::INFINITY), // TODO: popup/tooltip width - }; + let size = vec2(ctx.input.screen_size.x.min(350.0), std::f32::INFINITY); // TODO: popup/tooltip width + let inner_rect = Rect::from_min_size(window_pos + window_padding, size); + let mut contents_region = Region::new(ctx.clone(), layer, Id::popup(), inner_rect); add_contents(&mut contents_region); diff --git a/emigui/src/math.rs b/emigui/src/math.rs index 13fbe168..873b36f5 100644 --- a/emigui/src/math.rs +++ b/emigui/src/math.rs @@ -272,6 +272,16 @@ impl Rect { Rect::from_min_size(self.min() + amnt, self.size()) } + // keep min + pub fn set_width(&mut self, w: f32) { + self.max.x = self.min.x + w; + } + + // keep min + pub fn set_height(&mut self, h: f32) { + self.max.y = self.min.y + h; + } + pub fn contains(&self, p: Pos2) -> bool { self.min.x <= p.x && p.x <= self.min.x + self.size().x diff --git a/emigui/src/region.rs b/emigui/src/region.rs index 74d029e0..08380f0e 100644 --- a/emigui/src/region.rs +++ b/emigui/src/region.rs @@ -21,20 +21,42 @@ pub struct Region { pub(crate) align: Align, + // The `rect` represents where in space the region is + // and its max size (original available_space). + // Note that the size may be infinite in one or both dimensions. + // The widgets will TRY to fit within the rect, + // but may overflow (which you will see in bounding_size). + // It will use the rect (which never changes) as a + // clip rect when painting the contents of the region. + pub(crate) rect: Rect, + /// Where the next widget will be put. - /// Progresses along self.dir + /// Progresses along self.dir. + /// Initially set to rect.min() pub(crate) cursor: Pos2, /// Bounding box of children. /// We keep track of our max-size along the orthogonal to self.dir + /// Initially set to zero. pub(crate) bounding_size: Vec2, - - /// This how much more space we can take up without overflowing our parent. - /// Shrinks as cursor increments. - pub(crate) available_space: Vec2, } impl Region { + pub fn new(ctx: Arc, layer: Layer, id: Id, rect: Rect) -> Self { + let style = ctx.style(); + Region { + ctx, + layer, + style, + id, + dir: Direction::Vertical, + align: Align::Min, + rect, + cursor: rect.min(), + bounding_size: Vec2::default(), + } + } + /// It is up to the caller to make sure there is room for this. /// Can be used for free painting. /// NOTE: all coordinates are screen coordinates! @@ -63,16 +85,18 @@ impl Region { &*self.ctx.fonts } - pub fn width(&self) -> f32 { - self.available_space.x + pub fn available_width(&self) -> f32 { + self.rect.max().x - self.cursor.x } - pub fn height(&self) -> f32 { - self.available_space.y + pub fn available_height(&self) -> f32 { + self.rect.max().y - self.cursor.y } - pub fn size(&self) -> Vec2 { - self.available_space + /// This how much more space we can take up without overflowing our parent. + /// Shrinks as cursor increments. + pub fn available_space(&self) -> Vec2 { + self.rect.max() - self.cursor } pub fn direction(&self) -> Direction { @@ -103,11 +127,11 @@ impl Region { let id = self.make_unique_id(&text); let text_style = TextStyle::Button; let font = &self.fonts()[text_style]; - let (text, text_size) = font.layout_multiline(&text, self.width()); + let (text, text_size) = font.layout_multiline(&text, self.available_width()); let text_cursor = self.cursor + self.style.button_padding; let interact = self.reserve_space( vec2( - self.available_space.x, + self.available_width(), text_size.y + 2.0 * self.style.button_padding.y, ), Some(id), @@ -180,6 +204,7 @@ impl Region { F: FnOnce(&mut Region), { let indent = vec2(self.style.indent, 0.0); + let region_pos = self.cursor + indent; let mut child_region = Region { ctx: self.ctx.clone(), layer: self.layer, @@ -187,9 +212,9 @@ impl Region { id: self.id, dir: self.dir, align: Align::Min, - cursor: self.cursor + indent, + rect: Rect::from_min_max(region_pos, self.rect.max()), + cursor: region_pos, bounding_size: vec2(0.0, 0.0), - available_space: self.available_space - indent, }; add_contents(&mut child_region); let size = child_region.bounding_size; @@ -198,16 +223,17 @@ impl Region { /// Return a sub-region relative to the parent pub fn relative_region(&mut self, rect: Rect) -> Region { + let region_pos = self.cursor + rect.min().to_vec2(); Region { ctx: self.ctx.clone(), layer: self.layer, style: self.style, id: self.id, dir: self.dir, - cursor: self.cursor + rect.min().to_vec2(), align: self.align, + rect: Rect::from_min_max(region_pos, self.rect.max()), + cursor: region_pos, bounding_size: vec2(0.0, 0.0), - available_space: rect.size(), } } @@ -215,12 +241,12 @@ impl Region { pub fn column(&mut self, column_position: Align, width: f32) -> Region { let x = match column_position { Align::Min => 0.0, - Align::Center => self.available_space.x / 2.0 - width / 2.0, - Align::Max => self.available_space.x - width, + Align::Center => self.available_width() / 2.0 - width / 2.0, + Align::Max => self.available_width() - width, }; self.relative_region(Rect::from_min_size( pos2(x, 0.0), - vec2(width, self.available_space.y), + vec2(width, self.available_height()), )) } @@ -247,9 +273,9 @@ impl Region { id: self.id, dir, align, + rect: Rect::from_min_max(self.cursor, self.rect.max()), cursor: self.cursor, bounding_size: vec2(0.0, 0.0), - available_space: self.available_space, }; add_contents(&mut child_region); let size = child_region.bounding_size; @@ -285,19 +311,22 @@ impl Region { // 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_space.x - total_padding) / (num_columns as f32); + let column_width = (self.available_width() - total_padding) / (num_columns as f32); let mut columns: Vec = (0..num_columns) - .map(|col_idx| Region { - ctx: self.ctx.clone(), - layer: self.layer, - style: self.style, - id: self.make_child_region_id(&("column", col_idx)), - dir: Direction::Vertical, - align: self.align, - cursor: self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0), - bounding_size: vec2(0.0, 0.0), - available_space: vec2(column_width, self.available_space.y), + .map(|col_idx| { + let pos = self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0); + Region { + ctx: self.ctx.clone(), + layer: self.layer, + style: self.style, + id: self.make_child_region_id(&("column", col_idx)), + dir: Direction::Vertical, + align: self.align, + rect: Rect::from_min_max(pos, pos2(pos.x + column_width, self.rect.max().y)), + cursor: pos, + bounding_size: vec2(0.0, 0.0), + } }) .collect(); @@ -309,7 +338,7 @@ impl Region { max_height = size.y.max(max_height); } - self.reserve_space_without_padding(vec2(self.available_space.x, max_height)); + self.reserve_space_without_padding(vec2(self.available_width(), max_height)); result } @@ -334,21 +363,19 @@ impl Region { if self.dir == Direction::Horizontal { pos.y += match self.align { Align::Min => 0.0, - Align::Center => 0.5 * (self.available_space.y - size.y), - Align::Max => self.available_space.y - size.y, + Align::Center => 0.5 * (self.available_height() - size.y), + Align::Max => self.available_height() - size.y, }; self.cursor.x += size.x; - self.available_space.x -= size.x; self.bounding_size.x += size.x; self.bounding_size.y = self.bounding_size.y.max(size.y); } else { pos.x += match self.align { Align::Min => 0.0, - Align::Center => 0.5 * (self.available_space.x - size.x), - Align::Max => self.available_space.x - size.x, + Align::Center => 0.5 * (self.available_width() - size.x), + Align::Max => self.available_width() - size.x, }; self.cursor.y += size.y; - self.available_space.y -= size.x; self.bounding_size.y += size.y; self.bounding_size.x = self.bounding_size.x.max(size.x); } diff --git a/emigui/src/texture_atlas.rs b/emigui/src/texture_atlas.rs index b7a0b1fe..83c79984 100644 --- a/emigui/src/texture_atlas.rs +++ b/emigui/src/texture_atlas.rs @@ -103,8 +103,8 @@ impl Texture { self.height )); let mut size = vec2(self.width as f32, self.height as f32); - if size.x > region.width() { - size *= region.width() / size.x; + if size.x > region.available_width() { + size *= region.available_width() / size.x; } let interact = region.reserve_space(size, None); let rect = interact.rect; diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index 549c269b..82fa30b6 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -50,7 +50,7 @@ macro_rules! label { impl Widget for Label { fn add_to(self, region: &mut Region) -> GuiResponse { let font = ®ion.fonts()[self.text_style]; - let (text, text_size) = font.layout_multiline(&self.text, region.width()); + let (text, text_size) = font.layout_multiline(&self.text, region.available_width()); let interact = region.reserve_space(text_size, None); region.add_text(interact.rect.min(), self.text_style, text, self.text_color); region.response(interact) @@ -83,7 +83,7 @@ impl Widget for Button { let id = region.make_position_id(); let text_style = TextStyle::Button; let font = ®ion.fonts()[text_style]; - let (text, text_size) = font.layout_multiline(&self.text, region.width()); + let (text, text_size) = font.layout_multiline(&self.text, region.available_width()); let padding = region.style().button_padding; let mut size = text_size + 2.0 * padding; size.y = size.y.max(region.style().clickable_diameter); @@ -129,7 +129,7 @@ impl<'a> Widget for Checkbox<'a> { let id = region.make_position_id(); let text_style = TextStyle::Button; let font = ®ion.fonts()[text_style]; - let (text, text_size) = font.layout_multiline(&self.text, region.width()); + let (text, text_size) = font.layout_multiline(&self.text, region.available_width()); let interact = region.reserve_space( region.style().button_padding + vec2(region.style().start_icon_width, 0.0) @@ -203,7 +203,7 @@ impl Widget for RadioButton { let id = region.make_position_id(); let text_style = TextStyle::Button; let font = ®ion.fonts()[text_style]; - let (text, text_size) = font.layout_multiline(&self.text, region.width()); + let (text, text_size) = font.layout_multiline(&self.text, region.available_width()); let interact = region.reserve_space( region.style().button_padding + vec2(region.style().start_icon_width, 0.0) @@ -353,7 +353,7 @@ impl<'a> Widget for Slider<'a> { let naked = Slider { text: None, ..self }; if text_on_top { - let (text, text_size) = font.layout_multiline(&full_text, region.width()); + let (text, text_size) = font.layout_multiline(&full_text, region.available_width()); let pos = region.reserve_space_without_padding(text_size); region.add_text(pos, text_style, text, text_color); naked.add_to(region) @@ -361,7 +361,8 @@ impl<'a> Widget for Slider<'a> { region.columns(2, |columns| { let response = naked.add_to(&mut columns[0]); - columns[1].available_space.y = response.rect.size().y; + // columns[1].available_space.y = response.rect.size().y; + columns[1].rect.set_height(response.rect.size().y); // TODO: explain this line columns[1].horizontal(Align::Center, |region| { region.add(Label::new(full_text)); }); @@ -378,7 +379,7 @@ impl<'a> Widget for Slider<'a> { let id = region.make_position_id(); let interact = region.reserve_space( Vec2 { - x: region.available_space.x, + x: region.available_width(), y: height, }, Some(id), @@ -472,7 +473,7 @@ impl Separator { impl Widget for Separator { fn add_to(self, region: &mut Region) -> GuiResponse { - let available_space = region.available_space; + let available_space = region.available_space(); let (points, interact) = match region.direction() { Direction::Horizontal => { let interact = region.reserve_space(vec2(self.min_length, available_space.y), None); diff --git a/emigui/src/window.rs b/emigui/src/window.rs index d86160d3..4a37384f 100644 --- a/emigui/src/window.rs +++ b/emigui/src/window.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{layout::Direction, mesher::Path, widgets::*, *}; +use crate::{mesher::Path, widgets::*, *}; #[derive(Clone, Copy, Debug)] pub struct WindowState { @@ -54,19 +54,33 @@ impl Window { self } + pub fn shrink_to_fit_content(mut self, shrink_to_fit_content: bool) -> Self { + self.shrink_to_fit_content = shrink_to_fit_content; + self + } + + pub fn expand_to_fit_content(mut self, expand_to_fit_content: bool) -> Self { + self.expand_to_fit_content = expand_to_fit_content; + self + } + pub fn show(self, ctx: &Arc, add_contents: F) where F: FnOnce(&mut Region), { let default_pos = self.default_pos.unwrap_or(pos2(100.0, 100.0)); // TODO let default_size = vec2(200.0, 50.0); // TODO + let default_rect = Rect::from_min_size(default_pos, default_size); let id = ctx.make_unique_id(&self.title, default_pos); - let mut state = ctx - .memory - .lock() - .get_or_create_window(id, Rect::from_min_size(default_pos, default_size)); + let (mut state, is_new_window) = match ctx.memory.lock().get_window(id) { + Some(state) => (state, false), + None => { + let state = WindowState { rect: default_rect }; + (state, true) + } + }; let layer = Layer::Window(id); let where_to_put_background = ctx.graphics.lock().layer(layer).len(); @@ -74,17 +88,11 @@ impl Window { let style = ctx.style(); let window_padding = style.window_padding; - let mut contents_region = Region { - ctx: ctx.clone(), - layer, - style, - id, - dir: Direction::Vertical, - align: Align::Min, - cursor: state.rect.min() + window_padding, - bounding_size: vec2(0.0, 0.0), - available_space: state.rect.size() - 2.0 * window_padding, - }; + let inner_rect = Rect::from_min_size( + state.rect.min() + window_padding, + state.rect.size() - 2.0 * window_padding, + ); + let mut contents_region = Region::new(ctx.clone(), layer, id, inner_rect); // Show top bar: contents_region.add(Label::new(self.title).text_style(TextStyle::Heading)); @@ -104,7 +112,7 @@ impl Window { new_outer_size = new_outer_size.min(desired_outer_size); } - if self.expand_to_fit_content { + if self.expand_to_fit_content || is_new_window { new_outer_size = new_outer_size.max(desired_outer_size); } diff --git a/example_glium/src/main.rs b/example_glium/src/main.rs index 6a9e8e87..771fd184 100644 --- a/example_glium/src/main.rs +++ b/example_glium/src/main.rs @@ -82,7 +82,7 @@ fn main() { emigui.new_frame(raw_input); let mut region = emigui.background_region(); - let mut region = region.left_column(region.width().min(480.0)); + let mut region = region.left_column(region.available_width().min(480.0)); region.set_align(Align::Min); region.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading)); if region.add(Button::new("Quit")).clicked { @@ -94,12 +94,18 @@ fn main() { // TODO: Make it even simpler to show a window Window::new("Test window").show(region.ctx(), |region| { region.add(label!("Grab the window and move it around!")); + + region.add(label!( + "This window can be reisized, but not smaller than the contents." + )); }); - Window::new("Another test window") + Window::new("Resize me!") .default_pos(pos2(400.0, 100.0)) + .expand_to_fit_content(false) .show(region.ctx(), |region| { - region.add(label!("This might be on top of the other window?")); - region.add(label!("Second line of text")); + region.add(label!( + "This window may shrink so small that its contents no longer fit." + )); }); let mesh = emigui.paint(); diff --git a/example_wasm/src/lib.rs b/example_wasm/src/lib.rs index 3516dba8..dcff6f55 100644 --- a/example_wasm/src/lib.rs +++ b/example_wasm/src/lib.rs @@ -43,7 +43,7 @@ impl State { self.emigui.new_frame(raw_input); let mut region = self.emigui.background_region(); - let mut region = region.centered_column(region.width().min(480.0)); + let mut region = region.centered_column(region.available_width().min(480.0)); region.add(label!("Emigui!").text_style(TextStyle::Heading)); region.add(label!("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.")); region.add(label!(