use std::any::Any;
use std::borrow::Cow;
use std::collections::hash_map;
use std::fmt::{Debug, Write};
use std::ops::{
Add, AddAssign, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
RangeToInclusive, Sub,
};
use std::sync::Arc;
use ahash::AHashMap;
use figures::units::{Lp, Px, UPx};
use figures::{
Fraction, IntoSigned, IntoUnsigned, Rect, Round, ScreenScale, Size, UnscaledUnit, Zero,
};
use intentional::{Cast, CastFrom, CastInto};
pub use kludgine::cosmic_text::{FamilyOwned, Style, Weight};
pub use kludgine::shapes::CornerRadii;
pub use kludgine::Color;
pub use palette::OklabHue;
use palette::{IntoColor, Okhsl, Srgb};
use crate::animation::{EasingFunction, ZeroToOne};
use crate::context::{Trackable, WidgetContext};
use crate::names::Name;
use crate::utils::Lazy;
use crate::value::{Dynamic, IntoValue, Source, Value};
use crate::widget::MakeWidget;
use crate::widgets::input::CowString;
use crate::widgets::ComponentProbe;
#[macro_use]
pub mod components;
#[derive(Clone, Default)]
pub struct Styles(Arc<StyleData>);
impl Styles {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self(Arc::new(StyleData {
components: AHashMap::with_capacity(capacity),
}))
}
pub fn insert_named(&mut self, name: ComponentName, component: impl IntoStoredComponent) {
Arc::make_mut(&mut self.0)
.components
.insert(name, component.into_stored_component());
}
pub fn insert_local_named(&mut self, name: ComponentName, component: impl IntoStoredComponent) {
let mut component = component.into_stored_component();
component.inheritable = false;
Arc::make_mut(&mut self.0)
.components
.insert(name, component);
}
pub fn insert(&mut self, name: &impl NamedComponent, component: impl IntoComponentValue) {
let name = name.name().into_owned();
self.insert_named(name, component);
}
pub fn insert_local(&mut self, name: &impl NamedComponent, component: impl IntoComponentValue) {
let name = name.name().into_owned();
self.insert_local_named(name, component);
}
pub fn insert_dynamic(
&mut self,
name: &impl NamedComponent,
dynamic: impl IntoDynamicComponentValue,
) {
let component = match dynamic.into_dynamic_component() {
Value::Constant(dynamic) => Value::Constant(Component::Dynamic(dynamic)),
Value::Dynamic(dynamic) => Value::Dynamic(dynamic.map_each_cloned(Component::Dynamic)),
};
self.insert(name, component);
}
pub fn insert_local_dynamic(
&mut self,
name: &impl NamedComponent,
dynamic: impl IntoDynamicComponentValue,
) {
let component = match dynamic.into_dynamic_component() {
Value::Constant(dynamic) => Value::Constant(Component::Dynamic(dynamic)),
Value::Dynamic(dynamic) => Value::Dynamic(dynamic.map_each_cloned(Component::Dynamic)),
};
self.insert_local(name, component);
}
#[must_use]
pub fn with<C: ComponentDefinition>(
mut self,
name: &C,
component: impl IntoValue<C::ComponentType>,
) -> Self
where
Value<C::ComponentType>: IntoComponentValue,
{
self.insert_named(
name.name().into_owned(),
component.into_value().into_component_value(),
);
self
}
#[must_use]
pub fn with_local<C: ComponentDefinition>(
mut self,
name: &C,
component: impl IntoValue<C::ComponentType>,
) -> Self
where
Value<C::ComponentType>: IntoComponentValue,
{
self.insert_local_named(
name.name().into_owned(),
component.into_value().into_component_value(),
);
self
}
#[must_use]
pub fn with_dynamic<C: ComponentDefinition>(
mut self,
name: &C,
dynamic: impl IntoDynamicComponentValue,
) -> Self {
self.insert_dynamic(name, dynamic);
self
}
#[must_use]
pub fn with_local_dynamic<C: ComponentDefinition>(
mut self,
name: &C,
dynamic: impl IntoDynamicComponentValue,
) -> Self {
self.insert_local_dynamic(name, dynamic);
self
}
#[must_use]
pub fn get_with_fallback<Fallback>(
&self,
component: &impl NamedComponent,
fallback: &Fallback,
context: &WidgetContext<'_>,
) -> Fallback::ComponentType
where
Fallback: ComponentDefinition,
{
self.0
.components
.get(&component.name())
.or_else(|| self.0.components.get(&fallback.name()))
.and_then(|stored| Self::resolve_component(&stored.component, context))
.unwrap_or_else(|| fallback.default_value(context))
}
fn resolve_component<T>(component: &Value<Component>, context: &WidgetContext<'_>) -> Option<T>
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,
}
}
}
#[must_use]
pub fn try_get<Named>(
&self,
component: &Named,
context: &WidgetContext<'_>,
) -> Option<Named::ComponentType>
where
Named: ComponentDefinition,
{
self.0
.components
.get(&component.name())
.and_then(|stored| Self::resolve_component(&stored.component, context))
}
#[must_use]
pub fn get<Named>(&self, component: &Named, context: &WidgetContext<'_>) -> Named::ComponentType
where
Named: ComponentDefinition,
{
self.try_get(component, context)
.unwrap_or_else(|| component.default_value(context))
}
pub fn inherit_from(&mut self, other: Styles) {
for (name, mut value) in Arc::try_unwrap(other.0)
.unwrap_or_else(|err| err.as_ref().clone())
.components
{
if !value.inheritable || self.0.components.contains_key(&name) {
continue;
}
value.inherited = true;
self.insert_named(name, value);
}
}
#[must_use]
pub fn into_inherited(self) -> Self {
if self.0.components.values().any(|stored| !stored.inheritable) {
Self(Arc::new(StyleData {
components: Arc::try_unwrap(self.0)
.unwrap_or_else(|err| err.as_ref().clone())
.components
.into_iter()
.filter(|(_, stored)| stored.inheritable)
.collect(),
}))
} else {
self
}
}
}
impl Debug for Styles {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut map = f.debug_struct("Styles");
let mut component_name = String::new();
for (name, stored) in &self.0.components {
component_name.clear();
write!(&mut component_name, "{name:?}")?;
map.field(&component_name, &stored.component);
}
map.finish()
}
}
impl FromIterator<(ComponentName, Component)> for Styles {
fn from_iter<T: IntoIterator<Item = (ComponentName, Component)>>(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 = StylesIntoIter;
type Item = (ComponentName, Value<Component>);
fn into_iter(self) -> Self::IntoIter {
StylesIntoIter {
into_iter: Arc::try_unwrap(self.0)
.unwrap_or_else(|err| err.as_ref().clone())
.components
.into_iter(),
}
}
}
pub struct StylesIntoIter {
into_iter: hash_map::IntoIter<ComponentName, StoredComponent>,
}
impl Iterator for StylesIntoIter {
type Item = (ComponentName, Value<Component>);
fn next(&mut self) -> Option<Self::Item> {
self.into_iter
.next()
.map(|(name, stored)| (name, stored.component))
}
}
#[derive(Default, Clone)]
struct StyleData {
components: AHashMap<ComponentName, StoredComponent>,
}
#[derive(Clone)]
pub struct StoredComponent {
inherited: bool,
inheritable: bool,
component: Value<Component>,
}
impl StoredComponent {
pub fn local(component: impl IntoComponentValue) -> Self {
Self {
inherited: false,
inheritable: false,
component: component.into_component_value(),
}
}
}
pub trait IntoComponentValue {
fn into_component_value(self) -> Value<Component>;
}
pub trait IntoStoredComponent {
fn into_stored_component(self) -> StoredComponent;
}
impl<T> IntoStoredComponent for T
where
T: IntoComponentValue,
{
fn into_stored_component(self) -> StoredComponent {
StoredComponent {
inherited: false,
inheritable: true,
component: self.into_component_value(),
}
}
}
impl IntoStoredComponent for StoredComponent {
fn into_stored_component(self) -> StoredComponent {
self
}
}
impl<T> IntoComponentValue for T
where
T: Into<Component>,
{
fn into_component_value(self) -> Value<Component> {
Value::Constant(self.into())
}
}
impl<T> IntoComponentValue for Value<T>
where
T: Clone + Send + 'static,
Component: From<T>,
{
fn into_component_value(self) -> Value<Component> {
self.map_each(|v| Component::from(v.clone()))
}
}
impl<T> IntoComponentValue for Dynamic<T>
where
T: Clone + Send + 'static,
Component: From<T>,
{
fn into_component_value(self) -> Value<Component> {
Value::Dynamic(self.map_each_into())
}
}
pub trait IntoDynamicComponentValue {
fn into_dynamic_component(self) -> Value<DynamicComponent>;
}
impl IntoDynamicComponentValue for DynamicComponent {
fn into_dynamic_component(self) -> Value<DynamicComponent> {
Value::Constant(self)
}
}
impl<T> IntoDynamicComponentValue for T
where
T: ComponentDefinition + Clone + Send + Sync + 'static,
{
fn into_dynamic_component(self) -> Value<DynamicComponent> {
Value::Constant(DynamicComponent::from(self))
}
}
impl<T> IntoDynamicComponentValue for Dynamic<T>
where
T: ComponentDefinition + Clone + Send + Sync + 'static,
{
fn into_dynamic_component(self) -> Value<DynamicComponent> {
Value::Dynamic(self.map_each_into())
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Component {
Color(Color),
Dimension(Dimension),
DimensionRange(DimensionRange),
Percent(ZeroToOne),
Easing(EasingFunction),
VisualOrder(VisualOrder),
FocusableWidgets(FocusableWidgets),
ContainerLevel(ContainerLevel),
FontFamily(FamilyOwned),
FontWeight(Weight),
FontStyle(Style),
String(CowString),
HorizontalAlign(HorizontalAlign),
VerticalAlign(VerticalAlign),
Custom(CustomComponent),
Dynamic(DynamicComponent),
}
impl Component {
pub fn custom<T>(component: T) -> Self
where
T: RequireInvalidation + Debug + Send + Sync + 'static,
{
Self::Custom(CustomComponent::new(component))
}
#[must_use]
pub fn dynamic<T, Func>(resolve: Func) -> Self
where
Func:
for<'a, 'context> Fn(&'a WidgetContext<'context>) -> Option<T> + Send + Sync + 'static,
T: ComponentType,
{
Self::Dynamic(DynamicComponent::new(move |context| {
resolve(context).map(T::into_component)
}))
}
}
macro_rules! impl_component_from_string {
($type:ty) => {
impl From<$type> for Component {
fn from(s: $type) -> Self {
Self::String(s.into())
}
}
};
}
impl_component_from_string!(String);
impl_component_from_string!(CowString);
impl_component_from_string!(&'_ str);
macro_rules! impl_component_try_from_string {
($type:ty) => {
impl TryFrom<Component> for $type {
type Error = Component;
fn try_from(s: Component) -> Result<Self, Self::Error> {
match s {
Component::String(s) => Ok(s.into()),
other => Err(other),
}
}
}
};
}
impl_component_try_from_string!(String);
impl_component_try_from_string!(CowString);
impl From<FamilyOwned> for Component {
fn from(value: FamilyOwned) -> Self {
Self::FontFamily(value)
}
}
impl TryFrom<Component> for FamilyOwned {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::FontFamily(family) => Ok(family),
other => Err(other),
}
}
}
impl RequireInvalidation for FamilyOwned {
fn requires_invalidation(&self) -> bool {
true
}
}
impl From<Weight> for Component {
fn from(value: Weight) -> Self {
Self::FontWeight(value)
}
}
impl TryFrom<Component> for Weight {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::FontWeight(weight) => Ok(weight),
other => Err(other),
}
}
}
impl RequireInvalidation for Weight {
fn requires_invalidation(&self) -> bool {
true
}
}
impl From<Style> for Component {
fn from(value: Style) -> Self {
Self::FontStyle(value)
}
}
impl TryFrom<Component> for Style {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::FontStyle(style) => Ok(style),
other => Err(other),
}
}
}
impl RequireInvalidation for Style {
fn requires_invalidation(&self) -> bool {
true
}
}
impl From<Color> for Component {
fn from(value: Color) -> Self {
Self::Color(value)
}
}
impl TryFrom<Component> for Color {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Color(color) => Ok(color),
other => Err(other),
}
}
}
impl RequireInvalidation for Color {
fn requires_invalidation(&self) -> bool {
false
}
}
impl From<Dimension> for Component {
fn from(value: Dimension) -> Self {
Self::Dimension(value)
}
}
impl TryFrom<Component> for Dimension {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Dimension(color) => Ok(color),
other => Err(other),
}
}
}
impl RequireInvalidation for Dimension {
fn requires_invalidation(&self) -> bool {
true
}
}
impl From<Px> for Component {
fn from(value: Px) -> Self {
Self::from(Dimension::from(value))
}
}
impl TryFrom<Component> for Px {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Dimension(Dimension::Px(px)) => Ok(px),
other => Err(other),
}
}
}
impl RequireInvalidation for Px {
fn requires_invalidation(&self) -> bool {
true
}
}
impl From<Lp> for Component {
fn from(value: Lp) -> Self {
Self::from(Dimension::from(value))
}
}
impl TryFrom<Component> for Lp {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Dimension(Dimension::Lp(px)) => Ok(px),
other => Err(other),
}
}
}
impl RequireInvalidation for Lp {
fn requires_invalidation(&self) -> bool {
true
}
}
impl<Unit> From<CornerRadii<Unit>> for Component
where
Dimension: From<Unit>,
Unit: Debug + Send + Sync + 'static,
{
fn from(radii: CornerRadii<Unit>) -> Self {
let radii = CornerRadii {
top_left: Dimension::from(radii.top_left),
top_right: Dimension::from(radii.top_right),
bottom_right: Dimension::from(radii.bottom_right),
bottom_left: Dimension::from(radii.bottom_left),
};
Component::custom(radii)
}
}
impl<Unit> RequireInvalidation for CornerRadii<Unit> {
fn requires_invalidation(&self) -> bool {
true
}
}
impl TryFrom<Component> for CornerRadii<Dimension> {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Custom(custom) => custom
.downcast()
.copied()
.ok_or_else(|| Component::Custom(custom)),
other => Err(other),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FlexibleDimension {
Auto,
Dimension(Dimension),
}
impl Zero for FlexibleDimension {
const ZERO: Self = Self::Dimension(Dimension::ZERO);
fn is_zero(&self) -> bool {
match self {
FlexibleDimension::Auto => false,
FlexibleDimension::Dimension(dim) => dim.is_zero(),
}
}
}
impl Debug for FlexibleDimension {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Auto => f.write_str("Auto"),
Self::Dimension(arg0) => Debug::fmt(arg0, f),
}
}
}
impl Default for FlexibleDimension {
fn default() -> Self {
Self::ZERO
}
}
impl From<Dimension> for FlexibleDimension {
fn from(dimension: Dimension) -> Self {
Self::Dimension(dimension)
}
}
impl From<Px> for FlexibleDimension {
fn from(value: Px) -> Self {
Self::from(Dimension::from(value))
}
}
impl From<Lp> for FlexibleDimension {
fn from(value: Lp) -> Self {
Self::from(Dimension::from(value))
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Dimension {
Px(Px),
Lp(Lp),
}
impl Debug for Dimension {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Px(arg0) => Debug::fmt(arg0, f),
Self::Lp(arg0) => Debug::fmt(arg0, f),
}
}
}
impl Default for Dimension {
fn default() -> Self {
Self::ZERO
}
}
impl From<UPx> for Dimension {
fn from(value: UPx) -> Self {
Self::Px(value.into_signed())
}
}
impl From<Px> for Dimension {
fn from(value: Px) -> Self {
Self::Px(value)
}
}
impl From<Lp> for Dimension {
fn from(value: Lp) -> Self {
Self::Lp(value)
}
}
impl Zero for Dimension {
const ZERO: Self = Dimension::Px(Px::ZERO);
fn is_zero(&self) -> bool {
match self {
Dimension::Px(x) => x.is_zero(),
Dimension::Lp(x) => x.is_zero(),
}
}
}
impl ScreenScale for Dimension {
type Lp = Lp;
type Px = Px;
type UPx = UPx;
fn into_px(self, scale: figures::Fraction) -> Px {
match self {
Dimension::Px(px) => px,
Dimension::Lp(lp) => lp.into_px(scale),
}
}
fn from_px(px: Px, _scale: figures::Fraction) -> Self {
Self::from(px)
}
fn into_lp(self, scale: figures::Fraction) -> Lp {
match self {
Dimension::Px(px) => px.into_lp(scale),
Dimension::Lp(lp) => lp,
}
}
fn from_lp(lp: Lp, _scale: figures::Fraction) -> Self {
Self::from(lp)
}
fn into_upx(self, scale: Fraction) -> Self::UPx {
match self {
Dimension::Px(px) => px.into_unsigned(),
Dimension::Lp(lp) => lp.into_upx(scale),
}
}
fn from_upx(px: Self::UPx, _scale: Fraction) -> Self {
Self::from(px.into_signed())
}
}
impl Mul<i32> for Dimension {
type Output = Dimension;
fn mul(self, rhs: i32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val * rhs),
Self::Lp(val) => Self::Lp(val * rhs),
}
}
}
impl Mul<f32> for Dimension {
type Output = Dimension;
fn mul(self, rhs: f32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val * rhs),
Self::Lp(val) => Self::Lp(val * rhs),
}
}
}
impl Div<i32> for Dimension {
type Output = Dimension;
fn div(self, rhs: i32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val / rhs),
Self::Lp(val) => Self::Lp(val / rhs),
}
}
}
impl Div<f32> for Dimension {
type Output = Dimension;
fn div(self, rhs: f32) -> Self::Output {
match self {
Self::Px(val) => Self::Px(val / rhs),
Self::Lp(val) => Self::Lp(val / rhs),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DimensionRange {
pub start: Bound<Dimension>,
pub end: Bound<Dimension>,
}
impl Default for DimensionRange {
fn default() -> Self {
Self {
start: Bound::Unbounded,
end: Bound::Unbounded,
}
}
}
impl DimensionRange {
#[must_use]
pub fn exact_dimension(&self) -> Option<Dimension> {
match (self.start, self.end) {
(Bound::Excluded(start), Bound::Included(end)) if start == end => Some(start),
_ => None,
}
}
#[must_use]
pub fn clamp(&self, mut size: UPx, scale: Fraction) -> UPx {
if let Some(min) = self.minimum() {
size = size.max(min.into_upx(scale));
}
if let Some(max) = self.maximum() {
size = size.min(max.into_upx(scale));
}
size
}
#[must_use]
pub fn minimum(&self) -> Option<Dimension> {
match self.start {
Bound::Unbounded => None,
Bound::Excluded(Dimension::Lp(lp)) => Some(Dimension::Lp(lp + 1)),
Bound::Excluded(Dimension::Px(px)) => Some(Dimension::Px(px + 1)),
Bound::Included(value) => Some(value),
}
}
#[must_use]
pub fn maximum(&self) -> Option<Dimension> {
match self.end {
Bound::Unbounded => None,
Bound::Excluded(Dimension::Lp(lp)) => Some(Dimension::Lp(lp - 1)),
Bound::Excluded(Dimension::Px(px)) => Some(Dimension::Px(px - 1)),
Bound::Included(value) => Some(value),
}
}
#[must_use]
pub const fn is_unbounded(&self) -> bool {
matches!(&self.start, Bound::Unbounded) && matches!(&self.end, Bound::Unbounded)
}
}
impl<T> From<T> for DimensionRange
where
T: Into<Dimension>,
{
fn from(value: T) -> Self {
let dimension = value.into();
Self::from(dimension..=dimension)
}
}
impl<T> From<Range<T>> for DimensionRange
where
T: Into<Dimension>,
{
fn from(value: Range<T>) -> Self {
Self {
start: Bound::Included(value.start.into()),
end: Bound::Excluded(value.end.into()),
}
}
}
impl From<RangeFull> for DimensionRange {
fn from(_: RangeFull) -> Self {
Self {
start: Bound::Unbounded,
end: Bound::Unbounded,
}
}
}
impl<T> From<RangeInclusive<T>> for DimensionRange
where
T: Into<Dimension> + Clone,
{
fn from(value: RangeInclusive<T>) -> Self {
Self {
start: Bound::Included(value.start().clone().into()),
end: Bound::Included(value.end().clone().into()),
}
}
}
impl<T> From<RangeFrom<T>> for DimensionRange
where
T: Into<Dimension>,
{
fn from(value: RangeFrom<T>) -> Self {
Self {
start: Bound::Included(value.start.into()),
end: Bound::Unbounded,
}
}
}
impl<T> From<RangeTo<T>> for DimensionRange
where
T: Into<Dimension>,
{
fn from(value: RangeTo<T>) -> Self {
Self {
start: Bound::Unbounded,
end: Bound::Excluded(value.end.into()),
}
}
}
impl<T> From<RangeToInclusive<T>> for DimensionRange
where
T: Into<Dimension>,
{
fn from(value: RangeToInclusive<T>) -> Self {
Self {
start: Bound::Unbounded,
end: Bound::Included(value.end.into()),
}
}
}
impl From<DimensionRange> for Component {
fn from(value: DimensionRange) -> Self {
Component::DimensionRange(value)
}
}
impl TryFrom<Component> for DimensionRange {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::DimensionRange(value) => Ok(value),
other => Err(other),
}
}
}
impl RequireInvalidation for DimensionRange {
fn requires_invalidation(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct CustomComponent(Arc<dyn AnyComponent>);
impl CustomComponent {
pub fn new<T>(value: T) -> Self
where
T: RequireInvalidation + Debug + Send + Sync + 'static,
{
Self(Arc::new(value))
}
#[must_use]
pub fn downcast<T>(&self) -> Option<&T>
where
T: Debug + Send + Sync + 'static,
{
self.0.as_ref().as_any().downcast_ref()
}
}
impl PartialEq for CustomComponent {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl RequireInvalidation for CustomComponent {
fn requires_invalidation(&self) -> bool {
self.0.requires_invalidation()
}
}
impl ComponentType for CustomComponent {
fn into_component(self) -> Component {
Component::Custom(self)
}
fn try_from_component(component: Component) -> Result<Self, Component> {
match component {
Component::Custom(custom) => Ok(custom),
other => Err(other),
}
}
}
trait AnyComponent: RequireInvalidation + Send + Sync + Debug {
fn as_any(&self) -> &dyn Any;
}
impl<T> AnyComponent for T
where
T: RequireInvalidation + Debug + Send + Sync + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct ComponentName {
pub group: Name,
pub name: Name,
}
impl ComponentName {
pub fn new(group: impl Into<Name>, name: impl Into<Name>) -> Self {
Self {
group: group.into(),
name: name.into(),
}
}
}
impl Debug for ComponentName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}.{:?}", &self.group, &self.name)
}
}
impl From<&'static Lazy<ComponentName>> for ComponentName {
fn from(value: &'static Lazy<ComponentName>) -> Self {
(**value).clone()
}
}
pub trait NamedComponent: Sized {
fn name(&self) -> Cow<'_, ComponentName>;
}
pub trait ComponentDefinition: NamedComponent {
type ComponentType: ComponentType;
fn default_value(&self, context: &WidgetContext<'_>) -> Self::ComponentType;
}
pub trait ContextFreeComponent: ComponentDefinition {
fn default(&self) -> Self::ComponentType;
fn probe(self) -> ComponentProbe<Self> {
ComponentProbe::default_for(self)
}
fn probe_wrapping(self, child: impl MakeWidget) -> ComponentProbe<Self> {
ComponentProbe::default_wrapping(self, child)
}
}
pub trait RequireInvalidation {
fn requires_invalidation(&self) -> bool;
}
pub trait ComponentType: RequireInvalidation + Sized {
fn into_component(self) -> Component;
fn try_from_component(component: Component) -> Result<Self, Component>;
}
impl<T> ComponentType for T
where
T: RequireInvalidation + Into<Component> + TryFrom<Component, Error = Component>,
{
fn into_component(self) -> Component {
self.into()
}
fn try_from_component(component: Component) -> Result<Self, Component> {
Self::try_from(component)
}
}
impl NamedComponent for ComponentName {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Borrowed(self)
}
}
impl NamedComponent for Cow<'_, ComponentName> {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Borrowed(self)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Edges<T = FlexibleDimension> {
pub left: T,
pub top: T,
pub right: T,
pub bottom: T,
}
impl<T> Edges<T> {
pub fn size(self) -> Size<T>
where
T: Add<Output = T> + Copy,
{
Size::new(self.width(), self.height())
}
pub fn map<U>(self, mut map: impl FnMut(T) -> U) -> Edges<U> {
Edges {
left: map(self.left),
top: map(self.top),
right: map(self.right),
bottom: map(self.bottom),
}
}
pub fn width(self) -> T
where
T: Add<Output = T>,
{
self.left + self.right
}
pub fn height(self) -> T
where
T: Add<Output = T>,
{
self.top + self.bottom
}
}
impl<T> Default for Edges<T>
where
T: Default,
{
fn default() -> Self {
Self {
left: T::default(),
right: T::default(),
top: T::default(),
bottom: Default::default(),
}
}
}
impl<T> Round for Edges<T>
where
T: Round,
{
fn round(self) -> Self {
self.map(Round::round)
}
fn ceil(self) -> Self {
self.map(Round::ceil)
}
fn floor(self) -> Self {
self.map(Round::floor)
}
}
impl<T> Edges<T> {
#[must_use]
pub fn with_top(mut self, top: impl Into<T>) -> Self {
self.top = top.into();
self
}
#[must_use]
pub fn with_bottom(mut self, bottom: impl Into<T>) -> Self {
self.bottom = bottom.into();
self
}
#[must_use]
pub fn with_right(mut self, right: impl Into<T>) -> Self {
self.right = right.into();
self
}
#[must_use]
pub fn with_left(mut self, left: impl Into<T>) -> Self {
self.left = left.into();
self
}
#[must_use]
pub fn with_horizontal(mut self, horizontal: impl Into<T>) -> Self
where
T: Clone,
{
self.left = horizontal.into();
self.right = self.left.clone();
self
}
#[must_use]
pub fn with_vertical(mut self, vertical: impl Into<T>) -> Self
where
T: Clone,
{
self.top = vertical.into();
self.bottom = self.top.clone();
self
}
}
impl<Unit> Zero for Edges<Unit>
where
Unit: Zero,
{
const ZERO: Self = Self {
left: Unit::ZERO,
top: Unit::ZERO,
right: Unit::ZERO,
bottom: Unit::ZERO,
};
fn is_zero(&self) -> bool {
self.left.is_zero() && self.top.is_zero() && self.right.is_zero() && self.bottom.is_zero()
}
}
impl Edges<Dimension> {
#[must_use]
pub fn uniform<D>(dimension: D) -> Self
where
D: Into<Dimension>,
{
let dimension = dimension.into();
Self::from(dimension)
}
}
impl<T> From<T> for Edges<T>
where
T: Clone,
{
fn from(value: T) -> Self {
Self {
left: value.clone(),
right: value.clone(),
top: value.clone(),
bottom: value,
}
}
}
impl IntoValue<Edges<FlexibleDimension>> for FlexibleDimension {
fn into_value(self) -> Value<Edges<FlexibleDimension>> {
Value::Constant(Edges::from(self))
}
}
impl IntoValue<Edges<FlexibleDimension>> for Dimension {
fn into_value(self) -> Value<Edges<FlexibleDimension>> {
FlexibleDimension::Dimension(self).into_value()
}
}
impl IntoValue<Edges<FlexibleDimension>> for Px {
fn into_value(self) -> Value<Edges<FlexibleDimension>> {
Dimension::from(self).into_value()
}
}
impl IntoValue<Edges<FlexibleDimension>> for Lp {
fn into_value(self) -> Value<Edges<FlexibleDimension>> {
Dimension::from(self).into_value()
}
}
impl IntoValue<Edges<Dimension>> for Dimension {
fn into_value(self) -> Value<Edges<Dimension>> {
Value::Constant(Edges::from(self))
}
}
impl IntoValue<Edges<Dimension>> for Px {
fn into_value(self) -> Value<Edges<Dimension>> {
Dimension::from(self).into_value()
}
}
impl IntoValue<Edges<Dimension>> for Lp {
fn into_value(self) -> Value<Edges<Dimension>> {
Dimension::from(self).into_value()
}
}
impl IntoValue<Dimension> for Px {
fn into_value(self) -> Value<Dimension> {
Dimension::from(self).into_value()
}
}
impl IntoValue<Dimension> for Lp {
fn into_value(self) -> Value<Dimension> {
Dimension::from(self).into_value()
}
}
impl IntoValue<FlexibleDimension> for Px {
fn into_value(self) -> Value<FlexibleDimension> {
Dimension::from(self).into_value()
}
}
impl IntoValue<FlexibleDimension> for Lp {
fn into_value(self) -> Value<FlexibleDimension> {
Dimension::from(self).into_value()
}
}
impl IntoValue<FlexibleDimension> for Dimension {
fn into_value(self) -> Value<FlexibleDimension> {
FlexibleDimension::from(self).into_value()
}
}
impl IntoValue<CornerRadii<Dimension>> for Dimension {
fn into_value(self) -> Value<CornerRadii<Dimension>> {
Value::Constant(CornerRadii {
top_left: self,
top_right: self,
bottom_right: self,
bottom_left: self,
})
}
}
impl IntoValue<CornerRadii<Dimension>> for Lp {
fn into_value(self) -> Value<CornerRadii<Dimension>> {
Dimension::Lp(self).into_value()
}
}
impl IntoValue<CornerRadii<Dimension>> for Px {
fn into_value(self) -> Value<CornerRadii<Dimension>> {
Dimension::Px(self).into_value()
}
}
impl<U> ScreenScale for Edges<U>
where
U: ScreenScale<Px = Px, UPx = UPx, Lp = Lp>,
{
type Lp = Edges<Lp>;
type Px = Edges<Px>;
type UPx = Edges<UPx>;
fn into_px(self, scale: Fraction) -> Self::Px {
Edges {
left: self.left.into_px(scale),
top: self.top.into_px(scale),
right: self.right.into_px(scale),
bottom: self.bottom.into_px(scale),
}
}
fn from_px(px: Self::Px, scale: Fraction) -> Self {
Self {
left: U::from_px(px.left, scale),
top: U::from_px(px.top, scale),
right: U::from_px(px.right, scale),
bottom: U::from_px(px.bottom, scale),
}
}
fn into_upx(self, scale: Fraction) -> Self::UPx {
Edges {
left: self.left.into_upx(scale),
top: self.top.into_upx(scale),
right: self.right.into_upx(scale),
bottom: self.bottom.into_upx(scale),
}
}
fn from_upx(px: Self::UPx, scale: Fraction) -> Self {
Self {
left: U::from_upx(px.left, scale),
top: U::from_upx(px.top, scale),
right: U::from_upx(px.right, scale),
bottom: U::from_upx(px.bottom, scale),
}
}
fn into_lp(self, scale: Fraction) -> Self::Lp {
Edges {
left: self.left.into_lp(scale),
top: self.top.into_lp(scale),
right: self.right.into_lp(scale),
bottom: self.bottom.into_lp(scale),
}
}
fn from_lp(lp: Self::Lp, scale: Fraction) -> Self {
Self {
left: U::from_lp(lp.left, scale),
top: U::from_lp(lp.top, scale),
right: U::from_lp(lp.right, scale),
bottom: U::from_lp(lp.bottom, scale),
}
}
}
impl<U, R> Add for Edges<U>
where
U: Add<Output = R>,
{
type Output = Edges<R>;
fn add(self, rhs: Self) -> Self::Output {
Edges {
left: self.left + rhs.left,
top: self.top + rhs.top,
right: self.right + rhs.right,
bottom: self.bottom + rhs.bottom,
}
}
}
impl<U, R> AddAssign<Edges<R>> for Edges<U>
where
U: AddAssign<R>,
{
fn add_assign(&mut self, rhs: Edges<R>) {
self.left += rhs.left;
self.top += rhs.top;
self.right += rhs.right;
self.bottom += rhs.bottom;
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ThemePair {
pub light: Theme,
pub dark: Theme,
pub primary_fixed: FixedTheme,
pub secondary_fixed: FixedTheme,
pub tertiary_fixed: FixedTheme,
pub scrim: Color,
pub shadow: Color,
}
impl ThemePair {
#[must_use]
pub fn from_scheme(scheme: &ColorScheme) -> Self {
Self {
light: Theme::light_from_sources(
scheme.primary,
scheme.secondary,
scheme.tertiary,
scheme.error,
scheme.neutral,
scheme.neutral_variant,
),
dark: Theme::dark_from_sources(
scheme.primary,
scheme.secondary,
scheme.tertiary,
scheme.error,
scheme.neutral,
scheme.neutral_variant,
),
primary_fixed: FixedTheme::from_source(scheme.primary),
secondary_fixed: FixedTheme::from_source(scheme.secondary),
tertiary_fixed: FixedTheme::from_source(scheme.tertiary),
scrim: scheme.neutral.color(1),
shadow: scheme.neutral.color(1),
}
}
}
impl From<ColorScheme> for ThemePair {
fn from(scheme: ColorScheme) -> Self {
Self::from_scheme(&scheme)
}
}
impl Default for ThemePair {
fn default() -> Self {
Self::from(ColorScheme::default())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Theme {
pub primary: ColorTheme,
pub secondary: ColorTheme,
pub tertiary: ColorTheme,
pub error: ColorTheme,
pub surface: SurfaceTheme,
}
impl Theme {
#[must_use]
pub fn light_from_sources(
primary: ColorSource,
secondary: ColorSource,
tertiary: ColorSource,
error: ColorSource,
neutral: ColorSource,
neutral_variant: ColorSource,
) -> Self {
Self {
primary: ColorTheme::light_from_source(primary),
secondary: ColorTheme::light_from_source(secondary),
tertiary: ColorTheme::light_from_source(tertiary),
error: ColorTheme::light_from_source(error),
surface: SurfaceTheme::light_from_sources(neutral, neutral_variant),
}
}
#[must_use]
pub fn dark_from_sources(
primary: ColorSource,
secondary: ColorSource,
tertiary: ColorSource,
error: ColorSource,
neutral: ColorSource,
neutral_variant: ColorSource,
) -> Self {
Self {
primary: ColorTheme::dark_from_source(primary),
secondary: ColorTheme::dark_from_source(secondary),
tertiary: ColorTheme::dark_from_source(tertiary),
error: ColorTheme::dark_from_source(error),
surface: SurfaceTheme::dark_from_sources(neutral, neutral_variant),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SurfaceTheme {
pub color: Color,
pub dim_color: Color,
pub bright_color: Color,
pub lowest_container: Color,
pub low_container: Color,
pub container: Color,
pub high_container: Color,
pub highest_container: Color,
pub opaque_widget: Color,
pub on_color: Color,
pub on_color_variant: Color,
pub outline: Color,
pub outline_variant: Color,
}
impl SurfaceTheme {
#[must_use]
pub fn light_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self {
Self {
color: neutral.color(97),
dim_color: neutral.color(70),
bright_color: neutral.color(99),
opaque_widget: neutral_variant.color(75),
lowest_container: neutral.color(95),
low_container: neutral.color(92),
container: neutral.color(90),
high_container: neutral.color(85),
highest_container: neutral.color(80),
on_color: neutral.color(10),
on_color_variant: neutral_variant.color(30),
outline: neutral_variant.color(50),
outline_variant: neutral.color(60),
}
}
#[must_use]
pub fn dark_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self {
Self {
color: neutral.color(10),
dim_color: neutral.color(2),
bright_color: neutral.color(11),
opaque_widget: neutral_variant.color(40),
lowest_container: neutral.color(15),
low_container: neutral.color(20),
container: neutral.color(25),
high_container: neutral.color(30),
highest_container: neutral.color(35),
on_color: neutral.color(90),
on_color_variant: neutral_variant.color(70),
outline: neutral_variant.color(60),
outline_variant: neutral.color(50),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ColorTheme {
pub color: Color,
pub color_dim: Color,
pub color_bright: Color,
pub on_color: Color,
pub container: Color,
pub on_container: Color,
}
impl ColorTheme {
#[must_use]
pub fn light_from_source(source: ColorSource) -> Self {
Self {
color: source.color(50),
color_dim: source.color(30),
color_bright: source.color(60),
on_color: source.color(100),
container: source.color(90),
on_container: source.color(10),
}
}
#[must_use]
pub fn dark_from_source(source: ColorSource) -> Self {
Self {
color: source.color(60),
color_dim: source.color(50),
color_bright: source.color(70),
on_color: source.color(10),
container: source.color(30),
on_container: source.color(90),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FixedTheme {
pub color: Color,
pub dim_color: Color,
pub on_color: Color,
pub on_color_variant: Color,
}
impl FixedTheme {
#[must_use]
pub fn from_source(source: ColorSource) -> Self {
Self {
color: source.color(60),
dim_color: source.color(50),
on_color: source.color(10),
on_color_variant: source.color(40),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct ColorSource {
pub hue: OklabHue,
pub saturation: ZeroToOne,
}
impl PartialEq for ColorSource {
fn eq(&self, other: &Self) -> bool {
(self.hue.into_degrees() - other.hue.into_degrees()).abs() < f32::EPSILON
&& self.saturation == other.saturation
}
}
impl ColorSource {
#[must_use]
pub fn new(hue: impl Into<OklabHue>, saturation: impl Into<ZeroToOne>) -> Self {
Self {
hue: hue.into(),
saturation: saturation.into(),
}
}
#[must_use]
pub fn color(self, lightness: impl Lightness) -> Color {
let rgb: palette::Srgb =
Okhsl::new(self.hue, *self.saturation, *lightness.into_lightness()).into_color();
Color::new_f32(rgb.red, rgb.green, rgb.blue, 1.0)
}
#[must_use]
pub fn contrast_between(self, other: Self) -> ZeroToOne {
let saturation_delta = self.saturation.difference_between(other.saturation);
let average_saturation = ZeroToOne::new((*self.saturation + *other.saturation) / 2.);
let self_hue = self.hue.into_positive_degrees();
let other_hue = other.hue.into_positive_degrees();
let hue_delta = ZeroToOne::new(
if self_hue < other_hue {
let hue_delta_a = other_hue - self_hue;
let hue_delta_b = self_hue + 360. - other_hue;
hue_delta_a.min(hue_delta_b)
} else {
let hue_delta_a = self_hue - other_hue;
let hue_delta_b = other_hue + 360. - self_hue;
hue_delta_a.min(hue_delta_b)
} / 180.,
);
ZeroToOne::new((*saturation_delta + *hue_delta * *average_saturation) / 2.)
}
}
pub trait Lightness {
fn into_lightness(self) -> ZeroToOne;
}
impl Lightness for ZeroToOne {
fn into_lightness(self) -> ZeroToOne {
self
}
}
impl Lightness for f32 {
fn into_lightness(self) -> ZeroToOne {
ZeroToOne::new(self)
}
}
impl Lightness for u8 {
fn into_lightness(self) -> ZeroToOne {
ZeroToOne::new(f32::from(self) / 100.)
}
}
pub trait ColorExt: Copy {
fn into_hsla(self) -> Hsla;
fn source(self) -> ColorSource {
self.into_hsla().hsl.source
}
#[must_use]
fn lightness(self) -> ZeroToOne {
self.into_hsla().hsl.lightness
}
fn lighten_by(self, amount: ZeroToOne) -> Color {
let mut hsla = self.into_hsla();
hsla.hsl.lightness /= amount;
hsla.into()
}
fn darken_by(self, amount: ZeroToOne) -> Color {
let mut hsla = self.into_hsla();
hsla.hsl.lightness *= amount;
hsla.into()
}
fn contrast_between(
self,
check_source: ColorSource,
check_lightness: ZeroToOne,
check_alpha: ZeroToOne,
) -> ZeroToOne;
#[must_use]
fn most_contrasting(self, others: &[Self]) -> Self
where
Self: Copy;
}
impl ColorExt for Color {
fn into_hsla(self) -> Hsla {
let mut hsl: palette::Okhsl =
Srgb::new(self.red_f32(), self.green_f32(), self.blue_f32()).into_color();
if hsl.saturation.is_nan() && self.red() == 255 && self.green() == 255 && self.blue() == 255
{
hsl.saturation = 0.0;
}
Hsla {
hsl: Hsl {
source: ColorSource {
hue: hsl.hue,
saturation: ZeroToOne::new(hsl.saturation),
},
lightness: ZeroToOne::new(hsl.lightness),
},
alpha: ZeroToOne::new(self.alpha_f32()),
}
}
fn contrast_between(
self,
check_source: ColorSource,
check_lightness: ZeroToOne,
check_alpha: ZeroToOne,
) -> ZeroToOne {
let other = self.into_hsla();
let lightness_delta = other.hsl.lightness.difference_between(check_lightness);
let source_change = check_source.contrast_between(other.hsl.source);
let alpha_delta = check_alpha.difference_between(other.alpha);
ZeroToOne::new((*lightness_delta * 3. + *source_change + *alpha_delta) / 5.)
}
fn most_contrasting(self, others: &[Self]) -> Self
where
Self: Copy,
{
let check = self.into_hsla();
let mut others = others.iter().copied();
let mut most_contrasting = others.next().expect("at least one comparison");
let mut most_contrast_amount =
most_contrasting.contrast_between(check.hsl.source, check.hsl.lightness, check.alpha);
for other in others {
let contrast_amount =
other.contrast_between(check.hsl.source, check.hsl.lightness, check.alpha);
if contrast_amount > most_contrast_amount {
most_contrasting = other;
most_contrast_amount = contrast_amount;
}
}
most_contrasting
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Hsla {
pub hsl: Hsl,
pub alpha: ZeroToOne,
}
impl From<Color> for Hsla {
fn from(value: Color) -> Self {
value.into_hsla()
}
}
impl From<Hsla> for Color {
fn from(value: Hsla) -> Self {
Color::from(value.hsl).with_alpha_f32(*value.alpha)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Hsl {
pub source: ColorSource,
pub lightness: ZeroToOne,
}
impl From<Color> for Hsl {
fn from(value: Color) -> Self {
value.into_hsla().hsl
}
}
impl From<Hsl> for Color {
fn from(value: Hsl) -> Self {
value.source.color(value.lightness)
}
}
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VisualOrder {
pub horizontal: HorizontalOrder,
pub vertical: VerticalOrder,
}
impl VisualOrder {
#[must_use]
pub const fn right_to_left() -> Self {
Self {
horizontal: HorizontalOrder::RightToLeft,
vertical: VerticalOrder::TopToBottom,
}
}
#[must_use]
pub const fn left_to_right() -> Self {
Self {
horizontal: HorizontalOrder::LeftToRight,
vertical: VerticalOrder::TopToBottom,
}
}
#[must_use]
pub fn rev(self) -> Self {
Self {
horizontal: self.horizontal.rev(),
vertical: self.vertical.rev(),
}
}
}
impl From<VisualOrder> for Component {
fn from(value: VisualOrder) -> Self {
Self::VisualOrder(value)
}
}
impl TryFrom<Component> for VisualOrder {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::VisualOrder(order) => Ok(order),
other => Err(other),
}
}
}
impl RequireInvalidation for VisualOrder {
fn requires_invalidation(&self) -> bool {
true
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HorizontalOrder {
LeftToRight,
RightToLeft,
}
impl HorizontalOrder {
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::LeftToRight => Self::RightToLeft,
Self::RightToLeft => Self::LeftToRight,
}
}
pub(crate) fn sort_key(self, rect: &Rect<Px>) -> Px {
match self {
HorizontalOrder::LeftToRight => rect.origin.x,
HorizontalOrder::RightToLeft => -(rect.origin.x + rect.size.width),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum VerticalOrder {
TopToBottom,
BottomToTop,
}
impl VerticalOrder {
#[must_use]
pub fn rev(self) -> Self {
match self {
Self::TopToBottom => VerticalOrder::BottomToTop,
Self::BottomToTop => VerticalOrder::TopToBottom,
}
}
pub(crate) fn max_px(self) -> Px {
match self {
VerticalOrder::TopToBottom => Px::MAX,
VerticalOrder::BottomToTop => Px::MIN,
}
}
pub(crate) fn smallest_px(self, a: Px, b: Px) -> Px {
match self {
VerticalOrder::TopToBottom => a.min(b),
VerticalOrder::BottomToTop => b.max(a),
}
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FocusableWidgets {
#[default]
All,
OnlyTextual,
}
impl FocusableWidgets {
#[must_use]
pub const fn is_all(self) -> bool {
matches!(self, Self::All)
}
#[must_use]
pub const fn is_only_textual(self) -> bool {
matches!(self, Self::OnlyTextual)
}
}
impl From<FocusableWidgets> for Component {
fn from(value: FocusableWidgets) -> Self {
Self::FocusableWidgets(value)
}
}
impl TryFrom<Component> for FocusableWidgets {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::FocusableWidgets(focus) => Ok(focus),
other => Err(other),
}
}
}
impl RequireInvalidation for FocusableWidgets {
fn requires_invalidation(&self) -> bool {
false
}
}
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ContainerLevel {
#[default]
Lowest,
Low,
Mid,
High,
Highest,
}
impl ContainerLevel {
#[must_use]
pub const fn next(self) -> Option<Self> {
match self {
Self::Lowest => Some(Self::Low),
Self::Low => Some(Self::Mid),
Self::Mid => Some(Self::High),
Self::High => Some(Self::Highest),
Self::Highest => None,
}
}
}
impl From<ContainerLevel> for Component {
fn from(value: ContainerLevel) -> Self {
Self::ContainerLevel(value)
}
}
impl TryFrom<Component> for ContainerLevel {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::ContainerLevel(level) => Ok(level),
other => Err(other),
}
}
}
impl RequireInvalidation for ContainerLevel {
fn requires_invalidation(&self) -> bool {
true
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ColorSchemeBuilder {
pub primary: ColorSource,
pub secondary: Option<ColorSource>,
pub tertiary: Option<ColorSource>,
pub error: Option<ColorSource>,
pub neutral: Option<ColorSource>,
pub neutral_variant: Option<ColorSource>,
hue_shift: OklabHue,
}
impl ColorSchemeBuilder {
#[must_use]
pub fn new(primary: impl ProtoColor) -> Self {
Self {
primary: primary.into_source(ZeroToOne::new(0.8)),
secondary: None,
tertiary: None,
error: None,
neutral: None,
neutral_variant: None,
hue_shift: OklabHue::new(30.),
}
}
fn generate_secondary(&self) -> ColorSource {
ColorSource {
hue: self.primary.hue + self.hue_shift,
saturation: self.primary.saturation / 2.,
}
}
fn generate_tertiary(&self, secondary: ColorSource) -> ColorSource {
let hue_shift = (secondary.hue - self.primary.hue).into_degrees().signum()
* self.hue_shift.into_degrees();
ColorSource {
hue: self.primary.hue - hue_shift,
saturation: self.primary.saturation / 3.,
}
}
fn generate_error(&self, secondary: ColorSource, tertiary: ColorSource) -> ColorSource {
let mut error = ColorSource::new(30., self.primary.saturation);
let shift_degrees = self.hue_shift.into_positive_degrees().ceil().cast::<u32>();
let mut iters_left = (360 - (shift_degrees - 1)) / shift_degrees;
while iters_left > 0
&& [self.primary, secondary, tertiary]
.iter()
.any(|c| c.contrast_between(error) < 0.20)
{
error.hue -= self.hue_shift;
iters_left -= 1;
}
error
}
fn generate_neutral(&self) -> ColorSource {
ColorSource {
hue: self.primary.hue,
saturation: ZeroToOne::new(0.01),
}
}
fn generate_neutral_variant(&self) -> ColorSource {
ColorSource {
hue: self.primary.hue,
saturation: self.primary.saturation / 10.,
}
}
#[must_use]
pub fn secondary(mut self, secondary: impl ProtoColor) -> Self {
self.secondary = Some(secondary.into_source(self.primary.saturation / 2.));
self
}
#[must_use]
pub fn tertiary(mut self, tertiary: impl ProtoColor) -> Self {
self.tertiary = Some(tertiary.into_source(self.primary.saturation / 3.));
self
}
#[must_use]
pub fn error(mut self, error: impl ProtoColor) -> Self {
self.error = Some(error.into_source(self.primary.saturation));
self
}
#[must_use]
pub fn neutral(mut self, neutral: impl ProtoColor) -> Self {
self.neutral = Some(neutral.into_source(0.01));
self
}
#[must_use]
pub fn neutral_variant(mut self, neutral_variant: impl ProtoColor) -> Self {
self.neutral_variant = Some(neutral_variant.into_source(self.primary.saturation / 10.));
self
}
#[must_use]
pub fn hue_shift(mut self, hue_shift: impl Into<OklabHue>) -> Self {
self.hue_shift = hue_shift.into();
self
}
#[must_use]
pub fn build(self) -> ColorScheme {
let secondary = self.secondary.unwrap_or_else(|| self.generate_secondary());
let tertiary = self
.tertiary
.unwrap_or_else(|| self.generate_tertiary(secondary));
ColorScheme {
primary: self.primary,
secondary,
tertiary,
error: self
.error
.unwrap_or_else(|| self.generate_error(secondary, tertiary)),
neutral: self.neutral.unwrap_or_else(|| self.generate_neutral()),
neutral_variant: self
.neutral_variant
.unwrap_or_else(|| self.generate_neutral_variant()),
}
}
}
pub trait ProtoColor: Sized {
#[must_use]
fn hue(&self) -> OklabHue;
#[must_use]
fn saturation(&self) -> Option<ZeroToOne>;
#[must_use]
fn into_source(self, saturation_if_not_provided: impl Into<ZeroToOne>) -> ColorSource {
let saturation = self
.saturation()
.unwrap_or_else(|| saturation_if_not_provided.into());
ColorSource::new(self.hue(), saturation)
}
}
impl ProtoColor for &'_ ColorSource {
fn hue(&self) -> OklabHue {
self.hue
}
fn saturation(&self) -> Option<ZeroToOne> {
Some(self.saturation)
}
}
impl ProtoColor for f32 {
fn hue(&self) -> OklabHue {
(*self).into()
}
fn saturation(&self) -> Option<ZeroToOne> {
None
}
}
impl ProtoColor for OklabHue {
fn hue(&self) -> OklabHue {
*self
}
fn saturation(&self) -> Option<ZeroToOne> {
None
}
}
impl ProtoColor for ColorSource {
fn hue(&self) -> OklabHue {
self.hue
}
fn saturation(&self) -> Option<ZeroToOne> {
Some(self.saturation)
}
}
impl<Hue, Saturation> ProtoColor for (Hue, Saturation)
where
Hue: Into<OklabHue> + Copy,
Saturation: Into<ZeroToOne> + Copy,
{
fn hue(&self) -> OklabHue {
self.0.into()
}
fn saturation(&self) -> Option<ZeroToOne> {
Some(self.1.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ColorScheme {
pub primary: ColorSource,
pub secondary: ColorSource,
pub tertiary: ColorSource,
pub error: ColorSource,
pub neutral: ColorSource,
pub neutral_variant: ColorSource,
}
impl ColorScheme {
#[must_use]
pub fn from_primary(primary: impl ProtoColor) -> Self {
ColorSchemeBuilder::new(primary).build()
}
}
impl Default for ColorScheme {
fn default() -> Self {
Self::from_primary(290.)
}
}
impl From<ColorSource> for ColorScheme {
fn from(primary: ColorSource) -> Self {
ColorScheme::from_primary(primary)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FontFamilyList(Arc<Vec<FamilyOwned>>);
impl Default for FontFamilyList {
fn default() -> Self {
static DEFAULT: Lazy<FontFamilyList> = Lazy::new(|| FontFamilyList::from(vec![]));
DEFAULT.clone()
}
}
impl FontFamilyList {
pub fn push(&mut self, family: FamilyOwned) {
Arc::make_mut(&mut self.0).push(family);
}
}
impl Deref for FontFamilyList {
type Target = [FamilyOwned];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromIterator<FamilyOwned> for FontFamilyList {
fn from_iter<T: IntoIterator<Item = FamilyOwned>>(iter: T) -> Self {
Self(Arc::new(iter.into_iter().collect()))
}
}
impl From<FamilyOwned> for FontFamilyList {
fn from(value: FamilyOwned) -> Self {
Self::from(vec![value])
}
}
impl From<Vec<FamilyOwned>> for FontFamilyList {
fn from(value: Vec<FamilyOwned>) -> Self {
Self(Arc::new(value))
}
}
impl IntoValue<FontFamilyList> for FamilyOwned {
fn into_value(self) -> Value<FontFamilyList> {
FontFamilyList::from(self).into_value()
}
}
impl IntoValue<FontFamilyList> for Vec<FamilyOwned> {
fn into_value(self) -> Value<FontFamilyList> {
FontFamilyList::from(self).into_value()
}
}
impl From<FontFamilyList> for Component {
fn from(list: FontFamilyList) -> Self {
Component::custom(list)
}
}
impl RequireInvalidation for FontFamilyList {
fn requires_invalidation(&self) -> bool {
true
}
}
impl TryFrom<Component> for FontFamilyList {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Custom(custom) => custom
.downcast()
.cloned()
.ok_or_else(|| Component::Custom(custom)),
other => Err(other),
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum HorizontalAlign {
#[default]
Left,
Center,
Right,
}
impl HorizontalAlign {
pub fn alignment_offset<Unit>(self, measured: Unit, available_space: Unit) -> Unit
where
Unit: Sub<Output = Unit> + Mul<Output = Unit> + UnscaledUnit + Zero,
Unit::Representation: CastFrom<i32>,
{
match self {
Self::Left => Unit::ZERO,
Self::Center => (available_space - measured) * Unit::from_unscaled(2.cast_into()),
Self::Right => available_space - measured,
}
}
}
impl From<HorizontalAlign> for Component {
fn from(value: HorizontalAlign) -> Self {
Self::HorizontalAlign(value)
}
}
impl TryFrom<Component> for HorizontalAlign {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::HorizontalAlign(level) => Ok(level),
other => Err(other),
}
}
}
impl RequireInvalidation for HorizontalAlign {
fn requires_invalidation(&self) -> bool {
true
}
}
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
pub enum VerticalAlign {
#[default] Top,
Center,
Bottom,
}
impl VerticalAlign {
pub fn align<Unit>(self, measured: Unit, available_space: Unit) -> Unit
where
Unit: Sub<Output = Unit> + Mul<Output = Unit> + UnscaledUnit + Zero,
Unit::Representation: CastFrom<i32>,
{
match self {
Self::Top => Unit::ZERO,
Self::Center => (available_space - measured) * Unit::from_unscaled(2.cast_into()),
Self::Bottom => available_space - measured,
}
}
}
impl From<VerticalAlign> for Component {
fn from(value: VerticalAlign) -> Self {
Self::VerticalAlign(value)
}
}
impl TryFrom<Component> for VerticalAlign {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::VerticalAlign(level) => Ok(level),
other => Err(other),
}
}
}
impl RequireInvalidation for VerticalAlign {
fn requires_invalidation(&self) -> bool {
true
}
}
#[derive(Clone)]
pub struct DynamicComponent(Arc<dyn DynamicComponentResolver>);
impl Debug for DynamicComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("DynamicComponent").finish()
}
}
impl PartialEq for DynamicComponent {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
pub trait DynamicComponentResolver: Send + Sync + 'static {
fn resolve_component(&self, context: &WidgetContext<'_>) -> Option<Component>;
}
struct DynamicFunctionWrapper<F>(F);
impl<T> DynamicComponentResolver for DynamicFunctionWrapper<T>
where
T: for<'a, 'context> Fn(&'a WidgetContext<'context>) -> Option<Component>
+ Send
+ Sync
+ 'static,
{
fn resolve_component(&self, context: &WidgetContext<'_>) -> Option<Component> {
self.0(context)
}
}
impl<T> DynamicComponentResolver for T
where
T: ComponentDefinition + Clone + Send + Sync + 'static,
{
fn resolve_component(&self, context: &WidgetContext<'_>) -> Option<Component> {
Some(context.get(self).into_component())
}
}
impl<T> From<T> for DynamicComponent
where
T: DynamicComponentResolver,
{
fn from(resolve: T) -> Self {
Self(Arc::new(resolve))
}
}
impl DynamicComponent {
#[must_use]
pub fn new<Func>(resolve: Func) -> Self
where
Func: for<'a, 'context> Fn(&'a WidgetContext<'context>) -> Option<Component>
+ Send
+ Sync
+ 'static,
{
Self::from(DynamicFunctionWrapper(resolve))
}
#[must_use]
pub fn resolve(&self, context: &WidgetContext<'_>) -> Option<Component> {
self.0.resolve_component(context)
}
}