1use std::time::{SystemTime, UNIX_EPOCH};
2
3use rmux_proto::{PaneTarget, SessionName};
4
5pub use rmux_proto::PaneId;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct PaneGeometry {
10 x: u16,
11 y: u16,
12 cols: u16,
13 rows: u16,
14}
15
16impl PaneGeometry {
17 #[must_use]
19 pub const fn new(x: u16, y: u16, cols: u16, rows: u16) -> Self {
20 Self { x, y, cols, rows }
21 }
22
23 #[must_use]
25 pub const fn x(&self) -> u16 {
26 self.x
27 }
28
29 #[must_use]
31 pub const fn y(&self) -> u16 {
32 self.y
33 }
34
35 #[must_use]
37 pub const fn cols(&self) -> u16 {
38 self.cols
39 }
40
41 #[must_use]
43 pub const fn rows(&self) -> u16 {
44 self.rows
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct Pane {
51 id: PaneId,
52 index: u32,
53 geometry: PaneGeometry,
54 active_point: u64,
55 created_at: i64,
56 activity_at: i64,
57}
58
59impl Pane {
60 #[must_use]
62 pub const fn id(&self) -> PaneId {
63 self.id
64 }
65
66 #[must_use]
68 pub const fn index(&self) -> u32 {
69 self.index
70 }
71
72 #[must_use]
74 pub const fn geometry(&self) -> PaneGeometry {
75 self.geometry
76 }
77
78 pub const fn active_point(&self) -> u64 {
81 self.active_point
82 }
83
84 #[must_use]
86 pub const fn created_at(&self) -> i64 {
87 self.created_at
88 }
89
90 #[must_use]
92 pub const fn activity_at(&self) -> i64 {
93 self.activity_at
94 }
95
96 pub(crate) fn touch_activity(&mut self) {
97 self.activity_at = current_unix_timestamp();
98 }
99
100 #[must_use]
102 pub fn target(&self, session_name: &SessionName) -> PaneTarget {
103 PaneTarget::new(session_name.clone(), self.index)
104 }
105
106 #[cfg_attr(not(test), allow(dead_code))]
107 pub(crate) fn new(index: u32, geometry: PaneGeometry) -> Self {
108 Self::new_with_id(PaneId::new(index), index, geometry)
109 }
110
111 pub(crate) fn new_with_id(id: PaneId, index: u32, geometry: PaneGeometry) -> Self {
112 let now = current_unix_timestamp();
113 Self {
114 id,
115 index,
116 geometry,
117 active_point: 0,
118 created_at: now,
119 activity_at: now,
120 }
121 }
122
123 pub(crate) fn set_geometry(&mut self, geometry: PaneGeometry) {
124 self.geometry = geometry;
125 }
126
127 pub(crate) fn set_index(&mut self, index: u32) {
128 self.index = index;
129 }
130
131 pub(crate) fn set_active_point(&mut self, active_point: u64) {
132 self.active_point = active_point;
133 }
134}
135
136fn current_unix_timestamp() -> i64 {
137 SystemTime::now()
138 .duration_since(UNIX_EPOCH)
139 .map(|duration| duration.as_secs().min(i64::MAX as u64) as i64)
140 .unwrap_or(0)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::{Pane, PaneGeometry, PaneId};
146 use rmux_proto::SessionName;
147
148 #[test]
149 fn geometry_accessors_match_constructor_values() {
150 let geometry = PaneGeometry::new(4, 7, 80, 24);
151
152 assert_eq!(geometry.x(), 4);
153 assert_eq!(geometry.y(), 7);
154 assert_eq!(geometry.cols(), 80);
155 assert_eq!(geometry.rows(), 24);
156 }
157
158 #[test]
159 fn pane_target_uses_session_name_and_index() {
160 let pane = Pane::new(3, PaneGeometry::new(0, 0, 10, 5));
161 let session_name = SessionName::new("alpha").expect("valid session name");
162
163 assert_eq!(pane.target(&session_name).to_string(), "alpha:0.3");
164 }
165
166 #[test]
167 fn pane_id_is_stable_and_independent_from_display_index() {
168 let pane = Pane::new_with_id(PaneId::new(9), 3, PaneGeometry::new(0, 0, 10, 5));
169
170 assert_eq!(pane.id(), PaneId::new(9));
171 assert_eq!(pane.index(), 3);
172 }
173
174 #[test]
175 fn set_geometry_replaces_the_existing_rectangle() {
176 let mut pane = Pane::new(0, PaneGeometry::new(0, 0, 10, 5));
177 let replacement = PaneGeometry::new(12, 1, 34, 50);
178
179 pane.set_geometry(replacement);
180
181 assert_eq!(pane.geometry(), replacement);
182 }
183}