From 2c0ca77e09ab9dcce9197ed239360a9aadbae8e7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 14 Jan 2019 07:54:06 -0600 Subject: [PATCH] Add support for alignment (min, center, max) in horizontal and vertical layouts --- emigui/src/emigui.rs | 1 + emigui/src/layout.rs | 58 ++++++++++++++++++++++++++++++++---------- emigui/src/lib.rs | 3 +-- emigui/src/style.rs | 8 +++--- emigui/src/widgets.rs | 42 +++++++++++++++--------------- emigui_wasm/src/app.rs | 7 +++-- emigui_wasm/src/lib.rs | 4 +-- 7 files changed, 78 insertions(+), 45 deletions(-) diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index a98b967c..1098cb56 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -80,6 +80,7 @@ impl Emigui { options: self.data.options(), id: Default::default(), dir: layout::Direction::Vertical, + align: layout::Align::Center, cursor: Default::default(), bounding_size: Default::default(), available_space: size, diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index ae86ed41..69076f4f 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -43,7 +43,7 @@ impl Default for LayoutOptions { button_padding: vec2(5.0, 3.0), item_spacing: vec2(8.0, 4.0), indent: 21.0, - clickable_diameter: 10.0, + clickable_diameter: 38.0, start_icon_width: 20.0, } } @@ -115,6 +115,25 @@ impl Default for Direction { } } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Align { + /// Left/Top + Min, + + /// Note: requires a bounded/known available_width. + Center, + + /// Right/Bottom + /// Note: requires a bounded/known available_width. + Max, +} + +impl Default for Align { + fn default() -> Align { + Align::Min + } +} + // ---------------------------------------------------------------------------- pub type Id = u64; @@ -217,6 +236,7 @@ where options, id: Default::default(), dir: Direction::Vertical, + align: Align::Min, cursor: window_pos + window_padding, bounding_size: vec2(0.0, 0.0), available_space: vec2(400.0, std::f32::INFINITY), // TODO: popup/tooltip width @@ -252,6 +272,8 @@ pub struct Region { /// Doesn't change. pub(crate) dir: Direction, + pub(crate) align: Align, + /// Changes only along self.dir pub(crate) cursor: Vec2, @@ -281,10 +303,6 @@ impl Region { self.data.input() } - pub fn cursor(&self) -> Vec2 { - self.cursor - } - pub fn fonts(&self) -> &Fonts { &*self.data.fonts } @@ -363,6 +381,7 @@ impl Region { options: self.options, 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, @@ -373,20 +392,21 @@ impl Region { } /// A horizontally centered region of the given width. - pub fn centered_column(&mut self, width: f32) -> Region { + pub fn centered_column(&mut self, width: f32, align: Align) -> Region { Region { data: self.data.clone(), options: self.options, id: self.id, dir: self.dir, cursor: vec2((self.available_space.x - width) / 2.0, self.cursor.y), + align, bounding_size: vec2(0.0, 0.0), available_space: vec2(width, self.available_space.y), } } /// Start a region with horizontal layout - pub fn horizontal(&mut self, add_contents: F) + pub fn horizontal(&mut self, align: Align, add_contents: F) where F: FnOnce(&mut Region), { @@ -395,6 +415,7 @@ impl Region { options: self.options, id: self.id, dir: Direction::Horizontal, + align, cursor: self.cursor, bounding_size: vec2(0.0, 0.0), available_space: self.available_space, @@ -425,6 +446,7 @@ impl Region { options: self.options, 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), @@ -452,12 +474,9 @@ impl Region { // ------------------------------------------------------------------------ pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option) -> InteractInfo { - let rect = Rect { - pos: self.cursor, - size, - }; + let pos = self.reserve_space_without_padding(size + self.options().item_spacing); - self.reserve_space_without_padding(size + self.options().item_spacing); + let rect = Rect::from_min_size(pos, size); let hovered = rect.contains(self.input().mouse_pos); let clicked = hovered && self.input().mouse_clicked; @@ -479,20 +498,31 @@ impl Region { } } - // TODO: Return a Rect /// Reserve this much space and move the cursor. - pub fn reserve_space_without_padding(&mut self, size: Vec2) { + 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 { diff --git a/emigui/src/lib.rs b/emigui/src/lib.rs index 62b0865c..1e372f5c 100644 --- a/emigui/src/lib.rs +++ b/emigui/src/lib.rs @@ -20,8 +20,7 @@ pub mod widgets; pub use crate::{ emigui::Emigui, fonts::TextStyle, - layout::LayoutOptions, - layout::Region, + layout::{Align, LayoutOptions, Region}, painter::{Frame, Painter, Vertex}, style::Style, types::RawInput, diff --git a/emigui/src/style.rs b/emigui/src/style.rs index e45c7851..44c21139 100644 --- a/emigui/src/style.rs +++ b/emigui/src/style.rs @@ -186,12 +186,14 @@ fn translate_cmd(out_commands: &mut Vec, style: &Style, cmd: GuiCmd) { value, } => { let rect = interact.rect; - let thin_rect = Rect::from_center_size(rect.center(), vec2(rect.size.x, 6.0)); + let thickness = rect.size().y; + let thin_size = vec2(rect.size.x, thickness / 2.0); + let thin_rect = Rect::from_center_size(rect.center(), thin_size); let marker_center_x = remap_clamp(value, min, max, rect.min().x, rect.max().x); let marker_rect = Rect::from_center_size( vec2(marker_center_x, thin_rect.center().y), - vec2(16.0, 16.0), + vec2(thickness, thickness), ); out_commands.push(PaintCmd::Rect { @@ -202,7 +204,7 @@ fn translate_cmd(out_commands: &mut Vec, style: &Style, cmd: GuiCmd) { }); out_commands.push(PaintCmd::Rect { - corner_radius: 3.0, + corner_radius: 6.0, fill_color: Some(style.interact_fill_color(&interact)), outline: None, rect: marker_rect, diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index 233ddb91..b714c8d9 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -1,6 +1,6 @@ use crate::{ fonts::TextStyle, - layout::{make_id, Direction, GuiResponse, Id, Region}, + layout::{make_id, Align, Direction, GuiResponse, Id, Region}, math::{remap_clamp, vec2, Vec2}, types::{Color, GuiCmd, PaintCmd}, }; @@ -41,8 +41,8 @@ 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()); - region.add_text(region.cursor(), self.text_style, text); let interact = region.reserve_space(text_size, None); + region.add_text(interact.rect.min(), self.text_style, text); region.response(interact) } } @@ -65,9 +65,9 @@ impl Widget for Button { let text_style = TextStyle::Button; let font = ®ion.fonts()[text_style]; let (text, text_size) = font.layout_multiline(&self.text, region.width()); - let text_cursor = region.cursor() + region.options().button_padding; let interact = region.reserve_space(text_size + 2.0 * region.options().button_padding, Some(id)); + let text_cursor = interact.rect.min() + region.options().button_padding; region.add_graphic(GuiCmd::Button { interact }); region.add_text(text_cursor, text_style, text); region.response(interact) @@ -97,9 +97,6 @@ impl<'a> Widget for Checkbox<'a> { let text_style = TextStyle::Button; let font = ®ion.fonts()[text_style]; let (text, text_size) = font.layout_multiline(&self.text, region.width()); - let text_cursor = region.cursor() - + region.options().button_padding - + vec2(region.options().start_icon_width, 0.0); let interact = region.reserve_space( region.options().button_padding + vec2(region.options().start_icon_width, 0.0) @@ -107,6 +104,9 @@ impl<'a> Widget for Checkbox<'a> { + region.options().button_padding, Some(id), ); + let text_cursor = interact.rect.min() + + region.options().button_padding + + vec2(region.options().start_icon_width, 0.0); if interact.clicked { *self.checked = !*self.checked; } @@ -146,9 +146,6 @@ impl Widget for RadioButton { let text_style = TextStyle::Button; let font = ®ion.fonts()[text_style]; let (text, text_size) = font.layout_multiline(&self.text, region.width()); - let text_cursor = region.cursor() - + region.options().button_padding - + vec2(region.options().start_icon_width, 0.0); let interact = region.reserve_space( region.options().button_padding + vec2(region.options().start_icon_width, 0.0) @@ -156,6 +153,9 @@ impl Widget for RadioButton { + region.options().button_padding, Some(id), ); + let text_cursor = interact.rect.min() + + region.options().button_padding + + vec2(region.options().start_icon_width, 0.0); region.add_graphic(GuiCmd::RadioButton { checked: self.checked, interact, @@ -215,14 +215,18 @@ impl<'a> Widget for Slider<'a> { if text_on_top { let (text, text_size) = font.layout_multiline(&full_text, region.width()); - region.add_text(region.cursor(), text_style, text); - region.reserve_space_without_padding(text_size); + let pos = region.reserve_space_without_padding(text_size); + region.add_text(pos, text_style, text); naked.add_to(region) } else { region.columns(2, |columns| { let response = naked.add_to(&mut columns[0]); + columns[1].available_space.y = response.rect.size().y; - columns[1].add(label(full_text)); // TODO: centered! + columns[1].horizontal(Align::Center, |region| { + region.add(label(full_text)); + }); + response }) } @@ -295,23 +299,21 @@ impl Widget for Separator { let available_space = region.available_space; let (points, interact) = match region.direction() { Direction::Horizontal => { - let (rect, interact) = - region.reserve_space(vec2(self.width, available_space.y), None); + let interact = region.reserve_space(vec2(self.width, available_space.y), None); ( vec![ - vec2(rect.center().x, rect.min().y), - vec2(rect.center().x, rect.max().y), + vec2(interact.rect.center().x, interact.rect.min().y), + vec2(interact.rect.center().x, interact.rect.max().y), ], interact, ) } Direction::Vertical => { - let (rect, interact) = - region.reserve_space(vec2(available_space.x, self.width), None); + let interact = region.reserve_space(vec2(available_space.x, self.width), None); ( vec![ - vec2(rect.min().x, rect.center().y), - vec2(rect.max().x, rect.center().y), + vec2(interact.rect.min().x, interact.rect.center().y), + vec2(interact.rect.max().x, interact.rect.center().y), ], interact, ) diff --git a/emigui_wasm/src/app.rs b/emigui_wasm/src/app.rs index b47c8933..d4f98060 100644 --- a/emigui_wasm/src/app.rs +++ b/emigui_wasm/src/app.rs @@ -1,4 +1,4 @@ -use emigui::{math::*, types::*, widgets::*, Region, TextStyle}; +use emigui::{math::*, types::*, widgets::*, Align, Region, TextStyle}; pub struct App { checked: bool, @@ -41,7 +41,7 @@ impl App { gui.add(Checkbox::new(&mut self.checked, "checkbox")); - gui.horizontal(|gui| { + gui.horizontal(Align::Min, |gui| { if gui .add(radio(self.selected_alternative == 0, "First")) .clicked @@ -78,7 +78,7 @@ impl App { 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(); + let pos = gui.reserve_space(self.size, None).rect.min(); gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Rect { corner_radius: self.corner_radius, fill_color: Some(srgba(136, 136, 136, 255)), @@ -88,7 +88,6 @@ impl App { color: srgba(255, 255, 255, 255), }), }])); - gui.reserve_space(self.size, None); }); } } diff --git a/emigui_wasm/src/lib.rs b/emigui_wasm/src/lib.rs index 67a49f50..7a84dac6 100644 --- a/emigui_wasm/src/lib.rs +++ b/emigui_wasm/src/lib.rs @@ -5,7 +5,7 @@ extern crate wasm_bindgen; extern crate emigui; -use emigui::{widgets::label, Emigui, RawInput}; +use emigui::{widgets::label, Align, Emigui, RawInput}; use wasm_bindgen::prelude::*; @@ -46,7 +46,7 @@ impl State { self.emigui.new_frame(raw_input); let mut region = self.emigui.whole_screen_region(); - let mut region = region.centered_column(480.0); + let mut region = region.centered_column(480.0, Align::Min); self.app.show_gui(&mut region); self.emigui.example(&mut region);