Skip to main content

gitkraft_gui/widgets/
header.rs

1//! Top toolbar / header bar for the GitKraft main layout.
2//!
3//! Shows: repo name │ branch name │ fetch button │ toggle sidebar.
4
5use iced::widget::{button, container, row, text, Space};
6use iced::{Alignment, Element, Length};
7
8use crate::features::editor::editor_selector;
9use crate::features::theme::view::theme_selector;
10use crate::icons;
11use crate::message::Message;
12use crate::state::GitKraft;
13use crate::theme;
14use crate::view_utils;
15
16/// Render the top toolbar row.
17pub fn view(state: &GitKraft) -> Element<'_, Message> {
18    let tab = state.active_tab();
19    let c = state.colors();
20
21    // ── Repo name ─────────────────────────────────────────────────────────
22    let repo_icon = icon!(icons::FOLDER_OPEN, 14, c.accent);
23
24    let repo_name = text(state.repo_display_name())
25        .size(14)
26        .color(c.text_primary);
27
28    let separator = || text("|").size(14).color(c.border);
29
30    // ── Branch indicator ──────────────────────────────────────────────────
31    let branch_icon = icon!(icons::GIT_BRANCH, 14, c.green);
32
33    let branch_name_str = tab.current_branch.as_deref().unwrap_or("(detached)");
34
35    let branch_label = text(branch_name_str).size(14).color(c.text_primary);
36
37    // ── Repo state badge (if not clean) ───────────────────────────────────
38    let state_badge: Element<'_, Message> = if let Some(ref info) = tab.repo_info {
39        if info.state != gitkraft_core::RepoState::Clean {
40            text(format!(" [{}]", info.state))
41                .size(12)
42                .color(c.yellow)
43                .into()
44        } else {
45            Space::new().into()
46        }
47    } else {
48        Space::new().into()
49    };
50
51    // ── Fetch button ──────────────────────────────────────────────────────
52    let fetch_icon = icon!(icons::CLOUD_ARROW_DOWN, 14, c.accent);
53
54    let fetch_msg = (!tab.remotes.is_empty()).then_some(Message::Fetch);
55    let fetch_btn = crate::view_utils::on_press_maybe(
56        button(
57            row![fetch_icon, Space::new().width(4), text("Fetch").size(12)]
58                .align_y(Alignment::Center),
59        )
60        .padding([4, 10])
61        .style(theme::toolbar_button),
62        fetch_msg,
63    );
64
65    // ── Open another repo button ──────────────────────────────────────────
66    let open_icon = icon!(icons::FOLDER_OPEN, 14, c.text_secondary);
67
68    let open_btn = view_utils::toolbar_btn(open_icon, "Open", Message::OpenRepo);
69
70    // ── Close repo button (return to welcome screen) ──────────────────────
71    let close_icon = icon!(icons::X_CIRCLE, 14, c.text_secondary);
72
73    let close_btn = view_utils::toolbar_btn(close_icon, "Close", Message::CloseRepo);
74
75    // ── Toggle sidebar ────────────────────────────────────────────────────
76    let sidebar_icon_char = if state.sidebar_expanded {
77        icons::CHEVRON_LEFT
78    } else {
79        icons::CHEVRON_RIGHT
80    };
81    let sidebar_icon = icon!(sidebar_icon_char, 14, c.text_secondary);
82
83    let sidebar_btn = button(sidebar_icon)
84        .padding([4, 8])
85        .style(theme::icon_button)
86        .on_press(Message::ToggleSidebar);
87
88    // ── Loading indicator ─────────────────────────────────────────────
89    // Use FluxFrames::CORNERS from tui-spinner so both GUI and TUI share
90    // the same symbol set — no duplication.
91    let spinner_frame: String = {
92        let frames = tui_spinner::FluxFrames::CORNERS; // &'static [char]
93        let ch = frames[state.animation_tick as usize % frames.len()];
94        ch.to_string()
95    };
96
97    let loading_indicator: Element<'_, Message> = if tab.is_loading {
98        row![
99            text(spinner_frame)
100                .size(15)
101                .color(c.accent)
102                .font(iced::Font::MONOSPACE),
103            iced::widget::Space::new().width(4),
104            text("Loading…").size(12).color(c.yellow),
105        ]
106        .align_y(iced::Alignment::Center)
107        .into()
108    } else {
109        Space::new().into()
110    };
111
112    // ── Assemble ──────────────────────────────────────────────────────────
113    let left_items = row![
114        sidebar_btn,
115        Space::new().width(8),
116        repo_icon,
117        Space::new().width(6),
118        repo_name,
119        Space::new().width(10),
120        separator(),
121        Space::new().width(10),
122        branch_icon,
123        Space::new().width(6),
124        branch_label,
125        state_badge,
126        Space::new().width(10),
127        separator(),
128        Space::new().width(10),
129        loading_indicator,
130    ]
131    .align_y(Alignment::Center);
132
133    let right_items = row![
134        fetch_btn,
135        Space::new().width(4),
136        open_btn,
137        Space::new().width(4),
138        close_btn,
139        Space::new().width(8),
140        theme_selector(state.current_theme_index),
141        Space::new().width(4),
142        editor_selector(&state.editor),
143    ]
144    .align_y(Alignment::Center);
145
146    let toolbar = row![
147        container(left_items).width(Length::Fill).clip(true),
148        right_items,
149    ]
150    .align_y(Alignment::Center)
151    .padding([6, 12])
152    .width(Length::Fill);
153
154    container(toolbar)
155        .width(Length::Fill)
156        .style(theme::header_style)
157        .into()
158}