1use crate::core;
20use crate::core::border;
21use crate::core::layout;
22use crate::core::mouse;
23use crate::core::renderer;
24use crate::core::widget::Tree;
25use crate::core::{Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget};
26
27pub fn horizontal<'a, Theme>(height: impl Into<Pixels>) -> Rule<'a, Theme>
29where
30 Theme: Catalog,
31{
32 Rule {
33 thickness: Length::Fixed(height.into().0),
34 is_vertical: false,
35 class: Theme::default(),
36 }
37}
38
39pub fn vertical<'a, Theme>(width: impl Into<Pixels>) -> Rule<'a, Theme>
41where
42 Theme: Catalog,
43{
44 Rule {
45 thickness: Length::Fixed(width.into().0),
46 is_vertical: true,
47 class: Theme::default(),
48 }
49}
50
51pub struct Rule<'a, Theme = crate::Theme>
70where
71 Theme: Catalog,
72{
73 thickness: Length,
74 is_vertical: bool,
75 class: Theme::Class<'a>,
76}
77
78impl<'a, Theme> Rule<'a, Theme>
79where
80 Theme: Catalog,
81{
82 #[must_use]
84 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
85 where
86 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
87 {
88 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
89 self
90 }
91
92 #[cfg(feature = "advanced")]
94 #[must_use]
95 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
96 self.class = class.into();
97 self
98 }
99}
100
101impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Rule<'_, Theme>
102where
103 Renderer: core::Renderer,
104 Theme: Catalog,
105{
106 fn size(&self) -> Size<Length> {
107 if self.is_vertical {
108 Size {
109 width: self.thickness,
110 height: Length::Fill,
111 }
112 } else {
113 Size {
114 width: Length::Fill,
115 height: self.thickness,
116 }
117 }
118 }
119
120 fn layout(
121 &mut self,
122 _tree: &mut Tree,
123 _renderer: &Renderer,
124 limits: &layout::Limits,
125 ) -> layout::Node {
126 let size = <Self as Widget<(), Theme, Renderer>>::size(self);
127
128 layout::atomic(limits, size.width, size.height)
129 }
130
131 fn draw(
132 &self,
133 _tree: &Tree,
134 renderer: &mut Renderer,
135 theme: &Theme,
136 _style: &renderer::Style,
137 layout: Layout<'_>,
138 _cursor: mouse::Cursor,
139 _viewport: &Rectangle,
140 ) {
141 let bounds = layout.bounds();
142 let style = theme.style(&self.class);
143
144 let mut bounds = if self.is_vertical {
145 let line_x = bounds.x;
146
147 let (offset, line_height) = style.fill_mode.fill(bounds.height);
148 let line_y = bounds.y + offset;
149
150 Rectangle {
151 x: line_x,
152 y: line_y,
153 width: bounds.width,
154 height: line_height,
155 }
156 } else {
157 let line_y = bounds.y;
158
159 let (offset, line_width) = style.fill_mode.fill(bounds.width);
160 let line_x = bounds.x + offset;
161
162 Rectangle {
163 x: line_x,
164 y: line_y,
165 width: line_width,
166 height: bounds.height,
167 }
168 };
169
170 if style.snap {
171 let unit = 1.0 / renderer.scale_factor().unwrap_or(1.0);
172
173 bounds.width = bounds.width.max(unit);
174 bounds.height = bounds.height.max(unit);
175 }
176
177 renderer.fill_quad(
178 renderer::Quad {
179 bounds,
180 border: border::rounded(style.radius),
181 snap: style.snap,
182 ..renderer::Quad::default()
183 },
184 style.color,
185 );
186 }
187}
188
189impl<'a, Message, Theme, Renderer> From<Rule<'a, Theme>> for Element<'a, Message, Theme, Renderer>
190where
191 Message: 'a,
192 Theme: 'a + Catalog,
193 Renderer: 'a + core::Renderer,
194{
195 fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
196 Element::new(rule)
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq)]
202pub struct Style {
203 pub color: Color,
205 pub radius: border::Radius,
207 pub fill_mode: FillMode,
209 pub snap: bool,
211}
212
213#[derive(Debug, Clone, Copy, PartialEq)]
215pub enum FillMode {
216 Full,
218 Percent(f32),
223 Padded(u16),
225 AsymmetricPadding(u16, u16),
228}
229
230impl FillMode {
231 pub fn fill(&self, space: f32) -> (f32, f32) {
239 match *self {
240 FillMode::Full => (0.0, space),
241 FillMode::Percent(percent) => {
242 if percent >= 100.0 {
243 (0.0, space)
244 } else {
245 let percent_width = (space * percent / 100.0).round();
246
247 (((space - percent_width) / 2.0).round(), percent_width)
248 }
249 }
250 FillMode::Padded(padding) => {
251 if padding == 0 {
252 (0.0, space)
253 } else {
254 let padding = padding as f32;
255 let mut line_width = space - (padding * 2.0);
256 if line_width < 0.0 {
257 line_width = 0.0;
258 }
259
260 (padding, line_width)
261 }
262 }
263 FillMode::AsymmetricPadding(first_pad, second_pad) => {
264 let first_pad = first_pad as f32;
265 let second_pad = second_pad as f32;
266 let mut line_width = space - first_pad - second_pad;
267 if line_width < 0.0 {
268 line_width = 0.0;
269 }
270
271 (first_pad, line_width)
272 }
273 }
274 }
275}
276
277pub trait Catalog: Sized {
279 type Class<'a>;
281
282 fn default<'a>() -> Self::Class<'a>;
284
285 fn style(&self, class: &Self::Class<'_>) -> Style;
287}
288
289pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
293
294impl Catalog for Theme {
295 type Class<'a> = StyleFn<'a, Self>;
296
297 fn default<'a>() -> Self::Class<'a> {
298 Box::new(default)
299 }
300
301 fn style(&self, class: &Self::Class<'_>) -> Style {
302 class(self)
303 }
304}
305
306pub fn default(theme: &Theme) -> Style {
308 let palette = theme.extended_palette();
309
310 Style {
311 color: palette.background.strong.color,
312 radius: 0.0.into(),
313 fill_mode: FillMode::Full,
314 snap: true,
315 }
316}
317
318pub fn weak(theme: &Theme) -> Style {
320 let palette = theme.extended_palette();
321
322 Style {
323 color: palette.background.weak.color,
324 radius: 0.0.into(),
325 fill_mode: FillMode::Full,
326 snap: true,
327 }
328}