1use iced_core::{
2 self,
3 border::Border,
4 layout::{self, Layout, Limits, Node},
5 mouse::{self, Cursor},
6 widget::{tree::{self, Tree}, Operation},
7 Alignment, Background, Clipboard, Color, Element, Length,
8 Padding, Rectangle, Shadow, Shell, Size, Theme, Widget, event,
9 Vector, renderer, overlay,
10};
11
12use crate::styles::{equal_radius, get_rgb_color};
13
14pub struct HovContainer<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer,>
15 where
16 Renderer: iced_core::renderer::Renderer,
17 Theme: Catalog,
18 {
19 content: Vec<Element<'a, Message, Theme, Renderer>>,
20 padding: Padding,
21 height: Length,
22 width: Length,
23 on_hover: Option<OnHover<'a, Message>>,
24 on_exit: Option<OnExit<'a, Message>>,
25 hover_col: Option<iced::Color>,
26 theme: Theme::Class<'a>
27}
28
29impl<'a, Message, Theme, Renderer> HovContainer<'a, Message, Theme, Renderer>
30 where
31 Renderer: iced_core::renderer::Renderer,
32 Theme: Catalog,
33 {
34 pub fn new() -> Self{
35 let content = Vec::new();
36 Self {
37 content,
38 padding: DEFAULT_PADDING,
39 height: Length::Shrink,
40 width: Length::Shrink,
41 on_hover: None,
42 on_exit: None,
43 hover_col: None,
44 theme: Theme::default(),
45 }
46 }
47
48 pub fn with_content(content: Vec<Element<'a, Message, Theme, Renderer >>) -> Self{
49 Self {
50 content,
51 padding: DEFAULT_PADDING,
52 height: Length::Shrink,
53 width: Length::Shrink,
54 on_hover: None,
55 on_exit: None,
56 hover_col: None,
57 theme: Theme::default(),
58 }
59 }
60
61 pub fn with_capacity(capacity: usize) -> Self {
62 Self::with_content(Vec::with_capacity(capacity))
63 }
64
65 pub fn with_children(children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>) -> Self {
66 let iterator = children.into_iter();
67
68 Self::with_capacity(iterator.size_hint().0).extend(iterator)
69 }
70
71 pub fn width(mut self, width: Length) -> Self {
72 self.width = width;
73 self
74 }
75
76 pub fn height(mut self, height: Length) -> Self {
77 self.height = height;
78 self
79 }
80
81 pub fn on_hover(mut self, message: Message) -> Self{
82 self.on_hover = Some(OnHover::Direct(message));
83 self
84 }
85
86 pub fn on_exit(mut self, message: Message) -> Self {
87 self.on_exit = Some(OnExit::Direct(message));
88 self
89 }
90
91 pub fn padding<P>(mut self, padding: P) -> Self
92 where
93 P: Into<Padding> {
94 self.padding = padding.into();
95 self
96 }
97
98 pub fn hover_color(mut self, color: Color) -> Self {
99 self.hover_col = Some(color);
100 self
101 }
102
103 pub fn push(
104 mut self,
105 child: impl Into<Element<'a, Message, Theme, Renderer>>,
106 ) -> Self {
107 let child = child.into();
108 let child_size = child.as_widget().size_hint();
109
110 self.width = self.width.enclose(child_size.width);
111 self.height = self.height.enclose(child_size.height);
112
113 self.content.push(child);
114 self
115 }
116
117 pub fn extend(
118 self,
119 children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>) -> Self {
120 children.into_iter().fold(self, Self::push)
121 }
122
123 pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
124 where
125 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
126 {
127 self.theme = (Box::new(style) as StyleFn<'a, Theme>).into();
128 self
129 }
130
131
132
133}
134
135enum OnHover <'a, Message> {
136 Direct(Message),
137 Closure(Box<dyn Fn() -> Message + 'a>),
138}
139
140enum OnExit <'a, Message> {
141 Direct(Message),
142 Closure(Box<dyn Fn() -> Message + 'a>),
143}
144
145impl <'a, Message: Clone> OnHover <'a, Message> {
146 fn get(&self) -> Message {
147 match self {
148 OnHover::Direct(message) => message.clone(),
149 OnHover::Closure(f) => f(),
150 }
151 }
152}
153
154impl <'a, Message: Clone> OnExit <'a, Message> {
155 fn get(&self) -> Message {
156 match self {
157 OnExit::Direct(message) => message.clone(),
158 OnExit::Closure(f) => f(),
159 }
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
164struct State {
165 is_hovered: bool,
166}
167
168impl <'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
169 for HovContainer<'a, Message, Theme, Renderer>
170where
171 Message: 'a + Clone,
172 Renderer: 'a + iced_core::renderer::Renderer,
173 Theme: Catalog,
174{
175 fn tag(&self) -> tree::Tag {
176 tree::Tag::of::<State>()
177 }
178
179 fn state(&self) -> tree::State {
180 tree::State::new(State::default())
181 }
182
183 fn diff(&self, tree: &mut Tree) {
184 tree.diff_children(&self.content);
185 }
186
187 fn size(&self) -> Size<Length>{
188 Size {
189 width: self.width,
190 height: self.height,
191 }
192 }
193
194 fn children(&self) -> Vec<Tree> {
195 self.content.iter().map(Tree::new).collect()
196 }
197
198 fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
199 layout::flex::resolve(
200 layout::flex::Axis::Horizontal,
201 renderer,
202 limits,
203 self.width,
204 self.height,
205 self.padding,
206 2.0,
207 Alignment::Center,
208 &self.content,
209 &mut tree.children,
210 )
211 }
212
213 fn operate(&self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation) {
214 operation.container(None, layout.bounds(), &mut |operation| {
215 self.content.iter().zip(&mut tree.children).zip(layout.children())
216 .for_each(|((child, state), layout)| {
217 child.as_widget().operate(state, layout, renderer, operation);
218 })
219 })
220 }
221
222 fn on_event(&mut self, tree: &mut Tree, event: event::Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle,) -> event::Status {
223
224 self.content
226 .iter_mut()
227 .zip(&mut tree.children)
228 .zip(layout.children())
229 .map(|((child, state), layout)| {
230 child.as_widget_mut().on_event(
231 state,
232 event.clone(),
233 layout,
234 cursor,
235 renderer,
236 clipboard,
237 shell,
238 viewport,
239 )
240 })
241 .fold(event::Status::Ignored, event::Status::merge);
242
243 if let Some(on_exit) = self.on_exit.as_ref().map(OnExit::get) {
244 if let Some(on_hover) = self.on_hover.as_ref().map(OnHover::get)
245 {
246 match tree.state {
247 tree::State::None => {
248 return event::Status::Ignored;
250 }
251 tree::State::Some(_) => {
252 let state = tree.state.downcast_mut::<State>();
253 let was_hovered = state.is_hovered;
255 let mut now_hovered = false;
256 if let Some(position) = cursor.position() {
257 now_hovered = layout.bounds().contains(position);
258 }
259
260 match (was_hovered, now_hovered) {
261 (true, false) => {
262 state.is_hovered = now_hovered;
263 shell.publish(on_exit);
264 return event::Status::Captured;
265 }
266 (false, true) => {
267 state.is_hovered = now_hovered;
268 shell.publish(on_hover);
269 return event::Status::Captured;
270 }
271 _ => {
272 return event::Status::Ignored;
273 }
274 }
275 }
276 }
277 }
278 else {
279 event::Status::Ignored
280 }
281 }
282 else {
283 match tree.state {
284 tree::State::None => {
285 return event::Status::Ignored;
287 }
288 tree::State::Some(_) => {
289 let state = tree.state.downcast_mut::<State>();
290 let was_hovered = state.is_hovered;
292 let mut now_hovered = false;
293 if let Some(position) = cursor.position() {
294 now_hovered = layout.bounds().contains(position);
295 }
296
297 match (was_hovered, now_hovered) {
298 (true, false) => {
299 state.is_hovered = now_hovered;
300 return event::Status::Ignored;
301 }
302 (false, true) => {
303 state.is_hovered = now_hovered;
304 return event::Status::Ignored;
305 }
306 _ => {
307 return event::Status::Ignored;
308 }
309 }
310 }
311 }
312 event::Status::Captured
313 }
314
315 }
316 fn draw(&self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, _style: &renderer::Style, layout: Layout, cursor: Cursor, rect: &Rectangle) {
317 let bounds = layout.bounds();
318 let content_layout = layout.children().next().unwrap();
319 let is_mouse_over = cursor.is_over(bounds);
320
321 let status = if self.on_hover.is_none() {
322 Status::Disabled
323 } else if is_mouse_over && tree.state.downcast_ref::<State>().is_hovered{
324 Status::Hovered
325 } else {
326 Status::NotHovered
327 };
328
329 let style = theme.style(&self.theme, status);
330
331 if style.background.is_some() ||
332 style.border.width > 0.0 ||
333 style.shadow.color.a > 0.0
334 {
335 renderer.fill_quad(
336 renderer::Quad {
337 bounds,
338 border: style.border,
339 shadow: style.shadow,
340 },
341 style.background.unwrap_or(Background::Color(Color::TRANSPARENT))
342 );
343 }
344
345 for ((child, state), layout) in self
346 .content
347 .iter()
348 .zip(&tree.children)
349 .zip(layout.children())
350 {
351 child.as_widget().draw(
352 &tree.children[0],
353 renderer,
354 theme,
355 &renderer::Style {
356 text_color: style.text_color,
357 },
358 content_layout,
359 cursor,
360 &rect,
361 );
362
363 }
364 }
365
366 fn overlay<'b> (
367 &'b mut self,
368 tree: &'b mut Tree,
369 layout: Layout<'_>,
370 renderer: &Renderer,
371 translation: Vector
372 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
373 overlay::from_children(
374 &mut self.content,
375 tree,
376 layout,
377 renderer,
378 translation,
379 )
380 }
381}
382
383impl <'a, Message, Theme, Renderer> From <HovContainer<'a, Message, Theme, Renderer>>
384 for Element<'a, Message, Theme, Renderer>
385where
386 Message: Clone + 'a,
387 Theme: Catalog + 'a,
388 Renderer: iced_core::Renderer + 'a,
389{
390 fn from (hov_cont: HovContainer<'a, Message, Theme, Renderer>) -> Self {
391 Self::new(hov_cont)
392 }
393}
394
395impl <'a, Message, Theme, Renderer: iced_core::Renderer> FromIterator <Element<'a, Message, Theme, Renderer>>
396 for HovContainer<'a, Message, Theme, Renderer>
397 where
398 Theme: Catalog,
399{
400 fn from_iter<T: IntoIterator<Item = Element<'a, Message, Theme, Renderer>>>(iter: T) -> Self {
401 Self::with_children(iter)
402 }
403}
404
405pub(crate) const DEFAULT_PADDING: Padding = Padding {
406 top: 5.0,
407 bottom: 5.0,
408 right: 10.0,
409 left: 10.0,
410};
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub enum Status{
414 Disabled,
415 Hovered,
416 NotHovered,
417}
418
419
420#[derive(Debug, Clone, Copy, PartialEq)]
421pub struct Style {
422 pub background: Option<Background>,
423 pub text_color: Color,
424 pub border: Border,
425 pub shadow: Shadow,
426}
427
428impl Style {
429 pub fn with_border_color(self, border: Border) -> Self {
430 Self {
431 border: border,
432 ..self
433 }
434 }
435}
436
437impl Default for Style {
438 fn default() -> Self {
439 Self {
440 background: None,
441 text_color: Color::WHITE,
442 border: Border::default(),
443 shadow: Shadow::default(),
444 }
445 }
446}
447
448pub trait Catalog {
449 type Class<'a>;
450
451 fn default<'a>() -> Self::Class<'a>;
452
453 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
454}
455
456pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
457
458impl Catalog for Theme {
459 type Class<'a> = StyleFn<'a, Self>;
460
461 fn default<'a>() -> Self::Class<'a> {
462 Box::new(primary)
463 }
464
465 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
466 class(self, status)
467 }
468}
469
470pub fn primary(theme: &Theme, status: Status) -> Style {
471 let _palette = theme.extended_palette();
472 match status {
473 Status::Disabled => {
474 Style {
475 background: Some(Background::Color(Color::TRANSPARENT)),
476 text_color: get_rgb_color(255, 255, 255),
477 border: Border {color: get_rgb_color(100, 100, 100), width: 2.0, radius: equal_radius(5)},
478 ..Default::default()
479 }
480 },
481 Status::Hovered => {
482 Style {
483 background: Some(Background::Color(Color::TRANSPARENT)),
484 text_color: get_rgb_color(255, 255, 255),
485 border: Border {color: get_rgb_color(0, 0, 255), width: 2.0, radius: equal_radius(5)},
486 ..Default::default()
487 }
488 },
489 Status::NotHovered => {
490 Style {
491 background: Some(Background::Color(Color::TRANSPARENT)),
492 text_color: get_rgb_color(255, 255, 255),
493 border: Border {color: get_rgb_color(150, 150, 150), width: 2.0, radius: equal_radius(5)},
494 ..Default::default()
495 }
496 }
497 }
498}
499
500pub fn auto_style(unhov_color: Color, hov_color: Color, width: i32, radius: u32) -> impl Fn(&Theme, Status) -> Style {
501 move |_theme: &Theme, status | {
502 match status {
503 Status::Disabled => {
504 let border = Border {
505 color: unhov_color,
506 width: width as f32,
507 radius: equal_radius(radius),
508 };
509 Style::with_border_color(Style{..Default::default()}, border)
510 },
511 Status::Hovered => {
512 let border = Border {
513 color: hov_color,
514 width: width as f32,
515 radius: equal_radius(radius),
516 };
517 Style::with_border_color(Style{..Default::default()}, border)
518 },
519 Status::NotHovered => {
520 let border = Border {
521 color: unhov_color,
522 width: width as f32,
523 radius: equal_radius(radius),
524 };
525 Style::with_border_color(Style{..Default::default()}, border)
526 },
527 }
528 }
529}
530