mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Add Sense::scroll
This commit is contained in:
@@ -197,7 +197,7 @@ impl Scene {
|
|||||||
UiBuilder::new()
|
UiBuilder::new()
|
||||||
.layer_id(scene_layer_id)
|
.layer_id(scene_layer_id)
|
||||||
.max_rect(Rect::from_min_size(Pos2::ZERO, self.max_inner_size))
|
.max_rect(Rect::from_min_size(Pos2::ZERO, self.max_inner_size))
|
||||||
.sense(self.sense),
|
.sense(self.sense | Sense::scroll()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut pan_response = local_ui.response();
|
let mut pan_response = local_ui.response();
|
||||||
@@ -245,7 +245,7 @@ impl Scene {
|
|||||||
{
|
{
|
||||||
let pointer_in_scene = to_global.inverse() * mouse_pos;
|
let pointer_in_scene = to_global.inverse() * mouse_pos;
|
||||||
let zoom_delta = ui.input(|i| i.zoom_delta());
|
let zoom_delta = ui.input(|i| i.zoom_delta());
|
||||||
let pan_delta = ui.input(|i| i.smooth_scroll_delta());
|
let pan_delta = resp.scroll_delta();
|
||||||
|
|
||||||
// Most of the time we can return early. This is also important to
|
// Most of the time we can return early. This is also important to
|
||||||
// avoid `ui_from_scene` to change slightly due to floating point errors.
|
// avoid `ui_from_scene` to change slightly due to floating point errors.
|
||||||
|
|||||||
@@ -683,6 +683,9 @@ struct Prepared {
|
|||||||
/// The response from dragging the background (if enabled)
|
/// The response from dragging the background (if enabled)
|
||||||
background_drag_response: Option<Response>,
|
background_drag_response: Option<Response>,
|
||||||
|
|
||||||
|
/// The response from the scroll-sensing widget (registered early for correct hit-test ordering)
|
||||||
|
scroll_response: Option<Response>,
|
||||||
|
|
||||||
animated: bool,
|
animated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,6 +873,45 @@ impl ScrollArea {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Register a scroll-sensing widget BEFORE the content so that inner ScrollAreas
|
||||||
|
// (registered later) are on top in the widget list and win hit-testing.
|
||||||
|
let scroll_response = {
|
||||||
|
let scroll_sense = if scroll_source.mouse_wheel && ui.is_enabled() {
|
||||||
|
let mut sense = Sense::empty();
|
||||||
|
for d in 0..2 {
|
||||||
|
if !direction_enabled[d] || !state.content_is_too_large[d] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let can_scroll_start = state.offset[d] > 0.0;
|
||||||
|
let can_scroll_end = !state.scroll_stuck_to_end[d];
|
||||||
|
if d == 0 {
|
||||||
|
// horizontal
|
||||||
|
if can_scroll_start {
|
||||||
|
sense |= Sense::SCROLL_LEFT;
|
||||||
|
}
|
||||||
|
if can_scroll_end {
|
||||||
|
sense |= Sense::SCROLL_RIGHT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// vertical
|
||||||
|
if can_scroll_start {
|
||||||
|
sense |= Sense::SCROLL_UP;
|
||||||
|
}
|
||||||
|
if can_scroll_end {
|
||||||
|
sense |= Sense::SCROLL_DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sense
|
||||||
|
} else {
|
||||||
|
Sense::hover()
|
||||||
|
};
|
||||||
|
// Use the interact_rect from the previous frame (like background_drag_response).
|
||||||
|
state
|
||||||
|
.interact_rect
|
||||||
|
.map(|rect| ui.interact(rect, id.with("scroll"), scroll_sense))
|
||||||
|
};
|
||||||
|
|
||||||
// Scroll with an animation if we have a target offset (that hasn't been cleared by the code
|
// Scroll with an animation if we have a target offset (that hasn't been cleared by the code
|
||||||
// above).
|
// above).
|
||||||
for d in 0..2 {
|
for d in 0..2 {
|
||||||
@@ -922,6 +964,7 @@ impl ScrollArea {
|
|||||||
stick_to_end,
|
stick_to_end,
|
||||||
saved_scroll_target,
|
saved_scroll_target,
|
||||||
background_drag_response,
|
background_drag_response,
|
||||||
|
scroll_response,
|
||||||
animated,
|
animated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1049,6 +1092,7 @@ impl Prepared {
|
|||||||
stick_to_end,
|
stick_to_end,
|
||||||
saved_scroll_target,
|
saved_scroll_target,
|
||||||
background_drag_response,
|
background_drag_response,
|
||||||
|
scroll_response,
|
||||||
animated,
|
animated,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
@@ -1174,39 +1218,61 @@ impl Prepared {
|
|||||||
&& ui.ctx().dragged_id().is_none()
|
&& ui.ctx().dragged_id().is_none()
|
||||||
|| is_dragging_background;
|
|| is_dragging_background;
|
||||||
|
|
||||||
if scroll_source.mouse_wheel && ui.is_enabled() && is_hovering_outer_rect {
|
// Use the scroll response registered in begin() for correct hit-test ordering.
|
||||||
|
if let Some(scroll_response) = &scroll_response {
|
||||||
let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction
|
let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction
|
||||||
&& direction_enabled[0] != direction_enabled[1];
|
&& direction_enabled[0] != direction_enabled[1];
|
||||||
|
|
||||||
|
// Per-axis: only consume scroll delta if this widget is the scroll target for that axis.
|
||||||
|
let is_scrolled_axis = [
|
||||||
|
scroll_response.scrolled_horizontal(),
|
||||||
|
scroll_response.scrolled_vertical(),
|
||||||
|
];
|
||||||
|
|
||||||
for d in 0..2 {
|
for d in 0..2 {
|
||||||
if direction_enabled[d] {
|
if !direction_enabled[d] {
|
||||||
let scroll_delta = ui.input(|input| {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When always_scroll_enabled_direction is set, a single-axis scroll area
|
||||||
|
// consumes both axes of scroll delta. We need the scroll target for either axis.
|
||||||
|
let is_target = if always_scroll_enabled_direction {
|
||||||
|
is_scrolled_axis[0] || is_scrolled_axis[1]
|
||||||
|
} else {
|
||||||
|
is_scrolled_axis[d]
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_target {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scroll_delta = ui.input(|input| {
|
||||||
|
if always_scroll_enabled_direction {
|
||||||
|
// no bidirectional scrolling; allow horizontal scrolling without pressing shift
|
||||||
|
input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1]
|
||||||
|
} else {
|
||||||
|
input.smooth_scroll_delta()[d]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let scroll_delta = scroll_delta * wheel_scroll_multiplier[d];
|
||||||
|
|
||||||
|
let scrolling_up = state.offset[d] > 0.0 && scroll_delta > 0.0;
|
||||||
|
let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta < 0.0;
|
||||||
|
|
||||||
|
if scrolling_up || scrolling_down {
|
||||||
|
state.offset[d] -= scroll_delta;
|
||||||
|
|
||||||
|
// Clear scroll delta so no parent scroll will use it:
|
||||||
|
ui.input_mut(|input| {
|
||||||
if always_scroll_enabled_direction {
|
if always_scroll_enabled_direction {
|
||||||
// no bidirectional scrolling; allow horizontal scrolling without pressing shift
|
input.smooth_scroll_delta = Vec2::ZERO;
|
||||||
input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1]
|
|
||||||
} else {
|
} else {
|
||||||
input.smooth_scroll_delta()[d]
|
input.smooth_scroll_delta[d] = 0.0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let scroll_delta = scroll_delta * wheel_scroll_multiplier[d];
|
|
||||||
|
|
||||||
let scrolling_up = state.offset[d] > 0.0 && scroll_delta > 0.0;
|
state.scroll_stuck_to_end[d] = false;
|
||||||
let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta < 0.0;
|
state.offset_target[d] = None;
|
||||||
|
|
||||||
if scrolling_up || scrolling_down {
|
|
||||||
state.offset[d] -= scroll_delta;
|
|
||||||
|
|
||||||
// Clear scroll delta so no parent scroll will use it:
|
|
||||||
ui.input_mut(|input| {
|
|
||||||
if always_scroll_enabled_direction {
|
|
||||||
input.smooth_scroll_delta = Vec2::ZERO;
|
|
||||||
} else {
|
|
||||||
input.smooth_scroll_delta[d] = 0.0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
state.scroll_stuck_to_end[d] = false;
|
|
||||||
state.offset_target[d] = None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1442,6 +1442,14 @@ impl Context {
|
|||||||
Flags::DRAG_STOPPED,
|
Flags::DRAG_STOPPED,
|
||||||
Some(id) == viewport.interact_widgets.drag_stopped,
|
Some(id) == viewport.interact_widgets.drag_stopped,
|
||||||
);
|
);
|
||||||
|
res.flags.set(
|
||||||
|
Flags::SCROLLED_HORIZONTAL,
|
||||||
|
Some(id) == viewport.interact_widgets.scrolled_horizontal,
|
||||||
|
);
|
||||||
|
res.flags.set(
|
||||||
|
Flags::SCROLLED_VERTICAL,
|
||||||
|
Some(id) == viewport.interact_widgets.scrolled_vertical,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let clicked = Some(id) == viewport.interact_widgets.clicked;
|
let clicked = Some(id) == viewport.interact_widgets.clicked;
|
||||||
@@ -2471,6 +2479,8 @@ impl Context {
|
|||||||
drag_stopped: _,
|
drag_stopped: _,
|
||||||
contains_pointer,
|
contains_pointer,
|
||||||
hovered,
|
hovered,
|
||||||
|
scrolled_horizontal: _,
|
||||||
|
scrolled_vertical: _,
|
||||||
} = interact_widgets;
|
} = interact_widgets;
|
||||||
|
|
||||||
if true {
|
if true {
|
||||||
@@ -2522,6 +2532,10 @@ impl Context {
|
|||||||
contains_pointer,
|
contains_pointer,
|
||||||
click,
|
click,
|
||||||
drag,
|
drag,
|
||||||
|
scroll_left: _,
|
||||||
|
scroll_right: _,
|
||||||
|
scroll_up: _,
|
||||||
|
scroll_down: _,
|
||||||
} = hits;
|
} = hits;
|
||||||
|
|
||||||
if false {
|
if false {
|
||||||
|
|||||||
@@ -35,6 +35,18 @@ pub struct WidgetHits {
|
|||||||
///
|
///
|
||||||
/// This is the top one under the pointer, or closest one of the top-most.
|
/// This is the top one under the pointer, or closest one of the top-most.
|
||||||
pub drag: Option<WidgetRect>,
|
pub drag: Option<WidgetRect>,
|
||||||
|
|
||||||
|
/// The topmost widget that senses leftward scroll events under the pointer.
|
||||||
|
pub scroll_left: Option<WidgetRect>,
|
||||||
|
|
||||||
|
/// The topmost widget that senses rightward scroll events under the pointer.
|
||||||
|
pub scroll_right: Option<WidgetRect>,
|
||||||
|
|
||||||
|
/// The topmost widget that senses upward scroll events under the pointer.
|
||||||
|
pub scroll_up: Option<WidgetRect>,
|
||||||
|
|
||||||
|
/// The topmost widget that senses downward scroll events under the pointer.
|
||||||
|
pub scroll_down: Option<WidgetRect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the top or closest widgets to the given position,
|
/// Find the top or closest widgets to the given position,
|
||||||
@@ -132,6 +144,10 @@ pub fn hit_test(
|
|||||||
if !w.enabled {
|
if !w.enabled {
|
||||||
w.sense -= Sense::CLICK;
|
w.sense -= Sense::CLICK;
|
||||||
w.sense -= Sense::DRAG;
|
w.sense -= Sense::DRAG;
|
||||||
|
w.sense -= Sense::SCROLL_LEFT;
|
||||||
|
w.sense -= Sense::SCROLL_RIGHT;
|
||||||
|
w.sense -= Sense::SCROLL_UP;
|
||||||
|
w.sense -= Sense::SCROLL_DOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +170,40 @@ pub fn hit_test(
|
|||||||
|
|
||||||
let mut hits = hit_test_on_close(&close, pos);
|
let mut hits = hit_test_on_close(&close, pos);
|
||||||
|
|
||||||
|
// Find the topmost widgets that sense scroll per direction.
|
||||||
|
hits.scroll_left = find_closest_within(
|
||||||
|
close
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|w| w.sense.senses_scroll_left()),
|
||||||
|
pos,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
hits.scroll_right = find_closest_within(
|
||||||
|
close
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|w| w.sense.senses_scroll_right()),
|
||||||
|
pos,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
hits.scroll_up = find_closest_within(
|
||||||
|
close
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|w| w.sense.senses_scroll_up()),
|
||||||
|
pos,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
hits.scroll_down = find_closest_within(
|
||||||
|
close
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|w| w.sense.senses_scroll_down()),
|
||||||
|
pos,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
|
||||||
hits.contains_pointer = close
|
hits.contains_pointer = close
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|widget| widget.interact_rect.contains(pos))
|
.filter(|widget| widget.interact_rect.contains(pos))
|
||||||
@@ -190,6 +240,34 @@ pub fn hit_test(
|
|||||||
);
|
);
|
||||||
restore_widget_rect(wr);
|
restore_widget_rect(wr);
|
||||||
}
|
}
|
||||||
|
if let Some(wr) = &mut hits.scroll_left {
|
||||||
|
debug_assert!(
|
||||||
|
wr.sense.senses_scroll_left(),
|
||||||
|
"We should only return scroll hits if they sense scroll"
|
||||||
|
);
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
|
if let Some(wr) = &mut hits.scroll_right {
|
||||||
|
debug_assert!(
|
||||||
|
wr.sense.senses_scroll_right(),
|
||||||
|
"We should only return scroll hits if they sense scroll"
|
||||||
|
);
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
|
if let Some(wr) = &mut hits.scroll_up {
|
||||||
|
debug_assert!(
|
||||||
|
wr.sense.senses_scroll_up(),
|
||||||
|
"We should only return scroll hits if they sense scroll"
|
||||||
|
);
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
|
if let Some(wr) = &mut hits.scroll_down {
|
||||||
|
debug_assert!(
|
||||||
|
wr.sense.senses_scroll_down(),
|
||||||
|
"We should only return scroll hits if they sense scroll"
|
||||||
|
);
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hits
|
hits
|
||||||
|
|||||||
@@ -54,6 +54,18 @@ pub struct InteractionSnapshot {
|
|||||||
/// This is usually a larger set than [`Self::hovered`],
|
/// This is usually a larger set than [`Self::hovered`],
|
||||||
/// and can be used for e.g. drag-and-drop zones.
|
/// and can be used for e.g. drag-and-drop zones.
|
||||||
pub contains_pointer: IdSet,
|
pub contains_pointer: IdSet,
|
||||||
|
|
||||||
|
/// The widget that should receive horizontal scroll events this frame.
|
||||||
|
///
|
||||||
|
/// This is the topmost horizontal-scroll-sensing widget under the pointer,
|
||||||
|
/// but only set when `smooth_scroll_delta.x` is non-zero.
|
||||||
|
pub scrolled_horizontal: Option<Id>,
|
||||||
|
|
||||||
|
/// The widget that should receive vertical scroll events this frame.
|
||||||
|
///
|
||||||
|
/// This is the topmost vertical-scroll-sensing widget under the pointer,
|
||||||
|
/// but only set when `smooth_scroll_delta.y` is non-zero.
|
||||||
|
pub scrolled_vertical: Option<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InteractionSnapshot {
|
impl InteractionSnapshot {
|
||||||
@@ -66,6 +78,8 @@ impl InteractionSnapshot {
|
|||||||
drag_stopped,
|
drag_stopped,
|
||||||
hovered,
|
hovered,
|
||||||
contains_pointer,
|
contains_pointer,
|
||||||
|
scrolled_horizontal,
|
||||||
|
scrolled_vertical,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
fn id_ui<'a>(ui: &mut crate::Ui, widgets: impl IntoIterator<Item = &'a Id>) {
|
fn id_ui<'a>(ui: &mut crate::Ui, widgets: impl IntoIterator<Item = &'a Id>) {
|
||||||
@@ -102,6 +116,14 @@ impl InteractionSnapshot {
|
|||||||
ui.label("contains_pointer");
|
ui.label("contains_pointer");
|
||||||
id_ui(ui, contains_pointer);
|
id_ui(ui, contains_pointer);
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("scrolled_horizontal");
|
||||||
|
id_ui(ui, scrolled_horizontal);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("scrolled_vertical");
|
||||||
|
id_ui(ui, scrolled_vertical);
|
||||||
|
ui.end_row();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,6 +309,24 @@ pub(crate) fn interact(
|
|||||||
hovered
|
hovered
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Scroll: pick the directional scroll target based on scroll delta sign.
|
||||||
|
// In egui, positive smooth_scroll_delta.y means content moves down (scrolling toward top),
|
||||||
|
// and positive smooth_scroll_delta.x means content moves right (scrolling toward left).
|
||||||
|
let scrolled_horizontal = if input.smooth_scroll_delta.x > 0.0 {
|
||||||
|
hits.scroll_left.map(|w| w.id)
|
||||||
|
} else if input.smooth_scroll_delta.x < 0.0 {
|
||||||
|
hits.scroll_right.map(|w| w.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let scrolled_vertical = if input.smooth_scroll_delta.y > 0.0 {
|
||||||
|
hits.scroll_up.map(|w| w.id)
|
||||||
|
} else if input.smooth_scroll_delta.y < 0.0 {
|
||||||
|
hits.scroll_down.map(|w| w.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
InteractionSnapshot {
|
InteractionSnapshot {
|
||||||
clicked,
|
clicked,
|
||||||
long_touched,
|
long_touched,
|
||||||
@@ -295,5 +335,7 @@ pub(crate) fn interact(
|
|||||||
drag_stopped,
|
drag_stopped,
|
||||||
hovered,
|
hovered,
|
||||||
contains_pointer,
|
contains_pointer,
|
||||||
|
scrolled_horizontal,
|
||||||
|
scrolled_vertical,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,8 +135,14 @@ bitflags::bitflags! {
|
|||||||
/// for instance if an existing slider value was clamped to the given range.
|
/// for instance if an existing slider value was clamped to the given range.
|
||||||
const CHANGED = 1<<11;
|
const CHANGED = 1<<11;
|
||||||
|
|
||||||
|
/// This widget is receiving horizontal scroll events this frame.
|
||||||
|
const SCROLLED_HORIZONTAL = 1<<12;
|
||||||
|
|
||||||
|
/// This widget is receiving vertical scroll events this frame.
|
||||||
|
const SCROLLED_VERTICAL = 1<<13;
|
||||||
|
|
||||||
/// Should this container be closed?
|
/// Should this container be closed?
|
||||||
const CLOSE = 1<<12;
|
const CLOSE = 1<<14;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,6 +402,59 @@ impl Response {
|
|||||||
self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button))
|
self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this widget receiving scroll events on any axis this frame?
|
||||||
|
///
|
||||||
|
/// This is determined by hit-testing: the topmost widget under the pointer
|
||||||
|
/// that senses scroll (via [`Sense::scroll`]) receives the scroll events.
|
||||||
|
/// Horizontal and vertical scroll are resolved independently, so an inner
|
||||||
|
/// widget may handle one axis while an outer widget handles the other.
|
||||||
|
///
|
||||||
|
/// See also: [`Self::scroll_delta`].
|
||||||
|
#[inline]
|
||||||
|
pub fn scrolled(&self) -> bool {
|
||||||
|
self.flags
|
||||||
|
.intersects(Flags::SCROLLED_HORIZONTAL | Flags::SCROLLED_VERTICAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this widget receiving horizontal scroll events this frame?
|
||||||
|
#[inline]
|
||||||
|
pub fn scrolled_horizontal(&self) -> bool {
|
||||||
|
self.flags.contains(Flags::SCROLLED_HORIZONTAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this widget receiving vertical scroll events this frame?
|
||||||
|
#[inline]
|
||||||
|
pub fn scrolled_vertical(&self) -> bool {
|
||||||
|
self.flags.contains(Flags::SCROLLED_VERTICAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The smooth scroll delta for this widget, filtered to the axes it is receiving.
|
||||||
|
///
|
||||||
|
/// Returns [`Vec2::ZERO`] if this widget is not being scrolled.
|
||||||
|
/// Each axis is independently zero if this widget is not the scroll target for that axis.
|
||||||
|
/// Use [`Sense::scroll`] to opt in to receiving scroll events.
|
||||||
|
///
|
||||||
|
/// See also: [`Self::scrolled`].
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll_delta(&self) -> Vec2 {
|
||||||
|
if !self.scrolled() {
|
||||||
|
return Vec2::ZERO;
|
||||||
|
}
|
||||||
|
let delta = self.ctx.input(|i| i.smooth_scroll_delta);
|
||||||
|
Vec2::new(
|
||||||
|
if self.scrolled_horizontal() {
|
||||||
|
delta.x
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
},
|
||||||
|
if self.scrolled_vertical() {
|
||||||
|
delta.y
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// If dragged, how many points were we dragged in since last frame?
|
/// If dragged, how many points were we dragged in since last frame?
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn drag_delta(&self) -> Vec2 {
|
pub fn drag_delta(&self) -> Vec2 {
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ bitflags::bitflags! {
|
|||||||
/// Anything interactive + labels that can be focused
|
/// Anything interactive + labels that can be focused
|
||||||
/// for the benefit of screen readers.
|
/// for the benefit of screen readers.
|
||||||
const FOCUSABLE = 1<<2;
|
const FOCUSABLE = 1<<2;
|
||||||
|
|
||||||
|
/// This widget wants to receive leftward scroll events.
|
||||||
|
const SCROLL_LEFT = 1<<3;
|
||||||
|
|
||||||
|
/// This widget wants to receive rightward scroll events.
|
||||||
|
const SCROLL_RIGHT = 1<<4;
|
||||||
|
|
||||||
|
/// This widget wants to receive upward scroll events.
|
||||||
|
const SCROLL_UP = 1<<5;
|
||||||
|
|
||||||
|
/// This widget wants to receive downward scroll events.
|
||||||
|
const SCROLL_DOWN = 1<<6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +46,18 @@ impl std::fmt::Debug for Sense {
|
|||||||
if self.is_focusable() {
|
if self.is_focusable() {
|
||||||
write!(f, " focusable")?;
|
write!(f, " focusable")?;
|
||||||
}
|
}
|
||||||
|
if self.senses_scroll_left() {
|
||||||
|
write!(f, " scroll_left")?;
|
||||||
|
}
|
||||||
|
if self.senses_scroll_right() {
|
||||||
|
write!(f, " scroll_right")?;
|
||||||
|
}
|
||||||
|
if self.senses_scroll_up() {
|
||||||
|
write!(f, " scroll_up")?;
|
||||||
|
}
|
||||||
|
if self.senses_scroll_down() {
|
||||||
|
write!(f, " scroll_down")?;
|
||||||
|
}
|
||||||
write!(f, " }}")
|
write!(f, " }}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,6 +106,48 @@ impl Sense {
|
|||||||
Self::CLICK | Self::FOCUSABLE | Self::DRAG
|
Self::CLICK | Self::FOCUSABLE | Self::DRAG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sense scroll events in all four directions.
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll() -> Self {
|
||||||
|
Self::SCROLL_LEFT | Self::SCROLL_RIGHT | Self::SCROLL_UP | Self::SCROLL_DOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sense only horizontal scroll events (left and right).
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll_horizontal() -> Self {
|
||||||
|
Self::SCROLL_LEFT | Self::SCROLL_RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sense only vertical scroll events (up and down).
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll_vertical() -> Self {
|
||||||
|
Self::SCROLL_UP | Self::SCROLL_DOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sense only leftward scroll events.
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll_left() -> Self {
|
||||||
|
Self::SCROLL_LEFT
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sense only rightward scroll events.
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll_right() -> Self {
|
||||||
|
Self::SCROLL_RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sense only upward scroll events.
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll_up() -> Self {
|
||||||
|
Self::SCROLL_UP
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sense only downward scroll events.
|
||||||
|
#[inline]
|
||||||
|
pub fn scroll_down() -> Self {
|
||||||
|
Self::SCROLL_DOWN
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if we sense either clicks or drags.
|
/// Returns true if we sense either clicks or drags.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn interactive(&self) -> bool {
|
pub fn interactive(&self) -> bool {
|
||||||
@@ -102,4 +168,46 @@ impl Sense {
|
|||||||
pub fn is_focusable(&self) -> bool {
|
pub fn is_focusable(&self) -> bool {
|
||||||
self.contains(Self::FOCUSABLE)
|
self.contains(Self::FOCUSABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does this sense any scroll events?
|
||||||
|
#[inline]
|
||||||
|
pub fn senses_scroll(&self) -> bool {
|
||||||
|
self.intersects(Self::SCROLL_LEFT | Self::SCROLL_RIGHT | Self::SCROLL_UP | Self::SCROLL_DOWN)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this sense horizontal scroll events (left or right)?
|
||||||
|
#[inline]
|
||||||
|
pub fn senses_scroll_horizontal(&self) -> bool {
|
||||||
|
self.intersects(Self::SCROLL_LEFT | Self::SCROLL_RIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this sense vertical scroll events (up or down)?
|
||||||
|
#[inline]
|
||||||
|
pub fn senses_scroll_vertical(&self) -> bool {
|
||||||
|
self.intersects(Self::SCROLL_UP | Self::SCROLL_DOWN)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this sense leftward scroll events?
|
||||||
|
#[inline]
|
||||||
|
pub fn senses_scroll_left(&self) -> bool {
|
||||||
|
self.contains(Self::SCROLL_LEFT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this sense rightward scroll events?
|
||||||
|
#[inline]
|
||||||
|
pub fn senses_scroll_right(&self) -> bool {
|
||||||
|
self.contains(Self::SCROLL_RIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this sense upward scroll events?
|
||||||
|
#[inline]
|
||||||
|
pub fn senses_scroll_up(&self) -> bool {
|
||||||
|
self.contains(Self::SCROLL_UP)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this sense downward scroll events?
|
||||||
|
#[inline]
|
||||||
|
pub fn senses_scroll_down(&self) -> bool {
|
||||||
|
self.contains(Self::SCROLL_DOWN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use egui::{
|
use egui::{
|
||||||
Align, Align2, Color32, DragValue, NumExt as _, Rect, ScrollArea, Sense, Slider, TextStyle,
|
pos2, scroll_area::ScrollBarVisibility, Align, Align2, Color32, DragValue, NumExt as _, Rect, ScrollArea, Sense,
|
||||||
TextWrapMode, Ui, Vec2, Widget as _, pos2, scroll_area::ScrollBarVisibility,
|
Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
@@ -13,6 +13,7 @@ enum ScrollDemo {
|
|||||||
LargeCanvas,
|
LargeCanvas,
|
||||||
StickToEnd,
|
StickToEnd,
|
||||||
Bidirectional,
|
Bidirectional,
|
||||||
|
Nested,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
@@ -61,6 +62,7 @@ impl crate::View for Scrolling {
|
|||||||
);
|
);
|
||||||
ui.selectable_value(&mut self.demo, ScrollDemo::StickToEnd, "Stick to end");
|
ui.selectable_value(&mut self.demo, ScrollDemo::StickToEnd, "Stick to end");
|
||||||
ui.selectable_value(&mut self.demo, ScrollDemo::Bidirectional, "Bidirectional");
|
ui.selectable_value(&mut self.demo, ScrollDemo::Bidirectional, "Bidirectional");
|
||||||
|
ui.selectable_value(&mut self.demo, ScrollDemo::Nested, "Nested");
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
match self.demo {
|
match self.demo {
|
||||||
@@ -87,6 +89,9 @@ impl crate::View for Scrolling {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
ScrollDemo::Nested => {
|
||||||
|
nested_scroll_demo(ui);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,3 +397,46 @@ impl crate::View for ScrollStickTo {
|
|||||||
ui.request_repaint();
|
ui.request_repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nested_scroll_demo(ui: &mut Ui) {
|
||||||
|
ui.label(
|
||||||
|
"Nested scroll areas: only the inner-most scroll area under the pointer receives scroll events.",
|
||||||
|
);
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
let outer_row_height = 100.0;
|
||||||
|
let inner_row_height = 20.0;
|
||||||
|
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.id_salt("outer")
|
||||||
|
.auto_shrink(false)
|
||||||
|
.show_rows(ui, outer_row_height, 100, |ui, range| {
|
||||||
|
ui.style_mut().interaction.selectable_labels = false;
|
||||||
|
for outer_row in range {
|
||||||
|
egui::Frame::group(ui.style()).show(ui, |ui| {
|
||||||
|
ScrollArea::horizontal()
|
||||||
|
.id_salt(format!("outer_row_{outer_row}"))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
for col in 0..20 {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.set_width(100.0);
|
||||||
|
ui.label(format!("Column {}", col + 1));
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.id_salt(format!("inner_vert_{col}_{outer_row}"))
|
||||||
|
.max_height(outer_row_height)
|
||||||
|
.show_rows(ui, inner_row_height, 100, |ui, range| {
|
||||||
|
for inner_row in range {
|
||||||
|
ui.label(format!("Col {} Row {}", col + 1, inner_row + 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user