remove the multi-theme system
This commit is contained in:
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "aurora",
|
|
||||||
"name": "Aurora",
|
|
||||||
"author": "Coop",
|
|
||||||
"url": "https://coopchat.xyz",
|
|
||||||
"light": {
|
|
||||||
"background": "#fdfcfeff",
|
|
||||||
"surface_background": "#f8f8ffff",
|
|
||||||
"elevated_surface_background": "#f0f1feff",
|
|
||||||
"panel_background": "#fdfcfeff",
|
|
||||||
"overlay": "#211f4300",
|
|
||||||
"title_bar": "#f0f1feff",
|
|
||||||
"title_bar_inactive": "#fdfcfeff",
|
|
||||||
"window_border": "#dadcffff",
|
|
||||||
"border": "#dadcffff",
|
|
||||||
"border_variant": "#cbcdffff",
|
|
||||||
"border_focused": "#5b5bd6ff",
|
|
||||||
"border_selected": "#5b5bd6ff",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#e6e7ffff",
|
|
||||||
"ring": "#5151cdff",
|
|
||||||
"text": "#1f2d5cff",
|
|
||||||
"text_muted": "#5753c6ff",
|
|
||||||
"text_placeholder": "#9b9ef0ff",
|
|
||||||
"text_accent": "#5b5bd6ff",
|
|
||||||
"text_danger": "#e54d2eff",
|
|
||||||
"text_warning": "#f76b15ff",
|
|
||||||
"icon": "#5753c6ff",
|
|
||||||
"icon_muted": "#9b9ef0ff",
|
|
||||||
"icon_accent": "#5151cdff",
|
|
||||||
"element_foreground": "#ffffffff",
|
|
||||||
"element_background": "#5b5bd6ff",
|
|
||||||
"element_hover": "#5151cdff",
|
|
||||||
"element_active": "#6e56cfff",
|
|
||||||
"element_selected": "#654dc4ff",
|
|
||||||
"element_disabled": "#5b5bd64d",
|
|
||||||
"secondary_foreground": "#1f2d5cff",
|
|
||||||
"secondary_background": "#f0f1feff",
|
|
||||||
"secondary_hover": "#e6e7ffff",
|
|
||||||
"secondary_active": "#dadcffff",
|
|
||||||
"secondary_selected": "#dadcffff",
|
|
||||||
"secondary_disabled": "#5b5bd64d",
|
|
||||||
"danger_foreground": "#ffffffff",
|
|
||||||
"danger_background": "#feebe7ff",
|
|
||||||
"danger_hover": "#ffcdc2ff",
|
|
||||||
"danger_active": "#fdbdafff",
|
|
||||||
"danger_selected": "#fdbdafff",
|
|
||||||
"danger_disabled": "#e54d2e4d",
|
|
||||||
"warning_foreground": "#ffffffff",
|
|
||||||
"warning_background": "#fff7edff",
|
|
||||||
"warning_hover": "#ffd19aff",
|
|
||||||
"warning_active": "#ffc182ff",
|
|
||||||
"warning_selected": "#ffc182ff",
|
|
||||||
"warning_disabled": "#f76b154d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#f0f1feff",
|
|
||||||
"ghost_element_hover": "#211f430d",
|
|
||||||
"ghost_element_active": "#211f431a",
|
|
||||||
"ghost_element_selected": "#211f431a",
|
|
||||||
"ghost_element_disabled": "#211f4305",
|
|
||||||
"tab_background": "#f0f1feff",
|
|
||||||
"tab_foreground": "#5753c6ff",
|
|
||||||
"tab_hover_background": "#211f430d",
|
|
||||||
"tab_active_background": "#fdfcfeff",
|
|
||||||
"tab_active_foreground": "#1f2d5cff",
|
|
||||||
"scrollbar_thumb_background": "#211f431a",
|
|
||||||
"scrollbar_thumb_hover_background": "#211f4326",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#00000000",
|
|
||||||
"drop_target_background": "#5b5bd61a",
|
|
||||||
"cursor": "#5b5bd6ff",
|
|
||||||
"selection": "#5b5bd640"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#14121fff",
|
|
||||||
"surface_background": "#1b1525ff",
|
|
||||||
"elevated_surface_background": "#291f43ff",
|
|
||||||
"panel_background": "#14121fff",
|
|
||||||
"overlay": "#baa7ff1a",
|
|
||||||
"title_bar": "#291f43ff",
|
|
||||||
"title_bar_inactive": "#14121fff",
|
|
||||||
"window_border": "#473876ff",
|
|
||||||
"border": "#473876ff",
|
|
||||||
"border_variant": "#3c2e69ff",
|
|
||||||
"border_focused": "#7d66d9ff",
|
|
||||||
"border_selected": "#7d66d9ff",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#33255bff",
|
|
||||||
"ring": "#6e56cfff",
|
|
||||||
"text": "#e2ddfeff",
|
|
||||||
"text_muted": "#baa7ffff",
|
|
||||||
"text_placeholder": "#6958adff",
|
|
||||||
"text_accent": "#baa7ffff",
|
|
||||||
"text_danger": "#ff977dff",
|
|
||||||
"text_warning": "#ffa057ff",
|
|
||||||
"icon": "#baa7ffff",
|
|
||||||
"icon_muted": "#6958adff",
|
|
||||||
"icon_accent": "#6e56cfff",
|
|
||||||
"element_foreground": "#14121fff",
|
|
||||||
"element_background": "#7d66d9ff",
|
|
||||||
"element_hover": "#baa7ffff",
|
|
||||||
"element_active": "#6e56cfff",
|
|
||||||
"element_selected": "#654dc4ff",
|
|
||||||
"element_disabled": "#7d66d94d",
|
|
||||||
"secondary_foreground": "#e2ddfeff",
|
|
||||||
"secondary_background": "#291f43ff",
|
|
||||||
"secondary_hover": "#33255bff",
|
|
||||||
"secondary_active": "#3c2e69ff",
|
|
||||||
"secondary_selected": "#3c2e69ff",
|
|
||||||
"secondary_disabled": "#7d66d94d",
|
|
||||||
"danger_foreground": "#181111ff",
|
|
||||||
"danger_background": "#391714ff",
|
|
||||||
"danger_hover": "#5e1c16ff",
|
|
||||||
"danger_active": "#6e2920ff",
|
|
||||||
"danger_selected": "#6e2920ff",
|
|
||||||
"danger_disabled": "#ff977d4d",
|
|
||||||
"warning_foreground": "#17120eff",
|
|
||||||
"warning_background": "#331e0bff",
|
|
||||||
"warning_hover": "#562800ff",
|
|
||||||
"warning_active": "#66350cff",
|
|
||||||
"warning_selected": "#66350cff",
|
|
||||||
"warning_disabled": "#ffa0574d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#291f43ff",
|
|
||||||
"ghost_element_hover": "#baa7ff0d",
|
|
||||||
"ghost_element_active": "#baa7ff1a",
|
|
||||||
"ghost_element_selected": "#baa7ff1a",
|
|
||||||
"ghost_element_disabled": "#baa7ff05",
|
|
||||||
"tab_background": "#291f43ff",
|
|
||||||
"tab_foreground": "#baa7ffff",
|
|
||||||
"tab_hover_background": "#baa7ff0d",
|
|
||||||
"tab_active_background": "#14121fff",
|
|
||||||
"tab_active_foreground": "#e2ddfeff",
|
|
||||||
"scrollbar_thumb_background": "#baa7ff1a",
|
|
||||||
"scrollbar_thumb_hover_background": "#baa7ff26",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#00000000",
|
|
||||||
"drop_target_background": "#baa7ff1a",
|
|
||||||
"cursor": "#baa7ffff",
|
|
||||||
"selection": "#baa7ff40"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "catppuccin-frappe",
|
|
||||||
"name": "Catppuccin Frappé",
|
|
||||||
"author": "Catppuccin Org (ported by Coop)",
|
|
||||||
"url": "https://catppuccin.com",
|
|
||||||
"light": {
|
|
||||||
"background": "#303446",
|
|
||||||
"surface_background": "#292c3c",
|
|
||||||
"elevated_surface_background": "#232634",
|
|
||||||
"panel_background": "#303446",
|
|
||||||
"overlay": "#c6d0f51a",
|
|
||||||
"title_bar": "#232634",
|
|
||||||
"title_bar_inactive": "#303446",
|
|
||||||
"window_border": "#51576d",
|
|
||||||
"border": "#51576d",
|
|
||||||
"border_variant": "#414559",
|
|
||||||
"border_focused": "#8caaee",
|
|
||||||
"border_selected": "#8caaee",
|
|
||||||
"border_transparent": "#c6d0f500",
|
|
||||||
"border_disabled": "#292c3c",
|
|
||||||
"ring": "#babbf1",
|
|
||||||
"text": "#c6d0f5",
|
|
||||||
"text_muted": "#a5adce",
|
|
||||||
"text_placeholder": "#838ba7",
|
|
||||||
"text_accent": "#8caaee",
|
|
||||||
"text_danger": "#e78284",
|
|
||||||
"text_warning": "#ef9f76",
|
|
||||||
"icon": "#a5adce",
|
|
||||||
"icon_muted": "#838ba7",
|
|
||||||
"icon_accent": "#babbf1",
|
|
||||||
"element_foreground": "#303446",
|
|
||||||
"element_background": "#8caaee",
|
|
||||||
"element_hover": "#babbf1",
|
|
||||||
"element_active": "#99d1db",
|
|
||||||
"element_selected": "#85c1dc",
|
|
||||||
"element_disabled": "#8caaee4d",
|
|
||||||
"secondary_foreground": "#c6d0f5",
|
|
||||||
"secondary_background": "#414559",
|
|
||||||
"secondary_hover": "#51576d",
|
|
||||||
"secondary_active": "#626880",
|
|
||||||
"secondary_selected": "#626880",
|
|
||||||
"secondary_disabled": "#8caaee4d",
|
|
||||||
"danger_foreground": "#303446",
|
|
||||||
"danger_background": "#e78284",
|
|
||||||
"danger_hover": "#ea999c",
|
|
||||||
"danger_active": "#ef9f76",
|
|
||||||
"danger_selected": "#e5c890",
|
|
||||||
"danger_disabled": "#e782844d",
|
|
||||||
"warning_foreground": "#303446",
|
|
||||||
"warning_background": "#ef9f76",
|
|
||||||
"warning_hover": "#e5c890",
|
|
||||||
"warning_active": "#a6d189",
|
|
||||||
"warning_selected": "#81c8be",
|
|
||||||
"warning_disabled": "#ef9f764d",
|
|
||||||
"ghost_element_background": "#c6d0f500",
|
|
||||||
"ghost_element_background_alt": "#292c3c",
|
|
||||||
"ghost_element_hover": "#c6d0f50d",
|
|
||||||
"ghost_element_active": "#c6d0f51a",
|
|
||||||
"ghost_element_selected": "#c6d0f51a",
|
|
||||||
"ghost_element_disabled": "#c6d0f505",
|
|
||||||
"tab_background": "#232634",
|
|
||||||
"tab_foreground": "#a5adce",
|
|
||||||
"tab_hover_background": "#c6d0f50d",
|
|
||||||
"tab_active_background": "#303446",
|
|
||||||
"tab_active_foreground": "#c6d0f5",
|
|
||||||
"scrollbar_thumb_background": "#c6d0f51a",
|
|
||||||
"scrollbar_thumb_hover_background": "#c6d0f526",
|
|
||||||
"scrollbar_thumb_border": "#c6d0f500",
|
|
||||||
"scrollbar_track_background": "#c6d0f500",
|
|
||||||
"scrollbar_track_border": "#c6d0f500",
|
|
||||||
"drop_target_background": "#8caaee1a",
|
|
||||||
"cursor": "#8caaee",
|
|
||||||
"selection": "#8caaee40"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#303446",
|
|
||||||
"surface_background": "#292c3c",
|
|
||||||
"elevated_surface_background": "#232634",
|
|
||||||
"panel_background": "#303446",
|
|
||||||
"overlay": "#c6d0f51a",
|
|
||||||
"title_bar": "#232634",
|
|
||||||
"title_bar_inactive": "#303446",
|
|
||||||
"window_border": "#51576d",
|
|
||||||
"border": "#51576d",
|
|
||||||
"border_variant": "#414559",
|
|
||||||
"border_focused": "#8caaee",
|
|
||||||
"border_selected": "#8caaee",
|
|
||||||
"border_transparent": "#c6d0f500",
|
|
||||||
"border_disabled": "#292c3c",
|
|
||||||
"ring": "#babbf1",
|
|
||||||
"text": "#c6d0f5",
|
|
||||||
"text_muted": "#a5adce",
|
|
||||||
"text_placeholder": "#838ba7",
|
|
||||||
"text_accent": "#8caaee",
|
|
||||||
"text_danger": "#e78284",
|
|
||||||
"text_warning": "#ef9f76",
|
|
||||||
"icon": "#a5adce",
|
|
||||||
"icon_muted": "#838ba7",
|
|
||||||
"icon_accent": "#babbf1",
|
|
||||||
"element_foreground": "#303446",
|
|
||||||
"element_background": "#8caaee",
|
|
||||||
"element_hover": "#babbf1",
|
|
||||||
"element_active": "#99d1db",
|
|
||||||
"element_selected": "#85c1dc",
|
|
||||||
"element_disabled": "#8caaee4d",
|
|
||||||
"secondary_foreground": "#c6d0f5",
|
|
||||||
"secondary_background": "#414559",
|
|
||||||
"secondary_hover": "#51576d",
|
|
||||||
"secondary_active": "#626880",
|
|
||||||
"secondary_selected": "#626880",
|
|
||||||
"secondary_disabled": "#8caaee4d",
|
|
||||||
"danger_foreground": "#303446",
|
|
||||||
"danger_background": "#e78284",
|
|
||||||
"danger_hover": "#ea999c",
|
|
||||||
"danger_active": "#ef9f76",
|
|
||||||
"danger_selected": "#e5c890",
|
|
||||||
"danger_disabled": "#e782844d",
|
|
||||||
"warning_foreground": "#303446",
|
|
||||||
"warning_background": "#ef9f76",
|
|
||||||
"warning_hover": "#e5c890",
|
|
||||||
"warning_active": "#a6d189",
|
|
||||||
"warning_selected": "#81c8be",
|
|
||||||
"warning_disabled": "#ef9f764d",
|
|
||||||
"ghost_element_background": "#c6d0f500",
|
|
||||||
"ghost_element_background_alt": "#292c3c",
|
|
||||||
"ghost_element_hover": "#c6d0f50d",
|
|
||||||
"ghost_element_active": "#c6d0f51a",
|
|
||||||
"ghost_element_selected": "#c6d0f51a",
|
|
||||||
"ghost_element_disabled": "#c6d0f505",
|
|
||||||
"tab_background": "#232634",
|
|
||||||
"tab_foreground": "#a5adce",
|
|
||||||
"tab_hover_background": "#c6d0f50d",
|
|
||||||
"tab_active_background": "#303446",
|
|
||||||
"tab_active_foreground": "#c6d0f5",
|
|
||||||
"scrollbar_thumb_background": "#c6d0f51a",
|
|
||||||
"scrollbar_thumb_hover_background": "#c6d0f526",
|
|
||||||
"scrollbar_thumb_border": "#c6d0f500",
|
|
||||||
"scrollbar_track_background": "#c6d0f500",
|
|
||||||
"scrollbar_track_border": "#c6d0f500",
|
|
||||||
"drop_target_background": "#8caaee1a",
|
|
||||||
"cursor": "#8caaee",
|
|
||||||
"selection": "#8caaee40"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "catppuccin-latte",
|
|
||||||
"name": "Catppuccin Latte",
|
|
||||||
"author": "Catppuccin Org (ported by Coop)",
|
|
||||||
"url": "https://catppuccin.com",
|
|
||||||
"light": {
|
|
||||||
"background": "#eff1f5",
|
|
||||||
"surface_background": "#e6e9ef",
|
|
||||||
"elevated_surface_background": "#dce0e8",
|
|
||||||
"panel_background": "#eff1f5",
|
|
||||||
"overlay": "#4c4f691a",
|
|
||||||
"title_bar": "#dce0e8",
|
|
||||||
"title_bar_inactive": "#eff1f5",
|
|
||||||
"window_border": "#bcc0cc",
|
|
||||||
"border": "#bcc0cc",
|
|
||||||
"border_variant": "#ccd0da",
|
|
||||||
"border_focused": "#1e66f5",
|
|
||||||
"border_selected": "#1e66f5",
|
|
||||||
"border_transparent": "#4c4f6900",
|
|
||||||
"border_disabled": "#e6e9ef",
|
|
||||||
"ring": "#7287fd",
|
|
||||||
"text": "#4c4f69",
|
|
||||||
"text_muted": "#6c6f85",
|
|
||||||
"text_placeholder": "#8c8fa1",
|
|
||||||
"text_accent": "#1e66f5",
|
|
||||||
"text_danger": "#d20f39",
|
|
||||||
"text_warning": "#fe640b",
|
|
||||||
"icon": "#6c6f85",
|
|
||||||
"icon_muted": "#8c8fa1",
|
|
||||||
"icon_accent": "#7287fd",
|
|
||||||
"element_foreground": "#eff1f5",
|
|
||||||
"element_background": "#1e66f5",
|
|
||||||
"element_hover": "#7287fd",
|
|
||||||
"element_active": "#04a5e5",
|
|
||||||
"element_selected": "#209fb5",
|
|
||||||
"element_disabled": "#1e66f54d",
|
|
||||||
"secondary_foreground": "#4c4f69",
|
|
||||||
"secondary_background": "#ccd0da",
|
|
||||||
"secondary_hover": "#bcc0cc",
|
|
||||||
"secondary_active": "#acb0be",
|
|
||||||
"secondary_selected": "#acb0be",
|
|
||||||
"secondary_disabled": "#1e66f54d",
|
|
||||||
"danger_foreground": "#eff1f5",
|
|
||||||
"danger_background": "#d20f39",
|
|
||||||
"danger_hover": "#e64553",
|
|
||||||
"danger_active": "#fe640b",
|
|
||||||
"danger_selected": "#df8e1d",
|
|
||||||
"danger_disabled": "#d20f394d",
|
|
||||||
"warning_foreground": "#eff1f5",
|
|
||||||
"warning_background": "#fe640b",
|
|
||||||
"warning_hover": "#df8e1d",
|
|
||||||
"warning_active": "#40a02b",
|
|
||||||
"warning_selected": "#179299",
|
|
||||||
"warning_disabled": "#fe640b4d",
|
|
||||||
"ghost_element_background": "#4c4f6900",
|
|
||||||
"ghost_element_background_alt": "#e6e9ef",
|
|
||||||
"ghost_element_hover": "#4c4f690d",
|
|
||||||
"ghost_element_active": "#4c4f691a",
|
|
||||||
"ghost_element_selected": "#4c4f691a",
|
|
||||||
"ghost_element_disabled": "#4c4f6905",
|
|
||||||
"tab_background": "#e6e9ef",
|
|
||||||
"tab_foreground": "#6c6f85",
|
|
||||||
"tab_hover_background": "#4c4f690d",
|
|
||||||
"tab_active_background": "#eff1f5",
|
|
||||||
"tab_active_foreground": "#4c4f69",
|
|
||||||
"scrollbar_thumb_background": "#4c4f691a",
|
|
||||||
"scrollbar_thumb_hover_background": "#4c4f6926",
|
|
||||||
"scrollbar_thumb_border": "#4c4f6900",
|
|
||||||
"scrollbar_track_background": "#4c4f6900",
|
|
||||||
"scrollbar_track_border": "#4c4f6900",
|
|
||||||
"drop_target_background": "#1e66f51a",
|
|
||||||
"cursor": "#1e66f5",
|
|
||||||
"selection": "#1e66f540"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#eff1f5",
|
|
||||||
"surface_background": "#e6e9ef",
|
|
||||||
"elevated_surface_background": "#dce0e8",
|
|
||||||
"panel_background": "#eff1f5",
|
|
||||||
"overlay": "#4c4f691a",
|
|
||||||
"title_bar": "#dce0e8",
|
|
||||||
"title_bar_inactive": "#eff1f5",
|
|
||||||
"window_border": "#bcc0cc",
|
|
||||||
"border": "#bcc0cc",
|
|
||||||
"border_variant": "#ccd0da",
|
|
||||||
"border_focused": "#1e66f5",
|
|
||||||
"border_selected": "#1e66f5",
|
|
||||||
"border_transparent": "#4c4f6900",
|
|
||||||
"border_disabled": "#e6e9ef",
|
|
||||||
"ring": "#7287fd",
|
|
||||||
"text": "#4c4f69",
|
|
||||||
"text_muted": "#6c6f85",
|
|
||||||
"text_placeholder": "#8c8fa1",
|
|
||||||
"text_accent": "#1e66f5",
|
|
||||||
"text_danger": "#d20f39",
|
|
||||||
"text_warning": "#fe640b",
|
|
||||||
"icon": "#6c6f85",
|
|
||||||
"icon_muted": "#8c8fa1",
|
|
||||||
"icon_accent": "#7287fd",
|
|
||||||
"element_foreground": "#eff1f5",
|
|
||||||
"element_background": "#1e66f5",
|
|
||||||
"element_hover": "#7287fd",
|
|
||||||
"element_active": "#04a5e5",
|
|
||||||
"element_selected": "#209fb5",
|
|
||||||
"element_disabled": "#1e66f54d",
|
|
||||||
"secondary_foreground": "#4c4f69",
|
|
||||||
"secondary_background": "#ccd0da",
|
|
||||||
"secondary_hover": "#bcc0cc",
|
|
||||||
"secondary_active": "#acb0be",
|
|
||||||
"secondary_selected": "#acb0be",
|
|
||||||
"secondary_disabled": "#1e66f54d",
|
|
||||||
"danger_foreground": "#eff1f5",
|
|
||||||
"danger_background": "#d20f39",
|
|
||||||
"danger_hover": "#e64553",
|
|
||||||
"danger_active": "#fe640b",
|
|
||||||
"danger_selected": "#df8e1d",
|
|
||||||
"danger_disabled": "#d20f394d",
|
|
||||||
"warning_foreground": "#eff1f5",
|
|
||||||
"warning_background": "#fe640b",
|
|
||||||
"warning_hover": "#df8e1d",
|
|
||||||
"warning_active": "#40a02b",
|
|
||||||
"warning_selected": "#179299",
|
|
||||||
"warning_disabled": "#fe640b4d",
|
|
||||||
"ghost_element_background": "#4c4f6900",
|
|
||||||
"ghost_element_background_alt": "#e6e9ef",
|
|
||||||
"ghost_element_hover": "#4c4f690d",
|
|
||||||
"ghost_element_active": "#4c4f691a",
|
|
||||||
"ghost_element_selected": "#4c4f691a",
|
|
||||||
"ghost_element_disabled": "#4c4f6905",
|
|
||||||
"tab_background": "#e6e9ef",
|
|
||||||
"tab_foreground": "#6c6f85",
|
|
||||||
"tab_hover_background": "#4c4f690d",
|
|
||||||
"tab_active_background": "#eff1f5",
|
|
||||||
"tab_active_foreground": "#4c4f69",
|
|
||||||
"scrollbar_thumb_background": "#4c4f691a",
|
|
||||||
"scrollbar_thumb_hover_background": "#4c4f6926",
|
|
||||||
"scrollbar_thumb_border": "#4c4f6900",
|
|
||||||
"scrollbar_track_background": "#4c4f6900",
|
|
||||||
"scrollbar_track_border": "#4c4f6900",
|
|
||||||
"drop_target_background": "#1e66f51a",
|
|
||||||
"cursor": "#1e66f5",
|
|
||||||
"selection": "#1e66f540"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "catppuccin-macchiato",
|
|
||||||
"name": "Catppuccin Macchiato",
|
|
||||||
"author": "Catppuccin Org (ported by Coop)",
|
|
||||||
"url": "https://catppuccin.com",
|
|
||||||
"light": {
|
|
||||||
"background": "#24273a",
|
|
||||||
"surface_background": "#1e2030",
|
|
||||||
"elevated_surface_background": "#181926",
|
|
||||||
"panel_background": "#24273a",
|
|
||||||
"overlay": "#cad3f51a",
|
|
||||||
"title_bar": "#181926",
|
|
||||||
"title_bar_inactive": "#24273a",
|
|
||||||
"window_border": "#494d64",
|
|
||||||
"border": "#494d64",
|
|
||||||
"border_variant": "#363a4f",
|
|
||||||
"border_focused": "#8aadf4",
|
|
||||||
"border_selected": "#8aadf4",
|
|
||||||
"border_transparent": "#cad3f500",
|
|
||||||
"border_disabled": "#1e2030",
|
|
||||||
"ring": "#b7bdf8",
|
|
||||||
"text": "#cad3f5",
|
|
||||||
"text_muted": "#a5adcb",
|
|
||||||
"text_placeholder": "#8087a2",
|
|
||||||
"text_accent": "#8aadf4",
|
|
||||||
"text_danger": "#ed8796",
|
|
||||||
"text_warning": "#f5a97f",
|
|
||||||
"icon": "#a5adcb",
|
|
||||||
"icon_muted": "#8087a2",
|
|
||||||
"icon_accent": "#b7bdf8",
|
|
||||||
"element_foreground": "#24273a",
|
|
||||||
"element_background": "#8aadf4",
|
|
||||||
"element_hover": "#b7bdf8",
|
|
||||||
"element_active": "#91d7e3",
|
|
||||||
"element_selected": "#7dc4e4",
|
|
||||||
"element_disabled": "#8aadf44d",
|
|
||||||
"secondary_foreground": "#cad3f5",
|
|
||||||
"secondary_background": "#363a4f",
|
|
||||||
"secondary_hover": "#494d64",
|
|
||||||
"secondary_active": "#5b6078",
|
|
||||||
"secondary_selected": "#5b6078",
|
|
||||||
"secondary_disabled": "#8aadf44d",
|
|
||||||
"danger_foreground": "#24273a",
|
|
||||||
"danger_background": "#ed8796",
|
|
||||||
"danger_hover": "#ee99a0",
|
|
||||||
"danger_active": "#f5a97f",
|
|
||||||
"danger_selected": "#eed49f",
|
|
||||||
"danger_disabled": "#ed87964d",
|
|
||||||
"warning_foreground": "#24273a",
|
|
||||||
"warning_background": "#f5a97f",
|
|
||||||
"warning_hover": "#eed49f",
|
|
||||||
"warning_active": "#a6da95",
|
|
||||||
"warning_selected": "#8bd5ca",
|
|
||||||
"warning_disabled": "#f5a97f4d",
|
|
||||||
"ghost_element_background": "#cad3f500",
|
|
||||||
"ghost_element_background_alt": "#1e2030",
|
|
||||||
"ghost_element_hover": "#cad3f50d",
|
|
||||||
"ghost_element_active": "#cad3f51a",
|
|
||||||
"ghost_element_selected": "#cad3f51a",
|
|
||||||
"ghost_element_disabled": "#cad3f505",
|
|
||||||
"tab_background": "#181926",
|
|
||||||
"tab_foreground": "#a5adcb",
|
|
||||||
"tab_hover_background": "#cad3f50d",
|
|
||||||
"tab_active_background": "#24273a",
|
|
||||||
"tab_active_foreground": "#cad3f5",
|
|
||||||
"scrollbar_thumb_background": "#cad3f51a",
|
|
||||||
"scrollbar_thumb_hover_background": "#cad3f526",
|
|
||||||
"scrollbar_thumb_border": "#cad3f500",
|
|
||||||
"scrollbar_track_background": "#cad3f500",
|
|
||||||
"scrollbar_track_border": "#cad3f500",
|
|
||||||
"drop_target_background": "#8aadf41a",
|
|
||||||
"cursor": "#8aadf4",
|
|
||||||
"selection": "#8aadf440"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#24273a",
|
|
||||||
"surface_background": "#1e2030",
|
|
||||||
"elevated_surface_background": "#181926",
|
|
||||||
"panel_background": "#24273a",
|
|
||||||
"overlay": "#cad3f51a",
|
|
||||||
"title_bar": "#181926",
|
|
||||||
"title_bar_inactive": "#24273a",
|
|
||||||
"window_border": "#494d64",
|
|
||||||
"border": "#494d64",
|
|
||||||
"border_variant": "#363a4f",
|
|
||||||
"border_focused": "#8aadf4",
|
|
||||||
"border_selected": "#8aadf4",
|
|
||||||
"border_transparent": "#cad3f500",
|
|
||||||
"border_disabled": "#1e2030",
|
|
||||||
"ring": "#b7bdf8",
|
|
||||||
"text": "#cad3f5",
|
|
||||||
"text_muted": "#a5adcb",
|
|
||||||
"text_placeholder": "#8087a2",
|
|
||||||
"text_accent": "#8aadf4",
|
|
||||||
"text_danger": "#ed8796",
|
|
||||||
"text_warning": "#f5a97f",
|
|
||||||
"icon": "#a5adcb",
|
|
||||||
"icon_muted": "#8087a2",
|
|
||||||
"icon_accent": "#b7bdf8",
|
|
||||||
"element_foreground": "#24273a",
|
|
||||||
"element_background": "#8aadf4",
|
|
||||||
"element_hover": "#b7bdf8",
|
|
||||||
"element_active": "#91d7e3",
|
|
||||||
"element_selected": "#7dc4e4",
|
|
||||||
"element_disabled": "#8aadf44d",
|
|
||||||
"secondary_foreground": "#cad3f5",
|
|
||||||
"secondary_background": "#363a4f",
|
|
||||||
"secondary_hover": "#494d64",
|
|
||||||
"secondary_active": "#5b6078",
|
|
||||||
"secondary_selected": "#5b6078",
|
|
||||||
"secondary_disabled": "#8aadf44d",
|
|
||||||
"danger_foreground": "#24273a",
|
|
||||||
"danger_background": "#ed8796",
|
|
||||||
"danger_hover": "#ee99a0",
|
|
||||||
"danger_active": "#f5a97f",
|
|
||||||
"danger_selected": "#eed49f",
|
|
||||||
"danger_disabled": "#ed87964d",
|
|
||||||
"warning_foreground": "#24273a",
|
|
||||||
"warning_background": "#f5a97f",
|
|
||||||
"warning_hover": "#eed49f",
|
|
||||||
"warning_active": "#a6da95",
|
|
||||||
"warning_selected": "#8bd5ca",
|
|
||||||
"warning_disabled": "#f5a97f4d",
|
|
||||||
"ghost_element_background": "#cad3f500",
|
|
||||||
"ghost_element_background_alt": "#1e2030",
|
|
||||||
"ghost_element_hover": "#cad3f50d",
|
|
||||||
"ghost_element_active": "#cad3f51a",
|
|
||||||
"ghost_element_selected": "#cad3f51a",
|
|
||||||
"ghost_element_disabled": "#cad3f505",
|
|
||||||
"tab_background": "#181926",
|
|
||||||
"tab_foreground": "#a5adcb",
|
|
||||||
"tab_hover_background": "#cad3f50d",
|
|
||||||
"tab_active_background": "#24273a",
|
|
||||||
"tab_active_foreground": "#cad3f5",
|
|
||||||
"scrollbar_thumb_background": "#cad3f51a",
|
|
||||||
"scrollbar_thumb_hover_background": "#cad3f526",
|
|
||||||
"scrollbar_thumb_border": "#cad3f500",
|
|
||||||
"scrollbar_track_background": "#cad3f500",
|
|
||||||
"scrollbar_track_border": "#cad3f500",
|
|
||||||
"drop_target_background": "#8aadf41a",
|
|
||||||
"cursor": "#8aadf4",
|
|
||||||
"selection": "#8aadf440"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "catppuccin-mocha",
|
|
||||||
"name": "Catppuccin Mocha",
|
|
||||||
"author": "Catppuccin Org (ported by Coop)",
|
|
||||||
"url": "https://catppuccin.com",
|
|
||||||
"light": {
|
|
||||||
"background": "#1e1e2e",
|
|
||||||
"surface_background": "#181825",
|
|
||||||
"elevated_surface_background": "#11111b",
|
|
||||||
"panel_background": "#1e1e2e",
|
|
||||||
"overlay": "#cdd6f41a",
|
|
||||||
"title_bar": "#11111b",
|
|
||||||
"title_bar_inactive": "#1e1e2e",
|
|
||||||
"window_border": "#45475a",
|
|
||||||
"border": "#45475a",
|
|
||||||
"border_variant": "#313244",
|
|
||||||
"border_focused": "#89b4fa",
|
|
||||||
"border_selected": "#89b4fa",
|
|
||||||
"border_transparent": "#cdd6f400",
|
|
||||||
"border_disabled": "#181825",
|
|
||||||
"ring": "#b4befe",
|
|
||||||
"text": "#cdd6f4",
|
|
||||||
"text_muted": "#a6adc8",
|
|
||||||
"text_placeholder": "#7f849c",
|
|
||||||
"text_accent": "#89b4fa",
|
|
||||||
"text_danger": "#f38ba8",
|
|
||||||
"text_warning": "#fab387",
|
|
||||||
"icon": "#a6adc8",
|
|
||||||
"icon_muted": "#7f849c",
|
|
||||||
"icon_accent": "#b4befe",
|
|
||||||
"element_foreground": "#1e1e2e",
|
|
||||||
"element_background": "#89b4fa",
|
|
||||||
"element_hover": "#b4befe",
|
|
||||||
"element_active": "#89dceb",
|
|
||||||
"element_selected": "#74c7ec",
|
|
||||||
"element_disabled": "#89b4fa4d",
|
|
||||||
"secondary_foreground": "#cdd6f4",
|
|
||||||
"secondary_background": "#313244",
|
|
||||||
"secondary_hover": "#45475a",
|
|
||||||
"secondary_active": "#585b70",
|
|
||||||
"secondary_selected": "#585b70",
|
|
||||||
"secondary_disabled": "#89b4fa4d",
|
|
||||||
"danger_foreground": "#1e1e2e",
|
|
||||||
"danger_background": "#f38ba8",
|
|
||||||
"danger_hover": "#eba0ac",
|
|
||||||
"danger_active": "#fab387",
|
|
||||||
"danger_selected": "#f9e2af",
|
|
||||||
"danger_disabled": "#f38ba84d",
|
|
||||||
"warning_foreground": "#1e1e2e",
|
|
||||||
"warning_background": "#fab387",
|
|
||||||
"warning_hover": "#f9e2af",
|
|
||||||
"warning_active": "#a6e3a1",
|
|
||||||
"warning_selected": "#94e2d5",
|
|
||||||
"warning_disabled": "#fab3874d",
|
|
||||||
"ghost_element_background": "#cdd6f400",
|
|
||||||
"ghost_element_background_alt": "#181825",
|
|
||||||
"ghost_element_hover": "#cdd6f40d",
|
|
||||||
"ghost_element_active": "#cdd6f41a",
|
|
||||||
"ghost_element_selected": "#cdd6f41a",
|
|
||||||
"ghost_element_disabled": "#cdd6f405",
|
|
||||||
"tab_background": "#11111b",
|
|
||||||
"tab_foreground": "#a6adc8",
|
|
||||||
"tab_hover_background": "#cdd6f40d",
|
|
||||||
"tab_active_background": "#1e1e2e",
|
|
||||||
"tab_active_foreground": "#cdd6f4",
|
|
||||||
"scrollbar_thumb_background": "#cdd6f41a",
|
|
||||||
"scrollbar_thumb_hover_background": "#cdd6f426",
|
|
||||||
"scrollbar_thumb_border": "#cdd6f400",
|
|
||||||
"scrollbar_track_background": "#cdd6f400",
|
|
||||||
"scrollbar_track_border": "#cdd6f400",
|
|
||||||
"drop_target_background": "#89b4fa1a",
|
|
||||||
"cursor": "#89b4fa",
|
|
||||||
"selection": "#89b4fa40"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#1e1e2e",
|
|
||||||
"surface_background": "#181825",
|
|
||||||
"elevated_surface_background": "#11111b",
|
|
||||||
"panel_background": "#1e1e2e",
|
|
||||||
"overlay": "#cdd6f41a",
|
|
||||||
"title_bar": "#11111b",
|
|
||||||
"title_bar_inactive": "#1e1e2e",
|
|
||||||
"window_border": "#45475a",
|
|
||||||
"border": "#45475a",
|
|
||||||
"border_variant": "#313244",
|
|
||||||
"border_focused": "#89b4fa",
|
|
||||||
"border_selected": "#89b4fa",
|
|
||||||
"border_transparent": "#cdd6f400",
|
|
||||||
"border_disabled": "#181825",
|
|
||||||
"ring": "#b4befe",
|
|
||||||
"text": "#cdd6f4",
|
|
||||||
"text_muted": "#a6adc8",
|
|
||||||
"text_placeholder": "#7f849c",
|
|
||||||
"text_accent": "#89b4fa",
|
|
||||||
"text_danger": "#f38ba8",
|
|
||||||
"text_warning": "#fab387",
|
|
||||||
"icon": "#a6adc8",
|
|
||||||
"icon_muted": "#7f849c",
|
|
||||||
"icon_accent": "#b4befe",
|
|
||||||
"element_foreground": "#1e1e2e",
|
|
||||||
"element_background": "#89b4fa",
|
|
||||||
"element_hover": "#b4befe",
|
|
||||||
"element_active": "#89dceb",
|
|
||||||
"element_selected": "#74c7ec",
|
|
||||||
"element_disabled": "#89b4fa4d",
|
|
||||||
"secondary_foreground": "#cdd6f4",
|
|
||||||
"secondary_background": "#313244",
|
|
||||||
"secondary_hover": "#45475a",
|
|
||||||
"secondary_active": "#585b70",
|
|
||||||
"secondary_selected": "#585b70",
|
|
||||||
"secondary_disabled": "#89b4fa4d",
|
|
||||||
"danger_foreground": "#1e1e2e",
|
|
||||||
"danger_background": "#f38ba8",
|
|
||||||
"danger_hover": "#eba0ac",
|
|
||||||
"danger_active": "#fab387",
|
|
||||||
"danger_selected": "#f9e2af",
|
|
||||||
"danger_disabled": "#f38ba84d",
|
|
||||||
"warning_foreground": "#1e1e2e",
|
|
||||||
"warning_background": "#fab387",
|
|
||||||
"warning_hover": "#f9e2af",
|
|
||||||
"warning_active": "#a6e3a1",
|
|
||||||
"warning_selected": "#94e2d5",
|
|
||||||
"warning_disabled": "#fab3874d",
|
|
||||||
"ghost_element_background": "#cdd6f400",
|
|
||||||
"ghost_element_background_alt": "#181825",
|
|
||||||
"ghost_element_hover": "#cdd6f40d",
|
|
||||||
"ghost_element_active": "#cdd6f41a",
|
|
||||||
"ghost_element_selected": "#cdd6f41a",
|
|
||||||
"ghost_element_disabled": "#cdd6f405",
|
|
||||||
"tab_background": "#11111b",
|
|
||||||
"tab_foreground": "#a6adc8",
|
|
||||||
"tab_hover_background": "#cdd6f40d",
|
|
||||||
"tab_active_background": "#1e1e2e",
|
|
||||||
"tab_active_foreground": "#cdd6f4",
|
|
||||||
"scrollbar_thumb_background": "#cdd6f41a",
|
|
||||||
"scrollbar_thumb_hover_background": "#cdd6f426",
|
|
||||||
"scrollbar_thumb_border": "#cdd6f400",
|
|
||||||
"scrollbar_track_background": "#cdd6f400",
|
|
||||||
"scrollbar_track_border": "#cdd6f400",
|
|
||||||
"drop_target_background": "#89b4fa1a",
|
|
||||||
"cursor": "#89b4fa",
|
|
||||||
"selection": "#89b4fa40"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "flexoki",
|
|
||||||
"name": "Flexoki",
|
|
||||||
"author": "Steph Ango (ported by Coop)",
|
|
||||||
"url": "https://stephango.com/flexoki",
|
|
||||||
"light": {
|
|
||||||
"background": "#FFFCF0",
|
|
||||||
"surface_background": "#F2F0E5",
|
|
||||||
"elevated_surface_background": "#E6E4D9",
|
|
||||||
"panel_background": "#FFFCF0",
|
|
||||||
"overlay": "#100F0F1A",
|
|
||||||
"title_bar": "#E6E4D9",
|
|
||||||
"title_bar_inactive": "#FFFCF0",
|
|
||||||
"window_border": "#CECDC3",
|
|
||||||
"border": "#CECDC3",
|
|
||||||
"border_variant": "#DAD8CE",
|
|
||||||
"border_focused": "#24837B",
|
|
||||||
"border_selected": "#24837B",
|
|
||||||
"border_transparent": "#100F0F00",
|
|
||||||
"border_disabled": "#E6E4D9",
|
|
||||||
"ring": "#3AA99F",
|
|
||||||
"text": "#100F0F",
|
|
||||||
"text_muted": "#6F6E69",
|
|
||||||
"text_placeholder": "#B7B5AC",
|
|
||||||
"text_accent": "#24837B",
|
|
||||||
"text_danger": "#AF3029",
|
|
||||||
"text_warning": "#BC5215",
|
|
||||||
"icon": "#6F6E69",
|
|
||||||
"icon_muted": "#B7B5AC",
|
|
||||||
"icon_accent": "#3AA99F",
|
|
||||||
"element_foreground": "#FFFCF0",
|
|
||||||
"element_background": "#24837B",
|
|
||||||
"element_hover": "#3AA99F",
|
|
||||||
"element_active": "#1C1B1A",
|
|
||||||
"element_selected": "#100F0F",
|
|
||||||
"element_disabled": "#24837B4D",
|
|
||||||
"secondary_foreground": "#100F0F",
|
|
||||||
"secondary_background": "#E6E4D9",
|
|
||||||
"secondary_hover": "#DAD8CE",
|
|
||||||
"secondary_active": "#CECDC3",
|
|
||||||
"secondary_selected": "#CECDC3",
|
|
||||||
"secondary_disabled": "#24837B4D",
|
|
||||||
"danger_foreground": "#FFFCF0",
|
|
||||||
"danger_background": "#AF3029",
|
|
||||||
"danger_hover": "#D14D41",
|
|
||||||
"danger_active": "#1C1B1A",
|
|
||||||
"danger_selected": "#100F0F",
|
|
||||||
"danger_disabled": "#AF30294D",
|
|
||||||
"warning_foreground": "#FFFCF0",
|
|
||||||
"warning_background": "#BC5215",
|
|
||||||
"warning_hover": "#DA702C",
|
|
||||||
"warning_active": "#1C1B1A",
|
|
||||||
"warning_selected": "#100F0F",
|
|
||||||
"warning_disabled": "#BC52154D",
|
|
||||||
"ghost_element_background": "#100F0F00",
|
|
||||||
"ghost_element_background_alt": "#F2F0E5",
|
|
||||||
"ghost_element_hover": "#100F0F0D",
|
|
||||||
"ghost_element_active": "#100F0F1A",
|
|
||||||
"ghost_element_selected": "#100F0F1A",
|
|
||||||
"ghost_element_disabled": "#100F0F05",
|
|
||||||
"tab_background": "#E6E4D9",
|
|
||||||
"tab_foreground": "#6F6E69",
|
|
||||||
"tab_hover_background": "#100F0F0D",
|
|
||||||
"tab_active_background": "#FFFCF0",
|
|
||||||
"tab_active_foreground": "#100F0F",
|
|
||||||
"scrollbar_thumb_background": "#100F0F1A",
|
|
||||||
"scrollbar_thumb_hover_background": "#100F0F26",
|
|
||||||
"scrollbar_thumb_border": "#100F0F00",
|
|
||||||
"scrollbar_track_background": "#100F0F00",
|
|
||||||
"scrollbar_track_border": "#100F0F00",
|
|
||||||
"drop_target_background": "#24837B1A",
|
|
||||||
"cursor": "#24837B",
|
|
||||||
"selection": "#24837B40"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#100F0F",
|
|
||||||
"surface_background": "#1C1B1A",
|
|
||||||
"elevated_surface_background": "#282726",
|
|
||||||
"panel_background": "#100F0F",
|
|
||||||
"overlay": "#FFFCF01A",
|
|
||||||
"title_bar": "#282726",
|
|
||||||
"title_bar_inactive": "#100F0F",
|
|
||||||
"window_border": "#403E3C",
|
|
||||||
"border": "#403E3C",
|
|
||||||
"border_variant": "#343331",
|
|
||||||
"border_focused": "#3AA99F",
|
|
||||||
"border_selected": "#3AA99F",
|
|
||||||
"border_transparent": "#100F0F00",
|
|
||||||
"border_disabled": "#282726",
|
|
||||||
"ring": "#24837B",
|
|
||||||
"text": "#CECDC3",
|
|
||||||
"text_muted": "#878580",
|
|
||||||
"text_placeholder": "#575653",
|
|
||||||
"text_accent": "#3AA99F",
|
|
||||||
"text_danger": "#D14D41",
|
|
||||||
"text_warning": "#DA702C",
|
|
||||||
"icon": "#878580",
|
|
||||||
"icon_muted": "#575653",
|
|
||||||
"icon_accent": "#24837B",
|
|
||||||
"element_foreground": "#100F0F",
|
|
||||||
"element_background": "#3AA99F",
|
|
||||||
"element_hover": "#24837B",
|
|
||||||
"element_active": "#CECDC3",
|
|
||||||
"element_selected": "#F2F0E5",
|
|
||||||
"element_disabled": "#3AA99F4D",
|
|
||||||
"secondary_foreground": "#CECDC3",
|
|
||||||
"secondary_background": "#1C1B1A",
|
|
||||||
"secondary_hover": "#282726",
|
|
||||||
"secondary_active": "#343331",
|
|
||||||
"secondary_selected": "#343331",
|
|
||||||
"secondary_disabled": "#3AA99F4D",
|
|
||||||
"danger_foreground": "#100F0F",
|
|
||||||
"danger_background": "#D14D41",
|
|
||||||
"danger_hover": "#AF3029",
|
|
||||||
"danger_active": "#CECDC3",
|
|
||||||
"danger_selected": "#F2F0E5",
|
|
||||||
"danger_disabled": "#D14D414D",
|
|
||||||
"warning_foreground": "#100F0F",
|
|
||||||
"warning_background": "#DA702C",
|
|
||||||
"warning_hover": "#BC5215",
|
|
||||||
"warning_active": "#CECDC3",
|
|
||||||
"warning_selected": "#F2F0E5",
|
|
||||||
"warning_disabled": "#DA702C4D",
|
|
||||||
"ghost_element_background": "#100F0F00",
|
|
||||||
"ghost_element_background_alt": "#1C1B1A",
|
|
||||||
"ghost_element_hover": "#FFFCF00D",
|
|
||||||
"ghost_element_active": "#FFFCF01A",
|
|
||||||
"ghost_element_selected": "#FFFCF01A",
|
|
||||||
"ghost_element_disabled": "#FFFCF005",
|
|
||||||
"tab_background": "#282726",
|
|
||||||
"tab_foreground": "#878580",
|
|
||||||
"tab_hover_background": "#FFFCF00D",
|
|
||||||
"tab_active_background": "#100F0F",
|
|
||||||
"tab_active_foreground": "#CECDC3",
|
|
||||||
"scrollbar_thumb_background": "#FFFCF01A",
|
|
||||||
"scrollbar_thumb_hover_background": "#FFFCF026",
|
|
||||||
"scrollbar_thumb_border": "#100F0F00",
|
|
||||||
"scrollbar_track_background": "#100F0F00",
|
|
||||||
"scrollbar_track_border": "#100F0F00",
|
|
||||||
"drop_target_background": "#3AA99F1A",
|
|
||||||
"cursor": "#3AA99F",
|
|
||||||
"selection": "#3AA99F40"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "forest",
|
|
||||||
"name": "Forest",
|
|
||||||
"author": "Coop",
|
|
||||||
"url": "https://coopchat.xyz",
|
|
||||||
"light": {
|
|
||||||
"background": "#fbfefcff",
|
|
||||||
"surface_background": "#f4fbf6ff",
|
|
||||||
"elevated_surface_background": "#e9f6e9ff",
|
|
||||||
"panel_background": "#fbfefcff",
|
|
||||||
"overlay": "#193b2d1a",
|
|
||||||
"title_bar": "#e9f6e9ff",
|
|
||||||
"title_bar_inactive": "#fbfefcff",
|
|
||||||
"window_border": "#c4e8d1ff",
|
|
||||||
"border": "#c4e8d1ff",
|
|
||||||
"border_variant": "#b2ddb5ff",
|
|
||||||
"border_focused": "#30a46cff",
|
|
||||||
"border_selected": "#30a46cff",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#e0f3e6ff",
|
|
||||||
"ring": "#2b9a66ff",
|
|
||||||
"text": "#193b2dff",
|
|
||||||
"text_muted": "#2f7c57ff",
|
|
||||||
"text_placeholder": "#8eceaaff",
|
|
||||||
"text_accent": "#30a46cff",
|
|
||||||
"text_danger": "#e54d2eff",
|
|
||||||
"text_warning": "#f76b15ff",
|
|
||||||
"icon": "#2f7c57ff",
|
|
||||||
"icon_muted": "#8eceaaff",
|
|
||||||
"icon_accent": "#2b9a66ff",
|
|
||||||
"element_foreground": "#ffffffff",
|
|
||||||
"element_background": "#30a46cff",
|
|
||||||
"element_hover": "#2b9a66ff",
|
|
||||||
"element_active": "#2a7e3bff",
|
|
||||||
"element_selected": "#218358ff",
|
|
||||||
"element_disabled": "#30a46c4d",
|
|
||||||
"secondary_foreground": "#193b2dff",
|
|
||||||
"secondary_background": "#e9f6e9ff",
|
|
||||||
"secondary_hover": "#daf1dbff",
|
|
||||||
"secondary_active": "#c4e8d1ff",
|
|
||||||
"secondary_selected": "#c4e8d1ff",
|
|
||||||
"secondary_disabled": "#30a46c4d",
|
|
||||||
"danger_foreground": "#ffffffff",
|
|
||||||
"danger_background": "#feebe7ff",
|
|
||||||
"danger_hover": "#ffcdc2ff",
|
|
||||||
"danger_active": "#fdbdafff",
|
|
||||||
"danger_selected": "#fdbdafff",
|
|
||||||
"danger_disabled": "#e54d2e4d",
|
|
||||||
"warning_foreground": "#ffffffff",
|
|
||||||
"warning_background": "#fff7edff",
|
|
||||||
"warning_hover": "#ffd19aff",
|
|
||||||
"warning_active": "#ffc182ff",
|
|
||||||
"warning_selected": "#ffc182ff",
|
|
||||||
"warning_disabled": "#f76b154d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#e9f6e9ff",
|
|
||||||
"ghost_element_hover": "#193b2d0d",
|
|
||||||
"ghost_element_active": "#193b2d1a",
|
|
||||||
"ghost_element_selected": "#193b2d1a",
|
|
||||||
"ghost_element_disabled": "#193b2d05",
|
|
||||||
"tab_background": "#e9f6e9ff",
|
|
||||||
"tab_foreground": "#2f7c57ff",
|
|
||||||
"tab_hover_background": "#193b2d0d",
|
|
||||||
"tab_active_background": "#fbfefcff",
|
|
||||||
"tab_active_foreground": "#193b2dff",
|
|
||||||
"scrollbar_thumb_background": "#193b2d1a",
|
|
||||||
"scrollbar_thumb_hover_background": "#193b2d26",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#00000000",
|
|
||||||
"drop_target_background": "#30a46c1a",
|
|
||||||
"cursor": "#30a46cff",
|
|
||||||
"selection": "#30a46c40"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#0e1512ff",
|
|
||||||
"surface_background": "#121b17ff",
|
|
||||||
"elevated_surface_background": "#132d21ff",
|
|
||||||
"panel_background": "#0e1512ff",
|
|
||||||
"overlay": "#b1f1cb1a",
|
|
||||||
"title_bar": "#132d21ff",
|
|
||||||
"title_bar_inactive": "#0e1512ff",
|
|
||||||
"window_border": "#20573eff",
|
|
||||||
"border": "#20573eff",
|
|
||||||
"border_variant": "#174933ff",
|
|
||||||
"border_focused": "#33b074ff",
|
|
||||||
"border_selected": "#33b074ff",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#113b29ff",
|
|
||||||
"ring": "#33b074ff",
|
|
||||||
"text": "#b1f1cbff",
|
|
||||||
"text_muted": "#71d083ff",
|
|
||||||
"text_placeholder": "#2f7c57ff",
|
|
||||||
"text_accent": "#3dd68cff",
|
|
||||||
"text_danger": "#ff977dff",
|
|
||||||
"text_warning": "#ffa057ff",
|
|
||||||
"icon": "#71d083ff",
|
|
||||||
"icon_muted": "#2f7c57ff",
|
|
||||||
"icon_accent": "#33b074ff",
|
|
||||||
"element_foreground": "#0e1512ff",
|
|
||||||
"element_background": "#3dd68cff",
|
|
||||||
"element_hover": "#71d083ff",
|
|
||||||
"element_active": "#33b074ff",
|
|
||||||
"element_selected": "#30a46cff",
|
|
||||||
"element_disabled": "#3dd68c4d",
|
|
||||||
"secondary_foreground": "#b1f1cbff",
|
|
||||||
"secondary_background": "#132d21ff",
|
|
||||||
"secondary_hover": "#113b29ff",
|
|
||||||
"secondary_active": "#174933ff",
|
|
||||||
"secondary_selected": "#174933ff",
|
|
||||||
"secondary_disabled": "#3dd68c4d",
|
|
||||||
"danger_foreground": "#181111ff",
|
|
||||||
"danger_background": "#391714ff",
|
|
||||||
"danger_hover": "#5e1c16ff",
|
|
||||||
"danger_active": "#6e2920ff",
|
|
||||||
"danger_selected": "#6e2920ff",
|
|
||||||
"danger_disabled": "#ff977d4d",
|
|
||||||
"warning_foreground": "#17120eff",
|
|
||||||
"warning_background": "#331e0bff",
|
|
||||||
"warning_hover": "#562800ff",
|
|
||||||
"warning_active": "#66350cff",
|
|
||||||
"warning_selected": "#66350cff",
|
|
||||||
"warning_disabled": "#ffa0574d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#132d21ff",
|
|
||||||
"ghost_element_hover": "#b1f1cb0d",
|
|
||||||
"ghost_element_active": "#b1f1cb1a",
|
|
||||||
"ghost_element_selected": "#b1f1cb1a",
|
|
||||||
"ghost_element_disabled": "#b1f1cb05",
|
|
||||||
"tab_background": "#132d21ff",
|
|
||||||
"tab_foreground": "#71d083ff",
|
|
||||||
"tab_hover_background": "#b1f1cb0d",
|
|
||||||
"tab_active_background": "#0e1512ff",
|
|
||||||
"tab_active_foreground": "#b1f1cbff",
|
|
||||||
"scrollbar_thumb_background": "#b1f1cb1a",
|
|
||||||
"scrollbar_thumb_hover_background": "#b1f1cb26",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#00000000",
|
|
||||||
"drop_target_background": "#3dd68c1a",
|
|
||||||
"cursor": "#3dd68cff",
|
|
||||||
"selection": "#3dd68c40"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "ocean",
|
|
||||||
"name": "Ocean",
|
|
||||||
"author": "Coop",
|
|
||||||
"url": "https://coopchat.xyz",
|
|
||||||
"light": {
|
|
||||||
"background": "#fafefeff",
|
|
||||||
"surface_background": "#f2fbfaff",
|
|
||||||
"elevated_surface_background": "#e6f7f7ff",
|
|
||||||
"panel_background": "#fafefeff",
|
|
||||||
"overlay": "#00333f1a",
|
|
||||||
"title_bar": "#e6f7f7ff",
|
|
||||||
"title_bar_inactive": "#fafefeff",
|
|
||||||
"window_border": "#cce5e9ff",
|
|
||||||
"border": "#cce5e9ff",
|
|
||||||
"border_variant": "#b8dde3ff",
|
|
||||||
"border_focused": "#00a2c7ff",
|
|
||||||
"border_selected": "#00a2c7ff",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#e0f0f2ff",
|
|
||||||
"ring": "#0797b9ff",
|
|
||||||
"text": "#0d3c48ff",
|
|
||||||
"text_muted": "#107d98ff",
|
|
||||||
"text_placeholder": "#60b3d7ff",
|
|
||||||
"text_accent": "#00a2c7ff",
|
|
||||||
"text_danger": "#e54d2eff",
|
|
||||||
"text_warning": "#f76b15ff",
|
|
||||||
"icon": "#107d98ff",
|
|
||||||
"icon_muted": "#60b3d7ff",
|
|
||||||
"icon_accent": "#0797b9ff",
|
|
||||||
"element_foreground": "#ffffffff",
|
|
||||||
"element_background": "#00a2c7ff",
|
|
||||||
"element_hover": "#0797b9ff",
|
|
||||||
"element_active": "#12667eff",
|
|
||||||
"element_selected": "#0d4a5cff",
|
|
||||||
"element_disabled": "#00a2c74d",
|
|
||||||
"secondary_foreground": "#0d4a5cff",
|
|
||||||
"secondary_background": "#ddf9f2ff",
|
|
||||||
"secondary_hover": "#c8f4e9ff",
|
|
||||||
"secondary_active": "#b3ecdeff",
|
|
||||||
"secondary_selected": "#b3ecdeff",
|
|
||||||
"secondary_disabled": "#00a2c74d",
|
|
||||||
"danger_foreground": "#ffffffff",
|
|
||||||
"danger_background": "#feebe7ff",
|
|
||||||
"danger_hover": "#ffcdc2ff",
|
|
||||||
"danger_active": "#fdbdafff",
|
|
||||||
"danger_selected": "#fdbdafff",
|
|
||||||
"danger_disabled": "#e54d2e4d",
|
|
||||||
"warning_foreground": "#ffffffff",
|
|
||||||
"warning_background": "#fff7edff",
|
|
||||||
"warning_hover": "#ffd19aff",
|
|
||||||
"warning_active": "#ffc182ff",
|
|
||||||
"warning_selected": "#ffc182ff",
|
|
||||||
"warning_disabled": "#f76b154d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#e6f7f7ff",
|
|
||||||
"ghost_element_hover": "#00333f0d",
|
|
||||||
"ghost_element_active": "#00333f1a",
|
|
||||||
"ghost_element_selected": "#00333f1a",
|
|
||||||
"ghost_element_disabled": "#00333f05",
|
|
||||||
"tab_background": "#e6f7f7ff",
|
|
||||||
"tab_foreground": "#107d98ff",
|
|
||||||
"tab_hover_background": "#00333f0d",
|
|
||||||
"tab_active_background": "#fafefeff",
|
|
||||||
"tab_active_foreground": "#0d3c48ff",
|
|
||||||
"scrollbar_thumb_background": "#00333f1a",
|
|
||||||
"scrollbar_thumb_hover_background": "#00333f26",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#00000000",
|
|
||||||
"drop_target_background": "#00a2c71a",
|
|
||||||
"cursor": "#00a2c7ff",
|
|
||||||
"selection": "#00a2c740"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#0b161aff",
|
|
||||||
"surface_background": "#101b20ff",
|
|
||||||
"elevated_surface_background": "#082c36ff",
|
|
||||||
"panel_background": "#0b161aff",
|
|
||||||
"overlay": "#c2f3ff1a",
|
|
||||||
"title_bar": "#082c36ff",
|
|
||||||
"title_bar_inactive": "#0b161aff",
|
|
||||||
"window_border": "#1b537bff",
|
|
||||||
"border": "#1b537bff",
|
|
||||||
"border_variant": "#154467ff",
|
|
||||||
"border_focused": "#00a2c7ff",
|
|
||||||
"border_selected": "#00a2c7ff",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#112840ff",
|
|
||||||
"ring": "#23afd0ff",
|
|
||||||
"text": "#b6ecf7ff",
|
|
||||||
"text_muted": "#4ccce6ff",
|
|
||||||
"text_placeholder": "#197caeff",
|
|
||||||
"text_accent": "#7ce2feff",
|
|
||||||
"text_danger": "#ff977dff",
|
|
||||||
"text_warning": "#ffa057ff",
|
|
||||||
"icon": "#4ccce6ff",
|
|
||||||
"icon_muted": "#197caeff",
|
|
||||||
"icon_accent": "#23afd0ff",
|
|
||||||
"element_foreground": "#0b161aff",
|
|
||||||
"element_background": "#7ce2feff",
|
|
||||||
"element_hover": "#a8eeffff",
|
|
||||||
"element_active": "#23afd0ff",
|
|
||||||
"element_selected": "#00a2c7ff",
|
|
||||||
"element_disabled": "#7ce2fe4d",
|
|
||||||
"secondary_foreground": "#adf0ddff",
|
|
||||||
"secondary_background": "#0d2d2aff",
|
|
||||||
"secondary_hover": "#023b37ff",
|
|
||||||
"secondary_active": "#084843ff",
|
|
||||||
"secondary_selected": "#084843ff",
|
|
||||||
"secondary_disabled": "#7ce2fe4d",
|
|
||||||
"danger_foreground": "#181111ff",
|
|
||||||
"danger_background": "#391714ff",
|
|
||||||
"danger_hover": "#5e1c16ff",
|
|
||||||
"danger_active": "#6e2920ff",
|
|
||||||
"danger_selected": "#6e2920ff",
|
|
||||||
"danger_disabled": "#ff977d4d",
|
|
||||||
"warning_foreground": "#17120eff",
|
|
||||||
"warning_background": "#331e0bff",
|
|
||||||
"warning_hover": "#562800ff",
|
|
||||||
"warning_active": "#66350cff",
|
|
||||||
"warning_selected": "#66350cff",
|
|
||||||
"warning_disabled": "#ffa0574d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#082c36ff",
|
|
||||||
"ghost_element_hover": "#c2f3ff0d",
|
|
||||||
"ghost_element_active": "#c2f3ff1a",
|
|
||||||
"ghost_element_selected": "#c2f3ff1a",
|
|
||||||
"ghost_element_disabled": "#c2f3ff05",
|
|
||||||
"tab_background": "#082c36ff",
|
|
||||||
"tab_foreground": "#4ccce6ff",
|
|
||||||
"tab_hover_background": "#c2f3ff0d",
|
|
||||||
"tab_active_background": "#0b161aff",
|
|
||||||
"tab_active_foreground": "#b6ecf7ff",
|
|
||||||
"scrollbar_thumb_background": "#c2f3ff1a",
|
|
||||||
"scrollbar_thumb_hover_background": "#c2f3ff26",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#00000000",
|
|
||||||
"drop_target_background": "#7ce2fe1a",
|
|
||||||
"cursor": "#7ce2feff",
|
|
||||||
"selection": "#7ce2fe40"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ use rust_embed::RustEmbed;
|
|||||||
#[include = "fonts/**/*"]
|
#[include = "fonts/**/*"]
|
||||||
#[include = "brand/**/*"]
|
#[include = "brand/**/*"]
|
||||||
#[include = "icons/**/*"]
|
#[include = "icons/**/*"]
|
||||||
#[include = "themes/**/*"]
|
|
||||||
#[exclude = "*.DS_Store"]
|
#[exclude = "*.DS_Store"]
|
||||||
pub struct Assets;
|
pub struct Assets;
|
||||||
|
|
||||||
|
|||||||
+41
-51
@@ -1,12 +1,10 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use anyhow::{Error, anyhow};
|
use anyhow::{Error, anyhow};
|
||||||
use common::config_dir;
|
use common::config_dir;
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
|
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use theme::{Theme, ThemeFamily, ThemeMode};
|
use theme::{AppExt as _, AppearanceMode};
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
AppSettings::set_global(cx.new(AppSettings::new), cx)
|
AppSettings::set_global(cx.new(AppSettings::new), cx)
|
||||||
@@ -53,7 +51,7 @@ macro_rules! setting_getters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setting_accessors! {
|
setting_accessors! {
|
||||||
pub theme_mode: ThemeMode,
|
pub appearance_mode: AppearanceMode,
|
||||||
pub hide_avatar: bool,
|
pub hide_avatar: bool,
|
||||||
pub screening: bool,
|
pub screening: bool,
|
||||||
pub nip4e: bool,
|
pub nip4e: bool,
|
||||||
@@ -119,11 +117,11 @@ impl RoomConfig {
|
|||||||
/// Settings
|
/// Settings
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// Theme
|
/// Which appearance to paint: follow the OS, or pin light or dark.
|
||||||
pub theme: Option<String>,
|
/// `alias` reads the pre-bezel `theme_mode` key so old config files keep
|
||||||
|
/// their pinned choice.
|
||||||
/// Theme mode
|
#[serde(default, alias = "theme_mode")]
|
||||||
pub theme_mode: ThemeMode,
|
pub appearance_mode: AppearanceMode,
|
||||||
|
|
||||||
/// Hide user avatars
|
/// Hide user avatars
|
||||||
pub hide_avatar: bool,
|
pub hide_avatar: bool,
|
||||||
@@ -152,8 +150,7 @@ pub struct Settings {
|
|||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
theme: None,
|
appearance_mode: AppearanceMode::default(),
|
||||||
theme_mode: ThemeMode::default(),
|
|
||||||
hide_avatar: false,
|
hide_avatar: false,
|
||||||
screening: true,
|
screening: true,
|
||||||
nip4e: false,
|
nip4e: false,
|
||||||
@@ -243,7 +240,7 @@ impl AppSettings {
|
|||||||
// Update settings
|
// Update settings
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_settings(settings, cx);
|
this.set_settings(settings, cx);
|
||||||
this.apply_theme(None, cx);
|
this.apply_theme(cx);
|
||||||
cx.refresh_windows();
|
cx.refresh_windows();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
@@ -264,44 +261,10 @@ impl AppSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set theme
|
/// Apply the persisted appearance mode to the app
|
||||||
pub fn set_theme<T>(&mut self, theme: T, window: &mut Window, cx: &mut Context<Self>)
|
pub fn apply_theme(&mut self, cx: &mut Context<Self>) {
|
||||||
where
|
let mode = self.inner.read(cx).appearance_mode;
|
||||||
T: Into<String>,
|
cx.set_appearance_mode(mode);
|
||||||
{
|
|
||||||
// Update settings
|
|
||||||
self.inner.update(cx, |this, cx| {
|
|
||||||
this.theme = Some(theme.into());
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply the new theme
|
|
||||||
self.apply_theme(Some(window), cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset theme
|
|
||||||
pub fn reset_theme(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
self.inner.update(cx, |this, cx| {
|
|
||||||
this.theme = None;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
self.apply_theme(Some(window), cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply theme
|
|
||||||
pub fn apply_theme(&mut self, mut window: Option<&mut Window>, cx: &mut Context<Self>) {
|
|
||||||
if let Some(name) = self.inner.read(cx).theme.as_ref() {
|
|
||||||
let mode = self.inner.read(cx).theme_mode;
|
|
||||||
|
|
||||||
if let Ok(new_theme) = ThemeFamily::from_assets(name) {
|
|
||||||
Theme::apply_theme(Rc::new(new_theme), window.as_deref_mut(), cx);
|
|
||||||
Theme::change(mode, window, cx);
|
|
||||||
} else {
|
|
||||||
log::info!("Failed to load theme: {name}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Theme::apply_theme(Rc::new(ThemeFamily::default()), window, cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if decoupling encryption key is enabled
|
/// Check if decoupling encryption key is enabled
|
||||||
@@ -347,3 +310,30 @@ impl AppSettings {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn json_with(partial: &str) -> Result<Settings, serde_json::Error> {
|
||||||
|
// Every field the persisted format has always required; `partial`
|
||||||
|
// contributes the fields under test.
|
||||||
|
serde_json::from_str(&format!(
|
||||||
|
r#"{{ {} "hide_avatar": false, "screening": true, "nip4e": false,
|
||||||
|
"trusted_relays": [], "file_server": "https://nostr.download/" }}"#,
|
||||||
|
partial
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn old_theme_mode_key_keeps_its_pinned_choice() {
|
||||||
|
let settings = json_with(r#""theme_mode": "Dark", "#).unwrap();
|
||||||
|
assert_eq!(settings.appearance_mode, AppearanceMode::Dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_appearance_mode_falls_back_to_system() {
|
||||||
|
let settings = json_with("").unwrap();
|
||||||
|
assert_eq!(settings.appearance_mode, AppearanceMode::System);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+56
-425
@@ -2,486 +2,117 @@ use gpui::Hsla;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::paint::{ink_for, plate_active_for, plate_hover_for, wash_for};
|
|
||||||
use crate::syntax::SyntaxPalette;
|
use crate::syntax::SyntaxPalette;
|
||||||
use crate::{ThemeMode, palette};
|
use crate::{ThemeMode, palette};
|
||||||
|
|
||||||
/// The full token set.
|
|
||||||
///
|
|
||||||
/// The first groups are the legacy coop tokens; they stay until every call
|
|
||||||
/// site has moved to the bezel tokens (see docs/ui-theme-bezel-migration.md),
|
|
||||||
/// and are derived from the tokens by [`ThemeColors::sync_legacy`]. The bezel
|
|
||||||
/// token groups follow. Deserialization refines over the built-in dark
|
|
||||||
/// palette, so a JSON that names only some fields still yields a complete
|
|
||||||
/// palette.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ThemeColors {
|
pub struct ThemeColors {
|
||||||
// Surface colors
|
/// The main content plane.
|
||||||
pub background: Hsla,
|
pub bg: Hsla,
|
||||||
pub surface_background: Hsla,
|
/// The shell / sidebar plane.
|
||||||
pub elevated_surface_background: Hsla,
|
pub surface: Hsla,
|
||||||
pub panel_background: Hsla,
|
/// Opaque pills and chips raised above the panel.
|
||||||
pub overlay: Hsla,
|
pub surface_raised: Hsla,
|
||||||
pub title_bar: Hsla,
|
/// An inline card resting on the main plane.
|
||||||
pub title_bar_inactive: Hsla,
|
pub surface_card: Hsla,
|
||||||
pub window_border: Hsla,
|
/// A modal dialog, over the scrim.
|
||||||
|
pub surface_dialog: Hsla,
|
||||||
|
/// Popovers and menus — the highest plane.
|
||||||
|
pub surface_overlay: Hsla,
|
||||||
|
/// Row hover — translucent ink over whatever is beneath.
|
||||||
|
pub element_hover: Hsla,
|
||||||
|
/// Pressed or selected, one rung past [`ThemeColors::element_hover`].
|
||||||
|
pub element_active: Hsla,
|
||||||
|
|
||||||
// Border colors
|
/// The standard hairline border.
|
||||||
pub border: Hsla,
|
pub border: Hsla,
|
||||||
pub border_variant: Hsla,
|
/// Dividers inside cards, nested frames.
|
||||||
pub border_focused: Hsla,
|
pub border_faint: Hsla,
|
||||||
pub border_selected: Hsla,
|
/// Focused or raised edges.
|
||||||
pub border_transparent: Hsla,
|
pub border_strong: Hsla,
|
||||||
pub border_disabled: Hsla,
|
/// The focus ring.
|
||||||
pub ring: Hsla,
|
pub ring: Hsla,
|
||||||
|
|
||||||
// Text colors
|
/// Primary text.
|
||||||
pub text: Hsla,
|
pub text: Hsla,
|
||||||
|
/// Secondary text.
|
||||||
pub text_muted: Hsla,
|
pub text_muted: Hsla,
|
||||||
pub text_placeholder: Hsla,
|
/// Placeholders and disabled text.
|
||||||
pub text_accent: Hsla,
|
|
||||||
pub text_danger: Hsla,
|
|
||||||
pub text_warning: Hsla,
|
|
||||||
|
|
||||||
// Icon colors
|
|
||||||
pub icon: Hsla,
|
|
||||||
pub icon_muted: Hsla,
|
|
||||||
pub icon_accent: Hsla,
|
|
||||||
|
|
||||||
// Element colors
|
|
||||||
pub element_foreground: Hsla,
|
|
||||||
pub element_background: Hsla,
|
|
||||||
pub element_hover: Hsla,
|
|
||||||
pub element_active: Hsla,
|
|
||||||
pub element_selected: Hsla,
|
|
||||||
pub element_disabled: Hsla,
|
|
||||||
|
|
||||||
// Secondary element colors
|
|
||||||
pub secondary_foreground: Hsla,
|
|
||||||
pub secondary_background: Hsla,
|
|
||||||
pub secondary_hover: Hsla,
|
|
||||||
pub secondary_active: Hsla,
|
|
||||||
pub secondary_selected: Hsla,
|
|
||||||
pub secondary_disabled: Hsla,
|
|
||||||
|
|
||||||
// Danger element colors
|
|
||||||
pub danger_foreground: Hsla,
|
|
||||||
pub danger_background: Hsla,
|
|
||||||
pub danger_hover: Hsla,
|
|
||||||
pub danger_active: Hsla,
|
|
||||||
pub danger_selected: Hsla,
|
|
||||||
pub danger_disabled: Hsla,
|
|
||||||
|
|
||||||
// Warning element colors
|
|
||||||
pub warning_foreground: Hsla,
|
|
||||||
pub warning_background: Hsla,
|
|
||||||
pub warning_hover: Hsla,
|
|
||||||
pub warning_active: Hsla,
|
|
||||||
pub warning_selected: Hsla,
|
|
||||||
pub warning_disabled: Hsla,
|
|
||||||
|
|
||||||
// Ghost element colors
|
|
||||||
pub ghost_element_background: Hsla,
|
|
||||||
pub ghost_element_background_alt: Hsla,
|
|
||||||
pub ghost_element_hover: Hsla,
|
|
||||||
pub ghost_element_active: Hsla,
|
|
||||||
pub ghost_element_selected: Hsla,
|
|
||||||
pub ghost_element_disabled: Hsla,
|
|
||||||
|
|
||||||
// Tab colors
|
|
||||||
pub tab_background: Hsla,
|
|
||||||
pub tab_foreground: Hsla,
|
|
||||||
pub tab_hover_background: Hsla,
|
|
||||||
pub tab_active_background: Hsla,
|
|
||||||
pub tab_active_foreground: Hsla,
|
|
||||||
|
|
||||||
// Scrollbar colors
|
|
||||||
pub scrollbar_thumb_background: Hsla,
|
|
||||||
pub scrollbar_thumb_hover_background: Hsla,
|
|
||||||
pub scrollbar_thumb_border: Hsla,
|
|
||||||
pub scrollbar_track_background: Hsla,
|
|
||||||
pub scrollbar_track_border: Hsla,
|
|
||||||
|
|
||||||
// Interactive colors
|
|
||||||
pub drop_target_background: Hsla,
|
|
||||||
pub cursor: Hsla,
|
|
||||||
pub selection: Hsla,
|
|
||||||
|
|
||||||
// Bezel tokens: neutral surfaces
|
|
||||||
/// Main content panel.
|
|
||||||
pub bg: Hsla,
|
|
||||||
/// Shell / sidebar surface.
|
|
||||||
pub surface: Hsla,
|
|
||||||
/// Raised surface: opaque pills and chips that sit proud of the panel.
|
|
||||||
pub surface_raised: Hsla,
|
|
||||||
/// Inline card resting on the main panel.
|
|
||||||
pub surface_card: Hsla,
|
|
||||||
/// Modal dialog, floating over the scrim.
|
|
||||||
pub surface_dialog: Hsla,
|
|
||||||
/// Popover, menu and command-palette surface — the highest plane.
|
|
||||||
pub surface_overlay: Hsla,
|
|
||||||
|
|
||||||
// Bezel tokens: hairlines
|
|
||||||
/// Faintest hairline: dividers inside a card, nested frames.
|
|
||||||
pub border_faint: Hsla,
|
|
||||||
/// Stronger border for focused/raised edges.
|
|
||||||
pub border_strong: Hsla,
|
|
||||||
|
|
||||||
// Bezel tokens: text
|
|
||||||
/// Faint text: placeholders, disabled.
|
|
||||||
pub text_faint: Hsla,
|
pub text_faint: Hsla,
|
||||||
/// One notch below `text_muted` — the diff file-path tone.
|
/// Diff file paths — one notch below [`ThemeColors::text_muted`].
|
||||||
pub text_dim: Hsla,
|
pub text_dim: Hsla,
|
||||||
|
|
||||||
// Bezel tokens: high-contrast solid
|
/// The text cursor.
|
||||||
/// The maximum-contrast solid fill: the primary button plate.
|
pub cursor: Hsla,
|
||||||
|
/// The selected-text wash.
|
||||||
|
pub selection: Hsla,
|
||||||
|
|
||||||
|
/// The primary button plate — the maximum-contrast fill.
|
||||||
pub solid: Hsla,
|
pub solid: Hsla,
|
||||||
/// Label/icon color on top of [`ThemeColors::solid`] — its inverse.
|
/// Label ink on [`ThemeColors::solid`].
|
||||||
pub on_solid: Hsla,
|
pub on_solid: Hsla,
|
||||||
|
|
||||||
// Bezel tokens: accents
|
/// Emphasis for text and icons; neutral by default.
|
||||||
/// Accent — the emphasis weight for text and icons. Neutral by default.
|
|
||||||
pub accent: Hsla,
|
pub accent: Hsla,
|
||||||
/// Stronger accent for fills that carry [`ThemeColors::on_accent`].
|
/// Accent fills carrying [`ThemeColors::on_accent`].
|
||||||
pub accent_strong: Hsla,
|
pub accent_strong: Hsla,
|
||||||
/// Label color on top of [`ThemeColors::accent_strong`].
|
/// Label ink on [`ThemeColors::accent_strong`].
|
||||||
pub on_accent: Hsla,
|
pub on_accent: Hsla,
|
||||||
|
|
||||||
// Bezel tokens: semantic
|
/// Errors and destructive actions.
|
||||||
/// Danger — red (errors, stop button).
|
|
||||||
pub danger: Hsla,
|
pub danger: Hsla,
|
||||||
/// Softer danger for secondary/inline error copy.
|
/// Inline error copy.
|
||||||
pub danger_muted: Hsla,
|
pub danger_muted: Hsla,
|
||||||
/// Destructive-action button fill (danger plate, carries `on_accent`).
|
/// The destructive plate — white ink in both appearances.
|
||||||
pub danger_strong: Hsla,
|
pub danger_strong: Hsla,
|
||||||
/// Warning — amber.
|
/// Warnings.
|
||||||
pub warning: Hsla,
|
pub warning: Hsla,
|
||||||
/// Softer warning for secondary copy.
|
/// The translucent warning wash behind chips and banners.
|
||||||
pub warning_muted: Hsla,
|
pub warning_muted: Hsla,
|
||||||
/// Success / online — emerald.
|
/// Success and online presence.
|
||||||
pub success: Hsla,
|
pub success: Hsla,
|
||||||
/// Softer success for text on a success-tinted chip.
|
/// Text on success-tinted chips.
|
||||||
pub success_muted: Hsla,
|
pub success_muted: Hsla,
|
||||||
/// Working / streaming indicator — pink.
|
/// Working or streaming states.
|
||||||
pub busy: Hsla,
|
pub busy: Hsla,
|
||||||
|
|
||||||
// Bezel tokens: components
|
/// Hover over [`ThemeColors::surface_raised`].
|
||||||
/// Hover tone for an *opaque* raised pill.
|
|
||||||
pub surface_raised_hover: Hsla,
|
pub surface_raised_hover: Hsla,
|
||||||
/// Recessed band behind a header or footer strip.
|
/// A recessed header / footer strip.
|
||||||
pub band: Hsla,
|
pub band: Hsla,
|
||||||
/// Input plates.
|
/// Input backgrounds.
|
||||||
pub input_bg: Hsla,
|
pub input_bg: Hsla,
|
||||||
/// Text caret.
|
/// The text caret.
|
||||||
pub caret: Hsla,
|
pub caret: Hsla,
|
||||||
/// Where a dragged block would land.
|
/// The line marking where a drag will land.
|
||||||
pub drop_line: Hsla,
|
pub drop_line: Hsla,
|
||||||
/// Fill of the region a dragged pane would dock into.
|
/// The fill of the region a dragged pane would dock into.
|
||||||
pub drop_target: Hsla,
|
pub drop_target: Hsla,
|
||||||
|
|
||||||
// Bezel tokens: code & diff
|
|
||||||
/// Inline-code text.
|
/// Inline-code text.
|
||||||
pub code_text: Hsla,
|
pub code_text: Hsla,
|
||||||
/// Inline-code wash behind [`ThemeColors::code_text`].
|
/// The wash behind [`ThemeColors::code_text`].
|
||||||
pub code_wash: Hsla,
|
pub code_wash: Hsla,
|
||||||
/// Shared paint-only syntax palette.
|
/// The shared syntax palette, read at paint time.
|
||||||
pub syntax: SyntaxPalette,
|
pub syntax: SyntaxPalette,
|
||||||
/// Diff: added lines.
|
/// Diff additions.
|
||||||
pub diff_add: Hsla,
|
pub diff_add: Hsla,
|
||||||
/// Diff: deleted lines.
|
/// Diff deletions.
|
||||||
pub diff_del: Hsla,
|
pub diff_del: Hsla,
|
||||||
/// Diff: hunk-header wash.
|
/// The diff hunk-header wash.
|
||||||
pub diff_hunk_bg: Hsla,
|
pub diff_hunk_bg: Hsla,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ThemeColors {
|
impl Default for ThemeColors {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::dark()
|
palette::dark()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemeColors {
|
impl ThemeColors {
|
||||||
/// Returns the default colors for light themes.
|
|
||||||
pub(crate) fn light() -> Self {
|
|
||||||
palette::light()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the default colors for dark themes.
|
|
||||||
pub(crate) fn dark() -> Self {
|
|
||||||
palette::dark()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the default colors for an appearance.
|
/// Returns the default colors for an appearance.
|
||||||
pub fn for_appearance(appearance: ThemeMode) -> Self {
|
pub fn for_appearance(appearance: ThemeMode) -> Self {
|
||||||
palette::for_appearance(appearance)
|
palette::for_appearance(appearance)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A placeholder instance whose legacy fields the palette overwrites.
|
|
||||||
pub(crate) fn legacy_default() -> Self {
|
|
||||||
let transparent = gpui::transparent_black();
|
|
||||||
Self {
|
|
||||||
background: transparent,
|
|
||||||
surface_background: transparent,
|
|
||||||
elevated_surface_background: transparent,
|
|
||||||
panel_background: transparent,
|
|
||||||
overlay: transparent,
|
|
||||||
title_bar: transparent,
|
|
||||||
title_bar_inactive: transparent,
|
|
||||||
window_border: transparent,
|
|
||||||
|
|
||||||
border: transparent,
|
|
||||||
border_variant: transparent,
|
|
||||||
border_focused: transparent,
|
|
||||||
border_selected: transparent,
|
|
||||||
border_transparent: transparent,
|
|
||||||
border_disabled: transparent,
|
|
||||||
ring: transparent,
|
|
||||||
|
|
||||||
text: transparent,
|
|
||||||
text_muted: transparent,
|
|
||||||
text_placeholder: transparent,
|
|
||||||
text_accent: transparent,
|
|
||||||
text_danger: transparent,
|
|
||||||
text_warning: transparent,
|
|
||||||
|
|
||||||
icon: transparent,
|
|
||||||
icon_muted: transparent,
|
|
||||||
icon_accent: transparent,
|
|
||||||
|
|
||||||
element_foreground: transparent,
|
|
||||||
element_background: transparent,
|
|
||||||
element_hover: transparent,
|
|
||||||
element_active: transparent,
|
|
||||||
element_selected: transparent,
|
|
||||||
element_disabled: transparent,
|
|
||||||
|
|
||||||
secondary_foreground: transparent,
|
|
||||||
secondary_background: transparent,
|
|
||||||
secondary_hover: transparent,
|
|
||||||
secondary_active: transparent,
|
|
||||||
secondary_selected: transparent,
|
|
||||||
secondary_disabled: transparent,
|
|
||||||
|
|
||||||
danger_foreground: transparent,
|
|
||||||
danger_background: transparent,
|
|
||||||
danger_hover: transparent,
|
|
||||||
danger_active: transparent,
|
|
||||||
danger_selected: transparent,
|
|
||||||
danger_disabled: transparent,
|
|
||||||
|
|
||||||
warning_foreground: transparent,
|
|
||||||
warning_background: transparent,
|
|
||||||
warning_hover: transparent,
|
|
||||||
warning_active: transparent,
|
|
||||||
warning_selected: transparent,
|
|
||||||
warning_disabled: transparent,
|
|
||||||
|
|
||||||
ghost_element_background: transparent,
|
|
||||||
ghost_element_background_alt: transparent,
|
|
||||||
ghost_element_hover: transparent,
|
|
||||||
ghost_element_active: transparent,
|
|
||||||
ghost_element_selected: transparent,
|
|
||||||
ghost_element_disabled: transparent,
|
|
||||||
|
|
||||||
tab_background: transparent,
|
|
||||||
tab_foreground: transparent,
|
|
||||||
tab_hover_background: transparent,
|
|
||||||
tab_active_background: transparent,
|
|
||||||
tab_active_foreground: transparent,
|
|
||||||
|
|
||||||
scrollbar_thumb_background: transparent,
|
|
||||||
scrollbar_thumb_hover_background: transparent,
|
|
||||||
scrollbar_thumb_border: transparent,
|
|
||||||
scrollbar_track_background: transparent,
|
|
||||||
scrollbar_track_border: transparent,
|
|
||||||
|
|
||||||
drop_target_background: transparent,
|
|
||||||
cursor: transparent,
|
|
||||||
selection: transparent,
|
|
||||||
|
|
||||||
bg: transparent,
|
|
||||||
surface: transparent,
|
|
||||||
surface_raised: transparent,
|
|
||||||
surface_card: transparent,
|
|
||||||
surface_dialog: transparent,
|
|
||||||
surface_overlay: transparent,
|
|
||||||
|
|
||||||
border_faint: transparent,
|
|
||||||
border_strong: transparent,
|
|
||||||
|
|
||||||
text_faint: transparent,
|
|
||||||
text_dim: transparent,
|
|
||||||
|
|
||||||
solid: transparent,
|
|
||||||
on_solid: transparent,
|
|
||||||
|
|
||||||
accent: transparent,
|
|
||||||
accent_strong: transparent,
|
|
||||||
on_accent: transparent,
|
|
||||||
|
|
||||||
danger: transparent,
|
|
||||||
danger_muted: transparent,
|
|
||||||
danger_strong: transparent,
|
|
||||||
warning: transparent,
|
|
||||||
warning_muted: transparent,
|
|
||||||
success: transparent,
|
|
||||||
success_muted: transparent,
|
|
||||||
busy: transparent,
|
|
||||||
|
|
||||||
surface_raised_hover: transparent,
|
|
||||||
band: transparent,
|
|
||||||
input_bg: transparent,
|
|
||||||
caret: transparent,
|
|
||||||
drop_line: transparent,
|
|
||||||
drop_target: transparent,
|
|
||||||
|
|
||||||
code_text: transparent,
|
|
||||||
code_wash: transparent,
|
|
||||||
syntax: crate::syntax::SyntaxPalette::dark(transparent, transparent, transparent),
|
|
||||||
diff_add: transparent,
|
|
||||||
diff_del: transparent,
|
|
||||||
diff_hunk_bg: transparent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derive the legacy fields from the tokens.
|
|
||||||
pub(crate) fn sync_legacy(&mut self, appearance: ThemeMode) {
|
|
||||||
// Surfaces
|
|
||||||
self.background = self.bg;
|
|
||||||
self.surface_background = self.surface;
|
|
||||||
self.elevated_surface_background = self.surface_card;
|
|
||||||
self.panel_background = self.surface;
|
|
||||||
self.overlay = ink_for(appearance, 0.10);
|
|
||||||
self.title_bar = gpui::transparent_black();
|
|
||||||
self.title_bar_inactive = self.bg;
|
|
||||||
self.window_border = self.border;
|
|
||||||
|
|
||||||
// Borders
|
|
||||||
self.border_variant = self.border_faint;
|
|
||||||
self.border_focused = self.border_strong;
|
|
||||||
self.border_selected = self.border_strong;
|
|
||||||
self.border_transparent = gpui::transparent_black();
|
|
||||||
self.border_disabled = self.border_faint;
|
|
||||||
|
|
||||||
// Text
|
|
||||||
self.text_placeholder = self.text_faint;
|
|
||||||
self.text_accent = self.accent;
|
|
||||||
self.text_danger = self.danger;
|
|
||||||
self.text_warning = self.warning;
|
|
||||||
|
|
||||||
// Icons
|
|
||||||
self.icon = self.text_muted;
|
|
||||||
self.icon_muted = self.text_faint;
|
|
||||||
self.icon_accent = self.accent;
|
|
||||||
|
|
||||||
// Elements
|
|
||||||
self.element_foreground = self.on_solid;
|
|
||||||
self.element_background = self.solid;
|
|
||||||
self.element_selected = self.element_active;
|
|
||||||
self.element_disabled = wash_for(appearance, 0.06);
|
|
||||||
|
|
||||||
// Secondary
|
|
||||||
self.secondary_foreground = self.text;
|
|
||||||
self.secondary_background = self.surface_raised;
|
|
||||||
self.secondary_hover = self.element_hover;
|
|
||||||
self.secondary_active = self.element_active;
|
|
||||||
self.secondary_selected = self.element_active;
|
|
||||||
self.secondary_disabled = wash_for(appearance, 0.06);
|
|
||||||
|
|
||||||
// Danger
|
|
||||||
self.danger_foreground = self.on_accent;
|
|
||||||
self.danger_background = self.danger_strong;
|
|
||||||
self.danger_hover = plate_hover_for(appearance, self.danger_strong);
|
|
||||||
self.danger_active = plate_active_for(appearance, self.danger_strong);
|
|
||||||
self.danger_selected = self.danger_muted;
|
|
||||||
self.danger_disabled = wash_for(appearance, 0.06);
|
|
||||||
|
|
||||||
// Warning
|
|
||||||
// warning_muted is a translucent amber wash, so its ink is the amber
|
|
||||||
// token itself, and its state washes deepen the plate.
|
|
||||||
self.warning_foreground = self.warning;
|
|
||||||
self.warning_background = self.warning_muted;
|
|
||||||
self.warning_hover = plate_hover_for(appearance, self.warning_muted);
|
|
||||||
self.warning_active = plate_active_for(appearance, self.warning_muted);
|
|
||||||
self.warning_selected = self.element_active;
|
|
||||||
self.warning_disabled = wash_for(appearance, 0.06);
|
|
||||||
|
|
||||||
// Ghost
|
|
||||||
self.ghost_element_background = gpui::transparent_black();
|
|
||||||
self.ghost_element_background_alt = self.surface_raised;
|
|
||||||
self.ghost_element_hover = self.element_hover;
|
|
||||||
self.ghost_element_active = self.element_active;
|
|
||||||
self.ghost_element_selected = self.element_active;
|
|
||||||
self.ghost_element_disabled = wash_for(appearance, 0.06);
|
|
||||||
|
|
||||||
// Tabs
|
|
||||||
self.tab_background = self.surface;
|
|
||||||
self.tab_foreground = self.text_muted;
|
|
||||||
self.tab_hover_background = self.element_hover;
|
|
||||||
self.tab_active_background = self.element_active;
|
|
||||||
self.tab_active_foreground = self.text;
|
|
||||||
|
|
||||||
// Scrollbar
|
|
||||||
self.scrollbar_thumb_background = ink_for(appearance, 0.20);
|
|
||||||
self.scrollbar_thumb_hover_background = ink_for(appearance, 0.30);
|
|
||||||
self.scrollbar_thumb_border = gpui::transparent_black();
|
|
||||||
self.scrollbar_track_background = gpui::transparent_black();
|
|
||||||
self.scrollbar_track_border = self.border_faint;
|
|
||||||
|
|
||||||
// Interactive
|
|
||||||
self.drop_target_background = self.drop_target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Best-effort re-derivation of the tokens from a legacy palette, for
|
|
||||||
/// old-schema JSON themes loaded through the registry. Fields with no
|
|
||||||
/// legacy source (`success`, `busy`, the syntax palette, the diff colors)
|
|
||||||
/// keep the built-in palette's value for `appearance`.
|
|
||||||
pub fn rederive_tokens(&mut self, appearance: ThemeMode) {
|
|
||||||
let base = Self::for_appearance(appearance);
|
|
||||||
self.success = base.success;
|
|
||||||
self.success_muted = base.success_muted;
|
|
||||||
self.busy = base.busy;
|
|
||||||
self.syntax = base.syntax;
|
|
||||||
self.diff_add = base.diff_add;
|
|
||||||
self.diff_del = base.diff_del;
|
|
||||||
self.diff_hunk_bg = base.diff_hunk_bg;
|
|
||||||
|
|
||||||
self.bg = self.background;
|
|
||||||
self.surface = self.surface_background;
|
|
||||||
self.surface_raised = self.elevated_surface_background;
|
|
||||||
self.surface_card = self.elevated_surface_background;
|
|
||||||
self.surface_dialog = self.elevated_surface_background;
|
|
||||||
self.surface_overlay = self.elevated_surface_background;
|
|
||||||
|
|
||||||
self.border_faint = self.border_variant;
|
|
||||||
self.border_strong = self.border_focused;
|
|
||||||
|
|
||||||
self.text_faint = self.text_placeholder;
|
|
||||||
self.text_dim = self.text_muted;
|
|
||||||
|
|
||||||
self.solid = self.element_background;
|
|
||||||
self.on_solid = self.element_foreground;
|
|
||||||
|
|
||||||
self.accent = self.text_accent;
|
|
||||||
self.accent_strong = self.element_background;
|
|
||||||
self.on_accent = self.element_foreground;
|
|
||||||
|
|
||||||
self.danger = self.text_danger;
|
|
||||||
self.danger_muted = self.text_danger;
|
|
||||||
self.danger_strong = self.danger_background;
|
|
||||||
|
|
||||||
self.warning = self.text_warning;
|
|
||||||
self.warning_muted = self.warning_background;
|
|
||||||
|
|
||||||
self.surface_raised_hover = self.element_hover;
|
|
||||||
self.band = self.ghost_element_background_alt;
|
|
||||||
self.input_bg = self.surface_background;
|
|
||||||
self.caret = self.cursor;
|
|
||||||
self.drop_line = self.border_focused;
|
|
||||||
self.drop_target = self.drop_target_background;
|
|
||||||
|
|
||||||
self.code_text = self.text;
|
|
||||||
self.code_wash = self.ghost_element_hover;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-89
@@ -1,7 +1,5 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use appearance::AppearanceMode;
|
|
||||||
use gpui::{App, Global, Pixels, SharedString, Window, px};
|
use gpui::{App, Global, Pixels, SharedString, Window, px};
|
||||||
|
|
||||||
mod brand;
|
mod brand;
|
||||||
@@ -13,14 +11,13 @@ mod notification;
|
|||||||
mod paint;
|
mod paint;
|
||||||
mod palette;
|
mod palette;
|
||||||
mod platform_kind;
|
mod platform_kind;
|
||||||
mod registry;
|
|
||||||
mod scale;
|
|
||||||
mod scrollbar_mode;
|
mod scrollbar_mode;
|
||||||
mod syntax;
|
mod syntax;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
||||||
pub mod appearance;
|
pub mod appearance;
|
||||||
|
|
||||||
|
pub use appearance::AppearanceMode;
|
||||||
pub use brand::{BASE_COLORS, Brand, Ink, TextInk, Tint};
|
pub use brand::{BASE_COLORS, Brand, Ink, TextInk, Tint};
|
||||||
pub use color::{
|
pub use color::{
|
||||||
contrast_ratio, flatten, grey, hsl_to_rgb, lightness, mix, neutral, oklch, oklch_to_srgb,
|
contrast_ratio, flatten, grey, hsl_to_rgb, lightness, mix, neutral, oklch, oklch_to_srgb,
|
||||||
@@ -38,8 +35,6 @@ pub use paint::{
|
|||||||
plate_active, plate_hover, scrim, set_current_appearance, surface_shadows, wash,
|
plate_active, plate_hover, scrim, set_current_appearance, surface_shadows, wash,
|
||||||
};
|
};
|
||||||
pub use platform_kind::PlatformKind;
|
pub use platform_kind::PlatformKind;
|
||||||
pub use registry::*;
|
|
||||||
pub use scale::*;
|
|
||||||
pub use scrollbar_mode::*;
|
pub use scrollbar_mode::*;
|
||||||
pub use syntax::{HighlightKind, SyntaxPalette};
|
pub use syntax::{HighlightKind, SyntaxPalette};
|
||||||
pub use theme::*;
|
pub use theme::*;
|
||||||
@@ -67,13 +62,11 @@ pub const TABBAR_HEIGHT: Pixels = px(36.0);
|
|||||||
pub const SIDEBAR_WIDTH: Pixels = px(240.);
|
pub const SIDEBAR_WIDTH: Pixels = px(240.);
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
registry::init(cx);
|
|
||||||
appearance::init(AppearanceMode::System, cx);
|
appearance::init(AppearanceMode::System, cx);
|
||||||
Theme::sync_scrollbar_appearance(cx);
|
Theme::sync_scrollbar_appearance(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An alternative palette source. Runs first on every appearance install, so
|
/// An alternative palette source.
|
||||||
/// an override survives a light/dark switch.
|
|
||||||
pub type PaletteBuilder = fn(ThemeMode) -> ThemeColors;
|
pub type PaletteBuilder = fn(ThemeMode) -> ThemeColors;
|
||||||
|
|
||||||
struct GlobalPaletteBuilder(Option<PaletteBuilder>);
|
struct GlobalPaletteBuilder(Option<PaletteBuilder>);
|
||||||
@@ -82,13 +75,8 @@ impl Global for GlobalPaletteBuilder {}
|
|||||||
|
|
||||||
/// Install a brand or an alternative palette on the app.
|
/// Install a brand or an alternative palette on the app.
|
||||||
pub trait AppExt {
|
pub trait AppExt {
|
||||||
/// Install a brand and repaint. Call before [`appearance::init`] to have
|
|
||||||
/// the first frame already branded.
|
|
||||||
fn set_brand(&mut self, brand: Brand);
|
fn set_brand(&mut self, brand: Brand);
|
||||||
/// Install an alternative palette builder and repaint. The builder runs
|
|
||||||
/// first on every appearance install.
|
|
||||||
fn set_palette(&mut self, build: PaletteBuilder);
|
fn set_palette(&mut self, build: PaletteBuilder);
|
||||||
/// Change the appearance preference (`System`, `Light` or `Dark`).
|
|
||||||
fn set_appearance_mode(&mut self, mode: AppearanceMode);
|
fn set_appearance_mode(&mut self, mode: AppearanceMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,13 +107,6 @@ fn reinstall(cx: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mirror the active coop theme into the `gpui-base` global theme.
|
/// Mirror the active coop theme into the `gpui-base` global theme.
|
||||||
///
|
|
||||||
/// Base paints a few things from its own tokens -- the focus ring, the wash
|
|
||||||
/// behind selected text, scrollbars, and overlay backdrops -- so the two
|
|
||||||
/// globals have to agree or those details drift away from the palette.
|
|
||||||
///
|
|
||||||
/// This is a no-op before the coop theme global exists; [`Theme::change`] is
|
|
||||||
/// the authoritative hook that keeps the projection current.
|
|
||||||
pub fn sync_base(cx: &mut App) {
|
pub fn sync_base(cx: &mut App) {
|
||||||
let Some(theme) = cx.try_global::<Theme>() else {
|
let Some(theme) = cx.try_global::<Theme>() else {
|
||||||
return;
|
return;
|
||||||
@@ -163,8 +144,6 @@ pub fn sync_base(cx: &mut App) {
|
|||||||
tokens.accent = colors.element_hover;
|
tokens.accent = colors.element_hover;
|
||||||
tokens.accent_foreground = colors.text;
|
tokens.accent_foreground = colors.text;
|
||||||
tokens.destructive = colors.danger_strong;
|
tokens.destructive = colors.danger_strong;
|
||||||
// Bezel's destructive pairing: white ink on the red plate in both
|
|
||||||
// appearances.
|
|
||||||
tokens.destructive_foreground = gpui::white();
|
tokens.destructive_foreground = gpui::white();
|
||||||
tokens.border = colors.border;
|
tokens.border = colors.border;
|
||||||
tokens.input = colors.border;
|
tokens.input = colors.border;
|
||||||
@@ -185,41 +164,14 @@ impl ActiveTheme for App {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
/// Theme colors
|
|
||||||
pub colors: ThemeColors,
|
pub colors: ThemeColors,
|
||||||
|
|
||||||
/// Theme family
|
|
||||||
pub theme: Rc<ThemeFamily>,
|
|
||||||
|
|
||||||
/// The appearance of the theme (light or dark).
|
|
||||||
pub mode: ThemeMode,
|
pub mode: ThemeMode,
|
||||||
|
|
||||||
/// The font family for the application.
|
|
||||||
pub font_family: SharedString,
|
pub font_family: SharedString,
|
||||||
|
|
||||||
/// The family prose is set in — what markdown shapes a document's body
|
|
||||||
/// with. Defaults to `font_family`.
|
|
||||||
pub font_body: SharedString,
|
pub font_body: SharedString,
|
||||||
|
|
||||||
/// Monospace family for code and terminal.
|
|
||||||
pub font_mono: SharedString,
|
pub font_mono: SharedString,
|
||||||
|
|
||||||
/// The root font size for the application, default is 15px.
|
|
||||||
pub font_size: Pixels,
|
pub font_size: Pixels,
|
||||||
|
|
||||||
/// Radius for the general elements.
|
|
||||||
pub radius: Pixels,
|
|
||||||
|
|
||||||
/// Radius for the large elements, e.g.: dialog, notification.
|
|
||||||
pub radius_lg: Pixels,
|
|
||||||
|
|
||||||
/// Enable shadow for the general elements. default is true
|
|
||||||
pub shadow: bool,
|
pub shadow: bool,
|
||||||
|
|
||||||
/// Show the scrollbar mode, default: scrolling
|
|
||||||
pub scrollbar_mode: ScrollbarMode,
|
pub scrollbar_mode: ScrollbarMode,
|
||||||
|
|
||||||
/// Notification settings
|
|
||||||
pub notification: NotificationSettings,
|
pub notification: NotificationSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,26 +221,13 @@ impl Theme {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a new theme to the application.
|
|
||||||
pub fn apply_theme(new_theme: Rc<ThemeFamily>, window: Option<&mut Window>, cx: &mut App) {
|
|
||||||
let theme = cx.global_mut::<Theme>();
|
|
||||||
let mode = theme.mode;
|
|
||||||
// Update the theme
|
|
||||||
theme.theme = new_theme;
|
|
||||||
// Emit a theme change event
|
|
||||||
Self::change(mode, window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the app's appearance
|
/// Change the app's appearance
|
||||||
pub fn change<M>(mode: M, window: Option<&mut Window>, cx: &mut App)
|
pub fn change<M>(mode: M, window: Option<&mut Window>, cx: &mut App)
|
||||||
where
|
where
|
||||||
M: Into<ThemeMode>,
|
M: Into<ThemeMode>,
|
||||||
{
|
{
|
||||||
if !cx.has_global::<Theme>() {
|
if !cx.has_global::<Theme>() {
|
||||||
let default_theme = ThemeFamily::default();
|
cx.set_global(Theme::default());
|
||||||
let theme = Theme::from(default_theme);
|
|
||||||
|
|
||||||
cx.set_global(theme);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mode = mode.into();
|
let mode = mode.into();
|
||||||
@@ -297,23 +236,13 @@ impl Theme {
|
|||||||
.and_then(|builder| builder.0);
|
.and_then(|builder| builder.0);
|
||||||
let mut colors = match builder {
|
let mut colors = match builder {
|
||||||
Some(build) => build(mode),
|
Some(build) => build(mode),
|
||||||
None => {
|
None => ThemeColors::for_appearance(mode),
|
||||||
let family = Theme::global_mut(cx).theme.clone();
|
|
||||||
if mode.is_dark() {
|
|
||||||
*family.dark()
|
|
||||||
} else {
|
|
||||||
*family.light()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A default brand changes nothing, so a JSON theme's own colors are
|
// A default brand changes nothing; anything else rotates the tokens.
|
||||||
// left alone; anything else rotates the tokens and re-derives the
|
|
||||||
// legacy fields from them.
|
|
||||||
let installed_brand = brand::installed(cx);
|
let installed_brand = brand::installed(cx);
|
||||||
if !installed_brand.is_default() {
|
if !installed_brand.is_default() {
|
||||||
installed_brand.apply(mode, &mut colors);
|
installed_brand.apply(mode, &mut colors);
|
||||||
colors.sync_legacy(mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let theme = Theme::global_mut(cx);
|
let theme = Theme::global_mut(cx);
|
||||||
@@ -338,8 +267,8 @@ impl Theme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ThemeFamily> for Theme {
|
impl Default for Theme {
|
||||||
fn from(family: ThemeFamily) -> Self {
|
fn default() -> Self {
|
||||||
let platform = PlatformKind::platform();
|
let platform = PlatformKind::platform();
|
||||||
let mode = ThemeMode::default();
|
let mode = ThemeMode::default();
|
||||||
|
|
||||||
@@ -349,25 +278,16 @@ impl From<ThemeFamily> for Theme {
|
|||||||
_ => ".SystemUIFont",
|
_ => ".SystemUIFont",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define the theme colors based on the appearance
|
Self {
|
||||||
let colors = match mode {
|
|
||||||
ThemeMode::Light => family.light(),
|
|
||||||
ThemeMode::Dark => family.dark(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Theme {
|
|
||||||
font_size: px(15.),
|
font_size: px(15.),
|
||||||
font_family: font_family.into(),
|
font_family: font_family.into(),
|
||||||
font_body: font_family.into(),
|
font_body: font_family.into(),
|
||||||
font_mono: system_mono().into(),
|
font_mono: system_mono().into(),
|
||||||
radius: px(layout::button_radius()),
|
|
||||||
radius_lg: px(layout::surface_radius()),
|
|
||||||
shadow: true,
|
shadow: true,
|
||||||
scrollbar_mode: ScrollbarMode::default(),
|
scrollbar_mode: ScrollbarMode::default(),
|
||||||
notification: NotificationSettings::default(),
|
notification: NotificationSettings::default(),
|
||||||
mode,
|
mode,
|
||||||
colors: *colors,
|
colors: ThemeColors::for_appearance(mode),
|
||||||
theme: Rc::new(family),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-84
@@ -1,63 +1,44 @@
|
|||||||
//! The two concrete palettes: dark and light. Ported from bezel
|
|
||||||
//! (`crates/theme/src/theme/palettes.rs`, MIT License, Copyright (c) 2026
|
|
||||||
//! bezel contributors).
|
|
||||||
//!
|
|
||||||
//! Light is designed, not inverted — mirroring lightness reverses surface
|
|
||||||
//! order, elevation and accent contrast. Each light text token lands within
|
|
||||||
//! ~0.5 of its dark counterpart's ratio; `tests/contrast.rs` asserts it.
|
|
||||||
|
|
||||||
use gpui::{hsla, rgb};
|
use gpui::{hsla, rgb};
|
||||||
|
|
||||||
use crate::paint::{band_for, hairline_for};
|
use crate::paint::{band_for, hairline_for};
|
||||||
use crate::syntax::SyntaxPalette;
|
use crate::syntax::SyntaxPalette;
|
||||||
use crate::{Ink, TextInk, ThemeColors, ThemeMode, color};
|
use crate::{Ink, TextInk, ThemeColors, ThemeMode, color};
|
||||||
|
|
||||||
/// Build the dark palette. The surface tones are sampled from bezel's
|
|
||||||
/// reference screenshots: main panel `#060606`, shell/sidebar `#0d0d0d`.
|
|
||||||
pub(crate) fn dark() -> ThemeColors {
|
pub(crate) fn dark() -> ThemeColors {
|
||||||
let mut colors = ThemeColors {
|
let warning = color::oklch(0.828, 0.189, 84.429);
|
||||||
// ---- neutral surfaces ----
|
ThemeColors {
|
||||||
bg: color::grey(6), // main panel — #060606
|
bg: color::grey(6),
|
||||||
surface: color::grey(13), // shell / sidebar — #0d0d0d
|
surface: color::grey(13),
|
||||||
surface_raised: color::neutral(0.235),
|
surface_raised: color::neutral(0.235),
|
||||||
surface_card: color::grey(0x0e),
|
surface_card: color::grey(0x0e),
|
||||||
surface_dialog: color::grey(0x10),
|
surface_dialog: color::grey(0x10),
|
||||||
surface_overlay: color::grey(0x16),
|
surface_overlay: color::grey(0x16),
|
||||||
// The reference app's `--color-hover`, pure ink: white at 8%.
|
|
||||||
element_hover: hsla(0.0, 0.0, 1.0, 0.08),
|
element_hover: hsla(0.0, 0.0, 1.0, 0.08),
|
||||||
// `--color-active`, a rung above the hover: white at 12%.
|
|
||||||
element_active: hsla(0.0, 0.0, 1.0, 0.12),
|
element_active: hsla(0.0, 0.0, 1.0, 0.12),
|
||||||
|
|
||||||
// ---- hairlines ----
|
|
||||||
border_faint: hairline_for(ThemeMode::Dark, 0.06),
|
border_faint: hairline_for(ThemeMode::Dark, 0.06),
|
||||||
border: hairline_for(ThemeMode::Dark, 0.08),
|
border: hairline_for(ThemeMode::Dark, 0.08),
|
||||||
border_strong: hairline_for(ThemeMode::Dark, 0.14),
|
border_strong: hairline_for(ThemeMode::Dark, 0.14),
|
||||||
|
|
||||||
// ---- text ----
|
|
||||||
text: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.text),
|
text: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.text),
|
||||||
text_muted: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.muted),
|
text_muted: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.muted),
|
||||||
text_faint: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.faint),
|
text_faint: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.faint),
|
||||||
text_dim: color::grey(0x98),
|
text_dim: color::grey(0x98),
|
||||||
|
|
||||||
// ---- high-contrast solid ----
|
|
||||||
solid: color::neutral(0.922),
|
solid: color::neutral(0.922),
|
||||||
on_solid: color::grey(0x0e),
|
on_solid: color::grey(0x0e),
|
||||||
|
|
||||||
// ---- accents, neutral by default ----
|
|
||||||
accent: color::neutral(0.673),
|
accent: color::neutral(0.673),
|
||||||
accent_strong: color::neutral(0.922),
|
accent_strong: color::neutral(0.922),
|
||||||
on_accent: color::grey(0x0e),
|
on_accent: color::grey(0x0e),
|
||||||
|
|
||||||
// ---- semantic ----
|
danger: color::oklch(0.704, 0.191, 22.216),
|
||||||
danger: color::oklch(0.704, 0.191, 22.216), // red-400
|
danger_muted: color::oklch(0.808, 0.114, 19.571),
|
||||||
danger_muted: color::oklch(0.808, 0.114, 19.571), // red-300
|
|
||||||
danger_strong: color::oklch(0.58, 0.16, 25.0),
|
danger_strong: color::oklch(0.58, 0.16, 25.0),
|
||||||
warning: color::oklch(0.828, 0.189, 84.429), // amber-400
|
success: color::oklch(0.765, 0.177, 163.223),
|
||||||
success: color::oklch(0.765, 0.177, 163.223), // emerald-400
|
success_muted: color::oklch(0.845, 0.143, 164.978),
|
||||||
success_muted: color::oklch(0.845, 0.143, 164.978), // emerald-300
|
busy: color::oklch(0.718, 0.202, 349.761),
|
||||||
busy: color::oklch(0.718, 0.202, 349.761), // pink-400
|
|
||||||
|
|
||||||
// ---- components ----
|
|
||||||
surface_raised_hover: color::neutral(0.29),
|
surface_raised_hover: color::neutral(0.29),
|
||||||
band: band_for(ThemeMode::Dark),
|
band: band_for(ThemeMode::Dark),
|
||||||
input_bg: hsla(0.0, 0.0, 1.0, 0.03),
|
input_bg: hsla(0.0, 0.0, 1.0, 0.03),
|
||||||
@@ -68,7 +49,6 @@ pub(crate) fn dark() -> ThemeColors {
|
|||||||
drop_line: hairline_for(ThemeMode::Dark, 0.35),
|
drop_line: hairline_for(ThemeMode::Dark, 0.35),
|
||||||
drop_target: hsla(0.0, 0.0, 1.0, 0.10),
|
drop_target: hsla(0.0, 0.0, 1.0, 0.10),
|
||||||
|
|
||||||
// ---- code & diff ----
|
|
||||||
code_text: color::neutral(0.94),
|
code_text: color::neutral(0.94),
|
||||||
code_wash: hsla(0.0, 0.0, 1.0, 0.08),
|
code_wash: hsla(0.0, 0.0, 1.0, 0.08),
|
||||||
syntax: SyntaxPalette::dark(
|
syntax: SyntaxPalette::dark(
|
||||||
@@ -76,82 +56,54 @@ pub(crate) fn dark() -> ThemeColors {
|
|||||||
color::neutral(0.60),
|
color::neutral(0.60),
|
||||||
color::oklch(0.704, 0.191, 22.216),
|
color::oklch(0.704, 0.191, 22.216),
|
||||||
),
|
),
|
||||||
diff_add: color::oklch(0.765, 0.177, 163.223), // emerald-400
|
diff_add: color::oklch(0.765, 0.177, 163.223),
|
||||||
diff_del: color::oklch(0.704, 0.191, 22.216), // red-400
|
diff_del: color::oklch(0.704, 0.191, 22.216),
|
||||||
diff_hunk_bg: hsla(0.6, 0.35, 0.6, 0.05),
|
diff_hunk_bg: hsla(0.6, 0.35, 0.6, 0.05),
|
||||||
|
|
||||||
..ThemeColors::legacy_default()
|
warning,
|
||||||
};
|
warning_muted: warning.alpha(0.16),
|
||||||
// Same warning-chip rule as light: a translucent amber wash under the
|
}
|
||||||
// `warning` token, not an opaque plate.
|
|
||||||
colors.warning_muted = colors.warning.alpha(0.16);
|
|
||||||
colors.sync_legacy(ThemeMode::Dark);
|
|
||||||
colors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the light palette. Neutrals are the same oklch scale read from the
|
|
||||||
/// other end, but the *roles* are reassigned rather than mirrored: content
|
|
||||||
/// plane white, chrome grey, raised surfaces white-plus-shadow. Text tones
|
|
||||||
/// reproduce the dark theme's contrast ratios, and accents drop from the 400
|
|
||||||
/// to the 600 step at identical hue so they clear WCAG AA on white.
|
|
||||||
pub(crate) fn light() -> ThemeColors {
|
pub(crate) fn light() -> ThemeColors {
|
||||||
let mut colors = ThemeColors {
|
let warning = color::oklch(0.555, 0.163, 48.998);
|
||||||
// ---- neutral surfaces ----
|
ThemeColors {
|
||||||
bg: color::grey(0xff), // main panel — clean white
|
bg: color::grey(0xff),
|
||||||
// Deeper than ~neutral-100 looks on paper: the content card is pure
|
|
||||||
// white and sits *inside* this surface, so too small a step leaves the
|
|
||||||
// whole window one flat sheet with a hairline drawn on it.
|
|
||||||
surface: color::neutral(0.968),
|
surface: color::neutral(0.968),
|
||||||
// A real grey, NOT white: this is the opaque-plate tone, and those sit
|
|
||||||
// directly on the white content plane with no border or shadow to save
|
|
||||||
// them.
|
|
||||||
surface_raised: color::neutral(0.940),
|
surface_raised: color::neutral(0.940),
|
||||||
surface_card: color::grey(0xff),
|
surface_card: color::grey(0xff),
|
||||||
surface_dialog: color::grey(0xff),
|
surface_dialog: color::grey(0xff),
|
||||||
surface_overlay: color::grey(0xff),
|
surface_overlay: color::grey(0xff),
|
||||||
// The same token in light: black at 4%.
|
|
||||||
element_hover: hsla(0.0, 0.0, 0.0, 0.04),
|
element_hover: hsla(0.0, 0.0, 0.0, 0.04),
|
||||||
// The same rung in light: black at 6%.
|
|
||||||
element_active: hsla(0.0, 0.0, 0.0, 0.06),
|
element_active: hsla(0.0, 0.0, 0.0, 0.06),
|
||||||
|
|
||||||
// ---- hairlines ----
|
|
||||||
border_faint: hairline_for(ThemeMode::Light, 0.06),
|
border_faint: hairline_for(ThemeMode::Light, 0.06),
|
||||||
border: hairline_for(ThemeMode::Light, 0.08),
|
border: hairline_for(ThemeMode::Light, 0.08),
|
||||||
border_strong: hairline_for(ThemeMode::Light, 0.14),
|
border_strong: hairline_for(ThemeMode::Light, 0.14),
|
||||||
|
|
||||||
// ---- text ----
|
|
||||||
text: TextInk::color(ThemeMode::Light, Ink::APPKIT.light.text),
|
text: TextInk::color(ThemeMode::Light, Ink::APPKIT.light.text),
|
||||||
// One rung darker than AppKit's secondary label: sidebar and list
|
|
||||||
// labels sit on the chrome grey and need ~5:1, not the system ~4:1.
|
|
||||||
text_muted: color::neutral(0.45),
|
text_muted: color::neutral(0.45),
|
||||||
text_faint: TextInk::color(ThemeMode::Light, Ink::APPKIT.light.faint),
|
text_faint: TextInk::color(ThemeMode::Light, Ink::APPKIT.light.faint),
|
||||||
text_dim: color::neutral(0.50),
|
text_dim: color::neutral(0.50),
|
||||||
|
|
||||||
// ---- high-contrast solid ----
|
|
||||||
solid: color::neutral(0.205),
|
solid: color::neutral(0.205),
|
||||||
on_solid: color::neutral(0.985),
|
on_solid: color::neutral(0.985),
|
||||||
|
|
||||||
// ---- accents, neutral by default ----
|
|
||||||
accent: color::neutral(0.511),
|
accent: color::neutral(0.511),
|
||||||
accent_strong: color::neutral(0.205),
|
accent_strong: color::neutral(0.205),
|
||||||
on_accent: color::neutral(0.985),
|
on_accent: color::neutral(0.985),
|
||||||
|
|
||||||
// ---- semantic ----
|
danger: color::oklch(0.577, 0.245, 27.325),
|
||||||
danger: color::oklch(0.577, 0.245, 27.325), // red-600
|
danger_muted: color::oklch(0.505, 0.213, 27.518),
|
||||||
danger_muted: color::oklch(0.505, 0.213, 27.518), // red-700
|
|
||||||
danger_strong: color::oklch(0.51, 0.20, 25.0),
|
danger_strong: color::oklch(0.51, 0.20, 25.0),
|
||||||
warning: color::oklch(0.555, 0.163, 48.998), // amber-700 — carries 12px text
|
warning,
|
||||||
success: color::oklch(0.596, 0.145, 163.225), // emerald-600
|
warning_muted: warning.alpha(0.10),
|
||||||
success_muted: color::oklch(0.508, 0.118, 165.612), // emerald-700
|
success: color::oklch(0.596, 0.145, 163.225),
|
||||||
busy: color::oklch(0.592, 0.249, 0.584), // pink-600
|
success_muted: color::oklch(0.508, 0.118, 165.612),
|
||||||
|
busy: color::oklch(0.592, 0.249, 0.584),
|
||||||
|
|
||||||
// ---- components ----
|
|
||||||
// Opaque pills darken on hover here rather than brighten — same
|
|
||||||
// "brighten the plate, don't wash it out" rule, read the other way.
|
|
||||||
surface_raised_hover: color::neutral(0.900),
|
surface_raised_hover: color::neutral(0.900),
|
||||||
band: band_for(ThemeMode::Light),
|
band: band_for(ThemeMode::Light),
|
||||||
// Inputs read as one rung below the chrome grey — the old scale's
|
|
||||||
// step-3 input plate. Pure white vanished against the content plane.
|
|
||||||
input_bg: color::neutral(0.950),
|
input_bg: color::neutral(0.950),
|
||||||
selection: rgb(0xb3d7ff).into(),
|
selection: rgb(0xb3d7ff).into(),
|
||||||
cursor: color::neutral(0.205),
|
cursor: color::neutral(0.205),
|
||||||
@@ -160,7 +112,6 @@ pub(crate) fn light() -> ThemeColors {
|
|||||||
drop_line: hairline_for(ThemeMode::Light, 0.35),
|
drop_line: hairline_for(ThemeMode::Light, 0.35),
|
||||||
drop_target: hsla(0.0, 0.0, 0.0, 0.06),
|
drop_target: hsla(0.0, 0.0, 0.0, 0.06),
|
||||||
|
|
||||||
// ---- code & diff ----
|
|
||||||
code_text: color::neutral(0.18),
|
code_text: color::neutral(0.18),
|
||||||
code_wash: hsla(0.0, 0.0, 0.0, 0.06),
|
code_wash: hsla(0.0, 0.0, 0.0, 0.06),
|
||||||
syntax: SyntaxPalette::light(
|
syntax: SyntaxPalette::light(
|
||||||
@@ -168,18 +119,10 @@ pub(crate) fn light() -> ThemeColors {
|
|||||||
color::neutral(0.48),
|
color::neutral(0.48),
|
||||||
color::oklch(0.505, 0.213, 27.518),
|
color::oklch(0.505, 0.213, 27.518),
|
||||||
),
|
),
|
||||||
diff_add: color::oklch(0.596, 0.145, 163.225), // emerald-600
|
diff_add: color::oklch(0.596, 0.145, 163.225),
|
||||||
diff_del: color::oklch(0.577, 0.245, 27.325), // red-600
|
diff_del: color::oklch(0.577, 0.245, 27.325),
|
||||||
diff_hunk_bg: hsla(0.6, 0.35, 0.35, 0.07),
|
diff_hunk_bg: hsla(0.6, 0.35, 0.35, 0.07),
|
||||||
|
}
|
||||||
..ThemeColors::legacy_default()
|
|
||||||
};
|
|
||||||
// The warning chip is a translucent amber wash, not an opaque plate: the
|
|
||||||
// 700-800 amber reads as muddy brown, and a wash lets the label ride on
|
|
||||||
// the `warning` token in both appearances.
|
|
||||||
colors.warning_muted = colors.warning.alpha(0.10);
|
|
||||||
colors.sync_legacy(ThemeMode::Light);
|
|
||||||
colors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn for_appearance(appearance: ThemeMode) -> ThemeColors {
|
pub(crate) fn for_appearance(appearance: ThemeMode) -> ThemeColors {
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
|
||||||
use gpui::{App, AppContext, AssetSource, Context, Entity, Global, SharedString};
|
|
||||||
|
|
||||||
use crate::{ThemeFamily, ThemeMode};
|
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
|
||||||
ThemeRegistry::set_global(cx.new(ThemeRegistry::new), cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GlobalThemeRegistry(Entity<ThemeRegistry>);
|
|
||||||
|
|
||||||
impl Global for GlobalThemeRegistry {}
|
|
||||||
|
|
||||||
pub struct ThemeRegistry {
|
|
||||||
/// Map of theme names to theme families
|
|
||||||
themes: HashMap<SharedString, Rc<ThemeFamily>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeRegistry {
|
|
||||||
/// Retrieve the global theme registry state
|
|
||||||
pub fn global(cx: &App) -> Entity<Self> {
|
|
||||||
cx.global::<GlobalThemeRegistry>().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the global theme registry instance
|
|
||||||
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
|
|
||||||
cx.set_global(GlobalThemeRegistry(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new theme registry instance
|
|
||||||
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
|
|
||||||
let mut themes = HashMap::new();
|
|
||||||
let asset = cx.asset_source();
|
|
||||||
|
|
||||||
if let Ok(paths) = asset.list("themes") {
|
|
||||||
for path in paths.into_iter() {
|
|
||||||
match Self::load(&path, asset) {
|
|
||||||
Ok(theme) => {
|
|
||||||
themes.insert(path, Rc::new(theme));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to load theme: {path}. Error: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { themes }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a theme from the asset source.
|
|
||||||
fn load(path: &str, asset: &Arc<dyn AssetSource>) -> Result<ThemeFamily, Error> {
|
|
||||||
// Load the theme file from the assets
|
|
||||||
let content = asset.load(path)?.context("Theme not found")?;
|
|
||||||
|
|
||||||
// Parse the JSON content into a Theme Family struct, and re-derive the
|
|
||||||
// tokens from the legacy fields the file carries.
|
|
||||||
let mut theme: ThemeFamily = serde_json::from_slice(&content)?;
|
|
||||||
theme.light.rederive_tokens(ThemeMode::Light);
|
|
||||||
theme.dark.rederive_tokens(ThemeMode::Dark);
|
|
||||||
|
|
||||||
Ok(theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the map of themes.
|
|
||||||
pub fn themes(&self) -> &HashMap<SharedString, Rc<ThemeFamily>> {
|
|
||||||
&self.themes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
use gpui::Hsla;
|
|
||||||
|
|
||||||
/// A collection of colors that are used to style the UI.
|
|
||||||
///
|
|
||||||
/// Each step has a semantic meaning, and is used to style different parts of the UI.
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
pub struct ColorScaleStep(usize);
|
|
||||||
|
|
||||||
impl ColorScaleStep {
|
|
||||||
pub const EIGHT: Self = Self(8);
|
|
||||||
pub const ELEVEN: Self = Self(11);
|
|
||||||
pub const FIVE: Self = Self(5);
|
|
||||||
pub const FOUR: Self = Self(4);
|
|
||||||
pub const NINE: Self = Self(9);
|
|
||||||
pub const ONE: Self = Self(1);
|
|
||||||
pub const SEVEN: Self = Self(7);
|
|
||||||
pub const SIX: Self = Self(6);
|
|
||||||
pub const TEN: Self = Self(10);
|
|
||||||
pub const THREE: Self = Self(3);
|
|
||||||
pub const TWELVE: Self = Self(12);
|
|
||||||
pub const TWO: Self = Self(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scale of colors for a given [`ColorScaleSet`].
|
|
||||||
///
|
|
||||||
/// Each [`ColorScale`] contains exactly 12 colors. Refer to
|
|
||||||
/// [`ColorScaleStep`] for a reference of what each step is used for.
|
|
||||||
pub struct ColorScale(Vec<Hsla>);
|
|
||||||
|
|
||||||
impl FromIterator<Hsla> for ColorScale {
|
|
||||||
fn from_iter<T: IntoIterator<Item = Hsla>>(iter: T) -> Self {
|
|
||||||
Self(Vec::from_iter(iter))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ColorScale {
|
|
||||||
/// Returns the specified step in the [`ColorScale`].
|
|
||||||
#[inline]
|
|
||||||
pub fn step(&self, step: ColorScaleStep) -> Hsla {
|
|
||||||
// Steps are one-based, so we need convert to the zero-based vec index.
|
|
||||||
self.0[step.0 - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 1` - Used for main application backgrounds.
|
|
||||||
///
|
|
||||||
/// This step provides a neutral base for any overlaying components, ideal for applications' main backdrop or empty spaces such as canvas areas.
|
|
||||||
///
|
|
||||||
#[inline]
|
|
||||||
pub fn step_1(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::ONE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 2` - Used for both main application backgrounds and subtle component backgrounds.
|
|
||||||
///
|
|
||||||
/// Like `Step 1`, this step allows variations in background styles, from striped tables, sidebar backgrounds, to card backgrounds.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_2(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::TWO)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 3` - Used for UI component backgrounds in their normal states.
|
|
||||||
///
|
|
||||||
/// This step maintains accessibility by guaranteeing a contrast ratio of 4.5:1 with steps 11 and 12 for text. It could also suit hover states for transparent components.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_3(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::THREE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 4` - Used for UI component backgrounds in their hover states.
|
|
||||||
///
|
|
||||||
/// Also suited for pressed or selected states of components with a transparent background.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_4(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::FOUR)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 5` - Used for UI component backgrounds in their pressed or selected states.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_5(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::FIVE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 6` - Used for subtle borders on non-interactive components.
|
|
||||||
///
|
|
||||||
/// Its usage spans from sidebars' borders, headers' dividers, cards' outlines, to alerts' edges and separators.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_6(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::SIX)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 7` - Used for subtle borders on interactive components.
|
|
||||||
///
|
|
||||||
/// This step subtly delineates the boundary of elements users interact with.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_7(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::SEVEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 8` - Used for stronger borders on interactive components and focus rings.
|
|
||||||
///
|
|
||||||
/// It strengthens the visibility and accessibility of active elements and their focus states.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_8(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::EIGHT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 9` - Used for solid backgrounds.
|
|
||||||
///
|
|
||||||
/// `Step 9` is the most saturated step, having the least mix of white or black.
|
|
||||||
///
|
|
||||||
/// Due to its high chroma, `Step 9` is versatile and particularly useful for semantic colors such as
|
|
||||||
/// error, warning, and success indicators.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_9(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::NINE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 10` - Used for hovered or active solid backgrounds, particularly when `Step 9` is their normal state.
|
|
||||||
///
|
|
||||||
/// May also be used for extremely low contrast text. This should be used sparingly, as it may be difficult to read.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_10(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::TEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 11` - Used for text and icons requiring low contrast or less emphasis.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_11(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::ELEVEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Step 12` - Used for text and icons requiring high contrast or prominence.
|
|
||||||
#[inline]
|
|
||||||
pub fn step_12(&self) -> Hsla {
|
|
||||||
self.step(ColorScaleStep::TWELVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale.
|
|
||||||
///
|
|
||||||
/// No longer used to build the built-in palettes; kept public until the
|
|
||||||
/// deprecation window closes (docs/ui-theme-bezel-migration.md, Phase 5).
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct ColorScaleSet {
|
|
||||||
light: ColorScale,
|
|
||||||
dark: ColorScale,
|
|
||||||
light_alpha: ColorScale,
|
|
||||||
dark_alpha: ColorScale,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl ColorScaleSet {
|
|
||||||
pub(crate) fn new(
|
|
||||||
light: ColorScale,
|
|
||||||
light_alpha: ColorScale,
|
|
||||||
dark: ColorScale,
|
|
||||||
dark_alpha: ColorScale,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
light,
|
|
||||||
light_alpha,
|
|
||||||
dark,
|
|
||||||
dark_alpha,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn light(&self) -> &ColorScale {
|
|
||||||
&self.light
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn light_alpha(&self) -> &ColorScale {
|
|
||||||
&self.light_alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn dark(&self) -> &ColorScale {
|
|
||||||
&self.dark
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn dark_alpha(&self) -> &ColorScale {
|
|
||||||
&self.dark_alpha
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-330
@@ -1,11 +1,7 @@
|
|||||||
use std::path::Path;
|
use gpui::WindowAppearance;
|
||||||
|
|
||||||
use gpui::{SharedString, WindowAppearance};
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::ThemeColors;
|
/// Which appearance the palette is painted for.
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Hash, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Hash, Deserialize, Serialize)]
|
||||||
pub enum ThemeMode {
|
pub enum ThemeMode {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -44,327 +40,3 @@ impl From<WindowAppearance> for ThemeMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Theme family
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)]
|
|
||||||
pub struct ThemeFamily {
|
|
||||||
/// The unique identifier for the theme.
|
|
||||||
pub id: String,
|
|
||||||
|
|
||||||
/// The name of the theme.
|
|
||||||
pub name: SharedString,
|
|
||||||
|
|
||||||
/// The author of the theme.
|
|
||||||
pub author: SharedString,
|
|
||||||
|
|
||||||
/// The URL of the theme.
|
|
||||||
pub url: String,
|
|
||||||
|
|
||||||
/// The light colors for the theme.
|
|
||||||
pub light: ThemeColors,
|
|
||||||
|
|
||||||
/// The dark colors for the theme.
|
|
||||||
pub dark: ThemeColors,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ThemeFamily {
|
|
||||||
fn default() -> Self {
|
|
||||||
ThemeFamily {
|
|
||||||
id: "coop".into(),
|
|
||||||
name: "Coop Default Theme".into(),
|
|
||||||
author: "Coop".into(),
|
|
||||||
url: "https://github.com/reyakov/coop".into(),
|
|
||||||
light: ThemeColors::light(),
|
|
||||||
dark: ThemeColors::dark(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeFamily {
|
|
||||||
/// Returns the light colors for the theme.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn light(&self) -> &ThemeColors {
|
|
||||||
&self.light
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the dark colors for the theme.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn dark(&self) -> &ThemeColors {
|
|
||||||
&self.dark
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a theme family from a JSON file.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `path` - Path to the JSON file containing the theme family. This can be
|
|
||||||
/// an absolute path or a path relative to the current working directory.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// Returns `Ok(ThemeFamily)` if the file was successfully loaded and parsed,
|
|
||||||
/// or `Err(anyhow::Error)` if there was an error reading or parsing the file.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if:
|
|
||||||
/// - The file cannot be read (permission issues, file doesn't exist, etc.)
|
|
||||||
/// - The file contains invalid JSON
|
|
||||||
/// - The JSON structure doesn't match the `ThemeFamily` schema
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// use theme::ThemeFamily;
|
|
||||||
///
|
|
||||||
/// # fn main() -> anyhow::Result<()> {
|
|
||||||
/// // Load from a relative path
|
|
||||||
/// let theme = ThemeFamily::from_file("assets/themes/my-theme.json")?;
|
|
||||||
///
|
|
||||||
/// // Load from an absolute path
|
|
||||||
/// let theme = ThemeFamily::from_file("/path/to/themes/my-theme.json")?;
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn from_file<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
|
||||||
let json_data = std::fs::read(path)?;
|
|
||||||
let mut theme_family: ThemeFamily = serde_json::from_slice(&json_data)?;
|
|
||||||
theme_family.light.rederive_tokens(ThemeMode::Light);
|
|
||||||
theme_family.dark.rederive_tokens(ThemeMode::Dark);
|
|
||||||
|
|
||||||
Ok(theme_family)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a theme family from a JSON file in the assets/themes directory.
|
|
||||||
///
|
|
||||||
/// This function looks for the file at `assets/themes/{name}.json` relative
|
|
||||||
/// to the current working directory. This is useful for loading themes
|
|
||||||
/// from the standard theme directory in the project structure.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `name` - Name of the theme file (without the .json extension)
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// Returns `Ok(ThemeFamily)` if the file was successfully loaded and parsed,
|
|
||||||
/// or `Err(anyhow::Error)` if there was an error reading or parsing the file.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if:
|
|
||||||
/// - The file cannot be read (permission issues, file doesn't exist, etc.)
|
|
||||||
/// - The file contains invalid JSON
|
|
||||||
/// - The JSON structure doesn't match the `ThemeFamily` schema
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// use theme::ThemeFamily;
|
|
||||||
///
|
|
||||||
/// # fn main() -> anyhow::Result<()> {
|
|
||||||
/// // Assuming the file exists at `assets/themes/my-theme.json`
|
|
||||||
/// let theme = ThemeFamily::from_assets("themes/my-theme.json")?;
|
|
||||||
///
|
|
||||||
/// println!("Loaded theme: {}", theme.name);
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn from_assets(target: &str) -> anyhow::Result<Self> {
|
|
||||||
let path = format!("assets/{target}");
|
|
||||||
Self::from_file(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use tempfile::tempdir;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file() {
|
|
||||||
// Create a temporary directory for our test
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
let file_path = dir.path().join("test-theme.json");
|
|
||||||
|
|
||||||
// Create a minimal valid theme JSON with hex colors
|
|
||||||
// Using simple hex colors that Hsla can parse
|
|
||||||
// Note: We need to escape the # characters in the raw string
|
|
||||||
let json_data = r##"{
|
|
||||||
"id": "test-theme",
|
|
||||||
"name": "Test Theme",
|
|
||||||
"author": "Coop",
|
|
||||||
"url": "https://github.com/reyakov/coop",
|
|
||||||
"light": {
|
|
||||||
"background": "#ffffff",
|
|
||||||
"surface_background": "#fafafa",
|
|
||||||
"elevated_surface_background": "#f5f5f5",
|
|
||||||
"panel_background": "#ffffff",
|
|
||||||
"overlay": "#0000001a",
|
|
||||||
"title_bar": "#00000000",
|
|
||||||
"title_bar_inactive": "#ffffff",
|
|
||||||
"window_border": "#c7c7cf",
|
|
||||||
"border": "#dbdbdb",
|
|
||||||
"border_variant": "#d1d1d1",
|
|
||||||
"border_focused": "#3366cc",
|
|
||||||
"border_selected": "#3366cc",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#e6e6e6",
|
|
||||||
"ring": "#4d79d6",
|
|
||||||
"text": "#1a1a1a",
|
|
||||||
"text_muted": "#4d4d4d",
|
|
||||||
"text_placeholder": "#808080",
|
|
||||||
"text_accent": "#3366cc",
|
|
||||||
"icon": "#4d4d4d",
|
|
||||||
"icon_muted": "#808080",
|
|
||||||
"icon_accent": "#3366cc",
|
|
||||||
"element_foreground": "#ffffff",
|
|
||||||
"element_background": "#3366cc",
|
|
||||||
"element_hover": "#3366cce6",
|
|
||||||
"element_active": "#2e5cb8",
|
|
||||||
"element_selected": "#2952a3",
|
|
||||||
"element_disabled": "#3366cc4d",
|
|
||||||
"secondary_foreground": "#2952a3",
|
|
||||||
"secondary_background": "#e6ecf5",
|
|
||||||
"secondary_hover": "#3366cc1a",
|
|
||||||
"secondary_active": "#d9e2f0",
|
|
||||||
"secondary_selected": "#d9e2f0",
|
|
||||||
"secondary_disabled": "#3366cc4d",
|
|
||||||
"danger_foreground": "#ffffff",
|
|
||||||
"danger_background": "#f5e6e6",
|
|
||||||
"danger_hover": "#cc33331a",
|
|
||||||
"danger_active": "#f0d9d9",
|
|
||||||
"danger_selected": "#f0d9d9",
|
|
||||||
"danger_disabled": "#cc33334d",
|
|
||||||
"warning_foreground": "#1a1a1a",
|
|
||||||
"warning_background": "#f5f0e6",
|
|
||||||
"warning_hover": "#cc99331a",
|
|
||||||
"warning_active": "#f0ead9",
|
|
||||||
"warning_selected": "#f0ead9",
|
|
||||||
"warning_disabled": "#cc99334d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#e6e6e6",
|
|
||||||
"ghost_element_hover": "#0000001a",
|
|
||||||
"ghost_element_active": "#d9d9d9",
|
|
||||||
"ghost_element_selected": "#d9d9d9",
|
|
||||||
"ghost_element_disabled": "#0000000d",
|
|
||||||
"tab_inactive_background": "#e6e6e6",
|
|
||||||
"tab_hover_background": "#e0e0e0",
|
|
||||||
"tab_active_background": "#d9d9d9",
|
|
||||||
"scrollbar_thumb_background": "#00000033",
|
|
||||||
"scrollbar_thumb_hover_background": "#0000004d",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#d9d9d9",
|
|
||||||
"drop_target_background": "#3366cc1a",
|
|
||||||
"cursor": "#3399ff",
|
|
||||||
"selection": "#3399ff40"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#1a1a1a",
|
|
||||||
"surface_background": "#1f1f1f",
|
|
||||||
"elevated_surface_background": "#242424",
|
|
||||||
"panel_background": "#262626",
|
|
||||||
"overlay": "#ffffff1a",
|
|
||||||
"title_bar": "#00000000",
|
|
||||||
"title_bar_inactive": "#1a1a1a",
|
|
||||||
"window_border": "#404046",
|
|
||||||
"border": "#404040",
|
|
||||||
"border_variant": "#383838",
|
|
||||||
"border_focused": "#4d79d6",
|
|
||||||
"border_selected": "#4d79d6",
|
|
||||||
"border_transparent": "#00000000",
|
|
||||||
"border_disabled": "#2e2e2e",
|
|
||||||
"ring": "#668cdf",
|
|
||||||
"text": "#f2f2f2",
|
|
||||||
"text_muted": "#b3b3b3",
|
|
||||||
"text_placeholder": "#808080",
|
|
||||||
"text_accent": "#668cdf",
|
|
||||||
"icon": "#b3b3b3",
|
|
||||||
"icon_muted": "#808080",
|
|
||||||
"icon_accent": "#668cdf",
|
|
||||||
"element_foreground": "#ffffff",
|
|
||||||
"element_background": "#4d79d6",
|
|
||||||
"element_hover": "#4d79d6e6",
|
|
||||||
"element_active": "#456dc1",
|
|
||||||
"element_selected": "#3e62ac",
|
|
||||||
"element_disabled": "#4d79d64d",
|
|
||||||
"secondary_foreground": "#3e62ac",
|
|
||||||
"secondary_background": "#2a3652",
|
|
||||||
"secondary_hover": "#4d79d61a",
|
|
||||||
"secondary_active": "#303d5c",
|
|
||||||
"secondary_selected": "#303d5c",
|
|
||||||
"secondary_disabled": "#4d79d64d",
|
|
||||||
"danger_foreground": "#ffffff",
|
|
||||||
"danger_background": "#522a2a",
|
|
||||||
"danger_hover": "#d64d4d1a",
|
|
||||||
"danger_active": "#5c3030",
|
|
||||||
"danger_selected": "#5c3030",
|
|
||||||
"danger_disabled": "#d64d4d4d",
|
|
||||||
"warning_foreground": "#f2f2f2",
|
|
||||||
"warning_background": "#52482a",
|
|
||||||
"warning_hover": "#d6b34d1a",
|
|
||||||
"warning_active": "#5c5430",
|
|
||||||
"warning_selected": "#5c5430",
|
|
||||||
"warning_disabled": "#d6b34d4d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#2e2e2e",
|
|
||||||
"ghost_element_hover": "#ffffff1a",
|
|
||||||
"ghost_element_active": "#383838",
|
|
||||||
"ghost_element_selected": "#383838",
|
|
||||||
"ghost_element_disabled": "#ffffff0d",
|
|
||||||
"tab_inactive_background": "#2e2e2e",
|
|
||||||
"tab_hover_background": "#333333",
|
|
||||||
"tab_active_background": "#383838",
|
|
||||||
"scrollbar_thumb_background": "#ffffff33",
|
|
||||||
"scrollbar_thumb_hover_background": "#ffffff4d",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#383838",
|
|
||||||
"drop_target_background": "#4d79d61a",
|
|
||||||
"cursor": "#4db3ff",
|
|
||||||
"selection": "#4db3ff40"
|
|
||||||
}
|
|
||||||
}"##;
|
|
||||||
|
|
||||||
// Write the JSON to the file
|
|
||||||
fs::write(&file_path, json_data).unwrap();
|
|
||||||
|
|
||||||
// Test loading the theme from file
|
|
||||||
let theme = ThemeFamily::from_file(&file_path).unwrap();
|
|
||||||
|
|
||||||
// Verify the loaded theme
|
|
||||||
assert_eq!(theme.id, "test-theme");
|
|
||||||
assert_eq!(theme.name, "Test Theme");
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
dir.close().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file_nonexistent() {
|
|
||||||
// Test that loading a non-existent file returns an error
|
|
||||||
let result = ThemeFamily::from_file("non-existent-file.json");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_file_invalid_json() {
|
|
||||||
// Create a temporary directory for our test
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
let file_path = dir.path().join("invalid-theme.json");
|
|
||||||
|
|
||||||
// Write invalid JSON
|
|
||||||
fs::write(&file_path, "invalid json").unwrap();
|
|
||||||
|
|
||||||
// Test that loading invalid JSON returns an error
|
|
||||||
let result = ThemeFamily::from_file(&file_path);
|
|
||||||
assert!(result.is_err());
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
dir.close().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! palette was tuned for.
|
//! palette was tuned for.
|
||||||
|
|
||||||
use gpui::Hsla;
|
use gpui::Hsla;
|
||||||
use theme::{Brand, ThemeColors, ThemeFamily, ThemeMode, Tint, contrast_ratio, flatten};
|
use theme::{Brand, ThemeColors, ThemeMode, Tint, contrast_ratio, flatten};
|
||||||
|
|
||||||
// The shipped palette's measured floors (AppKit-style ink, per bezel):
|
// The shipped palette's measured floors (AppKit-style ink, per bezel):
|
||||||
// text/bg is 14.4:1 dark and 15.1:1 light, muted lands near 6.3 dark and
|
// text/bg is 14.4:1 dark and 15.1:1 light, muted lands near 6.3 dark and
|
||||||
@@ -123,111 +123,3 @@ fn warning_wash_carries_its_label() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn legacy_aliases_track_tokens() {
|
|
||||||
for appearance in [ThemeMode::Dark, ThemeMode::Light] {
|
|
||||||
let colors = ThemeColors::for_appearance(appearance);
|
|
||||||
assert_eq!(colors.background, colors.bg);
|
|
||||||
assert_eq!(colors.surface_background, colors.surface);
|
|
||||||
assert_eq!(colors.text_placeholder, colors.text_faint);
|
|
||||||
assert_eq!(colors.element_background, colors.solid);
|
|
||||||
assert_eq!(colors.secondary_background, colors.surface_raised);
|
|
||||||
assert_eq!(colors.tab_active_background, colors.element_active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rederive_round_trips_a_legacy_theme() {
|
|
||||||
let json = br##"{
|
|
||||||
"id": "test", "name": "Test", "author": "coop", "url": "",
|
|
||||||
"light": {
|
|
||||||
"background": "#ffffff", "surface_background": "#fafafa",
|
|
||||||
"elevated_surface_background": "#f5f5f5", "panel_background": "#ffffff",
|
|
||||||
"overlay": "#0000001a", "title_bar": "#00000000",
|
|
||||||
"title_bar_inactive": "#ffffff", "window_border": "#c7c7cf",
|
|
||||||
"border": "#dbdbdb", "border_variant": "#d1d1d1",
|
|
||||||
"border_focused": "#3366cc", "border_selected": "#3366cc",
|
|
||||||
"border_transparent": "#00000000", "border_disabled": "#e6e6e6",
|
|
||||||
"ring": "#4d79d6", "text": "#1a1a1a", "text_muted": "#4d4d4d",
|
|
||||||
"text_placeholder": "#808080", "text_accent": "#3366cc",
|
|
||||||
"text_danger": "#cc3333", "text_warning": "#cc9933",
|
|
||||||
"icon": "#4d4d4d", "icon_muted": "#808080", "icon_accent": "#3366cc",
|
|
||||||
"element_foreground": "#ffffff", "element_background": "#3366cc",
|
|
||||||
"element_hover": "#3366cce6", "element_active": "#2e5cb8",
|
|
||||||
"element_selected": "#2952a3", "element_disabled": "#3366cc4d",
|
|
||||||
"secondary_foreground": "#2952a3", "secondary_background": "#e6ecf5",
|
|
||||||
"secondary_hover": "#3366cc1a", "secondary_active": "#d9e2f0",
|
|
||||||
"secondary_selected": "#d9e2f0", "secondary_disabled": "#3366cc4d",
|
|
||||||
"danger_foreground": "#ffffff", "danger_background": "#f5e6e6",
|
|
||||||
"danger_hover": "#cc33331a", "danger_active": "#f0d9d9",
|
|
||||||
"danger_selected": "#f0d9d9", "danger_disabled": "#cc33334d",
|
|
||||||
"warning_foreground": "#1a1a1a", "warning_background": "#f5f0e6",
|
|
||||||
"warning_hover": "#cc99331a", "warning_active": "#f0ead9",
|
|
||||||
"warning_selected": "#f0ead9", "warning_disabled": "#cc99334d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#e6e6e6",
|
|
||||||
"ghost_element_hover": "#0000001a", "ghost_element_active": "#d9d9d9",
|
|
||||||
"ghost_element_selected": "#d9d9d9", "ghost_element_disabled": "#0000000d",
|
|
||||||
"tab_inactive_background": "#e6e6e6", "tab_hover_background": "#e0e0e0",
|
|
||||||
"tab_active_background": "#d9d9d9",
|
|
||||||
"scrollbar_thumb_background": "#00000033",
|
|
||||||
"scrollbar_thumb_hover_background": "#0000004d",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#d9d9d9",
|
|
||||||
"drop_target_background": "#3366cc1a",
|
|
||||||
"cursor": "#3399ff", "selection": "#3399ff40"
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
"background": "#1a1a1a", "surface_background": "#1f1f1f",
|
|
||||||
"elevated_surface_background": "#242424", "panel_background": "#262626",
|
|
||||||
"overlay": "#ffffff1a", "title_bar": "#00000000",
|
|
||||||
"title_bar_inactive": "#1a1a1a", "window_border": "#404046",
|
|
||||||
"border": "#404040", "border_variant": "#383838",
|
|
||||||
"border_focused": "#4d79d6", "border_selected": "#4d79d6",
|
|
||||||
"border_transparent": "#00000000", "border_disabled": "#2e2e2e",
|
|
||||||
"ring": "#668cdf", "text": "#f2f2f2", "text_muted": "#b3b3b3",
|
|
||||||
"text_placeholder": "#808080", "text_accent": "#668cdf",
|
|
||||||
"text_danger": "#d64d4d", "text_warning": "#d6b34d",
|
|
||||||
"icon": "#b3b3b3", "icon_muted": "#808080", "icon_accent": "#668cdf",
|
|
||||||
"element_foreground": "#ffffff", "element_background": "#4d79d6",
|
|
||||||
"element_hover": "#4d79d6e6", "element_active": "#456dc1",
|
|
||||||
"element_selected": "#3e62ac", "element_disabled": "#4d79d64d",
|
|
||||||
"secondary_foreground": "#3e62ac", "secondary_background": "#2a3652",
|
|
||||||
"secondary_hover": "#4d79d61a", "secondary_active": "#303d5c",
|
|
||||||
"secondary_selected": "#303d5c", "secondary_disabled": "#4d79d64d",
|
|
||||||
"danger_foreground": "#ffffff", "danger_background": "#522a2a",
|
|
||||||
"danger_hover": "#d64d4d1a", "danger_active": "#5c3030",
|
|
||||||
"danger_selected": "#5c3030", "danger_disabled": "#d64d4d4d",
|
|
||||||
"warning_foreground": "#f2f2f2", "warning_background": "#52482a",
|
|
||||||
"warning_hover": "#d6b34d1a", "warning_active": "#5c5430",
|
|
||||||
"warning_selected": "#5c5430", "warning_disabled": "#d6b34d4d",
|
|
||||||
"ghost_element_background": "#00000000",
|
|
||||||
"ghost_element_background_alt": "#2e2e2e",
|
|
||||||
"ghost_element_hover": "#ffffff1a", "ghost_element_active": "#383838",
|
|
||||||
"ghost_element_selected": "#383838", "ghost_element_disabled": "#ffffff0d",
|
|
||||||
"tab_inactive_background": "#2e2e2e", "tab_hover_background": "#333333",
|
|
||||||
"tab_active_background": "#383838",
|
|
||||||
"scrollbar_thumb_background": "#ffffff33",
|
|
||||||
"scrollbar_thumb_hover_background": "#ffffff4d",
|
|
||||||
"scrollbar_thumb_border": "#00000000",
|
|
||||||
"scrollbar_track_background": "#00000000",
|
|
||||||
"scrollbar_track_border": "#383838",
|
|
||||||
"drop_target_background": "#4d79d61a",
|
|
||||||
"cursor": "#4db3ff", "selection": "#4db3ff40"
|
|
||||||
}
|
|
||||||
}"##;
|
|
||||||
|
|
||||||
let family: ThemeFamily = serde_json::from_slice(json).expect("parse");
|
|
||||||
let mut light = family.light;
|
|
||||||
light.rederive_tokens(ThemeMode::Light);
|
|
||||||
assert_eq!(light.bg, light.background, "bg tracks the JSON background");
|
|
||||||
assert_eq!(light.surface, light.surface_background);
|
|
||||||
assert_eq!(light.solid, light.element_background);
|
|
||||||
// Fields with no legacy source keep the built-in palette's value.
|
|
||||||
assert_eq!(
|
|
||||||
light.success,
|
|
||||||
ThemeColors::for_appearance(ThemeMode::Light).success
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ use std::panic::Location;
|
|||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||||
ScrollHandle, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, px,
|
ScrollHandle, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,
|
||||||
};
|
};
|
||||||
use gpui_base::{Scrollbar, ScrollbarAxis, ScrollbarHandle};
|
use gpui_base::{Scrollbar, ScrollbarAxis};
|
||||||
use theme::ActiveTheme as _;
|
|
||||||
|
|
||||||
use crate::StyledExt as _;
|
use crate::StyledExt as _;
|
||||||
|
|
||||||
@@ -19,53 +18,6 @@ pub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Eleme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The scrollbar the application's theme describes: a 10px rail holding a 6px rounded thumb that grows to 8px under the pointer.
|
|
||||||
fn scrollbar<H: ScrollbarHandle + Clone>(
|
|
||||||
scroll_handle: &H,
|
|
||||||
axis: ScrollbarAxis,
|
|
||||||
cx: &App,
|
|
||||||
) -> Scrollbar {
|
|
||||||
let theme = cx.theme();
|
|
||||||
let (thumb_width, thumb_radius) = if theme.scrollbar_mode.is_scrolling() {
|
|
||||||
(px(6.), px(3.))
|
|
||||||
} else {
|
|
||||||
(px(8.), px(4.))
|
|
||||||
};
|
|
||||||
|
|
||||||
Scrollbar::new(scroll_handle).axis(axis).styles(|styles| {
|
|
||||||
styles
|
|
||||||
.track(|track| {
|
|
||||||
track
|
|
||||||
.bg(theme.scrollbar_track_background)
|
|
||||||
.border_color(theme.scrollbar_thumb_border)
|
|
||||||
.width(px(10.))
|
|
||||||
})
|
|
||||||
.track_hover(|track| track.bg(theme.scrollbar_thumb_background).width(px(10.)))
|
|
||||||
.thumb(|thumb| {
|
|
||||||
thumb
|
|
||||||
.bg(theme.scrollbar_thumb_background)
|
|
||||||
.width(thumb_width)
|
|
||||||
.inset(px(1.))
|
|
||||||
.radius(thumb_radius)
|
|
||||||
.min_length(px(48.))
|
|
||||||
})
|
|
||||||
.thumb_hover(|thumb| {
|
|
||||||
thumb
|
|
||||||
.bg(theme.scrollbar_thumb_hover_background)
|
|
||||||
.width(px(8.))
|
|
||||||
.inset(px(1.))
|
|
||||||
.radius(px(4.))
|
|
||||||
})
|
|
||||||
.thumb_active(|thumb| {
|
|
||||||
thumb
|
|
||||||
.bg(theme.scrollbar_thumb_hover_background)
|
|
||||||
.width(px(8.))
|
|
||||||
.inset(px(1.))
|
|
||||||
.radius(px(4.))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scrollable element wrapper that adds scrollbars to an interactive element.
|
/// A scrollable element wrapper that adds scrollbars to an interactive element.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Scrollable<E: InteractiveElement + Styled + ParentElement + Element> {
|
pub struct Scrollable<E: InteractiveElement + Styled + ParentElement + Element> {
|
||||||
@@ -130,7 +82,11 @@ where
|
|||||||
})
|
})
|
||||||
.child(self.element.size_auto().flex_1()),
|
.child(self.element.size_auto().flex_1()),
|
||||||
)
|
)
|
||||||
.child(scrollbar(&scroll_handle, self.axis, cx).id("scrollbar"))
|
.child(
|
||||||
|
Scrollbar::new(&scroll_handle)
|
||||||
|
.axis(self.axis)
|
||||||
|
.id("scrollbar"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use gpui::{
|
|||||||
Window, div, px,
|
Window, div, px,
|
||||||
};
|
};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use theme::{ActiveTheme, Theme, ThemeMode};
|
use theme::{ActiveTheme, AppExt as _, AppearanceMode};
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::group_box::{GroupBox, GroupBoxVariants};
|
use ui::group_box::{GroupBox, GroupBoxVariants};
|
||||||
use ui::input::{Input, InputState};
|
use ui::input::{Input, InputState};
|
||||||
@@ -47,10 +47,10 @@ impl Preferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the theme mode (light or dark)
|
/// Set the appearance mode (system, light or dark)
|
||||||
fn set_theme_mode(mode: ThemeMode, window: &mut Window, cx: &mut App) {
|
fn set_appearance_mode(mode: AppearanceMode, cx: &mut App) {
|
||||||
AppSettings::update_theme_mode(mode, cx);
|
AppSettings::update_appearance_mode(mode, cx);
|
||||||
Theme::change(mode, Some(window), cx);
|
cx.set_appearance_mode(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,14 +58,13 @@ impl Render for Preferences {
|
|||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
const SCREENING: &str = "Show an screening dialog to verify the unknown sender.";
|
const SCREENING: &str = "Show an screening dialog to verify the unknown sender.";
|
||||||
const AVATAR: &str = "Hide all avatar pictures to improve performance.";
|
const AVATAR: &str = "Hide all avatar pictures to improve performance.";
|
||||||
const MODE: &str = "Use the selected light or dark theme, or to follow the OS.";
|
const MODE: &str = "Follow the OS, or always use light or dark.";
|
||||||
const NIP4E: &str = "Use a dedicated key to encrypt and decrypt messages.";
|
const NIP4E: &str = "Use a dedicated key to encrypt and decrypt messages.";
|
||||||
const RESET: &str = "Reset the theme to the default one.";
|
|
||||||
|
|
||||||
let screening = AppSettings::get_screening(cx);
|
let screening = AppSettings::get_screening(cx);
|
||||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
let nip4e = AppSettings::get_nip4e(cx);
|
let nip4e = AppSettings::get_nip4e(cx);
|
||||||
let theme_mode = AppSettings::get_theme_mode(cx);
|
let appearance_mode = AppSettings::get_appearance_mode(cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
@@ -113,46 +112,17 @@ impl Render for Preferences {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("theme-mode")
|
Button::new("appearance-mode")
|
||||||
.label(theme_mode.name())
|
.label(appearance_mode.label())
|
||||||
.ghost_alt()
|
.ghost_alt()
|
||||||
.small()
|
.small()
|
||||||
.dropdown_menu(|this, _window, _cx| {
|
.dropdown_menu(|this, _window, _cx| {
|
||||||
this.item(PopupMenuItem::new("Light").on_click(
|
AppearanceMode::ALL.iter().fold(this, |menu, mode| {
|
||||||
|_, window, cx| {
|
menu.item(PopupMenuItem::new(mode.label()).on_click(
|
||||||
Self::set_theme_mode(ThemeMode::Light, window, cx);
|
move |_, _window, cx| {
|
||||||
},
|
Self::set_appearance_mode(*mode, cx);
|
||||||
))
|
},
|
||||||
.item(
|
))
|
||||||
PopupMenuItem::new("Dark").on_click(|_, window, cx| {
|
|
||||||
Self::set_theme_mode(ThemeMode::Dark, window, cx);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_3()
|
|
||||||
.justify_between()
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.child(div().text_sm().child(SharedString::from("Reset theme")))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.child(SharedString::from(RESET)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("reset")
|
|
||||||
.label("Reset")
|
|
||||||
.ghost_alt()
|
|
||||||
.small()
|
|
||||||
.on_click(move |_ev, window, cx| {
|
|
||||||
AppSettings::global(cx).update(cx, |this, cx| {
|
|
||||||
this.reset_theme(window, cx);
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ::settings::AppSettings;
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use auto_update::AutoUpdater;
|
use auto_update::AutoUpdater;
|
||||||
use chat::{ChatEvent, ChatRegistry};
|
use chat::{ChatEvent, ChatRegistry};
|
||||||
@@ -15,11 +14,11 @@ use nostr_sdk::prelude::*;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::{NostrRegistry, StateEvent};
|
use state::{NostrRegistry, StateEvent};
|
||||||
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry, button_radius};
|
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme};
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::Button;
|
||||||
use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
|
use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
|
||||||
use ui::notification::{Notification, NotificationKind};
|
use ui::notification::{Notification, NotificationKind};
|
||||||
use ui::{IconName, Root, Sizable, WindowExtension, h_flex, v_flex};
|
use ui::{Root, WindowExtension, v_flex};
|
||||||
|
|
||||||
use crate::dialogs::restore::RestoreEncryption;
|
use crate::dialogs::restore::RestoreEncryption;
|
||||||
use crate::dialogs::{join_group, new_chat, new_group, profile, relays, settings};
|
use crate::dialogs::{join_group, new_chat, new_group, profile, relays, settings};
|
||||||
@@ -40,7 +39,6 @@ struct MsgRelayNotification;
|
|||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||||
#[action(namespace = workspace, no_json)]
|
#[action(namespace = workspace, no_json)]
|
||||||
enum Command {
|
enum Command {
|
||||||
ToggleTheme,
|
|
||||||
Update,
|
Update,
|
||||||
RefreshMessagingRelays,
|
RefreshMessagingRelays,
|
||||||
BackupEncryption,
|
BackupEncryption,
|
||||||
@@ -327,9 +325,6 @@ impl Workspace {
|
|||||||
Command::ResetEncryption => {
|
Command::ResetEncryption => {
|
||||||
self.confirm_reset_encryption(window, cx);
|
self.confirm_reset_encryption(window, cx);
|
||||||
}
|
}
|
||||||
Command::ToggleTheme => {
|
|
||||||
self.theme_selector(window, cx);
|
|
||||||
}
|
|
||||||
Command::BackupEncryption => {
|
Command::BackupEncryption => {
|
||||||
let device = DeviceRegistry::global(cx).downgrade();
|
let device = DeviceRegistry::global(cx).downgrade();
|
||||||
let save_dialog = cx.prompt_for_new_path(download_dir(), Some("encryption.txt"));
|
let save_dialog = cx.prompt_for_new_path(download_dir(), Some("encryption.txt"));
|
||||||
@@ -448,85 +443,6 @@ impl Workspace {
|
|||||||
.child(restore.clone())
|
.child(restore.clone())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
window.open_dialog(cx, move |this, _window, cx| {
|
|
||||||
let registry = ThemeRegistry::global(cx);
|
|
||||||
let themes = registry.read(cx).themes();
|
|
||||||
|
|
||||||
this.width(px(520.))
|
|
||||||
.show_close(true)
|
|
||||||
.title("Select theme")
|
|
||||||
.child(v_flex().gap_2().w_full().children({
|
|
||||||
let mut items = vec![];
|
|
||||||
|
|
||||||
for (ix, (path, theme)) in themes.iter().enumerate() {
|
|
||||||
items.push(
|
|
||||||
h_flex()
|
|
||||||
.id(ix)
|
|
||||||
.group("")
|
|
||||||
.px_2()
|
|
||||||
.h_8()
|
|
||||||
.w_full()
|
|
||||||
.justify_between()
|
|
||||||
.rounded(px(button_radius()))
|
|
||||||
.hover(|this| this.bg(cx.theme().element_hover))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1p5()
|
|
||||||
.flex_1()
|
|
||||||
.text_sm()
|
|
||||||
.child(theme.name.clone())
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.italic()
|
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.child(theme.author.clone()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.invisible()
|
|
||||||
.group_hover("", |this| this.visible())
|
|
||||||
.child(
|
|
||||||
Button::new(format!("url-{ix}"))
|
|
||||||
.icon(IconName::Link)
|
|
||||||
.ghost()
|
|
||||||
.small()
|
|
||||||
.on_click({
|
|
||||||
let theme = theme.clone();
|
|
||||||
move |_ev, _window, cx| {
|
|
||||||
cx.open_url(&theme.url);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new(format!("set-{ix}"))
|
|
||||||
.icon(IconName::Check)
|
|
||||||
.primary()
|
|
||||||
.small()
|
|
||||||
.on_click({
|
|
||||||
let path = path.clone();
|
|
||||||
move |_ev, window, cx| {
|
|
||||||
let settings = AppSettings::global(cx);
|
|
||||||
let path = path.clone();
|
|
||||||
|
|
||||||
settings.update(cx, |this, cx| {
|
|
||||||
this.set_theme(path, window, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Workspace {
|
impl Render for Workspace {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use ui::button::{Button, ButtonVariants};
|
|||||||
use ui::dock::{Panel, PanelEvent};
|
use ui::dock::{Panel, PanelEvent};
|
||||||
use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex};
|
use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex};
|
||||||
|
|
||||||
use crate::Command;
|
|
||||||
use crate::dialogs::profile;
|
use crate::dialogs::profile;
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<GreeterPanel> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<GreeterPanel> {
|
||||||
@@ -138,19 +137,6 @@ impl Render for GreeterPanel {
|
|||||||
.on_click(cx.listener(move |this, _, window, cx| {
|
.on_click(cx.listener(move |this, _, window, cx| {
|
||||||
this.open_profile(window, cx)
|
this.open_profile(window, cx)
|
||||||
})),
|
})),
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("theme")
|
|
||||||
.icon(Icon::new(IconName::Moon))
|
|
||||||
.label("Change theme")
|
|
||||||
.ghost()
|
|
||||||
.small()
|
|
||||||
.on_click(cx.listener(move |_, _, window, cx| {
|
|
||||||
window.dispatch_action(
|
|
||||||
Box::new(Command::ToggleTheme),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ impl Sidebar {
|
|||||||
)
|
)
|
||||||
.menu_with_icon("Relays", IconName::Relay, Box::new(Command::ShowRelayList))
|
.menu_with_icon("Relays", IconName::Relay, Box::new(Command::ShowRelayList))
|
||||||
.separator()
|
.separator()
|
||||||
.menu_with_icon("Themes", IconName::Sun, Box::new(Command::ToggleTheme))
|
|
||||||
.menu_with_icon(
|
.menu_with_icon(
|
||||||
"Settings",
|
"Settings",
|
||||||
IconName::Settings,
|
IconName::Settings,
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ fn main() {
|
|||||||
// Initialize components
|
// Initialize components
|
||||||
ui::init(cx);
|
ui::init(cx);
|
||||||
|
|
||||||
// Initialize theme registry
|
// Initialize theme
|
||||||
theme::init(cx);
|
theme::init(cx);
|
||||||
|
|
||||||
// Load embedded fonts in assets/fonts
|
// Load embedded fonts in assets/fonts
|
||||||
|
|||||||
@@ -477,38 +477,67 @@ Follow-ups noted: chat_ui paints delivery/status marks with hardcoded
|
|||||||
status-dot recipe in a later polish pass; warning strips carry no 20%-alpha
|
status-dot recipe in a later polish pass; warning strips carry no 20%-alpha
|
||||||
ring (no border existed; adding one is a structure change).
|
ring (no border existed; adding one is a structure change).
|
||||||
|
|
||||||
### Phase 4 — Settings, registry, shipped themes
|
### Phase 4 — Settings, appearance, and the removal of the multi-theme system (done)
|
||||||
|
|
||||||
- [ ] `crates/settings/src/lib.rs`: replace persisted `theme_mode: ThemeMode`
|
The original plan (appearance_mode with back-compat, registry re-schema,
|
||||||
with `appearance_mode: AppearanceMode` (`System`/`Light`/`Dark`).
|
brand-izing the 8 shipped JSONs) was superseded by a decision: **coop no
|
||||||
Serde-compat: custom `Deserialize` that maps the old `Light`/`Dark`
|
longer ships multiple themes**. The bezel token palettes (dark and light,
|
||||||
values and falls back to `System` (or version the field) so existing
|
optionally rotated by a `Brand`) are the only theming surface. Done:
|
||||||
configs keep loading. `apply_theme` becomes: install registry overrides
|
|
||||||
→ `set_brand` → `appearance::set_mode`.
|
- [x] `crates/settings`: persisted `theme_mode: ThemeMode` →
|
||||||
- [ ] `ThemeRegistry` + `assets/themes/*.json`: new schema —
|
`appearance_mode: AppearanceMode` with
|
||||||
`{ id, name, author, brand?: { tint, accent, radius }, overrides: {
|
`#[serde(default, alias = "theme_mode")]` — an old config's pinned
|
||||||
light?: {...}, dark?: {...} } }` where overrides are a *subset* of
|
Light/Dark survives; absence falls back to `System`. Round-trip tests
|
||||||
semantic tokens. Re-derive the 8 shipped themes as override deltas over
|
cover both. `set_theme`/`reset_theme` deleted; `apply_theme` is now
|
||||||
the new base palette (catppuccin hues go through `Brand.tint`; only hues
|
`cx.set_appearance_mode(mode)`.
|
||||||
rotation cannot reach stay as overrides — flexoki is the likely
|
- [x] `ThemeRegistry`, `assets/themes/*.json` (8 files), and the
|
||||||
candidate).
|
`themes/**/*` asset embed are deleted. `theme::init` is
|
||||||
- [ ] `workspace/src/dialogs/settings.rs`: add the `System` appearance option
|
`appearance::init` + scrollbar sync.
|
||||||
next to Light/Dark.
|
- [x] `ThemeFamily`, `from_file`/`from_assets`, the theme picker dialog
|
||||||
- [ ] Contrast tests from Phase 1 run against every registered theme — a
|
(`theme_selector` + `Command::ToggleTheme` + its header/greeter menu
|
||||||
shipped theme that fails contrast is a bug in that JSON, fix the JSON.
|
entries), and the settings dialog's "Reset theme" row are deleted.
|
||||||
|
The appearance picker is now System/Light/Dark via
|
||||||
|
`AppearanceMode::ALL` and `AppExt::set_appearance_mode`.
|
||||||
|
- [x] The old-schema JSON bridge (`rederive_tokens`, `sync_legacy`,
|
||||||
|
`legacy_default`) is gone with the schema.
|
||||||
|
|
||||||
|
### Phase 5 — Removal and docs (done)
|
||||||
|
|
||||||
|
- [x] Deleted `ThemeColors` legacy fields (~60), `ColorScale`/`ColorScaleSet`
|
||||||
|
(`scale.rs`), `ThemeColors::light/dark` wrappers, `Theme::apply_theme`,
|
||||||
|
and the `Theme` fields `theme: Rc<ThemeFamily>`, `radius`, `radius_lg`
|
||||||
|
(every site reads the ratio functions now). `ThemeColors` stays public:
|
||||||
|
it *is* the token set, in the bezel style.
|
||||||
|
- [x] `crates/ui/src/scroll/scrollable.rs` no longer paints scrollbar colors
|
||||||
|
from coop tokens — gpui-base's `Scrollbar` paints from its own global
|
||||||
|
theme (kept in step by `sync_base`'s scrollbar mode), with base's
|
||||||
|
defaults for thumb/track.
|
||||||
|
- [x] `ui::init` boot order verified: `theme::init` (appearance::init)
|
||||||
|
runs before the first window in desktop and web; workspace still
|
||||||
|
observes window appearance for live System flips. Desktop/web
|
||||||
|
comments updated.
|
||||||
|
- [x] Final census: only new token names remain; workspace compiles; tests
|
||||||
|
green (theme 5, settings 2 incl. serde compat, 52 chat_ui).
|
||||||
|
|
||||||
|
Follow-ups noted: chat_ui's hardcoded `gpui::green()`/`red()` status marks
|
||||||
|
are candidates for the `success`/`busy`/`danger` status-dot recipe; the
|
||||||
|
nip29 pin chip pairs `solid` with a `text_muted` label (predates the
|
||||||
|
migration) — flip its label to `on_solid` if it reads loud.
|
||||||
|
|
||||||
### Phase 5 — Removal and docs
|
### Phase 5 — Removal and docs
|
||||||
|
|
||||||
- [ ] Delete `ThemeColors`, the deprecated aliases, `ColorScale`/
|
*(Executed together with Phase 4 — see above.)*
|
||||||
|
|
||||||
|
- [x] Delete `ThemeColors`, the deprecated aliases, `ColorScale`/
|
||||||
`ColorScaleSet` public exports (`scale.rs` either goes private as the
|
`ColorScaleSet` public exports (`scale.rs` either goes private as the
|
||||||
palette generator's internal or is deleted), `ThemeMode`.
|
palette generator's internal or is deleted), `ThemeMode`.
|
||||||
- [ ] `crates/ui/src/lib.rs`: `theme::init` already runs `appearance::init`;
|
- [x] `crates/ui/src/lib.rs`: `theme::init` already runs `appearance::init`;
|
||||||
verify `ui::init`'s callers boot the theme before the first window, and
|
verify `ui::init`'s callers boot the theme before the first window, and
|
||||||
that every opened window observes appearance changes
|
that every opened window observes appearance changes
|
||||||
(`theme::observe_window` or workspace's sync-on-activation hook).
|
(`theme::observe_window` or workspace's sync-on-activation hook).
|
||||||
- [ ] Update `README`/docs mentions of theme customization; document
|
- [x] Update `README`/docs mentions of theme customization; document
|
||||||
`Brand` + override JSON as the two supported ways to theme coop.
|
`Brand` + override JSON as the two supported ways to theme coop.
|
||||||
- [ ] Final census run: only new token names remain.
|
- [x] Final census run: only new token names remain.
|
||||||
|
|
||||||
## Risks
|
## Risks
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ pub fn run() -> Result<(), JsValue> {
|
|||||||
// Initialize components
|
// Initialize components
|
||||||
ui::init(cx);
|
ui::init(cx);
|
||||||
|
|
||||||
// Initialize theme registry
|
// Initialize theme
|
||||||
theme::init(cx);
|
theme::init(cx);
|
||||||
|
|
||||||
// Initialize settings
|
// Initialize settings
|
||||||
|
|||||||
Reference in New Issue
Block a user