Simplify and unify queries about available space

This commit is contained in:
Emil Ernerfeldt 2020-05-12 18:21:09 +02:00
parent 6f7bc3cfac
commit 7a1c97ccfe
15 changed files with 91 additions and 108 deletions

View file

@ -61,7 +61,11 @@ impl CollapsingHeader {
let text_pos = ui.cursor() + vec2(ui.style().indent, 0.0); let text_pos = ui.cursor() + vec2(ui.style().indent, 0.0);
let (title, text_size) = label.layout(text_pos, ui); let (title, text_size) = label.layout(text_pos, ui);
let text_max_x = text_pos.x + text_size.x; let text_max_x = text_pos.x + text_size.x;
let desired_width = ui.available_space_min().x.max(text_max_x - ui.cursor().x); let desired_width = ui
.available_finite()
.size()
.x
.max(text_max_x - ui.cursor().x);
let interact = ui.reserve_space( let interact = ui.reserve_space(
vec2( vec2(
@ -115,15 +119,21 @@ impl CollapsingHeader {
if animate { if animate {
ui.indent(id, |child_ui| { ui.indent(id, |child_ui| {
let max_height = if state.open { let max_height = if state.open {
let full_height = state.open_height.unwrap_or(1000.0); if let Some(full_height) = state.open_height {
remap(time_since_toggle, 0.0..=animation_time, 0.0..=full_height) remap(time_since_toggle, 0.0..=animation_time, 0.0..=full_height)
} else {
// First frame of expansion.
// We don't know full height yet, but we will next frame.
// Just use a placehodler value that shows some movement:
10.0
}
} else { } else {
let full_height = state.open_height.unwrap_or_default(); let full_height = state.open_height.unwrap_or_default();
remap_clamp(time_since_toggle, 0.0..=animation_time, full_height..=0.0) remap_clamp(time_since_toggle, 0.0..=animation_time, full_height..=0.0)
}; };
let mut clip_rect = child_ui.clip_rect(); let mut clip_rect = child_ui.clip_rect();
clip_rect.max.y = clip_rect.max.y.min(child_ui.cursor().y + max_height); clip_rect.max.y = clip_rect.max.y.min(child_ui.rect().top() + max_height);
child_ui.set_clip_rect(clip_rect); child_ui.set_clip_rect(clip_rect);
let top_left = child_ui.top_left(); let top_left = child_ui.top_left();

View file

@ -58,18 +58,14 @@ impl Frame {
outline, outline,
} = self; } = self;
let outer_pos = ui.cursor(); let outer_rect = ui.available();
let inner_rect = let inner_rect = outer_rect.expand2(-margin);
Rect::from_min_size(outer_pos + margin, ui.available_space() - 2.0 * margin);
let where_to_put_background = ui.paint_list_len(); let where_to_put_background = ui.paint_list_len();
let mut child_ui = ui.child_ui(inner_rect); let mut child_ui = ui.child_ui(inner_rect);
add_contents(&mut child_ui); add_contents(&mut child_ui);
let inner_size = child_ui.bounding_size(); let outer_rect = Rect::from_min_max(outer_rect.min, child_ui.child_bounds().max + margin);
let inner_size = inner_size.ceil(); // TODO: round to pixel
let outer_rect = Rect::from_min_size(outer_pos, margin + inner_size + margin);
ui.insert_paint_cmd( ui.insert_paint_cmd(
where_to_put_background, where_to_put_background,
@ -81,7 +77,7 @@ impl Frame {
}, },
); );
ui.expand_to_include_child(child_ui.child_bounds().expand2(margin)); ui.expand_to_include_child(outer_rect);
// TODO: move up cursor? // TODO: move cursor in parent ui
} }
} }

View file

@ -31,7 +31,7 @@ pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
ui.set_style(style); ui.set_style(style);
// Take full width and fixed height: // Take full width and fixed height:
ui.expand_to_size(vec2(ui.available_width(), ui.style().menu_bar.height)); ui.expand_to_size(vec2(ui.available().width(), ui.style().menu_bar.height));
add_contents(ui) add_contents(ui)
}) })
}) })

