This commit is contained in:
2026-10-28 09:34:22 +07:00
parent 823f4b245c
commit b4d3359d4f
29 changed files with 6785 additions and 4 deletions
+35
View File
@@ -0,0 +1,35 @@
name: CI
on:
push:
branches: [main, develop]
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
check:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Format
run: cargo fmt --check
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Test
run: cargo test --workspace
- name: Feature checks (core)
run: |
cargo check -p gpui-native-core --no-default-features
cargo check -p gpui-native-core --all-features
for f in button checkbox switch radio toggle link slider progress avatar input popover tooltip menu dialog sheet toast; do
cargo check -p gpui-native-core --no-default-features --features "$f"
done
- name: Feature checks (facade)
run: |
cargo check -p gpui-native-kit --no-default-features
cargo check -p gpui-native-kit --all-features
Generated
+5980
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -1,6 +1,27 @@
[workspace]
members = ["crates/theme", "crates/icons", "crates/core", "examples/gallery"]
[package] [package]
name = "gpui-native-kit" name = "gpui-native-kit"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
description = "GPUI components that feel native to the operating system"
[features]
default = ["core"]
core = ["dep:gpui-native-core"]
[dependencies] [dependencies]
gpui = { workspace = true }
gpui-native-core = { path = "crates/core", optional = true }
gpui-native-icons = { path = "crates/icons" }
gpui-native-theme = { path = "crates/theme" }
[workspace.dependencies]
gpui = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531" }
gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531", features = [
"font-kit",
] }
gpui-native-core = { path = "crates/core" }
gpui-native-icons = { path = "crates/icons" }
gpui-native-theme = { path = "crates/theme" }
+45
View File
@@ -0,0 +1,45 @@
[package]
name = "gpui-native-core"
version = "0.1.0"
edition = "2024"
description = "The GPUI components every application needs, one cargo feature each"
[dependencies]
gpui = { workspace = true }
[features]
default = ["full"]
full = [
"button",
"checkbox",
"switch",
"radio",
"toggle",
"link",
"slider",
"progress",
"avatar",
"input",
"popover",
"tooltip",
"menu",
"dialog",
"sheet",
"toast",
]
button = []
checkbox = []
switch = []
radio = []
toggle = []
link = []
slider = []
progress = []
avatar = []
input = []
popover = []
tooltip = []
menu = []
dialog = []
sheet = []
toast = []
+8
View File
@@ -0,0 +1,8 @@
//! The components every application needs, one cargo feature each.
//!
//! Components land one per feature: `button`, `checkbox`, `input`, …
//! `default-features = false` plus the features you want for a lean build;
//! `full` (the default) for everything. Features gate whole modules and are
//! additive only; each component adds its `#[cfg(feature = "…")] pub mod`
//! declaration together with its file. Shared helpers promoted per the
//! third-use rule live in the ungated `common` module once one exists.
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "gpui-native-icons"
version = "0.1.0"
edition = "2024"
description = "System icons for GPUI: semantic roles resolved per platform, with a bundled Lucide fallback"
[dependencies]
gpui = { workspace = true }
+9
View File
@@ -0,0 +1,9 @@
# Licenses
## Bundled icons (`assets/*.svg`)
Lucide, ISC License, © Lucide Contributors. Fetched from https://github.com/lucide-icons/lucide (main) on 2026-10-28. These are the offline fallback glyphs, see https://lucide.dev/license.
## Platform glyph sets (not bundled)
SF Symbols (macOS) and Segoe Fluent Icons (Windows) are resolved through the operating system at runtime and are never extracted, modified, or redistributed. Freedesktop icons are loaded from the user's installed icon themes.
+13
View File
@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20 6 9 17l-5-5" />
</svg>

After

Width:  |  Height:  |  Size: 239 B

+13
View File
@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m6 9 6 6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 236 B

+13
View File
@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m15 18-6-6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 238 B

