drawing/
drawing.rs

1use std::time::Duration;
2
3use gtk::cairo::{Context, Operator};
4use gtk::prelude::*;
5use relm4::abstractions::DrawHandler;
6use relm4::{Component, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt};
7
8#[derive(Debug)]
9enum Msg {
10    AddPoint((f64, f64)),
11    Reset,
12    Resize,
13}
14
15#[derive(Debug)]
16struct UpdatePointsMsg;
17
18struct App {
19    width: f64,
20    height: f64,
21    points: Vec<Point>,
22    handler: DrawHandler,
23}
24
25#[relm4::component]
26impl Component for App {
27    type Init = ();
28    type Input = Msg;
29    type Output = ();
30    type CommandOutput = UpdatePointsMsg;
31
32    view! {
33      gtk::Window {
34        set_default_size: (600, 300),
35
36        gtk::Box {
37          set_orientation: gtk::Orientation::Vertical,
38          set_margin_all: 10,
39          set_spacing: 10,
40          set_hexpand: true,
41
42          gtk::Label {
43            set_label: "Left-click to add circles, resize or right-click to reset!",
44          },
45
46          #[local_ref]
47          area -> gtk::DrawingArea {
48            set_vexpand: true,
49            set_hexpand: true,
50
51            add_controller = gtk::GestureClick {
52              set_button: 0,
53              connect_pressed[sender] => move |controller, _, x, y| {
54                if controller.current_button() == gtk::gdk::BUTTON_SECONDARY {
55                    sender.input(Msg::Reset);
56                } else {
57                    sender.input(Msg::AddPoint((x, y)));
58                }
59              }
60            },
61            connect_resize[sender] => move |_, _, _| {
62                sender.input(Msg::Resize);
63            }
64          },
65        }
66      }
67    }
68
69    fn update(&mut self, msg: Msg, _sender: ComponentSender<Self>, _root: &Self::Root) {
70        let cx = self.handler.get_context();
71
72        match msg {
73            Msg::AddPoint((x, y)) => {
74                self.points.push(Point::new(x, y));
75            }
76            Msg::Resize => {
77                self.width = self.handler.width() as f64;
78                self.height = self.handler.height() as f64;
79            }
80            Msg::Reset => {
81                cx.set_operator(Operator::Clear);
82                cx.set_source_rgba(0.0, 0.0, 0.0, 0.0);
83                cx.paint().expect("Couldn't fill context");
84            }
85        }
86
87        draw(&cx, &self.points);
88    }
89
90    fn update_cmd(&mut self, _: UpdatePointsMsg, _: ComponentSender<Self>, _root: &Self::Root) {
91        for point in &mut self.points {
92            let Point { x, y, .. } = point;
93            if *x < 0.0 {
94                point.xs = point.xs.abs();
95            } else if *x > self.width {
96                point.xs = -point.xs.abs();
97            }
98            *x = x.clamp(0.0, self.width);
99            *x += point.xs;
100
101            if *y < 0.0 {
102                point.ys = point.ys.abs();
103            } else if *y > self.height {
104                point.ys = -point.ys.abs();
105            }
106            *y = y.clamp(0.0, self.height);
107            *y += point.ys;
108        }
109
110        let cx = self.handler.get_context();
111        draw(&cx, &self.points);
112    }
113
114    fn init(
115        _: Self::Init,
116        root: Self::Root,
117        sender: ComponentSender<Self>,
118    ) -> ComponentParts<Self> {
119        let model = App {
120            width: 100.0,
121            height: 100.0,
122            points: Vec::new(),
123            handler: DrawHandler::new(),
124        };
125
126        let area = model.handler.drawing_area();
127        let widgets = view_output!();
128
129        sender.command(|out, shutdown| {
130            shutdown
131                .register(async move {
132                    loop {
133                        tokio::time::sleep(Duration::from_millis(20)).await;
134                        out.send(UpdatePointsMsg).unwrap();
135                    }
136                })
137                .drop_on_shutdown()
138        });
139
140        ComponentParts { model, widgets }
141    }
142}
143
144struct Point {
145    x: f64,
146    y: f64,
147    xs: f64,
148    ys: f64,
149    color: Color,
150}
151
152impl Point {
153    fn new(x: f64, y: f64) -> Point {
154        let angle: f64 = rand::random::<f64>() * std::f64::consts::PI * 2.0;
155        Point {
156            x,
157            y,
158            xs: angle.sin() * 7.0,
159            ys: angle.cos() * 7.0,
160            color: Color::random(),
161        }
162    }
163}
164
165struct Color {
166    r: f64,
167    g: f64,
168    b: f64,
169}
170
171impl Color {
172    fn random() -> Color {
173        Color {
174            r: rand::random(),
175            g: rand::random(),
176            b: rand::random(),
177        }
178    }
179}
180
181fn draw(cx: &Context, points: &[Point]) {
182    for point in points {
183        let Point {
184            x,
185            y,
186            color: Color { r, g, b },
187            ..
188        } = *point;
189        cx.set_source_rgb(r, g, b);
190        cx.arc(x, y, 10.0, 0.0, std::f64::consts::PI * 2.0);
191        cx.fill().expect("Couldn't fill arc");
192    }
193}
194
195fn main() {
196    let app = RelmApp::new("relm4.examples.drawing");
197    app.run::<App>(());
198}