Add support for alignment (min, center, max) in horizontal and vertical layouts

This commit is contained in:
Emil Ernerfeldt 2019-01-14 07:54:06 -06:00
parent 3f84836c20
commit 2c0ca77e09
7 changed files with 78 additions and 45 deletions

View file

@ -80,6 +80,7 @@ impl Emigui {
options: self.data.options(), options: self.data.options(),
id: Default::default(), id: Default::default(),
dir: layout::Direction::Vertical, dir: layout::Direction::Vertical,
align: layout::Align::Center,
cursor: Default::default(), cursor: Default::default(),
bounding_size: Default::default(), bounding_size: Default::default(),
available_space: size, available_space: size,

View file

@ -43,7 +43,7 @@ impl Default for LayoutOptions {
button_padding: vec2(5.0, 3.0), button_padding: vec2(5.0, 3.0),
item_spacing: vec2(8.0, 4.0), item_spacing: vec2(8.0, 4.0),
indent: 21.0, indent: 21.0,
clickable_diameter: 10.0, clickable_diameter: 38.0,
start_icon_width: 20.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; pub type Id = u64;
@ -217,6 +236,7 @@ where
options, options,
id: Default::default(), id: Default::default(),
dir: Direction::Vertical, dir: Direction::Vertical,
align: Align::Min,
cursor: window_pos + window_padding, cursor: window_pos + window_padding,
bounding_size: vec2(0.0, 0.0), bounding_size: vec2(0.0, 0.0),
available_space: vec2(400.0, std::f32::INFINITY), // TODO: popup/tooltip width available_space: vec2(400.0, std::f32::INFINITY), // TODO: popup/tooltip width
@ -252,6 +272,8 @@ pub struct Region {
/// Doesn't change. /// Doesn't change.
pub(crate) dir: Direction, pub(crate) dir: Direction,
pub(crate) align: Align,
/// Changes only along self.dir /// Changes only along self.dir
pub(crate) cursor: Vec2, pub(crate) cursor: Vec2,
@ -281,10 +303,6 @@ impl Region {
self.data.input() self.data.input()
} }
pub fn cursor(&self) -> Vec2 {
self.cursor
}
pub fn fonts(&self) -> &Fonts { pub fn fonts(&self) -> &Fonts {
&*self.data.fonts &*self.data.fonts
} }
@ -363,6 +381,7 @@ impl Region {
options: self.options, options: self.options,
id: self.id, id: self.id,
dir: self.dir, dir: self.dir,
align: Align::Min,
cursor: self.cursor + indent, cursor: self.cursor + indent,
bounding_size: vec2(0.0, 0.0), bounding_size: vec2(0.0, 0.0),
available_space: self.available_space - indent, available_space: self.available_space - indent,
@ -373,20 +392,21 @@ impl Region {
} }
/// A horizontally centered region of the given width. /// 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 { Region {
data: self.data.clone(), data: self.data.clone(),
options: self.options, options: self.options,
id: self.id, id: self.id,
dir: self.dir, dir: self.dir,
cursor: vec2((self.available_space.x - width) / 2.0, self.cursor.y), cursor: vec2((self.available_space.x - width) / 2.0, self.cursor.y),
align,
bounding_size: vec2(0.0, 0.0), bounding_size: vec2(0.0, 0.0),
available_space: vec2(width, self.available_space.y), available_space: vec2(width, self.available_space.y),
} }
} }
/// Start a region with horizontal layout /// Start a region with horizontal layout
pub fn horizontal<F>(&mut self, add_contents: F) pub fn horizontal<F>(&mut self, align: Align, add_contents: F)
where where
F: FnOnce(&mut Region), F: FnOnce(&mut Region),
{ {
@ -395,6 +415,7 @@ impl Region {
options: self.options, options: self.options,
id: self.id, id: self.id,
dir: Direction::Horizontal, dir: Direction::Horizontal,
align,
cursor: self.cursor, cursor: self.cursor,
bounding_size: vec2(0.0, 0.0), bounding_size: vec2(0.0, 0.0),
available_space: self.available_space, available_space: self.available_space,
@ -425,6 +446,7 @@ impl Region {
options: self.options, options: self.options,
id: self.make_child_id(&("column", col_idx)), id: self.make_child_id(&("column", col_idx)),
dir: Direction::Vertical, dir: Direction::Vertical,
align: self.align,
cursor: self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0), cursor: self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0),
bounding_size: vec2(0.0, 0.0), bounding_size: vec2(0.0, 0.0),
available_space: vec2(column_width, self.available_space.y), 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<Id>) -> InteractInfo { pub fn reserve_space(&mut self, size: Vec2, interaction_id: Option<Id>) -> InteractInfo {
let rect = Rect { let pos = self.reserve_space_without_padding(size + self.options().item_spacing);
pos: self.cursor,
size,
};
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 hovered = rect.contains(self.input().mouse_pos);
let clicked = hovered && self.input().mouse_clicked; 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. /// 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 { 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.cursor.x += size.x;
self.available_space.x -= size.x; self.available_space.x -= size.x;
self.bounding_size.x += size.x; self.bounding_size.x += size.x;
self.bounding_size.y = self.bounding_size.y.max(size.y); self.bounding_size.y = self.bounding_size.y.max(size.y);
} else { } 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.cursor.y += size.y;
self.available_space.y -= size.x; self.available_space.y -= size.x;
self.bounding_size.y += size.y; self.bounding_size.y += size.y;
self.bounding_size.x = self.bounding_size.x.max(size.x); self.bounding_size.x = self.bounding_size.x.max(size.x);
} }
pos
} }
pub fn make_child_id<H: Hash>(&self, child_id: &H) -> Id { pub fn make_child_id<H: Hash>(&self, child_id: &H) -> Id {

View file

@ -20,8 +20,7 @@ pub mod widgets;
pub use crate::{ pub use crate::{
emigui::Emigui, emigui::Emigui,
fonts::TextStyle, fonts::TextStyle,
layout::LayoutOptions, layout::{Align, LayoutOptions, Region},
layout::Region,
painter::{Frame, Painter, Vertex}, painter::{Frame, Painter, Vertex},
style::Style, style::Style,
types::RawInput, types::RawInput,

View file

@ -186,12 +186,14 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
value, value,
} => { } => {
let rect = interact.rect; 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_center_x = remap_clamp(value, min, max, rect.min().x, rect.max().x);
let marker_rect = Rect::from_center_size( let marker_rect = Rect::from_center_size(
vec2(marker_center_x, thin_rect.center().y), vec2(marker_center_x, thin_rect.center().y),
vec2(16.0, 16.0), vec2(thickness, thickness),
); );
out_commands.push(PaintCmd::Rect { out_commands.push(PaintCmd::Rect {
@ -202,7 +204,7 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, style: &Style, cmd: GuiCmd) {
}); });
out_commands.push(PaintCmd::Rect { out_commands.push(PaintCmd::Rect {
corner_radius: 3.0, corner_radius: 6.0,
fill_color: Some(style.interact_fill_color(&interact)), fill_color: Some(style.interact_fill_color(&interact)),
outline: None, outline: None,
rect: marker_rect, rect: marker_rect,

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
fonts::TextStyle, fonts::TextStyle,
layout::{make_id, Direction, GuiResponse, Id, Region}, layout::{make_id, Align, Direction, GuiResponse, Id, Region},
math::{remap_clamp, vec2, Vec2}, math::{remap_clamp, vec2, Vec2},
types::{Color, GuiCmd, PaintCmd}, types::{Color, GuiCmd, PaintCmd},
}; };
@ -41,8 +41,8 @@ impl Widget for Label {
fn add_to(self, region: &mut Region) -> GuiResponse { fn add_to(self, region: &mut Region) -> GuiResponse {
let font = &region.fonts()[self.text_style]; let font = &region.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.width());
region.add_text(region.cursor(), self.text_style, text);
let interact = region.reserve_space(text_size, None); let interact = region.reserve_space(text_size, None);
region.add_text(interact.rect.min(), self.text_style, text);
region.response(interact) region.response(interact)
} }
} }
@ -65,9 +65,9 @@ impl Widget for Button {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &region.fonts()[text_style]; let font = &region.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, region.width()); let (text, text_size) = font.layout_multiline(&self.text, region.width());
let text_cursor = region.cursor() + region.options().button_padding;
let interact = let interact =
region.reserve_space(text_size + 2.0 * region.options().button_padding, Some(id)); 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_graphic(GuiCmd::Button { interact });
region.add_text(text_cursor, text_style, text); region.add_text(text_cursor, text_style, text);
region.response(interact) region.response(interact)
@ -97,9 +97,6 @@ impl<'a> Widget for Checkbox<'a> {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &region.fonts()[text_style]; let font = &region.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, region.width()); 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( let interact = region.reserve_space(
region.options().button_padding region.options().button_padding
+ vec2(region.options().start_icon_width, 0.0) + vec2(region.options().start_icon_width, 0.0)
@ -107,6 +104,9 @@ impl<'a> Widget for Checkbox<'a> {
+ region.options().button_padding, + region.options().button_padding,
Some(id), Some(id),
); );
let text_cursor = interact.rect.min()
+ region.options().button_padding
+ vec2(region.options().start_icon_width, 0.0);
if interact.clicked { if interact.clicked {
*self.checked = !*self.checked; *self.checked = !*self.checked;
} }
@ -146,9 +146,6 @@ impl Widget for RadioButton {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &region.fonts()[text_style]; let font = &region.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, region.width()); 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( let interact = region.reserve_space(
region.options().button_padding region.options().button_padding
+ vec2(region.options().start_icon_width, 0.0) + vec2(region.options().start_icon_width, 0.0)
@ -156,6 +153,9 @@ impl Widget for RadioButton {
+ region.options().button_padding, + region.options().button_padding,
Some(id), 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 { region.add_graphic(GuiCmd::RadioButton {
checked: self.checked, checked: self.checked,
interact, interact,
@ -215,14 +215,18 @@ impl<'a> Widget for Slider<'a> {
if text_on_top { 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.width());
region.add_text(region.cursor(), text_style, text); let pos = region.reserve_space_without_padding(text_size);
region.reserve_space_without_padding(text_size); region.add_text(pos, text_style, text);
naked.add_to(region) naked.add_to(region)
} else { } else {
region.columns(2, |columns| { region.columns(2, |columns| {
let response = naked.add_to(&mut columns[0]); 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].add(label(full_text)); // TODO: centered! columns[1].horizontal(Align::Center, |region| {
region.add(label(full_text));
});
response response
}) })
} }
@ -295,23 +299,21 @@ impl Widget for Separator {
let available_space = region.available_space; let available_space = region.available_space;
let (points, interact) = match region.direction() { let (points, interact) = match region.direction() {
Direction::Horizontal => { Direction::Horizontal => {
let (rect, interact) = let interact = region.reserve_space(vec2(self.width, available_space.y), None);
region.reserve_space(vec2(self.width, available_space.y), None);
( (
vec![ vec![
vec2(rect.center().x, rect.min().y), vec2(interact.rect.center().x, interact.rect.min().y),
vec2(rect.center().x, rect.max().y), vec2(interact.rect.center().x, interact.rect.max().y),
], ],
interact, interact,
) )
} }
Direction::Vertical => { Direction::Vertical => {
let (rect, interact) = let interact = region.reserve_space(vec2(available_space.x, self.width), None);
region.reserve_space(vec2(available_space.x, self.width), None);
( (
vec![ vec![
vec2(rect.min().x, rect.center().y), vec2(interact.rect.min().x, interact.rect.center().y),
vec2(rect.max().x, rect.center().y), vec2(interact.rect.max().x, interact.rect.center().y),
], ],
interact, interact,
) )

View file

@ -1,4 +1,4 @@
use emigui::{math::*, types::*, widgets::*, Region, TextStyle}; use emigui::{math::*, types::*, widgets::*, Align, Region, TextStyle};
pub struct App { pub struct App {
checked: bool, checked: bool,
@ -41,7 +41,7 @@ impl App {
gui.add(Checkbox::new(&mut self.checked, "checkbox")); gui.add(Checkbox::new(&mut self.checked, "checkbox"));
gui.horizontal(|gui| { gui.horizontal(Align::Min, |gui| {
if gui if gui
.add(radio(self.selected_alternative == 0, "First")) .add(radio(self.selected_alternative == 0, "First"))
.clicked .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.corner_radius, 0.0, 50.0).text("corner_radius"));
gui.add(Slider::new(&mut self.stroke_width, 0.0, 10.0).text("stroke_width")); 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 { gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Rect {
corner_radius: self.corner_radius, corner_radius: self.corner_radius,
fill_color: Some(srgba(136, 136, 136, 255)), fill_color: Some(srgba(136, 136, 136, 255)),
@ -88,7 +88,6 @@ impl App {
color: srgba(255, 255, 255, 255), color: srgba(255, 255, 255, 255),
}), }),
}])); }]));
gui.reserve_space(self.size, None);
}); });
} }
} }

View file

@ -5,7 +5,7 @@ extern crate wasm_bindgen;
extern crate emigui; extern crate emigui;
use emigui::{widgets::label, Emigui, RawInput}; use emigui::{widgets::label, Align, Emigui, RawInput};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -46,7 +46,7 @@ impl State {
self.emigui.new_frame(raw_input); self.emigui.new_frame(raw_input);
let mut region = self.emigui.whole_screen_region(); 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.app.show_gui(&mut region);
self.emigui.example(&mut region); self.emigui.example(&mut region);