diff --git a/assets/themes/aurora.json b/assets/themes/aurora.json deleted file mode 100644 index 83d78f8c..00000000 --- a/assets/themes/aurora.json +++ /dev/null @@ -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" - } -} diff --git a/assets/themes/catppuccin-frappe.json b/assets/themes/catppuccin-frappe.json deleted file mode 100644 index a1b42732..00000000 --- a/assets/themes/catppuccin-frappe.json +++ /dev/null @@ -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" - } -} diff --git a/assets/themes/catppuccin-latte.json b/assets/themes/catppuccin-latte.json deleted file mode 100644 index c9dedbdb..00000000 --- a/assets/themes/catppuccin-latte.json +++ /dev/null @@ -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" - } -} diff --git a/assets/themes/catppuccin-macchiato.json b/assets/themes/catppuccin-macchiato.json deleted file mode 100644 index d19a93a1..00000000 --- a/assets/themes/catppuccin-macchiato.json +++ /dev/null @@ -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" - } -} diff --git a/assets/themes/catppuccin-mocha.json b/assets/themes/catppuccin-mocha.json deleted file mode 100644 index fd987c83..00000000 --- a/assets/themes/catppuccin-mocha.json +++ /dev/null @@ -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" - } -} diff --git a/assets/themes/flexoki.json b/assets/themes/flexoki.json deleted file mode 100644 index b7ee4148..00000000 --- a/assets/themes/flexoki.json +++ /dev/null @@ -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" - } -} diff --git a/assets/themes/forest.json b/assets/themes/forest.json deleted file mode 100644 index 6c3be0bf..00000000 --- a/assets/themes/forest.json +++ /dev/null @@ -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" - } -} diff --git a/assets/themes/ocean.json b/assets/themes/ocean.json deleted file mode 100644 index 6c1ce2cf..00000000 --- a/assets/themes/ocean.json +++ /dev/null @@ -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" - } -} diff --git a/crates/assets/src/lib.rs b/crates/assets/src/lib.rs index a1f9b9ee..a226cfa5 100644 --- a/crates/assets/src/lib.rs +++ b/crates/assets/src/lib.rs @@ -7,7 +7,6 @@ use rust_embed::RustEmbed; #[include = "fonts/**/*"] #[include = "brand/**/*"] #[include = "icons/**/*"] -#[include = "themes/**/*"] #[exclude = "*.DS_Store"] pub struct Assets; diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index e4b455cc..4922936d 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -1,12 +1,10 @@ -use std::rc::Rc; - use anyhow::{Error, anyhow}; 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 serde::{Deserialize, Serialize}; use smallvec::{SmallVec, smallvec}; -use theme::{Theme, ThemeFamily, ThemeMode}; +use theme::{AppExt as _, AppearanceMode}; pub fn init(cx: &mut App) { AppSettings::set_global(cx.new(AppSettings::new), cx) @@ -53,7 +51,7 @@ macro_rules! setting_getters { } setting_accessors! { - pub theme_mode: ThemeMode, + pub appearance_mode: AppearanceMode, pub hide_avatar: bool, pub screening: bool, pub nip4e: bool, @@ -119,11 +117,11 @@ impl RoomConfig { /// Settings #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Settings { - /// Theme - pub theme: Option, - - /// Theme mode - pub theme_mode: ThemeMode, + /// Which appearance to paint: follow the OS, or pin light or dark. + /// `alias` reads the pre-bezel `theme_mode` key so old config files keep + /// their pinned choice. + #[serde(default, alias = "theme_mode")] + pub appearance_mode: AppearanceMode, /// Hide user avatars pub hide_avatar: bool, @@ -152,8 +150,7 @@ pub struct Settings { impl Default for Settings { fn default() -> Self { Self { - theme: None, - theme_mode: ThemeMode::default(), + appearance_mode: AppearanceMode::default(), hide_avatar: false, screening: true, nip4e: false, @@ -243,7 +240,7 @@ impl AppSettings { // Update settings this.update(cx, |this, cx| { this.set_settings(settings, cx); - this.apply_theme(None, cx); + this.apply_theme(cx); cx.refresh_windows(); }) .ok(); @@ -264,44 +261,10 @@ impl AppSettings { } } - /// Set theme - pub fn set_theme(&mut self, theme: T, window: &mut Window, cx: &mut Context) - where - T: Into, - { - // 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.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) { - 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); - } + /// Apply the persisted appearance mode to the app + pub fn apply_theme(&mut self, cx: &mut Context) { + let mode = self.inner.read(cx).appearance_mode; + cx.set_appearance_mode(mode); } /// 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 { + // 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); + } +} diff --git a/crates/theme/src/colors.rs b/crates/theme/src/colors.rs index 81208a12..2834a430 100644 --- a/crates/theme/src/colors.rs +++ b/crates/theme/src/colors.rs @@ -2,486 +2,117 @@ use gpui::Hsla; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::paint::{ink_for, plate_active_for, plate_hover_for, wash_for}; use crate::syntax::SyntaxPalette; 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)] #[serde(default)] pub struct ThemeColors { - // Surface colors - pub background: Hsla, - pub surface_background: Hsla, - pub elevated_surface_background: Hsla, - pub panel_background: Hsla, - pub overlay: Hsla, - pub title_bar: Hsla, - pub title_bar_inactive: Hsla, - pub window_border: Hsla, + /// The main content plane. + pub bg: Hsla, + /// The shell / sidebar plane. + pub surface: Hsla, + /// Opaque pills and chips raised above the panel. + pub surface_raised: Hsla, + /// An inline card resting on the main plane. + pub surface_card: 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_variant: Hsla, - pub border_focused: Hsla, - pub border_selected: Hsla, - pub border_transparent: Hsla, - pub border_disabled: Hsla, + /// Dividers inside cards, nested frames. + pub border_faint: Hsla, + /// Focused or raised edges. + pub border_strong: Hsla, + /// The focus ring. pub ring: Hsla, - // Text colors + /// Primary text. pub text: Hsla, + /// Secondary text. pub text_muted: Hsla, - pub text_placeholder: Hsla, - 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. + /// Placeholders and disabled text. 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, - // Bezel tokens: high-contrast solid - /// The maximum-contrast solid fill: the primary button plate. + /// The text cursor. + pub cursor: Hsla, + /// The selected-text wash. + pub selection: Hsla, + + /// The primary button plate — the maximum-contrast fill. pub solid: Hsla, - /// Label/icon color on top of [`ThemeColors::solid`] — its inverse. + /// Label ink on [`ThemeColors::solid`]. pub on_solid: Hsla, - // Bezel tokens: accents - /// Accent — the emphasis weight for text and icons. Neutral by default. + /// Emphasis for text and icons; neutral by default. pub accent: Hsla, - /// Stronger accent for fills that carry [`ThemeColors::on_accent`]. + /// Accent fills carrying [`ThemeColors::on_accent`]. pub accent_strong: Hsla, - /// Label color on top of [`ThemeColors::accent_strong`]. + /// Label ink on [`ThemeColors::accent_strong`]. pub on_accent: Hsla, - // Bezel tokens: semantic - /// Danger — red (errors, stop button). + /// Errors and destructive actions. pub danger: Hsla, - /// Softer danger for secondary/inline error copy. + /// Inline error copy. 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, - /// Warning — amber. + /// Warnings. pub warning: Hsla, - /// Softer warning for secondary copy. + /// The translucent warning wash behind chips and banners. pub warning_muted: Hsla, - /// Success / online — emerald. + /// Success and online presence. pub success: Hsla, - /// Softer success for text on a success-tinted chip. + /// Text on success-tinted chips. pub success_muted: Hsla, - /// Working / streaming indicator — pink. + /// Working or streaming states. pub busy: Hsla, - // Bezel tokens: components - /// Hover tone for an *opaque* raised pill. + /// Hover over [`ThemeColors::surface_raised`]. pub surface_raised_hover: Hsla, - /// Recessed band behind a header or footer strip. + /// A recessed header / footer strip. pub band: Hsla, - /// Input plates. + /// Input backgrounds. pub input_bg: Hsla, - /// Text caret. + /// The text caret. pub caret: Hsla, - /// Where a dragged block would land. + /// The line marking where a drag will land. 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, - // Bezel tokens: code & diff /// Inline-code text. pub code_text: Hsla, - /// Inline-code wash behind [`ThemeColors::code_text`]. + /// The wash behind [`ThemeColors::code_text`]. pub code_wash: Hsla, - /// Shared paint-only syntax palette. + /// The shared syntax palette, read at paint time. pub syntax: SyntaxPalette, - /// Diff: added lines. + /// Diff additions. pub diff_add: Hsla, - /// Diff: deleted lines. + /// Diff deletions. pub diff_del: Hsla, - /// Diff: hunk-header wash. + /// The diff hunk-header wash. pub diff_hunk_bg: Hsla, } impl Default for ThemeColors { fn default() -> Self { - Self::dark() + palette::dark() } } 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. pub fn for_appearance(appearance: ThemeMode) -> Self { 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; - } } diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index c8637133..3b2c13c6 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -1,7 +1,5 @@ use std::ops::{Deref, DerefMut}; -use std::rc::Rc; -use appearance::AppearanceMode; use gpui::{App, Global, Pixels, SharedString, Window, px}; mod brand; @@ -13,14 +11,13 @@ mod notification; mod paint; mod palette; mod platform_kind; -mod registry; -mod scale; mod scrollbar_mode; mod syntax; mod theme; pub mod appearance; +pub use appearance::AppearanceMode; pub use brand::{BASE_COLORS, Brand, Ink, TextInk, Tint}; pub use color::{ 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, }; pub use platform_kind::PlatformKind; -pub use registry::*; -pub use scale::*; pub use scrollbar_mode::*; pub use syntax::{HighlightKind, SyntaxPalette}; pub use theme::*; @@ -67,13 +62,11 @@ pub const TABBAR_HEIGHT: Pixels = px(36.0); pub const SIDEBAR_WIDTH: Pixels = px(240.); pub fn init(cx: &mut App) { - registry::init(cx); appearance::init(AppearanceMode::System, cx); Theme::sync_scrollbar_appearance(cx); } -/// An alternative palette source. Runs first on every appearance install, so -/// an override survives a light/dark switch. +/// An alternative palette source. pub type PaletteBuilder = fn(ThemeMode) -> ThemeColors; struct GlobalPaletteBuilder(Option); @@ -82,13 +75,8 @@ impl Global for GlobalPaletteBuilder {} /// Install a brand or an alternative palette on the app. 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); - /// Install an alternative palette builder and repaint. The builder runs - /// first on every appearance install. fn set_palette(&mut self, build: PaletteBuilder); - /// Change the appearance preference (`System`, `Light` or `Dark`). 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. -/// -/// 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) { let Some(theme) = cx.try_global::() else { return; @@ -163,8 +144,6 @@ pub fn sync_base(cx: &mut App) { tokens.accent = colors.element_hover; tokens.accent_foreground = colors.text; tokens.destructive = colors.danger_strong; - // Bezel's destructive pairing: white ink on the red plate in both - // appearances. tokens.destructive_foreground = gpui::white(); tokens.border = colors.border; tokens.input = colors.border; @@ -185,41 +164,14 @@ impl ActiveTheme for App { #[derive(Debug, Clone)] pub struct Theme { - /// Theme colors pub colors: ThemeColors, - - /// Theme family - pub theme: Rc, - - /// The appearance of the theme (light or dark). pub mode: ThemeMode, - - /// The font family for the application. 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, - - /// Monospace family for code and terminal. pub font_mono: SharedString, - - /// The root font size for the application, default is 15px. 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, - - /// Show the scrollbar mode, default: scrolling pub scrollbar_mode: ScrollbarMode, - - /// Notification settings pub notification: NotificationSettings, } @@ -269,26 +221,13 @@ impl Theme { }; } - /// Apply a new theme to the application. - pub fn apply_theme(new_theme: Rc, window: Option<&mut Window>, cx: &mut App) { - let theme = cx.global_mut::(); - 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 pub fn change(mode: M, window: Option<&mut Window>, cx: &mut App) where M: Into, { if !cx.has_global::() { - let default_theme = ThemeFamily::default(); - let theme = Theme::from(default_theme); - - cx.set_global(theme); + cx.set_global(Theme::default()); } let mode = mode.into(); @@ -297,23 +236,13 @@ impl Theme { .and_then(|builder| builder.0); let mut colors = match builder { Some(build) => build(mode), - None => { - let family = Theme::global_mut(cx).theme.clone(); - if mode.is_dark() { - *family.dark() - } else { - *family.light() - } - } + None => ThemeColors::for_appearance(mode), }; - // A default brand changes nothing, so a JSON theme's own colors are - // left alone; anything else rotates the tokens and re-derives the - // legacy fields from them. + // A default brand changes nothing; anything else rotates the tokens. let installed_brand = brand::installed(cx); if !installed_brand.is_default() { installed_brand.apply(mode, &mut colors); - colors.sync_legacy(mode); } let theme = Theme::global_mut(cx); @@ -338,8 +267,8 @@ impl Theme { } } -impl From for Theme { - fn from(family: ThemeFamily) -> Self { +impl Default for Theme { + fn default() -> Self { let platform = PlatformKind::platform(); let mode = ThemeMode::default(); @@ -349,25 +278,16 @@ impl From for Theme { _ => ".SystemUIFont", }; - // Define the theme colors based on the appearance - let colors = match mode { - ThemeMode::Light => family.light(), - ThemeMode::Dark => family.dark(), - }; - - Theme { + Self { font_size: px(15.), font_family: font_family.into(), font_body: font_family.into(), font_mono: system_mono().into(), - radius: px(layout::button_radius()), - radius_lg: px(layout::surface_radius()), shadow: true, scrollbar_mode: ScrollbarMode::default(), notification: NotificationSettings::default(), mode, - colors: *colors, - theme: Rc::new(family), + colors: ThemeColors::for_appearance(mode), } } } diff --git a/crates/theme/src/palette.rs b/crates/theme/src/palette.rs index e3c96f59..f88aa993 100644 --- a/crates/theme/src/palette.rs +++ b/crates/theme/src/palette.rs @@ -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 crate::paint::{band_for, hairline_for}; use crate::syntax::SyntaxPalette; 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 { - let mut colors = ThemeColors { - // ---- neutral surfaces ---- - bg: color::grey(6), // main panel — #060606 - surface: color::grey(13), // shell / sidebar — #0d0d0d + let warning = color::oklch(0.828, 0.189, 84.429); + ThemeColors { + bg: color::grey(6), + surface: color::grey(13), surface_raised: color::neutral(0.235), surface_card: color::grey(0x0e), surface_dialog: color::grey(0x10), 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), - // `--color-active`, a rung above the hover: white at 12%. element_active: hsla(0.0, 0.0, 1.0, 0.12), - // ---- hairlines ---- border_faint: hairline_for(ThemeMode::Dark, 0.06), border: hairline_for(ThemeMode::Dark, 0.08), border_strong: hairline_for(ThemeMode::Dark, 0.14), - // ---- text ---- text: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.text), text_muted: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.muted), text_faint: TextInk::color(ThemeMode::Dark, Ink::APPKIT.dark.faint), text_dim: color::grey(0x98), - // ---- high-contrast solid ---- solid: color::neutral(0.922), on_solid: color::grey(0x0e), - // ---- accents, neutral by default ---- accent: color::neutral(0.673), accent_strong: color::neutral(0.922), on_accent: color::grey(0x0e), - // ---- semantic ---- - danger: color::oklch(0.704, 0.191, 22.216), // red-400 - danger_muted: color::oklch(0.808, 0.114, 19.571), // red-300 + danger: color::oklch(0.704, 0.191, 22.216), + danger_muted: color::oklch(0.808, 0.114, 19.571), 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), // emerald-400 - success_muted: color::oklch(0.845, 0.143, 164.978), // emerald-300 - busy: color::oklch(0.718, 0.202, 349.761), // pink-400 + success: color::oklch(0.765, 0.177, 163.223), + success_muted: color::oklch(0.845, 0.143, 164.978), + busy: color::oklch(0.718, 0.202, 349.761), - // ---- components ---- surface_raised_hover: color::neutral(0.29), band: band_for(ThemeMode::Dark), 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_target: hsla(0.0, 0.0, 1.0, 0.10), - // ---- code & diff ---- code_text: color::neutral(0.94), code_wash: hsla(0.0, 0.0, 1.0, 0.08), syntax: SyntaxPalette::dark( @@ -76,82 +56,54 @@ pub(crate) fn dark() -> ThemeColors { color::neutral(0.60), color::oklch(0.704, 0.191, 22.216), ), - diff_add: color::oklch(0.765, 0.177, 163.223), // emerald-400 - diff_del: color::oklch(0.704, 0.191, 22.216), // red-400 + diff_add: color::oklch(0.765, 0.177, 163.223), + diff_del: color::oklch(0.704, 0.191, 22.216), diff_hunk_bg: hsla(0.6, 0.35, 0.6, 0.05), - ..ThemeColors::legacy_default() - }; - // 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 + warning, + warning_muted: warning.alpha(0.16), + } } -/// 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 { - let mut colors = ThemeColors { - // ---- neutral surfaces ---- - bg: color::grey(0xff), // main panel — clean white - // 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. + let warning = color::oklch(0.555, 0.163, 48.998); + ThemeColors { + bg: color::grey(0xff), 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_card: color::grey(0xff), surface_dialog: 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), - // The same rung in light: black at 6%. element_active: hsla(0.0, 0.0, 0.0, 0.06), - // ---- hairlines ---- border_faint: hairline_for(ThemeMode::Light, 0.06), border: hairline_for(ThemeMode::Light, 0.08), border_strong: hairline_for(ThemeMode::Light, 0.14), - // ---- 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_faint: TextInk::color(ThemeMode::Light, Ink::APPKIT.light.faint), text_dim: color::neutral(0.50), - // ---- high-contrast solid ---- solid: color::neutral(0.205), on_solid: color::neutral(0.985), - // ---- accents, neutral by default ---- accent: color::neutral(0.511), accent_strong: color::neutral(0.205), on_accent: color::neutral(0.985), - // ---- semantic ---- - danger: color::oklch(0.577, 0.245, 27.325), // red-600 - danger_muted: color::oklch(0.505, 0.213, 27.518), // red-700 + danger: color::oklch(0.577, 0.245, 27.325), + danger_muted: color::oklch(0.505, 0.213, 27.518), danger_strong: color::oklch(0.51, 0.20, 25.0), - warning: color::oklch(0.555, 0.163, 48.998), // amber-700 — carries 12px text - success: color::oklch(0.596, 0.145, 163.225), // emerald-600 - success_muted: color::oklch(0.508, 0.118, 165.612), // emerald-700 - busy: color::oklch(0.592, 0.249, 0.584), // pink-600 + warning, + warning_muted: warning.alpha(0.10), + success: color::oklch(0.596, 0.145, 163.225), + 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), 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), selection: rgb(0xb3d7ff).into(), cursor: color::neutral(0.205), @@ -160,7 +112,6 @@ pub(crate) fn light() -> ThemeColors { drop_line: hairline_for(ThemeMode::Light, 0.35), drop_target: hsla(0.0, 0.0, 0.0, 0.06), - // ---- code & diff ---- code_text: color::neutral(0.18), code_wash: hsla(0.0, 0.0, 0.0, 0.06), syntax: SyntaxPalette::light( @@ -168,18 +119,10 @@ pub(crate) fn light() -> ThemeColors { color::neutral(0.48), color::oklch(0.505, 0.213, 27.518), ), - diff_add: color::oklch(0.596, 0.145, 163.225), // emerald-600 - diff_del: color::oklch(0.577, 0.245, 27.325), // red-600 + diff_add: color::oklch(0.596, 0.145, 163.225), + diff_del: color::oklch(0.577, 0.245, 27.325), 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 { diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs deleted file mode 100644 index 5312b168..00000000 --- a/crates/theme/src/registry.rs +++ /dev/null @@ -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); - -impl Global for GlobalThemeRegistry {} - -pub struct ThemeRegistry { - /// Map of theme names to theme families - themes: HashMap>, -} - -impl ThemeRegistry { - /// Retrieve the global theme registry state - pub fn global(cx: &App) -> Entity { - cx.global::().0.clone() - } - - /// Set the global theme registry instance - pub(crate) fn set_global(state: Entity, cx: &mut App) { - cx.set_global(GlobalThemeRegistry(state)); - } - - /// Create a new theme registry instance - pub(crate) fn new(cx: &mut Context) -> 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) -> Result { - // 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> { - &self.themes - } -} diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs deleted file mode 100644 index ef7c1e11..00000000 --- a/crates/theme/src/scale.rs +++ /dev/null @@ -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); - -impl FromIterator for ColorScale { - fn from_iter>(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 - } -} diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index f366bc77..37982199 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1,11 +1,7 @@ -use std::path::Path; - -use gpui::{SharedString, WindowAppearance}; -use schemars::JsonSchema; +use gpui::WindowAppearance; 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)] pub enum ThemeMode { #[default] @@ -44,327 +40,3 @@ impl From 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>(path: P) -> anyhow::Result { - 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 { - 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(); - } -} diff --git a/crates/theme/tests/contrast.rs b/crates/theme/tests/contrast.rs index d8d7d7a1..681c516f 100644 --- a/crates/theme/tests/contrast.rs +++ b/crates/theme/tests/contrast.rs @@ -4,7 +4,7 @@ //! palette was tuned for. 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): // 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 - ); -} diff --git a/crates/ui/src/scroll/scrollable.rs b/crates/ui/src/scroll/scrollable.rs index 24249fc4..e63cc301 100644 --- a/crates/ui/src/scroll/scrollable.rs +++ b/crates/ui/src/scroll/scrollable.rs @@ -3,10 +3,9 @@ use std::panic::Location; use gpui::prelude::FluentBuilder; use gpui::{ 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 theme::ActiveTheme as _; +use gpui_base::{Scrollbar, ScrollbarAxis}; 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( - 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. #[derive(IntoElement)] pub struct Scrollable { @@ -130,7 +82,11 @@ where }) .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"), + ) } } diff --git a/crates/workspace/src/dialogs/settings.rs b/crates/workspace/src/dialogs/settings.rs index afa123e1..5b9cac70 100644 --- a/crates/workspace/src/dialogs/settings.rs +++ b/crates/workspace/src/dialogs/settings.rs @@ -4,7 +4,7 @@ use gpui::{ Window, div, px, }; use settings::AppSettings; -use theme::{ActiveTheme, Theme, ThemeMode}; +use theme::{ActiveTheme, AppExt as _, AppearanceMode}; use ui::button::{Button, ButtonVariants}; use ui::group_box::{GroupBox, GroupBoxVariants}; use ui::input::{Input, InputState}; @@ -47,10 +47,10 @@ impl Preferences { } } - /// Set the theme mode (light or dark) - fn set_theme_mode(mode: ThemeMode, window: &mut Window, cx: &mut App) { - AppSettings::update_theme_mode(mode, cx); - Theme::change(mode, Some(window), cx); + /// Set the appearance mode (system, light or dark) + fn set_appearance_mode(mode: AppearanceMode, cx: &mut App) { + AppSettings::update_appearance_mode(mode, cx); + cx.set_appearance_mode(mode); } } @@ -58,14 +58,13 @@ impl Render for Preferences { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { const SCREENING: &str = "Show an screening dialog to verify the unknown sender."; 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 RESET: &str = "Reset the theme to the default one."; let screening = AppSettings::get_screening(cx); let hide_avatar = AppSettings::get_hide_avatar(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() .gap_4() @@ -113,46 +112,17 @@ impl Render for Preferences { ), ) .child( - Button::new("theme-mode") - .label(theme_mode.name()) + Button::new("appearance-mode") + .label(appearance_mode.label()) .ghost_alt() .small() .dropdown_menu(|this, _window, _cx| { - this.item(PopupMenuItem::new("Light").on_click( - |_, window, cx| { - Self::set_theme_mode(ThemeMode::Light, window, 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); + AppearanceMode::ALL.iter().fold(this, |menu, mode| { + menu.item(PopupMenuItem::new(mode.label()).on_click( + move |_, _window, cx| { + Self::set_appearance_mode(*mode, cx); + }, + )) }) }), ), diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 51b98e8f..912c3b2c 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use ::settings::AppSettings; use anyhow::Error; use auto_update::AutoUpdater; use chat::{ChatEvent, ChatRegistry}; @@ -15,11 +14,11 @@ use nostr_sdk::prelude::*; use serde::Deserialize; use smallvec::{SmallVec, smallvec}; use state::{NostrRegistry, StateEvent}; -use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry, button_radius}; -use ui::button::{Button, ButtonVariants}; +use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme}; +use ui::button::Button; use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle}; 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::{join_group, new_chat, new_group, profile, relays, settings}; @@ -40,7 +39,6 @@ struct MsgRelayNotification; #[derive(Action, Clone, PartialEq, Eq, Deserialize)] #[action(namespace = workspace, no_json)] enum Command { - ToggleTheme, Update, RefreshMessagingRelays, BackupEncryption, @@ -327,9 +325,6 @@ impl Workspace { Command::ResetEncryption => { self.confirm_reset_encryption(window, cx); } - Command::ToggleTheme => { - self.theme_selector(window, cx); - } Command::BackupEncryption => { let device = DeviceRegistry::global(cx).downgrade(); let save_dialog = cx.prompt_for_new_path(download_dir(), Some("encryption.txt")); @@ -448,85 +443,6 @@ impl Workspace { .child(restore.clone()) }); } - - fn theme_selector(&mut self, window: &mut Window, cx: &mut Context) { - 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 { diff --git a/crates/workspace/src/panels/greeter.rs b/crates/workspace/src/panels/greeter.rs index 88f22a38..347d495e 100644 --- a/crates/workspace/src/panels/greeter.rs +++ b/crates/workspace/src/panels/greeter.rs @@ -8,7 +8,6 @@ use ui::button::{Button, ButtonVariants}; use ui::dock::{Panel, PanelEvent}; use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex}; -use crate::Command; use crate::dialogs::profile; pub fn init(window: &mut Window, cx: &mut App) -> Entity { @@ -138,19 +137,6 @@ impl Render for GreeterPanel { .on_click(cx.listener(move |this, _, 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, - ); - })), ), ), ), diff --git a/crates/workspace/src/sidebar/header.rs b/crates/workspace/src/sidebar/header.rs index ffbab4a3..36cc7f62 100644 --- a/crates/workspace/src/sidebar/header.rs +++ b/crates/workspace/src/sidebar/header.rs @@ -57,7 +57,6 @@ impl Sidebar { ) .menu_with_icon("Relays", IconName::Relay, Box::new(Command::ShowRelayList)) .separator() - .menu_with_icon("Themes", IconName::Sun, Box::new(Command::ToggleTheme)) .menu_with_icon( "Settings", IconName::Settings, diff --git a/desktop/src/main.rs b/desktop/src/main.rs index a1efb187..53591e91 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -34,7 +34,7 @@ fn main() { // Initialize components ui::init(cx); - // Initialize theme registry + // Initialize theme theme::init(cx); // Load embedded fonts in assets/fonts diff --git a/docs/ui-theme-bezel-migration.md b/docs/ui-theme-bezel-migration.md index 835f5796..c2c5e376 100644 --- a/docs/ui-theme-bezel-migration.md +++ b/docs/ui-theme-bezel-migration.md @@ -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 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` - with `appearance_mode: AppearanceMode` (`System`/`Light`/`Dark`). - Serde-compat: custom `Deserialize` that maps the old `Light`/`Dark` - values and falls back to `System` (or version the field) so existing - configs keep loading. `apply_theme` becomes: install registry overrides - → `set_brand` → `appearance::set_mode`. -- [ ] `ThemeRegistry` + `assets/themes/*.json`: new schema — - `{ id, name, author, brand?: { tint, accent, radius }, overrides: { - light?: {...}, dark?: {...} } }` where overrides are a *subset* of - semantic tokens. Re-derive the 8 shipped themes as override deltas over - the new base palette (catppuccin hues go through `Brand.tint`; only hues - rotation cannot reach stay as overrides — flexoki is the likely - candidate). -- [ ] `workspace/src/dialogs/settings.rs`: add the `System` appearance option - next to Light/Dark. -- [ ] Contrast tests from Phase 1 run against every registered theme — a - shipped theme that fails contrast is a bug in that JSON, fix the JSON. +The original plan (appearance_mode with back-compat, registry re-schema, +brand-izing the 8 shipped JSONs) was superseded by a decision: **coop no +longer ships multiple themes**. The bezel token palettes (dark and light, +optionally rotated by a `Brand`) are the only theming surface. Done: + +- [x] `crates/settings`: persisted `theme_mode: ThemeMode` → + `appearance_mode: AppearanceMode` with + `#[serde(default, alias = "theme_mode")]` — an old config's pinned + Light/Dark survives; absence falls back to `System`. Round-trip tests + cover both. `set_theme`/`reset_theme` deleted; `apply_theme` is now + `cx.set_appearance_mode(mode)`. +- [x] `ThemeRegistry`, `assets/themes/*.json` (8 files), and the + `themes/**/*` asset embed are deleted. `theme::init` is + `appearance::init` + scrollbar sync. +- [x] `ThemeFamily`, `from_file`/`from_assets`, the theme picker dialog + (`theme_selector` + `Command::ToggleTheme` + its header/greeter menu + 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`, `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 -- [ ] 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 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 that every opened window observes appearance changes (`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. -- [ ] Final census run: only new token names remain. +- [x] Final census run: only new token names remain. ## Risks diff --git a/web/src/lib.rs b/web/src/lib.rs index 91398653..8cc205f9 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -50,7 +50,7 @@ pub fn run() -> Result<(), JsValue> { // Initialize components ui::init(cx); - // Initialize theme registry + // Initialize theme theme::init(cx); // Initialize settings