//! Contains code related to the styling of widgets use std::ops::Add; pub use super::units::{KPositionType, LayoutType, Units}; use super::BoxShadow; use bevy::prelude::Color; use bevy::prelude::Component; use bevy::prelude::ReflectComponent; use bevy::prelude::Vec2; use bevy::prelude::Vec3; use bevy::prelude::Vec4; use bevy::reflect::FromReflect; use bevy::reflect::Reflect; use bevy::window::CursorIcon; use crate::cursor::PointerEvents; use crate::render::material::MaterialHandle; use super::AsRefOption; pub use super::Corner; pub use super::Edge; use super::RenderCommand; /// Just a wrapper around bevy's CursorIcon so we can define a default. #[derive(Debug, Reflect, Clone, PartialEq, Eq)] pub struct KCursorIcon(#[reflect(ignore)] pub CursorIcon); impl Default for KCursorIcon { fn default() -> Self { Self(CursorIcon::Default) } } /// The base container of all style properties /// /// The default value for this enum is [`StyleProp::Unset`]. #[derive(Debug, Reflect, Clone, PartialEq, Eq)] pub enum StyleProp<T: Default + Clone + Reflect + FromReflect> { /// This prop is unset, meaning its actual value is not determined until style resolution, /// wherein it will be set to the property's default value. /// /// When [applying](Style::apply) styles, only style properties of this type may be /// overwritten. Unset, /// Like [StyleProp::Unset], properties of this type wait until style resolution for their /// actual values to be determined, wherein it will be set to the property's default value. Default, /// Properties of this type inherit their value from their parent (determined at style resolution). Inherit, /// Set a specific value for this property Value(T), } impl<T> Default for StyleProp<T> where T: Default + Clone + Reflect + FromReflect, { fn default() -> Self { Self::Unset } } impl<T> StyleProp<T> where T: Default + Clone + Reflect + FromReflect, { /// Resolves this style property into a concrete value. /// /// # Panics /// /// Panics if the style property has been set to [`StyleProp::Inherit`] since it needs to /// first be assigned the value of its parent before being resolved. pub fn resolve(&self) -> T { match self { StyleProp::Unset => T::default(), StyleProp::Default => T::default(), StyleProp::Value(value) => value.clone(), StyleProp::Inherit => panic!("All styles should be merged before resolving!"), } } /// Returns the concrete value of this style property or the provided default. /// /// If this style property is not [`StyleProp::Value`], then the provided default /// will be returned. pub fn resolve_or(&self, default: T) -> T { if let Self::Value(value) = self { value.clone() } else { default } } /// Returns the concrete value of this style property as an Option<T> or None. /// /// If this style property is not [`StyleProp::Value`], then the none /// will be returned. pub fn resolve_as_option(&self) -> Option<T> { if let Self::Value(value) = self { Some(value.clone()) } else { None } } /// Returns the concrete value of this style property or computes it from a closure. /// /// If this style property is not [`StyleProp::Value`], then the return value will be /// computed from the provided closure. pub fn resolve_or_else<F: FnOnce() -> T>(&self, f: F) -> T { if let Self::Value(value) = self { value.clone() } else { f() } } /// Returns the concrete value of this style property or the default value. /// /// This is similar to the standard [`resolve`](Self::resolve) method, however, it /// will _not_ panic on a [`StyleProp::Inherit`]. pub fn resolve_or_default(&self) -> T { if let Self::Value(value) = self { value.clone() } else { T::default() } } /// Returns the first property to not be [unset](StyleProp::Unset) /// /// If none found, returns [`StyleProp::Unset`] pub fn select<'a>(props: &'_ [&'a StyleProp<T>]) -> &'a Self { for prop in props { if !matches!(prop, StyleProp::Unset) { return prop; } } &StyleProp::Unset } } impl<T: Default + Clone + Reflect + FromReflect> From<T> for StyleProp<T> { fn from(value: T) -> Self { StyleProp::Value(value) } } /// A macro that simply wraps the definition struct of [`Style`], allowing /// some methods to be automatically defined. Otherwise, there would be a _lot_ of /// copying and pasting, resulting in fragile code. macro_rules! define_styles { ( // #[derive(...)] // #[cfg(...)] $(#[$attr: meta])* // pub struct Styles { ... } $vis: vis struct $name: ident { // pub field_1: StyleProp<f32>, // #[cfg(...)] // field_2: StyleProp<Color>, $( $(#[$field_attr: meta])* $field_vis: vis $field: ident : $field_type: ty ),* // (Optional trailing comma) $(,)? } ) => { $(#[$attr])* $vis struct $name { $( $(#[$field_attr])* $field_vis $field: $field_type ),* } impl $name { /// Returns a `Style` object where all fields are set to [`StyleProp::Default`] /// /// This should only be used when default properties are required or desired. Otherwise, you /// may be better off using `Style::default` (which sets all properties to [`StyleProp::Unset`]). pub fn new_default() -> Self { Self { $($field: StyleProp::Default),* } } /// If any field is set to [`StyleProp::Inherit`], its value will be taken from `other` pub fn inherit(&mut self, other: &Self) { $( if matches!(self.$field, StyleProp::Inherit) { self.$field = other.$field.clone(); } )* } /// Applies a `Style` over this one /// /// Values from `other` are applied to any field in this one that is marked as [`StyleProp::Unset`] pub fn apply<T: AsRefOption<KStyle>>(&mut self, other: T) { if let Some(other) = other.as_ref_option() { $( if matches!(self.$field, StyleProp::Unset) { self.$field = other.$field.clone(); } )* } } /// Applies the given style and returns the updated style /// /// This is simply a builder-like wrapper around the [`Style::apply`] method. pub fn with_style<T: AsRefOption<KStyle>>(mut self, other: T) -> Self { self.apply(other); self } } }; } define_styles! { /// A struct used to define the look of a widget /// /// # Example /// /// All fields are `pub`, so you can simply define your styles like: /// /// ``` /// # use kayak_core::styles::{Style, StyleProp, Units}; /// let style = Style { /// width: StyleProp::Value(Units::Pixels(100.0)), /// // Or using `into()` to convert to `StyleProp` /// // width: Units::Pixels(100.0).into(), /// ..Default::default() /// }; /// ``` /// /// You can also create styles using other styles in a builder-like syntax: /// /// ``` /// # use kayak_core::styles::{Style, StyleProp, Units}; /// let style_a = Style { /// width: Units::Pixels(100.0).into(), /// ..Default::default() /// }; /// let style_b = Style { /// height: Units::Pixels(100.0).into(), /// ..Default::default() /// }; /// /// let style = Style::default() // <- Initializes all fields as `StyleProp::Unset` /// // Applied first (sets any `StyleProp::Unset` fields) /// .with_style(&style_a) /// // Applied second (sets any remaining `StyleProp::Unset` fields) /// .with_style(&style_b); /// ``` #[derive(Component, Reflect, Debug, Default, Clone, PartialEq)] #[reflect(Component)] pub struct KStyle { /// The background color of this widget /// /// Only applies to widgets marked [`RenderCommand::Quad`] pub background_color : StyleProp<Color>, /// The color of the border around this widget /// /// Currently, this controls all border sides. /// /// Only applies to widgets marked [`RenderCommand::Quad`] pub border_color: StyleProp<Color>, /// The radius of the corners (in pixels) /// /// The order is (Top, Right, Bottom, Left). /// /// Only applies to widgets marked [`RenderCommand::Quad`] and [`RenderCommand::Image`] pub border_radius: StyleProp<Corner<f32>>, /// The widths of the borders (in pixels) /// /// The order is (Top, Right, Bottom, Left). /// /// Only applies to widgets marked [`RenderCommand::Quad`] pub border: StyleProp<Edge<f32>>, /// The distance between the bottom edge of this widget and the bottom edge of its containing widget pub bottom: StyleProp<Units>, /// The text color for this widget /// /// This property defaults to [`StyleProp::Inherit`] meaning that setting this field to some value will /// cause all descendents to receive that value, up to the next set value. /// /// Only applies to widgets marked [`RenderCommand::Text`] pub color: StyleProp<Color>, /// The spacing between child widgets along the horizontal axis pub col_between: StyleProp<Units>, /// The cursor icon to display when hovering this widget #[reflect(ignore)] pub cursor: StyleProp<KCursorIcon>, /// The font name for this widget /// /// Only applies to [`RenderCommand::Text`] pub font: StyleProp<String>, /// The font size for this widget, in pixels /// /// Only applies to [`RenderCommand::Text`] pub font_size: StyleProp<f32>, /// The height of this widget pub height: StyleProp<Units>, /// The layout method for children of this widget pub layout_type: StyleProp<LayoutType>, /// The distance between the left edge of this widget and the left edge of its containing widget pub left: StyleProp<Units>, /// The line height for this widget, in pixels /// /// Only applies to [`RenderCommand::Text`] pub line_height: StyleProp<f32>, /// The maximum height of this widget pub max_height: StyleProp<Units>, /// The maximum width of this widget pub max_width: StyleProp<Units>, /// The minimum height of this widget pub min_height: StyleProp<Units>, /// The minimum width of this widget pub min_width: StyleProp<Units>, /// The positional offset from this widget's default position /// /// This property has lower precedence than its more specific counterparts /// ([`top`](Self::top), [`right`](Self::right), [`bottom`](Self::bottom), and [`left`](Self::left)), /// allowing it to be overridden. /// /// For widgets with a [`position_type`](Self::position_type) of [`PositionType`](PositionType::ParentDirected) /// this acts like margin around the widget. For [`PositionType`](PositionType::SelfDirected) this /// acts as the actual position from the parent. pub offset: StyleProp<Edge<Units>>, /// The inner padding between the edges of this widget and its children /// /// This property has lower precedence than its more specific counterparts /// ([`padding_top`](Self::padding_top), [`padding_right`](Self::padding_right), /// [`padding_bottom`](Self::padding_bottom), and [`padding_left`](Self::padding_left)), allowing it /// to be overridden. /// /// A child with their own padding properties set to anything other than [`Units::Auto`] will /// override the padding set by this widget. pub padding: StyleProp<Edge<Units>>, /// The inner padding between the bottom edge of this widget and its children /// /// A child with their own `bottom` property set to anything other than `Units::Auto` will /// override the padding set by this widget pub padding_bottom: StyleProp<Units>, /// The inner padding between the left edge of this widget and its children /// /// A child with their own `left` property set to anything other than `Units::Auto` will /// override the padding set by this widget pub padding_left: StyleProp<Units>, /// The inner padding between the right edge of this widget and its children /// /// A child with their own `right` property set to anything other than `Units::Auto` will /// override the padding set by this widget pub padding_right: StyleProp<Units>, /// The inner padding between the top edge of this widget and its children /// /// A child with their own `top` property set to anything other than `Units::Auto` will /// override the padding set by this widget pub padding_top: StyleProp<Units>, /// Controls how the pointer interacts with the widget /// /// This can be used to block pointer events on itself and/or its children if needed, allowing /// the event to "pass through" to widgets below. pub pointer_events: StyleProp<PointerEvents>, /// The position type of the widget relative to its parent pub position_type: StyleProp<KPositionType>, /// The render method for this widget /// /// This controls what actually gets rendered and how it's rendered. pub render_command: StyleProp<RenderCommand>, /// The distance between the right edge of this widget and the right edge of its containing widget pub right: StyleProp<Units>, /// The spacing between child widgets along the vertical axis pub row_between: StyleProp<Units>, /// The distance between the top edge of this widget and the top edge of its containing widget pub top: StyleProp<Units>, /// The width of this widget pub width: StyleProp<Units>, /// The z-index relative to it's parent. pub z_index: StyleProp<i32>, /// The list of rows when using the grid layout /// /// This is specified in the parent widget and the children have to specify their `row_index`. pub grid_rows: StyleProp<Vec<Units>>, /// The list of columns when using the grid layout /// /// This is specified in the parent widget and the children have to specify their `col_index`. pub grid_cols: StyleProp<Vec<Units>>, /// The row index of this widget when using the grid layout /// /// This references the `grid_rows` property of the parent widget. pub row_index: StyleProp<usize>, /// The column index of this widget when using the grid layout /// /// This references the `grid_cols` property of the parent widget. pub col_index: StyleProp<usize>, /// The number rows that this widget spans when using the grid layout /// /// Specified in the child widget. pub row_span: StyleProp<usize>, /// The number columns that this widget spans when using the grid layout /// /// Specified in the child widget. pub col_span: StyleProp<usize>, /// The opacity of the widget and it's children /// /// This is also known as grouped opacity /// WARNING! This splits the widget and it's children into a new render pass. So use it sparingly!!! pub opacity: StyleProp<f32>, /// Box shadow /// Currently only applied to quads pub box_shadow: StyleProp<Vec<BoxShadow>>, /// Overrides the default renderer with a custom material #[reflect(ignore)] pub material: StyleProp<MaterialHandle>, } } impl KStyle { /// Returns a `Style` object where all fields are set to their own initial values /// /// This is the actual "default" to apply over any field marked as [`StyleProp::Unset`] before /// resolving the style. pub fn initial() -> Self { Self { background_color: StyleProp::Default, border: StyleProp::Default, border_color: StyleProp::Default, border_radius: StyleProp::Default, bottom: StyleProp::Default, color: StyleProp::Inherit, cursor: StyleProp::Inherit, col_between: StyleProp::Default, font: StyleProp::Inherit, font_size: StyleProp::Inherit, height: StyleProp::Default, layout_type: StyleProp::Default, line_height: StyleProp::Inherit, left: StyleProp::Default, max_height: StyleProp::Default, max_width: StyleProp::Default, min_height: StyleProp::Default, min_width: StyleProp::Default, offset: StyleProp::Default, padding: StyleProp::Default, padding_bottom: StyleProp::Default, padding_left: StyleProp::Default, padding_right: StyleProp::Default, padding_top: StyleProp::Default, pointer_events: StyleProp::Default, position_type: StyleProp::Default, render_command: StyleProp::Value(RenderCommand::Layout), right: StyleProp::Default, row_between: StyleProp::Default, top: StyleProp::Default, width: StyleProp::Default, z_index: StyleProp::Default, grid_rows: StyleProp::Default, grid_cols: StyleProp::Default, row_index: StyleProp::Default, col_index: StyleProp::Default, row_span: StyleProp::Default, col_span: StyleProp::Default, opacity: StyleProp::Value(1.0), box_shadow: StyleProp::Default, material: StyleProp::Default, } } pub fn lerp(&self, b: &Self, x: f32) -> Self { let mut new_styles = self.clone(); // Default to A styles. new_styles.background_color = if let StyleProp::Value(color_a) = self.background_color { if let StyleProp::Value(color_b) = b.background_color { StyleProp::Value(hsv_lerp(&color_a, &color_b, x)) } else { StyleProp::Value(color_a) } } else { self.background_color.clone() }; new_styles.border = if let StyleProp::Value(border_a) = self.border { if let StyleProp::Value(border_b) = b.border { StyleProp::Value(Edge::new( lerp(border_a.top, border_b.top, x), lerp(border_a.right, border_b.right, x), lerp(border_a.bottom, border_b.bottom, x), lerp(border_a.left, border_b.left, x), )) } else { StyleProp::Value(border_a) } } else { self.border.clone() }; new_styles.border_color = if let StyleProp::Value(color_a) = self.border_color { if let StyleProp::Value(color_b) = b.border_color { StyleProp::Value(lerp_lch(color_a, color_b, x)) } else { StyleProp::Value(color_a) } } else { self.border_color.clone() }; new_styles.border_radius = if let StyleProp::Value(border_a) = self.border_radius { if let StyleProp::Value(border_b) = b.border_radius { StyleProp::Value(Corner::new( lerp(border_a.top_left, border_b.top_left, x), lerp(border_a.top_right, border_b.top_right, x), lerp(border_a.bottom_left, border_b.bottom_left, x), lerp(border_a.bottom_right, border_b.bottom_right, x), )) } else { StyleProp::Value(border_a) } } else { self.border_radius.clone() }; new_styles.bottom = lerp_units(&self.bottom, &b.bottom, x); new_styles.color = if let StyleProp::Value(color_a) = self.color { if let StyleProp::Value(color_b) = b.color { StyleProp::Value(hsv_lerp(&color_a, &color_b, x)) } else { StyleProp::Value(color_a) } } else { self.color.clone() }; new_styles.font_size = lerp_f32(&new_styles.font_size, &b.font_size, x); new_styles.height = lerp_units(&self.height, &b.height, x); new_styles.line_height = lerp_f32(&new_styles.line_height, &b.line_height, x); new_styles.left = lerp_units(&self.left, &b.left, x); new_styles.max_height = lerp_units(&self.max_height, &b.max_height, x); new_styles.max_width = lerp_units(&self.max_width, &b.max_width, x); new_styles.min_height = lerp_units(&self.min_height, &b.min_height, x); new_styles.min_width = lerp_units(&self.min_width, &b.min_width, x); new_styles.offset = if let StyleProp::Value(edge_a) = self.offset { if let StyleProp::Value(edge_b) = b.offset { StyleProp::Value(Edge::new( get_value(lerp_units( &StyleProp::Value(edge_a.top), &StyleProp::Value(edge_b.top), x, )), get_value(lerp_units( &StyleProp::Value(edge_a.right), &StyleProp::Value(edge_b.right), x, )), get_value(lerp_units( &StyleProp::Value(edge_a.bottom), &StyleProp::Value(edge_b.bottom), x, )), get_value(lerp_units( &StyleProp::Value(edge_a.left), &StyleProp::Value(edge_b.left), x, )), )) } else { StyleProp::Value(edge_a) } } else { self.offset.clone() }; new_styles.padding = if let StyleProp::Value(edge_a) = self.padding { if let StyleProp::Value(edge_b) = b.padding { StyleProp::Value(Edge::new( get_value(lerp_units( &StyleProp::Value(edge_a.top), &StyleProp::Value(edge_b.top), x, )), get_value(lerp_units( &StyleProp::Value(edge_a.right), &StyleProp::Value(edge_b.right), x, )), get_value(lerp_units( &StyleProp::Value(edge_a.bottom), &StyleProp::Value(edge_b.bottom), x, )), get_value(lerp_units( &StyleProp::Value(edge_a.left), &StyleProp::Value(edge_b.left), x, )), )) } else { StyleProp::Value(edge_a) } } else { self.padding.clone() }; new_styles.padding_bottom = lerp_units(&self.padding_bottom, &b.padding_bottom, x); new_styles.padding_left = lerp_units(&self.padding_left, &b.padding_left, x); new_styles.padding_right = lerp_units(&self.padding_right, &b.padding_right, x); new_styles.padding_top = lerp_units(&self.padding_top, &b.padding_top, x); new_styles.right = lerp_units(&self.right, &b.right, x); new_styles.top = lerp_units(&self.top, &b.top, x); new_styles.width = lerp_units(&self.width, &b.width, x); new_styles.opacity = lerp_f32(&self.opacity, &b.opacity, x); new_styles } } fn get_value<T: Default + Clone + FromReflect>(a: StyleProp<T>) -> T { match a { StyleProp::Value(v) => v, _ => { panic!("Unexpected lack of value!"); } } } fn lerp_f32(prop_a: &StyleProp<f32>, prop_b: &StyleProp<f32>, x: f32) -> StyleProp<f32> { if let StyleProp::Value(f_a) = prop_a { if let StyleProp::Value(f_b) = prop_b { StyleProp::Value(lerp(*f_a, *f_b, x)) } else { StyleProp::Value(*f_a) } } else { prop_a.clone() } } fn lerp_units(prop_a: &StyleProp<Units>, prop_b: &StyleProp<Units>, x: f32) -> StyleProp<Units> { if let StyleProp::Value(unit_a) = prop_a { if let StyleProp::Value(unit_b) = prop_b { StyleProp::Value(match (unit_a, unit_b) { (Units::Pixels(a), Units::Pixels(b)) => Units::Pixels(lerp(*a, *b, x)), (Units::Percentage(a), Units::Percentage(b)) => Units::Percentage(lerp(*a, *b, x)), (Units::Stretch(a), Units::Stretch(b)) => Units::Stretch(lerp(*a, *b, x)), _ => { bevy::prelude::warn!( "Cannot lerp between non-matching units! Unit_A: {:?}, Unit_B: {:?}", unit_a, unit_b ); *unit_a } }) } else { StyleProp::Value(*unit_a) } } else { prop_a.clone() } } fn lerp_ang(a: f32, b: f32, x: f32) -> f32 { let ang = ((((a - b) % std::f32::consts::TAU) + std::f32::consts::PI * 3.) % std::f32::consts::TAU) - std::f32::consts::PI; ang * x + b } /// Linear interpolation between two colors in Lch space fn lerp_lch(a: Color, b: Color, x: f32) -> Color { let [a_r, a_g, a_b, a_a] = a.as_lcha_f32(); let [b_r, b_g, b_b, b_a] = b.as_lcha_f32(); let hue = lerp_ang(a_b, b_b, x); let a_xy = Vec2::new(a_r, a_g); let b_xy = Vec2::new(b_r, b_g); let xy = b_xy.lerp(a_xy, x); let alpha = lerp(a_a, b_a, x); Color::Lcha { lightness: xy.x, chroma: xy.y, hue, alpha, } .as_rgba() } fn rgb_to_hsv(from: &Color) -> Vec3 { // xyz <-> hsv let r = from.r(); let g = from.g(); let b = from.b(); let mut res = Vec3::ZERO; let min = r.min(g).min(b); let max = r.max(g).max(b); // Value res.z = max; let delta = max - min; // calc Saturation if max != 0.0 { res.y = delta / max; } else { res.x = -1.0; res.y = 0.0; return res; } // calc Hue if r == max { // between Yellow & Magenta res.x = (g - b) / delta; } else if g == max { // cyan to yellow res.x = 2.0 + (b - r) / delta; } else { // b == max // Megnta to cyan res.x = 4.0 + (r - g) / delta; } res.x *= 60.0; // Convert to degrees if res.x < 0.0 { res.x += 360.0; // Unwrap angle in case of negative } res } fn hsv_to_rgb(from: &Vec3) -> Color { let h = from.x; let s = from.y; let v = from.z; // Calc base values let c = s * v; let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs()); let m = v - c; let mut res = Vec4::new(0.0, 0.0, 0.0, 1.0); if (0.0..60.0).contains(&h) { res.x = c; res.y = x; res.z = 0.0; } else if (60.0..120.0).contains(&h) { res.x = x; res.y = c; res.z = 0.0; } else if (120.0..180.0).contains(&h) { res.x = 0.0; res.y = c; res.z = x; } else if (180.0..240.0).contains(&h) { res.x = 0.0; res.y = x; res.z = c; } else if (240.0..300.0).contains(&h) { res.x = x; res.y = 0.0; res.z = c; } else { res.x = c; res.y = 0.0; res.z = x; } res += Vec4::new(m, m, m, 0.0); Color::rgba_from_array(res) } fn hsv_lerp(from: &Color, to: &Color, amount: f32) -> Color { let from_a = from.a(); let to_a = to.a(); let from = rgb_to_hsv(from); let to = rgb_to_hsv(to); let mut res = from.lerp(to, amount); if from.x < 0.0 { res.x = to.x; } let mut color = hsv_to_rgb(&res); color.set_a(lerp(from_a, to_a, amount).clamp(0.0, 1.0)); color } pub(crate) fn lerp(a: f32, b: f32, x: f32) -> f32 { a * (1.0 - x) + b * x } impl Add for KStyle { type Output = KStyle; /// Defines the `+` operator for [`Style`]. This is a convenience wrapper of the `self.with_style()` method and useful for concatenating many small `Style` variables. /// Similar to `with_style()` In a `StyleA + StyleB` operation, values from `StyleB` are applied to any field of StyleA that are marked as [`StyleProp::Unset`]. /// /// Note: since the changes are applied only to unset fields, addition is *not* commutative. This means StyleA + StyleB != StyleB + StyleA for most cases. fn add(self, other: KStyle) -> KStyle { self.with_style(other) } } #[cfg(test)] mod tests { use super::{Edge, KStyle, StyleProp, Units}; #[test] fn styles_should_equal() { let mut a = KStyle::default(); let mut b = KStyle::default(); assert_eq!(a, b); a.height = StyleProp::Default; b.height = StyleProp::Inherit; assert_ne!(a, b); } #[test] fn style_should_inherit_property() { let border = Edge::new(1.0, 2.0, 3.0, 4.0); let parent = KStyle { border: StyleProp::Value(border), ..Default::default() }; let mut child = KStyle { border: StyleProp::Inherit, ..Default::default() }; child.inherit(&parent); assert_eq!(border, child.border.resolve()); } #[test] #[should_panic] fn style_should_panic_on_resolve_inherit_property() { let style = KStyle { color: StyleProp::Inherit, ..Default::default() }; let _ = style.color.resolve(); } #[test] fn style_should_apply_styles_on_unset_property() { let mut base_style = KStyle::default(); let other_style = KStyle { width: StyleProp::Value(Units::Pixels(123.0)), ..Default::default() }; assert_ne!(base_style.width, other_style.width); base_style.apply(&other_style); assert_eq!(base_style.width, other_style.width); } #[test] fn style_should_not_apply_styles_on_non_unset_property() { let mut base_style = KStyle { width: StyleProp::Default, ..Default::default() }; let other_style = KStyle { width: StyleProp::Value(Units::Pixels(123.0)), ..Default::default() }; assert_ne!(base_style.width, other_style.width); base_style.apply(&other_style); assert_ne!(base_style.width, other_style.width); } #[test] fn style_should_apply_option_style() { let mut base_style = KStyle::default(); let other_style = Some(KStyle { width: StyleProp::Value(Units::Pixels(123.0)), ..Default::default() }); assert_ne!(base_style.width, other_style.as_ref().unwrap().width); base_style.apply(&other_style); assert_eq!(base_style.width, other_style.as_ref().unwrap().width); base_style.apply(None); assert_eq!(base_style.width, other_style.as_ref().unwrap().width); } #[test] fn style_should_not_apply_none() { let expected = KStyle::default(); let mut base_style = expected.clone(); assert_eq!(expected, base_style); base_style.apply(None); assert_eq!(expected, base_style); } #[test] fn styles_should_be_buildable() { let expected_left = StyleProp::Default; let expected_width = StyleProp::Value(Units::Stretch(1.0)); let expected_height = StyleProp::Inherit; let expected = KStyle { left: expected_left.clone(), width: expected_width.clone(), height: expected_height.clone(), ..Default::default() }; let style = KStyle::default() // Pass ownership .with_style(KStyle { height: expected_height, ..Default::default() }) // Pass ownership of option .with_style(Some(KStyle { left: expected_left, ..Default::default() })) // Pass reference .with_style(&KStyle { width: expected_width, ..Default::default() }); assert_eq!(expected, style); } #[test] fn styles_should_add() { let expected_left = StyleProp::Default; let expected_width = StyleProp::Value(Units::Stretch(1.0)); let expected_height = StyleProp::Inherit; let expected = KStyle { left: expected_left.clone(), width: expected_width.clone(), height: expected_height.clone(), ..Default::default() }; let style_a = KStyle::default(); let style_b = KStyle { height: expected_height, ..Default::default() }; let style_c = KStyle { left: expected_left, ..Default::default() }; let style_d = KStyle { width: expected_width, ..Default::default() }; assert_eq!(expected, style_a + style_b + style_c + style_d); } #[test] fn value_should_convert_to_property() { let expected_width = Units::Pixels(123.0); let expected = StyleProp::Value(expected_width); let property: StyleProp<_> = expected_width.into(); assert_eq!(expected, property); } #[test] fn value_should_resolve_with_given_value() { let expected: f32 = 123.0; let property = StyleProp::Default; assert_eq!(expected, property.resolve_or(expected)); assert_eq!(expected, property.resolve_or_else(|| expected)); assert_eq!(f32::default(), property.resolve_or_default()); } }