refactor: redesign theme tokens and split catppuccin themes

This commit is contained in:
2026-05-13 20:02:26 +08:00
parent af5fd60eb5
commit 2c3de1fd6e
20 changed files with 797 additions and 667 deletions

View File

@@ -9,7 +9,7 @@ use crate::{
api::{self},
app,
component::{
font_icon::{FontIcon, font_icon},
font_icon::{FontIcon, FontIconSvg, font_icon},
text::text,
},
query::{self, QueryStatus, read_query, use_query},
@@ -134,6 +134,19 @@ impl gpui::RenderOnce for IssueListItem {
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
let theme = app::current_theme(cx);
fn pill(label: impl gpui::IntoElement + gpui::Styled, icon: FontIconSvg) -> gpui::Div {
div()
.flex()
.flex_row()
.items_center()
.justify_start()
.rounded_full()
.px_2()
.gap_1()
.child(icon.size_3())
.child(label.text_xs())
}
let repo_name_text = match self.repo_name {
| Some(name) => text(name),
| None => text("Unknown repo"),
@@ -141,39 +154,43 @@ impl gpui::RenderOnce for IssueListItem {
.text_xs()
.opacity(0.5);
let icon = if self.is_draft {
font_icon(FontIcon::PullRequestDraft)
.text_color(theme.colors.text)
.opacity(0.5)
let status_pill = if self.is_draft {
pill(
text("Draft").text_color(theme.colors.text),
font_icon(FontIcon::PullRequestDraft).text_color(theme.colors.text),
)
.bg(theme.colors.surface)
} else {
match self.status {
| api::issues::PullRequestState::Closed => {
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger)
| api::issues::PullRequestState::Closed => pill(
text("Closed").text_color(theme.colors.danger_on_solid),
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger_on_solid),
)
.bg(theme.colors.danger_solid),
| api::issues::PullRequestState::Merged => pill(
text("Merged").text_color(theme.colors.accent_on_solid),
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.accent_on_solid),
)
.bg(theme.colors.accent_solid),
| _ => pill(
text("Open").text_color(theme.colors.success_on_solid),
font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success_on_solid),
)
.bg(theme.colors.success_solid),
}
| api::issues::PullRequestState::Merged => {
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.success)
}
| _ => font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success),
}
}
.flex_shrink_0()
.size_4();
let description_text = match self.description {
| Some(description) => text(description).text_xs(),
| None => text("No description provided").opacity(0.5).text_xs(),
};
let pills_row = div().flex().flex_row().gap_1().my_1().child(status_pill);
div()
.relative()
.w_full()
.px_1p5()
.px_3()
.py_1()
.gap_2()
.flex()
.flex_row()
.items_center()
.child(icon)
.child(
div()
.flex_1()
@@ -191,33 +208,30 @@ impl gpui::RenderOnce for IssueListItem {
.min_w_0()
.line_clamp(2),
)
.child(description_text),
.child(pills_row),
)
.when(!self.is_last, |it| {
it.border_b_1().border_color(theme.colors.border)
})
.when(self.is_selected, |it| {
it.bg(gpui::Rgba {
a: 0.05,
..theme.colors.accent
})
.overflow_hidden()
.border_r_1()
.child(
div()
.absolute()
.right_0()
.top_0()
.bottom_0()
.w_px()
.bg(theme.colors.accent)
.shadow(vec![gpui::BoxShadow {
blur_radius: px(16.),
spread_radius: px(2.),
color: gpui::Hsla::from(theme.colors.accent).alpha(0.8),
offset: point(px(-2.), px(0.)),
}]),
)
it.bg(theme.colors.selection_bg)
.overflow_hidden()
.border_r_1()
.child(
div()
.absolute()
.right_0()
.top_0()
.bottom_0()
.w_px()
.bg(theme.colors.selection_border)
.shadow(vec![gpui::BoxShadow {
blur_radius: px(16.),
spread_radius: px(2.),
color: gpui::Hsla::from(theme.colors.selection_border).alpha(0.8),
offset: point(px(-2.), px(0.)),
}]),
)
})
}
}

View File

