1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use crate::{icons::ICON_STAR_FILLED, prelude::*};

#[derive(Lens)]
pub struct Rating {
    rating: u32,
    max_rating: u32,
    on_change: Option<Box<dyn Fn(&mut EventContext, u32)>>,
}

pub(crate) enum RatingEvent {
    SetRating(u32),
    EmitRating,
    Increment,
    Decrement,
}

impl Rating {
    pub fn new(cx: &mut Context, max_rating: u32, lens: impl Lens<Target = u32>) -> Handle<Self> {
        Self { rating: lens.get(cx), max_rating, on_change: None }
            .build(cx, |cx| {
                for i in 1..max_rating + 1 {
                    Svg::new(cx, ICON_STAR_FILLED)
                        // .navigable(true)
                        .checkable(true)
                        .numeric_value(1)
                        .role(Role::RadioButton)
                        .default_action_verb(DefaultActionVerb::Click)
                        .checked(lens.map(move |val| *val >= i))
                        .toggle_class("foo", Rating::rating.map(move |val| *val >= i))
                        .on_hover(move |ex| ex.emit(RatingEvent::SetRating(i)))
                        .on_press(|ex| ex.emit(RatingEvent::EmitRating));
                }
            })
            .numeric_value(Self::rating)
            .navigable(true)
            .role(Role::RadioGroup)
            .bind(lens, |handle, lens| {
                let val = lens.get(&handle);
                handle.modify(|rating| rating.rating = val);
            })
    }
}

impl View for Rating {
    fn element(&self) -> Option<&'static str> {
        Some("rating")
    }

    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
        event.map(|rating_event, _| match rating_event {
            RatingEvent::SetRating(val) => self.rating = *val,
            RatingEvent::EmitRating => {
                if let Some(callback) = &self.on_change {
                    (callback)(cx, self.rating)
                }
            }
            RatingEvent::Increment => {
                self.rating += 1;
                self.rating %= self.max_rating + 1;
                cx.emit(RatingEvent::EmitRating);
            }
            RatingEvent::Decrement => {
                self.rating =
                    if self.rating == 0 { self.max_rating } else { self.rating.saturating_sub(1) };
                cx.emit(RatingEvent::EmitRating);
            }
        });

        event.map(|window_event, _| match window_event {
            WindowEvent::KeyDown(code, _) => match code {
                Code::ArrowLeft => {
                    cx.emit(RatingEvent::Decrement);
                }

                Code::ArrowRight => {
                    cx.emit(RatingEvent::Increment);
                }

                _ => {}
            },

            _ => {}
        });
    }
}

impl<'a> Handle<'a, Rating> {
    pub fn on_change<F>(self, callback: F) -> Self
    where
        F: 'static + Fn(&mut EventContext, u32),
    {
        self.modify(|rating| rating.on_change = Some(Box::new(callback)))
    }
}