Merge pull request #2973 from nz366/auto-scroll
Add `auto_scroll` support for `scrollable`
This commit is contained in:
commit
c21289df4a
13 changed files with 513 additions and 108 deletions
|
|
@ -45,6 +45,10 @@ impl text::Renderer for () {
|
||||||
const ICON_FONT: Font = Font::DEFAULT;
|
const ICON_FONT: Font = Font::DEFAULT;
|
||||||
const CHECKMARK_ICON: char = '0';
|
const CHECKMARK_ICON: char = '0';
|
||||||
const ARROW_DOWN_ICON: char = '0';
|
const ARROW_DOWN_ICON: char = '0';
|
||||||
|
const SCROLL_UP_ICON: char = '0';
|
||||||
|
const SCROLL_DOWN_ICON: char = '0';
|
||||||
|
const SCROLL_LEFT_ICON: char = '0';
|
||||||
|
const SCROLL_RIGHT_ICON: char = '0';
|
||||||
const ICED_LOGO: char = '0';
|
const ICED_LOGO: char = '0';
|
||||||
|
|
||||||
fn default_font(&self) -> Self::Font {
|
fn default_font(&self) -> Self::Font {
|
||||||
|
|
|
||||||
|
|
@ -312,6 +312,26 @@ pub trait Renderer: crate::Renderer {
|
||||||
/// [`ICON_FONT`]: Self::ICON_FONT
|
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||||
const ARROW_DOWN_ICON: char;
|
const ARROW_DOWN_ICON: char;
|
||||||
|
|
||||||
|
/// The `char` representing a ^ icon in the built-in [`ICON_FONT`].
|
||||||
|
///
|
||||||
|
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||||
|
const SCROLL_UP_ICON: char;
|
||||||
|
|
||||||
|
/// The `char` representing a v icon in the built-in [`ICON_FONT`].
|
||||||
|
///
|
||||||
|
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||||
|
const SCROLL_DOWN_ICON: char;
|
||||||
|
|
||||||
|
/// The `char` representing a < icon in the built-in [`ICON_FONT`].
|
||||||
|
///
|
||||||
|
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||||
|
const SCROLL_LEFT_ICON: char;
|
||||||
|
|
||||||
|
/// The `char` representing a > icon in the built-in [`ICON_FONT`].
|
||||||
|
///
|
||||||
|
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||||
|
const SCROLL_RIGHT_ICON: char;
|
||||||
|
|
||||||
/// The 'char' representing the iced logo in the built-in ['ICON_FONT'].
|
/// The 'char' representing the iced logo in the built-in ['ICON_FONT'].
|
||||||
///
|
///
|
||||||
/// ['ICON_FONT']: Self::ICON_FONT
|
/// ['ICON_FONT']: Self::ICON_FONT
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,9 @@ impl Gallery {
|
||||||
.height(grid::aspect_ratio(Preview::WIDTH, Preview::HEIGHT))
|
.height(grid::aspect_ratio(Preview::WIDTH, Preview::HEIGHT))
|
||||||
.spacing(10);
|
.spacing(10);
|
||||||
|
|
||||||
let content = container(scrollable(gallery).spacing(10)).padding(10);
|
let content =
|
||||||
|
container(scrollable(gallery).spacing(10).auto_scroll(true))
|
||||||
|
.padding(10);
|
||||||
let viewer = self.viewer.view(self.now);
|
let viewer = self.viewer.view(self.now);
|
||||||
|
|
||||||
stack![content, viewer].into()
|
stack![content, viewer].into()
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,8 @@ impl ScrollableDemo {
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.id(SCROLLABLE)
|
.id(SCROLLABLE)
|
||||||
.on_scroll(Message::Scrolled),
|
.on_scroll(Message::Scrolled)
|
||||||
|
.auto_scroll(true),
|
||||||
Direction::Horizontal => scrollable(
|
Direction::Horizontal => scrollable(
|
||||||
row![
|
row![
|
||||||
scroll_to_end_button(),
|
scroll_to_end_button(),
|
||||||
|
|
@ -236,7 +237,8 @@ impl ScrollableDemo {
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.id(SCROLLABLE)
|
.id(SCROLLABLE)
|
||||||
.on_scroll(Message::Scrolled),
|
.on_scroll(Message::Scrolled)
|
||||||
|
.auto_scroll(true),
|
||||||
Direction::Multi => scrollable(
|
Direction::Multi => scrollable(
|
||||||
//horizontal content
|
//horizontal content
|
||||||
row![
|
row![
|
||||||
|
|
@ -283,7 +285,8 @@ impl ScrollableDemo {
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.id(SCROLLABLE)
|
.id(SCROLLABLE)
|
||||||
.on_scroll(Message::Scrolled),
|
.on_scroll(Message::Scrolled)
|
||||||
|
.auto_scroll(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
let progress_bars: Element<Message> = match self.scrollable_direction {
|
let progress_bars: Element<Message> = match self.scrollable_direction {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,8 @@ impl Styling {
|
||||||
"You did it!"
|
"You did it!"
|
||||||
])
|
])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill);
|
.height(Fill)
|
||||||
|
.auto_scroll(true);
|
||||||
|
|
||||||
let check = checkbox(self.checkbox_value)
|
let check = checkbox(self.checkbox_value)
|
||||||
.label("Check me!")
|
.label("Check me!")
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,8 @@ impl Tour {
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
}))
|
}))
|
||||||
.spacing(10);
|
.spacing(10)
|
||||||
|
.auto_scroll(true);
|
||||||
|
|
||||||
center_y(scrollable).padding(10).into()
|
center_y(scrollable).padding(10).into()
|
||||||
}
|
}
|
||||||
|
|
@ -626,7 +627,7 @@ pub enum Layout {
|
||||||
impl Default for Tour {
|
impl Default for Tour {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
screen: Screen::Welcome,
|
screen: Screen::Scrollable,
|
||||||
slider: 50,
|
slider: 50,
|
||||||
layout: Layout::Row,
|
layout: Layout::Row,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -98,6 +98,10 @@ where
|
||||||
const ICON_FONT: Self::Font = A::ICON_FONT;
|
const ICON_FONT: Self::Font = A::ICON_FONT;
|
||||||
const CHECKMARK_ICON: char = A::CHECKMARK_ICON;
|
const CHECKMARK_ICON: char = A::CHECKMARK_ICON;
|
||||||
const ARROW_DOWN_ICON: char = A::ARROW_DOWN_ICON;
|
const ARROW_DOWN_ICON: char = A::ARROW_DOWN_ICON;
|
||||||
|
const SCROLL_UP_ICON: char = A::SCROLL_UP_ICON;
|
||||||
|
const SCROLL_DOWN_ICON: char = A::SCROLL_DOWN_ICON;
|
||||||
|
const SCROLL_LEFT_ICON: char = A::SCROLL_LEFT_ICON;
|
||||||
|
const SCROLL_RIGHT_ICON: char = A::SCROLL_RIGHT_ICON;
|
||||||
const ICED_LOGO: char = A::ICED_LOGO;
|
const ICED_LOGO: char = A::ICED_LOGO;
|
||||||
|
|
||||||
fn default_font(&self) -> Self::Font {
|
fn default_font(&self) -> Self::Font {
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,10 @@ impl core::text::Renderer for Renderer {
|
||||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||||
const ICED_LOGO: char = '\u{e801}';
|
const ICED_LOGO: char = '\u{e801}';
|
||||||
|
const SCROLL_UP_ICON: char = '\u{e802}';
|
||||||
|
const SCROLL_DOWN_ICON: char = '\u{e803}';
|
||||||
|
const SCROLL_LEFT_ICON: char = '\u{e804}';
|
||||||
|
const SCROLL_RIGHT_ICON: char = '\u{e805}';
|
||||||
|
|
||||||
fn default_font(&self) -> Self::Font {
|
fn default_font(&self) -> Self::Font {
|
||||||
self.default_font
|
self.default_font
|
||||||
|
|
|
||||||
|
|
@ -722,6 +722,10 @@ impl core::text::Renderer for Renderer {
|
||||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||||
const ICED_LOGO: char = '\u{e801}';
|
const ICED_LOGO: char = '\u{e801}';
|
||||||
|
const SCROLL_UP_ICON: char = '\u{e802}';
|
||||||
|
const SCROLL_DOWN_ICON: char = '\u{e803}';
|
||||||
|
const SCROLL_LEFT_ICON: char = '\u{e804}';
|
||||||
|
const SCROLL_RIGHT_ICON: char = '\u{e805}';
|
||||||
|
|
||||||
fn default_font(&self) -> Self::Font {
|
fn default_font(&self) -> Self::Font {
|
||||||
self.default_font
|
self.default_font
|
||||||
|
|
|
||||||
|
|
@ -1048,7 +1048,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>(
|
||||||
) -> Scrollable<'a, Message, Theme, Renderer>
|
) -> Scrollable<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Theme: scrollable::Catalog + 'a,
|
Theme: scrollable::Catalog + 'a,
|
||||||
Renderer: core::Renderer,
|
Renderer: core::text::Renderer,
|
||||||
{
|
{
|
||||||
Scrollable::new(content)
|
Scrollable::new(content)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ impl Default for State {
|
||||||
struct Overlay<'a, 'b, Message, Theme, Renderer>
|
struct Overlay<'a, 'b, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: crate::core::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
position: Point,
|
position: Point,
|
||||||
viewport: Rectangle,
|
viewport: Rectangle,
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,14 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use crate::container;
|
use crate::container;
|
||||||
|
use crate::core::alignment;
|
||||||
use crate::core::border::{self, Border};
|
use crate::core::border::{self, Border};
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
|
use crate::core::text;
|
||||||
use crate::core::time::{Duration, Instant};
|
use crate::core::time::{Duration, Instant};
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
|
|
@ -69,12 +71,13 @@ pub struct Scrollable<
|
||||||
Renderer = crate::Renderer,
|
Renderer = crate::Renderer,
|
||||||
> where
|
> where
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: core::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
id: Option<widget::Id>,
|
id: Option<widget::Id>,
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Length,
|
height: Length,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
|
auto_scroll: bool,
|
||||||
content: Element<'a, Message, Theme, Renderer>,
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
|
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
|
@ -84,7 +87,7 @@ pub struct Scrollable<
|
||||||
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: core::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates a new vertical [`Scrollable`].
|
/// Creates a new vertical [`Scrollable`].
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
|
@ -103,6 +106,7 @@ where
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
height: Length::Shrink,
|
height: Length::Shrink,
|
||||||
direction: direction.into(),
|
direction: direction.into(),
|
||||||
|
auto_scroll: false,
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
on_scroll: None,
|
on_scroll: None,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
|
@ -225,6 +229,15 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether the user should be allowed to auto-scroll the [`Scrollable`]
|
||||||
|
/// with the middle mouse button.
|
||||||
|
///
|
||||||
|
/// By default, it is disabled.
|
||||||
|
pub fn auto_scroll(mut self, auto_scroll: bool) -> Self {
|
||||||
|
self.auto_scroll = auto_scroll;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the style of this [`Scrollable`].
|
/// Sets the style of this [`Scrollable`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
|
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
|
||||||
|
|
@ -388,7 +401,7 @@ impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
for Scrollable<'_, Message, Theme, Renderer>
|
for Scrollable<'_, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: core::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
tree::Tag::of::<State>()
|
tree::Tag::of::<State>()
|
||||||
|
|
@ -567,6 +580,9 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
|
const AUTOSCROLL_DEADZONE: f32 = 20.0;
|
||||||
|
const AUTOSCROLL_SMOOTHNESS: f32 = 1.5;
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let cursor_over_scrollable = cursor.position_over(bounds);
|
let cursor_over_scrollable = cursor.position_over(bounds);
|
||||||
|
|
@ -601,7 +617,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut update = || {
|
let mut update = || {
|
||||||
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
|
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at() {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
|
|
@ -657,8 +673,9 @@ where
|
||||||
content_bounds,
|
content_bounds,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.y_scroller_grabbed_at =
|
state.interaction = Interaction::YScrollerGrabbed(
|
||||||
Some(scroller_grabbed_at);
|
scroller_grabbed_at,
|
||||||
|
);
|
||||||
|
|
||||||
let _ = notify_scroll(
|
let _ = notify_scroll(
|
||||||
state,
|
state,
|
||||||
|
|
@ -675,7 +692,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
|
if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at() {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
|
|
@ -730,8 +747,9 @@ where
|
||||||
content_bounds,
|
content_bounds,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.x_scroller_grabbed_at =
|
state.interaction = Interaction::XScrollerGrabbed(
|
||||||
Some(scroller_grabbed_at);
|
scroller_grabbed_at,
|
||||||
|
);
|
||||||
|
|
||||||
let _ = notify_scroll(
|
let _ = notify_scroll(
|
||||||
state,
|
state,
|
||||||
|
|
@ -748,6 +766,23 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches!(state.interaction, Interaction::AutoScrolling { .. })
|
||||||
|
&& matches!(
|
||||||
|
event,
|
||||||
|
Event::Mouse(
|
||||||
|
mouse::Event::ButtonPressed(_)
|
||||||
|
| mouse::Event::WheelScrolled { .. }
|
||||||
|
) | Event::Touch(_)
|
||||||
|
| Event::Keyboard(_)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
state.interaction = Interaction::None;
|
||||||
|
shell.capture_event();
|
||||||
|
shell.invalidate_layout();
|
||||||
|
shell.request_redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if state.last_scrolled.is_none()
|
if state.last_scrolled.is_none()
|
||||||
|| !matches!(
|
|| !matches!(
|
||||||
event,
|
event,
|
||||||
|
|
@ -800,10 +835,7 @@ where
|
||||||
| touch::Event::FingerLost { .. }
|
| touch::Event::FingerLost { .. }
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
state.scroll_area_touched_at = None;
|
state.interaction = Interaction::None;
|
||||||
state.x_scroller_grabbed_at = None;
|
|
||||||
state.y_scroller_grabbed_at = None;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -811,15 +843,6 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Keyboard(keyboard::Event::ModifiersChanged(
|
|
||||||
modifiers,
|
|
||||||
)) = event
|
|
||||||
{
|
|
||||||
state.keyboard_modifiers = *modifiers;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
|
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
|
||||||
if cursor_over_scrollable.is_none() {
|
if cursor_over_scrollable.is_none() {
|
||||||
|
|
@ -874,58 +897,190 @@ where
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonPressed(
|
||||||
|
mouse::Button::Middle,
|
||||||
|
)) if self.auto_scroll
|
||||||
|
&& matches!(state.interaction, Interaction::None) =>
|
||||||
|
{
|
||||||
|
let Some(origin) = cursor_over_scrollable else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
state.interaction = Interaction::AutoScrolling {
|
||||||
|
origin,
|
||||||
|
current: origin,
|
||||||
|
last_frame: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
shell.capture_event();
|
||||||
|
shell.invalidate_layout();
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
Event::Touch(event)
|
Event::Touch(event)
|
||||||
if state.scroll_area_touched_at.is_some()
|
if matches!(
|
||||||
|| (!mouse_over_y_scrollbar
|
state.interaction,
|
||||||
&& !mouse_over_x_scrollbar) =>
|
Interaction::TouchScrolling(_)
|
||||||
|
) || (!mouse_over_y_scrollbar
|
||||||
|
&& !mouse_over_x_scrollbar) =>
|
||||||
{
|
{
|
||||||
match event {
|
match event {
|
||||||
touch::Event::FingerPressed { .. } => {
|
touch::Event::FingerPressed { .. } => {
|
||||||
if cursor_over_scrollable.is_none() {
|
let Some(position) = cursor_over_scrollable else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
state.scroll_area_touched_at = cursor.position();
|
state.interaction =
|
||||||
|
Interaction::TouchScrolling(position);
|
||||||
}
|
}
|
||||||
touch::Event::FingerMoved { .. } => {
|
touch::Event::FingerMoved { .. } => {
|
||||||
if let Some(scroll_box_touched_at) =
|
let Interaction::TouchScrolling(
|
||||||
state.scroll_area_touched_at
|
scroll_box_touched_at,
|
||||||
{
|
) = state.interaction
|
||||||
let Some(cursor_position) = cursor.position()
|
else {
|
||||||
else {
|
return;
|
||||||
return;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let delta = Vector::new(
|
let Some(cursor_position) = cursor.position()
|
||||||
scroll_box_touched_at.x - cursor_position.x,
|
else {
|
||||||
scroll_box_touched_at.y - cursor_position.y,
|
return;
|
||||||
);
|
};
|
||||||
|
|
||||||
state.scroll(
|
let delta = Vector::new(
|
||||||
self.direction.align(delta),
|
scroll_box_touched_at.x - cursor_position.x,
|
||||||
bounds,
|
scroll_box_touched_at.y - cursor_position.y,
|
||||||
content_bounds,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
state.scroll_area_touched_at =
|
state.scroll(
|
||||||
Some(cursor_position);
|
self.direction.align(delta),
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: bubble up touch movements if not consumed.
|
state.interaction =
|
||||||
let _ = notify_scroll(
|
Interaction::TouchScrolling(cursor_position);
|
||||||
state,
|
|
||||||
&self.on_scroll,
|
// TODO: bubble up touch movements if not consumed.
|
||||||
bounds,
|
let _ = notify_scroll(
|
||||||
content_bounds,
|
state,
|
||||||
shell,
|
&self.on_scroll,
|
||||||
);
|
bounds,
|
||||||
}
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::RedrawRequested(_)) => {
|
Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
||||||
|
if let Interaction::AutoScrolling {
|
||||||
|
origin,
|
||||||
|
last_frame,
|
||||||
|
..
|
||||||
|
} = state.interaction
|
||||||
|
{
|
||||||
|
let delta = *position - origin;
|
||||||
|
|
||||||
|
state.interaction = Interaction::AutoScrolling {
|
||||||
|
origin,
|
||||||
|
current: *position,
|
||||||
|
last_frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (delta.x.abs() >= AUTOSCROLL_DEADZONE
|
||||||
|
|| delta.y.abs() >= AUTOSCROLL_DEADZONE)
|
||||||
|
&& last_frame.is_none()
|
||||||
|
{
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(keyboard::Event::ModifiersChanged(
|
||||||
|
modifiers,
|
||||||
|
)) => {
|
||||||
|
state.keyboard_modifiers = *modifiers;
|
||||||
|
}
|
||||||
|
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||||
|
if let Interaction::AutoScrolling {
|
||||||
|
origin,
|
||||||
|
current,
|
||||||
|
last_frame,
|
||||||
|
} = state.interaction
|
||||||
|
{
|
||||||
|
if last_frame == Some(*now) {
|
||||||
|
shell.request_redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.interaction = Interaction::AutoScrolling {
|
||||||
|
origin,
|
||||||
|
current,
|
||||||
|
last_frame: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut delta = current - origin;
|
||||||
|
|
||||||
|
if delta.x.abs() < AUTOSCROLL_DEADZONE {
|
||||||
|
delta.x = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if delta.y.abs() < AUTOSCROLL_DEADZONE {
|
||||||
|
delta.y = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if delta.x != 0.0 || delta.y != 0.0 {
|
||||||
|
let time_delta =
|
||||||
|
if let Some(last_frame) = last_frame {
|
||||||
|
*now - last_frame
|
||||||
|
} else {
|
||||||
|
Duration::ZERO
|
||||||
|
};
|
||||||
|
|
||||||
|
let scroll_factor = time_delta.as_secs_f32();
|
||||||
|
|
||||||
|
state.scroll(
|
||||||
|
self.direction.align(Vector::new(
|
||||||
|
delta.x.signum()
|
||||||
|
* delta
|
||||||
|
.x
|
||||||
|
.abs()
|
||||||
|
.powf(AUTOSCROLL_SMOOTHNESS)
|
||||||
|
* scroll_factor,
|
||||||
|
delta.y.signum()
|
||||||
|
* delta
|
||||||
|
.y
|
||||||
|
.abs()
|
||||||
|
.powf(AUTOSCROLL_SMOOTHNESS)
|
||||||
|
* scroll_factor,
|
||||||
|
)),
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
let has_scrolled = notify_scroll(
|
||||||
|
state,
|
||||||
|
&self.on_scroll,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
|
|
||||||
|
if has_scrolled || time_delta.is_zero() {
|
||||||
|
state.interaction =
|
||||||
|
Interaction::AutoScrolling {
|
||||||
|
origin,
|
||||||
|
current,
|
||||||
|
last_frame: Some(*now),
|
||||||
|
};
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = notify_viewport(
|
let _ = notify_viewport(
|
||||||
state,
|
state,
|
||||||
&self.on_scroll,
|
&self.on_scroll,
|
||||||
|
|
@ -940,15 +1095,13 @@ where
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
let status = if state.y_scroller_grabbed_at.is_some()
|
let status = if state.scrollers_grabbed() {
|
||||||
|| state.x_scroller_grabbed_at.is_some()
|
|
||||||
{
|
|
||||||
Status::Dragged {
|
Status::Dragged {
|
||||||
is_horizontal_scrollbar_dragged: state
|
is_horizontal_scrollbar_dragged: state
|
||||||
.x_scroller_grabbed_at
|
.x_scroller_grabbed_at()
|
||||||
.is_some(),
|
.is_some(),
|
||||||
is_vertical_scrollbar_dragged: state
|
is_vertical_scrollbar_dragged: state
|
||||||
.y_scroller_grabbed_at
|
.y_scroller_grabbed_at()
|
||||||
.is_some(),
|
.is_some(),
|
||||||
is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(),
|
is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(),
|
||||||
is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(),
|
is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(),
|
||||||
|
|
@ -1212,24 +1365,196 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||||
|
let state = tree.state.downcast_ref::<State>();
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let content_layout = layout.children().next().unwrap();
|
let content_layout = layout.children().next().unwrap();
|
||||||
let content_bounds = content_layout.bounds();
|
let content_bounds = content_layout.bounds();
|
||||||
let visible_bounds = bounds.intersection(viewport).unwrap_or(*viewport);
|
let visible_bounds = bounds.intersection(viewport).unwrap_or(*viewport);
|
||||||
|
let offset = state.translation(self.direction, bounds, content_bounds);
|
||||||
|
|
||||||
let offset = tree.state.downcast_ref::<State>().translation(
|
let overlay = self.content.as_widget_mut().overlay(
|
||||||
self.direction,
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.content.as_widget_mut().overlay(
|
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
layout.children().next().unwrap(),
|
layout.children().next().unwrap(),
|
||||||
renderer,
|
renderer,
|
||||||
&visible_bounds,
|
&visible_bounds,
|
||||||
translation - offset,
|
translation - offset,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
let icon = if let Interaction::AutoScrolling { origin, .. } =
|
||||||
|
state.interaction
|
||||||
|
{
|
||||||
|
let scrollbars =
|
||||||
|
Scrollbars::new(state, self.direction, bounds, content_bounds);
|
||||||
|
|
||||||
|
Some(overlay::Element::new(Box::new(AutoScrollIcon {
|
||||||
|
origin,
|
||||||
|
vertical: scrollbars.y.is_some(),
|
||||||
|
horizontal: scrollbars.x.is_some(),
|
||||||
|
class: &self.class,
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match (overlay, icon) {
|
||||||
|
(None, None) => None,
|
||||||
|
(None, Some(icon)) => Some(icon),
|
||||||
|
(Some(overlay), None) => Some(overlay),
|
||||||
|
(Some(overlay), Some(icon)) => Some(overlay::Element::new(
|
||||||
|
Box::new(overlay::Group::with_children(vec![overlay, icon])),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AutoScrollIcon<'a, Class> {
|
||||||
|
origin: Point,
|
||||||
|
vertical: bool,
|
||||||
|
horizontal: bool,
|
||||||
|
class: &'a Class,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Class> AutoScrollIcon<'_, Class> {
|
||||||
|
const SIZE: f32 = 40.0;
|
||||||
|
const DOT: f32 = Self::SIZE / 10.0;
|
||||||
|
const PADDING: f32 = Self::SIZE / 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message, Theme, Renderer> core::Overlay<Message, Theme, Renderer>
|
||||||
|
for AutoScrollIcon<'_, Theme::Class<'_>>
|
||||||
|
where
|
||||||
|
Renderer: text::Renderer,
|
||||||
|
Theme: Catalog,
|
||||||
|
{
|
||||||
|
fn layout(&mut self, _renderer: &Renderer, _bounds: Size) -> layout::Node {
|
||||||
|
layout::Node::new(Size::new(Self::SIZE, Self::SIZE))
|
||||||
|
.move_to(self.origin - Vector::new(Self::SIZE, Self::SIZE) / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let style = theme
|
||||||
|
.style(
|
||||||
|
self.class,
|
||||||
|
Status::Active {
|
||||||
|
is_horizontal_scrollbar_disabled: false,
|
||||||
|
is_vertical_scrollbar_disabled: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.auto_scroll;
|
||||||
|
|
||||||
|
renderer.with_layer(Rectangle::INFINITE, |renderer| {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border: style.border,
|
||||||
|
shadow: core::Shadow {
|
||||||
|
color: Color::BLACK.scale_alpha(0.8),
|
||||||
|
offset: Vector::new(1.0, 1.0),
|
||||||
|
blur_radius: 3.0,
|
||||||
|
},
|
||||||
|
snap: false,
|
||||||
|
},
|
||||||
|
style.background,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle::new(
|
||||||
|
bounds.center()
|
||||||
|
- Vector::new(Self::DOT, Self::DOT) / 2.0,
|
||||||
|
Size::new(Self::DOT, Self::DOT),
|
||||||
|
),
|
||||||
|
border: border::rounded(bounds.width),
|
||||||
|
snap: false,
|
||||||
|
..renderer::Quad::default()
|
||||||
|
},
|
||||||
|
style.icon,
|
||||||
|
);
|
||||||
|
|
||||||
|
let arrow = core::Text {
|
||||||
|
content: String::new(),
|
||||||
|
bounds: bounds.size(),
|
||||||
|
size: Pixels::from(12),
|
||||||
|
line_height: text::LineHeight::Relative(1.0),
|
||||||
|
font: Renderer::ICON_FONT,
|
||||||
|
align_x: text::Alignment::Center,
|
||||||
|
align_y: alignment::Vertical::Center,
|
||||||
|
shaping: text::Shaping::Basic,
|
||||||
|
wrapping: text::Wrapping::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.vertical {
|
||||||
|
renderer.fill_text(
|
||||||
|
core::Text {
|
||||||
|
content: Renderer::SCROLL_UP_ICON.to_string(),
|
||||||
|
align_y: alignment::Vertical::Top,
|
||||||
|
..arrow
|
||||||
|
},
|
||||||
|
Point::new(
|
||||||
|
bounds.center_x(),
|
||||||
|
bounds.y + Self::PADDING - 1.0,
|
||||||
|
),
|
||||||
|
style.icon,
|
||||||
|
bounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.fill_text(
|
||||||
|
core::Text {
|
||||||
|
content: Renderer::SCROLL_DOWN_ICON.to_string(),
|
||||||
|
align_y: alignment::Vertical::Bottom,
|
||||||
|
..arrow
|
||||||
|
},
|
||||||
|
Point::new(
|
||||||
|
bounds.center_x(),
|
||||||
|
bounds.y + bounds.height - Self::PADDING + 1.0,
|
||||||
|
),
|
||||||
|
style.icon,
|
||||||
|
bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.horizontal {
|
||||||
|
renderer.fill_text(
|
||||||
|
core::Text {
|
||||||
|
content: Renderer::SCROLL_LEFT_ICON.to_string(),
|
||||||
|
align_x: text::Alignment::Left,
|
||||||
|
..arrow
|
||||||
|
},
|
||||||
|
Point::new(
|
||||||
|
bounds.x + Self::PADDING,
|
||||||
|
bounds.center_y() + 1.0,
|
||||||
|
),
|
||||||
|
style.icon,
|
||||||
|
bounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.fill_text(
|
||||||
|
core::Text {
|
||||||
|
content: Renderer::SCROLL_RIGHT_ICON.to_string(),
|
||||||
|
align_x: text::Alignment::Right,
|
||||||
|
..arrow
|
||||||
|
},
|
||||||
|
Point::new(
|
||||||
|
bounds.x + bounds.width - Self::PADDING,
|
||||||
|
bounds.center_y() + 1.0,
|
||||||
|
),
|
||||||
|
style.icon,
|
||||||
|
bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(&self) -> f32 {
|
||||||
|
f32::MAX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1239,7 +1564,7 @@ impl<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
Theme: 'a + Catalog,
|
Theme: 'a + Catalog,
|
||||||
Renderer: 'a + core::Renderer,
|
Renderer: 'a + text::Renderer,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
text_input: Scrollable<'a, Message, Theme, Renderer>,
|
text_input: Scrollable<'a, Message, Theme, Renderer>,
|
||||||
|
|
@ -1318,25 +1643,34 @@ fn notify_viewport<Message>(
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct State {
|
struct State {
|
||||||
scroll_area_touched_at: Option<Point>,
|
|
||||||
offset_y: Offset,
|
offset_y: Offset,
|
||||||
y_scroller_grabbed_at: Option<f32>,
|
|
||||||
offset_x: Offset,
|
offset_x: Offset,
|
||||||
x_scroller_grabbed_at: Option<f32>,
|
interaction: Interaction,
|
||||||
keyboard_modifiers: keyboard::Modifiers,
|
keyboard_modifiers: keyboard::Modifiers,
|
||||||
last_notified: Option<Viewport>,
|
last_notified: Option<Viewport>,
|
||||||
last_scrolled: Option<Instant>,
|
last_scrolled: Option<Instant>,
|
||||||
is_scrollbar_visible: bool,
|
is_scrollbar_visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Interaction {
|
||||||
|
None,
|
||||||
|
YScrollerGrabbed(f32),
|
||||||
|
XScrollerGrabbed(f32),
|
||||||
|
TouchScrolling(Point),
|
||||||
|
AutoScrolling {
|
||||||
|
origin: Point,
|
||||||
|
current: Point,
|
||||||
|
last_frame: Option<Instant>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
scroll_area_touched_at: None,
|
|
||||||
offset_y: Offset::Absolute(0.0),
|
offset_y: Offset::Absolute(0.0),
|
||||||
y_scroller_grabbed_at: None,
|
|
||||||
offset_x: Offset::Absolute(0.0),
|
offset_x: Offset::Absolute(0.0),
|
||||||
x_scroller_grabbed_at: None,
|
interaction: Interaction::None,
|
||||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||||
last_notified: None,
|
last_notified: None,
|
||||||
last_scrolled: None,
|
last_scrolled: None,
|
||||||
|
|
@ -1455,14 +1789,11 @@ impl Viewport {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Creates a new [`State`] with the scrollbar(s) at the beginning.
|
fn new() -> Self {
|
||||||
pub fn new() -> Self {
|
|
||||||
State::default()
|
State::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a scrolling offset to the current [`State`], given the bounds of
|
fn scroll(
|
||||||
/// the [`Scrollable`] and its contents.
|
|
||||||
pub fn scroll(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
delta: Vector<f32>,
|
delta: Vector<f32>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
|
@ -1485,11 +1816,7 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scrolls the [`Scrollable`] to a relative amount along the y axis.
|
fn scroll_y_to(
|
||||||
///
|
|
||||||
/// `0` represents scrollbar at the beginning, while `1` represents scrollbar at
|
|
||||||
/// the end.
|
|
||||||
pub fn scroll_y_to(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
percentage: f32,
|
percentage: f32,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
|
@ -1499,11 +1826,7 @@ impl State {
|
||||||
self.unsnap(bounds, content_bounds);
|
self.unsnap(bounds, content_bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scrolls the [`Scrollable`] to a relative amount along the x axis.
|
fn scroll_x_to(
|
||||||
///
|
|
||||||
/// `0` represents scrollbar at the beginning, while `1` represents scrollbar at
|
|
||||||
/// the end.
|
|
||||||
pub fn scroll_x_to(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
percentage: f32,
|
percentage: f32,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
|
@ -1513,14 +1836,12 @@ impl State {
|
||||||
self.unsnap(bounds, content_bounds);
|
self.unsnap(bounds, content_bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snaps the scroll position to a [`RelativeOffset`].
|
fn snap_to(&mut self, offset: RelativeOffset) {
|
||||||
pub fn snap_to(&mut self, offset: RelativeOffset) {
|
|
||||||
self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0));
|
self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0));
|
||||||
self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
|
self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll to the provided [`AbsoluteOffset`].
|
fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
||||||
pub fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
|
||||||
self.offset_x = Offset::Absolute(offset.x.max(0.0));
|
self.offset_x = Offset::Absolute(offset.x.max(0.0));
|
||||||
self.offset_y = Offset::Absolute(offset.y.max(0.0));
|
self.offset_y = Offset::Absolute(offset.y.max(0.0));
|
||||||
}
|
}
|
||||||
|
|
@ -1580,10 +1901,27 @@ impl State {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether any scroller is currently grabbed or not.
|
fn scrollers_grabbed(&self) -> bool {
|
||||||
pub fn scrollers_grabbed(&self) -> bool {
|
matches!(
|
||||||
self.x_scroller_grabbed_at.is_some()
|
self.interaction,
|
||||||
|| self.y_scroller_grabbed_at.is_some()
|
Interaction::YScrollerGrabbed(_) | Interaction::XScrollerGrabbed(_),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn y_scroller_grabbed_at(&self) -> Option<f32> {
|
||||||
|
let Interaction::YScrollerGrabbed(at) = self.interaction else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(at)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x_scroller_grabbed_at(&self) -> Option<f32> {
|
||||||
|
let Interaction::XScrollerGrabbed(at) = self.interaction else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(at)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1935,6 +2273,8 @@ pub struct Style {
|
||||||
pub horizontal_rail: Rail,
|
pub horizontal_rail: Rail,
|
||||||
/// The [`Background`] of the gap between a horizontal and vertical scrollbar.
|
/// The [`Background`] of the gap between a horizontal and vertical scrollbar.
|
||||||
pub gap: Option<Background>,
|
pub gap: Option<Background>,
|
||||||
|
/// The appearance of the [`AutoScroll`] overlay.
|
||||||
|
pub auto_scroll: AutoScroll,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The appearance of the scrollbar of a scrollable.
|
/// The appearance of the scrollbar of a scrollable.
|
||||||
|
|
@ -1957,6 +2297,17 @@ pub struct Scroller {
|
||||||
pub border: Border,
|
pub border: Border,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The appearance of the autoscroll overlay of a scrollable.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct AutoScroll {
|
||||||
|
/// The [`Background`] of the [`AutoScroll`] overlay.
|
||||||
|
pub background: Background,
|
||||||
|
/// The [`Border`] of the [`AutoScroll`] overlay.
|
||||||
|
pub border: Border,
|
||||||
|
/// The [`Color`] for the arrow icons of the [`AutoScroll`] overlay.
|
||||||
|
pub icon: Color,
|
||||||
|
}
|
||||||
|
|
||||||
/// The theme catalog of a [`Scrollable`].
|
/// The theme catalog of a [`Scrollable`].
|
||||||
pub trait Catalog {
|
pub trait Catalog {
|
||||||
/// The item class of the [`Catalog`].
|
/// The item class of the [`Catalog`].
|
||||||
|
|
@ -1997,12 +2348,21 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let auto_scroll = AutoScroll {
|
||||||
|
background: palette.background.weak.color.scale_alpha(0.9).into(),
|
||||||
|
border: border::rounded(u32::MAX)
|
||||||
|
.width(1)
|
||||||
|
.color(palette.background.weak.text.scale_alpha(0.8)),
|
||||||
|
icon: palette.background.weak.text.scale_alpha(0.8),
|
||||||
|
};
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Status::Active { .. } => Style {
|
Status::Active { .. } => Style {
|
||||||
container: container::Style::default(),
|
container: container::Style::default(),
|
||||||
vertical_rail: scrollbar,
|
vertical_rail: scrollbar,
|
||||||
horizontal_rail: scrollbar,
|
horizontal_rail: scrollbar,
|
||||||
gap: None,
|
gap: None,
|
||||||
|
auto_scroll,
|
||||||
},
|
},
|
||||||
Status::Hovered {
|
Status::Hovered {
|
||||||
is_horizontal_scrollbar_hovered,
|
is_horizontal_scrollbar_hovered,
|
||||||
|
|
@ -2030,6 +2390,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
scrollbar
|
scrollbar
|
||||||
},
|
},
|
||||||
gap: None,
|
gap: None,
|
||||||
|
auto_scroll,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Status::Dragged {
|
Status::Dragged {
|
||||||
|
|
@ -2058,6 +2419,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
scrollbar
|
scrollbar
|
||||||
},
|
},
|
||||||
gap: None,
|
gap: None,
|
||||||
|
auto_scroll,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue