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(),
dir: layout::Direction::Vertical,
align: layout::Align::Center,
rect: Rect::from_min_size(Default::default(), self.ctx.input.screen_size),
cursor: 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
pub fn show_popup<F>(ctx: &Arc<Context>, window_pos: Pos2, add_contents: F)
where
@ -105,17 +106,9 @@ where
let style = ctx.style();
let window_padding = style.window_padding;
let mut contents_region = Region {
ctx: ctx.clone(),
layer,
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
};
let size = vec2(ctx.input.screen_size.x.min(350.0), std::f32::INFINITY); // TODO: popup/tooltip width
let inner_rect = Rect::from_min_size(window_pos + window_padding, size);
let mut contents_region = Region::new(ctx.clone(), layer, Id::popup(), inner_rect);
add_contents(&mut contents_region);

View file

@ -272,6 +272,16 @@ impl Rect {
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 {
self.min.x <= p.x
&& p.x <= self.min.x + self.size().x

View file

@ -21,20 +21,42 @@ pub struct Region {
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.
/// Progresses along self.dir
/// Progresses along self.dir.
/// Initially set to rect.min()
pub(crate) cursor: Pos2,
/// Bounding box of children.
/// We keep track of our max-size along the orthogonal to self.dir
/// Initially set to zero.
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 {
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.
/// Can be used for free painting.
/// NOTE: all coordinates are screen coordinates!
@ -63,16 +85,18 @@ impl Region {
&*self.ctx.fonts
}
pub fn width(&self) -> f32 {
self.available_space.x
pub fn available_width(&self) -> f32 {
self.rect.max().x - self.cursor.x
}
pub fn height(&self) -> f32 {
self.available_space.y
pub fn available_height(&self) -> f32 {
self.rect.max().y - self.cursor.y
}
pub fn size(&self) -> Vec2 {
self.available_space
/// This how much more space we can take up without overflowing our parent.
/// Shrinks as cursor increments.
pub fn available_space(&self) -> Vec2 {
self.rect.max() - self.cursor
}
pub fn direction(&self) -> Direction {
@ -103,11 +127,11 @@ impl Region {
let id = self.make_unique_id(&text);
let text_style = TextStyle::Button;
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 interact = self.reserve_space(
vec2(
self.available_space.x,
self.available_width(),
text_size.y + 2.0 * self.style.button_padding.y,
),
Some(id),
@ -180,6 +204,7 @@ impl Region {
F: FnOnce(&mut Region),
{
let indent = vec2(self.style.indent, 0.0);
let region_pos = self.cursor + indent;
let mut child_region = Region {
ctx: self.ctx.clone(),
layer: self.layer,
@ -187,9 +212,9 @@ impl Region {
id: self.id,
dir: self.dir,
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),
available_space: self.available_space - indent,
};
add_contents(&mut child_region);
let size = child_region.bounding_size;
@ -198,16 +223,17 @@ impl Region {
/// Return a sub-region relative to the parent
pub fn relative_region(&mut self, rect: Rect) -> Region {
let region_pos = self.cursor + rect.min().to_vec2();
Region {
ctx: self.ctx.clone(),
layer: self.layer,
style: self.style,
id: self.id,
dir: self.dir,
cursor: self.cursor + rect.min().to_vec2(),
align: self.align,
rect: Rect::from_min_max(region_pos, self.rect.max()),
cursor: region_pos,
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 {
let x = match column_position {
Align::Min => 0.0,
Align::Center => self.available_space.x / 2.0 - width / 2.0,
Align::Max => self.available_space.x - width,
Align::Center => self.available_width() / 2.0 - width / 2.0,
Align::Max => self.available_width() - width,
};
self.relative_region(Rect::from_min_size(
pos2(x, 0.0),
vec2(width, self.available_space.y),
vec2(width, self.available_height()),
))
}
@ -247,9 +273,9 @@ impl Region {
id: self.id,
dir,
align,
rect: Rect::from_min_max(self.cursor, self.rect.max()),
cursor: self.cursor,
bounding_size: vec2(0.0, 0.0),
available_space: self.available_space,
};
add_contents(&mut child_region);
let size = child_region.bounding_size;
@ -285,19 +311,22 @@ impl Region {
// TODO: ensure there is space
let padding = self.style.item_spacing.x;
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)
.map(|col_idx| Region {
ctx: self.ctx.clone(),
layer: self.layer,
style: self.style,
id: self.make_child_region_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),
.map(|col_idx| {
let pos = self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0);
Region {
ctx: self.ctx.clone(),
layer: self.layer,
style: self.style,
id: self.make_child_region_id(&("column", col_idx)),
dir: Direction::Vertical,
align: self.align,
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();
@ -309,7 +338,7 @@ impl Region {
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
}
@ -334,21 +363,19 @@ impl Region {
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,
Align::Center => 0.5 * (self.available_height() - size.y),
Align::Max => self.available_height() - 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,
Align::Center => 0.5 * (self.available_width() - size.x),
Align::Max => self.available_width() - 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);
}

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::{layout::Direction, mesher::Path, widgets::*, *};
use crate::{mesher::Path, widgets::*, *};
#[derive(Clone, Copy, Debug)]
pub struct WindowState {
@ -54,19 +54,33 @@ impl Window {
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)
where
F: FnOnce(&mut Region),
{
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_rect = Rect::from_min_size(default_pos, default_size);
let id = ctx.make_unique_id(&self.title, default_pos);
let mut state = ctx
.memory
.lock()
.get_or_create_window(id, Rect::from_min_size(default_pos, default_size));
let (mut state, is_new_window) = match ctx.memory.lock().get_window(id) {
Some(state) => (state, false),
None => {
let state = WindowState { rect: default_rect };
(state, true)
}
};
let layer = Layer::Window(id);
let where_to_put_background = ctx.graphics.lock().layer(layer).len();
@ -74,17 +88,11 @@ impl Window {
let style = ctx.style();
let window_padding = style.window_padding;
let mut contents_region = Region {
ctx: ctx.clone(),
layer,
style,
id,
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,
};
let inner_rect = Rect::from_min_size(
state.rect.min() + window_padding,
state.rect.size() - 2.0 * window_padding,
);
let mut contents_region = Region::new(ctx.clone(), layer, id, inner_rect);
// Show top bar:
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);
}
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);
}

View file

@ -82,7 +82,7 @@ fn main() {
emigui.new_frame(raw_input);
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.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading));
if region.add(Button::new("Quit")).clicked {
@ -94,12 +94,18 @@ fn main() {
// TODO: Make it even simpler to show a window
Window::new("Test window").show(region.ctx(), |region| {
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))
.expand_to_fit_content(false)
.show(region.ctx(), |region| {
region.add(label!("This might be on top of the other window?"));
region.add(label!("Second line of text"));
region.add(label!(
"This window may shrink so small that its contents no longer fit."
));
});
let mesh = emigui.paint();

View file

@ -43,7 +43,7 @@ impl State {
self.emigui.new_frame(raw_input);
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 is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."));
region.add(label!(