Skip to main content

ravenrs_widgets/
modifiers.rs

1use ravenrs_core::{Border, Insets, Shadow};
2use ravenrs_layout::{Align, Axis, LayoutStyle};
3
4use crate::context::{BuildCx, NodeId, Paint, QuadPaint};
5use crate::{Fill, Tooltip, View};
6
7/// A view wrapped in a box with layout and background modifiers; see [`ViewExt`].
8#[derive(Debug)]
9pub struct Styled<V> {
10    content: V,
11    style: LayoutStyle,
12    background: Option<Fill>,
13    radius: f32,
14    border: Option<(f32, Fill)>,
15    shadow: Option<Shadow>,
16}
17
18/// SwiftUI-style modifiers available on every view.
19///
20/// ```
21/// use ravenrs_style::Semantic;
22/// use ravenrs_widgets::{Text, ViewExt};
23///
24/// let chip = Text::new("New").padding((4.0, 10.0)).background(Semantic::Control).corner_radius(999.0);
25/// ```
26pub trait ViewExt: View + Sized {
27    /// Adds padding inside the box, in logical pixels.
28    fn padding(self, padding: impl Into<Insets>) -> Styled<Self> {
29        Styled::new(self).padding(padding)
30    }
31    /// Paints a background behind the content.
32    fn background(self, fill: impl Into<Fill>) -> Styled<Self> {
33        Styled::new(self).background(fill)
34    }
35    /// Fixes the width and/or height in logical pixels; `None` keeps the content size.
36    fn frame(self, width: Option<f32>, height: Option<f32>) -> Styled<Self> {
37        Styled::new(self).frame(width, height)
38    }
39    /// Fixes the width in logical pixels.
40    fn width(self, width: f32) -> Styled<Self> {
41        Styled::new(self).width(width)
42    }
43    /// Fixes the height in logical pixels.
44    fn height(self, height: f32) -> Styled<Self> {
45        Styled::new(self).height(height)
46    }
47    /// Takes a share of the free space in the parent stack.
48    fn grow(self, grow: f32) -> Styled<Self> {
49        Styled::new(self).grow(grow)
50    }
51    /// Stretches across the parent stack's cross axis.
52    fn stretch(self) -> Styled<Self> {
53        Styled::new(self).stretch()
54    }
55    /// Centers the content inside the box.
56    fn centered(self) -> Styled<Self> {
57        Styled::new(self).centered()
58    }
59    /// Shows `text` above the view while the pointer is over it; see [`Tooltip`].
60    fn tooltip(self, text: impl Into<String>) -> Tooltip<Self> {
61        Tooltip::new(self, text)
62    }
63    /// Draws the view and everything in it at this opacity, from `0.0` to `1.0`, easing to it
64    /// when it changes. Layout and input stay as they are; see [`Faded`].
65    fn opacity(self, opacity: f32) -> Faded<Self> {
66        Faded { content: self, opacity: opacity.clamp(0.0, 1.0) }
67    }
68}
69
70/// A view drawn at an opacity, such as the last result a playground keeps while its code has
71/// problems; see [`ViewExt::opacity`]. Each shape fades on its own, so overlapping shapes show
72/// through each other while it is partly faded.
73#[derive(Debug)]
74pub struct Faded<V> {
75    content: V,
76    opacity: f32,
77}
78
79impl<V: View> View for Faded<V> {
80    fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
81        let node = self.content.build(cx);
82        let effect = ravenrs_core::Effect { opacity: self.opacity, ..ravenrs_core::Effect::IDENTITY };
83        let transition = cx.theme().motion.fast;
84        cx.motion(node, crate::Motion::new().effect(effect, transition));
85        node
86    }
87}
88
89impl<V: View> ViewExt for V {}
90
91#[cfg_attr(feature = "live", ravenrs_live::expose)]
92impl<V: View> Styled<V> {
93    fn new(content: V) -> Self {
94        Self {
95            content,
96            style: LayoutStyle { align: Align::Stretch, ..LayoutStyle::stack(Axis::Vertical) },
97            background: None,
98            radius: 0.0,
99            border: None,
100            shadow: None,
101        }
102    }
103
104    /// Sets the padding inside the box, in logical pixels.
105    pub fn padding(mut self, padding: impl Into<Insets>) -> Self {
106        self.style.padding = padding.into();
107        self
108    }
109
110    /// Paints a background behind the content.
111    pub fn background(mut self, fill: impl Into<Fill>) -> Self {
112        self.background = Some(fill.into());
113        self
114    }
115
116    /// Rounds the box corners; radius in logical pixels.
117    pub fn corner_radius(mut self, radius: f32) -> Self {
118        self.radius = radius;
119        self
120    }
121
122    /// Draws a border of `width` logical pixels.
123    pub fn border(mut self, width: f32, fill: impl Into<Fill>) -> Self {
124        self.border = Some((width, fill.into()));
125        self
126    }
127
128    /// Casts a drop shadow behind the box.
129    pub fn shadow(mut self, shadow: Shadow) -> Self {
130        self.shadow = Some(shadow);
131        self
132    }
133
134    /// Fixes the width and/or height in logical pixels; `None` keeps the content size.
135    pub fn frame(mut self, width: Option<f32>, height: Option<f32>) -> Self {
136        self.style.width = width;
137        self.style.height = height;
138        self
139    }
140
141    /// Fixes the width in logical pixels.
142    pub fn width(mut self, width: f32) -> Self {
143        self.style.width = Some(width);
144        self
145    }
146
147    /// Fixes the height in logical pixels.
148    pub fn height(mut self, height: f32) -> Self {
149        self.style.height = Some(height);
150        self
151    }
152
153    /// Caps the width in logical pixels.
154    pub fn max_width(mut self, width: f32) -> Self {
155        self.style.max_width = Some(width);
156        self
157    }
158
159    /// Takes a share of the free space in the parent stack.
160    pub fn grow(mut self, grow: f32) -> Self {
161        self.style.grow = grow;
162        self.style.min_width.get_or_insert(0.0);
163        self.style.min_height.get_or_insert(0.0);
164        self
165    }
166
167    /// Stretches across the parent stack's cross axis.
168    pub fn stretch(mut self) -> Self {
169        self.style.align_self = Some(Align::Stretch);
170        self
171    }
172
173    /// Centers the content inside the box.
174    pub fn centered(mut self) -> Self {
175        self.style.align = Align::Center;
176        self.style.justify = ravenrs_layout::Justify::Center;
177        self
178    }
179}
180
181impl<V: View> View for Styled<V> {
182    fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
183        let child = cx.child(&self.content);
184        let theme = *cx.theme();
185        let paint = if self.background.is_some() || self.border.is_some() || self.shadow.is_some() {
186            let fill = self.background.map_or(ravenrs_core::Color::TRANSPARENT, |f| f.resolve(&theme));
187            Paint::Quad(QuadPaint {
188                fill: fill.into(),
189                corner_radius: self.radius,
190                border: self.border.map(|(width, f)| Border { width, color: f.resolve(&theme) }),
191                shadow: self.shadow,
192            })
193        } else {
194            Paint::None
195        };
196        cx.container(self.style, &[child], paint)
197    }
198}