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