Browse Source

Add ui.allocate_response(…): allocate space and check for interactions

pull/83/head
Emil Ernerfeldt 4 years ago
parent
commit
48dfcde65f
  1. 1
      CHANGELOG.md
  2. 2
      egui/src/containers/combo_box.rs
  3. 2
      egui/src/containers/frame.rs
  4. 9
      egui/src/containers/resize.rs
  5. 2
      egui/src/containers/scroll_area.rs
  6. 8
      egui/src/containers/window.rs
  7. 5
      egui/src/demos/app.rs
  8. 11
      egui/src/demos/color_test.rs
  9. 10
      egui/src/demos/demo_window.rs
  10. 5
      egui/src/demos/drag_and_drop.rs
  11. 20
      egui/src/demos/toggle_switch.rs
  12. 4
      egui/src/introspection.rs
  13. 26
      egui/src/ui.rs
  14. 5
      egui/src/widgets/button.rs
  15. 22
      egui/src/widgets/color_picker.rs
  16. 2
      egui/src/widgets/drag_value.rs
  17. 6
      egui/src/widgets/image.rs
  18. 52
      egui/src/widgets/mod.rs
  19. 9
      egui/src/widgets/slider.rs

1
CHANGELOG.md

@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* You can now control the minimum and maixumum number of decimals to show in a `Slider` or `DragValue`.
* Add `egui::math::Rot2`: rotation helper.
* `Response` now contains the `Id` of the widget it pertains to.
* `ui.allocate_response` that allocated space and checks for interactions.
### Changed 🔧

2
egui/src/containers/combo_box.rs

@ -108,7 +108,7 @@ fn button_frame(
},
);
ui.allocate_space(outer_rect.size());
ui.advance_cursor_after_rect(outer_rect);
response
}

2
egui/src/containers/frame.rs

@ -148,7 +148,7 @@ impl Prepared {
},
);
ui.allocate_space(outer_rect.size());
ui.advance_cursor_after_rect(outer_rect);
outer_rect
}

9
egui/src/containers/resize.rs

