Skip to main content

journey/widgets/
layout.rs

1//! Pure geometry for journey's two screens.
2//!
3//! The generic [`Shell`](crate::widgets::Shell) places each child by calling
4//! one of these functions with the container bounds, so the browse (gitk) and
5//! commit (git-gui) layouts are simply two sets of rectangles.
6
7use saudade::Rect;
8
9// ---- browse (gitk) layout -------------------------------------------------
10
11/// Height of the menu bar (shared by both screens).
12pub const MENU_H: i32 = 20;
13/// Height of the search toolbar below the menu.
14pub const TOOLBAR_H: i32 = 26;
15/// Width of the changed-files pane on the lower left.
16pub const FILES_W: i32 = 300;
17/// Fraction of the content height given to the history pane.
18pub const HISTORY_FRAC: f32 = 0.46;
19/// Slight breathing room around — and between — the three browse panes
20/// (history, files, diff), so they don't run into each other or the window
21/// edge.
22pub const BROWSE_PAD: i32 = 4;
23/// At or below this logical window width the left pane (the browse files list
24/// and the commit file lists) is capped at a third of the width, so the diff
25/// and the other right-hand panes keep the lion's share of a cramped window.
26pub const NARROW_W: i32 = 800;
27
28/// A third of the window width — the cap applied to the left pane on narrow
29/// windows. `None` above [`NARROW_W`], where the fixed widths stand.
30fn narrow_left_cap(b: Rect) -> Option<i32> {
31    (b.w <= NARROW_W).then_some(b.w / 3)
32}
33
34pub fn browse_menu(b: Rect) -> Rect {
35    Rect::new(b.x, b.y, b.w, MENU_H)
36}
37
38pub fn browse_toolbar(b: Rect) -> Rect {
39    Rect::new(b.x, b.y + MENU_H, b.w, TOOLBAR_H)
40}
41
42/// Top of the padded three-pane content area, below the menu and toolbar.
43fn content_y(b: Rect) -> i32 {
44    b.y + MENU_H + TOOLBAR_H + BROWSE_PAD
45}
46
47/// Height available to the three panes, after top and bottom padding.
48fn content_h(b: Rect) -> i32 {
49    (b.h - MENU_H - TOOLBAR_H - 2 * BROWSE_PAD).max(0)
50}
51
52/// Height of the history pane: a fraction of the content area, leaving a gap
53/// above the lower (files + diff) band.
54fn history_h(b: Rect) -> i32 {
55    let avail = (content_h(b) - BROWSE_PAD).max(0);
56    (avail as f32 * HISTORY_FRAC).round() as i32
57}
58
59pub fn browse_history(b: Rect) -> Rect {
60    Rect::new(
61        b.x + BROWSE_PAD,
62        content_y(b),
63        (b.w - 2 * BROWSE_PAD).max(0),
64        history_h(b),
65    )
66}
67
68pub fn browse_files(b: Rect) -> Rect {
69    let (lower_y, lower_h) = lower_band(b);
70    Rect::new(b.x + BROWSE_PAD, lower_y, clamp_files_w(b), lower_h)
71}
72
73pub fn browse_diff(b: Rect) -> Rect {
74    let (lower_y, lower_h) = lower_band(b);
75    let files_w = clamp_files_w(b);
76    let diff_x = b.x + 2 * BROWSE_PAD + files_w;
77    let diff_w = (b.w - files_w - 3 * BROWSE_PAD).max(0);
78    Rect::new(diff_x, lower_y, diff_w, lower_h)
79}
80
81/// The (top, height) of the lower band holding the files and diff panes, below
82/// the history pane and the gap under it.
83fn lower_band(b: Rect) -> (i32, i32) {
84    let lower_y = content_y(b) + history_h(b) + BROWSE_PAD;
85    let lower_h = (content_h(b) - history_h(b) - BROWSE_PAD).max(0);
86    (lower_y, lower_h)
87}
88
89/// Width of the files pane, clamped so the diff pane keeps a usable minimum
90/// once the inter-pane gap is accounted for, and capped to a third of the
91/// window on narrow layouts (see [`narrow_left_cap`]).
92fn clamp_files_w(b: Rect) -> i32 {
93    let w = FILES_W.min((b.w - 3 * BROWSE_PAD - 80).max(0));
94    match narrow_left_cap(b) {
95        Some(cap) => w.min(cap),
96        None => w,
97    }
98}
99
100// ---- commit (git-gui) layout ----------------------------------------------
101//
102// Both columns share one vertical grid: a top section (heading over a pane) and
103// a bottom section of the same shape, above a button band. So the left lists
104// line up row-for-row with the right diff and editor — "Unstaged Changes" with
105// "Diff", "Staged Changes" with "Commit Message" — and the action buttons share
106// one baseline.
107
108const LEFT_W: i32 = 320;
109/// Margin between the panes and the window edges.
110const PAD: i32 = 6;
111/// Space between the two columns, split evenly across the `LEFT_W` divider.
112/// Narrower than the outer `PAD` so the lists and the diff sit close together.
113const GUTTER: i32 = 6;
114const HEADING_H: i32 = 18;
115/// Height of the bottom band reserved for the action buttons on both columns.
116const BTN_BAND_H: i32 = 34;
117const BTN_GAP: i32 = 4;
118const LEFT_BTN_H: i32 = 24;
119const AMEND_H: i32 = 24;
120const COMMIT_BTN_H: i32 = 26;
121
122pub fn commit_menu(b: Rect) -> Rect {
123    Rect::new(b.x, b.y, b.w, MENU_H)
124}
125
126fn content_top(b: Rect) -> i32 {
127    b.y + MENU_H
128}
129fn content_height(b: Rect) -> i32 {
130    (b.h - MENU_H).max(0)
131}
132
133// Shared vertical grid -------------------------------------------------------
134
135/// Combined height of the two stacked sections, above the button band.
136fn sections_h(b: Rect) -> i32 {
137    (content_height(b) - BTN_BAND_H).max(0)
138}
139/// Height of a single section (heading + pane).
140fn section_h(b: Rect) -> i32 {
141    sections_h(b) / 2
142}
143fn top_label_y(b: Rect) -> i32 {
144    content_top(b) + 2
145}
146fn top_pane_y(b: Rect) -> i32 {
147    top_label_y(b) + HEADING_H
148}
149fn top_pane_h(b: Rect) -> i32 {
150    (section_h(b) - HEADING_H - 4).max(0)
151}
152fn bottom_label_y(b: Rect) -> i32 {
153    content_top(b) + section_h(b) + 2
154}
155fn bottom_pane_y(b: Rect) -> i32 {
156    bottom_label_y(b) + HEADING_H
157}
158fn bottom_pane_h(b: Rect) -> i32 {
159    (sections_h(b) - section_h(b) - HEADING_H - 4).max(0)
160}
161/// Top of a button of height `bh`, vertically centered in the bottom band so
162/// the left and right rows line up regardless of each button's height.
163fn btn_y(b: Rect, bh: i32) -> i32 {
164    content_top(b) + sections_h(b) + ((BTN_BAND_H - bh) / 2).max(0)
165}
166
167// Left column (unstaged / staged file lists) ---------------------------------
168
169fn left_x(b: Rect) -> i32 {
170    b.x + PAD
171}
172/// Width of the left column (the file lists). Fixed at [`LEFT_W`], but capped
173/// to a third of the window on narrow layouts (see [`narrow_left_cap`]) so the
174/// diff and message editor keep the majority of the space.
175fn left_col_w(b: Rect) -> i32 {
176    match narrow_left_cap(b) {
177        Some(cap) => LEFT_W.min(cap),
178        None => LEFT_W,
179    }
180}
181fn left_w(b: Rect) -> i32 {
182    (left_col_w(b) - PAD - GUTTER / 2).max(0)
183}
184
185pub fn commit_unstaged_label(b: Rect) -> Rect {
186    Rect::new(left_x(b), top_label_y(b), left_w(b), HEADING_H)
187}
188
189pub fn commit_unstaged_list(b: Rect) -> Rect {
190    Rect::new(left_x(b), top_pane_y(b), left_w(b), top_pane_h(b))
191}
192
193pub fn commit_staged_label(b: Rect) -> Rect {
194    Rect::new(left_x(b), bottom_label_y(b), left_w(b), HEADING_H)
195}
196
197pub fn commit_staged_list(b: Rect) -> Rect {
198    Rect::new(left_x(b), bottom_pane_y(b), left_w(b), bottom_pane_h(b))
199}
200
201/// Width of each of the three left-column action buttons: they split the column
202/// width evenly (minus the two gaps) so the row spans the full list width above
203/// it at any window size.
204fn left_btn_w(b: Rect) -> i32 {
205    ((left_w(b) - 2 * BTN_GAP) / 3).max(0)
206}
207
208/// Left edge of the left-column button in `slot` (0..3), packed left-to-right.
209fn left_btn_x(b: Rect, slot: i32) -> i32 {
210    left_x(b) + slot * (left_btn_w(b) + BTN_GAP)
211}
212
213pub fn commit_stage_btn(b: Rect) -> Rect {
214    Rect::new(
215        left_btn_x(b, 0),
216        btn_y(b, LEFT_BTN_H),
217        left_btn_w(b),
218        LEFT_BTN_H,
219    )
220}
221
222pub fn commit_unstage_btn(b: Rect) -> Rect {
223    Rect::new(
224        left_btn_x(b, 1),
225        btn_y(b, LEFT_BTN_H),
226        left_btn_w(b),
227        LEFT_BTN_H,
228    )
229}
230
231pub fn commit_rescan_btn(b: Rect) -> Rect {
232    Rect::new(
233        left_btn_x(b, 2),
234        btn_y(b, LEFT_BTN_H),
235        left_btn_w(b),
236        LEFT_BTN_H,
237    )
238}
239
240// Right column (diff view / commit message editor) ---------------------------
241
242/// Left edge of the right column: a half-gutter past the divider, which moves
243/// in with the left column on narrow layouts.
244fn right_inner_x(b: Rect) -> i32 {
245    b.x + left_col_w(b) + GUTTER / 2
246}
247/// Width of the right column: from `right_inner_x` to a `PAD` window margin.
248fn right_inner_w(b: Rect) -> i32 {
249    (b.x + b.w - PAD - right_inner_x(b)).max(0)
250}
251
252pub fn commit_diff_label(b: Rect) -> Rect {
253    Rect::new(
254        right_inner_x(b),
255        top_label_y(b),
256        right_inner_w(b),
257        HEADING_H,
258    )
259}
260
261pub fn commit_diff(b: Rect) -> Rect {
262    Rect::new(
263        right_inner_x(b),
264        top_pane_y(b),
265        right_inner_w(b),
266        top_pane_h(b),
267    )
268}
269
270pub fn commit_msg_label(b: Rect) -> Rect {
271    Rect::new(
272        right_inner_x(b),
273        bottom_label_y(b),
274        right_inner_w(b),
275        HEADING_H,
276    )
277}
278
279pub fn commit_editor(b: Rect) -> Rect {
280    Rect::new(
281        right_inner_x(b),
282        bottom_pane_y(b),
283        right_inner_w(b),
284        bottom_pane_h(b),
285    )
286}
287
288pub fn commit_amend(b: Rect) -> Rect {
289    Rect::new(right_inner_x(b), btn_y(b, AMEND_H), 180, AMEND_H)
290}
291
292pub fn commit_commit_btn(b: Rect) -> Rect {
293    let w = 110;
294    Rect::new(
295        right_inner_x(b) + right_inner_w(b) - w,
296        btn_y(b, COMMIT_BTN_H),
297        w,
298        COMMIT_BTN_H,
299    )
300}