Skip to main content

vizia_core/views/
rating.rs

1use crate::{icons::ICON_STAR_FILLED, prelude::*};
2
3/// A view which represents a rating as a number of filled stars.
4pub struct Rating {
5    rating: Signal<u32>,
6    max_rating: u32,
7    on_change: Option<Box<dyn Fn(&mut EventContext, u32)>>,
8}
9
10pub(crate) enum RatingEvent {
11    SetRating(u32),
12    EmitRating,
13    Increment,
14    Decrement,
15}
16
17impl Rating {
18    /// Creates a new [Rating] view.
19    pub fn new(
20        cx: &mut Context,
21        max_rating: u32,
22        rating: impl Res<u32> + SignalGet<u32> + SignalMap<u32>,
23    ) -> Handle<Self> {
24        let local_rating = Signal::new(rating.get());
25        Self { rating: local_rating, max_rating, on_change: None }
26            .build(cx, |cx| {
27                for i in 1..max_rating + 1 {
28                    Svg::new(cx, ICON_STAR_FILLED)
29                        // .navigable(true)
30                        .checkable(true)
31                        .numeric_value(1)
32                        .role(Role::RadioButton)
33                        .checked(rating.map(move |r| *r >= i))
34                        .toggle_class("foo", local_rating.map(move |r| *r >= i))
35                        .on_hover(move |ex| ex.emit(RatingEvent::SetRating(i)))
36                        .on_press(|ex| ex.emit(RatingEvent::EmitRating));
37                }
38            })
39            .numeric_value(rating)
40            .navigable(true)
41            .role(Role::RadioGroup)
42            .bind(rating, move |handle| {
43                let val = rating.get();
44                handle.modify(|rating| rating.rating.set(val));
45            })
46    }
47}
48
49impl View for Rating {
50    fn element(&self) -> Option<&'static str> {
51        Some("rating")
52    }
53
54    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
55        event.map(|rating_event, _| match rating_event {
56            RatingEvent::SetRating(val) => self.rating.set(*val),
57            RatingEvent::EmitRating => {
58                if let Some(callback) = &self.on_change {
59                    (callback)(cx, self.rating.get());
60                }
61            }
62            RatingEvent::Increment => {
63                self.rating.set((self.rating.get() + 1) % (self.max_rating + 1));
64                cx.emit(RatingEvent::EmitRating);
65            }
66            RatingEvent::Decrement => {
67                self.rating.set(if self.rating.get() == 0 {
68                    self.max_rating
69                } else {
70                    self.rating.get().saturating_sub(1)
71                });
72                cx.emit(RatingEvent::EmitRating);
73            }
74        });
75
76        event.map(|window_event, _| match window_event {
77            WindowEvent::KeyDown(code, _) => match code {
78                Code::ArrowLeft => {
79                    cx.emit(RatingEvent::Decrement);
80                }
81
82                Code::ArrowRight => {
83                    cx.emit(RatingEvent::Increment);
84                }
85
86                _ => {}
87            },
88
89            _ => {}
90        });
91    }
92}
93
94impl Handle<'_, Rating> {
95    /// Set the callback which is triggered when the rating changes.
96    pub fn on_change<F>(self, callback: F) -> Self
97    where
98        F: 'static + Fn(&mut EventContext, u32),
99    {
100        self.modify(|rating| rating.on_change = Some(Box::new(callback)))
101    }
102}