@ -265,18 +265,19 @@ impl Resize {
// ------------------------------
if self.with_stroke || self.resizable {
let size = if self.with_stroke || self.resizable {
// We show how large we are,
// so we must follow the contents:
state.desired_size = state.desired_size.max(state.last_content_size);
// We are as large as we look
ui.allocate_space(state.desired_size);
state.desired_size
} else {
// Probably a window.
ui.allocate_space(state.last_content_size);
}
state.last_content_size
};
ui.advance_cursor_after_rect(Rect::from_min_size(content_ui.min_rect().min, size));
// ------------------------------

2
egui/src/containers/scroll_area.rs

@ -291,7 +291,7 @@ impl Prepared {
outer_rect.size().x,
outer_rect.size().y.min(content_size.y), // shrink if content is so small that we don't need scroll bars
);
ui.allocate_space(size);
ui.advance_cursor_after_rect(Rect::from_min_size(outer_rect.min, size));
if show_scroll_this_frame != state.show_scroll {
ui.ctx().request_repaint();

8
egui/src/containers/window.rs

@ -225,6 +225,8 @@ impl<'open> Window<'open> {
let mut area = area.begin(ctx);
let title_content_spacing = 2.0 * ctx.style().spacing.item_spacing.y;
// First interact (move etc) to avoid frame delay:
let last_frame_outer_rect = area.state().rect();
let interaction = if possible.movable || possible.resizable {
@ -238,8 +240,7 @@ impl<'open> Window<'open> {
.and_then(|window_interaction| {
// Calculate roughly how much larger the window size is compared to the inner rect
let title_bar_height = if with_title_bar {
title_label.font_height(ctx.fonts(), &ctx.style())
+ 1.0 * ctx.style().spacing.item_spacing.y // this could be better
title_label.font_height(ctx.fonts(), &ctx.style()) + title_content_spacing
} else {
0.0
};
@ -292,8 +293,7 @@ impl<'open> Window<'open> {
.add_contents(&mut frame.content_ui, collapsing_id, |ui| {
resize.show(ui, |ui| {
if title_bar.is_some() {
// Add some spacing between title and content:
ui.allocate_space(ui.style().spacing.item_spacing);
ui.advance_cursor(title_content_spacing);
}
if let Some(scroll) = scroll {

5
egui/src/demos/app.rs

@ -109,8 +109,9 @@ impl FrameHistory {
// TODO: we should not use `slider_width` as default graph width.
let height = ui.style().spacing.slider_width;
let (id, rect) = ui.allocate_space(vec2(ui.available_size_before_wrap_finite().x, height));
let response = ui.interact(rect, id, Sense::hover());
let size = vec2(ui.available_size_before_wrap_finite().x, height);
let response = ui.allocate_response(size, Sense::hover());
let rect = response.rect;
let style = ui.style().noninteractive();
let mut cmds = vec![PaintCmd::Rect {

11
egui/src/demos/color_test.rs

@ -267,11 +267,10 @@ impl ColorTest {
fn vertex_gradient(ui: &mut Ui, bg_fill: Srgba, gradient: &Gradient) -> Response {
use crate::paint::*;
let (id, rect) = ui.allocate_space(GRADIENT_SIZE);
let response = ui.interact(rect, id, Sense::hover());
let response = ui.allocate_response(GRADIENT_SIZE, Sense::hover());
if bg_fill != Default::default() {
let mut triangles = Triangles::default();
triangles.add_colored_rect(rect, bg_fill);
triangles.add_colored_rect(response.rect, bg_fill);
ui.painter().add(PaintCmd::triangles(triangles));
}
{
@ -280,9 +279,9 @@ fn vertex_gradient(ui: &mut Ui, bg_fill: Srgba, gradient: &Gradient) -> Response
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(rect.x_range(), t);
triangles.colored_vertex(pos2(x, rect.top()), color);
triangles.colored_vertex(pos2(x, rect.bottom()), color);
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);
if i < n - 1 {
let i = i as u32;
triangles.add_triangle(2 * i, 2 * i + 1, 2 * i + 2);

10
egui/src/demos/demo_window.rs

@ -87,10 +87,10 @@ impl DemoWindow {
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.label("You can pretty easily paint your own small icons:");
let (_id, rect) = ui.allocate_space(Vec2::splat(16.0));
let response = ui.allocate_response(Vec2::splat(16.0), Sense::hover());
let painter = ui.painter();
let c = rect.center();
let r = rect.width() / 2.0 - 1.0;
let c = response.rect.center();
let r = response.rect.width() / 2.0 - 1.0;
let color = Srgba::gray(128);
let stroke = Stroke::new(1.0, color);
painter.circle_stroke(c, r, stroke);
@ -206,9 +206,9 @@ impl BoxPainting {
ui.horizontal_wrapped(|ui| {
for _ in 0..self.num_boxes {
let (_id, rect) = ui.allocate_space(self.size);
let response = ui.allocate_response(self.size, Sense::hover());
ui.painter().rect(
rect,
response.rect,
self.corner_radius,
Srgba::gray(64),
Stroke::new(self.stroke_width, WHITE),

5
egui/src/demos/drag_and_drop.rs

@ -50,8 +50,7 @@ pub fn drop_target<R>(
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 (id, outer_rect) = ui.allocate_space(outer_rect.size());
let response = ui.interact(outer_rect, id, Sense::hover());
let response = ui.allocate_response(outer_rect.size(), Sense::hover());
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered {
ui.style().visuals.widgets.active
@ -69,7 +68,7 @@ pub fn drop_target<R>(
corner_radius: style.corner_radius,
fill: style.bg_fill,
stroke: style.bg_stroke,
rect: outer_rect,
rect: response.rect,
},
);

20
egui/src/demos/toggle_switch.rs

@ -23,13 +23,11 @@ pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response {
let desired_size = ui.style().spacing.interact_size;
// 2. Allocating space:
// This is where we get a region (`Rect`) of the screen assigned.
// We also get an automatically generated `Id` which can be used for interactions and animations.
// (To get an `Id` that is persistent over long time, use `ui.make_persistent_id`).
let (id, rect) = ui.allocate_space(desired_size);
// 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, Sense::click());
// 3. Interact: Time to check for clicks!.
let response = ui.interact(rect, id, Sense::click());
if response.clicked {
*on = !*on;
}
@ -38,7 +36,7 @@ pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response {
// First let's ask for a simple animation from Egui.
// Egui keeps track of changes in the boolean associated with the id and
// returns an animated value in the 0-1 range for how much "on" we are.
let how_on = ui.ctx().animate_bool(id, *on);
let how_on = ui.ctx().animate_bool(response.id, *on);
// We will follow the current style by asking
// "how should something that is being interacted with be painted?".
// This will, for instance, give us different colors when the widget is hovered or clicked.
@ -47,6 +45,7 @@ pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response {
let on_bg_fill = Rgba::new(0.0, 0.5, 0.25, 1.0);
let bg_fill = 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`:
@ -64,16 +63,15 @@ pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response {
#[allow(dead_code)]
fn toggle_compact(ui: &mut Ui, on: &mut bool) -> Response {
let desired_size = ui.style().spacing.interact_size;
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, Sense::click());
let response = ui.allocate_response(desired_size, Sense::click());
*on ^= response.clicked; // toggle if clicked
let how_on = ui.ctx().animate_bool(id, *on);
let how_on = ui.ctx().animate_bool(response.id, *on);
let visuals = ui.style().interact(&response);
let off_bg_fill = Rgba::new(0.0, 0.0, 0.0, 0.0);
let on_bg_fill = Rgba::new(0.0, 0.5, 0.25, 1.0);
let bg_fill = 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 = lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
@ -87,7 +85,7 @@ fn toggle_compact(ui: &mut Ui, on: &mut bool) -> Response {
pub fn demo(ui: &mut Ui, on: &mut bool) {
ui.horizontal_wrapped_for_text(TextStyle::Button, |ui| {
ui.label("It's easy to create your own widgets!");
ui.label("This toggle switch is just one function of 20 lines of code:");
ui.label("This toggle switch is just one function and 15 lines of code:");
toggle(ui, on).on_hover_text("Click to toggle");
ui.add(__egui_github_link_file!());
});

4
egui/src/introspection.rs

@ -18,8 +18,8 @@ impl Texture {
if size.x > ui.available_width() {
size *= ui.available_width() / size.x;
}
let (id, rect) = ui.allocate_space(size);
let response = ui.interact(rect, id, Sense::hover());
let response = ui.allocate_response(size, Sense::hover());
let rect = response.rect;
let mut triangles = Triangles::default();
triangles.add_rect_with_uv(rect, [pos2(0.0, 0.0), pos2(1.0, 1.0)].into(), WHITE);
ui.painter().add(PaintCmd::triangles(triangles));

26
egui/src/ui.rs

@ -416,6 +416,30 @@ impl Ui {
self.layout.advance_cursor(&mut self.region, amount);
}
/// Allocate space for a widget and check for interaction in the space.
/// Returns a `Response` which contains a rectangle, id, and interaction info.
///
/// ## How sizes are negotiated
/// 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.
/// 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
/// for justified layouts, like in menus.
///
/// You will never get a rectangle that is smaller than the amount of space you asked for.
///
/// ```
/// # let mut ui = egui::Ui::__test();
/// let response = ui.allocate_response(egui::vec2(100.0, 200.0), egui::Sense::click());
/// if response.clicked { /* … */ }
/// ui.painter().rect_stroke(response.rect, 0.0, (1.0, egui::color::WHITE));
/// ```
pub fn allocate_response(&mut self, desired_size: Vec2, sense: Sense) -> Response {
let (id, rect) = self.allocate_space(desired_size);
self.interact(rect, id, sense)
}
/// Reserve this much space and move the cursor.
/// Returns where to put the widget.
///
@ -427,7 +451,7 @@ impl Ui {
/// You may get MORE space than you asked for, for instance
/// for justified layouts, like in menus.
///
/// You may get LESS space than you asked for if the current layout won't fit what you asked for.
/// You will never get a rectangle that is smaller than the amount of space you asked for.
///
/// Returns an automatic `Id` (which you can use for interaction) and the `Rect` of where to put your widget.
///

5
egui/src/widgets/button.rs

@ -62,10 +62,9 @@ impl Widget for ImageButton {
let button_padding = ui.style().spacing.button_padding;
let desired_size = image.desired_size() + 2.0 * button_padding;
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, sense);
let response = ui.allocate_response(desired_size, sense);
if ui.clip_rect().intersects(rect) {
if ui.clip_rect().intersects(response.rect) {
let visuals = ui.style().interact(&response);
if selected {

22
egui/src/widgets/color_picker.rs

@ -43,11 +43,10 @@ pub fn show_color(ui: &mut Ui, color: impl Into<Srgba>, desired_size: Vec2) -> R
}
fn show_srgba(ui: &mut Ui, srgba: Srgba, desired_size: Vec2) -> Response {
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, Sense::hover());
background_checkers(ui.painter(), rect);
let response = ui.allocate_response(desired_size, Sense::hover());
background_checkers(ui.painter(), response.rect);
ui.painter().add(PaintCmd::Rect {
rect,
rect: response.rect,
corner_radius: 2.0,
fill: srgba,
stroke: Stroke::new(3.0, srgba.to_opaque()),
@ -57,12 +56,11 @@ fn show_srgba(ui: &mut Ui, srgba: Srgba, desired_size: Vec2) -> Response {
fn color_button(ui: &mut Ui, color: Srgba) -> Response {
let desired_size = ui.style().spacing.interact_size;
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, Sense::click());
let response = ui.allocate_response(desired_size, Sense::click());
let visuals = ui.style().interact(&response);
background_checkers(ui.painter(), rect);
background_checkers(ui.painter(), response.rect);
ui.painter().add(PaintCmd::Rect {
rect,
rect: response.rect,
corner_radius: visuals.corner_radius.at_most(2.0),
fill: color,
stroke: visuals.fg_stroke,
@ -77,9 +75,9 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Srgba
ui.style().spacing.slider_width,
ui.style().spacing.interact_size.y * 2.0,
);
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.allocate_response(desired_size, Sense::click_and_drag());
let rect = response.rect;
let response = ui.interact(rect, id, Sense::click_and_drag());
if response.active {
if let Some(mpos) = ui.input().mouse.pos {
*value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
@ -135,9 +133,9 @@ fn color_slider_2d(
color_at: impl Fn(f32, f32) -> Srgba,
) -> Response {
let desired_size = Vec2::splat(ui.style().spacing.slider_width);
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.allocate_response(desired_size, Sense::click_and_drag());
let rect = response.rect;
let response = ui.interact(rect, id, Sense::click_and_drag());
if response.active {
if let Some(mpos) = ui.input().mouse.pos {
*x_value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);

2
egui/src/widgets/drag_value.rs

@ -179,7 +179,7 @@ impl<'a> Widget for DragValue<'a> {
.text_style(TextStyle::Monospace);
let response = ui.add(button);
let response = response.on_hover_text(format!(
" {}{}{}\nDrag to edit or click to enter a value.",
"{}{}{}\nDrag to edit or click to enter a value.",
prefix,
value as f32, // Show full precision value on-hover. TODO: figure out f64 vs f32
suffix

6
egui/src/widgets/image.rs

@ -72,8 +72,8 @@ impl Image {
impl Widget for Image {
fn ui(self, ui: &mut Ui) -> Response {
let (id, rect) = ui.allocate_space(self.desired_size);
self.paint_at(ui, rect);
ui.interact(rect, id, Sense::hover())
let response = ui.allocate_response(self.desired_size, Sense::hover());
self.paint_at(ui, response.rect);
response
}
}

52
egui/src/widgets/mod.rs

@ -186,9 +186,10 @@ impl Widget for Label {
total_response
} else {
let galley = self.layout(ui);
let (id, rect) = ui.allocate_space(galley.size);
let response = ui.interact(rect, id, Sense::hover());
let rect = ui.layout().align_size_within_rect(galley.size, rect);
let response = ui.allocate_response(galley.size, Sense::click());
let rect = ui
.layout()
.align_size_within_rect(galley.size, response.rect);
self.paint_galley(ui, rect.min, galley);
response
}
@ -261,9 +262,8 @@ 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 (id, rect) = ui.allocate_space(galley.size);
let response = ui.allocate_response(galley.size, Sense::click());
let response = ui.interact(rect, id, Sense::click());
if response.hovered {
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
}
@ -404,11 +404,9 @@ impl Widget for Button {
desired_size.y = desired_size.y.at_least(ui.style().spacing.interact_size.y);
}
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, sense);
let response = ui.allocate_response(desired_size, sense);
if ui.clip_rect().intersects(rect) {
if ui.clip_rect().intersects(response.rect) {
let visuals = ui.style().interact(&response);
let text_cursor = ui
.layout()
@ -489,20 +487,20 @@ 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 (id, rect) = ui.allocate_space(desired_size);
let rect = ui.layout().align_size_within_rect(desired_size, rect);
let response = ui.interact(rect, id, Sense::click());
let response = ui.allocate_response(desired_size, Sense::click());
let rect = ui
.layout()
.align_size_within_rect(desired_size, response.rect);
if response.clicked {
*checked = !*checked;
}
let visuals = ui.style().interact(&response);
let text_cursor = pos2(
response.rect.min.x + button_padding.x + icon_width + icon_spacing,
response.rect.center().y - 0.5 * galley.size.y,
rect.min.x + button_padding.x + icon_width + icon_spacing,
rect.center().y - 0.5 * galley.size.y,
);
let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(response.rect);
let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(rect);
ui.painter().add(PaintCmd::Rect {
rect: big_icon_rect,
corner_radius: visuals.corner_radius,
@ -583,18 +581,19 @@ 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 (id, rect) = ui.allocate_space(desired_size);
let rect = ui.layout().align_size_within_rect(desired_size, rect);
let response = ui.interact(rect, id, Sense::click());
let response = ui.allocate_response(desired_size, Sense::click());
let rect = ui
.layout()
.align_size_within_rect(desired_size, response.rect);
let text_cursor = pos2(
response.rect.min.x + button_padding.x + icon_width + icon_spacing,
response.rect.center().y - 0.5 * galley.size.y,
rect.min.x + button_padding.x + icon_width + icon_spacing,
rect.center().y - 0.5 * galley.size.y,
);
let visuals = ui.style().interact(&response);
let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(response.rect);
let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(rect);
let painter = ui.painter();
@ -659,9 +658,7 @@ impl Widget for SelectableLabel {
let mut desired_size = total_extra + galley.size;
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
let (id, rect) = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, Sense::click());
let response = ui.allocate_response(desired_size, Sense::click());
let text_cursor = pos2(
response.rect.min.x + button_padding.x,
@ -723,7 +720,8 @@ impl Widget for Separator {
vec2(available_space.x, spacing)
};
let (id, rect) = ui.allocate_space(size);
let response = ui.allocate_response(size, Sense::hover());
let rect = response.rect;
let points = if ui.layout().main_dir().is_horizontal() {
[
pos2(rect.center().x, rect.top()),
@ -737,6 +735,6 @@ impl Widget for Separator {
};
let stroke = ui.style().visuals.widgets.noninteractive.bg_stroke;
ui.painter().line_segment(points, stroke);
ui.interact(rect, id, Sense::hover())
response
}
}

9
egui/src/widgets/slider.rs

@ -241,10 +241,9 @@ fn x_range(rect: &Rect) -> RangeInclusive<f32> {
impl<'a> Slider<'a> {
/// Just the slider, no text
fn allocate_slide_space(&self, ui: &mut Ui, height: f32) -> Response {
fn allocate_slider_space(&self, ui: &mut Ui, height: f32) -> Response {
let desired_size = vec2(ui.style().spacing.slider_width, height);
let (id, rect) = ui.allocate_space(desired_size);
ui.interact(rect, id, Sense::click_and_drag())
ui.allocate_response(desired_size, Sense::click_and_drag())
}
/// Just the slider, no text
@ -388,7 +387,7 @@ impl<'a> Widget for Slider<'a> {
if self.text.is_some() {
ui.horizontal(|ui| {
let slider_response = self.allocate_slide_space(ui, height);
let slider_response = self.allocate_slider_space(ui, height);
self.slider_ui(ui, &slider_response);
let x_range = x_range(&slider_response.rect);
self.value_ui(ui, x_range);
@ -397,7 +396,7 @@ impl<'a> Widget for Slider<'a> {
})
.0
} else {
let response = self.allocate_slide_space(ui, height);
let response = self.allocate_slider_space(ui, height);
self.slider_ui(ui, &response);
response
}

Loading…
Cancel
Save