Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New widget interaction logic #4026

Merged
merged 36 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3964a16
Better naming `widgets_this_frame` and `widgets_prev_frame`
emilk Feb 10, 2024
ce06b03
Add HitTest
emilk Feb 10, 2024
0d1f869
Calculate the WidgetHits each frame
emilk Feb 10, 2024
5ed87a1
WidgetRects: add a by_id map
emilk Feb 11, 2024
78e1c93
Rename "Interaction" to "InteractionState"
emilk Feb 11, 2024
7f6adca
Add new interaction code
emilk Feb 11, 2024
bad2364
Run new interaction code each frame
emilk Feb 11, 2024
d6d5023
Add LayerId to WidgetRect
emilk Feb 11, 2024
f3d2e17
Switch to the new interaction code
emilk Feb 11, 2024
ea18192
Simplify window resizing
emilk Feb 11, 2024
8121333
Bug fixes
emilk Feb 11, 2024
21cac17
Refactor and simplify interaction code
emilk Feb 15, 2024
0db7a49
Better debug-painting
emilk Feb 15, 2024
c5b1e0e
Better hit test logic
emilk Feb 15, 2024
3508d4b
Implement panel resizing
emilk Feb 15, 2024
4cec71f
Even better hit test
emilk Feb 15, 2024
db22bc2
Fix Resize widget
emilk Feb 15, 2024
2345106
Use WidgetRect more
emilk Feb 15, 2024
ae84d6c
Improve hit test
emilk Feb 15, 2024
e2f99ad
Add debug ui for interaction
emilk Feb 15, 2024
7b31547
Fix drag-and-drop
emilk Feb 15, 2024
9eb811e
Some compilation fixes and improvements
emilk Feb 15, 2024
6ca7691
Better docs for `Response`
emilk Feb 16, 2024
4454414
Persist dragged id even if widget is gone
emilk Feb 16, 2024
ec91006
Deprecate drag-interaction on `Memory`
emilk Feb 16, 2024
f636e2e
Fix for drag stuff
emilk Feb 16, 2024
a132f08
Consistency: call it `drag_stopped` for symmetry with `drag_started`
emilk Feb 17, 2024
dc12d83
Deprecate `ui.interact_with_hovered`
emilk Feb 17, 2024
05f6505
Document widget interaction
emilk Feb 17, 2024
8d7cf52
Silence a clippy thing
emilk Feb 17, 2024
6e875df
Revert changes to Cargo.lock
emilk Feb 17, 2024
709ed34
Add `Context::is_being_dragged`
emilk Feb 17, 2024
bea5fc2
Improve docstrings and comments
emilk Feb 17, 2024
1790fd4
Merge branch 'master' into emilk/hit-test
emilk Feb 17, 2024
3e701c7
Merge branch 'master' into emilk/hit-test
emilk Feb 17, 2024
472612a
Small tweaks
emilk Feb 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,22 +313,21 @@ impl Area {
let mut move_response = {
let interact_id = layer_id.id.with("move");
let sense = if movable {
Sense::click_and_drag()
Sense::drag()
} else if interactable {
Sense::click() // allow clicks to bring to front
} else {
Sense::hover()
};

let move_response = ctx.interact(
Rect::EVERYTHING,
ctx.style().spacing.item_spacing,
let move_response = ctx.create_widget(WidgetRect {
id: interact_id,
layer_id,
interact_id,
state.rect(),
rect: state.rect(),
interact_rect: state.rect(),
sense,
enabled,
);
});

if movable && move_response.dragged() {
state.pivot_pos += move_response.drag_delta();
Expand Down
135 changes: 57 additions & 78 deletions crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,48 +238,22 @@ impl SidePanel {
ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel");
}

let resize_id = id.with("__resize");
let mut resize_hover = false;
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
if let Some(pointer) = ui.ctx().pointer_latest_pos() {
let we_are_on_top = ui
.ctx()
.layer_id_at(pointer)
.map_or(true, |top_layer_id| top_layer_id == ui.layer_id());
let pointer = if let Some(transform) = ui
.ctx()
.memory(|m| m.layer_transforms.get(&ui.layer_id()).cloned())
{
transform.inverse() * pointer
} else {
pointer
};

let resize_x = side.opposite().side_x(panel_rect);
let mouse_over_resize_line = we_are_on_top
&& panel_rect.y_range().contains(pointer.y)
&& (resize_x - pointer.x).abs()
<= ui.style().interaction.resize_grab_radius_side;

if ui.input(|i| i.pointer.any_pressed() && i.pointer.any_down())
&& mouse_over_resize_line
{
ui.memory_mut(|mem| mem.set_dragged_id(resize_id));
}
is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id));
if is_resizing {
let width = (pointer.x - side.side_x(panel_rect)).abs();
let width = clamp_to_range(width, width_range).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
}
// First we read the resize interaction results, to avoid frame latency in the resize:
if let Some(resize_response) = ui.ctx().read_response(resize_id) {
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();

let dragging_something_else =
ui.input(|i| i.pointer.any_down() || i.pointer.any_pressed());
resize_hover = mouse_over_resize_line && !dragging_something_else;

if resize_hover || is_resizing {
ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal);
if is_resizing {
if let Some(pointer) = resize_response.interact_pointer_pos() {
let width = (pointer.x - side.side_x(panel_rect)).abs();
let width =
clamp_to_range(width, width_range).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
}
}
}
}
Expand Down Expand Up @@ -309,6 +283,22 @@ impl SidePanel {
}
ui.expand_to_include_rect(rect);

if resizable {
// Now we do the actual resize interaction, on top of all the contents.
// Otherwise its input could be eaten by the contents, e.g. a
// `ScrollArea` on either side of the panel boundary.
let resize_x = side.opposite().side_x(panel_rect);
let resize_rect = Rect::from_x_y_ranges(resize_x..=resize_x, panel_rect.y_range())
.expand2(vec2(ui.style().interaction.resize_grab_radius_side, 0.0));
let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();
}

if resize_hover || is_resizing {
ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal);
}

