Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/ui_components/link.rs
1use crate::{
2 elements::{Border, Container, Element, Hoverable, MouseState, MouseStateHandle, Text},
3 fonts::Properties,
4 platform::Cursor,
5 ui_components::components::{UiComponent, UiComponentStyles},
6 EventContext,
7};
8 
9pub type OnClickFn = Box<dyn Fn(&mut EventContext)>;
10 
11pub struct Link {
12 text: String, // TODO figure out how it can be ui element (or icon?)
13 /// A URL that should be opened in the user's default web browser when clicked.
14 url: Option<String>,
15 /// A callback that should be fired when clicked.
16 /// Commonly dispatches an action from the calling view.
17 callback: Option<OnClickFn>,
18 styles: LinkStyles,
19 hover_state: MouseStateHandle,
20}
21 
22#[derive(Copy, Clone)]
23pub struct LinkStyles {
24 pub base: UiComponentStyles,
25 pub hovered: Option<UiComponentStyles>,
26 pub clicked: Option<UiComponentStyles>,
27 pub soft_wrap: bool,
28}
29 
30impl LinkStyles {
31 fn merge(&self, style: UiComponentStyles) -> Self {
32 Self {
33 base: self.base.merge(style),
34 hovered: Some(self.hovered.unwrap_or(self.base).merge(style)),
35 clicked: Some(self.clicked.unwrap_or(self.base).merge(style)),
36 soft_wrap: self.soft_wrap,
37 }
38 }
39}
40 
41impl UiComponent for Link {
42 type ElementType = Container;
43 fn build(self) -> Container {
44 let url = self.url.clone();
45 Container::new(
46 Hoverable::new(self.hover_state.clone(), |state| {
47 let styles = self.styles(state);
48 let mut text = Text::new(
49 self.text.clone(),
50 styles.font_family_id.unwrap(),
51 styles.font_size.unwrap_or(14.),
52 )
53 .soft_wrap(self.styles.soft_wrap);
54 
55 if let Some(font_color) = styles.font_color {
56 text = text.with_color(font_color);
57 }
58 
59 if let Some(weight) = styles.font_weight {
60 text = text.with_style(Properties::default().weight(weight));
61 }
62 
63 match (styles.border_width, styles.border_color) {
64 (Some(border_width), Some(border_color)) => Container::new(text.finish())
65 .with_border(Border::bottom(border_width).with_border_fill(border_color))
66 // Pull down the element by 1px so that the 1px border doesn't affect the
67 // vertical positioning of the element. Without this, the text won't be
68 // vertically aligned with neighboring `Text` elements.
69 .with_margin_bottom(-border_width)
70 .finish(),
71 (_, _) => text.finish(),
72 }
73 })
74 .on_click(move |ctx, app, _| {
75 if let Some(url) = &url {
76 app.open_url(url);
77 }
78 
79 if let Some(callback) = &self.callback {
80 callback(ctx);
81 }
82 })
83 .with_cursor(Cursor::PointingHand)
84 .finish(),
85 )
86 }
87 
88 /// Overwrites _some_ styles passed in `style` parameter
89 fn with_style(self, styles: UiComponentStyles) -> Self {
90 Link {
91 text: self.text.clone(),
92 url: self.url,
93 callback: self.callback,
94 styles: self.styles.merge(styles),
95 hover_state: self.hover_state,
96 }
97 }
98}
99 
100impl Link {
101 pub fn new(
102 text: String,
103 url: Option<String>,
104 callback: Option<OnClickFn>,
105 mouse_state: MouseStateHandle,
106 styles: LinkStyles,
107 ) -> Self {
108 Link {
109 text,
110 url,
111 callback,
112 styles,
113 hover_state: mouse_state,
114 }
115 }
116 
117 fn styles(&self, state: &MouseState) -> UiComponentStyles {
118 if state.is_hovered() {
119 if state.is_clicked() {
120 return self.styles.clicked.unwrap_or(self.styles.base);
121 }
122 return self.styles.hovered.unwrap_or(self.styles.base);
123 }
124 self.styles.base
125 }
126 
127 pub fn with_hovered_style(mut self, hover_style: UiComponentStyles) -> Self {
128 if let Some(style) = &mut self.styles.hovered {
129 *style = style.merge(hover_style);
130 }
131 self
132 }
133 
134 pub fn with_clicked_style(mut self, hover_style: UiComponentStyles) -> Self {
135 if let Some(style) = &mut self.styles.clicked {
136 *style = style.merge(hover_style);
137 }
138 self
139 }
140 
141 pub fn soft_wrap(mut self, soft_wrap: bool) -> Self {
142 self.styles.soft_wrap = soft_wrap;
143 self
144 }
145}
146