boxmux_lib/model/
layout.rs

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            // Check if muxbox has a tab order and add it to the list
222            if muxbox.tab_order.is_some() {
223                muxboxes.push(muxbox);
224            }
225
226            // If children exist, iterate over them recursively
227            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            // Start recursion for each top-level child
245            if let Some(children) = &self.children {
246                for muxbox in children {
247                    collect_muxboxes_recursive(muxbox, &mut muxboxes);
248                }
249            }
250
251            // Sort muxboxes by their tab order
252            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; // Early return if there are no muxboxes
286        }
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(), // Get next muxbox, wrap around if at the end
292            None => 0, // No muxbox is selected, select the first one
293        };
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; // Early return if there are no muxboxes
303        }
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 // Wrap around to the last muxbox if the first one is currently selected
311                } else {
312                    index - 1 // Select the previous muxbox
313                }
314            }
315            None => muxboxes.len() - 1, // No muxbox is selected, select the last one
316        };
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            // Collect matching boxes and their children, then sort by z_index (highest first)
384            let mut candidates: Vec<&MuxBox> = Vec::new();
385            
386            // Find all boxes that contain the click coordinates
387            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            // Sort candidates by z_index (highest first for click priority)
399            candidates.sort_by_key(|muxbox| std::cmp::Reverse(muxbox.effective_z_index()));
400            
401            // Check candidates in z_index order (highest z_index first)
402            for muxbox in candidates {
403                // Check children first (they take priority over parent)
404                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 this muxbox (highest z_index among candidates)
410                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        // Get references to children, defaulting to an empty slice if None
426        let self_children = self.children.as_deref().unwrap_or(&[]);
427        let other_children = other.children.as_deref().unwrap_or(&[]);
428
429        // Compare each pair of children
430        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        // Handle extra children in other
436        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        // Handle extra children in self
448        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, // Representing removal
455                });
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                // Check if the update is for a child muxbox
469                if self.children.as_ref().map_or(false, |children| {
470                    children.iter().any(|p| p.id == *entity_id)
471                }) {
472                    // Find the child muxbox and apply the update
473                    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 the entity_id matches the parent itself and field is "children", apply to all children
491            if update.field_name == "children" {
492                match update.new_value {
493                    Value::Null => {
494                        // Removing all children
495                        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                                // Assign new children
503                                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                                        // Update existing child
511                                        *existing_child = new_child;
512                                    } else {
513                                        // Add new child
514                                        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
576// Implement Updatable for Layout
577impl Updatable for Layout {
578    fn generate_diff(&self, other: &Self) -> Vec<FieldUpdate> {
579        let mut updates = Vec::new();
580
581        // Compare each field
582        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()), // This is the entity id of the layout, not the muxbox
597                    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        // Compare other fields similarly...
606        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()), // This is the entity id of the layout, not the muxbox
611                    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()), // This is the entity id of the layout, not the muxbox
622                    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()), // This is the entity id of the layout, not the muxbox
633                    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()), // This is the entity id of the layout, not the muxbox
644                    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()), // This is the entity id of the layout, not the muxbox
655                    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()), // This is the entity id of the layout, not the muxbox
666                    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()), // This is the entity id of the layout, not the muxbox
677                    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()), // This is the entity id of the layout, not the muxbox
688                    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()), // This is the entity id of the layout, not the muxbox
699                    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()), // This is the entity id of the layout, not the muxbox
710                    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()), // This is the entity id of the layout, not the muxbox
721                    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()), // This is the entity id of the layout, not the muxbox
732                    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()), // This is the entity id of the layout, not the muxbox
743                    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()), // This is the entity id of the layout, not the muxbox
754                    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()), // This is the entity id of the layout, not the muxbox
765                    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()), // This is the entity id of the layout, not the muxbox
930                    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()), // This is the entity id of the layout, not the muxbox
941                    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()), // This is the entity id of the layout, not the muxbox
952                    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()), // This is the entity id of the layout, not the muxbox
963                    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()), // This is the entity id of the layout, not the muxbox
974                    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    // === Helper Functions ===
1217
1218    /// Creates a basic test muxbox with the given id.
1219    /// This helper demonstrates how to create a MuxBox for Layout testing.
1220    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    /// Creates a test Layout with the given id and optional children.
1237    /// This helper demonstrates how to create a Layout for testing.
1238    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    // === Layout Default Tests ===
1250
1251    /// Tests that Layout::default() creates a layout with expected default values.
1252    /// This test demonstrates the default Layout construction behavior.
1253    #[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    /// Tests that Layout::new() creates a layout with expected default values.
1266    /// This test demonstrates the Layout::new() construction behavior.
1267    #[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    // === Layout Creation Tests ===
1280
1281    /// Tests creating a Layout with specific values.
1282    /// This test demonstrates how to create a Layout with custom properties.
1283    #[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    // === Layout MuxBox Management Tests ===
1308
1309    /// Tests that Layout::get_muxbox_by_id() finds muxboxes correctly.
1310    /// This test demonstrates the muxbox retrieval feature.
1311    #[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    /// Tests that Layout::get_muxbox_by_id() finds nested muxboxes correctly.
1326    /// This test demonstrates the recursive muxbox retrieval feature.
1327    #[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    /// Tests that Layout::get_muxbox_by_id_mut() finds and allows modification.
1343    /// This test demonstrates the mutable muxbox retrieval feature.
1344    #[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        // Modify the muxbox
1354        found_muxbox.unwrap().title = Some("Modified Title".to_string());
1355
1356        // Verify the modification
1357        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    /// Tests that Layout::get_muxbox_by_id_mut() handles empty layout.
1365    /// This test demonstrates edge case handling in mutable muxbox retrieval.
1366    #[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    // === Layout MuxBox Selection Tests ===
1375
1376    /// Tests that Layout::get_selected_muxboxes() returns selected muxboxes.
1377    /// This test demonstrates the selected muxbox retrieval feature.
1378    #[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    /// Tests that Layout::get_selected_muxboxes() handles no selected muxboxes.
1397    /// This test demonstrates edge case handling in selected muxbox retrieval.
1398    #[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    /// Tests that Layout::select_only_muxbox() selects only the specified muxbox.
1409    /// This test demonstrates the exclusive muxbox selection feature.
1410    #[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    /// Tests that Layout::select_only_muxbox() handles nonexistent muxbox.
1430    /// This test demonstrates edge case handling in muxbox selection.
1431    #[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        // All muxboxes should be deselected
1441        let selected = layout.get_selected_muxboxes();
1442        assert_eq!(selected.len(), 0);
1443    }
1444
1445    /// Tests that Layout::deselect_all_muxboxes() deselects all muxboxes.
1446    /// This test demonstrates the muxbox deselection feature.
1447    #[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    // === Layout Tab Order Tests ===
1464
1465    /// Tests that Layout::get_muxboxes_in_tab_order() returns muxboxes in tab order.
1466    /// This test demonstrates the tab order retrieval feature.
1467    #[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"); // tab_order: "1"
1482        assert_eq!(muxboxes_in_order[1].id, "muxbox3"); // tab_order: "2"
1483        assert_eq!(muxboxes_in_order[2].id, "muxbox1"); // tab_order: "3"
1484    }
1485
1486    /// Tests that Layout::get_muxboxes_in_tab_order() ignores muxboxes without tab_order.
1487    /// This test demonstrates tab order filtering behavior.
1488    #[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; // No tab order
1496        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    /// Tests that Layout::get_muxboxes_in_tab_order() handles nested muxboxes.
1507    /// This test demonstrates recursive tab order retrieval.
1508    #[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    /// Tests that Layout::select_next_muxbox() advances selection correctly.
1526    /// This test demonstrates the next muxbox selection feature.
1527    #[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    /// Tests that Layout::select_next_muxbox() wraps around to first muxbox.
1551    /// This test demonstrates the wrap-around behavior in next muxbox selection.
1552    #[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); // Last muxbox selected
1562
1563        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"); // Wrapped to first
1570    }
1571
1572    /// Tests that Layout::select_next_muxbox() handles no selection.
1573    /// This test demonstrates next muxbox selection with no current selection.
1574    #[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"); // First muxbox selected
1592    }
1593
1594    /// Tests that Layout::select_previous_muxbox() moves selection backwards.
1595    /// This test demonstrates the previous muxbox selection feature.
1596    #[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    /// Tests that Layout::select_previous_muxbox() wraps around to last muxbox.
1620    /// This test demonstrates the wrap-around behavior in previous muxbox selection.
1621    #[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); // First muxbox selected
1630        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"); // Wrapped to last
1639    }
1640
1641    /// Tests that Layout::select_previous_muxbox() handles no selection.
1642    /// This test demonstrates previous muxbox selection with no current selection.
1643    #[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"); // Last muxbox selected
1661    }
1662
1663    /// Tests that Layout navigation handles empty muxbox lists.
1664    /// This test demonstrates edge case handling in muxbox navigation.
1665    #[test]
1666    fn test_layout_navigation_empty_muxboxes() {
1667        let mut layout = create_test_layout("test", None);
1668
1669        // These should not panic
1670        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    /// Tests that Layout navigation handles muxboxes without tab order.
1678    /// This test demonstrates edge case handling with non-tabbable muxboxes.
1679    #[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        // These should not panic
1690        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    // === Layout All MuxBoxes Tests ===
1698
1699    /// Tests that Layout::get_all_muxboxes() returns all muxboxes.
1700    /// This test demonstrates the all muxboxes retrieval feature.
1701    #[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    /// Tests that Layout::get_all_muxboxes() includes nested muxboxes.
1714    /// This test demonstrates recursive muxbox retrieval.
1715    #[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    /// Tests that Layout::get_all_muxboxes() handles empty layout.
1732    /// This test demonstrates edge case handling in all muxboxes retrieval.
1733    #[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    // === Layout Clone Tests ===
1742
1743    /// Tests that Layout implements Clone correctly.
1744    /// This test demonstrates Layout cloning behavior.
1745    #[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    /// Tests that Layout cloning includes nested muxboxes.
1763    /// This test demonstrates Layout cloning with nested structure.
1764    #[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    // === Layout Hash Tests ===
1802
1803    /// Tests that Layout implements Hash correctly.
1804    /// This test demonstrates Layout hashing behavior.
1805    #[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    // === Layout PartialEq Tests ===
1829
1830    /// Tests that Layout implements PartialEq correctly.
1831    /// This test demonstrates Layout equality comparison.
1832    #[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    /// Tests that Layout equality considers all fields.
1845    /// This test demonstrates comprehensive Layout equality checking.
1846    #[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), // Different root value
1873            active: Some(false),
1874            ..Default::default()
1875        };
1876
1877        assert_eq!(layout1, layout2);
1878        assert_ne!(layout1, layout3);
1879    }
1880
1881    // === Layout Edge Cases ===
1882
1883    /// Tests that Layout handles operations on empty children gracefully.
1884    /// This test demonstrates edge case handling with empty children.
1885    #[test]
1886    fn test_layout_empty_children_operations() {
1887        let mut layout = create_test_layout("test", Some(vec![]));
1888
1889        // These should not panic
1890        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    /// Tests that Layout handles None children gracefully.
1906    /// This test demonstrates edge case handling with None children.
1907    #[test]
1908    fn test_layout_none_children_operations() {
1909        let mut layout = create_test_layout("test", None);
1910
1911        // These should not panic
1912        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}