@@ -1,6 +1,6 @@
use gpui::{
AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled,
div, img, point, prelude::FluentBuilder, px,
div, img, prelude::FluentBuilder,
};
use crate::{
@@ -24,7 +24,7 @@ pub(crate) struct PullRequestView {
#[derive(gpui::IntoElement)]
struct Toolbar {}
pub fn new(cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
pub fn new(_cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
PullRequestView {
markdown_viewer: None,
pull_request_query: None,
@@ -89,37 +89,41 @@ impl PullRequestView {
.rounded_full();
match pr.state {
| api::issues::PullRequestState::Open => {
status_pill = status_pill
.bg(theme.colors.success)
.child(
font_icon(FontIcon::PullRequestArrow)
.size_3()
.text_color(theme.colors.accent_text),
)
.child(text("Open").text_color(theme.colors.accent_text).text_xs());
}
| api::issues::PullRequestState::Closed => {
status_pill = status_pill
.bg(theme.colors.danger)
.child(
font_icon(FontIcon::PullRequestClosed)
.size_3()
.text_color(theme.colors.accent_text),
)
.child(
text("Closed")
.text_color(theme.colors.accent_text)
| api::issues::PullRequestState::Open => {
status_pill = status_pill
.bg(theme.colors.success_solid)
.child(
font_icon(FontIcon::PullRequestArrow)
.size_3()
.text_color(theme.colors.success_on_solid),
)
.child(
text("Open")
.text_color(theme.colors.success_on_solid)
.text_xs(),
);
}
| api::issues::PullRequestState::Closed => {
status_pill = status_pill
.bg(theme.colors.danger_solid)
.child(
font_icon(FontIcon::PullRequestClosed)
.size_3()
.text_color(theme.colors.danger_on_solid),
)
.child(
text("Closed")
.text_color(theme.colors.danger_on_solid)
.text_xs(),
);
}
| api::issues::PullRequestState::Merged => {
status_pill = status_pill.bg(theme.colors.accent_solid).child(
text("Merged")
.text_color(theme.colors.accent_on_solid)
.text_xs(),
);
}
| api::issues::PullRequestState::Merged => {
status_pill = status_pill.bg(theme.colors.accent).child(
text("Merged")
.text_color(theme.colors.accent_text)
.text_xs(),
);
}
}
}
let merge_text = match (
@@ -127,48 +131,48 @@ impl PullRequestView {
pr.base_branch_name.as_ref(),
pr.head_branch_name.as_ref(),
) {
| (Some(author), Some(base_branch), Some(head_branch)) => {
let str = format!(
"{} requested to merge {} into {}",
author.login, head_branch, base_branch
);
| (Some(author), Some(base_branch), Some(head_branch)) => {
let str = format!(
"{} requested to merge {} into {}",
author.login, head_branch, base_branch
);
let head_branch_text_offset = author.login.len() + 20;
let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
let head_branch_text_offset = author.login.len() + 20;
let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
let highlights = [
(
0..author.login.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
..Default::default()
},
),
(
head_branch_text_offset..head_branch_text_offset + head_branch.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent.into()),
..Default::default()
},
),
(
base_branch_text_offset..base_branch_text_offset + base_branch.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent.into()),
..Default::default()
},
),
];
let highlights = [
(
0..author.login.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
..Default::default()
},
),
(
head_branch_text_offset..head_branch_text_offset + head_branch.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent_fg.into()),
..Default::default()
},
),
(
base_branch_text_offset..base_branch_text_offset + base_branch.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent_fg.into()),
..Default::default()
},
),
];
Some((
author,
gpui::StyledText::new(str).with_highlights(highlights),
))
}
Some((
author,
gpui::StyledText::new(str).with_highlights(highlights),
))
}
| _ => None,
| _ => None,
};
let metadata_line =
@@ -257,24 +261,24 @@ impl gpui::Render for PullRequestView {
cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
div().size_full().child(match &self.pull_request_query {
| Some(q) => match read_query(q, cx) {
| QueryStatus::Loaded(pr) => self.pr_content(pr, cx),
| QueryStatus::Err(e) => div()
.size_full()
.child(format!("{:?}", e))
.into_any_element(),
| QueryStatus::Loading => div()
.size_full()
.child("loading pr content")
.into_any_element(),
},
| None => div().size_full().child("no pr selected").into_any_element(),
| Some(q) => match read_query(q, cx) {
| QueryStatus::Loaded(pr) => self.pr_content(pr, cx),
| QueryStatus::Err(e) => div()
.size_full()
.child(format!("{:?}", e))
.into_any_element(),
| QueryStatus::Loading => div()
.size_full()
.child("loading pr content")
.into_any_element(),
},
| None => div().size_full().child("no pr selected").into_any_element(),
})
}
}
impl gpui::RenderOnce for Toolbar {
fn render(self, window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
fn toolbar_button(id: impl Into<gpui::ElementId>) -> Button {
button(id)
.px_2p5()

View File

@@ -5,7 +5,6 @@ use crate::{
screen::dashboard::{
issue_list::{self, IssueList},
pull_request_view::{self, PullRequestView},
sidebar::{self, Sidebar, SidebarItemValue},
titlebar::{self, TitleBar},
},
};
@@ -13,7 +12,6 @@ use crate::{
pub(crate) struct Screen {
titlebar: gpui::Entity<TitleBar>,
issue_list: gpui::Entity<IssueList>,
sidebar: gpui::Entity<Sidebar>,
pull_request_view: gpui::Entity<PullRequestView>,
issue_filter: Option<&'static str>,
@@ -23,7 +21,6 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
let mut screen = Screen {
titlebar: cx.new(titlebar::new),
issue_list: cx.new(issue_list::new),
sidebar: cx.new(|_| sidebar::new()),
pull_request_view: cx.new(pull_request_view::new),
issue_filter: None,
@@ -34,35 +31,15 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
impl Screen {
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
let on_item_change = cx.listener(|this, value, _, cx| {
this.handle_sidebar_item_change(value, cx);
});
self.sidebar.update(cx, |sidebar, _| {
sidebar.on_item_change(on_item_change);
});
_ = cx
.subscribe(&self.issue_list, |this, _, event, cx| match event {
| issue_list::Event::ItemSelected(pr_id) => {
this.handle_issue_list_item_selected(pr_id, cx);
}
| issue_list::Event::ItemSelected(pr_id) => {
this.handle_issue_list_item_selected(pr_id, cx);
}
})
.detach();
}
fn handle_sidebar_item_change(
&mut self,
value: &SidebarItemValue,
cx: &mut gpui::Context<Self>,
) {
match value {
| SidebarItemValue::PullRequest { filter } => {
self.issue_filter = Some(*filter);
cx.notify();
}
}
}
fn handle_issue_list_item_selected(
&mut self,
id: &api::issues::Id,
@@ -97,21 +74,12 @@ impl gpui::Render for Screen {
.flex_1()
.min_h_0()
.w_full()
.child(
div()
.w_40()
.flex_shrink_0()
.h_full()
.child(self.sidebar.clone()),
)
.child(
div()
.w_64()
.flex_shrink_0()
.h_full()
.bg(theme.colors.surface)
.border_x_1()
.border_color(theme.colors.border)
.bg(theme.colors.surface_chrome)
.overflow_hidden()
.child(self.issue_list.clone()),
)

View File

@@ -160,19 +160,19 @@ impl gpui::RenderOnce for SidebarItem {
.px_2()
.py_1()
.gap_2()
.child(font_icon(self.icon).size_3().when(self.is_selected, |it| {
it.text_color(theme.colors.accent_text)
}))
.child(
font_icon(self.icon)
.size_3()
.when(self.is_selected, |it| it.text_color(theme.colors.accent_fg)),
)
.child(
text(self.title)
.text_sm()
.leading_tight()
.when(self.is_selected, |it| {
it.text_color(theme.colors.accent_text)
}),
.when(self.is_selected, |it| it.text_color(theme.colors.accent_fg)),
),
)
.when_some(self.on_click, |it, f| it.on_click(f))
.when(self.is_selected, |it| it.bg(theme.colors.accent))
.when(self.is_selected, |it| it.bg(theme.colors.selection_bg))
}
}

View File

@@ -1,7 +1,7 @@
use gpui::{ParentElement, Styled, TitlebarOptions, div};
use gpui::{ParentElement, Styled, div};
use crate::component::button::button;
use crate::query::{self, QueryStatus, read_query, use_lazy_query, use_query};
use crate::query::{self, QueryStatus, read_query, use_lazy_query};
use crate::{
api, app,
component::{
@@ -32,13 +32,13 @@ impl gpui::Render for TitleBar {
let user = read_query(&self.fetch_user_query, cx);
let user_avatar = match user {
QueryStatus::Err(api::Error::Unauthenticated) => div().absolute().right_2p5().child(
| QueryStatus::Err(api::Error::Unauthenticated) => div().absolute().right_2p5().child(
button("login-btn")
.leading(font_icon(FontIcon::Github))
.label("Login"),
),
_ => div(),
| _ => div(),
};
div()
@@ -50,7 +50,7 @@ impl gpui::Render for TitleBar {
.flex()
.px(g.safe_area.size.width)
.py_2()
.bg(g.current_theme.colors.background)
.bg(g.current_theme.colors.surface_chrome)
.text_color(g.current_theme.colors.text)
.relative()
.border_b_1()
@@ -61,7 +61,7 @@ impl gpui::Render for TitleBar {
}
impl RepoSelector {
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
pub fn new(_cx: &mut gpui::Context<Self>) -> Self {
Self {}
}
}