+13
View File
@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m9 18 6-6-6-6" />
</svg>

After

Width:  |  Height:  |  Size: 237 B

+13
View File
@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m18 15-6-6-6 6" />
</svg>

After

Width:  |  Height:  |  Size: 238 B

+13
View File
@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
</svg>

After

Width:  |  Height:  |  Size: 342 B

+15
View File
@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>

After

Width:  |  Height:  |  Size: 294 B

+13
View File
@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14" />
</svg>

After

Width:  |  Height:  |  Size: 232 B

+14
View File
@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>

After

Width:  |  Height:  |  Size: 256 B

+14
View File
@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" />
</svg>

After

Width:  |  Height:  |  Size: 275 B

+15
View File
@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
<path d="M12 9v4" />
<path d="M12 17h.01" />
</svg>

After

Width:  |  Height:  |  Size: 345 B

+14
View File
@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>

After

Width:  |  Height:  |  Size: 260 B

+166
View File
@@ -0,0 +1,166 @@
//! System icons for GPUI: components and apps reference semantic roles,
//! and the crate resolves each role to a glyph for the current platform.
//!
//! Today every role resolves to the bundled Lucide fallback. Platform
//! resolvers land with their phases: SF Symbols (macOS), Segoe Fluent
//! Icons (Windows), freedesktop icon themes (Linux/Omarchy).
use gpui::prelude::FluentBuilder as _;
use gpui::{
AssetSource, Hsla, IntoElement, ParentElement as _, RenderOnce, SharedString, Styled as _,
Window, div, px, svg,
};
/// The semantic glyphs UI components need. Grows one role at a time as
/// components call for it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IconRole {
Check,
Close,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronUp,
Plus,
Minus,
Search,
Folder,
Warning,
Info,
}
impl IconRole {
pub fn all() -> &'static [Self] {
&[
Self::Check,
Self::Close,
Self::ChevronDown,
Self::ChevronLeft,
Self::ChevronRight,
Self::ChevronUp,
Self::Plus,
Self::Minus,
Self::Search,
Self::Folder,
Self::Warning,
Self::Info,
]
}
fn asset_path(self) -> &'static str {
match self {
Self::Check => "icons/check.svg",
Self::Close => "icons/x.svg",
Self::ChevronDown => "icons/chevron-down.svg",
Self::ChevronLeft => "icons/chevron-left.svg",
Self::ChevronRight => "icons/chevron-right.svg",
Self::ChevronUp => "icons/chevron-up.svg",
Self::Plus => "icons/plus.svg",
Self::Minus => "icons/minus.svg",
Self::Search => "icons/search.svg",
Self::Folder => "icons/folder.svg",
Self::Warning => "icons/triangle-alert.svg",
Self::Info => "icons/info.svg",
}
}
}
/// An icon element. Unpainted unless a color is set (gpui paints SVGs from
/// the element's text color only).
#[derive(IntoElement)]
pub struct Icon {
role: IconRole,
size: f32,
color: Option<Hsla>,
}
/// Creates an icon for the role at the default size of 16px.
pub fn icon(role: IconRole) -> Icon {
Icon {
role,
size: 16.0,
color: None,
}
}
impl Icon {
pub fn size(mut self, size: f32) -> Self {
self.size = size;
self
}
pub fn color(mut self, color: Hsla) -> Self {
self.color = Some(color);
self
}
}
impl RenderOnce for Icon {
fn render(self, _window: &mut Window, _cx: &mut gpui::App) -> impl IntoElement {
div().flex_none().size(px(self.size)).child(
svg()
.size_full()
.path(self.role.asset_path())
.when_some(self.color, |el, color| el.text_color(color)),
)
}
}
/// The asset source serving this crate's bundled glyphs. Register it on the
/// application: `application().with_assets(IconAssets)`.
pub struct IconAssets;
fn asset_bytes(path: &str) -> Option<&'static [u8]> {
match path {
"icons/check.svg" => Some(include_bytes!("../assets/check.svg")),
"icons/x.svg" => Some(include_bytes!("../assets/x.svg")),
"icons/chevron-down.svg" => Some(include_bytes!("../assets/chevron-down.svg")),
"icons/chevron-left.svg" => Some(include_bytes!("../assets/chevron-left.svg")),
"icons/chevron-right.svg" => Some(include_bytes!("../assets/chevron-right.svg")),
"icons/chevron-up.svg" => Some(include_bytes!("../assets/chevron-up.svg")),
"icons/plus.svg" => Some(include_bytes!("../assets/plus.svg")),
"icons/minus.svg" => Some(include_bytes!("../assets/minus.svg")),
"icons/search.svg" => Some(include_bytes!("../assets/search.svg")),
"icons/folder.svg" => Some(include_bytes!("../assets/folder.svg")),
"icons/triangle-alert.svg" => Some(include_bytes!("../assets/triangle-alert.svg")),
"icons/info.svg" => Some(include_bytes!("../assets/info.svg")),
_ => None,
}
}
impl AssetSource for IconAssets {
fn load(&self, path: &str) -> gpui::Result<Option<std::borrow::Cow<'static, [u8]>>> {
Ok(asset_bytes(path).map(std::borrow::Cow::Borrowed))
}
fn list(&self, path: &str) -> gpui::Result<Vec<SharedString>> {
if path.is_empty() {
Ok(IconRole::all()
.iter()
.map(|role| SharedString::from(role.asset_path()))
.collect())
} else {
Ok(vec![])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_role_resolves_to_a_bundled_asset() {
for role in IconRole::all() {
assert!(
asset_bytes(role.asset_path()).is_some(),
"missing bundled asset for {role:?}"
);
}
}
#[test]
fn unknown_assets_report_none() {
assert_eq!(asset_bytes("icons/nope.svg"), None);
}
}
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "gpui-native-theme"
version = "0.1.0"
edition = "2024"
description = "Native theme engine: reads the OS theme and resolves it into semantic tokens"
[dependencies]
gpui = { workspace = true }
+81
View File
@@ -0,0 +1,81 @@
//! Semantic color roles. This module is the only place in the workspace
//! allowed to hold color literals.
use gpui::{Hsla, rgb};
fn hex(hex: u32) -> Hsla {
rgb(hex).into()
}
/// The semantic color roles components paint from.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ColorTokens {
pub background: Hsla,
pub foreground: Hsla,
pub surface: Hsla,
pub surface_foreground: Hsla,
pub primary: Hsla,
pub primary_foreground: Hsla,
pub secondary: Hsla,
pub secondary_foreground: Hsla,
pub muted: Hsla,
pub muted_foreground: Hsla,
pub accent: Hsla,
pub accent_foreground: Hsla,
pub destructive: Hsla,
pub destructive_foreground: Hsla,
pub border: Hsla,
pub input: Hsla,
pub ring: Hsla,
pub selection: Hsla,
}
impl ColorTokens {
/// Neutral light palette; the fallback when no system theme applies.
pub fn light() -> Self {
Self {
background: hex(0xffffff),
foreground: hex(0x18181b),
surface: hex(0xfafafa),
surface_foreground: hex(0x18181b),
primary: hex(0x2563eb),
primary_foreground: hex(0xffffff),
secondary: hex(0xf4f4f5),
secondary_foreground: hex(0x18181b),
muted: hex(0xf4f4f5),
muted_foreground: hex(0x71717a),
accent: hex(0x2563eb),
accent_foreground: hex(0xffffff),
destructive: hex(0xdc2626),
destructive_foreground: hex(0xffffff),
border: hex(0xe4e4e7),
input: hex(0xe4e4e7),
ring: hex(0x93c5fd),
selection: hex(0xdbeafe),
}
}
/// Neutral dark palette; the fallback when no system theme applies.
pub fn dark() -> Self {
Self {
background: hex(0x09090b),
foreground: hex(0xfafafa),
surface: hex(0x18181b),
surface_foreground: hex(0xfafafa),
primary: hex(0x3b82f6),
primary_foreground: hex(0xffffff),
secondary: hex(0x27272a),
secondary_foreground: hex(0xfafafa),
muted: hex(0x27272a),
muted_foreground: hex(0xa1a1aa),
accent: hex(0x3b82f6),
accent_foreground: hex(0xffffff),
destructive: hex(0xef4444),
destructive_foreground: hex(0xffffff),
border: hex(0x27272a),
input: hex(0x27272a),
ring: hex(0x1d4ed8),
selection: hex(0x1e3a8a),
}
}
}
+68
View File
@@ -0,0 +1,68 @@
//! The theme engine: reads the running system theme and resolves it into the
//! semantic tokens every component paints from.
//!
//! Phase B replaces the bundled light/dark presets with platform adapters
//! (`NSColor` / `UISettings` / portal / Omarchy) behind a `ThemeSource` seam.
mod colors;
pub use colors::ColorTokens;
use gpui::{App, Global, WindowAppearance};
/// Light or dark, resolved from the system appearance.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ThemeVariant {
#[default]
Light,
Dark,
}
impl ThemeVariant {
fn from_appearance(appearance: WindowAppearance) -> Self {
match appearance {
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
}
}
}
/// The resolved theme, installed as the app-wide global.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Theme {
pub variant: ThemeVariant,
pub colors: ColorTokens,
}
impl Global for Theme {}
impl Theme {
/// Reads the theme global. Panics before `init`.
pub fn global(cx: &App) -> &Self {
cx.global::<Self>()
}
pub fn for_variant(variant: ThemeVariant) -> Self {
Self {
variant,
colors: match variant {
ThemeVariant::Light => ColorTokens::light(),
ThemeVariant::Dark => ColorTokens::dark(),
},
}
}
/// Installs the theme global for the current system appearance.
pub fn init(cx: &mut App) {
let variant = ThemeVariant::from_appearance(cx.window_appearance());
cx.set_global(Self::for_variant(variant));
}
/// Re-installs the global when a window's appearance differs from it.
pub fn sync_from_window(window: &gpui::Window, cx: &mut App) {
let variant = ThemeVariant::from_appearance(window.appearance());
if cx.global::<Self>().variant != variant {
cx.set_global(Self::for_variant(variant));
}
}
}
+1 -1
View File
@@ -254,6 +254,6 @@ Dropped from rev 3: D2, D5, D9 (port-era decisions). D10 (no icon bundling) is s
## 12. Immediate next steps ## 12. Immediate next steps
1. Phase A scaffold (§4.1, §7): workspace, toolchain pin, CI, theme stub, icons crate with `IconRole` + Lucide fallback, core feature scaffolding, facade, gallery shell. 1. ~~Phase A scaffold~~ ✅ — workspace, toolchain pin, CI, theme seed (neutral light/dark presets + `Theme` global + appearance sync), icons crate (`IconRole` + Lucide fallback + `IconAssets`), core feature scaffolding, facade, gallery shell.
2. Phase B theme engine core, macOS adapter first. 2. Phase B theme engine core, macOS adapter first.
3. Then wave 1 controls. 3. Then wave 1 controls.
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "gallery"
version = "0.1.0"
edition = "2024"
description = "Live preview of every component against the resolved system theme"
[dependencies]
gpui = { workspace = true }
gpui_platform = { workspace = true }
gpui-native-icons = { workspace = true }
gpui-native-theme = { workspace = true }
[[bin]]
name = "gallery"
path = "src/main.rs"
+154
View File
@@ -0,0 +1,154 @@
use gpui::{
App, AppContext as _, Bounds, Context, IntoElement, ParentElement as _, Render, Styled as _,
Window, WindowBounds, WindowOptions, div, px, size,
};
use gpui_native_icons::{IconAssets, IconRole, icon};
use gpui_native_theme::Theme;
use gpui_platform::application;
struct Gallery;
impl Render for Gallery {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = Theme::global(cx);
let colors = theme.colors;
let variant = theme.variant;
let swatches: [(&str, gpui::Hsla); 18] = [
("background", colors.background),
("foreground", colors.foreground),
("surface", colors.surface),
("surface_foreground", colors.surface_foreground),
("primary", colors.primary),
("primary_foreground", colors.primary_foreground),
("secondary", colors.secondary),
("secondary_foreground", colors.secondary_foreground),
("muted", colors.muted),
("muted_foreground", colors.muted_foreground),
("accent", colors.accent),
("accent_foreground", colors.accent_foreground),
("destructive", colors.destructive),
("destructive_foreground", colors.destructive_foreground),
("border", colors.border),
("input", colors.input),
("ring", colors.ring),
("selection", colors.selection),
];
div()
.size_full()
.font_family(".SystemUIFont")
.bg(colors.background)
.text_color(colors.foreground)
.p_6()
.flex()
.flex_col()
.gap_5()
.child(
div()
.flex()
.flex_col()
.gap_1()
.child(
div()
.text_xl()
.font_weight(gpui::FontWeight::SEMIBOLD)
.child("gpui-native-kit"),
)
.child(
div()
.text_sm()
.text_color(colors.muted_foreground)
.child(format!(
"phase A scaffold — variant: {variant:?} (system-resolved)"
)),
),
)
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_sm()
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(colors.muted_foreground)
.child("color tokens"),
)
.child(div().flex().flex_wrap().gap_2().children(swatches.map(
|(name, color)| {
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.size_6()
.rounded_full()
.border_1()
.border_color(colors.border)
.bg(color),
)
.child(
div()
.text_xs()
.text_color(colors.muted_foreground)
.child(name),
)
},
))),
)
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_sm()
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(colors.muted_foreground)
.child("icons (Lucide fallback; platform resolvers land later)"),
)
.child(
div().flex().items_center().gap_4().children(
IconRole::all()
.iter()
.map(|role| icon(*role).size(20.0).color(colors.foreground)),
),
),
)
}
}
fn main() {
application().with_assets(IconAssets).run(|cx: &mut App| {
Theme::init(cx);
let bounds = Bounds::centered(None, size(px(720.0), px(480.0)), cx);
if let Err(err) = cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
titlebar: Some(gpui::TitlebarOptions {
title: Some("gpui-native-kit gallery".into()),
..Default::default()
}),
..Default::default()
},
|window, cx| {
Theme::sync_from_window(window, cx);
// Detached on purpose: the subscription lives as long as the window.
window
.observe_window_appearance(|window, cx| {
Theme::sync_from_window(window, cx);
cx.refresh_windows();
})
.detach();
cx.new(|_| Gallery)
},
) {
eprintln!("failed to open gallery window: {err}");
}
cx.activate(true);
});
}
+4
View File
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.98.1"
profile = "minimal"
components = ["rustfmt", "clippy"]
+19
View File
@@ -0,0 +1,19 @@
//! GPUI components that feel native to the operating system.
//!
//! A thin facade: depend on `gpui-native-core` (and later siblings) directly
//! for a lean build, or on this crate for everything behind features.
pub use gpui_native_icons as icons;
pub use gpui_native_theme as theme;
#[cfg(feature = "core")]
#[allow(unused_imports)] // empty until the first component lands
pub use gpui_native_core::*;
use gpui::App;
/// Initializes the framework: installs the theme global from the current
/// system appearance. Call once before opening windows.
pub fn init(cx: &mut App) {
theme::Theme::init(cx);
}
-3
View File
@@ -1,3 +0,0 @@
fn main() {
println!("Hello, world!");
}