From 1254adefe100c99a049e09d3c0b9b42adcdc2f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Mon, 30 Oct 2023 13:48:48 +0100 Subject: [PATCH] Add a `hover` style selector --- src/context.rs | 11 ++-- src/style.rs | 117 ++++++++++++++++++++++++++++++++++++------- src/view.rs | 25 +++------ src/window_handle.rs | 1 + 4 files changed, 114 insertions(+), 40 deletions(-) diff --git a/src/context.rs b/src/context.rs index 1c7458f0..2c22b521 100644 --- a/src/context.rs +++ b/src/context.rs @@ -46,6 +46,7 @@ pub struct ViewState { pub(crate) node: Node, pub(crate) children_nodes: Vec, pub(crate) request_layout: bool, + pub(crate) hover_sensitive: bool, pub(crate) viewport: Option, pub(crate) layout_rect: Rect, pub(crate) animation: Option, @@ -76,6 +77,7 @@ impl ViewState { viewport: None, layout_rect: Rect::ZERO, request_layout: true, + hover_sensitive: false, animation: None, base_style: None, style: Style::BASE, @@ -669,6 +671,7 @@ pub struct InteractionState { pub(crate) struct StyleCx { pub(crate) current: Rc, + pub(crate) direct: StyleMap, saved: Vec>, } @@ -676,6 +679,7 @@ impl StyleCx { fn new() -> Self { Self { current: Default::default(), + direct: Default::default(), saved: Default::default(), } } @@ -763,10 +767,9 @@ impl<'a> LayoutCx<'a> { pub fn get_prop(&self, _prop: P) -> Option { self.style - .current - .map - .get(&P::prop_ref()) - .and_then(|v| v.downcast_ref().cloned()) + .direct + .get_prop::

() + .or_else(|| self.style.current.get_prop::

()) } pub(crate) fn clear(&mut self) { diff --git a/src/style.rs b/src/style.rs index 6666338b..8f98cec4 100644 --- a/src/style.rs +++ b/src/style.rs @@ -28,6 +28,7 @@ use floem_renderer::cosmic_text::{LineHeightValue, Style as FontStyle, Weight}; use peniko::Color; use std::any::Any; +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::hash::Hash; use std::hash::Hasher; @@ -42,6 +43,7 @@ use taffy::{ style::{FlexWrap, LengthPercentage, Style as TaffyStyle}, }; +use crate::context::InteractionState; use crate::context::LayoutCx; use crate::unit::{Px, PxPct, PxPctAuto, UnitExt}; use crate::views::scroll_bar_color; @@ -196,11 +198,83 @@ macro_rules! prop_extracter { }; } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum StyleMapValue { + Val(T), + /// Use the default value for the style, typically from the underlying `ComputedStyle` + Unset, +} + +impl StyleMapValue { + pub(crate) fn as_ref(&self) -> Option<&T> { + match self { + Self::Val(v) => Some(v), + Self::Unset => None, + } + } +} + #[derive(Default, Clone, Debug)] pub(crate) struct StyleMap { - pub(crate) map: HashMap>, + pub(crate) map: HashMap>>, + pub(crate) selectors: HashMap, } +impl StyleMap { + pub(crate) fn get_prop(&self) -> Option { + self.map + .get(&P::prop_ref()) + .and_then(|v| v.as_ref()) + .and_then(|v| v.downcast_ref().cloned()) + } + + pub(crate) fn hover_sensitive(&self) -> bool { + self.selectors + .iter() + .any(|(selector, map)| *selector == StyleSelector::Hover || map.hover_sensitive()) + } + + pub(crate) fn apply_interact_state(&mut self, interact_state: InteractionState) { + if interact_state.is_hovered && !interact_state.is_disabled { + if let Some(mut map) = self.selectors.remove(&StyleSelector::Hover) { + map.apply_interact_state(interact_state); + self.apply(map); + } + } + } + + pub(crate) fn apply_only_inherited(map: &mut Rc, over: &StyleMap) { + let any_inherited = over.map.iter().any(|(p, _)| p.info.inherited); + + if any_inherited { + let inherited = over + .map + .iter() + .filter(|(p, _)| p.info.inherited) + .map(|(p, v)| (*p, v.clone())); + + Rc::make_mut(map).map.extend(inherited); + } + } + + fn set_selector(&mut self, selector: StyleSelector, map: StyleMap) { + match self.selectors.entry(selector) { + Entry::Occupied(mut e) => e.get_mut().apply(map), + Entry::Vacant(e) => { + e.insert(map); + } + } + } + + fn apply(&mut self, over: StyleMap) { + self.map.extend(over.map); + for (selector, map) in over.selectors { + self.set_selector(selector, map); + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum StyleSelector { Hover, Focus, @@ -350,7 +424,7 @@ macro_rules! define_styles { #[derive(Debug, Clone)] pub struct Style { - pub(crate) other: Option>>>, + pub(crate) other: Option, $( pub $name: StyleValue<$typ>, )* @@ -378,12 +452,7 @@ macro_rules! define_styles { /// for any missing values. pub fn compute(self, underlying: &ComputedStyle) -> ComputedStyle { ComputedStyle { - other: StyleMap { - map: self.other.unwrap_or_default() - .into_iter() - .filter_map(|(k, mut v)| v.as_mut().map(|v| (k, v.clone()))) - .collect() - }, + other: self.other.unwrap_or_default(), $( $name: self.$name.unwrap_or_else(|| underlying.$name.clone()), )* @@ -399,16 +468,8 @@ macro_rules! define_styles { /// `ComputedStyle` or using the value in the `Style`. pub fn apply(self, over: Style) -> Style { let mut other = self.other.unwrap_or_default(); - for (k, v) in over.other.unwrap_or_default() { - match v { - StyleValue::Val(..) => { - other.insert(k, v); - }, - StyleValue::Base => (), - StyleValue::Unset => { - other.remove(&k); - } - } + if let Some(over) = over.other { + other.apply(over); } Style { other: Some(other), @@ -519,7 +580,25 @@ impl Style { pub fn set_style_value(mut self, _prop: P, value: StyleValue) -> Self { let mut other = self.other.unwrap_or_default(); - other.insert(P::prop_ref(), value.map(|v| -> Rc { Rc::new(v) })); + let insert: StyleMapValue> = match value { + StyleValue::Val(value) => StyleMapValue::Val(Rc::new(value)), + StyleValue::Unset => StyleMapValue::Unset, + StyleValue::Base => { + other.map.remove(&P::prop_ref()); + self.other = Some(other); + return self; + } + }; + other.map.insert(P::prop_ref(), insert); + self.other = Some(other); + self + } + + pub fn hover(mut self, style: impl Fn(Style) -> Style + 'static) -> Self { + // FIXME: Ignores field style properties. + let over = style(Style::BASE).other.unwrap_or_default(); + let mut other = self.other.unwrap_or_default(); + other.set_selector(StyleSelector::Hover, over); self.other = Some(other); self } diff --git a/src/view.rs b/src/view.rs index c5fac0cd..8529f6b7 100644 --- a/src/view.rs +++ b/src/view.rs @@ -83,7 +83,7 @@ //! //! -use std::{any::Any, rc::Rc}; +use std::any::Any; use bitflags::bitflags; use floem_renderer::Renderer; @@ -196,22 +196,13 @@ pub trait View { cx.app_state_mut().compute_style(self.id(), view_style); let style = cx.app_state_mut().get_computed_style(self.id()).clone(); - cx.style.current = Rc::new(StyleMap { - map: cx - .style - .current - .map - .iter() - .map(|(p, v)| (*p, v.clone())) - .chain( - style - .other - .map - .into_iter() - .filter(|(p, _)| p.info.inherited), - ) - .collect(), - }); + cx.app_state_mut().view_state(self.id()).hover_sensitive = style.other.hover_sensitive(); + + let interact_state = cx.app_state().get_interact_state(&self.id()); + cx.style.direct = style.other.clone(); + cx.style.direct.apply_interact_state(interact_state); + StyleMap::apply_only_inherited(&mut cx.style.current, &cx.style.direct); + if style.color.is_some() { cx.color = style.color; } diff --git a/src/window_handle.rs b/src/window_handle.rs index 78d27683..9cd92297 100644 --- a/src/window_handle.rs +++ b/src/window_handle.rs @@ -234,6 +234,7 @@ impl WindowHandle { if view_state.hover_style.is_some() || view_state.active_style.is_some() || view_state.animation.is_some() + || view_state.hover_sensitive { cx.app_state.request_layout(*id); }