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