├── .gitignore ├── core ├── src │ ├── lib.rs │ ├── utils.rs │ ├── components │ │ ├── mod.rs │ │ ├── view.rs │ │ ├── vstack.rs │ │ └── text.rs │ └── theme.rs └── Cargo.toml ├── Cargo.toml ├── playground ├── Cargo.toml └── src │ ├── component.rs │ └── main.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | target 3 | Cargo.lock 4 | foo.css 5 | foo.html 6 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod components; 2 | pub mod theme; 3 | pub mod utils; 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "core", 5 | "playground" 6 | ] -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "core" 3 | version = "0.1.0" 4 | authors = ["adamaho"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | nanoid = "0.3.0" -------------------------------------------------------------------------------- /playground/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "playground" 3 | version = "0.1.0" 4 | authors = ["adamaho "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | core = { path = "../core" } 9 | tokio = { version = "1", features = ["full"] } 10 | warp = "0.3" 11 | 12 | -------------------------------------------------------------------------------- /core/src/utils.rs: -------------------------------------------------------------------------------- 1 | use nanoid::nanoid; 2 | 3 | pub fn make_class() -> String { 4 | let alphabet: [char; 26] = [ 5 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 6 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 7 | ]; 8 | 9 | format!("mule-{}", nanoid!(6, &alphabet)) 10 | } -------------------------------------------------------------------------------- /core/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod text; 2 | pub mod view; 3 | pub mod vstack; 4 | 5 | /// Implemented to provide a struct or enum the ability to render custom html or css. 6 | pub trait Component { 7 | /// html 8 | fn html(&self) -> String; 9 | 10 | /// css 11 | fn css(&self) -> String; 12 | } 13 | 14 | /// Trait that is required to convert to css 15 | pub trait CSS { 16 | fn css(&self) -> String; 17 | } 18 | 19 | #[macro_export] 20 | macro_rules! html { 21 | ($tag:ident, $class:ident, $content:ident) => { 22 | format!("<{} class=\"{}\">{}", $tag, $class, $content, $tag) 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /playground/src/component.rs: -------------------------------------------------------------------------------- 1 | use core::components::text::{Text, TextAlignment, TextType}; 2 | use core::components::vstack::VStack; 3 | use core::components::Component; 4 | 5 | pub struct MyCustomComponent; 6 | 7 | pub fn write_paragraphs() -> Box { 8 | let mut paragraphs: Vec> = Vec::new(); 9 | 10 | for _ in 0..10 { 11 | paragraphs.push(Box::new( 12 | Text::new("this is a paragraph").with_alignment(TextAlignment::Center), 13 | )); 14 | } 15 | 16 | Box::new(VStack::new(paragraphs)) 17 | } 18 | 19 | impl MyCustomComponent { 20 | pub fn new() -> VStack { 21 | VStack::new(vec![ 22 | Box::new( 23 | Text::new("Mule") 24 | .with_text_type(TextType::Heading1) 25 | .with_alignment(TextAlignment::Center), 26 | ), 27 | write_paragraphs(), 28 | ]) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /playground/src/main.rs: -------------------------------------------------------------------------------- 1 | use warp::Filter; 2 | 3 | pub mod component; 4 | 5 | use core::components::text::{Text, TextType}; 6 | use core::components::view::View; 7 | use core::components::vstack::{VStack}; 8 | use core::theme::{Color, Edges}; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let route = warp::any().map(move || { 13 | warp::reply::html( 14 | View::new( 15 | VStack::new(vec![ 16 | Box::new(Text::new("My Doggie Site").with_text_type(TextType::Heading1)), 17 | Box::new( 18 | Text::new("My Dog is the best dog in the world") 19 | ), 20 | ]) 21 | .with_padding(Edges::Vertical, 1.5) 22 | .with_padding(Edges::Horizontal, 3.5) 23 | ) 24 | .with_title("My Website Title") 25 | .with_background(Color::Gray900) 26 | .render(), 27 | ) 28 | }); 29 | 30 | warp::serve(route).run(([127, 0, 0, 1], 3030)).await; 31 | } 32 | -------------------------------------------------------------------------------- /core/src/components/view.rs: -------------------------------------------------------------------------------- 1 | use super::Component; 2 | use crate::theme::Color; 3 | 4 | pub struct View 5 | where 6 | T: Component, 7 | { 8 | child: T, 9 | title: String, 10 | background: Color 11 | } 12 | 13 | impl View 14 | where 15 | T: Component, 16 | { 17 | pub fn new(child: T) -> View { 18 | View { 19 | child, 20 | title: String::from("title"), 21 | background: Color::Gray50 22 | } 23 | } 24 | 25 | /// Change the document title of the page 26 | pub fn with_title(mut self, title: &str) -> View { 27 | self.title = String::from(title); 28 | self 29 | } 30 | 31 | 32 | pub fn with_background(mut self, color: Color) -> View { 33 | self.background = color; 34 | self 35 | } 36 | 37 | pub fn render(&self) -> String { 38 | format!( 39 | r#" 40 | 41 | 42 | 43 | {} 44 | 45 | {} 46 | 65 | 66 | "#, 67 | self.title, 68 | self.child.html(), 69 | self.background.hex(), 70 | self.child.css() 71 | ) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mule (Definitely a Work in Progress) 2 | 3 | The night I started this project I was on the couch drinking a Moscow Mule. Couldn't think of a better name, so for now its called mule. TLDR; Naming is hard. 4 | 5 | Truthfully, not sure if it is worth the work because HTML, CSS and Javascript do this a lot better than rust. This is more of a fun thought experiment to see if there would be any benefits to doing this in rust. So far the only benefit I see is a developer doesnt need to know HTML or CSS to create a static blog site in rust. 6 | 7 | ### Example 8 | Use any rust web framework like actix, rocket, warp or tide and return a `text/html` string in order. Below is the example of what it looks like to create a simple html page using Mule and Warp as the web framework. 9 | 10 | ```rust 11 | use warp::Filter; 12 | 13 | pub mod component; 14 | 15 | use core::components::view::View; 16 | use core::components::text::{Text, TextType, TextColor}; 17 | use core::components::vstack::VStack; 18 | 19 | #[tokio::main] 20 | async fn main() { 21 | let route = 22 | warp::any().map(move || warp::reply::html(View::new( 23 | VStack::new(vec![ 24 | Box::new(Text::new("My Doggie Site").with_text_type(TextType::Heading1)), 25 | Box::new(Text::new("My Dog is the best dog in the world").with_color(TextColor::Purple)) 26 | ]) 27 | ) 28 | .with_title("My Website Title") 29 | .render())); 30 | 31 | warp::serve(route).run(([127, 0, 0, 1], 3030)).await; 32 | } 33 | ``` 34 | 35 | This creates the following HTML and CSS: 36 | 37 | ```html 38 | 39 | 40 | 41 | My Website Title 42 | 43 |

My Doggie Site

44 |

My Dog is the best dog in the world

45 |
46 | 61 | 62 | ``` -------------------------------------------------------------------------------- /core/src/components/vstack.rs: -------------------------------------------------------------------------------- 1 | use super::{Component, CSS}; 2 | 3 | use crate::html; 4 | use crate::theme::{Color, Padding, Edges}; 5 | use crate::utils::make_class; 6 | 7 | pub struct VStack { 8 | children: Vec>, 9 | class: String, 10 | style: Style, 11 | } 12 | 13 | impl VStack { 14 | pub fn new(children: Vec>) -> VStack { 15 | VStack { 16 | children, 17 | class: make_class(), 18 | style: Style { 19 | horizontal_alignment: VStackHorizontalAlignment::Leading, 20 | background: Color::Gray50, 21 | padding: Padding { 22 | top: 0.0, 23 | right: 0.0, 24 | bottom: 0.0, 25 | left: 0.0 26 | } 27 | }, 28 | } 29 | } 30 | 31 | pub fn with_alignment(mut self, alignment: VStackHorizontalAlignment) -> VStack { 32 | self.style.horizontal_alignment = alignment; 33 | self 34 | } 35 | 36 | pub fn with_background(mut self, color: Color) -> VStack { 37 | self.style.background = color; 38 | self 39 | } 40 | 41 | /// TODO: Turn this into a derive macro for better reusability 42 | pub fn with_padding(mut self, edges: Edges, amount: f32) -> VStack { 43 | match edges { 44 | Edges::Top => { 45 | self.style.padding.top = amount; 46 | self 47 | }, 48 | Edges::Right => { 49 | self.style.padding.right = amount; 50 | self 51 | }, 52 | Edges::Bottom => { 53 | self.style.padding.bottom = amount; 54 | self 55 | }, 56 | Edges::Left => { 57 | self.style.padding.left = amount; 58 | self 59 | }, 60 | Edges::All => { 61 | self.style.padding.top = amount; 62 | self.style.padding.right = amount; 63 | self.style.padding.bottom = amount; 64 | self.style.padding.left = amount; 65 | self 66 | }, 67 | Edges::Horizontal => { 68 | self.style.padding.right = amount; 69 | self.style.padding.left = amount; 70 | self 71 | }, 72 | Edges::Vertical => { 73 | self.style.padding.top = amount; 74 | self.style.padding.bottom = amount; 75 | self 76 | }, 77 | } 78 | } 79 | } 80 | 81 | impl Component for VStack { 82 | fn html(&self) -> String { 83 | let mut content = String::new(); 84 | 85 | for child in self.children.iter() { 86 | content.push_str(&format!("{}\n", child.html())); 87 | } 88 | 89 | let tag = "div"; 90 | let class = &self.class; 91 | 92 | html!(tag, class, content) 93 | } 94 | 95 | fn css(&self) -> String { 96 | let mut css = String::new(); 97 | 98 | for child in self.children.iter() { 99 | css.push_str(&format!("{}\n", child.css())); 100 | } 101 | 102 | format!( 103 | ".{} {{ height: 100%; {}{} }} {}", 104 | self.class, 105 | self.style.horizontal_alignment.css(), 106 | self.style.padding.css(), 107 | css 108 | ) 109 | } 110 | } 111 | 112 | pub enum VStackHorizontalAlignment { 113 | Leading, 114 | Center, 115 | Trailing, 116 | } 117 | 118 | impl CSS for VStackHorizontalAlignment { 119 | fn css(&self) -> String { 120 | let horizontal_alignment = match *self { 121 | VStackHorizontalAlignment::Leading => "flex-start", 122 | VStackHorizontalAlignment::Center => "center", 123 | VStackHorizontalAlignment::Trailing => "flex-end", 124 | }; 125 | 126 | format!( 127 | r#" 128 | display: flex; 129 | flex-direction: column; 130 | align-items: {}; 131 | "#, 132 | horizontal_alignment 133 | ) 134 | } 135 | } 136 | 137 | struct Style { 138 | background: Color, 139 | horizontal_alignment: VStackHorizontalAlignment, 140 | padding: Padding 141 | } 142 | -------------------------------------------------------------------------------- /core/src/theme.rs: -------------------------------------------------------------------------------- 1 | use crate::components::CSS; 2 | 3 | pub enum Color { 4 | Gray50, 5 | Gray100, 6 | Gray200, 7 | Gray300, 8 | Gray400, 9 | Gray500, 10 | Gray600, 11 | Gray700, 12 | Gray800, 13 | Gray900, 14 | Red50, 15 | Red100, 16 | Red200, 17 | Red300, 18 | Red400, 19 | Red500, 20 | Red600, 21 | Red700, 22 | Red800, 23 | Red900, 24 | Yellow50, 25 | Yellow100, 26 | Yellow200, 27 | Yellow300, 28 | Yellow400, 29 | Yellow500, 30 | Yellow600, 31 | Yellow700, 32 | Yellow800, 33 | Yellow900, 34 | Green50, 35 | Green100, 36 | Green200, 37 | Green300, 38 | Green400, 39 | Green500, 40 | Green600, 41 | Green700, 42 | Green800, 43 | Green900, 44 | Blue50, 45 | Blue100, 46 | Blue200, 47 | Blue300, 48 | Blue400, 49 | Blue500, 50 | Blue600, 51 | Blue700, 52 | Blue800, 53 | Blue900, 54 | Indigo50, 55 | Indigo100, 56 | Indigo200, 57 | Indigo300, 58 | Indigo400, 59 | Indigo500, 60 | Indigo600, 61 | Indigo700, 62 | Indigo800, 63 | Indigo900, 64 | Purple50, 65 | Purple100, 66 | Purple200, 67 | Purple300, 68 | Purple400, 69 | Purple500, 70 | Purple600, 71 | Purple700, 72 | Purple800, 73 | Purple900, 74 | Pink50, 75 | Pink100, 76 | Pink200, 77 | Pink300, 78 | Pink400, 79 | Pink500, 80 | Pink600, 81 | Pink700, 82 | Pink800, 83 | Pink900, 84 | } 85 | 86 | impl Color { 87 | 88 | /// TODO: add RGBA 89 | pub fn hex(&self) -> &'static str { 90 | match *self { 91 | Color::Gray50 => "#F9FAFB", 92 | Color::Gray100 => "#F3F4F6", 93 | Color::Gray200 => "#E5E7EB", 94 | Color::Gray300 => "#D1D5DB", 95 | Color::Gray400 => "#9CA3AF", 96 | Color::Gray500 => "#6B7280", 97 | Color::Gray600 => "#4B5563", 98 | Color::Gray700 => "#374151", 99 | Color::Gray800 => "#1F2937", 100 | Color::Gray900 => "#111827", 101 | Color::Red50 => "#FEF2F2", 102 | Color::Red100 => "#FEE2E2", 103 | Color::Red200 => "#FECACA", 104 | Color::Red300 => "#FCA5A5", 105 | Color::Red400 => "#F87171", 106 | Color::Red500 => "#EF4444", 107 | Color::Red600 => "#DC2626", 108 | Color::Red700 => "#B91C1C", 109 | Color::Red800 => "#991B1B", 110 | Color::Red900 => "#7F1D1D", 111 | Color::Yellow50 => "#FFFBEB", 112 | Color::Yellow100 => "#FEF3C7", 113 | Color::Yellow200 => "#FDE68A", 114 | Color::Yellow300 => "#FCD34D", 115 | Color::Yellow400 => "#FBBF24", 116 | Color::Yellow500 => "#F59E0B", 117 | Color::Yellow600 => "#D97706", 118 | Color::Yellow700 => "#B45309", 119 | Color::Yellow800 => "#92400E", 120 | Color::Yellow900 => "#78350F", 121 | Color::Green50 => "#ECFDF5", 122 | Color::Green100 => "#D1FAE5", 123 | Color::Green200 => "#A7F3D0", 124 | Color::Green300 => "#6EE7B7", 125 | Color::Green400 => "#34D399", 126 | Color::Green500 => "#10B981", 127 | Color::Green600 => "#059669", 128 | Color::Green700 => "#047857", 129 | Color::Green800 => "#065F46", 130 | Color::Green900 => "#064E3B", 131 | Color::Blue50 => "#EFF6FF", 132 | Color::Blue100 => "#DBEAFE", 133 | Color::Blue200 => "#BFDBFE", 134 | Color::Blue300 => "#93C5FD", 135 | Color::Blue400 => "#60A5FA", 136 | Color::Blue500 => "#3B82F6", 137 | Color::Blue600 => "#2563EB", 138 | Color::Blue700 => "#1D4ED8", 139 | Color::Blue800 => "#1E40AF", 140 | Color::Blue900 => "#1E3A8A", 141 | Color::Indigo50 => "#EEF2FF", 142 | Color::Indigo100 => "#E0E7FF", 143 | Color::Indigo200 => "#C7D2FE", 144 | Color::Indigo300 => "#A5B4FC", 145 | Color::Indigo400 => "#818CF8", 146 | Color::Indigo500 => "#6366F1", 147 | Color::Indigo600 => "#4F46E5", 148 | Color::Indigo700 => "#4338CA", 149 | Color::Indigo800 => "#3730A3", 150 | Color::Indigo900 => "#312E81", 151 | Color::Purple50 => "#F5F3FF", 152 | Color::Purple100 => "#EDE9FE", 153 | Color::Purple200 => "#DDD6FE", 154 | Color::Purple300 => "#C4B5FD", 155 | Color::Purple400 => "#A78BFA", 156 | Color::Purple500 => "#8B5CF6", 157 | Color::Purple600 => "#7C3AED", 158 | Color::Purple700 => "#6D28D9", 159 | Color::Purple800 => "#5B21B6", 160 | Color::Purple900 => "#4C1D95", 161 | Color::Pink50 => "#FDF2F8", 162 | Color::Pink100 => "#FCE7F3", 163 | Color::Pink200 => "#FBCFE8", 164 | Color::Pink300 => "#F9A8D4", 165 | Color::Pink400 => "#F472B6", 166 | Color::Pink500 => "#EC4899", 167 | Color::Pink600 => "#DB2777", 168 | Color::Pink700 => "#BE185D", 169 | Color::Pink800 => "#9D174D", 170 | Color::Pink900 => "#831843", 171 | } 172 | } 173 | } 174 | 175 | pub struct Padding { 176 | /// padding in rem to apply to top 177 | pub top: f32, 178 | 179 | /// padding in rem to apply to right 180 | pub right: f32, 181 | 182 | /// padding in rem to apply to bottom 183 | pub bottom: f32, 184 | 185 | /// padding in rem to apply to left 186 | pub left: f32, 187 | } 188 | 189 | impl CSS for Padding { 190 | fn css(&self) -> String { 191 | format!("padding: {}rem {}rem {}rem {}rem;", self.top, self.right, self.bottom, self.left) 192 | } 193 | } 194 | 195 | pub enum Edges { 196 | Top, 197 | Right, 198 | Bottom, 199 | Left, 200 | All, 201 | Horizontal, 202 | Vertical 203 | } 204 | 205 | -------------------------------------------------------------------------------- /core/src/components/text.rs: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | use std::fmt; 3 | 4 | use super::{Component, CSS}; 5 | use crate::theme::Color; 6 | use crate::html; 7 | use crate::utils; 8 | 9 | /// A component that displays one or more lines of text. 10 | /// 11 | /// 12 | /// ## Example 13 | /// ``` 14 | /// Text::new("This is an example"); 15 | /// ``` 16 | pub struct Text { 17 | child: String, 18 | class: String, 19 | html_tag: HTMLTextTag, 20 | style: Style, 21 | } 22 | 23 | impl Text { 24 | /// Create a new `Text` element. 25 | /// 26 | /// ``` 27 | /// Text::new("This is a text element") 28 | /// ``` 29 | pub fn new(content: &str) -> Text { 30 | Text { 31 | child: content.to_string(), 32 | html_tag: HTMLTextTag::P, 33 | class: utils::make_class(), 34 | style: Style { 35 | font: Font { 36 | size: 1.0, 37 | weight: FontWeight::Regular, 38 | family: String::from("Lato"), 39 | design: String::from("sans-serif"), 40 | }, 41 | color: Color::Gray50, 42 | alignment: TextAlignment::Leading, 43 | decoration: TextDecoration::None, 44 | transform: TextTransform::None, 45 | }, 46 | } 47 | } 48 | 49 | /// Modify the semantics and styling of the text element using `with_text_type`. For example, 50 | /// If we wanted to change the semantics to match that of a heading we can do the following: 51 | /// 52 | /// ``` 53 | /// Text::new("Text as Heading").with_text_type(TextStyle::Heading1) 54 | /// ``` 55 | pub fn with_text_type(mut self, text_type: TextType) -> Text { 56 | match text_type { 57 | TextType::Heading1 => { 58 | self.html_tag = HTMLTextTag::H1; 59 | self.style.font.size = 3.0; 60 | self.style.font.weight = FontWeight::Heavy; 61 | self 62 | } 63 | TextType::Heading2 => { 64 | self.html_tag = HTMLTextTag::H2; 65 | self.style.font.size = 2.25; 66 | self.style.font.weight = FontWeight::Bold; 67 | self 68 | } 69 | TextType::Heading3 => { 70 | self.html_tag = HTMLTextTag::H3; 71 | self.style.font.size = 1.875; 72 | self.style.font.weight = FontWeight::Bold; 73 | self 74 | } 75 | TextType::Heading4 => { 76 | self.html_tag = HTMLTextTag::H4; 77 | self.style.font.size = 1.5; 78 | self.style.font.weight = FontWeight::Bold; 79 | self 80 | } 81 | TextType::Heading5 => { 82 | self.html_tag = HTMLTextTag::H5; 83 | self.style.font.size = 1.25; 84 | self.style.font.weight = FontWeight::Semibold; 85 | self 86 | } 87 | TextType::Heading6 => { 88 | self.html_tag = HTMLTextTag::H6; 89 | self.style.font.size = 1.125; 90 | self.style.font.weight = FontWeight::Semibold; 91 | self 92 | } 93 | TextType::Paragraph => { 94 | self.html_tag = HTMLTextTag::P; 95 | self.style.font.size = 1.0; 96 | self.style.font.weight = FontWeight::Regular; 97 | self 98 | } 99 | TextType::Code => { 100 | self.html_tag = HTMLTextTag::Code; 101 | self.style.font.size = 1.0; 102 | self.style.font.weight = FontWeight::Regular; 103 | self.style.font.family = String::from("monospace"); 104 | self 105 | } 106 | } 107 | } 108 | 109 | pub fn with_color(mut self, color: Color) -> Text { 110 | self.style.color = color; 111 | self 112 | } 113 | 114 | pub fn with_alignment(mut self, alignment: TextAlignment) -> Text { 115 | self.style.alignment = alignment; 116 | self 117 | } 118 | } 119 | 120 | impl Component for Text { 121 | /// Render the HTMl output of a Text element 122 | fn html(&self) -> String { 123 | let tag = &self.html_tag; 124 | let class = &self.class; 125 | let child = &self.child; 126 | 127 | html!(tag, class, child) 128 | } 129 | 130 | /// Render the CSS output of a Text element 131 | fn css(&self) -> String { 132 | format!( 133 | ".{} {{margin:0;color:{};{}{}{}{}}}", 134 | self.class, 135 | self.style.color.hex(), 136 | self.style.font.css(), 137 | self.style.alignment.css(), 138 | self.style.transform.css(), 139 | self.style.decoration.css() 140 | ) 141 | } 142 | } 143 | 144 | /// The style of the 145 | struct Style { 146 | font: Font, 147 | color: Color, 148 | alignment: TextAlignment, 149 | decoration: TextDecoration, 150 | transform: TextTransform, 151 | } 152 | 153 | /// The semantic text type for the text element 154 | pub enum TextType { 155 | Heading1, 156 | Heading2, 157 | Heading3, 158 | Heading4, 159 | Heading5, 160 | Heading6, 161 | Paragraph, 162 | Code, 163 | } 164 | 165 | /// The HTML tag to describe the text element 166 | enum HTMLTextTag { 167 | H1, 168 | H2, 169 | H3, 170 | H4, 171 | H5, 172 | H6, 173 | P, 174 | Code, 175 | } 176 | 177 | /// Convert the enum to an HTML tag string 178 | impl fmt::Display for HTMLTextTag { 179 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 180 | let tag = match *self { 181 | HTMLTextTag::H1 => String::from("h1"), 182 | HTMLTextTag::H2 => String::from("h2"), 183 | HTMLTextTag::H3 => String::from("h3"), 184 | HTMLTextTag::H4 => String::from("h4"), 185 | HTMLTextTag::H5 => String::from("h5"), 186 | HTMLTextTag::H6 => String::from("h6"), 187 | HTMLTextTag::P => String::from("p"), 188 | HTMLTextTag::Code => String::from("code"), 189 | }; 190 | write!(f, "{}", tag) 191 | } 192 | } 193 | 194 | pub enum TextAlignment { 195 | Center, 196 | Leading, 197 | Trailing, 198 | } 199 | 200 | impl CSS for TextAlignment { 201 | fn css(&self) -> String { 202 | let alignment = match *self { 203 | TextAlignment::Center => "center", 204 | TextAlignment::Leading => "left", 205 | TextAlignment::Trailing => "right", 206 | }; 207 | 208 | format!("text-align: {};", alignment) 209 | } 210 | } 211 | 212 | pub enum TextDecoration { 213 | Underline, 214 | Overline, 215 | LineThrough, 216 | None, 217 | } 218 | 219 | impl CSS for TextDecoration { 220 | fn css(&self) -> String { 221 | let decoration = match *self { 222 | TextDecoration::Underline => "underline", 223 | TextDecoration::Overline => "overline", 224 | TextDecoration::LineThrough => "line-through", 225 | TextDecoration::None => "none", 226 | }; 227 | 228 | format!("text-decoration: {};", decoration) 229 | } 230 | } 231 | 232 | pub enum TextTransform { 233 | Uppercase, 234 | Lowercase, 235 | Capitalize, 236 | None, 237 | } 238 | 239 | impl CSS for TextTransform { 240 | fn css(&self) -> String { 241 | let transform = match *self { 242 | TextTransform::Uppercase => "uppercase", 243 | TextTransform::Lowercase => "lowercase", 244 | TextTransform::Capitalize => "capitalize", 245 | TextTransform::None => "none", 246 | }; 247 | 248 | format!("text-decoration: {};", transform) 249 | } 250 | } 251 | 252 | /// All possible font weights 253 | #[derive(Debug)] 254 | pub enum FontWeight { 255 | Heavy, 256 | Bold, 257 | Semibold, 258 | Medium, 259 | Regular, 260 | Light, 261 | Thin, 262 | Ultralight, 263 | } 264 | 265 | // default font weight 266 | impl Default for FontWeight { 267 | fn default() -> FontWeight { 268 | FontWeight::Regular 269 | } 270 | } 271 | 272 | // convert font weight to css 273 | impl CSS for FontWeight { 274 | fn css(&self) -> String { 275 | let weight = match *self { 276 | FontWeight::Heavy => "800", 277 | FontWeight::Bold => "700", 278 | FontWeight::Semibold => "600", 279 | FontWeight::Medium => "500", 280 | FontWeight::Regular => "400", 281 | FontWeight::Light => "300", 282 | FontWeight::Thin => "200", 283 | FontWeight::Ultralight => "100", 284 | }; 285 | 286 | String::from(format!("font-weight: {}", weight)) 287 | } 288 | } 289 | 290 | // all font values 291 | pub struct Font { 292 | pub size: f32, 293 | pub weight: FontWeight, 294 | pub family: String, 295 | pub design: String, 296 | } 297 | 298 | // convert font to css 299 | impl CSS for Font { 300 | fn css(&self) -> String { 301 | let css = format!( 302 | "font-size: {}rem;{};font-family: {}, {};", 303 | self.size, 304 | self.weight.css(), 305 | self.family, 306 | self.design 307 | ); 308 | String::from(css) 309 | } 310 | } 311 | --------------------------------------------------------------------------------