diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eecf320f..b65dc5638 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Added ⭐ + +* Add `ui.allocate_at_least` and `ui.allocate_exact_size`. ## 0.7.0 - 2021-01-04 diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index 6c1a74495..a01c9a47b 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -7,6 +7,7 @@ use crate::{ impl Texture { pub fn ui(&self, ui: &mut Ui) { + // Show font texture in demo Ui ui.label(format!( "Texture size: {} x {} (hover to zoom)", self.width, self.height @@ -18,8 +19,7 @@ impl Texture { if size.x > ui.available_width() { size *= ui.available_width() / size.x; } - let response = ui.allocate_response(size, Sense::hover()); - let rect = response.rect; + let (rect, response) = ui.allocate_at_least(size, Sense::hover()); let mut triangles = Triangles::default(); triangles.add_rect_with_uv( rect, diff --git a/egui/src/ui.rs b/egui/src/ui.rs index a53b3a318..64dc8d1f4 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -468,6 +468,27 @@ impl Ui { self.interact(rect, id, sense) } + /// Returns a `Rect` with exactly what you asked for. + /// + /// The response rect will be larger if this is part of a justified layout or similar. + /// This means that iof this is a narrow widget in a wide justified layout, then + /// the widget will react to interactions outside the returned `Rect`. + pub fn allocate_exact_size(&mut self, desired_size: Vec2, sense: Sense) -> (Rect, Response) { + let response = self.allocate_response(desired_size, sense); + let rect = self + .layout() + .align_size_within_rect(desired_size, response.rect); + (rect, response) + } + + /// Allocate at least as much space as needed, and interact with that rect. + /// + /// The returned `Rect` will be the same size as `Response::rect`. + pub fn allocate_at_least(&mut self, desired_size: Vec2, sense: Sense) -> (Rect, Response) { + let response = self.allocate_response(desired_size, sense); + (response.rect, response) + } + /// Reserve this much space and move the cursor. /// Returns where to put the widget. /// diff --git a/egui/src/widgets/button.rs b/egui/src/widgets/button.rs index f977f6c16..cdb461ac0 100644 --- a/egui/src/widgets/button.rs +++ b/egui/src/widgets/button.rs @@ -106,23 +106,19 @@ impl Widget for Button { desired_size.y = desired_size.y.at_least(ui.style().spacing.interact_size.y); } - let response = ui.allocate_response(desired_size, sense); + let (rect, response) = ui.allocate_at_least(desired_size, sense); - if ui.clip_rect().intersects(response.rect) { + if ui.clip_rect().intersects(rect) { let visuals = ui.style().interact(&response); let text_cursor = ui .layout() - .align_size_within_rect(galley.size, response.rect.shrink2(button_padding)) + .align_size_within_rect(galley.size, rect.shrink2(button_padding)) .min; if frame { let fill = fill.unwrap_or(visuals.bg_fill); - ui.painter().rect( - response.rect, - visuals.corner_radius, - fill, - visuals.bg_stroke, - ); + ui.painter() + .rect(rect, visuals.corner_radius, fill, visuals.bg_stroke); } let text_color = text_color @@ -189,10 +185,7 @@ impl<'a> Widget for Checkbox<'a> { let mut desired_size = total_extra + galley.size; desired_size = desired_size.at_least(spacing.interact_size); desired_size.y = desired_size.y.max(icon_width); - let response = ui.allocate_response(desired_size, Sense::click()); - let rect = ui - .layout() - .align_size_within_rect(desired_size, response.rect); + let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); if response.clicked { *checked = !*checked; } @@ -283,10 +276,7 @@ impl Widget for RadioButton { let mut desired_size = total_extra + galley.size; desired_size = desired_size.at_least(ui.style().spacing.interact_size); desired_size.y = desired_size.y.max(icon_width); - let response = ui.allocate_response(desired_size, Sense::click()); - let rect = ui - .layout() - .align_size_within_rect(desired_size, response.rect); + let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); let text_cursor = pos2( rect.min.x + button_padding.x + icon_width + icon_spacing, @@ -390,28 +380,27 @@ impl Widget for ImageButton { let button_padding = ui.style().spacing.button_padding; let desired_size = image.desired_size() + 2.0 * button_padding; - let response = ui.allocate_response(desired_size, sense); + let (rect, response) = ui.allocate_at_least(desired_size, sense); - if ui.clip_rect().intersects(response.rect) { + if ui.clip_rect().intersects(rect) { let visuals = ui.style().interact(&response); if selected { let selection = ui.style().visuals.selection; ui.painter() - .rect(response.rect, 0.0, selection.bg_fill, selection.stroke); + .rect(rect, 0.0, selection.bg_fill, selection.stroke); } else if frame { ui.painter().rect( - response.rect, + rect, visuals.corner_radius, visuals.bg_fill, visuals.bg_stroke, ); } - let image_rect = ui.layout().align_size_within_rect( - image.desired_size(), - response.rect.shrink2(button_padding), - ); + let image_rect = ui + .layout() + .align_size_within_rect(image.desired_size(), rect.shrink2(button_padding)); image.paint_at(ui, image_rect); } diff --git a/egui/src/widgets/color_picker.rs b/egui/src/widgets/color_picker.rs index 8f945c4cb..2c6310710 100644 --- a/egui/src/widgets/color_picker.rs +++ b/egui/src/widgets/color_picker.rs @@ -45,10 +45,10 @@ pub fn show_color(ui: &mut Ui, color: impl Into, desired_size: Vec2) -> } fn show_srgba(ui: &mut Ui, srgba: Color32, desired_size: Vec2) -> Response { - let response = ui.allocate_response(desired_size, Sense::hover()); - background_checkers(ui.painter(), response.rect); + let (rect, response) = ui.allocate_at_least(desired_size, Sense::hover()); + background_checkers(ui.painter(), rect); ui.painter().add(PaintCmd::Rect { - rect: response.rect, + rect, corner_radius: 2.0, fill: srgba, stroke: Stroke::new(3.0, srgba.to_opaque()), @@ -58,11 +58,11 @@ fn show_srgba(ui: &mut Ui, srgba: Color32, desired_size: Vec2) -> Response { fn color_button(ui: &mut Ui, color: Color32) -> Response { let desired_size = ui.style().spacing.interact_size; - let response = ui.allocate_response(desired_size, Sense::click()); + let (rect, response) = ui.allocate_at_least(desired_size, Sense::click()); let visuals = ui.style().interact(&response); - background_checkers(ui.painter(), response.rect); + background_checkers(ui.painter(), rect); ui.painter().add(PaintCmd::Rect { - rect: response.rect, + rect, corner_radius: visuals.corner_radius.at_most(2.0), fill: color, stroke: visuals.fg_stroke, @@ -77,8 +77,7 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color ui.style().spacing.slider_width, ui.style().spacing.interact_size.y * 2.0, ); - let response = ui.allocate_response(desired_size, Sense::click_and_drag()); - let rect = response.rect; + let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag()); if response.active { if let Some(mpos) = ui.input().mouse.pos { @@ -135,8 +134,7 @@ fn color_slider_2d( color_at: impl Fn(f32, f32) -> Color32, ) -> Response { let desired_size = Vec2::splat(ui.style().spacing.slider_width); - let response = ui.allocate_response(desired_size, Sense::click_and_drag()); - let rect = response.rect; + let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag()); if response.active { if let Some(mpos) = ui.input().mouse.pos { diff --git a/egui/src/widgets/hyperlink.rs b/egui/src/widgets/hyperlink.rs index bd6f3f686..bf04fbc66 100644 --- a/egui/src/widgets/hyperlink.rs +++ b/egui/src/widgets/hyperlink.rs @@ -46,7 +46,7 @@ impl Widget for Hyperlink { let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style); let font = &ui.fonts()[text_style]; let galley = font.layout_multiline(text, ui.available_width()); - let response = ui.allocate_response(galley.size, Sense::click()); + let (rect, response) = ui.allocate_exact_size(galley.size, Sense::click()); if response.hovered { ui.ctx().output().cursor_icon = CursorIcon::PointingHand; @@ -61,7 +61,7 @@ impl Widget for Hyperlink { if response.hovered { // Underline: for line in &galley.rows { - let pos = response.rect.min; + let pos = rect.min; let y = pos.y + line.y_max; let y = ui.painter().round_to_pixel(y); let min_x = pos.x + line.min_x(); @@ -73,8 +73,7 @@ impl Widget for Hyperlink { } } - ui.painter() - .galley(response.rect.min, galley, text_style, color); + ui.painter().galley(rect.min, galley, text_style, color); response.on_hover_text(url) } diff --git a/egui/src/widgets/image.rs b/egui/src/widgets/image.rs index 640613a98..852ddd8e6 100644 --- a/egui/src/widgets/image.rs +++ b/egui/src/widgets/image.rs @@ -73,8 +73,8 @@ impl Image { impl Widget for Image { fn ui(self, ui: &mut Ui) -> Response { - let response = ui.allocate_response(self.desired_size, Sense::hover()); - self.paint_at(ui, response.rect); + let (rect, response) = ui.allocate_at_least(self.desired_size, Sense::hover()); + self.paint_at(ui, rect); response } } diff --git a/egui/src/widgets/label.rs b/egui/src/widgets/label.rs index eb41285f1..0b5a955cc 100644 --- a/egui/src/widgets/label.rs +++ b/egui/src/widgets/label.rs @@ -157,10 +157,7 @@ impl Widget for Label { total_response } else { let galley = self.layout(ui); - let response = ui.allocate_response(galley.size, Sense::click()); - let rect = ui - .layout() - .align_size_within_rect(galley.size, response.rect); + let (rect, response) = ui.allocate_exact_size(galley.size, Sense::click()); self.paint_galley(ui, rect.min, galley); response } diff --git a/egui/src/widgets/selected_label.rs b/egui/src/widgets/selected_label.rs index b325dee04..ebd734c86 100644 --- a/egui/src/widgets/selected_label.rs +++ b/egui/src/widgets/selected_label.rs @@ -33,11 +33,11 @@ impl Widget for SelectableLabel { let mut desired_size = total_extra + galley.size; desired_size = desired_size.at_least(ui.style().spacing.interact_size); - let response = ui.allocate_response(desired_size, Sense::click()); + let (rect, response) = ui.allocate_at_least(desired_size, Sense::click()); let text_cursor = pos2( - response.rect.min.x + button_padding.x, - response.rect.center().y - 0.5 * galley.size.y, + rect.min.x + button_padding.x, + rect.center().y - 0.5 * galley.size.y, ); let visuals = ui.style().interact(&response); @@ -48,8 +48,7 @@ impl Widget for SelectableLabel { } else { Default::default() }; - ui.painter() - .rect(response.rect, 0.0, bg_fill, visuals.bg_stroke); + ui.painter().rect(rect, 0.0, bg_fill, visuals.bg_stroke); } let text_color = ui diff --git a/egui/src/widgets/separator.rs b/egui/src/widgets/separator.rs index 83a4d9731..53507c9af 100644 --- a/egui/src/widgets/separator.rs +++ b/egui/src/widgets/separator.rs @@ -30,8 +30,7 @@ impl Widget for Separator { vec2(available_space.x, spacing) }; - let response = ui.allocate_response(size, Sense::hover()); - let rect = response.rect; + let (rect, response) = ui.allocate_at_least(size, Sense::hover()); let points = if ui.layout().main_dir().is_horizontal() { [ pos2(rect.center().x, rect.top()), diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index 0747600ca..4772e0908 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -294,10 +294,10 @@ impl ColorTest { fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Response { use egui::paint::*; - let response = ui.allocate_response(GRADIENT_SIZE, Sense::hover()); + let (rect, response) = ui.allocate_at_least(GRADIENT_SIZE, Sense::hover()); if bg_fill != Default::default() { let mut triangles = Triangles::default(); - triangles.add_colored_rect(response.rect, bg_fill); + triangles.add_colored_rect(rect, bg_fill); ui.painter().add(PaintCmd::triangles(triangles)); } { @@ -306,9 +306,9 @@ fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Respon let mut triangles = Triangles::default(); for (i, &color) in gradient.0.iter().enumerate() { let t = i as f32 / (n as f32 - 1.0); - let x = lerp(response.rect.x_range(), t); - triangles.colored_vertex(pos2(x, response.rect.top()), color); - triangles.colored_vertex(pos2(x, response.rect.bottom()), color); + let x = lerp(rect.x_range(), t); + triangles.colored_vertex(pos2(x, rect.top()), color); + triangles.colored_vertex(pos2(x, rect.bottom()), color); if i < n - 1 { let i = i as u32; triangles.add_triangle(2 * i, 2 * i + 1, 2 * i + 2); diff --git a/egui_demo_lib/src/apps/demo/demo_window.rs b/egui_demo_lib/src/apps/demo/demo_window.rs index 81d13cb65..0af9f68ae 100644 --- a/egui_demo_lib/src/apps/demo/demo_window.rs +++ b/egui_demo_lib/src/apps/demo/demo_window.rs @@ -89,10 +89,10 @@ impl DemoWindow { ui.horizontal(|ui| { ui.label("You can pretty easily paint your own small icons:"); use std::f32::consts::TAU; - let response = ui.allocate_response(Vec2::splat(16.0), Sense::hover()); + let (rect, _response) = ui.allocate_at_least(Vec2::splat(16.0), Sense::hover()); let painter = ui.painter(); - let c = response.rect.center(); - let r = response.rect.width() / 2.0 - 1.0; + let c = rect.center(); + let r = rect.width() / 2.0 - 1.0; let color = Color32::from_gray(128); let stroke = Stroke::new(1.0, color); painter.circle_stroke(c, r, stroke); @@ -207,9 +207,9 @@ impl BoxPainting { ui.horizontal_wrapped(|ui| { for _ in 0..self.num_boxes { - let response = ui.allocate_response(self.size, Sense::hover()); + let (rect, _response) = ui.allocate_at_least(self.size, Sense::hover()); ui.painter().rect( - response.rect, + rect, self.corner_radius, Color32::from_gray(64), Stroke::new(self.stroke_width, Color32::WHITE), diff --git a/egui_demo_lib/src/apps/demo/drag_and_drop.rs b/egui_demo_lib/src/apps/demo/drag_and_drop.rs index c58dfd37f..32bb7a576 100644 --- a/egui_demo_lib/src/apps/demo/drag_and_drop.rs +++ b/egui_demo_lib/src/apps/demo/drag_and_drop.rs @@ -47,7 +47,7 @@ pub fn drop_target( let mut content_ui = ui.child_ui(inner_rect, *ui.layout()); let ret = body(&mut content_ui); let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin); - let response = ui.allocate_response(outer_rect.size(), Sense::hover()); + let (rect, response) = ui.allocate_at_least(outer_rect.size(), Sense::hover()); let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered { ui.style().visuals.widgets.active @@ -65,7 +65,7 @@ pub fn drop_target( corner_radius: style.corner_radius, fill: style.bg_fill, stroke: style.bg_stroke, - rect: response.rect, + rect, }, ); diff --git a/egui_demo_lib/src/apps/demo/toggle_switch.rs b/egui_demo_lib/src/apps/demo/toggle_switch.rs index e7e9f57dd..f79512305 100644 --- a/egui_demo_lib/src/apps/demo/toggle_switch.rs +++ b/egui_demo_lib/src/apps/demo/toggle_switch.rs @@ -24,7 +24,7 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { // 2. Allocating space: // This is where we get a region of the screen assigned. // We also tell the Ui to sense clicks in the allocated region. - let response = ui.allocate_response(desired_size, egui::Sense::click()); + let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); // 3. Interact: Time to check for clicks!. if response.clicked { @@ -44,7 +44,6 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { let on_bg_fill = egui::Rgba::from_rgb(0.0, 0.5, 0.25); let bg_fill = egui::lerp(off_bg_fill..=on_bg_fill, how_on); // All coordinates are in absolute screen coordinates so we use `rect` to place the elements. - let rect = response.rect; let radius = 0.5 * rect.height(); ui.painter().rect(rect, radius, bg_fill, visuals.bg_stroke); // Paint the circle, animating it from left to right with `how_on`: @@ -62,7 +61,7 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { #[allow(dead_code)] fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { let desired_size = ui.style().spacing.interact_size; - let response = ui.allocate_response(desired_size, egui::Sense::click()); + let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); *on ^= response.clicked; // toggle if clicked let how_on = ui.ctx().animate_bool(response.id, *on); @@ -70,7 +69,6 @@ fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { let off_bg_fill = egui::Rgba::TRANSPARENT; let on_bg_fill = egui::Rgba::from_rgb(0.0, 0.5, 0.25); let bg_fill = egui::lerp(off_bg_fill..=on_bg_fill, how_on); - let rect = response.rect; let radius = 0.5 * rect.height(); ui.painter().rect(rect, radius, bg_fill, visuals.bg_stroke); let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on); diff --git a/egui_demo_lib/src/frame_history.rs b/egui_demo_lib/src/frame_history.rs index be4173b81..2214358d9 100644 --- a/egui_demo_lib/src/frame_history.rs +++ b/egui_demo_lib/src/frame_history.rs @@ -66,8 +66,7 @@ impl FrameHistory { // TODO: we should not use `slider_width` as default graph width. let height = ui.style().spacing.slider_width; let size = vec2(ui.available_size_before_wrap_finite().x, height); - let response = ui.allocate_response(size, Sense::hover()); - let rect = response.rect; + let (rect, response) = ui.allocate_at_least(size, Sense::hover()); let style = ui.style().noninteractive(); let mut cmds = vec![PaintCmd::Rect {