diff --git a/Cargo.lock b/Cargo.lock index 00ae2000..2d0bdee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7440,11 +7440,9 @@ dependencies = [ "gpui-base", "gpui-pre", "log", - "schemars", "serde", "serde_json", "sha2 0.10.9", - "tempfile", "thiserror 2.0.21", ] 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..f8d74015 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 nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use smallvec::{SmallVec, smallvec}; -use theme::{Theme, ThemeFamily, ThemeMode}; +use theme::{AccentSelection, Appearance, SurfacePreference, Theme, ThemeSelection}; pub fn init(cx: &mut App) { AppSettings::set_global(cx.new(AppSettings::new), cx) @@ -53,7 +51,10 @@ macro_rules! setting_getters { } setting_accessors! { - pub theme_mode: ThemeMode, + pub appearance: Appearance, + pub accent_selection: AccentSelection, + pub surface_preference: SurfacePreference, + pub theme_selection: ThemeSelection, pub hide_avatar: bool, pub screening: bool, pub nip4e: bool, @@ -119,11 +120,26 @@ impl RoomConfig { /// Settings #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Settings { - /// Theme - pub theme: Option, + /// The per-appearance theme variant selection + #[serde(default)] + pub theme_selection: ThemeSelection, - /// Theme mode - pub theme_mode: ThemeMode, + /// The active appearance; persisted under the legacy `theme_mode` key so + /// old settings files ("Light"/"Dark") keep loading + #[serde( + default, + rename = "theme_mode", + deserialize_with = "deserialize_appearance" + )] + pub appearance: Appearance, + + /// The accent color override applied on top of the selected theme + #[serde(default)] + pub accent_selection: AccentSelection, + + /// The preferred surface treatment + #[serde(default)] + pub surface_preference: SurfacePreference, /// Hide user avatars pub hide_avatar: bool, @@ -152,8 +168,10 @@ pub struct Settings { impl Default for Settings { fn default() -> Self { Self { - theme: None, - theme_mode: ThemeMode::default(), + theme_selection: ThemeSelection::default(), + appearance: Appearance::default(), + accent_selection: AccentSelection::default(), + surface_preference: SurfacePreference::default(), hide_avatar: false, screening: true, nip4e: false, @@ -165,6 +183,21 @@ impl Default for Settings { } } +/// Accept the legacy "Light"/"Dark" capitalization alongside "light"/"dark". +fn deserialize_appearance<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let value = String::deserialize(deserializer)?; + match value.to_lowercase().as_str() { + "light" => Ok(Appearance::Light), + "dark" => Ok(Appearance::Dark), + _ => Err(serde::de::Error::custom(format!( + "unknown appearance: {value}" + ))), + } +} + struct GlobalAppSettings(Entity); impl Global for GlobalAppSettings {} @@ -264,44 +297,43 @@ impl AppSettings { } } - /// Set theme - pub fn set_theme(&mut self, theme: T, window: &mut Window, cx: &mut Context) - where - T: Into, - { - // Update settings + /// Set the theme variant for the current appearance + pub fn set_theme_variant(&mut self, id: String, window: &mut Window, cx: &mut Context) { self.inner.update(cx, |this, cx| { - this.theme = Some(theme.into()); + let mut selection = this.theme_selection.clone(); + selection.set_variant(this.appearance, id); + this.theme_selection = selection; cx.notify(); }); - // Apply the new theme self.apply_theme(Some(window), cx); } - /// Reset theme + /// Reset the theme selection to the defaults pub fn reset_theme(&mut self, window: &mut Window, cx: &mut Context) { self.inner.update(cx, |this, cx| { - this.theme = None; + this.theme_selection = ThemeSelection::default(); 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; + pub fn apply_theme(&mut self, window: Option<&mut Window>, cx: &mut Context) { + let (selection, accent, surface, appearance) = { + let settings = self.inner.read(cx); + ( + settings.theme_selection.clone(), + settings.accent_selection, + settings.surface_preference, + settings.appearance, + ) + }; - 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); - } + Theme::set_selection(selection, cx); + Theme::set_accent_selection(accent, cx); + Theme::set_surface_preference(surface, cx); + Theme::change(appearance, window, cx); } /// Check if decoupling encryption key is enabled diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index e016e270..f0e04b24 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -13,7 +13,3 @@ serde.workspace = true serde_json.workspace = true sha2.workspace = true thiserror.workspace = true -schemars.workspace = true - -[dev-dependencies] -tempfile = "3.10" diff --git a/crates/theme/src/builtins.rs b/crates/theme/src/builtins.rs index 057afcc2..d31576e3 100644 --- a/crates/theme/src/builtins.rs +++ b/crates/theme/src/builtins.rs @@ -1,5 +1,3 @@ -// Ported from zeronsh/zeron crates/theme/src/builtins.rs -// at revision 916cb1ccb1342c2061963a5f6ecf5c977ba46f2c (MIT). #![allow(dead_code)] use std::collections::BTreeMap; diff --git a/crates/theme/src/colors.rs b/crates/theme/src/colors.rs deleted file mode 100644 index f2ca4bd0..00000000 --- a/crates/theme/src/colors.rs +++ /dev/null @@ -1,583 +0,0 @@ -use gpui::{Hsla, Rgba, hsla}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::scale::{ColorScale, ColorScaleSet}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize, JsonSchema)] -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, - - // Border colors - pub border: Hsla, - pub border_variant: Hsla, - pub border_focused: Hsla, - pub border_selected: Hsla, - pub border_transparent: Hsla, - pub border_disabled: Hsla, - pub ring: Hsla, - - // Text colors - pub text: Hsla, - 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, -} - -/// The default colors for the theme. -/// -/// Themes that do not specify all colors are refined off of these defaults. -impl ThemeColors { - /// Returns the default colors for light themes. - /// - /// Themes that do not specify all colors are refined off of these defaults. - pub(crate) fn light() -> Self { - Self { - background: neutral().light().step_1(), - surface_background: neutral().light().step_2(), - elevated_surface_background: neutral().light().step_3(), - panel_background: neutral().light().step_1(), - overlay: neutral().light_alpha().step_3(), - title_bar: neutral().light().step_2(), - title_bar_inactive: neutral().light().step_1(), - window_border: hsl(240.0, 5.9, 78.0), - - border: neutral().light().step_6(), - border_variant: neutral().light().step_4(), - border_focused: brand().light().step_7(), - border_selected: brand().light().step_7(), - border_transparent: gpui::transparent_black(), - border_disabled: neutral().light().step_3(), - ring: brand().light().step_8(), - - text: neutral().light().step_12(), - text_muted: neutral().light().step_11(), - text_placeholder: neutral().light().step_10(), - text_accent: brand().light().step_9(), - text_danger: danger().light().step_9(), - text_warning: warning().light().step_9(), - - icon: neutral().light().step_11(), - icon_muted: neutral().light().step_10(), - icon_accent: brand().light().step_11(), - - element_foreground: brand().light().step_12(), - element_background: brand().light().step_9(), - element_hover: brand().light_alpha().step_10(), - element_active: brand().light().step_10(), - element_selected: brand().light().step_11(), - element_disabled: brand().light_alpha().step_3(), - - secondary_foreground: brand().light().step_11(), - secondary_background: brand().light().step_3(), - secondary_hover: brand().light_alpha().step_4(), - secondary_active: brand().light().step_5(), - secondary_selected: brand().light().step_5(), - secondary_disabled: brand().light_alpha().step_3(), - - danger_foreground: danger().light().step_12(), - danger_background: danger().light().step_3(), - danger_hover: danger().light_alpha().step_4(), - danger_active: danger().light().step_5(), - danger_selected: danger().light().step_5(), - danger_disabled: danger().light_alpha().step_3(), - - warning_foreground: warning().light().step_12(), - warning_background: warning().light().step_3(), - warning_hover: warning().light_alpha().step_4(), - warning_active: warning().light().step_5(), - warning_selected: warning().light().step_5(), - warning_disabled: warning().light_alpha().step_3(), - - ghost_element_background: gpui::transparent_black(), - ghost_element_background_alt: neutral().light().step_3(), - ghost_element_hover: neutral().light_alpha().step_4(), - ghost_element_active: neutral().light().step_5(), - ghost_element_selected: neutral().light().step_5(), - ghost_element_disabled: neutral().light_alpha().step_2(), - - tab_background: neutral().light().step_3(), - tab_foreground: neutral().light().step_11(), - tab_hover_background: neutral().light_alpha().step_4(), - tab_active_background: neutral().light().step_1(), - tab_active_foreground: neutral().light().step_12(), - - scrollbar_thumb_background: neutral().light_alpha().step_3(), - scrollbar_thumb_hover_background: neutral().light_alpha().step_4(), - scrollbar_thumb_border: gpui::transparent_black(), - scrollbar_track_background: gpui::transparent_black(), - scrollbar_track_border: gpui::transparent_black(), - - drop_target_background: brand().light_alpha().step_2(), - cursor: hsl(200., 100., 50.), - selection: hsl(200., 100., 50.).alpha(0.25), - } - } - - /// Returns the default colors for dark themes. - /// - /// Themes that do not specify all colors are refined off of these defaults. - pub(crate) fn dark() -> Self { - Self { - background: neutral().dark().step_1(), - surface_background: neutral().dark().step_2(), - elevated_surface_background: neutral().dark().step_3(), - panel_background: neutral().dark().step_1(), - overlay: neutral().dark_alpha().step_3(), - title_bar: neutral().dark().step_2(), - title_bar_inactive: neutral().dark().step_1(), - window_border: hsl(240.0, 3.7, 28.0), - - border: neutral().dark().step_6(), - border_variant: neutral().dark().step_4(), - border_focused: brand().dark().step_7(), - border_selected: brand().dark().step_7(), - border_transparent: gpui::transparent_black(), - border_disabled: neutral().dark().step_3(), - ring: brand().dark().step_8(), - - text: neutral().dark().step_12(), - text_muted: neutral().dark().step_11(), - text_placeholder: neutral().dark().step_10(), - text_accent: brand().dark().step_9(), - text_danger: danger().dark().step_9(), - text_warning: warning().dark().step_9(), - - icon: neutral().dark().step_11(), - icon_muted: neutral().dark().step_10(), - icon_accent: brand().dark().step_11(), - - element_foreground: brand().dark().step_1(), - element_background: brand().dark().step_9(), - element_hover: brand().dark_alpha().step_10(), - element_active: brand().dark().step_10(), - element_selected: brand().dark().step_11(), - element_disabled: brand().dark_alpha().step_3(), - - secondary_foreground: brand().dark().step_12(), - secondary_background: brand().dark().step_3(), - secondary_hover: brand().dark_alpha().step_4(), - secondary_active: brand().dark().step_5(), - secondary_selected: brand().dark().step_5(), - secondary_disabled: brand().dark_alpha().step_3(), - - danger_foreground: danger().dark().step_12(), - danger_background: danger().dark().step_3(), - danger_hover: danger().dark_alpha().step_4(), - danger_active: danger().dark().step_5(), - danger_selected: danger().dark().step_5(), - danger_disabled: danger().dark_alpha().step_3(), - - warning_foreground: warning().dark().step_12(), - warning_background: warning().dark().step_3(), - warning_hover: warning().dark_alpha().step_4(), - warning_active: warning().dark().step_5(), - warning_selected: warning().dark().step_5(), - warning_disabled: warning().dark_alpha().step_3(), - - ghost_element_background: gpui::transparent_black(), - ghost_element_background_alt: neutral().dark().step_3(), - ghost_element_hover: neutral().dark_alpha().step_4(), - ghost_element_active: neutral().dark().step_5(), - ghost_element_selected: neutral().dark().step_5(), - ghost_element_disabled: neutral().dark_alpha().step_2(), - - tab_background: neutral().dark().step_3(), - tab_foreground: neutral().dark().step_11(), - tab_hover_background: neutral().dark_alpha().step_4(), - tab_active_background: neutral().dark().step_1(), - tab_active_foreground: neutral().dark().step_12(), - - scrollbar_thumb_background: neutral().dark_alpha().step_3(), - scrollbar_thumb_hover_background: neutral().dark_alpha().step_4(), - scrollbar_thumb_border: gpui::transparent_black(), - scrollbar_track_background: gpui::transparent_black(), - scrollbar_track_border: gpui::transparent_black(), - - drop_target_background: brand().dark_alpha().step_2(), - cursor: hsl(200., 100., 50.), - selection: hsl(200., 100., 50.).alpha(0.25), - } - } -} - -fn neutral() -> ColorScaleSet { - gray() -} - -fn brand() -> ColorScaleSet { - yellow() -} - -fn danger() -> ColorScaleSet { - tomato() -} - -fn warning() -> ColorScaleSet { - orange() -} - -/// Make a [gpui::Hsla] color. -/// -/// - h: 0..360.0 -/// - s: 0.0..100.0 -/// - l: 0.0..100.0 -pub(crate) fn hsl(h: f32, s: f32, l: f32) -> Hsla { - hsla(h / 360., s / 100.0, l / 100.0, 1.0) -} - -type StaticColorScale = [&'static str; 12]; - -struct StaticColorScaleSet { - light: StaticColorScale, - light_alpha: StaticColorScale, - dark: StaticColorScale, - dark_alpha: StaticColorScale, -} - -impl TryFrom for ColorScaleSet { - type Error = anyhow::Error; - - fn try_from(value: StaticColorScaleSet) -> Result { - fn to_color_scale(scale: StaticColorScale) -> Result { - scale - .into_iter() - .map(|color| Rgba::try_from(color).map(Hsla::from)) - .collect::, _>>() - .map(ColorScale::from_iter) - } - - Ok(Self::new( - to_color_scale(value.light)?, - to_color_scale(value.light_alpha)?, - to_color_scale(value.dark)?, - to_color_scale(value.dark_alpha)?, - )) - } -} - -pub(crate) fn gray() -> ColorScaleSet { - StaticColorScaleSet { - light: [ - "#fcfcfcff", - "#f9f9f9ff", - "#f0f0f0ff", - "#e8e8e8ff", - "#e0e0e0ff", - "#d9d9d9ff", - "#cececeff", - "#bbbbbbff", - "#8d8d8dff", - "#838383ff", - "#646464ff", - "#202020ff", - ], - light_alpha: [ - "#00000003", - "#00000006", - "#0000000f", - "#00000017", - "#0000001f", - "#00000026", - "#00000031", - "#00000044", - "#00000072", - "#0000007c", - "#0000009b", - "#000000df", - ], - dark: [ - "#111111ff", - "#191919ff", - "#222222ff", - "#2a2a2aff", - "#313131ff", - "#3a3a3aff", - "#484848ff", - "#606060ff", - "#6e6e6eff", - "#7b7b7bff", - "#b4b4b4ff", - "#eeeeeeff", - ], - dark_alpha: [ - "#00000000", - "#ffffff09", - "#ffffff12", - "#ffffff1b", - "#ffffff22", - "#ffffff2c", - "#ffffff3b", - "#ffffff55", - "#ffffff64", - "#ffffff72", - "#ffffffaf", - "#ffffffed", - ], - } - .try_into() - .unwrap() -} - -pub(crate) fn yellow() -> ColorScaleSet { - StaticColorScaleSet { - light: [ - "#fdfdf9ff", - "#fefce9ff", - "#fffab8ff", - "#fff394ff", - "#ffe770ff", - "#f3d768ff", - "#e4c767ff", - "#d5ae39ff", - "#ffe629ff", - "#ffdc00ff", - "#9e6c00ff", - "#473b1fff", - ], - light_alpha: [ - "#aaaa0006", - "#f4dd0016", - "#ffee0047", - "#ffe3016b", - "#ffd5008f", - "#ebbc0097", - "#d2a10098", - "#c99700c6", - "#ffe100d6", - "#ffdc00ff", - "#9e6c00ff", - "#2e2000e0", - ], - dark: [ - "#14120bff", - "#1b180fff", - "#2d2305ff", - "#362b00ff", - "#433500ff", - "#524202ff", - "#665417ff", - "#836a21ff", - "#ffe629ff", - "#ffff57ff", - "#f5e147ff", - "#f6eeb4ff", - ], - dark_alpha: [ - "#d1510004", - "#f9b4000b", - "#ffaa001e", - "#fdb70028", - "#febb0036", - "#fec40046", - "#fdcb225c", - "#fdca327b", - "#ffe629ff", - "#ffff57ff", - "#fee949f5", - "#fef6baf6", - ], - } - .try_into() - .unwrap() -} - -pub(crate) fn orange() -> ColorScaleSet { - StaticColorScaleSet { - light: [ - "#fefcfbff", - "#fff7edff", - "#ffefd6ff", - "#ffdfb5ff", - "#ffd19aff", - "#ffc182ff", - "#f5ae73ff", - "#ec9455ff", - "#f76b15ff", - "#ef5f00ff", - "#cc4e00ff", - "#582d1dff", - ], - light_alpha: [ - "#c0400004", - "#ff8e0012", - "#ff9c0029", - "#ff91014a", - "#ff8b0065", - "#ff81007d", - "#ed6c008c", - "#e35f00aa", - "#f65e00ea", - "#ef5f00ff", - "#cc4e00ff", - "#431200e2", - ], - dark: [ - "#17120eff", - "#1e160fff", - "#331e0bff", - "#462100ff", - "#562800ff", - "#66350cff", - "#7e451dff", - "#a35829ff", - "#f76b15ff", - "#ff801fff", - "#ffa057ff", - "#ffe0c2ff", - ], - dark_alpha: [ - "#ec360007", - "#fe6d000e", - "#fb6a0025", - "#ff590039", - "#ff61004a", - "#fd75045c", - "#ff832c75", - "#fe84389d", - "#fe6d15f7", - "#ff801fff", - "#ffa057ff", - "#ffe0c2ff", - ], - } - .try_into() - .unwrap() -} - -pub(crate) fn tomato() -> ColorScaleSet { - StaticColorScaleSet { - light: [ - "#fffcfcff", - "#fff8f7ff", - "#feebe7ff", - "#ffdcd3ff", - "#ffcdc2ff", - "#fdbdafff", - "#f5a898ff", - "#ec8e7bff", - "#e54d2eff", - "#dd4425ff", - "#d13415ff", - "#5c271fff", - ], - light_alpha: [ - "#ff000003", - "#ff200008", - "#f52b0018", - "#ff35002c", - "#ff2e003d", - "#f92d0050", - "#e7280067", - "#db250084", - "#df2600d1", - "#d72400da", - "#cd2200ea", - "#460900e0", - ], - dark: [ - "#181111ff", - "#1f1513ff", - "#391714ff", - "#4e1511ff", - "#5e1c16ff", - "#6e2920ff", - "#853a2dff", - "#ac4d39ff", - "#e54d2eff", - "#ec6142ff", - "#ff977dff", - "#fbd3cbff", - ], - dark_alpha: [ - "#f1121208", - "#ff55330f", - "#ff35232b", - "#fd201142", - "#fe332153", - "#ff4f3864", - "#fd644a7d", - "#fe6d4ea7", - "#fe5431e4", - "#ff6847eb", - "#ff977dff", - "#ffd6cefb", - ], - } - .try_into() - .unwrap() -} diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index f4900f2e..c5106186 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -1,29 +1,21 @@ -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; +use gpui::{App, Pixels, px}; -use gpui::{App, Global, Pixels, SharedString, Window, px}; - -pub mod model; - -mod colors; +pub mod builtins; mod geometry; +pub mod model; mod notification; mod platform_kind; -mod registry; -mod scale; +mod resolved; +mod runtime; mod scrollbar_mode; -mod theme; -mod builtins; - -pub use colors::*; pub use geometry::*; +pub use model::*; pub use notification::*; pub use platform_kind::PlatformKind; -pub use registry::*; -pub use scale::*; +pub use resolved::{ResolvedAccentRoles, ResolvedColors}; +pub use runtime::Theme; pub use scrollbar_mode::*; -pub use theme::*; /// Defines window border radius for platforms that use client side decorations. pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0); @@ -44,8 +36,6 @@ pub const TABBAR_HEIGHT: Pixels = px(36.0); pub const SIDEBAR_WIDTH: Pixels = px(240.); pub fn init(cx: &mut App) { - registry::init(cx); - Theme::sync_system_appearance(None, cx); Theme::sync_scrollbar_appearance(cx); } @@ -68,7 +58,7 @@ pub fn sync_base(cx: &mut App) { return; }; - let appearance = if theme.mode.is_dark() { + let appearance = if theme.appearance.is_dark() { gpui_base::ThemeAppearance::Dark } else { gpui_base::ThemeAppearance::Light @@ -79,6 +69,7 @@ pub fn sync_base(cx: &mut App) { ScrollbarMode::Always => gpui_base::ScrollbarMode::Always, }; let colors = theme.colors; + let accent = theme.accent; let font_family = theme.font_family.clone(); let base = gpui_base::Theme::global_mut(cx); @@ -89,22 +80,22 @@ pub fn sync_base(cx: &mut App) { let tokens = &mut base.tokens.colors; tokens.background = colors.background; tokens.foreground = colors.text; - tokens.surface = colors.surface_background; + tokens.surface = colors.shell; tokens.surface_foreground = colors.text; - tokens.primary = colors.element_background; - tokens.primary_foreground = colors.element_foreground; - tokens.secondary = colors.secondary_background; - tokens.secondary_foreground = colors.secondary_foreground; - tokens.muted = colors.ghost_element_background_alt; + tokens.primary = accent.primary; + tokens.primary_foreground = accent.on; + tokens.secondary = colors.raised; + tokens.secondary_foreground = colors.text; + tokens.muted = colors.hover; tokens.muted_foreground = colors.text_muted; - tokens.accent = colors.ghost_element_hover; + tokens.accent = accent.wash; tokens.accent_foreground = colors.text; - tokens.destructive = colors.danger_background; - tokens.destructive_foreground = colors.danger_foreground; + tokens.destructive = colors.danger; + tokens.destructive_foreground = accent.on_danger; tokens.border = colors.border; - tokens.input = colors.border; - tokens.ring = colors.ring; - tokens.selection = colors.selection; + tokens.input = colors.input; + tokens.ring = accent.primary; + tokens.selection = accent.selection; } pub trait ActiveTheme { @@ -117,164 +108,3 @@ impl ActiveTheme for App { Theme::global(self) } } - -#[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 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, -} - -impl Deref for Theme { - type Target = ThemeColors; - - fn deref(&self) -> &Self::Target { - &self.colors - } -} - -impl DerefMut for Theme { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.colors - } -} - -impl Global for Theme {} - -impl Theme { - /// Returns the global theme reference - pub fn global(cx: &App) -> &Theme { - cx.global::() - } - - /// Returns the global theme mutable reference - pub(crate) fn global_mut(cx: &mut App) -> &mut Theme { - cx.global_mut::() - } - - /// Returns true if the theme is dark. - pub fn is_dark(&self) -> bool { - self.mode.is_dark() - } - - /// Sync the theme with the system appearance - pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) { - let appearance = window - .as_ref() - .map(|window| window.appearance()) - .unwrap_or_else(|| cx.window_appearance()); - - Self::change(appearance, window, cx); - } - - /// Sync the Scrollbar showing behavior with the system - pub fn sync_scrollbar_appearance(cx: &mut App) { - Theme::global_mut(cx).scrollbar_mode = if cx.should_auto_hide_scrollbars() { - ScrollbarMode::Scrolling - } else { - ScrollbarMode::Hover - }; - } - - /// 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); - } - - let mode = mode.into(); - let theme = cx.global_mut::(); - - // Set the theme mode - theme.mode = mode; - - // Set the theme colors - if mode.is_dark() { - theme.colors = *theme.theme.dark(); - } else { - theme.colors = *theme.theme.light(); - } - - // Refresh the window if available - if let Some(window) = window { - window.refresh(); - } - - // Keep the base-layer projection in step with the coop palette - sync_base(cx); - } -} - -impl From for Theme { - fn from(family: ThemeFamily) -> Self { - let platform = PlatformKind::platform(); - let mode = ThemeMode::default(); - - // Define the font family based on the platform. - let font_family = match platform { - PlatformKind::Linux => "Inter", - _ => ".SystemUIFont", - }; - - // Define the theme colors based on the appearance - let colors = match mode { - ThemeMode::Light => family.light(), - ThemeMode::Dark => family.dark(), - }; - - Theme { - font_size: px(15.), - font_family: font_family.into(), - radius: px(6.), - radius_lg: px(10.), - shadow: true, - scrollbar_mode: ScrollbarMode::default(), - notification: NotificationSettings::default(), - mode, - colors: *colors, - theme: Rc::new(family), - } - } -} diff --git a/crates/theme/src/model.rs b/crates/theme/src/model.rs index 08e0cd58..72c39a5c 100644 --- a/crates/theme/src/model.rs +++ b/crates/theme/src/model.rs @@ -1,7 +1,3 @@ -// Ported from zeronsh/zeron crates/theme/src/lib.rs -// at revision 916cb1ccb1342c2061963a5f6ecf5c977ba46f2c (MIT). -// The custom theme library and VS Code import modules are out of scope for now -// and will be ported in a later phase. #![allow(dead_code)] use std::collections::{BTreeMap, HashSet}; @@ -19,10 +15,6 @@ fn custom_families() -> &'static RwLock> { } /// Replace the process-wide custom portion of the runtime registry. -/// -/// The UI calls this after loading or mutating the durable theme library. The -/// returned registry remains source-neutral: renderers still see only resolved -/// families and variants. pub fn replace_custom_families(families: Vec) { *custom_families() .write() @@ -53,8 +45,6 @@ pub enum SurfaceTreatment { Frosted, } -/// The device-local policy applied to a theme variant's recommended surface -/// treatment. This is deliberately independent from theme and accent choices. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum SurfacePreference { @@ -474,8 +464,7 @@ pub struct ThemeVariant { pub family_id: String, pub name: String, pub appearance: Appearance, - /// The author's recommended treatment. Runtime applies the user's - /// [`SurfacePreference`] before components consume the variant. + /// The author's recommended treatment. #[serde(alias = "surfaceTreatment")] pub recommended_surface_treatment: SurfaceTreatment, pub colors: ThemeColors, @@ -617,9 +606,7 @@ impl ThemeRegistry { )); } for (index, color) in variant.terminal.ansi.iter().enumerate() { - // Slots 0 and 8 are structural black/dim colors. Every - // chromatic normal and bright slot is expected to remain - // distinguishable as terminal text. + // Slots 0 and 8 are structural black/dim colors. if index % 8 == 0 { continue; } @@ -660,8 +647,6 @@ pub enum ValidationSeverity { Error, } -/// Whether a validation issue makes a theme structurally unsafe to install or -/// describes a visual quality problem that the compiler/runtime can harden. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ValidationCategory { diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs deleted file mode 100644 index 3cf3719d..00000000 --- a/crates/theme/src/registry.rs +++ /dev/null @@ -1,70 +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; - -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 - let theme: ThemeFamily = serde_json::from_slice(&content)?; - - Ok(theme) - } - - /// Returns a reference to the map of themes. - pub fn themes(&self) -> &HashMap> { - &self.themes - } -} diff --git a/crates/theme/src/resolved.rs b/crates/theme/src/resolved.rs new file mode 100644 index 00000000..607fdbc6 --- /dev/null +++ b/crates/theme/src/resolved.rs @@ -0,0 +1,281 @@ +#![allow(dead_code)] // transitional: scaffold fields die in Phase 5, unconsumed roles live on + +use gpui::Hsla; + +use crate::model::{AccentRoles, Color, ThemeColors, ThemeVariant}; + +fn hsla(color: Color) -> Hsla { + let packed = ((color.r as u32) << 24) + | ((color.g as u32) << 16) + | ((color.b as u32) << 8) + | color.a as u32; + Hsla::from(gpui::rgba(packed)) +} + +#[derive(Debug, Clone, Copy)] +pub struct ResolvedAccentRoles { + pub primary: Hsla, + pub strong: Hsla, + pub wash: Hsla, + pub on: Hsla, + pub selection: Hsla, + pub caret: Hsla, + pub activity: Hsla, + pub glyph: [Hsla; 3], + pub hover: Hsla, + pub active: Hsla, + pub selected: Hsla, + /// Accent text guaranteed readable on `wash`. + pub text_on_wash: Hsla, + /// Text guaranteed readable on the flat `danger` color. + pub on_danger: Hsla, +} + +impl ResolvedAccentRoles { + pub fn resolve(accent: &AccentRoles, colors: &ThemeColors) -> Self { + let background = colors.background; + let wash_background = accent.wash.blend_over(background); + Self { + primary: hsla(accent.primary), + strong: hsla(accent.strong), + wash: hsla(accent.wash), + on: hsla(accent.on), + selection: hsla(accent.selection), + caret: hsla(accent.caret), + activity: hsla(accent.activity), + glyph: [ + hsla(accent.glyph[0]), + hsla(accent.glyph[1]), + hsla(accent.glyph[2]), + ], + hover: hsla(accent.primary.mix(background, 0.12)), + active: hsla(accent.primary.mix(background, 0.2)), + selected: hsla(accent.primary.mix(background, 0.28)), + text_on_wash: hsla(accent.primary.ensure_contrast(wash_background, 4.5)), + on_danger: hsla(colors.danger.best_on_color()), + } + } +} + +/// The `Hsla` snapshot components render from, rebuilt whenever the active +/// variant, accent, or appearance changes. +/// +/// The fields after `diff_hunk` are a transitional scaffold (D6): they keep the +/// removed role names alive — computed from zeron roles — so the ~51 downstream +/// files keep compiling until the Phase 3 rewrite retires them, one by one. +/// Everything below `drop_target_background` is deleted in Phase 5. +#[derive(Debug, Clone, Copy)] +pub struct ResolvedColors { + pub background: Hsla, + pub shell: Hsla, + pub raised: Hsla, + pub card: Hsla, + pub dialog: Hsla, + pub overlay: Hsla, + pub hover: Hsla, + pub active: Hsla, + pub border: Hsla, + pub border_strong: Hsla, + pub text: Hsla, + pub text_muted: Hsla, + pub text_faint: Hsla, + pub solid: Hsla, + pub on_solid: Hsla, + pub danger: Hsla, + pub danger_muted: Hsla, + pub warning: Hsla, + pub warning_muted: Hsla, + pub success: Hsla, + pub success_muted: Hsla, + pub input: Hsla, + pub cursor: Hsla, + pub diff_add: Hsla, + pub diff_delete: Hsla, + pub diff_hunk: Hsla, + // Transitional scaffold — delete in Phase 5. + pub surface_background: Hsla, + pub elevated_surface_background: Hsla, + pub panel_background: Hsla, + pub border_variant: Hsla, + pub border_focused: Hsla, + pub border_selected: Hsla, + pub border_transparent: Hsla, + pub ring: Hsla, + pub selection: Hsla, + pub text_placeholder: Hsla, + pub text_accent: Hsla, + pub text_danger: Hsla, + pub text_warning: Hsla, + pub icon: Hsla, + pub icon_muted: Hsla, + pub icon_accent: Hsla, + pub element_background: Hsla, + pub element_foreground: Hsla, + pub element_hover: Hsla, + pub element_active: Hsla, + pub element_selected: Hsla, + pub element_disabled: Hsla, + pub secondary_background: Hsla, + pub secondary_foreground: Hsla, + pub secondary_hover: Hsla, + pub secondary_active: Hsla, + pub secondary_selected: Hsla, + pub secondary_disabled: Hsla, + pub danger_foreground: Hsla, + pub danger_background: Hsla, + pub danger_hover: Hsla, + pub danger_active: Hsla, + pub danger_selected: Hsla, + pub danger_disabled: Hsla, + pub warning_foreground: Hsla, + pub warning_background: Hsla, + pub warning_hover: Hsla, + pub warning_active: Hsla, + pub warning_selected: Hsla, + pub warning_disabled: Hsla, + 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, + pub tab_background: Hsla, + pub tab_foreground: Hsla, + pub tab_hover_background: Hsla, + pub tab_active_background: Hsla, + pub tab_active_foreground: Hsla, + 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, + pub drop_target_background: Hsla, +} + +impl ResolvedColors { + pub fn resolve(variant: &ThemeVariant, accent: &AccentRoles) -> Self { + let colors = variant.colors.clone(); + let ThemeColors { + background, + shell, + raised, + card, + dialog, + overlay, + hover, + active, + border, + border_strong, + text, + text_muted, + text_faint, + solid, + on_solid, + danger, + danger_muted, + warning, + warning_muted, + success, + success_muted, + input, + cursor, + diff_add, + diff_delete, + diff_hunk, + } = colors; + + let transparent = Color::rgba(0, 0, 0, 0); + let danger_tint = danger_muted; + let warning_tint = warning_muted; + + Self { + background: hsla(background), + shell: hsla(shell), + raised: hsla(raised), + card: hsla(card), + dialog: hsla(dialog), + overlay: hsla(overlay), + hover: hsla(hover), + active: hsla(active), + border: hsla(border), + border_strong: hsla(border_strong), + text: hsla(text), + text_muted: hsla(text_muted), + text_faint: hsla(text_faint), + solid: hsla(solid), + on_solid: hsla(on_solid), + danger: hsla(danger), + danger_muted: hsla(danger_muted), + warning: hsla(warning), + warning_muted: hsla(warning_muted), + success: hsla(success), + success_muted: hsla(success_muted), + input: hsla(input), + cursor: hsla(cursor), + diff_add: hsla(diff_add), + diff_delete: hsla(diff_delete), + diff_hunk: hsla(diff_hunk), + surface_background: hsla(shell), + elevated_surface_background: hsla(raised), + panel_background: hsla(shell), + border_variant: hsla(border), + border_focused: hsla(accent.primary), + border_selected: hsla(accent.primary), + border_transparent: hsla(transparent), + ring: hsla(accent.primary), + selection: hsla(accent.selection), + text_placeholder: hsla(text_faint), + text_accent: hsla(accent.primary), + text_danger: hsla(danger), + text_warning: hsla(warning), + icon: hsla(text_muted), + icon_muted: hsla(text_faint), + icon_accent: hsla(accent.primary), + element_background: hsla(accent.primary), + element_foreground: hsla(accent.on), + element_hover: hsla(accent.primary.mix(background, 0.12)), + element_active: hsla(accent.primary.mix(background, 0.2)), + element_selected: hsla(accent.primary.mix(background, 0.28)), + element_disabled: hsla(accent.primary.with_alpha(0.3)), + secondary_background: hsla(accent.wash), + secondary_foreground: hsla( + accent + .primary + .ensure_contrast(accent.wash.blend_over(background), 4.5), + ), + secondary_hover: hsla(accent.selection), + secondary_active: hsla(accent.selection), + secondary_selected: hsla(accent.selection), + secondary_disabled: hsla(accent.wash.with_alpha(0.5)), + danger_foreground: hsla(danger.ensure_contrast(danger_tint, 4.5)), + danger_background: hsla(danger_tint), + danger_hover: hsla(danger.with_alpha(0.15)), + danger_active: hsla(danger.with_alpha(0.25)), + danger_selected: hsla(danger.with_alpha(0.25)), + danger_disabled: hsla(danger.with_alpha(0.12)), + warning_foreground: hsla(warning.ensure_contrast(warning_tint, 4.5)), + warning_background: hsla(warning_tint), + warning_hover: hsla(warning.with_alpha(0.15)), + warning_active: hsla(warning.with_alpha(0.25)), + warning_selected: hsla(warning.with_alpha(0.25)), + warning_disabled: hsla(warning.with_alpha(0.12)), + ghost_element_background: hsla(transparent), + ghost_element_background_alt: hsla(raised), + ghost_element_hover: hsla(hover), + ghost_element_active: hsla(active), + ghost_element_selected: hsla(active), + ghost_element_disabled: hsla(border.with_alpha(0.4)), + tab_background: hsla(shell), + tab_foreground: hsla(text_muted), + tab_hover_background: hsla(hover), + tab_active_background: hsla(background), + tab_active_foreground: hsla(text), + scrollbar_thumb_background: hsla(border.with_alpha(0.7)), + scrollbar_thumb_hover_background: hsla(border_strong), + scrollbar_thumb_border: hsla(transparent), + scrollbar_track_background: hsla(transparent), + scrollbar_track_border: hsla(border), + drop_target_background: hsla(accent.selection), + } + } +} diff --git a/crates/theme/src/runtime.rs b/crates/theme/src/runtime.rs new file mode 100644 index 00000000..dd09f7d4 --- /dev/null +++ b/crates/theme/src/runtime.rs @@ -0,0 +1,225 @@ +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +use gpui::{App, Global, Pixels, SharedString, Window, px}; + +use crate::model::{ + AccentSelection, Appearance, SurfacePreference, SurfaceTreatment, ThemeRegistry, + ThemeSelection, ThemeVariant, +}; +use crate::resolved::{ResolvedAccentRoles, ResolvedColors}; +use crate::{NotificationSettings, PlatformKind, ScrollbarMode, sync_base}; + +impl From for Appearance { + fn from(appearance: gpui::WindowAppearance) -> Self { + match appearance { + gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => Self::Dark, + gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => Self::Light, + } + } +} + +#[derive(Debug, Clone)] +pub struct Theme { + /// The active theme variant. + pub variant: Arc, + + /// The active appearance (light or dark). + pub appearance: Appearance, + + /// The per-appearance variant selection. + pub theme_selection: ThemeSelection, + + /// The accent override applied on top of the variant. + pub accent_selection: AccentSelection, + + /// The user's surface treatment preference. + pub surface_preference: SurfacePreference, + + /// The surface treatment in effect for this session. + pub surface: SurfaceTreatment, + + /// The resolved accent roles. + pub accent: ResolvedAccentRoles, + + /// The resolved color snapshot. + pub colors: ResolvedColors, + + /// The font family for the application. + pub font_family: 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, +} + +impl Deref for Theme { + type Target = ResolvedColors; + + fn deref(&self) -> &Self::Target { + &self.colors + } +} + +impl DerefMut for Theme { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.colors + } +} + +impl Global for Theme {} + +impl Theme { + /// Returns the global theme reference + pub fn global(cx: &App) -> &Theme { + cx.global::() + } + + /// Returns the global theme mutable reference + pub(crate) fn global_mut(cx: &mut App) -> &mut Theme { + cx.global_mut::() + } + + /// Returns true if the theme is dark. + pub fn is_dark(&self) -> bool { + self.appearance.is_dark() + } + + fn new() -> Self { + let platform = PlatformKind::platform(); + let appearance = Appearance::default(); + + // Define the font family based on the platform. + let font_family = match platform { + PlatformKind::Linux => "Inter", + _ => ".SystemUIFont", + }; + + let selection = ThemeSelection::default(); + let registry = ThemeRegistry::active(); + let variant = registry.resolve(&selection, appearance).clone(); + let accent = variant.accent_for(AccentSelection::default()); + let surface = SurfacePreference::default().resolve(variant.recommended_surface_treatment); + let resolved_accent = ResolvedAccentRoles::resolve(&accent, &variant.colors); + let colors = ResolvedColors::resolve(&variant, &accent); + + Self { + variant: Arc::new(variant), + appearance, + theme_selection: selection, + accent_selection: AccentSelection::default(), + surface_preference: SurfacePreference::default(), + surface, + accent: resolved_accent, + colors, + font_family: font_family.into(), + font_size: px(15.), + radius: px(6.), + radius_lg: px(10.), + shadow: true, + scrollbar_mode: ScrollbarMode::default(), + notification: NotificationSettings::default(), + } + } + + /// Sync the theme with the system appearance + pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) { + let appearance = window + .as_ref() + .map(|window| window.appearance()) + .unwrap_or_else(|| cx.window_appearance()); + + Self::change(appearance, window, cx); + } + + /// Sync the Scrollbar showing behavior with the system + pub fn sync_scrollbar_appearance(cx: &mut App) { + Theme::global_mut(cx).scrollbar_mode = if cx.should_auto_hide_scrollbars() { + ScrollbarMode::Scrolling + } else { + ScrollbarMode::Hover + }; + } + + /// Apply a theme variant by id for the current appearance. + pub fn apply_variant(id: &str, window: Option<&mut Window>, cx: &mut App) { + let appearance = Theme::global(cx).appearance; + Theme::global_mut(cx) + .theme_selection + .set_variant(appearance, id); + Self::change(appearance, window, cx); + } + + /// Replace the per-appearance variant selection. + pub fn set_selection(selection: ThemeSelection, cx: &mut App) { + Theme::global_mut(cx).theme_selection = selection; + } + + /// Replace the accent override and re-resolve the theme. + pub fn set_accent_selection(selection: AccentSelection, cx: &mut App) { + Theme::global_mut(cx).accent_selection = selection; + } + + /// Replace the surface treatment preference. + pub fn set_surface_preference(preference: SurfacePreference, cx: &mut App) { + Theme::global_mut(cx).surface_preference = preference; + } + + /// Change the app's appearance, resolving the selected variant (with its + /// accent override) into a fresh color snapshot. + pub fn change(appearance: M, window: Option<&mut Window>, cx: &mut App) + where + M: Into, + { + if !cx.has_global::() { + cx.set_global(Theme::new()); + } + + let appearance = appearance.into(); + let (selection, accent_selection, surface_preference) = { + let theme = cx.global_mut::(); + theme.appearance = appearance; + ( + theme.theme_selection.clone(), + theme.accent_selection, + theme.surface_preference, + ) + }; + + let registry = ThemeRegistry::active(); + let variant = registry.resolve(&selection, appearance).clone(); + let accent = variant.accent_for(accent_selection); + let surface = surface_preference.resolve(variant.recommended_surface_treatment); + let colors = ResolvedColors::resolve(&variant, &accent); + + { + let theme = cx.global_mut::(); + theme.variant = Arc::new(variant.clone()); + theme.accent = ResolvedAccentRoles::resolve(&accent, &variant.colors); + theme.surface = surface; + theme.colors = colors; + } + + // Refresh the window if available + if let Some(window) = window { + window.refresh(); + } + + // Keep the base-layer projection in step with the coop palette + sync_base(cx); + } +} diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs deleted file mode 100644 index 4359a5bd..00000000 --- a/crates/theme/src/scale.rs +++ /dev/null @@ -1,177 +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. -pub struct ColorScaleSet { - light: ColorScale, - dark: ColorScale, - light_alpha: ColorScale, - dark_alpha: ColorScale, -} - -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/scrollbar_mode.rs b/crates/theme/src/scrollbar_mode.rs index b1e4e319..a5ffb7cf 100644 --- a/crates/theme/src/scrollbar_mode.rs +++ b/crates/theme/src/scrollbar_mode.rs @@ -1,7 +1,6 @@ -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub enum ScrollbarMode { #[default] Scrolling, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs deleted file mode 100644 index 3d48f359..00000000 --- a/crates/theme/src/theme.rs +++ /dev/null @@ -1,359 +0,0 @@ -use std::path::Path; - -use gpui::{SharedString, WindowAppearance}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::ThemeColors; - -#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Hash, Deserialize, Serialize)] -pub enum ThemeMode { - #[default] - Light, - Dark, -} - -impl ThemeMode { - pub fn is_dark(&self) -> bool { - matches!(self, Self::Dark) - } - - /// Return theme name: `light`, `dark`. - pub fn name(&self) -> &'static str { - match self { - ThemeMode::Light => "Light", - ThemeMode::Dark => "Dark", - } - } -} - -impl From for ThemeMode { - fn from(appearance: WindowAppearance) -> Self { - match appearance { - WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark, - WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light, - } - } -} - -/// 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(crate) fn from_file>(path: P) -> anyhow::Result { - let json_data = std::fs::read(path)?; - let theme_family = serde_json::from_slice(&json_data)?; - - 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/workspace/src/dialogs/settings.rs b/crates/workspace/src/dialogs/settings.rs index 185c814a..eff81226 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, Appearance, Theme}; 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 (light or dark) + fn set_appearance(appearance: Appearance, window: &mut Window, cx: &mut App) { + AppSettings::update_appearance(appearance, cx); + Theme::change(appearance, Some(window), cx); } } @@ -65,7 +65,7 @@ impl Render for Preferences { 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 = AppSettings::get_appearance(cx); v_flex() .gap_4() @@ -114,18 +114,22 @@ impl Render for Preferences { ) .child( Button::new("theme-mode") - .label(theme_mode.name()) + .label(if appearance.is_dark() { + "Dark" + } else { + "Light" + }) .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); + Self::set_appearance(Appearance::Light, window, cx); }, )) .item( PopupMenuItem::new("Dark").on_click(|_, window, cx| { - Self::set_theme_mode(ThemeMode::Dark, window, cx); + Self::set_appearance(Appearance::Dark, window, cx); }), ) }), diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index bf6d7f0e..073298db 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -15,7 +15,7 @@ use nostr_sdk::prelude::*; use serde::Deserialize; use smallvec::{SmallVec, smallvec}; use state::{NostrRegistry, StateEvent}; -use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry}; +use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry, ThemeVariant}; use ui::button::{Button, ButtonVariants}; use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle}; use ui::notification::{Notification, NotificationKind}; @@ -464,17 +464,20 @@ impl Workspace { } 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(); + let appearance = cx.theme().appearance; + let variants: Vec = ThemeRegistry::active() + .variants_for(appearance) + .cloned() + .collect(); + window.open_dialog(cx, move |this, _window, cx| { 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() { + for (ix, variant) in variants.iter().enumerate() { items.push( h_flex() .id(ix) @@ -491,13 +494,15 @@ impl Workspace { .gap_1p5() .flex_1() .text_sm() - .child(theme.name.clone()) + .child(SharedString::from(variant.name.clone())) .child( div() .text_xs() .italic() .text_color(cx.theme().text_muted) - .child(theme.author.clone()), + .child(SharedString::from( + variant.family_id.clone(), + )), ), ) .child( @@ -511,9 +516,9 @@ impl Workspace { .ghost() .small() .on_click({ - let theme = theme.clone(); + let url = variant.source.url.clone(); move |_ev, _window, cx| { - cx.open_url(&theme.url); + cx.open_url(&url); } }), ) @@ -523,13 +528,16 @@ impl Workspace { .primary() .small() .on_click({ - let path = path.clone(); + let id = variant.id.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); + this.set_theme_variant( + id.clone(), + window, + cx, + ); }) } }),