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 pub(crate) fn set_activity_at(&mut self, activity_at: i64) {
101 self.activity_at = activity_at;
102 }
103
104 #[must_use]
106 pub fn target(&self, session_name: &SessionName) -> PaneTarget {
107 PaneTarget::new(session_name.clone(), self.index)
108 }
109
110 #[cfg_attr(not(test), allow(dead_code))]
111 pub(crate) fn new(index: u32, geometry: PaneGeometry) -> Self {
112 Self::new_with_id(PaneId::new(index), index, geometry)
113 }
114
115 pub(crate) fn new_with_id(id: PaneId, index: u32, geometry: PaneGeometry) -> Self {
116 let now = current_unix_timestamp();
117 Self {
118 id,
119 index,
120 geometry,
121 active_point: 0,
122 created_at: now,
123 activity_at: now,
124 }
125 }
126
127 pub(crate) fn set_geometry(&mut self, geometry: PaneGeometry) {
128 self.geometry = geometry;
129 }
130
131 pub(crate) fn set_index(&mut self, index: u32) {
132 self.index = index;
133 }
134
135 pub(crate) fn set_active_point(&mut self, active_point: u64) {
136 self.active_point = active_point;
137 }
138}
139
140fn current_unix_timestamp() -> i64 {
141 SystemTime::now()
142 .duration_since(UNIX_EPOCH)
143 .map(|duration| duration.as_secs().min(i64::MAX as u64) as i64)
144 .unwrap_or(0)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::{Pane, PaneGeometry, PaneId};
150 use rmux_proto::SessionName;
151
152 #[test]
153 fn geometry_accessors_match_constructor_values() {
154 let geometry = PaneGeometry::new(4, 7, 80, 24);
155
156 assert_eq!(geometry.x(), 4);
157 assert_eq!(geometry.y(), 7);
158 assert_eq!(geometry.cols(), 80);
159 assert_eq!(geometry.rows(), 24);
160 }
161
162 #[test]
163 fn pane_target_uses_session_name_and_index() {
164 let pane = Pane::new(3, PaneGeometry::new(0, 0, 10, 5));
165 let session_name = SessionName::new("alpha").expect("valid session name");
166
167 assert_eq!(pane.target(&session_name).to_string(), "alpha:0.3");
168 }
169
170 #[test]
171 fn pane_id_is_stable_and_independent_from_display_index() {
172 let pane = Pane::new_with_id(PaneId::new(9), 3, PaneGeometry::new(0, 0, 10, 5));
173
174 assert_eq!(pane.id(), PaneId::new(9));
175 assert_eq!(pane.index(), 3);
176 }
177
178 #[test]
179 fn set_geometry_replaces_the_existing_rectangle() {
180 let mut pane = Pane::new(0, PaneGeometry::new(0, 0, 10, 5));
181 let replacement = PaneGeometry::new(12, 1, 34, 50);
182
183 pane.set_geometry(replacement);
184
185 assert_eq!(pane.geometry(), replacement);
186 }
187}