View file

@ -143,9 +143,9 @@ impl Resize {
// TODO: a common trait for Things that follow this pattern // TODO: a common trait for Things that follow this pattern
impl Resize { impl Resize {
pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) { pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
let id = ui.make_child_id("scroll"); let id = ui.make_child_id("resize");
self.min_size = self.min_size.min(ui.available_space()); self.min_size = self.min_size.min(ui.available().size());
self.max_size = self.max_size.min(ui.available_space()); self.max_size = self.max_size.min(ui.available().size());
self.max_size = self.max_size.max(self.min_size); self.max_size = self.max_size.max(self.min_size);
let (is_new, mut state) = match ui.memory().resize.get(&id) { let (is_new, mut state) = match ui.memory().resize.get(&id) {

View file

@ -69,8 +69,8 @@ impl ScrollArea {
}; };
let outer_size = vec2( let outer_size = vec2(
outer_ui.available_width(), outer_ui.available().width(),
outer_ui.available_height().min(self.max_height), outer_ui.available().height().min(self.max_height),
); );
let inner_size = outer_size - vec2(current_scroll_bar_width, 0.0); let inner_size = outer_size - vec2(current_scroll_bar_width, 0.0);

View file

@ -354,7 +354,7 @@ impl Painting {
ui.add_custom_contents(vec2(f32::INFINITY, 200.0), |ui| { ui.add_custom_contents(vec2(f32::INFINITY, 200.0), |ui| {
let canvas_corner = ui.cursor(); let canvas_corner = ui.cursor();
let interact = ui.reserve_space(ui.available_space(), Some(ui.id())); let interact = ui.reserve_space(ui.available().size(), Some(ui.id()));
ui.set_clip_rect(ui.clip_rect().intersect(interact.rect)); // Make sure we don't paint out of bounds ui.set_clip_rect(ui.clip_rect().intersect(interact.rect)); // Make sure we don't paint out of bounds
if self.lines.is_empty() { if self.lines.is_empty() {

View file

@ -123,7 +123,7 @@ impl FractalClock {
Hand::from_length_angle(0.5, angle_from_period(12.0 * 60.0 * 60.0)), Hand::from_length_angle(0.5, angle_from_period(12.0 * 60.0 * 60.0)),
]; ];
let rect = ui.available_rect_min(); let rect = ui.available_finite();
let scale = self.zoom * rect.width().min(rect.height()); let scale = self.zoom * rect.width().min(rect.height());
let mut paint_line = |points: [Pos2; 2], color: Color, width: f32| { let mut paint_line = |points: [Pos2; 2], color: Color, width: f32| {

View file

@ -382,13 +382,13 @@ impl Rect {
/// Expand by this much in each direction, keeping the center /// Expand by this much in each direction, keeping the center
#[must_use] #[must_use]
pub fn expand(self, amnt: f32) -> Self { pub fn expand(self, amnt: f32) -> Self {
Rect::from_center_size(self.center(), self.size() + 2.0 * vec2(amnt, amnt)) self.expand2(Vec2::splat(amnt))
} }
/// Expand by this much in each direction, keeping the center /// Expand by this much in each direction, keeping the center
#[must_use] #[must_use]
pub fn expand2(self, amnt: Vec2) -> Self { pub fn expand2(self, amnt: Vec2) -> Self {
Rect::from_center_size(self.center(), self.size() + 2.0 * amnt) Rect::from_min_max(self.min - amnt, self.max + amnt)
} }
#[must_use] #[must_use]

View file

@ -100,8 +100,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 > ui.available_width() { if size.x > ui.available().width() {
size *= ui.available_width() / size.x; size *= ui.available().width() / size.x;
} }
let interact = ui.reserve_space(size, None); let interact = ui.reserve_space(size, None);
let rect = interact.rect; let rect = interact.rect;

View file

@ -231,33 +231,19 @@ impl Ui {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Layout related measures: // Layout related measures:
/// A zero should be intepreted as "as little as possible". /// The available space at the moment, given the current cursor.
/// An infinite value should be intereted as "as much as you want"
pub fn available_width(&self) -> f32 {
self.available_space().x
}
/// A zero should be intepreted as "as little as possible".
/// An infinite value should be intereted as "as much as you want"
pub fn available_height(&self) -> f32 {
self.available_space().y
}
/// This how much more space we can take up without overflowing our parent. /// This how much more space we can take up without overflowing our parent.
/// Shrinks as cursor increments. /// Shrinks as widgets allocate space and the cursor moves.
/// A zero size should be intepreted as "as little as possible". /// A small rectangle should be intepreted as "as little as possible".
/// An infinite size should be intereted as "as much as you want" /// An infinite rectangle should be interpred as "as much as you want"
pub fn available_space(&self) -> Vec2 { pub fn available(&self) -> Rect {
self.bottom_right() - self.cursor Rect::from_min_max(self.cursor, self.bottom_right())
} }
/// Use this for components that want to grow witout bounds. /// This is like `available()`, but will never be infinite.
pub fn available_space_min(&self) -> Vec2 { /// Use this for components that want to grow without bounds (but shouldn't).
self.finite_bottom_right() - self.cursor pub fn available_finite(&self) -> Rect {
} Rect::from_min_max(self.cursor, self.finite_bottom_right())
pub fn available_rect_min(&self) -> Rect {
Rect::from_min_size(self.cursor, self.available_space_min())
} }
pub fn direction(&self) -> Direction { pub fn direction(&self) -> Direction {
@ -344,7 +330,7 @@ impl Ui {
/// # How sizes are negotiated /// # How sizes are negotiated
/// Each widget should have a *minimum desired size* and a *desired size*. /// 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. /// 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_space()` and use that. /// 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 /// You may get MORE space than you asked for, for instance
/// for `Justified` aligned layouts, like in menus. /// for `Justified` aligned layouts, like in menus.
@ -355,8 +341,8 @@ impl Ui {
self.cursor = self.round_pos_to_pixels(self.cursor); self.cursor = self.round_pos_to_pixels(self.cursor);
// For debug rendering // For debug rendering
let too_wide = child_size.x > self.available_width(); let too_wide = child_size.x > self.available().width();
let too_high = child_size.x > self.available_height(); let too_high = child_size.x > self.available().height();
let rect = self.reserve_space_impl(child_size); let rect = self.reserve_space_impl(child_size);
@ -371,40 +357,19 @@ impl Ui {
let color = color::srgba(200, 0, 0, 255); let color = color::srgba(200, 0, 0, 255);
let width = 2.5; let width = 2.5;
let mut paint_line_seg =
|a, b| self.add_paint_cmd(PaintCmd::line_segment([a, b], color, width));
if too_wide { if too_wide {
self.add_paint_cmd(PaintCmd::line_segment( paint_line_seg(rect.left_top(), rect.left_bottom());
[rect.left_top(), rect.left_bottom()], paint_line_seg(rect.left_center(), rect.right_center());
color, paint_line_seg(rect.right_top(), rect.right_bottom());
width,
));
self.add_paint_cmd(PaintCmd::line_segment(
[rect.left_center(), rect.right_center()],
color,
width,
));
self.add_paint_cmd(PaintCmd::line_segment(
[rect.right_top(), rect.right_bottom()],
color,
width,
));
} }
if too_high { if too_high {
self.add_paint_cmd(PaintCmd::line_segment( paint_line_seg(rect.left_top(), rect.right_top());
[rect.left_top(), rect.right_top()], paint_line_seg(rect.center_top(), rect.center_bottom());
color, paint_line_seg(rect.left_bottom(), rect.right_bottom());
width,
));
self.add_paint_cmd(PaintCmd::line_segment(
[rect.center_top(), rect.center_bottom()],
color,
width,
));
self.add_paint_cmd(PaintCmd::line_segment(
[rect.left_bottom(), rect.right_bottom()],
color,
width,
));
} }
} }
@ -419,12 +384,12 @@ impl Ui {
if self.dir == Direction::Horizontal { if self.dir == Direction::Horizontal {
child_pos.y += match self.align { child_pos.y += match self.align {
Align::Min | Align::Justified => 0.0, Align::Min | Align::Justified => 0.0,
Align::Center => 0.5 * (self.available_height() - child_size.y), Align::Center => 0.5 * (self.available().height() - child_size.y),
Align::Max => self.available_height() - child_size.y, Align::Max => self.available().height() - child_size.y,
}; };
if self.align == Align::Justified && self.available_height().is_finite() { if self.align == Align::Justified && self.available().height().is_finite() {
// Fill full height // Fill full height
child_size.y = child_size.y.max(self.available_height()); child_size.y = child_size.y.max(self.available().height());
} }
self.child_bounds.extend_with(self.cursor + child_size); self.child_bounds.extend_with(self.cursor + child_size);
self.cursor.x += child_size.x; self.cursor.x += child_size.x;
@ -432,12 +397,12 @@ impl Ui {
} else { } else {
child_pos.x += match self.align { child_pos.x += match self.align {
Align::Min | Align::Justified => 0.0, Align::Min | Align::Justified => 0.0,
Align::Center => 0.5 * (self.available_width() - child_size.x), Align::Center => 0.5 * (self.available().width() - child_size.x),
Align::Max => self.available_width() - child_size.x, Align::Max => self.available().width() - child_size.x,
}; };
if self.align == Align::Justified && self.available_width().is_finite() { if self.align == Align::Justified && self.available().width().is_finite() {
// Fill full width // Fill full width
child_size.x = child_size.x.max(self.available_width()); child_size.x = child_size.x.max(self.available().width());
} }
self.child_bounds.extend_with(self.cursor + child_size); self.child_bounds.extend_with(self.cursor + child_size);
self.cursor.y += child_size.y; self.cursor.y += child_size.y;
@ -490,6 +455,18 @@ impl Ui {
self.ctx.debug_text(pos, text); self.ctx.debug_text(pos, text);
} }
pub fn debug_rect(&mut self, rect: Rect, text: &str) {
self.add_paint_cmd(PaintCmd::Rect {
corner_radius: 0.0,
fill_color: None,
outline: Some(Outline::new(1.0, color::RED)),
rect,
});
let align = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
self.floating_text(rect.min, text, text_style, align, Some(color::RED));
}
/// Show some text anywhere in the ui. /// Show some text anywhere in the ui.
/// To center the text at the given position, use `align: (Center, Center)`. /// To center the text at the given position, use `align: (Center, Center)`.
/// If you want to draw text floating on top of everything, /// If you want to draw text floating on top of everything,
@ -565,7 +542,7 @@ impl Ui {
/// After `add_contents` is called the contents of `bounding_size` /// After `add_contents` is called the contents of `bounding_size`
/// will decide how much space will be used in the parent ui. /// will decide how much space will be used in the parent ui.
pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Ui)) { pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Ui)) {
let size = size.min(self.available_space()); let size = size.min(self.available().size());
let child_rect = Rect::from_min_size(self.cursor, size); let child_rect = Rect::from_min_size(self.cursor, size);
let mut child_ui = Ui { let mut child_ui = Ui {
..self.child_ui(child_rect) ..self.child_ui(child_rect)
@ -597,7 +574,7 @@ impl Ui {
// draw a grey line on the left to mark the indented section // draw a grey line on the left to mark the indented section
let line_start = child_rect.min - indent * 0.5; let line_start = child_rect.min - indent * 0.5;
let line_start = line_start.round(); // TODO: round to pixel instead let line_start = line_start.round(); // TODO: round to pixel instead
let line_end = pos2(line_start.x, line_start.y + size.y - 8.0); let line_end = pos2(line_start.x, line_start.y + size.y - 2.0);
self.add_paint_cmd(PaintCmd::line_segment( self.add_paint_cmd(PaintCmd::line_segment(
[line_start, line_end], [line_start, line_end],
gray(150, 255), gray(150, 255),
@ -623,16 +600,16 @@ impl Ui {
pub fn column(&mut self, column_position: Align, mut width: f32) -> Ui { pub fn column(&mut self, column_position: Align, mut width: f32) -> Ui {
let x = match column_position { let x = match column_position {
Align::Min => 0.0, Align::Min => 0.0,
Align::Center => self.available_width() / 2.0 - width / 2.0, Align::Center => self.available().width() / 2.0 - width / 2.0,
Align::Max => self.available_width() - width, Align::Max => self.available().width() - width,
Align::Justified => { Align::Justified => {
width = self.available_width(); width = self.available().width();
0.0 0.0
} }
}; };
self.child_ui(Rect::from_min_size( self.child_ui(Rect::from_min_size(
self.cursor + vec2(x, 0.0), self.cursor + vec2(x, 0.0),
vec2(width, self.available_height()), vec2(width, self.available().height()),
)) ))
} }
@ -678,7 +655,7 @@ impl Ui {
// TODO: ensure there is space // TODO: ensure there is space
let spacing = self.style.item_spacing.x; let spacing = self.style.item_spacing.x;
let total_spacing = spacing * (num_columns as f32 - 1.0); let total_spacing = spacing * (num_columns as f32 - 1.0);
let column_width = (self.available_width() - total_spacing) / (num_columns as f32); let column_width = (self.available().width() - total_spacing) / (num_columns as f32);
let mut columns: Vec<Self> = (0..num_columns) let mut columns: Vec<Self> = (0..num_columns)
.map(|col_idx| { .map(|col_idx| {
@ -707,7 +684,7 @@ impl Ui {
max_height = size.y.max(max_height); max_height = size.y.max(max_height);
} }
let size = vec2(self.available_width().max(sum_width), max_height); let size = vec2(self.available().width().max(sum_width), max_height);
self.reserve_space(size, None); self.reserve_space(size, None);
result result
} }

View file

@ -149,7 +149,7 @@ impl Widget for Hyperlink {
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let line_spacing = font.line_spacing(); let line_spacing = font.line_spacing();
// TODO: underline // TODO: underline
let (text, text_size) = font.layout_multiline(&self.text, ui.available_width()); let (text, text_size) = font.layout_multiline(&self.text, ui.available().width());
let interact = ui.reserve_space(text_size, Some(id)); let interact = ui.reserve_space(text_size, Some(id));
if interact.hovered { if interact.hovered {
ui.ctx().output().cursor_icon = CursorIcon::PointingHand; ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
@ -215,7 +215,7 @@ impl Widget for Button {
let id = ui.make_position_id(); let id = ui.make_position_id();
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, ui.available_width()); let (text, text_size) = font.layout_multiline(&self.text, ui.available().width());
let padding = ui.style().button_padding; let padding = ui.style().button_padding;
let mut size = text_size + 2.0 * padding; let mut size = text_size + 2.0 * padding;
size.y = size.y.max(ui.style().clickable_diameter); size.y = size.y.max(ui.style().clickable_diameter);
@ -341,7 +341,7 @@ impl Widget for RadioButton {
let id = ui.make_position_id(); let id = ui.make_position_id();
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let (text, text_size) = font.layout_multiline(&self.text, ui.available_width()); let (text, text_size) = font.layout_multiline(&self.text, ui.available().width());
let interact = ui.reserve_space( let interact = ui.reserve_space(
ui.style().button_padding ui.style().button_padding
+ vec2(ui.style().start_icon_width, 0.0) + vec2(ui.style().start_icon_width, 0.0)
@ -422,7 +422,7 @@ impl Separator {
impl Widget for Separator { impl Widget for Separator {
fn ui(self, ui: &mut Ui) -> GuiResponse { fn ui(self, ui: &mut Ui) -> GuiResponse {
let available_space = ui.available_space_min(); let available_space = ui.available_finite().size();
let extra = self.extra; let extra = self.extra;
let (points, interact) = match ui.direction() { let (points, interact) = match ui.direction() {

View file

@ -116,7 +116,7 @@ impl<'a> Widget for Slider<'a> {
let slider_sans_text = Slider { text: None, ..self }; let slider_sans_text = Slider { text: None, ..self };
if text_on_top { if text_on_top {
// let (text, text_size) = font.layout_multiline(&full_text, ui.available_width()); // let (text, text_size) = font.layout_multiline(&full_text, ui.available().width());
let (text, text_size) = font.layout_single_line(&full_text); let (text, text_size) = font.layout_single_line(&full_text);
let pos = ui.reserve_space(text_size, None).rect.min; let pos = ui.reserve_space(text_size, None).rect.min;
ui.add_text(pos, text_style, text, text_color); ui.add_text(pos, text_style, text, text_color);
@ -144,7 +144,7 @@ impl<'a> Widget for Slider<'a> {
let interact = ui.reserve_space( let interact = ui.reserve_space(
Vec2 { Vec2 {
x: ui.available_width(), x: ui.available().width(),
y: height, y: height,
}, },
Some(id), Some(id),

View file

@ -40,8 +40,8 @@ impl<'t> Widget for TextEdit<'t> {
let font = &ui.fonts()[self.text_style]; let font = &ui.fonts()[self.text_style];
let line_spacing = font.line_spacing(); let line_spacing = font.line_spacing();
let (text, text_size) = font.layout_multiline(self.text.as_str(), ui.available_width()); let (text, text_size) = font.layout_multiline(self.text.as_str(), ui.available().width());
let desired_size = text_size.max(vec2(ui.available_width(), line_spacing)); let desired_size = text_size.max(vec2(ui.available().width(), line_spacing));
let interact = ui.reserve_space(desired_size, Some(id)); let interact = ui.reserve_space(desired_size, Some(id));
if interact.clicked { if interact.clicked {

View file

@ -110,7 +110,7 @@ fn main() {
ctx.begin_frame(raw_input.clone()); // TODO: avoid clone ctx.begin_frame(raw_input.clone()); // TODO: avoid clone
let mut ui = ctx.fullscreen_ui(); let mut ui = ctx.fullscreen_ui();
example_app.ui(&mut ui); example_app.ui(&mut ui);
let mut ui = ui.centered_column(ui.available_width().min(480.0)); let mut ui = ui.centered_column(ui.available().width().min(480.0));
ui.set_align(Align::Min); ui.set_align(Align::Min);
ui.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading)); ui.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading));
if ui.add(Button::new("Quit")).clicked { if ui.add(Button::new("Quit")).clicked {

View file

@ -42,7 +42,7 @@ impl State {
let mut ui = self.ctx.fullscreen_ui(); let mut ui = self.ctx.fullscreen_ui();
self.example_app.ui(&mut ui); self.example_app.ui(&mut ui);
let mut ui = ui.centered_column(ui.available_width().min(480.0)); let mut ui = ui.centered_column(ui.available().width().min(480.0));
ui.set_align(Align::Min); ui.set_align(Align::Min);
ui.add(label!("Emigui!").text_style(TextStyle::Heading)); 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("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");