1use crate::{model::muxbox::MuxBox, screen_bounds, Bounds, EntityType, FieldUpdate, Updatable};
2use core::hash::Hash;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::{collections::HashMap, hash::Hasher};
6
7#[derive(Debug, Deserialize, Serialize, Default, PartialEq)]
8pub struct Layout {
9 pub id: String,
10 pub title: Option<String>,
11 pub refresh_interval: Option<u64>,
12 pub children: Option<Vec<MuxBox>>,
13 pub fill: Option<bool>,
14 pub fill_char: Option<char>,
15 pub selected_fill_char: Option<char>,
16 pub highlighted_fill_char: Option<char>,
17 pub border_color: Option<String>,
18 pub selected_border_color: Option<String>,
19 pub highlighted_border_color: Option<String>,
20 pub bg_color: Option<String>,
21 pub selected_bg_color: Option<String>,
22 pub highlighted_bg_color: Option<String>,
23 pub fg_color: Option<String>,
24 pub selected_fg_color: Option<String>,
25 pub highlighted_fg_color: Option<String>,
26 pub title_fg_color: Option<String>,
27 pub title_bg_color: Option<String>,
28 pub title_position: Option<String>,
29 pub selected_title_bg_color: Option<String>,
30 pub selected_title_fg_color: Option<String>,
31 pub highlighted_title_bg_color: Option<String>,
32 pub highlighted_title_fg_color: Option<String>,
33 pub menu_fg_color: Option<String>,
34 pub menu_bg_color: Option<String>,
35 pub selected_menu_fg_color: Option<String>,
36 pub selected_menu_bg_color: Option<String>,
37 pub highlighted_menu_fg_color: Option<String>,
38 pub highlighted_menu_bg_color: Option<String>,
39 pub error_border_color: Option<String>,
40 pub error_bg_color: Option<String>,
41 pub error_fg_color: Option<String>,
42 pub error_title_bg_color: Option<String>,
43 pub error_title_fg_color: Option<String>,
44 pub error_selected_border_color: Option<String>,
45 pub error_selected_bg_color: Option<String>,
46 pub error_selected_fg_color: Option<String>,
47 pub error_selected_title_bg_color: Option<String>,
48 pub error_selected_title_fg_color: Option<String>,
49 pub overflow_behavior: Option<String>,
50 pub root: Option<bool>,
51 #[serde(default)]
52 pub on_keypress: Option<HashMap<String, Vec<String>>>,
53 pub active: Option<bool>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub muxbox_ids_in_tab_order: Option<Vec<String>>,
56}
57
58impl Hash for Layout {
59 fn hash<H: Hasher>(&self, state: &mut H) {
60 self.id.hash(state);
61 self.title.hash(state);
62 self.refresh_interval.hash(state);
63 if let Some(children) = &self.children {
64 for muxbox in children {
65 muxbox.hash(state);
66 }
67 }
68 self.fill.hash(state);
69 self.fill_char.hash(state);
70 self.selected_fill_char.hash(state);
71 self.highlighted_fill_char.hash(state);
72 self.border_color.hash(state);
73 self.selected_border_color.hash(state);
74 self.highlighted_border_color.hash(state);
75 self.bg_color.hash(state);
76 self.selected_bg_color.hash(state);
77 self.highlighted_bg_color.hash(state);
78 self.fg_color.hash(state);
79 self.selected_fg_color.hash(state);
80 self.highlighted_fg_color.hash(state);
81 self.title_fg_color.hash(state);
82 self.title_bg_color.hash(state);
83 self.title_position.hash(state);
84 self.selected_title_bg_color.hash(state);
85 self.selected_title_fg_color.hash(state);
86 self.highlighted_title_bg_color.hash(state);
87 self.highlighted_title_fg_color.hash(state);
88 self.menu_fg_color.hash(state);
89 self.menu_bg_color.hash(state);
90 self.selected_menu_fg_color.hash(state);
91 self.selected_menu_bg_color.hash(state);
92 self.highlighted_menu_fg_color.hash(state);
93 self.highlighted_menu_bg_color.hash(state);
94 self.error_border_color.hash(state);
95 self.error_bg_color.hash(state);
96 self.error_fg_color.hash(state);
97 self.error_title_bg_color.hash(state);
98 self.error_title_fg_color.hash(state);
99 self.error_selected_border_color.hash(state);
100 self.error_selected_bg_color.hash(state);
101 self.error_selected_fg_color.hash(state);
102 self.error_selected_title_bg_color.hash(state);
103 self.error_selected_title_fg_color.hash(state);
104 self.overflow_behavior.hash(state);
105 self.root.hash(state);
106 self.active.hash(state);
107 }
108}
109
110impl Layout {
111 pub fn new() -> Self {
112 Layout {
113 id: String::new(),
114 title: None,
115 refresh_interval: None,
116 children: None,
117 fill: None,
118 fill_char: None,
119 selected_fill_char: None,
120 highlighted_fill_char: None,
121 border_color: None,
122 selected_border_color: None,
123 highlighted_border_color: None,
124 bg_color: None,
125 selected_bg_color: None,
126 highlighted_bg_color: None,
127 fg_color: None,
128 selected_fg_color: None,
129 highlighted_fg_color: None,
130 title_fg_color: None,
131 title_bg_color: None,
132 title_position: None,
133 selected_title_bg_color: None,
134 selected_title_fg_color: None,
135 highlighted_title_bg_color: None,
136 highlighted_title_fg_color: None,
137 menu_fg_color: None,
138 menu_bg_color: None,
139 selected_menu_fg_color: None,
140 selected_menu_bg_color: None,
141 highlighted_menu_fg_color: None,
142 highlighted_menu_bg_color: None,
143 error_border_color: None,
144 error_bg_color: None,
145 error_fg_color: None,
146 error_title_bg_color: None,
147 error_title_fg_color: None,
148 error_selected_border_color: None,
149 error_selected_bg_color: None,
150 error_selected_fg_color: None,
151 error_selected_title_bg_color: None,
152 error_selected_title_fg_color: None,
153 overflow_behavior: None,
154 root: Some(false),
155 on_keypress: None,
156 active: Some(false),
157 muxbox_ids_in_tab_order: None,
158 }
159 }
160
161 pub fn get_muxbox_by_id(&self, id: &str) -> Option<&MuxBox> {
162 fn recursive_search<'a>(muxboxes: &'a [MuxBox], id: &str) -> Option<&'a MuxBox> {
163 for muxbox in muxboxes {
164 if muxbox.id == id {
165 return Some(muxbox);
166 }
167 if let Some(ref children) = muxbox.children {
168 if let Some(found) = recursive_search(children, id) {
169 return Some(found);
170 }
171 }
172 }
173 None
174 }
175
176 if let Some(ref children) = self.children {
177 recursive_search(children, id)
178 } else {
179 None
180 }
181 }
182
183 pub fn get_muxbox_by_id_mut(&mut self, id: &str) -> Option<&mut MuxBox> {
184 fn recursive_search<'a>(muxboxes: &'a mut [MuxBox], id: &str) -> Option<&'a mut MuxBox> {
185 for muxbox in muxboxes {
186 if muxbox.id == id {
187 return Some(muxbox);
188 }
189 if let Some(ref mut children) = muxbox.children {
190 if let Some(found) = recursive_search(children, id) {
191 return Some(found);
192 }
193 }
194 }
195 None
196 }
197
198 if let Some(ref mut children) = self.children {
199 recursive_search(children, id)
200 } else {
201 None
202 }
203 }
204
205 pub fn get_selected_muxboxes(&self) -> Vec<&MuxBox> {
206 fn recursive_collect<'a>(muxboxes: &'a [MuxBox], selected_muxboxes: &mut Vec<&'a MuxBox>) {
207 for muxbox in muxboxes {
208 if muxbox.selected.unwrap_or(false) {
209 selected_muxboxes.push(muxbox);
210 }
211 if let Some(ref children) = muxbox.children {
212 recursive_collect(children, selected_muxboxes);
213 }
214 }
215 }
216
217 let mut selected_muxboxes = Vec::new();
218
219 if let Some(ref children) = self.children {
220 recursive_collect(children, &mut selected_muxboxes);
221 }
222 selected_muxboxes
223 }
224
225 pub fn select_only_muxbox(&mut self, id: &str) {
226 fn recursive_select(muxboxes: &mut [MuxBox], id: &str) {
227 for muxbox in muxboxes {
228 muxbox.selected = Some(muxbox.id == id);
229 if let Some(ref mut children) = muxbox.children {
230 recursive_select(children, id);
231 }
232 }
233 }
234
235 if let Some(ref mut children) = self.children {
236 recursive_select(children, id);
237 }
238 }
239
240 pub fn get_muxboxes_in_tab_order(&mut self) -> Vec<&MuxBox> {
241 fn collect_muxboxes_recursive<'a>(muxbox: &'a MuxBox, muxboxes: &mut Vec<&'a MuxBox>) {
242 if muxbox.tab_order.is_some() {
244 muxboxes.push(muxbox);
245 }
246
247 if let Some(children) = &muxbox.children {
249 for child in children {
250 collect_muxboxes_recursive(child, muxboxes);
251 }
252 }
253 }
254
255 if self.muxbox_ids_in_tab_order.is_some() {
256 let mut muxboxes = Vec::new();
257 for muxbox_id in self.muxbox_ids_in_tab_order.as_ref().unwrap() {
258 if let Some(muxbox) = self.get_muxbox_by_id(muxbox_id) {
259 muxboxes.push(muxbox);
260 }
261 }
262 muxboxes
263 } else {
264 let mut muxboxes = Vec::new();
265 if let Some(children) = &self.children {
267 for muxbox in children {
268 collect_muxboxes_recursive(muxbox, &mut muxboxes);
269 }
270 }
271
272 muxboxes.sort_by(|a, b| {
274 a.tab_order
275 .as_ref()
276 .unwrap()
277 .cmp(b.tab_order.as_ref().unwrap())
278 });
279
280 self.muxbox_ids_in_tab_order = Some(muxboxes.iter().map(|p| p.id.clone()).collect());
281
282 muxboxes
283 }
284 }
285
286 pub fn get_all_muxboxes(&self) -> Vec<&MuxBox> {
287 fn recursive_collect<'a>(muxboxes: &'a [MuxBox], all_muxboxes: &mut Vec<&'a MuxBox>) {
288 for muxbox in muxboxes {
289 all_muxboxes.push(muxbox);
290 if let Some(ref children) = muxbox.children {
291 recursive_collect(children, all_muxboxes);
292 }
293 }
294 }
295
296 let mut all_muxboxes = Vec::new();
297 if let Some(ref children) = self.children {
298 recursive_collect(children, &mut all_muxboxes);
299 }
300 all_muxboxes
301 }
302
303 pub fn select_next_muxbox(&mut self) {
304 let muxboxes = self.get_muxboxes_in_tab_order();
305 if muxboxes.is_empty() {
306 return; }
308
309 let selected_muxbox_index = muxboxes.iter().position(|p| p.selected.unwrap_or(false));
310
311 let next_muxbox_index = match selected_muxbox_index {
312 Some(index) => (index + 1) % muxboxes.len(), None => 0, };
315
316 let next_muxbox_id = muxboxes[next_muxbox_index].id.clone();
317 self.select_only_muxbox(&next_muxbox_id);
318 }
319
320 pub fn select_previous_muxbox(&mut self) {
321 let muxboxes = self.get_muxboxes_in_tab_order();
322 if muxboxes.is_empty() {
323 return; }
325
326 let selected_muxbox_index = muxboxes.iter().position(|p| p.selected.unwrap_or(false));
327
328 let previous_muxbox_index = match selected_muxbox_index {
329 Some(index) => {
330 if index == 0 {
331 muxboxes.len() - 1 } else {
333 index - 1 }
335 }
336 None => muxboxes.len() - 1, };
338
339 let previous_muxbox_id = muxboxes[previous_muxbox_index].id.clone();
340 self.select_only_muxbox(&previous_muxbox_id);
341 }
342
343 pub fn deselect_all_muxboxes(&mut self) {
344 if let Some(children) = &mut self.children {
345 for muxbox in children {
346 muxbox.selected = Some(false);
347 }
348 }
349 }
350
351 pub fn replace_muxbox_recursive(&mut self, replacement_muxbox: &MuxBox) -> Option<bool> {
352 fn replace_in_muxboxes(muxboxes: &mut [MuxBox], replacement: &MuxBox) -> bool {
353 for muxbox in muxboxes {
354 if muxbox.id == replacement.id {
355 *muxbox = replacement.clone();
356 return true;
357 }
358 if let Some(ref mut children) = muxbox.children {
359 if replace_in_muxboxes(children, replacement) {
360 return true;
361 }
362 }
363 }
364 false
365 }
366
367 if let Some(ref mut children) = self.children {
368 Some(replace_in_muxboxes(children, replacement_muxbox))
369 } else {
370 Some(false)
371 }
372 }
373
374 pub fn find_muxbox_with_choice(&self, choice_id: &str) -> Option<&MuxBox> {
375 fn find_in_muxboxes<'a>(muxboxes: &'a [MuxBox], choice_id: &str) -> Option<&'a MuxBox> {
376 for muxbox in muxboxes {
377 for stream in muxbox.streams.values() {
380 if let Some(choices) = &stream.choices {
381 if choices.iter().any(|c| c.id == choice_id) {
382 return Some(muxbox);
383 }
384 }
385 }
386 if let Some(ref children) = muxbox.children {
387 if let Some(found) = find_in_muxboxes(children, choice_id) {
388 return Some(found);
389 }
390 }
391 }
392 None
393 }
394
395 if let Some(ref children) = self.children {
396 find_in_muxboxes(children, choice_id)
397 } else {
398 None
399 }
400 }
401
402 pub fn find_muxbox_at_coordinates(&self, x: u16, y: u16) -> Option<&MuxBox> {
403 let root_bounds = screen_bounds();
404 self.find_muxbox_at_coordinates_with_bounds(x, y, &root_bounds)
405 }
406
407 pub fn find_muxbox_at_coordinates_with_bounds(
408 &self,
409 x: u16,
410 y: u16,
411 root_bounds: &Bounds,
412 ) -> Option<&MuxBox> {
413 fn find_in_muxboxes_at_coords<'a>(
414 muxboxes: &'a [MuxBox],
415 x: usize,
416 y: usize,
417 parent_bounds: &Bounds,
418 ) -> Option<&'a MuxBox> {
419 let mut top: Option<(&MuxBox, Bounds)> = None;
428 let mut top_z = i32::MIN;
429 for muxbox in muxboxes {
430 let bounds = muxbox.bounds_with_parent(parent_bounds);
431 if bounds.contains_point(x, y) {
432 let z = muxbox.effective_z_index();
433 if top.is_none() || z >= top_z {
435 top_z = z;
436 top = Some((muxbox, bounds));
437 }
438 }
439 }
440
441 if let Some((muxbox, bounds)) = top {
442 if let Some(ref children) = muxbox.children {
444 if let Some(child_muxbox) = find_in_muxboxes_at_coords(children, x, y, &bounds)
445 {
446 return Some(child_muxbox);
447 }
448 }
449 return Some(muxbox);
451 }
452 None
453 }
454
455 if let Some(ref children) = self.children {
456 find_in_muxboxes_at_coords(children, x as usize, y as usize, root_bounds)
457 } else {
458 None
459 }
460 }
461
462 fn generate_children_diff(&self, other: &Self) -> Vec<FieldUpdate> {
463 let mut updates = Vec::new();
464
465 let self_children = self.children.as_deref().unwrap_or(&[]);
467 let other_children = other.children.as_deref().unwrap_or(&[]);
468
469 for (self_child, other_child) in self_children.iter().zip(other_children) {
471 let child_diffs = self_child.generate_diff(other_child);
472 updates.extend(child_diffs.into_iter());
473 }
474
475 if self_children.len() < other_children.len() {
477 for other_child in &other_children[self_children.len()..] {
478 updates.push(FieldUpdate {
479 entity_type: EntityType::MuxBox,
480 entity_id: Some(other_child.id.clone()),
481 field_name: "children".to_string(),
482 new_value: serde_json::to_value(other_child).unwrap(),
483 });
484 }
485 }
486
487 if self_children.len() > other_children.len() {
489 for self_child in &self_children[other_children.len()..] {
490 updates.push(FieldUpdate {
491 entity_type: EntityType::MuxBox,
492 entity_id: Some(self_child.id.clone()),
493 field_name: "children".to_string(),
494 new_value: Value::Null, });
496 }
497 }
498
499 updates
500 }
501
502 fn apply_children_updates(&mut self, updates: Vec<FieldUpdate>) {
503 for update in updates {
504 if update.entity_type != EntityType::MuxBox {
505 continue;
506 }
507 if let Some(entity_id) = &update.entity_id {
508 if self
510 .children
511 .as_ref()
512 .is_some_and(|children| children.iter().any(|p| p.id == *entity_id))
513 {
514 if let Some(child_muxbox) = self
516 .children
517 .as_mut()
518 .unwrap()
519 .iter_mut()
520 .find(|p| p.id == *entity_id)
521 {
522 child_muxbox.apply_updates(vec![FieldUpdate {
523 entity_type: EntityType::MuxBox,
524 entity_id: Some(child_muxbox.id.clone()),
525 field_name: update.field_name.clone(),
526 new_value: update.new_value.clone(),
527 }]);
528 }
529 }
530 }
531
532 if update.field_name == "children" {
534 match update.new_value {
535 Value::Null => {
536 self.children = None;
538 }
539 _ => {
540 if let Ok(new_children) =
541 serde_json::from_value::<Vec<MuxBox>>(update.new_value.clone())
542 {
543 if self.children.is_none() {
544 self.children = Some(new_children);
546 } else {
547 let self_children = self.children.as_mut().unwrap();
548 for new_child in new_children {
549 if let Some(existing_child) =
550 self_children.iter_mut().find(|p| p.id == new_child.id)
551 {
552 *existing_child = new_child;
554 } else {
555 self_children.push(new_child);
557 }
558 }
559 }
560 }
561 }
562 }
563 }
564 }
565 }
566}
567
568impl Clone for Layout {
569 fn clone(&self) -> Self {
570 let mut cloned_children = None;
571 if let Some(ref children) = self.children {
572 cloned_children = Some(children.to_vec());
573 }
574
575 Layout {
576 id: self.id.clone(),
577 title: self.title.clone(),
578 refresh_interval: self.refresh_interval,
579 children: cloned_children,
580 fill: self.fill,
581 fill_char: self.fill_char,
582 selected_fill_char: self.selected_fill_char,
583 highlighted_fill_char: self.highlighted_fill_char,
584 border_color: self.border_color.clone(),
585 selected_border_color: self.selected_border_color.clone(),
586 highlighted_border_color: self.highlighted_border_color.clone(),
587 bg_color: self.bg_color.clone(),
588 selected_bg_color: self.selected_bg_color.clone(),
589 highlighted_bg_color: self.highlighted_bg_color.clone(),
590 fg_color: self.fg_color.clone(),
591 selected_fg_color: self.selected_fg_color.clone(),
592 highlighted_fg_color: self.highlighted_fg_color.clone(),
593 title_fg_color: self.title_fg_color.clone(),
594 title_bg_color: self.title_bg_color.clone(),
595 title_position: self.title_position.clone(),
596 selected_title_bg_color: self.selected_title_bg_color.clone(),
597 selected_title_fg_color: self.selected_title_fg_color.clone(),
598 highlighted_title_bg_color: self.highlighted_title_bg_color.clone(),
599 highlighted_title_fg_color: self.highlighted_title_fg_color.clone(),
600 menu_fg_color: self.menu_fg_color.clone(),
601 menu_bg_color: self.menu_bg_color.clone(),
602 selected_menu_fg_color: self.selected_menu_fg_color.clone(),
603 selected_menu_bg_color: self.selected_menu_bg_color.clone(),
604 highlighted_menu_fg_color: self.highlighted_menu_fg_color.clone(),
605 highlighted_menu_bg_color: self.highlighted_menu_bg_color.clone(),
606 error_border_color: self.error_border_color.clone(),
607 error_bg_color: self.error_bg_color.clone(),
608 error_fg_color: self.error_fg_color.clone(),
609 error_title_bg_color: self.error_title_bg_color.clone(),
610 error_title_fg_color: self.error_title_fg_color.clone(),
611 error_selected_border_color: self.error_selected_border_color.clone(),
612 error_selected_bg_color: self.error_selected_bg_color.clone(),
613 error_selected_fg_color: self.error_selected_fg_color.clone(),
614 error_selected_title_bg_color: self.error_selected_title_bg_color.clone(),
615 error_selected_title_fg_color: self.error_selected_title_fg_color.clone(),
616 overflow_behavior: self.overflow_behavior.clone(),
617 root: self.root,
618 on_keypress: self.on_keypress.clone(),
619 active: self.active,
620 muxbox_ids_in_tab_order: self.muxbox_ids_in_tab_order.clone(),
621 }
622 }
623}
624
625impl Updatable for Layout {
627 fn generate_diff(&self, other: &Self) -> Vec<FieldUpdate> {
628 let mut updates = Vec::new();
629
630 if self.title != other.title {
632 if let Some(new_value) = &other.title {
633 updates.push(FieldUpdate {
634 entity_type: EntityType::Layout,
635 entity_id: Some(self.id.clone()),
636 field_name: "title".to_string(),
637 new_value: serde_json::to_value(new_value).unwrap(),
638 });
639 }
640 }
641 if self.refresh_interval != other.refresh_interval {
642 if let Some(new_value) = other.refresh_interval {
643 updates.push(FieldUpdate {
644 entity_type: EntityType::Layout,
645 entity_id: Some(self.id.clone()), field_name: "refresh_interval".to_string(),
647 new_value: serde_json::to_value(new_value).unwrap(),
648 });
649 }
650 }
651
652 updates.extend(self.generate_children_diff(other));
653
654 if self.fill != other.fill {
656 if let Some(new_value) = other.fill {
657 updates.push(FieldUpdate {
658 entity_type: EntityType::Layout,
659 entity_id: Some(self.id.clone()), field_name: "fill".to_string(),
661 new_value: serde_json::to_value(new_value).unwrap(),
662 });
663 }
664 }
665
666 if self.fill_char != other.fill_char {
667 if let Some(new_value) = other.fill_char {
668 updates.push(FieldUpdate {
669 entity_type: EntityType::Layout,
670 entity_id: Some(self.id.clone()), field_name: "fill_char".to_string(),
672 new_value: serde_json::to_value(new_value).unwrap(),
673 });
674 }
675 }
676
677 if self.selected_fill_char != other.selected_fill_char {
678 if let Some(new_value) = other.selected_fill_char {
679 updates.push(FieldUpdate {
680 entity_type: EntityType::Layout,
681 entity_id: Some(self.id.clone()), field_name: "selected_fill_char".to_string(),
683 new_value: serde_json::to_value(new_value).unwrap(),
684 });
685 }
686 }
687
688 if self.border_color != other.border_color {
689 if let Some(new_value) = &other.border_color {
690 updates.push(FieldUpdate {
691 entity_type: EntityType::Layout,
692 entity_id: Some(self.id.clone()), field_name: "border_color".to_string(),
694 new_value: serde_json::to_value(new_value).unwrap(),
695 });
696 }
697 }
698
699 if self.selected_border_color != other.selected_border_color {
700 if let Some(new_value) = &other.selected_border_color {
701 updates.push(FieldUpdate {
702 entity_type: EntityType::Layout,
703 entity_id: Some(self.id.clone()), field_name: "selected_border_color".to_string(),
705 new_value: serde_json::to_value(new_value).unwrap(),
706 });
707 }
708 }
709
710 if self.bg_color != other.bg_color {
711 if let Some(new_value) = &other.bg_color {
712 updates.push(FieldUpdate {
713 entity_type: EntityType::Layout,
714 entity_id: Some(self.id.clone()), field_name: "bg_color".to_string(),
716 new_value: serde_json::to_value(new_value).unwrap(),
717 });
718 }
719 }
720
721 if self.selected_bg_color != other.selected_bg_color {
722 if let Some(new_value) = &other.selected_bg_color {
723 updates.push(FieldUpdate {
724 entity_type: EntityType::Layout,
725 entity_id: Some(self.id.clone()), field_name: "selected_bg_color".to_string(),
727 new_value: serde_json::to_value(new_value).unwrap(),
728 });
729 }
730 }
731
732 if self.fg_color != other.fg_color {
733 if let Some(new_value) = &other.fg_color {
734 updates.push(FieldUpdate {
735 entity_type: EntityType::Layout,
736 entity_id: Some(self.id.clone()), field_name: "fg_color".to_string(),
738 new_value: serde_json::to_value(new_value).unwrap(),
739 });
740 }
741 }
742
743 if self.selected_fg_color != other.selected_fg_color {
744 if let Some(new_value) = &other.selected_fg_color {
745 updates.push(FieldUpdate {
746 entity_type: EntityType::Layout,
747 entity_id: Some(self.id.clone()), field_name: "selected_fg_color".to_string(),
749 new_value: serde_json::to_value(new_value).unwrap(),
750 });
751 }
752 }
753
754 if self.title_fg_color != other.title_fg_color {
755 if let Some(new_value) = &other.title_fg_color {
756 updates.push(FieldUpdate {
757 entity_type: EntityType::Layout,
758 entity_id: Some(self.id.clone()), field_name: "title_fg_color".to_string(),
760 new_value: serde_json::to_value(new_value).unwrap(),
761 });
762 }
763 }
764
765 if self.title_bg_color != other.title_bg_color {
766 if let Some(new_value) = &other.title_bg_color {
767 updates.push(FieldUpdate {
768 entity_type: EntityType::Layout,
769 entity_id: Some(self.id.clone()), field_name: "title_bg_color".to_string(),
771 new_value: serde_json::to_value(new_value).unwrap(),
772 });
773 }
774 }
775
776 if self.title_position != other.title_position {
777 if let Some(new_value) = &other.title_position {
778 updates.push(FieldUpdate {
779 entity_type: EntityType::Layout,
780 entity_id: Some(self.id.clone()), field_name: "title_position".to_string(),
782 new_value: serde_json::to_value(new_value).unwrap(),
783 });
784 }
785 }
786
787 if self.selected_title_bg_color != other.selected_title_bg_color {
788 if let Some(new_value) = &other.selected_title_bg_color {
789 updates.push(FieldUpdate {
790 entity_type: EntityType::Layout,
791 entity_id: Some(self.id.clone()), field_name: "selected_title_bg_color".to_string(),
793 new_value: serde_json::to_value(new_value).unwrap(),
794 });
795 }
796 }
797
798 if self.selected_title_fg_color != other.selected_title_fg_color {
799 if let Some(new_value) = &other.selected_title_fg_color {
800 updates.push(FieldUpdate {
801 entity_type: EntityType::Layout,
802 entity_id: Some(self.id.clone()), field_name: "selected_title_fg_color".to_string(),
804 new_value: serde_json::to_value(new_value).unwrap(),
805 });
806 }
807 }
808
809 if self.menu_fg_color != other.menu_fg_color {
810 if let Some(new_value) = &other.menu_fg_color {
811 updates.push(FieldUpdate {
812 entity_type: EntityType::Layout,
813 entity_id: Some(self.id.clone()),
814 field_name: "menu_fg_color".to_string(),
815 new_value: serde_json::to_value(new_value).unwrap(),
816 });
817 }
818 }
819
820 if self.menu_bg_color != other.menu_bg_color {
821 if let Some(new_value) = &other.menu_bg_color {
822 updates.push(FieldUpdate {
823 entity_type: EntityType::Layout,
824 entity_id: Some(self.id.clone()),
825 field_name: "menu_bg_color".to_string(),
826 new_value: serde_json::to_value(new_value).unwrap(),
827 });
828 }
829 }
830
831 if self.selected_menu_fg_color != other.selected_menu_fg_color {
832 if let Some(new_value) = &other.selected_menu_fg_color {
833 updates.push(FieldUpdate {
834 entity_type: EntityType::Layout,
835 entity_id: Some(self.id.clone()),
836 field_name: "selected_menu_fg_color".to_string(),
837 new_value: serde_json::to_value(new_value).unwrap(),
838 });
839 }
840 }
841
842 if self.selected_menu_bg_color != other.selected_menu_bg_color {
843 if let Some(new_value) = &other.selected_menu_bg_color {
844 updates.push(FieldUpdate {
845 entity_type: EntityType::Layout,
846 entity_id: Some(self.id.clone()),
847 field_name: "selected_menu_bg_color".to_string(),
848 new_value: serde_json::to_value(new_value).unwrap(),
849 });
850 }
851 }
852
853 if self.error_border_color != other.error_border_color {
854 if let Some(new_value) = &other.error_border_color {
855 updates.push(FieldUpdate {
856 entity_type: EntityType::Layout,
857 entity_id: Some(self.id.clone()),
858 field_name: "error_border_color".to_string(),
859 new_value: serde_json::to_value(new_value).unwrap(),
860 });
861 }
862 }
863
864 if self.error_bg_color != other.error_bg_color {
865 if let Some(new_value) = &other.error_bg_color {
866 updates.push(FieldUpdate {
867 entity_type: EntityType::Layout,
868 entity_id: Some(self.id.clone()),
869 field_name: "error_bg_color".to_string(),
870 new_value: serde_json::to_value(new_value).unwrap(),
871 });
872 }
873 }
874
875 if self.error_fg_color != other.error_fg_color {
876 if let Some(new_value) = &other.error_fg_color {
877 updates.push(FieldUpdate {
878 entity_type: EntityType::Layout,
879 entity_id: Some(self.id.clone()),
880 field_name: "error_fg_color".to_string(),
881 new_value: serde_json::to_value(new_value).unwrap(),
882 });
883 }
884 }
885
886 if self.error_title_bg_color != other.error_title_bg_color {
887 if let Some(new_value) = &other.error_title_bg_color {
888 updates.push(FieldUpdate {
889 entity_type: EntityType::Layout,
890 entity_id: Some(self.id.clone()),
891 field_name: "error_title_bg_color".to_string(),
892 new_value: serde_json::to_value(new_value).unwrap(),
893 });
894 }
895 }
896
897 if self.error_title_fg_color != other.error_title_fg_color {
898 if let Some(new_value) = &other.error_title_fg_color {
899 updates.push(FieldUpdate {
900 entity_type: EntityType::Layout,
901 entity_id: Some(self.id.clone()),
902 field_name: "error_title_fg_color".to_string(),
903 new_value: serde_json::to_value(new_value).unwrap(),
904 });
905 }
906 }
907
908 if self.error_selected_border_color != other.error_selected_border_color {
909 if let Some(new_value) = &other.error_selected_border_color {
910 updates.push(FieldUpdate {
911 entity_type: EntityType::Layout,
912 entity_id: Some(self.id.clone()),
913 field_name: "error_selected_border_color".to_string(),
914 new_value: serde_json::to_value(new_value).unwrap(),
915 });
916 }
917 }
918
919 if self.error_selected_bg_color != other.error_selected_bg_color {
920 if let Some(new_value) = &other.error_selected_bg_color {
921 updates.push(FieldUpdate {
922 entity_type: EntityType::Layout,
923 entity_id: Some(self.id.clone()),
924 field_name: "error_selected_bg_color".to_string(),
925 new_value: serde_json::to_value(new_value).unwrap(),
926 });
927 }
928 }
929
930 if self.error_selected_fg_color != other.error_selected_fg_color {
931 if let Some(new_value) = &other.error_selected_fg_color {
932 updates.push(FieldUpdate {
933 entity_type: EntityType::Layout,
934 entity_id: Some(self.id.clone()),
935 field_name: "error_selected_fg_color".to_string(),
936 new_value: serde_json::to_value(new_value).unwrap(),
937 });
938 }
939 }
940
941 if self.error_selected_title_bg_color != other.error_selected_title_bg_color {
942 if let Some(new_value) = &other.error_selected_title_bg_color {
943 updates.push(FieldUpdate {
944 entity_type: EntityType::Layout,
945 entity_id: Some(self.id.clone()),
946 field_name: "error_selected_title_bg_color".to_string(),
947 new_value: serde_json::to_value(new_value).unwrap(),
948 });
949 }
950 }
951
952 if self.error_selected_title_fg_color != other.error_selected_title_fg_color {
953 if let Some(new_value) = &other.error_selected_title_fg_color {
954 updates.push(FieldUpdate {
955 entity_type: EntityType::Layout,
956 entity_id: Some(self.id.clone()),
957 field_name: "error_selected_title_fg_color".to_string(),
958 new_value: serde_json::to_value(new_value).unwrap(),
959 });
960 }
961 }
962
963 if self.overflow_behavior != other.overflow_behavior {
964 if let Some(new_value) = &other.overflow_behavior {
965 updates.push(FieldUpdate {
966 entity_type: EntityType::Layout,
967 entity_id: Some(self.id.clone()), field_name: "overflow_behavior".to_string(),
969 new_value: serde_json::to_value(new_value).unwrap(),
970 });
971 }
972 }
973
974 if self.root != other.root {
975 if let Some(new_value) = other.root {
976 updates.push(FieldUpdate {
977 entity_type: EntityType::Layout,
978 entity_id: Some(self.id.clone()), field_name: "root".to_string(),
980 new_value: serde_json::to_value(new_value).unwrap(),
981 });
982 }
983 }
984
985 if self.on_keypress != other.on_keypress {
986 if let Some(new_value) = &other.on_keypress {
987 updates.push(FieldUpdate {
988 entity_type: EntityType::Layout,
989 entity_id: Some(self.id.clone()), field_name: "on_keypress".to_string(),
991 new_value: serde_json::to_value(new_value).unwrap(),
992 });
993 }
994 }
995
996 if self.active != other.active {
997 if let Some(new_value) = other.active {
998 updates.push(FieldUpdate {
999 entity_type: EntityType::Layout,
1000 entity_id: Some(self.id.clone()), field_name: "active".to_string(),
1002 new_value: serde_json::to_value(new_value).unwrap(),
1003 });
1004 }
1005 }
1006
1007 if self.muxbox_ids_in_tab_order != other.muxbox_ids_in_tab_order {
1008 if let Some(new_value) = &other.muxbox_ids_in_tab_order {
1009 updates.push(FieldUpdate {
1010 entity_type: EntityType::Layout,
1011 entity_id: Some(self.id.clone()), field_name: "muxbox_ids_in_tab_order".to_string(),
1013 new_value: serde_json::to_value(new_value).unwrap(),
1014 });
1015 }
1016 }
1017
1018 updates
1019 }
1020
1021 fn apply_updates(&mut self, updates: Vec<FieldUpdate>) {
1022 let updates_for_children = updates.clone();
1023
1024 for update in updates {
1025 if update.entity_type != EntityType::Layout {
1026 continue;
1027 }
1028 match update.field_name.as_str() {
1029 "title" => {
1030 if let Some(new_title) = update.new_value.as_str() {
1031 self.title = Some(new_title.to_string());
1032 }
1033 }
1034 "refresh_interval" => {
1035 if let Some(new_refresh_interval) = update.new_value.as_u64() {
1036 self.refresh_interval = Some(new_refresh_interval);
1037 }
1038 }
1039 "fill" => {
1040 if let Some(new_fill) = update.new_value.as_bool() {
1041 self.fill = Some(new_fill);
1042 }
1043 }
1044 "fill_char" => {
1045 if let Some(new_fill_char) = update.new_value.as_str() {
1046 self.fill_char = new_fill_char.chars().next();
1047 }
1048 }
1049 "selected_fill_char" => {
1050 if let Some(new_selected_fill_char) = update.new_value.as_str() {
1051 self.selected_fill_char = new_selected_fill_char.chars().next();
1052 }
1053 }
1054 "border_color" => {
1055 if let Some(new_border_color) = update.new_value.as_str() {
1056 self.border_color = Some(new_border_color.to_string());
1057 }
1058 }
1059 "selected_border_color" => {
1060 if let Some(new_selected_border_color) = update.new_value.as_str() {
1061 self.selected_border_color = Some(new_selected_border_color.to_string());
1062 }
1063 }
1064 "bg_color" => {
1065 if let Some(new_bg_color) = update.new_value.as_str() {
1066 self.bg_color = Some(new_bg_color.to_string());
1067 }
1068 }
1069 "selected_bg_color" => {
1070 if let Some(new_selected_bg_color) = update.new_value.as_str() {
1071 self.selected_bg_color = Some(new_selected_bg_color.to_string());
1072 }
1073 }
1074 "fg_color" => {
1075 if let Some(new_fg_color) = update.new_value.as_str() {
1076 self.fg_color = Some(new_fg_color.to_string());
1077 }
1078 }
1079 "selected_fg_color" => {
1080 if let Some(new_selected_fg_color) = update.new_value.as_str() {
1081 self.selected_fg_color = Some(new_selected_fg_color.to_string());
1082 }
1083 }
1084 "title_fg_color" => {
1085 if let Some(new_title_fg_color) = update.new_value.as_str() {
1086 self.title_fg_color = Some(new_title_fg_color.to_string());
1087 }
1088 }
1089 "title_bg_color" => {
1090 if let Some(new_title_bg_color) = update.new_value.as_str() {
1091 self.title_bg_color = Some(new_title_bg_color.to_string());
1092 }
1093 }
1094 "title_position" => {
1095 if let Some(new_title_position) = update.new_value.as_str() {
1096 self.title_position = Some(new_title_position.to_string());
1097 }
1098 }
1099 "selected_title_bg_color" => {
1100 if let Some(new_selected_title_bg_color) = update.new_value.as_str() {
1101 self.selected_title_bg_color =
1102 Some(new_selected_title_bg_color.to_string());
1103 }
1104 }
1105 "selected_title_fg_color" => {
1106 if let Some(new_selected_title_fg_color) = update.new_value.as_str() {
1107 self.selected_title_fg_color =
1108 Some(new_selected_title_fg_color.to_string());
1109 }
1110 }
1111 "menu_fg_color" => {
1112 if let Some(new_menu_fg_color) = update.new_value.as_str() {
1113 self.menu_fg_color = Some(new_menu_fg_color.to_string());
1114 }
1115 }
1116 "menu_bg_color" => {
1117 if let Some(new_menu_bg_color) = update.new_value.as_str() {
1118 self.menu_bg_color = Some(new_menu_bg_color.to_string());
1119 }
1120 }
1121 "selected_menu_fg_color" => {
1122 if let Some(new_selected_menu_fg_color) = update.new_value.as_str() {
1123 self.selected_menu_fg_color = Some(new_selected_menu_fg_color.to_string());
1124 }
1125 }
1126 "selected_menu_bg_color" => {
1127 if let Some(new_selected_menu_bg_color) = update.new_value.as_str() {
1128 self.selected_menu_bg_color = Some(new_selected_menu_bg_color.to_string());
1129 }
1130 }
1131 "error_border_color" => {
1132 if let Some(new_error_border_color) = update.new_value.as_str() {
1133 self.error_border_color = Some(new_error_border_color.to_string());
1134 }
1135 }
1136 "error_bg_color" => {
1137 if let Some(new_error_bg_color) = update.new_value.as_str() {
1138 self.error_bg_color = Some(new_error_bg_color.to_string());
1139 }
1140 }
1141 "error_fg_color" => {
1142 if let Some(new_error_fg_color) = update.new_value.as_str() {
1143 self.error_fg_color = Some(new_error_fg_color.to_string());
1144 }
1145 }
1146 "error_title_bg_color" => {
1147 if let Some(new_error_title_bg_color) = update.new_value.as_str() {
1148 self.error_title_bg_color = Some(new_error_title_bg_color.to_string());
1149 }
1150 }
1151 "error_title_fg_color" => {
1152 if let Some(new_error_title_fg_color) = update.new_value.as_str() {
1153 self.error_title_fg_color = Some(new_error_title_fg_color.to_string());
1154 }
1155 }
1156 "error_selected_border_color" => {
1157 if let Some(new_error_selected_border_color) = update.new_value.as_str() {
1158 self.error_selected_border_color =
1159 Some(new_error_selected_border_color.to_string());
1160 }
1161 }
1162 "error_selected_bg_color" => {
1163 if let Some(new_error_selected_bg_color) = update.new_value.as_str() {
1164 self.error_selected_bg_color =
1165 Some(new_error_selected_bg_color.to_string());
1166 }
1167 }
1168 "error_selected_fg_color" => {
1169 if let Some(new_error_selected_fg_color) = update.new_value.as_str() {
1170 self.error_selected_fg_color =
1171 Some(new_error_selected_fg_color.to_string());
1172 }
1173 }
1174 "error_selected_title_bg_color" => {
1175 if let Some(new_error_selected_title_bg_color) = update.new_value.as_str() {
1176 self.error_selected_title_bg_color =
1177 Some(new_error_selected_title_bg_color.to_string());
1178 }
1179 }
1180 "error_selected_title_fg_color" => {
1181 if let Some(new_error_selected_title_fg_color) = update.new_value.as_str() {
1182 self.error_selected_title_fg_color =
1183 Some(new_error_selected_title_fg_color.to_string());
1184 }
1185 }
1186 "overflow_behavior" => {
1187 if let Some(new_overflow_behavior) = update.new_value.as_str() {
1188 self.overflow_behavior = Some(new_overflow_behavior.to_string());
1189 }
1190 }
1191 "root" => {
1192 if let Some(new_root) = update.new_value.as_bool() {
1193 self.root = Some(new_root);
1194 }
1195 }
1196 "on_keypress" => {
1197 if let Some(new_on_keypress) = update.new_value.as_object() {
1198 self.on_keypress = Some(
1199 new_on_keypress
1200 .iter()
1201 .map(|(k, v)| {
1202 (
1203 k.clone(),
1204 v.as_array()
1205 .unwrap()
1206 .iter()
1207 .map(|v| v.as_str().unwrap().to_string())
1208 .collect(),
1209 )
1210 })
1211 .collect(),
1212 );
1213 }
1214 }
1215 "active" => {
1216 if let Some(new_active) = update.new_value.as_bool() {
1217 self.active = Some(new_active);
1218 }
1219 }
1220 "muxbox_ids_in_tab_order" => {
1221 if let Some(new_muxbox_ids_in_tab_order) = update.new_value.as_array() {
1222 self.muxbox_ids_in_tab_order = Some(
1223 new_muxbox_ids_in_tab_order
1224 .iter()
1225 .map(|v| v.as_str().unwrap().to_string())
1226 .collect(),
1227 );
1228 }
1229 }
1230
1231 _ => {
1232 log::warn!(
1233 "Layout::apply_updates: Ignoring unknown field name: {}",
1234 update.field_name
1235 );
1236 }
1237 }
1238 }
1239 self.apply_children_updates(updates_for_children);
1240 }
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245 use super::*;
1246 use crate::model::common::InputBounds;
1247 use crate::model::muxbox::MuxBox;
1248
1249 fn create_test_muxbox(id: &str) -> MuxBox {
1254 MuxBox {
1255 id: id.to_string(),
1256 title: Some(format!("Test MuxBox {}", id)),
1257 position: InputBounds {
1258 x1: "0%".to_string(),
1259 y1: "0%".to_string(),
1260 x2: "100%".to_string(),
1261 y2: "100%".to_string(),
1262 },
1263 tab_order: Some(id.to_string()),
1264 selected: Some(false),
1265 ..Default::default()
1266 }
1267 }
1268
1269 fn create_test_layout(id: &str, children: Option<Vec<MuxBox>>) -> Layout {
1272 Layout {
1273 id: id.to_string(),
1274 title: Some(format!("Test Layout {}", id)),
1275 children,
1276 root: Some(false),
1277 active: Some(false),
1278 ..Default::default()
1279 }
1280 }
1281
1282 #[test]
1287 fn test_layout_default() {
1288 let layout = Layout::default();
1289 assert_eq!(layout.id, "");
1290 assert_eq!(layout.title, None);
1291 assert_eq!(layout.children, None);
1292 assert_eq!(layout.root, None);
1293 assert_eq!(layout.active, None);
1294 assert_eq!(layout.refresh_interval, None);
1295 assert_eq!(layout.muxbox_ids_in_tab_order, None);
1296 }
1297
1298 #[test]
1301 fn test_layout_new() {
1302 let layout = Layout::new();
1303 assert_eq!(layout.id, "");
1304 assert_eq!(layout.title, None);
1305 assert_eq!(layout.children, None);
1306 assert_eq!(layout.root, Some(false));
1307 assert_eq!(layout.active, Some(false));
1308 assert_eq!(layout.refresh_interval, None);
1309 assert_eq!(layout.muxbox_ids_in_tab_order, None);
1310 }
1311
1312 #[test]
1317 fn test_layout_creation() {
1318 let muxbox1 = create_test_muxbox("muxbox1");
1319 let muxbox2 = create_test_muxbox("muxbox2");
1320 let children = vec![muxbox1, muxbox2];
1321
1322 let layout = Layout {
1323 id: "test_layout".to_string(),
1324 title: Some("Test Layout".to_string()),
1325 children: Some(children),
1326 root: Some(true),
1327 active: Some(true),
1328 refresh_interval: Some(1000),
1329 ..Default::default()
1330 };
1331
1332 assert_eq!(layout.id, "test_layout");
1333 assert_eq!(layout.title, Some("Test Layout".to_string()));
1334 assert_eq!(layout.children.as_ref().unwrap().len(), 2);
1335 assert_eq!(layout.root, Some(true));
1336 assert_eq!(layout.active, Some(true));
1337 assert_eq!(layout.refresh_interval, Some(1000));
1338 }
1339
1340 #[test]
1345 fn test_layout_get_muxbox_by_id() {
1346 let muxbox1 = create_test_muxbox("muxbox1");
1347 let muxbox2 = create_test_muxbox("muxbox2");
1348 let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1349
1350 let found_muxbox = layout.get_muxbox_by_id("muxbox1");
1351 assert!(found_muxbox.is_some());
1352 assert_eq!(found_muxbox.unwrap().id, "muxbox1");
1353
1354 let not_found = layout.get_muxbox_by_id("nonexistent");
1355 assert!(not_found.is_none());
1356 }
1357
1358 #[test]
1361 fn test_layout_get_muxbox_by_id_nested() {
1362 let child_muxbox = create_test_muxbox("child");
1363 let parent_muxbox = MuxBox {
1364 id: "parent".to_string(),
1365 children: Some(vec![child_muxbox]),
1366 ..Default::default()
1367 };
1368 let layout = create_test_layout("test", Some(vec![parent_muxbox]));
1369
1370 let found_child = layout.get_muxbox_by_id("child");
1371 assert!(found_child.is_some());
1372 assert_eq!(found_child.unwrap().id, "child");
1373 }
1374
1375 #[test]
1378 fn test_layout_get_muxbox_by_id_mut() {
1379 let muxbox1 = create_test_muxbox("muxbox1");
1380 let muxbox2 = create_test_muxbox("muxbox2");
1381 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1382
1383 let found_muxbox = layout.get_muxbox_by_id_mut("muxbox1");
1384 assert!(found_muxbox.is_some());
1385
1386 found_muxbox.unwrap().title = Some("Modified Title".to_string());
1388
1389 let verified_muxbox = layout.get_muxbox_by_id("muxbox1");
1391 assert_eq!(
1392 verified_muxbox.unwrap().title,
1393 Some("Modified Title".to_string())
1394 );
1395 }
1396
1397 #[test]
1400 fn test_layout_get_muxbox_by_id_mut_empty() {
1401 let mut layout = create_test_layout("test", None);
1402
1403 let found_muxbox = layout.get_muxbox_by_id_mut("nonexistent");
1404 assert!(found_muxbox.is_none());
1405 }
1406
1407 #[test]
1412 fn test_layout_get_selected_muxboxes() {
1413 let mut muxbox1 = create_test_muxbox("muxbox1");
1414 let mut muxbox2 = create_test_muxbox("muxbox2");
1415 let mut muxbox3 = create_test_muxbox("muxbox3");
1416
1417 muxbox1.selected = Some(true);
1418 muxbox2.selected = Some(false);
1419 muxbox3.selected = Some(true);
1420
1421 let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1422
1423 let selected = layout.get_selected_muxboxes();
1424 assert_eq!(selected.len(), 2);
1425 assert_eq!(selected[0].id, "muxbox1");
1426 assert_eq!(selected[1].id, "muxbox3");
1427 }
1428
1429 #[test]
1432 fn test_layout_get_selected_muxboxes_none() {
1433 let muxbox1 = create_test_muxbox("muxbox1");
1434 let muxbox2 = create_test_muxbox("muxbox2");
1435 let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1436
1437 let selected = layout.get_selected_muxboxes();
1438 assert_eq!(selected.len(), 0);
1439 }
1440
1441 #[test]
1444 fn test_layout_select_only_muxbox() {
1445 let mut muxbox1 = create_test_muxbox("muxbox1");
1446 let mut muxbox2 = create_test_muxbox("muxbox2");
1447 let mut muxbox3 = create_test_muxbox("muxbox3");
1448
1449 muxbox1.selected = Some(true);
1450 muxbox2.selected = Some(true);
1451 muxbox3.selected = Some(false);
1452
1453 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1454
1455 layout.select_only_muxbox("muxbox2");
1456
1457 let selected = layout.get_selected_muxboxes();
1458 assert_eq!(selected.len(), 1);
1459 assert_eq!(selected[0].id, "muxbox2");
1460 }
1461
1462 #[test]
1465 fn test_layout_select_only_muxbox_nonexistent() {
1466 let mut muxbox1 = create_test_muxbox("muxbox1");
1467 muxbox1.selected = Some(true);
1468
1469 let mut layout = create_test_layout("test", Some(vec![muxbox1]));
1470
1471 layout.select_only_muxbox("nonexistent");
1472
1473 let selected = layout.get_selected_muxboxes();
1475 assert_eq!(selected.len(), 0);
1476 }
1477
1478 #[test]
1481 fn test_layout_deselect_all_muxboxes() {
1482 let mut muxbox1 = create_test_muxbox("muxbox1");
1483 let mut muxbox2 = create_test_muxbox("muxbox2");
1484
1485 muxbox1.selected = Some(true);
1486 muxbox2.selected = Some(true);
1487
1488 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1489
1490 layout.deselect_all_muxboxes();
1491
1492 let selected = layout.get_selected_muxboxes();
1493 assert_eq!(selected.len(), 0);
1494 }
1495
1496 #[test]
1501 fn test_layout_get_muxboxes_in_tab_order() {
1502 let mut muxbox1 = create_test_muxbox("muxbox1");
1503 let mut muxbox2 = create_test_muxbox("muxbox2");
1504 let mut muxbox3 = create_test_muxbox("muxbox3");
1505
1506 muxbox1.tab_order = Some("3".to_string());
1507 muxbox2.tab_order = Some("1".to_string());
1508 muxbox3.tab_order = Some("2".to_string());
1509
1510 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1511
1512 let muxboxes_in_order = layout.get_muxboxes_in_tab_order();
1513 assert_eq!(muxboxes_in_order.len(), 3);
1514 assert_eq!(muxboxes_in_order[0].id, "muxbox2"); assert_eq!(muxboxes_in_order[1].id, "muxbox3"); assert_eq!(muxboxes_in_order[2].id, "muxbox1"); }
1518
1519 #[test]
1522 fn test_layout_get_muxboxes_in_tab_order_filtered() {
1523 let mut muxbox1 = create_test_muxbox("muxbox1");
1524 let mut muxbox2 = create_test_muxbox("muxbox2");
1525 let mut muxbox3 = create_test_muxbox("muxbox3");
1526
1527 muxbox1.tab_order = Some("1".to_string());
1528 muxbox2.tab_order = None; muxbox3.tab_order = Some("2".to_string());
1530
1531 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1532
1533 let muxboxes_in_order = layout.get_muxboxes_in_tab_order();
1534 assert_eq!(muxboxes_in_order.len(), 2);
1535 assert_eq!(muxboxes_in_order[0].id, "muxbox1");
1536 assert_eq!(muxboxes_in_order[1].id, "muxbox3");
1537 }
1538
1539 #[test]
1542 fn test_layout_get_muxboxes_in_tab_order_nested() {
1543 let mut child_muxbox = create_test_muxbox("child");
1544 child_muxbox.tab_order = Some("2".to_string());
1545
1546 let mut parent_muxbox = create_test_muxbox("parent");
1547 parent_muxbox.tab_order = Some("1".to_string());
1548 parent_muxbox.children = Some(vec![child_muxbox]);
1549
1550 let mut layout = create_test_layout("test", Some(vec![parent_muxbox]));
1551
1552 let muxboxes_in_order = layout.get_muxboxes_in_tab_order();
1553 assert_eq!(muxboxes_in_order.len(), 2);
1554 assert_eq!(muxboxes_in_order[0].id, "parent");
1555 assert_eq!(muxboxes_in_order[1].id, "child");
1556 }
1557
1558 #[test]
1561 fn test_layout_select_next_muxbox() {
1562 let mut muxbox1 = create_test_muxbox("muxbox1");
1563 let mut muxbox2 = create_test_muxbox("muxbox2");
1564 let mut muxbox3 = create_test_muxbox("muxbox3");
1565
1566 muxbox1.tab_order = Some("1".to_string());
1567 muxbox2.tab_order = Some("2".to_string());
1568 muxbox3.tab_order = Some("3".to_string());
1569
1570 muxbox1.selected = Some(true);
1571 muxbox2.selected = Some(false);
1572 muxbox3.selected = Some(false);
1573
1574 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1575
1576 layout.select_next_muxbox();
1577
1578 let selected = layout.get_selected_muxboxes();
1579 assert_eq!(selected.len(), 1);
1580 assert_eq!(selected[0].id, "muxbox2");
1581 }
1582
1583 #[test]
1586 fn test_layout_select_next_muxbox_wrap_around() {
1587 let mut muxbox1 = create_test_muxbox("muxbox1");
1588 let mut muxbox2 = create_test_muxbox("muxbox2");
1589
1590 muxbox1.tab_order = Some("1".to_string());
1591 muxbox2.tab_order = Some("2".to_string());
1592
1593 muxbox1.selected = Some(false);
1594 muxbox2.selected = Some(true); let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1597
1598 layout.select_next_muxbox();
1599
1600 let selected = layout.get_selected_muxboxes();
1601 assert_eq!(selected.len(), 1);
1602 assert_eq!(selected[0].id, "muxbox1"); }
1604
1605 #[test]
1608 fn test_layout_select_next_muxbox_no_selection() {
1609 let mut muxbox1 = create_test_muxbox("muxbox1");
1610 let mut muxbox2 = create_test_muxbox("muxbox2");
1611
1612 muxbox1.tab_order = Some("1".to_string());
1613 muxbox2.tab_order = Some("2".to_string());
1614
1615 muxbox1.selected = Some(false);
1616 muxbox2.selected = Some(false);
1617
1618 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1619
1620 layout.select_next_muxbox();
1621
1622 let selected = layout.get_selected_muxboxes();
1623 assert_eq!(selected.len(), 1);
1624 assert_eq!(selected[0].id, "muxbox1"); }
1626
1627 #[test]
1630 fn test_layout_select_previous_muxbox() {
1631 let mut muxbox1 = create_test_muxbox("muxbox1");
1632 let mut muxbox2 = create_test_muxbox("muxbox2");
1633 let mut muxbox3 = create_test_muxbox("muxbox3");
1634
1635 muxbox1.tab_order = Some("1".to_string());
1636 muxbox2.tab_order = Some("2".to_string());
1637 muxbox3.tab_order = Some("3".to_string());
1638
1639 muxbox1.selected = Some(false);
1640 muxbox2.selected = Some(true);
1641 muxbox3.selected = Some(false);
1642
1643 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1644
1645 layout.select_previous_muxbox();
1646
1647 let selected = layout.get_selected_muxboxes();
1648 assert_eq!(selected.len(), 1);
1649 assert_eq!(selected[0].id, "muxbox1");
1650 }
1651
1652 #[test]
1655 fn test_layout_select_previous_muxbox_wrap_around() {
1656 let mut muxbox1 = create_test_muxbox("muxbox1");
1657 let mut muxbox2 = create_test_muxbox("muxbox2");
1658
1659 muxbox1.tab_order = Some("1".to_string());
1660 muxbox2.tab_order = Some("2".to_string());
1661
1662 muxbox1.selected = Some(true); muxbox2.selected = Some(false);
1664
1665 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1666
1667 layout.select_previous_muxbox();
1668
1669 let selected = layout.get_selected_muxboxes();
1670 assert_eq!(selected.len(), 1);
1671 assert_eq!(selected[0].id, "muxbox2"); }
1673
1674 #[test]
1677 fn test_layout_select_previous_muxbox_no_selection() {
1678 let mut muxbox1 = create_test_muxbox("muxbox1");
1679 let mut muxbox2 = create_test_muxbox("muxbox2");
1680
1681 muxbox1.tab_order = Some("1".to_string());
1682 muxbox2.tab_order = Some("2".to_string());
1683
1684 muxbox1.selected = Some(false);
1685 muxbox2.selected = Some(false);
1686
1687 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1688
1689 layout.select_previous_muxbox();
1690
1691 let selected = layout.get_selected_muxboxes();
1692 assert_eq!(selected.len(), 1);
1693 assert_eq!(selected[0].id, "muxbox2"); }
1695
1696 #[test]
1699 fn test_layout_navigation_empty_muxboxes() {
1700 let mut layout = create_test_layout("test", None);
1701
1702 layout.select_next_muxbox();
1704 layout.select_previous_muxbox();
1705
1706 let selected = layout.get_selected_muxboxes();
1707 assert_eq!(selected.len(), 0);
1708 }
1709
1710 #[test]
1713 fn test_layout_navigation_no_tab_order() {
1714 let mut muxbox1 = create_test_muxbox("muxbox1");
1715 let mut muxbox2 = create_test_muxbox("muxbox2");
1716
1717 muxbox1.tab_order = None;
1718 muxbox2.tab_order = None;
1719
1720 let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1721
1722 layout.select_next_muxbox();
1724 layout.select_previous_muxbox();
1725
1726 let selected = layout.get_selected_muxboxes();
1727 assert_eq!(selected.len(), 0);
1728 }
1729
1730 #[test]
1735 fn test_layout_get_all_muxboxes() {
1736 let muxbox1 = create_test_muxbox("muxbox1");
1737 let muxbox2 = create_test_muxbox("muxbox2");
1738 let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1739
1740 let all_muxboxes = layout.get_all_muxboxes();
1741 assert_eq!(all_muxboxes.len(), 2);
1742 assert_eq!(all_muxboxes[0].id, "muxbox1");
1743 assert_eq!(all_muxboxes[1].id, "muxbox2");
1744 }
1745
1746 #[test]
1749 fn test_layout_get_all_muxboxes_nested() {
1750 let child_muxbox = create_test_muxbox("child");
1751 let parent_muxbox = MuxBox {
1752 id: "parent".to_string(),
1753 children: Some(vec![child_muxbox]),
1754 ..Default::default()
1755 };
1756 let layout = create_test_layout("test", Some(vec![parent_muxbox]));
1757
1758 let all_muxboxes = layout.get_all_muxboxes();
1759 assert_eq!(all_muxboxes.len(), 2);
1760 assert_eq!(all_muxboxes[0].id, "parent");
1761 assert_eq!(all_muxboxes[1].id, "child");
1762 }
1763
1764 #[test]
1767 fn test_layout_get_all_muxboxes_empty() {
1768 let layout = create_test_layout("test", None);
1769
1770 let all_muxboxes = layout.get_all_muxboxes();
1771 assert_eq!(all_muxboxes.len(), 0);
1772 }
1773
1774 #[test]
1779 fn test_layout_clone() {
1780 let muxbox1 = create_test_muxbox("muxbox1");
1781 let muxbox2 = create_test_muxbox("muxbox2");
1782 let layout1 = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1783 let layout2 = layout1.clone();
1784
1785 assert_eq!(layout1.id, layout2.id);
1786 assert_eq!(layout1.title, layout2.title);
1787 assert_eq!(
1788 layout1.children.as_ref().unwrap().len(),
1789 layout2.children.as_ref().unwrap().len()
1790 );
1791 assert_eq!(layout1.root, layout2.root);
1792 assert_eq!(layout1.active, layout2.active);
1793 }
1794
1795 #[test]
1798 fn test_layout_clone_nested() {
1799 let child_muxbox = create_test_muxbox("child");
1800 let parent_muxbox = MuxBox {
1801 id: "parent".to_string(),
1802 children: Some(vec![child_muxbox]),
1803 ..Default::default()
1804 };
1805 let layout1 = create_test_layout("test", Some(vec![parent_muxbox]));
1806 let layout2 = layout1.clone();
1807
1808 assert_eq!(
1809 layout1.children.as_ref().unwrap()[0]
1810 .children
1811 .as_ref()
1812 .unwrap()
1813 .len(),
1814 layout2.children.as_ref().unwrap()[0]
1815 .children
1816 .as_ref()
1817 .unwrap()
1818 .len()
1819 );
1820 assert_eq!(
1821 layout1.children.as_ref().unwrap()[0]
1822 .children
1823 .as_ref()
1824 .unwrap()[0]
1825 .id,
1826 layout2.children.as_ref().unwrap()[0]
1827 .children
1828 .as_ref()
1829 .unwrap()[0]
1830 .id
1831 );
1832 }
1833
1834 #[test]
1839 fn test_layout_hash() {
1840 let muxbox1 = create_test_muxbox("muxbox1");
1841 let muxbox2 = create_test_muxbox("muxbox2");
1842 let layout1 = create_test_layout("test", Some(vec![muxbox1.clone(), muxbox2.clone()]));
1843 let layout2 = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1844 let layout3 = create_test_layout("other", Some(vec![]));
1845
1846 use std::collections::hash_map::DefaultHasher;
1847 use std::hash::{Hash, Hasher};
1848
1849 let mut hasher1 = DefaultHasher::new();
1850 let mut hasher2 = DefaultHasher::new();
1851 let mut hasher3 = DefaultHasher::new();
1852
1853 layout1.hash(&mut hasher1);
1854 layout2.hash(&mut hasher2);
1855 layout3.hash(&mut hasher3);
1856
1857 assert_eq!(hasher1.finish(), hasher2.finish());
1858 assert_ne!(hasher1.finish(), hasher3.finish());
1859 }
1860
1861 #[test]
1866 fn test_layout_equality() {
1867 let muxbox1 = create_test_muxbox("muxbox1");
1868 let muxbox2 = create_test_muxbox("muxbox2");
1869 let layout1 = create_test_layout("test", Some(vec![muxbox1.clone(), muxbox2.clone()]));
1870 let layout2 = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1871 let layout3 = create_test_layout("other", Some(vec![]));
1872
1873 assert_eq!(layout1, layout2);
1874 assert_ne!(layout1, layout3);
1875 }
1876
1877 #[test]
1880 fn test_layout_equality_comprehensive() {
1881 let muxbox = create_test_muxbox("muxbox");
1882
1883 let layout1 = Layout {
1884 id: "test".to_string(),
1885 title: Some("Test".to_string()),
1886 children: Some(vec![muxbox.clone()]),
1887 root: Some(true),
1888 active: Some(false),
1889 ..Default::default()
1890 };
1891
1892 let layout2 = Layout {
1893 id: "test".to_string(),
1894 title: Some("Test".to_string()),
1895 children: Some(vec![muxbox.clone()]),
1896 root: Some(true),
1897 active: Some(false),
1898 ..Default::default()
1899 };
1900
1901 let layout3 = Layout {
1902 id: "test".to_string(),
1903 title: Some("Test".to_string()),
1904 children: Some(vec![muxbox]),
1905 root: Some(false), active: Some(false),
1907 ..Default::default()
1908 };
1909
1910 assert_eq!(layout1, layout2);
1911 assert_ne!(layout1, layout3);
1912 }
1913
1914 #[test]
1919 fn test_layout_empty_children_operations() {
1920 let mut layout = create_test_layout("test", Some(vec![]));
1921
1922 let muxboxes = layout.get_all_muxboxes();
1924 assert_eq!(muxboxes.len(), 0);
1925
1926 let selected = layout.get_selected_muxboxes();
1927 assert_eq!(selected.len(), 0);
1928
1929 let tab_ordered = layout.get_muxboxes_in_tab_order();
1930 assert_eq!(tab_ordered.len(), 0);
1931
1932 layout.select_next_muxbox();
1933 layout.select_previous_muxbox();
1934 layout.select_only_muxbox("nonexistent");
1935 layout.deselect_all_muxboxes();
1936 }
1937
1938 #[test]
1941 fn test_layout_none_children_operations() {
1942 let mut layout = create_test_layout("test", None);
1943
1944 let muxboxes = layout.get_all_muxboxes();
1946 assert_eq!(muxboxes.len(), 0);
1947
1948 let selected = layout.get_selected_muxboxes();
1949 assert_eq!(selected.len(), 0);
1950
1951 let tab_ordered = layout.get_muxboxes_in_tab_order();
1952 assert_eq!(tab_ordered.len(), 0);
1953
1954 layout.select_next_muxbox();
1955 layout.select_previous_muxbox();
1956 layout.select_only_muxbox("nonexistent");
1957 layout.deselect_all_muxboxes();
1958 }
1959}