Browse Source

Refactor: break out Layout to own struct/file

readable-ids
Emil Ernerfeldt 5 years ago
parent
commit
d4204f03c0
  1. 2
      emigui/src/containers/collapsing_header.rs
  2. 3
      emigui/src/containers/menu.rs
  3. 27
      emigui/src/examples/app.rs
  4. 128
      emigui/src/layout.rs
  5. 2
      emigui/src/lib.rs
  6. 96
      emigui/src/ui.rs
  7. 2
      emigui/src/widgets.rs
  8. 3
      emigui/src/widgets/slider.rs
  9. 2
      example_glium/src/main.rs
  10. 3
      example_wasm/src/lib.rs

2
emigui/src/containers/collapsing_header.rs

@ -46,7 +46,7 @@ impl CollapsingHeader {
impl CollapsingHeader {
pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) -> GuiResponse {
assert!(
ui.direction() == Direction::Vertical,
ui.layout().dir() == Direction::Vertical,
"Horizontal collapsing is unimplemented"
);
let Self {

3
emigui/src/containers/menu.rs

@ -79,8 +79,7 @@ pub fn menu(ui: &mut Ui, title: impl Into<String>, add_contents: impl FnOnce(&mu
style.interact.hovered.corner_radius = 0.0;
style.interact.inactive.corner_radius = 0.0;
ui.set_style(style);
ui.set_align(Align::Justified);
ui.set_layout(Layout::justified(Direction::Vertical));
add_contents(ui)
})
})

27
emigui/src/examples/app.rs

@ -445,22 +445,27 @@ use crate::layout::*;
#[serde(default)]
struct LayoutExample {
dir: Direction,
align: Align,
align: Option<Align>, // None == jusitifed
}
impl Default for LayoutExample {
fn default() -> Self {
Self {
dir: Direction::Vertical,
align: Align::Min,
align: Some(Align::Center),
}
}
}
impl LayoutExample {
pub fn ui(&mut self, ui: &mut Ui) {
ui.set_direction(self.dir);
ui.set_align(self.align);
Resize::default()
.default_size(vec2(200.0, 200.0))
.show(ui, |ui| self.contents_ui(ui));
}
pub fn contents_ui(&mut self, ui: &mut Ui) {
ui.set_layout(Layout::from_dir_align(self.dir, self.align));
ui.add(label!("Available space: {:?}", ui.available().size()));
if ui.add(Button::new("Reset")).clicked {
@ -481,19 +486,27 @@ impl LayoutExample {
}
ui.add(Separator::new());
ui.add(label!("Align:"));
for &align in &[Align::Min, Align::Center, Align::Max, Align::Justified] {
for &align in &[Align::Min, Align::Center, Align::Max] {
if ui
.add(RadioButton::new(
self.align == align,
self.align == Some(align),
format!("{:?}", align),
))
.clicked
{
self.align = align;
self.align = Some(align);
}
}
if ui
.add(RadioButton::new(self.align == None, "Justified"))
.tooltip_text("Try to fill full width/heigth (e.g. buttons)")
.clicked
{
self.align = None;
}
}
}

128
emigui/src/layout.rs

@ -1,6 +1,6 @@
use serde_derive::{Deserialize, Serialize};
use crate::math::*;
use crate::{math::*, style::Style};
// ----------------------------------------------------------------------------
@ -29,10 +29,6 @@ pub enum Align {
/// Right/Bottom
/// Note: requires a bounded/known available_width.
Max,
/// Full width/height.
/// Use this when you want
Justified,
}
impl Default for Align {
@ -41,17 +37,135 @@ impl Default for Align {
}
}
/// Used e.g. to anchor a piece of text to a part of the rectangle.
/// Give a position within the rect, specified by the aligns
pub fn align_rect(rect: Rect, align: (Align, Align)) -> Rect {
let x = match align.0 {
Align::Min | Align::Justified => rect.left(),
Align::Min => rect.left(),
Align::Center => rect.left() - 0.5 * rect.width(),
Align::Max => rect.left() - rect.width(),
};
let y = match align.1 {
Align::Min | Align::Justified => rect.top(),
Align::Min => rect.top(),
Align::Center => rect.top() - 0.5 * rect.height(),
Align::Max => rect.top() - rect.height(),
};
Rect::from_min_size(pos2(x, y), rect.size())
}
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct Layout {
/// Lay out things horizontally or vertically?
dir: Direction,
/// For vertical layouts: put things to left, center or right?
/// For horizontal layouts: put things to top, center or bottom?
/// None means justified, which means full width (vertical layout) or height (horizontal layouts).
align: Option<Align>,
}
impl Default for Layout {
fn default() -> Self {
Self {
dir: Direction::Vertical,
align: Some(Align::Min),
}
}
}
impl Layout {
/// None align means justified, e.g. fill full width/height.
pub fn from_dir_align(dir: Direction, align: Option<Align>) -> Self {
Self { dir, align }
}
pub fn vertical(align: Align) -> Self {
Self {
dir: Direction::Vertical,
align: Some(align),
}
}
pub fn horizontal(align: Align) -> Self {
Self {
dir: Direction::Horizontal,
align: Some(align),
}
}
/// Full-width layout.
/// Nice for menues etc where each button is full width.
pub fn justified(dir: Direction) -> Self {
Self { dir, align: None }
}
pub fn dir(&self) -> Direction {
self.dir
}
/// Reserve this much space and move the cursor.
/// Returns where to put the widget.
///
/// # How sizes are negotiated
/// Each widget should have a *minimum desired size* and a *desired size*.
/// When asking for space, ask AT LEAST for you minimum, and don't ask for more than you need.
/// If you want to fill the space, ask about `available().size()` and use that.
///
/// You may get MORE space than you asked for, for instance
/// for `Justified` aligned layouts, like in menus.
///
/// You may get LESS space than you asked for if the current layout won't fit what you asked for.
pub fn allocate_space(
&self,
cursor: &mut Pos2,
style: &Style,
available_size: Vec2,
mut child_size: Vec2,
) -> Rect {
let available_size = available_size.max(child_size);
let mut child_pos = *cursor;
if self.dir == Direction::Horizontal {
if let Some(align) = self.align {
if align != Align::Min {
debug_assert!(available_size.y.is_finite());
debug_assert!(child_size.y.is_finite());
}
child_pos.y += match align {
Align::Min => 0.0,
Align::Center => 0.5 * (available_size.y - child_size.y),
Align::Max => available_size.y - child_size.y,
};
} else {
// justified: fill full height
child_size.y = child_size.y.max(available_size.y);
}
cursor.x += child_size.x;
cursor.x += style.item_spacing.x; // Where to put next thing, if there is a next thing
} else {
if let Some(align) = self.align {
if align != Align::Min {
debug_assert!(available_size.y.is_finite());
debug_assert!(child_size.y.is_finite());
}
child_pos.x += match align {
Align::Min => 0.0,
Align::Center => 0.5 * (available_size.x - child_size.x),
Align::Max => available_size.x - child_size.x,
};
} else {
// justified: fill full width
child_size.x = child_size.x.max(available_size.x);
};
cursor.y += child_size.y;
cursor.y += style.item_spacing.y; // Where to put next thing, if there is a next thing
}
Rect::from_min_size(child_pos, child_size)
}
}

2
emigui/src/lib.rs

@ -49,7 +49,7 @@ pub use {
id::Id,
input::*,
layers::*,
layout::Align,
layout::*,
math::*,
memory::Memory,
mesher::{Mesh, PaintBatches, Vertex},

96
emigui/src/ui.rs

@ -41,18 +41,14 @@ pub struct Ui {
/// Overide default style in this ui
style: Style,
// Layout stuff follows. TODO: move to own type and abstract.
/// Doesn't change.
dir: Direction,
align: Align,
layout: Layout,
/// Where the next widget will be put.
/// Progresses along self.dir.
/// Initially set to rect.min
/// If something has already been added, this will point ot style.item_spacing beyond the latest child.
/// The cursor can thus be style.item_spacing pixels outside of the child_bounds.
cursor: Pos2,
cursor: Pos2, // TODO: move into Layout?
}
impl Ui {
@ -69,9 +65,8 @@ impl Ui {
desired_rect: rect,
child_bounds: Rect::from_min_size(rect.min, Vec2::zero()), // TODO: Rect::nothing() ?
style,
layout: Default::default(),
cursor: rect.min,
dir: Direction::Vertical,
align: Align::Min,
}
}
@ -82,15 +77,14 @@ impl Ui {
let clip_rect = self.clip_rect(); // Keep it unless the child explciitly desires differently
Ui {
ctx: self.ctx.clone(),
layer: self.layer,
style: self.style,
id: self.id,
layer: self.layer,
clip_rect,
desired_rect: child_rect,
cursor: child_rect.min,
child_bounds: Rect::from_min_size(child_rect.min, Vec2::zero()), // TODO: Rect::nothing() ?
dir: self.dir,
align: self.align,
style: self.style,
layout: self.layout,
cursor: child_rect.min,
}
}
@ -248,19 +242,13 @@ impl Ui {
Rect::from_min_max(self.cursor, self.finite_bottom_right())
}
// TODO: remove
pub fn direction(&self) -> Direction {
self.dir
pub fn layout(&self) -> &Layout {
&self.layout
}
// TODO: remove
pub fn set_direction(&mut self, dir: Direction) {
self.dir = dir;
}
// TODO: remove
pub fn set_align(&mut self, align: Align) {
self.align = align;
pub fn set_layout(&mut self, layout: Layout) {
self.layout = layout;
}
// ------------------------------------------------------------------------
@ -332,6 +320,7 @@ impl Ui {
/// Reserve this much space and move the cursor.
/// Returns where to put the widget.
///
/// # How sizes are negotiated
/// Each widget should have a *minimum desired size* and a *desired size*.
/// When asking for space, ask AT LEAST for you minimum, and don't ask for more than you need.
@ -384,40 +373,13 @@ impl Ui {
/// Reserve this much space and move the cursor.
/// Returns where to put the widget.
fn reserve_space_impl(&mut self, mut child_size: Vec2) -> Rect {
fn reserve_space_impl(&mut self, child_size: Vec2) -> Rect {
let available_size = self.available_finite().size();
let available_size = available_size.max(child_size);
let mut child_pos = self.cursor;
if self.dir == Direction::Horizontal {
child_pos.y += match self.align {
Align::Min | Align::Justified => 0.0,
Align::Center => 0.5 * (available_size.y - child_size.y),
Align::Max => available_size.y - child_size.y,
};
if self.align == Align::Justified && available_size.y.is_finite() {
// Fill full height
child_size.y = child_size.y.max(available_size.y);
}
self.child_bounds.extend_with(self.cursor + child_size);
self.cursor.x += child_size.x;
self.cursor.x += self.style.item_spacing.x; // Where to put next thing, if there is a next thing
} else {
child_pos.x += match self.align {
Align::Min | Align::Justified => 0.0,
Align::Center => 0.5 * (available_size.x - child_size.x),
Align::Max => available_size.x - child_size.x,
};
if self.align == Align::Justified && available_size.x.is_finite() {
// Fill full width
child_size.x = child_size.x.max(available_size.x);
}
self.child_bounds.extend_with(self.cursor + child_size);
self.cursor.y += child_size.y;
self.cursor.y += self.style.item_spacing.y; // Where to put next thing, if there is a next thing
}
Rect::from_min_size(child_pos, child_size)
let child_rect =
self.layout
.allocate_space(&mut self.cursor, &self.style, available_size, child_size);
self.child_bounds = self.child_bounds.union(child_rect);
child_rect
}
// ------------------------------------------------
@ -564,14 +526,13 @@ impl Ui {
add_contents: impl FnOnce(&mut Ui),
) -> InteractInfo {
assert!(
self.dir == Direction::Vertical,
self.layout().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_ui = Ui {
id: self.id.with(id_source),
align: Align::Min,
..self.child_ui(child_rect)
};
add_contents(&mut child_ui);
@ -603,15 +564,11 @@ impl Ui {
}
/// A column ui with a given width.
pub fn column(&mut self, column_position: Align, mut width: f32) -> Ui {
pub fn column(&mut self, column_position: Align, width: f32) -> Ui {
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,
Align::Justified => {
width = self.available().width();
0.0
}
};
self.child_ui(Rect::from_min_size(
self.cursor + vec2(x, 0.0),
@ -621,24 +578,22 @@ impl Ui {
/// Start a ui with horizontal layout
pub fn horizontal(&mut self, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo {
self.inner_layout(Direction::Horizontal, Align::Min, add_contents)
self.inner_layout(Layout::horizontal(Align::Min), add_contents)
}
/// Start a ui with vertical layout
pub fn vertical(&mut self, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo {
self.inner_layout(Direction::Vertical, Align::Min, add_contents)
self.inner_layout(Layout::vertical(Align::Min), add_contents)
}
pub fn inner_layout(
&mut self,
dir: Direction,
align: Align,
layout: Layout,
add_contents: impl FnOnce(&mut Self),
) -> InteractInfo {
let child_rect = Rect::from_min_max(self.cursor, self.bottom_right());
let mut child_ui = Self {
dir,
align,
layout,
..self.child_ui(child_rect)
};
add_contents(&mut child_ui);
@ -646,7 +601,7 @@ impl Ui {
self.reserve_space(size, None)
}
/// Temporarily split split a vertical layout into several columns.
/// Temporarily split split an Ui into several columns.
///
/// ``` ignore
/// ui.columns(2, |columns| {
@ -671,7 +626,6 @@ impl Ui {
Self {
id: self.make_child_id(&("column", col_idx)),
dir: Direction::Vertical,
..self.child_ui(child_rect)
}
})

2
emigui/src/widgets.rs

@ -421,7 +421,7 @@ impl Widget for Separator {
let available_space = ui.available_finite().size();
let extra = self.extra;
let (points, interact) = match ui.direction() {
let (points, interact) = match ui.layout().dir() {
Direction::Horizontal => {
let interact = ui.reserve_space(vec2(self.min_spacing, available_space.y), None);
(

3
emigui/src/widgets/slider.rs

@ -128,8 +128,7 @@ impl<'a> Widget for Slider<'a> {
// Place the text in line with the slider on the left:
columns[1].set_desired_height(slider_response.rect.height());
columns[1].horizontal(|ui| {
ui.set_align(Align::Center);
columns[1].inner_layout(Layout::horizontal(Align::Center), |ui| {
ui.add(Label::new(full_text).multiline(false));
});

2
example_glium/src/main.rs

@ -111,7 +111,7 @@ fn main() {
let mut ui = ctx.fullscreen_ui();
example_app.ui(&mut ui);
let mut ui = ui.centered_column(ui.available().width().min(480.0));
ui.set_align(Align::Min);
ui.set_layout(Layout::vertical(Align::Min));
ui.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading));
if ui.add(Button::new("Quit")).clicked {
running = false;

3
example_wasm/src/lib.rs

@ -43,7 +43,7 @@ impl State {
let mut ui = self.ctx.fullscreen_ui();
self.example_app.ui(&mut ui);
let mut ui = ui.centered_column(ui.available().width().min(480.0));
ui.set_align(Align::Min);
ui.set_layout(Layout::vertical(Align::Min));
ui.add(label!("Emigui!").text_style(TextStyle::Heading));
ui.add_label("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
ui.add_label(
@ -57,7 +57,6 @@ impl State {
});
ui.add(Separator::new());
ui.set_align(Align::Min);
ui.add_label("WebGl painter info:");
ui.indent("webgl region id", |ui| {
ui.add_label(self.webgl_painter.debug_info());

Loading…
Cancel
Save