//! Types for styling widgets. use std::any::Any; use std::borrow::Cow; use std::collections::hash_map; use std::fmt::Debug; use std::ops::{ Add, AddAssign, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, }; use std::panic::{RefUnwindSafe, UnwindSafe}; use std::sync::Arc; use ahash::AHashMap; use kludgine::cosmic_text::{FamilyOwned, Style, Weight}; use kludgine::figures::units::{Lp, Px, UPx}; use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Rect, ScreenScale, Size, Zero}; use kludgine::shapes::CornerRadii; use kludgine::Color; use palette::{IntoColor, Okhsl, OklabHue, Srgb}; use crate::animation::{EasingFunction, ZeroToOne}; use crate::context::WidgetContext; use crate::names::Name; use crate::utils::Lazy; use crate::value::{Dynamic, IntoValue, Value}; #[macro_use] pub mod components; /// A collection of style components organized by their name. #[derive(Clone, Debug, Default)] pub struct Styles(Arc); impl Styles { /// Returns an empty collection. #[must_use] pub fn new() -> Self { Self::default() } /// Returns a collection with the capacity to hold up to `capacity` elements /// without reallocating. #[must_use] pub fn with_capacity(capacity: usize) -> Self { Self(Arc::new(StyleData { components: AHashMap::with_capacity(capacity), })) } /// Inserts a [`Component`] with a given name. pub fn insert_named(&mut self, name: ComponentName, component: impl IntoComponentValue) { Arc::make_mut(&mut self.0) .components .insert(name, component.into_component_value()); } /// Inserts a [`Component`] using then name provided. pub fn insert(&mut self, name: &impl NamedComponent, component: impl IntoComponentValue) { let name = name.name().into_owned(); self.insert_named(name, component); } /// Inserts a [`Component`] using then name provided, resolving the value /// through `dynamic`. pub fn insert_dynamic( &mut self, name: &impl NamedComponent, dynamic: impl Into, ) { self.insert(name, Component::Dynamic(dynamic.into())); } /// Adds a [`Component`] for the name provided and returns self. #[must_use] pub fn with( mut self, name: &C, component: impl IntoValue, ) -> Self where Value: IntoComponentValue, { self.insert(name, component.into_value()); self } /// Adds a [`Component`] using then name provided, resolving the value /// through `dynamic`. This function returns self. #[must_use] pub fn with_dynamic( mut self, name: &C, dynamic: impl Into, ) -> Self { self.insert_dynamic(name, dynamic); self } /// Returns the associated component for the given name, if found. #[must_use] pub fn get_with_fallback( &self, component: &impl NamedComponent, fallback: &Fallback, context: &WidgetContext<'_, '_>, ) -> Fallback::ComponentType where Fallback: ComponentDefinition + ?Sized, { self.0 .components .get(&component.name()) .or_else(|| self.0.components.get(&fallback.name())) .and_then(|component| Self::resolve_component(component, context)) .unwrap_or_else(|| fallback.default_value(context)) } fn resolve_component( component: &Value, context: &WidgetContext<'_, '_>, ) -> Option where T: ComponentType, { let mut resolved = component.get(); loop { match T::try_from_component(resolved) { Ok(value) => { if value.requires_invalidation() { component.invalidate_when_changed(context); } else { component.redraw_when_changed(context); } break Some(value); } Err(Component::Dynamic(dynamic)) => { let Some(new_component) = dynamic.resolve(context) else { break None; }; resolved = new_component; } Err(_) => break None, } } } /// Returns the component associated with the given name, or if not found, /// returns the default value provided by the definition. #[must_use] pub fn get( &self, component: &Named, context: &WidgetContext<'_, '_>, ) -> Named::ComponentType where Named: ComponentDefinition + ?Sized, { self.0 .components .get(&component.name()) .and_then(|component| Self::resolve_component(component, context)) .unwrap_or_else(|| component.default_value(context)) } /// Inserts all components from `other`, overwriting any existing entries /// with the same [`ComponentName`]. pub fn append(&mut self, other: Styles) { for (name, value) in other { self.insert_named(name, value); } } } #[derive(Debug, Default, Clone)] struct StyleData { components: AHashMap>, } /// A value that can be converted into a `Value`. pub trait IntoComponentValue { /// Returns `self` stored in a component value. fn into_component_value(self) -> Value; } impl IntoComponentValue for T where T: Into, { fn into_component_value(self) -> Value { Value::Constant(self.into()) } } impl IntoComponentValue for Value where T: Clone + Send + 'static, Component: From, { fn into_component_value(self) -> Value { self.map_each(|v| Component::from(v.clone())) } } impl IntoComponentValue for Dynamic where T: Clone + Send + 'static, Component: From, { fn into_component_value(self) -> Value { Value::Dynamic(self.map_each_into()) } } impl FromIterator<(ComponentName, Component)> for Styles { fn from_iter>(iter: T) -> Self { let iter = iter.into_iter(); let mut styles = Self::with_capacity(iter.size_hint().0); for (name, component) in iter { styles.insert_named(name, component); } styles } } impl IntoIterator for Styles { type IntoIter = hash_map::IntoIter>; type Item = (ComponentName, Value); fn into_iter(self) -> Self::IntoIter { Arc::try_unwrap(self.0) .unwrap_or_else(|err| err.as_ref().clone()) .components .into_iter() } } // /// An iterator over the owned contents of a [`Styles`] instance. // pub struct StylesIntoIter { // main: hash_map::IntoIter>, // } // impl Iterator for StylesIntoIter { // type Item = (ComponentName, Value); // fn next(&mut self) -> Option { // loop { // if let Some((group, names)) = &mut self.names { // if let Some((name, component)) = names.next() { // return Some((ComponentName::new(group.clone(), name), component)); // } // self.names = None; // } // let (group, names) = self.main.next()?; // self.names = Some((group, names.into_iter())); // } // } // } /// A value of a style component. #[derive(Debug, Clone, PartialEq)] pub enum Component { /// A color. Color(Color), /// A single-dimension measurement. Dimension(Dimension), /// A single-dimension measurement. DimensionRange(DimensionRange), /// A percentage between 0.0 and 1.0. Percent(ZeroToOne), /// An easing function for animations. Easing(EasingFunction), /// A visual ordering to use for layout. VisualOrder(VisualOrder), /// A description of what widgets should be focusable. FocusableWidgets(FocusableWidgets), /// A description of the depth of a /// [`Container`](crate::widgets::Container). ContainerLevel(ContainerLevel), /// A font family. FontFamily(FamilyOwned), /// The weight (boldness) of a font. FontWeight(Weight), /// The style of a font. FontStyle(Style), /// A custom component type. Custom(CustomComponent), /// This component should use the associated value in the named class. Dynamic(DynamicComponent), } impl Component { /// Returns a [`CustomComponent`] created from `component`. /// /// Custom components allow storing nearly any type in the style system. pub fn custom(component: T) -> Self where T: RequireInvalidation + RefUnwindSafe + UnwindSafe + Debug + Send + Sync + 'static, { Self::Custom(CustomComponent::new(component)) } /// Returns a new [`DynamicComponent`] which allows resolving a component at /// runtime. #[must_use] pub fn dynamic(resolve: Func) -> Self where Func: for<'a, 'context, 'widget> Fn(&'a WidgetContext<'context, 'widget>) -> Option + RefUnwindSafe + Send + Sync + 'static, T: ComponentType, { Self::Dynamic(DynamicComponent::new(move |context| { resolve(context).map(T::into_component) })) } } impl From for Component { fn from(value: FamilyOwned) -> Self { Self::FontFamily(value) } } impl TryFrom for FamilyOwned { type Error = Component; fn try_from(value: Component) -> Result { match value { Component::FontFamily(family) => Ok(family), other => Err(other), } } } impl RequireInvalidation for FamilyOwned { fn requires_invalidation(&self) -> bool { true } } impl From for Component { fn from(value: Weight) -> Self { Self::FontWeight(value) } } impl TryFrom for Weight { type Error = Component; fn try_from(value: Component) -> Result { match value { Component::FontWeight(weight) => Ok(weight), other => Err(other), } } } impl RequireInvalidation for Weight { fn requires_invalidation(&self) -> bool { true } } impl From