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}