Skip to content

Commit

Permalink
feat: add padding and margins for text elements as Spacing
Browse files Browse the repository at this point in the history
  • Loading branch information
JarKz authored Jul 24, 2024
1 parent 510af14 commit 598353e
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 39 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ size = 4
radius = 10
color = "#000"

[display.title]
margin = { top = 5, left = 25 }

[display.body]
margin = { top = 12, left = 25 }

[[app]]
name = "Telegram Desktop"
[app.display]
Expand Down
27 changes: 20 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use once_cell::sync::Lazy;
use serde::Deserialize;
use std::{collections::HashMap, fs, path::Path, str::Chars};

use self::sorting::Sorting;

pub mod sorting;
pub mod spacing;

use sorting::Sorting;
use spacing::Spacing;

pub static CONFIG: Lazy<Config> = Lazy::new(Config::init);

Expand Down Expand Up @@ -250,7 +252,7 @@ impl From<String> for Anchor {
pub struct DisplayConfig {
image_size: Option<u16>,

padding: Option<u8>,
padding: Option<Spacing>,

border: Option<Border>,

Expand All @@ -267,8 +269,8 @@ impl DisplayConfig {
self.image_size.unwrap()
}

pub fn padding(&self) -> u8 {
self.padding.unwrap()
pub fn padding(&self) -> &Spacing {
self.padding.as_ref().unwrap()
}

pub fn border(&self) -> &Border {
Expand Down Expand Up @@ -301,7 +303,7 @@ impl DisplayConfig {
}

if self.padding.is_none() {
self.padding = Some(0);
self.padding = Some(Default::default());
}

if self.border.is_none() {
Expand Down Expand Up @@ -507,6 +509,7 @@ impl Border {

#[derive(Debug, Deserialize, Default, Clone)]
pub struct TextProperty {
margin: Option<Spacing>,
alignment: Option<TextAlignment>,
line_spacing: Option<u8>,
}
Expand All @@ -522,6 +525,10 @@ pub enum TextAlignment {
}

impl TextProperty {
pub fn margin(&self) -> &Spacing {
self.margin.as_ref().unwrap()
}

pub fn alignment(&self) -> &TextAlignment {
self.alignment.as_ref().unwrap()
}
Expand All @@ -531,6 +538,10 @@ impl TextProperty {
}

pub fn fill_empty_by_default(&mut self, entity: &str) {
if self.margin.is_none() {
self.margin = Some(Default::default());
}

if self.alignment.is_none() {
if entity == "title" {
self.alignment = Some(TextAlignment::Center);
Expand Down Expand Up @@ -575,7 +586,7 @@ impl AppConfig {
if let Some(display) = self.display.as_mut() {
display.image_size = display.image_size.or(other.image_size);

display.padding = display.padding.or(other.padding);
display.padding = display.padding.clone().or(other.padding.clone());

if let Some(border) = display.border.as_mut() {
let other_border = other.border(); // The other type shall have border
Expand All @@ -600,6 +611,7 @@ impl AppConfig {
if let Some(title) = display.title.as_mut() {
let other_title = other.title();

title.margin = title.margin.clone().or(other_title.margin.clone());
title.alignment = title.alignment.clone().or(other_title.alignment.clone());
title.line_spacing = title.line_spacing.or(other_title.line_spacing);
} else {
Expand All @@ -609,6 +621,7 @@ impl AppConfig {
if let Some(body) = display.body.as_mut() {
let other_body = other.body();

body.margin = body.margin.clone().or(other_body.margin.clone());
body.alignment = body.alignment.clone().or(other_body.alignment.clone());
body.line_spacing = body.line_spacing.or(other_body.line_spacing);
} else {
Expand Down
240 changes: 240 additions & 0 deletions src/config/spacing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
use std::{collections::HashMap, marker::PhantomData};

use serde::{de::Visitor, Deserialize};

#[derive(Debug, Default, Clone)]
pub struct Spacing {
top: u8,
right: u8,
bottom: u8,
left: u8,
}

impl Spacing {
const POSSIBLE_KEYS: [&'static str; 6] =
["top", "right", "bottom", "left", "vertical", "horizontal"];

fn all_directional(val: u8) -> Self {
Self {
top: val,
bottom: val,
right: val,
left: val,
}
}

fn cross(vertical: u8, horizontal: u8) -> Self {
Self {
top: vertical,
bottom: vertical,
right: horizontal,
left: horizontal,
}
}

pub fn top(&self) -> u8 {
self.top
}

pub fn right(&self) -> u8 {
self.right
}

pub fn bottom(&self) -> u8 {
self.bottom
}

pub fn left(&self) -> u8 {
self.left
}

pub fn shrink(&self, width: &mut usize, height: &mut usize) {
*width -= self.left as usize + self.right as usize;
*height -= self.top as usize + self.bottom as usize;
}
}

impl From<i64> for Spacing {
fn from(value: i64) -> Self {
Spacing::all_directional(value.clamp(0, u8::MAX as i64) as u8)
}
}

impl From<Vec<u8>> for Spacing {
fn from(value: Vec<u8>) -> Self {
match value.len() {
1 => Spacing::all_directional(value[0]),
2 => Spacing::cross(value[0], value[1]),
3 => Spacing {
top: value[0],
right: value[1],
left: value[1],
bottom: value[2],
},
4 => Spacing {
top: value[0],
right: value[1],
bottom: value[2],
left: value[3],
},
_ => unreachable!(),
}
}
}

impl From<HashMap<String, u8>> for Spacing {
fn from(map: HashMap<String, u8>) -> Self {
let vertical = map.get("vertical");
let horizontal = map.get("horizontal");
let top = map.get("top");
let bottom = map.get("bottom");
let right = map.get("right");
let left = map.get("left");

Self {
top: *top.or(vertical).unwrap_or(&0),
bottom: *bottom.or(vertical).unwrap_or(&0),
right: *right.or(horizontal).unwrap_or(&0),
left: *left.or(horizontal).unwrap_or(&0),
}
}
}

impl<'de> Deserialize<'de> for Spacing {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(PaddingVisitor(PhantomData))
}
}

struct PaddingVisitor<T>(PhantomData<fn() -> T>);

impl<'de, T> Visitor<'de> for PaddingVisitor<T>
where
T: Deserialize<'de> + From<HashMap<String, u8>> + From<Vec<u8>> + From<i64>,
{
type Value = T;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
formatter,
r#"either u8, [u8, u8], [u8, u8, u8], [u8, u8, u8, u8] or Table.
Example:
# All-directional margin
margin = 3
# The application can also apply the CSS-like values:
# Applies vertical and horizontal paddings respectively
padding = [0, 5]
# Applies top, horizontal and bottom paddings respectively
margin = [3, 2, 5]
# Applies top, right, bottom, left paddings respectively
padding = [1, 2, 3, 4]
# When you want to declare in explicit way:
# Sets only top padding
padding = {{ top = 3 }}
# Sets only top and right padding
padding = {{ top = 5, right = 6 }}
# Insead of
# padding = {{ top = 5, right = 6, bottom = 5 }}
# Write
padding = {{ vertical = 5, right = 6 }}
# If gots collision of values the error will throws because of ambuguity
# padding = {{ top = 5, vertical = 6 }}
# You can apply the same way for margin
margin = {{ top = 5, horizontal = 10 }}"#
)
}

fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut fields = vec![];
while let Some(value) = seq.next_element::<u8>()? {
fields.push(value);
}

match fields.len() {
1..=4 => Ok(fields.into()),
other => Err(serde::de::Error::invalid_length(other, &self)),
}
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut custom_padding = HashMap::new();

while let Some((key, value)) = map.next_entry::<String, u8>()? {
if !Spacing::POSSIBLE_KEYS.contains(&key.as_str()) {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(key.as_str()),
&self,
));
}

match key.as_str() {
"top" | "bottom" if custom_padding.contains_key("vertical") => {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(key.as_str()),
&self,
))
}
"vertical"
if custom_padding.contains_key("top")
|| custom_padding.contains_key("bottom") =>
{
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(key.as_str()),
&self,
))
}
"right" | "left" if custom_padding.contains_key("horizontal") => {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(key.as_str()),
&self,
))
}
"horizontal"
if custom_padding.contains_key("right")
|| custom_padding.contains_key("left") =>
{
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(key.as_str()),
&self,
))
}
_ => (),
}

custom_padding.insert(key, value);
}

if !custom_padding.is_empty() {
Ok(custom_padding.into())
} else {
Err(serde::de::Error::invalid_length(0, &self))
}
}
}
Loading

0 comments on commit 598353e

Please sign in to comment.