diff --git a/src/animate/anim_id.rs b/src/animate/anim_id.rs index 83e23338..cc0184b1 100644 --- a/src/animate/anim_id.rs +++ b/src/animate/anim_id.rs @@ -1,6 +1,6 @@ -use std::sync::atomic::AtomicUsize; +use std::{rc::Rc, sync::atomic::AtomicUsize}; -use crate::update::ANIM_UPDATE_MESSAGES; +use crate::{style::StyleProp, update::ANIM_UPDATE_MESSAGES}; use super::{anim_val::AnimValue, AnimPropKind, AnimUpdateMsg}; @@ -28,4 +28,17 @@ impl AnimId { }); }); } + + pub(crate) fn update_style_prop(&self, _prop: P, val: P::Type) { + ANIM_UPDATE_MESSAGES.with(|msgs| { + let mut msgs = msgs.borrow_mut(); + msgs.push(AnimUpdateMsg::Prop { + id: *self, + kind: AnimPropKind::Prop { + prop: P::prop_ref(), + }, + val: AnimValue::Prop(Rc::new(val)), + }); + }); + } } diff --git a/src/animate/anim_val.rs b/src/animate/anim_val.rs index aaeee8fe..fba19f40 100644 --- a/src/animate/anim_val.rs +++ b/src/animate/anim_val.rs @@ -1,23 +1,24 @@ +use std::{any::Any, rc::Rc}; + use peniko::Color; #[derive(Debug, Clone)] pub enum AnimValue { Float(f64), Color(Color), + Prop(Rc), } impl AnimValue { pub fn get_f32(self) -> f32 { - match self { - AnimValue::Float(v) => v as f32, - AnimValue::Color(_) => panic!(), - } + self.get_f64() as f32 } pub fn get_f64(self) -> f64 { match self { AnimValue::Float(v) => v, AnimValue::Color(_) => panic!(), + AnimValue::Prop(prop) => *prop.downcast_ref::().unwrap(), } } @@ -25,6 +26,15 @@ impl AnimValue { match self { AnimValue::Color(c) => c, AnimValue::Float(_) => panic!(), + AnimValue::Prop(prop) => *prop.downcast_ref::().unwrap(), + } + } + + pub fn get_any(self) -> Rc { + match self { + AnimValue::Color(_) => panic!(), + AnimValue::Float(_) => panic!(), + AnimValue::Prop(prop) => prop.clone(), } } } diff --git a/src/animate/animation.rs b/src/animate/animation.rs index ca3d379a..0660a393 100644 --- a/src/animate/animation.rs +++ b/src/animate/animation.rs @@ -1,3 +1,5 @@ +use crate::style::{Background, BorderColor, BorderRadius, TextColor}; + use super::{ anim_val::AnimValue, AnimId, AnimPropKind, AnimState, AnimStateKind, AnimatedProp, Easing, EasingFn, EasingMode, @@ -108,7 +110,7 @@ impl Animation { let border_radius = border_radius_fn(); self.id - .update_prop(AnimPropKind::BorderRadius, AnimValue::Float(border_radius)); + .update_style_prop(BorderRadius, border_radius.into()); }); self @@ -118,8 +120,7 @@ impl Animation { create_effect(move |_| { let color = color_fn(); - self.id - .update_prop(AnimPropKind::Color, AnimValue::Color(color)); + self.id.update_style_prop(TextColor, Some(color)); }); self @@ -129,8 +130,7 @@ impl Animation { create_effect(move |_| { let border_color = bord_color_fn(); - self.id - .update_prop(AnimPropKind::BorderColor, AnimValue::Color(border_color)); + self.id.update_style_prop(BorderColor, border_color); }); self @@ -140,8 +140,7 @@ impl Animation { create_effect(move |_| { let background = bg_fn(); - self.id - .update_prop(AnimPropKind::Background, AnimValue::Color(background)); + self.id.update_style_prop(Background, Some(background)); }); self diff --git a/src/animate/prop.rs b/src/animate/prop.rs index 46e95570..e23768d1 100644 --- a/src/animate/prop.rs +++ b/src/animate/prop.rs @@ -1,35 +1,45 @@ +use std::{any::Any, rc::Rc}; + use peniko::Color; -use crate::animate::AnimDirection; +use crate::{animate::AnimDirection, style::StylePropRef, unit::Px}; use super::{anim_val::AnimValue, assert_valid_time, SizeUnit}; #[derive(Clone, Debug)] pub enum AnimatedProp { - Width { from: f64, to: f64, unit: SizeUnit }, - Height { from: f64, to: f64, unit: SizeUnit }, - Scale { from: f64, to: f64 }, + Width { + from: f64, + to: f64, + unit: SizeUnit, + }, + Height { + from: f64, + to: f64, + unit: SizeUnit, + }, + Scale { + from: f64, + to: f64, + }, // Opacity { from: f64, to: f64 }, // TranslateX, // TranslateY, - Background { from: Color, to: Color }, - BorderRadius { from: f64, to: f64 }, - BorderWidth { from: f64, to: f64 }, - BorderColor { from: Color, to: Color }, - Color { from: Color, to: Color }, + Prop { + prop: StylePropRef, + from: Rc, + to: Rc, + }, } impl AnimatedProp { pub(crate) fn from(&self) -> AnimValue { match self { - AnimatedProp::Width { from, .. } - | AnimatedProp::Height { from, .. } - | AnimatedProp::BorderWidth { from, .. } - | AnimatedProp::BorderRadius { from, .. } => AnimValue::Float(*from), + AnimatedProp::Prop { from, .. } => AnimValue::Prop(from.clone()), + AnimatedProp::Width { from, .. } | AnimatedProp::Height { from, .. } => { + AnimValue::Float(*from) + } AnimatedProp::Scale { .. } => todo!(), - AnimatedProp::Background { from, .. } - | AnimatedProp::BorderColor { from, .. } - | AnimatedProp::Color { from, .. } => AnimValue::Color(*from), } } @@ -108,19 +118,40 @@ impl AnimatedProp { pub(crate) fn animate(&self, time: f64, direction: AnimDirection) -> AnimValue { match self { + AnimatedProp::Prop { prop, from, to } => { + if let Some(from) = from.downcast_ref::() { + let to = to.downcast_ref::().unwrap(); + return AnimValue::Prop(Rc::new(Px( + self.animate_float(from.0, to.0, time, direction) + ))); + } + if let Some(from) = from.downcast_ref::() { + let to = to.downcast_ref::().unwrap(); + return AnimValue::Prop(Rc::new( + self.animate_float(*from, *to, time, direction), + )); + } + if let Some(from) = from.downcast_ref::() { + let to = to.downcast_ref::().unwrap(); + return AnimValue::Prop(Rc::new( + self.animate_color(*from, *to, time, direction), + )); + } + if let Some(from) = from.downcast_ref::>() { + let to = to.downcast_ref::>().unwrap(); + let from = from.unwrap(); + let to = to.unwrap(); + return AnimValue::Prop(Rc::new(Some( + self.animate_color(from, to, time, direction), + ))); + } + panic!("unknown type for {prop:?}") + } AnimatedProp::Width { from, to, unit: _ } | AnimatedProp::Height { from, to, unit: _ } => { AnimValue::Float(self.animate_float(*from, *to, time, direction)) } - AnimatedProp::Background { from, to } - | AnimatedProp::BorderColor { from, to } - | AnimatedProp::Color { from, to } => { - AnimValue::Color(self.animate_color(*from, *to, time, direction)) - } AnimatedProp::Scale { .. } => todo!(), - AnimatedProp::BorderRadius { from, to } | AnimatedProp::BorderWidth { from, to } => { - AnimValue::Float(self.animate_float(*from, *to, time, direction)) - } } } } @@ -131,9 +162,6 @@ pub enum AnimPropKind { // TranslateX, // TranslateY, Width, - Background, - Color, Height, - BorderRadius, - BorderColor, + Prop { prop: StylePropRef }, } diff --git a/src/context.rs b/src/context.rs index 2c22b521..9771c9d3 100644 --- a/src/context.rs +++ b/src/context.rs @@ -24,7 +24,10 @@ use crate::{ menu::Menu, pointer::PointerInputEvent, responsive::{GridBreakpoints, ScreenSize, ScreenSizeBp}, - style::{ComputedStyle, CursorStyle, Style, StyleMap, StyleProp, StyleSelector}, + style::{ + BuiltinStyleReader, ComputedStyle, CursorStyle, DisplayProp, Style, StyleMap, StyleProp, + StyleSelector, + }, }; pub type EventCallback = dyn Fn(&Event) -> bool; @@ -176,17 +179,14 @@ impl ViewState { AnimPropKind::Height => { computed_style = computed_style.height(val.get_f32()); } - AnimPropKind::Background => { - computed_style = computed_style.background(val.get_color()); - } - AnimPropKind::Color => { - computed_style = computed_style.color(val.get_color()); - } - AnimPropKind::BorderRadius => { - computed_style = computed_style.border_radius(val.get_f32()); - } - AnimPropKind::BorderColor => { - computed_style = computed_style.border_color(val.get_color()); + AnimPropKind::Prop { prop } => { + computed_style.other = Some(computed_style.other.unwrap_or_default()); + computed_style + .other + .as_mut() + .unwrap() + .map + .insert(*prop, crate::style::StyleMapValue::Val(val.get_any())); } AnimPropKind::Scale => todo!(), } @@ -197,8 +197,18 @@ impl ViewState { } } + self.hover_sensitive = computed_style + .other + .as_ref() + .map(|map| map.hover_sensitive()) + .unwrap_or_default(); + + if let Some(map) = computed_style.other.as_mut() { + map.apply_interact_state(interact_state); + } + self.combined_style = computed_style.clone(); - self.computed_style = computed_style.compute(&ComputedStyle::default()); + self.computed_style = computed_style.compute(); } pub(crate) fn add_responsive_style(&mut self, size: ScreenSize, style: Style) { @@ -319,7 +329,7 @@ impl AppState { pub fn is_hidden(&self, id: Id) -> bool { self.view_states .get(&id) - .map(|s| s.computed_style.display == Display::None) + .map(|s| s.computed_style.get(DisplayProp) == Display::None) .unwrap_or(false) } @@ -390,6 +400,10 @@ impl AppState { &view_state.computed_style } + pub(crate) fn get_builtin_style(&mut self, id: Id) -> BuiltinStyleReader<'_> { + self.get_computed_style(id).get_builtin() + } + pub fn compute_layout(&mut self) { if let Some(root) = self.root { let _ = self.taffy.compute_layout( @@ -703,31 +717,9 @@ impl StyleCx { pub struct LayoutCx<'a> { app_state: &'a mut AppState, pub(crate) viewport: Option, - pub(crate) color: Option, pub(crate) style: StyleCx, - pub(crate) scroll_bar_hover_color: Option, - pub(crate) scroll_bar_drag_color: Option, - pub(crate) scroll_bar_rounded: Option, - pub(crate) scroll_bar_thickness: Option, - pub(crate) scroll_bar_edge_width: Option, - pub(crate) font_size: Option, - pub(crate) font_family: Option, - pub(crate) font_weight: Option, - pub(crate) font_style: Option, - pub(crate) line_height: Option, pub(crate) window_origin: Point, pub(crate) saved_viewports: Vec>, - pub(crate) saved_colors: Vec>, - pub(crate) saved_scroll_bar_hover_colors: Vec>, - pub(crate) saved_scroll_bar_drag_colors: Vec>, - pub(crate) saved_scroll_bar_roundeds: Vec>, - pub(crate) saved_scroll_bar_thicknesses: Vec>, - pub(crate) saved_scroll_bar_edge_widths: Vec>, - pub(crate) saved_font_sizes: Vec>, - pub(crate) saved_font_families: Vec>, - pub(crate) saved_font_weights: Vec>, - pub(crate) saved_font_styles: Vec>, - pub(crate) saved_line_heights: Vec>, pub(crate) saved_window_origins: Vec, } @@ -736,32 +728,10 @@ impl<'a> LayoutCx<'a> { Self { app_state, viewport: None, - color: None, - font_size: None, - font_family: None, - font_weight: None, - font_style: None, - line_height: None, window_origin: Point::ZERO, style: StyleCx::new(), saved_viewports: Vec::new(), - saved_colors: Vec::new(), - saved_font_sizes: Vec::new(), - saved_font_families: Vec::new(), - saved_font_weights: Vec::new(), - saved_font_styles: Vec::new(), - saved_line_heights: Vec::new(), saved_window_origins: Vec::new(), - scroll_bar_hover_color: None, - scroll_bar_drag_color: None, - scroll_bar_rounded: None, - scroll_bar_thickness: None, - scroll_bar_edge_width: None, - saved_scroll_bar_hover_colors: Vec::new(), - saved_scroll_bar_drag_colors: Vec::new(), - saved_scroll_bar_roundeds: Vec::new(), - saved_scroll_bar_thicknesses: Vec::new(), - saved_scroll_bar_edge_widths: Vec::new(), } } @@ -775,63 +745,20 @@ impl<'a> LayoutCx<'a> { pub(crate) fn clear(&mut self) { self.style.clear(); self.viewport = None; - self.scroll_bar_hover_color = None; - self.scroll_bar_drag_color = None; - self.scroll_bar_rounded = None; - self.scroll_bar_thickness = None; - self.scroll_bar_edge_width = None; - self.font_size = None; self.window_origin = Point::ZERO; - self.saved_colors.clear(); self.saved_viewports.clear(); - self.saved_scroll_bar_hover_colors.clear(); - self.saved_scroll_bar_drag_colors.clear(); - self.saved_scroll_bar_roundeds.clear(); - self.saved_scroll_bar_thicknesses.clear(); - self.saved_scroll_bar_edge_widths.clear(); - self.saved_font_sizes.clear(); - self.saved_font_families.clear(); - self.saved_font_weights.clear(); - self.saved_font_styles.clear(); - self.saved_line_heights.clear(); self.saved_window_origins.clear(); } pub fn save(&mut self) { self.style.save(); self.saved_viewports.push(self.viewport); - self.saved_colors.push(self.color); - self.saved_scroll_bar_hover_colors - .push(self.scroll_bar_hover_color); - self.saved_scroll_bar_drag_colors - .push(self.scroll_bar_drag_color); - self.saved_scroll_bar_roundeds.push(self.scroll_bar_rounded); - self.saved_scroll_bar_thicknesses - .push(self.scroll_bar_thickness); - self.saved_scroll_bar_edge_widths - .push(self.scroll_bar_edge_width); - self.saved_font_sizes.push(self.font_size); - self.saved_font_families.push(self.font_family.clone()); - self.saved_font_weights.push(self.font_weight); - self.saved_font_styles.push(self.font_style); - self.saved_line_heights.push(self.line_height); self.saved_window_origins.push(self.window_origin); } pub fn restore(&mut self) { self.style.restore(); self.viewport = self.saved_viewports.pop().unwrap_or_default(); - self.color = self.saved_colors.pop().unwrap_or_default(); - self.scroll_bar_hover_color = self.saved_scroll_bar_hover_colors.pop().unwrap_or_default(); - self.scroll_bar_drag_color = self.saved_scroll_bar_drag_colors.pop().unwrap_or_default(); - self.scroll_bar_rounded = self.saved_scroll_bar_roundeds.pop().unwrap_or_default(); - self.scroll_bar_thickness = self.saved_scroll_bar_thicknesses.pop().unwrap_or_default(); - self.scroll_bar_edge_width = self.saved_scroll_bar_edge_widths.pop().unwrap_or_default(); - self.font_size = self.saved_font_sizes.pop().unwrap_or_default(); - self.font_family = self.saved_font_families.pop().unwrap_or_default(); - self.font_weight = self.saved_font_weights.pop().unwrap_or_default(); - self.font_style = self.saved_font_styles.pop().unwrap_or_default(); - self.line_height = self.saved_line_heights.pop().unwrap_or_default(); self.window_origin = self.saved_window_origins.pop().unwrap_or_default(); } @@ -843,46 +770,6 @@ impl<'a> LayoutCx<'a> { self.app_state } - pub fn current_scroll_bar_hover_color(&self) -> Option { - self.scroll_bar_hover_color - } - - pub fn current_scroll_bar_drag_color(&self) -> Option { - self.scroll_bar_drag_color - } - - pub fn current_scroll_bar_rounded(&self) -> Option { - self.scroll_bar_rounded - } - - pub fn current_scroll_bar_thickness(&self) -> Option { - self.scroll_bar_thickness - } - - pub fn current_scroll_bar_edge_width(&self) -> Option { - self.scroll_bar_edge_width - } - - pub fn current_font_size(&self) -> Option { - self.font_size - } - - pub fn current_font_family(&self) -> Option<&str> { - self.font_family.as_deref() - } - - pub fn current_font_weight(&self) -> Option { - self.font_weight - } - - pub fn current_font_style(&self) -> Option { - self.font_style - } - - pub fn current_line_height(&self) -> Option { - self.line_height - } - pub fn current_viewport(&self) -> Option { self.viewport } @@ -1064,6 +951,10 @@ impl<'a> PaintCx<'a> { self.app_state.get_computed_style(id) } + pub(crate) fn get_builtin_style(&mut self, id: Id) -> BuiltinStyleReader<'_> { + self.app_state.get_builtin_style(id) + } + /// Clip the drawing area to the given shape. pub fn clip(&mut self, shape: &impl Shape) { let rect = if let Some(rect) = shape.as_rect() { diff --git a/src/style.rs b/src/style.rs index 8f98cec4..3db5c8d9 100644 --- a/src/style.rs +++ b/src/style.rs @@ -25,49 +25,69 @@ //! ``` //! -use floem_renderer::cosmic_text::{LineHeightValue, Style as FontStyle, Weight}; +use floem_renderer::cosmic_text; +use floem_renderer::cosmic_text::{LineHeightValue, Weight}; use peniko::Color; -use std::any::Any; +use std::any::{type_name, Any}; use std::collections::hash_map::Entry; use std::collections::HashMap; +use std::fmt::{self, Debug}; use std::hash::Hash; use std::hash::Hasher; use std::ptr; use std::rc::Rc; pub use taffy::style::{ - AlignContent, AlignItems, Dimension, Display, FlexDirection, JustifyContent, Position, + AlignContent, AlignItems, Dimension, Display, FlexDirection, FlexWrap, JustifyContent, Position, }; use taffy::{ geometry::Size, prelude::Rect, - style::{FlexWrap, LengthPercentage, Style as TaffyStyle}, + style::{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; pub trait StyleProp: Default + Copy + 'static { - type Type: Clone; + type Type: Clone + PartialEq + Debug; fn prop_ref() -> StylePropRef; fn default_value() -> Self::Type; } #[derive(Debug)] pub struct StylePropInfo { - #[allow(dead_code)] - pub(crate) name: &'static str, + pub(crate) name: fn() -> &'static str, pub(crate) inherited: bool, + pub(crate) default_as_any: fn() -> Rc, + pub(crate) debug_any: fn(val: &dyn Any) -> String, } impl StylePropInfo { - pub const fn new(name: &'static str, inherited: bool) -> Self { - StylePropInfo { name, inherited } + pub const fn new( + inherited: bool, + default_as_any: fn() -> Rc, + ) -> Self { + StylePropInfo { + name: || std::any::type_name::(), + inherited, + default_as_any, + debug_any: |val| { + if let Some(v) = val.downcast_ref::() { + format!("{:?}", v) + } else { + panic!( + "expected type {} for property {}", + type_name::(), + std::any::type_name::(), + ) + } + }, + } } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone)] pub struct StylePropRef { pub info: &'static StylePropInfo, } @@ -82,66 +102,96 @@ impl Hash for StylePropRef { } } impl Eq for StylePropRef {} +impl Debug for StylePropRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", (self.info.name)()) + } +} pub trait StylePropReader { + type State: Debug; type Type: Clone; - fn read(&mut self, cx: &LayoutCx); - fn get(&self) -> Self::Type; - fn new() -> Self; -} -pub struct DefaultReader { - value: P::Type, + /// Reads the property from the current style state. + /// Returns true if the property changed. + fn read(state: &mut Self::State, cx: &LayoutCx) -> bool; + + fn get(state: &Self::State) -> Self::Type; + fn new() -> Self::State; } -impl StylePropReader for DefaultReader

