Refactor Region: remember region rectangle

This will be used as a clip rectangle for generated paint commands.
This commit is contained in:
Emil Ernerfeldt 2020-04-20 00:48:54 +02:00
parent 7f85b2623b
commit 472e0b9afe
9 changed files with 128 additions and 83 deletions

View file

@ -49,9 +49,9 @@ impl Emigui {
id: Id::background(), id: Id::background(),
dir: layout::Direction::Vertical, dir: layout::Direction::Vertical,
align: layout::Align::Center, align: layout::Align::Center,
rect: Rect::from_min_size(Default::default(), self.ctx.input.screen_size),
cursor: Default::default(), cursor: Default::default(),
bounding_size: Default::default(), bounding_size: Default::default(),
available_space: self.ctx.input.screen_size,
} }
} }

View file

@ -94,6 +94,7 @@ pub fn align_rect(rect: Rect, align: (Align, Align)) -> Rect {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// TODO: move show_popup, and expand its features (default size, autosize, etc)
/// Show a pop-over window /// Show a pop-over window
pub fn show_popup<F>(ctx: &Arc<Context>, window_pos: Pos2, add_contents: F) pub fn show_popup<F>(ctx: &Arc<Context>, window_pos: Pos2, add_contents: F)
where where
@ -105,17 +106,9 @@ where
let style = ctx.style(); let style = ctx.style();
let window_padding = style.window_padding; let window_padding = style.window_padding;
let mut contents_region = Region { let size = vec2(ctx.input.screen_size.x.min(350.0), std::f32::INFINITY); // TODO: popup/tooltip width
ctx: ctx.clone(), let inner_rect = Rect::from_min_size(window_pos + window_padding, size);
layer, let mut contents_region = Region::new(ctx.clone(), layer, Id::popup(), inner_rect);
style,
id: Id::popup(),
dir: Direction::Vertical,
align: Align::Min,
cursor: window_pos + window_padding,
bounding_size: vec2(0.0, 0.0),
available_space: vec2(ctx.input.screen_size.x.min(350.0), std::f32::INFINITY), // TODO: popup/tooltip width
};
add_contents(&mut contents_region); add_contents(&mut contents_region);

View file

@ -272,6 +272,16 @@ impl Rect {
Rect::from_min_size(self.min() + amnt, self.size()) Rect::from_min_size(self.min() + amnt, self.size())
} }
// keep min
pub fn set_width(&mut self, w: f32) {
self.max.x = self.min.x + w;
}
// keep min
pub fn set_height(&mut self, h: f32) {
self.max.y = self.min.y + h;
}
pub fn contains(&self, p: Pos2) -> bool { pub fn contains(&self, p: Pos2) -> bool {
self.min.x <= p.x self.min.x <= p.x
&& p.x <= self.min.x + self.size().x && p.x <= self.min.x + self.size().x

View file

@ -21,20 +21,42 @@ pub struct Region {
pub(crate) align: Align, pub(crate) align: Align,
// The `rect` represents where in space the region is
// and its max size (original available_space).
// Note that the size may be infinite in one or both dimensions.
// The widgets will TRY to fit within the rect,
// but may overflow (which you will see in bounding_size).
// It will use the rect (which never changes) as a
// clip rect when painting the contents of the region.
pub(crate) rect: Rect,
/// Where the next widget will be put. /// Where the next widget will be put.
/// Progresses along self.dir /// Progresses along self.dir.
/// Initially set to rect.min()
pub(crate) cursor: Pos2, pub(crate) cursor: Pos2,
/// Bounding box of children. /// Bounding box of children.
/// We keep track of our max-size along the orthogonal to self.dir /// We keep track of our max-size along the orthogonal to self.dir
/// Initially set to zero.
pub(crate) bounding_size: Vec2, 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 { impl Region {
pub fn new(ctx: Arc<Context>, layer: Layer, id: Id, rect: Rect) -> Self {
let style = ctx.style();
Region {
ctx,
layer,
style,
id,
dir: Direction::Vertical,
align: Align::Min,
rect,
cursor: rect.min(),
bounding_size: Vec2::default(),
}
}
/// It is up to the caller to make sure there is room for this. /// It is up to the caller to make sure there is room for this.
/// Can be used for free painting. /// Can be used for free painting.
/// NOTE: all coordinates are screen coordinates! /// NOTE: all coordinates are screen coordinates!
@ -63,16 +85,18 @@ impl Region {
&*self.ctx.fonts &*self.ctx.fonts
} }
pub fn width(&self) -> f32 { pub fn available_width(&self) -> f32 {
self.available_space.x self.rect.max().x - self.cursor.x
} }
pub fn height(&self) -> f32 { pub fn available_height(&self) -> f32 {
self.available_space.y self.rect.max().y - self.cursor.y
} }
pub fn size(&self) -> Vec2 { /// This how much more space we can take up without overflowing our parent.
self.available_space /// Shrinks as cursor increments.
pub fn available_space(&self) -> Vec2 {
self.rect.max() - self.cursor
} }
pub fn direction(&self) -> Direction { pub fn direction(&self) -> Direction {
@ -103,11 +127,11 @@ impl Region {
let id = self.make_unique_id(&text); let id = self.make_unique_id(&text);
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &self.fonts()[text_style]; let font = &self.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&text, self.width()); let (text, text_size) = font.layout_multiline(&text, self.available_width());
let text_cursor = self.cursor + self.style.button_padding; let text_cursor = self.cursor + self.style.button_padding;
let interact = self.reserve_space( let interact = self.reserve_space(
vec2( vec2(
self.available_space.x, self.available_width(),
text_size.y + 2.0 * self.style.button_padding.y, text_size.y + 2.0 * self.style.button_padding.y,
), ),
Some(id), Some(id),
@ -180,6 +204,7 @@ impl Region {
F: FnOnce(&mut Region), F: FnOnce(&mut Region),
{ {
let indent = vec2(self.style.indent, 0.0); let indent = vec2(self.style.indent, 0.0);
let region_pos = self.cursor + indent;
let mut child_region = Region { let mut child_region = Region {
ctx: self.ctx.clone(), ctx: self.ctx.clone(),
layer: self.layer, layer: self.layer,
@ -187,9 +212,9 @@ impl Region {
id: self.id, id: self.id,
dir: self.dir, dir: self.dir,
align: Align::Min, align: Align::Min,
cursor: self.cursor + indent, rect: Rect::from_min_max(region_pos, self.rect.max()),
cursor: region_pos,
bounding_size: vec2(0.0, 0.0), bounding_size: vec2(0.0, 0.0),
available_space: self.available_space - indent,
}; };
add_contents(&mut child_region); add_contents(&mut child_region);
let size = child_region.bounding_size; let size = child_region.bounding_size;
@ -198,16 +223,17 @@ impl Region {
/// Return a sub-region relative to the parent /// Return a sub-region relative to the parent
pub fn relative_region(&mut self, rect: Rect) -> Region { pub fn relative_region(&mut self, rect: Rect) -> Region {
let region_pos = self.cursor + rect.min().to_vec2();
Region { Region {
ctx: self.ctx.clone(), ctx: self.ctx.clone(),
layer: self.layer, layer: self.layer,
style: self.style, style: self.style,
id: self.id, id: self.id,
dir: self.dir, dir: self.dir,
cursor: self.cursor + rect.min().to_vec2(),
align: self.align, align: self.align,
rect: Rect::from_min_max(region_pos, self.rect.max()),
cursor: region_pos,
bounding_size: vec2(0.0, 0.0), bounding_size: vec2(0.0, 0.0),
available_space: rect.size(),
} }
} }
@ -215,12 +241,12 @@ impl Region {
pub fn column(&mut self, column_position: Align, width: f32) -> Region { pub fn column(&mut self, column_position: Align, width: f32) -> Region {
let x = match column_position { let x = match column_position {
Align::Min => 0.0, Align::Min => 0.0,
Align::Center => self.available_space.x / 2.0 - width / 2.0, Align::Center => self.available_width() / 2.0 - width / 2.0,
Align::Max => self.available_space.x - width, Align::Max => self.available_width() - width,
}; };
self.relative_region(Rect::from_min_size( self.relative_region(Rect::from_min_size(
pos2(x, 0.0), pos2(x, 0.0),
vec2(width, self.available_space.y), vec2(width, self.available_height()),
)) ))
} }
@ -247,9 +273,9 @@ impl Region {
id: self.id, id: self.id,
dir, dir,
align, align,
rect: Rect::from_min_max(self.cursor, self.rect.max()),
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,
}; };
add_contents(&mut child_region); add_contents(&mut child_region);
let size = child_region.bounding_size; let size = child_region.bounding_size;
@ -285,19 +311,22 @@ impl Region {
// TODO: ensure there is space // TODO: ensure there is space
let padding = self.style.item_spacing.x; let padding = self.style.item_spacing.x;
let total_padding = padding * (num_columns as f32 - 1.0); let total_padding = padding * (num_columns as f32 - 1.0);
let column_width = (self.available_space.x - total_padding) / (num_columns as f32); let column_width = (self.available_width() - total_padding) / (num_columns as f32);
let mut columns: Vec<Region> = (0..num_columns) let mut columns: Vec<Region> = (0..num_columns)
.map(|col_idx| Region { .map(|col_idx| {
ctx: self.ctx.clone(), let pos = self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0);
layer: self.layer, Region {
style: self.style, ctx: self.ctx.clone(),
id: self.make_child_region_id(&("column", col_idx)), layer: self.layer,
dir: Direction::Vertical, style: self.style,
align: self.align, id: self.make_child_region_id(&("column", col_idx)),
cursor: self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0), dir: Direction::Vertical,
bounding_size: vec2(0.0, 0.0), align: self.align,
available_space: vec2(column_width, self.available_space.y), rect: Rect::from_min_max(pos, pos2(pos.x + column_width, self.rect.max().y)),
cursor: pos,
bounding_size: vec2(0.0, 0.0),
}
}) })
.collect(); .collect();
@ -309,7 +338,7 @@ impl Region {
max_height = size.y.max(max_height); max_height = size.y.max(max_height);
} }
self.reserve_space_without_padding(vec2(self.available_space.x, max_height)); self.reserve_space_without_padding(vec2(self.available_width(), max_height));
result result
} }
@ -334,21 +363,19 @@ impl Region {
if self.dir == Direction::Horizontal { if self.dir == Direction::Horizontal {
pos.y += match self.align { pos.y += match self.align {
Align::Min => 0.0, Align::Min => 0.0,
Align::Center => 0.5 * (self.available_space.y - size.y), Align::Center => 0.5 * (self.available_height() - size.y),
Align::Max => self.available_space.y - size.y, Align::Max => self.available_height() - size.y,
}; };
self.cursor.x += size.x; self.cursor.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 { pos.x += match self.align {
Align::Min => 0.0, Align::Min => 0.0,
Align::Center => 0.5 * (self.available_space.x - size.x), Align::Center => 0.5 * (self.available_width() - size.x),
Align::Max => self.available_space.x - size.x, Align::Max => self.available_width() - size.x,
}; };
self.cursor.y += size.y; self.cursor.y += size.y;
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);
} }

View file

@ -103,8 +103,8 @@ impl Texture {
self.height self.height
)); ));
let mut size = vec2(self.width as f32, self.height as f32); let mut size = vec2(self.width as f32, self.height as f32);
if size.x > region.width() { if size.x > region.available_width() {
size *= region.width() / size.x; size *= region.available_width() / size.x;
} }
let interact = region.reserve_space(size, None); let interact = region.reserve_space(size, None);
let rect = interact.rect; let rect = interact.rect;

View file

@ -50,7 +50,7 @@ macro_rules! label {
impl Widget for Label { 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.available_width());
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, self.text_color); region.add_text(interact.rect.min(), self.text_style, text, self.text_color);
region.response(interact) region.response(interact)
@ -83,7 +83,7 @@ impl Widget for Button {
let id = region.make_position_id(); let id = region.make_position_id();
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.available_width());
let padding = region.style().button_padding; let padding = region.style().button_padding;
let mut size = text_size + 2.0 * padding; let mut size = text_size + 2.0 * padding;
size.y = size.y.max(region.style().clickable_diameter); size.y = size.y.max(region.style().clickable_diameter);
@ -129,7 +129,7 @@ impl<'a> Widget for Checkbox<'a> {
let id = region.make_position_id(); let id = region.make_position_id();
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.available_width());
let interact = region.reserve_space( let interact = region.reserve_space(
region.style().button_padding region.style().button_padding
+ vec2(region.style().start_icon_width, 0.0) + vec2(region.style().start_icon_width, 0.0)
@ -203,7 +203,7 @@ impl Widget for RadioButton {
let id = region.make_position_id(); let id = region.make_position_id();
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.available_width());
let interact = region.reserve_space( let interact = region.reserve_space(
region.style().button_padding region.style().button_padding
+ vec2(region.style().start_icon_width, 0.0) + vec2(region.style().start_icon_width, 0.0)
@ -353,7 +353,7 @@ impl<'a> Widget for Slider<'a> {
let naked = Slider { text: None, ..self }; let naked = Slider { text: None, ..self };
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.available_width());
let pos = region.reserve_space_without_padding(text_size); let pos = region.reserve_space_without_padding(text_size);
region.add_text(pos, text_style, text, text_color); region.add_text(pos, text_style, text, text_color);
naked.add_to(region) naked.add_to(region)
@ -361,7 +361,8 @@ impl<'a> Widget for Slider<'a> {
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].rect.set_height(response.rect.size().y); // TODO: explain this line
columns[1].horizontal(Align::Center, |region| { columns[1].horizontal(Align::Center, |region| {
region.add(Label::new(full_text)); region.add(Label::new(full_text));
}); });
@ -378,7 +379,7 @@ impl<'a> Widget for Slider<'a> {
let id = region.make_position_id(); let id = region.make_position_id();
let interact = region.reserve_space( let interact = region.reserve_space(
Vec2 { Vec2 {
x: region.available_space.x, x: region.available_width(),
y: height, y: height,
}, },
Some(id), Some(id),
@ -472,7 +473,7 @@ impl Separator {
impl Widget for Separator { impl Widget for Separator {
fn add_to(self, region: &mut Region) -> GuiResponse { fn add_to(self, region: &mut Region) -> GuiResponse {
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 interact = region.reserve_space(vec2(self.min_length, available_space.y), None); let interact = region.reserve_space(vec2(self.min_length, available_space.y), None);

View file

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{layout::Direction, mesher::Path, widgets::*, *}; use crate::{mesher::Path, widgets::*, *};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct WindowState { pub struct WindowState {
@ -54,19 +54,33 @@ impl Window {
self self
} }
pub fn shrink_to_fit_content(mut self, shrink_to_fit_content: bool) -> Self {
self.shrink_to_fit_content = shrink_to_fit_content;
self
}
pub fn expand_to_fit_content(mut self, expand_to_fit_content: bool) -> Self {
self.expand_to_fit_content = expand_to_fit_content;
self
}
pub fn show<F>(self, ctx: &Arc<Context>, add_contents: F) pub fn show<F>(self, ctx: &Arc<Context>, add_contents: F)
where where
F: FnOnce(&mut Region), F: FnOnce(&mut Region),
{ {
let default_pos = self.default_pos.unwrap_or(pos2(100.0, 100.0)); // TODO let default_pos = self.default_pos.unwrap_or(pos2(100.0, 100.0)); // TODO
let default_size = vec2(200.0, 50.0); // TODO let default_size = vec2(200.0, 50.0); // TODO
let default_rect = Rect::from_min_size(default_pos, default_size);
let id = ctx.make_unique_id(&self.title, default_pos); let id = ctx.make_unique_id(&self.title, default_pos);
let mut state = ctx let (mut state, is_new_window) = match ctx.memory.lock().get_window(id) {
.memory Some(state) => (state, false),
.lock() None => {
.get_or_create_window(id, Rect::from_min_size(default_pos, default_size)); let state = WindowState { rect: default_rect };
(state, true)
}
};
let layer = Layer::Window(id); let layer = Layer::Window(id);
let where_to_put_background = ctx.graphics.lock().layer(layer).len(); let where_to_put_background = ctx.graphics.lock().layer(layer).len();
@ -74,17 +88,11 @@ impl Window {
let style = ctx.style(); let style = ctx.style();
let window_padding = style.window_padding; let window_padding = style.window_padding;
let mut contents_region = Region { let inner_rect = Rect::from_min_size(
ctx: ctx.clone(), state.rect.min() + window_padding,
layer, state.rect.size() - 2.0 * window_padding,
style, );
id, let mut contents_region = Region::new(ctx.clone(), layer, id, inner_rect);
dir: Direction::Vertical,
align: Align::Min,
cursor: state.rect.min() + window_padding,
bounding_size: vec2(0.0, 0.0),
available_space: state.rect.size() - 2.0 * window_padding,
};
// Show top bar: // Show top bar:
contents_region.add(Label::new(self.title).text_style(TextStyle::Heading)); contents_region.add(Label::new(self.title).text_style(TextStyle::Heading));
@ -104,7 +112,7 @@ impl Window {
new_outer_size = new_outer_size.min(desired_outer_size); new_outer_size = new_outer_size.min(desired_outer_size);
} }
if self.expand_to_fit_content { if self.expand_to_fit_content || is_new_window {
new_outer_size = new_outer_size.max(desired_outer_size); new_outer_size = new_outer_size.max(desired_outer_size);
} }

View file

@ -82,7 +82,7 @@ fn main() {
emigui.new_frame(raw_input); emigui.new_frame(raw_input);
let mut region = emigui.background_region(); let mut region = emigui.background_region();
let mut region = region.left_column(region.width().min(480.0)); let mut region = region.left_column(region.available_width().min(480.0));
region.set_align(Align::Min); region.set_align(Align::Min);
region.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading)); region.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading));
if region.add(Button::new("Quit")).clicked { if region.add(Button::new("Quit")).clicked {
@ -94,12 +94,18 @@ fn main() {
// TODO: Make it even simpler to show a window // TODO: Make it even simpler to show a window
Window::new("Test window").show(region.ctx(), |region| { Window::new("Test window").show(region.ctx(), |region| {
region.add(label!("Grab the window and move it around!")); region.add(label!("Grab the window and move it around!"));
region.add(label!(
"This window can be reisized, but not smaller than the contents."
));
}); });
Window::new("Another test window") Window::new("Resize me!")
.default_pos(pos2(400.0, 100.0)) .default_pos(pos2(400.0, 100.0))
.expand_to_fit_content(false)
.show(region.ctx(), |region| { .show(region.ctx(), |region| {
region.add(label!("This might be on top of the other window?")); region.add(label!(
region.add(label!("Second line of text")); "This window may shrink so small that its contents no longer fit."
));
}); });
let mesh = emigui.paint(); let mesh = emigui.paint();

View file

@ -43,7 +43,7 @@ impl State {
self.emigui.new_frame(raw_input); self.emigui.new_frame(raw_input);
let mut region = self.emigui.background_region(); let mut region = self.emigui.background_region();
let mut region = region.centered_column(region.width().min(480.0)); let mut region = region.centered_column(region.available_width().min(480.0));
region.add(label!("Emigui!").text_style(TextStyle::Heading)); region.add(label!("Emigui!").text_style(TextStyle::Heading));
region.add(label!("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.")); region.add(label!("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."));
region.add(label!( region.add(label!(