Add support for alignment (min, center, max) in horizontal and vertical layouts
This commit is contained in:
parent
3f84836c20
commit
2c0ca77e09
7 changed files with 78 additions and 45 deletions
|
@ -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,
|
||||
|
|
|
@ -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<F>(&mut self, add_contents: F)
|
||||
pub fn horizontal<F>(&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<Id>) -> 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<H: Hash>(&self, child_id: &H) -> Id {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -186,12 +186,14 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, 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<PaintCmd>, 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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue