mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Drag-to-scroll: now only on touch screens (#8181)
* Part of https://github.com/emilk/egui/issues/8180 Drag-to-scroll is a must-have on touch-screens, since there is no other way to scroll. However, when you are not on a touch screens, it is more surprising than useful.
This commit is contained in:
@@ -141,6 +141,51 @@ impl ScrollBarVisibility {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When [`ScrollArea`] should let the user scroll by dragging the content.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub enum DragScroll {
|
||||||
|
/// Never scroll on pointer drag.
|
||||||
|
Never,
|
||||||
|
|
||||||
|
/// Only allow drag-to-scroll when a touch screen is detected
|
||||||
|
/// (see [`crate::InputState::has_touch_screen`]). The recommended default.
|
||||||
|
#[default]
|
||||||
|
OnTouch,
|
||||||
|
|
||||||
|
/// Always allow drag-to-scroll, even with a mouse.
|
||||||
|
Always,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DragScroll {
|
||||||
|
/// Whether drag-to-scroll is currently active.
|
||||||
|
///
|
||||||
|
/// Checks if we have a touch screen (via [`crate::InputState::has_touch_screen`])
|
||||||
|
/// when `self` is [`Self::OnTouch`].
|
||||||
|
pub fn enabled(self, ctx: &Context) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Never => false,
|
||||||
|
Self::OnTouch => ctx.input(|i| i.has_touch_screen()),
|
||||||
|
Self::Always => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitOr for DragScroll {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
/// Combine two settings, picking the more permissive one.
|
||||||
|
/// `Always > OnTouch > Never`.
|
||||||
|
#[inline]
|
||||||
|
fn bitor(self, rhs: Self) -> Self::Output {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Self::Always, _) | (_, Self::Always) => Self::Always,
|
||||||
|
(Self::OnTouch, _) | (_, Self::OnTouch) => Self::OnTouch,
|
||||||
|
(Self::Never, Self::Never) => Self::Never,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// What is the source of scrolling for a [`ScrollArea`].
|
/// What is the source of scrolling for a [`ScrollArea`].
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
@@ -152,7 +197,11 @@ pub struct ScrollSource {
|
|||||||
pub scroll_bar: bool,
|
pub scroll_bar: bool,
|
||||||
|
|
||||||
/// Scroll the area by dragging the contents.
|
/// Scroll the area by dragging the contents.
|
||||||
pub drag: bool,
|
///
|
||||||
|
/// Defaults to [`DragScroll::OnTouch`]: only active when a touch screen is
|
||||||
|
/// detected. Set to [`DragScroll::Always`] to force it on, or
|
||||||
|
/// [`DragScroll::Never`] to disable.
|
||||||
|
pub drag: DragScroll,
|
||||||
|
|
||||||
/// Scroll the area by scrolling (or shift scrolling) the mouse wheel with
|
/// Scroll the area by scrolling (or shift scrolling) the mouse wheel with
|
||||||
/// the mouse cursor over the [`ScrollArea`].
|
/// the mouse cursor over the [`ScrollArea`].
|
||||||
@@ -160,35 +209,40 @@ pub struct ScrollSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ScrollSource {
|
impl Default for ScrollSource {
|
||||||
|
/// `scroll_bar` and `mouse_wheel` enabled; `drag` set to [`DragScroll::OnTouch`].
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::ALL
|
Self {
|
||||||
|
scroll_bar: true,
|
||||||
|
drag: DragScroll::OnTouch,
|
||||||
|
mouse_wheel: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollSource {
|
impl ScrollSource {
|
||||||
pub const NONE: Self = Self {
|
pub const NONE: Self = Self {
|
||||||
scroll_bar: false,
|
scroll_bar: false,
|
||||||
drag: false,
|
drag: DragScroll::Never,
|
||||||
mouse_wheel: false,
|
mouse_wheel: false,
|
||||||
};
|
};
|
||||||
pub const ALL: Self = Self {
|
pub const ALL: Self = Self {
|
||||||
scroll_bar: true,
|
scroll_bar: true,
|
||||||
drag: true,
|
drag: DragScroll::Always,
|
||||||
mouse_wheel: true,
|
mouse_wheel: true,
|
||||||
};
|
};
|
||||||
pub const SCROLL_BAR: Self = Self {
|
pub const SCROLL_BAR: Self = Self {
|
||||||
scroll_bar: true,
|
scroll_bar: true,
|
||||||
drag: false,
|
drag: DragScroll::Never,
|
||||||
mouse_wheel: false,
|
mouse_wheel: false,
|
||||||
};
|
};
|
||||||
pub const DRAG: Self = Self {
|
pub const DRAG: Self = Self {
|
||||||
scroll_bar: false,
|
scroll_bar: false,
|
||||||
drag: true,
|
drag: DragScroll::Always,
|
||||||
mouse_wheel: false,
|
mouse_wheel: false,
|
||||||
};
|
};
|
||||||
pub const MOUSE_WHEEL: Self = Self {
|
pub const MOUSE_WHEEL: Self = Self {
|
||||||
scroll_bar: false,
|
scroll_bar: false,
|
||||||
drag: false,
|
drag: DragScroll::Never,
|
||||||
mouse_wheel: true,
|
mouse_wheel: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -201,13 +255,13 @@ impl ScrollSource {
|
|||||||
/// Is anything enabled?
|
/// Is anything enabled?
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn any(&self) -> bool {
|
pub fn any(&self) -> bool {
|
||||||
self.scroll_bar | self.drag | self.mouse_wheel
|
self.scroll_bar || self.drag != DragScroll::Never || self.mouse_wheel
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is everything enabled?
|
/// Is everything enabled?
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_all(&self) -> bool {
|
pub fn is_all(&self) -> bool {
|
||||||
self.scroll_bar & self.drag & self.mouse_wheel
|
self.scroll_bar && self.drag == DragScroll::Always && self.mouse_wheel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,72 +824,74 @@ impl ScrollArea {
|
|||||||
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
||||||
let dt = ui.input(|i| i.stable_dt).at_most(0.1);
|
let dt = ui.input(|i| i.stable_dt).at_most(0.1);
|
||||||
|
|
||||||
let background_drag_response =
|
let background_drag_response = if scroll_source.drag.enabled(ui.ctx())
|
||||||
if scroll_source.drag && ui.is_enabled() && state.content_is_too_large.any() {
|
&& ui.is_enabled()
|
||||||
// Drag contents to scroll (for touch screens mostly).
|
&& state.content_is_too_large.any()
|
||||||
// We must do this BEFORE adding content to the `ScrollArea`,
|
{
|
||||||
// or we will steal input from the widgets we contain.
|
// Drag contents to scroll (for touch screens mostly).
|
||||||
let content_response_option = state
|
// We must do this BEFORE adding content to the `ScrollArea`,
|
||||||
.interact_rect
|
// or we will steal input from the widgets we contain.
|
||||||
.map(|rect| ui.interact(rect, id.with("area"), Sense::DRAG));
|
let content_response_option = state
|
||||||
|
.interact_rect
|
||||||
|
.map(|rect| ui.interact(rect, id.with("area"), Sense::DRAG));
|
||||||
|
|
||||||
|
if content_response_option
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|response| response.dragged())
|
||||||
|
{
|
||||||
|
for d in 0..2 {
|
||||||
|
if direction_enabled[d] {
|
||||||
|
ui.input(|input| {
|
||||||
|
state.offset[d] -= input.pointer.delta()[d];
|
||||||
|
});
|
||||||
|
state.scroll_stuck_to_end[d] = false;
|
||||||
|
state.offset_target[d] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Apply the cursor velocity to the scroll area when the user releases the drag.
|
||||||
if content_response_option
|
if content_response_option
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|response| response.dragged())
|
.is_some_and(|response| response.drag_stopped())
|
||||||
{
|
{
|
||||||
for d in 0..2 {
|
state.vel =
|
||||||
if direction_enabled[d] {
|
direction_enabled.to_vec2() * ui.input(|input| input.pointer.velocity());
|
||||||
ui.input(|input| {
|
}
|
||||||
state.offset[d] -= input.pointer.delta()[d];
|
for d in 0..2 {
|
||||||
});
|
// Kinetic scrolling
|
||||||
state.scroll_stuck_to_end[d] = false;
|
let stop_speed = 20.0; // Pixels per second.
|
||||||
state.offset_target[d] = None;
|
let friction_coeff = 1000.0; // Pixels per second squared.
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Apply the cursor velocity to the scroll area when the user releases the drag.
|
|
||||||
if content_response_option
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|response| response.drag_stopped())
|
|
||||||
{
|
|
||||||
state.vel = direction_enabled.to_vec2()
|
|
||||||
* ui.input(|input| input.pointer.velocity());
|
|
||||||
}
|
|
||||||
for d in 0..2 {
|
|
||||||
// Kinetic scrolling
|
|
||||||
let stop_speed = 20.0; // Pixels per second.
|
|
||||||
let friction_coeff = 1000.0; // Pixels per second squared.
|
|
||||||
|
|
||||||
let friction = friction_coeff * dt;
|
let friction = friction_coeff * dt;
|
||||||
if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed {
|
if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed {
|
||||||
state.vel[d] = 0.0;
|
state.vel[d] = 0.0;
|
||||||
} else {
|
} else {
|
||||||
state.vel[d] -= friction * state.vel[d].signum();
|
state.vel[d] -= friction * state.vel[d].signum();
|
||||||
// Offset has an inverted coordinate system compared to
|
// Offset has an inverted coordinate system compared to
|
||||||
// the velocity, so we subtract it instead of adding it
|
// the velocity, so we subtract it instead of adding it
|
||||||
state.offset[d] -= state.vel[d] * dt;
|
state.offset[d] -= state.vel[d] * dt;
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the desired mouse cursors.
|
// Set the desired mouse cursors.
|
||||||
if let Some(response) = &content_response_option {
|
if let Some(response) = &content_response_option {
|
||||||
if response.dragged()
|
if response.dragged()
|
||||||
&& let Some(cursor) = on_drag_cursor
|
&& let Some(cursor) = on_drag_cursor
|
||||||
{
|
{
|
||||||
ui.set_cursor_icon(cursor);
|
ui.set_cursor_icon(cursor);
|
||||||
} else if response.hovered()
|
} else if response.hovered()
|
||||||
&& let Some(cursor) = on_hover_cursor
|
&& let Some(cursor) = on_hover_cursor
|
||||||
{
|
{
|
||||||
ui.set_cursor_icon(cursor);
|
ui.set_cursor_icon(cursor);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content_response_option
|
content_response_option
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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).
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use epaint::CornerRadiusF32;
|
|||||||
use crate::collapsing_header::CollapsingState;
|
use crate::collapsing_header::CollapsingState;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use super::scroll_area::{ScrollBarVisibility, ScrollSource};
|
use super::scroll_area::{DragScroll, ScrollBarVisibility, ScrollSource};
|
||||||
use super::{Area, Frame, Resize, ScrollArea, area, resize};
|
use super::{Area, Frame, Resize, ScrollArea, area, resize};
|
||||||
|
|
||||||
/// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default).
|
/// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default).
|
||||||
@@ -438,11 +438,13 @@ impl<'a> Window<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable/disable scrolling on the window by dragging with the pointer. `true` by default.
|
/// Controls scrolling the window by dragging the contents with the pointer.
|
||||||
///
|
///
|
||||||
/// See [`ScrollArea::scroll_source`] for more.
|
/// Defaults to [`DragScroll::OnTouch`] — only active when a touch screen is detected.
|
||||||
|
///
|
||||||
|
/// See [`ScrollArea::scroll_source`] and [`DragScroll`] for more.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self {
|
pub fn drag_to_scroll(mut self, drag_to_scroll: DragScroll) -> Self {
|
||||||
self.scroll = self.scroll.scroll_source(ScrollSource {
|
self.scroll = self.scroll.scroll_source(ScrollSource {
|
||||||
drag: drag_to_scroll,
|
drag: drag_to_scroll,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, Id, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b,
|
Align, Id, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b,
|
||||||
scroll_area::{ScrollAreaOutput, ScrollBarVisibility, ScrollSource},
|
scroll_area::{DragScroll, ScrollAreaOutput, ScrollBarVisibility, ScrollSource},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -180,7 +180,7 @@ fn to_sizing(columns: &[Column]) -> crate::sizing::Sizing {
|
|||||||
|
|
||||||
struct TableScrollOptions {
|
struct TableScrollOptions {
|
||||||
vscroll: bool,
|
vscroll: bool,
|
||||||
drag_to_scroll: bool,
|
drag_to_scroll: DragScroll,
|
||||||
stick_to_bottom: bool,
|
stick_to_bottom: bool,
|
||||||
scroll_to_row: Option<(usize, Option<Align>)>,
|
scroll_to_row: Option<(usize, Option<Align>)>,
|
||||||
scroll_offset_y: Option<f32>,
|
scroll_offset_y: Option<f32>,
|
||||||
@@ -195,7 +195,7 @@ impl Default for TableScrollOptions {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
vscroll: true,
|
vscroll: true,
|
||||||
drag_to_scroll: true,
|
drag_to_scroll: DragScroll::OnTouch,
|
||||||
stick_to_bottom: false,
|
stick_to_bottom: false,
|
||||||
scroll_to_row: None,
|
scroll_to_row: None,
|
||||||
scroll_offset_y: None,
|
scroll_offset_y: None,
|
||||||
@@ -318,11 +318,13 @@ impl<'a> TableBuilder<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables scrolling the table's contents using mouse drag (default: `true`).
|
/// Controls scrolling the table's contents by dragging with the pointer.
|
||||||
///
|
///
|
||||||
/// See [`ScrollArea::scroll_source`] for more.
|
/// Defaults to [`DragScroll::OnTouch`] — only active when a touch screen is detected.
|
||||||
|
///
|
||||||
|
/// See [`ScrollArea::scroll_source`] and [`DragScroll`] for more.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self {
|
pub fn drag_to_scroll(mut self, drag_to_scroll: DragScroll) -> Self {
|
||||||
self.scroll_options.drag_to_scroll = drag_to_scroll;
|
self.scroll_options.drag_to_scroll = drag_to_scroll;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user