mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 14:49:06 -04:00
Add Sense::scroll
This commit is contained in:
@@ -197,7 +197,7 @@ impl Scene {
|
||||
UiBuilder::new()
|
||||
.layer_id(scene_layer_id)
|
||||
.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();
|
||||
@@ -245,7 +245,7 @@ impl Scene {
|
||||
{
|
||||
let pointer_in_scene = to_global.inverse() * mouse_pos;
|
||||
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
|
||||
// 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)
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -870,6 +873,45 @@ impl ScrollArea {
|
||||
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
|
||||
// above).
|
||||
for d in 0..2 {
|
||||
@@ -922,6 +964,7 @@ impl ScrollArea {
|
||||
stick_to_end,
|
||||
saved_scroll_target,
|
||||
background_drag_response,
|
||||
scroll_response,
|
||||
animated,
|
||||
}
|
||||
}
|
||||
@@ -1049,6 +1092,7 @@ impl Prepared {
|
||||
stick_to_end,
|
||||
saved_scroll_target,
|
||||
background_drag_response,
|
||||
scroll_response,
|
||||
animated,
|
||||
} = self;
|
||||
|
||||
@@ -1174,39 +1218,61 @@ impl Prepared {
|
||||
&& ui.ctx().dragged_id().is_none()
|
||||
|| 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
|
||||
&& 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 {
|
||||
if direction_enabled[d] {
|
||||
let scroll_delta = ui.input(|input| {
|
||||
if !direction_enabled[d] {
|
||||
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 {
|
||||
// no bidirectional scrolling; allow horizontal scrolling without pressing shift
|
||||
input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1]
|
||||
input.smooth_scroll_delta = Vec2::ZERO;
|
||||
} 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;
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
state.scroll_stuck_to_end[d] = false;
|
||||
state.offset_target[d] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1442,6 +1442,14 @@ impl Context {
|
||||
Flags::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;
|
||||
@@ -2471,6 +2479,8 @@ impl Context {
|
||||
drag_stopped: _,
|
||||
contains_pointer,
|
||||
hovered,
|
||||
scrolled_horizontal: _,
|
||||
scrolled_vertical: _,
|
||||
} = interact_widgets;
|
||||
|
||||
if true {
|
||||
@@ -2522,6 +2532,10 @@ impl Context {
|
||||
contains_pointer,
|
||||
click,
|
||||
drag,
|
||||
scroll_left: _,
|
||||
scroll_right: _,
|
||||
scroll_up: _,
|
||||
scroll_down: _,
|
||||
} = hits;
|
||||
|
||||
if false {
|
||||
|
||||
@@ -35,6 +35,18 @@ pub struct WidgetHits {
|
||||
///
|
||||
/// This is the top one under the pointer, or closest one of the top-most.
|
||||
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,
|
||||
@@ -132,6 +144,10 @@ pub fn hit_test(
|
||||
if !w.enabled {
|
||||
w.sense -= Sense::CLICK;
|
||||
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);
|
||||
|
||||
// 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
|
||||
.iter()
|
||||
.filter(|widget| widget.interact_rect.contains(pos))
|
||||
@@ -190,6 +240,34 @@ pub fn hit_test(
|
||||
);
|
||||
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
|
||||
|
||||
@@ -54,6 +54,18 @@ pub struct InteractionSnapshot {
|
||||
/// This is usually a larger set than [`Self::hovered`],
|
||||
/// and can be used for e.g. drag-and-drop zones.
|
||||
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 {
|
||||
@@ -66,6 +78,8 @@ impl InteractionSnapshot {
|
||||
drag_stopped,
|
||||
hovered,
|
||||
contains_pointer,
|
||||
scrolled_horizontal,
|
||||
scrolled_vertical,
|
||||
} = self;
|
||||
|
||||
fn id_ui<'a>(ui: &mut crate::Ui, widgets: impl IntoIterator<Item = &'a Id>) {
|
||||
@@ -102,6 +116,14 @@ impl InteractionSnapshot {
|
||||
ui.label("contains_pointer");
|
||||
id_ui(ui, contains_pointer);
|
||||
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
|
||||
};
|
||||
|
||||
// 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 {
|
||||
clicked,
|
||||
long_touched,
|
||||
@@ -295,5 +335,7 @@ pub(crate) fn interact(
|
||||
drag_stopped,
|
||||
hovered,
|
||||
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.
|
||||
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?
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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?
|
||||
#[inline]
|
||||
pub fn drag_delta(&self) -> Vec2 {
|
||||
|
||||
@@ -19,6 +19,18 @@ bitflags::bitflags! {
|
||||
/// Anything interactive + labels that can be focused
|
||||
/// for the benefit of screen readers.
|
||||
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() {
|
||||
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, " }}")
|
||||
}
|
||||
}
|
||||
@@ -82,6 +106,48 @@ impl Sense {
|
||||
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.
|
||||
#[inline]
|
||||
pub fn interactive(&self) -> bool {
|
||||
@@ -102,4 +168,46 @@ impl Sense {
|
||||
pub fn is_focusable(&self) -> bool {
|
||||
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::{
|
||||
Align, Align2, Color32, DragValue, NumExt as _, Rect, ScrollArea, Sense, Slider, TextStyle,
|
||||
TextWrapMode, Ui, Vec2, Widget as _, pos2, scroll_area::ScrollBarVisibility,
|
||||
pos2, scroll_area::ScrollBarVisibility, Align, Align2, Color32, DragValue, NumExt as _, Rect, ScrollArea, Sense,
|
||||
Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget as _,
|
||||
};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
@@ -13,6 +13,7 @@ enum ScrollDemo {
|
||||
LargeCanvas,
|
||||
StickToEnd,
|
||||
Bidirectional,
|
||||
Nested,
|
||||
}
|
||||
|
||||
#[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::Bidirectional, "Bidirectional");
|
||||
ui.selectable_value(&mut self.demo, ScrollDemo::Nested, "Nested");
|
||||
});
|
||||
ui.separator();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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