10 Commits
Author SHA1 Message Date
reya 16cd0ae903 update theme 2026-10-08 13:55:23 +07:00
reya f54f5f35a8 Rebrand Zeron theme family as Coop 2026-10-08 09:58:23 +07:00
reya 4dfc9fd668 simplify theme crate to builtin-only registry 2026-10-08 09:25:24 +07:00
reya af20a1b150 remove transitional scaffold roles from ResolvedColors 2026-10-08 09:00:58 +07:00
reya 02b9cc238f add accent color and surface preferences to settings 2026-10-08 08:56:57 +07:00
reya f4b435b160 wip 2026-10-08 08:51:11 +07:00
reya 2efb6ac5ca wip 2026-10-08 08:42:44 +07:00
reya 069f031af9 port zeron theme domain model and builtins 2026-10-08 08:15:13 +07:00
reya 73bb7fd811 credit zeron theme as upstream source 2026-10-08 08:06:41 +07:00
reya c59dcdbc0b add plan 2026-10-08 07:58:56 +07:00
75 changed files with 2909 additions and 2915 deletions
Generated
+2 -3
View File
@@ -7436,14 +7436,13 @@ dependencies = [
name = "theme" name = "theme"
version = "1.0.2" version = "1.0.2"
dependencies = [ dependencies = [
"anyhow",
"gpui-base", "gpui-base",
"gpui-pre", "gpui-pre",
"log", "log",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"tempfile", "sha2 0.10.9",
"thiserror 2.0.21",
] ]
[[package]] [[package]]
+1
View File
@@ -33,6 +33,7 @@ nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "ni
aes-gcm = "0.10" aes-gcm = "0.10"
base64 = "0.22" base64 = "0.22"
sha2 = "0.10" sha2 = "0.10"
thiserror = "2.0"
data-encoding = "2" data-encoding = "2"
hkdf = "0.12" hkdf = "0.12"
# Pinned to the instances `nostr` already builds: the NIP-44 message-key disclosure # Pinned to the instances `nostr` already builds: the NIP-44 message-key disclosure
-144
View File
@@ -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"
}
}
-144
View File
@@ -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"
}
}
-144
View File
@@ -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"
}
}
-144
View File
@@ -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"
}
}
-144
View File
@@ -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"
}
}
-144
View File
@@ -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"
}
}
-144
View File
@@ -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"
}
}
-144
View File
@@ -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"
}
}
-1
View File
@@ -7,7 +7,6 @@ use rust_embed::RustEmbed;
#[include = "fonts/**/*"] #[include = "fonts/**/*"]
#[include = "brand/**/*"] #[include = "brand/**/*"]
#[include = "icons/**/*"] #[include = "icons/**/*"]
#[include = "themes/**/*"]
#[exclude = "*.DS_Store"] #[exclude = "*.DS_Store"]
pub struct Assets; pub struct Assets;
+33 -33
View File
@@ -904,7 +904,7 @@ impl ChatPanel {
.h_10() .h_10()
.justify_center() .justify_center()
.text_sm() .text_sm()
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.child("Message isn't traced yet"), .child("Message isn't traced yet"),
) )
@@ -919,7 +919,7 @@ impl ChatPanel {
.h_7() .h_7()
.px_2() .px_2()
.gap_2() .gap_2()
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.text_sm() .text_sm()
.child(div().size_1p5().rounded_full().bg(gpui::green())) .child(div().size_1p5().rounded_full().bg(gpui::green()))
@@ -951,7 +951,7 @@ impl ChatPanel {
.h_7() .h_7()
.px_2() .px_2()
.gap_2() .gap_2()
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.text_sm() .text_sm()
.child(div().size_1p5().rounded_full().bg(gpui::green())) .child(div().size_1p5().rounded_full().bg(gpui::green()))
@@ -975,7 +975,7 @@ impl ChatPanel {
svg() svg()
.path("brand/coop.svg") .path("brand/coop.svg")
.size_12() .size_12()
.text_color(cx.theme().ghost_element_background_alt), .text_color(cx.theme().raised),
) )
.message(PRIVATE_NOTICE) .message(PRIVATE_NOTICE)
.into_any_element() .into_any_element()
@@ -991,13 +991,13 @@ impl ChatPanel {
.justify_center() .justify_center()
.text_center() .text_center()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.line_height(relative(1.3)) .line_height(relative(1.3))
.child( .child(
svg() svg()
.path("brand/coop.svg") .path("brand/coop.svg")
.size_12() .size_12()
.text_color(cx.theme().ghost_element_active), .text_color(cx.theme().active),
) )
.child(PRIVATE_NOTICE) .child(PRIVATE_NOTICE)
.into_any_element() .into_any_element()
@@ -1020,8 +1020,8 @@ impl ChatPanel {
.size_8() .size_8()
.justify_center() .justify_center()
.rounded_full() .rounded_full()
.bg(cx.theme().warning_background) .bg(cx.theme().warning_muted)
.text_color(cx.theme().warning_foreground) .text_color(cx.theme().warning)
.child(Icon::new(IconName::Warning).small()), .child(Icon::new(IconName::Warning).small()),
) )
.child( .child(
@@ -1155,13 +1155,13 @@ impl ChatPanel {
}) })
.overlay( .overlay(
div() div()
.group_hover("", |this| this.bg(cx.theme().element_active)) .group_hover("", |this| this.bg(cx.theme().hover))
.absolute() .absolute()
.left_0() .left_0()
.top_0() .top_0()
.w(px(2.)) .w(px(2.))
.h_full() .h_full()
.bg(cx.theme().border_transparent), .bg(gpui::transparent_black()),
) )
.overlay(self.render_actions(&id, &pk, cx)) .overlay(self.render_actions(&id, &pk, cx))
.on_mouse_down( .on_mouse_down(
@@ -1187,7 +1187,7 @@ impl ChatPanel {
return div().child( return div().child(
img(media[0].clone()) img(media[0].clone())
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.h(px(250.)) .h(px(250.))
.object_fit(ObjectFit::Cover) .object_fit(ObjectFit::Cover)
.rounded(cx.theme().radius), .rounded(cx.theme().radius),
@@ -1215,7 +1215,7 @@ impl ChatPanel {
img(item.clone()) img(item.clone())
.h_32() .h_32()
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.rounded(cx.theme().radius), .rounded(cx.theme().radius),
), ),
); );
@@ -1244,7 +1244,7 @@ impl ChatPanel {
.w_full() .w_full()
.px_2() .px_2()
.border_l_2() .border_l_2()
.border_color(cx.theme().element_active) .border_color(cx.theme().accent.strong)
.text_sm() .text_sm()
.child(div().font_semibold().child(author.name())) .child(div().font_semibold().child(author.name()))
.child( .child(
@@ -1254,7 +1254,7 @@ impl ChatPanel {
.line_clamp(1) .line_clamp(1)
.child(message.preview()), .child(message.preview()),
) )
.hover(|this| this.bg(cx.theme().elevated_surface_background)) .hover(|this| this.bg(cx.theme().hover))
.on_click({ .on_click({
let id = *id; let id = *id;
cx.listener(move |this, _event, _window, _cx| { cx.listener(move |this, _event, _window, _cx| {
@@ -1294,9 +1294,9 @@ impl ChatPanel {
.text_xs() .text_xs()
.border_1() .border_1()
.when(has_reacted, |this| { .when(has_reacted, |this| {
this.text_color(cx.theme().secondary_foreground) this.text_color(cx.theme().accent.text_on_wash)
.bg(cx.theme().secondary_background) .bg(cx.theme().accent.wash)
.border_color(cx.theme().secondary_active) .border_color(cx.theme().accent.selection)
}) })
.when(!has_reacted, |this| this.border_color(cx.theme().border)) .when(!has_reacted, |this| this.border_color(cx.theme().border))
.child(emoji) .child(emoji)
@@ -1330,7 +1330,7 @@ impl ChatPanel {
div() div()
.id(SharedString::from(id.to_hex())) .id(SharedString::from(id.to_hex()))
.child(label) .child(label)
.when(failed, |this| this.text_color(cx.theme().text_danger)) .when(failed, |this| this.text_color(cx.theme().danger))
.when_some(reports, |this, reports| { .when_some(reports, |this, reports| {
this.when(!pending, |this| { this.when(!pending, |this| {
this.on_click(move |_e, window, cx| { this.on_click(move |_e, window, cx| {
@@ -1386,8 +1386,8 @@ impl ChatPanel {
.w_full() .w_full()
.text_sm() .text_sm()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().warning_background) .bg(cx.theme().warning_muted)
.text_color(cx.theme().warning_foreground) .text_color(cx.theme().warning)
.child(div().flex_1().w_full().text_center().child(error)), .child(div().flex_1().w_full().text_center().child(error)),
) )
}) })
@@ -1406,7 +1406,7 @@ impl ChatPanel {
.p_1() .p_1()
.w_full() .w_full()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().danger_background) .bg(cx.theme().danger_muted)
.child( .child(
div() div()
.text_xs() .text_xs()
@@ -1417,7 +1417,7 @@ impl ChatPanel {
.child( .child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().danger_foreground) .text_color(cx.theme().danger)
.line_height(relative(1.25)) .line_height(relative(1.25))
.child(SharedString::from(msg.to_string())), .child(SharedString::from(msg.to_string())),
), ),
@@ -1436,7 +1436,7 @@ impl ChatPanel {
.p_1() .p_1()
.w_full() .w_full()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.child( .child(
div() div()
.text_xs() .text_xs()
@@ -1611,7 +1611,7 @@ impl ChatPanel {
.child( .child(
img(path.clone()) img(path.clone())
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.h(px(250.)) .h(px(250.))
.object_fit(ObjectFit::Cover) .object_fit(ObjectFit::Cover)
.rounded(cx.theme().radius), .rounded(cx.theme().radius),
@@ -1645,9 +1645,9 @@ impl ChatPanel {
.gap_2() .gap_2()
.p_2() .p_2()
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.child(Icon::new(IconName::Lock).text_color(cx.theme().icon_accent)) .child(Icon::new(IconName::Lock).text_color(cx.theme().accent.strong))
.child( .child(
v_flex() v_flex()
.min_w_0() .min_w_0()
@@ -1657,7 +1657,7 @@ impl ChatPanel {
.child( .child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(label), .child(label),
), ),
) )
@@ -1700,8 +1700,8 @@ impl ChatPanel {
.justify_center() .justify_center()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.text_xs() .text_xs()
.text_center() .text_center()
.child("Preview not available"), .child("Preview not available"),
@@ -1718,7 +1718,7 @@ impl ChatPanel {
.justify_center() .justify_center()
.rounded_full() .rounded_full()
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.bg(gpui::green()) .bg(gpui::green())
.child(Icon::new(IconName::Lock).size_2().text_color(gpui::white())), .child(Icon::new(IconName::Lock).size_2().text_color(gpui::white())),
) )
@@ -1753,7 +1753,7 @@ impl ChatPanel {
.w_full() .w_full()
.pl_2() .pl_2()
.border_l_2() .border_l_2()
.border_color(cx.theme().element_active) .border_color(cx.theme().accent.strong)
.child( .child(
div() div()
.flex() .flex()
@@ -1769,7 +1769,7 @@ impl ChatPanel {
.child("Replying to:") .child("Replying to:")
.child( .child(
div() div()
.text_color(cx.theme().text_accent) .text_color(cx.theme().accent.primary)
.child(profile.name()), .child(profile.name()),
), ),
) )
@@ -1994,7 +1994,7 @@ impl Render for ChatPanel {
div() div()
.px_1() .px_1()
.text_xs() .text_xs()
.text_color(cx.theme().text_warning) .text_color(cx.theme().warning)
.child(WARNING), .child(WARNING),
) )
}) })
+3 -3
View File
@@ -78,7 +78,7 @@ fn body(group: &WeakEntity<Group>, cx: &App) -> AnyElement {
"Supported kinds", "Supported kinds",
if items.is_empty() { if items.is_empty() {
div() div()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child("None") .child("None")
.into_any_element() .into_any_element()
} else { } else {
@@ -175,7 +175,7 @@ fn header(group: &Group, cx: &App) -> AnyElement {
.child( .child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.truncate() .truncate()
.child(SharedString::from(format!( .child(SharedString::from(format!(
"{} · {}", "{} · {}",
@@ -239,7 +239,7 @@ fn chip(label: impl Into<SharedString>, cx: &App) -> AnyElement {
.px_1p5() .px_1p5()
.py_0p5() .py_0p5()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().element_background) .bg(cx.theme().accent.wash)
.text_xs() .text_xs()
.child(label.into()) .child(label.into())
.into_any_element() .into_any_element()
+4 -4
View File
@@ -622,7 +622,7 @@ impl GroupPanel {
.child( .child(
Icon::new(IconName::Info) Icon::new(IconName::Info)
.small() .small()
.text_color(cx.theme().icon_muted), .text_color(cx.theme().text_faint),
) )
.child(div().flex_1().min_w_0().truncate().child(message)) .child(div().flex_1().min_w_0().truncate().child(message))
.child( .child(
@@ -662,7 +662,7 @@ impl GroupPanel {
.px_1p5() .px_1p5()
.py_0p5() .py_0p5()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().element_background) .bg(cx.theme().accent.wash)
.text_xs() .text_xs()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(pin_label(group, pin)) .child(pin_label(group, pin))
@@ -924,7 +924,7 @@ impl GroupPanel {
.h_8() .h_8()
.px_2() .px_2()
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.text_xs() .text_xs()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
@@ -1034,7 +1034,7 @@ impl Render for GroupPanel {
.size_full() .size_full()
.justify_center() .justify_center()
.text_sm() .text_sm()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child("No messages yet") .child("No messages yet")
.into_any_element(), .into_any_element(),
}), }),
+10 -10
View File
@@ -141,7 +141,7 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
.gap_2() .gap_2()
.py_1() .py_1()
.px_3() .px_3()
.hover(|this| this.bg(cx.theme().surface_background)) .hover(|this| this.bg(cx.theme().hover))
.child(badge(activity, cx)) .child(badge(activity, cx))
.when(!hide_avatar, |this| { .when(!hide_avatar, |this| {
this.child( this.child(
@@ -164,7 +164,7 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
.min_w_0() .min_w_0()
.truncate() .truncate()
.text_sm() .text_sm()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(SharedString::from(action(activity))), .child(SharedString::from(action(activity))),
) )
.child( .child(
@@ -172,7 +172,7 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
.flex_shrink_0() .flex_shrink_0()
.ml_1() .ml_1()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(activity.created_at.to_ago()), .child(activity.created_at.to_ago()),
) )
.into_any_element() .into_any_element()
@@ -180,11 +180,11 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
fn badge(activity: &Activity, cx: &App) -> AnyElement { fn badge(activity: &Activity, cx: &App) -> AnyElement {
let (icon, color) = match activity.kind { let (icon, color) = match activity.kind {
Kind::GroupJoinRequest => (IconName::Login, cx.theme().icon_accent), Kind::GroupJoinRequest => (IconName::Login, cx.theme().accent.strong),
Kind::GroupPutUser => (IconName::Login, cx.theme().icon_accent), Kind::GroupPutUser => (IconName::Login, cx.theme().accent.strong),
Kind::GroupLeaveRequest => (IconName::Logout, cx.theme().text_danger), Kind::GroupLeaveRequest => (IconName::Logout, cx.theme().danger),
Kind::GroupRemoveUser => (IconName::Ban, cx.theme().text_danger), Kind::GroupRemoveUser => (IconName::Ban, cx.theme().danger),
_ => (IconName::Info, cx.theme().icon_muted), _ => (IconName::Info, cx.theme().text_faint),
}; };
h_flex() h_flex()
@@ -211,7 +211,7 @@ fn media(media: &[SharedUri], cx: &App) -> AnyElement {
.child( .child(
img(only.clone()) img(only.clone())
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.h(px(250.)) .h(px(250.))
.object_fit(ObjectFit::Cover) .object_fit(ObjectFit::Cover)
.rounded(cx.theme().radius), .rounded(cx.theme().radius),
@@ -231,7 +231,7 @@ fn media(media: &[SharedUri], cx: &App) -> AnyElement {
img(item.clone()) img(item.clone())
.h_32() .h_32()
.border_1() .border_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.rounded(cx.theme().radius), .rounded(cx.theme().radius),
) )
})) }))
+2 -2
View File
@@ -748,7 +748,7 @@ impl DeviceRegistry {
.w_full() .w_full()
.px_2() .px_2()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.child( .child(
h_flex() h_flex()
.gap_2() .gap_2()
@@ -778,7 +778,7 @@ impl DeviceRegistry {
.w_full() .w_full()
.px_2() .px_2()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.child(code_label.clone()), .child(code_label.clone()),
), ),
), ),
+92 -32
View File
@@ -1,12 +1,10 @@
use std::rc::Rc;
use anyhow::{Error, anyhow}; use anyhow::{Error, anyhow};
use common::config_dir; use common::config_dir;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window}; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use theme::{Theme, ThemeFamily, ThemeMode}; use theme::{AccentSelection, Appearance, SurfacePreference, Theme, ThemeSelection};
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
AppSettings::set_global(cx.new(AppSettings::new), cx) AppSettings::set_global(cx.new(AppSettings::new), cx)
@@ -53,7 +51,10 @@ macro_rules! setting_getters {
} }
setting_accessors! { 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 hide_avatar: bool,
pub screening: bool, pub screening: bool,
pub nip4e: bool, pub nip4e: bool,
@@ -119,11 +120,26 @@ impl RoomConfig {
/// Settings /// Settings
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings { pub struct Settings {
/// Theme /// The per-appearance theme variant selection
pub theme: Option<String>, #[serde(default)]
pub theme_selection: ThemeSelection,
/// Theme mode /// The active appearance; persisted under the legacy `theme_mode` key so
pub theme_mode: ThemeMode, /// 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 /// Hide user avatars
pub hide_avatar: bool, pub hide_avatar: bool,
@@ -152,8 +168,10 @@ pub struct Settings {
impl Default for Settings { impl Default for Settings {
fn default() -> Self { fn default() -> Self {
Self { Self {
theme: None, theme_selection: ThemeSelection::default(),
theme_mode: ThemeMode::default(), appearance: Appearance::default(),
accent_selection: AccentSelection::default(),
surface_preference: SurfacePreference::default(),
hide_avatar: false, hide_avatar: false,
screening: true, screening: true,
nip4e: false, 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<Appearance, D::Error>
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<AppSettings>); struct GlobalAppSettings(Entity<AppSettings>);
impl Global for GlobalAppSettings {} impl Global for GlobalAppSettings {}
@@ -264,44 +297,71 @@ impl AppSettings {
} }
} }
/// Set theme /// Set the theme variant for the current appearance
pub fn set_theme<T>(&mut self, theme: T, window: &mut Window, cx: &mut Context<Self>) pub fn set_theme_variant(&mut self, id: String, window: &mut Window, cx: &mut Context<Self>) {
where
T: Into<String>,
{
// Update settings
self.inner.update(cx, |this, cx| { 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(); cx.notify();
}); });
// Apply the new theme
self.apply_theme(Some(window), cx); 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>) { pub fn reset_theme(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.inner.update(cx, |this, cx| { self.inner.update(cx, |this, cx| {
this.theme = None; this.theme_selection = ThemeSelection::default();
cx.notify();
});
self.apply_theme(Some(window), cx);
}
/// Set the accent color override and re-resolve the theme
pub fn apply_accent(
&mut self,
selection: AccentSelection,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.inner.update(cx, |this, cx| {
this.accent_selection = selection;
cx.notify();
});
self.apply_theme(Some(window), cx);
}
/// Set the surface treatment preference
pub fn apply_surface(
&mut self,
preference: SurfacePreference,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.inner.update(cx, |this, cx| {
this.surface_preference = preference;
cx.notify(); cx.notify();
}); });
self.apply_theme(Some(window), cx); self.apply_theme(Some(window), cx);
} }
/// Apply theme /// Apply theme
pub fn apply_theme(&mut self, mut window: Option<&mut Window>, cx: &mut Context<Self>) { pub fn apply_theme(&mut self, window: Option<&mut Window>, cx: &mut Context<Self>) {
if let Some(name) = self.inner.read(cx).theme.as_ref() { let (selection, accent, surface, appearance) = {
let mode = self.inner.read(cx).theme_mode; 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::set_selection(selection, cx);
Theme::apply_theme(Rc::new(new_theme), window.as_deref_mut(), cx); Theme::set_accent_selection(accent, cx);
Theme::change(mode, window, cx); Theme::set_surface_preference(surface, cx);
} else { Theme::change(appearance, window, cx);
log::info!("Failed to load theme: {name}");
}
} else {
Theme::apply_theme(Rc::new(ThemeFamily::default()), window, cx);
}
} }
/// Check if decoupling encryption key is enabled /// Check if decoupling encryption key is enabled
+2 -5
View File
@@ -7,11 +7,8 @@ publish.workspace = true
[dependencies] [dependencies]
gpui.workspace = true gpui.workspace = true
gpui-base.workspace = true gpui-base.workspace = true
anyhow.workspace = true
log.workspace = true log.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
schemars.workspace = true sha2.workspace = true
thiserror.workspace = true
[dev-dependencies]
tempfile = "3.10"
-1
View File
@@ -1 +0,0 @@
../../LICENSE
-1
View File
@@ -1 +0,0 @@
CREDITS: [zed/theme](https://github.com/zed-industries/zed/tree/main/crates/theme)
File diff suppressed because it is too large Load Diff
-583
View File
@@ -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<StaticColorScaleSet> for ColorScaleSet {
type Error = anyhow::Error;
fn try_from(value: StaticColorScaleSet) -> Result<Self, Self::Error> {
fn to_color_scale(scale: StaticColorScale) -> Result<ColorScale, anyhow::Error> {
scale
.into_iter()
.map(|color| Rgba::try_from(color).map(Hsla::from))
.collect::<Result<Vec<_>, _>>()
.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()
}
+1 -11
View File
@@ -3,7 +3,6 @@ use std::fmt::Debug;
use gpui::Axis; use gpui::Axis;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A enum for defining the side of the element.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Side { pub enum Side {
#[serde(rename = "left")] #[serde(rename = "left")]
@@ -13,20 +12,17 @@ pub enum Side {
} }
impl Side { impl Side {
/// Returns true if the side is left.
#[inline] #[inline]
pub fn is_left(&self) -> bool { pub fn is_left(&self) -> bool {
matches!(self, Self::Left) matches!(self, Self::Left)
} }
/// Returns true if the side is right.
#[inline] #[inline]
pub fn is_right(&self) -> bool { pub fn is_right(&self) -> bool {
matches!(self, Self::Right) matches!(self, Self::Right)
} }
} }
/// A trait to extend the [`Axis`] enum with utility methods.
pub trait AxisExt { pub trait AxisExt {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
fn is_horizontal(self) -> bool; fn is_horizontal(self) -> bool;
@@ -46,18 +42,12 @@ impl AxisExt for Axis {
} }
} }
/// A struct for defining the edges of an element. /// [`gpui::Edges`] with serde support.
///
/// A extend version of [`gpui::Edges`] to serialize/deserialize.
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)] #[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
#[repr(C)] #[repr(C)]
pub struct Edges<T: Clone + Debug + Default + PartialEq> { pub struct Edges<T: Clone + Debug + Default + PartialEq> {
/// The size of the top edge.
pub top: T, pub top: T,
/// The size of the right edge.
pub right: T, pub right: T,
/// The size of the bottom edge.
pub bottom: T, pub bottom: T,
/// The size of the left edge.
pub left: T, pub left: T,
} }
+27 -212
View File
@@ -1,70 +1,45 @@
use std::ops::{Deref, DerefMut}; use gpui::{App, Pixels, px};
use std::rc::Rc;
use gpui::{App, Global, Pixels, SharedString, Window, px}; mod builtins;
mod colors;
mod geometry; mod geometry;
mod model;
mod notification; mod notification;
mod platform_kind; mod platform_kind;
mod registry; mod resolved;
mod scale; mod runtime;
mod scrollbar_mode; mod scrollbar_mode;
mod theme;
pub use colors::*;
pub use geometry::*; pub use geometry::*;
pub use model::*;
pub use notification::*; pub use notification::*;
pub use platform_kind::PlatformKind; pub use platform_kind::PlatformKind;
pub use registry::*; pub use resolved::{ResolvedAccentRoles, ResolvedColors};
pub use scale::*; pub use runtime::Theme;
pub use scrollbar_mode::*; 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); pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0);
/// Defines window shadow size for platforms that use client side decorations.
pub const CLIENT_SIDE_DECORATION_SHADOW: Pixels = px(10.0); pub const CLIENT_SIDE_DECORATION_SHADOW: Pixels = px(10.0);
/// Defines window border size for platforms that use client side decorations.
pub const CLIENT_SIDE_DECORATION_BORDER: Pixels = px(1.0); pub const CLIENT_SIDE_DECORATION_BORDER: Pixels = px(1.0);
/// Defines window titlebar height
pub(crate) const TITLEBAR_HEIGHT: Pixels = px(36.0); pub(crate) const TITLEBAR_HEIGHT: Pixels = px(36.0);
/// Defines workspace tabbar height
pub const TABBAR_HEIGHT: Pixels = px(36.0); pub const TABBAR_HEIGHT: Pixels = px(36.0);
/// Defines default sidebar width
pub const SIDEBAR_WIDTH: Pixels = px(240.); pub const SIDEBAR_WIDTH: Pixels = px(240.);
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
registry::init(cx); for issue in ThemeRegistry::builtin().validate() {
log::warn!("theme {}: {}", issue.variant_id, issue.message);
}
Theme::sync_system_appearance(None, cx); Theme::change(Appearance::default(), None, cx);
Theme::sync_scrollbar_appearance(cx); Theme::sync_scrollbar_appearance(cx);
} }
/// Mirror the active coop theme into the `gpui-base` global theme. /// Project the active theme onto the `gpui-base` global (focus ring, selection wash, scrollbars, backdrops).
///
/// Base paints a few things from its own tokens -- the focus ring, the wash
/// behind selected text, scrollbars, and overlay backdrops -- so the two
/// globals have to agree or those details drift away from the palette.
///
/// Only roles base can act on are projected. Radius, spacing, typography sizes,
/// shadows, and scrollbar geometry keep their base defaults: coop has a single
/// `radius`/`radius_lg`/`font_size` where base has six-point scales, so any
/// mapping would be invented rather than derived.
///
/// This is a no-op before the coop theme global exists; [`Theme::change`] is the
/// authoritative hook that keeps the projection current.
pub fn sync_base(cx: &mut App) { pub fn sync_base(cx: &mut App) {
let Some(theme) = cx.try_global::<Theme>() else { let Some(theme) = cx.try_global::<Theme>() else {
return; return;
}; };
let appearance = if theme.mode.is_dark() { let appearance = if theme.appearance.is_dark() {
gpui_base::ThemeAppearance::Dark gpui_base::ThemeAppearance::Dark
} else { } else {
gpui_base::ThemeAppearance::Light gpui_base::ThemeAppearance::Light
@@ -75,6 +50,7 @@ pub fn sync_base(cx: &mut App) {
ScrollbarMode::Always => gpui_base::ScrollbarMode::Always, ScrollbarMode::Always => gpui_base::ScrollbarMode::Always,
}; };
let colors = theme.colors; let colors = theme.colors;
let accent = theme.accent;
let font_family = theme.font_family.clone(); let font_family = theme.font_family.clone();
let base = gpui_base::Theme::global_mut(cx); let base = gpui_base::Theme::global_mut(cx);
@@ -85,22 +61,22 @@ pub fn sync_base(cx: &mut App) {
let tokens = &mut base.tokens.colors; let tokens = &mut base.tokens.colors;
tokens.background = colors.background; tokens.background = colors.background;
tokens.foreground = colors.text; tokens.foreground = colors.text;
tokens.surface = colors.surface_background; tokens.surface = colors.shell;
tokens.surface_foreground = colors.text; tokens.surface_foreground = colors.text;
tokens.primary = colors.element_background; tokens.primary = accent.primary;
tokens.primary_foreground = colors.element_foreground; tokens.primary_foreground = accent.on;
tokens.secondary = colors.secondary_background; tokens.secondary = colors.raised;
tokens.secondary_foreground = colors.secondary_foreground; tokens.secondary_foreground = colors.text;
tokens.muted = colors.ghost_element_background_alt; tokens.muted = colors.hover;
tokens.muted_foreground = colors.text_muted; tokens.muted_foreground = colors.text_muted;
tokens.accent = colors.ghost_element_hover; tokens.accent = accent.wash;
tokens.accent_foreground = colors.text; tokens.accent_foreground = colors.text;
tokens.destructive = colors.danger_background; tokens.destructive = colors.danger;
tokens.destructive_foreground = colors.danger_foreground; tokens.destructive_foreground = accent.on_danger;
tokens.border = colors.border; tokens.border = colors.border;
tokens.input = colors.border; tokens.input = colors.input;
tokens.ring = colors.ring; tokens.ring = accent.strong;
tokens.selection = colors.selection; tokens.selection = accent.selection;
} }
pub trait ActiveTheme { pub trait ActiveTheme {
@@ -113,164 +89,3 @@ impl ActiveTheme for App {
Theme::global(self) Theme::global(self)
} }
} }
#[derive(Debug, Clone)]
pub struct Theme {
/// Theme colors
pub colors: ThemeColors,
/// Theme family
pub theme: Rc<ThemeFamily>,
/// 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::<Theme>()
}
/// Returns the global theme mutable reference
pub(crate) fn global_mut(cx: &mut App) -> &mut Theme {
cx.global_mut::<Theme>()
}
/// 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<ThemeFamily>, window: Option<&mut Window>, cx: &mut App) {
let theme = cx.global_mut::<Theme>();
let mode = theme.mode;
// Update the theme
theme.theme = new_theme;
// Emit a theme change event
Self::change(mode, window, cx);
}
/// Change the app's appearance
pub fn change<M>(mode: M, window: Option<&mut Window>, cx: &mut App)
where
M: Into<ThemeMode>,
{
if !cx.has_global::<Theme>() {
let default_theme = ThemeFamily::default();
let theme = Theme::from(default_theme);
cx.set_global(theme);
}
let mode = mode.into();
let theme = cx.global_mut::<Theme>();
// 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<ThemeFamily> 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),
}
}
}
+731
View File
@@ -0,0 +1,731 @@
use std::collections::{BTreeMap, HashSet};
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::builtins::builtin_registry;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Appearance {
#[default]
Dark,
Light,
}
impl Appearance {
pub fn is_dark(self) -> bool {
matches!(self, Self::Dark)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SurfaceTreatment {
#[default]
Opaque,
Frosted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SurfacePreference {
#[default]
ThemeDefault,
Frosted,
Opaque,
}
impl SurfacePreference {
pub fn resolve(self, recommended: SurfaceTreatment) -> SurfaceTreatment {
match self {
Self::ThemeDefault => recommended,
Self::Frosted => SurfaceTreatment::Frosted,
Self::Opaque => SurfaceTreatment::Opaque,
}
}
}
/// A color serialized as `#rrggbb` or `#rrggbbaa`.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const BLACK: Self = Self::rgb(0, 0, 0);
pub const WHITE: Self = Self::rgb(255, 255, 255);
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub fn with_alpha(self, alpha: f32) -> Self {
Self {
a: (alpha.clamp(0.0, 1.0) * 255.0).round() as u8,
..self
}
}
pub fn blend_over(self, background: Self) -> Self {
let alpha = self.a as f32 / 255.0;
let blend = |front: u8, back: u8| {
(front as f32 * alpha + back as f32 * (1.0 - alpha)).round() as u8
};
Self::rgb(
blend(self.r, background.r),
blend(self.g, background.g),
blend(self.b, background.b),
)
}
pub fn mix(self, other: Self, amount: f32) -> Self {
let amount = amount.clamp(0.0, 1.0);
let mix = |a: u8, b: u8| (a as f32 + (b as f32 - a as f32) * amount).round() as u8;
Self::rgba(
mix(self.r, other.r),
mix(self.g, other.g),
mix(self.b, other.b),
mix(self.a, other.a),
)
}
pub fn contrast(self, background: Self) -> f32 {
let foreground = if self.a == 255 {
self
} else {
self.blend_over(background)
};
let (a, b) = (foreground.luminance(), background.luminance());
(a.max(b) + 0.05) / (a.min(b) + 0.05)
}
pub fn best_on_color(self) -> Self {
// Dark surfaces carry white text even where black would contrast more.
if self.luminance() > 0.3 {
Self::BLACK
} else {
Self::WHITE
}
}
/// Move toward black or white until the requested contrast is met.
pub fn ensure_contrast(self, background: Self, minimum: f32) -> Self {
if self.contrast(background) >= minimum {
return self;
}
let target = if Self::BLACK.contrast(background) >= Self::WHITE.contrast(background) {
Self::BLACK
} else {
Self::WHITE
};
for step in 1..=20 {
let candidate = self.mix(target, step as f32 / 20.0);
if candidate.contrast(background) >= minimum {
return candidate;
}
}
target
}
fn luminance(self) -> f32 {
let linear = |channel: u8| {
let value = channel as f32 / 255.0;
if value <= 0.04045 {
value / 12.92
} else {
((value + 0.055) / 1.055).powf(2.4)
}
};
0.2126 * linear(self.r) + 0.7152 * linear(self.g) + 0.0722 * linear(self.b)
}
}
impl fmt::Debug for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.a == 255 {
write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
} else {
write!(
f,
"#{:02x}{:02x}{:02x}{:02x}",
self.r, self.g, self.b, self.a
)
}
}
}
impl FromStr for Color {
type Err = ColorParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let value = value.trim().strip_prefix('#').ok_or(ColorParseError)?;
let expand = |c: u8| (c << 4) | c;
let nibble = |c: u8| match c {
b'0'..=b'9' => Ok(c - b'0'),
b'a'..=b'f' => Ok(c - b'a' + 10),
b'A'..=b'F' => Ok(c - b'A' + 10),
_ => Err(ColorParseError),
};
let pair = |bytes: &[u8]| -> Result<u8, ColorParseError> {
Ok((nibble(bytes[0])? << 4) | nibble(bytes[1])?)
};
let bytes = value.as_bytes();
match bytes.len() {
3 => Ok(Self::rgb(
expand(nibble(bytes[0])?),
expand(nibble(bytes[1])?),
expand(nibble(bytes[2])?),
)),
4 => Ok(Self::rgba(
expand(nibble(bytes[0])?),
expand(nibble(bytes[1])?),
expand(nibble(bytes[2])?),
expand(nibble(bytes[3])?),
)),
6 => Ok(Self::rgb(
pair(&bytes[0..2])?,
pair(&bytes[2..4])?,
pair(&bytes[4..6])?,
)),
8 => Ok(Self::rgba(
pair(&bytes[0..2])?,
pair(&bytes[2..4])?,
pair(&bytes[4..6])?,
pair(&bytes[6..8])?,
)),
_ => Err(ColorParseError),
}
}
}
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("expected a CSS hex color (#rgb, #rgba, #rrggbb, or #rrggbbaa)")]
pub struct ColorParseError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AccentPreset {
#[default]
#[serde(alias = "coop")]
Coop,
Orange,
Amber,
Green,
Cyan,
Blue,
Pink,
}
impl AccentPreset {
pub const ALL: [Self; 7] = [
Self::Coop,
Self::Orange,
Self::Amber,
Self::Green,
Self::Cyan,
Self::Blue,
Self::Pink,
];
pub fn label(self) -> &'static str {
match self {
Self::Coop => "Coop",
Self::Orange => "Orange",
Self::Amber => "Amber",
Self::Green => "Green",
Self::Cyan => "Cyan",
Self::Blue => "Blue",
Self::Pink => "Pink",
}
}
pub fn color(self, appearance: Appearance) -> Color {
let (dark, light) = match self {
Self::Coop => ("#FFE629", "#FFE629"),
Self::Orange => ("#fb923c", "#c2410c"),
Self::Amber => ("#fbbf24", "#a16207"),
Self::Green => ("#4ade80", "#15803d"),
Self::Cyan => ("#22d3ee", "#0e7490"),
Self::Blue => ("#60a5fa", "#2563eb"),
Self::Pink => ("#f472b6", "#be185d"),
};
if appearance.is_dark() { dark } else { light }
.parse()
.expect("built-in accent colors are valid")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AccentSelection {
#[default]
ThemeDefault,
Preset(AccentPreset),
}
impl AccentSelection {
pub fn label(self) -> &'static str {
match self {
Self::ThemeDefault => "Theme default",
Self::Preset(preset) => preset.label(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccentRoles {
pub primary: Color,
pub strong: Color,
pub wash: Color,
pub on: Color,
pub selection: Color,
pub caret: Color,
pub activity: Color,
pub glyph: [Color; 3],
}
impl AccentRoles {
pub fn derive(primary: Color, appearance: Appearance, background: Color) -> Self {
// Light mode keeps the raw brand accent for fills; `strong` backs strokes at 3:1.
let (primary, mut strong) = if appearance.is_dark() {
let primary = primary.ensure_contrast(background, 3.0);
(primary, primary)
} else {
(primary, primary.ensure_contrast(background, 3.0))
};
let on = primary.best_on_color();
// 3:1 pairing so white-on-dark accents don't force the fill darker.
if on.contrast(strong) < 3.0 {
strong = strong.ensure_contrast(on, 3.0);
}
let light = primary.mix(
if appearance.is_dark() {
Color::WHITE
} else {
background
},
if appearance.is_dark() { 0.28 } else { 0.18 },
);
let deep = primary.mix(Color::BLACK, if appearance.is_dark() { 0.18 } else { 0.26 });
Self {
primary,
strong,
wash: primary.with_alpha(if appearance.is_dark() { 0.22 } else { 0.12 }),
on,
selection: primary.with_alpha(if appearance.is_dark() { 0.35 } else { 0.24 }),
caret: primary,
activity: primary,
glyph: [light, primary, deep],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeSelection {
pub light: String,
pub dark: String,
}
impl Default for ThemeSelection {
fn default() -> Self {
Self {
light: "coop-light".into(),
dark: "coop-dark".into(),
}
}
}
impl ThemeSelection {
pub fn variant_id(&self, appearance: Appearance) -> &str {
if appearance.is_dark() {
&self.dark
} else {
&self.light
}
}
pub fn set_variant(&mut self, appearance: Appearance, id: impl Into<String>) {
if appearance.is_dark() {
self.dark = id.into();
} else {
self.light = id.into();
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeSource {
pub format: String,
pub url: String,
pub revision: String,
pub license: String,
pub asset_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeColors {
pub background: Color,
pub shell: Color,
pub raised: Color,
pub card: Color,
pub dialog: Color,
pub overlay: Color,
pub hover: Color,
pub active: Color,
pub border: Color,
pub border_strong: Color,
pub text: Color,
pub text_muted: Color,
pub text_faint: Color,
pub solid: Color,
pub on_solid: Color,
pub danger: Color,
pub danger_muted: Color,
pub warning: Color,
pub warning_muted: Color,
pub success: Color,
pub success_muted: Color,
pub input: Color,
pub cursor: Color,
pub diff_add: Color,
pub diff_delete: Color,
pub diff_hunk: Color,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TerminalPalette {
pub background: Color,
pub foreground: Color,
pub selection: Color,
pub ansi: [Color; 16],
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeVariant {
pub id: String,
pub family_id: String,
pub name: String,
pub appearance: Appearance,
#[serde(alias = "surfaceTreatment")]
pub recommended_surface_treatment: SurfaceTreatment,
pub colors: ThemeColors,
pub accent: AccentRoles,
pub syntax: BTreeMap<String, Color>,
pub terminal: TerminalPalette,
pub source: ThemeSource,
}
impl ThemeVariant {
pub fn accent_for(&self, selection: AccentSelection) -> AccentRoles {
match selection {
AccentSelection::ThemeDefault => self.accent,
AccentSelection::Preset(preset) => AccentRoles::derive(
preset.color(self.appearance),
self.appearance,
self.colors.background,
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeFamily {
pub id: String,
pub name: String,
pub variants: Vec<ThemeVariant>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ThemeRegistry {
pub families: Vec<ThemeFamily>,
}
impl ThemeRegistry {
pub fn builtin() -> &'static Self {
builtin_registry()
}
pub fn variant(&self, id: &str) -> Option<&ThemeVariant> {
self.families
.iter()
.flat_map(|family| &family.variants)
.find(|variant| variant.id == id)
}
pub fn resolve(&self, selection: &ThemeSelection, appearance: Appearance) -> &ThemeVariant {
self.variant(selection.variant_id(appearance))
.or_else(|| {
self.variant(if appearance.is_dark() {
"coop-dark"
} else {
"coop-light"
})
})
.expect("the built-in registry always contains both Coop variants")
}
pub fn validate(&self) -> Vec<ValidationIssue> {
let mut issues = Vec::new();
let mut ids = HashSet::new();
for family in &self.families {
for variant in &family.variants {
if variant.id.trim().is_empty() || !ids.insert(&variant.id) {
issues.push(ValidationIssue::structural_error(
&variant.id,
"variant id must be unique",
));
}
validate_contrast(
&mut issues,
variant,
"text",
variant.colors.text,
variant.colors.background,
4.5,
);
validate_contrast(
&mut issues,
variant,
"muted text",
variant.colors.text_muted,
variant.colors.background,
4.5,
);
validate_contrast(
&mut issues,
variant,
"accent-strong",
variant.accent.strong,
variant.colors.background,
3.0,
);
validate_contrast(
&mut issues,
variant,
"on-accent",
variant.accent.on,
variant.accent.strong,
3.0,
);
validate_contrast(
&mut issues,
variant,
"terminal foreground",
variant.terminal.foreground,
variant.terminal.background,
4.5,
);
if variant.source.url.is_empty()
|| variant.source.revision.is_empty()
|| variant.source.license.is_empty()
|| variant.source.asset_hash.is_empty()
{
issues.push(ValidationIssue::structural_error(
&variant.id,
"source provenance is incomplete",
));
}
for (index, color) in variant.terminal.ansi.iter().enumerate() {
// Slots 0 and 8 are structural black/dim colors.
if index % 8 == 0 {
continue;
}
if color.contrast(variant.terminal.background) < 3.0 {
issues.push(ValidationIssue::contrast_warning(
&variant.id,
format!("terminal ANSI slot {index} is below 3:1"),
));
}
}
}
}
issues
}
}
fn validate_contrast(
issues: &mut Vec<ValidationIssue>,
variant: &ThemeVariant,
role: &str,
foreground: Color,
background: Color,
minimum: f32,
) {
let actual = foreground.contrast(background);
if actual < minimum {
issues.push(ValidationIssue::contrast_error(
&variant.id,
format!("{role} contrast is {actual:.2}:1; expected {minimum:.1}:1"),
));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationSeverity {
Warning,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationCategory {
Structural,
Contrast,
}
#[derive(Debug, Clone)]
pub struct ValidationIssue {
pub variant_id: String,
pub category: ValidationCategory,
pub severity: ValidationSeverity,
pub message: String,
}
impl ValidationIssue {
fn structural_error(variant_id: &str, message: impl Into<String>) -> Self {
Self {
variant_id: variant_id.into(),
category: ValidationCategory::Structural,
severity: ValidationSeverity::Error,
message: message.into(),
}
}
fn contrast_error(variant_id: &str, message: impl Into<String>) -> Self {
Self {
variant_id: variant_id.into(),
category: ValidationCategory::Contrast,
severity: ValidationSeverity::Error,
message: message.into(),
}
}
fn contrast_warning(variant_id: &str, message: impl Into<String>) -> Self {
Self {
variant_id: variant_id.into(),
category: ValidationCategory::Contrast,
severity: ValidationSeverity::Warning,
message: message.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn colors_round_trip_all_supported_css_hex_lengths() {
for source in ["#abc", "#abcd", "#102030", "#10203040"] {
let color: Color = source.parse().unwrap();
let json = serde_json::to_string(&color).unwrap();
assert_eq!(serde_json::from_str::<Color>(&json).unwrap(), color);
}
}
#[test]
fn accent_overlay_meets_interaction_contrast() {
for appearance in [Appearance::Light, Appearance::Dark] {
let background = if appearance.is_dark() {
Color::rgb(6, 6, 6)
} else {
Color::WHITE
};
for preset in AccentPreset::ALL {
let roles = AccentRoles::derive(preset.color(appearance), appearance, background);
assert!(
roles.strong.contrast(background) >= 3.0,
"{appearance:?} {preset:?}"
);
assert!(
roles.on.contrast(roles.strong) >= 3.0,
"{appearance:?} {preset:?}"
);
}
}
}
#[test]
fn surface_preference_resolves_independently_from_theme_recommendation() {
assert_eq!(
SurfacePreference::ThemeDefault.resolve(SurfaceTreatment::Frosted),
SurfaceTreatment::Frosted
);
assert_eq!(
SurfacePreference::ThemeDefault.resolve(SurfaceTreatment::Opaque),
SurfaceTreatment::Opaque
);
for recommended in [SurfaceTreatment::Frosted, SurfaceTreatment::Opaque] {
assert_eq!(
SurfacePreference::Frosted.resolve(recommended),
SurfaceTreatment::Frosted
);
assert_eq!(
SurfacePreference::Opaque.resolve(recommended),
SurfaceTreatment::Opaque
);
}
}
#[test]
fn builtins_have_complete_provenance_and_no_validation_errors() {
let registry = ThemeRegistry::builtin();
assert_eq!(registry.families.len(), 19);
assert_eq!(
registry
.families
.iter()
.map(|family| family.variants.len())
.sum::<usize>(),
30
);
assert!(registry.variant("coop-light").is_some());
assert!(registry.variant("coop-dark").is_some());
let errors: Vec<_> = registry
.validate()
.into_iter()
.filter(|issue| issue.severity == ValidationSeverity::Error)
.collect();
assert!(errors.is_empty(), "{errors:#?}");
}
}
-4
View File
@@ -2,14 +2,10 @@ use gpui::{Anchor, Pixels, px};
use crate::{Edges, TITLEBAR_HEIGHT}; use crate::{Edges, TITLEBAR_HEIGHT};
/// The settings for notifications.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NotificationSettings { pub struct NotificationSettings {
/// The placement of the notification, default: [`Anchor::TopRight`]
pub placement: Anchor, pub placement: Anchor,
/// The margins of the notification with respect to the window edges.
pub margins: Edges<Pixels>, pub margins: Edges<Pixels>,
/// The maximum number of notifications to show at once, default: 10
pub max_items: usize, pub max_items: usize,
} }
-70
View File
@@ -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<ThemeRegistry>);
impl Global for GlobalThemeRegistry {}
pub struct ThemeRegistry {
/// Map of theme names to theme families
themes: HashMap<SharedString, Rc<ThemeFamily>>,
}
impl ThemeRegistry {
/// Retrieve the global theme registry state
pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalThemeRegistry>().0.clone()
}
/// Set the global theme registry instance
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
cx.set_global(GlobalThemeRegistry(state));
}
/// Create a new theme registry instance
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
let mut themes = HashMap::new();
let asset = cx.asset_source();
if let Ok(paths) = asset.list("themes") {
for path in paths.into_iter() {
match Self::load(&path, asset) {
Ok(theme) => {
themes.insert(path, Rc::new(theme));
}
Err(e) => {
log::error!("Failed to load theme: {path}. Error: {e}");
}
}
}
}
Self { themes }
}
/// Load a theme from the asset source.
fn load(path: &str, asset: &Arc<dyn AssetSource>) -> Result<ThemeFamily, Error> {
// Load the theme file from the assets
let content = asset.load(path)?.context("Theme not found")?;
// Parse the JSON content into a Theme Family struct
let theme: ThemeFamily = serde_json::from_slice(&content)?;
Ok(theme)
}
/// Returns a reference to the map of themes.
pub fn themes(&self) -> &HashMap<SharedString, Rc<ThemeFamily>> {
&self.themes
}
}
+113
View File
@@ -0,0 +1,113 @@
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))
}
/// Accent roles resolved to [`Hsla`], with derived interaction states precomputed.
#[derive(Debug, Clone, Copy)]
pub struct ResolvedAccentRoles {
pub primary: Hsla,
/// Accent for strokes, icons, and text on the background; meets 3:1 contrast.
pub strong: Hsla,
pub wash: Hsla,
pub on: Hsla,
pub selection: Hsla,
pub hover: Hsla,
pub active: Hsla,
pub selected: Hsla,
/// Text guaranteed 4.5:1 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),
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 on every theme change.
#[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,
}
impl ResolvedColors {
pub fn resolve(variant: &ThemeVariant) -> Self {
let colors = &variant.colors;
Self {
background: hsla(colors.background),
shell: hsla(colors.shell),
raised: hsla(colors.raised),
card: hsla(colors.card),
dialog: hsla(colors.dialog),
overlay: hsla(colors.overlay),
hover: hsla(colors.hover),
active: hsla(colors.active),
border: hsla(colors.border),
border_strong: hsla(colors.border_strong),
text: hsla(colors.text),
text_muted: hsla(colors.text_muted),
text_faint: hsla(colors.text_faint),
solid: hsla(colors.solid),
on_solid: hsla(colors.on_solid),
danger: hsla(colors.danger),
danger_muted: hsla(colors.danger_muted),
warning: hsla(colors.warning),
warning_muted: hsla(colors.warning_muted),
success: hsla(colors.success),
success_muted: hsla(colors.success_muted),
input: hsla(colors.input),
cursor: hsla(colors.cursor),
diff_add: hsla(colors.diff_add),
diff_delete: hsla(colors.diff_delete),
diff_hunk: hsla(colors.diff_hunk),
}
}
}
+156
View File
@@ -0,0 +1,156 @@
use std::ops::Deref;
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};
#[derive(Debug, Clone)]
pub struct Theme {
pub variant: Arc<ThemeVariant>,
pub appearance: Appearance,
/// Named `theme_selection` to avoid shadowing the `selection` color role via `Deref`.
pub theme_selection: ThemeSelection,
pub accent_selection: AccentSelection,
pub surface_preference: SurfacePreference,
pub surface: SurfaceTreatment,
pub accent: ResolvedAccentRoles,
pub colors: ResolvedColors,
pub font_family: SharedString,
pub font_size: Pixels,
pub radius: Pixels,
pub radius_lg: Pixels,
pub shadow: bool,
pub scrollbar_mode: ScrollbarMode,
pub notification: NotificationSettings,
}
impl Deref for Theme {
type Target = ResolvedColors;
fn deref(&self) -> &Self::Target {
&self.colors
}
}
impl Global for Theme {}
impl Theme {
pub fn global(cx: &App) -> &Theme {
cx.global::<Theme>()
}
pub(crate) fn global_mut(cx: &mut App) -> &mut Theme {
cx.global_mut::<Theme>()
}
pub fn is_dark(&self) -> bool {
self.appearance.is_dark()
}
fn new() -> Self {
let appearance = Appearance::default();
let selection = ThemeSelection::default();
let font_family = match PlatformKind::platform() {
PlatformKind::Linux => "Inter",
_ => ".SystemUIFont",
};
let variant = ThemeRegistry::builtin()
.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);
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(),
}
}
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
};
}
pub fn set_selection(selection: ThemeSelection, cx: &mut App) {
Theme::global_mut(cx).theme_selection = selection;
}
pub fn set_accent_selection(selection: AccentSelection, cx: &mut App) {
Theme::global_mut(cx).accent_selection = selection;
}
pub fn set_surface_preference(preference: SurfacePreference, cx: &mut App) {
Theme::global_mut(cx).surface_preference = preference;
}
/// Switch appearance and re-resolve the selected variant, accent, and surface.
pub fn change<M>(appearance: M, window: Option<&mut Window>, cx: &mut App)
where
M: Into<Appearance>,
{
if !cx.has_global::<Theme>() {
cx.set_global(Theme::new());
}
let appearance = appearance.into();
let (selection, accent_selection, surface_preference) = {
let theme = cx.global_mut::<Theme>();
theme.appearance = appearance;
(
theme.theme_selection.clone(),
theme.accent_selection,
theme.surface_preference,
)
};
let variant = ThemeRegistry::builtin()
.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);
{
let theme = cx.global_mut::<Theme>();
theme.variant = Arc::new(variant.clone());
theme.accent = ResolvedAccentRoles::resolve(&accent, &variant.colors);
theme.surface = surface;
theme.colors = colors;
}
if let Some(window) = window {
window.refresh();
}
sync_base(cx);
}
}
-177
View File
@@ -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<Hsla>);
impl FromIterator<Hsla> for ColorScale {
fn from_iter<T: IntoIterator<Item = Hsla>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}
impl ColorScale {
/// Returns the specified step in the [`ColorScale`].
#[inline]
pub fn step(&self, step: ColorScaleStep) -> Hsla {
// Steps are one-based, so we need convert to the zero-based vec index.
self.0[step.0 - 1]
}
/// `Step 1` - Used for main application backgrounds.
///
/// This step provides a neutral base for any overlaying components, ideal for applications' main backdrop or empty spaces such as canvas areas.
///
#[inline]
pub fn step_1(&self) -> Hsla {
self.step(ColorScaleStep::ONE)
}
/// `Step 2` - Used for both main application backgrounds and subtle component backgrounds.
///
/// Like `Step 1`, this step allows variations in background styles, from striped tables, sidebar backgrounds, to card backgrounds.
#[inline]
pub fn step_2(&self) -> Hsla {
self.step(ColorScaleStep::TWO)
}
/// `Step 3` - Used for UI component backgrounds in their normal states.
///
/// This step maintains accessibility by guaranteeing a contrast ratio of 4.5:1 with steps 11 and 12 for text. It could also suit hover states for transparent components.
#[inline]
pub fn step_3(&self) -> Hsla {
self.step(ColorScaleStep::THREE)
}
/// `Step 4` - Used for UI component backgrounds in their hover states.
///
/// Also suited for pressed or selected states of components with a transparent background.
#[inline]
pub fn step_4(&self) -> Hsla {
self.step(ColorScaleStep::FOUR)
}
/// `Step 5` - Used for UI component backgrounds in their pressed or selected states.
#[inline]
pub fn step_5(&self) -> Hsla {
self.step(ColorScaleStep::FIVE)
}
/// `Step 6` - Used for subtle borders on non-interactive components.
///
/// Its usage spans from sidebars' borders, headers' dividers, cards' outlines, to alerts' edges and separators.
#[inline]
pub fn step_6(&self) -> Hsla {
self.step(ColorScaleStep::SIX)
}
/// `Step 7` - Used for subtle borders on interactive components.
///
/// This step subtly delineates the boundary of elements users interact with.
#[inline]
pub fn step_7(&self) -> Hsla {
self.step(ColorScaleStep::SEVEN)
}
/// `Step 8` - Used for stronger borders on interactive components and focus rings.
///
/// It strengthens the visibility and accessibility of active elements and their focus states.
#[inline]
pub fn step_8(&self) -> Hsla {
self.step(ColorScaleStep::EIGHT)
}
/// `Step 9` - Used for solid backgrounds.
///
/// `Step 9` is the most saturated step, having the least mix of white or black.
///
/// Due to its high chroma, `Step 9` is versatile and particularly useful for semantic colors such as
/// error, warning, and success indicators.
#[inline]
pub fn step_9(&self) -> Hsla {
self.step(ColorScaleStep::NINE)
}
/// `Step 10` - Used for hovered or active solid backgrounds, particularly when `Step 9` is their normal state.
///
/// May also be used for extremely low contrast text. This should be used sparingly, as it may be difficult to read.
#[inline]
pub fn step_10(&self) -> Hsla {
self.step(ColorScaleStep::TEN)
}
/// `Step 11` - Used for text and icons requiring low contrast or less emphasis.
#[inline]
pub fn step_11(&self) -> Hsla {
self.step(ColorScaleStep::ELEVEN)
}
/// `Step 12` - Used for text and icons requiring high contrast or prominence.
#[inline]
pub fn step_12(&self) -> Hsla {
self.step(ColorScaleStep::TWELVE)
}
}
/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale.
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
}
}
+1 -2
View File
@@ -1,7 +1,6 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; 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 { pub enum ScrollbarMode {
#[default] #[default]
Scrolling, Scrolling,
-359
View File
@@ -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<WindowAppearance> 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<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
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<Self> {
let path = format!("assets/{target}");
Self::from_file(path)
}
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::tempdir;
use super::*;
#[test]
fn test_from_file() {
// Create a temporary directory for our test
let dir = tempdir().unwrap();
let file_path = dir.path().join("test-theme.json");
// Create a minimal valid theme JSON with hex colors
// Using simple hex colors that Hsla can parse
// Note: We need to escape the # characters in the raw string
let json_data = r##"{
"id": "test-theme",
"name": "Test Theme",
"author": "Coop",
"url": "https://github.com/reyakov/coop",
"light": {
"background": "#ffffff",
"surface_background": "#fafafa",
"elevated_surface_background": "#f5f5f5",
"panel_background": "#ffffff",
"overlay": "#0000001a",
"title_bar": "#00000000",
"title_bar_inactive": "#ffffff",
"window_border": "#c7c7cf",
"border": "#dbdbdb",
"border_variant": "#d1d1d1",
"border_focused": "#3366cc",
"border_selected": "#3366cc",
"border_transparent": "#00000000",
"border_disabled": "#e6e6e6",
"ring": "#4d79d6",
"text": "#1a1a1a",
"text_muted": "#4d4d4d",
"text_placeholder": "#808080",
"text_accent": "#3366cc",
"icon": "#4d4d4d",
"icon_muted": "#808080",
"icon_accent": "#3366cc",
"element_foreground": "#ffffff",
"element_background": "#3366cc",
"element_hover": "#3366cce6",
"element_active": "#2e5cb8",
"element_selected": "#2952a3",
"element_disabled": "#3366cc4d",
"secondary_foreground": "#2952a3",
"secondary_background": "#e6ecf5",
"secondary_hover": "#3366cc1a",
"secondary_active": "#d9e2f0",
"secondary_selected": "#d9e2f0",
"secondary_disabled": "#3366cc4d",
"danger_foreground": "#ffffff",
"danger_background": "#f5e6e6",
"danger_hover": "#cc33331a",
"danger_active": "#f0d9d9",
"danger_selected": "#f0d9d9",
"danger_disabled": "#cc33334d",
"warning_foreground": "#1a1a1a",
"warning_background": "#f5f0e6",
"warning_hover": "#cc99331a",
"warning_active": "#f0ead9",
"warning_selected": "#f0ead9",
"warning_disabled": "#cc99334d",
"ghost_element_background": "#00000000",
"ghost_element_background_alt": "#e6e6e6",
"ghost_element_hover": "#0000001a",
"ghost_element_active": "#d9d9d9",
"ghost_element_selected": "#d9d9d9",
"ghost_element_disabled": "#0000000d",
"tab_inactive_background": "#e6e6e6",
"tab_hover_background": "#e0e0e0",
"tab_active_background": "#d9d9d9",
"scrollbar_thumb_background": "#00000033",
"scrollbar_thumb_hover_background": "#0000004d",
"scrollbar_thumb_border": "#00000000",
"scrollbar_track_background": "#00000000",
"scrollbar_track_border": "#d9d9d9",
"drop_target_background": "#3366cc1a",
"cursor": "#3399ff",
"selection": "#3399ff40"
},
"dark": {
"background": "#1a1a1a",
"surface_background": "#1f1f1f",
"elevated_surface_background": "#242424",
"panel_background": "#262626",
"overlay": "#ffffff1a",
"title_bar": "#00000000",
"title_bar_inactive": "#1a1a1a",
"window_border": "#404046",
"border": "#404040",
"border_variant": "#383838",
"border_focused": "#4d79d6",
"border_selected": "#4d79d6",
"border_transparent": "#00000000",
"border_disabled": "#2e2e2e",
"ring": "#668cdf",
"text": "#f2f2f2",
"text_muted": "#b3b3b3",
"text_placeholder": "#808080",
"text_accent": "#668cdf",
"icon": "#b3b3b3",
"icon_muted": "#808080",
"icon_accent": "#668cdf",
"element_foreground": "#ffffff",
"element_background": "#4d79d6",
"element_hover": "#4d79d6e6",
"element_active": "#456dc1",
"element_selected": "#3e62ac",
"element_disabled": "#4d79d64d",
"secondary_foreground": "#3e62ac",
"secondary_background": "#2a3652",
"secondary_hover": "#4d79d61a",
"secondary_active": "#303d5c",
"secondary_selected": "#303d5c",
"secondary_disabled": "#4d79d64d",
"danger_foreground": "#ffffff",
"danger_background": "#522a2a",
"danger_hover": "#d64d4d1a",
"danger_active": "#5c3030",
"danger_selected": "#5c3030",
"danger_disabled": "#d64d4d4d",
"warning_foreground": "#f2f2f2",
"warning_background": "#52482a",
"warning_hover": "#d6b34d1a",
"warning_active": "#5c5430",
"warning_selected": "#5c5430",
"warning_disabled": "#d6b34d4d",
"ghost_element_background": "#00000000",
"ghost_element_background_alt": "#2e2e2e",
"ghost_element_hover": "#ffffff1a",
"ghost_element_active": "#383838",
"ghost_element_selected": "#383838",
"ghost_element_disabled": "#ffffff0d",
"tab_inactive_background": "#2e2e2e",
"tab_hover_background": "#333333",
"tab_active_background": "#383838",
"scrollbar_thumb_background": "#ffffff33",
"scrollbar_thumb_hover_background": "#ffffff4d",
"scrollbar_thumb_border": "#00000000",
"scrollbar_track_background": "#00000000",
"scrollbar_track_border": "#383838",
"drop_target_background": "#4d79d61a",
"cursor": "#4db3ff",
"selection": "#4db3ff40"
}
}"##;
// Write the JSON to the file
fs::write(&file_path, json_data).unwrap();
// Test loading the theme from file
let theme = ThemeFamily::from_file(&file_path).unwrap();
// Verify the loaded theme
assert_eq!(theme.id, "test-theme");
assert_eq!(theme.name, "Test Theme");
// Clean up
dir.close().unwrap();
}
#[test]
fn test_from_file_nonexistent() {
// Test that loading a non-existent file returns an error
let result = ThemeFamily::from_file("non-existent-file.json");
assert!(result.is_err());
}
#[test]
fn test_from_file_invalid_json() {
// Create a temporary directory for our test
let dir = tempdir().unwrap();
let file_path = dir.path().join("invalid-theme.json");
// Write invalid JSON
fs::write(&file_path, "invalid json").unwrap();
// Test that loading invalid JSON returns an error
let result = ThemeFamily::from_file(&file_path);
assert!(result.is_err());
// Clean up
dir.close().unwrap();
}
}
+2 -2
View File
@@ -424,7 +424,7 @@ impl InteractiveElement for Avatar {
} }
impl RenderOnce for Avatar { impl RenderOnce for Avatar {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
let border_width = if self.border_color.is_some() { let border_width = if self.border_color.is_some() {
px(2.) px(2.)
} else { } else {
@@ -444,7 +444,7 @@ impl RenderOnce for Avatar {
.rounded_full() .rounded_full()
.object_fit(ObjectFit::Cover) .object_fit(ObjectFit::Cover)
.grayscale(grayscale) .grayscale(grayscale)
.bg(cx.theme().ghost_element_background) .bg(gpui::transparent_black())
.with_fallback(move || generated_avatar(seed.as_deref(), image_size)) .with_fallback(move || generated_avatar(seed.as_deref(), image_size))
.into_any_element() .into_any_element()
} }
+41 -41
View File
@@ -24,10 +24,10 @@ pub struct ButtonCustomVariant {
impl ButtonCustomVariant { impl ButtonCustomVariant {
pub fn new(_window: &Window, cx: &App) -> Self { pub fn new(_window: &Window, cx: &App) -> Self {
Self { Self {
color: cx.theme().element_background, color: cx.theme().accent.primary,
foreground: cx.theme().element_foreground, foreground: cx.theme().accent.on,
hover: cx.theme().element_hover, hover: cx.theme().accent.hover,
active: cx.theme().element_active, active: cx.theme().accent.active,
} }
} }
@@ -470,15 +470,15 @@ impl ButtonVariant {
fn bg_color(&self, cx: &App) -> Hsla { fn bg_color(&self, cx: &App) -> Hsla {
match self { match self {
ButtonVariant::Primary => cx.theme().element_background, ButtonVariant::Primary => cx.theme().accent.primary,
ButtonVariant::Secondary => cx.theme().secondary_background, ButtonVariant::Secondary => cx.theme().accent.wash,
ButtonVariant::Danger => cx.theme().danger_background, ButtonVariant::Danger => cx.theme().danger,
ButtonVariant::Warning => cx.theme().warning_background, ButtonVariant::Warning => cx.theme().warning,
ButtonVariant::Ghost { alt } => { ButtonVariant::Ghost { alt } => {
if *alt { if *alt {
cx.theme().ghost_element_background_alt cx.theme().raised
} else { } else {
cx.theme().ghost_element_background gpui::transparent_black()
} }
} }
ButtonVariant::Custom(colors) => colors.color, ButtonVariant::Custom(colors) => colors.color,
@@ -488,11 +488,11 @@ impl ButtonVariant {
fn text_color(&self, cx: &App) -> Hsla { fn text_color(&self, cx: &App) -> Hsla {
match self { match self {
ButtonVariant::Primary => cx.theme().element_foreground, ButtonVariant::Primary => cx.theme().accent.on,
ButtonVariant::Secondary => cx.theme().secondary_foreground, ButtonVariant::Secondary => cx.theme().accent.text_on_wash,
ButtonVariant::Danger => cx.theme().danger_foreground, ButtonVariant::Danger => cx.theme().accent.on_danger,
ButtonVariant::Warning => cx.theme().warning_foreground, ButtonVariant::Warning => cx.theme().accent.on_danger,
ButtonVariant::Transparent => cx.theme().text_placeholder, ButtonVariant::Transparent => cx.theme().text_faint,
ButtonVariant::Ghost { alt } => { ButtonVariant::Ghost { alt } => {
if *alt { if *alt {
cx.theme().text cx.theme().text
@@ -506,19 +506,19 @@ impl ButtonVariant {
fn hovered(&self, cx: &App) -> ButtonVariantStyle { fn hovered(&self, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().element_hover, ButtonVariant::Primary => cx.theme().accent.hover,
ButtonVariant::Secondary => cx.theme().secondary_hover, ButtonVariant::Secondary => cx.theme().accent.selection,
ButtonVariant::Danger => cx.theme().danger_hover, ButtonVariant::Danger => cx.theme().danger.opacity(0.15),
ButtonVariant::Warning => cx.theme().warning_hover, ButtonVariant::Warning => cx.theme().warning.opacity(0.15),
ButtonVariant::Ghost { .. } => cx.theme().ghost_element_hover, ButtonVariant::Ghost { .. } => cx.theme().hover,
ButtonVariant::Transparent => gpui::transparent_black(), ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.hover, ButtonVariant::Custom(colors) => colors.hover,
}; };
let fg = match self { let fg = match self {
ButtonVariant::Secondary => cx.theme().secondary_foreground, ButtonVariant::Secondary => cx.theme().accent.text_on_wash,
ButtonVariant::Ghost { .. } => cx.theme().text, ButtonVariant::Ghost { .. } => cx.theme().text,
ButtonVariant::Transparent => cx.theme().text_placeholder, ButtonVariant::Transparent => cx.theme().text_faint,
_ => self.text_color(cx), _ => self.text_color(cx),
}; };
@@ -527,18 +527,18 @@ impl ButtonVariant {
fn active(&self, cx: &App) -> ButtonVariantStyle { fn active(&self, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().element_active, ButtonVariant::Primary => cx.theme().accent.active,
ButtonVariant::Secondary => cx.theme().secondary_active, ButtonVariant::Secondary => cx.theme().accent.selection,
ButtonVariant::Danger => cx.theme().danger_active, ButtonVariant::Danger => cx.theme().danger.opacity(0.25),
ButtonVariant::Warning => cx.theme().warning_active, ButtonVariant::Warning => cx.theme().warning.opacity(0.25),
ButtonVariant::Ghost { .. } => cx.theme().ghost_element_active, ButtonVariant::Ghost { .. } => cx.theme().active,
ButtonVariant::Transparent => gpui::transparent_black(), ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.active, ButtonVariant::Custom(colors) => colors.active,
}; };
let fg = match self { let fg = match self {
ButtonVariant::Secondary => cx.theme().secondary_foreground, ButtonVariant::Secondary => cx.theme().accent.text_on_wash,
ButtonVariant::Transparent => cx.theme().text_placeholder, ButtonVariant::Transparent => cx.theme().text_faint,
_ => self.text_color(cx), _ => self.text_color(cx),
}; };
@@ -547,18 +547,18 @@ impl ButtonVariant {
fn selected(&self, cx: &App) -> ButtonVariantStyle { fn selected(&self, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().element_selected, ButtonVariant::Primary => cx.theme().accent.selected,
ButtonVariant::Secondary => cx.theme().secondary_selected, ButtonVariant::Secondary => cx.theme().accent.selection,
ButtonVariant::Danger => cx.theme().danger_selected, ButtonVariant::Danger => cx.theme().danger.opacity(0.25),
ButtonVariant::Warning => cx.theme().warning_selected, ButtonVariant::Warning => cx.theme().warning.opacity(0.25),
ButtonVariant::Ghost { .. } => cx.theme().ghost_element_selected, ButtonVariant::Ghost { .. } => cx.theme().active,
ButtonVariant::Transparent => gpui::transparent_black(), ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.active, ButtonVariant::Custom(colors) => colors.active,
}; };
let fg = match self { let fg = match self {
ButtonVariant::Secondary => cx.theme().secondary_foreground, ButtonVariant::Secondary => cx.theme().accent.text_on_wash,
ButtonVariant::Transparent => cx.theme().text_placeholder, ButtonVariant::Transparent => cx.theme().text_faint,
_ => self.text_color(cx), _ => self.text_color(cx),
}; };
@@ -567,11 +567,11 @@ impl ButtonVariant {
fn disabled(&self, cx: &App) -> ButtonVariantStyle { fn disabled(&self, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Danger => cx.theme().danger_disabled, ButtonVariant::Danger => cx.theme().danger.opacity(0.12),
ButtonVariant::Warning => cx.theme().warning_disabled, ButtonVariant::Warning => cx.theme().warning.opacity(0.12),
ButtonVariant::Ghost { .. } => cx.theme().ghost_element_disabled, ButtonVariant::Ghost { .. } => cx.theme().border.opacity(0.4),
ButtonVariant::Secondary => cx.theme().secondary_disabled, ButtonVariant::Secondary => cx.theme().accent.wash.opacity(0.5),
_ => cx.theme().element_disabled, _ => cx.theme().accent.primary.opacity(0.3),
}; };
let fg = match self { let fg = match self {
+5 -5
View File
@@ -341,7 +341,7 @@ impl RenderOnce for Dialog {
.id(layer_ix) .id(layer_ix)
.bg(cx.theme().background) .bg(cx.theme().background)
.border_1() .border_1()
.border_color(cx.theme().border.alpha(0.4)) .border_color(cx.theme().border)
.rounded(radius) .rounded(radius)
.when(cx.theme().shadow, |this| this.shadow_xl()) .when(cx.theme().shadow, |this| this.shadow_xl())
.min_h_24() .min_h_24()
@@ -381,10 +381,10 @@ impl RenderOnce for Dialog {
.icon(IconName::CloseCircleFill) .icon(IconName::CloseCircleFill)
.custom( .custom(
ButtonCustomVariant::new(window, cx) ButtonCustomVariant::new(window, cx)
.foreground(cx.theme().icon_muted) .foreground(cx.theme().text_faint)
.color(cx.theme().ghost_element_background) .color(gpui::transparent_black())
.hover(cx.theme().ghost_element_background) .hover(gpui::transparent_black())
.active(cx.theme().ghost_element_background), .active(gpui::transparent_black()),
) )
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
on_cancel(&ClickEvent::default(), window, cx); on_cancel(&ClickEvent::default(), window, cx);
+6 -6
View File
@@ -446,7 +446,7 @@ impl TabGroupSkin {
.flex_grow_1() .flex_grow_1()
.min_w_16() .min_w_16()
.when(droppable, |this| { .when(droppable, |this| {
this.drag_over::<DragPanel>(|this, _, _, cx| this.bg(cx.theme().surface_background)) this.drag_over::<DragPanel>(|this, _, _, cx| this.bg(cx.theme().accent.selection))
.on_drop({ .on_drop({
let group = TabGroupContext::clone(group); let group = TabGroupContext::clone(group);
move |drag: &DragPanel, window, cx| { move |drag: &DragPanel, window, cx| {
@@ -465,7 +465,7 @@ impl TabGroupSkin {
let bar = TabBar::new("tab-bar") let bar = TabBar::new("tab-bar")
.track_scroll(&self.scroll_handle) .track_scroll(&self.scroll_handle)
.h(TABBAR_HEIGHT) .h(TABBAR_HEIGHT)
.bg(cx.theme().panel_background) .bg(cx.theme().background)
.when(needs_traffic_light_padding, |this| { .when(needs_traffic_light_padding, |this| {
this.pl(px(TRAFFIC_LIGHT_PADDING)) this.pl(px(TRAFFIC_LIGHT_PADDING))
}) })
@@ -567,7 +567,7 @@ impl TabGroupSkin {
h_flex() h_flex()
.h(TABBAR_HEIGHT) .h(TABBAR_HEIGHT)
.w_full() .w_full()
.bg(cx.theme().panel_background) .bg(cx.theme().background)
.child(bar.flex_1()) .child(bar.flex_1())
.child(window_controls()) .child(window_controls())
.into_any_element() .into_any_element()
@@ -717,7 +717,7 @@ impl TabGroupRenderer for TabGroupSkin {
.child( .child(
div() div()
.size_full() .size_full()
.bg(cx.theme().panel_background) .bg(cx.theme().background)
.overflow_hidden() .overflow_hidden()
.child(panel.cached(StyleRefinement::default().v_flex().size_full())), .child(panel.cached(StyleRefinement::default().v_flex().size_full())),
) )
@@ -760,8 +760,8 @@ impl TabGroupRenderer for TabGroupSkin {
.h(height) .h(height)
.rounded(cx.theme().radius_lg) .rounded(cx.theme().radius_lg)
.border_1() .border_1()
.border_color(cx.theme().element_disabled) .border_color(cx.theme().accent.strong.opacity(0.3))
.bg(cx.theme().drop_target_background) .bg(cx.theme().accent.selection)
.into_any_element(), .into_any_element(),
) )
} }
+1 -1
View File
@@ -106,7 +106,7 @@ impl RenderOnce for GroupBox {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let (bg, has_paddings) = match self.variant { let (bg, has_paddings) = match self.variant {
GroupBoxVariant::Normal => (None, false), GroupBoxVariant::Normal => (None, false),
GroupBoxVariant::Fill => (Some(cx.theme().surface_background), true), GroupBoxVariant::Fill => (Some(cx.theme().shell), true),
}; };
v_flex() v_flex()
+1 -1
View File
@@ -291,7 +291,7 @@ impl From<Icon> for AnyElement {
impl Render for Icon { impl Render for Icon {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let text_color = self.text_color.unwrap_or_else(|| cx.theme().icon); let text_color = self.text_color.unwrap_or_else(|| cx.theme().text_muted);
let text_size = window.text_style().font_size.to_pixels(window.rem_size()); let text_size = window.text_style().font_size.to_pixels(window.rem_size());
let has_base_size = self.style.size.width.is_some() || self.style.size.height.is_some(); let has_base_size = self.style.size.width.is_some() || self.style.size.height.is_some();
+1 -1
View File
@@ -11,5 +11,5 @@ pub(crate) fn clear_button(cx: &App) -> Button {
.ghost() .ghost()
.xsmall() .xsmall()
.tab_stop(false) .tab_stop(false)
.text_color(cx.theme().icon_muted) .text_color(cx.theme().text_faint)
} }
+4 -4
View File
@@ -16,9 +16,9 @@ use crate::{IconName, Selectable, Sizable, Size, StyleSized, StyledExt, h_flex,
/// The background of an input frame, which reads muted while the input is disabled. /// The background of an input frame, which reads muted while the input is disabled.
fn input_background(disabled: bool, cx: &App) -> Hsla { fn input_background(disabled: bool, cx: &App) -> Hsla {
if disabled { if disabled {
cx.theme().surface_background cx.theme().shell
} else { } else {
cx.theme().elevated_surface_background cx.theme().input
} }
} }
@@ -32,9 +32,9 @@ fn input_editor_style(cx: &App) -> InputEditorStyle {
InputEditorStyle { InputEditorStyle {
foreground: theme.text, foreground: theme.text,
muted_foreground: theme.text_muted, muted_foreground: theme.text_muted,
background: theme.elevated_surface_background, background: theme.input,
border: theme.border, border: theme.border,
selection: theme.selection, selection: theme.accent.selection,
caret: theme.cursor, caret: theme.cursor,
..InputEditorStyle::default() ..InputEditorStyle::default()
} }
+1 -1
View File
@@ -199,7 +199,7 @@ impl RenderOnce for Kbd {
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.py_0p5() .py_0p5()
.px_1() .px_1()
.min_w_5() .min_w_5()
+2 -2
View File
@@ -69,8 +69,8 @@ impl RenderedText {
TextViewStyle::default() TextViewStyle::default()
.with_foreground(theme.text) .with_foreground(theme.text)
.with_muted_foreground(theme.text_muted) .with_muted_foreground(theme.text_muted)
.with_link(theme.text_accent) .with_link(theme.accent.strong)
.with_code_background(theme.elevated_surface_background) .with_code_background(theme.raised)
.with_border(theme.border) .with_border(theme.border)
.with_code_block(code_block) .with_code_block(code_block)
.with_dark(theme.is_dark()), .with_dark(theme.is_dark()),
+4 -4
View File
@@ -104,12 +104,12 @@ impl RenderOnce for MenuItemElement {
}) })
.when(!self.disabled, |this| { .when(!self.disabled, |this| {
this.group_hover(self.group_name, |this| { this.group_hover(self.group_name, |this| {
this.bg(cx.theme().secondary_background) this.bg(cx.theme().accent.wash)
.text_color(cx.theme().secondary_foreground) .text_color(cx.theme().accent.text_on_wash)
}) })
.when(self.selected, |this| { .when(self.selected, |this| {
this.bg(cx.theme().secondary_background) this.bg(cx.theme().accent.selection)
.text_color(cx.theme().secondary_foreground) .text_color(cx.theme().accent.text_on_wash)
}) })
.when_some(self.on_click, |this, on_click| { .when_some(self.on_click, |this, on_click| {
this.on_mouse_down(MouseButton::Left, move |_, _, cx| { this.on_mouse_down(MouseButton::Left, move |_, _, cx| {
+3 -3
View File
@@ -192,7 +192,7 @@ impl RenderOnce for MessageRow {
h_flex() h_flex()
.gap_2() .gap_2()
.text_sm() .text_sm()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.when_some(author, |this, author| { .when_some(author, |this, author| {
this.child(div().font_semibold().child(author)) this.child(div().font_semibold().child(author))
}) })
@@ -212,7 +212,7 @@ impl RenderOnce for MessageRow {
.when_some(on_double_click, |this, listener| { .when_some(on_double_click, |this, listener| {
this.on_double_click(listener) this.on_double_click(listener)
}) })
.hover(|this| this.bg(cx.theme().surface_background)) .hover(|this| this.bg(cx.theme().shell))
.into_any_element() .into_any_element()
} }
} }
@@ -315,7 +315,7 @@ impl RenderOnce for WelcomeMessage {
this.child( this.child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(message), .child(message),
) )
}), }),
+2 -4
View File
@@ -126,10 +126,8 @@ impl RenderOnce for Nav {
) )
.when(clickable, |this| { .when(clickable, |this| {
this.cursor_pointer() this.cursor_pointer()
.hover(|this| this.bg(cx.theme().ghost_element_hover)) .hover(|this| this.bg(cx.theme().hover))
.when(self.selected, |this| { .when(self.selected, |this| this.bg(cx.theme().active))
this.bg(cx.theme().ghost_element_active)
})
}) })
.when_some(self.on_click, |this, handler| { .when_some(self.on_click, |this, handler| {
this.on_click(move |event, window, cx| handler(event, window, cx)) this.on_click(move |event, window, cx| handler(event, window, cx))
+2 -4
View File
@@ -119,10 +119,8 @@ impl RenderOnce for NavItem {
}) })
.when(clickable, |this| { .when(clickable, |this| {
this.cursor_pointer() this.cursor_pointer()
.hover(|this| this.bg(cx.theme().ghost_element_hover)) .hover(|this| this.bg(cx.theme().hover))
.when(self.selected, |this| { .when(self.selected, |this| this.bg(cx.theme().active))
this.bg(cx.theme().ghost_element_active)
})
}) })
.when_some(self.on_click, |this, handler| { .when_some(self.on_click, |this, handler| {
this.on_click(move |event, window, cx| handler(event, window, cx)) this.on_click(move |event, window, cx| handler(event, window, cx))
+7 -7
View File
@@ -43,16 +43,16 @@ impl NotificationKind {
match self { match self {
Self::Info => Icon::new(IconName::Info) Self::Info => Icon::new(IconName::Info)
.with_size(Size::Medium) .with_size(Size::Medium)
.text_color(cx.theme().icon), .text_color(cx.theme().text_muted),
Self::Success => Icon::new(IconName::CheckCircle) Self::Success => Icon::new(IconName::CheckCircle)
.with_size(Size::Medium) .with_size(Size::Medium)
.text_color(cx.theme().icon_accent), .text_color(cx.theme().accent.strong),
Self::Warning => Icon::new(IconName::Warning) Self::Warning => Icon::new(IconName::Warning)
.with_size(Size::Medium) .with_size(Size::Medium)
.text_color(cx.theme().text_warning), .text_color(cx.theme().warning),
Self::Error => Icon::new(IconName::CloseCircle) Self::Error => Icon::new(IconName::CloseCircle)
.with_size(Size::Medium) .with_size(Size::Medium)
.text_color(cx.theme().danger_foreground), .text_color(cx.theme().danger),
} }
} }
} }
@@ -300,12 +300,12 @@ impl Render for Notification {
}; };
let background = match self.kind { let background = match self.kind {
Some(NotificationKind::Error) => cx.theme().danger_background, Some(NotificationKind::Error) => cx.theme().danger_muted,
_ => cx.theme().surface_background, _ => cx.theme().shell,
}; };
let text_color = match self.kind { let text_color = match self.kind {
Some(NotificationKind::Error) => cx.theme().danger_foreground, Some(NotificationKind::Error) => cx.theme().danger,
_ => cx.theme().text, _ => cx.theme().text,
}; };
+2 -2
View File
@@ -12,7 +12,7 @@ pub(crate) fn resize_handle_appearance() -> ResizeHandleRenderer {
Rc::new( Rc::new(
|context: &ResizeHandleContext, _: &mut Window, cx: &mut App| { |context: &ResizeHandleContext, _: &mut Window, cx: &mut App| {
let color = if context.is_active() { let color = if context.is_active() {
cx.theme().border_selected cx.theme().accent.strong
} else { } else {
cx.theme().border cx.theme().border
}; };
@@ -20,7 +20,7 @@ pub(crate) fn resize_handle_appearance() -> ResizeHandleRenderer {
Some( Some(
div() div()
.bg(cx.theme().border_variant) .bg(cx.theme().border)
.group_hover("handle", move |this| this.bg(color)) .group_hover("handle", move |this| this.bg(color))
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE)) .when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)) .when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE))
+1 -1
View File
@@ -381,7 +381,7 @@ impl Render for Root {
}) })
.size_full() .size_full()
.font_family(font_family) .font_family(font_family)
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.text_color(cx.theme().text) .text_color(cx.theme().text)
.child(TextSelectionLayer) .child(TextSelectionLayer)
.child(self.view.clone()), .child(self.view.clone()),
+6 -6
View File
@@ -57,14 +57,14 @@ fn scrollbar<H: ScrollbarHandle + Clone>(
styles styles
.track(|track| { .track(|track| {
track track
.bg(theme.scrollbar_track_background) .bg(gpui::transparent_black())
.border_color(theme.scrollbar_thumb_border) .border_color(gpui::transparent_black())
.width(px(10.)) .width(px(10.))
}) })
.track_hover(|track| track.bg(theme.scrollbar_thumb_background).width(px(10.))) .track_hover(|track| track.bg(theme.border.opacity(0.7)).width(px(10.)))
.thumb(|thumb| { .thumb(|thumb| {
thumb thumb
.bg(theme.scrollbar_thumb_background) .bg(theme.border.opacity(0.7))
.width(thumb_width) .width(thumb_width)
.inset(px(1.)) .inset(px(1.))
.radius(thumb_radius) .radius(thumb_radius)
@@ -72,14 +72,14 @@ fn scrollbar<H: ScrollbarHandle + Clone>(
}) })
.thumb_hover(|thumb| { .thumb_hover(|thumb| {
thumb thumb
.bg(theme.scrollbar_thumb_hover_background) .bg(theme.border_strong)
.width(px(8.)) .width(px(8.))
.inset(px(1.)) .inset(px(1.))
.radius(px(4.)) .radius(px(4.))
}) })
.thumb_active(|thumb| { .thumb_active(|thumb| {
thumb thumb
.bg(theme.scrollbar_thumb_hover_background) .bg(theme.border_strong)
.width(px(8.)) .width(px(8.))
.inset(px(1.)) .inset(px(1.))
.radius(px(4.)) .radius(px(4.))
+1 -1
View File
@@ -15,7 +15,7 @@ pub fn v_flex() -> Div {
/// Returns a `Div` as divider. /// Returns a `Div` as divider.
pub fn divider(cx: &App) -> Div { pub fn divider(cx: &App) -> Div {
div().my_1().w_full().h_px().bg(cx.theme().border_variant) div().my_1().w_full().h_px().bg(cx.theme().border)
} }
macro_rules! font_weight { macro_rules! font_weight {
+3 -3
View File
@@ -85,8 +85,8 @@ impl RenderOnce for Switch {
let on_click = self.on_click.clone(); let on_click = self.on_click.clone();
let (bg, toggle_bg) = match checked { let (bg, toggle_bg) = match checked {
true => (cx.theme().element_background, white()), true => (cx.theme().accent.primary, white()),
false => (cx.theme().elevated_surface_background, white()), false => (cx.theme().raised, white()),
}; };
let (bg, toggle_bg) = match self.disabled { let (bg, toggle_bg) = match self.disabled {
@@ -155,7 +155,7 @@ impl RenderOnce for Switch {
.flex() .flex()
.items_center() .items_center()
.border(inset) .border(inset)
.border_color(cx.theme().border_transparent) .border_color(gpui::transparent_black())
.bg(bg) .bg(bg)
.when(!self.disabled, |this| this.cursor_pointer()) .when(!self.disabled, |this| this.cursor_pointer())
.child( .child(
+6 -11
View File
@@ -193,9 +193,9 @@ impl RenderOnce for Tab {
let foreground = if disabled { let foreground = if disabled {
cx.theme().text_muted cx.theme().text_muted
} else if selected { } else if selected {
cx.theme().tab_active_foreground cx.theme().text
} else { } else {
cx.theme().tab_foreground cx.theme().text_muted
}; };
let content = h_flex() let content = h_flex()
@@ -227,11 +227,11 @@ impl RenderOnce for Tab {
.h_6() .h_6()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.when(selected && !disabled, |this| { .when(selected && !disabled, |this| {
this.bg(cx.theme().tab_active_background) this.bg(cx.theme().background)
.when(cx.theme().shadow, |this| this.shadow_sm()) .when(cx.theme().shadow, |this| this.shadow_sm())
}) })
.when(!selected && !disabled, |this| { .when(!selected && !disabled, |this| {
this.hover(|this| this.bg(cx.theme().tab_hover_background)) this.hover(|this| this.bg(cx.theme().hover))
}) })
}) })
.when(!segmented, |this| { .when(!segmented, |this| {
@@ -243,14 +243,9 @@ impl RenderOnce for Tab {
.text_sm() .text_sm()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.overflow_hidden() .overflow_hidden()
.when(selected && !disabled, |this| { .when(selected && !disabled, |this| this.bg(cx.theme().shell))
this.bg(cx.theme().tab_background)
})
.when(!selected && !disabled, |this| { .when(!selected && !disabled, |this| {
this.hover(|this| { this.hover(|this| this.bg(cx.theme().hover).text_color(cx.theme().text))
this.bg(cx.theme().tab_hover_background)
.text_color(cx.theme().tab_active_foreground)
})
}) })
}) })
.when_some(prefix, |this, prefix| this.child(prefix)) .when_some(prefix, |this, prefix| this.child(prefix))
+1 -3
View File
@@ -124,9 +124,7 @@ impl RenderOnce for TabBar {
.min_w_0() .min_w_0()
.refine_style(&self.style) .refine_style(&self.style)
.when(segmented, |this| { .when(segmented, |this| {
this.bg(cx.theme().tab_background) this.bg(cx.theme().shell).p_0p5().rounded(cx.theme().radius)
.p_0p5()
.rounded(cx.theme().radius)
}) })
.when_some(self.prefix, |this, prefix| this.child(prefix)) .when_some(self.prefix, |this, prefix| this.child(prefix))
.child( .child(
+5 -5
View File
@@ -79,7 +79,7 @@ impl ControlIcon {
#[inline] #[inline]
fn hover_fg(&self, cx: &App) -> Hsla { fn hover_fg(&self, cx: &App) -> Hsla {
if self.is_close() { if self.is_close() {
cx.theme().danger_foreground cx.theme().danger
} else { } else {
cx.theme().text cx.theme().text
} }
@@ -88,18 +88,18 @@ impl ControlIcon {
#[inline] #[inline]
fn hover_bg(&self, cx: &App) -> Hsla { fn hover_bg(&self, cx: &App) -> Hsla {
if self.is_close() { if self.is_close() {
cx.theme().danger_background cx.theme().danger_muted
} else { } else {
cx.theme().ghost_element_hover cx.theme().hover
} }
} }
#[inline] #[inline]
fn active_bg(&self, cx: &mut App) -> Hsla { fn active_bg(&self, cx: &mut App) -> Hsla {
if self.is_close() { if self.is_close() {
cx.theme().danger_active cx.theme().danger.opacity(0.25)
} else { } else {
cx.theme().ghost_element_active cx.theme().active
} }
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ impl Render for Tooltip {
.p_1p5() .p_1p5()
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.when(cx.theme().shadow, |this| this.shadow_sm()) .when(cx.theme().shadow, |this| this.shadow_sm())
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.text_xs() .text_xs()
+3 -3
View File
@@ -255,7 +255,7 @@ impl Render for ImportIdentity {
this.child( this.child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().text_warning) .text_color(cx.theme().warning)
.child(div().child(BUNKER_WARN)), .child(div().child(BUNKER_WARN)),
) )
}) })
@@ -263,7 +263,7 @@ impl Render for ImportIdentity {
this.child( this.child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().text_warning) .text_color(cx.theme().warning)
.child(div().child(KEY_WARN)), .child(div().child(KEY_WARN)),
) )
}), }),
@@ -297,7 +297,7 @@ impl Render for ImportIdentity {
div() div()
.text_xs() .text_xs()
.text_center() .text_center()
.text_color(cx.theme().text_danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
}) })
+4 -4
View File
@@ -380,9 +380,9 @@ impl NewChat {
.gap_1() .gap_1()
.p_0p5() .p_0p5()
.rounded_full() .rounded_full()
.bg(cx.theme().ghost_element_background_alt) .bg(cx.theme().raised)
.text_size(px(10.)) .text_size(px(10.))
.hover(|this| this.bg(cx.theme().ghost_element_hover)) .hover(|this| this.bg(cx.theme().hover))
.child( .child(
Avatar::from_source(profile.avatar()) Avatar::from_source(profile.avatar())
.seed(profile.avatar_seed()) .seed(profile.avatar_seed())
@@ -430,14 +430,14 @@ impl Render for NewChat {
this.child( this.child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().text_danger) .text_color(cx.theme().danger)
.child(error), .child(error),
) )
} else { } else {
this.child( this.child(
div() div()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(notice), .child(notice),
) )
} }
+2 -2
View File
@@ -366,7 +366,7 @@ impl Render for ProfileDialog {
.w_full() .w_full()
.items_center() .items_center()
.justify_center() .justify_center()
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.child(Avatar::new(avatar).seed(profile.avatar_seed()).large()) .child(Avatar::new(avatar).seed(profile.avatar_seed()).large())
.when(self.editing, |this| { .when(self.editing, |this| {
this.child( this.child(
@@ -392,7 +392,7 @@ impl Render for ProfileDialog {
.justify_center() .justify_center()
.gap_3() .gap_3()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.text_sm() .text_sm()
.child(shorten_pkey) .child(shorten_pkey)
.child( .child(
+3 -3
View File
@@ -154,7 +154,7 @@ impl RelayManager {
div() div()
.italic() .italic()
.text_xs() .text_xs()
.text_color(cx.theme().text_danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
}) })
@@ -225,7 +225,7 @@ impl RelayManager {
div() div()
.italic() .italic()
.text_xs() .text_xs()
.text_color(cx.theme().text_danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
}) })
@@ -647,7 +647,7 @@ fn relay_row(
.px_2() .px_2()
.justify_between() .justify_between()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
+1 -1
View File
@@ -122,7 +122,7 @@ impl Render for RestoreEncryption {
div() div()
.text_xs() .text_xs()
.text_center() .text_center()
.text_color(cx.theme().text_danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
}) })
+4 -4
View File
@@ -294,7 +294,7 @@ impl Screening {
.gap_1p5() .gap_1p5()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.text_sm() .text_sm()
.hover(|this| this.bg(cx.theme().elevated_surface_background)) .hover(|this| this.bg(cx.theme().raised))
.child(Avatar::new(profile.avatar()).small()) .child(Avatar::new(profile.avatar()).small())
.child(profile.name()), .child(profile.name()),
); );
@@ -353,7 +353,7 @@ impl Render for Screening {
.h_7() .h_7()
.justify_center() .justify_center()
.rounded_full() .rounded_full()
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.text_sm() .text_sm()
.truncate() .truncate()
.text_ellipsis() .text_ellipsis()
@@ -533,9 +533,9 @@ fn status_badge(status: Option<bool>, cx: &App) -> Div {
if let Some(status) = status { if let Some(status) = status {
this.child(Icon::new(IconName::CheckCircle).small().text_color({ this.child(Icon::new(IconName::CheckCircle).small().text_color({
if status { if status {
cx.theme().icon_accent cx.theme().accent.strong
} else { } else {
cx.theme().icon_muted cx.theme().text_faint
} }
})) }))
} else { } else {
+139 -10
View File
@@ -4,7 +4,7 @@ use gpui::{
Window, div, px, Window, div, px,
}; };
use settings::AppSettings; use settings::AppSettings;
use theme::{ActiveTheme, Theme, ThemeMode}; use theme::{AccentPreset, AccentSelection, ActiveTheme, Appearance, SurfacePreference, Theme};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::group_box::{GroupBox, GroupBoxVariants}; use ui::group_box::{GroupBox, GroupBoxVariants};
use ui::input::{Input, InputState}; use ui::input::{Input, InputState};
@@ -47,10 +47,10 @@ impl Preferences {
} }
} }
/// Set the theme mode (light or dark) /// Set the appearance (light or dark)
fn set_theme_mode(mode: ThemeMode, window: &mut Window, cx: &mut App) { fn set_appearance(appearance: Appearance, window: &mut Window, cx: &mut App) {
AppSettings::update_theme_mode(mode, cx); AppSettings::update_appearance(appearance, cx);
Theme::change(mode, Some(window), cx); Theme::change(appearance, Some(window), cx);
} }
} }
@@ -59,13 +59,17 @@ impl Render for Preferences {
const SCREENING: &str = "Show an screening dialog to verify the unknown sender."; const SCREENING: &str = "Show an screening dialog to verify the unknown sender.";
const AVATAR: &str = "Hide all avatar pictures to improve performance."; const AVATAR: &str = "Hide all avatar pictures to improve performance.";
const MODE: &str = "Use the selected light or dark theme, or to follow the OS."; const MODE: &str = "Use the selected light or dark theme, or to follow the OS.";
const ACCENT: &str = "Override the accent color of the selected theme.";
const SURFACE: &str = "Preferred surface treatment. Frosted rendering is not applied yet.";
const NIP4E: &str = "Use a dedicated key to encrypt and decrypt messages."; const NIP4E: &str = "Use a dedicated key to encrypt and decrypt messages.";
const RESET: &str = "Reset the theme to the default one."; const RESET: &str = "Reset the theme to the default one.";
let screening = AppSettings::get_screening(cx); let screening = AppSettings::get_screening(cx);
let hide_avatar = AppSettings::get_hide_avatar(cx); let hide_avatar = AppSettings::get_hide_avatar(cx);
let nip4e = AppSettings::get_nip4e(cx); let nip4e = AppSettings::get_nip4e(cx);
let theme_mode = AppSettings::get_theme_mode(cx); let appearance = AppSettings::get_appearance(cx);
let accent_selection = AppSettings::get_accent_selection(cx);
let surface_preference = AppSettings::get_surface_preference(cx);
v_flex() v_flex()
.gap_4() .gap_4()
@@ -114,23 +118,148 @@ impl Render for Preferences {
) )
.child( .child(
Button::new("theme-mode") Button::new("theme-mode")
.label(theme_mode.name()) .label(if appearance.is_dark() {
"Dark"
} else {
"Light"
})
.ghost_alt() .ghost_alt()
.small() .small()
.dropdown_menu(|this, _window, _cx| { .dropdown_menu(|this, _window, _cx| {
this.item(PopupMenuItem::new("Light").on_click( this.item(PopupMenuItem::new("Light").on_click(
|_, window, cx| { |_, window, cx| {
Self::set_theme_mode(ThemeMode::Light, window, cx); Self::set_appearance(Appearance::Light, window, cx);
}, },
)) ))
.item( .item(
PopupMenuItem::new("Dark").on_click(|_, window, cx| { PopupMenuItem::new("Dark").on_click(|_, window, cx| {
Self::set_theme_mode(ThemeMode::Dark, window, cx); Self::set_appearance(Appearance::Dark, window, cx);
}), }),
) )
}), }),
), ),
) )
.child(
h_flex()
.gap_3()
.justify_between()
.child(
v_flex()
.child(div().text_sm().child(SharedString::from("Accent")))
.child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.child(SharedString::from(ACCENT)),
),
)
.child(
Button::new("accent")
.label(accent_selection.label())
.ghost_alt()
.small()
.dropdown_menu(|menu, _window, _cx| {
let menu =
menu.item(
PopupMenuItem::new(
AccentSelection::ThemeDefault.label(),
)
.on_click(|_, window, cx| {
AppSettings::global(cx).update(cx, |s, cx| {
s.apply_accent(
AccentSelection::ThemeDefault,
window,
cx,
);
});
}),
);
let mut menu = menu;
for preset in AccentPreset::ALL {
menu = menu.item(
PopupMenuItem::new(preset.label()).on_click({
move |_, window, cx| {
AppSettings::global(cx).update(
cx,
|s, cx| {
s.apply_accent(
AccentSelection::Preset(preset),
window,
cx,
);
},
);
}
}),
);
}
menu
}),
),
)
.child(
h_flex()
.gap_3()
.justify_between()
.child(
v_flex()
.child(div().text_sm().child(SharedString::from("Surface")))
.child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.child(SharedString::from(SURFACE)),
),
)
.child(
Button::new("surface")
.label(match surface_preference {
SurfacePreference::ThemeDefault => "Theme default",
SurfacePreference::Frosted => "Frosted",
SurfacePreference::Opaque => "Opaque",
})
.ghost_alt()
.small()
.dropdown_menu(|menu, _window, _cx| {
menu.item(PopupMenuItem::new("Theme default").on_click(
|_, window, cx| {
AppSettings::global(cx).update(cx, |s, cx| {
s.apply_surface(
SurfacePreference::ThemeDefault,
window,
cx,
);
});
},
))
.item(PopupMenuItem::new("Frosted").on_click(
|_, window, cx| {
AppSettings::global(cx).update(cx, |s, cx| {
s.apply_surface(
SurfacePreference::Frosted,
window,
cx,
);
});
},
))
.item(
PopupMenuItem::new("Opaque").on_click(
|_, window, cx| {
AppSettings::global(cx).update(cx, |s, cx| {
s.apply_surface(
SurfacePreference::Opaque,
window,
cx,
);
});
},
),
)
}),
),
)
.child( .child(
h_flex() h_flex()
.gap_3() .gap_3()
@@ -199,7 +328,7 @@ impl Render for Preferences {
div() div()
.text_size(px(10.)) .text_size(px(10.))
.italic() .italic()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(SharedString::from("Only support blossom service")), .child(SharedString::from("Only support blossom service")),
), ),
), ),
+101 -73
View File
@@ -15,7 +15,7 @@ use nostr_sdk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{NostrRegistry, StateEvent}; use state::{NostrRegistry, StateEvent};
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry}; use theme::{ActiveTheme, SIDEBAR_WIDTH, ThemeRegistry, ThemeVariant};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle}; use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
use ui::notification::{Notification, NotificationKind}; use ui::notification::{Notification, NotificationKind};
@@ -82,13 +82,6 @@ impl Workspace {
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
subscriptions.push(
// Observe system appearance and update theme
cx.observe_window_appearance(window, |_this, window, cx| {
Theme::sync_system_appearance(Some(window), cx);
}),
);
subscriptions.push( subscriptions.push(
// Subscribe to the nostr events // Subscribe to the nostr events
cx.subscribe_in(&nostr, window, move |_this, _state, event, window, cx| { cx.subscribe_in(&nostr, window, move |_this, _state, event, window, cx| {
@@ -439,7 +432,7 @@ impl Workspace {
.child( .child(
div() div()
.italic() .italic()
.text_color(cx.theme().text_danger) .text_color(cx.theme().danger)
.child(SharedString::from(ENC_WARN)), .child(SharedString::from(ENC_WARN)),
), ),
) )
@@ -464,78 +457,113 @@ impl Workspace {
} }
fn theme_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn theme_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
window.open_dialog(cx, move |this, _window, cx| { let appearance = cx.theme().appearance;
let registry = ThemeRegistry::global(cx); let registry = ThemeRegistry::builtin();
let themes = registry.read(cx).themes(); let groups: Vec<(String, Vec<ThemeVariant>)> = registry
.families
.iter()
.filter_map(|family| {
let variants: Vec<ThemeVariant> = family
.variants
.iter()
.filter(|variant| variant.appearance == appearance)
.cloned()
.collect();
if variants.is_empty() {
None
} else {
Some((family.name.clone(), variants))
}
})
.collect();
window.open_dialog(cx, move |this, _window, cx| {
this.width(px(520.)) this.width(px(520.))
.show_close(true) .show_close(true)
.title("Select theme") .title("Select theme")
.child(v_flex().gap_2().w_full().children({ .child(v_flex().gap_2().w_full().children({
let mut items = vec![]; let mut items: Vec<gpui::AnyElement> = vec![];
for (ix, (path, theme)) in themes.iter().enumerate() { for (family_name, variants) in groups.iter() {
items.push( items.push(
h_flex() div()
.id(ix) .pt_2()
.group("") .text_xs()
.px_2() .italic()
.h_8() .text_color(cx.theme().text_faint)
.w_full() .child(SharedString::from(family_name.clone()))
.justify_between() .into_any_element(),
.rounded(cx.theme().radius)
.bg(cx.theme().ghost_element_background)
.hover(|this| this.bg(cx.theme().ghost_element_hover))
.child(
h_flex()
.gap_1p5()
.flex_1()
.text_sm()
.child(theme.name.clone())
.child(
div()
.text_xs()
.italic()
.text_color(cx.theme().text_muted)
.child(theme.author.clone()),
),
)
.child(
h_flex()
.gap_1()
.invisible()
.group_hover("", |this| this.visible())
.child(
Button::new(format!("url-{ix}"))
.icon(IconName::Link)
.ghost()
.small()
.on_click({
let theme = theme.clone();
move |_ev, _window, cx| {
cx.open_url(&theme.url);
}
}),
)
.child(
Button::new(format!("set-{ix}"))
.icon(IconName::Check)
.primary()
.small()
.on_click({
let path = path.clone();
move |_ev, window, cx| {
let settings = AppSettings::global(cx);
let path = path.clone();
settings.update(cx, |this, cx| {
this.set_theme(path, window, cx);
})
}
}),
),
),
); );
for (ix, variant) in variants.iter().enumerate() {
items.push(
h_flex()
.id(ix)
.group("")
.px_2()
.h_8()
.w_full()
.justify_between()
.rounded(cx.theme().radius)
.bg(gpui::transparent_black())
.hover(|this| this.bg(cx.theme().hover))
.child(
h_flex()
.gap_1p5()
.flex_1()
.text_sm()
.child(SharedString::from(variant.name.clone()))
.child(
div()
.text_xs()
.italic()
.text_color(cx.theme().text_faint)
.child(SharedString::from(
variant.source.license.clone(),
)),
),
)
.child(
h_flex()
.gap_1()
.invisible()
.group_hover("", |this| this.visible())
.child(
Button::new(format!("url-{ix}"))
.icon(IconName::Link)
.ghost()
.small()
.on_click({
let url = variant.source.url.clone();
move |_ev, _window, cx| {
cx.open_url(&url);
}
}),
)
.child(
Button::new(format!("set-{ix}"))
.icon(IconName::Check)
.primary()
.small()
.on_click({
let id = variant.id.clone();
move |_ev, window, cx| {
let settings = AppSettings::global(cx);
settings.update(cx, |this, cx| {
this.set_theme_variant(
id.clone(),
window,
cx,
);
})
}
}),
),
)
.into_any_element(),
);
}
} }
items items
+7 -7
View File
@@ -295,7 +295,7 @@ impl BrowsePanel {
.max_w_64() .max_w_64()
.px_1() .px_1()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.child( .child(
Input::new(&self.input) Input::new(&self.input)
.appearance(false) .appearance(false)
@@ -378,11 +378,11 @@ impl BrowsePanel {
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.cursor_pointer() .cursor_pointer()
.border_1() .border_1()
.border_color(cx.theme().border_transparent) .border_color(gpui::transparent_black())
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.hover(|this| { .hover(|this| {
this.bg(cx.theme().elevated_surface_background) this.bg(cx.theme().raised)
.border_color(cx.theme().border_focused) .border_color(cx.theme().accent.primary)
}) })
.child( .child(
v_flex() v_flex()
@@ -414,7 +414,7 @@ impl BrowsePanel {
div() div()
.truncate() .truncate()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(relay), .child(relay),
), ),
), ),
@@ -467,7 +467,7 @@ impl Panel for BrowsePanel {
.child( .child(
Icon::new(IconName::Compass) Icon::new(IconName::Compass)
.small() .small()
.text_color(cx.theme().icon_muted), .text_color(cx.theme().text_faint),
) )
.child(TITLE) .child(TITLE)
.into_any_element() .into_any_element()
+2 -2
View File
@@ -234,7 +234,7 @@ impl ContactListPanel {
.px_2() .px_2()
.justify_between() .justify_between()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.hover(|this| this.bg(cx.theme().ghost_element_hover)) .hover(|this| this.bg(cx.theme().hover))
.child( .child(
h_flex() h_flex()
.gap_2() .gap_2()
@@ -343,7 +343,7 @@ impl Render for ContactListPanel {
div() div()
.italic() .italic()
.text_xs() .text_xs()
.text_color(cx.theme().text_danger) .text_color(cx.theme().danger)
.child(error.clone()), .child(error.clone()),
) )
}), }),
+1 -1
View File
@@ -91,7 +91,7 @@ impl Render for GreeterPanel {
svg() svg()
.path("brand/coop.svg") .path("brand/coop.svg")
.size_12() .size_12()
.text_color(cx.theme().icon_muted), .text_color(cx.theme().text_faint),
) )
.child( .child(
v_flex() v_flex()
+1 -1
View File
@@ -35,7 +35,7 @@ impl Panel for InboxPanel {
.child( .child(
Icon::new(IconName::Inbox) Icon::new(IconName::Inbox)
.small() .small()
.text_color(cx.theme().icon_muted), .text_color(cx.theme().text_faint),
) )
.child(self.name.clone()) .child(self.name.clone())
.into_any_element() .into_any_element()
+1 -1
View File
@@ -35,7 +35,7 @@ impl Panel for RequestsPanel {
.child( .child(
Icon::new(IconName::Invite) Icon::new(IconName::Invite)
.small() .small()
.text_color(cx.theme().icon_muted), .text_color(cx.theme().text_faint),
) )
.child(self.name.clone()) .child(self.name.clone())
.into_any_element() .into_any_element()
+2 -2
View File
@@ -437,7 +437,7 @@ impl Panel for SearchPanel {
.child( .child(
Icon::new(IconName::Search) Icon::new(IconName::Search)
.small() .small()
.text_color(cx.theme().icon_muted), .text_color(cx.theme().text_faint),
) )
.child(self.name.clone()) .child(self.name.clone())
.into_any_element() .into_any_element()
@@ -500,7 +500,7 @@ impl Render for SearchPanel {
.gap_1() .gap_1()
.flex_1() .flex_1()
.border_b_1() .border_b_1()
.border_color(cx.theme().border_variant) .border_color(cx.theme().border)
.child( .child(
h_flex() h_flex()
.gap_0p5() .gap_0p5()
+3 -3
View File
@@ -230,10 +230,10 @@ impl Sidebar {
Nav::new(label) Nav::new(label)
.label(label) .label(label)
.suffix(Icon::new(icon).small().text_color(cx.theme().icon_muted)) .suffix(Icon::new(icon).small().text_color(cx.theme().text_faint))
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.on_click(cx.listener(move |this, _event, _window, cx| { .on_click(cx.listener(move |this, _event, _window, cx| {
match section { match section {
GroupSection::Admins => this.admins_open = !this.admins_open, GroupSection::Admins => this.admins_open = !this.admins_open,
@@ -277,7 +277,7 @@ impl Sidebar {
div() div()
.truncate() .truncate()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(SharedString::from(roles.join(", "))), .child(SharedString::from(roles.join(", "))),
) )
}) })
+4 -4
View File
@@ -89,7 +89,7 @@ impl Sidebar {
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.text_center() .text_center()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.child(SharedString::from( .child(SharedString::from(
request_count.to_string(), request_count.to_string(),
)), )),
@@ -102,7 +102,7 @@ impl Sidebar {
) )
}) })
.when(requests_open, |this| { .when(requests_open, |this| {
this.bg(cx.theme().ghost_element_active) this.bg(cx.theme().active)
}) })
.on_click(cx.listener(|this, _event, _window, cx| { .on_click(cx.listener(|this, _event, _window, cx| {
this.toggle_requests(cx); this.toggle_requests(cx);
@@ -111,7 +111,7 @@ impl Sidebar {
.child( .child(
NavItem::new("contacts", "Contacts", Icon::new(IconName::Book).small()) NavItem::new("contacts", "Contacts", Icon::new(IconName::Book).small())
.when(contacts_open, |this| { .when(contacts_open, |this| {
this.bg(cx.theme().ghost_element_active) this.bg(cx.theme().active)
}) })
.on_click(cx.listener(|this, _event, window, cx| { .on_click(cx.listener(|this, _event, window, cx| {
this.toggle_contacts(window, cx); this.toggle_contacts(window, cx);
@@ -124,7 +124,7 @@ impl Sidebar {
.px_4() .px_4()
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(list_title), .child(list_title),
) )
}) })
+2 -2
View File
@@ -277,7 +277,7 @@ impl Render for Sidebar {
.size_full() .size_full()
.relative() .relative()
.gap_2() .gap_2()
.bg(cx.theme().surface_background) .bg(cx.theme().shell)
.when_some(current_user.as_ref(), |this, current_user| { .when_some(current_user.as_ref(), |this, current_user| {
this.child(title_bar_drag_handlers( this.child(title_bar_drag_handlers(
self.render_user(current_user, cx), self.render_user(current_user, cx),
@@ -336,7 +336,7 @@ impl Render for Sidebar {
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(Indicator::new().small().color(cx.theme().icon_accent)) .child(Indicator::new().small().color(cx.theme().accent.strong))
.child("Getting messages..."), .child("Getting messages..."),
), ),
) )
+7 -7
View File
@@ -89,10 +89,10 @@ impl Sidebar {
Nav::new(label) Nav::new(label)
.label(label) .label(label)
.suffix(Icon::new(icon).small().text_color(cx.theme().icon_muted)) .suffix(Icon::new(icon).small().text_color(cx.theme().text_faint))
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.on_click(cx.listener(move |this, _event, _window, cx| { .on_click(cx.listener(move |this, _event, _window, cx| {
match section { match section {
ListSection::Pins => this.pins_open = !this.pins_open, ListSection::Pins => this.pins_open = !this.pins_open,
@@ -114,7 +114,7 @@ impl Sidebar {
.label(label) .label(label)
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.into_any_element() .into_any_element()
} }
SidebarRow::Empty(text) => v_flex() SidebarRow::Empty(text) => v_flex()
@@ -132,7 +132,7 @@ impl Sidebar {
.border_color(cx.theme().border) .border_color(cx.theme().border)
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(*text), .child(*text),
) )
.into_any_element(), .into_any_element(),
@@ -162,7 +162,7 @@ impl Sidebar {
div() div()
.font_normal() .font_normal()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(created_at), .child(created_at),
) )
.on_click(move |_event, window, cx| { .on_click(move |_event, window, cx| {
@@ -279,7 +279,7 @@ impl Sidebar {
div() div()
.font_normal() .font_normal()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(suffix), .child(suffix),
) )
}) })
@@ -353,7 +353,7 @@ impl Sidebar {
div() div()
.font_normal() .font_normal()
.text_xs() .text_xs()
.text_color(cx.theme().text_placeholder) .text_color(cx.theme().text_faint)
.child(detail), .child(detail),
) )
.on_click(move |_event, window, cx| { .on_click(move |_event, window, cx| {
+1 -1
View File
@@ -202,7 +202,7 @@ impl Sidebar {
.gap_1() .gap_1()
.w_full() .w_full()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background) .bg(cx.theme().raised)
.child( .child(
Input::new(&self.find_input) Input::new(&self.find_input)
.appearance(false) .appearance(false)