Refactor Region: remember region rectangle
This will be used as a clip rectangle for generated paint commands.
This commit is contained in:
parent
7f85b2623b
commit
472e0b9afe
9 changed files with 128 additions and 83 deletions
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -50,7 +50,7 @@ macro_rules! label {
|
|||
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());
|
||||
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 = ®ion.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 = ®ion.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 = ®ion.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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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!(
|
||||
|
|
Loading…
Reference in a new issue