cosmic/theme/style/
text_input.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Contains stylesheet implementation for [`cosmic::widget::text_input`].
5
6use crate::ext::ColorExt;
7use crate::widget::text_input::{Appearance, StyleSheet};
8use iced_core::Color;
9
10#[derive(Default)]
11pub enum TextInput {
12    #[default]
13    Default,
14    EditableText,
15    ExpandableSearch,
16    Search,
17    Inline,
18    Custom {
19        active: Box<dyn Fn(&crate::Theme) -> Appearance>,
20        error: Box<dyn Fn(&crate::Theme) -> Appearance>,
21        hovered: Box<dyn Fn(&crate::Theme) -> Appearance>,
22        focused: Box<dyn Fn(&crate::Theme) -> Appearance>,
23        disabled: Box<dyn Fn(&crate::Theme) -> Appearance>,
24    },
25}
26
27impl StyleSheet for crate::Theme {
28    type Style = TextInput;
29
30    fn active(&self, style: &Self::Style) -> Appearance {
31        let palette = self.cosmic();
32        let container = self.current_container();
33
34        let mut background: Color = container.component.base.into();
35        background.a = 0.25;
36
37        let corner = palette.corner_radii;
38        let label_color = palette.palette.neutral_9;
39        match style {
40            TextInput::Default => Appearance {
41                background: background.into(),
42                border_radius: corner.radius_s.into(),
43                border_width: 2.0,
44                border_offset: None,
45                border_color: container.component.divider.into(),
46                icon_color: None,
47                text_color: None,
48                placeholder_color: {
49                    let color: Color = container.on.into();
50                    color.blend_alpha(background, 0.7)
51                },
52                selected_text_color: palette.on_accent_color().into(),
53                selected_fill: palette.accent_color().into(),
54                label_color: label_color.into(),
55            },
56            TextInput::EditableText => Appearance {
57                background: Color::TRANSPARENT.into(),
58                border_radius: corner.radius_0.into(),
59                border_width: 0.0,
60                border_offset: None,
61                border_color: Color::TRANSPARENT,
62                icon_color: None,
63                text_color: None,
64                placeholder_color: {
65                    let color: Color = container.on.into();
66                    color.blend_alpha(background, 0.7)
67                },
68                selected_text_color: palette.on_accent_color().into(),
69                selected_fill: palette.accent_color().into(),
70                label_color: label_color.into(),
71            },
72            TextInput::ExpandableSearch => Appearance {
73                background: Color::TRANSPARENT.into(),
74                border_radius: corner.radius_xl.into(),
75                border_width: 0.0,
76                border_offset: None,
77                border_color: Color::TRANSPARENT,
78                icon_color: None,
79                text_color: None,
80                placeholder_color: {
81                    let color: Color = container.on.into();
82                    color.blend_alpha(background, 0.7)
83                },
84                selected_text_color: palette.on_accent_color().into(),
85                selected_fill: palette.accent_color().into(),
86                label_color: label_color.into(),
87            },
88            TextInput::Search => Appearance {
89                background: background.into(),
90                border_radius: corner.radius_xl.into(),
91                border_width: 2.0,
92                border_offset: None,
93                border_color: container.component.divider.into(),
94                icon_color: None,
95                text_color: None,
96                placeholder_color: {
97                    let color: Color = container.on.into();
98                    color.blend_alpha(background, 0.7)
99                },
100                selected_text_color: palette.on_accent_color().into(),
101                selected_fill: palette.accent_color().into(),
102                label_color: label_color.into(),
103            },
104            TextInput::Inline => Appearance {
105                background: Color::TRANSPARENT.into(),
106                border_radius: corner.radius_0.into(),
107                border_width: 0.0,
108                border_offset: None,
109                border_color: Color::TRANSPARENT,
110                icon_color: None,
111                text_color: None,
112                placeholder_color: {
113                    let color: Color = container.on.into();
114                    color.blend_alpha(background, 0.7)
115                },
116                selected_text_color: palette.on_accent_color().into(),
117                selected_fill: palette.accent_color().into(),
118                label_color: label_color.into(),
119            },
120            TextInput::Custom { active, .. } => active(self),
121        }
122    }
123
124    fn error(&self, style: &Self::Style) -> Appearance {
125        let palette = self.cosmic();
126        let container = self.current_container();
127
128        let mut background: Color = container.component.base.into();
129        background.a = 0.25;
130
131        let corner = palette.corner_radii;
132        let label_color = palette.palette.neutral_9;
133
134        match style {
135            TextInput::Default => Appearance {
136                background: background.into(),
137                border_radius: corner.radius_s.into(),
138                border_width: 2.0,
139                border_offset: Some(2.0),
140                border_color: Color::from(palette.destructive_color()),
141                icon_color: None,
142                text_color: None,
143                placeholder_color: {
144                    let color: Color = container.on.into();
145                    color.blend_alpha(background, 0.7)
146                },
147                selected_text_color: palette.on_accent_color().into(),
148                selected_fill: palette.accent_color().into(),
149                label_color: label_color.into(),
150            },
151            TextInput::Search | TextInput::ExpandableSearch => Appearance {
152                background: background.into(),
153                border_radius: corner.radius_xl.into(),
154                border_width: 0.0,
155                border_offset: None,
156                border_color: Color::TRANSPARENT,
157                icon_color: None,
158                text_color: None,
159                placeholder_color: {
160                    let color: Color = container.on.into();
161                    color.blend_alpha(background, 0.7)
162                },
163                selected_text_color: palette.on_accent_color().into(),
164                selected_fill: palette.accent_color().into(),
165                label_color: label_color.into(),
166            },
167            TextInput::EditableText | TextInput::Inline => Appearance {
168                background: Color::TRANSPARENT.into(),
169                border_radius: corner.radius_0.into(),
170                border_width: 0.0,
171                border_offset: None,
172                border_color: Color::TRANSPARENT,
173                icon_color: None,
174                text_color: None,
175                placeholder_color: {
176                    let color: Color = container.on.into();
177                    color.blend_alpha(background, 0.7)
178                },
179                selected_text_color: palette.on_accent_color().into(),
180                selected_fill: palette.accent_color().into(),
181                label_color: label_color.into(),
182            },
183            TextInput::Custom { error, .. } => error(self),
184        }
185    }
186
187    fn hovered(&self, style: &Self::Style) -> Appearance {
188        let palette = self.cosmic();
189        let container = self.current_container();
190
191        let mut background: Color = container.component.base.into();
192        background.a = 0.25;
193
194        let corner = palette.corner_radii;
195        let label_color = palette.palette.neutral_9;
196
197        match style {
198            TextInput::Default => Appearance {
199                background: background.into(),
200                border_radius: corner.radius_s.into(),
201                border_width: 2.0,
202                border_offset: None,
203                border_color: palette.accent.base.into(),
204                icon_color: None,
205                text_color: None,
206                placeholder_color: {
207                    let color: Color = container.on.into();
208                    color.blend_alpha(background, 0.7)
209                },
210                selected_text_color: palette.on_accent_color().into(),
211                selected_fill: palette.accent_color().into(),
212                label_color: label_color.into(),
213            },
214            TextInput::Search => Appearance {
215                background: background.into(),
216                border_radius: corner.radius_xl.into(),
217                border_offset: None,
218                border_width: 2.0,
219                border_color: palette.accent.base.into(),
220                icon_color: None,
221                text_color: None,
222                placeholder_color: {
223                    let color: Color = container.on.into();
224                    color.blend_alpha(background, 0.7)
225                },
226                selected_text_color: palette.on_accent_color().into(),
227                selected_fill: palette.accent_color().into(),
228                label_color: label_color.into(),
229            },
230            TextInput::ExpandableSearch => Appearance {
231                background: background.into(),
232                border_radius: corner.radius_xl.into(),
233                border_offset: None,
234                border_width: 0.0,
235                border_color: Color::TRANSPARENT,
236                icon_color: None,
237                text_color: None,
238                placeholder_color: {
239                    let color: Color = container.on.into();
240                    color.blend_alpha(background, 0.7)
241                },
242                selected_text_color: palette.on_accent_color().into(),
243                selected_fill: palette.accent_color().into(),
244                label_color: label_color.into(),
245            },
246            TextInput::EditableText => Appearance {
247                background: Color::TRANSPARENT.into(),
248                border_radius: corner.radius_0.into(),
249                border_width: 0.0,
250                border_offset: None,
251                border_color: Color::TRANSPARENT,
252                icon_color: None,
253                text_color: None,
254                placeholder_color: {
255                    let color: Color = container.on.into();
256                    color.blend_alpha(background, 0.7)
257                },
258                selected_text_color: palette.on_accent_color().into(),
259                selected_fill: palette.accent_color().into(),
260                label_color: label_color.into(),
261            },
262            TextInput::Inline => Appearance {
263                background: Color::from(container.component.hover).into(),
264                border_radius: corner.radius_0.into(),
265                border_width: 0.0,
266                border_offset: None,
267                border_color: Color::TRANSPARENT,
268                icon_color: None,
269                text_color: None,
270                placeholder_color: {
271                    let color: Color = container.on.into();
272                    color.blend_alpha(background, 0.7)
273                },
274                selected_text_color: palette.on_accent_color().into(),
275                selected_fill: palette.accent_color().into(),
276                label_color: label_color.into(),
277            },
278            TextInput::Custom { hovered, .. } => hovered(self),
279        }
280    }
281
282    fn focused(&self, style: &Self::Style) -> Appearance {
283        let palette = self.cosmic();
284        let container = self.current_container();
285
286        let mut background: Color = container.component.base.into();
287        background.a = 0.25;
288
289        let corner = palette.corner_radii;
290        let label_color = palette.palette.neutral_9;
291
292        match style {
293            TextInput::Default => Appearance {
294                background: background.into(),
295                border_radius: corner.radius_s.into(),
296                border_width: 2.0,
297                border_offset: Some(2.0),
298                border_color: palette.accent.base.into(),
299                icon_color: None,
300                text_color: None,
301                placeholder_color: {
302                    let color: Color = container.on.into();
303                    color.blend_alpha(background, 0.7)
304                },
305                selected_text_color: palette.on_accent_color().into(),
306                selected_fill: palette.accent_color().into(),
307                label_color: label_color.into(),
308            },
309            TextInput::Search | TextInput::ExpandableSearch => Appearance {
310                background: background.into(),
311                border_radius: corner.radius_xl.into(),
312                border_width: 2.0,
313                border_offset: Some(2.0),
314                border_color: palette.accent.base.into(),
315                icon_color: None,
316                text_color: None,
317                placeholder_color: {
318                    let color: Color = container.on.into();
319                    color.blend_alpha(background, 0.7)
320                },
321                selected_text_color: palette.on_accent_color().into(),
322                selected_fill: palette.accent_color().into(),
323                label_color: label_color.into(),
324            },
325            TextInput::EditableText => Appearance {
326                background: Color::TRANSPARENT.into(),
327                border_radius: corner.radius_0.into(),
328                border_width: 0.0,
329                border_offset: None,
330                border_color: Color::TRANSPARENT,
331                icon_color: None,
332                text_color: None,
333                placeholder_color: {
334                    let color: Color = container.on.into();
335                    color.blend_alpha(background, 0.7)
336                },
337                selected_text_color: palette.on_accent_color().into(),
338                selected_fill: palette.accent_color().into(),
339                label_color: label_color.into(),
340            },
341            TextInput::Inline => Appearance {
342                background: Color::TRANSPARENT.into(),
343                border_radius: corner.radius_0.into(),
344                border_width: 0.0,
345                border_offset: None,
346                border_color: Color::TRANSPARENT,
347                icon_color: None,
348                text_color: None,
349                placeholder_color: {
350                    let color: Color = container.on.into();
351                    color.blend_alpha(background, 0.7)
352                },
353                selected_text_color: palette.on_accent_color().into(),
354                selected_fill: palette.accent_color().into(),
355                label_color: label_color.into(),
356            },
357            TextInput::Custom { focused, .. } => focused(self),
358        }
359    }
360
361    fn disabled(&self, style: &Self::Style) -> Appearance {
362        if let TextInput::Custom { disabled, .. } = style {
363            return disabled(self);
364        }
365
366        self.active(style)
367    }
368}