envelope_cli/tui/
layout.rs

1//! Layout definitions for the TUI
2//!
3//! Defines the overall layout structure: sidebar, main panel, status bar.
4
5use ratatui::layout::{Constraint, Direction, Layout, Rect};
6
7/// Layout regions for the TUI
8pub struct AppLayout {
9    /// Sidebar area (accounts list, view switcher)
10    pub sidebar: Rect,
11    /// Main content area
12    pub main: Rect,
13    /// Status bar at the bottom
14    pub status_bar: Rect,
15}
16
17impl AppLayout {
18    /// Calculate layout from available area
19    pub fn new(area: Rect) -> Self {
20        // Split into main area and status bar
21        let vertical = Layout::default()
22            .direction(Direction::Vertical)
23            .constraints([
24                Constraint::Min(3),    // Main area
25                Constraint::Length(1), // Status bar
26            ])
27            .split(area);
28
29        // Split main area into sidebar and content
30        let horizontal = Layout::default()
31            .direction(Direction::Horizontal)
32            .constraints([
33                Constraint::Length(30), // Sidebar (fixed width)
34                Constraint::Min(40),    // Main content
35            ])
36            .split(vertical[0]);
37
38        Self {
39            sidebar: horizontal[0],
40            main: horizontal[1],
41            status_bar: vertical[1],
42        }
43    }
44}
45
46/// Layout for the sidebar
47pub struct SidebarLayout {
48    /// Title/header area
49    pub header: Rect,
50    /// Account list area
51    pub accounts: Rect,
52    /// View switcher area
53    pub view_switcher: Rect,
54}
55
56impl SidebarLayout {
57    /// Calculate sidebar layout
58    pub fn new(area: Rect) -> Self {
59        let chunks = Layout::default()
60            .direction(Direction::Vertical)
61            .constraints([
62                Constraint::Length(3), // Header
63                Constraint::Min(5),    // Accounts
64                Constraint::Length(5), // View switcher
65            ])
66            .split(area);
67
68        Self {
69            header: chunks[0],
70            accounts: chunks[1],
71            view_switcher: chunks[2],
72        }
73    }
74}
75
76/// Layout for the main panel header
77pub struct MainPanelLayout {
78    /// Header area (title, period selector for budget)
79    pub header: Rect,
80    /// Content area
81    pub content: Rect,
82}
83
84impl MainPanelLayout {
85    /// Calculate main panel layout
86    pub fn new(area: Rect) -> Self {
87        let chunks = Layout::default()
88            .direction(Direction::Vertical)
89            .constraints([
90                Constraint::Length(3), // Header
91                Constraint::Min(3),    // Content
92            ])
93            .split(area);
94
95        Self {
96            header: chunks[0],
97            content: chunks[1],
98        }
99    }
100}
101
102/// Layout for the budget view
103pub struct BudgetLayout {
104    /// Available to Budget header
105    pub atb_header: Rect,
106    /// Category table
107    pub categories: Rect,
108}
109
110impl BudgetLayout {
111    /// Calculate budget view layout
112    pub fn new(area: Rect) -> Self {
113        let chunks = Layout::default()
114            .direction(Direction::Vertical)
115            .constraints([
116                Constraint::Length(3), // ATB header
117                Constraint::Min(3),    // Categories
118            ])
119            .split(area);
120
121        Self {
122            atb_header: chunks[0],
123            categories: chunks[1],
124        }
125    }
126}
127
128/// Create a centered rect for dialogs
129pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
130    let popup_layout = Layout::default()
131        .direction(Direction::Vertical)
132        .constraints([
133            Constraint::Percentage((100 - percent_y) / 2),
134            Constraint::Percentage(percent_y),
135            Constraint::Percentage((100 - percent_y) / 2),
136        ])
137        .split(r);
138
139    Layout::default()
140        .direction(Direction::Horizontal)
141        .constraints([
142            Constraint::Percentage((100 - percent_x) / 2),
143            Constraint::Percentage(percent_x),
144            Constraint::Percentage((100 - percent_x) / 2),
145        ])
146        .split(popup_layout[1])[1]
147}
148
149/// Create a fixed-size centered rect for dialogs
150pub fn centered_rect_fixed(width: u16, height: u16, r: Rect) -> Rect {
151    let x = r.x + (r.width.saturating_sub(width)) / 2;
152    let y = r.y + (r.height.saturating_sub(height)) / 2;
153    Rect::new(x, y, width.min(r.width), height.min(r.height))
154}