style: professional dark theme refresh aligned with the design spec

- oak theme palette to a denser professional dark (near-black base,
  container/window surfaces, subtler secondary text).
- Dock tabs: selected tab solid accent with white text, hairline
  separators between panels.
- Timeline: green clip blocks, blue playhead, neutral track headers,
  brighter ruler labels, deep-green waveforms.
- Viewer: green timecode readout, solid-accent play button, chip-style
  fit/safe-margin buttons.
- Effect stack cards and node cards get rounded borders; spinbox
  values in amber per the design.
This commit is contained in:
2026-08-20 17:43:44 +08:00
parent 3870d6da06
commit dea33c713f
10 changed files with 167 additions and 64 deletions
+3
View File
@@ -173,6 +173,9 @@ impl Render for SpinBox {
.border_1()
.border_color(colors.border)
.bg(colors.background)
// Editable values render in the theme's amber (the
// design's gold numerals), distinct from labels.
.text_color(crate::theme::current_theme(cx).link)
.px_1()
.flex()
.items_center()
+10 -6
View File
@@ -45,18 +45,22 @@ fn rgba(hex: u32) -> Rgba {
impl OakTheme {
/// The olive-dark palette (oak's default).
///
/// Tuned to the design's professional dark scheme: a near-black content
/// base, slightly raised panel containers, a low-contrast separator and a
/// dimmed secondary text — the blue accent stays the single saturated hue.
pub fn olive_dark() -> Self {
Self {
name: "Olive Dark".into(),
window: rgba(0x353535),
base: rgba(0x191919),
alternate_base: rgba(0x353535),
text: rgb(0xffffff),
window: rgba(0x23272D),
base: rgba(0x16181D),
alternate_base: rgba(0x2E333B),
text: rgb(0xE8EAED),
accent: rgba(0x2A82DA),
accent_text: rgb(0xffffff),
link: rgba(0xE0B040),
disabled_text: rgba(0xA0A0A0),
disabled_button_text: rgba(0x808080),
disabled_text: rgba(0x7D838C),
disabled_button_text: rgba(0x565C66),
}
}
+53 -5
View File
@@ -414,11 +414,15 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
.px_2()
.py_0p5()
.bg(colors.container)
.border_t_1()
.border_color(colors.border)
.child(transport_button(
"gpui-widgets-viewer-in",
in_icon,
"⏮",
crate::i18n::tr("viewer.in_point", "入点"),
false,
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
this.emit(
ViewerEvent::InPointRequested {
@@ -433,6 +437,8 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
step_back_icon,
"⏪",
crate::i18n::tr("viewer.step_back", "上一帧"),
false,
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
this.emit(
ViewerEvent::StepRequested {
@@ -452,6 +458,8 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
} else {
crate::i18n::tr("viewer.play", "播放")
},
true,
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
let event = if this.transport.playing {
ViewerEvent::PauseRequested {
@@ -470,6 +478,8 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
step_forward_icon,
"⏩",
crate::i18n::tr("viewer.step_forward", "下一帧"),
false,
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
this.emit(
ViewerEvent::StepRequested {
@@ -485,6 +495,8 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
out_icon,
"⏭",
crate::i18n::tr("viewer.out_point", "出点"),
false,
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
this.emit(
ViewerEvent::OutPointRequested {
@@ -499,6 +511,8 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
None,
x_glyph(colors.text),
crate::i18n::tr("viewer.clear_range", "清除入出点"),
false,
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
this.emit(
ViewerEvent::ClearRangeRequested {
@@ -508,11 +522,19 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
);
}),
))
.child(div().px_2().text_xs().text_color(colors.text).child(timecode))
// The current timecode reads in the design's bright green.
.child(
div()
.px_2()
.text_xs()
.text_color(gpui::hsla(0.33, 0.75, 0.62, 1.0))
.child(timecode),
)
.child(div().flex_1())
.child(button(
"gpui-widgets-viewer-safe",
crate::i18n::tr("viewer.safe_frames", "安全框"),
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
this.show_safe_frames = !this.show_safe_frames;
this.emit(
@@ -526,6 +548,7 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
.child(button(
"gpui-widgets-viewer-zoom",
crate::i18n::tr("viewer.zoom", "缩放"),
&colors,
cx.listener(|this, _event: &ClickEvent, _window, cx| {
this.zoom = !this.zoom;
this.emit(
@@ -550,14 +573,20 @@ impl<C: PlaybackClock> Render for ViewerWidget<C> {
/// A transport icon button: a 16px icon on a 24px hit target with a
/// localized tooltip. Falls back to the `fallback` glyph when `icon` is
/// `None` (no resolver registered, or no file for the name).
/// `None` (no resolver registered, or no file for the name). `primary` marks
/// the design's accent-filled play/pause button; the rest are flat buttons
/// that surface on hover.
fn transport_button(
id: &'static str,
icon: Option<std::path::PathBuf>,
fallback: impl IntoElement,
tooltip: SharedString,
primary: bool,
colors: &gpui::colors::Colors,
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> impl IntoElement {
let background = colors.selected;
let hover = colors.container;
let mut el = div()
.id(id)
.debug_selector(move || id.into())
@@ -568,7 +597,19 @@ fn transport_button(
.justify_center()
.rounded_md()
.cursor_pointer()
.hover(|style| style.bg(gpui::colors::Colors::dark().selected))
.text_color(if primary {
colors.selected_text
} else {
colors.text
})
.when(primary, |style| style.bg(background))
.hover(move |style| {
if primary {
style.bg(background)
} else {
style.bg(hover)
}
})
.tooltip(move |window, cx| tooltip_view(tooltip.clone(), window, cx))
.on_click(on_click);
if let Some(path) = icon {
@@ -609,20 +650,27 @@ fn x_glyph(color: gpui::Rgba) -> impl IntoElement {
)
}
/// A small labeled button.
/// A small labeled button, styled as the design's bordered chip (the "适合 /
/// 安全框" controls at the transport bar's right end).
fn button(
id: &'static str,
label: impl IntoElement,
colors: &gpui::colors::Colors,
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> impl IntoElement {
let border = colors.border;
let hover = colors.separator;
div()
.id(id)
.debug_selector(move || id.into())
.px_2()
.py_0p5()
.rounded_md()
.border_1()
.border_color(border)
.text_color(colors.text)
.cursor_pointer()
.hover(|style| style.bg(gpui::colors::Colors::dark().selected))
.hover(move |style| style.bg(hover))
.on_click(on_click)
.child(label)
}