From 09cd0fe9f2d5d775cd1b645300ac56bc203a20cd Mon Sep 17 00:00:00 2001 From: pml68 Date: Sat, 22 Feb 2025 23:48:19 +0100 Subject: feat: start working on options backend (`ApplyOptions` trait) --- src/options.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/options.rs (limited to 'src/options.rs') diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..9514dcb --- /dev/null +++ b/src/options.rs @@ -0,0 +1,44 @@ +use std::collections::BTreeMap; +use std::str::FromStr; + +use iced::Padding; +use iced::widget::{Button, Column, Container, Image, Row, Svg, Text}; + +pub trait ApplyOptions { + fn apply_options(self, options: BTreeMap>) -> Self; +} + +impl<'a, Message> ApplyOptions for Button<'a, Message> { + fn apply_options(self, options: BTreeMap>) -> Self { + let mut button = self; + + if let Some(padding) = options.get("padding").expect("padding key") { + let padding: Padding = padding + .strip_prefix('[') + .and_then(|s| s.strip_suffix(']')) + .and_then(|s| { + Some( + s.split(',') + .map(|n| f32::from_str(n).unwrap()) + .collect::>(), + ) + }) + .and_then(|s| { + if s.len() == 4 { + Some(Padding { + top: s[0], + right: s[1], + bottom: s[2], + left: s[3], + }) + } else { + None + } + }) + .unwrap(); + button = button.padding(padding); + } + + button + } +} -- cgit v1.2.3 From a806922d47bb7ea3e401a99ed2c918c4ab745973 Mon Sep 17 00:00:00 2001 From: pml68 Date: Mon, 24 Feb 2025 19:08:32 +0100 Subject: feat: create separate `ValueFromStr` struct --- src/main.rs | 1 + src/options.rs | 30 +++++------------------------- src/types/rendered_element.rs | 4 ++-- src/values.rs | 7 +++++++ src/values/padding.rs | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 src/values.rs create mode 100644 src/values/padding.rs (limited to 'src/options.rs') diff --git a/src/main.rs b/src/main.rs index 6c4e8d6..20383f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod options; mod panes; mod theme; mod types; +mod values; mod widget; use std::path::PathBuf; diff --git a/src/options.rs b/src/options.rs index 9514dcb..5a3e1fb 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,41 +1,21 @@ use std::collections::BTreeMap; -use std::str::FromStr; use iced::Padding; +#[allow(unused_imports)] use iced::widget::{Button, Column, Container, Image, Row, Svg, Text}; +use crate::values::ValueFromStr; + pub trait ApplyOptions { fn apply_options(self, options: BTreeMap>) -> Self; } -impl<'a, Message> ApplyOptions for Button<'a, Message> { +impl ApplyOptions for Button<'_, Message> { fn apply_options(self, options: BTreeMap>) -> Self { let mut button = self; if let Some(padding) = options.get("padding").expect("padding key") { - let padding: Padding = padding - .strip_prefix('[') - .and_then(|s| s.strip_suffix(']')) - .and_then(|s| { - Some( - s.split(',') - .map(|n| f32::from_str(n).unwrap()) - .collect::>(), - ) - }) - .and_then(|s| { - if s.len() == 4 { - Some(Padding { - top: s[0], - right: s[1], - bottom: s[2], - left: s[3], - }) - } else { - None - } - }) - .unwrap(); + let padding: Padding = Padding::value_from_str(padding).unwrap(); button = button.padding(padding); } diff --git a/src/types/rendered_element.rs b/src/types/rendered_element.rs index 94c55a0..f4ae6f3 100755 --- a/src/types/rendered_element.rs +++ b/src/types/rendered_element.rs @@ -318,14 +318,14 @@ impl<'a> From for Element<'a, Message> { let content: Element<'a, Message> = match copy.name { ElementName::Text(s) => { - if &s == "" { + if s.is_empty() { widget::text("New Text").into() } else { widget::text(s).into() } } ElementName::Button(s) => { - if &s == "" { + if s.is_empty() { widget::button(widget::text("New Button")) .apply_options(copy.options) .into() diff --git a/src/values.rs b/src/values.rs new file mode 100644 index 0000000..0ac17cb --- /dev/null +++ b/src/values.rs @@ -0,0 +1,7 @@ +mod padding; + +pub trait ValueFromStr: Sized { + type Err; + + fn value_from_str(s: &str) -> Result; +} diff --git a/src/values/padding.rs b/src/values/padding.rs new file mode 100644 index 0000000..3cec7a7 --- /dev/null +++ b/src/values/padding.rs @@ -0,0 +1,37 @@ +use std::str::FromStr; + +use iced::Padding; + +use super::ValueFromStr; + +#[derive(Debug)] +pub enum PaddingError { + Nah, +} + +impl ValueFromStr for Padding { + type Err = PaddingError; + + fn value_from_str(s: &str) -> Result { + s.strip_prefix('[') + .and_then(|s| s.strip_suffix(']')) + .map(|s| { + s.split(',') + .map(|n| f32::from_str(n).unwrap()) + .collect::>() + }) + .and_then(|s| { + if s.len() == 4 { + Some(Padding { + top: s[0], + right: s[1], + bottom: s[2], + left: s[3], + }) + } else { + None + } + }) + .ok_or(PaddingError::Nah) + } +} -- cgit v1.2.3 From fb58080059510de9293fddaad7c543a3089b3824 Mon Sep 17 00:00:00 2001 From: pml68 Date: Mon, 24 Feb 2025 22:31:58 +0100 Subject: feat: implement `ValueFromStr` for Rotation --- src/options.rs | 58 +++++++++++++++++++++++++++++++-- src/types/rendered_element.rs | 40 ++++++++++++++++++++--- src/values.rs | 1 + src/values/padding.rs | 76 ++++++++++++++++++++++++++++++------------- src/values/rotation.rs | 37 +++++++++++++++++++++ 5 files changed, 183 insertions(+), 29 deletions(-) create mode 100644 src/values/rotation.rs (limited to 'src/options.rs') diff --git a/src/options.rs b/src/options.rs index 5a3e1fb..c381294 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,12 +1,12 @@ use std::collections::BTreeMap; -use iced::Padding; #[allow(unused_imports)] use iced::widget::{Button, Column, Container, Image, Row, Svg, Text}; +use iced::{Padding, Rotation}; use crate::values::ValueFromStr; -pub trait ApplyOptions { +pub trait ApplyOptions: Sized { fn apply_options(self, options: BTreeMap>) -> Self; } @@ -15,10 +15,62 @@ impl ApplyOptions for Button<'_, Message> { let mut button = self; if let Some(padding) = options.get("padding").expect("padding key") { - let padding: Padding = Padding::value_from_str(padding).unwrap(); + let padding = Padding::value_from_str(padding).unwrap(); button = button.padding(padding); } button } } + +impl ApplyOptions for Column<'_, Message> { + fn apply_options(self, options: BTreeMap>) -> Self { + let mut column = self; + + if let Some(padding) = options.get("padding").expect("padding key") { + let padding = Padding::value_from_str(padding).unwrap(); + column = column.padding(padding); + } + + column + } +} + +impl ApplyOptions for Row<'_, Message> { + fn apply_options(self, options: BTreeMap>) -> Self { + let mut row = self; + + if let Some(padding) = options.get("padding").expect("padding key") { + let padding = Padding::value_from_str(padding).unwrap(); + row = row.padding(padding); + } + + row + } +} + +impl ApplyOptions for Image { + fn apply_options(self, options: BTreeMap>) -> Self { + let mut image = self; + + if let Some(rotation) = options.get("rotation").expect("rotation key") { + let rotation = Rotation::value_from_str(rotation).unwrap(); + image = image.rotation(rotation); + } + + image + } +} + +impl ApplyOptions for Svg<'_> { + fn apply_options(self, options: BTreeMap>) -> Self { + let mut svg = self; + + if let Some(rotation) = options.get("rotation").expect("rotation key") { + let rotation = Rotation::value_from_str(rotation).unwrap(); + svg = svg.rotation(rotation); + } + + svg + } +} diff --git a/src/types/rendered_element.rs b/src/types/rendered_element.rs index f4ae6f3..f9a83c1 100755 --- a/src/types/rendered_element.rs +++ b/src/types/rendered_element.rs @@ -335,8 +335,12 @@ impl<'a> From for Element<'a, Message> { .into() } } - ElementName::Svg(p) => widget::svg(p).into(), - ElementName::Image(p) => widget::image(p).into(), + ElementName::Svg(p) => { + widget::svg(p).apply_options(copy.options).into() + } + ElementName::Image(p) => { + widget::image(p).apply_options(copy.options).into() + } ElementName::Container => { widget::container(if child_elements.len() == 1 { child_elements[0].clone().into() @@ -350,11 +354,13 @@ impl<'a> From for Element<'a, Message> { child_elements.into_iter().map(Into::into).collect(), ) .padding(20) + .apply_options(copy.options) .into(), ElementName::Column => widget::Column::from_vec( child_elements.into_iter().map(Into::into).collect(), ) .padding(20) + .apply_options(copy.options) .into(), }; iced_drop::droppable(content) @@ -446,13 +452,27 @@ pub fn button(text: &str) -> RenderedElement { } pub fn svg(path: &str) -> RenderedElement { - RenderedElement::new(ElementName::Svg(path.to_owned())) + RenderedElement::new(ElementName::Svg(path.to_owned())).preset_options(&[ + "width", + "height", + "content_fit", + "rotation", + "opacity", + ]) } pub fn image(path: &str) -> RenderedElement { - RenderedElement::new(ElementName::Image(path.to_owned())) + RenderedElement::new(ElementName::Image(path.to_owned())).preset_options(&[ + "width", + "height", + "content_fit", + "rotation", + "opacity", + "scale", + ]) } +// TODO: Container options pub fn container(content: Option) -> RenderedElement { match content { Some(el) => RenderedElement::with(ElementName::Container, vec![el]), @@ -462,6 +482,9 @@ pub fn container(content: Option) -> RenderedElement { pub fn row(child_elements: Option>) -> RenderedElement { RenderedElement::with(ElementName::Row, child_elements.unwrap_or_default()) + .preset_options(&[ + "spacing", "padding", "width", "height", "align_y", "clip", + ]) } pub fn column(child_elements: Option>) -> RenderedElement { @@ -469,4 +492,13 @@ pub fn column(child_elements: Option>) -> RenderedElement { ElementName::Column, child_elements.unwrap_or_default(), ) + .preset_options(&[ + "spacing", + "padding", + "width", + "height", + "max_width", + "align_x", + "clip", + ]) } diff --git a/src/values.rs b/src/values.rs index 0ac17cb..23e47d7 100644 --- a/src/values.rs +++ b/src/values.rs @@ -1,4 +1,5 @@ mod padding; +mod rotation; pub trait ValueFromStr: Sized { type Err; diff --git a/src/values/padding.rs b/src/values/padding.rs index 3cec7a7..01e333b 100644 --- a/src/values/padding.rs +++ b/src/values/padding.rs @@ -1,37 +1,69 @@ +use std::num::ParseFloatError; use std::str::FromStr; use iced::Padding; use super::ValueFromStr; -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum PaddingError { - Nah, + #[error("wrong number of values")] + WrongNumberOfValues, + #[error("float parsing error: {0}")] + ParseFloatError(ParseFloatError), + #[error("missing bracket")] + MissingBracket, + #[error("empty string given")] + Empty, +} + +impl From for PaddingError { + fn from(value: ParseFloatError) -> Self { + Self::ParseFloatError(value) + } } impl ValueFromStr for Padding { type Err = PaddingError; fn value_from_str(s: &str) -> Result { - s.strip_prefix('[') - .and_then(|s| s.strip_suffix(']')) - .map(|s| { - s.split(',') - .map(|n| f32::from_str(n).unwrap()) - .collect::>() - }) - .and_then(|s| { - if s.len() == 4 { - Some(Padding { - top: s[0], - right: s[1], - bottom: s[2], - left: s[3], - }) - } else { - None - } - }) - .ok_or(PaddingError::Nah) + if s.is_empty() { + return Err(PaddingError::Empty); + } + let values = s + .strip_prefix('[') + .ok_or(PaddingError::MissingBracket)? + .strip_suffix(']') + .ok_or(PaddingError::MissingBracket)? + .split(',') + .map(|n| f32::from_str(n)) + .collect::, _>>()?; + match values.len() { + 1 => Ok(Padding { + top: values[0], + right: values[0], + bottom: values[0], + left: values[0], + }), + 2 => Ok(Padding { + top: values[0], + right: values[1], + bottom: values[0], + left: values[1], + }), + 3 => Ok(Padding { + top: values[0], + right: values[1], + bottom: values[2], + left: values[1], + }), + 4 => Ok(Padding { + top: values[0], + right: values[1], + bottom: values[2], + left: values[3], + }), + _ => Err(PaddingError::WrongNumberOfValues), + } } } diff --git a/src/values/rotation.rs b/src/values/rotation.rs new file mode 100644 index 0000000..2b7a223 --- /dev/null +++ b/src/values/rotation.rs @@ -0,0 +1,37 @@ +use std::num::ParseFloatError; +use std::str::FromStr; + +use iced::{Radians, Rotation}; + +use super::ValueFromStr; + +#[derive(Debug, thiserror::Error)] +pub enum ParseRotationError { + #[error("float parsing error: {0}")] + ParseFloatError(ParseFloatError), + #[error("invalid prefix")] + InvalidPrefix, +} + +impl From for ParseRotationError { + fn from(value: ParseFloatError) -> Self { + Self::ParseFloatError(value) + } +} + +impl ValueFromStr for Rotation { + type Err = ParseRotationError; + + fn value_from_str(s: &str) -> Result { + if s.starts_with(|c: char| !c.is_digit(10)) { + let (prefix, value) = s.split_at(1); + match prefix { + "s" => Ok(Rotation::Solid(Radians(f32::from_str(value)?))), + "f" => Ok(Rotation::Floating(Radians(f32::from_str(value)?))), + _ => Err(ParseRotationError::InvalidPrefix), + } + } else { + Ok(Rotation::Floating(Radians(f32::from_str(s)?))) + } + } +} -- cgit v1.2.3 From 21941c6de6e0843147ccab7b4045943b7a878442 Mon Sep 17 00:00:00 2001 From: pml68 Date: Wed, 26 Feb 2025 23:24:42 +0100 Subject: feat: rework `Value` trait, create unit tests for parser implementations --- src/options.rs | 14 ++-- src/values.rs | 8 +- src/values/padding.rs | 214 +++++++++++++++++++++++++++++++++++++++---------- src/values/rotation.rs | 85 ++++++++++++++++++-- 4 files changed, 264 insertions(+), 57 deletions(-) (limited to 'src/options.rs') diff --git a/src/options.rs b/src/options.rs index c381294..90fc63f 100644 --- a/src/options.rs +++ b/src/options.rs @@ -4,9 +4,9 @@ use std::collections::BTreeMap; use iced::widget::{Button, Column, Container, Image, Row, Svg, Text}; use iced::{Padding, Rotation}; -use crate::values::ValueFromStr; +use crate::values::Value; -pub trait ApplyOptions: Sized { +pub trait ApplyOptions { fn apply_options(self, options: BTreeMap>) -> Self; } @@ -15,7 +15,7 @@ impl ApplyOptions for Button<'_, Message> { let mut button = self; if let Some(padding) = options.get("padding").expect("padding key") { - let padding = Padding::value_from_str(padding).unwrap(); + let padding = Padding::from_str(padding).unwrap(); button = button.padding(padding); } @@ -28,7 +28,7 @@ impl ApplyOptions for Column<'_, Message> { let mut column = self; if let Some(padding) = options.get("padding").expect("padding key") { - let padding = Padding::value_from_str(padding).unwrap(); + let padding = Padding::from_str(padding).unwrap(); column = column.padding(padding); } @@ -41,7 +41,7 @@ impl ApplyOptions for Row<'_, Message> { let mut row = self; if let Some(padding) = options.get("padding").expect("padding key") { - let padding = Padding::value_from_str(padding).unwrap(); + let padding = Padding::from_str(padding).unwrap(); row = row.padding(padding); } @@ -54,7 +54,7 @@ impl ApplyOptions for Image { let mut image = self; if let Some(rotation) = options.get("rotation").expect("rotation key") { - let rotation = Rotation::value_from_str(rotation).unwrap(); + let rotation = Rotation::from_str(rotation).unwrap(); image = image.rotation(rotation); } @@ -67,7 +67,7 @@ impl ApplyOptions for Svg<'_> { let mut svg = self; if let Some(rotation) = options.get("rotation").expect("rotation key") { - let rotation = Rotation::value_from_str(rotation).unwrap(); + let rotation = Rotation::from_str(rotation).unwrap(); svg = svg.rotation(rotation); } diff --git a/src/values.rs b/src/values.rs index 23e47d7..e71a8c4 100644 --- a/src/values.rs +++ b/src/values.rs @@ -1,8 +1,12 @@ mod padding; mod rotation; -pub trait ValueFromStr: Sized { +pub trait Value: Sized { type Err; - fn value_from_str(s: &str) -> Result; + fn from_str(s: &str) -> Result; + + // TODO remove this once RenderedElement's options field is redone + #[allow(dead_code)] + fn to_string(&self) -> String; } diff --git a/src/values/padding.rs b/src/values/padding.rs index 01e333b..12880a3 100644 --- a/src/values/padding.rs +++ b/src/values/padding.rs @@ -3,67 +3,197 @@ use std::str::FromStr; use iced::Padding; -use super::ValueFromStr; +use super::Value; -#[derive(Debug, thiserror::Error)] -pub enum PaddingError { - #[error("wrong number of values")] - WrongNumberOfValues, +#[derive(Debug, thiserror::Error, Clone, PartialEq)] +pub enum ParsePaddingError { + #[error("wrong number of values: {0}, expected 1-4")] + WrongNumberOfValues(usize), #[error("float parsing error: {0}")] ParseFloatError(ParseFloatError), #[error("missing bracket")] MissingBracket, - #[error("empty string given")] + #[error("cannot parse padding from empty string")] Empty, } -impl From for PaddingError { +impl From for ParsePaddingError { fn from(value: ParseFloatError) -> Self { Self::ParseFloatError(value) } } -impl ValueFromStr for Padding { - type Err = PaddingError; +impl Value for Padding { + type Err = ParsePaddingError; - fn value_from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { if s.is_empty() { - return Err(PaddingError::Empty); + return Err(ParsePaddingError::Empty); } - let values = s - .strip_prefix('[') - .ok_or(PaddingError::MissingBracket)? - .strip_suffix(']') - .ok_or(PaddingError::MissingBracket)? - .split(',') - .map(|n| f32::from_str(n)) - .collect::, _>>()?; - match values.len() { - 1 => Ok(Padding { - top: values[0], - right: values[0], - bottom: values[0], - left: values[0], + + if !s.contains(['[', ',', ']']) { + let value = f32::from_str(s)?; + Ok(Padding { + top: value, + right: value, + bottom: value, + left: value, + }) + } else { + let values = s + .strip_prefix('[') + .and_then(|s| s.strip_suffix(']')) + .ok_or(ParsePaddingError::MissingBracket)? + .split(',') + .map(str::trim) + .map(f32::from_str) + .collect::, _>>()?; + + match values.len() { + 1 => Ok(Padding { + top: values[0], + right: values[0], + bottom: values[0], + left: values[0], + }), + 2 => Ok(Padding { + top: values[0], + right: values[1], + bottom: values[0], + left: values[1], + }), + 3 => Ok(Padding { + top: values[0], + right: values[1], + bottom: values[2], + left: values[1], + }), + 4 => Ok(Padding { + top: values[0], + right: values[1], + bottom: values[2], + left: values[3], + }), + other => Err(ParsePaddingError::WrongNumberOfValues(other)), + } + } + } + + fn to_string(&self) -> String { + format!( + "[{}, {}, {}, {}]", + self.top, self.right, self.bottom, self.left + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_parse_single_value() { + assert_eq!( + Padding::from_str("[1.5]"), + Ok(Padding { + top: 1.5, + right: 1.5, + bottom: 1.5, + left: 1.5, }), - 2 => Ok(Padding { - top: values[0], - right: values[1], - bottom: values[0], - left: values[1], + ) + } + + #[test] + fn can_parse_single_value_without_brackets() { + assert_eq!( + Padding::from_str("1.5"), + Ok(Padding { + top: 1.5, + right: 1.5, + bottom: 1.5, + left: 1.5, }), - 3 => Ok(Padding { - top: values[0], - right: values[1], - bottom: values[2], - left: values[1], + ) + } + + #[test] + fn can_parse_two_values() { + assert_eq!( + Padding::from_str("[3.2, 6.7]"), + Ok(Padding { + top: 3.2, + right: 6.7, + bottom: 3.2, + left: 6.7, }), - 4 => Ok(Padding { - top: values[0], - right: values[1], - bottom: values[2], - left: values[3], + ) + } + + #[test] + fn can_parse_three_values() { + assert_eq!( + Padding::from_str("[4.8, 8.1,5.9]"), + Ok(Padding { + top: 4.8, + right: 8.1, + bottom: 5.9, + left: 8.1, }), - _ => Err(PaddingError::WrongNumberOfValues), - } + ) + } + + #[test] + fn can_parse_four_values() { + assert_eq!( + Padding::from_str("[35.4,74.6 ,53.1, 25.0]"), + Ok(Padding { + top: 35.4, + right: 74.6, + bottom: 53.1, + left: 25.0, + }), + ) + } + + #[test] + fn cant_parse_five_values() { + assert_eq!( + Padding::from_str("[1,2,3,4,5]"), + Err(ParsePaddingError::WrongNumberOfValues(5)), + ) + } + + #[test] + fn cant_parse_invalid_floats() { + assert_eq!( + Padding::from_str("[1f,2,3,4]"), + Err(ParsePaddingError::ParseFloatError( + f32::from_str("1f").expect_err("") + )) + ) + } + + #[test] + fn cant_parse_with_missing_bracket() { + assert_eq!( + Padding::from_str("1,2,3,4,5]"), + Err(ParsePaddingError::MissingBracket) + ); + + assert_eq!( + Padding::from_str("[1,2,3,4,5"), + Err(ParsePaddingError::MissingBracket) + ); + + assert_eq!( + Padding::from_str("1,2,3,4,5"), + Err(ParsePaddingError::MissingBracket) + ) + } + + #[test] + fn cant_parse_empty_string() { + assert_eq!(Padding::from_str(""), Err(ParsePaddingError::Empty)) } } diff --git a/src/values/rotation.rs b/src/values/rotation.rs index 2b7a223..da291b2 100644 --- a/src/values/rotation.rs +++ b/src/values/rotation.rs @@ -3,14 +3,16 @@ use std::str::FromStr; use iced::{Radians, Rotation}; -use super::ValueFromStr; +use super::Value; -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Clone, PartialEq)] pub enum ParseRotationError { #[error("float parsing error: {0}")] ParseFloatError(ParseFloatError), #[error("invalid prefix")] InvalidPrefix, + #[error("cannot parse rotation from empty string")] + Empty, } impl From for ParseRotationError { @@ -19,13 +21,17 @@ impl From for ParseRotationError { } } -impl ValueFromStr for Rotation { +impl Value for Rotation { type Err = ParseRotationError; - fn value_from_str(s: &str) -> Result { - if s.starts_with(|c: char| !c.is_digit(10)) { + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Err(ParseRotationError::Empty); + } + + if s.starts_with(|c: char| !c.is_ascii_digit()) { let (prefix, value) = s.split_at(1); - match prefix { + match prefix.to_lowercase().as_str() { "s" => Ok(Rotation::Solid(Radians(f32::from_str(value)?))), "f" => Ok(Rotation::Floating(Radians(f32::from_str(value)?))), _ => Err(ParseRotationError::InvalidPrefix), @@ -34,4 +40,71 @@ impl ValueFromStr for Rotation { Ok(Rotation::Floating(Radians(f32::from_str(s)?))) } } + + fn to_string(&self) -> String { + match self { + Self::Floating(value) => format!("f{}", value), + Self::Solid(value) => format!("s{}", value), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_parse_without_prefix() { + assert_eq!( + Rotation::from_str("10.5"), + Ok(Rotation::Floating(Radians(10.5))) + ) + } + + #[test] + fn can_parse_with_s_prefix() { + assert_eq!( + Rotation::from_str("s12.3"), + Ok(Rotation::Solid(Radians(12.3))) + ) + } + + #[test] + fn can_parse_with_f_prefix() { + assert_eq!( + Rotation::from_str("f16.9"), + Ok(Rotation::Floating(Radians(16.9))) + ) + } + + #[test] + fn can_parse_with_uppercase_prefix() { + assert_eq!( + Rotation::from_str("S9.4"), + Ok(Rotation::Solid(Radians(9.4))) + ) + } + + #[test] + fn cant_parse_invalid_prefix() { + assert_eq!( + Rotation::from_str("a6.0"), + Err(ParseRotationError::InvalidPrefix) + ) + } + + #[test] + fn cant_parse_invalid_float() { + assert_eq!( + Rotation::from_str("3.a"), + Err(ParseRotationError::ParseFloatError( + f32::from_str("3.a").expect_err("") + )) + ) + } + + #[test] + fn cant_parse_empty_string() { + assert_eq!(Rotation::from_str(""), Err(ParseRotationError::Empty)) + } } -- cgit v1.2.3 From 45abe9baebde17fe9e424219a7951f54b57803b2 Mon Sep 17 00:00:00 2001 From: pml68 Date: Sun, 23 Mar 2025 02:49:28 +0100 Subject: feat: finish `ApplyOptions` impls --- src/options.rs | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 259 insertions(+), 1 deletion(-) (limited to 'src/options.rs') diff --git a/src/options.rs b/src/options.rs index 90fc63f..2dc25d7 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,8 +1,10 @@ use std::collections::BTreeMap; +use std::str::FromStr; +use iced::widget::text::LineHeight; #[allow(unused_imports)] use iced::widget::{Button, Column, Container, Image, Row, Svg, Text}; -use iced::{Padding, Rotation}; +use iced::{Alignment, ContentFit, Length, Padding, Pixels, Rotation}; use crate::values::Value; @@ -14,24 +16,206 @@ impl ApplyOptions for Button<'_, Message> { fn apply_options(self, options: BTreeMap>) -> Self { let mut button = self; + if let Some(width) = options.get("width").expect("width key") { + let width = Length::from_str(width).unwrap(); + button = button.width(width); + } + + if let Some(height) = options.get("height").expect("height key") { + let height = Length::from_str(height).unwrap(); + button = button.height(height); + } + if let Some(padding) = options.get("padding").expect("padding key") { let padding = Padding::from_str(padding).unwrap(); button = button.padding(padding); } + if let Some(clip) = options.get("clip").expect("clip key") { + let clip = bool::from_str(clip).unwrap(); + button = button.clip(clip); + } + button } } +impl ApplyOptions for Text<'_> { + fn apply_options(self, options: BTreeMap>) -> Self { + let mut text = self; + + if let Some(size) = options.get("size").expect("size key") { + let size = Pixels::from_str(size).unwrap(); + text = text.size(size); + } + + if let Some(line_height) = + options.get("line_height").expect("line_height key") + { + let line_height = LineHeight::from_str(line_height).unwrap(); + text = text.line_height(line_height); + } + + if let Some(width) = options.get("width").expect("width key") { + let width = Length::from_str(width).unwrap(); + text = text.width(width); + } + + if let Some(height) = options.get("height").expect("height key") { + let height = Length::from_str(height).unwrap(); + text = text.height(height); + } + + if let Some(align_x) = options.get("align_x").expect("align_x key") { + let align_x = Alignment::from_str(align_x).unwrap(); + text = text.align_x(align_x); + } + + if let Some(align_y) = options.get("align_y").expect("align_y key") { + let align_y = Alignment::from_str(align_y).unwrap(); + text = text.align_y(align_y); + } + + text + } +} + +impl ApplyOptions for Container<'_, Message> { + fn apply_options(self, options: BTreeMap>) -> Self { + let mut container = self; + + if let Some(padding) = options.get("padding").expect("padding key") { + let padding = Padding::from_str(padding).unwrap(); + container = container.padding(padding); + } + + if let Some(width) = options.get("width").expect("width key") { + let width = Length::from_str(width).unwrap(); + container = container.width(width); + } + + if let Some(height) = options.get("height").expect("height key") { + let height = Length::from_str(height).unwrap(); + container = container.height(height); + } + + if let Some(max_width) = + options.get("max_width").expect("max_width key") + { + let max_width = Pixels::from_str(max_width).unwrap(); + container = container.max_width(max_width); + } + + if let Some(max_height) = + options.get("max_height").expect("max_height key") + { + let max_height = Pixels::from_str(max_height).unwrap(); + container = container.max_height(max_height); + } + + if let Some(center_x) = options.get("center_x").expect("center_x key") { + let center_x = Length::from_str(center_x).unwrap(); + container = container.center_x(center_x); + } + + if let Some(center_y) = options.get("center_y").expect("center_y key") { + let center_y = Length::from_str(center_y).unwrap(); + container = container.center_y(center_y); + } + + if let Some(center) = options.get("center").expect("center key") { + let center = Length::from_str(center).unwrap(); + container = container.center(center); + } + + if let Some(align_left) = + options.get("align_left").expect("align_left key") + { + let align_left = Length::from_str(align_left).unwrap(); + container = container.align_left(align_left); + } + + if let Some(align_right) = + options.get("align_right").expect("align_right key") + { + let align_right = Length::from_str(align_right).unwrap(); + container = container.align_right(align_right); + } + + if let Some(align_top) = + options.get("align_top").expect("align_top key") + { + let align_top = Length::from_str(align_top).unwrap(); + container = container.align_top(align_top); + } + + if let Some(align_bottom) = + options.get("align_bottom").expect("align_bottom key") + { + let align_bottom = Length::from_str(align_bottom).unwrap(); + container = container.align_bottom(align_bottom); + } + + if let Some(align_x) = options.get("align_x").expect("align_x key") { + let align_x = Alignment::from_str(align_x).unwrap(); + container = container.align_x(align_x); + } + + if let Some(align_y) = options.get("align_y").expect("align_y key") { + let align_y = Alignment::from_str(align_y).unwrap(); + container = container.align_y(align_y); + } + + if let Some(clip) = options.get("clip").expect("clip key") { + let clip = bool::from_str(clip).unwrap(); + container = container.clip(clip); + } + + container + } +} + impl ApplyOptions for Column<'_, Message> { fn apply_options(self, options: BTreeMap>) -> Self { let mut column = self; + if let Some(spacing) = options.get("spacing").expect("spacing key") { + let spacing = Pixels::from_str(spacing).unwrap(); + column = column.spacing(spacing); + } + if let Some(padding) = options.get("padding").expect("padding key") { let padding = Padding::from_str(padding).unwrap(); column = column.padding(padding); } + if let Some(width) = options.get("width").expect("width key") { + let width = Length::from_str(width).unwrap(); + column = column.width(width); + } + + if let Some(height) = options.get("height").expect("height key") { + let height = Length::from_str(height).unwrap(); + column = column.height(height); + } + + if let Some(max_width) = + options.get("max_width").expect("max_width key") + { + let max_width = Pixels::from_str(max_width).unwrap(); + column = column.max_width(max_width); + } + + if let Some(align_x) = options.get("align_x").expect("align_x key") { + let align_x = Alignment::from_str(align_x).unwrap(); + column = column.align_x(align_x); + } + + if let Some(clip) = options.get("clip").expect("clip key") { + let clip = bool::from_str(clip).unwrap(); + column = column.clip(clip); + } + column } } @@ -40,11 +224,36 @@ impl ApplyOptions for Row<'_, Message> { fn apply_options(self, options: BTreeMap>) -> Self { let mut row = self; + if let Some(spacing) = options.get("spacing").expect("spacing key") { + let spacing = Pixels::from_str(spacing).unwrap(); + row = row.spacing(spacing); + } + if let Some(padding) = options.get("padding").expect("padding key") { let padding = Padding::from_str(padding).unwrap(); row = row.padding(padding); } + if let Some(width) = options.get("width").expect("width key") { + let width = Length::from_str(width).unwrap(); + row = row.width(width); + } + + if let Some(height) = options.get("height").expect("height key") { + let height = Length::from_str(height).unwrap(); + row = row.height(height); + } + + if let Some(align_y) = options.get("align_y").expect("align_y key") { + let align_y = Alignment::from_str(align_y).unwrap(); + row = row.align_y(align_y); + } + + if let Some(clip) = options.get("clip").expect("clip key") { + let clip = bool::from_str(clip).unwrap(); + row = row.clip(clip); + } + row } } @@ -53,11 +262,38 @@ impl ApplyOptions for Image { fn apply_options(self, options: BTreeMap>) -> Self { let mut image = self; + if let Some(width) = options.get("width").expect("width key") { + let width = Length::from_str(width).unwrap(); + image = image.width(width); + } + + if let Some(height) = options.get("height").expect("height key") { + let height = Length::from_str(height).unwrap(); + image = image.height(height); + } + + if let Some(content_fit) = + options.get("content_fit").expect("content_fit key") + { + let content_fit = ContentFit::from_str(content_fit).unwrap(); + image = image.content_fit(content_fit); + } + if let Some(rotation) = options.get("rotation").expect("rotation key") { let rotation = Rotation::from_str(rotation).unwrap(); image = image.rotation(rotation); } + if let Some(opacity) = options.get("opacity").expect("opacity key") { + let opacity = f32::from_str(opacity).unwrap(); + image = image.opacity(opacity); + } + + if let Some(scale) = options.get("scale").expect("scale key") { + let scale = f32::from_str(scale).unwrap(); + image = image.scale(scale); + } + image } } @@ -66,11 +302,33 @@ impl ApplyOptions for Svg<'_> { fn apply_options(self, options: BTreeMap>) -> Self { let mut svg = self; + if let Some(width) = options.get("width").expect("width key") { + let width = Length::from_str(width).unwrap(); + svg = svg.width(width); + } + + if let Some(height) = options.get("height").expect("height key") { + let height = Length::from_str(height).unwrap(); + svg = svg.height(height); + } + + if let Some(content_fit) = + options.get("content_fit").expect("content_fit key") + { + let content_fit = ContentFit::from_str(content_fit).unwrap(); + svg = svg.content_fit(content_fit); + } + if let Some(rotation) = options.get("rotation").expect("rotation key") { let rotation = Rotation::from_str(rotation).unwrap(); svg = svg.rotation(rotation); } + if let Some(opacity) = options.get("opacity").expect("opacity key") { + let opacity = f32::from_str(opacity).unwrap(); + svg = svg.opacity(opacity); + } + svg } } -- cgit v1.2.3