diff --git a/CHANGELOG.md b/CHANGELOG.md index e37dd71fe..bb6c7c41e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,13 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [ ## Unreleased - ### Added ⭐ * Add horizontal scrolling support to `ScrollArea` and `Window` (opt-in). +### Changed 🔧 +* All `Ui`:s must now have a finite `max_rect`. + * Deprecated: `max_rect_finite`, `available_size_before_wrap_finite` and `available_rect_before_wrap_finite`. + ## 0.14.2 - 2021-08-28 - Window resize fix diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index a50370f00..6c9133f95 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -274,7 +274,18 @@ impl Prepared { } pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui { - let max_rect = Rect::from_min_size(self.state.pos, Vec2::INFINITY); + let max_rect = if ctx.available_rect().contains(self.state.pos) { + Rect::from_min_max(self.state.pos, ctx.available_rect().max) + } else { + Rect::from_min_max( + self.state.pos, + ctx.input() + .screen_rect() + .max + .max(self.state.pos + Vec2::splat(32.0)), + ) + }; + let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky let bounds = self.drag_bounds.unwrap_or_else(|| ctx.input().screen_rect); diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index d5032c665..582356d9b 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -266,7 +266,7 @@ impl CollapsingHeader { let id = ui.make_persistent_id(id_source); let button_padding = ui.spacing().button_padding; - let available = ui.available_rect_before_wrap_finite(); + let available = ui.available_rect_before_wrap(); let text_pos = available.min + vec2(ui.spacing().indent, 0.0); let galley = label.layout_width(ui, available.right() - text_pos.x); let text_max_x = text_pos.x + galley.size.x; diff --git a/egui/src/containers/panel.rs b/egui/src/containers/panel.rs index 60253ddb2..47482083b 100644 --- a/egui/src/containers/panel.rs +++ b/egui/src/containers/panel.rs @@ -212,7 +212,7 @@ impl SidePanel { panel_ui.expand_to_include_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { - ui.set_min_height(ui.max_rect_finite().height()); // Make sure the frame fills the full height + ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height ui.set_min_width(*width_range.start()); add_contents(ui) }); @@ -473,7 +473,7 @@ impl TopBottomPanel { panel_ui.expand_to_include_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { - ui.set_min_width(ui.max_rect_finite().width()); // Make the frame fill full width + ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width ui.set_min_height(*height_range.start()); add_contents(ui) }); @@ -588,7 +588,7 @@ impl CentralPanel { ) -> InnerResponse { let Self { frame } = self; - let panel_rect = ui.available_rect_before_wrap_finite(); + let panel_rect = ui.available_rect_before_wrap(); let mut panel_ui = ui.child_ui(panel_rect, Layout::top_down(Align::Min)); let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style())); diff --git a/egui/src/grid.rs b/egui/src/grid.rs index 2c0644e86..1b1c5896a 100644 --- a/egui/src/grid.rs +++ b/egui/src/grid.rs @@ -109,11 +109,6 @@ impl GridLayout { } pub(crate) fn available_rect(&self, region: &Region) -> Rect { - // required for putting CollapsingHeader in anything but the last column: - self.available_rect_finite(region) - } - - pub(crate) fn available_rect_finite(&self, region: &Region) -> Rect { let is_last_column = Some(self.col + 1) == self.num_columns; let width = if is_last_column { @@ -135,7 +130,7 @@ impl GridLayout { let available = region.max_rect.intersect(region.cursor); - let height = region.max_rect_finite().max.y - available.top(); + let height = region.max_rect.max.y - available.top(); let height = height .at_least(self.min_cell_size.y) .at_most(self.max_cell_size.y); @@ -351,7 +346,8 @@ impl Grid { // If somebody wants to wrap more things inside a cell, // then we should pick a default layout that matches that alignment, // which we do here: - ui.allocate_ui_at_rect(ui.cursor(), |ui| { + let max_rect = ui.cursor().intersect(ui.max_rect()); + ui.allocate_ui_at_rect(max_rect, |ui| { ui.horizontal(|ui| { let id = ui.make_persistent_id(id_source); let grid = GridLayout { diff --git a/egui/src/layout.rs b/egui/src/layout.rs index 33728cdf2..5a773cc35 100644 --- a/egui/src/layout.rs +++ b/egui/src/layout.rs @@ -43,26 +43,6 @@ pub(crate) struct Region { } impl Region { - /// This is like `max_rect`, but will never be infinite. - /// If the desired rect is infinite ("be as big as you want") - /// this will be bounded by `min_rect` instead. - pub fn max_rect_finite(&self) -> Rect { - let mut result = self.max_rect; - if !result.min.x.is_finite() { - result.min.x = self.min_rect.min.x; - } - if !result.min.y.is_finite() { - result.min.y = self.min_rect.min.y; - } - if !result.max.x.is_finite() { - result.max.x = self.min_rect.max.x; - } - if !result.max.y.is_finite() { - result.max.y = self.min_rect.max.y; - } - result - } - /// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect. pub fn expand_to_include_rect(&mut self, rect: Rect) { self.min_rect = self.min_rect.union(rect); @@ -340,8 +320,8 @@ impl Layout { /// ## Doing layout impl Layout { pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect { - crate::egui_assert!(size.x >= 0.0 && size.y >= 0.0); - crate::egui_assert!(!outer.is_negative()); + egui_assert!(size.x >= 0.0 && size.y >= 0.0); + egui_assert!(!outer.is_negative()); self.align2().align_size_within_rect(size, outer) } @@ -367,7 +347,8 @@ impl Layout { } pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region { - crate::egui_assert!(!max_rect.any_nan()); + egui_assert!(!max_rect.any_nan()); + egui_assert!(max_rect.is_finite()); let mut region = Region { min_rect: Rect::NOTHING, // temporary max_rect, @@ -382,10 +363,6 @@ impl Layout { self.available_from_cursor_max_rect(region.cursor, region.max_rect) } - pub(crate) fn available_rect_before_wrap_finite(&self, region: &Region) -> Rect { - self.available_from_cursor_max_rect(region.cursor, region.max_rect_finite()) - } - /// Amount of space available for a widget. /// For wrapping layouts, this is the maximum (after wrap). pub(crate) fn available_size(&self, r: &Region) -> Vec2 { @@ -406,6 +383,7 @@ impl Layout { fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect { egui_assert!(!cursor.any_nan()); egui_assert!(!max_rect.any_nan()); + egui_assert!(max_rect.is_finite()); // NOTE: in normal top-down layout the cursor has moved below the current max_rect, // but the available shouldn't be negative. @@ -470,7 +448,7 @@ impl Layout { /// Use `justify_and_align` to get the inner `widget_rect`. pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect { region.sanity_check(); - crate::egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); if self.main_wrap { let available_size = self.available_rect_before_wrap(region).size(); @@ -550,9 +528,9 @@ impl Layout { fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect { region.sanity_check(); - crate::egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); - let available_rect = self.available_rect_before_wrap_finite(region); + let available_rect = self.available_rect_before_wrap(region); let mut frame_size = child_size; @@ -591,8 +569,8 @@ impl Layout { /// Apply justify (fill width/height) and/or alignment after calling `next_space`. pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect { - crate::egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); - crate::egui_assert!(!frame.is_negative()); + egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + egui_assert!(!frame.is_negative()); if self.horizontal_justify() { child_size.x = child_size.x.at_least(frame.width()); // fill full width @@ -610,10 +588,10 @@ impl Layout { ) -> Rect { let frame = self.next_frame_ignore_wrap(region, size); let rect = self.align_size_within_rect(size, frame); - crate::egui_assert!(!rect.any_nan()); - crate::egui_assert!(!rect.is_negative()); - crate::egui_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY); - crate::egui_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY); + egui_assert!(!rect.any_nan()); + egui_assert!(!rect.is_negative()); + egui_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY); + egui_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY); rect } diff --git a/egui/src/menu.rs b/egui/src/menu.rs index db2810a0a..1d4e3c4d5 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -70,6 +70,40 @@ pub fn menu( menu_impl(ui, title, Box::new(add_contents)) } +pub(crate) fn menu_ui<'c, R>( + ctx: &CtxRef, + menu_id: impl std::hash::Hash, + pos: Pos2, + mut style: Style, + add_contents: impl FnOnce(&mut Ui) -> R + 'c, +) -> InnerResponse { + let area = Area::new(menu_id) + .order(Order::Foreground) + .fixed_pos(pos) + .interactable(false) + .drag_bounds(Rect::EVERYTHING); + let frame = Frame::menu(&style); + + area.show(ctx, |ui| { + frame + .show(ui, |ui| { + const DEFAULT_MENU_WIDTH: f32 = 150.0; // TODO: add to ui.spacing + ui.set_max_width(DEFAULT_MENU_WIDTH); + + // style.visuals.widgets.active.bg_fill = Color32::TRANSPARENT; + style.visuals.widgets.active.bg_stroke = Stroke::none(); + // style.visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT; + style.visuals.widgets.hovered.bg_stroke = Stroke::none(); + style.visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT; + style.visuals.widgets.inactive.bg_stroke = Stroke::none(); + ui.set_style(style); + ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents) + .inner + }) + .inner + }) +} + #[allow(clippy::needless_pass_by_value)] fn menu_impl<'c, R>( ui: &mut Ui, @@ -103,30 +137,14 @@ fn menu_impl<'c, R>( let inner = if bar_state.open_menu == Some(menu_id) || ui.ctx().memory().everything_is_visible() { - let area = Area::new(menu_id) - .order(Order::Foreground) - .fixed_pos(button_response.rect.left_bottom()); - let frame = Frame::menu(ui.style()); - - let inner = area - .show(ui.ctx(), |ui| { - frame - .show(ui, |ui| { - let mut style = (**ui.style()).clone(); - style.spacing.button_padding = vec2(2.0, 0.0); - // style.visuals.widgets.active.bg_fill = Color32::TRANSPARENT; - style.visuals.widgets.active.bg_stroke = Stroke::none(); - // style.visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT; - style.visuals.widgets.hovered.bg_stroke = Stroke::none(); - style.visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT; - style.visuals.widgets.inactive.bg_stroke = Stroke::none(); - ui.set_style(style); - ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents) - .inner - }) - .inner - }) - .inner; + let inner = menu_ui( + ui.ctx(), + menu_id, + button_response.rect.left_bottom(), + ui.style().as_ref().clone(), + add_contents, + ) + .inner; // TODO: this prevents sub-menus in menus. We should fix that. if ui.input().key_pressed(Key::Escape) || button_response.clicked_elsewhere() { diff --git a/egui/src/placer.rs b/egui/src/placer.rs index 2a32aa6bc..aeafba25c 100644 --- a/egui/src/placer.rs +++ b/egui/src/placer.rs @@ -58,11 +58,6 @@ impl Placer { self.region.max_rect } - #[inline(always)] - pub(crate) fn max_rect_finite(&self) -> Rect { - self.region.max_rect_finite() - } - #[inline(always)] pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) { self.region.min_rect = min_rect; @@ -96,14 +91,6 @@ impl Placer { } } - pub(crate) fn available_rect_before_wrap_finite(&self) -> Rect { - if let Some(grid) = &self.grid { - grid.available_rect_finite(&self.region) - } else { - self.layout.available_rect_before_wrap_finite(&self.region) - } - } - /// Amount of space available for a widget. /// For wrapping layouts, this is the maximum (after wrap). pub(crate) fn available_size(&self) -> Vec2 { diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 172e4c78e..5701e001f 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -367,10 +367,9 @@ impl Ui { self.placer.max_rect() } - /// This is like `max_rect()`, but will never be infinite. - /// This can be useful for widgets that expand to fit the available space. + #[deprecated = "Use .max_rect() instead"] pub fn max_rect_finite(&self) -> Rect { - self.placer.max_rect_finite() + self.max_rect() } /// Used for animation, kind of hacky @@ -501,22 +500,18 @@ impl Ui { self.placer.available_rect_before_wrap().size() } - /// This is like `available_size_before_wrap()`, but will never be infinite. - /// This can be useful for widgets that expand to fit the available space. - /// In most layouts the next widget will be put in the top left corner of this `Rect`. + #[deprecated = "Use .available_size_before_wrap() instead"] pub fn available_size_before_wrap_finite(&self) -> Vec2 { - self.placer.available_rect_before_wrap_finite().size() + self.available_size_before_wrap() } pub fn available_rect_before_wrap(&self) -> Rect { self.placer.available_rect_before_wrap() } - /// This is like `available_rect_before_wrap()`, but will never be infinite. - /// This can be useful for widgets that expand to fit the available space. - /// In most layouts the next widget will be put in the top left corner of this `Rect`. + #[deprecated = "Use .available_rect_before_wrap() instead"] pub fn available_rect_before_wrap_finite(&self) -> Rect { - self.placer.available_rect_before_wrap_finite() + self.available_rect_before_wrap() } } @@ -812,6 +807,7 @@ impl Ui { max_rect: Rect, add_contents: impl FnOnce(&mut Self) -> R, ) -> InnerResponse { + egui_assert!(max_rect.is_finite()); let mut child_ui = self.child_ui(max_rect, *self.layout()); let ret = add_contents(&mut child_ui); let final_child_rect = child_ui.min_rect(); @@ -1649,7 +1645,7 @@ impl Ui { /// Shows the given text where the next widget is to be placed /// if when [`Context::set_debug_on_hover`] has been turned on and the mouse is hovering the Ui. pub fn trace_location(&self, text: impl ToString) { - let rect = self.max_rect_finite(); + let rect = self.max_rect(); if self.style().debug.debug_on_hover && self.rect_contains_pointer(rect) { self.placer .debug_paint_cursor(&self.ctx().debug_painter(), text); diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 220cddad3..585123f6b 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -382,7 +382,7 @@ impl Widget for Plot { if let (Some(height), Some(aspect)) = (height, view_aspect) { height * aspect } else { - ui.available_size_before_wrap_finite().x + ui.available_size_before_wrap().x } }) .at_least(min_size.x); @@ -392,7 +392,7 @@ impl Widget for Plot { if let Some(aspect) = view_aspect { width / aspect } else { - ui.available_size_before_wrap_finite().y + ui.available_size_before_wrap().y } }) .at_least(min_size.y); diff --git a/egui/src/widgets/progress_bar.rs b/egui/src/widgets/progress_bar.rs index a42dd385f..50ae160de 100644 --- a/egui/src/widgets/progress_bar.rs +++ b/egui/src/widgets/progress_bar.rs @@ -67,8 +67,8 @@ impl Widget for ProgressBar { ui.ctx().request_repaint(); } - let desired_width = desired_width - .unwrap_or_else(|| ui.available_size_before_wrap_finite().x.at_least(96.0)); + let desired_width = + desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0)); let height = ui.spacing().interact_size.y; let (outer_rect, response) = ui.allocate_exact_size(vec2(desired_width, height), Sense::hover()); diff --git a/egui/src/widgets/separator.rs b/egui/src/widgets/separator.rs index 6463ad8db..ac6792864 100644 --- a/egui/src/widgets/separator.rs +++ b/egui/src/widgets/separator.rs @@ -59,7 +59,7 @@ impl Widget for Separator { let is_horizontal_line = is_horizontal_line .unwrap_or_else(|| ui.is_grid() || !ui.layout().main_dir().is_horizontal()); - let available_space = ui.available_size_before_wrap_finite(); + let available_space = ui.available_size_before_wrap(); let size = if is_horizontal_line { vec2(available_space.x, spacing) diff --git a/egui_demo_lib/src/apps/demo/layout_test.rs b/egui_demo_lib/src/apps/demo/layout_test.rs index adb305a09..e32972a75 100644 --- a/egui_demo_lib/src/apps/demo/layout_test.rs +++ b/egui_demo_lib/src/apps/demo/layout_test.rs @@ -53,18 +53,12 @@ impl super::View for LayoutTest { if self.main_wrap { if self.main_dir.is_horizontal() { ui.allocate_ui( - vec2( - ui.available_size_before_wrap_finite().x, - self.wrap_row_height, - ), + vec2(ui.available_size_before_wrap().x, self.wrap_row_height), |ui| ui.with_layout(self.layout(), demo_ui), ); } else { ui.allocate_ui( - vec2( - self.wrap_column_width, - ui.available_size_before_wrap_finite().y, - ), + vec2(self.wrap_column_width, ui.available_size_before_wrap().y), |ui| ui.with_layout(self.layout(), demo_ui), ); } diff --git a/egui_demo_lib/src/apps/demo/multi_touch.rs b/egui_demo_lib/src/apps/demo/multi_touch.rs index f1cbff7a1..bb4c71485 100644 --- a/egui_demo_lib/src/apps/demo/multi_touch.rs +++ b/egui_demo_lib/src/apps/demo/multi_touch.rs @@ -63,7 +63,7 @@ impl super::View for MultiTouch { // set up the drawing canvas with normalized coordinates: let (response, painter) = - ui.allocate_painter(ui.available_size_before_wrap_finite(), Sense::drag()); + ui.allocate_painter(ui.available_size_before_wrap(), Sense::drag()); // normalize painter coordinates to ±1 units in each direction with [0,0] in the center: let painter_proportions = response.rect.square_proportions(); let to_screen = RectTransform::from_to( diff --git a/egui_demo_lib/src/apps/demo/painting.rs b/egui_demo_lib/src/apps/demo/painting.rs index f16d18ddd..ed4f49327 100644 --- a/egui_demo_lib/src/apps/demo/painting.rs +++ b/egui_demo_lib/src/apps/demo/painting.rs @@ -31,7 +31,7 @@ impl Painting { pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response { let (mut response, painter) = - ui.allocate_painter(ui.available_size_before_wrap_finite(), Sense::drag()); + ui.allocate_painter(ui.available_size_before_wrap(), Sense::drag()); let to_screen = emath::RectTransform::from_to( Rect::from_min_size(Pos2::ZERO, response.rect.square_proportions()), diff --git a/egui_demo_lib/src/apps/fractal_clock.rs b/egui_demo_lib/src/apps/fractal_clock.rs index 4f9561444..d7b94e537 100644 --- a/egui_demo_lib/src/apps/fractal_clock.rs +++ b/egui_demo_lib/src/apps/fractal_clock.rs @@ -54,7 +54,7 @@ impl FractalClock { let painter = Painter::new( ui.ctx().clone(), ui.layer_id(), - ui.available_rect_before_wrap_finite(), + ui.available_rect_before_wrap(), ); self.paint(&painter); // Make sure we allocate what we used (everything) diff --git a/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs b/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs index 68a61b72a..96ae022ae 100644 --- a/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs +++ b/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs @@ -68,7 +68,7 @@ pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) { let where_to_put_background = ui.painter().add(Shape::Noop); let mut rect = ui.monospace(code).rect; rect = rect.expand(1.0); // looks better - rect.max.x = ui.max_rect_finite().max.x; + rect.max.x = ui.max_rect().max.x; let code_bg_color = ui.visuals().code_bg_color; ui.painter().set( where_to_put_background, diff --git a/egui_demo_lib/src/frame_history.rs b/egui_demo_lib/src/frame_history.rs index b8d52be70..bebe2e16e 100644 --- a/egui_demo_lib/src/frame_history.rs +++ b/egui_demo_lib/src/frame_history.rs @@ -64,7 +64,7 @@ impl FrameHistory { // TODO: we should not use `slider_width` as default graph width. let height = ui.spacing().slider_width; - let size = vec2(ui.available_size_before_wrap_finite().x, height); + let size = vec2(ui.available_size_before_wrap().x, height); let (rect, response) = ui.allocate_at_least(size, Sense::hover()); let style = ui.style().noninteractive();