1use serde_json::Value;
2use std::{collections::HashMap, error::Error, hash::Hash};
3
4use crate::{
5 draw_utils::{get_bg_color, get_fg_color},
6 screen_bounds, screen_height, screen_width,
7 utils::input_bounds_to_bounds,
8 AppContext, AppGraph, Layout, Message, MuxBox,
9};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
13pub enum EntityType {
14 AppContext,
15 App,
16 Layout,
17 MuxBox,
18}
19
20#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
22pub struct FieldUpdate {
23 pub entity_type: EntityType, pub entity_id: Option<String>, pub field_name: String, pub new_value: Value, }
28
29pub trait Updatable {
31 fn generate_diff(&self, other: &Self) -> Vec<FieldUpdate>;
33
34 fn apply_updates(&mut self, updates: Vec<FieldUpdate>);
36}
37
38#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
39pub struct Config {
40 pub frame_delay: u64,
41 pub locked: bool, }
43
44impl Hash for Config {
45 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
46 self.frame_delay.hash(state);
47 self.locked.hash(state);
48 }
49}
50
51impl Default for Config {
52 fn default() -> Self {
53 Config {
54 frame_delay: 30,
55 locked: false, }
57 }
58}
59
60impl Config {
61 pub fn new(frame_delay: u64) -> Self {
62 let result = Config {
63 frame_delay,
64 locked: false, };
66 result.validate();
67 result
68 }
69
70 pub fn new_with_lock(frame_delay: u64, locked: bool) -> Self {
71 let result = Config {
72 frame_delay,
73 locked,
74 };
75 result.validate();
76 result
77 }
78 pub fn validate(&self) {
79 if self.frame_delay == 0 {
80 panic!("Validation error: frame_delay cannot be 0");
81 }
82 }
83}
84
85#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
86pub enum SocketFunction {
87 ReplaceBoxContent {
88 box_id: String,
89 success: bool,
90 content: String,
91 },
92 ReplaceBoxScript {
93 box_id: String,
94 script: Vec<String>,
95 },
96 StopBoxRefresh {
97 box_id: String,
98 },
99 StartBoxRefresh {
100 box_id: String,
101 },
102 ReplaceBox {
103 box_id: String,
104 new_box: MuxBox,
105 },
106 SwitchActiveLayout {
107 layout_id: String,
108 },
109 AddBox {
110 layout_id: String,
111 muxbox: MuxBox,
112 },
113 RemoveBox {
114 box_id: String,
115 },
116 KillPtyProcess {
118 box_id: String,
119 },
120 RestartPtyProcess {
121 box_id: String,
122 },
123 QueryPtyStatus {
125 box_id: String,
126 },
127 SpawnPtyProcess {
129 box_id: String,
130 script: Vec<String>,
131 libs: Option<Vec<String>>,
132 redirect_output: Option<String>,
133 },
134 SendPtyInput {
136 box_id: String,
137 input: String,
138 },
139}
140
141pub fn run_socket_function(
142 socket_function: SocketFunction,
143 app_context: &AppContext,
144) -> Result<(AppContext, Vec<Message>), Box<dyn Error>> {
145 let app_context = app_context.clone();
146 let mut messages = Vec::new();
147 match socket_function {
148 SocketFunction::ReplaceBoxContent {
149 box_id,
150 success,
151 content,
152 } => {
153 messages.push(Message::MuxBoxOutputUpdate(box_id, success, content));
154 }
155 SocketFunction::ReplaceBoxScript { box_id, script } => {
156 messages.push(Message::MuxBoxScriptUpdate(box_id, script));
157 }
158 SocketFunction::StopBoxRefresh { box_id } => {
159 messages.push(Message::StopBoxRefresh(box_id));
160 }
161 SocketFunction::StartBoxRefresh { box_id } => {
162 messages.push(Message::StartBoxRefresh(box_id));
163 }
164 SocketFunction::ReplaceBox { box_id, new_box } => {
165 messages.push(Message::ReplaceMuxBox(box_id, new_box));
166 }
167 SocketFunction::SwitchActiveLayout { layout_id } => {
168 messages.push(Message::SwitchActiveLayout(layout_id));
169 }
170 SocketFunction::AddBox { layout_id, muxbox } => {
171 messages.push(Message::AddBox(layout_id, muxbox));
172 }
173 SocketFunction::RemoveBox { box_id } => {
174 messages.push(Message::RemoveBox(box_id));
175 }
176 SocketFunction::KillPtyProcess { box_id } => {
178 if let Some(pty_manager) = &app_context.pty_manager {
179 match pty_manager.kill_pty_process(&box_id) {
180 Ok(_) => {
181 messages.push(Message::MuxBoxOutputUpdate(
182 box_id.clone(),
183 true,
184 format!("PTY process killed for box {}", box_id),
185 ));
186 }
187 Err(err) => {
188 messages.push(Message::MuxBoxOutputUpdate(
189 box_id.clone(),
190 false,
191 format!("Failed to kill PTY process: {}", err),
192 ));
193 }
194 }
195 } else {
196 messages.push(Message::MuxBoxOutputUpdate(
197 box_id.clone(),
198 false,
199 "PTY manager not available".to_string(),
200 ));
201 }
202 }
203 SocketFunction::RestartPtyProcess { box_id } => {
204 if let Some(pty_manager) = &app_context.pty_manager {
205 match pty_manager.restart_pty_process(&box_id) {
206 Ok(_) => {
207 messages.push(Message::MuxBoxOutputUpdate(
208 box_id.clone(),
209 true,
210 format!("PTY process restarted for box {}", box_id),
211 ));
212 }
213 Err(err) => {
214 messages.push(Message::MuxBoxOutputUpdate(
215 box_id.clone(),
216 false,
217 format!("Failed to restart PTY process: {}", err),
218 ));
219 }
220 }
221 } else {
222 messages.push(Message::MuxBoxOutputUpdate(
223 box_id.clone(),
224 false,
225 "PTY manager not available".to_string(),
226 ));
227 }
228 }
229 SocketFunction::QueryPtyStatus { box_id } => {
231 if let Some(pty_manager) = &app_context.pty_manager {
232 if let Some(info) = pty_manager.get_detailed_process_info(&box_id) {
233 let status_info = format!(
234 "PTY Status - Box: {}, PID: {:?}, Status: {:?}, Running: {}, Buffer Lines: {}",
235 info.muxbox_id, info.process_id, info.status, info.is_running, info.buffer_lines
236 );
237 messages.push(Message::MuxBoxOutputUpdate(
238 box_id.clone(),
239 true,
240 status_info,
241 ));
242 } else {
243 messages.push(Message::MuxBoxOutputUpdate(
244 box_id.clone(),
245 false,
246 format!("No PTY process found for box {}", box_id),
247 ));
248 }
249 } else {
250 messages.push(Message::MuxBoxOutputUpdate(
251 box_id.clone(),
252 false,
253 "PTY manager not available".to_string(),
254 ));
255 }
256 }
257 SocketFunction::SpawnPtyProcess {
259 box_id,
260 script,
261 libs,
262 redirect_output,
263 } => {
264 if let Some(pty_manager) = &app_context.pty_manager {
265 let (temp_sender, _temp_receiver) = std::sync::mpsc::channel();
268 let temp_uuid = uuid::Uuid::new_v4();
269
270 let spawn_result = if redirect_output.is_some() {
271 pty_manager.spawn_pty_script_with_redirect(
272 box_id.clone(),
273 &script,
274 libs,
275 temp_sender,
276 temp_uuid,
277 redirect_output,
278 )
279 } else {
280 pty_manager.spawn_pty_script(
281 box_id.clone(),
282 &script,
283 libs,
284 temp_sender,
285 temp_uuid,
286 )
287 };
288
289 match spawn_result {
290 Ok(_) => {
291 messages.push(Message::MuxBoxOutputUpdate(
292 box_id.clone(),
293 true,
294 format!("PTY process spawned successfully for box {}", box_id),
295 ));
296 }
297 Err(err) => {
298 messages.push(Message::MuxBoxOutputUpdate(
299 box_id.clone(),
300 false,
301 format!("Failed to spawn PTY process: {}", err),
302 ));
303 }
304 }
305 } else {
306 messages.push(Message::MuxBoxOutputUpdate(
307 box_id.clone(),
308 false,
309 "PTY manager not available".to_string(),
310 ));
311 }
312 }
313 SocketFunction::SendPtyInput { box_id, input } => {
315 if let Some(pty_manager) = &app_context.pty_manager {
316 match pty_manager.send_input(&box_id, &input) {
317 Ok(_) => {
318 messages.push(Message::MuxBoxOutputUpdate(
319 box_id.clone(),
320 true,
321 format!("Input sent successfully to PTY process for box {}", box_id),
322 ));
323 }
324 Err(err) => {
325 messages.push(Message::MuxBoxOutputUpdate(
326 box_id.clone(),
327 false,
328 format!("Failed to send input to PTY process: {}", err),
329 ));
330 }
331 }
332 } else {
333 messages.push(Message::MuxBoxOutputUpdate(
334 box_id.clone(),
335 false,
336 "PTY manager not available".to_string(),
337 ));
338 }
339 }
340 }
341 Ok((app_context, messages))
342}
343
344#[derive(Clone, PartialEq, Debug)]
345pub struct Cell {
346 pub fg_color: String,
347 pub bg_color: String,
348 pub ch: char,
349}
350
351#[derive(Debug, Clone)]
352pub struct ScreenBuffer {
353 pub width: usize,
354 pub height: usize,
355 pub buffer: Vec<Vec<Cell>>,
356}
357
358impl Default for ScreenBuffer {
359 fn default() -> Self {
360 Self::new()
361 }
362}
363
364impl ScreenBuffer {
365 pub fn new() -> Self {
366 let default_cell = Cell {
367 fg_color: get_fg_color("white"),
368 bg_color: get_bg_color("black"),
369 ch: ' ',
370 };
371 let width = screen_width();
372 let height = screen_height();
373 let buffer = vec![vec![default_cell; width]; height];
374 ScreenBuffer {
375 width,
376 height,
377 buffer,
378 }
379 }
380
381 pub fn new_custom(width: usize, height: usize) -> Self {
382 let default_cell = Cell {
383 fg_color: get_fg_color("white"),
384 bg_color: get_bg_color("black"),
385 ch: ' ',
386 };
387 let buffer = vec![vec![default_cell; width]; height];
388 ScreenBuffer {
389 width,
390 height,
391 buffer,
392 }
393 }
394
395 pub fn clear(&mut self) {
396 let default_cell = Cell {
397 fg_color: get_fg_color("white"),
398 bg_color: get_bg_color("black"),
399 ch: ' ',
400 };
401 self.buffer = vec![vec![default_cell; self.width]; self.height];
402 }
403
404 pub fn update(&mut self, x: usize, y: usize, cell: Cell) {
405 if x < self.width && y < self.height {
406 self.buffer[y][x] = cell;
407 }
408 }
409
410 pub fn get(&self, x: usize, y: usize) -> Option<&Cell> {
411 if x < self.width && y < self.height {
412 Some(&self.buffer[y][x])
413 } else {
414 None
415 }
416 }
417
418 pub fn resize(&mut self, width: usize, height: usize) {
419 if height < self.height {
421 self.buffer.truncate(height);
422 }
423 if width < self.width {
424 for row in &mut self.buffer {
425 row.truncate(width);
426 }
427 }
428
429 if height > self.height {
431 let default_row = vec![
432 Cell {
433 fg_color: get_fg_color("white"),
434 bg_color: get_bg_color("black"),
435 ch: ' ',
436 };
437 width
438 ];
439
440 self.buffer.resize_with(height, || default_row.clone());
441 }
442 if width > self.width {
443 for row in &mut self.buffer {
444 row.resize_with(width, || Cell {
445 fg_color: get_fg_color("white"),
446 bg_color: get_bg_color("black"),
447 ch: ' ',
448 });
449 }
450 }
451
452 self.width = width;
454 self.height = height;
455 }
456}
457
458#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
459pub struct InputBounds {
460 pub x1: String,
461 pub y1: String,
462 pub x2: String,
463 pub y2: String,
464}
465
466impl InputBounds {
467 pub fn to_bounds(&self, parent_bounds: &Bounds) -> Bounds {
468 input_bounds_to_bounds(self, parent_bounds)
469 }
470}
471
472#[derive(Debug, Deserialize, Serialize, Clone)]
473pub struct Bounds {
474 pub x1: usize,
475 pub y1: usize,
476 pub x2: usize,
477 pub y2: usize,
478}
479
480impl PartialEq for Bounds {
481 fn eq(&self, other: &Self) -> bool {
482 self.x1 == other.x1 && self.y1 == other.y1 && self.x2 == other.x2 && self.y2 == other.y2
483 }
484}
485
486impl Eq for Bounds {}
487
488#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq, Default)]
489pub enum Anchor {
490 TopLeft,
491 TopRight,
492 BottomLeft,
493 BottomRight,
494 #[default]
495 Center,
496 CenterTop,
497 CenterBottom,
498 CenterLeft,
499 CenterRight,
500}
501
502impl Bounds {
503 pub fn new(x1: usize, y1: usize, x2: usize, y2: usize) -> Self {
504 Bounds { x1, y1, x2, y2 }
505 }
506
507 pub fn validate(&self) {
508 if self.x1 > self.x2 {
509 panic!(
510 "Validation error: x1 ({}) is greater than x2 ({})",
511 self.x1, self.x2
512 );
513 }
514 if self.y1 > self.y2 {
515 panic!(
516 "Validation error: y1 ({}) is greater than y2 ({})",
517 self.y1, self.y2
518 );
519 }
520 }
521
522 pub fn width(&self) -> usize {
523 self.x2.saturating_sub(self.x1).saturating_add(1)
525 }
526
527 pub fn height(&self) -> usize {
528 self.y2.saturating_sub(self.y1).saturating_add(1)
530 }
531
532 pub fn to_string(&self) -> String {
533 format!("({}, {}), ({}, {})", self.x1, self.y1, self.x2, self.y2)
534 }
535
536 pub fn extend(&mut self, horizontal_amount: usize, vertical_amount: usize, anchor: Anchor) {
537 match anchor {
538 Anchor::TopLeft => {
539 self.x1 = self.x1.saturating_sub(horizontal_amount);
540 self.y1 = self.y1.saturating_sub(vertical_amount);
541 }
542 Anchor::TopRight => {
543 self.x2 += horizontal_amount;
544 self.y1 = self.y1.saturating_sub(vertical_amount);
545 }
546 Anchor::BottomLeft => {
547 self.x1 = self.x1.saturating_sub(horizontal_amount);
548 self.y2 += vertical_amount;
549 }
550 Anchor::BottomRight => {
551 self.x2 += horizontal_amount;
552 self.y2 += vertical_amount;
553 }
554 Anchor::Center => {
555 let half_horizontal = horizontal_amount / 2;
556 let half_vertical = vertical_amount / 2;
557 self.x1 = self.x1.saturating_sub(half_horizontal);
558 self.y1 = self.y1.saturating_sub(half_vertical);
559 self.x2 += half_horizontal;
560 self.y2 += half_vertical;
561 }
562 Anchor::CenterTop => {
563 let half_horizontal = horizontal_amount / 2;
564 self.x1 = self.x1.saturating_sub(half_horizontal);
565 self.x2 += half_horizontal;
566 self.y1 = self.y1.saturating_sub(vertical_amount);
567 }
568 Anchor::CenterBottom => {
569 let half_horizontal = horizontal_amount / 2;
570 self.x1 = self.x1.saturating_sub(half_horizontal);
571 self.x2 += half_horizontal;
572 self.y2 += vertical_amount;
573 }
574 Anchor::CenterLeft => {
575 let half_vertical = vertical_amount / 2;
576 self.x1 = self.x1.saturating_sub(horizontal_amount);
577 self.y1 = self.y1.saturating_sub(half_vertical);
578 self.y2 += half_vertical;
579 }
580 Anchor::CenterRight => {
581 let half_vertical = vertical_amount / 2;
582 self.x2 += horizontal_amount;
583 self.y1 = self.y1.saturating_sub(half_vertical);
584 self.y2 += half_vertical;
585 }
586 }
587 self.validate();
588 }
589
590 pub fn contract(&mut self, horizontal_amount: usize, vertical_amount: usize, anchor: Anchor) {
591 match anchor {
592 Anchor::TopLeft => {
593 self.x1 += horizontal_amount;
594 self.y1 += vertical_amount;
595 }
596 Anchor::TopRight => {
597 self.x2 = self.x2.saturating_sub(horizontal_amount);
598 self.y1 += vertical_amount;
599 }
600 Anchor::BottomLeft => {
601 self.x1 += horizontal_amount;
602 self.y2 = self.y2.saturating_sub(vertical_amount);
603 }
604 Anchor::BottomRight => {
605 self.x2 = self.x2.saturating_sub(horizontal_amount);
606 self.y2 = self.y2.saturating_sub(vertical_amount);
607 }
608 Anchor::Center => {
609 let half_horizontal = horizontal_amount / 2;
610 let half_vertical = vertical_amount / 2;
611 self.x1 += half_horizontal;
612 self.y1 += half_vertical;
613 self.x2 = self.x2.saturating_sub(half_horizontal);
614 self.y2 = self.y2.saturating_sub(half_vertical);
615 }
616 Anchor::CenterTop => {
617 let half_horizontal = horizontal_amount / 2;
618 self.x1 += half_horizontal;
619 self.x2 = self.x2.saturating_sub(half_horizontal);
620 self.y1 += vertical_amount;
621 }
622 Anchor::CenterBottom => {
623 let half_horizontal = horizontal_amount / 2;
624 self.x1 += half_horizontal;
625 self.x2 = self.x2.saturating_sub(half_horizontal);
626 self.y2 = self.y2.saturating_sub(vertical_amount);
627 }
628 Anchor::CenterLeft => {
629 let half_vertical = vertical_amount / 2;
630 self.x1 += horizontal_amount;
631 self.y1 += half_vertical;
632 self.y2 = self.y2.saturating_sub(half_vertical);
633 }
634 Anchor::CenterRight => {
635 let half_vertical = vertical_amount / 2;
636 self.x2 = self.x2.saturating_sub(horizontal_amount);
637 self.y1 += half_vertical;
638 self.y2 = self.y2.saturating_sub(half_vertical);
639 }
640 }
641 self.validate();
642 }
643
644 pub fn move_to(&mut self, x: usize, y: usize, anchor: Anchor) {
645 match anchor {
646 Anchor::TopLeft => {
647 let width = self.width();
648 let height = self.height();
649 self.x1 = x;
650 self.y1 = y;
651 self.x2 = x + width;
652 self.y2 = y + height;
653 }
654 Anchor::TopRight => {
655 let width = self.width();
656 let height = self.height();
657 self.x2 = x;
658 self.y1 = y;
659 self.x1 = x - width;
660 self.y2 = y + height;
661 }
662 Anchor::BottomLeft => {
663 let width = self.width();
664 let height = self.height();
665 self.x1 = x;
666 self.y2 = y;
667 self.x2 = x + width;
668 self.y1 = y - height;
669 }
670 Anchor::BottomRight => {
671 let width = self.width();
672 let height = self.height();
673 self.x2 = x;
674 self.y2 = y;
675 self.x1 = x - width;
676 self.y1 = y - height;
677 }
678 Anchor::Center => {
679 let width = self.width();
680 let height = self.height();
681 let half_width = width / 2;
682 let half_height = height / 2;
683 self.x1 = x - half_width;
684 self.y1 = y - half_height;
685 self.x2 = x + half_width;
686 self.y2 = y + half_height;
687 }
688 Anchor::CenterTop => {
689 let width = self.width();
690 let height = self.height();
691 let half_width = width / 2;
692 self.x1 = x - half_width;
693 self.x2 = x + half_width;
694 self.y1 = y;
695 self.y2 = y + height;
696 }
697 Anchor::CenterBottom => {
698 let width = self.width();
699 let height = self.height();
700 let half_width = width / 2;
701 self.x1 = x - half_width;
702 self.x2 = x + half_width;
703 self.y2 = y;
704 self.y1 = y - height;
705 }
706 Anchor::CenterLeft => {
707 let width = self.width();
708 let height = self.height();
709 let half_height = height / 2;
710 self.x1 = x;
711 self.x2 = x + width;
712 self.y1 = y - half_height;
713 self.y2 = y + half_height;
714 }
715 Anchor::CenterRight => {
716 let width = self.width();
717 let height = self.height();
718 let half_height = height / 2;
719 self.x2 = x;
720 self.x1 = x - width;
721 self.y1 = y - half_height;
722 self.y2 = y + half_height;
723 }
724 }
725 self.validate();
726 }
727
728 pub fn move_by(&mut self, dx: isize, dy: isize) {
729 self.x1 = (self.x1 as isize + dx) as usize;
730 self.y1 = (self.y1 as isize + dy) as usize;
731 self.x2 = (self.x2 as isize + dx) as usize;
732 self.y2 = (self.y2 as isize + dy) as usize;
733 self.validate();
734 }
735
736 pub fn contains(&self, x: usize, y: usize) -> bool {
737 x >= self.x1 && x < self.x2 && y >= self.y1 && y < self.y2
738 }
739
740 pub fn contains_bounds(&self, other: &Bounds) -> bool {
741 self.contains(other.x1, other.y1) && self.contains(other.x2, other.y2)
742 }
743
744 pub fn intersects(&self, other: &Bounds) -> bool {
745 self.contains(other.x1, other.y1)
746 || self.contains(other.x2, other.y2)
747 || self.contains(other.x1, other.y2)
748 || self.contains(other.x2, other.y1)
749 }
750
751 pub fn intersection(&self, other: &Bounds) -> Option<Bounds> {
752 if self.intersects(other) {
753 Some(Bounds {
754 x1: self.x1.max(other.x1),
755 y1: self.y1.max(other.y1),
756 x2: self.x2.min(other.x2),
757 y2: self.y2.min(other.y2),
758 })
759 } else {
760 None
761 }
762 }
763
764 pub fn union(&self, other: &Bounds) -> Bounds {
765 Bounds {
766 x1: self.x1.min(other.x1),
767 y1: self.y1.min(other.y1),
768 x2: self.x2.max(other.x2),
769 y2: self.y2.max(other.y2),
770 }
771 }
772
773 pub fn translate(&self, dx: isize, dy: isize) -> Bounds {
774 Bounds {
775 x1: (self.x1 as isize + dx) as usize,
776 y1: (self.y1 as isize + dy) as usize,
777 x2: (self.x2 as isize + dx) as usize,
778 y2: (self.y2 as isize + dy) as usize,
779 }
780 }
781
782 pub fn center(&self) -> (usize, usize) {
783 ((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
784 }
785
786 pub fn center_x(&self) -> usize {
787 (self.x1 + self.x2) / 2
788 }
789
790 pub fn center_y(&self) -> usize {
791 (self.y1 + self.y2) / 2
792 }
793
794 pub fn top_left(&self) -> (usize, usize) {
795 (self.x1, self.y1)
796 }
797
798 pub fn top_right(&self) -> (usize, usize) {
799 (self.x2, self.y1)
800 }
801
802 pub fn bottom_left(&self) -> (usize, usize) {
803 (self.x1, self.y2)
804 }
805
806 pub fn bottom_right(&self) -> (usize, usize) {
807 (self.x2, self.y2)
808 }
809
810 pub fn top(&self) -> usize {
811 self.y1
812 }
813
814 pub fn bottom(&self) -> usize {
815 self.y2
816 }
817
818 pub fn left(&self) -> usize {
819 self.x1
820 }
821
822 pub fn right(&self) -> usize {
823 self.x2
824 }
825
826 pub fn center_top(&self) -> (usize, usize) {
827 ((self.x1 + self.x2) / 2, self.y1)
828 }
829
830 pub fn center_bottom(&self) -> (usize, usize) {
831 ((self.x1 + self.x2) / 2, self.y2)
832 }
833
834 pub fn center_left(&self) -> (usize, usize) {
835 (self.x1, (self.y1 + self.y2) / 2)
836 }
837
838 pub fn center_right(&self) -> (usize, usize) {
839 (self.x2, (self.y1 + self.y2) / 2)
840 }
841}
842
843pub fn calculate_initial_bounds(app_graph: &AppGraph, layout: &Layout) -> HashMap<String, Bounds> {
844 let mut bounds_map = HashMap::new();
845
846 fn dfs(
847 app_graph: &AppGraph,
848 layout_id: &str,
849 muxbox: &MuxBox,
850 parent_bounds: Bounds,
851 bounds_map: &mut HashMap<String, Bounds>,
852 ) {
853 let bounds = muxbox.absolute_bounds(Some(&parent_bounds));
854 bounds_map.insert(muxbox.id.clone(), bounds.clone());
855
856 if let Some(children) = &muxbox.children {
857 for child in children {
858 dfs(app_graph, layout_id, child, bounds.clone(), bounds_map);
859 }
860 }
861 }
862
863 let root_bounds = screen_bounds();
864 if let Some(children) = &layout.children {
865 for muxbox in children {
866 dfs(
867 app_graph,
868 &layout.id,
869 muxbox,
870 root_bounds.clone(),
871 &mut bounds_map,
872 );
873 }
874 }
875
876 bounds_map
877}
878
879pub fn adjust_bounds_with_constraints(
880 layout: &Layout,
881 mut bounds_map: HashMap<String, Bounds>,
882) -> HashMap<String, Bounds> {
883 fn apply_constraints(muxbox: &MuxBox, bounds: &mut Bounds) {
884 if let Some(min_width) = muxbox.min_width {
885 if bounds.width() < min_width {
886 bounds.extend(min_width - bounds.width(), 0, muxbox.anchor.clone());
887 }
888 }
889 if let Some(min_height) = muxbox.min_height {
890 if bounds.height() < min_height {
891 bounds.extend(0, min_height - bounds.height(), muxbox.anchor.clone());
892 }
893 }
894 if let Some(max_width) = muxbox.max_width {
895 if bounds.width() > max_width {
896 bounds.contract(bounds.width() - max_width, 0, muxbox.anchor.clone());
897 }
898 }
899 if let Some(max_height) = muxbox.max_height {
900 if bounds.height() > max_height {
901 bounds.contract(0, bounds.height() - max_height, muxbox.anchor.clone());
902 }
903 }
904 }
905
906 fn dfs(muxbox: &MuxBox, bounds_map: &mut HashMap<String, Bounds>) -> Bounds {
907 let mut bounds = bounds_map.remove(&muxbox.id).unwrap();
908 apply_constraints(muxbox, &mut bounds);
909 bounds_map.insert(muxbox.id.clone(), bounds.clone());
910
911 if let Some(children) = &muxbox.children {
912 for child in children {
913 let child_bounds = dfs(child, bounds_map);
914 bounds.x2 = bounds.x2.max(child_bounds.x2);
915 bounds.y2 = bounds.y2.max(child_bounds.y2);
916 }
917 }
918
919 bounds
920 }
921
922 fn revalidate_children(
923 muxbox: &MuxBox,
924 bounds_map: &mut HashMap<String, Bounds>,
925 parent_bounds: &Bounds,
926 ) {
927 if let Some(children) = &muxbox.children {
928 for child in children {
929 if let Some(child_bounds) = bounds_map.get_mut(&child.id) {
930 if child_bounds.x2 > parent_bounds.x2 {
932 child_bounds.x2 = parent_bounds.x2;
933 }
934 if child_bounds.y2 > parent_bounds.y2 {
935 child_bounds.y2 = parent_bounds.y2;
936 }
937 if child_bounds.x1 < parent_bounds.x1 {
938 child_bounds.x1 = parent_bounds.x1;
939 }
940 if child_bounds.y1 < parent_bounds.y1 {
941 child_bounds.y1 = parent_bounds.y1;
942 }
943 }
944 revalidate_children(child, bounds_map, parent_bounds);
945 }
946 }
947 }
948
949 if let Some(children) = &layout.children {
950 for muxbox in children {
951 let parent_bounds = dfs(muxbox, &mut bounds_map);
952 revalidate_children(muxbox, &mut bounds_map, &parent_bounds);
953 }
954 }
955
956 bounds_map
957}
958
959pub fn calculate_bounds_map(app_graph: &AppGraph, layout: &Layout) -> HashMap<String, Bounds> {
960 let bounds_map = calculate_initial_bounds(app_graph, layout);
961 adjust_bounds_with_constraints(layout, bounds_map)
962}
963
964use std::io::{Read, Write};
965use std::os::unix::net::UnixStream;
966
967pub fn send_json_to_socket(socket_path: &str, json: &str) -> Result<String, Box<dyn Error>> {
968 let mut stream = UnixStream::connect(socket_path)?;
969 stream.write_all(json.as_bytes())?;
970 let mut response = String::new();
971 stream.read_to_string(&mut response)?;
972 Ok(response)
973}
974
975#[cfg(test)]
976mod tests {
977 use super::*;
978
979 #[test]
984 fn test_config_new_valid_frame_delay() {
985 let config = Config::new(60);
986 assert_eq!(config.frame_delay, 60);
987 }
988
989 #[test]
992 #[should_panic(expected = "Validation error: frame_delay cannot be 0")]
993 fn test_config_new_zero_frame_delay_panics() {
994 Config::new(0);
995 }
996
997 #[test]
1000 fn test_config_default() {
1001 let config = Config::default();
1002 assert_eq!(config.frame_delay, 30);
1003 }
1004
1005 #[test]
1008 #[should_panic(expected = "Validation error: frame_delay cannot be 0")]
1009 fn test_config_validate_zero_frame_delay() {
1010 let config = Config {
1011 frame_delay: 0,
1012 locked: false,
1013 };
1014 config.validate();
1015 }
1016
1017 #[test]
1020 fn test_config_validate_valid() {
1021 let config = Config {
1022 frame_delay: 16,
1023 locked: false,
1024 };
1025 config.validate(); }
1027
1028 #[test]
1031 fn test_config_hash_consistency() {
1032 let config1 = Config::new(30);
1033 let config2 = Config::new(30);
1034 let config3 = Config::new(60);
1035
1036 use std::collections::hash_map::DefaultHasher;
1037 use std::hash::{Hash, Hasher};
1038
1039 let mut hasher1 = DefaultHasher::new();
1040 let mut hasher2 = DefaultHasher::new();
1041 let mut hasher3 = DefaultHasher::new();
1042
1043 config1.hash(&mut hasher1);
1044 config2.hash(&mut hasher2);
1045 config3.hash(&mut hasher3);
1046
1047 assert_eq!(hasher1.finish(), hasher2.finish());
1048 assert_ne!(hasher1.finish(), hasher3.finish());
1049 }
1050
1051 #[test]
1056 fn test_bounds_new() {
1057 let bounds = Bounds::new(10, 20, 100, 200);
1058 assert_eq!(bounds.x1, 10);
1059 assert_eq!(bounds.y1, 20);
1060 assert_eq!(bounds.x2, 100);
1061 assert_eq!(bounds.y2, 200);
1062 }
1063
1064 #[test]
1067 #[should_panic(expected = "Validation error: x1 (100) is greater than x2 (50)")]
1068 fn test_bounds_validate_invalid_x_coordinates() {
1069 let bounds = Bounds::new(100, 20, 50, 200);
1070 bounds.validate();
1071 }
1072
1073 #[test]
1076 #[should_panic(expected = "Validation error: y1 (200) is greater than y2 (100)")]
1077 fn test_bounds_validate_invalid_y_coordinates() {
1078 let bounds = Bounds::new(10, 200, 100, 100);
1079 bounds.validate();
1080 }
1081
1082 #[test]
1085 fn test_bounds_validate_valid() {
1086 let bounds = Bounds::new(10, 20, 100, 200);
1087 bounds.validate(); }
1089
1090 #[test]
1093 fn test_bounds_width() {
1094 let bounds = Bounds::new(10, 20, 100, 200);
1095 assert_eq!(bounds.width(), 90);
1096 }
1097
1098 #[test]
1101 fn test_bounds_height() {
1102 let bounds = Bounds::new(10, 20, 100, 200);
1103 assert_eq!(bounds.height(), 180);
1104 }
1105
1106 #[test]
1109 fn test_bounds_width_zero() {
1110 let bounds = Bounds::new(50, 20, 50, 200);
1111 assert_eq!(bounds.width(), 0);
1112 }
1113
1114 #[test]
1117 fn test_bounds_height_zero() {
1118 let bounds = Bounds::new(10, 50, 100, 50);
1119 assert_eq!(bounds.height(), 0);
1120 }
1121
1122 #[test]
1125 fn test_bounds_contains() {
1126 let bounds = Bounds::new(10, 20, 100, 200);
1127 assert!(bounds.contains(50, 100));
1128 assert!(bounds.contains(10, 20)); assert!(!bounds.contains(100, 200)); assert!(!bounds.contains(5, 100)); assert!(!bounds.contains(150, 100)); assert!(!bounds.contains(50, 10)); assert!(!bounds.contains(50, 250)); }
1135
1136 #[test]
1139 fn test_bounds_contains_bounds() {
1140 let outer = Bounds::new(10, 20, 100, 200);
1141 let inner = Bounds::new(30, 40, 80, 180);
1142 let overlapping = Bounds::new(5, 15, 50, 100);
1143
1144 assert!(outer.contains_bounds(&inner));
1145 assert!(!outer.contains_bounds(&overlapping));
1146 }
1147
1148 #[test]
1151 fn test_bounds_intersects() {
1152 let bounds1 = Bounds::new(10, 20, 100, 200);
1153 let bounds2 = Bounds::new(50, 100, 150, 250); let bounds3 = Bounds::new(200, 300, 250, 350); assert!(bounds1.intersects(&bounds2));
1157 assert!(!bounds1.intersects(&bounds3));
1158 }
1159
1160 #[test]
1163 fn test_bounds_intersection() {
1164 let bounds1 = Bounds::new(10, 20, 100, 200);
1165 let bounds2 = Bounds::new(50, 100, 150, 250);
1166 let bounds3 = Bounds::new(200, 300, 250, 350);
1167
1168 let intersection = bounds1.intersection(&bounds2);
1169 assert!(intersection.is_some());
1170 let intersection = intersection.unwrap();
1171 assert_eq!(intersection.x1, 50);
1172 assert_eq!(intersection.y1, 100);
1173 assert_eq!(intersection.x2, 100);
1174 assert_eq!(intersection.y2, 200);
1175
1176 assert!(bounds1.intersection(&bounds3).is_none());
1177 }
1178
1179 #[test]
1182 fn test_bounds_union() {
1183 let bounds1 = Bounds::new(10, 20, 100, 200);
1184 let bounds2 = Bounds::new(50, 100, 150, 250);
1185
1186 let union = bounds1.union(&bounds2);
1187 assert_eq!(union.x1, 10);
1188 assert_eq!(union.y1, 20);
1189 assert_eq!(union.x2, 150);
1190 assert_eq!(union.y2, 250);
1191 }
1192
1193 #[test]
1196 fn test_bounds_translate() {
1197 let bounds = Bounds::new(10, 20, 100, 200);
1198 let translated = bounds.translate(5, -10);
1199 assert_eq!(translated.x1, 15);
1200 assert_eq!(translated.y1, 10);
1201 assert_eq!(translated.x2, 105);
1202 assert_eq!(translated.y2, 190);
1203 }
1204
1205 #[test]
1208 fn test_bounds_center() {
1209 let bounds = Bounds::new(10, 20, 100, 200);
1210 let center = bounds.center();
1211 assert_eq!(center, (55, 110));
1212 }
1213
1214 #[test]
1217 fn test_bounds_center_x() {
1218 let bounds = Bounds::new(10, 20, 100, 200);
1219 assert_eq!(bounds.center_x(), 55);
1220 }
1221
1222 #[test]
1225 fn test_bounds_center_y() {
1226 let bounds = Bounds::new(10, 20, 100, 200);
1227 assert_eq!(bounds.center_y(), 110);
1228 }
1229
1230 #[test]
1233 fn test_bounds_extend_center() {
1234 let mut bounds = Bounds::new(50, 50, 100, 100);
1235 bounds.extend(20, 10, Anchor::Center);
1236 assert_eq!(bounds.x1, 40);
1237 assert_eq!(bounds.y1, 45);
1238 assert_eq!(bounds.x2, 110);
1239 assert_eq!(bounds.y2, 105);
1240 }
1241
1242 #[test]
1245 fn test_bounds_extend_top_left() {
1246 let mut bounds = Bounds::new(50, 50, 100, 100);
1247 bounds.extend(20, 10, Anchor::TopLeft);
1248 assert_eq!(bounds.x1, 30);
1249 assert_eq!(bounds.y1, 40);
1250 assert_eq!(bounds.x2, 100);
1251 assert_eq!(bounds.y2, 100);
1252 }
1253
1254 #[test]
1257 fn test_bounds_contract_center() {
1258 let mut bounds = Bounds::new(50, 50, 100, 100);
1259 bounds.contract(10, 20, Anchor::Center);
1260 assert_eq!(bounds.x1, 55);
1261 assert_eq!(bounds.y1, 60);
1262 assert_eq!(bounds.x2, 95);
1263 assert_eq!(bounds.y2, 90);
1264 }
1265
1266 #[test]
1269 fn test_bounds_move_to() {
1270 let mut bounds = Bounds::new(10, 20, 60, 70);
1271 bounds.move_to(100, 150, Anchor::TopLeft);
1272 assert_eq!(bounds.x1, 100);
1273 assert_eq!(bounds.y1, 150);
1274 assert_eq!(bounds.x2, 150);
1275 assert_eq!(bounds.y2, 200);
1276 }
1277
1278 #[test]
1281 fn test_bounds_move_by() {
1282 let mut bounds = Bounds::new(10, 20, 60, 70);
1283 bounds.move_by(5, -10);
1284 assert_eq!(bounds.x1, 15);
1285 assert_eq!(bounds.y1, 10);
1286 assert_eq!(bounds.x2, 65);
1287 assert_eq!(bounds.y2, 60);
1288 }
1289
1290 #[test]
1293 fn test_bounds_anchor_points() {
1294 let bounds = Bounds::new(10, 20, 100, 200);
1295
1296 assert_eq!(bounds.top_left(), (10, 20));
1297 assert_eq!(bounds.top_right(), (100, 20));
1298 assert_eq!(bounds.bottom_left(), (10, 200));
1299 assert_eq!(bounds.bottom_right(), (100, 200));
1300 assert_eq!(bounds.center_top(), (55, 20));
1301 assert_eq!(bounds.center_bottom(), (55, 200));
1302 assert_eq!(bounds.center_left(), (10, 110));
1303 assert_eq!(bounds.center_right(), (100, 110));
1304 assert_eq!(bounds.top(), 20);
1305 assert_eq!(bounds.bottom(), 200);
1306 assert_eq!(bounds.left(), 10);
1307 assert_eq!(bounds.right(), 100);
1308 }
1309
1310 #[test]
1313 fn test_bounds_to_string() {
1314 let bounds = Bounds::new(10, 20, 100, 200);
1315 assert_eq!(bounds.to_string(), "(10, 20), (100, 200)");
1316 }
1317
1318 #[test]
1323 fn test_input_bounds_to_bounds() {
1324 let input_bounds = InputBounds {
1325 x1: "25%".to_string(),
1326 y1: "50%".to_string(),
1327 x2: "75%".to_string(),
1328 y2: "100%".to_string(),
1329 };
1330 let parent_bounds = Bounds::new(0, 0, 100, 200);
1331 let bounds = input_bounds.to_bounds(&parent_bounds);
1332
1333 assert_eq!(bounds.x1, 25);
1334 assert_eq!(bounds.y1, 100);
1335 assert_eq!(bounds.x2, 74); assert_eq!(bounds.y2, 199); }
1338
1339 #[test]
1344 fn test_anchor_default() {
1345 let anchor = Anchor::default();
1346 assert_eq!(anchor, Anchor::Center);
1347 }
1348
1349 #[test]
1354 fn test_screenbuffer_new() {
1355 let screen_buffer = ScreenBuffer::new_custom(5, 5);
1356 assert_eq!(screen_buffer.width, 5);
1357 assert_eq!(screen_buffer.height, 5);
1358 assert_eq!(screen_buffer.buffer.len(), 5);
1359 assert_eq!(screen_buffer.buffer[0].len(), 5);
1360 }
1361
1362 #[test]
1365 fn test_screenbuffer_clear() {
1366 let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1367 let test_cell = Cell {
1368 fg_color: String::from("red"),
1369 bg_color: String::from("blue"),
1370 ch: 'X',
1371 };
1372 screen_buffer.update(2, 2, test_cell.clone());
1373 screen_buffer.clear();
1374 for row in screen_buffer.buffer.iter() {
1375 for cell in row.iter() {
1376 assert_eq!(cell.fg_color, get_fg_color("white"));
1377 assert_eq!(cell.bg_color, get_bg_color("black"));
1378 assert_eq!(cell.ch, ' ');
1379 }
1380 }
1381 }
1382
1383 #[test]
1386 fn test_screenbuffer_update() {
1387 let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1388 let test_cell = Cell {
1389 fg_color: String::from("red"),
1390 bg_color: String::from("blue"),
1391 ch: 'X',
1392 };
1393 screen_buffer.update(2, 2, test_cell.clone());
1394 assert_eq!(screen_buffer.get(2, 2).unwrap(), &test_cell);
1395 }
1396
1397 #[test]
1400 fn test_screenbuffer_get() {
1401 let screen_buffer = ScreenBuffer::new_custom(5, 5);
1402 assert!(screen_buffer.get(6, 6).is_none());
1403 assert!(screen_buffer.get(3, 3).is_some());
1404 }
1405
1406 #[test]
1409 fn test_screenbuffer_update_out_of_bounds() {
1410 let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1411 let test_cell = Cell {
1412 fg_color: String::from("red"),
1413 bg_color: String::from("blue"),
1414 ch: 'X',
1415 };
1416 screen_buffer.update(10, 10, test_cell); assert!(screen_buffer.get(10, 10).is_none());
1418 }
1419
1420 #[test]
1423 fn test_screenbuffer_resize() {
1424 let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1425 screen_buffer.resize(10, 8);
1426 assert_eq!(screen_buffer.width, 10);
1427 assert_eq!(screen_buffer.height, 8);
1428 assert_eq!(screen_buffer.buffer.len(), 8);
1429 assert_eq!(screen_buffer.buffer[0].len(), 10);
1430 }
1431
1432 #[test]
1435 fn test_screenbuffer_resize_shrink() {
1436 let mut screen_buffer = ScreenBuffer::new_custom(10, 10);
1437 screen_buffer.resize(5, 5);
1438 assert_eq!(screen_buffer.width, 5);
1439 assert_eq!(screen_buffer.height, 5);
1440 assert_eq!(screen_buffer.buffer.len(), 5);
1441 assert_eq!(screen_buffer.buffer[0].len(), 5);
1442 }
1443
1444 fn create_test_app_context() -> AppContext {
1449 let current_dir = std::env::current_dir().expect("Failed to get current directory");
1450 let dashboard_path = current_dir.join("layouts/tests.yaml");
1451 let app = crate::load_app_from_yaml(dashboard_path.to_str().unwrap())
1452 .expect("Failed to load app");
1453 AppContext::new(app, Config::default())
1454 }
1455
1456 #[test]
1461 fn test_run_socket_function_replace_muxbox_content() {
1462 let app_context = create_test_app_context();
1463 let socket_function = SocketFunction::ReplaceBoxContent {
1464 box_id: "test_muxbox".to_string(),
1465 success: true,
1466 content: "Test content".to_string(),
1467 };
1468
1469 let result = run_socket_function(socket_function, &app_context);
1470 assert!(result.is_ok());
1471
1472 let (_, messages) = result.unwrap();
1473 assert_eq!(messages.len(), 1);
1474 match &messages[0] {
1475 crate::Message::MuxBoxOutputUpdate(muxbox_id, success, content) => {
1476 assert_eq!(muxbox_id, "test_muxbox");
1477 assert_eq!(*success, true);
1478 assert_eq!(content, "Test content");
1479 }
1480 _ => panic!("Expected MuxBoxOutputUpdate message"),
1481 }
1482 }
1483
1484 #[test]
1487 fn test_run_socket_function_switch_active_layout() {
1488 let app_context = create_test_app_context();
1489 let socket_function = SocketFunction::SwitchActiveLayout {
1490 layout_id: "new_layout".to_string(),
1491 };
1492
1493 let result = run_socket_function(socket_function, &app_context);
1494 assert!(result.is_ok());
1495
1496 let (_, messages) = result.unwrap();
1497 assert_eq!(messages.len(), 1);
1498 match &messages[0] {
1499 crate::Message::SwitchActiveLayout(layout_id) => {
1500 assert_eq!(layout_id, "new_layout");
1501 }
1502 _ => panic!("Expected SwitchActiveLayout message"),
1503 }
1504 }
1505
1506 #[test]
1511 fn test_cell_clone_and_eq() {
1512 let cell1 = Cell {
1513 fg_color: "red".to_string(),
1514 bg_color: "blue".to_string(),
1515 ch: 'X',
1516 };
1517 let cell2 = cell1.clone();
1518 assert_eq!(cell1, cell2);
1519
1520 let cell3 = Cell {
1521 fg_color: "green".to_string(),
1522 bg_color: "blue".to_string(),
1523 ch: 'X',
1524 };
1525 assert_ne!(cell1, cell3);
1526 }
1527
1528 #[test]
1530 fn test_send_json_to_socket_function() {
1531 use std::os::unix::net::UnixListener;
1532 use std::thread;
1533 use std::time::Duration;
1534
1535 let socket_path = "/tmp/test_send_json.sock";
1536 let _ = std::fs::remove_file(socket_path);
1537
1538 let server_socket_path = socket_path.to_string();
1540 let server_handle = thread::spawn(move || {
1541 match UnixListener::bind(&server_socket_path) {
1542 Ok(listener) => {
1543 if let Some(Ok(mut stream)) = listener.incoming().next() {
1545 let mut buffer = Vec::new();
1546 let mut temp_buffer = [0; 1024];
1547
1548 match stream.read(&mut temp_buffer) {
1550 Ok(n) => {
1551 buffer.extend_from_slice(&temp_buffer[..n]);
1552 let _ = stream.write_all(b"Test Response");
1553 String::from_utf8_lossy(&buffer).to_string()
1554 }
1555 Err(_) => String::new(),
1556 }
1557 } else {
1558 String::new()
1559 }
1560 }
1561 Err(_) => String::new(),
1562 }
1563 });
1564
1565 thread::sleep(Duration::from_millis(100));
1567
1568 let test_json = r#"{"test": "message"}"#;
1570 let result = send_json_to_socket(socket_path, test_json);
1571
1572 match result {
1576 Ok(response) => {
1577 assert_eq!(response, "Test Response");
1578
1579 let received_message = server_handle.join().unwrap();
1581 assert_eq!(received_message, test_json);
1582 }
1583 Err(_) => {
1584 let _ = server_handle.join();
1587 }
1588 }
1589
1590 let _ = std::fs::remove_file(socket_path);
1592 }
1593}