openrr_gui/
velocity_sender.rs1#![allow(deprecated)] use arci::{BaseVelocity, MoveBase};
4use eframe::egui;
5use egui_extras::RetainedImage;
6use tracing::{debug, error};
7
8const DEFAULT_ACTIVE_VELOCITY_VALUE: f64 = 0.25;
9
10#[cfg(not(target_family = "wasm"))]
12pub fn velocity_sender<M>(move_base: M) -> Result<(), crate::Error>
13where
14 M: MoveBase + 'static,
15{
16 let native_options = eframe::NativeOptions {
17 viewport: egui::ViewportBuilder::default()
18 .with_inner_size([400.0, 400.0])
19 .with_icon(
20 eframe::icon_data::from_png_bytes(include_bytes!("../assets/icon/openrr.png"))
21 .unwrap(),
22 ),
23 ..eframe::NativeOptions::default()
24 };
25 eframe::run_native(
26 "Velocity Sender",
27 native_options,
28 Box::new(|_cc| Ok(Box::new(VelocitySender::new(move_base)))),
29 )
30 .map_err(|e| crate::Error::Other(e.to_string()))?; Ok(())
32}
33
34struct VelocitySender<M>
35where
36 M: MoveBase + 'static,
37{
38 move_base: M,
39
40 velocity: BaseVelocity,
41 show_velocity: bool,
42
43 up_button: RetainedImage,
44 down_button: RetainedImage,
45 left_button: RetainedImage,
46 right_button: RetainedImage,
47 counterclockwise_button: RetainedImage,
48 clockwise_button: RetainedImage,
49}
50
51impl<M> VelocitySender<M>
52where
53 M: MoveBase + 'static,
54{
55 fn new(move_base: M) -> Self {
56 macro_rules! button_image {
57 ($alt:expr, $img_path:expr $(,)?) => {
58 RetainedImage::from_image_bytes(
59 $alt,
60 include_bytes!(concat!("../assets/material-design-icons/", $img_path)),
61 )
62 .unwrap()
63 };
64 }
65
66 Self {
67 move_base,
68 velocity: BaseVelocity {
69 x: 0.0,
70 y: 0.0,
71 theta: 0.0,
72 },
73 up_button: button_image!("↑", "baseline_arrow_upward_white_24dp.png"),
74 down_button: button_image!("↓", "baseline_arrow_downward_white_24dp.png"),
75 left_button: button_image!("←", "baseline_arrow_back_white_24dp.png"),
76 right_button: button_image!("→", "baseline_arrow_forward_white_24dp.png"),
77 counterclockwise_button: button_image!("CCW", "baseline_undo_white_24dp_rotate90.png"),
79 clockwise_button: button_image!("CW", "baseline_redo_white_24dp_rotate270.png"),
81 show_velocity: true,
82 }
83 }
84}
85
86impl<M> eframe::App for VelocitySender<M>
87where
88 M: MoveBase + 'static,
89{
90 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
91 egui::CentralPanel::default().show(ctx, |ui| {
92 egui::warn_if_debug_build(ui);
93
94 ui.add_space(20.0);
95 ui.horizontal(|ui| {
96 ui.add_space(180.0);
97 if ui
98 .add(
99 egui::Button::image((
100 self.up_button.texture_id(ctx),
101 self.up_button.size_vec2(),
102 ))
103 .selected(self.velocity.x > 0.0),
104 )
105 .clicked()
106 {
107 if self.velocity.x > 0.0 {
108 self.velocity.x = 0.0;
109 } else {
110 self.velocity.x = DEFAULT_ACTIVE_VELOCITY_VALUE;
111 }
112 }
113 });
114 ui.horizontal(|ui| {
115 ui.add_space(100.0);
116 if ui
117 .add(
118 egui::Button::image((
119 self.clockwise_button.texture_id(ctx),
120 self.clockwise_button.size_vec2(),
121 ))
122 .selected(self.velocity.theta < 0.0),
123 )
124 .clicked()
125 {
126 if self.velocity.theta < 0.0 {
127 self.velocity.theta = 0.0;
128 } else {
129 self.velocity.theta = -DEFAULT_ACTIVE_VELOCITY_VALUE;
130 }
131 }
132 if ui
133 .add(
134 egui::Button::image((
135 self.left_button.texture_id(ctx),
136 self.left_button.size_vec2(),
137 ))
138 .selected(self.velocity.y > 0.0),
139 )
140 .clicked()
141 {
142 if self.velocity.y > 0.0 {
143 self.velocity.y = 0.0;
144 } else {
145 self.velocity.y = DEFAULT_ACTIVE_VELOCITY_VALUE;
146 }
147 }
148 if ui.button("Stop").clicked() {
149 self.velocity.x = 0.0;
150 self.velocity.y = 0.0;
151 self.velocity.theta = 0.0;
152 }
153 if ui
154 .add(
155 egui::Button::image((
156 self.right_button.texture_id(ctx),
157 self.right_button.size_vec2(),
158 ))
159 .selected(self.velocity.y < 0.0),
160 )
161 .clicked()
162 {
163 if self.velocity.y < 0.0 {
164 self.velocity.y = 0.0;
165 } else {
166 self.velocity.y = -DEFAULT_ACTIVE_VELOCITY_VALUE;
167 }
168 }
169 if ui
170 .add(
171 egui::Button::image((
172 self.counterclockwise_button.texture_id(ctx),
173 self.counterclockwise_button.size_vec2(),
174 ))
175 .selected(self.velocity.theta > 0.0),
176 )
177 .clicked()
178 {
179 if self.velocity.theta > 0.0 {
180 self.velocity.theta = 0.0;
181 } else {
182 self.velocity.theta = DEFAULT_ACTIVE_VELOCITY_VALUE;
183 }
184 }
185 });
186 ui.horizontal(|ui| {
187 ui.add_space(180.0);
188 if ui
189 .add(
190 egui::Button::image((
191 self.down_button.texture_id(ctx),
192 self.down_button.size_vec2(),
193 ))
194 .selected(self.velocity.x < 0.0),
195 )
196 .clicked()
197 {
198 if self.velocity.x < 0.0 {
199 self.velocity.x = 0.0;
200 } else {
201 self.velocity.x = -DEFAULT_ACTIVE_VELOCITY_VALUE;
202 }
203 }
204 });
205
206 ui.add_space(100.0);
207 ui.horizontal(|ui| {
208 ui.add_space(140.0);
209 ui.vertical(|ui| {
210 if self.show_velocity {
211 ui.add(egui::Slider::new(&mut self.velocity.x, -1.0..=1.0).text("x"));
212 ui.add(egui::Slider::new(&mut self.velocity.y, -1.0..=1.0).text("y"));
213 ui.add(egui::Slider::new(&mut self.velocity.theta, -1.0..=1.0).text("θ"));
214 }
215 });
216 });
217
218 ui.add_space(20.0);
219 ui.horizontal(|ui| {
220 ui.add_space(240.0);
221 ui.checkbox(&mut self.show_velocity, "Show details");
222 });
223
224 debug!(?self.velocity, "send_velocity");
226 if let Err(e) = self.move_base.send_velocity(&self.velocity) {
227 error!("{e}");
228 ui.colored_label(ui.visuals().error_fg_color, format!("Error: {e:#}"));
229 }
230 });
231 }
232}
233
234#[cfg(test)]
235mod test {
236 use arci::DummyMoveBase;
237 use assert_approx_eq::assert_approx_eq;
238
239 use super::*;
240
241 #[test]
242 fn test_velocity_sender() {
243 let move_base = DummyMoveBase::new();
244
245 let velocity_sender = VelocitySender::new(move_base);
246
247 assert_approx_eq!(velocity_sender.velocity.x, 0.0);
248 assert_approx_eq!(velocity_sender.velocity.y, 0.0);
249 assert_approx_eq!(velocity_sender.velocity.theta, 0.0);
250 }
251}