{ +impl StylePropReader for P { + type State = P::Type; type Type = P::Type; - fn read(&mut self, cx: &LayoutCx) { - self.value = cx + fn read(state: &mut Self::State, cx: &LayoutCx) -> bool { + let new = cx .get_prop(P::default()) .unwrap_or_else(|| P::default_value()); + let changed = new != *state; + *state = new; + changed } - fn get(&self) -> Self::Type { - self.value.clone() + fn get(state: &Self::State) -> Self::Type { + state.clone() } - fn new() -> Self { - Self { - value: P::default_value(), - } + fn new() -> Self::State { + P::default_value() + } +} + +impl StylePropReader for Option

{ + type State = Option; + type Type = Option; + fn read(state: &mut Self::State, cx: &LayoutCx) -> bool { + let new = cx.get_prop(P::default()); + let changed = new != *state; + *state = new; + changed + } + fn get(state: &Self::State) -> Self::Type { + state.clone() + } + fn new() -> Self::State { + None } } -pub struct OptionReader { - value: Option, +pub struct ExtratorField { + state: R::State, } -impl StylePropReader for OptionReader

{ - type Type = Option; - fn read(&mut self, cx: &LayoutCx) { - self.value = cx.get_prop(P::default()); +impl Debug for ExtratorField { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.state.fmt(f) } - fn get(&self) -> Self::Type { - self.value.clone() +} + +impl ExtratorField { + pub fn read(&mut self, cx: &LayoutCx) -> bool { + R::read(&mut self.state, cx) } - fn new() -> Self { - Self { value: None } + pub fn get(&self) -> R::Type { + R::get(&self.state) + } + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { state: R::new() } } } #[macro_export] macro_rules! prop { - ($v:vis $name:ident: $ty:ty { inherited: $inherited:expr } - = $default:expr + ($v:vis $name:ident: $ty:ty { $($options:tt)* } = $default:expr ) => { #[derive(Default, Copy, Clone)] #[allow(non_camel_case_types)] $v struct $name; - impl $crate::style::StyleProp for $name{ + impl $crate::style::StyleProp for $name { type Type = $ty; fn prop_ref() -> $crate::style::StylePropRef { - static INFO: $crate::style::StylePropInfo = $crate::style::StylePropInfo::new( - stringify!($name), - $inherited, + static INFO: $crate::style::StylePropInfo = $crate::style::StylePropInfo::new::<$name, $ty>( + prop!([impl inherited][$($options)*]), + || std::rc::Rc::new($name::default_value()), ); $crate::style::StylePropRef { info: &INFO } } @@ -149,34 +199,39 @@ macro_rules! prop { $default } } - - } + }; + ([impl inherited][inherited]) => { + true + }; + ([impl inherited][]) => { + false + }; } #[macro_export] macro_rules! prop_extracter { ( $vis:vis $name:ident { - $($prop_vis:vis $prop:ident $(: $reader:ty)?),* + $($prop_vis:vis $prop:ident: $reader:ty),* $(,)? } ) => { - #[allow(non_snake_case)] + #[derive(Debug)] $vis struct $name { $( - $prop: prop_extracter!([impl reader][$prop $(: $reader)?]), + $prop_vis $prop: $crate::style::ExtratorField<$reader>, )* } impl $name { - fn read(&mut self, cx: &$crate::context::LayoutCx) { - $($crate::style::StylePropReader::read(&mut self.$prop, cx);)* + $vis fn read(&mut self, cx: &$crate::context::LayoutCx) -> bool { + false + $(| self.$prop.read(cx))* } - $($prop_vis fn $prop(&self) -> ::Type + $($prop_vis fn $prop(&self) -> <$reader as $crate::style::StylePropReader>::Type { - $crate::style::StylePropReader::get(&self.$prop) + self.$prop.get() })* } @@ -184,18 +239,12 @@ macro_rules! prop_extracter { fn default() -> Self { Self { $( - $prop: $crate::style::StylePropReader::new(), + $prop: $crate::style::ExtratorField::new(), )* } } } }; - ([impl reader][$prop:ident: $reader:ty]) => { - $reader - }; - ([impl reader][$prop:ident]) => { - $crate::style::DefaultReader<$prop> - }; } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -214,7 +263,7 @@ impl StyleMapValue { } } -#[derive(Default, Clone, Debug)] +#[derive(Default, Clone)] pub(crate) struct StyleMap { pub(crate) map: HashMap>>, pub(crate) selectors: HashMap, @@ -225,7 +274,19 @@ impl StyleMap { self.map .get(&P::prop_ref()) .and_then(|v| v.as_ref()) - .and_then(|v| v.downcast_ref().cloned()) + .map(|v| v.downcast_ref::().unwrap().clone()) + } + + pub(crate) fn get_prop_style_value(&self) -> StyleValue { + self.map + .get(&P::prop_ref()) + .map(|v| match v { + StyleMapValue::Val(v) => { + StyleValue::Val(v.downcast_ref::().unwrap().clone()) + } + StyleMapValue::Unset => StyleValue::Unset, + }) + .unwrap_or(StyleValue::Base) } pub(crate) fn hover_sensitive(&self) -> bool { @@ -274,6 +335,30 @@ impl StyleMap { } } +impl Debug for StyleMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StyleMap") + .field( + "map", + &self + .map + .iter() + .map(|(p, v)| { + ( + *p, + match v { + StyleMapValue::Val(v) => (p.info.debug_any)(&**v), + StyleMapValue::Unset => "Unset".to_owned(), + }, + ) + }) + .collect::>(), + ) + .field("selectors", &self.selectors) + .finish() + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum StyleSelector { Hover, @@ -291,7 +376,7 @@ pub enum TextOverflow { Ellipsis, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum CursorStyle { Default, Pointer, @@ -310,7 +395,7 @@ pub enum CursorStyle { NwseResize, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct BoxShadow { pub blur_radius: f64, pub color: Color, @@ -389,193 +474,183 @@ impl From for StyleValue { } } -// Creates `ComputedStyle` which has definite values for the fields, barring some specific cases. -// Creates `Style` which has `StyleValue`s for the fields -macro_rules! define_styles { +/// A style with definite values for most fields. +#[derive(Default, Debug, Clone)] +pub struct ComputedStyle { + pub(crate) other: StyleMap, +} +impl ComputedStyle { + pub(crate) fn get(&self, _prop: P) -> P::Type { + self.other + .get_prop::

() + .unwrap_or_else(|| P::default_value()) + } + + pub(crate) fn get_builtin(&self) -> BuiltinStyleReader<'_> { + BuiltinStyleReader { style: self } + } +} +#[derive(Debug, Clone)] +pub struct Style { + pub(crate) other: Option, +} +impl Style { + pub const BASE: Style = Style { other: None }; + + pub const UNSET: Style = Style { other: None }; + + /// Convert this `Style` into a computed style. + pub fn compute(self) -> ComputedStyle { + ComputedStyle { + other: self.other.unwrap_or_default(), + } + } + + /// Apply another `Style` to this style, returning a new `Style` with the overrides + /// + /// `StyleValue::Val` will override the value with the given value + /// `StyleValue::Unset` will unset the value, causing it to fall back to the underlying + /// `ComputedStyle` (aka setting it to `None`) + /// `StyleValue::Base` will leave the value as-is, whether falling back to the underlying + /// `ComputedStyle` or using the value in the `Style`. + pub fn apply(self, over: Style) -> Style { + let mut other = self.other.unwrap_or_default(); + if let Some(over) = over.other { + other.apply(over); + } + Style { other: Some(other) } + } + + /// Apply multiple `Style`s to this style, returning a new `Style` with the overrides. + /// Later styles take precedence over earlier styles. + pub fn apply_overriding_styles(self, overrides: impl Iterator) -> Style { + overrides.fold(self, |acc, x| acc.apply(x)) + } +} + +macro_rules! define_style_methods { ( - $($name:ident $name_sv:ident $($opt:ident)?: $typ:ty = $val:expr),* $(,)? + $($type_name:ident $name:ident $name_sv:ident $($opt:ident)?: + $typ:ty { $($options:tt)* } = $val:expr),* + $(,)? ) => { - /// A style with definite values for most fields. - #[derive(Debug, Clone)] - pub struct ComputedStyle { - pub(crate) other: StyleMap, + $( + prop!(pub $type_name: $typ { $($options)* } = $val); + )* + impl Style { $( - pub $name: $typ, + define_style_methods!(decl: $type_name $name $name_sv $($opt)?: $typ = $val); )* } - impl ComputedStyle { + + impl BuiltinStyleReader<'_> { $( - pub fn $name(mut self, v: impl Into<$typ>) -> Self { - self.$name = v.into(); - self + #[allow(dead_code)] + pub(crate) fn $name(&self) -> $typ { + self.style.get($type_name) } )* } - impl Default for ComputedStyle { - fn default() -> Self { - Self { - other: Default::default(), - $( - $name: $val, - )* - } - } + }; + (decl: $type_name:ident $name:ident $name_sv:ident nocb: $typ:ty = $val:expr) => {}; + (decl: $type_name:ident $name:ident $name_sv:ident: $typ:ty = $val:expr) => { + pub fn $name(self, v: impl Into<$typ>) -> Self { + self.set($type_name, v.into()) } - #[derive(Debug, Clone)] - pub struct Style { - pub(crate) other: Option, - $( - pub $name: StyleValue<$typ>, - )* + pub fn $name_sv(self, v: StyleValue<$typ>) -> Self { + self.set_style_value($type_name, v) } - impl Style { - pub const BASE: Style = Style{ - other: None, - $( - $name: StyleValue::Base, - )* - }; - - pub const UNSET: Style = Style{ - other: None, - $( - $name: StyleValue::Unset, - )* - }; + } +} - $( - define_styles!(decl: $name $name_sv $($opt)?: $typ = $val); - )* +pub(crate) struct BuiltinStyleReader<'a> { + style: &'a ComputedStyle, +} - /// Convert this `Style` into a computed style, using the given `ComputedStyle` as a base - /// for any missing values. - pub fn compute(self, underlying: &ComputedStyle) -> ComputedStyle { - ComputedStyle { - other: self.other.unwrap_or_default(), - $( - $name: self.$name.unwrap_or_else(|| underlying.$name.clone()), - )* - } - } +define_style_methods!( + DisplayProp display display_sv: Display {} = Display::Flex, + PositionProp position position_sv: Position {} = Position::Relative, + Width width width_sv: PxPctAuto {} = PxPctAuto::Auto, + Height height height_sv: PxPctAuto {} = PxPctAuto::Auto, + MinWidth min_width min_width_sv: PxPctAuto {} = PxPctAuto::Auto, + MinHeight min_height min_height_sv: PxPctAuto {} = PxPctAuto::Auto, + MaxWidth max_width max_width_sv: PxPctAuto {} = PxPctAuto::Auto, + MaxHeight max_height max_height_sv: PxPctAuto {} = PxPctAuto::Auto, + FlexDirectionProp flex_direction flex_direction_sv: FlexDirection {} = FlexDirection::Row, + FlexWrapProp flex_wrap flex_wrap_sv: FlexWrap {} = FlexWrap::NoWrap, + FlexGrow flex_grow flex_grow_sv: f32 {} = 0.0, + FlexShrink flex_shrink flex_shrink_sv: f32 {} = 1.0, + FlexBasis flex_basis flex_basis_sv: PxPctAuto {} = PxPctAuto::Auto, + JustifyContentProp justify_content justify_content_sv: Option {} = None, + JustifySelf justify_self justify_self_sv: Option {} = None, + AlignItemsProp align_items align_items_sv: Option {} = None, + AlignContentProp align_content align_content_sv: Option {} = None, + AlignSelf align_self align_self_sv: Option {} = None, + BorderLeft border_left border_left_sv: Px {} = Px(0.0), + BorderTop border_top border_top_sv: Px {} = Px(0.0), + BorderRight border_right border_right_sv: Px {} = Px(0.0), + BorderBottom border_bottom border_bottom_sv: Px {} = Px(0.0), + BorderRadius border_radius border_radius_sv: Px {} = Px(0.0), + OutlineColor outline_color outline_color_sv: Color {} = Color::TRANSPARENT, + Outline outline outline_sv: Px {} = Px(0.0), + BorderColor border_color border_color_sv: Color {} = Color::BLACK, + PaddingLeft padding_left padding_left_sv: PxPct {} = PxPct::Px(0.0), + PaddingTop padding_top padding_top_sv: PxPct {} = PxPct::Px(0.0), + PaddingRight padding_right padding_right_sv: PxPct {} = PxPct::Px(0.0), + PaddingBottom padding_bottom padding_bottom_sv: PxPct {} = PxPct::Px(0.0), + MarginLeft margin_left margin_left_sv: PxPctAuto {} = PxPctAuto::Px(0.0), + MarginTop margin_top margin_top_sv: PxPctAuto {} = PxPctAuto::Px(0.0), + MarginRight margin_right margin_right_sv: PxPctAuto {} = PxPctAuto::Px(0.0), + MarginBottom margin_bottom margin_bottom_sv: PxPctAuto {} = PxPctAuto::Px(0.0), + InsetLeft inset_left inset_left_sv: PxPctAuto {} = PxPctAuto::Auto, + InsetTop inset_top inset_top_sv: PxPctAuto {} = PxPctAuto::Auto, + InsetRight inset_right inset_right_sv: PxPctAuto {} = PxPctAuto::Auto, + InsetBottom inset_bottom inset_bottom_sv: PxPctAuto {} = PxPctAuto::Auto, + ZIndex z_index z_index_sv nocb: Option {} = None, + Cursor cursor cursor_sv nocb: Option {} = None, + TextColor color color_sv nocb: Option {} = None, + Background background background_sv nocb: Option {} = None, + BoxShadowProp box_shadow box_shadow_sv nocb: Option {} = None, + FontSize font_size font_size_sv nocb: Option { inherited } = None, + FontFamily font_family font_family_sv nocb: Option { inherited } = None, + FontWeight font_weight font_weight_sv nocb: Option { inherited } = None, + FontStyle font_style font_style_sv nocb: Option { inherited } = None, + CursorColor cursor_color cursor_color_sv nocb: Option {} = None, + TextOverflowProp text_overflow text_overflow_sv: TextOverflow {} = TextOverflow::Wrap, + LineHeight line_height line_height_sv nocb: Option { inherited } = None, + AspectRatio aspect_ratio aspect_ratio_sv: Option {} = None, + Gap gap gap_sv: Size {} = Size::zero(), +); - /// Apply another `Style` to this style, returning a new `Style` with the overrides - /// - /// `StyleValue::Val` will override the value with the given value - /// `StyleValue::Unset` will unset the value, causing it to fall back to the underlying - /// `ComputedStyle` (aka setting it to `None`) - /// `StyleValue::Base` will leave the value as-is, whether falling back to the underlying - /// `ComputedStyle` or using the value in the `Style`. - pub fn apply(self, over: Style) -> Style { - let mut other = self.other.unwrap_or_default(); - if let Some(over) = over.other { - other.apply(over); - } - Style { - other: Some(other), - $( - $name: match (self.$name, over.$name) { - (_, StyleValue::Val(x)) => StyleValue::Val(x), - (StyleValue::Val(x), StyleValue::Base) => StyleValue::Val(x), - (StyleValue::Val(_), StyleValue::Unset) => StyleValue::Unset, - (StyleValue::Base, StyleValue::Base) => StyleValue::Base, - (StyleValue::Unset, StyleValue::Base) => StyleValue::Unset, - (StyleValue::Base, StyleValue::Unset) => StyleValue::Unset, - (StyleValue::Unset, StyleValue::Unset) => StyleValue::Unset, - }, - )* - } - } +prop_extracter! { + pub FontProps { + pub size: FontSize, + pub family: FontFamily, + pub weight: FontWeight, + pub style: FontStyle, + } +} - /// Apply multiple `Style`s to this style, returning a new `Style` with the overrides. - /// Later styles take precedence over earlier styles. - pub fn apply_overriding_styles(self, overrides: impl Iterator) -> Style { - overrides.fold(self, |acc, x| acc.apply(x)) - } - } - }; - // internal submacro - - // 'nocb' doesn't add a builder function - (decl: $name:ident $name_sv:ident nocb: $typ:ty = $val:expr) => {}; - (decl: $name:ident $name_sv:ident: $typ:ty = $val:expr) => { - pub fn $name(mut self, v: impl Into<$typ>) -> Self - { - self.$name = StyleValue::Val(v.into()); - self +impl Style { + pub fn get(&self, _prop: P) -> P::Type { + if let Some(other) = &self.other { + other.get_prop::

().unwrap_or_else(|| P::default_value()) + } else { + P::default_value() } + } - pub fn $name_sv(mut self, v: StyleValue<$typ>) -> Self - { - self.$name = v; - self + pub fn get_style_value(&self, _prop: P) -> StyleValue { + if let Some(other) = &self.other { + other.get_prop_style_value::

() + } else { + StyleValue::Base } } -} -define_styles!( - display display_sv: Display = Display::Flex, - position position_sv: Position = Position::Relative, - width width_sv: PxPctAuto = PxPctAuto::Auto, - height height_sv: PxPctAuto = PxPctAuto::Auto, - min_width min_width_sv: PxPctAuto = PxPctAuto::Auto, - min_height min_height_sv: PxPctAuto = PxPctAuto::Auto, - max_width max_width_sv: PxPctAuto = PxPctAuto::Auto, - max_height max_height_sv: PxPctAuto = PxPctAuto::Auto, - flex_direction flex_direction_sv: FlexDirection = FlexDirection::Row, - flex_wrap flex_wrap_sv: FlexWrap = FlexWrap::NoWrap, - flex_grow flex_grow_sv: f32 = 0.0, - flex_shrink flex_shrink_sv: f32 = 1.0, - flex_basis flex_basis_sv: PxPctAuto = PxPctAuto::Auto, - justify_content justify_content_sv: Option = None, - justify_self justify_self_sv: Option = None, - align_items align_items_sv: Option = None, - align_content align_content_sv: Option = None, - align_self align_self_sv: Option = None, - border_left border_left_sv: Px = Px(0.0), - border_top border_top_sv: Px = Px(0.0), - border_right border_right_sv: Px = Px(0.0), - border_bottom border_bottom_sv: Px = Px(0.0), - border_radius border_radius_sv: Px = Px(0.0), - outline_color outline_color_sv: Color = Color::TRANSPARENT, - outline outline_sv: Px = Px(0.0), - border_color border_color_sv: Color = Color::BLACK, - padding_left padding_left_sv: PxPct = PxPct::Px(0.0), - padding_top padding_top_sv: PxPct = PxPct::Px(0.0), - padding_right padding_right_sv: PxPct = PxPct::Px(0.0), - padding_bottom padding_bottom_sv: PxPct = PxPct::Px(0.0), - margin_left margin_left_sv: PxPctAuto = PxPctAuto::Px(0.0), - margin_top margin_top_sv: PxPctAuto = PxPctAuto::Px(0.0), - margin_right margin_right_sv: PxPctAuto = PxPctAuto::Px(0.0), - margin_bottom margin_bottom_sv: PxPctAuto = PxPctAuto::Px(0.0), - inset_left inset_left_sv: PxPctAuto = PxPctAuto::Auto, - inset_top inset_top_sv: PxPctAuto = PxPctAuto::Auto, - inset_right inset_right_sv: PxPctAuto = PxPctAuto::Auto, - inset_bottom inset_bottom_sv: PxPctAuto = PxPctAuto::Auto, - z_index z_index_sv nocb: Option = None, - cursor cursor_sv nocb: Option = None, - color color_sv nocb: Option = None, - background background_sv nocb: Option = None, - box_shadow box_shadow_sv nocb: Option = None, - scroll_bar_hover_color scroll_bar_hover_color_sv nocb: Option = None, - scroll_bar_drag_color scroll_bar_drag_color_sv nocb: Option = None, - scroll_bar_rounded scroll_bar_rounded_sv nocb: Option = None, - scroll_bar_thickness scroll_bar_thickness_sv nocb: Option = None, - scroll_bar_edge_width scroll_bar_edge_width_sv nocb: Option = None, - font_size font_size_sv nocb: Option = None, - font_family font_family_sv nocb: Option = None, - font_weight font_weight_sv nocb: Option = None, - font_style font_style_sv nocb: Option = None, - cursor_color cursor_color_sv nocb: Option = None, - text_overflow text_overflow_sv: TextOverflow = TextOverflow::Wrap, - line_height line_height_sv nocb: Option = None, - aspect_ratio aspect_ratio_sv: Option = None, - gap gap_sv: Size = Size::zero(), -); - -impl Style { - pub fn set(self, prop: P, value: P::Type) -> Self { - self.set_style_value(prop, StyleValue::Val(value)) + pub fn set(self, prop: P, value: impl Into) -> Self { + self.set_style_value(prop, StyleValue::Val(value.into())) } pub fn set_style_value(mut self, _prop: P, value: StyleValue) -> Self { @@ -595,7 +670,6 @@ impl Style { } 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); @@ -856,162 +930,74 @@ impl Style { .inset_bottom(inset) } - pub fn cursor(mut self, cursor: impl Into>) -> Self { - self.cursor = cursor.into().map(Some); - self - } - - pub fn color(mut self, color: impl Into>) -> Self { - self.color = color.into().map(Some); - self - } - - pub fn background(mut self, color: impl Into>) -> Self { - self.background = color.into().map(Some); - self - } - - pub fn box_shadow_blur(mut self, blur_radius: f64) -> Self { - if let Some(box_shadow) = self.box_shadow.as_mut() { - if let Some(box_shadow) = box_shadow.as_mut() { - box_shadow.blur_radius = blur_radius; - return self; - } - } - - self.box_shadow = Some(BoxShadow { - blur_radius, - ..Default::default() - }) - .into(); - self - } - - pub fn box_shadow_color(mut self, color: Color) -> Self { - if let Some(box_shadow) = self.box_shadow.as_mut() { - if let Some(box_shadow) = box_shadow.as_mut() { - box_shadow.color = color; - return self; - } - } - - self.box_shadow = Some(BoxShadow { - color, - ..Default::default() - }) - .into(); - self - } - - pub fn box_shadow_spread(mut self, spread: f64) -> Self { - if let Some(box_shadow) = self.box_shadow.as_mut() { - if let Some(box_shadow) = box_shadow.as_mut() { - box_shadow.spread = spread; - return self; - } - } - - self.box_shadow = Some(BoxShadow { - spread, - ..Default::default() - }) - .into(); - self + pub fn cursor(self, cursor: impl Into>) -> Self { + self.set_style_value(Cursor, cursor.into().map(Some)) } - pub fn box_shadow_h_offset(mut self, h_offset: f64) -> Self { - if let Some(box_shadow) = self.box_shadow.as_mut() { - if let Some(box_shadow) = box_shadow.as_mut() { - box_shadow.h_offset = h_offset; - return self; - } - } - - self.box_shadow = Some(BoxShadow { - h_offset, - ..Default::default() - }) - .into(); - self + pub fn color(self, color: impl Into>) -> Self { + self.set_style_value(TextColor, color.into().map(Some)) } - pub fn box_shadow_v_offset(mut self, v_offset: f64) -> Self { - if let Some(box_shadow) = self.box_shadow.as_mut() { - if let Some(box_shadow) = box_shadow.as_mut() { - box_shadow.v_offset = v_offset; - return self; - } - } - - self.box_shadow = Some(BoxShadow { - v_offset, - ..Default::default() - }) - .into(); - self + pub fn background(self, color: impl Into>) -> Self { + self.set_style_value(Background, color.into().map(Some)) } - pub fn scroll_bar_color(self, color: impl Into>) -> Self { - self.set_style_value(scroll_bar_color, color.into()) + pub fn box_shadow_blur(self, blur_radius: f64) -> Self { + let mut value = self.get(BoxShadowProp).unwrap_or_default(); + value.blur_radius = blur_radius; + self.set(BoxShadowProp, Some(value)) } - pub fn scroll_bar_hover_color(mut self, color: impl Into>) -> Self { - self.scroll_bar_hover_color = color.into().map(Some); - self + pub fn box_shadow_color(self, color: Color) -> Self { + let mut value = self.get(BoxShadowProp).unwrap_or_default(); + value.color = color; + self.set(BoxShadowProp, Some(value)) } - pub fn scroll_bar_drag_color(mut self, color: impl Into>) -> Self { - self.scroll_bar_drag_color = color.into().map(Some); - self + pub fn box_shadow_spread(self, spread: f64) -> Self { + let mut value = self.get(BoxShadowProp).unwrap_or_default(); + value.spread = spread; + self.set(BoxShadowProp, Some(value)) } - pub fn scroll_bar_rounded(mut self, rounded: impl Into>) -> Self { - self.scroll_bar_rounded = rounded.into().map(Some); - self + pub fn box_shadow_h_offset(self, h_offset: f64) -> Self { + let mut value = self.get(BoxShadowProp).unwrap_or_default(); + value.h_offset = h_offset; + self.set(BoxShadowProp, Some(value)) } - pub fn scroll_bar_thickness(mut self, thickness: impl Into) -> Self { - self.scroll_bar_thickness = StyleValue::Val(Some(thickness.into())); - self + pub fn box_shadow_v_offset(self, v_offset: f64) -> Self { + let mut value = self.get(BoxShadowProp).unwrap_or_default(); + value.v_offset = v_offset; + self.set(BoxShadowProp, Some(value)) } - pub fn scroll_bar_edge_width(mut self, edge_width: impl Into) -> Self { - self.scroll_bar_edge_width = StyleValue::Val(Some(edge_width.into())); - self + pub fn font_size(self, size: impl Into>) -> Self { + self.set_style_value(FontSize, size.into().map(Some)) } - pub fn font_size(mut self, size: impl Into>) -> Self { - self.font_size = size.into().map(Some); - self + pub fn font_family(self, family: impl Into>) -> Self { + self.set_style_value(FontFamily, family.into().map(Some)) } - pub fn font_family(mut self, family: impl Into>) -> Self { - self.font_family = family.into().map(Some); - self - } - - pub fn font_weight(mut self, weight: impl Into>) -> Self { - self.font_weight = weight.into().map(Some); - self + pub fn font_weight(self, weight: impl Into>) -> Self { + self.set_style_value(FontWeight, weight.into().map(Some)) } pub fn font_bold(self) -> Self { self.font_weight(Weight::BOLD) } - pub fn font_style(mut self, style: impl Into>) -> Self { - self.font_style = style.into().map(Some); - self + pub fn font_style(self, style: impl Into>) -> Self { + self.set_style_value(FontStyle, style.into().map(Some)) } - pub fn cursor_color(mut self, color: impl Into>) -> Self { - self.cursor_color = color.into().map(Some); - self + pub fn cursor_color(self, color: impl Into>) -> Self { + self.set_style_value(CursorColor, color.into().map(Some)) } - pub fn line_height(mut self, normal: f32) -> Self { - self.line_height = Some(LineHeightValue::Normal(normal)).into(); - self + pub fn line_height(self, normal: f32) -> Self { + self.set(LineHeight, Some(LineHeightValue::Normal(normal))) } pub fn text_ellipsis(self) -> Self { @@ -1023,58 +1009,57 @@ impl Style { } pub fn absolute(self) -> Self { - self.position(Position::Absolute) + self.position(taffy::style::Position::Absolute) } pub fn items_start(self) -> Self { - self.align_items(Some(AlignItems::FlexStart)) + self.align_items(Some(taffy::style::AlignItems::FlexStart)) } /// Defines the alignment along the cross axis as Centered pub fn items_center(self) -> Self { - self.align_items(Some(AlignItems::Center)) + self.align_items(Some(taffy::style::AlignItems::Center)) } pub fn items_end(self) -> Self { - self.align_items(Some(AlignItems::FlexEnd)) + self.align_items(Some(taffy::style::AlignItems::FlexEnd)) } /// Defines the alignment along the main axis as Centered pub fn justify_center(self) -> Self { - self.justify_content(Some(JustifyContent::Center)) + self.justify_content(Some(taffy::style::JustifyContent::Center)) } pub fn justify_end(self) -> Self { - self.justify_content(Some(JustifyContent::FlexEnd)) + self.justify_content(Some(taffy::style::JustifyContent::FlexEnd)) } pub fn justify_start(self) -> Self { - self.justify_content(Some(JustifyContent::FlexStart)) + self.justify_content(Some(taffy::style::JustifyContent::FlexStart)) } pub fn justify_between(self) -> Self { - self.justify_content(Some(JustifyContent::SpaceBetween)) + self.justify_content(Some(taffy::style::JustifyContent::SpaceBetween)) } pub fn hide(self) -> Self { - self.display(Display::None) + self.display(taffy::style::Display::None) } pub fn flex(self) -> Self { - self.display(Display::Flex) + self.display(taffy::style::Display::Flex) } pub fn flex_row(self) -> Self { - self.flex_direction(FlexDirection::Row) + self.flex_direction(taffy::style::FlexDirection::Row) } pub fn flex_col(self) -> Self { - self.flex_direction(FlexDirection::Column) + self.flex_direction(taffy::style::FlexDirection::Column) } - pub fn z_index(mut self, z_index: i32) -> Self { - self.z_index = Some(z_index).into(); - self + pub fn z_index(self, z_index: i32) -> Self { + self.set(ZIndex, Some(z_index)) } /// Allow the application of a function if the option exists. @@ -1112,57 +1097,58 @@ impl Style { impl ComputedStyle { pub fn to_taffy_style(&self) -> TaffyStyle { + let style = self.get_builtin(); TaffyStyle { - display: self.display, - position: self.position, + display: style.display(), + position: style.position(), size: taffy::prelude::Size { - width: self.width.into(), - height: self.height.into(), + width: style.width().into(), + height: style.height().into(), }, min_size: taffy::prelude::Size { - width: self.min_width.into(), - height: self.min_height.into(), + width: style.min_width().into(), + height: style.min_height().into(), }, max_size: taffy::prelude::Size { - width: self.max_width.into(), - height: self.max_height.into(), + width: style.max_width().into(), + height: style.max_height().into(), }, - flex_direction: self.flex_direction, - flex_grow: self.flex_grow, - flex_shrink: self.flex_shrink, - flex_basis: self.flex_basis.into(), - flex_wrap: self.flex_wrap, - justify_content: self.justify_content, - justify_self: self.justify_self, - align_items: self.align_items, - align_content: self.align_content, - align_self: self.align_self, - aspect_ratio: self.aspect_ratio, + flex_direction: style.flex_direction(), + flex_grow: style.flex_grow(), + flex_shrink: style.flex_shrink(), + flex_basis: style.flex_basis().into(), + flex_wrap: style.flex_wrap(), + justify_content: style.justify_content(), + justify_self: style.justify_self(), + align_items: style.align_items(), + align_content: style.align_content(), + align_self: style.align_self(), + aspect_ratio: style.aspect_ratio(), border: Rect { - left: LengthPercentage::Points(self.border_left.0 as f32), - top: LengthPercentage::Points(self.border_top.0 as f32), - right: LengthPercentage::Points(self.border_right.0 as f32), - bottom: LengthPercentage::Points(self.border_bottom.0 as f32), + left: LengthPercentage::Points(style.border_left().0 as f32), + top: LengthPercentage::Points(style.border_top().0 as f32), + right: LengthPercentage::Points(style.border_right().0 as f32), + bottom: LengthPercentage::Points(style.border_bottom().0 as f32), }, padding: Rect { - left: self.padding_left.into(), - top: self.padding_top.into(), - right: self.padding_right.into(), - bottom: self.padding_bottom.into(), + left: style.padding_left().into(), + top: style.padding_top().into(), + right: style.padding_right().into(), + bottom: style.padding_bottom().into(), }, margin: Rect { - left: self.margin_left.into(), - top: self.margin_top.into(), - right: self.margin_right.into(), - bottom: self.margin_bottom.into(), + left: style.margin_left().into(), + top: style.margin_top().into(), + right: style.margin_right().into(), + bottom: style.margin_bottom().into(), }, inset: Rect { - left: self.inset_left.into(), - top: self.inset_top.into(), - right: self.inset_right.into(), - bottom: self.inset_bottom.into(), + left: style.inset_left().into(), + top: style.inset_top().into(), + right: style.inset_right().into(), + bottom: style.inset_bottom().into(), }, - gap: self.gap, + gap: style.gap(), ..Default::default() } } @@ -1171,7 +1157,10 @@ impl ComputedStyle { #[cfg(test)] mod tests { use super::{Style, StyleValue}; - use crate::unit::PxPct; + use crate::{ + style::{PaddingBottom, PaddingLeft}, + unit::PxPct, + }; #[test] fn style_override() { @@ -1180,7 +1169,10 @@ mod tests { let style = style1.apply(style2); - assert_eq!(style.padding_left, StyleValue::Val(PxPct::Px(64.0))); + assert_eq!( + style.get_style_value(PaddingLeft), + StyleValue::Val(PxPct::Px(64.0)) + ); let style1 = Style::BASE.padding_left(32.0).padding_bottom(45.0); let style2 = Style::BASE @@ -1189,8 +1181,14 @@ mod tests { let style = style1.apply(style2); - assert_eq!(style.padding_left, StyleValue::Val(PxPct::Px(64.0))); - assert_eq!(style.padding_bottom, StyleValue::Val(PxPct::Px(45.0))); + assert_eq!( + style.get_style_value(PaddingLeft), + StyleValue::Val(PxPct::Px(64.0)) + ); + assert_eq!( + style.get_style_value(PaddingBottom), + StyleValue::Val(PxPct::Px(45.0)) + ); let style1 = Style::BASE.padding_left(32.0).padding_bottom(45.0); let style2 = Style::BASE @@ -1199,8 +1197,11 @@ mod tests { let style = style1.apply(style2); - assert_eq!(style.padding_left, StyleValue::Val(PxPct::Px(64.0))); - assert_eq!(style.padding_bottom, StyleValue::Unset); + assert_eq!( + style.get_style_value(PaddingLeft), + StyleValue::Val(PxPct::Px(64.0)) + ); + assert_eq!(style.get_style_value(PaddingBottom), StyleValue::Unset); let style1 = Style::BASE.padding_left(32.0).padding_bottom(45.0); let style2 = Style::BASE @@ -1211,8 +1212,11 @@ mod tests { let style = style1.apply_overriding_styles([style2, style3].into_iter()); - assert_eq!(style.padding_left, StyleValue::Val(PxPct::Px(64.0))); - assert_eq!(style.padding_bottom, StyleValue::Unset); + assert_eq!( + style.get_style_value(PaddingLeft), + StyleValue::Val(PxPct::Px(64.0)) + ); + assert_eq!(style.get_style_value(PaddingBottom), StyleValue::Unset); let style1 = Style::BASE.padding_left(32.0).padding_bottom(45.0); let style2 = Style::BASE @@ -1222,7 +1226,13 @@ mod tests { let style = style1.apply_overriding_styles([style2, style3].into_iter()); - assert_eq!(style.padding_left, StyleValue::Val(PxPct::Px(64.0))); - assert_eq!(style.padding_bottom, StyleValue::Val(PxPct::Px(100.0))); + assert_eq!( + style.get_style_value(PaddingLeft), + StyleValue::Val(PxPct::Px(64.0)) + ); + assert_eq!( + style.get_style_value(PaddingBottom), + StyleValue::Val(PxPct::Px(100.0)) + ); } } diff --git a/src/view.rs b/src/view.rs index 8529f6b7..b71e3b30 100644 --- a/src/view.rs +++ b/src/view.rs @@ -95,7 +95,7 @@ use crate::{ context::{AppState, DragState, EventCx, LayoutCx, PaintCx, UpdateCx}, event::{Event, EventListener}, id::Id, - style::{ComputedStyle, Style, StyleMap}, + style::{BoxShadowProp, ComputedStyle, Outline, OutlineColor, Style, StyleMap, ZIndex}, }; bitflags! { @@ -196,50 +196,9 @@ 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.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; - } - if style.scroll_bar_hover_color.is_some() { - cx.scroll_bar_hover_color = style.scroll_bar_hover_color; - } - if style.scroll_bar_drag_color.is_some() { - cx.scroll_bar_drag_color = style.scroll_bar_drag_color; - } - if style.scroll_bar_hover_color.is_some() { - cx.scroll_bar_hover_color = style.scroll_bar_hover_color; - } - if style.scroll_bar_rounded.is_some() { - cx.scroll_bar_rounded = style.scroll_bar_rounded; - } - if style.scroll_bar_thickness.is_some() { - cx.scroll_bar_thickness = style.scroll_bar_thickness.map(|v| v.0 as f32); - } - if style.scroll_bar_edge_width.is_some() { - cx.scroll_bar_edge_width = style.scroll_bar_edge_width.map(|v| v.0 as f32); - } - if style.font_size.is_some() { - cx.font_size = style.font_size; - } - if style.font_family.is_some() { - cx.font_family = style.font_family; - } - if style.font_weight.is_some() { - cx.font_weight = style.font_weight; - } - if style.font_style.is_some() { - cx.font_style = style.font_style; - } - if style.line_height.is_some() { - cx.line_height = style.line_height; - } - let node = self.layout(cx); cx.restore(); @@ -266,22 +225,6 @@ pub trait View { } cx.save(); - let style = cx.app_state_mut().get_computed_style(self.id()).clone(); - if style.font_size.is_some() { - cx.font_size = style.font_size; - } - if style.font_family.is_some() { - cx.font_family = style.font_family; - } - if style.font_weight.is_some() { - cx.font_weight = style.font_weight; - } - if style.font_style.is_some() { - cx.font_style = style.font_style; - } - if style.line_height.is_some() { - cx.line_height = style.line_height; - } let layout = cx .app_state() @@ -481,8 +424,8 @@ pub trait View { } } else { cx.app_state.hovered.insert(id); - let style = cx.app_state.get_computed_style(id); - if let Some(cursor) = style.cursor { + let style = cx.app_state.get_builtin_style(id); + if let Some(cursor) = style.cursor() { if cx.app_state.cursor.is_none() { cx.app_state.cursor = Some(cursor); } @@ -683,39 +626,12 @@ pub trait View { if !is_empty { let style = cx.app_state.get_computed_style(id).clone(); - if let Some(z_index) = style.z_index { + if let Some(z_index) = style.get(ZIndex) { cx.set_z_index(z_index); } paint_bg(cx, &style, size); - if style.color.is_some() { - cx.color = style.color; - } - if style.scroll_bar_rounded.is_some() { - cx.scroll_bar_rounded = style.scroll_bar_rounded; - } - if style.scroll_bar_thickness.is_some() { - cx.scroll_bar_thickness = style.scroll_bar_thickness.map(|v| v.0 as f32); - } - if style.scroll_bar_edge_width.is_some() { - cx.scroll_bar_edge_width = style.scroll_bar_edge_width.map(|v| v.0 as f32); - } - if style.font_size.is_some() { - cx.font_size = style.font_size; - } - if style.font_family.is_some() { - cx.font_family = style.font_family.clone(); - } - if style.font_weight.is_some() { - cx.font_weight = style.font_weight; - } - if style.font_style.is_some() { - cx.font_style = style.font_style; - } - if style.line_height.is_some() { - cx.line_height = style.line_height; - } self.paint(cx); paint_border(cx, &style, size); paint_outline(cx, &style, size) @@ -760,15 +676,12 @@ pub trait View { .combined_style .clone() .apply(dragging_style) - .compute(&ComputedStyle::default()) + .compute() } else { style }; paint_bg(cx, &style, size); - if style.color.is_some() { - cx.color = style.color; - } self.paint(cx); paint_border(cx, &style, size); paint_outline(cx, &style, size); @@ -789,8 +702,9 @@ pub trait View { fn paint(&mut self, cx: &mut PaintCx); } -fn paint_bg(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { - let radius = style.border_radius.0; +fn paint_bg(cx: &mut PaintCx, computed_style: &ComputedStyle, size: Size) { + let style = computed_style.get_builtin(); + let radius = style.border_radius().0; if radius > 0.0 { let rect = size.to_rect(); let width = rect.width(); @@ -798,14 +712,14 @@ fn paint_bg(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { if width > 0.0 && height > 0.0 && radius > width.max(height) / 2.0 { let radius = width.max(height) / 2.0; let circle = Circle::new(rect.center(), radius); - let bg = match style.background { + let bg = match style.background() { Some(color) => color, None => return, }; cx.fill(&circle, bg, 0.0); } else { - paint_box_shadow(cx, style, rect, Some(radius)); - let bg = match style.background { + paint_box_shadow(cx, computed_style, rect, Some(radius)); + let bg = match style.background() { Some(color) => color, None => return, }; @@ -813,8 +727,8 @@ fn paint_bg(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { cx.fill(&rounded_rect, bg, 0.0); } } else { - paint_box_shadow(cx, style, size.to_rect(), None); - let bg = match style.background { + paint_box_shadow(cx, computed_style, size.to_rect(), None); + let bg = match style.background() { Some(color) => color, None => return, }; @@ -823,7 +737,7 @@ fn paint_bg(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { } fn paint_box_shadow(cx: &mut PaintCx, style: &ComputedStyle, rect: Rect, rect_radius: Option) { - if let Some(shadow) = style.box_shadow.as_ref() { + if let Some(shadow) = style.get(BoxShadowProp).as_ref() { let inset = Insets::new( -shadow.h_offset / 2.0, -shadow.v_offset / 2.0, @@ -841,26 +755,28 @@ fn paint_box_shadow(cx: &mut PaintCx, style: &ComputedStyle, rect: Rect, rect_ra } fn paint_outline(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { - if style.outline.0 == 0. { + let outline = style.get(Outline).0; + if outline == 0. { // TODO: we should warn! when outline is < 0 return; } - let half = style.outline.0 / 2.0; + let half = outline / 2.0; let rect = size.to_rect().inflate(half, half); - cx.stroke(&rect, style.outline_color, style.outline.0); + cx.stroke(&rect, style.get(OutlineColor), outline); } fn paint_border(cx: &mut PaintCx, style: &ComputedStyle, size: Size) { - let left = style.border_left.0; - let top = style.border_top.0; - let right = style.border_right.0; - let bottom = style.border_bottom.0; + let style = style.get_builtin(); + let left = style.border_left().0; + let top = style.border_top().0; + let right = style.border_right().0; + let bottom = style.border_bottom().0; - let border_color = style.border_color; + let border_color = style.border_color(); if left == top && top == right && right == bottom && bottom == left && left > 0.0 { let half = left / 2.0; let rect = size.to_rect().inflate(-half, -half); - let radius = style.border_radius.0; + let radius = style.border_radius().0; if radius > 0.0 { cx.stroke(&rect.to_rounded_rect(radius), border_color, left); } else { diff --git a/src/views/clip.rs b/src/views/clip.rs index 31d8d217..c82ddcf0 100644 --- a/src/views/clip.rs +++ b/src/views/clip.rs @@ -81,8 +81,8 @@ impl View for Clip { fn paint(&mut self, cx: &mut crate::context::PaintCx) { cx.save(); - let style = cx.get_computed_style(self.id); - let radius = style.border_radius.0; + let style = cx.get_builtin_style(self.id); + let radius = style.border_radius().0; let size = cx .get_layout(self.id) .map(|layout| Size::new(layout.size.width as f64, layout.size.height as f64)) diff --git a/src/views/img.rs b/src/views/img.rs index 7413e096..af6fda6d 100644 --- a/src/views/img.rs +++ b/src/views/img.rs @@ -6,7 +6,7 @@ use sha2::{Digest, Sha256}; use crate::{ id::Id, - style::{ComputedStyle, Style}, + style::Style, unit::UnitExt, view::{ChangeFlags, View}, }; @@ -178,7 +178,7 @@ impl View for Img { let style = Style::BASE .width((width as f64).px()) .height((height as f64).px()) - .compute(&ComputedStyle::default()) + .compute() .to_taffy_style(); let _ = cx.app_state_mut().taffy.set_style(content_node, style); diff --git a/src/views/label.rs b/src/views/label.rs index 9d977ba4..f20eb5e5 100644 --- a/src/views/label.rs +++ b/src/views/label.rs @@ -2,14 +2,12 @@ use std::{any::Any, fmt::Display}; use crate::{ cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout}, - style::{ComputedStyle, TextOverflow}, + prop_extracter, + style::{FontProps, FontSize, LineHeight, TextColor, TextOverflow, TextOverflowProp}, unit::PxPct, }; use floem_reactive::create_effect; -use floem_renderer::{ - cosmic_text::{LineHeightValue, Style as FontStyle, Weight}, - Renderer, -}; +use floem_renderer::Renderer; use kurbo::{Point, Rect}; use peniko::Color; use taffy::prelude::Node; @@ -22,6 +20,14 @@ use crate::{ view::{ChangeFlags, View}, }; +prop_extracter! { + Extracter { + color: TextColor, + text_overflow: TextOverflowProp, + line_height: LineHeight, + } +} + pub struct Label { id: Id, label: String, @@ -30,13 +36,8 @@ pub struct Label { available_text: Option, available_width: Option, available_text_layout: Option, - color: Option, - font_size: Option, - font_family: Option, - font_weight: Option, - font_style: Option, - line_height: Option, - text_overflow: TextOverflow, + font: FontProps, + style: Extracter, } pub fn text(text: S) -> Label { @@ -58,36 +59,31 @@ pub fn label(label: impl Fn() -> S + 'static) -> Label { available_text: None, available_width: None, available_text_layout: None, - color: None, - font_size: None, - font_family: None, - font_weight: None, - font_style: None, - line_height: None, - text_overflow: TextOverflow::Wrap, + font: FontProps::default(), + style: Default::default(), } } impl Label { fn get_attrs_list(&self) -> AttrsList { - let mut attrs = Attrs::new().color(self.color.unwrap_or(Color::BLACK)); - if let Some(font_size) = self.font_size { + let mut attrs = Attrs::new().color(self.style.color().unwrap_or(Color::BLACK)); + if let Some(font_size) = self.font.size() { attrs = attrs.font_size(font_size); } - if let Some(font_style) = self.font_style { + if let Some(font_style) = self.font.style() { attrs = attrs.style(font_style); } - let font_family = self.font_family.as_ref().map(|font_family| { + let font_family = self.font.family().as_ref().map(|font_family| { let family: Vec = FamilyOwned::parse_list(font_family).collect(); family }); if let Some(font_family) = font_family.as_ref() { attrs = attrs.family(font_family); } - if let Some(font_weight) = self.font_weight { + if let Some(font_weight) = self.font.weight() { attrs = attrs.weight(font_weight); } - if let Some(line_height) = self.line_height { + if let Some(line_height) = self.style.line_height() { attrs = attrs.line_height(line_height); } AttrsList::new(attrs) @@ -153,29 +149,13 @@ impl View for Label { fn layout(&mut self, cx: &mut crate::context::LayoutCx) -> taffy::prelude::Node { cx.layout_node(self.id, true, |cx| { let (width, height) = if self.label.is_empty() { - (0.0, cx.current_font_size().unwrap_or(14.0)) + (0.0, cx.get_prop(FontSize).and_then(|s| s).unwrap_or(14.0)) } else { - let text_overflow = cx.app_state_mut().get_computed_style(self.id).text_overflow; - if self.color != cx.color - || self.font_size != cx.font_size - || self.font_family.as_deref() != cx.current_font_family() - || self.font_weight != cx.font_weight - || self.font_style != cx.font_style - || self.line_height != cx.line_height - || self.text_overflow != text_overflow - { - self.color = cx.color; - self.font_size = cx.font_size; - self.font_family = cx.current_font_family().map(|s| s.to_string()); - self.font_weight = cx.font_weight; - self.font_style = cx.font_style; - self.line_height = cx.line_height; - self.text_overflow = text_overflow; + if self.font.read(cx) | self.style.read(cx) { self.text_layout = None; self.available_text = None; self.available_width = None; self.available_text_layout = None; - self.set_text_layout(); } if self.text_layout.is_none() { self.set_text_layout(); @@ -185,7 +165,7 @@ impl View for Label { let width = size.width.ceil() as f32; let mut height = size.height as f32; - if text_overflow == TextOverflow::Wrap { + if self.style.text_overflow() == TextOverflow::Wrap { if let Some(t) = self.available_text_layout.as_ref() { height = height.max(t.size().height as f32); } @@ -207,7 +187,7 @@ impl View for Label { let style = Style::BASE .width(width) .height(height) - .compute(&ComputedStyle::default()) + .compute() .to_taffy_style(); let _ = cx.app_state_mut().taffy.set_style(text_node, style); @@ -220,23 +200,14 @@ impl View for Label { return None; } - if self.font_size != cx.font_size - || self.font_family.as_deref() != cx.current_font_family() - || self.font_weight != cx.font_weight - || self.font_style != cx.font_style - || self.line_height != cx.line_height - { - cx.app_state_mut().request_layout(self.id()); - } - let layout = cx.get_layout(self.id()).unwrap(); - let style = cx.app_state_mut().get_computed_style(self.id); - let text_overflow = style.text_overflow; - let padding_left = match style.padding_left { + let style = cx.app_state_mut().get_builtin_style(self.id); + let text_overflow = style.text_overflow(); + let padding_left = match style.padding_left() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; - let padding_right = match style.padding_right { + let padding_right = match style.padding_right() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; @@ -296,21 +267,6 @@ impl View for Label { return; } - if self.color != cx.color - || self.font_size != cx.font_size - || self.font_family.as_deref() != cx.font_family.as_deref() - || self.font_weight != cx.font_weight - || self.font_style != cx.font_style - || self.line_height != cx.line_height - { - self.color = cx.color; - self.font_size = cx.font_size; - self.font_family = cx.font_family.clone(); - self.font_weight = cx.font_weight; - self.font_style = cx.font_style; - self.line_height = cx.line_height; - self.set_text_layout(); - } let text_node = self.text_node.unwrap(); let location = cx.app_state.taffy.layout(text_node).unwrap().location; let point = Point::new(location.x as f64, location.y as f64); diff --git a/src/views/mod.rs b/src/views/mod.rs index 74faa4cc..b3cafe74 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -33,8 +33,8 @@ pub use decorator::*; mod virtual_list; pub use virtual_list::*; -mod scroll; -pub use scroll::*; +pub mod scroll; +pub use scroll::{scroll, Scroll}; mod tab; pub use tab::*; diff --git a/src/views/rich_text.rs b/src/views/rich_text.rs index aff73743..43baddbb 100644 --- a/src/views/rich_text.rs +++ b/src/views/rich_text.rs @@ -9,7 +9,7 @@ use crate::{ context::{EventCx, UpdateCx}, event::Event, id::Id, - style::{ComputedStyle, Style, TextOverflow}, + style::{Style, TextOverflow}, unit::PxPct, view::{ChangeFlags, View}, }; @@ -109,7 +109,7 @@ impl View for RichText { let style = Style::BASE .width(width) .height(height) - .compute(&ComputedStyle::default()) + .compute() .to_taffy_style(); let _ = cx.app_state_mut().taffy.set_style(text_node, style); vec![text_node] @@ -118,18 +118,18 @@ impl View for RichText { fn compute_layout(&mut self, cx: &mut crate::context::LayoutCx) -> Option { let layout = cx.get_layout(self.id()).unwrap(); - let style = cx.app_state_mut().get_computed_style(self.id); - let padding_left = match style.padding_left { + let style = cx.app_state_mut().get_builtin_style(self.id); + let padding_left = match style.padding_left() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; - let padding_right = match style.padding_right { + let padding_right = match style.padding_right() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; let padding = padding_left + padding_right; let available_width = layout.size.width - padding; - self.text_overflow = style.text_overflow; + self.text_overflow = style.text_overflow(); if self.text_overflow == TextOverflow::Wrap && self.available_width != available_width { self.available_width = available_width; self.text_layout.set_size(self.available_width, f32::MAX); diff --git a/src/views/scroll.rs b/src/views/scroll.rs index a312c861..5ce2bf30 100644 --- a/src/views/scroll.rs +++ b/src/views/scroll.rs @@ -9,8 +9,8 @@ use crate::{ event::Event, id::Id, prop, prop_extracter, - style::{ComputedStyle, OptionReader, Style, StyleValue}, - unit::PxPct, + style::{BorderRadius, PositionProp, Style}, + unit::{Px, PxPct}, view::{ChangeFlags, View}, }; @@ -40,55 +40,23 @@ enum BarHeldState { Horizontal(f64, Vec2), } -prop!(pub scroll_bar_color: Color { inherited: true } = Color::rgba8(0, 0, 0, 120)); -prop!(pub bg_active_color: Color { inherited: true } = Color::rgba8(0, 0, 0, 30)); +prop!(pub HandleColor: Color { inherited } = Color::rgba8(0, 0, 0, 120)); +prop!(pub HoverColor: Color { inherited } = Color::rgba8(0, 0, 0, 140)); +prop!(pub DragColor: Color { inherited } = Color::rgba8(0, 0, 0, 160)); +prop!(pub Rounded: bool { inherited } = cfg!(target_os = "macos")); +prop!(pub Thickness: Px { inherited } = Px(10.0)); +prop!(pub EdgeWidth: Px { inherited } = Px(0.0)); +prop!(pub BgActiveColor: Color { inherited } = Color::rgba8(0, 0, 0, 25)); prop_extracter! { - Extracter { - scroll_bar_color, - bg_active_color: OptionReader, - } -} - -pub struct ScrollBarStyle { - hover_color: Option, - drag_color: Option, - rounded: bool, - hide: bool, - thickness: f32, - edge_width: f32, -} - -impl ScrollBarStyle { - pub const BASE: Self = ScrollBarStyle { - hover_color: None, - drag_color: None, - rounded: cfg!(target_os = "macos"), - thickness: 10., - edge_width: 0., - hide: false, - }; - - pub fn hover_color(mut self, color: Color) -> Self { - self.hover_color = Some(color); - self - } - - pub fn drag_color(mut self, color: Color) -> Self { - self.drag_color = Some(color); - self - } - pub fn rounded(mut self, rounded: bool) -> Self { - self.rounded = rounded; - self - } - pub fn thickness(mut self, thickness: f32) -> Self { - self.thickness = thickness; - self - } - pub fn edge_width(mut self, edge_width: f32) -> Self { - self.edge_width = edge_width; - self + ScrollStyle { + handle_color: HandleColor, + hover_color: Option, + drag_color: Option, + rounded: Rounded, + thickness: Thickness, + edge_width: EdgeWidth, + bg_active_color: Option, } } @@ -110,8 +78,8 @@ pub struct Scroll { virtual_node: Option, propagate_pointer_wheel: bool, vertical_scroll_as_horizontal: bool, - scroll_bar_style: ScrollBarStyle, - style: Extracter, + style: ScrollStyle, + hide: bool, } pub fn scroll(child: V) -> Scroll { @@ -131,7 +99,7 @@ pub fn scroll(child: V) -> Scroll { virtual_node: None, propagate_pointer_wheel: false, vertical_scroll_as_horizontal: false, - scroll_bar_style: ScrollBarStyle::BASE, + hide: false, style: Default::default(), } } @@ -271,20 +239,20 @@ impl Scroll { let layout = app_state.get_layout(self.id).unwrap(); self.size = Size::new(layout.size.width as f64, layout.size.height as f64); - let style = app_state.get_computed_style(self.id); - let padding_left = match style.padding_left { + let style = app_state.get_builtin_style(self.id); + let padding_left = match style.padding_left() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; - let padding_right = match style.padding_right { + let padding_right = match style.padding_right() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; - let padding_top = match style.padding_top { + let padding_top = match style.padding_top() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; - let padding_bottom = match style.padding_bottom { + let padding_bottom = match style.padding_bottom() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; @@ -351,10 +319,10 @@ impl Scroll { } fn draw_bars(&self, cx: &mut PaintCx) { - let edge_width = self.scroll_bar_style.edge_width as f64; + let edge_width = self.style.edge_width().0; let scroll_offset = self.child_viewport.origin().to_vec2(); let radius = |rect: Rect, vertical| { - if self.scroll_bar_style.rounded { + if self.style.rounded() { if vertical { (rect.x1 - rect.x0) / 2. } else { @@ -367,19 +335,19 @@ impl Scroll { if let Some(bounds) = self.calc_vertical_bar_bounds(cx.app_state) { let color = if let BarHeldState::Vertical(..) = self.held { - if let Some(color) = self.scroll_bar_style.drag_color { + if let Some(color) = self.style.drag_color() { color } else { - self.style.scroll_bar_color() + self.style.handle_color() } } else if self.vbar_hover { - if let Some(color) = self.scroll_bar_style.hover_color { + if let Some(color) = self.style.hover_color() { color } else { - self.style.scroll_bar_color() + self.style.handle_color() } } else { - self.style.scroll_bar_color() + self.style.handle_color() }; if self.vbar_whole_hover || matches!(self.held, BarHeldState::Vertical(..)) { let mut bounds = bounds; @@ -400,19 +368,19 @@ impl Scroll { // Horizontal bar if let Some(bounds) = self.calc_horizontal_bar_bounds(cx.app_state) { let color = if let BarHeldState::Horizontal(..) = self.held { - if let Some(color) = self.scroll_bar_style.drag_color { + if let Some(color) = self.style.drag_color() { color } else { - self.style.scroll_bar_color() + self.style.handle_color() } } else if self.hbar_hover { - if let Some(color) = self.scroll_bar_style.hover_color { + if let Some(color) = self.style.hover_color() { color } else { - self.style.scroll_bar_color() + self.style.handle_color() } } else { - self.style.scroll_bar_color() + self.style.handle_color() }; if self.hbar_whole_hover || matches!(self.held, BarHeldState::Horizontal(..)) { let mut bounds = bounds; @@ -440,7 +408,7 @@ impl Scroll { return None; } - let bar_width = self.scroll_bar_style.thickness as f64; + let bar_width = self.style.thickness().0; let bar_pad = 0.0; let percent_visible = viewport_size.height / content_size.height; @@ -448,7 +416,7 @@ impl Scroll { let length = (percent_visible * viewport_size.height).ceil(); // Vertical scroll bar must have ast least the same height as it's width - let length = length.max(self.scroll_bar_style.thickness as f64); + let length = length.max(self.style.thickness().0); let top_y_offset = ((viewport_size.height - length) * percent_scrolled).ceil(); let bottom_y_offset = top_y_offset + length; @@ -471,7 +439,7 @@ impl Scroll { return None; } - let bar_width = self.scroll_bar_style.thickness as f64; + let bar_width = self.style.thickness().0; let bar_pad = 0.0; let percent_visible = viewport_size.width / content_size.width; @@ -648,7 +616,7 @@ impl View for Scroll { self.scroll_to(cx.app_state, origin); } ScrollState::HiddenBar(hide) => { - self.scroll_bar_style.hide = hide; + self.hide = hide; } ScrollState::PropagatePointerWheel(value) => { self.propagate_pointer_wheel = value; @@ -666,26 +634,14 @@ impl View for Scroll { fn layout(&mut self, cx: &mut crate::context::LayoutCx) -> taffy::prelude::Node { cx.layout_node(self.id, true, |cx| { - if let Some(color) = cx.scroll_bar_hover_color { - self.scroll_bar_style.hover_color = Some(color); - } - if let Some(color) = cx.scroll_bar_drag_color { - self.scroll_bar_style.drag_color = Some(color); - } self.style.read(cx); - if let Some(rounded) = cx.scroll_bar_rounded { - self.scroll_bar_style.rounded = rounded; - } - if let Some(thickness) = cx.scroll_bar_thickness { - self.scroll_bar_style.thickness = thickness; - } - if let Some(edge_width) = cx.scroll_bar_edge_width { - self.scroll_bar_style.edge_width = edge_width; - } let child_id = self.child.id(); let child_view = cx.app_state_mut().view_state(child_id); - child_view.style.position = StyleValue::Val(Position::Absolute); + child_view.style = child_view + .style + .clone() + .set(PositionProp, Position::Absolute); let child_node = self.child.layout_main(cx); let virtual_style = Style::BASE @@ -693,7 +649,7 @@ impl View for Scroll { .height(self.child_size.height) .min_width(0.0) .min_height(0.0) - .compute(&ComputedStyle::default()) + .compute() .to_taffy_style(); if self.virtual_node.is_none() { self.virtual_node = Some( @@ -732,7 +688,7 @@ impl View for Scroll { match &event { Event::PointerDown(event) => { - if !self.scroll_bar_style.hide && event.button.is_primary() { + if !self.hide && event.button.is_primary() { self.held = BarHeldState::None; let pos = event.pos + scroll_offset; @@ -790,7 +746,7 @@ impl View for Scroll { } } Event::PointerMove(event) => { - if !self.scroll_bar_style.hide { + if !self.hide { let pos = event.pos + scroll_offset; self.update_hover_states(cx.app_state, event.pos); @@ -866,17 +822,8 @@ impl View for Scroll { fn paint(&mut self, cx: &mut crate::context::PaintCx) { cx.save(); - if let Some(rounded) = cx.scroll_bar_rounded { - self.scroll_bar_style.rounded = rounded; - } - if let Some(thickness) = cx.scroll_bar_thickness { - self.scroll_bar_style.thickness = thickness; - } - if let Some(edge_width) = cx.scroll_bar_edge_width { - self.scroll_bar_style.edge_width = edge_width; - } let style = cx.get_computed_style(self.id); - let radius = style.border_radius.0; + let radius = style.get(BorderRadius).0; if radius > 0.0 { let rect = self.actual_rect.to_rounded_rect(radius); cx.clip(&rect); @@ -887,7 +834,7 @@ impl View for Scroll { self.child.paint_main(cx); cx.restore(); - if !self.scroll_bar_style.hide { + if !self.hide { self.draw_bars(cx); } } diff --git a/src/views/svg.rs b/src/views/svg.rs index 4fcf2572..8e54674d 100644 --- a/src/views/svg.rs +++ b/src/views/svg.rs @@ -115,7 +115,7 @@ impl View for Svg { let hash = self.svg_hash.as_ref().unwrap(); let layout = cx.get_layout(self.id).unwrap(); let rect = Size::new(layout.size.width as f64, layout.size.height as f64).to_rect(); - let color = cx.app_state.get_computed_style(self.id).color; + let color = cx.app_state.get_builtin_style(self.id).color(); cx.draw_svg(floem_renderer::Svg { tree, hash }, rect, color); } } diff --git a/src/views/tab.rs b/src/views/tab.rs index 6418e999..3cba2a15 100644 --- a/src/views/tab.rs +++ b/src/views/tab.rs @@ -8,6 +8,7 @@ use taffy::style::Display; use crate::{ context::{EventCx, UpdateCx}, id::Id, + style::DisplayProp, view::{ChangeFlags, View}, }; @@ -179,12 +180,15 @@ impl View for Tab { .filter_map(|(i, child)| { let child_id = child.as_ref()?.0.id(); let child_view = cx.app_state_mut().view_state(child_id); - if i != self.active { - // set display to none for non active child - child_view.style.display = Display::None.into(); - } else { - child_view.style.display = Display::Flex.into(); - } + child_view.style = child_view.style.clone().set( + DisplayProp, + if i != self.active { + // set display to none for non active child + Display::None + } else { + Display::Flex + }, + ); let node = child.as_mut()?.0.layout_main(cx); Some(node) }) diff --git a/src/views/text_input.rs b/src/views/text_input.rs index e55d5ff6..c34be8dd 100644 --- a/src/views/text_input.rs +++ b/src/views/text_input.rs @@ -1,15 +1,13 @@ use crate::action::exec_after; use crate::keyboard::{self, KeyEvent}; use crate::reactive::{create_effect, RwSignal}; +use crate::style::CursorStyle; +use crate::style::{FontProps, PaddingLeft}; use crate::unit::PxPct; -use crate::{context::LayoutCx, style::CursorStyle}; use clipboard::{ClipboardContext, ClipboardProvider}; use taffy::prelude::{Layout, Node}; -use floem_renderer::{ - cosmic_text::{Cursor, Style as FontStyle, Weight}, - Renderer, -}; +use floem_renderer::{cosmic_text::Cursor, Renderer}; use unicode_segmentation::UnicodeSegmentation; use winit::keyboard::{Key, ModifiersState, NamedKey, SmolStr}; @@ -21,10 +19,7 @@ use std::{ time::{Duration, Instant}, }; -use crate::{ - cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout}, - style::ComputedStyle, -}; +use crate::cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout}; use kurbo::{Point, Rect}; use crate::{ @@ -68,12 +63,9 @@ pub struct TextInput { clip_offset_x: f64, color: Option, selection: Option>, - font_size: f32, width: f32, height: f32, - font_family: Option, - font_weight: Option, - font_style: Option, + font: FontProps, input_kind: InputKind, cursor_width: f64, // TODO: make this configurable is_focused: bool, @@ -113,10 +105,7 @@ pub fn text_input(buffer: RwSignal) -> TextInput { clipped_text: None, clip_txt_buf: None, color: None, - font_size: DEFAULT_FONT_SIZE, - font_family: None, - font_weight: None, - font_style: None, + font: FontProps::default(), cursor_x: 0.0, selection: None, input_kind: InputKind::SingleLine, @@ -245,13 +234,6 @@ impl TextInput { } } - fn text_layout_changed(&self, cx: &LayoutCx) -> bool { - self.font_size != cx.current_font_size().unwrap_or(DEFAULT_FONT_SIZE) - || self.font_family.as_deref() != cx.current_font_family() - || self.font_weight != cx.font_weight - || self.font_style != cx.font_style - } - fn get_line_idx(&self) -> usize { match self.input_kind { InputKind::SingleLine => 0, @@ -370,8 +352,8 @@ impl TextInput { self.buffer .with_untracked(|buff| text_layout.set_text(buff, attrs.clone())); - self.width = APPROX_VISIBLE_CHARS * self.font_size; - self.height = self.font_size; + self.width = APPROX_VISIBLE_CHARS * self.font_size(); + self.height = self.font_size(); // main buff should always get updated self.text_buf = Some(text_layout.clone()); @@ -384,22 +366,26 @@ impl TextInput { } } + fn font_size(&self) -> f32 { + self.font.size().unwrap_or(DEFAULT_FONT_SIZE) + } + pub fn get_text_attrs(&self) -> AttrsList { let mut attrs = Attrs::new().color(self.color.unwrap_or(Color::BLACK)); - attrs = attrs.font_size(self.font_size); + attrs = attrs.font_size(self.font_size()); - if let Some(font_style) = self.font_style { + if let Some(font_style) = self.font.style() { attrs = attrs.style(font_style); } - let font_family = self.font_family.as_ref().map(|font_family| { + let font_family = self.font.family().as_ref().map(|font_family| { let family: Vec = FamilyOwned::parse_list(font_family).collect(); family }); if let Some(font_family) = font_family.as_ref() { attrs = attrs.family(font_family); } - if let Some(font_weight) = self.font_weight { + if let Some(font_weight) = self.font.weight() { attrs = attrs.weight(font_weight); } AttrsList::new(attrs) @@ -765,13 +751,13 @@ impl View for TextInput { } else { // Already focused - move cursor to click pos let layout = cx.get_layout(self.id()).unwrap(); - let style = cx.app_state.get_computed_style(self.id); + let style = cx.app_state.get_builtin_style(self.id); - let padding_left = match style.padding_left { + let padding_left = match style.padding_left() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; - let padding_top = match style.padding_top { + let padding_top = match style.padding_top() { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * layout.size.width, }; @@ -811,13 +797,7 @@ impl View for TextInput { fn layout(&mut self, cx: &mut crate::context::LayoutCx) -> taffy::prelude::Node { cx.layout_node(self.id, true, |cx| { self.is_focused = cx.app_state().is_focused(&self.id); - if self.text_layout_changed(cx) { - self.font_size = cx.current_font_size().unwrap_or(DEFAULT_FONT_SIZE); - self.font_family = cx.current_font_family().map(|s| s.to_string()); - self.font_weight = cx.font_weight; - self.font_style = cx.font_style; - self.update_text_layout(); - } else if self.text_buf.is_none() { + if self.font.read(cx) || self.text_buf.is_none() { self.update_text_layout(); } @@ -834,7 +814,7 @@ impl View for TextInput { let style = Style::BASE .width(self.width) .height(self.height) - .compute(&ComputedStyle::default()) + .compute() .to_taffy_style(); let _ = cx.app_state_mut().taffy.set_style(text_node, style); @@ -853,26 +833,16 @@ impl View for TextInput { return; } - if self.color != cx.color - || self.font_size != cx.font_size.unwrap_or(DEFAULT_FONT_SIZE) - || self.font_family.as_deref() != cx.font_family.as_deref() - || self.font_weight != cx.font_weight - || self.font_style != cx.font_style - { - self.color = cx.color; - self.font_size = cx.font_size.unwrap_or(DEFAULT_FONT_SIZE); - self.font_family = cx.font_family.clone(); - self.font_weight = cx.font_weight; - self.font_style = cx.font_style; - self.update_text_layout(); - } - let text_node = self.text_node.unwrap(); let text_buf = self.text_buf.as_ref().unwrap(); let buf_width = text_buf.size().width; let node_layout = *cx.app_state.taffy.layout(text_node).unwrap(); let node_width = node_layout.size.width as f64; - let cursor_color = cx.app_state.get_computed_style(self.id).cursor_color; + let cursor_color = cx + .app_state + .get_computed_style(self.id) + .get_builtin() + .cursor_color(); match self.input_kind { InputKind::SingleLine => { @@ -920,7 +890,7 @@ impl View for TextInput { let style = cx.app_state.get_computed_style(self.id); - let padding_left = match style.padding_left { + let padding_left = match style.get(PaddingLeft) { PxPct::Px(padding) => padding as f32, PxPct::Pct(pct) => pct as f32 * node_layout.size.width, }; diff --git a/src/window_handle.rs b/src/window_handle.rs index 9cd92297..65c5bfcf 100644 --- a/src/window_handle.rs +++ b/src/window_handle.rs @@ -855,40 +855,19 @@ impl WindowHandle { unit: SizeUnit::Px, } } - AnimPropKind::BorderRadius => { - let border_radius = view_state.computed_style.border_radius; - AnimatedProp::BorderRadius { - from: border_radius.0, - to: val.get_f64(), - } - } - AnimPropKind::BorderColor => { - let border_color = view_state.computed_style.border_color; - AnimatedProp::BorderColor { - from: border_color, - to: val.get_color(), - } - } - AnimPropKind::Background => { - //TODO: get from cx - let bg = view_state - .computed_style - .background - .expect("Bg must be set in the styles"); - AnimatedProp::Background { - from: bg, - to: val.get_color(), - } - } - AnimPropKind::Color => { + AnimPropKind::Prop { prop } => { //TODO: get from cx - let color = view_state + let from = view_state .computed_style - .color - .expect("Color must be set in the animated view's style"); - AnimatedProp::Color { - from: color, - to: val.get_color(), + .other + .map + .get(&prop) + .and_then(|v| v.as_ref().cloned()) + .unwrap_or_else(|| (prop.info.default_as_any)()); + AnimatedProp::Prop { + prop, + from, + to: val.get_any(), } } };