Skip to main content

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}