PanelState { rect }.store(ui.ctx(), id);

{
Expand Down Expand Up @@ -706,50 +696,22 @@ impl TopBottomPanel {
.check_for_id_clash(id, panel_rect, "TopBottomPanel");
}

let resize_id = id.with("__resize");
let mut resize_hover = false;
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
let latest_pos = ui.input(|i| i.pointer.latest_pos());
if let Some(pointer) = latest_pos {
let we_are_on_top = ui
.ctx()
.layer_id_at(pointer)
.map_or(true, |top_layer_id| top_layer_id == ui.layer_id());
let pointer = if let Some(transform) = ui
.ctx()
.memory(|m| m.layer_transforms.get(&ui.layer_id()).cloned())
{
transform.inverse() * pointer
} else {
pointer
};

let resize_y = side.opposite().side_y(panel_rect);
let mouse_over_resize_line = we_are_on_top
&& panel_rect.x_range().contains(pointer.x)
&& (resize_y - pointer.y).abs()
<= ui.style().interaction.resize_grab_radius_side;

if ui.input(|i| i.pointer.any_pressed() && i.pointer.any_down())
&& mouse_over_resize_line
{
ui.memory_mut(|mem| mem.set_dragged_id(resize_id));
}
is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id));
if is_resizing {
let height = (pointer.y - side.side_y(panel_rect)).abs();
let height =
clamp_to_range(height, height_range).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
}
// First we read the resize interaction results, to avoid frame latency in the resize:
if let Some(resize_response) = ui.ctx().read_response(resize_id) {
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();

let dragging_something_else =
ui.input(|i| i.pointer.any_down() || i.pointer.any_pressed());
resize_hover = mouse_over_resize_line && !dragging_something_else;

if resize_hover || is_resizing {
ui.ctx().set_cursor_icon(CursorIcon::ResizeVertical);
if is_resizing {
if let Some(pointer) = resize_response.interact_pointer_pos() {
let height = (pointer.y - side.side_y(panel_rect)).abs();
let height =
clamp_to_range(height, height_range).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
}
}
}
}
Expand Down Expand Up @@ -779,6 +741,23 @@ impl TopBottomPanel {
}
ui.expand_to_include_rect(rect);

if resizable {
// Now we do the actual resize interaction, on top of all the contents.
// Otherwise its input could be eaten by the contents, e.g. a
// `ScrollArea` on either side of the panel boundary.

let resize_y = side.opposite().side_y(panel_rect);
let resize_rect = Rect::from_x_y_ranges(panel_rect.x_range(), resize_y..=resize_y)
.expand2(vec2(0.0, ui.style().interaction.resize_grab_radius_side));
let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();
}

if resize_hover || is_resizing {
ui.ctx().set_cursor_icon(CursorIcon::ResizeVertical);
}

PanelState { rect }.store(ui.ctx(), id);

{
Expand Down
43 changes: 26 additions & 17 deletions crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ impl Resize {

struct Prepared {
id: Id,
corner_id: Option<Id>,
state: State,
corner_response: Option<Response>,
content_ui: Ui,
}

Expand Down Expand Up @@ -226,22 +226,17 @@ impl Resize {

let mut user_requested_size = state.requested_size.take();

let corner_response = if self.resizable {
// Resize-corner:
let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
let corner_rect =
Rect::from_min_size(position + state.desired_size - corner_size, corner_size);
let corner_response = ui.interact(corner_rect, id.with("corner"), Sense::drag());
let corner_id = self.resizable.then(|| id.with("__resize_corner"));

if let Some(pointer_pos) = corner_response.interact_pointer_pos() {
user_requested_size =
Some(pointer_pos - position + 0.5 * corner_response.rect.size());
if let Some(corner_id) = corner_id {
if let Some(corner_response) = ui.ctx().read_response(corner_id) {
if let Some(pointer_pos) = corner_response.interact_pointer_pos() {
// Respond to the interaction early to avoid frame delay.
user_requested_size =
Some(pointer_pos - position + 0.5 * corner_response.rect.size());
}
}

Some(corner_response)
} else {
None
};
}

if let Some(user_requested_size) = user_requested_size {
state.desired_size = user_requested_size;
Expand Down Expand Up @@ -279,8 +274,8 @@ impl Resize {

Prepared {
id,
corner_id,
state,
corner_response,
content_ui,
}
}
Expand All @@ -295,8 +290,8 @@ impl Resize {
fn end(self, ui: &mut Ui, prepared: Prepared) {
let Prepared {
id,
corner_id,
mut state,
corner_response,
content_ui,
} = prepared;

Expand All @@ -320,6 +315,20 @@ impl Resize {

// ------------------------------

let corner_response = if let Some(corner_id) = corner_id {
// We do the corner interaction last to place it on top of the content:
let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
let corner_rect = Rect::from_min_size(
content_ui.min_rect().left_top() + size - corner_size,
corner_size,
);
Some(ui.interact(corner_rect, corner_id, Sense::drag()))
} else {
None
};

// ------------------------------

if self.with_stroke && corner_response.is_some() {
let rect = Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size);
let rect = rect.expand(2.0); // breathing room for content
Expand Down
Loading
Loading