floem/style/custom.rs
1//! Custom styling traits for view-specific styling capabilities.
2//!
3//! This module provides traits that allow views to have specialized styling methods
4//! beyond the basic Style properties:
5//!
6//! - [`CustomStyle`] - Base trait for defining custom style types
7//! - [`CustomStylable`] - Trait for views that can accept custom styling
8
9use std::rc::Rc;
10
11use floem_reactive::UpdaterEffect;
12
13use crate::layout::responsive::ScreenSize;
14use crate::view::{IntoView, View};
15
16use super::{
17 NthChild, StructuralSelector, Style, StyleClass, StyleProp, StyleSelector, Transition,
18};
19
20/// A trait for custom styling of specific view types.
21///
22/// This trait allows views to have specialized styling methods beyond the basic Style properties.
23/// Each implementing type provides custom styling capabilities for a particular view type.
24///
25/// # Example
26/// ```rust
27/// use floem::prelude::*;
28/// use floem::style::CustomStylable;
29/// use palette::css;
30///
31/// // Using custom styling on a text view
32/// text("Hello").custom_style(|s: LabelCustomStyle| {
33/// s.selection_color(css::BLUE)
34/// });
35/// ```
36pub trait CustomStyle: Default + Clone + Into<Style> + From<Style> {
37 /// The CSS class associated with this custom style type.
38 type StyleClass: StyleClass;
39
40 /// Applies standard styling methods to this custom style.
41 ///
42 /// This method allows you to use any of the standard Style methods while working
43 /// within a custom style context.
44 ///
45 /// # Example
46 /// ```rust
47 /// # use floem::prelude::*;
48 /// # use floem::style::CustomStyle;
49 /// # use palette::css;
50 /// # let label_custom_style = LabelCustomStyle::new();
51 /// label_custom_style.style(|s| s.padding(10.0).background(css::RED))
52 /// # ;
53 /// ```
54 fn style(self, style: impl FnOnce(Style) -> Style) -> Self {
55 let self_style = self.into();
56 let new = style(self_style);
57 new.into()
58 }
59
60 /// Applies custom styling when the element is hovered.
61 ///
62 /// This method allows you to define how the custom style should change
63 /// when the mouse hovers over the element.
64 ///
65 /// # Example
66 /// ```rust
67 /// # use floem::prelude::*;
68 /// # use floem::style::CustomStyle;
69 /// # use palette::css;
70 /// # let label_custom_style = LabelCustomStyle::new();
71 /// label_custom_style.hover(|s| s.selection_color(css::BLUE))
72 /// # ;
73 /// ```
74 fn hover(self, style: impl FnOnce(Self) -> Self) -> Self {
75 let self_style: Style = self.into();
76 let new = self_style.selector(StyleSelector::Hover, |_| style(Self::default()).into());
77 new.into()
78 }
79
80 /// Applies custom styling when the element has keyboard focus.
81 ///
82 /// This method allows you to define how the custom style should change
83 /// when the element gains keyboard focus.
84 ///
85 /// # Example
86 /// ```rust
87 /// # use floem::prelude::*;
88 /// # use floem::style::CustomStyle;
89 /// # use palette::css;
90 /// # let label_custom_style = LabelCustomStyle::new();
91 /// label_custom_style.focus(|s| s.selection_color(css::GREEN))
92 /// # ;
93 /// ```
94 fn focus(self, style: impl FnOnce(Self) -> Self) -> Self {
95 let self_style: Style = self.into();
96 let new = self_style.selector(StyleSelector::Focus, |_| style(Self::default()).into());
97 new.into()
98 }
99
100 /// Similar to the `:focus-visible` css selector, this style only activates when tab navigation is used.
101 fn focus_visible(self, style: impl FnOnce(Self) -> Self) -> Self {
102 let self_style: Style = self.into();
103 let new = self_style.selector(StyleSelector::FocusVisible, |_| {
104 style(Self::default()).into()
105 });
106 new.into()
107 }
108
109 /// Similar to the `:focus-within` css selector, this style activates when this
110 /// view or any descendant is in the focus path.
111 fn focus_within(self, style: impl FnOnce(Self) -> Self) -> Self {
112 let self_style: Style = self.into();
113 let new = self_style.selector(StyleSelector::FocusWithin, |_| {
114 style(Self::default()).into()
115 });
116 new.into()
117 }
118
119 /// Similar to the `:first-child` css selector.
120 fn first_child(self, style: impl FnOnce(Self) -> Self) -> Self {
121 let self_style: Style = self.into();
122 let new = self_style.structural_selector(StructuralSelector::FirstChild, |_| {
123 style(Self::default()).into()
124 });
125 new.into()
126 }
127
128 /// Similar to the `:last-child` css selector.
129 fn last_child(self, style: impl FnOnce(Self) -> Self) -> Self {
130 let self_style: Style = self.into();
131 let new = self_style.structural_selector(StructuralSelector::LastChild, |_| {
132 style(Self::default()).into()
133 });
134 new.into()
135 }
136
137 /// Similar to the `:nth-child(...)` css selector.
138 fn nth_child(self, nth: NthChild, style: impl FnOnce(Self) -> Self) -> Self {
139 let self_style: Style = self.into();
140 let new = self_style.structural_selector(StructuralSelector::NthChild(nth), |_| {
141 style(Self::default()).into()
142 });
143 new.into()
144 }
145
146 /// Applies custom styling when the element is in a selected state.
147 ///
148 /// This method allows you to define how the custom style should change
149 /// when the element is selected.
150 ///
151 /// # Example
152 /// ```rust
153 /// # use floem::prelude::*;
154 /// # use floem::style::CustomStyle;
155 /// # use palette::css;
156 /// # let label_custom_style = LabelCustomStyle::new();
157 /// label_custom_style.selected(|s| s.selection_color(css::ORANGE))
158 /// # ;
159 /// ```
160 fn selected(self, style: impl FnOnce(Self) -> Self) -> Self {
161 let self_style: Style = self.into();
162 let new = self_style.selector(StyleSelector::Selected, |_| style(Self::default()).into());
163 new.into()
164 }
165
166 /// Applies custom styling when the element is disabled.
167 ///
168 /// This method allows you to define how the custom style should change
169 /// when the element is in a disabled state.
170 ///
171 /// # Example
172 /// ```rust
173 /// # use floem::prelude::*;
174 /// # use floem::style::CustomStyle;
175 /// # use palette::css;
176 /// # let label_custom_style = LabelCustomStyle::new();
177 /// label_custom_style.disabled(|s| s.selection_color(css::GRAY))
178 /// # ;
179 /// ```
180 fn disabled(self, style: impl FnOnce(Self) -> Self) -> Self {
181 let self_style: Style = self.into();
182 let new = self_style.selector(StyleSelector::Disabled, |_| style(Self::default()).into());
183 new.into()
184 }
185
186 /// Applies custom styling when the application is in dark mode.
187 ///
188 /// This method allows you to define how the custom style should change
189 /// when the application switches to dark mode.
190 ///
191 /// # Example
192 /// ```rust
193 /// # use floem::prelude::*;
194 /// # use floem::style::CustomStyle;
195 /// # use palette::css;
196 /// # let label_custom_style = LabelCustomStyle::new();
197 /// label_custom_style.dark_mode(|s| s.selection_color(css::WHITE))
198 /// # ;
199 /// ```
200 fn dark_mode(self, style: impl FnOnce(Self) -> Self) -> Self {
201 let self_style: Style = self.into();
202 let new = self_style.selector(StyleSelector::DarkMode, |_| style(Self::default()).into());
203 new.into()
204 }
205
206 /// Applies custom styling when the element is being actively pressed.
207 ///
208 /// This method allows you to define how the custom style should change
209 /// when the element is being actively pressed (e.g., mouse button down).
210 ///
211 /// # Example
212 /// ```rust
213 /// # use floem::prelude::*;
214 /// # use floem::style::CustomStyle;
215 /// # use palette::css;
216 /// # let label_custom_style = LabelCustomStyle::new();
217 /// label_custom_style.active(|s| s.selection_color(css::RED))
218 /// # ;
219 /// ```
220 fn active(self, style: impl FnOnce(Self) -> Self) -> Self {
221 let self_style: Style = self.into();
222 let new = self_style.selector(StyleSelector::Active, |_| style(Self::default()).into());
223 new.into()
224 }
225
226 /// Applies custom styling that activates at specific screen sizes (responsive design).
227 ///
228 /// This method allows you to define how the custom style should change
229 /// based on the screen size, enabling responsive custom styling.
230 ///
231 /// # Example
232 /// ```rust
233 /// # use floem::prelude::*;
234 /// # use floem::style::CustomStyle;
235 /// # use floem::layout::responsive::ScreenSize;
236 /// # use palette::css;
237 /// # let label_custom_style = LabelCustomStyle::new();
238 /// label_custom_style.responsive(ScreenSize::SM, |s| s.selection_color(css::PURPLE))
239 /// # ;
240 /// ```
241 fn responsive(self, size: ScreenSize, style: impl FnOnce(Self) -> Self) -> Self {
242 let over = style(Self::default());
243 let over_style: Style = over.into();
244 let mut self_style: Style = self.into();
245 self_style = self_style.responsive(size, |_| over_style);
246 self_style.into()
247 }
248
249 /// Applies custom styling when window width is at least `min`.
250 fn min_window_width(self, min: impl Into<super::Pt>, style: impl FnOnce(Self) -> Self) -> Self {
251 let over = style(Self::default());
252 let over_style: Style = over.into();
253 let self_style: Style = self.into();
254 self_style.min_window_width(min, |_| over_style).into()
255 }
256
257 /// Applies custom styling when window width is at most `max`.
258 fn max_window_width(self, max: impl Into<super::Pt>, style: impl FnOnce(Self) -> Self) -> Self {
259 let over = style(Self::default());
260 let over_style: Style = over.into();
261 let self_style: Style = self.into();
262 self_style.max_window_width(max, |_| over_style).into()
263 }
264
265 /// Applies custom styling when window width is within `[min, max]` (inclusive).
266 fn window_width_range(
267 self,
268 min: impl Into<super::Pt>,
269 max: impl Into<super::Pt>,
270 style: impl FnOnce(Self) -> Self,
271 ) -> Self {
272 let over = style(Self::default());
273 let over_style: Style = over.into();
274 let self_style: Style = self.into();
275 self_style
276 .window_width_range(min, max, |_| over_style)
277 .into()
278 }
279
280 /// Conditionally applies custom styling based on a boolean condition.
281 ///
282 /// This method allows you to apply custom styling only when a condition is true,
283 /// providing a convenient way to chain conditional styling operations.
284 ///
285 /// # Example
286 /// ```rust
287 /// # use floem::prelude::*;
288 /// # use floem::style::CustomStyle;
289 /// # use palette::css;
290 /// # let label_custom_style = LabelCustomStyle::new();
291 /// # let is_highlighted = true;
292 /// label_custom_style.apply_if(is_highlighted, |s| s.selection_color(css::YELLOW))
293 /// # ;
294 /// ```
295 fn apply_if(self, cond: bool, style: impl FnOnce(Self) -> Self) -> Self {
296 if cond { style(self) } else { self }
297 }
298
299 /// Conditionally applies custom styling based on an optional value.
300 ///
301 /// This method allows you to apply custom styling only when an optional value is Some,
302 /// passing the unwrapped value to the styling function.
303 ///
304 /// # Example
305 /// ```rust
306 /// # use floem::prelude::*;
307 /// # use floem::style::CustomStyle;
308 /// # use palette::css;
309 /// # let label_custom_style = LabelCustomStyle::new();
310 /// # let maybe_color = Some(css::BLUE);
311 /// label_custom_style.apply_opt(maybe_color, |s, color| s.selection_color(color))
312 /// # ;
313 /// ```
314 fn apply_opt<T>(self, opt: Option<T>, f: impl FnOnce(Self, T) -> Self) -> Self {
315 if let Some(t) = opt { f(self, t) } else { self }
316 }
317
318 /// Sets a transition animation for a specific custom style property.
319 ///
320 /// This method allows you to animate changes to custom style properties,
321 /// creating smooth transitions when the property values change.
322 ///
323 /// # Example
324 /// ```rust
325 /// # use floem::prelude::*;
326 /// # use floem::style::CustomStyle;
327 /// # use std::time::Duration;
328 /// # let label_custom_style = LabelCustomStyle::new();
329 /// // Note: Actual property types vary by custom style implementation
330 /// # let _ = label_custom_style;
331 /// ```
332 fn transition<P: StyleProp>(self, _prop: P, transition: Transition) -> Self {
333 let mut self_style: Style = self.into();
334 self_style
335 .map_mut()
336 .insert(P::prop_ref().info().transition_key, Rc::new(transition));
337 self_style.into()
338 }
339}
340
341/// A trait that enables views to accept custom styling beyond the standard Style properties.
342///
343/// This trait allows specific view types to provide their own specialized styling methods
344/// that are tailored to their functionality. For example, a label might have custom
345/// selection styling, or a button might have custom press animations.
346///
347/// # Type Parameters
348///
349/// * `S` - The custom style type associated with this view (e.g., `LabelCustomStyle`)
350///
351/// # Example
352///
353/// ```rust
354/// use floem::prelude::*;
355/// use floem::style::CustomStylable;
356/// use palette::css;
357///
358/// // Using custom styling on a view that implements CustomStylable
359/// text("Hello World")
360/// .custom_style(|s: LabelCustomStyle| {
361/// s.selection_color(css::BLUE)
362/// .selectable(false)
363/// });
364/// ```
365pub trait CustomStylable<S: CustomStyle + 'static>: IntoView<V = Self::DV> + Sized {
366 /// The view type that this custom stylable converts to.
367 type DV: View;
368
369 /// Applies custom styling to the view with access to specialized custom style methods.
370 ///
371 /// This method allows you to use custom styling methods that are specific to this
372 /// view type, going beyond the standard styling properties available on all views.
373 ///
374 /// # Parameters
375 ///
376 /// * `style` - A closure that takes the custom style type and returns the modified style
377 ///
378 /// # Implementation Note
379 ///
380 /// For trait implementors: Don't implement this method yourself, just use the trait's
381 /// default implementation. The default implementation properly handles style registration
382 /// and updates.
383 ///
384 /// # Example
385 ///
386 /// ```rust
387 /// use floem::prelude::*;
388 /// use floem::style::CustomStylable;
389 ///
390 /// // Custom styling with theme integration
391 /// text("Status")
392 /// .custom_style(|s: LabelCustomStyle| {
393 /// s.selection_color(Color::from_rgb8(100, 150, 255))
394 /// .selectable(true)
395 /// });
396 /// ```
397 fn custom_style(self, style: impl Fn(S) -> S + 'static) -> Self::DV {
398 let view = self.into_view();
399 let id = view.id();
400 let view_state = id.state();
401 let offset = view_state.borrow_mut().style.next_offset();
402 let style = UpdaterEffect::new(
403 move || style(S::default()),
404 move |style| id.update_style(offset, style.into()),
405 );
406 view_state.borrow_mut().style.push(style.into());
407 view
408 }
409}