From ebfa9cc7b6609f6700eb1c800f27fcddb2550d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Esp=C3=ADn?= Date: Mon, 16 Oct 2023 23:36:58 +0200 Subject: [PATCH] feat: Add hover and active effects for the scrollbar thumbs (#333) --- .../src/scroll_views/scroll_thumb.rs | 26 ++++++++++++++++--- .../src/scroll_views/scroll_view.rs | 21 ++++++++++++--- .../src/scroll_views/virtual_scroll_view.rs | 21 ++++++++++++--- crates/hooks/src/use_theme.rs | 6 +++++ 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/crates/components/src/scroll_views/scroll_thumb.rs b/crates/components/src/scroll_views/scroll_thumb.rs index 4fcc1b6b4..cdf501521 100644 --- a/crates/components/src/scroll_views/scroll_thumb.rs +++ b/crates/components/src/scroll_views/scroll_thumb.rs @@ -1,10 +1,11 @@ use dioxus::prelude::*; use freya_elements::elements as dioxus_elements; use freya_elements::events::MouseEvent; -use freya_hooks::{use_get_theme, ScrollbarTheme}; +use freya_hooks::use_get_theme; #[derive(Props)] pub struct ScrollThumbProps<'a> { + clicking_scrollbar: bool, onmousedown: EventHandler<'a, MouseEvent>, #[props(into)] width: String, @@ -12,15 +13,32 @@ pub struct ScrollThumbProps<'a> { height: String, } +#[derive(Debug, Default, PartialEq, Clone, Copy)] +pub enum ScrollThumbState { + #[default] + Idle, + // Thumb is being hovered + Hovering, +} + #[allow(non_snake_case)] pub fn ScrollThumb<'a>(cx: Scope<'a, ScrollThumbProps<'a>>) -> Element<'a> { let theme = use_get_theme(cx); - let ScrollbarTheme { - thumb_background, .. - } = &theme.scrollbar; + let state = use_state(cx, ScrollThumbState::default); + let thumb_background = match state.get() { + _ if cx.props.clicking_scrollbar => theme.scrollbar.active_thumb_background, + ScrollThumbState::Idle => theme.scrollbar.thumb_background, + ScrollThumbState::Hovering => theme.scrollbar.hover_thumb_background, + }; render!( rect { + onmouseenter: |_| { + state.set(ScrollThumbState::Hovering) + }, + onmouseleave: |_| { + state.set(ScrollThumbState::Idle) + }, onmousedown: |e| { cx.props.onmousedown.call(e); }, diff --git a/crates/components/src/scroll_views/scroll_view.rs b/crates/components/src/scroll_views/scroll_view.rs index f4060d4f1..e2a4042f8 100644 --- a/crates/components/src/scroll_views/scroll_view.rs +++ b/crates/components/src/scroll_views/scroll_view.rs @@ -227,18 +227,20 @@ pub fn ScrollView<'a>(cx: Scope<'a, ScrollViewProps<'a>>) -> Element { // Mark the Y axis scrollbar as the one being dragged let onmousedown_y = |e: MouseEvent| { let coordinates = e.get_element_coordinates(); - *clicking_scrollbar.write_silent() = Some((Axis::Y, coordinates.y)); + *clicking_scrollbar.write() = Some((Axis::Y, coordinates.y)); }; // Mark the X axis scrollbar as the one being dragged let onmousedown_x = |e: MouseEvent| { let coordinates = e.get_element_coordinates(); - *clicking_scrollbar.write_silent() = Some((Axis::X, coordinates.x)); + *clicking_scrollbar.write() = Some((Axis::X, coordinates.x)); }; // Unmark any scrollbar let onclick = |_: MouseEvent| { - *clicking_scrollbar.write_silent() = None; + if clicking_scrollbar.read().is_some() { + *clicking_scrollbar.write() = None; + } }; let horizontal_scrollbar_size = if horizontal_scrollbar_is_visible { @@ -252,6 +254,17 @@ pub fn ScrollView<'a>(cx: Scope<'a, ScrollViewProps<'a>>) -> Element { 0 }; + let is_scrolling_x = clicking_scrollbar + .read() + .as_ref() + .map(|f| f.0 == Axis::X) + .unwrap_or_default(); + let is_scrolling_y = clicking_scrollbar + .read() + .as_ref() + .map(|f| f.0 == Axis::Y) + .unwrap_or_default(); + render!( rect { role: "scrollView", @@ -284,6 +297,7 @@ pub fn ScrollView<'a>(cx: Scope<'a, ScrollViewProps<'a>>) -> Element { height: "{horizontal_scrollbar_size}", offset_x: "{scrollbar_x}", ScrollThumb { + clicking_scrollbar: is_scrolling_x, onmousedown: onmousedown_x, width: "{scrollbar_width}", height: "100%", @@ -295,6 +309,7 @@ pub fn ScrollView<'a>(cx: Scope<'a, ScrollViewProps<'a>>) -> Element { height: "100%", offset_y: "{scrollbar_y}", ScrollThumb { + clicking_scrollbar: is_scrolling_y, onmousedown: onmousedown_y, width: "100%", height: "{scrollbar_height}", diff --git a/crates/components/src/scroll_views/virtual_scroll_view.rs b/crates/components/src/scroll_views/virtual_scroll_view.rs index d90ea6e96..da208ba2f 100644 --- a/crates/components/src/scroll_views/virtual_scroll_view.rs +++ b/crates/components/src/scroll_views/virtual_scroll_view.rs @@ -262,18 +262,20 @@ pub fn VirtualScrollView<'a, T>(cx: Scope<'a, VirtualScrollViewProps<'a, T>>) -> // Mark the Y axis scrollbar as the one being dragged let onmousedown_y = |e: MouseEvent| { let coordinates = e.get_element_coordinates(); - *clicking_scrollbar.write_silent() = Some((Axis::Y, coordinates.y)); + *clicking_scrollbar.write() = Some((Axis::Y, coordinates.y)); }; // Mark the X axis scrollbar as the one being dragged let onmousedown_x = |e: MouseEvent| { let coordinates = e.get_element_coordinates(); - *clicking_scrollbar.write_silent() = Some((Axis::X, coordinates.x)); + *clicking_scrollbar.write() = Some((Axis::X, coordinates.x)); }; // Unmark any scrollbar let onclick = |_: MouseEvent| { - *clicking_scrollbar.write_silent() = None; + if clicking_scrollbar.read().is_some() { + *clicking_scrollbar.write() = None; + } }; let horizontal_scrollbar_size = if horizontal_scrollbar_is_visible { @@ -305,6 +307,17 @@ pub fn VirtualScrollView<'a, T>(cx: Scope<'a, VirtualScrollViewProps<'a, T>>) -> let children = render_range.map(|i| (cx.props.builder)((i + 1, i, cx, &cx.props.builder_values))); + let is_scrolling_x = clicking_scrollbar + .read() + .as_ref() + .map(|f| f.0 == Axis::X) + .unwrap_or_default(); + let is_scrolling_y = clicking_scrollbar + .read() + .as_ref() + .map(|f| f.0 == Axis::Y) + .unwrap_or_default(); + render!( rect { role: "scrollView", @@ -335,6 +348,7 @@ pub fn VirtualScrollView<'a, T>(cx: Scope<'a, VirtualScrollViewProps<'a, T>>) -> height: "{horizontal_scrollbar_size}", offset_x: "{scrollbar_x}", ScrollThumb { + clicking_scrollbar: is_scrolling_x, onmousedown: onmousedown_x, width: "{scrollbar_width}", height: "100%", @@ -346,6 +360,7 @@ pub fn VirtualScrollView<'a, T>(cx: Scope<'a, VirtualScrollViewProps<'a, T>>) -> height: "100%", offset_y: "{scrollbar_y}", ScrollThumb { + clicking_scrollbar: is_scrolling_y, onmousedown: onmousedown_y, width: "100%", height: "{scrollbar_height}", diff --git a/crates/hooks/src/use_theme.rs b/crates/hooks/src/use_theme.rs index 63c60d479..ec33780b6 100644 --- a/crates/hooks/src/use_theme.rs +++ b/crates/hooks/src/use_theme.rs @@ -71,6 +71,8 @@ pub struct SwitchTheme { pub struct ScrollbarTheme { pub background: &'static str, pub thumb_background: &'static str, + pub hover_thumb_background: &'static str, + pub active_thumb_background: &'static str, } /// Theming properties for the App body. @@ -175,6 +177,8 @@ pub const LIGHT_THEME: Theme = Theme { scrollbar: ScrollbarTheme { background: "rgb(225, 225, 225)", thumb_background: "rgb(135, 135, 135)", + hover_thumb_background: "rgb(115, 115, 115)", + active_thumb_background: "rgb(95, 95, 95)", }, tooltip: TooltipTheme { background: "rgb(230,230,230)", @@ -240,6 +244,8 @@ pub const DARK_THEME: Theme = Theme { scrollbar: ScrollbarTheme { background: "rgb(35, 35, 35)", thumb_background: "rgb(100, 100, 100)", + hover_thumb_background: "rgb(120, 120, 120)", + active_thumb_background: "rgb(140, 140, 140)", }, tooltip: TooltipTheme { background: "rgb(35,35,35)",