theme: build out the theme engine core with resolve pipeline

This commit is contained in:
2026-10-28 10:54:02 +07:00
parent b4d3359d4f
commit 12d4120bfd
14 changed files with 1434 additions and 37 deletions
+152 -16
View File
@@ -1,6 +1,8 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
App, AppContext as _, Bounds, Context, IntoElement, ParentElement as _, Render, Styled as _,
Window, WindowBounds, WindowOptions, div, px, size,
App, AppContext as _, Bounds, ClickEvent, Context, InteractiveElement as _, IntoElement,
ParentElement as _, Render, SharedString, StatefulInteractiveElement as _, Styled as _, Window,
WindowBounds, WindowOptions, div, px, size,
};
use gpui_native_icons::{IconAssets, IconRole, icon};
use gpui_native_theme::Theme;
@@ -13,6 +15,9 @@ impl Render for Gallery {
let theme = Theme::global(cx);
let colors = theme.colors;
let variant = theme.variant;
let source = gpui_native_theme::detected_source(cx);
let mode_override = gpui_native_theme::mode_override(cx);
let active_preset = gpui_native_theme::active_preset(cx);
let swatches: [(&str, gpui::Hsla); 18] = [
("background", colors.background),
@@ -34,6 +39,34 @@ impl Render for Gallery {
("ring", colors.ring),
("selection", colors.selection),
];
let shades = [
("base", theme.ramps.primary.base),
("hover", theme.ramps.primary.hover),
("pressed", theme.ramps.primary.pressed),
("subtle", theme.ramps.primary.subtle),
];
let modes: [(SharedString, Option<gpui_native_theme::ThemeVariant>, bool); 3] = [
("System".into(), None, mode_override.is_none()),
(
"Light".into(),
Some(gpui_native_theme::ThemeVariant::Light),
mode_override == Some(gpui_native_theme::ThemeVariant::Light),
),
(
"Dark".into(),
Some(gpui_native_theme::ThemeVariant::Dark),
mode_override == Some(gpui_native_theme::ThemeVariant::Dark),
),
];
let presets: [(SharedString, Option<&'static str>, bool); 2] = [
("Follow system".into(), None, active_preset.is_none()),
(
"Tokyo Night".into(),
Some("tokyo-night"),
active_preset == Some("tokyo-night"),
),
];
div()
.size_full()
@@ -60,7 +93,7 @@ impl Render for Gallery {
.text_sm()
.text_color(colors.muted_foreground)
.child(format!(
"phase A scaffold — variant: {variant:?} (system-resolved)"
"phase B — theme engine · source: {source} · variant: {variant:?}"
)),
),
)
@@ -69,13 +102,78 @@ impl Render for Gallery {
.flex()
.flex_col()
.gap_2()
.child(section_label("mode", colors))
.child(
div()
.text_sm()
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(colors.muted_foreground)
.child("color tokens"),
)
.flex()
.gap_2()
.children(modes.map(|(label, value, active)| {
chip(
format!("mode-{label}"),
label,
active,
colors,
move |_, _, cx| gpui_native_theme::set_mode_override(value, cx),
)
})),
),
)
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(section_label("preset", colors))
.child(
div()
.flex()
.gap_2()
.children(presets.map(|(label, value, active)| {
chip(
format!("preset-{label}"),
label,
active,
colors,
move |_, _, cx| {
let _ = gpui_native_theme::set_preset(value, cx);
},
)
})),
),
)
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(section_label("accent shades (derived, OKLCH)", colors))
.child(div().flex().gap_2().children(shades.map(|(name, color)| {
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.size_6()
.rounded_full()
.border_1()
.border_color(colors.border)
.bg(color),
)
.child(
div()
.text_xs()
.text_color(colors.muted_foreground)
.child(name),
)
}))),
)
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(section_label("color tokens", colors))
.child(div().flex().flex_wrap().gap_2().children(swatches.map(
|(name, color)| {
div()
@@ -104,13 +202,10 @@ impl Render for Gallery {
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_sm()
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(colors.muted_foreground)
.child("icons (Lucide fallback; platform resolvers land later)"),
)
.child(section_label(
"icons (Lucide fallback; platform resolvers land later)",
colors,
))
.child(
div().flex().items_center().gap_4().children(
IconRole::all()
@@ -122,10 +217,51 @@ impl Render for Gallery {
}
}
fn section_label(text: &str, colors: gpui_native_theme::ColorTokens) -> impl IntoElement + use<> {
div()
.text_sm()
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(colors.muted_foreground)
.child(text.to_string())
}
fn chip<F>(
id: String,
label: SharedString,
active: bool,
colors: gpui_native_theme::ColorTokens,
on_click: F,
) -> impl IntoElement + use<F>
where
F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
{
div()
.id(SharedString::from(id))
.px_3()
.py_1()
.rounded_full()
.border_1()
.text_xs()
.cursor_pointer()
.when(active, |el| {
el.bg(colors.primary)
.border_color(colors.primary)
.text_color(colors.primary_foreground)
})
.when(!active, |el| {
el.bg(colors.secondary)
.border_color(colors.border)
.text_color(colors.secondary_foreground)
.hover(|el| el.bg(colors.muted))
})
.on_click(move |event, window, cx| on_click(event, window, cx))
.child(label)
}
fn main() {
application().with_assets(IconAssets).run(|cx: &mut App| {
Theme::init(cx);
let bounds = Bounds::centered(None, size(px(720.0), px(480.0)), cx);
let bounds = Bounds::centered(None, size(px(720.0), px(560.0)), cx);
if let Err(err) = cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),