extract workspace crates and switch to gpui-native-kit
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run
Rust / build (macos-latest, stable) (pull_request) Waiting to run
Rust / build (ubuntu-latest, stable) (pull_request) Waiting to run
Rust / build (windows-latest, stable) (pull_request) Waiting to run
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run
Rust / build (macos-latest, stable) (pull_request) Waiting to run
Rust / build (ubuntu-latest, stable) (pull_request) Waiting to run
Rust / build (windows-latest, stable) (pull_request) Waiting to run
This commit is contained in:
Generated
+1087
-3317
File diff suppressed because it is too large
Load Diff
+6
-11
@@ -2,6 +2,7 @@
|
||||
resolver = "2"
|
||||
members = ["crates/*", "desktop"]
|
||||
default-members = ["desktop"]
|
||||
exclude = ["crates/dock", "crates/signed_ui", "crates/workspace"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.0"
|
||||
@@ -9,20 +10,14 @@ edition = "2024"
|
||||
publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
gpui = { package = "gpui-pre", version = "0.3.8" }
|
||||
gpui_platform = { package = "gpui-pre-platform", version = "0.3.8", features = ["font-kit", "x11", "wayland"] }
|
||||
reqwest_client = { package = "gpui-pre-reqwest-client", version = "0.3.8" }
|
||||
gpui = { git = "https://github.com/zed-industries/zed", rev = "f8cfc7ed9e57503def9479138894f3139f5400cc" }
|
||||
gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "f8cfc7ed9e57503def9479138894f3139f5400cc", features = ["font-kit"] }
|
||||
reqwest_client = { git = "https://github.com/zed-industries/zed", rev = "f8cfc7ed9e57503def9479138894f3139f5400cc" }
|
||||
|
||||
# GPUI Kit; exact pins keep the graph on the gpui-pre 0.3.8 closure that
|
||||
# native-theme-gpui 0.6.1 is verified against (0.7.3 and 0.8 pin 0.3.9).
|
||||
gpui-component = { version = "=0.7.1", features = ["tree-sitter-languages"] }
|
||||
gpui-base = "=0.7.1"
|
||||
gpui-native-kit = { git = "https://git.reya.info/reya/gpui-native-kit", rev = "4469a4c280194db033dd0abf9c4d6b8f4daa1858", features = ["layout", "editor"] }
|
||||
|
||||
native-theme = { version = "0.6.1", features = ["watch"] }
|
||||
native-theme-gpui = { version = "0.6.1", default-features = false, features = ["system-fonts"] }
|
||||
|
||||
dock = { path = "crates/dock" }
|
||||
settings = { path = "crates/settings" }
|
||||
signed_app = { path = "crates/signed_app" }
|
||||
|
||||
nostr = { git = "https://github.com/rust-nostr/nostr", features = ["nip59", "nip49", "nip44", "os-rng"] }
|
||||
nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" }
|
||||
|
||||
@@ -6,8 +6,7 @@ publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
gpui-component.workspace = true
|
||||
anyhow.workspace = true
|
||||
gpui-native-kit.workspace = true
|
||||
rust-embed.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,433 +0,0 @@
|
||||
{
|
||||
"$schema": "https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json",
|
||||
"name": "Signed",
|
||||
"author": "Signed",
|
||||
"themes": [
|
||||
{
|
||||
"is_default": true,
|
||||
"name": "Signed Light",
|
||||
"mode": "light",
|
||||
"colors": {
|
||||
"accent.background": "#F0F0F0",
|
||||
"accent.foreground": "#202020",
|
||||
"accordion.background": "#FFFFFF",
|
||||
"background": "#FFFFFF",
|
||||
"border": "#E4E4E7",
|
||||
"caret": "#202020",
|
||||
"chart.1": "#93C5FD",
|
||||
"chart.2": "#3B82F6",
|
||||
"chart.3": "#2563EB",
|
||||
"chart.4": "#1D4ED8",
|
||||
"chart.5": "#1E40AF",
|
||||
"chart_bullish": "green-600",
|
||||
"chart_bearish": "red-600",
|
||||
"danger.background": "red-500",
|
||||
"danger.foreground": "neutral-50",
|
||||
"description_list.label.foreground": "#202020",
|
||||
"drag.border": "#65A30D",
|
||||
"drop_target.background": "#C6FF4D40",
|
||||
"foreground": "#18181B",
|
||||
"group_box.background": "#F0F0F0",
|
||||
"group_box.foreground": "#202020",
|
||||
"info.background": "cyan-500",
|
||||
"info.foreground": "neutral-50",
|
||||
"input.border": "#E4E4E7",
|
||||
"link": "#3F6212",
|
||||
"link.active": "#3F6212",
|
||||
"link.hover": "#65A30D",
|
||||
"list.background": "#FFFFFF",
|
||||
"list.active.background": "#C6FF4D33",
|
||||
"list.active.border": "#65A30D",
|
||||
"list.even.background": "#F9F9F9",
|
||||
"list.head.background": "#F9F9F9",
|
||||
"list.hover.background": "#F0F0F0",
|
||||
"muted.background": "#F9F9F9",
|
||||
"muted.foreground": "#646464",
|
||||
"overlay": "#0000000D",
|
||||
"popover.background": "#FFFFFF",
|
||||
"popover.foreground": "#18181B",
|
||||
"primary.background": "#C6FF4D",
|
||||
"primary.active.background": "#65A30D",
|
||||
"primary.active.foreground": "#F7FEE7",
|
||||
"primary.foreground": "#1A2E05",
|
||||
"primary.hover.background": "#B7F03E",
|
||||
"progress.bar.background": "#18181B",
|
||||
"ring": "#BBBBBB",
|
||||
"scrollbar.background": "#F9F9F900",
|
||||
"scrollbar.thumb.background": "#BBBBBBE6",
|
||||
"scrollbar.thumb.hover.background": "#BBBBBB",
|
||||
"secondary.background": "#F0F0F0",
|
||||
"secondary.active.background": "#E0E0E0",
|
||||
"secondary.foreground": "#202020",
|
||||
"secondary.hover.background": "#E8E8E8",
|
||||
"selection.background": "#55A0FC",
|
||||
"sidebar.background": "#F9F9F9",
|
||||
"sidebar.accent.background": "#F0F0F0",
|
||||
"sidebar.accent.foreground": "#202020",
|
||||
"sidebar.border": "#E8E8E8",
|
||||
"sidebar.foreground": "#202020",
|
||||
"sidebar.primary.background": "#C6FF4D",
|
||||
"sidebar.primary.foreground": "#1A2E05",
|
||||
"skeleton.background": "#F0F0F0",
|
||||
"slider.background": "#18181B",
|
||||
"slider.thumb.background": "#FFFFFF",
|
||||
"status_bar.background": "#F9F9F9",
|
||||
"status_bar.border": "#E8E8E8",
|
||||
"success.background": "#2FBF71",
|
||||
"success.foreground": "#052E16",
|
||||
"switch.background": "#CECECE",
|
||||
"switch.thumb.background": "#FFFFFF",
|
||||
"tab.background": "#E8E8E8",
|
||||
"tab.active.background": "#F5F5F5",
|
||||
"tab.active.foreground": "#18181B",
|
||||
"tab.foreground": "#646464",
|
||||
"tab_bar.background": "#F0F0F0",
|
||||
"tab_bar.segmented.background": "#F0F0F0",
|
||||
"table.background": "#FFFFFF",
|
||||
"table.active.background": "#C6FF4D33",
|
||||
"table.active.border": "#65A30D",
|
||||
"table.even.background": "#F9F9F9",
|
||||
"table.head.background": "#F9F9F9",
|
||||
"table.head.foreground": "#838383",
|
||||
"table.hover.background": "#F0F0F0",
|
||||
"table.row.border": "#E8E8E8B3",
|
||||
"tiles.background": "#F9F9F9",
|
||||
"title_bar.background": "#F9F9F9",
|
||||
"title_bar.border": "#E8E8E8",
|
||||
"warning.background": "yellow-500",
|
||||
"warning.foreground": "neutral-50",
|
||||
"window.border": "#E8E8E8",
|
||||
"base.red": "red-600",
|
||||
"base.red.light": "red-400",
|
||||
"base.green": "#16A34A",
|
||||
"base.green.light": "green-400",
|
||||
"base.blue": "blue-600",
|
||||
"base.blue.light": "blue-400",
|
||||
"base.yellow": "yellow-600",
|
||||
"base.yellow.light": "yellow-400",
|
||||
"base.magenta": "purple-600",
|
||||
"base.magenta.light": "purple-400",
|
||||
"base.cyan": "cyan-600",
|
||||
"base.cyan.light": "cyan-400"
|
||||
},
|
||||
"highlight": {
|
||||
"editor.foreground": "#000000",
|
||||
"editor.background": "#ffffff",
|
||||
"editor.active_line.background": "#F5F5F5",
|
||||
"editor.line_number": "#929292",
|
||||
"editor.active_line_number": "#000000",
|
||||
"editor.invisible": "#73737366",
|
||||
"conflict": "#C5060B",
|
||||
"created": "#1642FF",
|
||||
"hidden": "#6D6D6D",
|
||||
"hint": "#9e5dff",
|
||||
"modified": "#9e7008",
|
||||
"predictive": "#A4ABB6",
|
||||
"warning": "#C99401",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#957931"
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#C5060B"
|
||||
},
|
||||
"comment": {
|
||||
"color": "#007fff"
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#007fff"
|
||||
},
|
||||
"constant": {
|
||||
"color": "#C5060B"
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"emphasis": {
|
||||
"font_style": "italic"
|
||||
},
|
||||
"emphasis.strong": {
|
||||
"font_weight": 700
|
||||
},
|
||||
"function": {
|
||||
"color": "#0000A2"
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#0000A2",
|
||||
"font_style": "normal"
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6A7293",
|
||||
"font_style": "italic"
|
||||
},
|
||||
"number": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"string": {
|
||||
"color": "#036A07"
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#036A07"
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#036A07"
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#d21f07"
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#d21f07"
|
||||
},
|
||||
"tag": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#6F42C1"
|
||||
},
|
||||
"text.code.span": {
|
||||
"color": "#6F42C1"
|
||||
},
|
||||
"title": {
|
||||
"color": "#0433FF"
|
||||
},
|
||||
"type": {
|
||||
"color": "#6f42c1"
|
||||
},
|
||||
"property": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"variable": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#C5060B"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"is_default": true,
|
||||
"name": "Signed Dark",
|
||||
"mode": "dark",
|
||||
"colors": {
|
||||
"accent.background": "#222222",
|
||||
"accent.foreground": "#EEEEEE",
|
||||
"accordion.background": "#0A0A0A",
|
||||
"background": "#0A0A0A",
|
||||
"border": "#27272A",
|
||||
"caret": "#EEEEEE",
|
||||
"chart.1": "#93C5FD",
|
||||
"chart.2": "#3B82F6",
|
||||
"chart.3": "#2563EB",
|
||||
"chart.4": "#1D4ED8",
|
||||
"chart.5": "#1E40AF",
|
||||
"chart_bullish": "green-600",
|
||||
"chart_bearish": "red-600",
|
||||
"danger.background": "red-400",
|
||||
"danger.foreground": "red-600",
|
||||
"description_list.label.background": "#191919",
|
||||
"description_list.label.foreground": "#EEEEEE",
|
||||
"drag.border": "#C6FF4D",
|
||||
"drop_target.background": "#C6FF4D2E",
|
||||
"foreground": "#FAFAFA",
|
||||
"group_box.background": "#191919",
|
||||
"group_box.foreground": "#EEEEEE",
|
||||
"info.background": "cyan-400",
|
||||
"info.foreground": "cyan-600",
|
||||
"input.border": "#2F2F2F",
|
||||
"link": "#A3E635",
|
||||
"link.active": "#A3E635",
|
||||
"link.hover": "#C6FF4D",
|
||||
"list.background": "#0A0A0A",
|
||||
"list.active.background": "#C6FF4D33",
|
||||
"list.active.border": "#C6FF4D",
|
||||
"list.even.background": "#191919",
|
||||
"list.head.background": "#191919",
|
||||
"list.hover.background": "#222222",
|
||||
"muted.background": "#191919",
|
||||
"muted.foreground": "#B4B4B4",
|
||||
"overlay": "#00000033",
|
||||
"popover.background": "#0A0A0A",
|
||||
"popover.foreground": "#FAFAFA",
|
||||
"primary.background": "#C6FF4D",
|
||||
"primary.active.background": "#65A30D",
|
||||
"primary.active.foreground": "#F7FEE7",
|
||||
"primary.foreground": "#1A2E05",
|
||||
"primary.hover.background": "#D6FF7A",
|
||||
"progress.bar.background": "#FAFAFA",
|
||||
"ring": "#606060",
|
||||
"scrollbar.background": "#19191900",
|
||||
"scrollbar.thumb.background": "#606060E6",
|
||||
"scrollbar.thumb.hover.background": "#606060",
|
||||
"secondary.background": "#222222",
|
||||
"secondary.active.background": "#313131",
|
||||
"secondary.foreground": "#EEEEEE",
|
||||
"secondary.hover.background": "#2A2A2A",
|
||||
"selection.background": "#1D4ED8",
|
||||
"sidebar.background": "#111111",
|
||||
"sidebar.accent.background": "#222222",
|
||||
"sidebar.accent.foreground": "#EEEEEE",
|
||||
"sidebar.border": "#2A2A2A",
|
||||
"sidebar.foreground": "#EEEEEE",
|
||||
"sidebar.primary.background": "#C6FF4D",
|
||||
"sidebar.primary.foreground": "#1A2E05",
|
||||
"skeleton.background": "#222222",
|
||||
"slider.background": "#FAFAFA",
|
||||
"slider.thumb.background": "#0A0A0A",
|
||||
"status_bar.background": "#191919",
|
||||
"status_bar.border": "#2A2A2A",
|
||||
"success.background": "#2FBF71",
|
||||
"success.foreground": "#052E16",
|
||||
"switch.background": "#484848",
|
||||
"switch.thumb.background": "#0A0A0A",
|
||||
"tab.background": "#2E2E2E",
|
||||
"tab.active.background": "#2A2A2A",
|
||||
"tab.active.foreground": "#FAFAFA",
|
||||
"tab.foreground": "#B4B4B4",
|
||||
"tab_bar.background": "#191919",
|
||||
"tab_bar.segmented.background": "#191919",
|
||||
"table.background": "#0A0A0A",
|
||||
"table.active.background": "#C6FF4D33",
|
||||
"table.active.border": "#C6FF4D",
|
||||
"table.even.background": "#191919",
|
||||
"table.head.background": "#191919",
|
||||
"table.head.foreground": "#7B7B7B",
|
||||
"table.hover.background": "#222222",
|
||||
"table.row.border": "#2A2A2AB3",
|
||||
"tiles.background": "#191919",
|
||||
"title_bar.background": "#191919",
|
||||
"title_bar.border": "#2A2A2A",
|
||||
"warning.background": "yellow-400",
|
||||
"warning.foreground": "yellow-600",
|
||||
"window.border": "#2A2A2A",
|
||||
"base.red": "red-400",
|
||||
"base.red.light": "red-300",
|
||||
"base.green": "#22C55E",
|
||||
"base.green.light": "green-300",
|
||||
"base.blue": "blue-400",
|
||||
"base.blue.light": "blue-300",
|
||||
"base.yellow": "yellow-400",
|
||||
"base.yellow.light": "yellow-300",
|
||||
"base.magenta": "purple-400",
|
||||
"base.magenta.light": "purple-300",
|
||||
"base.cyan": "cyan-400",
|
||||
"base.cyan.light": "cyan-300"
|
||||
},
|
||||
"highlight": {
|
||||
"editor.foreground": "#CACCCA",
|
||||
"editor.background": "#0a0a0a",
|
||||
"editor.active_line.background": "#171717",
|
||||
"editor.line_number": "#8F8F8F",
|
||||
"editor.active_line_number": "#DDDDDD",
|
||||
"editor.invisible": "#73737366",
|
||||
"conflict": "#D2602D",
|
||||
"created": "#3f72e2",
|
||||
"created.background": "#0C4619",
|
||||
"deleted.background": "#46190C",
|
||||
"error.background": "#46190C",
|
||||
"error.border": "#E44A4F",
|
||||
"hidden": "#9E9E9E",
|
||||
"hint": "#b283f8",
|
||||
"hint.background": "#250c4b",
|
||||
"hint.border": "#3f0891",
|
||||
"info.background": "#0059D1",
|
||||
"info.border": "#0059D1",
|
||||
"modified": "#B0A878",
|
||||
"modified.background": "#3A310E",
|
||||
"predictive": "#5D5945",
|
||||
"success.background": "#0C4619",
|
||||
"warning.background": "#3A310E",
|
||||
"warning.border": "#7B6508",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#7FAEF9"
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#CC9E00"
|
||||
},
|
||||
"comment": {
|
||||
"color": "#9D9D9D"
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#9D9D9D"
|
||||
},
|
||||
"constant": {
|
||||
"color": "#CC9E00"
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#CBA6F7"
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#CACCCA"
|
||||
},
|
||||
"emphasis": {
|
||||
"font_style": "italic"
|
||||
},
|
||||
"emphasis.strong": {
|
||||
"font_weight": 700
|
||||
},
|
||||
"function": {
|
||||
"color": "#B3C5F3"
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#87B1F6"
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#419CFF",
|
||||
"font_style": "normal"
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#7faef9",
|
||||
"font_style": "italic"
|
||||
},
|
||||
"number": {
|
||||
"color": "#CC9E00"
|
||||
},
|
||||
"string": {
|
||||
"color": "#A3E09F"
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#68DC7C"
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#68DC7C"
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#A3E09F"
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#A3E09F"
|
||||
},
|
||||
"tag": {
|
||||
"color": "#419CFF"
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#A3E09F"
|
||||
},
|
||||
"text.code.span": {
|
||||
"color": "#A3E09F"
|
||||
},
|
||||
"title": {
|
||||
"color": "#CC9E00",
|
||||
"font_weight": 600
|
||||
},
|
||||
"type": {
|
||||
"color": "#CBA6F7"
|
||||
},
|
||||
"property": {
|
||||
"color": "#BCC4E0"
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#419CFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+11
-29
@@ -1,12 +1,10 @@
|
||||
use anyhow::Context;
|
||||
use gpui::{AssetSource, Result, SharedString};
|
||||
use gpui_component::IconNamed;
|
||||
use gpui_native_kit::icons::IconNamed;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "assets"]
|
||||
#[include = "icons/**/*.svg"]
|
||||
#[include = "themes/**/*.json"]
|
||||
#[include = "backgrounds/**/*.jpg"]
|
||||
#[include = "backgrounds/**/*.png"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
@@ -14,35 +12,19 @@ pub struct Assets;
|
||||
|
||||
impl AssetSource for Assets {
|
||||
fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
|
||||
Self::get(path)
|
||||
.map(|f| Some(f.data))
|
||||
.with_context(|| format!("loading asset at path {path:?}"))
|
||||
if let Some(file) = Self::get(path) {
|
||||
return Ok(Some(file.data));
|
||||
}
|
||||
gpui_native_kit::icons::IconAssets.load(path)
|
||||
}
|
||||
|
||||
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
|
||||
Ok(Self::iter()
|
||||
.filter_map(|p| {
|
||||
if p.starts_with(path) {
|
||||
Some(p.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Assets {
|
||||
pub fn themes(&self) -> Vec<(String, String)> {
|
||||
Self::iter()
|
||||
.filter(|path| path.starts_with("themes/"))
|
||||
.filter_map(|path| {
|
||||
let data = Self::get(path.as_ref())?;
|
||||
let name = path.strip_prefix("themes/").unwrap_or(path.as_ref());
|
||||
let content = std::str::from_utf8(data.data.as_ref()).ok()?.to_owned();
|
||||
Some((name.to_owned(), content))
|
||||
})
|
||||
.collect()
|
||||
let mut paths: Vec<SharedString> = Self::iter()
|
||||
.filter(|p| p.starts_with(path))
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
paths.extend(gpui_native_kit::icons::IconAssets.list(path)?);
|
||||
Ok(paths)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,3 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -28,58 +28,6 @@ pub enum EventFetchingStrategy {
|
||||
Uncensored,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ThemeSettings {
|
||||
/// Name of the light theme in the theme registry.
|
||||
pub light_theme: String,
|
||||
/// Name of the dark theme in the theme registry.
|
||||
pub dark_theme: String,
|
||||
/// The base font size in pixels.
|
||||
pub font_size: f32,
|
||||
/// The monospace font size in pixels.
|
||||
pub mono_font_size: f32,
|
||||
/// Corner radius for general elements in pixels.
|
||||
pub radius: f32,
|
||||
/// Corner radius for large elements, dialogs and notifications, in pixels.
|
||||
pub radius_lg: f32,
|
||||
/// Whether focused controls draw a ring outside their border.
|
||||
pub focus_ring: bool,
|
||||
/// Whether to apply a shadow to elements.
|
||||
pub shadow: bool,
|
||||
}
|
||||
|
||||
impl Default for ThemeSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
light_theme: "Signed Light".into(),
|
||||
dark_theme: "Signed Dark".into(),
|
||||
font_size: 16.0,
|
||||
mono_font_size: 13.0,
|
||||
radius: 4.0,
|
||||
radius_lg: 8.0,
|
||||
focus_ring: false,
|
||||
shadow: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tab bar chrome.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TabBarSettings {
|
||||
/// Hide the previous and next tab buttons.
|
||||
pub hide_navigation_buttons: bool,
|
||||
}
|
||||
|
||||
impl Default for TabBarSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hide_navigation_buttons: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct GraspServersSettings {
|
||||
@@ -151,8 +99,6 @@ pub struct CreateRepositorySettings {
|
||||
pub struct Settings {
|
||||
pub appearance: AppearanceMode,
|
||||
pub event_fetching: EventFetchingStrategy,
|
||||
pub theme: ThemeSettings,
|
||||
pub tab_bar: TabBarSettings,
|
||||
pub grasp_servers: GraspServersSettings,
|
||||
pub local_repos: LocalReposSettings,
|
||||
pub checkouts: CheckoutsSettings,
|
||||
@@ -168,10 +114,6 @@ mod tests {
|
||||
let settings = Settings {
|
||||
appearance: AppearanceMode::Dark,
|
||||
event_fetching: EventFetchingStrategy::Curated,
|
||||
theme: ThemeSettings {
|
||||
radius: 8.0,
|
||||
..Default::default()
|
||||
},
|
||||
create_repository: CreateRepositorySettings {
|
||||
default_folder: Some(PathBuf::from("/tmp/repos")),
|
||||
},
|
||||
@@ -185,18 +127,34 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn partial_json_merges_with_defaults() {
|
||||
let settings: Settings =
|
||||
serde_json::from_str(r#"{"appearance": "dark", "theme": {"radius": 4.0}}"#).unwrap();
|
||||
let settings: Settings = serde_json::from_str(
|
||||
r#"{"appearance": "dark", "grasp_servers": {"default_servers": []}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(settings.appearance, AppearanceMode::Dark);
|
||||
assert_eq!(settings.theme.radius, 4.0);
|
||||
assert_eq!(settings.grasp_servers.default_servers, Vec::<String>::new());
|
||||
// Unset fields fall back to their defaults, including the fetching strategy.
|
||||
assert_eq!(settings.event_fetching, EventFetchingStrategy::Uncensored);
|
||||
// The rest of the theme and the other groups keep their defaults.
|
||||
assert_eq!(settings.theme.light_theme, "Signed Light");
|
||||
assert_eq!(settings.grasp_servers, GraspServersSettings::default());
|
||||
assert_eq!(settings.local_repos, LocalReposSettings::default());
|
||||
assert_eq!(settings.create_repository.default_folder, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_theme_keys_are_ignored() {
|
||||
let settings: Settings = serde_json::from_str(
|
||||
r#"{"appearance": "light", "theme": {"radius": 8.0}, "tab_bar": {"hide_navigation_buttons": false}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(settings.appearance, AppearanceMode::Light);
|
||||
assert_eq!(
|
||||
settings,
|
||||
Settings {
|
||||
appearance: AppearanceMode::Light,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_fetching_uses_snake_case() {
|
||||
let json = serde_json::to_string(&EventFetchingStrategy::Curated).unwrap();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "signed_app"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
assets = { path = "../assets" }
|
||||
metadata = { path = "../metadata" }
|
||||
paths = { path = "../paths" }
|
||||
settings = { path = "../settings" }
|
||||
signed_core = { path = "../signed_core" }
|
||||
signed_git = { path = "../signed_git" }
|
||||
signed_nostr = { path = "../signed_nostr" }
|
||||
signed_state = { path = "../signed_state" }
|
||||
utils = { path = "../utils" }
|
||||
|
||||
gpui.workspace = true
|
||||
gpui-native-kit.workspace = true
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
@@ -0,0 +1,55 @@
|
||||
use gpui::{
|
||||
App, AppContext as _, Context, Entity, IntoElement, ParentElement as _, Render, Styled as _,
|
||||
Window, div,
|
||||
};
|
||||
use gpui_native_kit::empty_state::EmptyState;
|
||||
use gpui_native_kit::layout::dock::DockState;
|
||||
use gpui_native_kit::layout::shell::{LayoutState, Shell};
|
||||
use gpui_native_kit::theme::{ActiveTheme as _, ThemeVariant, follow_system, set_mode_override};
|
||||
use settings::{AppearanceMode, Settings};
|
||||
|
||||
use crate::dialogs::DialogHost;
|
||||
|
||||
pub struct AppRoot {
|
||||
layout: Entity<LayoutState>,
|
||||
dock: Entity<DockState>,
|
||||
dialogs: Entity<DialogHost>,
|
||||
}
|
||||
|
||||
impl AppRoot {
|
||||
pub fn new(_window: &mut Window, cx: &mut App) -> Self {
|
||||
let layout = LayoutState::new(cx);
|
||||
let dock = DockState::new(cx);
|
||||
let dialogs = cx.new(DialogHost::new);
|
||||
DialogHost::set_global(dialogs.clone(), cx);
|
||||
|
||||
Self {
|
||||
layout,
|
||||
dock,
|
||||
dialogs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AppRoot {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
div()
|
||||
.size_full()
|
||||
.font_family(".SystemUIFont")
|
||||
.text_color(theme.colors.foreground)
|
||||
.child(
|
||||
Shell::new("signed-shell", self.layout.clone())
|
||||
.center(EmptyState::new("Signed").hint("UI migration in progress")),
|
||||
)
|
||||
.child(self.dialogs.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_appearance(settings: &Settings, cx: &mut App) {
|
||||
match settings.appearance {
|
||||
AppearanceMode::System => follow_system(cx),
|
||||
AppearanceMode::Light => set_mode_override(Some(ThemeVariant::Light), cx),
|
||||
AppearanceMode::Dark => set_mode_override(Some(ThemeVariant::Dark), cx),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
AnyView, App, Context, Entity, Global, IntoElement, ParentElement as _, Render, Styled as _,
|
||||
Window, div,
|
||||
};
|
||||
|
||||
struct GlobalDialogHost(Entity<DialogHost>);
|
||||
|
||||
impl Global for GlobalDialogHost {}
|
||||
|
||||
/// Owns the modal currently mounted at the window root.
|
||||
pub struct DialogHost {
|
||||
open: Option<AnyView>,
|
||||
}
|
||||
|
||||
impl DialogHost {
|
||||
pub fn new(_cx: &mut Context<Self>) -> Self {
|
||||
Self { open: None }
|
||||
}
|
||||
|
||||
pub fn set_global(entity: Entity<Self>, cx: &mut App) {
|
||||
cx.set_global(GlobalDialogHost(entity));
|
||||
}
|
||||
|
||||
pub fn global(cx: &App) -> Entity<Self> {
|
||||
cx.global::<GlobalDialogHost>().0.clone()
|
||||
}
|
||||
|
||||
pub fn open(&mut self, view: AnyView, cx: &mut Context<Self>) {
|
||||
self.open = Some(view);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn close(&mut self, cx: &mut Context<Self>) {
|
||||
self.open = None;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DialogHost {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.when_some(self.open.clone(), |host, view| host.child(view))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
mod app;
|
||||
mod dialogs;
|
||||
mod panels;
|
||||
mod views;
|
||||
|
||||
pub use app::{AppRoot, apply_appearance};
|
||||
|
||||
use gpui::{App, AppContext as _, Entity, Window};
|
||||
|
||||
pub fn root(window: &mut Window, cx: &mut App) -> Entity<AppRoot> {
|
||||
cx.new(|cx| AppRoot::new(window, cx))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
+2
-6
@@ -31,16 +31,12 @@ assets = { path = "../crates/assets" }
|
||||
metadata = { path = "../crates/metadata" }
|
||||
paths = { path = "../crates/paths" }
|
||||
settings = { path = "../crates/settings" }
|
||||
signed_app = { path = "../crates/signed_app" }
|
||||
signed_state = { path = "../crates/signed_state" }
|
||||
workspace = { path = "../crates/workspace" }
|
||||
|
||||
gpui.workspace = true
|
||||
gpui_platform.workspace = true
|
||||
dock = { workspace = true }
|
||||
gpui-component.workspace = true
|
||||
native-theme.workspace = true
|
||||
native-theme-gpui.workspace = true
|
||||
flume.workspace = true
|
||||
gpui-native-kit.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
+36
-141
@@ -1,13 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assets::Assets;
|
||||
use dock::TAB_BAR_HEIGHT;
|
||||
use gpui::*;
|
||||
use gpui_component::{Theme, ThemeMode, ThemeRegistry, theme};
|
||||
use gpui_native_kit::theme::{ActiveTheme as _, Theme};
|
||||
use gpui_platform::application;
|
||||
use native_theme::watch::ThemeSubscription;
|
||||
use native_theme_gpui::{SystemTheme, apply_system_theme};
|
||||
use settings::{AppearanceMode, Settings, SettingsStore};
|
||||
use settings::SettingsStore;
|
||||
|
||||
actions!(signed, [Quit]);
|
||||
|
||||
@@ -18,168 +15,66 @@ fn main() {
|
||||
.with_assets(Assets)
|
||||
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()))
|
||||
.run(move |cx| {
|
||||
gpui_component::init(cx);
|
||||
theme::init(cx);
|
||||
gpui_native_kit::init(cx);
|
||||
gpui_native_kit::editor::code::bind_code_view_keys(cx);
|
||||
gpui_native_kit::editor::markdown::bind_text_view_keys(cx);
|
||||
|
||||
cx.bind_keys([KeyBinding::new("secondary-q", Quit, None)]);
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("cmd-q", Quit, None),
|
||||
KeyBinding::new("ctrl-q", Quit, None),
|
||||
]);
|
||||
cx.on_action(|_: &Quit, cx| cx.quit());
|
||||
|
||||
// The persisted settings must load before the theme is applied.
|
||||
let store = cx.new(|cx| SettingsStore::new(paths::settings_file(), cx));
|
||||
SettingsStore::set_global(store.clone(), cx);
|
||||
|
||||
let settings = store.read(cx).settings().clone();
|
||||
{
|
||||
let registry = ThemeRegistry::global_mut(cx);
|
||||
for (name, content) in Assets.themes() {
|
||||
if let Err(err) = registry.load_themes_from_str(&content) {
|
||||
tracing::error!("Failed to load theme {name}: {err}");
|
||||
}
|
||||
}
|
||||
signed_app::apply_appearance(&settings, cx);
|
||||
|
||||
if let Err(error) = std::fs::create_dir_all(paths::nostr_dir()) {
|
||||
tracing::warn!("failed to create nostr dir: {error}");
|
||||
}
|
||||
|
||||
match apply_native_theme(&settings, cx) {
|
||||
Ok(()) => start_theme_watcher(cx),
|
||||
Err(error) => {
|
||||
tracing::warn!("native theme unavailable ({error})");
|
||||
|
||||
let registry = ThemeRegistry::global_mut(cx);
|
||||
let light_theme = registry
|
||||
.themes()
|
||||
.get(settings.theme.light_theme.as_str())
|
||||
.cloned();
|
||||
|
||||
let dark_theme = registry
|
||||
.themes()
|
||||
.get(settings.theme.dark_theme.as_str())
|
||||
.cloned();
|
||||
|
||||
let theme = Theme::global_mut(cx);
|
||||
theme.radius = px(settings.theme.radius);
|
||||
theme.radius_lg = px(settings.theme.radius_lg);
|
||||
theme.focus_ring = settings.theme.focus_ring;
|
||||
theme.shadow = settings.theme.shadow;
|
||||
theme.font_size = px(settings.theme.font_size);
|
||||
theme.mono_font_size = px(settings.theme.mono_font_size);
|
||||
|
||||
if let Some(light) = light_theme {
|
||||
theme.light_theme = light;
|
||||
} else {
|
||||
tracing::warn!("{} theme is missing", settings.theme.light_theme);
|
||||
}
|
||||
|
||||
if let Some(dark) = dark_theme {
|
||||
theme.dark_theme = dark;
|
||||
} else {
|
||||
tracing::warn!("{} theme is missing", settings.theme.dark_theme);
|
||||
}
|
||||
|
||||
match settings.appearance {
|
||||
AppearanceMode::System => Theme::sync_system_appearance(None, cx),
|
||||
AppearanceMode::Light => Theme::change(ThemeMode::Light, None, cx),
|
||||
AppearanceMode::Dark => Theme::change(ThemeMode::Dark, None, cx),
|
||||
}
|
||||
}
|
||||
if let Err(error) = std::fs::create_dir_all(paths::repos_dir()) {
|
||||
tracing::warn!("failed to create repos dir: {error}");
|
||||
}
|
||||
|
||||
std::fs::create_dir_all(paths::nostr_dir()).ok();
|
||||
std::fs::create_dir_all(paths::repos_dir()).ok();
|
||||
|
||||
signed_state::init(
|
||||
paths::nostr_dir(),
|
||||
paths::repos_dir().clone(),
|
||||
settings.local_repos.scan_paths,
|
||||
settings.local_repos.scan_paths.clone(),
|
||||
cx,
|
||||
);
|
||||
metadata::init(cx);
|
||||
|
||||
cx.set_app_identity("info.reya.signed", "Signed");
|
||||
|
||||
let theme = cx.theme();
|
||||
let bounds = Bounds::centered(None, size(px(1120.0), px(750.0)), cx);
|
||||
let opts = WindowOptions {
|
||||
window_background: WindowBackgroundAppearance::Opaque,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
let options = WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
window_min_size: Some(size(px(960.0), px(640.0))),
|
||||
kind: WindowKind::Normal,
|
||||
app_id: Some("Signed".to_owned()),
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: Some(SharedString::new_static("Signed")),
|
||||
// Center the 14pt traffic-light buttons on the tab bar.
|
||||
traffic_light_position: Some(point(
|
||||
px(9.0),
|
||||
px(TAB_BAR_HEIGHT / px(2.) - 14. / 2.),
|
||||
)),
|
||||
title: Some("Signed".into()),
|
||||
appears_transparent: true,
|
||||
..Default::default()
|
||||
}),
|
||||
app_owns_titlebar_drag: true,
|
||||
window_background: theme.window_background,
|
||||
app_id: Some("Signed".to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
cx.open_window(opts, workspace::root).ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Err(error) = cx.open_window(options, |window, cx| {
|
||||
Theme::sync_from_window(window, cx);
|
||||
Theme::sync_window_rem_size(window, cx);
|
||||
window
|
||||
.observe_window_appearance(|window, cx| {
|
||||
Theme::sync_from_window(window, cx);
|
||||
Theme::sync_window_rem_size(window, cx);
|
||||
cx.refresh_windows();
|
||||
})
|
||||
.detach();
|
||||
signed_app::root(window, cx)
|
||||
}) {
|
||||
tracing::error!("failed to open window: {error}");
|
||||
}
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
/// Apply the OS theme to the app, scaling the font size to match the system font size.
|
||||
fn apply_native_theme(settings: &Settings, cx: &mut App) -> native_theme::Result<()> {
|
||||
let mut system = SystemTheme::from_system()?;
|
||||
let native = system.light.defaults.font.size;
|
||||
if native > 0.0 {
|
||||
let scaling = settings.theme.font_size / native;
|
||||
system.accessibility.text_scaling_factor *= scaling;
|
||||
}
|
||||
|
||||
apply_system_theme(&system, cx);
|
||||
|
||||
match settings.appearance {
|
||||
AppearanceMode::System => Theme::sync_system_appearance(None, cx),
|
||||
AppearanceMode::Light => Theme::change(ThemeMode::Light, None, cx),
|
||||
AppearanceMode::Dark => Theme::change(ThemeMode::Dark, None, cx),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Holds the watcher subscription so it lives as long as the app.
|
||||
struct ThemeWatcher {
|
||||
_subscription: ThemeSubscription,
|
||||
}
|
||||
|
||||
impl Global for ThemeWatcher {}
|
||||
|
||||
/// Re-apply the OS theme whenever it changes at runtime.
|
||||
fn start_theme_watcher(cx: &mut App) {
|
||||
let (sender, receiver) = flume::unbounded();
|
||||
match native_theme::watch::on_theme_change(move |event| {
|
||||
if let Err(error) = sender.send(event) {
|
||||
tracing::warn!("theme change dropped ({error})");
|
||||
}
|
||||
}) {
|
||||
Ok(subscription) => {
|
||||
cx.set_global(ThemeWatcher {
|
||||
_subscription: subscription,
|
||||
});
|
||||
cx.spawn(async move |cx| {
|
||||
while receiver.recv_async().await.is_ok() {
|
||||
tracing::info!("OS theme changed; re-applying");
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::global(cx);
|
||||
let settings = store.read(cx).settings().clone();
|
||||
if let Err(error) = apply_native_theme(&settings, cx) {
|
||||
tracing::warn!("failed to apply updated OS theme ({error})");
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!("OS theme watching unavailable ({error})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# gpui-native-kit gaps
|
||||
|
||||
Known Signed features the kit does not currently support. Recorded instead of
|
||||
modifying the kit or reimplementing a parallel mechanism.
|
||||
|
||||
1. **Custom themes.** `Signed Light`/`Signed Dark`, theme registry names, font
|
||||
sizes, radii, focus ring, and shadows are removed. The kit derives colors,
|
||||
radii, fonts, and chrome from the OS; `AppearanceMode` is preserved through
|
||||
`set_mode_override`/`follow_system`.
|
||||
2. **Tab bar navigation.** The previous/next tab buttons and
|
||||
`tab_bar.hide_navigation_buttons` are removed; the kit tab strip has none.
|
||||
3. **Traffic-light placement.** Custom macOS traffic-light position and the 44px
|
||||
tab bar are removed. The kit uses `theme.titlebar_height` and a fixed 78px
|
||||
traffic-light pad.
|
||||
4. **Virtual list.** The kit has no data-driven virtual list. Screens keep
|
||||
`gpui::uniform_list` for fixed-height lists and `ScrollArea` for the Inbox.
|
||||
5. **Editable code field.** The kit code view is read-only and supports four
|
||||
grammars. Signed only previews files read-only; other extensions render as
|
||||
plain text.
|
||||
6. **Hunk-only diffs.** Kit `DiffViewState` needs the full old and new texts;
|
||||
`CommitDiff` carries hunks only. The custom hunk rows are retained.
|
||||
7. **Tree data.** The kit tree is flat with caller-managed expansion, and has no
|
||||
file glyph; folders use the folder role.
|
||||
8. **Dynamic dock panels.** `DockState` has no unregister or `open_view` API.
|
||||
Per-repository/per-entity panels register a builder keyed by a unique id and
|
||||
stay in the builder map. No dock persistence, zoom, or bottom/right
|
||||
placement; splits only.
|
||||
9. **Dialogs.** Dialogs are elements mounted at the window root; there is no
|
||||
`window.open_dialog`, dialog stack, or non-dismissible option. A single-slot
|
||||
`DialogHost` is used, and the passphrase dialog ignores dismiss requests.
|
||||
10. **Notifications.** The kit toast shows one toast at a time; backend errors
|
||||
queue but only the current toast is visible.
|
||||
11. **Segmented badges.** `Segmented` has no badge slot; filter counts fold
|
||||
into labels.
|
||||
12. **Combobox trigger.** `Combobox` has no custom trigger renderer; branch/tag
|
||||
selectors lose the leading git icon and custom caret.
|
||||
13. **Icons.** The built-in role set is limited. App-specific glyphs remain in
|
||||
`assets::CustomIconName` through `IconNamed`; kit icons are unpainted
|
||||
unless given a color.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[toolchain]
|
||||
channel = "1.96"
|
||||
channel = "1.98.1"
|
||||
profile = "minimal"
|
||||
components = ["rustfmt", "clippy"]
|
||||
targets = [
|
||||
|
||||
Reference in New Issue
Block a user