1use serde::{Deserialize, Serialize};
2use std::{
3 fmt::Display,
4 hash::{Hash, Hasher},
5};
6
7use crate::data::FloatingPaneCoordinates;
8use crate::input::layout::{PercentOrFixed, SplitDirection, SplitSize};
9use crate::position::Position;
10
11#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)]
14pub struct PaneGeom {
15 pub x: usize,
16 pub y: usize,
17 pub rows: Dimension,
18 pub cols: Dimension,
19 pub stacked: Option<usize>, pub is_pinned: bool, pub logical_position: Option<usize>, }
23
24impl PartialEq for PaneGeom {
25 fn eq(&self, other: &Self) -> bool {
26 self.x == other.x
29 && self.y == other.y
30 && self.rows == other.rows
31 && self.cols == other.cols
32 && self.stacked == other.stacked
33 }
34}
35
36impl std::hash::Hash for PaneGeom {
37 fn hash<H: Hasher>(&self, state: &mut H) {
38 self.x.hash(state);
40 self.y.hash(state);
41 self.rows.hash(state);
42 self.cols.hash(state);
43 self.stacked.hash(state);
44 }
45}
46
47impl Eq for PaneGeom {}
48
49#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
50pub struct Viewport {
51 pub x: usize,
52 pub y: usize,
53 pub rows: usize,
54 pub cols: usize,
55}
56
57impl Viewport {
58 pub fn has_positive_size(&self) -> bool {
59 self.rows > 0 && self.cols > 0
60 }
61}
62
63#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
64pub struct Offset {
65 pub top: usize,
66 pub bottom: usize,
67 pub right: usize,
68 pub left: usize,
69}
70
71#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
72pub struct Size {
73 pub rows: usize,
74 pub cols: usize,
75}
76
77#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
78pub struct SizeInPixels {
79 pub height: usize,
80 pub width: usize,
81}
82
83#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
84pub struct Insets {
85 pub top: usize,
86 pub bottom: usize,
87 pub left: usize,
88 pub right: usize,
89}
90
91#[derive(Eq, Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Hash)]
92pub struct Dimension {
93 pub constraint: Constraint,
94 pub(crate) inner: usize,
95}
96
97impl Default for Dimension {
98 fn default() -> Self {
99 Self::percent(100.0)
100 }
101}
102
103impl Dimension {
104 pub fn fixed(size: usize) -> Dimension {
105 Self {
106 constraint: Constraint::Fixed(size),
107 inner: size,
108 }
109 }
110
111 pub fn percent(percent: f64) -> Dimension {
112 Self {
113 constraint: Constraint::Percent(percent),
114 inner: 1,
115 }
116 }
117
118 pub fn as_usize(&self) -> usize {
119 self.inner
120 }
121
122 pub fn as_percent(&self) -> Option<f64> {
123 if let Constraint::Percent(p) = self.constraint {
124 Some(p)
125 } else {
126 None
127 }
128 }
129
130 pub fn set_percent(&mut self, percent: f64) {
131 self.constraint = Constraint::Percent(percent);
132 }
133
134 pub fn set_inner(&mut self, inner: usize) {
135 self.inner = inner;
136 }
137
138 pub fn adjust_inner(&mut self, full_size: usize) -> f64 {
139 match self.constraint {
143 Constraint::Percent(percent) => {
144 let new_inner = (percent / 100.0) * full_size as f64;
145 let rounded = new_inner.floor();
146 let leftover = rounded - new_inner;
147 self.set_inner(rounded as usize);
148 leftover
149 },
150 Constraint::Fixed(fixed_size) => {
151 self.set_inner(fixed_size);
152 0.0
153 },
154 }
155 }
156 pub fn increase_inner(&mut self, by: usize) {
157 self.inner += by;
158 }
159 pub fn decrease_inner(&mut self, by: usize) {
160 self.inner = self.inner.saturating_sub(by);
161 }
162
163 pub fn is_fixed(&self) -> bool {
164 matches!(self.constraint, Constraint::Fixed(_))
165 }
166 pub fn is_percent(&self) -> bool {
167 matches!(self.constraint, Constraint::Percent(_))
168 }
169 pub fn from_split_size(split_size: SplitSize, full_size: usize) -> Self {
170 match split_size {
171 SplitSize::Fixed(fixed) => Dimension {
172 constraint: Constraint::Fixed(fixed),
173 inner: fixed,
174 },
175 SplitSize::Percent(percent) => Dimension {
176 constraint: Constraint::Percent(percent as f64),
177 inner: ((percent as f64 / 100.0) * full_size as f64).floor() as usize,
178 },
179 }
180 }
181 pub fn from_percent_or_fixed(size: PercentOrFixed, full_size: usize) -> Self {
182 match size {
183 PercentOrFixed::Fixed(fixed) => Dimension {
184 constraint: Constraint::Fixed(fixed),
185 inner: fixed,
186 },
187 PercentOrFixed::Percent(percent) => Dimension {
188 constraint: Constraint::Percent(percent as f64),
189 inner: ((percent as f64 / 100.0) * full_size as f64).floor() as usize,
190 },
191 }
192 }
193 pub fn split_out(&mut self, by: f64) -> Self {
194 match self.constraint {
195 Constraint::Percent(percent) => {
196 let split_out_value = percent / by;
197 let split_out_inner_value = self.inner / by as usize;
198 self.constraint = Constraint::Percent(percent - split_out_value);
199 self.inner = self.inner.saturating_sub(split_out_inner_value);
200 let mut split_out_dimension = Self::percent(split_out_value);
201 split_out_dimension.inner = split_out_inner_value;
202 split_out_dimension
203 },
204 Constraint::Fixed(fixed) => {
205 let split_out_value = fixed / by as usize;
206 self.constraint = Constraint::Fixed(fixed - split_out_value);
207 Self::fixed(split_out_value)
208 },
209 }
210 }
211 pub fn reduce_by(&mut self, by: f64, by_inner: usize) {
212 match self.constraint {
213 Constraint::Percent(percent) => {
214 self.constraint = Constraint::Percent(percent - by);
215 self.inner = self.inner.saturating_sub(by_inner);
216 },
217 Constraint::Fixed(_fixed) => {
218 log::error!("Cannot reduce_by fixed dimensions");
219 },
220 }
221 }
222}
223
224#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
225pub enum Constraint {
226 Fixed(usize),
228 Percent(f64),
230}
231
232impl Display for Constraint {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 let actual = match self {
235 Constraint::Fixed(v) => *v as f64,
236 Constraint::Percent(v) => *v,
237 };
238 write!(f, "{}", actual)?;
239 Ok(())
240 }
241}
242
243#[allow(clippy::derive_hash_xor_eq)]
244impl Hash for Constraint {
245 fn hash<H: Hasher>(&self, state: &mut H) {
246 match self {
247 Constraint::Fixed(size) => size.hash(state),
248 Constraint::Percent(size) => (*size as usize).hash(state),
249 }
250 }
251}
252
253impl Eq for Constraint {}
254
255impl PaneGeom {
256 pub fn contains(&self, point: &Position) -> bool {
257 let col = point.column.0 as usize;
258 let row = point.line.0 as usize;
259 self.x <= col
260 && col < self.x + self.cols.as_usize()
261 && self.y <= row
262 && row < self.y + self.rows.as_usize()
263 }
264 pub fn is_at_least_minimum_size(&self) -> bool {
265 self.rows.as_usize() > 0 && self.cols.as_usize() > 0
266 }
267 pub fn is_flexible_in_direction(&self, split_direction: SplitDirection) -> bool {
268 match split_direction {
269 SplitDirection::Vertical => self.cols.is_percent(),
270 SplitDirection::Horizontal => self.rows.is_percent(),
271 }
272 }
273 pub fn apply_floating_pane_position(
274 &mut self,
275 x: Option<PercentOrFixed>,
276 y: Option<PercentOrFixed>,
277 width: Option<PercentOrFixed>,
278 height: Option<PercentOrFixed>,
279 viewport_cols: usize,
280 viewport_rows: usize,
281 ) {
282 if let Some(width) = &width {
283 self.cols = Dimension::fixed(width.to_fixed(viewport_cols));
284 }
285 if let Some(height) = &height {
286 self.rows = Dimension::fixed(height.to_fixed(viewport_rows));
287 }
288 self.x = match &x {
289 Some(x) => x.to_fixed(viewport_cols),
290 None if width.is_some() => viewport_cols.saturating_sub(self.cols.as_usize()) / 2,
291 None => self.x,
292 };
293 self.y = match &y {
294 Some(y) => y.to_fixed(viewport_rows),
295 None if height.is_some() => viewport_rows.saturating_sub(self.rows.as_usize()) / 2,
296 None => self.y,
297 };
298 }
299 pub fn adjust_coordinates(
300 &mut self,
301 floating_pane_coordinates: FloatingPaneCoordinates,
302 viewport: Viewport,
303 ) {
304 self.apply_floating_pane_position(
305 floating_pane_coordinates.x,
306 floating_pane_coordinates.y,
307 floating_pane_coordinates.width,
308 floating_pane_coordinates.height,
309 viewport.cols,
310 viewport.rows,
311 );
312 if self.x < viewport.x {
313 self.x = viewport.x;
314 } else if self.x > viewport.x + viewport.cols {
315 self.x = (viewport.x + viewport.cols).saturating_sub(self.cols.as_usize());
316 }
317 if self.y < viewport.y {
318 self.y = viewport.y;
319 } else if self.y > viewport.y + viewport.rows {
320 self.y = (viewport.y + viewport.rows).saturating_sub(self.rows.as_usize());
321 }
322 if self.x + self.cols.as_usize() > viewport.x + viewport.cols {
323 let new_cols = (viewport.x + viewport.cols).saturating_sub(self.x);
324 self.cols.set_inner(new_cols);
325 }
326 if self.y + self.rows.as_usize() > viewport.y + viewport.rows {
327 let new_rows = (viewport.y + viewport.rows).saturating_sub(self.y);
328 self.rows.set_inner(new_rows);
329 }
330 }
331 pub fn combine_vertically_with(&self, geom_below: &PaneGeom) -> Option<Self> {
332 match (self.rows.constraint, geom_below.rows.constraint) {
333 (Constraint::Percent(self_percent), Constraint::Percent(geom_below_percent)) => {
334 let mut combined = self.clone();
335 combined.rows = Dimension::percent(self_percent + geom_below_percent);
336 combined.rows.inner = self.rows.inner + geom_below.rows.inner;
337 Some(combined)
338 },
339 _ => {
340 log::error!("Can't combine fixed panes");
341 None
342 },
343 }
344 }
345 pub fn combine_horizontally_with(&self, geom_to_the_right: &PaneGeom) -> Option<Self> {
346 match (self.cols.constraint, geom_to_the_right.cols.constraint) {
347 (Constraint::Percent(self_percent), Constraint::Percent(geom_to_the_right_percent)) => {
348 let mut combined = self.clone();
349 combined.cols = Dimension::percent(self_percent + geom_to_the_right_percent);
350 combined.cols.inner = self.cols.inner + geom_to_the_right.cols.inner;
351 Some(combined)
352 },
353 _ => {
354 log::error!("Can't combine fixed panes");
355 None
356 },
357 }
358 }
359 pub fn combine_vertically_with_many(&self, geoms_below: &Vec<PaneGeom>) -> Option<Self> {
360 let mut combined = self.clone();
363 for geom_below in geoms_below {
364 match (combined.rows.constraint, geom_below.rows.constraint) {
365 (
366 Constraint::Percent(combined_percent),
367 Constraint::Percent(geom_below_percent),
368 ) => {
369 let new_rows_inner = combined.rows.inner + geom_below.rows.inner;
370 combined.rows = Dimension::percent(combined_percent + geom_below_percent);
371 combined.rows.inner = new_rows_inner;
372 },
373 _ => {
374 log::error!("Can't combine fixed panes");
375 return None;
376 },
377 }
378 }
379 Some(combined)
380 }
381 pub fn combine_horizontally_with_many(
382 &self,
383 geoms_to_the_right: &Vec<PaneGeom>,
384 ) -> Option<Self> {
385 let mut combined = self.clone();
388 for geom_to_the_right in geoms_to_the_right {
389 match (combined.cols.constraint, geom_to_the_right.cols.constraint) {
390 (
391 Constraint::Percent(combined_percent),
392 Constraint::Percent(geom_to_the_right_percent),
393 ) => {
394 let new_cols = combined.cols.inner + geom_to_the_right.cols.inner;
395 combined.cols =
396 Dimension::percent(combined_percent + geom_to_the_right_percent);
397 combined.cols.inner = new_cols;
398 },
399 _ => {
400 log::error!("Can't combine fixed panes");
401 return None;
402 },
403 }
404 }
405 Some(combined)
406 }
407 pub fn is_stacked(&self) -> bool {
408 self.stacked.is_some()
409 }
410}
411
412impl Display for PaneGeom {
413 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414 write!(f, "{{ ")?;
415 write!(f, r#""x": {},"#, self.x)?;
416 write!(f, r#""y": {},"#, self.y)?;
417 write!(f, r#""cols": {},"#, self.cols.constraint)?;
418 write!(f, r#""rows": {},"#, self.rows.constraint)?;
419 write!(f, r#""stacked": {:?}"#, self.stacked)?;
420 write!(f, r#""logical_position": {:?}"#, self.logical_position)?;
421 write!(f, " }}")?;
422
423 Ok(())
424 }
425}
426
427impl Offset {
428 pub fn frame(size: usize) -> Self {
429 Self {
430 top: size,
431 bottom: size,
432 right: size,
433 left: size,
434 }
435 }
436
437 pub fn shift_right_and_top(right: usize, top: usize) -> Self {
438 Self {
439 right,
440 top,
441 ..Default::default()
442 }
443 }
444
445 pub fn shift_right(right: usize) -> Self {
446 Self {
447 right,
448 ..Default::default()
449 }
450 }
451
452 pub fn shift_right_top_and_bottom(right: usize, top: usize, bottom: usize) -> Self {
453 Self {
454 right,
455 top,
456 bottom,
457 ..Default::default()
458 }
459 }
460
461 pub fn shift(bottom: usize, right: usize) -> Self {
464 Self {
465 bottom,
466 right,
467 ..Default::default()
468 }
469 }
470}
471
472impl From<PaneGeom> for Viewport {
473 fn from(pane: PaneGeom) -> Self {
474 Self {
475 x: pane.x,
476 y: pane.y,
477 rows: pane.rows.as_usize(),
478 cols: pane.cols.as_usize(),
479 }
480 }
481}
482
483impl From<Size> for Viewport {
484 fn from(size: Size) -> Self {
485 Self {
486 rows: size.rows,
487 cols: size.cols,
488 ..Default::default()
489 }
490 }
491}
492
493impl From<&PaneGeom> for Size {
494 fn from(pane_geom: &PaneGeom) -> Self {
495 Self {
496 rows: pane_geom.rows.as_usize(),
497 cols: pane_geom.cols.as_usize(),
498 }
499 }
500}
501
502impl From<&Size> for PaneGeom {
503 fn from(size: &Size) -> Self {
504 let mut rows = Dimension::percent(100.0);
505 let mut cols = Dimension::percent(100.0);
506 rows.set_inner(size.rows);
507 cols.set_inner(size.cols);
508 Self {
509 rows,
510 cols,
511 ..Default::default()
512 }
513 }
514}