add muted_hover token and rework tab strip visuals
This commit is contained in:
@@ -41,6 +41,7 @@ pub struct Button {
|
|||||||
size: Size,
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
tab_stop: bool,
|
tab_stop: bool,
|
||||||
|
debug_selector: Option<Rc<dyn Fn() -> String>>,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
tooltip: Option<TooltipBuilder>,
|
tooltip: Option<TooltipBuilder>,
|
||||||
on_click: Option<ClickHandler>,
|
on_click: Option<ClickHandler>,
|
||||||
@@ -60,6 +61,7 @@ impl Button {
|
|||||||
size: Size::default(),
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
tab_stop: true,
|
tab_stop: true,
|
||||||
|
debug_selector: None,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
@@ -102,6 +104,15 @@ impl Button {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the debug-bounds selector, for headless tests.
|
||||||
|
pub fn debug_selector<F>(mut self, selector: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn() -> String + 'static,
|
||||||
|
{
|
||||||
|
self.debug_selector = Some(Rc::new(selector));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Renders the button inert, unfocusable, and dimmed.
|
/// Renders the button inert, unfocusable, and dimmed.
|
||||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||||
self.disabled = disabled;
|
self.disabled = disabled;
|
||||||
@@ -160,7 +171,7 @@ impl RenderOnce for Button {
|
|||||||
ButtonVariant::Ghost => (
|
ButtonVariant::Ghost => (
|
||||||
None,
|
None,
|
||||||
colors.foreground,
|
colors.foreground,
|
||||||
colors.muted,
|
colors.muted_hover,
|
||||||
ramps.secondary.pressed,
|
ramps.secondary.pressed,
|
||||||
),
|
),
|
||||||
ButtonVariant::Destructive => (
|
ButtonVariant::Destructive => (
|
||||||
@@ -173,6 +184,9 @@ impl RenderOnce for Button {
|
|||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
|
.when_some(self.debug_selector, |button, selector| {
|
||||||
|
button.debug_selector(move || selector())
|
||||||
|
})
|
||||||
.role(Role::Button)
|
.role(Role::Button)
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.when(icon_only, |button| button.size(metrics.control_height))
|
.when(icon_only, |button| button.size(metrics.control_height))
|
||||||
|
|||||||
@@ -19,5 +19,9 @@ default = ["full"]
|
|||||||
full = ["list", "sidebar", "tabs", "tree"]
|
full = ["list", "sidebar", "tabs", "tree"]
|
||||||
list = []
|
list = []
|
||||||
sidebar = []
|
sidebar = []
|
||||||
tabs = ["dep:gpui-native-core", "gpui-native-core/button"]
|
tabs = [
|
||||||
|
"dep:gpui-native-core",
|
||||||
|
"gpui-native-core/button",
|
||||||
|
"gpui-native-core/tooltip",
|
||||||
|
]
|
||||||
tree = []
|
tree = []
|
||||||
|
|||||||
+37
-59
@@ -8,6 +8,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use gpui_native_core::button::{Button, ButtonVariant};
|
use gpui_native_core::button::{Button, ButtonVariant};
|
||||||
use gpui_native_core::common::Sizable as _;
|
use gpui_native_core::common::Sizable as _;
|
||||||
|
use gpui_native_core::tooltip::Tooltip;
|
||||||
use gpui_native_icons::{IconRole, icon};
|
use gpui_native_icons::{IconRole, icon};
|
||||||
use gpui_native_theme::{ActiveTheme as _, Size};
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
use gpui_native_utils::adjacent_selectable;
|
use gpui_native_utils::adjacent_selectable;
|
||||||
@@ -277,6 +278,7 @@ fn render_tab(
|
|||||||
|
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let metrics = theme.sizes.resolve(Size::default());
|
let metrics = theme.sizes.resolve(Size::default());
|
||||||
|
let small = theme.sizes.small;
|
||||||
|
|
||||||
let label_color = match variant {
|
let label_color = match variant {
|
||||||
TabsVariant::Underline => {
|
TabsVariant::Underline => {
|
||||||
@@ -319,36 +321,41 @@ fn render_tab(
|
|||||||
.py_1()
|
.py_1()
|
||||||
.rounded_tl(theme.radius.sm)
|
.rounded_tl(theme.radius.sm)
|
||||||
.rounded_tr(theme.radius.sm)
|
.rounded_tr(theme.radius.sm)
|
||||||
|
.border_b_2()
|
||||||
|
.border_color(gpui::transparent_black())
|
||||||
.when(selected, |strip_tab| {
|
.when(selected, |strip_tab| {
|
||||||
strip_tab
|
strip_tab.border_color(theme.ramps.primary.base)
|
||||||
.border_b_2()
|
|
||||||
.border_color(theme.ramps.primary.base)
|
|
||||||
}),
|
}),
|
||||||
TabsVariant::Pill => strip_tab
|
TabsVariant::Pill => strip_tab
|
||||||
.h(metrics.control_height)
|
.h(small.control_height + metrics.plate_inset * 2.0)
|
||||||
.px(metrics.padding_x)
|
.px(metrics.dense_padding_x)
|
||||||
|
.pr(metrics.plate_inset)
|
||||||
.rounded(theme.radius.control)
|
.rounded(theme.radius.control)
|
||||||
.when(selected, |strip_tab| {
|
.when(selected, |strip_tab| {
|
||||||
strip_tab
|
strip_tab
|
||||||
.bg(theme.colors.muted)
|
.bg(theme.colors.muted)
|
||||||
.font_weight(gpui::FontWeight::MEDIUM)
|
.hover(|strip_tab| strip_tab.bg(theme.colors.muted_hover))
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let lead = tab.lead.take();
|
let lead = tab.lead.take();
|
||||||
|
|
||||||
strip_tab = match lead {
|
strip_tab = match lead {
|
||||||
Some(lead) => strip_tab.child(lead),
|
Some(lead) => strip_tab.child(lead),
|
||||||
None => strip_tab.child(
|
None => match variant {
|
||||||
div()
|
TabsVariant::Underline => strip_tab.child(div().flex_none().pb_0p5().when_some(
|
||||||
.flex_none()
|
tab.icon,
|
||||||
.when(variant == TabsVariant::Underline, |slot| slot.pb_0p5())
|
|slot, role| {
|
||||||
.when_some(tab.icon, |slot, role| {
|
|
||||||
slot.child(icon(role).size(metrics.dense_icon_size).color(label_color))
|
slot.child(icon(role).size(metrics.dense_icon_size).color(label_color))
|
||||||
}),
|
},
|
||||||
),
|
)),
|
||||||
|
TabsVariant::Pill => strip_tab.when_some(tab.icon, |strip_tab, role| {
|
||||||
|
strip_tab.child(icon(role).size(metrics.dense_icon_size).color(label_color))
|
||||||
|
}),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
strip_tab = strip_tab.child(strip_tab_label(&tab, selected, variant, cx));
|
strip_tab = strip_tab.child(tab.label.clone());
|
||||||
|
|
||||||
if variant == TabsVariant::Pill {
|
if variant == TabsVariant::Pill {
|
||||||
strip_tab = strip_tab.child(div().flex_none().w(metrics.padding_x * 1.5));
|
strip_tab = strip_tab.child(div().flex_none().w(metrics.padding_x * 1.5));
|
||||||
@@ -389,55 +396,26 @@ fn render_tab_close(
|
|||||||
index: usize,
|
index: usize,
|
||||||
on_close: &Option<CloseHandler>,
|
on_close: &Option<CloseHandler>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
// The wrapper carries the test selector; the button paints itself.
|
Button::new(("tab-close", index))
|
||||||
div()
|
.icon(IconRole::Close)
|
||||||
.id(("tab-close", index))
|
.variant(ButtonVariant::Ghost)
|
||||||
|
.small()
|
||||||
|
.tab_stop(false)
|
||||||
.debug_selector({
|
.debug_selector({
|
||||||
let id = format!("{tabs_id}-close-{}", tab.label);
|
let id = format!("{tabs_id}-close-{}", tab.label);
|
||||||
move || id
|
move || id.clone()
|
||||||
})
|
})
|
||||||
.flex_none()
|
.tooltip(Tooltip::text("Close"))
|
||||||
.child(
|
.accessibility_label(SharedString::from(format!("Close {}", tab.label)))
|
||||||
Button::new(("tab-close-button", index))
|
.on_click({
|
||||||
.icon(IconRole::Close)
|
let on_close = on_close.clone();
|
||||||
.variant(ButtonVariant::Ghost)
|
move |_, window, cx| {
|
||||||
.small()
|
cx.stop_propagation();
|
||||||
.tab_stop(false)
|
if let Some(on_close) = &on_close {
|
||||||
.accessibility_label(SharedString::from(format!("Close {}", tab.label)))
|
on_close(index, window, cx);
|
||||||
.on_click({
|
}
|
||||||
let on_close = on_close.clone();
|
|
||||||
move |_, window, cx| {
|
|
||||||
cx.stop_propagation();
|
|
||||||
if let Some(on_close) = &on_close {
|
|
||||||
on_close(index, window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn strip_tab_label(tab: &Tab, selected: bool, variant: TabsVariant, cx: &App) -> AnyElement {
|
|
||||||
let theme = cx.theme();
|
|
||||||
let color = match variant {
|
|
||||||
TabsVariant::Underline => {
|
|
||||||
if selected {
|
|
||||||
theme.ramps.primary.base
|
|
||||||
} else {
|
|
||||||
theme.colors.foreground
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
TabsVariant::Pill => {
|
|
||||||
if tab.disabled {
|
|
||||||
theme.colors.muted_foreground
|
|
||||||
} else {
|
|
||||||
theme.colors.foreground
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
div()
|
|
||||||
.text_color(color)
|
|
||||||
.child(tab.label.clone())
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+39
-49
@@ -587,7 +587,7 @@ impl DockState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The tree the dock renders from, one frame at a time.
|
/// The tree the dock renders from.
|
||||||
fn snapshot(&self) -> TreeView {
|
fn snapshot(&self) -> TreeView {
|
||||||
fn snap(node: &Node) -> TreeView {
|
fn snap(node: &Node) -> TreeView {
|
||||||
match node {
|
match node {
|
||||||
@@ -843,7 +843,7 @@ fn render_pane(
|
|||||||
content.child(view.cached(StyleRefinement::default().absolute().size_full()))
|
content.child(view.cached(StyleRefinement::default().absolute().size_full()))
|
||||||
})
|
})
|
||||||
.when(cx.has_active_drag(), |content| {
|
.when(cx.has_active_drag(), |content| {
|
||||||
content.child(render_drop_zones(state, pane))
|
content.children(render_drop_zones(state, pane))
|
||||||
});
|
});
|
||||||
|
|
||||||
let menu = ContextMenu::new(SharedString::from(format!(
|
let menu = ContextMenu::new(SharedString::from(format!(
|
||||||
@@ -854,7 +854,6 @@ fn render_pane(
|
|||||||
let dock_id = dock_id.clone();
|
let dock_id = dock_id.clone();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let pane = pane.clone();
|
let pane = pane.clone();
|
||||||
let strip_height = metrics.strip_height;
|
|
||||||
let padding_x = metrics.dense_padding_x;
|
let padding_x = metrics.dense_padding_x;
|
||||||
move |_, _| {
|
move |_, _| {
|
||||||
let selected = pane
|
let selected = pane
|
||||||
@@ -922,15 +921,13 @@ fn render_pane(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chrome-less like the future titlebar: the pills carry the look.
|
// Chrome-less like the future titlebar: the bar wraps the pills.
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.h(strip_height)
|
|
||||||
.w_full()
|
.w_full()
|
||||||
.items_center()
|
.items_center()
|
||||||
.pr(padding_x)
|
.pr(padding_x)
|
||||||
.child(tabs)
|
.child(tabs)
|
||||||
.child(div().flex_1())
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -982,56 +979,49 @@ fn render_pane(
|
|||||||
///
|
///
|
||||||
/// - Four edge bands that split
|
/// - Four edge bands that split
|
||||||
/// - The center that moves the tab in.
|
/// - The center that moves the tab in.
|
||||||
fn render_drop_zones(state: &Entity<DockState>, pane: &PaneView) -> AnyElement {
|
fn render_drop_zones(state: &Entity<DockState>, pane: &PaneView) -> Vec<AnyElement> {
|
||||||
let Some(anchor) = pane
|
let Some(anchor) = pane
|
||||||
.active
|
.active
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| pane.tabs.first().map(|tab| tab.panel.clone()))
|
.or_else(|| pane.tabs.first().map(|tab| tab.panel.clone()))
|
||||||
else {
|
else {
|
||||||
return div().into_any_element();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
div()
|
vec![
|
||||||
.absolute()
|
drop_zone(state, &anchor, Some(SplitDirection::Left))
|
||||||
.inset_0()
|
.absolute()
|
||||||
.child(
|
.top_0()
|
||||||
drop_zone(state, &anchor, Some(SplitDirection::Left))
|
.bottom_0()
|
||||||
.absolute()
|
.left_0()
|
||||||
.top_0()
|
.w(relative(0.25))
|
||||||
.bottom_0()
|
.into_any_element(),
|
||||||
.left_0()
|
drop_zone(state, &anchor, Some(SplitDirection::Right))
|
||||||
.w(relative(0.25)),
|
.absolute()
|
||||||
)
|
.top_0()
|
||||||
.child(
|
.bottom_0()
|
||||||
drop_zone(state, &anchor, Some(SplitDirection::Right))
|
.right_0()
|
||||||
.absolute()
|
.w(relative(0.25))
|
||||||
.top_0()
|
.into_any_element(),
|
||||||
.bottom_0()
|
drop_zone(state, &anchor, Some(SplitDirection::Top))
|
||||||
.right_0()
|
.absolute()
|
||||||
.w(relative(0.25)),
|
.top_0()
|
||||||
)
|
.left(relative(0.25))
|
||||||
.child(
|
.right(relative(0.25))
|
||||||
drop_zone(state, &anchor, Some(SplitDirection::Top))
|
.h(relative(0.25))
|
||||||
.absolute()
|
.into_any_element(),
|
||||||
.top_0()
|
drop_zone(state, &anchor, Some(SplitDirection::Bottom))
|
||||||
.left(relative(0.25))
|
.absolute()
|
||||||
.right(relative(0.25))
|
.bottom_0()
|
||||||
.h(relative(0.25)),
|
.left(relative(0.25))
|
||||||
)
|
.right(relative(0.25))
|
||||||
.child(
|
.h(relative(0.25))
|
||||||
drop_zone(state, &anchor, Some(SplitDirection::Bottom))
|
.into_any_element(),
|
||||||
.absolute()
|
drop_zone(state, &anchor, None)
|
||||||
.bottom_0()
|
.absolute()
|
||||||
.left(relative(0.25))
|
.inset(relative(0.25))
|
||||||
.right(relative(0.25))
|
.into_any_element(),
|
||||||
.h(relative(0.25)),
|
]
|
||||||
)
|
|
||||||
.child(
|
|
||||||
drop_zone(state, &anchor, None)
|
|
||||||
.absolute()
|
|
||||||
.inset(relative(0.25)),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a drop zone for panel dragging.
|
/// Renders a drop zone for panel dragging.
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub struct ColorTokens {
|
|||||||
pub secondary: Hsla,
|
pub secondary: Hsla,
|
||||||
pub secondary_foreground: Hsla,
|
pub secondary_foreground: Hsla,
|
||||||
pub muted: Hsla,
|
pub muted: Hsla,
|
||||||
|
pub muted_hover: Hsla,
|
||||||
pub muted_foreground: Hsla,
|
pub muted_foreground: Hsla,
|
||||||
pub accent: Hsla,
|
pub accent: Hsla,
|
||||||
pub accent_foreground: Hsla,
|
pub accent_foreground: Hsla,
|
||||||
@@ -58,6 +59,7 @@ impl ColorTokens {
|
|||||||
secondary: hex(0xf4f4f5),
|
secondary: hex(0xf4f4f5),
|
||||||
secondary_foreground: hex(0x18181b),
|
secondary_foreground: hex(0x18181b),
|
||||||
muted: hex(0xf4f4f5),
|
muted: hex(0xf4f4f5),
|
||||||
|
muted_hover: hex(0xe4e4e7),
|
||||||
muted_foreground: hex(0x71717a),
|
muted_foreground: hex(0x71717a),
|
||||||
accent: hex(0x2563eb),
|
accent: hex(0x2563eb),
|
||||||
accent_foreground: hex(0xffffff),
|
accent_foreground: hex(0xffffff),
|
||||||
@@ -91,6 +93,7 @@ impl ColorTokens {
|
|||||||
secondary: hex(0x27272a),
|
secondary: hex(0x27272a),
|
||||||
secondary_foreground: hex(0xfafafa),
|
secondary_foreground: hex(0xfafafa),
|
||||||
muted: hex(0x27272a),
|
muted: hex(0x27272a),
|
||||||
|
muted_hover: hex(0x3f3f46),
|
||||||
muted_foreground: hex(0xa1a1aa),
|
muted_foreground: hex(0xa1a1aa),
|
||||||
accent: hex(0x3b82f6),
|
accent: hex(0x3b82f6),
|
||||||
accent_foreground: hex(0xffffff),
|
accent_foreground: hex(0xffffff),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub struct ThemeDefinition {
|
|||||||
pub secondary: Option<String>,
|
pub secondary: Option<String>,
|
||||||
pub secondary_foreground: Option<String>,
|
pub secondary_foreground: Option<String>,
|
||||||
pub muted: Option<String>,
|
pub muted: Option<String>,
|
||||||
|
pub muted_hover: Option<String>,
|
||||||
pub muted_foreground: Option<String>,
|
pub muted_foreground: Option<String>,
|
||||||
pub destructive: Option<String>,
|
pub destructive: Option<String>,
|
||||||
pub destructive_foreground: Option<String>,
|
pub destructive_foreground: Option<String>,
|
||||||
@@ -88,6 +89,10 @@ impl ThemeDefinition {
|
|||||||
parse_hex(self.muted.as_deref()?)
|
parse_hex(self.muted.as_deref()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn muted_hover(&self) -> Option<Hsla> {
|
||||||
|
parse_hex(self.muted_hover.as_deref()?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn muted_foreground(&self) -> Option<Hsla> {
|
pub fn muted_foreground(&self) -> Option<Hsla> {
|
||||||
parse_hex(self.muted_foreground.as_deref()?)
|
parse_hex(self.muted_foreground.as_deref()?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,11 @@ pub(crate) fn resolve(
|
|||||||
base.secondary_foreground,
|
base.secondary_foreground,
|
||||||
),
|
),
|
||||||
muted: pick(def_color(|d| d.muted()), system_palette.muted, base.muted),
|
muted: pick(def_color(|d| d.muted()), system_palette.muted, base.muted),
|
||||||
|
muted_hover: pick(
|
||||||
|
def_color(|d| d.muted_hover()),
|
||||||
|
system_palette.muted_hover,
|
||||||
|
base.muted_hover,
|
||||||
|
),
|
||||||
muted_foreground: pick(
|
muted_foreground: pick(
|
||||||
def_color(|d| d.muted_foreground()),
|
def_color(|d| d.muted_foreground()),
|
||||||
system_palette.muted_foreground,
|
system_palette.muted_foreground,
|
||||||
@@ -601,4 +606,20 @@ mod tests {
|
|||||||
let preset_stated = resolve(&system(ThemeVariant::Dark, palette, None), Some(&def), None);
|
let preset_stated = resolve(&system(ThemeVariant::Dark, palette, None), Some(&def), None);
|
||||||
assert_eq!(preset_stated.colors.info, hsla(0x0ea5e9));
|
assert_eq!(preset_stated.colors.info, hsla(0x0ea5e9));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn muted_hover_follows_the_overlay_chain() {
|
||||||
|
// No platform states a hover wash; a preset or user file is the only
|
||||||
|
// override above the neutral base.
|
||||||
|
let palette = PartialPalette {
|
||||||
|
muted_hover: Some(hsla(0x155e75)),
|
||||||
|
..PartialPalette::default()
|
||||||
|
};
|
||||||
|
let stated = resolve(&system(ThemeVariant::Dark, palette, None), None, None);
|
||||||
|
assert_eq!(stated.colors.muted_hover, hsla(0x155e75));
|
||||||
|
|
||||||
|
let def = ThemeDefinition::parse_toml("muted_hover = \"#0ea5e9\"\n").unwrap();
|
||||||
|
let preset_stated = resolve(&system(ThemeVariant::Dark, palette, None), Some(&def), None);
|
||||||
|
assert_eq!(preset_stated.colors.muted_hover, hsla(0x0ea5e9));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ impl MacosSource {
|
|||||||
secondary: None,
|
secondary: None,
|
||||||
secondary_foreground: Self::color_to_hsla(&NSColor::secondaryLabelColor()),
|
secondary_foreground: Self::color_to_hsla(&NSColor::secondaryLabelColor()),
|
||||||
muted: None,
|
muted: None,
|
||||||
|
muted_hover: None,
|
||||||
muted_foreground: Self::color_to_hsla(&NSColor::secondaryLabelColor()),
|
muted_foreground: Self::color_to_hsla(&NSColor::secondaryLabelColor()),
|
||||||
destructive: Self::color_to_hsla(&NSColor::systemRedColor()),
|
destructive: Self::color_to_hsla(&NSColor::systemRedColor()),
|
||||||
success: None,
|
success: None,
|
||||||
|
|||||||
@@ -50,9 +50,11 @@ pub struct PartialPalette {
|
|||||||
pub secondary: Option<Hsla>,
|
pub secondary: Option<Hsla>,
|
||||||
pub secondary_foreground: Option<Hsla>,
|
pub secondary_foreground: Option<Hsla>,
|
||||||
pub muted: Option<Hsla>,
|
pub muted: Option<Hsla>,
|
||||||
|
/// No platform states a hover wash, presets override it.
|
||||||
|
pub muted_hover: Option<Hsla>,
|
||||||
pub muted_foreground: Option<Hsla>,
|
pub muted_foreground: Option<Hsla>,
|
||||||
pub destructive: Option<Hsla>,
|
pub destructive: Option<Hsla>,
|
||||||
/// No platform states severity colors; presets override them.
|
/// No platform states severity colors, presets override them.
|
||||||
pub success: Option<Hsla>,
|
pub success: Option<Hsla>,
|
||||||
pub warning: Option<Hsla>,
|
pub warning: Option<Hsla>,
|
||||||
pub info: Option<Hsla>,
|
pub info: Option<Hsla>,
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ The set stays role-minimal and grows one concept at a time, driven by the unit t
|
|||||||
- ~~**Scrollbar thumb token + metrics**~~ — ✅ landed with the ScrollArea unit (2026-10-29): `colors.scrollbar_thumb` (translucent over content: black in light, light gray in dark; TOML-overridable) and `Theme.scrollbar` (`width` 8 pt, `inset` 2 pt, `min_thumb` 24 pt).
|
- ~~**Scrollbar thumb token + metrics**~~ — ✅ landed with the ScrollArea unit (2026-10-29): `colors.scrollbar_thumb` (translucent over content: black in light, light gray in dark; TOML-overridable) and `Theme.scrollbar` (`width` 8 pt, `inset` 2 pt, `min_thumb` 24 pt).
|
||||||
- ~~**`success` / `warning` roles**~~ — ✅ landed with Toast (2026-10-29): `colors.success`/`warning` (+ foregrounds; light and dark variants; TOML-overridable; no platform states them) and derived `Ramps.success`/`warning` via the OKLCH engine, matching the destructive treatment. **`info` joined them (2026-10-31)** — the fourth severity, same treatment (`colors.info` + foreground + `Ramps.info`, neutral sky blue, no platform states it, TOML `info =`). Trigger: the Alert's Info severity was painting from the primary ramp, so a warm platform accent (GNOME red, Omarchy red themes) made Info read as Danger; the token keeps Info calm however the accent turns out. Severity tokens now use the same picked value for the ramp and the token (the success/warning tokens used to re-pick with the system slot dropped, diverging from their own ramps if a platform ever stated one).
|
- ~~**`success` / `warning` roles**~~ — ✅ landed with Toast (2026-10-29): `colors.success`/`warning` (+ foregrounds; light and dark variants; TOML-overridable; no platform states them) and derived `Ramps.success`/`warning` via the OKLCH engine, matching the destructive treatment. **`info` joined them (2026-10-31)** — the fourth severity, same treatment (`colors.info` + foreground + `Ramps.info`, neutral sky blue, no platform states it, TOML `info =`). Trigger: the Alert's Info severity was painting from the primary ramp, so a warm platform accent (GNOME red, Omarchy red themes) made Info read as Danger; the token keeps Info calm however the accent turns out. Severity tokens now use the same picked value for the ramp and the token (the success/warning tokens used to re-pick with the system slot dropped, diverging from their own ramps if a platform ever stated one).
|
||||||
- **Motion tokens** — after the reverted framework attempt (2026-10-30), motion stays minimal: `Motion.overlay` (200 ms) is the only token. Components reach for gpui's own easings and `SpringConfig` inline (D21); a token is added only when a third component shares the same value (D16). TOML `[motion]` overrides and per-platform motion presets wait for a real need. ~~The reduce-motion forwarding fix~~ ✅ landed (pass a, 2026-10-30): `reapply` calls `cx.set_reduce_motion(resolved.accessibility.reduce_motion)`, so gpui's animation elements snap under the OS setting.
|
- **Motion tokens** — after the reverted framework attempt (2026-10-30), motion stays minimal: `Motion.overlay` (200 ms) is the only token. Components reach for gpui's own easings and `SpringConfig` inline (D21); a token is added only when a third component shares the same value (D16). TOML `[motion]` overrides and per-platform motion presets wait for a real need. ~~The reduce-motion forwarding fix~~ ✅ landed (pass a, 2026-10-30): `reapply` calls `cx.set_reduce_motion(resolved.accessibility.reduce_motion)`, so gpui's animation elements snap under the OS setting.
|
||||||
|
- ~~**Ghost hover token**~~ — ✅ landed with the dock tabs (2026-11-02): `colors.muted_hover` (light zinc-200 / dark zinc-700, one step past `muted`; TOML `muted_hover =`; no platform states a hover wash). Trigger: the ghost Button's hover painted `muted` — the same token the selected pill fills with — so hovering a close control on a selected tab was invisible. `Button::Ghost` and the selected pill hover from `muted_hover` now.
|
||||||
- ~~**`knob` / control-fill role**~~ — resolved without a token: Slider (unit 7) shipped on the Switch's borrowed roles (`primary_foreground` fill, `border` off-track); a dedicated role waits for a third consumer.
|
- ~~**`knob` / control-fill role**~~ — resolved without a token: Slider (unit 7) shipped on the Switch's borrowed roles (`primary_foreground` fill, `border` off-track); a dedicated role waits for a third consumer.
|
||||||
|
|
||||||
Controls through unit 10 paint from existing roles and ramps.
|
Controls through unit 10 paint from existing roles and ramps.
|
||||||
|
|||||||
Reference in New Issue
Block a user