mirror of
https://github.com/ZSeven-W/openpencil.git
synced 2026-05-31 19:04:29 +07:00
feat(panels): add git-panel button to the top bar
The top bar had no affordance to open the git panel (only a keyboard/menu path). Add a TS-style git button just right of the file name: a `GitBranch` glyph + the current branch name when in a repo. Always shown (per request) — a click toggles the git panel, which offers `init` when the doc isn't yet a repo. - new `Icon::GitBranch` lucide glyph; - `TopBar.git_branch` from `git_panel.branch`; `git_button_rect` (CJK-aware width estimate so it clears a CJK file name) shared by paint + hit-test; `TopBarHit::ToggleGitPanel`; - native host mirrors `main.rs` toggle bookkeeping (per-frame refresh does the scan); web host toggles `git_panel.open`.
This commit is contained in:
parent
e2f0e4e1a0
commit
a4b70fa055
6 changed files with 110 additions and 0 deletions
|
|
@ -74,6 +74,8 @@ pub enum Icon {
|
||||||
PanelLeft,
|
PanelLeft,
|
||||||
/// FolderOpen — TopBar folder.
|
/// FolderOpen — TopBar folder.
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
|
/// GitBranch — TopBar git-panel toggle next to the file name.
|
||||||
|
GitBranch,
|
||||||
/// Sparkles — agent active indicator.
|
/// Sparkles — agent active indicator.
|
||||||
Sparkles,
|
Sparkles,
|
||||||
/// X — close affordance.
|
/// X — close affordance.
|
||||||
|
|
@ -257,6 +259,7 @@ impl Icon {
|
||||||
Icon::Hash => HASH,
|
Icon::Hash => HASH,
|
||||||
Icon::PanelLeft => PANEL_LEFT,
|
Icon::PanelLeft => PANEL_LEFT,
|
||||||
Icon::FolderOpen => FOLDER_OPEN,
|
Icon::FolderOpen => FOLDER_OPEN,
|
||||||
|
Icon::GitBranch => GIT_BRANCH,
|
||||||
Icon::Sparkles => SPARKLES,
|
Icon::Sparkles => SPARKLES,
|
||||||
Icon::Close => CLOSE,
|
Icon::Close => CLOSE,
|
||||||
Icon::Trash => TRASH,
|
Icon::Trash => TRASH,
|
||||||
|
|
@ -402,6 +405,7 @@ impl Icon {
|
||||||
"download" => Icon::Download,
|
"download" => Icon::Download,
|
||||||
"file-text" => Icon::FileText,
|
"file-text" => Icon::FileText,
|
||||||
"folder-open" | "folder" => Icon::FolderOpen,
|
"folder-open" | "folder" => Icon::FolderOpen,
|
||||||
|
"git-branch" | "git" => Icon::GitBranch,
|
||||||
"sparkles" => Icon::Sparkles,
|
"sparkles" => Icon::Sparkles,
|
||||||
"diamond" => Icon::Diamond,
|
"diamond" => Icon::Diamond,
|
||||||
"component" => Icon::Component,
|
"component" => Icon::Component,
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,14 @@ pub(super) const FOLDER_OPEN: &[&str] = &[
|
||||||
"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",
|
"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub(super) const GIT_BRANCH: &[&str] = &[
|
||||||
|
// lucide git-branch: line + two r=3 circles + connecting arc.
|
||||||
|
"M6 3v12",
|
||||||
|
"M18 6 m-3 0 a3 3 0 1 0 6 0 a3 3 0 1 0 -6 0",
|
||||||
|
"M6 18 m-3 0 a3 3 0 1 0 6 0 a3 3 0 1 0 -6 0",
|
||||||
|
"M18 9a9 9 0 0 1-9 9",
|
||||||
|
];
|
||||||
|
|
||||||
pub(super) const SPARKLES: &[&str] = &[
|
pub(super) const SPARKLES: &[&str] = &[
|
||||||
"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",
|
"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",
|
||||||
"M20 2v4",
|
"M20 2v4",
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ fn every_variant_paints_at_least_one_primitive() {
|
||||||
Icon::Hash,
|
Icon::Hash,
|
||||||
Icon::PanelLeft,
|
Icon::PanelLeft,
|
||||||
Icon::FolderOpen,
|
Icon::FolderOpen,
|
||||||
|
Icon::GitBranch,
|
||||||
Icon::Sparkles,
|
Icon::Sparkles,
|
||||||
Icon::Close,
|
Icon::Close,
|
||||||
Icon::ChevronUp,
|
Icon::ChevronUp,
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ pub enum TopBarHit {
|
||||||
ToggleLocale,
|
ToggleLocale,
|
||||||
/// Agents and MCP chip — open the agent settings modal.
|
/// Agents and MCP chip — open the agent settings modal.
|
||||||
OpenAgentSettings,
|
OpenAgentSettings,
|
||||||
|
/// Git-branch button next to the file name — toggle the git panel.
|
||||||
|
ToggleGitPanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TopBar {
|
pub struct TopBar {
|
||||||
|
|
@ -108,6 +110,10 @@ pub struct TopBar {
|
||||||
/// Dark mode (click to go light), a Moon glyph in Light mode
|
/// Dark mode (click to go light), a Moon glyph in Light mode
|
||||||
/// (click to go dark).
|
/// (click to go dark).
|
||||||
pub theme_mode: op_editor_core::ThemeMode,
|
pub theme_mode: op_editor_core::ThemeMode,
|
||||||
|
/// Current git branch when the open document is in a repo — shown
|
||||||
|
/// beside the file name. `None` = no branch (the button still
|
||||||
|
/// paints, icon-only, as a toggle for the git panel).
|
||||||
|
pub git_branch: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TopBar {
|
impl TopBar {
|
||||||
|
|
@ -125,6 +131,7 @@ impl TopBar {
|
||||||
traffic_hover: false,
|
traffic_hover: false,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
theme_mode: op_editor_core::ThemeMode::Dark,
|
theme_mode: op_editor_core::ThemeMode::Dark,
|
||||||
|
git_branch: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,6 +168,7 @@ impl TopBar {
|
||||||
traffic_hover: ui.topbar_traffic_hover,
|
traffic_hover: ui.topbar_traffic_hover,
|
||||||
fullscreen: ui.window_fullscreen,
|
fullscreen: ui.window_fullscreen,
|
||||||
theme_mode: ui.theme_mode,
|
theme_mode: ui.theme_mode,
|
||||||
|
git_branch: ui.git_panel.branch.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,6 +271,34 @@ impl TopBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Git-panel toggle button — sits just right of the centred file
|
||||||
|
/// name. Width holds the branch glyph plus an optional branch
|
||||||
|
/// label. Shared by paint + hit-test so they can't drift.
|
||||||
|
fn git_button_rect(&self, top_bar_rect: Rect) -> Rect {
|
||||||
|
let center_y = top_bar_rect.origin.y + top_bar_rect.size.y / 2.0;
|
||||||
|
// The name is *centred* using the 9 px/char heuristic, but a
|
||||||
|
// CJK glyph renders ~14 px wide, so the real right edge is
|
||||||
|
// further out — use a CJK-aware estimate so the button clears
|
||||||
|
// the (often CJK) file name instead of overlapping it.
|
||||||
|
let center_approx = self.file_name.chars().count() as f32 * 9.0;
|
||||||
|
let render_w: f32 = self
|
||||||
|
.file_name
|
||||||
|
.chars()
|
||||||
|
.map(|c| if is_wide_glyph(c) { 14.0 } else { 7.5 })
|
||||||
|
.sum();
|
||||||
|
let filename_left = top_bar_rect.origin.x + (top_bar_rect.size.x - center_approx) / 2.0;
|
||||||
|
let filename_right = filename_left + render_w;
|
||||||
|
let branch_w = self
|
||||||
|
.git_branch
|
||||||
|
.as_deref()
|
||||||
|
.map(|b| 6.0 + b.chars().count() as f32 * 7.0)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
Rect {
|
||||||
|
origin: Point2D::new(filename_right + 10.0, center_y - ICON_BUTTON / 2.0),
|
||||||
|
size: Point2D::new(ICON_SIZE + 8.0 + branch_w, ICON_BUTTON),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve a press on the left-edge window-control dots.
|
/// Resolve a press on the left-edge window-control dots.
|
||||||
/// `None` for a press anywhere else (including the app's own
|
/// `None` for a press anywhere else (including the app's own
|
||||||
/// buttons). The desktop runner consults this before its normal
|
/// buttons). The desktop runner consults this before its normal
|
||||||
|
|
@ -334,6 +370,10 @@ impl TopBar {
|
||||||
if rect_contains(figma_rect, point) {
|
if rect_contains(figma_rect, point) {
|
||||||
return Some(TopBarHit::OpenFigmaImport);
|
return Some(TopBarHit::OpenFigmaImport);
|
||||||
}
|
}
|
||||||
|
// Git-panel toggle, just right of the centred file name.
|
||||||
|
if rect_contains(self.git_button_rect(rect), point) {
|
||||||
|
return Some(TopBarHit::ToggleGitPanel);
|
||||||
|
}
|
||||||
// Right cluster: Maximize / Sun / Globe-with-chevron (right→left).
|
// Right cluster: Maximize / Sun / Globe-with-chevron (right→left).
|
||||||
// Maximize + Sun are normal ICON_BUTTON wide; Globe is
|
// Maximize + Sun are normal ICON_BUTTON wide; Globe is
|
||||||
// GLOBE_BUTTON_WIDTH wide because it carries a chevron.
|
// GLOBE_BUTTON_WIDTH wide because it carries a chevron.
|
||||||
|
|
@ -546,6 +586,33 @@ impl Widget for TopBar {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Git-panel button just right of the file name (TS GitButton):
|
||||||
|
// a branch glyph + optional branch name. Always shown — a
|
||||||
|
// click toggles the git panel (which offers `init` when the
|
||||||
|
// doc isn't yet in a repo).
|
||||||
|
let git_rect = self.git_button_rect(rect);
|
||||||
|
draw_icon(
|
||||||
|
cx.backend,
|
||||||
|
Icon::GitBranch,
|
||||||
|
Point2D::new(git_rect.origin.x, glyph_top(center_y, ICON_SIZE)),
|
||||||
|
ICON_SIZE,
|
||||||
|
self.theme.muted_foreground,
|
||||||
|
1.4,
|
||||||
|
);
|
||||||
|
if let Some(branch) = self.git_branch.as_deref() {
|
||||||
|
let label = TextLayout::single_run(
|
||||||
|
branch,
|
||||||
|
"system-ui",
|
||||||
|
11.0,
|
||||||
|
to_jian_color(self.theme.muted_foreground),
|
||||||
|
Point2D::new(0.0, 0.0),
|
||||||
|
);
|
||||||
|
cx.backend.draw_text(
|
||||||
|
&label,
|
||||||
|
Point2D::new(git_rect.origin.x + ICON_SIZE + 6.0, center_y + 4.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Right cluster ──────────────────────────────────────
|
// ── Right cluster ──────────────────────────────────────
|
||||||
// Right → left: Maximize | Sun | Globe+Chevron. Globe is a
|
// Right → left: Maximize | Sun | Globe+Chevron. Globe is a
|
||||||
// wider compound button (signals the dropdown affordance).
|
// wider compound button (signals the dropdown affordance).
|
||||||
|
|
@ -741,6 +808,14 @@ fn paint_figma_button(cx: &mut PaintCx<'_>, theme: &Theme, x: f32, center_y: f32
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rough "is this a full-width (CJK/Hangul/full-width-form) glyph"
|
||||||
|
/// test — used only to estimate the rendered file-name width so the
|
||||||
|
/// git button clears it.
|
||||||
|
fn is_wide_glyph(c: char) -> bool {
|
||||||
|
let cp = c as u32;
|
||||||
|
matches!(cp, 0x1100..=0x11FF | 0x2E80..=0x9FFF | 0xAC00..=0xD7AF | 0xF900..=0xFAFF | 0xFF00..=0xFFEF)
|
||||||
|
}
|
||||||
|
|
||||||
/// Top-left y for a glyph of `size` vertically centred on `center_y`.
|
/// Top-left y for a glyph of `size` vertically centred on `center_y`.
|
||||||
/// Every top-bar glyph routes through this so the whole bar shares
|
/// Every top-bar glyph routes through this so the whole bar shares
|
||||||
/// one center line.
|
/// one center line.
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,25 @@ impl WidgetHostNative {
|
||||||
self.mark_dirty();
|
self.mark_dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
TopBarHit::ToggleGitPanel => {
|
||||||
|
// Mirror `main.rs` A::ToggleGitPanel bookkeeping; the
|
||||||
|
// binary's per-frame `if git_panel.open { refresh }`
|
||||||
|
// performs the actual repo scan.
|
||||||
|
let panel = &mut self.editor_state.editor_ui.git_panel;
|
||||||
|
let opening = !panel.open;
|
||||||
|
panel.open = opening;
|
||||||
|
if opening {
|
||||||
|
panel.loading = true;
|
||||||
|
} else {
|
||||||
|
panel.commit_focused = false;
|
||||||
|
panel.remote_focused = false;
|
||||||
|
panel.https_focused = false;
|
||||||
|
panel.diff = None;
|
||||||
|
panel.merge_resolve = None;
|
||||||
|
}
|
||||||
|
self.mark_dirty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rect_contains(top_bar_rect, Point2D::new(x, y)) {
|
if rect_contains(top_bar_rect, Point2D::new(x, y)) {
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,9 @@ impl WidgetHost {
|
||||||
TopBarHit::OpenFigmaImport => {
|
TopBarHit::OpenFigmaImport => {
|
||||||
self.editor_state.editor_ui.figma_import_open = true;
|
self.editor_state.editor_ui.figma_import_open = true;
|
||||||
}
|
}
|
||||||
|
TopBarHit::ToggleGitPanel => {
|
||||||
|
self.editor_state.editor_ui.git_panel.open ^= true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.mark_dirty();
|
self.mark_dirty();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue