1use crate::core;
20use crate::core::border;
21use crate::core::layout;
22use crate::core::mouse;
23use crate::core::renderer;
24use crate::core::widget::{Meta, Tree};
25use crate::core::{Color, 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<Theme> Meta for Rule<'_, Theme> where Theme: Catalog {}
102
103impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Rule<'_, Theme>
104where
105 Renderer: core::Renderer,
106 Theme: Catalog,
107{
108 fn size(&self) -> Size<Length> {
109 if self.is_vertical {
110 Size {
111 width: self.thickness,
112 height: Length::Fill,
113 }
114 } else {
115 Size {
116 width: Length::Fill,
117 height: self.thickness,
118 }
119 }
120 }
121
122 fn layout(&mut self, tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits) {
123 let size = <Self as Widget<(), Theme, Renderer>>::size(self);
124
125 tree.size = layout::atomic(limits, size.width, size.height);
126 }
127
128 fn draw(
129 &self,
130 _tree: &Tree,
131 renderer: &mut Renderer,
132 theme: &Theme,
133 _style: &renderer::Style,
134 layout: Layout,
135 _cursor: mouse::Cursor,
136 _viewport: &Rectangle,
137 ) {
138 let bounds = layout.bounds();
139 let style = theme.style(&self.class);
140
141 let mut bounds = if self.is_vertical {
142 let line_x = bounds.x;
143
144 let (offset, line_height) = style.fill_mode.fill(bounds.height);
145 let line_y = bounds.y + offset;
146
147 Rectangle {
148 x: line_x,
149 y: line_y,
150 width: bounds.width,
151 height: line_height,
152 }
153 } else {
154 let line_y = bounds.y;
155
156 let (offset, line_width) = style.fill_mode.fill(bounds.width);
157 let line_x = bounds.x + offset;
158
159 Rectangle {
160 x: line_x,
161 y: line_y,
162 width: line_width,
163 height: bounds.height,
164 }
165 };
166
167 if style.snap {
168 let unit = 1.0 / renderer.hint_factor().unwrap_or(1.0);
169
170 bounds.width = bounds.width.max(unit);
171 bounds.height = bounds.height.max(unit);
172 }
173
174 renderer.fill_quad(
175 renderer::Quad {
176 bounds,
177 border: border::rounded(style.radius),
178 snap: style.snap,
179 ..renderer::Quad::default()
180 },
181 style.color,
182 );
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq)]
188pub struct Style {
189 pub color: Color,
191 pub radius: border::Radius,
193 pub fill_mode: FillMode,
195 pub snap: bool,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq)]
201pub enum FillMode {
202 Full,
204 Percent(f32),
209 Padded(u16),
211 AsymmetricPadding(u16, u16),
214}
215
216impl FillMode {
217 pub fn fill(&self, space: f32) -> (f32, f32) {
225 match *self {
226 FillMode::Full => (0.0, space),
227 FillMode::Percent(percent) => {
228 if percent >= 100.0 {
229 (0.0, space)
230 } else {
231 let percent_width = (space * percent / 100.0).round();
232
233 (((space - percent_width) / 2.0).round(), percent_width)
234 }
235 }
236 FillMode::Padded(padding) => {
237 if padding == 0 {
238 (0.0, space)
239 } else {
240 let padding = padding as f32;
241 let mut line_width = space - (padding * 2.0);
242 if line_width < 0.0 {
243 line_width = 0.0;
244 }
245
246 (padding, line_width)
247 }
248 }
249 FillMode::AsymmetricPadding(first_pad, second_pad) => {
250 let first_pad = first_pad as f32;
251 let second_pad = second_pad as f32;
252 let mut line_width = space - first_pad - second_pad;
253 if line_width < 0.0 {
254 line_width = 0.0;
255 }
256
257 (first_pad, line_width)
258 }
259 }
260 }
261}
262
263pub trait Catalog: Sized {
265 type Class<'a>;
267
268 fn default<'a>() -> Self::Class<'a>;
270
271 fn style(&self, class: &Self::Class<'_>) -> Style;
273}
274
275pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
279
280impl Catalog for Theme {
281 type Class<'a> = StyleFn<'a, Self>;
282
283 fn default<'a>() -> Self::Class<'a> {
284 Box::new(default)
285 }
286
287 fn style(&self, class: &Self::Class<'_>) -> Style {
288 class(self)
289 }
290}
291
292pub fn default(theme: &Theme) -> Style {
294 let palette = theme.palette();
295
296 Style {
297 color: palette.background.strong.color,
298 radius: 0.0.into(),
299 fill_mode: FillMode::Full,
300 snap: true,
301 }
302}
303
304pub fn weak(theme: &Theme) -> Style {
306 let palette = theme.palette();
307
308 Style {
309 color: palette.background.weak.color,
310 radius: 0.0.into(),
311 fill_mode: FillMode::Full,
312 snap: true,
313 }
314}