From f97dcdc9b56ad5c6e13856cbc5fd6c7f30c1dfab Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 1 May 2020 09:35:44 +0200 Subject: [PATCH] Reorder some functions in Region --- emigui/src/example_app.rs | 10 +- emigui/src/region.rs | 444 +++++++++++++++++++------------------- emigui/src/widgets.rs | 3 +- example_wasm/src/lib.rs | 2 +- 4 files changed, 233 insertions(+), 226 deletions(-) diff --git a/emigui/src/example_app.rs b/emigui/src/example_app.rs index 37947e9c..f980f5fa 100644 --- a/emigui/src/example_app.rs +++ b/emigui/src/example_app.rs @@ -50,7 +50,7 @@ impl ExampleWindow { "Emigui is an experimental immediate mode GUI written in Rust." )); - region.horizontal(Align::Min, |region| { + region.horizontal(|region| { region.add_label("Project home page:"); region.add_hyperlink("https://github.com/emilk/emigui/"); }); @@ -59,7 +59,7 @@ impl ExampleWindow { CollapsingHeader::new("Widgets") .default_open() .show(region, |region| { - region.horizontal(Align::Min, |region| { + region.horizontal(|region| { region.add(label!("Text can have").text_color(srgba(110, 255, 110, 255))); region.add(label!("color").text_color(srgba(128, 140, 255, 255))); region.add(label!("and tooltips (hover me)")).tooltip_text( @@ -69,7 +69,7 @@ impl ExampleWindow { region.add(Checkbox::new(&mut self.checked, "checkbox")); - region.horizontal(Align::Min, |region| { + region.horizontal(|region| { if region.add(radio(self.radio == 0, "First")).clicked { self.radio = 0; } @@ -81,7 +81,7 @@ impl ExampleWindow { } }); - region.horizontal(Align::Min, |region| { + region.horizontal(|region| { if region .add(Button::new("Click me")) .tooltip_text("This will just increase a counter.") @@ -101,7 +101,7 @@ impl ExampleWindow { } for (i, text) in self.text_inputs.iter_mut().enumerate() { - region.horizontal(Align::Min, |region|{ + region.horizontal(|region|{ region.add(label!("Text input {}: ", i)); region.add(TextEdit::new(text).id(i)); }); // TODO: .tooltip_text("Enter text to edit me") diff --git a/emigui/src/region.rs b/emigui/src/region.rs index 71eddaaf..85db5294 100644 --- a/emigui/src/region.rs +++ b/emigui/src/region.rs @@ -93,38 +93,7 @@ impl Region { } } - /// 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! - pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) { - self.ctx - .graphics - .lock() - .layer(self.layer) - .push((self.clip_rect(), paint_cmd)) - } - - pub fn add_paint_cmds(&mut self, mut cmds: Vec) { - let clip_rect = self.clip_rect(); - self.ctx - .graphics - .lock() - .layer(self.layer) - .extend(cmds.drain(..).map(|cmd| (clip_rect, cmd))); - } - - /// Insert a paint cmd before existing ones - pub fn insert_paint_cmd(&mut self, pos: usize, paint_cmd: PaintCmd) { - self.ctx - .graphics - .lock() - .layer(self.layer) - .insert(pos, (self.clip_rect(), paint_cmd)); - } - - pub fn paint_list_len(&self) -> usize { - self.ctx.graphics.lock().layer(self.layer).len() - } + // ------------------------------------------------- pub fn round_to_pixel(&self, point: f32) -> f32 { self.ctx.round_to_pixel(point) @@ -211,6 +180,20 @@ impl Region { // ------------------------------------------------------------------------ + pub fn contains_mouse(&self, rect: &Rect) -> bool { + self.ctx.contains_mouse(self.layer, &self.clip_rect, rect) + } + + pub fn has_kb_focus(&self, id: Id) -> bool { + self.memory().kb_focus_id == Some(id) + } + + pub fn request_kb_focus(&self, id: Id) { + self.memory().kb_focus_id = Some(id); + } + + // ------------------------------------------------------------------------ + /// 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 @@ -263,193 +246,6 @@ impl Region { } } - // ------------------------------------------------------------------------ - // Sub-regions: - - /// Create a child region at the current cursor. - /// `size` is the desired size. - /// Actual size may be much smaller if `avilable_size()` is not enough. - /// Set `size` to `Vec::infinity()` to get as much space as possible. - /// Just because you ask for a lot of space does not mean you have to use it! - /// After `add_contents` is called the contents of `bounding_size` - /// will decide how much space will be used in the parent region. - pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Region)) { - let size = size.min(self.available_space()); - let child_rect = Rect::from_min_size(self.cursor, size); - let mut child_region = Region { - ..self.child_region(child_rect) - }; - add_contents(&mut child_region); - self.reserve_space(child_region.bounding_size(), None); - } - - /// Create a child region which is indented to the right - pub fn indent(&mut self, id_source: impl Hash, add_contents: impl FnOnce(&mut Region)) { - assert!( - self.dir == Direction::Vertical, - "You can only indent vertical layouts" - ); - let indent = vec2(self.style.indent, 0.0); - let child_rect = Rect::from_min_max(self.cursor + indent, self.bottom_right()); - let mut child_region = Region { - id: self.id.with(id_source), - align: Align::Min, - ..self.child_region(child_rect) - }; - add_contents(&mut child_region); - let size = child_region.bounding_size(); - - // draw a grey line on the left to mark the region - let line_start = child_rect.min - indent * 0.5; - let line_start = line_start.round(); // TODO: round to pixel instead - let line_end = pos2(line_start.x, line_start.y + size.y - 8.0); - self.add_paint_cmd(PaintCmd::Line { - points: vec![line_start, line_end], - color: gray(150, 255), - width: self.style.line_width, - }); - - self.reserve_space(indent + size, None); - } - - pub fn left_column(&mut self, width: f32) -> Region { - self.column(Align::Min, width) - } - - pub fn centered_column(&mut self, width: f32) -> Region { - self.column(Align::Center, width) - } - - pub fn right_column(&mut self, width: f32) -> Region { - self.column(Align::Max, width) - } - - /// A column region with a given width. - pub fn column(&mut self, column_position: Align, width: f32) -> Region { - let x = match column_position { - Align::Min => 0.0, - Align::Center => self.available_width() / 2.0 - width / 2.0, - Align::Max => self.available_width() - width, - }; - self.child_region(Rect::from_min_size( - self.cursor + vec2(x, 0.0), - vec2(width, self.available_height()), - )) - } - - /// 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, - align: Align, - add_contents: impl FnOnce(&mut Region), - ) { - let child_rect = Rect::from_min_max(self.cursor, self.bottom_right()); - let mut child_region = Region { - dir, - align, - ..self.child_region(child_rect) - }; - add_contents(&mut child_region); - let size = child_region.bounding_size(); - self.reserve_space(size, None); - } - - /// Temporarily split split a vertical layout into several columns. - /// - /// region.columns(2, |columns| { - /// columns[0].add(emigui::widgets::label!("First column")); - /// columns[1].add(emigui::widgets::label!("Second column")); - /// }); - pub fn columns(&mut self, num_columns: usize, add_contents: F) -> R - where - F: FnOnce(&mut [Region]) -> R, - { - // TODO: ensure there is space - 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 + spacing), 0.0); - let child_rect = - Rect::from_min_max(pos, pos2(pos.x + column_width, self.bottom_right().y)); - - Region { - id: self.make_child_id(&("column", col_idx)), - dir: Direction::Vertical, - ..self.child_region(child_rect) - } - }) - .collect(); - - 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); - } - - let size = vec2(self.available_width().max(sum_width), max_height); - self.reserve_space(size, None); - result - } - - // ------------------------------------------------------------------------ - - pub fn contains_mouse(&self, rect: &Rect) -> bool { - self.ctx.contains_mouse(self.layer, &self.clip_rect, rect) - } - - pub fn has_kb_focus(&self, id: Id) -> bool { - self.memory().kb_focus_id == Some(id) - } - - pub fn request_kb_focus(&self, id: Id) { - self.memory().kb_focus_id = Some(id); - } - - // ------------------------------------------------------------------------ - - pub fn add(&mut self, widget: impl Widget) -> GuiResponse { - widget.ui(self) - } - - // Convenience functions: - - pub fn add_label(&mut self, text: impl Into) -> GuiResponse { - self.add(Label::new(text)) - } - - pub fn add_hyperlink(&mut self, url: impl Into) -> GuiResponse { - self.add(Hyperlink::new(url)) - } - - pub fn collapsing( - &mut self, - text: impl Into, - add_contents: impl FnOnce(&mut Region), - ) -> GuiResponse { - CollapsingHeader::new(text).show(self, add_contents) - } - // ------------------------------------------------------------------------ // Stuff that moves the cursor, i.e. allocates space in this region! @@ -539,7 +335,42 @@ impl Region { child_pos } + // ------------------------------------------------ + // Painting related stuff + + /// 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! + pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) { + self.ctx + .graphics + .lock() + .layer(self.layer) + .push((self.clip_rect(), paint_cmd)) + } + + pub fn add_paint_cmds(&mut self, mut cmds: Vec) { + let clip_rect = self.clip_rect(); + self.ctx + .graphics + .lock() + .layer(self.layer) + .extend(cmds.drain(..).map(|cmd| (clip_rect, cmd))); + } + + /// Insert a paint cmd before existing ones + pub fn insert_paint_cmd(&mut self, pos: usize, paint_cmd: PaintCmd) { + self.ctx + .graphics + .lock() + .layer(self.layer) + .insert(pos, (self.clip_rect(), paint_cmd)); + } + + pub fn paint_list_len(&self) -> usize { + self.ctx.graphics.lock().layer(self.layer).len() + } /// Paint some debug text at current cursor pub fn debug_text(&self, text: &str) { @@ -584,4 +415,179 @@ impl Region { }); } } + + // ------------------------------------------------------------------------ + // Addding Widgets + + pub fn add(&mut self, widget: impl Widget) -> GuiResponse { + widget.ui(self) + } + + // Convenience functions: + + pub fn add_label(&mut self, text: impl Into) -> GuiResponse { + self.add(Label::new(text)) + } + + pub fn add_hyperlink(&mut self, url: impl Into) -> GuiResponse { + self.add(Hyperlink::new(url)) + } + + // ------------------------------------------------------------------------ + // Addding Containers / Sub-Regions: + + pub fn collapsing( + &mut self, + text: impl Into, + add_contents: impl FnOnce(&mut Region), + ) -> GuiResponse { + CollapsingHeader::new(text).show(self, add_contents) + } + + /// Create a child region at the current cursor. + /// `size` is the desired size. + /// Actual size may be much smaller if `avilable_size()` is not enough. + /// Set `size` to `Vec::infinity()` to get as much space as possible. + /// Just because you ask for a lot of space does not mean you have to use it! + /// After `add_contents` is called the contents of `bounding_size` + /// will decide how much space will be used in the parent region. + pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Region)) { + let size = size.min(self.available_space()); + let child_rect = Rect::from_min_size(self.cursor, size); + let mut child_region = Region { + ..self.child_region(child_rect) + }; + add_contents(&mut child_region); + self.reserve_space(child_region.bounding_size(), None); + } + + /// Create a child region which is indented to the right + pub fn indent(&mut self, id_source: impl Hash, add_contents: impl FnOnce(&mut Region)) { + assert!( + self.dir == Direction::Vertical, + "You can only indent vertical layouts" + ); + let indent = vec2(self.style.indent, 0.0); + let child_rect = Rect::from_min_max(self.cursor + indent, self.bottom_right()); + let mut child_region = Region { + id: self.id.with(id_source), + align: Align::Min, + ..self.child_region(child_rect) + }; + add_contents(&mut child_region); + let size = child_region.bounding_size(); + + // draw a grey line on the left to mark the region + let line_start = child_rect.min - indent * 0.5; + let line_start = line_start.round(); // TODO: round to pixel instead + let line_end = pos2(line_start.x, line_start.y + size.y - 8.0); + self.add_paint_cmd(PaintCmd::Line { + points: vec![line_start, line_end], + color: gray(150, 255), + width: self.style.line_width, + }); + + self.reserve_space(indent + size, None); + } + + pub fn left_column(&mut self, width: f32) -> Region { + self.column(Align::Min, width) + } + + pub fn centered_column(&mut self, width: f32) -> Region { + self.column(Align::Center, width) + } + + pub fn right_column(&mut self, width: f32) -> Region { + self.column(Align::Max, width) + } + + /// A column region with a given width. + pub fn column(&mut self, column_position: Align, width: f32) -> Region { + let x = match column_position { + Align::Min => 0.0, + Align::Center => self.available_width() / 2.0 - width / 2.0, + Align::Max => self.available_width() - width, + }; + self.child_region(Rect::from_min_size( + self.cursor + vec2(x, 0.0), + vec2(width, self.available_height()), + )) + } + + /// Start a region with horizontal layout + pub fn horizontal(&mut self, add_contents: impl FnOnce(&mut Region)) { + self.inner_layout(Direction::Horizontal, Align::Min, add_contents) + } + + /// Start a region with vertical layout + pub fn vertical(&mut self, add_contents: impl FnOnce(&mut Region)) { + self.inner_layout(Direction::Vertical, Align::Min, add_contents) + } + + pub fn inner_layout( + &mut self, + dir: Direction, + align: Align, + add_contents: impl FnOnce(&mut Region), + ) { + let child_rect = Rect::from_min_max(self.cursor, self.bottom_right()); + let mut child_region = Region { + dir, + align, + ..self.child_region(child_rect) + }; + add_contents(&mut child_region); + let size = child_region.bounding_size(); + self.reserve_space(size, None); + } + + /// Temporarily split split a vertical layout into several columns. + /// + /// region.columns(2, |columns| { + /// columns[0].add(emigui::widgets::label!("First column")); + /// columns[1].add(emigui::widgets::label!("Second column")); + /// }); + pub fn columns(&mut self, num_columns: usize, add_contents: F) -> R + where + F: FnOnce(&mut [Region]) -> R, + { + // TODO: ensure there is space + 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 + spacing), 0.0); + let child_rect = + Rect::from_min_max(pos, pos2(pos.x + column_width, self.bottom_right().y)); + + Region { + id: self.make_child_id(&("column", col_idx)), + dir: Direction::Vertical, + ..self.child_region(child_rect) + } + }) + .collect(); + + 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); + } + + let size = vec2(self.available_width().max(sum_width), max_height); + self.reserve_space(size, None); + result + } + + // ------------------------------------------------ } diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index ff1e4dd5..4f05b2ab 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -448,7 +448,8 @@ impl<'a> Widget for Slider<'a> { columns[1] .desired_rect .set_height(slider_response.rect.height()); - columns[1].horizontal(Align::Center, |region| { + columns[1].horizontal(|region| { + region.align = Align::Center; region.add(Label::new(full_text).multiline(false)); }); diff --git a/example_wasm/src/lib.rs b/example_wasm/src/lib.rs index 0e59c806..744062a7 100644 --- a/example_wasm/src/lib.rs +++ b/example_wasm/src/lib.rs @@ -55,7 +55,7 @@ impl State { ); region.add_label("This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech."); region.add_label("This is also work in progress, and not ready for production... yet :)"); - region.horizontal(Align::Min, |region| { + region.horizontal(|region| { region.add_label("Project home page:"); region.add_hyperlink("https://github.com/emilk/emigui/"); });