diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index 928be4b8..cb629a4f 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -1,13 +1,6 @@ use std::sync::Arc; -use crate::{ - label, layout, - layout::Region, - mesher::Mesher, - types::{GuiInput, PaintCmd}, - widgets::*, - FontDefinitions, Fonts, Layer, Mesh, RawInput, Texture, -}; +use crate::{layout, mesher::Mesher, widgets::*, *}; #[derive(Clone, Copy, Default)] struct Stats { diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index 5016ec4b..b07e7a88 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -1,20 +1,9 @@ use std::{ - collections::{HashMap, HashSet}, hash::Hash, sync::{Arc, Mutex}, }; -use crate::{ - color::{self, Color}, - font::TextFragment, - fonts::{Fonts, TextStyle}, - math::*, - style::Style, - types::*, - widgets::{Label, Widget}, - window::WindowState, - GraphicLayers, Layer, -}; +use crate::{widgets::*, *}; // ---------------------------------------------------------------------------- @@ -33,7 +22,7 @@ pub struct GuiResponse { pub rect: Rect, /// Used for showing a popup (if any) - data: Arc, + pub data: Arc, } impl GuiResponse { @@ -61,55 +50,6 @@ impl GuiResponse { // ---------------------------------------------------------------------------- -#[derive(Clone, Debug, Default)] -pub struct Memory { - /// The widget being interacted with (e.g. dragged, in case of a slider). - active_id: Option, - - /// Which foldable regions are open. - open_foldables: HashSet, - - windows: HashMap, - - /// Top is last - window_order: Vec, -} - -impl Memory { - /// default_rect: where to put it if it does NOT exist - pub fn get_or_create_window(&mut self, id: Id, default_rect: Rect) -> WindowState { - if let Some(state) = self.windows.get(&id) { - *state - } else { - let state = WindowState { rect: default_rect }; - self.windows.insert(id, state); - self.window_order.push(id); - state - } - } - - pub fn set_window_state(&mut self, id: Id, state: WindowState) { - self.windows.insert(id, state); - } - - pub fn layer_at(&self, pos: Vec2) -> Layer { - for window_id in self.window_order.iter().rev() { - if let Some(state) = self.windows.get(window_id) { - if state.rect.contains(pos) { - return Layer::Window(*window_id); - } - } - } - Layer::Background - } - - pub fn move_window_to_top(&mut self, _id: Id) { - // TODO - } -} - -// ---------------------------------------------------------------------------- - #[derive(Clone, Copy, Debug, PartialEq)] pub enum Direction { Horizontal, @@ -217,8 +157,6 @@ impl Data { pub fn interact(&self, layer: Layer, rect: Rect, interaction_id: Option) -> InteractInfo { let mut memory = self.memory.lock().unwrap(); - // TODO: check for windows on top of the current rect! - let hovered = if let Some(mouse_pos) = self.input.mouse_pos { if rect.contains(mouse_pos) { let is_something_else_active = @@ -307,446 +245,3 @@ where }, ); } - -// ---------------------------------------------------------------------------- - -/// Represents a region of the screen -/// with a type of layout (horizontal or vertical). -/// TODO: make Region a trait so we can have type-safe HorizontalRegion etc? -pub struct Region { - pub(crate) data: Arc, - - /// Where to put the graphics output of this Region - pub(crate) layer: Layer, - - pub(crate) style: Style, - - /// Unique ID of this region. - pub(crate) id: Id, - - /// Doesn't change. - pub(crate) dir: Direction, - - pub(crate) align: Align, - - /// Where the next widget will be put. - /// Progresses along self.dir - pub(crate) cursor: Vec2, - - /// Bounding box of children. - /// We keep track of our max-size along the orthogonal to self.dir - 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 { - /// 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.data - .graphics - .lock() - .unwrap() - .layer(self.layer) - .push(paint_cmd) - } - - pub fn add_paint_cmds(&mut self, mut cmds: Vec) { - self.data - .graphics - .lock() - .unwrap() - .layer(self.layer) - .append(&mut cmds) - } - - /// Options for this region, and any child regions we may spawn. - pub fn style(&self) -> &Style { - &self.style - } - - pub fn data(&self) -> &Arc { - &self.data - } - - pub fn input(&self) -> &GuiInput { - self.data.input() - } - - pub fn fonts(&self) -> &Fonts { - &*self.data.fonts - } - - pub fn width(&self) -> f32 { - self.available_space.x - } - - pub fn height(&self) -> f32 { - self.available_space.y - } - - pub fn size(&self) -> Vec2 { - self.available_space - } - - pub fn direction(&self) -> Direction { - self.dir - } - - pub fn cursor(&self) -> Vec2 { - self.cursor - } - - pub fn set_align(&mut self, align: Align) { - self.align = align; - } - - // ------------------------------------------------------------------------ - // Sub-regions: - - pub fn foldable(&mut self, text: S, add_contents: F) -> GuiResponse - where - S: Into, - F: FnOnce(&mut Region), - { - assert!( - self.dir == Direction::Vertical, - "Horizontal foldable is unimplemented" - ); - let text: String = text.into(); - let id = self.make_child_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_cursor = self.cursor + self.style.button_padding; - let interact = self.reserve_space( - vec2( - self.available_space.x, - text_size.y + 2.0 * self.style.button_padding.y, - ), - Some(id), - ); - - let open = { - let mut memory = self.data.memory.lock().unwrap(); - if interact.clicked { - if memory.open_foldables.contains(&id) { - memory.open_foldables.remove(&id); - } else { - memory.open_foldables.insert(id); - } - } - memory.open_foldables.contains(&id) - }; - - let fill_color = self.style.interact_fill_color(&interact); - let stroke_color = self.style.interact_stroke_color(&interact); - - self.add_paint_cmd(PaintCmd::Rect { - corner_radius: 5.0, - fill_color: Some(fill_color), - outline: Some(Outline { - width: 1.0, - color: color::WHITE, - }), - rect: interact.rect, - }); - - let (small_icon_rect, _) = self.style.icon_rectangles(&interact.rect); - // Draw a minus: - self.add_paint_cmd(PaintCmd::Line { - points: vec![ - vec2(small_icon_rect.min().x, small_icon_rect.center().y), - vec2(small_icon_rect.max().x, small_icon_rect.center().y), - ], - color: stroke_color, - width: self.style.line_width, - }); - if !open { - // Draw it as a plus: - self.add_paint_cmd(PaintCmd::Line { - points: vec![ - vec2(small_icon_rect.center().x, small_icon_rect.min().y), - vec2(small_icon_rect.center().x, small_icon_rect.max().y), - ], - color: stroke_color, - width: self.style.line_width, - }); - } - - self.add_text( - text_cursor + vec2(self.style.start_icon_width, 0.0), - text_style, - text, - None, - ); - - if open { - let old_id = self.id; - self.id = id; - self.indent(add_contents); - self.id = old_id; - } - - self.response(interact) - } - - /// Create a child region which is indented to the right - pub fn indent(&mut self, add_contents: F) - where - F: FnOnce(&mut Region), - { - let indent = vec2(self.style.indent, 0.0); - let mut child_region = Region { - data: self.data.clone(), - layer: self.layer, - style: self.style, - id: self.id, - dir: self.dir, - align: Align::Min, - cursor: self.cursor + indent, - bounding_size: vec2(0.0, 0.0), - available_space: self.available_space - indent, - }; - add_contents(&mut child_region); - let size = child_region.bounding_size; - self.reserve_space_without_padding(indent + size); - } - - /// Return a sub-region relative to the parent - pub fn relative_region(&mut self, rect: Rect) -> Region { - Region { - data: self.data.clone(), - layer: self.layer, - style: self.style, - id: self.id, - dir: self.dir, - cursor: self.cursor + rect.min(), - align: self.align, - bounding_size: vec2(0.0, 0.0), - available_space: rect.size(), - } - } - - /// 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_space.x / 2.0 - width / 2.0, - Align::Max => self.available_space.x - width, - }; - self.relative_region(Rect::from_min_size( - vec2(x, 0.0), - vec2(width, self.available_space.y), - )) - } - - 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) - } - - pub fn inner_layout(&mut self, dir: Direction, align: Align, add_contents: F) - where - F: FnOnce(&mut Region), - { - let mut child_region = Region { - data: self.data.clone(), - layer: self.layer, - style: self.style, - id: self.id, - dir, - align, - 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; - self.reserve_space_without_padding(size); - } - - /// Start a region with horizontal layout - pub fn horizontal(&mut self, align: Align, add_contents: F) - where - F: 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: F) - where - F: FnOnce(&mut Region), - { - self.inner_layout(Direction::Vertical, align, add_contents) - } - - /// 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 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 mut columns: Vec = (0..num_columns) - .map(|col_idx| Region { - data: self.data.clone(), - layer: self.layer, - style: self.style, - id: self.make_child_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), - }) - .collect(); - - let result = add_contents(&mut columns[..]); - - 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_space.x, max_height)); - result - } - - // ------------------------------------------------------------------------ - - pub fn add(&mut self, widget: W) -> GuiResponse { - widget.add_to(self) - } - - // ------------------------------------------------------------------------ - - pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option) -> InteractInfo { - let pos = self.reserve_space_without_padding(size + self.style.item_spacing); - let rect = Rect::from_min_size(pos, size); - self.data.interact(self.layer, 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) -> Vec2 { - let mut pos = self.cursor; - 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, - }; - 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, - }; - 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); - } - pos - } - - 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); - child_id.hash(&mut hasher); - hasher.finish() - } - - 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() - }) - } - - // Helper function - pub fn floating_text( - &mut self, - pos: Vec2, - text: &str, - text_style: TextStyle, - align: (Align, Align), - text_color: Option, - ) -> Vec2 { - let font = &self.fonts()[text_style]; - let (text, text_size) = font.layout_multiline(text, std::f32::INFINITY); - - let x = match align.0 { - Align::Min => pos.x, - Align::Center => pos.x - 0.5 * text_size.x, - Align::Max => pos.x - text_size.x, - }; - let y = match align.1 { - Align::Min => pos.y, - Align::Center => pos.y - 0.5 * text_size.y, - Align::Max => pos.y - text_size.y, - }; - self.add_text(vec2(x, y), text_style, text, text_color); - text_size - } - - pub fn add_text( - &mut self, - pos: Vec2, - text_style: TextStyle, - text: Vec, - color: Option, - ) { - let color = color.unwrap_or_else(|| self.style().text_color()); - for fragment in text { - self.add_paint_cmd(PaintCmd::Text { - color, - pos: pos + vec2(0.0, fragment.y_offset), - text: fragment.text, - text_style, - x_offsets: fragment.x_offsets, - }); - } - } - - 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, - data: self.data.clone(), - } - } -} diff --git a/emigui/src/lib.rs b/emigui/src/lib.rs index 5ffca308..df4d073a 100644 --- a/emigui/src/lib.rs +++ b/emigui/src/lib.rs @@ -14,7 +14,9 @@ mod fonts; mod layers; mod layout; pub mod math; +mod memory; pub mod mesher; +mod region; mod style; mod texture_atlas; mod types; @@ -26,9 +28,11 @@ pub use { color::Color, fonts::{FontDefinitions, Fonts, TextStyle}, layers::*, - layout::{Align, Id, Region}, + layout::{Align, Id}, math::*, + memory::Memory, mesher::{Mesh, Vertex}, + region::Region, style::Style, texture_atlas::Texture, types::*, diff --git a/emigui/src/memory.rs b/emigui/src/memory.rs new file mode 100644 index 00000000..3110aec2 --- /dev/null +++ b/emigui/src/memory.rs @@ -0,0 +1,50 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{window::WindowState, *}; + +#[derive(Clone, Debug, Default)] +pub struct Memory { + /// The widget being interacted with (e.g. dragged, in case of a slider). + pub(crate) active_id: Option, + + /// Which foldable regions are open. + pub(crate) open_foldables: HashSet, + + windows: HashMap, + + /// Top is last + window_order: Vec, +} + +impl Memory { + /// default_rect: where to put it if it does NOT exist + pub fn get_or_create_window(&mut self, id: Id, default_rect: Rect) -> WindowState { + if let Some(state) = self.windows.get(&id) { + *state + } else { + let state = WindowState { rect: default_rect }; + self.windows.insert(id, state); + self.window_order.push(id); + state + } + } + + pub fn set_window_state(&mut self, id: Id, state: WindowState) { + self.windows.insert(id, state); + } + + pub fn layer_at(&self, pos: Vec2) -> Layer { + for window_id in self.window_order.iter().rev() { + if let Some(state) = self.windows.get(window_id) { + if state.rect.contains(pos) { + return Layer::Window(*window_id); + } + } + } + Layer::Background + } + + pub fn move_window_to_top(&mut self, _id: Id) { + // TODO + } +} diff --git a/emigui/src/region.rs b/emigui/src/region.rs new file mode 100644 index 00000000..186fab67 --- /dev/null +++ b/emigui/src/region.rs @@ -0,0 +1,444 @@ +use std::{hash::Hash, sync::Arc}; + +use crate::{font::TextFragment, layout::*, widgets::*, *}; + +/// Represents a region of the screen +/// with a type of layout (horizontal or vertical). +/// TODO: make Region a trait so we can have type-safe HorizontalRegion etc? +pub struct Region { + pub(crate) data: Arc, + + /// Where to put the graphics output of this Region + pub(crate) layer: Layer, + + pub(crate) style: Style, + + /// Unique ID of this region. + pub(crate) id: Id, + + /// Doesn't change. + pub(crate) dir: Direction, + + pub(crate) align: Align, + + /// Where the next widget will be put. + /// Progresses along self.dir + pub(crate) cursor: Vec2, + + /// Bounding box of children. + /// We keep track of our max-size along the orthogonal to self.dir + 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 { + /// 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.data + .graphics + .lock() + .unwrap() + .layer(self.layer) + .push(paint_cmd) + } + + pub fn add_paint_cmds(&mut self, mut cmds: Vec) { + self.data + .graphics + .lock() + .unwrap() + .layer(self.layer) + .append(&mut cmds) + } + + /// Options for this region, and any child regions we may spawn. + pub fn style(&self) -> &Style { + &self.style + } + + pub fn data(&self) -> &Arc { + &self.data + } + + pub fn input(&self) -> &GuiInput { + self.data.input() + } + + pub fn fonts(&self) -> &Fonts { + &*self.data.fonts + } + + pub fn width(&self) -> f32 { + self.available_space.x + } + + pub fn height(&self) -> f32 { + self.available_space.y + } + + pub fn size(&self) -> Vec2 { + self.available_space + } + + pub fn direction(&self) -> Direction { + self.dir + } + + pub fn cursor(&self) -> Vec2 { + self.cursor + } + + pub fn set_align(&mut self, align: Align) { + self.align = align; + } + + // ------------------------------------------------------------------------ + // Sub-regions: + + pub fn foldable(&mut self, text: S, add_contents: F) -> GuiResponse + where + S: Into, + F: FnOnce(&mut Region), + { + assert!( + self.dir == Direction::Vertical, + "Horizontal foldable is unimplemented" + ); + let text: String = text.into(); + let id = self.make_child_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_cursor = self.cursor + self.style.button_padding; + let interact = self.reserve_space( + vec2( + self.available_space.x, + text_size.y + 2.0 * self.style.button_padding.y, + ), + Some(id), + ); + + let open = { + let mut memory = self.data.memory.lock().unwrap(); + if interact.clicked { + if memory.open_foldables.contains(&id) { + memory.open_foldables.remove(&id); + } else { + memory.open_foldables.insert(id); + } + } + memory.open_foldables.contains(&id) + }; + + let fill_color = self.style.interact_fill_color(&interact); + let stroke_color = self.style.interact_stroke_color(&interact); + + self.add_paint_cmd(PaintCmd::Rect { + corner_radius: 5.0, + fill_color: Some(fill_color), + outline: Some(Outline { + width: 1.0, + color: color::WHITE, + }), + rect: interact.rect, + }); + + let (small_icon_rect, _) = self.style.icon_rectangles(&interact.rect); + // Draw a minus: + self.add_paint_cmd(PaintCmd::Line { + points: vec![ + vec2(small_icon_rect.min().x, small_icon_rect.center().y), + vec2(small_icon_rect.max().x, small_icon_rect.center().y), + ], + color: stroke_color, + width: self.style.line_width, + }); + if !open { + // Draw it as a plus: + self.add_paint_cmd(PaintCmd::Line { + points: vec![ + vec2(small_icon_rect.center().x, small_icon_rect.min().y), + vec2(small_icon_rect.center().x, small_icon_rect.max().y), + ], + color: stroke_color, + width: self.style.line_width, + }); + } + + self.add_text( + text_cursor + vec2(self.style.start_icon_width, 0.0), + text_style, + text, + None, + ); + + if open { + let old_id = self.id; + self.id = id; + self.indent(add_contents); + self.id = old_id; + } + + self.response(interact) + } + + /// Create a child region which is indented to the right + pub fn indent(&mut self, add_contents: F) + where + F: FnOnce(&mut Region), + { + let indent = vec2(self.style.indent, 0.0); + let mut child_region = Region { + data: self.data.clone(), + layer: self.layer, + style: self.style, + id: self.id, + dir: self.dir, + align: Align::Min, + cursor: self.cursor + indent, + bounding_size: vec2(0.0, 0.0), + available_space: self.available_space - indent, + }; + add_contents(&mut child_region); + let size = child_region.bounding_size; + self.reserve_space_without_padding(indent + size); + } + + /// Return a sub-region relative to the parent + pub fn relative_region(&mut self, rect: Rect) -> Region { + Region { + data: self.data.clone(), + layer: self.layer, + style: self.style, + id: self.id, + dir: self.dir, + cursor: self.cursor + rect.min(), + align: self.align, + bounding_size: vec2(0.0, 0.0), + available_space: rect.size(), + } + } + + /// 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_space.x / 2.0 - width / 2.0, + Align::Max => self.available_space.x - width, + }; + self.relative_region(Rect::from_min_size( + vec2(x, 0.0), + vec2(width, self.available_space.y), + )) + } + + 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) + } + + pub fn inner_layout(&mut self, dir: Direction, align: Align, add_contents: F) + where + F: FnOnce(&mut Region), + { + let mut child_region = Region { + data: self.data.clone(), + layer: self.layer, + style: self.style, + id: self.id, + dir, + align, + 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; + self.reserve_space_without_padding(size); + } + + /// Start a region with horizontal layout + pub fn horizontal(&mut self, align: Align, add_contents: F) + where + F: 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: F) + where + F: FnOnce(&mut Region), + { + self.inner_layout(Direction::Vertical, align, add_contents) + } + + /// 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 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 mut columns: Vec = (0..num_columns) + .map(|col_idx| Region { + data: self.data.clone(), + layer: self.layer, + style: self.style, + id: self.make_child_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), + }) + .collect(); + + let result = add_contents(&mut columns[..]); + + 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_space.x, max_height)); + result + } + + // ------------------------------------------------------------------------ + + pub fn add(&mut self, widget: W) -> GuiResponse { + widget.add_to(self) + } + + // ------------------------------------------------------------------------ + + pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option) -> InteractInfo { + let pos = self.reserve_space_without_padding(size + self.style.item_spacing); + let rect = Rect::from_min_size(pos, size); + self.data.interact(self.layer, 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) -> Vec2 { + let mut pos = self.cursor; + 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, + }; + 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, + }; + 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); + } + pos + } + + 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); + child_id.hash(&mut hasher); + hasher.finish() + } + + 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() + }) + } + + // Helper function + pub fn floating_text( + &mut self, + pos: Vec2, + text: &str, + text_style: TextStyle, + align: (Align, Align), + text_color: Option, + ) -> Vec2 { + let font = &self.fonts()[text_style]; + let (text, text_size) = font.layout_multiline(text, std::f32::INFINITY); + + let x = match align.0 { + Align::Min => pos.x, + Align::Center => pos.x - 0.5 * text_size.x, + Align::Max => pos.x - text_size.x, + }; + let y = match align.1 { + Align::Min => pos.y, + Align::Center => pos.y - 0.5 * text_size.y, + Align::Max => pos.y - text_size.y, + }; + self.add_text(vec2(x, y), text_style, text, text_color); + text_size + } + + pub fn add_text( + &mut self, + pos: Vec2, + text_style: TextStyle, + text: Vec, + color: Option, + ) { + let color = color.unwrap_or_else(|| self.style().text_color()); + for fragment in text { + self.add_paint_cmd(PaintCmd::Text { + color, + pos: pos + vec2(0.0, fragment.y_offset), + text: fragment.text, + text_style, + x_offsets: fragment.x_offsets, + }); + } + } + + 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, + data: self.data.clone(), + } + } +} diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index f77cd300..0ad26964 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -1,11 +1,8 @@ #![allow(clippy::new_without_default_derive)] use crate::{ - color::{self, Color}, - fonts::TextStyle, - layout::{make_id, Align, Direction, GuiResponse, Id, Region}, - math::{remap_clamp, vec2, Rect, Vec2}, - types::{Outline, PaintCmd}, + layout::{make_id, Direction, GuiResponse}, + *, }; // ----------------------------------------------------------------------------