mod action_select;
mod confirm;
mod error;
mod issue_loading;
mod items;
mod state;
mod workspace;
use ratatui::layout::Rect;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SizeHint {
pub percent_x: u16,
pub percent_y: u16,
pub min_width: Option<u16>,
pub min_height: Option<u16>,
}
impl SizeHint {
#[must_use]
pub const fn percent(x: u16, y: u16) -> Self {
Self {
percent_x: x,
percent_y: y,
min_width: None,
min_height: None,
}
}
#[must_use]
pub const fn with_min_width(mut self, w: u16) -> Self {
self.min_width = Some(w);
self
}
#[must_use]
pub const fn with_min_height(mut self, h: u16) -> Self {
self.min_height = Some(h);
self
}
}
pub trait PopupSizing {
fn size_hint(&self) -> SizeHint;
fn compute_area(&self, container: Rect) -> Rect {
centered_rect_with_hint(self.size_hint(), container)
}
}
#[must_use]
pub fn centered_rect_with_hint(hint: SizeHint, r: Rect) -> Rect {
let percent_width = r.width * hint.percent_x / 100;
let percent_height = r.height * hint.percent_y / 100;
let width = hint
.min_width
.map_or(percent_width, |min| percent_width.max(min))
.min(r.width);
let height = hint
.min_height
.map_or(percent_height, |min| percent_height.max(min))
.min(r.height);
let x = r.x + (r.width.saturating_sub(width)) / 2;
let y = r.y + (r.height.saturating_sub(height)) / 2;
Rect::new(x, y, width, height)
}
pub use action_select::ActionSelectPopup;
pub use confirm::{ConfirmPermissionsPopup, ConfirmQuitPopup};
pub use error::{ErrorPopup, SessionTerminatedPopup};
pub use issue_loading::IssueLoadingPopup;
pub use items::{IssueItem, SessionItem, WorktreeItem};
pub use state::{ActionSelectPopupState, LoadingOperation, PopupSection, WorkspacePopupState};
pub use workspace::WorkspacePopup;
#[must_use]
pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let width = r.width * percent_x / 100;
let height = r.height * percent_y / 100;
let x = r.x + (r.width.saturating_sub(width)) / 2;
let y = r.y + (r.height.saturating_sub(height)) / 2;
Rect::new(x, y, width, height)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_centered_rect() {
let outer = Rect::new(0, 0, 100, 50);
let inner = centered_rect(50, 40, outer);
assert_eq!(inner.width, 50);
assert_eq!(inner.height, 20);
assert_eq!(inner.x, 25);
assert_eq!(inner.y, 15);
}
#[test]
fn test_centered_rect_small() {
let outer = Rect::new(10, 10, 20, 10);
let inner = centered_rect(50, 50, outer);
assert_eq!(inner.width, 10);
assert_eq!(inner.height, 5);
assert_eq!(inner.x, 15);
assert_eq!(inner.y, 12);
}
#[test]
fn test_size_hint_percent() {
let hint = SizeHint::percent(50, 30);
assert_eq!(hint.percent_x, 50);
assert_eq!(hint.percent_y, 30);
assert_eq!(hint.min_width, None);
assert_eq!(hint.min_height, None);
}
#[test]
fn test_size_hint_builder() {
let hint = SizeHint::percent(50, 30)
.with_min_width(40)
.with_min_height(10);
assert_eq!(hint.percent_x, 50);
assert_eq!(hint.percent_y, 30);
assert_eq!(hint.min_width, Some(40));
assert_eq!(hint.min_height, Some(10));
}
#[test]
fn test_centered_rect_with_hint_basic() {
let outer = Rect::new(0, 0, 100, 50);
let hint = SizeHint::percent(50, 40);
let inner = centered_rect_with_hint(hint, outer);
assert_eq!(inner.width, 50);
assert_eq!(inner.height, 20);
assert_eq!(inner.x, 25);
assert_eq!(inner.y, 15);
}
#[test]
fn test_centered_rect_with_hint_min_width_enforced() {
let outer = Rect::new(0, 0, 100, 50);
let hint = SizeHint::percent(20, 40).with_min_width(40);
let inner = centered_rect_with_hint(hint, outer);
assert_eq!(inner.width, 40); assert_eq!(inner.height, 20);
assert_eq!(inner.x, 30); }
#[test]
fn test_centered_rect_with_hint_min_height_enforced() {
let outer = Rect::new(0, 0, 100, 50);
let hint = SizeHint::percent(50, 10).with_min_height(15);
let inner = centered_rect_with_hint(hint, outer);
assert_eq!(inner.width, 50);
assert_eq!(inner.height, 15); assert_eq!(inner.y, 17); }
#[test]
fn test_centered_rect_with_hint_clamp_to_container() {
let outer = Rect::new(0, 0, 30, 20);
let hint = SizeHint::percent(50, 50)
.with_min_width(50)
.with_min_height(25);
let inner = centered_rect_with_hint(hint, outer);
assert_eq!(inner.width, 30); assert_eq!(inner.height, 20); assert_eq!(inner.x, 0);
assert_eq!(inner.y, 0);
}
#[test]
fn test_popup_sizing_trait() {
struct TestPopup;
impl PopupSizing for TestPopup {
fn size_hint(&self) -> SizeHint {
SizeHint::percent(60, 40).with_min_width(30)
}
}
let popup = TestPopup;
let outer = Rect::new(0, 0, 100, 50);
let area = popup.compute_area(outer);
assert_eq!(area.width, 60);
assert_eq!(area.height, 20);
}
}