pub mod components;
pub mod paint;
pub mod parser;
use crate::io::CssAsset;
use crate::styles::components::UiStyle;
use bevy::prelude::*;
use bevy::text::{FontSize, LineHeight};
use bevy::window::SystemCursorIcon;
use std::cmp::PartialEq;
use std::collections::{HashMap, HashSet};
#[derive(Resource, Default)]
pub struct ExistingCssIDs(pub HashSet<String>);
#[derive(Component, Reflect, Debug, Clone, Deref, DerefMut)]
#[reflect(Component)]
pub struct TagName(pub String);
#[derive(Component, Reflect, Debug, Clone, PartialEq, Eq)]
#[reflect(Component)]
pub struct CssClass(pub Vec<String>);
#[derive(Component, Reflect, Debug, Clone, PartialEq, Eq)]
#[reflect(Component)]
pub struct CssID(pub String);
#[derive(Component, Reflect, Debug, Clone, Default, PartialEq)]
#[reflect(Component)]
pub struct CssSource(pub Vec<Handle<CssAsset>>);
impl CssSource {
pub fn from_path(asset_server: &AssetServer, path: &str) -> Self {
Self(vec![asset_server.load::<CssAsset>(path.to_string())])
}
pub fn push_path(&mut self, asset_server: &AssetServer, path: &str) {
self.0.push(asset_server.load::<CssAsset>(path.to_string()));
}
pub fn from_paths(
asset_server: &AssetServer,
paths: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Self(
paths
.into_iter()
.map(|p| asset_server.load::<CssAsset>(p.into()))
.collect(),
)
}
}
#[derive(Reflect, Default, Clone, PartialEq, Debug)]
pub struct Radius {
pub top_left: Val,
pub top_right: Val,
pub bottom_left: Val,
pub bottom_right: Val,
}
impl Radius {
pub fn all(val: Val) -> Self {
Self {
top_left: val,
top_right: val,
bottom_left: val,
bottom_right: val,
}
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub struct Background {
pub color: Color,
pub image: Option<String>,
pub gradient: Option<LinearGradient>,
}
impl Default for Background {
fn default() -> Self {
Self {
color: Color::NONE,
image: None,
gradient: None,
}
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub struct BackgroundPosition {
pub x: BackgroundPositionValue,
pub y: BackgroundPositionValue,
}
impl Default for BackgroundPosition {
fn default() -> Self {
Self {
x: BackgroundPositionValue::Percent(0.0),
y: BackgroundPositionValue::Percent(0.0),
}
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub enum BackgroundPositionValue {
Percent(f32),
Px(f32),
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub enum BackgroundSize {
Auto,
Cover,
Contain,
Explicit(BackgroundSizeValue, BackgroundSizeValue),
}
impl Default for BackgroundSize {
fn default() -> Self {
Self::Auto
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub enum BackgroundSizeValue {
Auto,
Percent(f32),
Px(f32),
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub enum BackgroundAttachment {
Scroll,
Fixed,
Local,
}
impl Default for BackgroundAttachment {
fn default() -> Self {
Self::Scroll
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub enum BackdropFilter {
Blur(f32),
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub struct LinearGradient {
pub angle: f32,
pub stops: Vec<GradientStop>,
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub struct GradientStop {
pub color: Color,
pub position: Option<GradientStopPosition>,
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub enum GradientStopPosition {
Percent(f32),
Px(f32),
}
#[derive(Reflect, Debug, Clone, PartialEq, Copy)]
pub enum FontWeight {
Thin = 100,
ExtraLight = 200,
Light = 300,
Normal = 400,
Medium = 500,
SemiBold = 600,
Bold = 700,
ExtraBold = 800,
Black = 900,
}
impl FontWeight {
pub fn from_name(name: &str) -> Option<Self> {
let n = name.trim().to_ascii_lowercase();
match n.as_str() {
"thin" => Some(Self::Thin),
"extralight" | "extra-light" => Some(Self::ExtraLight),
"light" => Some(Self::Light),
"normal" | "regular" => Some(Self::Normal),
"medium" => Some(Self::Medium),
"semibold" | "semi-bold" => Some(Self::SemiBold),
"bold" => Some(Self::Bold),
"extrabold" | "extra-bold" => Some(Self::ExtraBold),
"black" | "heavy" => Some(Self::Black),
_ => None,
}
}
pub fn from_number(value: u16) -> Option<Self> {
Some(match value {
100 => Self::Thin,
200 => Self::ExtraLight,
300 => Self::Light,
400 => Self::Normal,
500 => Self::Medium,
600 => Self::SemiBold,
700 => Self::Bold,
800 => Self::ExtraBold,
900 => Self::Black,
_ => Self::Normal,
})
}
pub fn as_number(self) -> u16 {
self as u16
}
}
#[derive(Reflect, Debug, Clone, Copy, PartialEq, Eq)]
pub enum IconPlace {
Left,
Right,
}
impl Default for IconPlace {
fn default() -> Self {
IconPlace::Right
}
}
pub fn font_size_to_px(font_size: FontSize, rem_size: Option<f32>) -> f32 {
font_size.eval(Vec2::ZERO, rem_size.unwrap_or(1.0))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CalcUnit {
None,
Px,
Percent,
Rem,
Vw,
Vh,
VMin,
VMax,
Deg,
Rad,
Turn,
Fr,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CalcValue {
pub value: f32,
pub unit: CalcUnit,
}
impl CalcValue {
pub fn new(value: f32, unit: CalcUnit) -> Self {
Self { value, unit }
}
fn to_length_px(self, ctx: CalcContext) -> Option<f32> {
match self.unit {
CalcUnit::Px => Some(self.value),
CalcUnit::Percent => Some(ctx.base * self.value / 100.0),
CalcUnit::Vw => Some(ctx.viewport.x * self.value / 100.0),
CalcUnit::Vh => Some(ctx.viewport.y * self.value / 100.0),
CalcUnit::VMin => Some(ctx.viewport.x.min(ctx.viewport.y) * self.value / 100.0),
CalcUnit::VMax => Some(ctx.viewport.x.max(ctx.viewport.y) * self.value / 100.0),
CalcUnit::None if self.value == 0.0 => Some(0.0),
_ => None,
}
}
fn to_angle_radians(self) -> Option<f32> {
match self.unit {
CalcUnit::None => Some(self.value),
CalcUnit::Deg => Some(self.value.to_radians()),
CalcUnit::Rad => Some(self.value),
CalcUnit::Turn => Some(self.value * std::f32::consts::TAU),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CalcExpr {
Value(CalcValue),
Add(Box<CalcExpr>, Box<CalcExpr>),
Sub(Box<CalcExpr>, Box<CalcExpr>),
Mul(Box<CalcExpr>, Box<CalcExpr>),
Div(Box<CalcExpr>, Box<CalcExpr>),
Min(Vec<CalcExpr>),
Max(Vec<CalcExpr>),
Sin(Box<CalcExpr>),
}
#[derive(Debug, Clone, Copy)]
pub struct CalcContext {
pub base: f32,
pub viewport: Vec2,
}
impl CalcExpr {
pub fn eval_length(&self, ctx: CalcContext) -> Option<f32> {
match self {
CalcExpr::Value(value) => value.to_length_px(ctx),
CalcExpr::Add(a, b) => Some(a.eval_length(ctx)? + b.eval_length(ctx)?),
CalcExpr::Sub(a, b) => Some(a.eval_length(ctx)? - b.eval_length(ctx)?),
CalcExpr::Mul(a, b) => {
let left_len = a.eval_length(ctx);
let right_len = b.eval_length(ctx);
let left_num = a.eval_unitless();
let right_num = b.eval_unitless();
if let (Some(len), Some(num)) = (left_len, right_num) {
return Some(len * num);
}
if let (Some(num), Some(len)) = (left_num, right_len) {
return Some(len * num);
}
None
}
CalcExpr::Div(a, b) => {
let denom = b.eval_unitless()?;
if denom == 0.0 {
return None;
}
Some(a.eval_length(ctx)? / denom)
}
CalcExpr::Min(values) => {
let mut best: Option<f32> = None;
for value in values {
let resolved = value.eval_length(ctx)?;
best = Some(match best {
Some(current) => current.min(resolved),
None => resolved,
});
}
best
}
CalcExpr::Max(values) => {
let mut best: Option<f32> = None;
for value in values {
let resolved = value.eval_length(ctx)?;
best = Some(match best {
Some(current) => current.max(resolved),
None => resolved,
});
}
best
}
CalcExpr::Sin(inner) => {
let angle = inner.eval_angle_radians()?;
Some(angle.sin())
}
}
}
pub fn eval_unitless(&self) -> Option<f32> {
match self {
CalcExpr::Value(value) if value.unit == CalcUnit::None => Some(value.value),
CalcExpr::Add(a, b) => Some(a.eval_unitless()? + b.eval_unitless()?),
CalcExpr::Sub(a, b) => Some(a.eval_unitless()? - b.eval_unitless()?),
CalcExpr::Mul(a, b) => Some(a.eval_unitless()? * b.eval_unitless()?),
CalcExpr::Div(a, b) => {
let denom = b.eval_unitless()?;
if denom == 0.0 {
return None;
}
Some(a.eval_unitless()? / denom)
}
CalcExpr::Min(values) => {
let mut best: Option<f32> = None;
for value in values {
let resolved = value.eval_unitless()?;
best = Some(match best {
Some(current) => current.min(resolved),
None => resolved,
});
}
best
}
CalcExpr::Max(values) => {
let mut best: Option<f32> = None;
for value in values {
let resolved = value.eval_unitless()?;
best = Some(match best {
Some(current) => current.max(resolved),
None => resolved,
});
}
best
}
CalcExpr::Sin(inner) => {
let angle = inner.eval_angle_radians()?;
Some(angle.sin())
}
_ => None,
}
}
fn eval_angle_radians(&self) -> Option<f32> {
match self {
CalcExpr::Value(value) => value.to_angle_radians(),
CalcExpr::Add(a, b) => Some(a.eval_angle_radians()? + b.eval_angle_radians()?),
CalcExpr::Sub(a, b) => Some(a.eval_angle_radians()? - b.eval_angle_radians()?),
CalcExpr::Mul(a, b) => {
let left = a.eval_angle_radians();
let right = b.eval_angle_radians();
let left_num = a.eval_unitless();
let right_num = b.eval_unitless();
if let (Some(angle), Some(num)) = (left, right_num) {
return Some(angle * num);
}
if let (Some(num), Some(angle)) = (left_num, right) {
return Some(angle * num);
}
None
}
CalcExpr::Div(a, b) => {
let denom = b.eval_unitless()?;
if denom == 0.0 {
return None;
}
Some(a.eval_angle_radians()? / denom)
}
CalcExpr::Min(values) => {
let mut best: Option<f32> = None;
for value in values {
let resolved = value.eval_angle_radians()?;
best = Some(match best {
Some(current) => current.min(resolved),
None => resolved,
});
}
best
}
CalcExpr::Max(values) => {
let mut best: Option<f32> = None;
for value in values {
let resolved = value.eval_angle_radians()?;
best = Some(match best {
Some(current) => current.max(resolved),
None => resolved,
});
}
best
}
CalcExpr::Sin(inner) => {
let angle = inner.eval_angle_radians()?;
Some(angle.sin())
}
}
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub struct FontFamily(pub String);
#[derive(Reflect, Debug, Clone, PartialEq, Eq, Copy)]
pub enum TransitionTiming {
Linear,
Ease,
EaseIn,
EaseOut,
EaseInOut,
}
impl TransitionTiming {
pub fn apply(self, t: f32) -> f32 {
match self {
TransitionTiming::Linear => t,
TransitionTiming::Ease => t * t * (3.0 - 2.0 * t),
TransitionTiming::EaseIn => t * t,
TransitionTiming::EaseOut => 1.0 - (1.0 - t).powi(2),
TransitionTiming::EaseInOut => {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
}
}
}
}
pub fn from_name(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"linear" => Some(Self::Linear),
"ease" => Some(Self::Ease),
"ease-in" => Some(Self::EaseIn),
"ease-out" => Some(Self::EaseOut),
"ease-in-out" => Some(Self::EaseInOut),
_ => None,
}
}
}
impl Default for TransitionTiming {
fn default() -> Self {
TransitionTiming::EaseInOut
}
}
#[derive(Reflect, Debug, Clone, PartialEq, Eq, Copy)]
pub enum AnimationDirection {
Normal,
Reverse,
Alternate,
AlternateReverse,
}
impl AnimationDirection {
pub fn from_name(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"normal" => Some(Self::Normal),
"reverse" => Some(Self::Reverse),
"alternate" => Some(Self::Alternate),
"alternate-reverse" => Some(Self::AlternateReverse),
_ => None,
}
}
}
impl Default for AnimationDirection {
fn default() -> Self {
AnimationDirection::Normal
}
}
#[derive(Reflect, Debug, Clone, PartialEq, Eq, Copy)]
pub enum TransitionProperty {
All,
Color,
Background,
Transform,
}
impl Default for TransitionProperty {
fn default() -> Self {
TransitionProperty::All
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub struct AnimationSpec {
pub name: String,
pub duration: f32,
pub delay: f32,
pub timing: TransitionTiming,
pub iterations: Option<f32>,
pub direction: AnimationDirection,
}
impl Default for AnimationSpec {
fn default() -> Self {
Self {
name: String::new(),
duration: 0.0,
delay: 0.0,
timing: TransitionTiming::Ease,
iterations: Some(1.0),
direction: AnimationDirection::Normal,
}
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub struct TransitionSpec {
pub properties: Vec<TransitionProperty>,
pub duration: f32,
pub delay: f32,
pub timing: TransitionTiming,
}
impl Default for TransitionSpec {
fn default() -> Self {
Self {
properties: vec![TransitionProperty::All],
duration: 0.3,
delay: 0.0,
timing: TransitionTiming::EaseInOut,
}
}
}
#[derive(Reflect, Default, Debug, Clone, PartialEq)]
pub struct AnimationKeyframe {
pub progress: f32,
pub style: Style,
}
#[derive(Reflect, Default, Debug, Clone, PartialEq)]
pub struct ParsedCss {
pub styles: HashMap<String, StylePair>,
pub keyframes: HashMap<String, Vec<AnimationKeyframe>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MediaQueryCondition {
Always,
Never,
MinWidth(f32),
MaxWidth(f32),
Width(f32),
MinHeight(f32),
MaxHeight(f32),
Height(f32),
OrientationLandscape,
OrientationPortrait,
Not(Box<MediaQueryCondition>),
And(Vec<MediaQueryCondition>),
Or(Vec<MediaQueryCondition>),
}
impl MediaQueryCondition {
fn compound_cache_key(prefix: &str, parts: &[MediaQueryCondition]) -> String {
let mut key = String::from(prefix);
key.push('(');
for (idx, part) in parts.iter().enumerate() {
if idx > 0 {
key.push(',');
}
key.push_str(&part.cache_key());
}
key.push(')');
key
}
pub fn matches_viewport(&self, viewport: Vec2) -> bool {
const EPSILON: f32 = 0.5;
match self {
MediaQueryCondition::Always => true,
MediaQueryCondition::Never => false,
MediaQueryCondition::MinWidth(value) => viewport.x + EPSILON >= *value,
MediaQueryCondition::MaxWidth(value) => viewport.x - EPSILON <= *value,
MediaQueryCondition::Width(value) => (viewport.x - *value).abs() <= EPSILON,
MediaQueryCondition::MinHeight(value) => viewport.y + EPSILON >= *value,
MediaQueryCondition::MaxHeight(value) => viewport.y - EPSILON <= *value,
MediaQueryCondition::Height(value) => (viewport.y - *value).abs() <= EPSILON,
MediaQueryCondition::OrientationLandscape => viewport.x >= viewport.y,
MediaQueryCondition::OrientationPortrait => viewport.y > viewport.x,
MediaQueryCondition::Not(inner) => !inner.matches_viewport(viewport),
MediaQueryCondition::And(parts) => parts
.iter()
.all(|condition| condition.matches_viewport(viewport)),
MediaQueryCondition::Or(parts) => parts
.iter()
.any(|condition| condition.matches_viewport(viewport)),
}
}
pub fn cache_key(&self) -> String {
match self {
MediaQueryCondition::Always => "always".to_string(),
MediaQueryCondition::Never => "never".to_string(),
MediaQueryCondition::MinWidth(value) => format!("minw:{value:.3}"),
MediaQueryCondition::MaxWidth(value) => format!("maxw:{value:.3}"),
MediaQueryCondition::Width(value) => format!("w:{value:.3}"),
MediaQueryCondition::MinHeight(value) => format!("minh:{value:.3}"),
MediaQueryCondition::MaxHeight(value) => format!("maxh:{value:.3}"),
MediaQueryCondition::Height(value) => format!("h:{value:.3}"),
MediaQueryCondition::OrientationLandscape => "orientation:landscape".to_string(),
MediaQueryCondition::OrientationPortrait => "orientation:portrait".to_string(),
MediaQueryCondition::Not(inner) => format!("not({})", inner.cache_key()),
MediaQueryCondition::And(parts) => Self::compound_cache_key("and", parts),
MediaQueryCondition::Or(parts) => Self::compound_cache_key("or", parts),
}
}
}
#[derive(Reflect, Default, Debug, Clone, PartialEq)]
pub struct StylePair {
pub important: Style,
pub normal: Style,
pub origin: usize,
pub selector: String,
#[reflect(ignore)]
pub media: Option<MediaQueryCondition>,
}
#[derive(Reflect, Default, Debug, Clone, PartialEq)]
pub struct TransformStyle {
pub translation: Option<Val2>,
pub translation_x: Option<Val>,
pub translation_y: Option<Val>,
pub scale: Option<Vec2>,
pub scale_x: Option<f32>,
pub scale_y: Option<f32>,
pub rotation: Option<f32>,
}
impl TransformStyle {
pub fn is_empty(&self) -> bool {
self.translation.is_none()
&& self.translation_x.is_none()
&& self.translation_y.is_none()
&& self.scale.is_none()
&& self.scale_x.is_none()
&& self.scale_y.is_none()
&& self.rotation.is_none()
}
}
#[derive(Reflect, Debug, Clone, PartialEq)]
pub enum CursorStyle {
System(SystemCursorIcon),
Custom(String),
}
#[derive(Reflect, Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextTransform {
None,
Uppercase,
Lowercase,
Capitalize,
}
#[derive(Reflect, Default, Debug, Clone, PartialEq)]
pub struct Style {
pub display: Option<Display>,
pub box_sizing: Option<BoxSizing>,
pub position_type: Option<PositionType>,
pub width: Option<Val>,
#[reflect(ignore)]
pub width_calc: Option<CalcExpr>,
pub min_width: Option<Val>,
#[reflect(ignore)]
pub min_width_calc: Option<CalcExpr>,
pub max_width: Option<Val>,
#[reflect(ignore)]
pub max_width_calc: Option<CalcExpr>,
pub height: Option<Val>,
#[reflect(ignore)]
pub height_calc: Option<CalcExpr>,
pub min_height: Option<Val>,
#[reflect(ignore)]
pub min_height_calc: Option<CalcExpr>,
pub max_height: Option<Val>,
#[reflect(ignore)]
pub max_height_calc: Option<CalcExpr>,
pub left: Option<Val>,
#[reflect(ignore)]
pub left_calc: Option<CalcExpr>,
pub top: Option<Val>,
#[reflect(ignore)]
pub top_calc: Option<CalcExpr>,
pub right: Option<Val>,
#[reflect(ignore)]
pub right_calc: Option<CalcExpr>,
pub bottom: Option<Val>,
#[reflect(ignore)]
pub bottom_calc: Option<CalcExpr>,
pub padding: Option<UiRect>,
pub margin: Option<UiRect>,
pub border: Option<UiRect>,
pub overflow: Option<Overflow>,
pub color: Option<Color>,
pub background: Option<Background>,
pub backdrop_filter: Option<BackdropFilter>,
pub background_position: Option<BackgroundPosition>,
pub background_size: Option<BackgroundSize>,
pub background_attachment: Option<BackgroundAttachment>,
pub border_color: Option<Color>,
pub border_width: Option<Val>,
pub border_radius: Option<Radius>,
pub outline_width: Option<Val>,
pub outline_offset: Option<Val>,
pub outline_color: Option<Color>,
pub font_size: Option<FontSize>,
pub font_family: Option<FontFamily>,
pub font_weight: Option<FontWeight>,
pub line_height: Option<LineHeight>,
pub box_shadow: Option<BoxShadow>,
pub text_shadow: Option<TextShadow>,
pub text_transform: Option<TextTransform>,
pub justify_content: Option<JustifyContent>,
pub justify_items: Option<JustifyItems>,
pub justify_self: Option<JustifySelf>,
pub align_content: Option<AlignContent>,
pub align_items: Option<AlignItems>,
pub align_self: Option<AlignSelf>,
pub flex_direction: Option<FlexDirection>,
pub flex_grow: Option<f32>,
pub flex_shrink: Option<f32>,
pub flex_basis: Option<Val>,
#[reflect(ignore)]
pub flex_basis_calc: Option<CalcExpr>,
pub flex_wrap: Option<FlexWrap>,
pub grid_row: Option<GridPlacement>,
pub grid_column: Option<GridPlacement>,
pub grid_auto_flow: Option<GridAutoFlow>,
pub grid_template_rows: Option<Vec<RepeatedGridTrack>>,
pub grid_template_columns: Option<Vec<RepeatedGridTrack>>,
pub grid_auto_rows: Option<Vec<GridTrack>>,
pub grid_auto_columns: Option<Vec<GridTrack>>,
pub gap: Option<Val>,
#[reflect(ignore)]
pub gap_calc: Option<CalcExpr>,
pub row_gap: Option<Val>,
#[reflect(ignore)]
pub row_gap_calc: Option<CalcExpr>,
pub column_gap: Option<Val>,
#[reflect(ignore)]
pub column_gap_calc: Option<CalcExpr>,
pub text_align: Option<Justify>,
pub text_wrap: Option<LineBreak>,
pub z_index: Option<i32>,
pub cursor: Option<CursorStyle>,
pub pointer_events: Option<Pickable>,
pub scrollbar_width: Option<f32>,
pub transition: Option<TransitionSpec>,
pub transform: TransformStyle,
pub animation: Option<AnimationSpec>,
}
impl Style {
pub fn merge(&mut self, other: &Style) {
merge_opt(&mut self.display, &other.display);
merge_opt(&mut self.box_sizing, &other.box_sizing);
merge_opt(&mut self.position_type, &other.position_type);
merge_val_with_calc(
&mut self.width,
&mut self.width_calc,
&other.width,
&other.width_calc,
);
merge_val_with_calc(
&mut self.min_width,
&mut self.min_width_calc,
&other.min_width,
&other.min_width_calc,
);
merge_val_with_calc(
&mut self.max_width,
&mut self.max_width_calc,
&other.max_width,
&other.max_width_calc,
);
merge_val_with_calc(
&mut self.height,
&mut self.height_calc,
&other.height,
&other.height_calc,
);
merge_val_with_calc(
&mut self.min_height,
&mut self.min_height_calc,
&other.min_height,
&other.min_height_calc,
);
merge_val_with_calc(
&mut self.max_height,
&mut self.max_height_calc,
&other.max_height,
&other.max_height_calc,
);
merge_val_with_calc(
&mut self.left,
&mut self.left_calc,
&other.left,
&other.left_calc,
);
merge_val_with_calc(
&mut self.top,
&mut self.top_calc,
&other.top,
&other.top_calc,
);
merge_val_with_calc(
&mut self.right,
&mut self.right_calc,
&other.right,
&other.right_calc,
);
merge_val_with_calc(
&mut self.bottom,
&mut self.bottom_calc,
&other.bottom,
&other.bottom_calc,
);
merge_opt(&mut self.padding, &other.padding);
merge_opt(&mut self.margin, &other.margin);
merge_opt(&mut self.border, &other.border);
merge_opt(&mut self.overflow, &other.overflow);
merge_opt(&mut self.color, &other.color);
merge_opt(&mut self.background, &other.background);
merge_opt(&mut self.backdrop_filter, &other.backdrop_filter);
merge_opt(&mut self.background_position, &other.background_position);
merge_opt(&mut self.background_size, &other.background_size);
merge_opt(
&mut self.background_attachment,
&other.background_attachment,
);
merge_opt(&mut self.border_color, &other.border_color);
merge_opt(&mut self.border_width, &other.border_width);
merge_opt(&mut self.border_radius, &other.border_radius);
merge_opt(&mut self.outline_width, &other.outline_width);
merge_opt(&mut self.outline_offset, &other.outline_offset);
merge_opt(&mut self.outline_color, &other.outline_color);
merge_opt(&mut self.font_size, &other.font_size);
merge_opt(&mut self.font_family, &other.font_family);
merge_opt(&mut self.font_weight, &other.font_weight);
merge_opt(&mut self.line_height, &other.line_height);
merge_opt(&mut self.box_shadow, &other.box_shadow);
merge_opt(&mut self.text_shadow, &other.text_shadow);
merge_opt(&mut self.text_transform, &other.text_transform);
merge_opt(&mut self.justify_content, &other.justify_content);
merge_opt(&mut self.justify_items, &other.justify_items);
merge_opt(&mut self.justify_self, &other.justify_self);
merge_opt(&mut self.align_content, &other.align_content);
merge_opt(&mut self.align_items, &other.align_items);
merge_opt(&mut self.align_self, &other.align_self);
merge_opt(&mut self.flex_direction, &other.flex_direction);
merge_opt(&mut self.flex_wrap, &other.flex_wrap);
merge_opt(&mut self.flex_grow, &other.flex_grow);
merge_opt(&mut self.flex_shrink, &other.flex_shrink);
merge_val_with_calc(
&mut self.flex_basis,
&mut self.flex_basis_calc,
&other.flex_basis,
&other.flex_basis_calc,
);
merge_opt(&mut self.grid_row, &other.grid_row);
merge_opt(&mut self.grid_column, &other.grid_column);
merge_opt(&mut self.grid_auto_flow, &other.grid_auto_flow);
merge_opt(&mut self.grid_template_rows, &other.grid_template_rows);
merge_opt(
&mut self.grid_template_columns,
&other.grid_template_columns,
);
merge_opt(&mut self.grid_auto_rows, &other.grid_auto_rows);
merge_opt(&mut self.grid_auto_columns, &other.grid_auto_columns);
merge_val_with_calc(
&mut self.gap,
&mut self.gap_calc,
&other.gap,
&other.gap_calc,
);
merge_val_with_calc(
&mut self.row_gap,
&mut self.row_gap_calc,
&other.row_gap,
&other.row_gap_calc,
);
merge_val_with_calc(
&mut self.column_gap,
&mut self.column_gap_calc,
&other.column_gap,
&other.column_gap_calc,
);
merge_opt(&mut self.text_align, &other.text_align);
merge_opt(&mut self.text_wrap, &other.text_wrap);
merge_opt(&mut self.z_index, &other.z_index);
merge_opt(&mut self.cursor, &other.cursor);
merge_opt(&mut self.pointer_events, &other.pointer_events);
merge_opt(&mut self.scrollbar_width, &other.scrollbar_width);
merge_opt(&mut self.transition, &other.transition);
merge_opt(
&mut self.transform.translation,
&other.transform.translation,
);
merge_opt(
&mut self.transform.translation_x,
&other.transform.translation_x,
);
merge_opt(
&mut self.transform.translation_y,
&other.transform.translation_y,
);
merge_opt(&mut self.transform.scale, &other.transform.scale);
merge_opt(&mut self.transform.scale_x, &other.transform.scale_x);
merge_opt(&mut self.transform.scale_y, &other.transform.scale_y);
merge_opt(&mut self.transform.rotation, &other.transform.rotation);
merge_opt(&mut self.animation, &other.animation);
}
}
#[inline]
fn merge_opt<T: Clone>(dst: &mut Option<T>, src: &Option<T>) {
if let Some(v) = src.as_ref() {
*dst = Some(v.clone());
}
}
#[inline]
fn merge_val_with_calc<T: Clone>(
dst_val: &mut Option<T>,
dst_calc: &mut Option<CalcExpr>,
src_val: &Option<T>,
src_calc: &Option<CalcExpr>,
) {
if let Some(calc) = src_calc.as_ref() {
*dst_calc = Some(calc.clone());
*dst_val = None;
} else if let Some(val) = src_val.as_ref() {
*dst_val = Some(val.clone());
*dst_calc = None;
}
}
pub struct ExtendedStylingPlugin;
impl Plugin for ExtendedStylingPlugin {
fn build(&self, app: &mut App) {
app.register_type::<UiStyle>();
app.register_type::<CssClass>();
app.register_type::<CssID>();
}
}