1use crate::{command::bytes::ConstCommandBuilder, timeout::CommandCategory, visca_command};
8
9visca_command! {
10 pub struct SetMenuDisplay {
16 on: bool,
17 };
18 prefix = [0x01, 0x06, 0x06];
19 param = if *on { 0x02 } else { 0x03 };
20 max_param_size = 1;
21 category = CommandCategory::Quick;
22}
23
24impl SetMenuDisplay {
25 pub fn new(on: bool) -> Self {
27 Self { on }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
35#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
36#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
37pub enum MenuDirection {
38 Up,
40 Down,
42 Left,
44 Right,
46}
47
48impl crate::command::ViscaCommand for MenuNavigate {
50 const MAX_SIZE: usize = 9;
51 const TIMEOUT_CATEGORY: CommandCategory = CommandCategory::Quick;
52
53 fn write_into(
54 &self,
55 camera_id: crate::camera_id::CameraId,
56 buffer: &mut [u8],
57 ) -> Result<usize, crate::Error> {
58 let mut builder = ConstCommandBuilder::<9>::new();
59 builder.push_mut(camera_id.to_address_byte());
60 builder.append_mut(&[0x01, 0x06, 0x01, 0x0E, 0x0E]);
61 match self.direction {
62 MenuDirection::Up => builder.append_mut(&[0x03, 0x01]),
63 MenuDirection::Down => builder.append_mut(&[0x03, 0x02]),
64 MenuDirection::Left => builder.append_mut(&[0x01, 0x03]),
65 MenuDirection::Right => builder.append_mut(&[0x02, 0x03]),
66 };
67 builder.terminate().build_into(buffer)
68 }
69}
70
71#[derive(Debug, Clone, Copy)]
80pub struct MenuNavigate {
81 direction: MenuDirection,
82}
83
84impl MenuNavigate {
85 pub fn new(direction: MenuDirection) -> Self {
87 Self { direction }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
95#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
96#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
97pub enum MenuAction {
98 Select,
100 Cancel,
102}
103
104impl From<MenuAction> for u8 {
105 fn from(action: MenuAction) -> u8 {
106 match action {
107 MenuAction::Select => 0x05,
108 MenuAction::Cancel => 0x04,
109 }
110 }
111}
112
113visca_command! {
114 pub struct PerformMenuAction {
120 action: MenuAction,
121 };
122 prefix = [0x01, 0x06, 0x06];
123 param = u8::from(*action);
124 max_param_size = 1;
125 category = CommandCategory::Quick;
126}
127
128impl PerformMenuAction {
129 pub fn new(action: MenuAction) -> Self {
131 Self { action }
132 }
133}
134
135#[derive(Debug, Copy, Clone)]
142pub struct DirectMenuControl {
143 pub control1: u8,
146 pub control2: u8,
149}
150
151impl crate::command::ViscaCommand for DirectMenuControl {
152 const MAX_SIZE: usize = 8;
153 const TIMEOUT_CATEGORY: CommandCategory = CommandCategory::Quick;
154
155 fn write_into(
156 &self,
157 camera_id: crate::camera_id::CameraId,
158 buffer: &mut [u8],
159 ) -> Result<usize, crate::Error> {
160 let mut builder = ConstCommandBuilder::<8>::new();
161 builder.push_mut(camera_id.to_address_byte());
162 builder.append_mut(&[0x01, 0x7E, 0x04, 0x72]);
163 builder.push_mut(self.control1);
164 builder.push_mut(self.control2);
165 builder.terminate().build_into(buffer)
166 }
167}
168
169impl DirectMenuControl {
170 pub fn new(control1: u8, control2: u8) -> Self {
172 Self { control1, control2 }
173 }
174
175 pub fn open_close() -> Self {
177 Self::new(0x00, 0x01)
178 }
179}
180
181#[cfg(test)]
182#[allow(clippy::expect_used, clippy::unwrap_used)]
183mod tests {
184 use super::*;
185
186 use crate::{command::bytes::VISCA_TERMINATOR, macros::test_utils::visca_test};
187
188 visca_test!(
189 SetMenuDisplay,
190 test_menu_display_on,
191 SetMenuDisplay::new(true),
192 &[0x81, 0x01, 0x06, 0x06, 0x02, VISCA_TERMINATOR]
193 );
194
195 visca_test!(
196 SetMenuDisplay,
197 test_menu_display_off,
198 SetMenuDisplay::new(false),
199 &[0x81, 0x01, 0x06, 0x06, 0x03, VISCA_TERMINATOR]
200 );
201
202 visca_test!(
203 MenuNavigate,
204 test_menu_navigate_up,
205 MenuNavigate::new(MenuDirection::Up),
206 &[
207 0x81,
208 0x01,
209 0x06,
210 0x01,
211 0x0E,
212 0x0E,
213 0x03,
214 0x01,
215 VISCA_TERMINATOR
216 ]
217 );
218
219 visca_test!(
220 MenuNavigate,
221 test_menu_navigate_down,
222 MenuNavigate::new(MenuDirection::Down),
223 &[
224 0x81,
225 0x01,
226 0x06,
227 0x01,
228 0x0E,
229 0x0E,
230 0x03,
231 0x02,
232 VISCA_TERMINATOR
233 ]
234 );
235
236 visca_test!(
237 MenuNavigate,
238 test_menu_navigate_left,
239 MenuNavigate::new(MenuDirection::Left),
240 &[
241 0x81,
242 0x01,
243 0x06,
244 0x01,
245 0x0E,
246 0x0E,
247 0x01,
248 0x03,
249 VISCA_TERMINATOR
250 ]
251 );
252
253 visca_test!(
254 MenuNavigate,
255 test_menu_navigate_right,
256 MenuNavigate::new(MenuDirection::Right),
257 &[
258 0x81,
259 0x01,
260 0x06,
261 0x01,
262 0x0E,
263 0x0E,
264 0x02,
265 0x03,
266 VISCA_TERMINATOR
267 ]
268 );
269
270 visca_test!(
271 PerformMenuAction,
272 test_menu_select,
273 PerformMenuAction::new(MenuAction::Select),
274 &[0x81, 0x01, 0x06, 0x06, 0x05, VISCA_TERMINATOR]
275 );
276
277 visca_test!(
278 PerformMenuAction,
279 test_menu_cancel,
280 PerformMenuAction::new(MenuAction::Cancel),
281 &[0x81, 0x01, 0x06, 0x06, 0x04, VISCA_TERMINATOR]
282 );
283
284 visca_test!(
285 DirectMenuControl,
286 test_direct_menu_control,
287 DirectMenuControl::new(0x00, 0x01),
288 &[0x81, 0x01, 0x7E, 0x04, 0x72, 0x00, 0x01, VISCA_TERMINATOR]
289 );
290
291 visca_test!(
292 DirectMenuControl,
293 test_direct_menu_open_close,
294 DirectMenuControl::open_close(),
295 &[0x81, 0x01, 0x7E, 0x04, 0x72, 0x00, 0x01, VISCA_TERMINATOR]
296 );
297}