gpui: Extract gpui_platform out of gpui (#49277)

#2874 on steroids

Before you mark this PR as ready for review, make sure that you have:
- [ ] Added a solid test coverage and/or screenshots from doing manual
testing
- [ ] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- N/A

---------

Co-authored-by: Eric Holk <eric@zed.dev>
This commit is contained in:
Piotr OsiewiczandEric Holk authored and GitHub committed 2026-02-19 18:57:49 +01:00
1 parent 9e19a291cd
commit dc4bba93ef
178 files changed
+3114 -3204

No files matched your search

Generated
+146 -20
View File
@@ -3438,6 +3438,7 @@ dependencies = [
"db", "db",
"fs", "fs",
"gpui", "gpui",
"gpui_platform",
"language", "language",
"log", "log",
"node_runtime", "node_runtime",
@@ -5242,6 +5243,7 @@ dependencies = [
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"gpui_platform",
"gpui_tokio", "gpui_tokio",
"http_client", "http_client",
"indoc", "indoc",
@@ -5779,6 +5781,7 @@ dependencies = [
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"gpui_platform",
"gpui_tokio", "gpui_tokio",
"handlebars 4.5.0", "handlebars 4.5.0",
"language", "language",
@@ -5814,7 +5817,7 @@ dependencies = [
name = "eval_utils" name = "eval_utils"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"gpui", "gpui_platform",
"serde", "serde",
"smol", "smol",
] ]
@@ -5931,7 +5934,7 @@ dependencies = [
"env_logger 0.11.8", "env_logger 0.11.8",
"extension", "extension",
"fs", "fs",
"gpui", "gpui_platform",
"language", "language",
"log", "log",
"reqwest_client", "reqwest_client",
@@ -6498,6 +6501,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"fs", "fs",
"gpui", "gpui",
"gpui_platform",
] ]
[[package]] [[package]]
@@ -7348,16 +7352,11 @@ name = "gpui"
version = "0.2.2" version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"as-raw-xcb-connection",
"ashpd",
"async-task", "async-task",
"backtrace", "backtrace",
"bindgen 0.71.1", "bindgen 0.71.1",
"bitflags 2.10.0", "bitflags 2.10.0",
"block", "block",
"bytemuck",
"calloop",
"calloop-wayland-source",
"cbindgen", "cbindgen",
"chrono", "chrono",
"circular-buffer", "circular-buffer",
@@ -7369,21 +7368,19 @@ dependencies = [
"core-graphics 0.24.0", "core-graphics 0.24.0",
"core-text", "core-text",
"core-video", "core-video",
"cosmic-text",
"ctor", "ctor",
"derive_more 0.99.20", "derive_more 0.99.20",
"embed-resource", "embed-resource",
"env_logger 0.11.8", "env_logger 0.11.8",
"etagere", "etagere",
"filedescriptor",
"foreign-types 0.5.0", "foreign-types 0.5.0",
"futures 0.3.31", "futures 0.3.31",
"gpui_macros", "gpui_macros",
"gpui_platform",
"http_client", "http_client",
"image", "image",
"inventory", "inventory",
"itertools 0.14.0", "itertools 0.14.0",
"libc",
"log", "log",
"lyon", "lyon",
"mach2 0.5.0", "mach2 0.5.0",
@@ -7394,8 +7391,6 @@ dependencies = [
"objc", "objc",
"objc2", "objc2",
"objc2-metal", "objc2-metal",
"oo7",
"open",
"parking", "parking",
"parking_lot", "parking_lot",
"pathfinder_geometry", "pathfinder_geometry",
@@ -7411,7 +7406,6 @@ dependencies = [
"scheduler", "scheduler",
"schemars", "schemars",
"seahash", "seahash",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"slotmap", "slotmap",
@@ -7421,7 +7415,6 @@ dependencies = [
"stacksafe", "stacksafe",
"strum 0.27.2", "strum 0.27.2",
"sum_tree", "sum_tree",
"swash",
"taffy", "taffy",
"thiserror 2.0.17", "thiserror 2.0.17",
"unicode-segmentation", "unicode-segmentation",
@@ -7430,17 +7423,50 @@ dependencies = [
"util_macros", "util_macros",
"uuid", "uuid",
"waker-fn", "waker-fn",
"windows 0.61.3",
"zed-font-kit",
"zed-scap",
]
[[package]]
name = "gpui_linux"
version = "0.1.0"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
"ashpd",
"bitflags 2.10.0",
"bytemuck",
"calloop",
"calloop-wayland-source",
"collections",
"cosmic-text",
"filedescriptor",
"futures 0.3.31",
"gpui",
"gpui_wgpu",
"http_client",
"itertools 0.14.0",
"libc",
"log",
"oo7",
"open",
"parking_lot",
"pathfinder_geometry",
"profiling",
"raw-window-handle",
"smallvec",
"smol",
"strum 0.27.2",
"swash",
"util",
"uuid",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-cursor", "wayland-cursor",
"wayland-protocols", "wayland-protocols",
"wayland-protocols-plasma", "wayland-protocols-plasma",
"wayland-protocols-wlr", "wayland-protocols-wlr",
"wgpu",
"windows 0.61.3",
"windows-core 0.61.2",
"windows-numerics 0.2.0",
"windows-registry 0.5.3",
"x11-clipboard", "x11-clipboard",
"x11rb", "x11rb",
"xkbcommon", "xkbcommon",
@@ -7449,6 +7475,47 @@ dependencies = [
"zed-xim", "zed-xim",
] ]
[[package]]
name = "gpui_macos"
version = "0.1.0"
dependencies = [
"anyhow",
"async-task",
"bindgen 0.71.1",
"block",
"cbindgen",
"cocoa 0.26.0",
"collections",
"core-foundation 0.10.0",
"core-foundation-sys",
"core-graphics 0.24.0",
"core-text",
"core-video",
"ctor",
"derive_more 0.99.20",
"etagere",
"foreign-types 0.5.0",
"futures 0.3.31",
"gpui",
"image",
"itertools 0.14.0",
"libc",
"log",
"mach2 0.5.0",
"media",
"metal 0.29.0",
"objc",
"parking_lot",
"pathfinder_geometry",
"raw-window-handle",
"semver",
"smallvec",
"strum 0.27.2",
"util",
"uuid",
"zed-font-kit",
]
[[package]] [[package]]
name = "gpui_macros" name = "gpui_macros"
version = "0.1.0" version = "0.1.0"
@@ -7460,6 +7527,16 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "gpui_platform"
version = "0.1.0"
dependencies = [
"gpui",
"gpui_linux",
"gpui_macos",
"gpui_windows",
]
[[package]] [[package]]
name = "gpui_tokio" name = "gpui_tokio"
version = "0.1.0" version = "0.1.0"
@@ -7470,6 +7547,49 @@ dependencies = [
"util", "util",
] ]
[[package]]
name = "gpui_wgpu"
version = "0.1.0"
dependencies = [
"anyhow",
"bytemuck",
"collections",
"etagere",
"gpui",
"log",
"parking_lot",
"profiling",
"raw-window-handle",
"smol",
"util",
"wgpu",
]
[[package]]
name = "gpui_windows"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"etagere",
"futures 0.3.31",
"gpui",
"image",
"itertools 0.14.0",
"log",
"parking_lot",
"rand 0.9.2",
"raw-window-handle",
"smallvec",
"util",
"uuid",
"windows 0.61.3",
"windows-core 0.61.2",
"windows-numerics 0.2.0",
"windows-registry 0.5.3",
"zed-scap",
]
[[package]] [[package]]
name = "grid" name = "grid"
version = "0.18.0" version = "0.18.0"
@@ -9534,6 +9654,7 @@ dependencies = [
"cpal", "cpal",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"gpui_platform",
"gpui_tokio", "gpui_tokio",
"http_client_tls", "http_client_tls",
"image", "image",
@@ -9786,6 +9907,7 @@ dependencies = [
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"gpui_platform",
"language", "language",
"languages", "languages",
"linkify", "linkify",
@@ -12736,6 +12858,7 @@ dependencies = [
"client", "client",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"gpui_platform",
"http_client", "http_client",
"language", "language",
"node_runtime", "node_runtime",
@@ -13702,6 +13825,7 @@ dependencies = [
"git2", "git2",
"git_hosting_providers", "git_hosting_providers",
"gpui", "gpui",
"gpui_platform",
"gpui_tokio", "gpui_tokio",
"http_client", "http_client",
"image", "image",
@@ -15911,6 +16035,7 @@ dependencies = [
"editor", "editor",
"fuzzy", "fuzzy",
"gpui", "gpui",
"gpui_platform",
"indoc", "indoc",
"language", "language",
"log", "log",
@@ -20724,7 +20849,7 @@ name = "worktree_benchmarks"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"fs", "fs",
"gpui", "gpui_platform",
"settings", "settings",
"worktree", "worktree",
] ]
@@ -21140,6 +21265,7 @@ dependencies = [
"git_ui", "git_ui",
"go_to_line", "go_to_line",
"gpui", "gpui",
"gpui_platform",
"gpui_tokio", "gpui_tokio",
"http_client", "http_client",
"image", "image",
+12
View File
@@ -83,7 +83,12 @@ members = [
"crates/go_to_line", "crates/go_to_line",
"crates/google_ai", "crates/google_ai",
"crates/gpui", "crates/gpui",
"crates/gpui_linux",
"crates/gpui_macos",
"crates/gpui_macros", "crates/gpui_macros",
"crates/gpui_platform",
"crates/gpui_wgpu",
"crates/gpui_windows",
"crates/gpui_tokio", "crates/gpui_tokio",
"crates/html_to_markdown", "crates/html_to_markdown",
"crates/http_client", "crates/http_client",
@@ -321,7 +326,12 @@ git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" } go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" } google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false } gpui = { path = "crates/gpui", default-features = false }
gpui_linux = { path = "crates/gpui_linux", default-features = false }
gpui_macos = { path = "crates/gpui_macos", default-features = false }
gpui_macros = { path = "crates/gpui_macros" } gpui_macros = { path = "crates/gpui_macros" }
gpui_platform = { path = "crates/gpui_platform", default-features = false }
gpui_wgpu = { path = "crates/gpui_wgpu" }
gpui_windows = { path = "crates/gpui_windows", default-features = false }
gpui_tokio = { path = "crates/gpui_tokio" } gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" } html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" } http_client = { path = "crates/http_client" }
@@ -819,6 +829,8 @@ codegen-units = 16
# (without this cargo will compile ~400 crates twice) # (without this cargo will compile ~400 crates twice)
[profile.dev.build-override] [profile.dev.build-override]
codegen-units = 16 codegen-units = 16
split-debuginfo = "unpacked"
debug = true
[profile.dev.package] [profile.dev.package]
# proc-macros start # proc-macros start
+3 -2
View File
@@ -13,7 +13,6 @@ path = "src/component_preview.rs"
[features] [features]
default = [] default = []
preview = []
test-support = ["db/test-support"] test-support = ["db/test-support"]
[dependencies] [dependencies]
@@ -39,7 +38,9 @@ ui_input.workspace = true
uuid.workspace = true uuid.workspace = true
workspace.workspace = true workspace.workspace = true
[dev-dependencies]
gpui_platform = { workspace = true, features = ["screen-capture"] }
[[example]] [[example]]
name = "component_preview" name = "component_preview"
path = "examples/component_preview.rs" path = "examples/component_preview.rs"
required-features = ["preview"]
@@ -1,18 +1,143 @@
//! Component Preview Example //! Component Preview Example
//! //!
//! Run with: `cargo run -p component_preview --example component_preview --features="preview"` //! Run with: `cargo run -p component_preview --example component_preview"`
//! use fs::RealFs;
//! To use this in other projects, add the following to your `Cargo.toml`: use gpui::{AppContext as _, Bounds, KeyBinding, WindowBounds, WindowOptions, actions, size};
//!
//! ```toml use client::{Client, UserStore};
//! [dependencies] use language::LanguageRegistry;
//! component_preview = { path = "../component_preview", features = ["preview"] } use node_runtime::NodeRuntime;
//! use project::Project;
//! [[example]] use reqwest_client::ReqwestClient;
//! name = "component_preview" use session::{AppSession, Session};
//! path = "examples/component_preview.rs" use std::sync::Arc;
//! ``` use ui::{App, px};
use workspace::{AppState, Workspace, WorkspaceStore};
use component_preview::{ComponentPreview, init};
actions!(zed, [Quit]);
fn quit(_: &Quit, cx: &mut App) {
cx.quit();
}
fn main() { fn main() {
component_preview::run_component_preview(); gpui_platform::application().run(|cx| {
component::init();
cx.on_action(quit);
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
let version = release_channel::AppVersion::load(env!("CARGO_PKG_VERSION"), None, None);
release_channel::init(version, cx);
let http_client =
ReqwestClient::user_agent("component_preview").expect("Failed to create HTTP client");
cx.set_http_client(Arc::new(http_client));
let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
<dyn fs::Fs>::set_global(fs.clone(), cx);
settings::init(cx);
theme::init(theme::LoadThemes::JustBase, cx);
let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
let client = Client::production(cx);
client::init(&client, cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
let session_id = uuid::Uuid::new_v4().to_string();
let session = cx.foreground_executor().block_on(Session::new(session_id));
let session = cx.new(|cx| AppSession::new(session, cx));
let node_runtime = NodeRuntime::unavailable();
let app_state = Arc::new(AppState {
languages,
client,
user_store,
workspace_store,
fs,
build_window_options: |_, _| Default::default(),
node_runtime,
session,
});
AppState::set_global(Arc::downgrade(&app_state), cx);
workspace::init(app_state.clone(), cx);
init(app_state.clone(), cx);
let size = size(px(1200.), px(800.));
let bounds = Bounds::centered(None, size, cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
{
move |window, cx| {
let app_state = app_state;
theme::setup_ui_font(window, cx);
let project = Project::local(
app_state.client.clone(),
app_state.node_runtime.clone(),
app_state.user_store.clone(),
app_state.languages.clone(),
app_state.fs.clone(),
None,
project::LocalProjectFlags {
init_worktree_trust: false,
..Default::default()
},
cx,
);
let workspace = cx.new(|cx| {
Workspace::new(
Default::default(),
project.clone(),
app_state.clone(),
window,
cx,
)
});
workspace.update(cx, |workspace, cx| {
let weak_workspace = cx.entity().downgrade();
let language_registry = app_state.languages.clone();
let user_store = app_state.user_store.clone();
let component_preview = cx.new(|cx| {
ComponentPreview::new(
weak_workspace,
project,
language_registry,
user_store,
None,
None,
window,
cx,
)
.expect("Failed to create component preview")
});
workspace.add_item_to_active_pane(
Box::new(component_preview),
None,
true,
window,
cx,
);
});
workspace
}
},
)
.expect("Failed to open component preview window");
cx.activate(true);
});
} }
@@ -1,4 +1,3 @@
mod component_preview_example;
mod persistence; mod persistence;
use client::UserStore; use client::UserStore;
@@ -20,9 +19,6 @@ use workspace::{
Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items, item::ItemEvent, Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items, item::ItemEvent,
}; };
#[allow(unused_imports)]
pub use component_preview_example::*;
pub fn init(app_state: Arc<AppState>, cx: &mut App) { pub fn init(app_state: Arc<AppState>, cx: &mut App) {
workspace::register_serializable_item::<ComponentPreview>(cx); workspace::register_serializable_item::<ComponentPreview>(cx);
@@ -85,13 +81,13 @@ impl From<SharedString> for PreviewEntry {
} }
#[derive(Default, Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
enum PreviewPage { pub enum PreviewPage {
#[default] #[default]
AllComponents, AllComponents,
Component(ComponentId), Component(ComponentId),
} }
struct ComponentPreview { pub struct ComponentPreview {
active_page: PreviewPage, active_page: PreviewPage,
reset_key: usize, reset_key: usize,
component_list: ListState, component_list: ListState,
@@ -1,148 +0,0 @@
/// Run the component preview application.
///
/// This initializes the application with minimal required infrastructure
/// and opens a workspace with the ComponentPreview item.
#[cfg(feature = "preview")]
pub fn run_component_preview() {
use fs::RealFs;
use gpui::{
AppContext as _, Application, Bounds, KeyBinding, WindowBounds, WindowOptions, actions,
size,
};
use client::{Client, UserStore};
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
use project::Project;
use reqwest_client::ReqwestClient;
use session::{AppSession, Session};
use std::sync::Arc;
use ui::{App, px};
use workspace::{AppState, Workspace, WorkspaceStore};
use crate::{ComponentPreview, init};
actions!(zed, [Quit]);
fn quit(_: &Quit, cx: &mut App) {
cx.quit();
}
Application::new().run(|cx| {
component::init();
cx.on_action(quit);
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
let version = release_channel::AppVersion::load(env!("CARGO_PKG_VERSION"), None, None);
release_channel::init(version, cx);
let http_client =
ReqwestClient::user_agent("component_preview").expect("Failed to create HTTP client");
cx.set_http_client(Arc::new(http_client));
let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
<dyn fs::Fs>::set_global(fs.clone(), cx);
settings::init(cx);
theme::init(theme::LoadThemes::JustBase, cx);
let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
let client = Client::production(cx);
client::init(&client, cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
let session_id = uuid::Uuid::new_v4().to_string();
let session = cx.foreground_executor().block_on(Session::new(session_id));
let session = cx.new(|cx| AppSession::new(session, cx));
let node_runtime = NodeRuntime::unavailable();
let app_state = Arc::new(AppState {
languages,
client,
user_store,
workspace_store,
fs,
build_window_options: |_, _| Default::default(),
node_runtime,
session,
});
AppState::set_global(Arc::downgrade(&app_state), cx);
workspace::init(app_state.clone(), cx);
init(app_state.clone(), cx);
let size = size(px(1200.), px(800.));
let bounds = Bounds::centered(None, size, cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
{
move |window, cx| {
let app_state = app_state;
theme::setup_ui_font(window, cx);
let project = Project::local(
app_state.client.clone(),
app_state.node_runtime.clone(),
app_state.user_store.clone(),
app_state.languages.clone(),
app_state.fs.clone(),
None,
project::LocalProjectFlags {
init_worktree_trust: false,
..Default::default()
},
cx,
);
let workspace = cx.new(|cx| {
Workspace::new(
Default::default(),
project.clone(),
app_state.clone(),
window,
cx,
)
});
workspace.update(cx, |workspace, cx| {
let weak_workspace = cx.entity().downgrade();
let language_registry = app_state.languages.clone();
let user_store = app_state.user_store.clone();
let component_preview = cx.new(|cx| {
ComponentPreview::new(
weak_workspace,
project,
language_registry,
user_store,
None,
None,
window,
cx,
)
.expect("Failed to create component preview")
});
workspace.add_item_to_active_pane(
Box::new(component_preview),
None,
true,
window,
cx,
);
});
workspace
}
},
)
.expect("Failed to open component preview window");
cx.activate(true);
});
}
+1
View File
@@ -27,6 +27,7 @@ extension.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
gpui_platform.workspace = true
gpui_tokio.workspace = true gpui_tokio.workspace = true
indoc.workspace = true indoc.workspace = true
language.workspace = true language.workspace = true
+2 -2
View File
@@ -31,7 +31,7 @@ use collections::HashSet;
use edit_prediction::EditPredictionStore; use edit_prediction::EditPredictionStore;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::{SinkExt as _, StreamExt as _}; use futures::{SinkExt as _, StreamExt as _};
use gpui::{AppContext as _, Application, BackgroundExecutor, Task}; use gpui::{AppContext as _, BackgroundExecutor, Task};
use zeta_prompt::ZetaFormat; use zeta_prompt::ZetaFormat;
use reqwest_client::ReqwestClient; use reqwest_client::ReqwestClient;
@@ -851,7 +851,7 @@ fn main() {
} }
let http_client = Arc::new(ReqwestClient::new()); let http_client = Arc::new(ReqwestClient::new());
let app = Application::headless().with_http_client(http_client); let app = gpui_platform::headless().with_http_client(http_client);
app.run(move |cx| { app.run(move |cx| {
let app_state = Arc::new(headless::init(cx)); let app_state = Arc::new(headless::init(cx));
+1
View File
@@ -38,6 +38,7 @@ extension.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
gpui_platform.workspace = true
gpui_tokio.workspace = true gpui_tokio.workspace = true
handlebars.workspace = true handlebars.workspace = true
language.workspace = true language.workspace = true
+2 -2
View File
@@ -18,7 +18,7 @@ use collections::{HashMap, HashSet};
use extension::ExtensionHostProxy; use extension::ExtensionHostProxy;
use futures::future; use futures::future;
use gpui::http_client::read_proxy_from_env; use gpui::http_client::read_proxy_from_env;
use gpui::{App, AppContext, Application, AsyncApp, Entity, UpdateGlobal}; use gpui::{App, AppContext, AsyncApp, Entity, UpdateGlobal};
use gpui_tokio::Tokio; use gpui_tokio::Tokio;
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::{ConfiguredModel, LanguageModel, LanguageModelRegistry, SelectedModel}; use language_model::{ConfiguredModel, LanguageModel, LanguageModelRegistry, SelectedModel};
@@ -114,7 +114,7 @@ fn main() {
let languages: HashSet<String> = args.languages.into_iter().collect(); let languages: HashSet<String> = args.languages.into_iter().collect();
let http_client = Arc::new(ReqwestClient::new()); let http_client = Arc::new(ReqwestClient::new());
let app = Application::headless().with_http_client(http_client); let app = gpui_platform::headless().with_http_client(http_client);
let all_threads = examples::all(&examples_dir); let all_threads = examples::all(&examples_dir);
app.run(move |cx| { app.run(move |cx| {
+1 -1
View File
@@ -13,6 +13,6 @@ path = "src/eval_utils.rs"
doctest = false doctest = false
[dependencies] [dependencies]
gpui.workspace = true gpui_platform.workspace = true
serde.workspace = true serde.workspace = true
smol.workspace = true smol.workspace = true
+1 -1
View File
@@ -82,7 +82,7 @@ pub fn eval<P>(
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let executor = gpui::background_executor(); let executor = gpui_platform::background_executor();
let semaphore = Arc::new(smol::lock::Semaphore::new(32)); let semaphore = Arc::new(smol::lock::Semaphore::new(32));
let evalf = Arc::new(evalf); let evalf = Arc::new(evalf);
// Warm the cache once // Warm the cache once
+1 -1
View File
@@ -19,7 +19,7 @@ cloud_api_types.workspace = true
env_logger.workspace = true env_logger.workspace = true
extension.workspace = true extension.workspace = true
fs.workspace = true fs.workspace = true
gpui.workspace = true gpui_platform.workspace = true
language.workspace = true language.workspace = true
log.workspace = true log.workspace = true
reqwest_client.workspace = true reqwest_client.workspace = true
+1 -1
View File
@@ -35,7 +35,7 @@ async fn main() -> Result<()> {
env_logger::init(); env_logger::init();
let args = Args::parse(); let args = Args::parse();
let fs = Arc::new(RealFs::new(None, gpui::background_executor())); let fs = Arc::new(RealFs::new(None, gpui_platform::background_executor()));
let engine = wasmtime::Engine::default(); let engine = wasmtime::Engine::default();
let mut wasm_store = WasmStore::new(&engine)?; let mut wasm_store = WasmStore::new(&engine)?;
+2 -1
View File
@@ -6,7 +6,8 @@ edition.workspace = true
[dependencies] [dependencies]
fs.workspace = true fs.workspace = true
gpui = {workspace = true, features = ["windows-manifest"]} gpui.workspace = true
gpui_platform.workspace = true
[lints] [lints]
workspace = true workspace = true
+4 -2
View File
@@ -1,12 +1,14 @@
use fs::Fs; use fs::Fs;
use gpui::{AppContext, Application}; use gpui::AppContext;
use gpui_platform::headless;
fn main() { fn main() {
let Some(path_to_read) = std::env::args().nth(1) else { let Some(path_to_read) = std::env::args().nth(1) else {
println!("Expected path to read as 1st argument."); println!("Expected path to read as 1st argument.");
return; return;
}; };
let _ = Application::headless().run(|cx| { let _ = headless().run(|cx| {
let fs = fs::RealFs::new(None, cx.background_executor().clone()); let fs = fs::RealFs::new(None, cx.background_executor().clone());
cx.background_spawn(async move { cx.background_spawn(async move {
let timer = std::time::Instant::now(); let timer = std::time::Instant::now();
+8 -98
View File
@@ -31,37 +31,8 @@ leak-detection = ["backtrace"]
runtime_shaders = [] runtime_shaders = []
wayland = [ wayland = [
"bitflags", "bitflags",
"wgpu",
"bytemuck",
"ashpd/wayland",
"cosmic-text",
"font-kit",
"calloop-wayland-source",
"wayland-backend",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-plasma",
"wayland-protocols-wlr",
"filedescriptor",
"xkbcommon",
"open",
]
x11 = [
"wgpu",
"bytemuck",
"ashpd",
"cosmic-text",
"font-kit",
"as-raw-xcb-connection",
"x11rb",
"xkbcommon",
"xim",
"x11-clipboard",
"filedescriptor",
"open",
"scap?/x11",
] ]
x11 = []
screen-capture = [ screen-capture = [
"scap", "scap",
] ]
@@ -76,7 +47,7 @@ anyhow.workspace = true
async-task = "4.7" async-task = "4.7"
backtrace = { workspace = true, optional = true } backtrace = { workspace = true, optional = true }
bitflags = { workspace = true, optional = true } bitflags = { workspace = true, optional = true }
bytemuck = { version = "1", optional = true }
collections.workspace = true collections.workspace = true
ctor.workspace = true ctor.workspace = true
derive_more.workspace = true derive_more.workspace = true
@@ -107,7 +78,6 @@ usvg = { version = "0.45.0", default-features = false }
util_macros.workspace = true util_macros.workspace = true
schemars.workspace = true schemars.workspace = true
seahash = "4.1" seahash = "4.1"
semver.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
slotmap.workspace = true slotmap.workspace = true
@@ -122,7 +92,6 @@ util.workspace = true
uuid.workspace = true uuid.workspace = true
waker-fn = "1.2.0" waker-fn = "1.2.0"
lyon = "1.0" lyon = "1.0"
libc.workspace = true
pin-project = "1.1.10" pin-project = "1.1.10"
circular-buffer.workspace = true circular-buffer.workspace = true
spin = "0.10.0" spin = "0.10.0"
@@ -154,76 +123,17 @@ pathfinder_geometry = "0.5"
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies] [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies]
scap = { workspace = true, optional = true } scap = { workspace = true, optional = true }
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
# Always used
oo7 = { version = "0.5.0", default-features = false, features = [
"async-std",
"native_crypto",
] }
# Used in both windowing options
ashpd = { workspace = true, optional = true }
wgpu = { workspace = true, optional = true }
cosmic-text = { version = "0.17.0", optional = true }
swash = { version = "0.2.6" }
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", features = [
"source-fontconfig-dlopen",
], optional = true }
calloop = "0.14.3"
filedescriptor = { version = "0.8.2", optional = true }
open = { version = "5.2.0", optional = true }
xkbcommon = { version = "0.8.0", features = ["wayland", "x11"], optional = true }
# Wayland
calloop-wayland-source = { version = "0.4.1", optional = true }
wayland-backend = { version = "0.3.3", features = [
"client_system",
"dlopen",
], optional = true }
wayland-client = { version = "0.31.11", optional = true }
wayland-cursor = { version = "0.31.11", optional = true }
wayland-protocols = { version = "0.32.9", features = [
"client",
"staging",
"unstable",
], optional = true }
wayland-protocols-plasma = { version = "0.3.9", features = [
"client",
], optional = true }
wayland-protocols-wlr = { version = "0.3.9", features = [
"client",
], optional = true }
# X11
as-raw-xcb-connection = { version = "1", optional = true }
x11rb = { version = "0.13.1", features = [
"allow-unsafe-code",
"xkb",
"randr",
"xinput",
"cursor",
"resource_manager",
"sync",
], optional = true }
# WARNING: If you change this, you must also publish a new version of zed-xim to crates.io
xim = { git = "https://github.com/zed-industries/xim-rs.git", rev = "16f35a2c881b815a2b6cdfd6687988e84f8447d8" , features = [
"x11rb-xcb",
"x11rb-client",
], package = "zed-xim", version = "0.4.0-zed", optional = true }
x11-clipboard = { version = "0.9.3", optional = true }
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
rand.workspace = true windows = { version = "0.61", features = ["Win32_Foundation"] }
windows.workspace = true
windows-core.workspace = true
windows-numerics = "0.2"
windows-registry = "0.5"
[dev-dependencies] [dev-dependencies]
backtrace.workspace = true backtrace.workspace = true
collections = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] }
env_logger.workspace = true env_logger.workspace = true
gpui_platform.workspace = true
http_client = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] }
lyon = { version = "1.0", features = ["extra"] } lyon = { version = "1.0", features = ["extra"] }
pretty_assertions.workspace = true pretty_assertions.workspace = true
@@ -233,17 +143,17 @@ scheduler = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true unicode-segmentation.workspace = true
util = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] }
[target.'cfg(target_os = "windows")'.build-dependencies] [target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = "3.0" embed-resource = "3.0"
windows-registry = "0.5"
[target.'cfg(target_os = "macos")'.build-dependencies] [target.'cfg(target_os = "macos")'.build-dependencies]
bindgen = "0.71" bindgen = "0.71"
cbindgen = { version = "0.28.0", default-features = false } cbindgen = { version = "0.28.0", default-features = false }
naga.workspace = true naga.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
naga.workspace = true
[[example]] [[example]]
+11 -454
View File
@@ -1,463 +1,20 @@
#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")] #![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
#![cfg_attr(not(target_os = "macos"), allow(unused))] #![cfg_attr(not(target_os = "macos"), allow(unused))]
use std::env;
fn main() { fn main() {
let target = env::var("CARGO_CFG_TARGET_OS");
println!("cargo::rustc-check-cfg=cfg(gles)"); println!("cargo::rustc-check-cfg=cfg(gles)");
match target.as_deref() { #[cfg(all(target_os = "windows", feature = "windows-manifest"))]
Ok("macos") => { embed_resource();
#[cfg(target_os = "macos")]
macos::build();
}
Ok("windows") => {
#[cfg(target_os = "windows")]
windows::build();
}
_ => (),
};
}
#[cfg(target_os = "macos")]
mod macos {
use std::{
env,
path::{Path, PathBuf},
};
use cbindgen::Config;
pub(super) fn build() {
generate_dispatch_bindings();
let header_path = generate_shader_bindings();
#[cfg(feature = "runtime_shaders")]
emit_stitched_shaders(&header_path);
#[cfg(not(feature = "runtime_shaders"))]
compile_metal_shaders(&header_path);
}
fn generate_dispatch_bindings() {
println!("cargo:rustc-link-lib=framework=System");
let bindings = bindgen::Builder::default()
.header("src/platform/mac/dispatch.h")
.allowlist_var("_dispatch_main_q")
.allowlist_var("_dispatch_source_type_data_add")
.allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
.allowlist_var("DISPATCH_QUEUE_PRIORITY_LOW")
.allowlist_var("DISPATCH_TIME_NOW")
.allowlist_function("dispatch_get_global_queue")
.allowlist_function("dispatch_async_f")
.allowlist_function("dispatch_after_f")
.allowlist_function("dispatch_time")
.allowlist_function("dispatch_source_merge_data")
.allowlist_function("dispatch_source_create")
.allowlist_function("dispatch_source_set_event_handler_f")
.allowlist_function("dispatch_resume")
.allowlist_function("dispatch_suspend")
.allowlist_function("dispatch_source_cancel")
.allowlist_function("dispatch_set_context")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.layout_tests(false)
.generate()
.expect("unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("dispatch_sys.rs"))
.expect("couldn't write dispatch bindings");
}
fn generate_shader_bindings() -> PathBuf {
let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let mut config = Config {
include_guard: Some("SCENE_H".into()),
language: cbindgen::Language::C,
no_includes: true,
..Default::default()
};
config.export.include.extend([
"Bounds".into(),
"Corners".into(),
"Edges".into(),
"Size".into(),
"Pixels".into(),
"PointF".into(),
"Hsla".into(),
"ContentMask".into(),
"Uniforms".into(),
"AtlasTile".into(),
"PathRasterizationInputIndex".into(),
"PathVertex_ScaledPixels".into(),
"PathRasterizationVertex".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"QuadInputIndex".into(),
"Underline".into(),
"UnderlineInputIndex".into(),
"Quad".into(),
"BorderStyle".into(),
"SpriteInputIndex".into(),
"MonochromeSprite".into(),
"PolychromeSprite".into(),
"PathSprite".into(),
"SurfaceInputIndex".into(),
"SurfaceBounds".into(),
"TransformationMatrix".into(),
]);
config.no_includes = true;
config.enumeration.prefix_with_name = true;
let mut builder = cbindgen::Builder::new();
let src_paths = [
crate_dir.join("src/scene.rs"),
crate_dir.join("src/geometry.rs"),
crate_dir.join("src/color.rs"),
crate_dir.join("src/window.rs"),
crate_dir.join("src/platform.rs"),
crate_dir.join("src/platform/mac/metal_renderer.rs"),
];
for src_path in src_paths {
println!("cargo:rerun-if-changed={}", src_path.display());
builder = builder.with_src(src_path);
}
builder
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file(&output_path);
output_path
}
/// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
/// so that it is self-contained.
#[cfg(feature = "runtime_shaders")]
fn emit_stitched_shaders(header_path: &Path) {
use std::str::FromStr;
fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
let header_contents = std::fs::read_to_string(header)?;
let shader_contents = std::fs::read_to_string(shader_path)?;
let stitched_contents = format!("{header_contents}\n{shader_contents}");
let out_path =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
std::fs::write(&out_path, stitched_contents)?;
Ok(out_path)
}
let shader_source_path = "./src/platform/mac/shaders.metal";
let shader_path = PathBuf::from_str(shader_source_path).unwrap();
stitch_header(header_path, &shader_path).unwrap();
println!("cargo:rerun-if-changed={}", &shader_source_path);
}
#[cfg(not(feature = "runtime_shaders"))]
fn compile_metal_shaders(header_path: &Path) {
use std::process::{self, Command};
let shader_path = "./src/platform/mac/shaders.metal";
let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
let metallib_output_path =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
println!("cargo:rerun-if-changed={}", shader_path);
let output = Command::new("xcrun")
.args([
"-sdk",
"macosx",
"metal",
"-gline-tables-only",
"-mmacosx-version-min=10.15.7",
"-MO",
"-c",
shader_path,
"-include",
(header_path.to_str().unwrap()),
"-o",
])
.arg(&air_output_path)
.output()
.unwrap();
if !output.status.success() {
println!(
"cargo::error=metal shader compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
let output = Command::new("xcrun")
.args(["-sdk", "macosx", "metallib"])
.arg(air_output_path)
.arg("-o")
.arg(metallib_output_path)
.output()
.unwrap();
if !output.status.success() {
println!(
"cargo::error=metallib compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
}
} }
#[cfg(target_os = "windows")] #[cfg(all(target_os = "windows", feature = "windows-manifest"))]
mod windows { fn embed_resource() {
use std::{ let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
ffi::OsString, let rc_file = std::path::Path::new("resources/windows/gpui.rc");
fs, println!("cargo:rerun-if-changed={}", manifest.display());
io::Write, println!("cargo:rerun-if-changed={}", rc_file.display());
path::{Path, PathBuf}, embed_resource::compile(rc_file, embed_resource::NONE)
process::{self, Command}, .manifest_required()
}; .unwrap();
pub(super) fn build() {
// Compile HLSL shaders
#[cfg(not(debug_assertions))]
compile_shaders();
// Embed the Windows manifest and resource file
#[cfg(feature = "windows-manifest")]
embed_resource();
}
#[cfg(feature = "windows-manifest")]
fn embed_resource() {
let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
let rc_file = std::path::Path::new("resources/windows/gpui.rc");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rerun-if-changed={}", rc_file.display());
embed_resource::compile(rc_file, embed_resource::NONE)
.manifest_required()
.unwrap();
}
/// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
fn compile_shaders() {
let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src/platform/windows/shaders.hlsl");
let out_dir = std::env::var("OUT_DIR").unwrap();
println!("cargo:rerun-if-changed={}", shader_path.display());
// Check if fxc.exe is available
let fxc_path = find_fxc_compiler();
// Define all modules
let modules = [
"quad",
"shadow",
"path_rasterization",
"path_sprite",
"underline",
"monochrome_sprite",
"subpixel_sprite",
"polychrome_sprite",
];
let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
if Path::new(&rust_binding_path).exists() {
fs::remove_file(&rust_binding_path)
.expect("Failed to remove existing Rust binding file");
}
for module in modules {
compile_shader_for_module(
module,
&out_dir,
&fxc_path,
shader_path.to_str().unwrap(),
&rust_binding_path,
);
}
{
let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src/platform/windows/color_text_raster.hlsl");
compile_shader_for_module(
"emoji_rasterization",
&out_dir,
&fxc_path,
shader_path.to_str().unwrap(),
&rust_binding_path,
);
}
}
/// Locate `binary` in the newest installed Windows SDK.
pub fn find_latest_windows_sdk_binary(
binary: &str,
) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
let key = windows_registry::LOCAL_MACHINE
.open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?;
let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\"
let install_folder_bin = Path::new(&install_folder).join("bin");
let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)?
.flatten()
.filter(|entry| entry.path().is_dir())
.filter_map(|entry| entry.file_name().into_string().ok())
.collect();
versions.sort_by_key(|s| {
s.split('.')
.filter_map(|p| p.parse().ok())
.collect::<Vec<u32>>()
});
let arch = match std::env::consts::ARCH {
"x86_64" => "x64",
"aarch64" => "arm64",
_ => Err(format!(
"Unsupported architecture: {}",
std::env::consts::ARCH
))?,
};
if let Some(highest_version) = versions.last() {
return Ok(Some(
install_folder_bin
.join(highest_version)
.join(arch)
.join(binary),
));
}
Ok(None)
}
/// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
fn find_fxc_compiler() -> String {
// Check environment variable
if let Ok(path) = std::env::var("GPUI_FXC_PATH")
&& Path::new(&path).exists()
{
return path;
}
// Try to find in PATH
// NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
if let Ok(output) = std::process::Command::new("where.exe")
.arg("fxc.exe")
.output()
&& output.status.success()
{
let path = String::from_utf8_lossy(&output.stdout);
return path.trim().to_string();
}
if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") {
return path.to_string_lossy().into_owned();
}
panic!("Failed to find fxc.exe");
}
fn compile_shader_for_module(
module: &str,
out_dir: &str,
fxc_path: &str,
shader_path: &str,
rust_binding_path: &str,
) {
// Compile vertex shader
let output_file = format!("{}/{}_vs.h", out_dir, module);
let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
compile_shader_impl(
fxc_path,
&format!("{module}_vertex"),
&output_file,
&const_name,
shader_path,
"vs_4_1",
);
generate_rust_binding(&const_name, &output_file, rust_binding_path);
// Compile fragment shader
let output_file = format!("{}/{}_ps.h", out_dir, module);
let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
compile_shader_impl(
fxc_path,
&format!("{module}_fragment"),
&output_file,
&const_name,
shader_path,
"ps_4_1",
);
generate_rust_binding(&const_name, &output_file, rust_binding_path);
}
fn compile_shader_impl(
fxc_path: &str,
entry_point: &str,
output_path: &str,
var_name: &str,
shader_path: &str,
target: &str,
) {
let output = Command::new(fxc_path)
.args([
"/T",
target,
"/E",
entry_point,
"/Fh",
output_path,
"/Vn",
var_name,
"/O3",
shader_path,
])
.output();
match output {
Ok(result) => {
if result.status.success() {
return;
}
println!(
"cargo::error=Shader compilation failed for {}:\n{}",
entry_point,
String::from_utf8_lossy(&result.stderr)
);
process::exit(1);
}
Err(e) => {
println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
process::exit(1);
}
}
}
fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
let const_definition = {
let global_var_start = header_content.find("const BYTE").unwrap();
let global_var = &header_content[global_var_start..];
let equal = global_var.find('=').unwrap();
global_var[equal + 1..].trim()
};
let rust_binding = format!(
"const {}: &[u8] = &{}\n",
const_name,
const_definition.replace('{', "[").replace('}', "]")
);
let mut options = fs::OpenOptions::new()
.create(true)
.append(true)
.open(output_path)
.expect("Failed to open Rust binding file");
options
.write_all(rust_binding.as_bytes())
.expect("Failed to write Rust binding file");
}
} }
+19 -20
View File
@@ -2,10 +2,11 @@ use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use gpui::{ use gpui::{
Animation, AnimationExt as _, App, Application, AssetSource, Bounds, Context, SharedString, Animation, AnimationExt as _, App, AssetSource, Bounds, Context, SharedString, Transformation,
Transformation, Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage, Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage, prelude::*, px,
prelude::*, px, size, svg, size, svg,
}; };
use gpui_platform::application;
struct Assets {} struct Assets {}
@@ -101,21 +102,19 @@ impl Render for AnimationExample {
} }
fn main() { fn main() {
Application::new() application().with_assets(Assets {}).run(|cx: &mut App| {
.with_assets(Assets {}) let options = WindowOptions {
.run(|cx: &mut App| { window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
let options = WindowOptions { None,
window_bounds: Some(WindowBounds::Windowed(Bounds::centered( size(px(300.), px(300.)),
None, cx,
size(px(300.), px(300.)), ))),
cx, ..Default::default()
))), };
..Default::default() cx.open_window(options, |_, cx| {
}; cx.activate(false);
cx.open_window(options, |_, cx| { cx.new(|_| AnimationExample {})
cx.activate(false); })
cx.new(|_| AnimationExample {}) .unwrap();
}) });
.unwrap();
});
} }
+5 -4
View File
@@ -1,10 +1,11 @@
use std::{ops::Range, rc::Rc, time::Duration}; use std::{ops::Range, rc::Rc, time::Duration};
use gpui::{ use gpui::{
App, Application, Bounds, Context, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, App, Bounds, Context, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Render,
Render, SharedString, UniformListScrollHandle, Window, WindowBounds, WindowOptions, canvas, SharedString, UniformListScrollHandle, Window, WindowBounds, WindowOptions, canvas, div, point,
div, point, prelude::*, px, rgb, size, uniform_list, prelude::*, px, rgb, size, uniform_list,
}; };
use gpui_platform::application;
const TOTAL_ITEMS: usize = 10000; const TOTAL_ITEMS: usize = 10000;
const SCROLLBAR_THUMB_WIDTH: Pixels = px(8.); const SCROLLBAR_THUMB_WIDTH: Pixels = px(8.);
@@ -447,7 +448,7 @@ impl Render for DataTable {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
focus: true, focus: true,
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds, App, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds, WindowOptions, div,
WindowOptions, div, prelude::*, px, rgb, size, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct DragInfo { struct DragInfo {
@@ -121,7 +122,7 @@ impl Render for DragDrop {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx); let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window,
Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
}; };
use gpui_platform::application;
actions!(example, [Tab, TabPrev, Quit]); actions!(example, [Tab, TabPrev, Quit]);
@@ -192,7 +193,7 @@ impl Render for Example {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("tab", Tab, None), KeyBinding::new("tab", Tab, None),
KeyBinding::new("shift-tab", TabPrev, None), KeyBinding::new("shift-tab", TabPrev, None),
+3 -2
View File
@@ -1,4 +1,5 @@
use gpui::{App, Application, Context, Render, Window, WindowOptions, div, img, prelude::*}; use gpui::{App, Context, Render, Window, WindowOptions, div, img, prelude::*};
use gpui_platform::application;
use std::path::PathBuf; use std::path::PathBuf;
struct GifViewer { struct GifViewer {
@@ -24,7 +25,7 @@ impl Render for GifViewer {
fn main() { fn main() {
env_logger::init(); env_logger::init();
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let gif_path = let gif_path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/image/black-cat-typing.gif"); PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/image/black-cat-typing.gif");
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, ColorSpace, Context, Half, Render, Window, WindowOptions, canvas, App, Bounds, ColorSpace, Context, Half, Render, Window, WindowOptions, canvas, div,
div, linear_color_stop, linear_gradient, point, prelude::*, px, size, linear_color_stop, linear_gradient, point, prelude::*, px, size,
}; };
use gpui_platform::application;
struct GradientViewer { struct GradientViewer {
color_space: ColorSpace, color_space: ColorSpace,
@@ -243,7 +244,7 @@ impl Render for GradientViewer {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
focus: true, focus: true,
+3 -3
View File
@@ -1,7 +1,7 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*, App, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
px, rgb, size,
}; };
use gpui_platform::application;
// https://en.wikipedia.org/wiki/Holy_grail_(web_design) // https://en.wikipedia.org/wiki/Holy_grail_(web_design)
struct HolyGrailExample {} struct HolyGrailExample {}
@@ -65,7 +65,7 @@ impl Render for HolyGrailExample {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div, App, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div, prelude::*, px,
prelude::*, px, rgb, size, rgb, size,
}; };
use gpui_platform::application;
struct HelloWorld { struct HelloWorld {
text: SharedString, text: SharedString,
@@ -87,7 +88,7 @@ impl Render for HelloWorld {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+5 -4
View File
@@ -4,10 +4,11 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use gpui::{ use gpui::{
App, AppContext, Application, AssetSource, Bounds, Context, ImageSource, KeyBinding, Menu, App, AppContext, AssetSource, Bounds, Context, ImageSource, KeyBinding, Menu, MenuItem, Point,
MenuItem, Point, SharedString, SharedUri, TitlebarOptions, Window, WindowBounds, WindowOptions, SharedString, SharedUri, TitlebarOptions, Window, WindowBounds, WindowOptions, actions, div,
actions, div, img, prelude::*, px, rgb, size, img, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
use reqwest_client::ReqwestClient; use reqwest_client::ReqwestClient;
struct Assets { struct Assets {
@@ -150,7 +151,7 @@ fn main() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
Application::new() application()
.with_assets(Assets { .with_assets(Assets {
base: manifest_dir.join("examples"), base: manifest_dir.join("examples"),
}) })
+4 -3
View File
@@ -1,10 +1,11 @@
use futures::FutureExt; use futures::FutureExt;
use gpui::{ use gpui::{
App, AppContext, Application, Asset as _, AssetLogger, Bounds, ClickEvent, Context, ElementId, App, AppContext, Asset as _, AssetLogger, Bounds, ClickEvent, Context, ElementId, Entity,
Entity, ImageAssetLoader, ImageCache, ImageCacheProvider, KeyBinding, Menu, MenuItem, ImageAssetLoader, ImageCache, ImageCacheProvider, KeyBinding, Menu, MenuItem,
RetainAllImageCache, SharedString, TitlebarOptions, Window, WindowBounds, WindowOptions, RetainAllImageCache, SharedString, TitlebarOptions, Window, WindowBounds, WindowOptions,
actions, div, hash, image_cache, img, prelude::*, px, rgb, size, actions, div, hash, image_cache, img, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
use reqwest_client::ReqwestClient; use reqwest_client::ReqwestClient;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
@@ -247,7 +248,7 @@ actions!(image, [Quit]);
fn main() { fn main() {
env_logger::init(); env_logger::init();
Application::new().run(move |cx: &mut App| { application().run(move |cx: &mut App| {
let http_client = ReqwestClient::user_agent("gpui example").unwrap(); let http_client = ReqwestClient::user_agent("gpui example").unwrap();
cx.set_http_client(Arc::new(http_client)); cx.set_http_client(Arc::new(http_client));
+18 -19
View File
@@ -1,11 +1,12 @@
use std::{path::Path, sync::Arc, time::Duration}; use std::{path::Path, sync::Arc, time::Duration};
use gpui::{ use gpui::{
Animation, AnimationExt, App, Application, Asset, AssetLogger, AssetSource, Bounds, Context, Animation, AnimationExt, App, Asset, AssetLogger, AssetSource, Bounds, Context, Hsla,
Hsla, ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, RenderImage, ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, RenderImage,
Resource, SharedString, Window, WindowBounds, WindowOptions, black, div, img, prelude::*, Resource, SharedString, Window, WindowBounds, WindowOptions, black, div, img, prelude::*,
pulsating_between, px, red, size, pulsating_between, px, red, size,
}; };
use gpui_platform::application;
struct Assets {} struct Assets {}
@@ -193,21 +194,19 @@ impl Render for ImageLoadingExample {
fn main() { fn main() {
env_logger::init(); env_logger::init();
Application::new() application().with_assets(Assets {}).run(|cx: &mut App| {
.with_assets(Assets {}) let options = WindowOptions {
.run(|cx: &mut App| { window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
let options = WindowOptions { None,
window_bounds: Some(WindowBounds::Windowed(Bounds::centered( size(px(300.), px(300.)),
None, cx,
size(px(300.), px(300.)), ))),
cx, ..Default::default()
))), };
..Default::default() cx.open_window(options, |_, cx| {
}; cx.activate(false);
cx.open_window(options, |_, cx| { cx.new(|_| ImageLoadingExample {})
cx.activate(false); })
cx.new(|_| ImageLoadingExample {}) .unwrap();
}) });
.unwrap();
});
} }
+5 -4
View File
@@ -1,13 +1,14 @@
use std::ops::Range; use std::ops::Range;
use gpui::{ use gpui::{
App, Application, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler, App, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler, Entity,
Entity, EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, Keystroke, EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, Keystroke, LayoutId,
LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
ShapedLine, SharedString, Style, TextRun, UTF16Selection, UnderlineStyle, Window, WindowBounds, ShapedLine, SharedString, Style, TextRun, UTF16Selection, UnderlineStyle, Window, WindowBounds,
WindowOptions, actions, black, div, fill, hsla, opaque_grey, point, prelude::*, px, relative, WindowOptions, actions, black, div, fill, hsla, opaque_grey, point, prelude::*, px, relative,
rgb, rgba, size, white, yellow, rgb, rgba, size, white, yellow,
}; };
use gpui_platform::application;
use unicode_segmentation::*; use unicode_segmentation::*;
actions!( actions!(
@@ -682,7 +683,7 @@ impl Render for InputExample {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("backspace", Backspace, None), KeyBinding::new("backspace", Backspace, None),
+4 -4
View File
@@ -11,10 +11,10 @@ mod example {
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use gpui::{ use gpui::{
App, Application, Bounds, Context, FontWeight, Size, Window, WindowBackgroundAppearance, App, Bounds, Context, FontWeight, Size, Window, WindowBackgroundAppearance, WindowBounds,
WindowBounds, WindowKind, WindowOptions, div, layer_shell::*, point, prelude::*, px, rems, WindowKind, WindowOptions, div, layer_shell::*, point, prelude::*, px, rems, rgba, white,
rgba, white,
}; };
use gpui_platform::application;
struct LayerShellExample; struct LayerShellExample;
@@ -60,7 +60,7 @@ mod example {
} }
pub fn main() { pub fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
titlebar: None, titlebar: None,
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, MousePressureEvent, PressureStage, Window, WindowBounds, App, Bounds, Context, MousePressureEvent, PressureStage, Window, WindowBounds, WindowOptions,
WindowOptions, div, prelude::*, px, rgb, size, div, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
struct MousePressureExample { struct MousePressureExample {
pressure_stage: PressureStage, pressure_stage: PressureStage,
@@ -44,7 +45,7 @@ impl MousePressureExample {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.open_window( cx.open_window(
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, FocusHandle, KeyBinding, Window, WindowBounds, App, Bounds, Context, FocusHandle, KeyBinding, Window, WindowBounds, WindowOptions, actions,
WindowOptions, actions, div, prelude::*, px, rgb, size, div, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
actions!(example, [CloseWindow]); actions!(example, [CloseWindow]);
@@ -35,7 +36,7 @@ impl Render for ExampleWindow {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let mut bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); let mut bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.bind_keys([KeyBinding::new("cmd-w", CloseWindow, None)]); cx.bind_keys([KeyBinding::new("cmd-w", CloseWindow, None)]);
+4 -3
View File
@@ -2,9 +2,10 @@ use std::{fs, path::PathBuf};
use anyhow::Result; use anyhow::Result;
use gpui::{ use gpui::{
App, Application, AssetSource, Bounds, BoxShadow, ClickEvent, Context, SharedString, Task, App, AssetSource, Bounds, BoxShadow, ClickEvent, Context, SharedString, Task, Window,
Window, WindowBounds, WindowOptions, div, hsla, img, point, prelude::*, px, rgb, size, svg, WindowBounds, WindowOptions, div, hsla, img, point, prelude::*, px, rgb, size, svg,
}; };
use gpui_platform::application;
struct Assets { struct Assets {
base: PathBuf, base: PathBuf,
@@ -156,7 +157,7 @@ impl Render for HelloWorld {
} }
fn main() { fn main() {
Application::new() application()
.with_assets(Assets { .with_assets(Assets {
base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"), base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"),
}) })
+3 -2
View File
@@ -1,4 +1,5 @@
use gpui::{App, Application, Context, Entity, EventEmitter, prelude::*}; use gpui::{App, Context, Entity, EventEmitter, prelude::*};
use gpui_platform::application;
struct Counter { struct Counter {
count: usize, count: usize,
@@ -11,7 +12,7 @@ struct Change {
impl EventEmitter<Change> for Counter {} impl EventEmitter<Change> for Counter {}
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 }); let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
let subscriber = cx.new(|cx: &mut Context<Counter>| { let subscriber = cx.new(|cx: &mut Context<Counter>| {
cx.subscribe(&counter, |subscriber, _emitter, event, _cx| { cx.subscribe(&counter, |subscriber, _emitter, event, _cx| {
+5 -4
View File
@@ -1,8 +1,9 @@
use gpui::{ use gpui::{
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
PathStyle, Pixels, Point, Render, StrokeOptions, Window, WindowOptions, canvas, div, Point, Render, StrokeOptions, Window, WindowOptions, canvas, div, linear_color_stop,
linear_color_stop, linear_gradient, point, prelude::*, px, quad, rgb, size, linear_gradient, point, prelude::*, px, quad, rgb, size,
}; };
use gpui_platform::application;
struct PaintingViewer { struct PaintingViewer {
default_lines: Vec<(Path<Pixels>, Background)>, default_lines: Vec<(Path<Pixels>, Background)>,
@@ -445,7 +446,7 @@ impl Render for PaintingViewer {
} }
fn main() { fn main() {
Application::new().run(|cx| { application().run(|cx| {
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
focus: true, focus: true,
+5 -4
View File
@@ -1,8 +1,9 @@
use gpui::{ use gpui::{
Application, Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render, Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render, TitlebarOptions,
TitlebarOptions, Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop, Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop, linear_gradient, point,
linear_gradient, point, prelude::*, px, rgb, size, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
const DEFAULT_WINDOW_WIDTH: Pixels = px(1024.0); const DEFAULT_WINDOW_WIDTH: Pixels = px(1024.0);
const DEFAULT_WINDOW_HEIGHT: Pixels = px(768.0); const DEFAULT_WINDOW_HEIGHT: Pixels = px(768.0);
@@ -69,7 +70,7 @@ impl Render for PaintingViewer {
} }
fn main() { fn main() {
Application::new().run(|cx| { application().run(|cx| {
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
titlebar: Some(TitlebarOptions { titlebar: Some(TitlebarOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, AppContext, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, App, AppContext, Bounds, Context, Window, WindowBounds, WindowOptions, div, linear_color_stop,
linear_color_stop, linear_gradient, pattern_slash, prelude::*, px, rgb, size, linear_gradient, pattern_slash, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
struct PatternExample; struct PatternExample;
@@ -99,7 +100,7 @@ impl Render for PatternExample {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx); let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Context, Corner, Div, Hsla, Stateful, Window, WindowOptions, anchored, App, Context, Corner, Div, Hsla, Stateful, Window, WindowOptions, anchored, deferred, div,
deferred, div, prelude::*, px, prelude::*, px,
}; };
use gpui_platform::application;
/// An example show use deferred to create a floating layers. /// An example show use deferred to create a floating layers.
struct HelloWorld { struct HelloWorld {
@@ -161,7 +162,7 @@ impl Render for HelloWorld {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.open_window(WindowOptions::default(), |_, cx| { cx.open_window(WindowOptions::default(), |_, cx| {
cx.new(|_| HelloWorld { cx.new(|_| HelloWorld {
open: false, open: false,
+3 -5
View File
@@ -1,7 +1,5 @@
use gpui::{ use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size};
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, use gpui_platform::application;
size,
};
struct Scrollable {} struct Scrollable {}
@@ -45,7 +43,7 @@ impl Render for Scrollable {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window, App, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window, WindowOptions,
WindowOptions, actions, div, prelude::*, rgb, actions, div, prelude::*, rgb,
}; };
use gpui_platform::application;
struct SetMenus; struct SetMenus;
@@ -20,7 +21,7 @@ impl Render for SetMenus {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.set_global(AppState::new()); cx.set_global(AppState::new());
// Bring the menu bar to the foreground (so you can see the menu bar) // Bring the menu bar to the foreground (so you can see the menu bar)
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, BoxShadow, Context, Div, SharedString, Window, WindowBounds, App, Bounds, BoxShadow, Context, Div, SharedString, Window, WindowBounds, WindowOptions, div,
WindowOptions, div, hsla, point, prelude::*, px, relative, rgb, size, hsla, point, prelude::*, px, relative, rgb, size,
}; };
use gpui_platform::application;
struct Shadow {} struct Shadow {}
@@ -569,7 +570,7 @@ impl Render for Shadow {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx); let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -3,9 +3,10 @@ use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use gpui::{ use gpui::{
App, Application, AssetSource, Bounds, Context, SharedString, Window, WindowBounds, App, AssetSource, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div,
WindowOptions, div, prelude::*, px, rgb, size, svg, prelude::*, px, rgb, size, svg,
}; };
use gpui_platform::application;
struct Assets { struct Assets {
base: PathBuf, base: PathBuf,
@@ -68,7 +69,7 @@ impl Render for SvgExample {
} }
fn main() { fn main() {
Application::new() application()
.with_assets(Assets { .with_assets(Assets {
base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"), base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"),
}) })
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window,
Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
}; };
use gpui_platform::application;
actions!(example, [Tab, TabPrev]); actions!(example, [Tab, TabPrev]);
@@ -178,7 +179,7 @@ impl Render for Example {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("tab", Tab, None), KeyBinding::new("tab", Tab, None),
KeyBinding::new("shift-tab", TabPrev, None), KeyBinding::new("shift-tab", TabPrev, None),
+3 -2
View File
@@ -7,9 +7,10 @@
//! Run tests: cargo test -p gpui --example testing --features test-support //! Run tests: cargo test -p gpui --example testing --features test-support
use gpui::{ use gpui::{
App, Application, Bounds, Context, FocusHandle, Focusable, Render, Task, Window, WindowBounds, App, Bounds, Context, FocusHandle, Focusable, Render, Task, Window, WindowBounds,
WindowOptions, actions, div, prelude::*, px, rgb, size, WindowOptions, actions, div, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
actions!(counter, [Increment, Decrement]); actions!(counter, [Increment, Decrement]);
@@ -176,7 +177,7 @@ impl Render for Counter {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.bind_keys([ cx.bind_keys([
gpui::KeyBinding::new("up", Increment, Some("Counter")), gpui::KeyBinding::new("up", Increment, Some("Counter")),
gpui::KeyBinding::new("down", Decrement, Some("Counter")), gpui::KeyBinding::new("down", Decrement, Some("Counter")),
+5 -4
View File
@@ -4,10 +4,11 @@ use std::{
}; };
use gpui::{ use gpui::{
AbsoluteLength, App, Application, Context, DefiniteLength, ElementId, Global, Hsla, Menu, AbsoluteLength, App, Context, DefiniteLength, ElementId, Global, Hsla, Menu, SharedString,
SharedString, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds, colors::DefaultColors,
colors::DefaultColors, div, point, prelude::*, px, relative, rgb, size, div, point, prelude::*, px, relative, rgb, size,
}; };
use gpui_platform::application;
use std::iter; use std::iter;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -298,7 +299,7 @@ impl Render for TextExample {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
cx.set_menus(vec![Menu { cx.set_menus(vec![Menu {
name: "GPUI Typography".into(), name: "GPUI Typography".into(),
items: vec![], items: vec![],
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds, App, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds, WindowOptions,
WindowOptions, div, prelude::*, px, size, div, prelude::*, px, size,
}; };
use gpui_platform::application;
struct HelloWorld {} struct HelloWorld {}
@@ -81,7 +82,7 @@ impl Render for HelloWorld {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx); let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, TextOverflow, Window, WindowBounds, WindowOptions, div, App, Bounds, Context, TextOverflow, Window, WindowBounds, WindowOptions, div, prelude::*, px,
prelude::*, px, size, size,
}; };
use gpui_platform::application;
struct HelloWorld {} struct HelloWorld {}
@@ -108,7 +109,7 @@ impl Render for HelloWorld {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx); let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+3 -5
View File
@@ -2,10 +2,8 @@
//! handle deep hierarchies (even though it cannot just yet!). //! handle deep hierarchies (even though it cannot just yet!).
use std::sync::LazyLock; use std::sync::LazyLock;
use gpui::{ use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size};
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, use gpui_platform::application;
size,
};
struct Tree {} struct Tree {}
@@ -32,7 +30,7 @@ impl Render for Tree {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
rgb, size, uniform_list, uniform_list,
}; };
use gpui_platform::application;
struct UniformListExample {} struct UniformListExample {}
@@ -36,7 +37,7 @@ impl Render for UniformListExample {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+4 -3
View File
@@ -1,7 +1,8 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Window, WindowBounds, App, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Window, WindowBounds, WindowKind,
WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size, WindowOptions, actions, div, prelude::*, px, rgb, size,
}; };
use gpui_platform::application;
struct SubWindow { struct SubWindow {
custom_titlebar: bool, custom_titlebar: bool,
@@ -306,7 +307,7 @@ impl Render for WindowDemo {
actions!(window, [Quit]); actions!(window, [Quit]);
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx); let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
cx.open_window( cx.open_window(
+3 -2
View File
@@ -1,8 +1,9 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, DisplayId, Hsla, Pixels, SharedString, Size, Window, App, Bounds, Context, DisplayId, Hsla, Pixels, SharedString, Size, Window,
WindowBackgroundAppearance, WindowBounds, WindowKind, WindowOptions, div, point, prelude::*, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowOptions, div, point, prelude::*,
px, rgb, px, rgb,
}; };
use gpui_platform::application;
struct WindowContent { struct WindowContent {
text: SharedString, text: SharedString,
@@ -68,7 +69,7 @@ fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> Window
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
// Create several new windows, positioned in the top right corner of each screen // Create several new windows, positioned in the top right corner of each screen
let size = Size { let size = Size {
width: px(350.), width: px(350.),
+6 -5
View File
@@ -1,9 +1,10 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton, App, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton, Pixels,
Pixels, Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds, Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
WindowDecorations, WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size, WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black,
transparent_black, white, white,
}; };
use gpui_platform::application;
struct WindowShadow {} struct WindowShadow {}
@@ -203,7 +204,7 @@ fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> O
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx); let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
+5 -5
View File
@@ -7,7 +7,7 @@
//! # struct Counter { //! # struct Counter {
//! # count: usize, //! # count: usize,
//! # } //! # }
//! Application::new().run(|cx: &mut App| { //! gpui_platform::application().run(|cx: &mut App| {
//! let _counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 }); //! let _counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
//! // ... //! // ...
//! }); //! });
@@ -22,7 +22,7 @@
//! # struct Counter { //! # struct Counter {
//! # count: usize, //! # count: usize,
//! # } //! # }
//! Application::new().run(|cx: &mut App| { //! gpui_platform::application().run(|cx: &mut App| {
//! let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 }); //! let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
//! // Call `update` to access the model's state. //! // Call `update` to access the model's state.
//! counter.update(cx, |counter: &mut Counter, _cx: &mut Context<Counter>| { //! counter.update(cx, |counter: &mut Counter, _cx: &mut Context<Counter>| {
@@ -42,7 +42,7 @@
//! # struct Counter { //! # struct Counter {
//! # count: usize, //! # count: usize,
//! # } //! # }
//! Application::new().run(|cx: &mut App| { //! gpui_platform::application().run(|cx: &mut App| {
//! let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 }); //! let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
//! counter.update(cx, |counter, cx| { //! counter.update(cx, |counter, cx| {
//! counter.count += 1; //! counter.count += 1;
@@ -60,7 +60,7 @@
//! # struct Counter { //! # struct Counter {
//! # count: usize, //! # count: usize,
//! # } //! # }
//! Application::new().run(|cx: &mut App| { //! gpui_platform::application().run(|cx: &mut App| {
//! let first_counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 }); //! let first_counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
//! //!
//! let second_counter = cx.new(|cx: &mut Context<Counter>| { //! let second_counter = cx.new(|cx: &mut Context<Counter>| {
@@ -114,7 +114,7 @@
//! # increment: usize, //! # increment: usize,
//! # } //! # }
//! # impl EventEmitter<CounterChangeEvent> for Counter {} //! # impl EventEmitter<CounterChangeEvent> for Counter {}
//! Application::new().run(|cx: &mut App| { //! gpui_platform::application().run(|cx: &mut App| {
//! let first_counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 }); //! let first_counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
//! //!
//! let second_counter = cx.new(|cx: &mut Context<Counter>| { //! let second_counter = cx.new(|cx: &mut Context<Counter>| {
+4 -19
View File
@@ -46,7 +46,7 @@ use crate::{
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem,
ThermalState, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, ThermalState, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors}, colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus, hash, init_app_menus,
}; };
mod async_context; mod async_context;
@@ -132,25 +132,10 @@ pub struct Application(Rc<AppCell>);
/// Represents an application before it is fully launched. Once your app is /// Represents an application before it is fully launched. Once your app is
/// configured, you'll start the app with `App::run`. /// configured, you'll start the app with `App::run`.
impl Application { impl Application {
/// Builds an app with the given asset source. /// Builds an app with a caller-provided platform implementation.
#[allow(clippy::new_without_default)] pub fn with_platform(platform: Rc<dyn Platform>) -> Self {
pub fn new() -> Self {
#[cfg(any(test, feature = "test-support"))]
log::info!("GPUI was compiled in test mode");
Self(App::new_app( Self(App::new_app(
current_platform(false), platform,
Arc::new(()),
Arc::new(NullHttpClient),
))
}
/// Build an app in headless mode. This prevents opening windows,
/// but makes it possible to run an application in an context like
/// SSH, where GUI applications are not allowed.
pub fn headless() -> Self {
Self(App::new_app(
current_platform(true),
Arc::new(()), Arc::new(()),
Arc::new(NullHttpClient), Arc::new(NullHttpClient),
)) ))
+7 -119
View File
@@ -42,15 +42,18 @@ impl VisualTestAppContext {
/// ///
/// Note: This uses a no-op asset source, so SVG icons won't render. /// Note: This uses a no-op asset source, so SVG icons won't render.
/// Use `with_asset_source` to provide real assets for icon rendering. /// Use `with_asset_source` to provide real assets for icon rendering.
pub fn new() -> Self { pub fn new(platform: Rc<dyn Platform>) -> Self {
Self::with_asset_source(Arc::new(())) Self::with_asset_source(platform, Arc::new(()))
} }
/// Creates a new `VisualTestAppContext` with a custom asset source. /// Creates a new `VisualTestAppContext` with a custom asset source.
/// ///
/// Use this when you need SVG icons to render properly in visual tests. /// Use this when you need SVG icons to render properly in visual tests.
/// Pass the real `Assets` struct to enable icon rendering. /// Pass the real `Assets` struct to enable icon rendering.
pub fn with_asset_source(asset_source: Arc<dyn AssetSource>) -> Self { pub fn with_asset_source(
platform: Rc<dyn Platform>,
asset_source: Arc<dyn AssetSource>,
) -> Self {
// Use a seeded RNG for deterministic behavior // Use a seeded RNG for deterministic behavior
let seed = std::env::var("SEED") let seed = std::env::var("SEED")
.ok() .ok()
@@ -59,7 +62,7 @@ impl VisualTestAppContext {
// Create a visual test platform that combines real Mac rendering // Create a visual test platform that combines real Mac rendering
// with controllable TestDispatcher for deterministic task scheduling // with controllable TestDispatcher for deterministic task scheduling
let platform = Rc::new(VisualTestPlatform::new(seed)); let platform = Rc::new(VisualTestPlatform::new(platform, seed));
// Get the dispatcher and executors from the platform // Get the dispatcher and executors from the platform
let dispatcher = platform.dispatcher().clone(); let dispatcher = platform.dispatcher().clone();
@@ -391,12 +394,6 @@ impl VisualTestAppContext {
} }
} }
impl Default for VisualTestAppContext {
fn default() -> Self {
Self::new()
}
}
impl AppContext for VisualTestAppContext { impl AppContext for VisualTestAppContext {
fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> { fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
let mut app = self.app.borrow_mut(); let mut app = self.app.borrow_mut();
@@ -476,112 +473,3 @@ impl AppContext for VisualTestAppContext {
callback(app.global::<G>(), &app) callback(app.global::<G>(), &app)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::Empty;
use std::cell::RefCell;
// Note: All VisualTestAppContext tests are ignored by default because they require
// the macOS main thread. Standard Rust tests run on worker threads, which causes
// SIGABRT when interacting with macOS AppKit/Cocoa APIs.
//
// To run these tests, use:
// cargo test -p gpui visual_test_context -- --ignored --test-threads=1
#[test]
#[ignore] // Requires macOS main thread
fn test_foreground_tasks_run_with_run_until_parked() {
let mut cx = VisualTestAppContext::new();
let task_ran = Rc::new(RefCell::new(false));
// Spawn a foreground task via the App's spawn method
// This should use our TestDispatcher, not the MacDispatcher
{
let task_ran = task_ran.clone();
cx.update(|cx| {
cx.spawn(async move |_| {
*task_ran.borrow_mut() = true;
})
.detach();
});
}
// The task should not have run yet
assert!(!*task_ran.borrow());
// Run until parked should execute the foreground task
cx.run_until_parked();
// Now the task should have run
assert!(*task_ran.borrow());
}
#[test]
#[ignore] // Requires macOS main thread
fn test_advance_clock_triggers_delayed_tasks() {
let mut cx = VisualTestAppContext::new();
let task_ran = Rc::new(RefCell::new(false));
// Spawn a task that waits for a timer
{
let task_ran = task_ran.clone();
let executor = cx.background_executor.clone();
cx.update(|cx| {
cx.spawn(async move |_| {
executor.timer(Duration::from_millis(500)).await;
*task_ran.borrow_mut() = true;
})
.detach();
});
}
// Run until parked - the task should be waiting on the timer
cx.run_until_parked();
assert!(!*task_ran.borrow());
// Advance clock past the timer duration
cx.advance_clock(Duration::from_millis(600));
// Now the task should have completed
assert!(*task_ran.borrow());
}
#[test]
#[ignore] // Requires macOS main thread - window creation fails on test threads
fn test_window_spawn_uses_test_dispatcher() {
let mut cx = VisualTestAppContext::new();
let task_ran = Rc::new(RefCell::new(false));
let window = cx
.open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
.expect("Failed to open window");
// Spawn a task via window.spawn - this is the critical test case
// for tooltip behavior, as tooltips use window.spawn for delayed show
{
let task_ran = task_ran.clone();
cx.update_window(window.into(), |_, window, cx| {
window
.spawn(cx, async move |_| {
*task_ran.borrow_mut() = true;
})
.detach();
})
.ok();
}
// The task should not have run yet
assert!(!*task_ran.borrow());
// Run until parked should execute the foreground task spawned via window
cx.run_until_parked();
// Now the task should have run
assert!(*task_ran.borrow());
}
}
+4 -3
View File
@@ -33,9 +33,10 @@ impl AssetSource for () {
pub struct ImageId(pub usize); pub struct ImageId(pub usize);
#[derive(PartialEq, Eq, Hash, Clone)] #[derive(PartialEq, Eq, Hash, Clone)]
pub(crate) struct RenderImageParams { #[expect(missing_docs)]
pub(crate) image_id: ImageId, pub struct RenderImageParams {
pub(crate) frame_index: usize, pub image_id: ImageId,
pub frame_index: usize,
} }
/// A cached and processed image, in BGRA format /// A cached and processed image, in BGRA format
+1 -1
View File
@@ -23,7 +23,7 @@ pub fn rgba(hex: u32) -> Rgba {
} }
/// Swap from RGBA with premultiplied alpha to BGRA /// Swap from RGBA with premultiplied alpha to BGRA
pub(crate) fn swap_rgba_pa_to_bgra(color: &mut [u8]) { pub fn swap_rgba_pa_to_bgra(color: &mut [u8]) {
color.swap(0, 2); color.swap(0, 2);
if color[3] > 0 { if color[3] > 0 {
let a = color[3] as f32 / 255.; let a = color[3] as f32 / 255.;
+13 -3
View File
@@ -1592,7 +1592,7 @@ impl<T: Clone + Debug + Default + PartialEq + Display + Add<T, Output = T>> Disp
impl Size<DevicePixels> { impl Size<DevicePixels> {
/// Converts the size from physical to logical pixels. /// Converts the size from physical to logical pixels.
pub(crate) fn to_pixels(self, scale_factor: f32) -> Size<Pixels> { pub fn to_pixels(self, scale_factor: f32) -> Size<Pixels> {
size( size(
px(self.width.0 as f32 / scale_factor), px(self.width.0 as f32 / scale_factor),
px(self.height.0 as f32 / scale_factor), px(self.height.0 as f32 / scale_factor),
@@ -1602,7 +1602,7 @@ impl Size<DevicePixels> {
impl Size<Pixels> { impl Size<Pixels> {
/// Converts the size from logical to physical pixels. /// Converts the size from logical to physical pixels.
pub(crate) fn to_device_pixels(self, scale_factor: f32) -> Size<DevicePixels> { pub fn to_device_pixels(self, scale_factor: f32) -> Size<DevicePixels> {
size( size(
DevicePixels((self.width.0 * scale_factor).round() as i32), DevicePixels((self.width.0 * scale_factor).round() as i32),
DevicePixels((self.height.0 * scale_factor).round() as i32), DevicePixels((self.height.0 * scale_factor).round() as i32),
@@ -2683,6 +2683,11 @@ impl Pixels {
/// The minimum value that can be represented by `Pixels`. /// The minimum value that can be represented by `Pixels`.
pub const MIN: Pixels = Pixels(f32::MIN); pub const MIN: Pixels = Pixels(f32::MIN);
/// Returns the raw `f32` value of this `Pixels`.
pub fn as_f32(self) -> f32 {
self.0
}
/// Floors the `Pixels` value to the nearest whole number. /// Floors the `Pixels` value to the nearest whole number.
/// ///
/// # Returns /// # Returns
@@ -2964,9 +2969,14 @@ impl From<usize> for DevicePixels {
/// display resolutions. /// display resolutions.
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, DivAssign, PartialEq)] #[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, DivAssign, PartialEq)]
#[repr(transparent)] #[repr(transparent)]
pub struct ScaledPixels(pub(crate) f32); pub struct ScaledPixels(pub f32);
impl ScaledPixels { impl ScaledPixels {
/// Returns the raw `f32` value of this `ScaledPixels`.
pub fn as_f32(self) -> f32 {
self.0
}
/// Floors the `ScaledPixels` value to the nearest whole number. /// Floors the `ScaledPixels` value to the nearest whole number.
/// ///
/// # Returns /// # Returns
+7 -4
View File
@@ -5,7 +5,8 @@
#![allow(unused_mut)] // False positives in platform specific code #![allow(unused_mut)] // False positives in platform specific code
extern crate self as gpui; extern crate self as gpui;
#[doc(hidden)]
pub static GPUI_MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");
#[macro_use] #[macro_use]
mod action; mod action;
mod app; mod app;
@@ -32,9 +33,11 @@ mod keymap;
mod path_builder; mod path_builder;
mod platform; mod platform;
pub mod prelude; pub mod prelude;
mod profiler; /// Profiling utilities for task timing and thread performance tracking.
pub mod profiler;
#[cfg(any(target_os = "windows", target_os = "linux"))] #[cfg(any(target_os = "windows", target_os = "linux"))]
mod queue; #[expect(missing_docs)]
pub mod queue;
mod scene; mod scene;
mod shared_string; mod shared_string;
mod shared_uri; mod shared_uri;
@@ -94,7 +97,7 @@ pub use path_builder::*;
pub use platform::*; pub use platform::*;
pub use profiler::*; pub use profiler::*;
#[cfg(any(target_os = "windows", target_os = "linux"))] #[cfg(any(target_os = "windows", target_os = "linux"))]
pub(crate) use queue::{PriorityQueueReceiver, PriorityQueueSender}; pub use queue::{PriorityQueueReceiver, PriorityQueueSender};
pub use refineable::*; pub use refineable::*;
pub use scene::*; pub use scene::*;
pub use shared_string::*; pub use shared_string::*;
+1 -1
View File
@@ -557,7 +557,7 @@ impl Deref for MouseExitEvent {
/// A collection of paths from the platform, such as from a file drop. /// A collection of paths from the platform, such as from a file drop.
#[derive(Debug, Clone, Default, Eq, PartialEq)] #[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>); pub struct ExternalPaths(pub SmallVec<[PathBuf; 2]>);
impl ExternalPaths { impl ExternalPaths {
/// Convert this collection of paths into a slice. /// Convert this collection of paths into a slice.
+1 -1
View File
@@ -262,7 +262,7 @@ impl KeyBindingContextPredicate {
/// Eval a predicate against a set of contexts, arranged from lowest to highest. /// Eval a predicate against a set of contexts, arranged from lowest to highest.
#[allow(unused)] #[allow(unused)]
pub(crate) fn eval(&self, contexts: &[KeyContext]) -> bool { pub fn eval(&self, contexts: &[KeyContext]) -> bool {
self.eval_inner(contexts, contexts) self.eval_inner(contexts, contexts)
} }
+106 -133
View File
@@ -2,17 +2,9 @@ mod app_menu;
mod keyboard; mod keyboard;
mod keystroke; mod keystroke;
#[cfg(any(target_os = "linux", target_os = "freebsd"))] #[cfg(all(target_os = "linux", feature = "wayland"))]
mod linux; #[expect(missing_docs)]
pub mod layer_shell;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(all(
any(target_os = "linux", target_os = "freebsd"),
any(feature = "wayland", feature = "x11")
))]
mod wgpu;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
mod test; mod test;
@@ -20,14 +12,21 @@ mod test;
#[cfg(all(target_os = "macos", any(test, feature = "test-support")))] #[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
mod visual_test; mod visual_test;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(all( #[cfg(all(
feature = "screen-capture", feature = "screen-capture",
any(target_os = "windows", target_os = "linux", target_os = "freebsd",) any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
))] ))]
pub(crate) mod scap_screen_capture; pub mod scap_screen_capture;
#[cfg(all(
any(target_os = "windows", target_os = "linux"),
feature = "screen-capture"
))]
pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
#[cfg(not(feature = "screen-capture"))]
pub(crate) type PlatformScreenCaptureFrame = ();
#[cfg(all(target_os = "macos", feature = "screen-capture"))]
pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
use crate::{ use crate::{
Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds, Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
@@ -69,17 +68,8 @@ pub use app_menu::*;
pub use keyboard::*; pub use keyboard::*;
pub use keystroke::*; pub use keystroke::*;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub(crate) use linux::*;
#[cfg(target_os = "macos")]
pub(crate) use mac::*;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub(crate) use test::*; pub(crate) use test::*;
#[cfg(target_os = "windows")]
pub(crate) use windows::*;
#[cfg(all(target_os = "linux", feature = "wayland"))]
pub use linux::layer_shell;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream}; pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
@@ -87,52 +77,8 @@ pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream}
#[cfg(all(target_os = "macos", any(test, feature = "test-support")))] #[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
pub use visual_test::VisualTestPlatform; pub use visual_test::VisualTestPlatform;
/// Returns a background executor for the current platform.
pub fn background_executor() -> BackgroundExecutor {
current_platform(true).background_executor()
}
#[cfg(target_os = "macos")]
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
Rc::new(MacPlatform::new(headless))
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
#[cfg(feature = "x11")]
use anyhow::Context as _;
if headless {
return Rc::new(HeadlessClient::new());
}
match guess_compositor() {
#[cfg(feature = "wayland")]
"Wayland" => Rc::new(WaylandClient::new()),
#[cfg(feature = "x11")]
"X11" => Rc::new(
X11Client::new()
.context("Failed to initialize X11 client.")
.unwrap(),
),
"Headless" => Rc::new(HeadlessClient::new()),
_ => unreachable!(),
}
}
#[cfg(target_os = "windows")]
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
Rc::new(
WindowsPlatform::new(headless)
.inspect_err(|err| show_error("Failed to launch", err.to_string()))
.unwrap(),
)
}
/// Return which compositor we're guessing we'll use. /// Return which compositor we're guessing we'll use.
/// Does not attempt to connect to the given compositor /// Does not attempt to connect to the given compositor.
#[cfg(any(target_os = "linux", target_os = "freebsd"))] #[cfg(any(target_os = "linux", target_os = "freebsd"))]
#[inline] #[inline]
pub fn guess_compositor() -> &'static str { pub fn guess_compositor() -> &'static str {
@@ -162,7 +108,8 @@ pub fn guess_compositor() -> &'static str {
} }
} }
pub(crate) trait Platform: 'static { #[expect(missing_docs)]
pub trait Platform: 'static {
fn background_executor(&self) -> BackgroundExecutor; fn background_executor(&self) -> BackgroundExecutor;
fn foreground_executor(&self) -> ForegroundExecutor; fn foreground_executor(&self) -> ForegroundExecutor;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>; fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
@@ -182,16 +129,10 @@ pub(crate) trait Platform: 'static {
None None
} }
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool;
#[cfg(not(feature = "screen-capture"))]
fn is_screen_capture_supported(&self) -> bool { fn is_screen_capture_supported(&self) -> bool {
false false
} }
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(&self)
-> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
#[cfg(not(feature = "screen-capture"))]
fn screen_capture_sources( fn screen_capture_sources(
&self, &self,
) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> { ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
@@ -370,6 +311,19 @@ pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
#[derive(PartialEq, Eq, Hash, Copy, Clone)] #[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct DisplayId(pub(crate) u32); pub struct DisplayId(pub(crate) u32);
impl DisplayId {
/// Create a new `DisplayId` from a raw platform display identifier.
pub fn new(id: u32) -> Self {
Self(id)
}
}
impl From<u32> for DisplayId {
fn from(id: u32) -> Self {
Self(id)
}
}
impl From<DisplayId> for u32 { impl From<DisplayId> for u32 {
fn from(id: DisplayId) -> Self { fn from(id: DisplayId) -> Self {
id.0 id.0
@@ -482,13 +436,16 @@ impl Tiling {
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub(crate) struct RequestFrameOptions { #[expect(missing_docs)]
pub(crate) require_presentation: bool, pub struct RequestFrameOptions {
/// Force refresh of all rendering states when true /// Whether a presentation is required.
pub(crate) force_render: bool, pub require_presentation: bool,
/// Force refresh of all rendering states when true.
pub force_render: bool,
} }
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle { #[expect(missing_docs)]
pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn bounds(&self) -> Bounds<Pixels>; fn bounds(&self) -> Bounds<Pixels>;
fn is_maximized(&self) -> bool; fn is_maximized(&self) -> bool;
fn window_bounds(&self) -> WindowBounds; fn window_bounds(&self) -> WindowBounds;
@@ -558,7 +515,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn set_tabbing_identifier(&self, _identifier: Option<String>) {} fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn get_raw_handle(&self) -> windows::HWND; fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
// Linux specific methods // Linux specific methods
fn inner_window_bounds(&self) -> WindowBounds { fn inner_window_bounds(&self) -> WindowBounds {
@@ -603,17 +560,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
pub type RunnableVariant = Runnable<RunnableMeta>; pub type RunnableVariant = Runnable<RunnableMeta>;
#[doc(hidden)] #[doc(hidden)]
pub struct TimerResolutionGuard { pub type TimerResolutionGuard = util::Deferred<Box<dyn FnOnce() + Send>>;
cleanup: Option<Box<dyn FnOnce() + Send>>,
}
impl Drop for TimerResolutionGuard {
fn drop(&mut self) {
if let Some(cleanup) = self.cleanup.take() {
cleanup();
}
}
}
/// This type is public so that our test macro can generate and use it, but it should not /// This type is public so that our test macro can generate and use it, but it should not
/// be considered part of our public API. /// be considered part of our public API.
@@ -632,7 +579,7 @@ pub trait PlatformDispatcher: Send + Sync {
} }
fn increase_timer_resolution(&self) -> TimerResolutionGuard { fn increase_timer_resolution(&self) -> TimerResolutionGuard {
TimerResolutionGuard { cleanup: None } util::defer(Box::new(|| {}))
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@@ -641,7 +588,8 @@ pub trait PlatformDispatcher: Send + Sync {
} }
} }
pub(crate) trait PlatformTextSystem: Send + Sync { #[expect(missing_docs)]
pub trait PlatformTextSystem: Send + Sync {
fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>; fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
fn all_font_names(&self) -> Vec<String>; fn all_font_names(&self) -> Vec<String>;
fn font_id(&self, descriptor: &Font) -> Result<FontId>; fn font_id(&self, descriptor: &Font) -> Result<FontId>;
@@ -660,8 +608,10 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
-> TextRenderingMode; -> TextRenderingMode;
} }
pub(crate) struct NoopTextSystem; #[expect(missing_docs)]
pub struct NoopTextSystem;
#[expect(missing_docs)]
impl NoopTextSystem { impl NoopTextSystem {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new() -> Self { pub fn new() -> Self {
@@ -791,8 +741,9 @@ impl PlatformTextSystem for NoopTextSystem {
// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp // Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT license. // Licensed under the MIT license.
/// Compute gamma correction ratios for subpixel text rendering.
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] { pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [ const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
[0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0 [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
[0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1 [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
@@ -824,7 +775,8 @@ pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
} }
#[derive(PartialEq, Eq, Hash, Clone)] #[derive(PartialEq, Eq, Hash, Clone)]
pub(crate) enum AtlasKey { #[expect(missing_docs)]
pub enum AtlasKey {
Glyph(RenderGlyphParams), Glyph(RenderGlyphParams),
Svg(RenderSvgParams), Svg(RenderSvgParams),
Image(RenderImageParams), Image(RenderImageParams),
@@ -838,7 +790,8 @@ impl AtlasKey {
), ),
allow(dead_code) allow(dead_code)
)] )]
pub(crate) fn texture_kind(&self) -> AtlasTextureKind { /// Returns the texture kind for this atlas key.
pub fn texture_kind(&self) -> AtlasTextureKind {
match self { match self {
AtlasKey::Glyph(params) => { AtlasKey::Glyph(params) => {
if params.is_emoji { if params.is_emoji {
@@ -873,7 +826,8 @@ impl From<RenderImageParams> for AtlasKey {
} }
} }
pub(crate) trait PlatformAtlas: Send + Sync { #[expect(missing_docs)]
pub trait PlatformAtlas: Send + Sync {
fn get_or_insert_with<'a>( fn get_or_insert_with<'a>(
&self, &self,
key: &AtlasKey, key: &AtlasKey,
@@ -882,9 +836,10 @@ pub(crate) trait PlatformAtlas: Send + Sync {
fn remove(&self, key: &AtlasKey); fn remove(&self, key: &AtlasKey);
} }
struct AtlasTextureList<T> { #[doc(hidden)]
textures: Vec<Option<T>>, pub struct AtlasTextureList<T> {
free_list: Vec<usize>, pub textures: Vec<Option<T>>,
pub free_list: Vec<usize>,
} }
impl<T> Default for AtlasTextureList<T> { impl<T> Default for AtlasTextureList<T> {
@@ -906,32 +861,40 @@ impl<T> ops::Index<usize> for AtlasTextureList<T> {
impl<T> AtlasTextureList<T> { impl<T> AtlasTextureList<T> {
#[allow(unused)] #[allow(unused)]
fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> { pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
self.free_list.clear(); self.free_list.clear();
self.textures.drain(..) self.textures.drain(..)
} }
#[allow(dead_code)] #[allow(dead_code)]
fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> { pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
self.textures.iter_mut().flatten() self.textures.iter_mut().flatten()
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)] #[repr(C)]
pub(crate) struct AtlasTile { #[expect(missing_docs)]
pub(crate) texture_id: AtlasTextureId, pub struct AtlasTile {
pub(crate) tile_id: TileId, /// The texture this tile belongs to.
pub(crate) padding: u32, pub texture_id: AtlasTextureId,
pub(crate) bounds: Bounds<DevicePixels>, /// The unique ID of this tile within its texture.
pub tile_id: TileId,
/// Padding around the tile content in pixels.
pub padding: u32,
/// The bounds of this tile within the texture.
pub bounds: Bounds<DevicePixels>,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(C)] #[repr(C)]
pub(crate) struct AtlasTextureId { #[expect(missing_docs)]
pub struct AtlasTextureId {
// We use u32 instead of usize for Metal Shader Language compatibility // We use u32 instead of usize for Metal Shader Language compatibility
pub(crate) index: u32, /// The index of this texture in the atlas.
pub(crate) kind: AtlasTextureKind, pub index: u32,
/// The kind of content stored in this texture.
pub kind: AtlasTextureKind,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -943,7 +906,8 @@ pub(crate) struct AtlasTextureId {
), ),
allow(dead_code) allow(dead_code)
)] )]
pub(crate) enum AtlasTextureKind { #[expect(missing_docs)]
pub enum AtlasTextureKind {
Monochrome = 0, Monochrome = 0,
Polychrome = 1, Polychrome = 1,
Subpixel = 2, Subpixel = 2,
@@ -951,7 +915,8 @@ pub(crate) enum AtlasTextureKind {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)] #[repr(C)]
pub(crate) struct TileId(pub(crate) u32); #[expect(missing_docs)]
pub struct TileId(pub u32);
impl From<etagere::AllocId> for TileId { impl From<etagere::AllocId> for TileId {
fn from(id: etagere::AllocId) -> Self { fn from(id: etagere::AllocId) -> Self {
@@ -965,11 +930,13 @@ impl From<TileId> for etagere::AllocId {
} }
} }
pub(crate) struct PlatformInputHandler { #[expect(missing_docs)]
pub struct PlatformInputHandler {
cx: AsyncWindowContext, cx: AsyncWindowContext,
handler: Box<dyn InputHandler>, handler: Box<dyn InputHandler>,
} }
#[expect(missing_docs)]
#[cfg_attr( #[cfg_attr(
all( all(
any(target_os = "linux", target_os = "freebsd"), any(target_os = "linux", target_os = "freebsd"),
@@ -982,7 +949,7 @@ impl PlatformInputHandler {
Self { cx, handler } Self { cx, handler }
} }
fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> { pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
self.cx self.cx
.update(|window, cx| { .update(|window, cx| {
self.handler self.handler
@@ -993,7 +960,7 @@ impl PlatformInputHandler {
} }
#[cfg_attr(target_os = "windows", allow(dead_code))] #[cfg_attr(target_os = "windows", allow(dead_code))]
fn marked_text_range(&mut self) -> Option<Range<usize>> { pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
self.cx self.cx
.update(|window, cx| self.handler.marked_text_range(window, cx)) .update(|window, cx| self.handler.marked_text_range(window, cx))
.ok() .ok()
@@ -1004,7 +971,7 @@ impl PlatformInputHandler {
any(target_os = "linux", target_os = "freebsd", target_os = "windows"), any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
allow(dead_code) allow(dead_code)
)] )]
fn text_for_range( pub fn text_for_range(
&mut self, &mut self,
range_utf16: Range<usize>, range_utf16: Range<usize>,
adjusted: &mut Option<Range<usize>>, adjusted: &mut Option<Range<usize>>,
@@ -1018,7 +985,7 @@ impl PlatformInputHandler {
.flatten() .flatten()
} }
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) { pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
self.cx self.cx
.update(|window, cx| { .update(|window, cx| {
self.handler self.handler
@@ -1047,13 +1014,13 @@ impl PlatformInputHandler {
} }
#[cfg_attr(target_os = "windows", allow(dead_code))] #[cfg_attr(target_os = "windows", allow(dead_code))]
fn unmark_text(&mut self) { pub fn unmark_text(&mut self) {
self.cx self.cx
.update(|window, cx| self.handler.unmark_text(window, cx)) .update(|window, cx| self.handler.unmark_text(window, cx))
.ok(); .ok();
} }
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> { pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
self.cx self.cx
.update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx)) .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
.ok() .ok()
@@ -1061,11 +1028,11 @@ impl PlatformInputHandler {
} }
#[allow(dead_code)] #[allow(dead_code)]
fn apple_press_and_hold_enabled(&mut self) -> bool { pub fn apple_press_and_hold_enabled(&mut self) -> bool {
self.handler.apple_press_and_hold_enabled() self.handler.apple_press_and_hold_enabled()
} }
pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) { pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
self.handler.replace_text_in_range(None, input, window, cx); self.handler.replace_text_in_range(None, input, window, cx);
} }
@@ -1091,7 +1058,7 @@ impl PlatformInputHandler {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool { pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
self.handler.accepts_text_input(window, cx) self.handler.accepts_text_input(window, cx)
} }
} }
@@ -1268,7 +1235,8 @@ pub struct WindowOptions {
), ),
allow(dead_code) allow(dead_code)
)] )]
pub(crate) struct WindowParams { #[expect(missing_docs)]
pub struct WindowParams {
pub bounds: Bounds<Pixels>, pub bounds: Bounds<Pixels>,
/// The titlebar configuration of the window /// The titlebar configuration of the window
@@ -1523,8 +1491,9 @@ impl PromptButton {
PromptButton::Cancel(label.into()) PromptButton::Cancel(label.into())
} }
/// Returns true if this button is a cancel button.
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn is_cancel(&self) -> bool { pub fn is_cancel(&self) -> bool {
matches!(self, PromptButton::Cancel(_)) matches!(self, PromptButton::Cancel(_))
} }
@@ -1642,7 +1611,8 @@ pub enum CursorStyle {
/// A clipboard item that should be copied to the clipboard /// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem { pub struct ClipboardItem {
entries: Vec<ClipboardEntry>, /// The entries in this clipboard item.
pub entries: Vec<ClipboardEntry>,
} }
/// Either a ClipboardString or a ClipboardImage /// Either a ClipboardString or a ClipboardImage
@@ -1842,7 +1812,7 @@ pub struct Image {
/// The raw image bytes /// The raw image bytes
pub bytes: Vec<u8>, pub bytes: Vec<u8>,
/// The unique ID for the image /// The unique ID for the image
id: u64, pub id: u64,
} }
impl Hash for Image { impl Hash for Image {
@@ -1960,8 +1930,10 @@ impl Image {
/// A clipboard item that should be copied to the clipboard /// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardString { pub struct ClipboardString {
pub(crate) text: String, /// The text content.
pub(crate) metadata: Option<String>, pub text: String,
/// Optional metadata associated with this clipboard string.
pub metadata: Option<String>,
} }
impl ClipboardString { impl ClipboardString {
@@ -2001,7 +1973,8 @@ impl ClipboardString {
} }
#[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))] #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
pub(crate) fn text_hash(text: &str) -> u64 { /// Compute a hash of the given text for clipboard change detection.
pub fn text_hash(text: &str) -> u64 {
let mut hasher = SeaHasher::new(); let mut hasher = SeaHasher::new();
text.hash(&mut hasher); text.hash(&mut hasher);
hasher.finish() hasher.finish()
+2 -1
View File
@@ -265,7 +265,8 @@ impl Keystroke {
impl KeybindingKeystroke { impl KeybindingKeystroke {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub(crate) fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self { #[expect(missing_docs)]
pub fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
KeybindingKeystroke { KeybindingKeystroke {
inner, inner,
display_modifiers, display_modifiers,
@@ -1,6 +1,5 @@
use bitflags::bitflags; use bitflags::bitflags;
use thiserror::Error; use thiserror::Error;
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
use crate::Pixels; use crate::Pixels;
@@ -22,17 +21,6 @@ pub enum Layer {
Overlay, Overlay,
} }
impl From<Layer> for zwlr_layer_shell_v1::Layer {
fn from(layer: Layer) -> Self {
match layer {
Layer::Background => Self::Background,
Layer::Bottom => Self::Bottom,
Layer::Top => Self::Top,
Layer::Overlay => Self::Overlay,
}
}
}
bitflags! { bitflags! {
/// Screen anchor point for layer_shell surfaces. These can be used in any combination, e.g. /// Screen anchor point for layer_shell surfaces. These can be used in any combination, e.g.
/// specifying `Anchor::LEFT | Anchor::RIGHT` will stretch the surface across the width of the /// specifying `Anchor::LEFT | Anchor::RIGHT` will stretch the surface across the width of the
@@ -50,12 +38,6 @@ bitflags! {
} }
} }
impl From<Anchor> for zwlr_layer_surface_v1::Anchor {
fn from(anchor: Anchor) -> Self {
Self::from_bits_truncate(anchor.bits())
}
}
/// Keyboard interactivity mode for the layer_shell surfaces. /// Keyboard interactivity mode for the layer_shell surfaces.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum KeyboardInteractivity { pub enum KeyboardInteractivity {
@@ -72,16 +54,6 @@ pub enum KeyboardInteractivity {
OnDemand, OnDemand,
} }
impl From<KeyboardInteractivity> for zwlr_layer_surface_v1::KeyboardInteractivity {
fn from(value: KeyboardInteractivity) -> Self {
match value {
KeyboardInteractivity::None => Self::None,
KeyboardInteractivity::Exclusive => Self::Exclusive,
KeyboardInteractivity::OnDemand => Self::OnDemand,
}
}
}
/// Options for creating a layer_shell window. /// Options for creating a layer_shell window.
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LayerShellOptions { pub struct LayerShellOptions {
-32
View File
@@ -1,32 +0,0 @@
mod dispatcher;
mod headless;
mod keyboard;
mod platform;
#[cfg(any(feature = "wayland", feature = "x11"))]
mod text_system;
#[cfg(feature = "wayland")]
mod wayland;
#[cfg(feature = "x11")]
mod x11;
#[cfg(any(feature = "wayland", feature = "x11"))]
mod xdg_desktop_portal;
pub(crate) use dispatcher::*;
pub(crate) use headless::*;
pub(crate) use keyboard::*;
pub(crate) use platform::*;
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) use text_system::*;
#[cfg(feature = "wayland")]
pub(crate) use wayland::*;
#[cfg(feature = "x11")]
pub(crate) use x11::*;
#[cfg(all(feature = "screen-capture", any(feature = "wayland", feature = "x11")))]
pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
#[cfg(not(all(feature = "screen-capture", any(feature = "wayland", feature = "x11"))))]
pub(crate) type PlatformScreenCaptureFrame = ();
#[cfg(feature = "wayland")]
pub use wayland::layer_shell;
-49
View File
@@ -1,49 +0,0 @@
mod client;
mod clipboard;
mod cursor;
mod display;
mod serial;
mod window;
/// Contains Types for configuring layer_shell surfaces.
pub mod layer_shell;
pub(crate) use client::*;
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
use crate::CursorStyle;
impl CursorStyle {
pub(super) fn to_shape(self) -> Shape {
match self {
CursorStyle::Arrow => Shape::Default,
CursorStyle::IBeam => Shape::Text,
CursorStyle::Crosshair => Shape::Crosshair,
CursorStyle::ClosedHand => Shape::Grabbing,
CursorStyle::OpenHand => Shape::Grab,
CursorStyle::PointingHand => Shape::Pointer,
CursorStyle::ResizeLeft => Shape::WResize,
CursorStyle::ResizeRight => Shape::EResize,
CursorStyle::ResizeLeftRight => Shape::EwResize,
CursorStyle::ResizeUp => Shape::NResize,
CursorStyle::ResizeDown => Shape::SResize,
CursorStyle::ResizeUpDown => Shape::NsResize,
CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize,
CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize,
CursorStyle::ResizeColumn => Shape::ColResize,
CursorStyle::ResizeRow => Shape::RowResize,
CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
CursorStyle::OperationNotAllowed => Shape::NotAllowed,
CursorStyle::DragLink => Shape::Alias,
CursorStyle::DragCopy => Shape::Copy,
CursorStyle::ContextualMenu => Shape::ContextMenu,
CursorStyle::None => {
#[cfg(debug_assertions)]
panic!("CursorStyle::None should be handled separately in the client");
#[cfg(not(debug_assertions))]
Shape::Default
}
}
}
}
-387
View File
@@ -1,387 +0,0 @@
use crate::{
Scene,
geometry::{
rect::RectF,
vector::{Vector2F, vec2f},
},
platform::{
self, Event, FontSystem, WindowBounds,
mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer},
},
};
use cocoa::{
appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
base::{YES, id, nil},
foundation::{NSPoint, NSRect, NSSize},
};
use ctor::ctor;
use foreign_types::ForeignTypeRef;
use objc::{
class,
declare::ClassDecl,
msg_send,
rc::StrongPtr,
runtime::{Class, Object, Protocol, Sel},
sel, sel_impl,
};
use std::{
cell::RefCell,
ffi::c_void,
ptr,
rc::{Rc, Weak},
sync::Arc,
};
use super::screen::Screen;
static mut VIEW_CLASS: *const Class = ptr::null();
const STATE_IVAR: &str = "state";
#[ctor]
unsafe fn build_classes() {
VIEW_CLASS = {
let mut decl = ClassDecl::new("GPUIStatusItemView", class!(NSView)).unwrap();
decl.add_ivar::<*mut c_void>(STATE_IVAR);
decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
decl.add_method(
sel!(mouseDown:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseUp:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(rightMouseDown:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(rightMouseUp:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(otherMouseDown:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(otherMouseUp:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseMoved:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseDragged:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(scrollWheel:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(flagsChanged:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(makeBackingLayer),
make_backing_layer as extern "C" fn(&Object, Sel) -> id,
);
decl.add_method(
sel!(viewDidChangeEffectiveAppearance),
view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
);
decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
decl.add_method(
sel!(displayLayer:),
display_layer as extern "C" fn(&Object, Sel, id),
);
decl.register()
};
}
pub struct StatusItem(Rc<RefCell<StatusItemState>>);
struct StatusItemState {
native_item: StrongPtr,
native_view: StrongPtr,
renderer: Renderer,
scene: Option<Scene>,
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
appearance_changed_callback: Option<Box<dyn FnMut()>>,
}
impl StatusItem {
pub fn add(fonts: Arc<dyn FontSystem>) -> Self {
unsafe {
let renderer = Renderer::new(false, fonts);
let status_bar = NSStatusBar::systemStatusBar(nil);
let native_item =
StrongPtr::retain(status_bar.statusItemWithLength_(NSSquareStatusItemLength));
let button = native_item.button();
let _: () = msg_send![button, setHidden: YES];
let native_view = msg_send![VIEW_CLASS, alloc];
let state = Rc::new(RefCell::new(StatusItemState {
native_item,
native_view: StrongPtr::new(native_view),
renderer,
scene: None,
event_callback: None,
appearance_changed_callback: None,
}));
let parent_view = button.superview().superview();
NSView::initWithFrame_(
native_view,
NSRect::new(NSPoint::new(0., 0.), NSView::frame(parent_view).size),
);
(*native_view).set_ivar(
STATE_IVAR,
Weak::into_raw(Rc::downgrade(&state)) as *const c_void,
);
native_view.setWantsBestResolutionOpenGLSurface_(YES);
native_view.setWantsLayer(YES);
let _: () = msg_send![
native_view,
setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
];
parent_view.addSubview_(native_view);
{
let state = state.borrow();
let layer = state.renderer.layer();
let scale_factor = state.scale_factor();
let size = state.content_size() * scale_factor;
layer.set_contents_scale(scale_factor.into());
layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
}
Self(state)
}
}
}
impl platform::Window for StatusItem {
fn bounds(&self) -> WindowBounds {
self.0.borrow().bounds()
}
fn content_size(&self) -> Vector2F {
self.0.borrow().content_size()
}
fn scale_factor(&self) -> f32 {
self.0.borrow().scale_factor()
}
fn appearance(&self) -> platform::Appearance {
unsafe {
let appearance: id =
msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
platform::Appearance::from_native(appearance)
}
}
fn screen(&self) -> Rc<dyn platform::Screen> {
unsafe {
Rc::new(Screen {
native_screen: self.0.borrow().native_window().screen(),
})
}
}
fn mouse_position(&self) -> Vector2F {
unimplemented!()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn set_input_handler(&mut self, _: Box<dyn platform::InputHandler>) {}
fn prompt(
&self,
_: crate::platform::PromptLevel,
_: &str,
_: &[&str],
) -> postage::oneshot::Receiver<usize> {
unimplemented!()
}
fn activate(&self) {
unimplemented!()
}
fn set_title(&mut self, _: &str) {
unimplemented!()
}
fn set_edited(&mut self, _: bool) {
unimplemented!()
}
fn show_character_palette(&self) {
unimplemented!()
}
fn minimize(&self) {
unimplemented!()
}
fn zoom(&self) {
unimplemented!()
}
fn present_scene(&mut self, scene: Scene) {
self.0.borrow_mut().scene = Some(scene);
unsafe {
let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
}
}
fn toggle_fullscreen(&self) {
unimplemented!()
}
fn on_event(&mut self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
self.0.borrow_mut().event_callback = Some(callback);
}
fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
fn on_moved(&mut self, _: Box<dyn FnMut()>) {}
fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
self.0.borrow_mut().appearance_changed_callback = Some(callback);
}
fn is_topmost_for_position(&self, _: Vector2F) -> bool {
true
}
}
impl StatusItemState {
fn bounds(&self) -> WindowBounds {
unsafe {
let window: id = self.native_window();
let screen_frame = window.screen().visibleFrame();
let window_frame = NSWindow::frame(window);
let origin = vec2f(
window_frame.origin.x as f32,
(window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
as f32,
);
let size = vec2f(
window_frame.size.width as f32,
window_frame.size.height as f32,
);
WindowBounds::Fixed(RectF::new(origin, size))
}
}
fn content_size(&self) -> Vector2F {
unsafe {
let NSSize { width, height, .. } =
NSView::frame(self.native_item.button().superview().superview()).size;
vec2f(width as f32, height as f32)
}
}
fn scale_factor(&self) -> f32 {
unsafe {
let window: id = msg_send![self.native_item.button(), window];
NSScreen::backingScaleFactor(window.screen()) as f32
}
}
pub fn native_window(&self) -> id {
unsafe { msg_send![self.native_item.button(), window] }
}
}
extern "C" fn dealloc_view(this: &Object, _: Sel) {
unsafe {
drop_state(this);
let _: () = msg_send![super(this, class!(NSView)), dealloc];
}
}
extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
unsafe {
if let Some(state) = get_state(this).upgrade() {
let mut state_borrow = state.as_ref().borrow_mut();
if let Some(event) =
Event::from_native(native_event, Some(state_borrow.content_size().y()))
{
if let Some(mut callback) = state_borrow.event_callback.take() {
drop(state_borrow);
callback(event);
state.borrow_mut().event_callback = Some(callback);
}
}
}
}
}
extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
if let Some(state) = unsafe { get_state(this).upgrade() } {
let state = state.borrow();
state.renderer.layer().as_ptr() as id
} else {
nil
}
}
extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
unsafe {
if let Some(state) = get_state(this).upgrade() {
let mut state = state.borrow_mut();
if let Some(scene) = state.scene.take() {
state.renderer.render(&scene);
}
}
}
}
extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
unsafe {
if let Some(state) = get_state(this).upgrade() {
let mut state_borrow = state.as_ref().borrow_mut();
if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
drop(state_borrow);
callback();
state.borrow_mut().appearance_changed_callback = Some(callback);
}
}
}
}
unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
let weak2 = weak1.clone();
let _ = Weak::into_raw(weak1);
weak2
}
unsafe fn drop_state(object: &Object) {
let raw: *const c_void = *object.get_ivar(STATE_IVAR);
Weak::from_raw(raw as *const RefCell<StatusItemState>);
}
@@ -1,37 +0,0 @@
use crate::WindowAppearance;
use cocoa::{
appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
base::id,
foundation::NSString,
};
use objc::{msg_send, sel, sel_impl};
use std::ffi::CStr;
impl WindowAppearance {
pub(crate) unsafe fn from_native(appearance: id) -> Self {
let name: id = msg_send![appearance, name];
unsafe {
if name == NSAppearanceNameVibrantLight {
Self::VibrantLight
} else if name == NSAppearanceNameVibrantDark {
Self::VibrantDark
} else if name == NSAppearanceNameAqua {
Self::Light
} else if name == NSAppearanceNameDarkAqua {
Self::Dark
} else {
println!(
"unknown appearance: {:?}",
CStr::from_ptr(name.UTF8String())
);
Self::Light
}
}
}
}
#[link(name = "AppKit", kind = "framework")]
unsafe extern "C" {
pub static NSAppearanceNameAqua: id;
pub static NSAppearanceNameDarkAqua: id;
}
@@ -15,7 +15,7 @@ use std::sync::atomic::{self, AtomicBool};
/// `scap_default_target_source` should be used instead on Wayland, since `scap_screen_sources` /// `scap_default_target_source` should be used instead on Wayland, since `scap_screen_sources`
/// won't return any results. /// won't return any results.
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn scap_screen_sources( pub fn scap_screen_sources(
foreground_executor: &ForegroundExecutor, foreground_executor: &ForegroundExecutor,
) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> { ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
let (sources_tx, sources_rx) = oneshot::channel(); let (sources_tx, sources_rx) = oneshot::channel();
-31
View File
@@ -15,11 +15,6 @@ use std::{
rc::{Rc, Weak}, rc::{Rc, Weak},
sync::Arc, sync::Arc,
}; };
#[cfg(target_os = "windows")]
use windows::Win32::{
Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance},
};
/// TestPlatform implements the Platform trait for use in tests. /// TestPlatform implements the Platform trait for use in tests.
pub(crate) struct TestPlatform { pub(crate) struct TestPlatform {
@@ -39,8 +34,6 @@ pub(crate) struct TestPlatform {
pub opened_url: RefCell<Option<String>>, pub opened_url: RefCell<Option<String>>,
pub text_system: Arc<dyn PlatformTextSystem>, pub text_system: Arc<dyn PlatformTextSystem>,
pub expect_restart: RefCell<Option<oneshot::Sender<Option<PathBuf>>>>, pub expect_restart: RefCell<Option<oneshot::Sender<Option<PathBuf>>>>,
#[cfg(target_os = "windows")]
bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
weak: Weak<Self>, weak: Weak<Self>,
} }
@@ -95,16 +88,6 @@ pub(crate) struct TestPrompts {
impl TestPlatform { impl TestPlatform {
pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> { pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
#[cfg(target_os = "windows")]
let bitmap_factory = unsafe {
windows::Win32::System::Ole::OleInitialize(None)
.expect("unable to initialize Windows OLE");
std::mem::ManuallyDrop::new(
CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
.expect("Error creating bitmap factory."),
)
};
let text_system = Arc::new(NoopTextSystem); let text_system = Arc::new(NoopTextSystem);
Rc::new_cyclic(|weak| TestPlatform { Rc::new_cyclic(|weak| TestPlatform {
@@ -123,8 +106,6 @@ impl TestPlatform {
current_find_pasteboard_item: Mutex::new(None), current_find_pasteboard_item: Mutex::new(None),
weak: weak.clone(), weak: weak.clone(),
opened_url: Default::default(), opened_url: Default::default(),
#[cfg(target_os = "windows")]
bitmap_factory,
text_system, text_system,
}) })
} }
@@ -288,12 +269,10 @@ impl Platform for TestPlatform {
Some(self.active_display.clone()) Some(self.active_display.clone())
} }
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool { fn is_screen_capture_supported(&self) -> bool {
true true
} }
#[cfg(feature = "screen-capture")]
fn screen_capture_sources( fn screen_capture_sources(
&self, &self,
) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> { ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
@@ -458,16 +437,6 @@ impl TestScreenCaptureSource {
} }
} }
#[cfg(target_os = "windows")]
impl Drop for TestPlatform {
fn drop(&mut self) {
unsafe {
std::mem::ManuallyDrop::drop(&mut self.bitmap_factory);
windows::Win32::System::Ole::OleUninitialize();
}
}
}
struct TestKeyboardLayout; struct TestKeyboardLayout;
impl PlatformKeyboardLayout for TestKeyboardLayout { impl PlatformKeyboardLayout for TestKeyboardLayout {
+2 -2
View File
@@ -32,7 +32,7 @@ pub(crate) struct TestWindowState {
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>); pub struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
impl HasWindowHandle for TestWindow { impl HasWindowHandle for TestWindow {
fn window_handle( fn window_handle(
@@ -51,7 +51,7 @@ impl HasDisplayHandle for TestWindow {
} }
impl TestWindow { impl TestWindow {
pub fn new( pub(crate) fn new(
handle: AnyWindowHandle, handle: AnyWindowHandle,
params: WindowParams, params: WindowParams,
platform: Weak<TestPlatform>, platform: Weak<TestPlatform>,
+22 -26
View File
@@ -9,7 +9,7 @@
use crate::ScreenCaptureSource; use crate::ScreenCaptureSource;
use crate::{ use crate::{
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap,
MacPlatform, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, Task, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, Task,
TestDispatcher, WindowAppearance, WindowParams, TestDispatcher, WindowAppearance, WindowParams,
}; };
@@ -33,7 +33,7 @@ pub struct VisualTestPlatform {
dispatcher: TestDispatcher, dispatcher: TestDispatcher,
background_executor: BackgroundExecutor, background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor, foreground_executor: ForegroundExecutor,
mac_platform: MacPlatform, platform: Rc<dyn Platform>,
clipboard: Mutex<Option<ClipboardItem>>, clipboard: Mutex<Option<ClipboardItem>>,
find_pasteboard: Mutex<Option<ClipboardItem>>, find_pasteboard: Mutex<Option<ClipboardItem>>,
} }
@@ -42,20 +42,18 @@ impl VisualTestPlatform {
/// Creates a new VisualTestPlatform with the given random seed. /// Creates a new VisualTestPlatform with the given random seed.
/// ///
/// The seed is used for deterministic random number generation in the TestDispatcher. /// The seed is used for deterministic random number generation in the TestDispatcher.
pub fn new(seed: u64) -> Self { pub fn new(platform: Rc<dyn Platform>, seed: u64) -> Self {
let dispatcher = TestDispatcher::new(seed); let dispatcher = TestDispatcher::new(seed);
let arc_dispatcher = Arc::new(dispatcher.clone()); let arc_dispatcher = Arc::new(dispatcher.clone());
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(arc_dispatcher); let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
let mac_platform = MacPlatform::new(false);
Self { Self {
dispatcher, dispatcher,
background_executor, background_executor,
foreground_executor, foreground_executor,
mac_platform, platform,
clipboard: Mutex::new(None), clipboard: Mutex::new(None),
find_pasteboard: Mutex::new(None), find_pasteboard: Mutex::new(None),
} }
@@ -77,7 +75,7 @@ impl Platform for VisualTestPlatform {
} }
fn text_system(&self) -> Arc<dyn PlatformTextSystem> { fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.mac_platform.text_system() self.platform.text_system()
} }
fn run(&self, _on_finish_launching: Box<dyn 'static + FnOnce()>) { fn run(&self, _on_finish_launching: Box<dyn 'static + FnOnce()>) {
@@ -97,31 +95,29 @@ impl Platform for VisualTestPlatform {
fn unhide_other_apps(&self) {} fn unhide_other_apps(&self) {}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> { fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
self.mac_platform.displays() self.platform.displays()
} }
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> { fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.mac_platform.primary_display() self.platform.primary_display()
} }
fn active_window(&self) -> Option<AnyWindowHandle> { fn active_window(&self) -> Option<AnyWindowHandle> {
self.mac_platform.active_window() self.platform.active_window()
} }
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> { fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
self.mac_platform.window_stack() self.platform.window_stack()
} }
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool { fn is_screen_capture_supported(&self) -> bool {
self.mac_platform.is_screen_capture_supported() self.platform.is_screen_capture_supported()
} }
#[cfg(feature = "screen-capture")]
fn screen_capture_sources( fn screen_capture_sources(
&self, &self,
) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> { ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
self.mac_platform.screen_capture_sources() self.platform.screen_capture_sources()
} }
fn open_window( fn open_window(
@@ -129,15 +125,15 @@ impl Platform for VisualTestPlatform {
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> { ) -> Result<Box<dyn PlatformWindow>> {
self.mac_platform.open_window(handle, options) self.platform.open_window(handle, options)
} }
fn window_appearance(&self) -> WindowAppearance { fn window_appearance(&self) -> WindowAppearance {
self.mac_platform.window_appearance() self.platform.window_appearance()
} }
fn open_url(&self, url: &str) { fn open_url(&self, url: &str) {
self.mac_platform.open_url(url) self.platform.open_url(url)
} }
fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {} fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {}
@@ -170,11 +166,11 @@ impl Platform for VisualTestPlatform {
} }
fn reveal_path(&self, path: &Path) { fn reveal_path(&self, path: &Path) {
self.mac_platform.reveal_path(path) self.platform.reveal_path(path)
} }
fn open_with_system(&self, path: &Path) { fn open_with_system(&self, path: &Path) {
self.mac_platform.open_with_system(path) self.platform.open_with_system(path)
} }
fn on_quit(&self, _callback: Box<dyn FnMut()>) {} fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
@@ -196,19 +192,19 @@ impl Platform for VisualTestPlatform {
fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {} fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
fn app_path(&self) -> Result<PathBuf> { fn app_path(&self) -> Result<PathBuf> {
self.mac_platform.app_path() self.platform.app_path()
} }
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
self.mac_platform.path_for_auxiliary_executable(name) self.platform.path_for_auxiliary_executable(name)
} }
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {
self.mac_platform.set_cursor_style(style) self.platform.set_cursor_style(style)
} }
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
self.mac_platform.should_auto_hide_scrollbars() self.platform.should_auto_hide_scrollbars()
} }
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
@@ -242,11 +238,11 @@ impl Platform for VisualTestPlatform {
} }
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> { fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
self.mac_platform.keyboard_layout() self.platform.keyboard_layout()
} }
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> { fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
self.mac_platform.keyboard_mapper() self.platform.keyboard_mapper()
} }
fn on_keyboard_layout_change(&self, _callback: Box<dyn FnMut()>) {} fn on_keyboard_layout_change(&self, _callback: Box<dyn FnMut()>) {}
-7
View File
@@ -1,7 +0,0 @@
mod wgpu_atlas;
mod wgpu_context;
mod wgpu_renderer;
pub(crate) use wgpu_atlas::*;
pub(crate) use wgpu_context::*;
pub(crate) use wgpu_renderer::*;
+17 -9
View File
@@ -30,7 +30,8 @@ pub struct ThreadTaskTimings {
} }
impl ThreadTaskTimings { impl ThreadTaskTimings {
pub(crate) fn convert(timings: &[GlobalThreadTimings]) -> Vec<Self> { /// Convert global thread timings into their structured format.
pub fn convert(timings: &[GlobalThreadTimings]) -> Vec<Self> {
timings timings
.iter() .iter()
.filter_map(|t| match t.timings.upgrade() { .filter_map(|t| match t.timings.upgrade() {
@@ -245,19 +246,24 @@ impl ProfilingCollector {
// Allow 20mb of task timing entries // Allow 20mb of task timing entries
const MAX_TASK_TIMINGS: usize = (20 * 1024 * 1024) / core::mem::size_of::<TaskTiming>(); const MAX_TASK_TIMINGS: usize = (20 * 1024 * 1024) / core::mem::size_of::<TaskTiming>();
pub(crate) type TaskTimings = circular_buffer::CircularBuffer<MAX_TASK_TIMINGS, TaskTiming>; #[doc(hidden)]
pub(crate) type GuardedTaskTimings = spin::Mutex<ThreadTimings>; pub type TaskTimings = circular_buffer::CircularBuffer<MAX_TASK_TIMINGS, TaskTiming>;
#[doc(hidden)]
pub type GuardedTaskTimings = spin::Mutex<ThreadTimings>;
pub(crate) struct GlobalThreadTimings { #[doc(hidden)]
pub struct GlobalThreadTimings {
pub thread_id: ThreadId, pub thread_id: ThreadId,
pub timings: std::sync::Weak<GuardedTaskTimings>, pub timings: std::sync::Weak<GuardedTaskTimings>,
} }
pub(crate) static GLOBAL_THREAD_TIMINGS: spin::Mutex<Vec<GlobalThreadTimings>> = #[doc(hidden)]
pub static GLOBAL_THREAD_TIMINGS: spin::Mutex<Vec<GlobalThreadTimings>> =
spin::Mutex::new(Vec::new()); spin::Mutex::new(Vec::new());
thread_local! { thread_local! {
pub(crate) static THREAD_TIMINGS: LazyCell<Arc<GuardedTaskTimings>> = LazyCell::new(|| { #[doc(hidden)]
pub static THREAD_TIMINGS: LazyCell<Arc<GuardedTaskTimings>> = LazyCell::new(|| {
let current_thread = std::thread::current(); let current_thread = std::thread::current();
let thread_name = current_thread.name(); let thread_name = current_thread.name();
let thread_id = current_thread.id(); let thread_id = current_thread.id();
@@ -277,7 +283,8 @@ thread_local! {
}); });
} }
pub(crate) struct ThreadTimings { #[doc(hidden)]
pub struct ThreadTimings {
pub thread_name: Option<String>, pub thread_name: Option<String>,
pub thread_id: ThreadId, pub thread_id: ThreadId,
pub timings: Box<TaskTimings>, pub timings: Box<TaskTimings>,
@@ -285,7 +292,7 @@ pub(crate) struct ThreadTimings {
} }
impl ThreadTimings { impl ThreadTimings {
pub(crate) fn new(thread_name: Option<String>, thread_id: ThreadId) -> Self { pub fn new(thread_name: Option<String>, thread_id: ThreadId) -> Self {
ThreadTimings { ThreadTimings {
thread_name, thread_name,
thread_id, thread_id,
@@ -310,8 +317,9 @@ impl Drop for ThreadTimings {
} }
} }
#[doc(hidden)]
#[allow(dead_code)] // Used by Linux and Windows dispatchers, not macOS #[allow(dead_code)] // Used by Linux and Windows dispatchers, not macOS
pub(crate) fn add_task_timing(timing: TaskTiming) { pub fn add_task_timing(timing: TaskTiming) {
THREAD_TIMINGS.with(|timings| { THREAD_TIMINGS.with(|timings| {
let mut timings = timings.lock(); let mut timings = timings.lock();
+18 -14
View File
@@ -86,7 +86,8 @@ impl<T> PriorityQueueState<T> {
} }
} }
pub(crate) struct PriorityQueueSender<T> { #[doc(hidden)]
pub struct PriorityQueueSender<T> {
state: Arc<PriorityQueueState<T>>, state: Arc<PriorityQueueState<T>>,
} }
@@ -95,7 +96,7 @@ impl<T> PriorityQueueSender<T> {
Self { state } Self { state }
} }
pub(crate) fn send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> { pub fn send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> {
self.state.send(priority, item)?; self.state.send(priority, item)?;
Ok(()) Ok(())
} }
@@ -109,7 +110,8 @@ impl<T> Drop for PriorityQueueSender<T> {
} }
} }
pub(crate) struct PriorityQueueReceiver<T> { #[doc(hidden)]
pub struct PriorityQueueReceiver<T> {
state: Arc<PriorityQueueState<T>>, state: Arc<PriorityQueueState<T>>,
rand: SmallRng, rand: SmallRng,
disconnected: bool, disconnected: bool,
@@ -128,7 +130,8 @@ impl<T> Clone for PriorityQueueReceiver<T> {
} }
} }
pub(crate) struct SendError<T>(T); #[doc(hidden)]
pub struct SendError<T>(pub T);
impl<T: fmt::Debug> fmt::Debug for SendError<T> { impl<T: fmt::Debug> fmt::Debug for SendError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -137,11 +140,12 @@ impl<T: fmt::Debug> fmt::Debug for SendError<T> {
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct RecvError; #[doc(hidden)]
pub struct RecvError;
#[allow(dead_code)] #[allow(dead_code)]
impl<T> PriorityQueueReceiver<T> { impl<T> PriorityQueueReceiver<T> {
pub(crate) fn new() -> (PriorityQueueSender<T>, Self) { pub fn new() -> (PriorityQueueSender<T>, Self) {
let state = PriorityQueueState { let state = PriorityQueueState {
queues: parking_lot::Mutex::new(PriorityQueues { queues: parking_lot::Mutex::new(PriorityQueues {
high_priority: VecDeque::new(), high_priority: VecDeque::new(),
@@ -175,7 +179,7 @@ impl<T> PriorityQueueReceiver<T> {
/// # Errors /// # Errors
/// ///
/// If the sender was dropped /// If the sender was dropped
pub(crate) fn try_pop(&mut self) -> Result<Option<T>, RecvError> { pub fn try_pop(&mut self) -> Result<Option<T>, RecvError> {
self.pop_inner(false) self.pop_inner(false)
} }
@@ -187,13 +191,13 @@ impl<T> PriorityQueueReceiver<T> {
/// # Errors /// # Errors
/// ///
/// If the sender was dropped /// If the sender was dropped
pub(crate) fn pop(&mut self) -> Result<T, RecvError> { pub fn pop(&mut self) -> Result<T, RecvError> {
self.pop_inner(true).map(|e| e.unwrap()) self.pop_inner(true).map(|e| e.unwrap())
} }
/// Returns an iterator over the elements of the queue /// Returns an iterator over the elements of the queue
/// this iterator will end when all elements have been consumed and will not wait for new ones. /// this iterator will end when all elements have been consumed and will not wait for new ones.
pub(crate) fn try_iter(self) -> TryIter<T> { pub fn try_iter(self) -> TryIter<T> {
TryIter { TryIter {
receiver: self, receiver: self,
ended: false, ended: false,
@@ -202,7 +206,7 @@ impl<T> PriorityQueueReceiver<T> {
/// Returns an iterator over the elements of the queue /// Returns an iterator over the elements of the queue
/// this iterator will wait for new elements if the queue is empty. /// this iterator will wait for new elements if the queue is empty.
pub(crate) fn iter(self) -> Iter<T> { pub fn iter(self) -> Iter<T> {
Iter(self) Iter(self)
} }
@@ -261,8 +265,8 @@ impl<T> Drop for PriorityQueueReceiver<T> {
} }
} }
/// If None is returned the sender disconnected #[doc(hidden)]
pub(crate) struct Iter<T>(PriorityQueueReceiver<T>); pub struct Iter<T>(PriorityQueueReceiver<T>);
impl<T> Iterator for Iter<T> { impl<T> Iterator for Iter<T> {
type Item = T; type Item = T;
@@ -272,8 +276,8 @@ impl<T> Iterator for Iter<T> {
} }
impl<T> FusedIterator for Iter<T> {} impl<T> FusedIterator for Iter<T> {}
/// If None is returned there are no more elements in the queue #[doc(hidden)]
pub(crate) struct TryIter<T> { pub struct TryIter<T> {
receiver: PriorityQueueReceiver<T>, receiver: PriorityQueueReceiver<T>,
ended: bool, ended: bool,
} }
+52 -33
View File
@@ -16,25 +16,29 @@ use std::{
}; };
#[allow(non_camel_case_types, unused)] #[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>; #[expect(missing_docs)]
pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub(crate) type DrawOrder = u32; #[expect(missing_docs)]
pub type DrawOrder = u32;
#[derive(Default)] #[derive(Default)]
pub(crate) struct Scene { #[expect(missing_docs)]
pub struct Scene {
pub(crate) paint_operations: Vec<PaintOperation>, pub(crate) paint_operations: Vec<PaintOperation>,
primitive_bounds: BoundsTree<ScaledPixels>, primitive_bounds: BoundsTree<ScaledPixels>,
layer_stack: Vec<DrawOrder>, layer_stack: Vec<DrawOrder>,
pub(crate) shadows: Vec<Shadow>, pub shadows: Vec<Shadow>,
pub(crate) quads: Vec<Quad>, pub quads: Vec<Quad>,
pub(crate) paths: Vec<Path<ScaledPixels>>, pub paths: Vec<Path<ScaledPixels>>,
pub(crate) underlines: Vec<Underline>, pub underlines: Vec<Underline>,
pub(crate) monochrome_sprites: Vec<MonochromeSprite>, pub monochrome_sprites: Vec<MonochromeSprite>,
pub(crate) subpixel_sprites: Vec<SubpixelSprite>, pub subpixel_sprites: Vec<SubpixelSprite>,
pub(crate) polychrome_sprites: Vec<PolychromeSprite>, pub polychrome_sprites: Vec<PolychromeSprite>,
pub(crate) surfaces: Vec<PaintSurface>, pub surfaces: Vec<PaintSurface>,
} }
#[expect(missing_docs)]
impl Scene { impl Scene {
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.paint_operations.clear(); self.paint_operations.clear();
@@ -151,7 +155,7 @@ impl Scene {
), ),
allow(dead_code) allow(dead_code)
)] )]
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ { pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
BatchIterator { BatchIterator {
shadows_start: 0, shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(), shadows_iter: self.shadows.iter().peekable(),
@@ -200,7 +204,8 @@ pub(crate) enum PaintOperation {
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum Primitive { #[expect(missing_docs)]
pub enum Primitive {
Shadow(Shadow), Shadow(Shadow),
Quad(Quad), Quad(Quad),
Path(Path<ScaledPixels>), Path(Path<ScaledPixels>),
@@ -211,6 +216,7 @@ pub(crate) enum Primitive {
Surface(PaintSurface), Surface(PaintSurface),
} }
#[expect(missing_docs)]
impl Primitive { impl Primitive {
pub fn bounds(&self) -> &Bounds<ScaledPixels> { pub fn bounds(&self) -> &Bounds<ScaledPixels> {
match self { match self {
@@ -453,7 +459,8 @@ impl<'a> Iterator for BatchIterator<'a> {
), ),
allow(dead_code) allow(dead_code)
)] )]
pub(crate) enum PrimitiveBatch { #[expect(missing_docs)]
pub enum PrimitiveBatch {
Shadows(Range<usize>), Shadows(Range<usize>),
Quads(Range<usize>), Quads(Range<usize>),
Paths(Range<usize>), Paths(Range<usize>),
@@ -476,7 +483,8 @@ pub(crate) enum PrimitiveBatch {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
#[repr(C)] #[repr(C)]
pub(crate) struct Quad { #[expect(missing_docs)]
pub struct Quad {
pub order: DrawOrder, pub order: DrawOrder,
pub border_style: BorderStyle, pub border_style: BorderStyle,
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
@@ -495,7 +503,8 @@ impl From<Quad> for Primitive {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[repr(C)] #[repr(C)]
pub(crate) struct Underline { #[expect(missing_docs)]
pub struct Underline {
pub order: DrawOrder, pub order: DrawOrder,
pub pad: u32, // align to 8 bytes pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
@@ -513,7 +522,8 @@ impl From<Underline> for Primitive {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[repr(C)] #[repr(C)]
pub(crate) struct Shadow { #[expect(missing_docs)]
pub struct Shadow {
pub order: DrawOrder, pub order: DrawOrder,
pub blur_radius: ScaledPixels, pub blur_radius: ScaledPixels,
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
@@ -644,7 +654,8 @@ impl Default for TransformationMatrix {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[repr(C)] #[repr(C)]
pub(crate) struct MonochromeSprite { #[expect(missing_docs)]
pub struct MonochromeSprite {
pub order: DrawOrder, pub order: DrawOrder,
pub pad: u32, // align to 8 bytes pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
@@ -662,7 +673,8 @@ impl From<MonochromeSprite> for Primitive {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[repr(C)] #[repr(C)]
pub(crate) struct SubpixelSprite { #[expect(missing_docs)]
pub struct SubpixelSprite {
pub order: DrawOrder, pub order: DrawOrder,
pub pad: u32, // align to 8 bytes pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
@@ -680,7 +692,8 @@ impl From<SubpixelSprite> for Primitive {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[repr(C)] #[repr(C)]
pub(crate) struct PolychromeSprite { #[expect(missing_docs)]
pub struct PolychromeSprite {
pub order: DrawOrder, pub order: DrawOrder,
pub pad: u32, // align to 8 bytes pub pad: u32, // align to 8 bytes
pub grayscale: bool, pub grayscale: bool,
@@ -698,7 +711,8 @@ impl From<PolychromeSprite> for Primitive {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct PaintSurface { #[expect(missing_docs)]
pub struct PaintSurface {
pub order: DrawOrder, pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>, pub content_mask: ContentMask<ScaledPixels>,
@@ -713,17 +727,19 @@ impl From<PaintSurface> for Primitive {
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PathId(pub(crate) usize); #[expect(missing_docs)]
pub struct PathId(pub usize);
/// A line made up of a series of vertices and control points. /// A line made up of a series of vertices and control points.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[expect(missing_docs)]
pub struct Path<P: Clone + Debug + Default + PartialEq> { pub struct Path<P: Clone + Debug + Default + PartialEq> {
pub(crate) id: PathId, pub id: PathId,
pub(crate) order: DrawOrder, pub order: DrawOrder,
pub(crate) bounds: Bounds<P>, pub bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>, pub content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>, pub vertices: Vec<PathVertex<P>>,
pub(crate) color: Background, pub color: Background,
start: Point<P>, start: Point<P>,
current: Point<P>, current: Point<P>,
contour_count: usize, contour_count: usize,
@@ -847,7 +863,8 @@ where
T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>, T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
{ {
#[allow(unused)] #[allow(unused)]
pub(crate) fn clipped_bounds(&self) -> Bounds<T> { #[expect(missing_docs)]
pub fn clipped_bounds(&self) -> Bounds<T> {
self.bounds.intersect(&self.content_mask.bounds) self.bounds.intersect(&self.content_mask.bounds)
} }
} }
@@ -860,12 +877,14 @@ impl From<Path<ScaledPixels>> for Primitive {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[repr(C)] #[repr(C)]
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> { #[expect(missing_docs)]
pub(crate) xy_position: Point<P>, pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
pub(crate) st_position: Point<f32>, pub xy_position: Point<P>,
pub(crate) content_mask: ContentMask<P>, pub st_position: Point<f32>,
pub content_mask: ContentMask<P>,
} }
#[expect(missing_docs)]
impl PathVertex<Pixels> { impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> { pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex { PathVertex {
+4 -3
View File
@@ -14,9 +14,10 @@ use std::{
pub const SMOOTH_SVG_SCALE_FACTOR: f32 = 2.; pub const SMOOTH_SVG_SCALE_FACTOR: f32 = 2.;
#[derive(Clone, PartialEq, Hash, Eq)] #[derive(Clone, PartialEq, Hash, Eq)]
pub(crate) struct RenderSvgParams { #[expect(missing_docs)]
pub(crate) path: SharedString, pub struct RenderSvgParams {
pub(crate) size: Size<DevicePixels>, pub path: SharedString,
pub size: Size<DevicePixels>,
} }
#[derive(Clone)] #[derive(Clone)]
+31 -27
View File
@@ -41,14 +41,15 @@ pub struct FontId(pub usize);
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
pub struct FontFamilyId(pub usize); pub struct FontFamilyId(pub usize);
pub(crate) const SUBPIXEL_VARIANTS_X: u8 = 4; /// Number of subpixel glyph variants along the X axis.
pub const SUBPIXEL_VARIANTS_X: u8 = 4;
pub(crate) const SUBPIXEL_VARIANTS_Y: u8 = /// Number of subpixel glyph variants along the Y axis.
if cfg!(target_os = "windows") || cfg!(target_os = "linux") { pub const SUBPIXEL_VARIANTS_Y: u8 = if cfg!(target_os = "windows") || cfg!(target_os = "linux") {
1 1
} else { } else {
SUBPIXEL_VARIANTS_X SUBPIXEL_VARIANTS_X
}; };
/// The GPUI text rendering sub system. /// The GPUI text rendering sub system.
pub struct TextSystem { pub struct TextSystem {
@@ -799,17 +800,18 @@ impl TextRun {
/// An identifier for a specific glyph, as returned by [`WindowTextSystem::layout_line`]. /// An identifier for a specific glyph, as returned by [`WindowTextSystem::layout_line`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)] #[repr(C)]
pub struct GlyphId(pub(crate) u32); pub struct GlyphId(pub u32);
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub(crate) struct RenderGlyphParams { #[expect(missing_docs)]
pub(crate) font_id: FontId, pub struct RenderGlyphParams {
pub(crate) glyph_id: GlyphId, pub font_id: FontId,
pub(crate) font_size: Pixels, pub glyph_id: GlyphId,
pub(crate) subpixel_variant: Point<u8>, pub font_size: Pixels,
pub(crate) scale_factor: f32, pub subpixel_variant: Point<u8>,
pub(crate) is_emoji: bool, pub scale_factor: f32,
pub(crate) subpixel_rendering: bool, pub is_emoji: bool,
pub subpixel_rendering: bool,
} }
impl Eq for RenderGlyphParams {} impl Eq for RenderGlyphParams {}
@@ -884,32 +886,32 @@ impl Font {
pub struct FontMetrics { pub struct FontMetrics {
/// The number of font units that make up the "em square", /// The number of font units that make up the "em square",
/// a scalable grid for determining the size of a typeface. /// a scalable grid for determining the size of a typeface.
pub(crate) units_per_em: u32, pub units_per_em: u32,
/// The vertical distance from the baseline of the font to the top of the glyph covers. /// The vertical distance from the baseline of the font to the top of the glyph covers.
pub(crate) ascent: f32, pub ascent: f32,
/// The vertical distance from the baseline of the font to the bottom of the glyph covers. /// The vertical distance from the baseline of the font to the bottom of the glyph covers.
pub(crate) descent: f32, pub descent: f32,
/// The recommended additional space to add between lines of type. /// The recommended additional space to add between lines of type.
pub(crate) line_gap: f32, pub line_gap: f32,
/// The suggested position of the underline. /// The suggested position of the underline.
pub(crate) underline_position: f32, pub underline_position: f32,
/// The suggested thickness of the underline. /// The suggested thickness of the underline.
pub(crate) underline_thickness: f32, pub underline_thickness: f32,
/// The height of a capital letter measured from the baseline of the font. /// The height of a capital letter measured from the baseline of the font.
pub(crate) cap_height: f32, pub cap_height: f32,
/// The height of a lowercase x. /// The height of a lowercase x.
pub(crate) x_height: f32, pub x_height: f32,
/// The outer limits of the area that the font covers. /// The outer limits of the area that the font covers.
/// Corresponds to the xMin / xMax / yMin / yMax values in the OpenType `head` table /// Corresponds to the xMin / xMax / yMin / yMax values in the OpenType `head` table
pub(crate) bounding_box: Bounds<f32>, pub bounding_box: Bounds<f32>,
} }
impl FontMetrics { impl FontMetrics {
@@ -954,8 +956,9 @@ impl FontMetrics {
} }
} }
/// Maps well-known virtual font names to their concrete equivalents.
#[allow(unused)] #[allow(unused)]
pub(crate) fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str { pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
// Note: the "Zed Plex" fonts were deprecated as we are not allowed to use "Plex" // Note: the "Zed Plex" fonts were deprecated as we are not allowed to use "Plex"
// in a derived font name. They are essentially indistinguishable from IBM Plex/Lilex, // in a derived font name. They are essentially indistinguishable from IBM Plex/Lilex,
// and so retained here for backward compatibility. // and so retained here for backward compatibility.
@@ -967,8 +970,9 @@ pub(crate) fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'
} }
} }
/// Like [`font_name_with_fallbacks`] but accepts and returns [`SharedString`] references.
#[allow(unused)] #[allow(unused)]
pub(crate) fn font_name_with_fallbacks_shared<'a>( pub fn font_name_with_fallbacks_shared<'a>(
name: &'a SharedString, name: &'a SharedString,
system: &'a SharedString, system: &'a SharedString,
) -> &'a SharedString { ) -> &'a SharedString {
+3 -2
View File
@@ -594,9 +594,10 @@ impl LineLayoutCache {
/// A run of text with a single font. /// A run of text with a single font.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[expect(missing_docs)]
pub struct FontRun { pub struct FontRun {
pub(crate) len: usize, pub len: usize,
pub(crate) font_id: FontId, pub font_id: FontId,
} }
trait AsCacheKeyRef { trait AsCacheKeyRef {
+4 -2
View File
@@ -59,7 +59,8 @@ mod prompts;
use crate::util::atomic_incr_if_not_zero; use crate::util::atomic_incr_if_not_zero;
pub use prompts::*; pub use prompts::*;
pub(crate) const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(864.)); /// Default window size used when no explicit size is provided.
pub const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(864.));
/// A 6:5 aspect ratio minimum window size to be used for functional, /// A 6:5 aspect ratio minimum window size to be used for functional,
/// additional-to-main-Zed windows, like the settings and rules library windows. /// additional-to-main-Zed windows, like the settings and rules library windows.
@@ -1447,7 +1448,8 @@ impl Window {
} }
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct DispatchEventResult { #[expect(missing_docs)]
pub struct DispatchEventResult {
pub propagate: bool, pub propagate: bool,
pub default_prevented: bool, pub default_prevented: bool,
} }
+134
View File
@@ -0,0 +1,134 @@
[package]
name = "gpui_linux"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/gpui_linux.rs"
[features]
default = ["wayland", "x11"]
test-support = ["gpui/test-support"]
wayland = [
"bitflags",
"gpui_wgpu",
"ashpd/wayland",
"cosmic-text",
"font-kit",
"calloop-wayland-source",
"wayland-backend",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-plasma",
"wayland-protocols-wlr",
"filedescriptor",
"xkbcommon",
"open",
]
x11 = [
"gpui_wgpu",
"ashpd",
"cosmic-text",
"font-kit",
"as-raw-xcb-connection",
"x11rb",
"xkbcommon",
"xim",
"x11-clipboard",
"filedescriptor",
"open",
"scap?/x11",
]
screen-capture = [
"gpui/screen-capture",
"scap",
]
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
anyhow.workspace = true
bytemuck = "1"
collections.workspace = true
futures.workspace = true
gpui.workspace = true
gpui_wgpu = { workspace = true, optional = true }
http_client.workspace = true
itertools.workspace = true
libc.workspace = true
log.workspace = true
parking_lot.workspace = true
pathfinder_geometry = "0.5"
profiling.workspace = true
smallvec.workspace = true
smol.workspace = true
strum.workspace = true
util.workspace = true
uuid.workspace = true
# Always used
oo7 = { version = "0.5.0", default-features = false, features = [
"async-std",
"native_crypto",
] }
calloop = "0.14.3"
raw-window-handle = "0.6"
# Used in both windowing options
ashpd = { workspace = true, optional = true }
cosmic-text = { version = "0.17.0", optional = true }
swash = { version = "0.2.6" }
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", features = [
"source-fontconfig-dlopen",
], optional = true }
bitflags = { workspace = true, optional = true }
filedescriptor = { version = "0.8.2", optional = true }
open = { version = "5.2.0", optional = true }
xkbcommon = { version = "0.8.0", features = ["wayland", "x11"], optional = true }
# Screen capture
scap = { workspace = true, optional = true }
# Wayland
calloop-wayland-source = { version = "0.4.1", optional = true }
wayland-backend = { version = "0.3.3", features = [
"client_system",
"dlopen",
], optional = true }
wayland-client = { version = "0.31.11", optional = true }
wayland-cursor = { version = "0.31.11", optional = true }
wayland-protocols = { version = "0.32.9", features = [
"client",
"staging",
"unstable",
], optional = true }
wayland-protocols-plasma = { version = "0.3.9", features = [
"client",
], optional = true }
wayland-protocols-wlr = { version = "0.3.9", features = [
"client",
], optional = true }
# X11
as-raw-xcb-connection = { version = "1", optional = true }
x11rb = { version = "0.13.1", features = [
"allow-unsafe-code",
"xkb",
"randr",
"xinput",
"cursor",
"resource_manager",
"sync",
], optional = true }
# WARNING: If you change this, you must also publish a new version of zed-xim to crates.io
xim = { git = "https://github.com/zed-industries/xim-rs.git", rev = "16f35a2c881b815a2b6cdfd6687988e84f8447d8", features = [
"x11rb-xcb",
"x11rb-client",
], package = "zed-xim", version = "0.4.0-zed", optional = true }
x11-clipboard = { version = "0.9.3", optional = true }
+1
View File
@@ -0,0 +1 @@
../../LICENSE-APACHE
+4
View File
@@ -0,0 +1,4 @@
#![cfg(any(target_os = "linux", target_os = "freebsd"))]
mod linux;
pub use linux::current_platform;
+57
View File
@@ -0,0 +1,57 @@
mod dispatcher;
mod headless;
mod keyboard;
mod platform;
#[cfg(any(feature = "wayland", feature = "x11"))]
mod text_system;
#[cfg(feature = "wayland")]
mod wayland;
#[cfg(feature = "x11")]
mod x11;
#[cfg(any(feature = "wayland", feature = "x11"))]
mod xdg_desktop_portal;
pub use dispatcher::*;
pub(crate) use headless::*;
pub(crate) use keyboard::*;
pub use platform::*;
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) use text_system::*;
#[cfg(feature = "wayland")]
pub(crate) use wayland::*;
#[cfg(feature = "x11")]
pub(crate) use x11::*;
use std::rc::Rc;
/// Returns the default platform implementation for the current OS.
pub fn current_platform(headless: bool) -> Rc<dyn gpui::Platform> {
#[cfg(feature = "x11")]
use anyhow::Context as _;
if headless {
return Rc::new(LinuxPlatform {
inner: HeadlessClient::new(),
});
}
match gpui::guess_compositor() {
#[cfg(feature = "wayland")]
"Wayland" => Rc::new(LinuxPlatform {
inner: WaylandClient::new(),
}),
#[cfg(feature = "x11")]
"X11" => Rc::new(LinuxPlatform {
inner: X11Client::new()
.context("Failed to initialize X11 client.")
.unwrap(),
}),
"Headless" => Rc::new(LinuxPlatform {
inner: HeadlessClient::new(),
}),
_ => unreachable!(),
}
}
@@ -11,7 +11,7 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use crate::{ use gpui::{
GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, PriorityQueueReceiver, GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, PriorityQueueReceiver,
PriorityQueueSender, RunnableVariant, THREAD_TIMINGS, TaskTiming, ThreadTaskTimings, profiler, PriorityQueueSender, RunnableVariant, THREAD_TIMINGS, TaskTiming, ThreadTaskTimings, profiler,
}; };
@@ -39,8 +39,7 @@ impl LinuxDispatcher {
let mut background_threads = (0..thread_count) let mut background_threads = (0..thread_count)
.map(|i| { .map(|i| {
let mut receiver: PriorityQueueReceiver<RunnableVariant> = let receiver: PriorityQueueReceiver<RunnableVariant> = background_receiver.clone();
background_receiver.clone();
std::thread::Builder::new() std::thread::Builder::new()
.name(format!("Worker-{i}")) .name(format!("Worker-{i}"))
.spawn(move || { .spawn(move || {
@@ -140,12 +139,12 @@ impl LinuxDispatcher {
} }
impl PlatformDispatcher for LinuxDispatcher { impl PlatformDispatcher for LinuxDispatcher {
fn get_all_timings(&self) -> Vec<crate::ThreadTaskTimings> { fn get_all_timings(&self) -> Vec<gpui::ThreadTaskTimings> {
let global_timings = GLOBAL_THREAD_TIMINGS.lock(); let global_timings = GLOBAL_THREAD_TIMINGS.lock();
ThreadTaskTimings::convert(&global_timings) ThreadTaskTimings::convert(&global_timings)
} }
fn get_current_thread_timings(&self) -> crate::ThreadTaskTimings { fn get_current_thread_timings(&self) -> gpui::ThreadTaskTimings {
THREAD_TIMINGS.with(|timings| { THREAD_TIMINGS.with(|timings| {
let timings = timings.lock(); let timings = timings.lock();
let thread_name = timings.thread_name.clone(); let thread_name = timings.thread_name.clone();
@@ -158,7 +157,7 @@ impl PlatformDispatcher for LinuxDispatcher {
vec.extend_from_slice(s1); vec.extend_from_slice(s1);
vec.extend_from_slice(s2); vec.extend_from_slice(s2);
crate::ThreadTaskTimings { gpui::ThreadTaskTimings {
thread_name, thread_name,
thread_id: std::thread::current().id(), thread_id: std::thread::current().id(),
timings: vec, timings: vec,
@@ -232,7 +231,7 @@ impl<T> PriorityQueueCalloopSender<T> {
Self { sender: tx, ping } Self { sender: tx, ping }
} }
fn send(&self, priority: Priority, item: T) -> Result<(), crate::queue::SendError<T>> { fn send(&self, priority: Priority, item: T) -> Result<(), gpui::queue::SendError<T>> {
let res = self.sender.send(priority, item); let res = self.sender.send(priority, item);
if res.is_ok() { if res.is_ok() {
self.ping.ping(); self.ping.ping();
@@ -312,7 +311,7 @@ impl<T> calloop::EventSource for PriorityQueueCalloopReceiver<T> {
.process_events(readiness, token, |(), &mut ()| { .process_events(readiness, token, |(), &mut ()| {
let mut is_empty = true; let mut is_empty = true;
let mut receiver = self.receiver.clone(); let receiver = self.receiver.clone();
for runnable in receiver.try_iter() { for runnable in receiver.try_iter() {
match runnable { match runnable {
Ok(r) => { Ok(r) => {
@@ -429,11 +428,11 @@ mod tests {
} }
// running 1 test // running 1 test
// test platform::linux::dispatcher::tests::tomato ... FAILED // test linux::dispatcher::tests::tomato ... FAILED
// failures: // failures:
// ---- platform::linux::dispatcher::tests::tomato stdout ---- // ---- linux::dispatcher::tests::tomato stdout ----
// [crates/gpui/src/platform/linux/dispatcher.rs:262:9] // [crates/gpui/src/platform/linux/dispatcher.rs:262:9]
// returning 1 tasks to process // returning 1 tasks to process
// [crates/gpui/src/platform/linux/dispatcher.rs:480:75] evt = Msg( // [crates/gpui/src/platform/linux/dispatcher.rs:480:75] evt = Msg(
@@ -441,6 +440,6 @@ mod tests {
// ) // )
// returning 0 tasks to process // returning 0 tasks to process
// thread 'platform::linux::dispatcher::tests::tomato' (478301) panicked at crates/gpui/src/platform/linux/dispatcher.rs:515:9: // thread 'linux::dispatcher::tests::tomato' (478301) panicked at crates/gpui/src/platform/linux/dispatcher.rs:515:9:
// assertion failed: data.got_closed // assertion failed: data.got_closed
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace // note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
@@ -4,11 +4,10 @@ use std::rc::Rc;
use calloop::{EventLoop, LoopHandle}; use calloop::{EventLoop, LoopHandle};
use util::ResultExt; use util::ResultExt;
use crate::platform::linux::LinuxClient; use crate::linux::{LinuxClient, LinuxCommon, LinuxKeyboardLayout};
use crate::platform::{LinuxCommon, PlatformWindow}; use gpui::{
use crate::{ AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, PlatformKeyboardLayout,
AnyWindowHandle, CursorStyle, DisplayId, LinuxKeyboardLayout, PlatformDisplay, PlatformWindow, WindowParams,
PlatformKeyboardLayout, WindowParams,
}; };
pub struct HeadlessClientState { pub struct HeadlessClientState {
@@ -65,17 +64,11 @@ impl LinuxClient for HeadlessClient {
None None
} }
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool {
false
}
#[cfg(feature = "screen-capture")]
fn screen_capture_sources( fn screen_capture_sources(
&self, &self,
) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>> ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>>
{ {
let (mut tx, rx) = futures::channel::oneshot::channel(); let (tx, rx) = futures::channel::oneshot::channel();
tx.send(Err(anyhow::anyhow!( tx.send(Err(anyhow::anyhow!(
"Headless mode does not support screen capture." "Headless mode does not support screen capture."
))) )))
@@ -109,15 +102,15 @@ impl LinuxClient for HeadlessClient {
fn reveal_path(&self, _path: std::path::PathBuf) {} fn reveal_path(&self, _path: std::path::PathBuf) {}
fn write_to_primary(&self, _item: crate::ClipboardItem) {} fn write_to_primary(&self, _item: gpui::ClipboardItem) {}
fn write_to_clipboard(&self, _item: crate::ClipboardItem) {} fn write_to_clipboard(&self, _item: gpui::ClipboardItem) {}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> { fn read_from_primary(&self) -> Option<gpui::ClipboardItem> {
None None
} }
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> { fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
None None
} }
@@ -1,4 +1,4 @@
use crate::{PlatformKeyboardLayout, SharedString}; use gpui::{PlatformKeyboardLayout, SharedString};
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct LinuxKeyboardLayout { pub(crate) struct LinuxKeyboardLayout {
@@ -21,15 +21,15 @@ use util::command::{new_command, new_std_command};
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
use xkbcommon::xkb::{self, Keycode, Keysym, State}; use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::{ use crate::linux::{LinuxDispatcher, PriorityQueueCalloopReceiver};
use gpui::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions, ForegroundExecutor, Keymap, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform,
Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
PlatformWindow, PriorityQueueCalloopReceiver, Result, RunnableVariant, Task, ThermalState, PlatformWindow, Result, RunnableVariant, Task, ThermalState, WindowAppearance, WindowParams,
WindowAppearance, WindowParams,
}; };
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
use crate::{Pixels, Point, px}; use gpui::{Pixels, Point, px};
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) const SCROLL_LINES: f32 = 3.0; pub(crate) const SCROLL_LINES: f32 = 3.0;
@@ -90,7 +90,7 @@ impl<T> ResultExt for anyhow::Result<T> {
} }
} }
pub trait LinuxClient { pub(crate) trait LinuxClient {
fn compositor_name(&self) -> &'static str; fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R; fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>; fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
@@ -99,11 +99,21 @@ pub trait LinuxClient {
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>; fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>; fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
#[cfg(feature = "screen-capture")] #[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool; fn is_screen_capture_supported(&self) -> bool {
false
}
#[cfg(feature = "screen-capture")] #[cfg(feature = "screen-capture")]
fn screen_capture_sources( fn screen_capture_sources(
&self, &self,
) -> oneshot::Receiver<Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>>; ) -> oneshot::Receiver<Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>> {
let (sources_tx, sources_rx) = oneshot::channel();
sources_tx
.send(Err(anyhow::anyhow!(
"gpui_linux was compiled without the screen-capture feature"
)))
.ok();
sources_rx
}
fn open_window( fn open_window(
&self, &self,
@@ -156,7 +166,7 @@ impl LinuxCommon {
let (main_sender, main_receiver) = PriorityQueueCalloopReceiver::new(); let (main_sender, main_receiver) = PriorityQueueCalloopReceiver::new();
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
let text_system = Arc::new(crate::CosmicTextSystem::new()); let text_system = Arc::new(crate::linux::CosmicTextSystem::new());
#[cfg(not(any(feature = "wayland", feature = "x11")))] #[cfg(not(any(feature = "wayland", feature = "x11")))]
let text_system = Arc::new(crate::NoopTextSystem::new()); let text_system = Arc::new(crate::NoopTextSystem::new());
@@ -181,29 +191,36 @@ impl LinuxCommon {
} }
} }
impl<P: LinuxClient + 'static> Platform for P { pub(crate) struct LinuxPlatform<P> {
pub(crate) inner: P,
}
impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
fn background_executor(&self) -> BackgroundExecutor { fn background_executor(&self) -> BackgroundExecutor {
self.with_common(|common| common.background_executor.clone()) self.inner
.with_common(|common| common.background_executor.clone())
} }
fn foreground_executor(&self) -> ForegroundExecutor { fn foreground_executor(&self) -> ForegroundExecutor {
self.with_common(|common| common.foreground_executor.clone()) self.inner
.with_common(|common| common.foreground_executor.clone())
} }
fn text_system(&self) -> Arc<dyn PlatformTextSystem> { fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.with_common(|common| common.text_system.clone()) self.inner.with_common(|common| common.text_system.clone())
} }
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> { fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
self.keyboard_layout() self.inner.keyboard_layout()
} }
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> { fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
Rc::new(crate::DummyKeyboardMapper) Rc::new(gpui::DummyKeyboardMapper)
} }
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) { fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback)); self.inner
.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
} }
fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {} fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
@@ -215,20 +232,22 @@ impl<P: LinuxClient + 'static> Platform for P {
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) { fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
on_finish_launching(); on_finish_launching();
LinuxClient::run(self); LinuxClient::run(&self.inner);
let quit = self.with_common(|common| common.callbacks.quit.take()); let quit = self
.inner
.with_common(|common| common.callbacks.quit.take());
if let Some(mut fun) = quit { if let Some(mut fun) = quit {
fun(); fun();
} }
} }
fn quit(&self) { fn quit(&self) {
self.with_common(|common| common.signal.stop()); self.inner.with_common(|common| common.signal.stop());
} }
fn compositor_name(&self) -> &'static str { fn compositor_name(&self) -> &'static str {
self.compositor_name() self.inner.compositor_name()
} }
fn restart(&self, binary_path: Option<PathBuf>) { fn restart(&self, binary_path: Option<PathBuf>) {
@@ -298,31 +317,31 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> { fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.primary_display() self.inner.primary_display()
} }
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> { fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
self.displays() self.inner.displays()
} }
#[cfg(feature = "screen-capture")] #[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool { fn is_screen_capture_supported(&self) -> bool {
self.is_screen_capture_supported() self.inner.is_screen_capture_supported()
} }
#[cfg(feature = "screen-capture")] #[cfg(feature = "screen-capture")]
fn screen_capture_sources( fn screen_capture_sources(
&self, &self,
) -> oneshot::Receiver<Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>> { ) -> oneshot::Receiver<Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>> {
self.screen_capture_sources() self.inner.screen_capture_sources()
} }
fn active_window(&self) -> Option<AnyWindowHandle> { fn active_window(&self) -> Option<AnyWindowHandle> {
self.active_window() self.inner.active_window()
} }
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> { fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
self.window_stack() self.inner.window_stack()
} }
fn open_window( fn open_window(
@@ -330,15 +349,16 @@ impl<P: LinuxClient + 'static> Platform for P {
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> anyhow::Result<Box<dyn PlatformWindow>> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
self.open_window(handle, options) self.inner.open_window(handle, options)
} }
fn open_url(&self, url: &str) { fn open_url(&self, url: &str) {
self.open_uri(url); self.inner.open_uri(url);
} }
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) { fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
self.with_common(|common| common.callbacks.open_urls = Some(callback)); self.inner
.with_common(|common| common.callbacks.open_urls = Some(callback));
} }
fn prompt_for_paths( fn prompt_for_paths(
@@ -351,7 +371,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let _ = (done_tx.send(Ok(None)), options); let _ = (done_tx.send(Ok(None)), options);
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
let identifier = self.window_identifier(); let identifier = self.inner.window_identifier();
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor() self.foreground_executor()
@@ -366,7 +386,7 @@ impl<P: LinuxClient + 'static> Platform for P {
.identifier(identifier.await) .identifier(identifier.await)
.modal(true) .modal(true)
.title(title) .title(title)
.accept_label(options.prompt.as_ref().map(crate::SharedString::as_str)) .accept_label(options.prompt.as_ref().map(gpui::SharedString::as_str))
.multiple(options.multiple) .multiple(options.multiple)
.directory(options.directories) .directory(options.directories)
.send() .send()
@@ -411,7 +431,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let _ = (done_tx.send(Ok(None)), directory, suggested_name); let _ = (done_tx.send(Ok(None)), directory, suggested_name);
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
let identifier = self.window_identifier(); let identifier = self.inner.window_identifier();
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor() self.foreground_executor()
@@ -468,7 +488,7 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
fn reveal_path(&self, path: &Path) { fn reveal_path(&self, path: &Path) {
self.reveal_path(path.to_owned()); self.inner.reveal_path(path.to_owned());
} }
fn open_with_system(&self, path: &Path) { fn open_with_system(&self, path: &Path) {
@@ -489,31 +509,31 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
fn on_quit(&self, callback: Box<dyn FnMut()>) { fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| { self.inner.with_common(|common| {
common.callbacks.quit = Some(callback); common.callbacks.quit = Some(callback);
}); });
} }
fn on_reopen(&self, callback: Box<dyn FnMut()>) { fn on_reopen(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| { self.inner.with_common(|common| {
common.callbacks.reopen = Some(callback); common.callbacks.reopen = Some(callback);
}); });
} }
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) { fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
self.with_common(|common| { self.inner.with_common(|common| {
common.callbacks.app_menu_action = Some(callback); common.callbacks.app_menu_action = Some(callback);
}); });
} }
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) { fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| { self.inner.with_common(|common| {
common.callbacks.will_open_app_menu = Some(callback); common.callbacks.will_open_app_menu = Some(callback);
}); });
} }
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) { fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
self.with_common(|common| { self.inner.with_common(|common| {
common.callbacks.validate_app_menu_command = Some(callback); common.callbacks.validate_app_menu_command = Some(callback);
}); });
} }
@@ -525,13 +545,13 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) { fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
self.with_common(|common| { self.inner.with_common(|common| {
common.menus = menus.into_iter().map(|menu| menu.owned()).collect(); common.menus = menus.into_iter().map(|menu| menu.owned()).collect();
}) })
} }
fn get_menus(&self) -> Option<Vec<OwnedMenu>> { fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
self.with_common(|common| Some(common.menus.clone())) self.inner.with_common(|common| Some(common.menus.clone()))
} }
fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) { fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) {
@@ -545,11 +565,11 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {
self.set_cursor_style(style) self.inner.set_cursor_style(style)
} }
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
self.with_common(|common| common.auto_hide_scrollbars) self.inner.with_common(|common| common.auto_hide_scrollbars)
} }
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> { fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
@@ -619,7 +639,7 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
fn window_appearance(&self) -> WindowAppearance { fn window_appearance(&self) -> WindowAppearance {
self.with_common(|common| common.appearance) self.inner.with_common(|common| common.appearance)
} }
fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> { fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
@@ -627,19 +647,19 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
fn write_to_primary(&self, item: ClipboardItem) { fn write_to_primary(&self, item: ClipboardItem) {
self.write_to_primary(item) self.inner.write_to_primary(item)
} }
fn write_to_clipboard(&self, item: ClipboardItem) { fn write_to_clipboard(&self, item: ClipboardItem) {
self.write_to_clipboard(item) self.inner.write_to_clipboard(item)
} }
fn read_from_primary(&self) -> Option<ClipboardItem> { fn read_from_primary(&self) -> Option<ClipboardItem> {
self.read_from_primary() self.inner.read_from_primary()
} }
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.read_from_clipboard() self.inner.read_from_clipboard()
} }
fn add_recent_document(&self, _path: &Path) {} fn add_recent_document(&self, _path: &Path) {}
@@ -750,39 +770,37 @@ pub(super) unsafe fn read_fd(fd: filedescriptor::FileDescriptor) -> Result<Vec<u
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
pub(super) const DEFAULT_CURSOR_ICON_NAME: &str = "left_ptr"; pub(super) const DEFAULT_CURSOR_ICON_NAME: &str = "left_ptr";
impl CursorStyle { #[cfg(any(feature = "wayland", feature = "x11"))]
#[cfg(any(feature = "wayland", feature = "x11"))] pub(super) fn cursor_style_to_icon_names(style: CursorStyle) -> &'static [&'static str] {
pub(super) fn to_icon_names(self) -> &'static [&'static str] { // Based on cursor names from chromium:
// Based on cursor names from chromium: // https://github.com/chromium/chromium/blob/d3069cf9c973dc3627fa75f64085c6a86c8f41bf/ui/base/cursor/cursor_factory.cc#L113
// https://github.com/chromium/chromium/blob/d3069cf9c973dc3627fa75f64085c6a86c8f41bf/ui/base/cursor/cursor_factory.cc#L113 match style {
match self { CursorStyle::Arrow => &[DEFAULT_CURSOR_ICON_NAME],
CursorStyle::Arrow => &[DEFAULT_CURSOR_ICON_NAME], CursorStyle::IBeam => &["text", "xterm"],
CursorStyle::IBeam => &["text", "xterm"], CursorStyle::Crosshair => &["crosshair", "cross"],
CursorStyle::Crosshair => &["crosshair", "cross"], CursorStyle::ClosedHand => &["closedhand", "grabbing", "hand2"],
CursorStyle::ClosedHand => &["closedhand", "grabbing", "hand2"], CursorStyle::OpenHand => &["openhand", "grab", "hand1"],
CursorStyle::OpenHand => &["openhand", "grab", "hand1"], CursorStyle::PointingHand => &["pointer", "hand", "hand2"],
CursorStyle::PointingHand => &["pointer", "hand", "hand2"], CursorStyle::ResizeLeft => &["w-resize", "left_side"],
CursorStyle::ResizeLeft => &["w-resize", "left_side"], CursorStyle::ResizeRight => &["e-resize", "right_side"],
CursorStyle::ResizeRight => &["e-resize", "right_side"], CursorStyle::ResizeLeftRight => &["ew-resize", "sb_h_double_arrow"],
CursorStyle::ResizeLeftRight => &["ew-resize", "sb_h_double_arrow"], CursorStyle::ResizeUp => &["n-resize", "top_side"],
CursorStyle::ResizeUp => &["n-resize", "top_side"], CursorStyle::ResizeDown => &["s-resize", "bottom_side"],
CursorStyle::ResizeDown => &["s-resize", "bottom_side"], CursorStyle::ResizeUpDown => &["sb_v_double_arrow", "ns-resize"],
CursorStyle::ResizeUpDown => &["sb_v_double_arrow", "ns-resize"], CursorStyle::ResizeUpLeftDownRight => &["size_fdiag", "bd_double_arrow", "nwse-resize"],
CursorStyle::ResizeUpLeftDownRight => &["size_fdiag", "bd_double_arrow", "nwse-resize"], CursorStyle::ResizeUpRightDownLeft => &["size_bdiag", "nesw-resize", "fd_double_arrow"],
CursorStyle::ResizeUpRightDownLeft => &["size_bdiag", "nesw-resize", "fd_double_arrow"], CursorStyle::ResizeColumn => &["col-resize", "sb_h_double_arrow"],
CursorStyle::ResizeColumn => &["col-resize", "sb_h_double_arrow"], CursorStyle::ResizeRow => &["row-resize", "sb_v_double_arrow"],
CursorStyle::ResizeRow => &["row-resize", "sb_v_double_arrow"], CursorStyle::IBeamCursorForVerticalLayout => &["vertical-text"],
CursorStyle::IBeamCursorForVerticalLayout => &["vertical-text"], CursorStyle::OperationNotAllowed => &["not-allowed", "crossed_circle"],
CursorStyle::OperationNotAllowed => &["not-allowed", "crossed_circle"], CursorStyle::DragLink => &["alias"],
CursorStyle::DragLink => &["alias"], CursorStyle::DragCopy => &["copy"],
CursorStyle::DragCopy => &["copy"], CursorStyle::ContextualMenu => &["context-menu"],
CursorStyle::ContextualMenu => &["context-menu"], CursorStyle::None => {
CursorStyle::None => { #[cfg(debug_assertions)]
#[cfg(debug_assertions)] panic!("CursorStyle::None should be handled separately in the client");
panic!("CursorStyle::None should be handled separately in the client"); #[cfg(not(debug_assertions))]
#[cfg(not(debug_assertions))] &[DEFAULT_CURSOR_ICON_NAME]
&[DEFAULT_CURSOR_ICON_NAME]
}
} }
} }
} }
@@ -856,222 +874,214 @@ fn guess_ascii(keycode: Keycode, shift: bool) -> Option<char> {
} }
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
impl crate::Keystroke { pub(super) fn keystroke_from_xkb(
pub(super) fn from_xkb( state: &State,
state: &State, mut modifiers: gpui::Modifiers,
mut modifiers: crate::Modifiers, keycode: Keycode,
keycode: Keycode, ) -> gpui::Keystroke {
) -> Self { let key_utf32 = state.key_get_utf32(keycode);
let key_utf32 = state.key_get_utf32(keycode); let key_utf8 = state.key_get_utf8(keycode);
let key_utf8 = state.key_get_utf8(keycode); let key_sym = state.key_get_one_sym(keycode);
let key_sym = state.key_get_one_sym(keycode);
let key = match key_sym { let key = match key_sym {
Keysym::Return => "enter".to_owned(), Keysym::Return => "enter".to_owned(),
Keysym::Prior => "pageup".to_owned(), Keysym::Prior => "pageup".to_owned(),
Keysym::Next => "pagedown".to_owned(), Keysym::Next => "pagedown".to_owned(),
Keysym::ISO_Left_Tab => "tab".to_owned(), Keysym::ISO_Left_Tab => "tab".to_owned(),
Keysym::KP_Prior => "pageup".to_owned(), Keysym::KP_Prior => "pageup".to_owned(),
Keysym::KP_Next => "pagedown".to_owned(), Keysym::KP_Next => "pagedown".to_owned(),
Keysym::XF86_Back => "back".to_owned(), Keysym::XF86_Back => "back".to_owned(),
Keysym::XF86_Forward => "forward".to_owned(), Keysym::XF86_Forward => "forward".to_owned(),
Keysym::XF86_Cut => "cut".to_owned(), Keysym::XF86_Cut => "cut".to_owned(),
Keysym::XF86_Copy => "copy".to_owned(), Keysym::XF86_Copy => "copy".to_owned(),
Keysym::XF86_Paste => "paste".to_owned(), Keysym::XF86_Paste => "paste".to_owned(),
Keysym::XF86_New => "new".to_owned(), Keysym::XF86_New => "new".to_owned(),
Keysym::XF86_Open => "open".to_owned(), Keysym::XF86_Open => "open".to_owned(),
Keysym::XF86_Save => "save".to_owned(), Keysym::XF86_Save => "save".to_owned(),
Keysym::comma => ",".to_owned(), Keysym::comma => ",".to_owned(),
Keysym::period => ".".to_owned(), Keysym::period => ".".to_owned(),
Keysym::less => "<".to_owned(), Keysym::less => "<".to_owned(),
Keysym::greater => ">".to_owned(), Keysym::greater => ">".to_owned(),
Keysym::slash => "/".to_owned(), Keysym::slash => "/".to_owned(),
Keysym::question => "?".to_owned(), Keysym::question => "?".to_owned(),
Keysym::semicolon => ";".to_owned(), Keysym::semicolon => ";".to_owned(),
Keysym::colon => ":".to_owned(), Keysym::colon => ":".to_owned(),
Keysym::apostrophe => "'".to_owned(), Keysym::apostrophe => "'".to_owned(),
Keysym::quotedbl => "\"".to_owned(), Keysym::quotedbl => "\"".to_owned(),
Keysym::bracketleft => "[".to_owned(), Keysym::bracketleft => "[".to_owned(),
Keysym::braceleft => "{".to_owned(), Keysym::braceleft => "{".to_owned(),
Keysym::bracketright => "]".to_owned(), Keysym::bracketright => "]".to_owned(),
Keysym::braceright => "}".to_owned(), Keysym::braceright => "}".to_owned(),
Keysym::backslash => "\\".to_owned(), Keysym::backslash => "\\".to_owned(),
Keysym::bar => "|".to_owned(), Keysym::bar => "|".to_owned(),
Keysym::grave => "`".to_owned(), Keysym::grave => "`".to_owned(),
Keysym::asciitilde => "~".to_owned(), Keysym::asciitilde => "~".to_owned(),
Keysym::exclam => "!".to_owned(), Keysym::exclam => "!".to_owned(),
Keysym::at => "@".to_owned(), Keysym::at => "@".to_owned(),
Keysym::numbersign => "#".to_owned(), Keysym::numbersign => "#".to_owned(),
Keysym::dollar => "$".to_owned(), Keysym::dollar => "$".to_owned(),
Keysym::percent => "%".to_owned(), Keysym::percent => "%".to_owned(),
Keysym::asciicircum => "^".to_owned(), Keysym::asciicircum => "^".to_owned(),
Keysym::ampersand => "&".to_owned(), Keysym::ampersand => "&".to_owned(),
Keysym::asterisk => "*".to_owned(), Keysym::asterisk => "*".to_owned(),
Keysym::parenleft => "(".to_owned(), Keysym::parenleft => "(".to_owned(),
Keysym::parenright => ")".to_owned(), Keysym::parenright => ")".to_owned(),
Keysym::minus => "-".to_owned(), Keysym::minus => "-".to_owned(),
Keysym::underscore => "_".to_owned(), Keysym::underscore => "_".to_owned(),
Keysym::equal => "=".to_owned(), Keysym::equal => "=".to_owned(),
Keysym::plus => "+".to_owned(), Keysym::plus => "+".to_owned(),
Keysym::space => "space".to_owned(), Keysym::space => "space".to_owned(),
Keysym::BackSpace => "backspace".to_owned(), Keysym::BackSpace => "backspace".to_owned(),
Keysym::Tab => "tab".to_owned(), Keysym::Tab => "tab".to_owned(),
Keysym::Delete => "delete".to_owned(), Keysym::Delete => "delete".to_owned(),
Keysym::Escape => "escape".to_owned(), Keysym::Escape => "escape".to_owned(),
Keysym::Left => "left".to_owned(), Keysym::Left => "left".to_owned(),
Keysym::Right => "right".to_owned(), Keysym::Right => "right".to_owned(),
Keysym::Up => "up".to_owned(), Keysym::Up => "up".to_owned(),
Keysym::Down => "down".to_owned(), Keysym::Down => "down".to_owned(),
Keysym::Home => "home".to_owned(), Keysym::Home => "home".to_owned(),
Keysym::End => "end".to_owned(), Keysym::End => "end".to_owned(),
Keysym::Insert => "insert".to_owned(), Keysym::Insert => "insert".to_owned(),
_ => { _ => {
let name = xkb::keysym_get_name(key_sym).to_lowercase(); let name = xkb::keysym_get_name(key_sym).to_lowercase();
if key_sym.is_keypad_key() { if key_sym.is_keypad_key() {
name.replace("kp_", "") name.replace("kp_", "")
} else if let Some(key) = key_utf8.chars().next() } else if let Some(key) = key_utf8.chars().next()
&& key_utf8.len() == 1 && key_utf8.len() == 1
&& key.is_ascii() && key.is_ascii()
{
if key.is_ascii_graphic() {
key_utf8.to_lowercase()
// map ctrl-a to `a`
// ctrl-0..9 may emit control codes like ctrl-[, but
// we don't want to map them to `[`
} else if key_utf32 <= 0x1f
&& !name.chars().next().is_some_and(|c| c.is_ascii_digit())
{ {
if key.is_ascii_graphic() { ((key_utf32 as u8 + 0x40) as char)
key_utf8.to_lowercase() .to_ascii_lowercase()
// map ctrl-a to `a` .to_string()
// ctrl-0..9 may emit control codes like ctrl-[, but
// we don't want to map them to `[`
} else if key_utf32 <= 0x1f
&& !name.chars().next().is_some_and(|c| c.is_ascii_digit())
{
((key_utf32 as u8 + 0x40) as char)
.to_ascii_lowercase()
.to_string()
} else {
name
}
} else if let Some(key_en) = guess_ascii(keycode, modifiers.shift) {
String::from(key_en)
} else { } else {
name name
} }
} } else if let Some(key_en) = guess_ascii(keycode, modifiers.shift) {
}; String::from(key_en)
} else {
if modifiers.shift { name
// we only include the shift for upper-case letters by convention,
// so don't include for numbers and symbols, but do include for
// tab/enter, etc.
if key.chars().count() == 1 && key.to_lowercase() == key.to_uppercase() {
modifiers.shift = false;
} }
} }
};
// Ignore control characters (and DEL) for the purposes of key_char if modifiers.shift {
let key_char = // we only include the shift for upper-case letters by convention,
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8); // so don't include for numbers and symbols, but do include for
// tab/enter, etc.
Self { if key.chars().count() == 1 && key.to_lowercase() == key.to_uppercase() {
modifiers, modifiers.shift = false;
key,
key_char,
} }
} }
/** // Ignore control characters (and DEL) for the purposes of key_char
* Returns which symbol the dead key represents let key_char =
* <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux> (key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
*/
pub fn underlying_dead_key(keysym: Keysym) -> Option<String> { gpui::Keystroke {
match keysym { modifiers,
Keysym::dead_grave => Some("`".to_owned()), key,
Keysym::dead_acute => Some("´".to_owned()), key_char,
Keysym::dead_circumflex => Some("^".to_owned()), }
Keysym::dead_tilde => Some("~".to_owned()), }
Keysym::dead_macron => Some("¯".to_owned()),
Keysym::dead_breve => Some("˘".to_owned()), /**
Keysym::dead_abovedot => Some("˙".to_owned()), * Returns which symbol the dead key represents
Keysym::dead_diaeresis => Some("¨".to_owned()), * <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
Keysym::dead_abovering => Some("˚".to_owned()), */
Keysym::dead_doubleacute => Some("˝".to_owned()), #[cfg(any(feature = "wayland", feature = "x11"))]
Keysym::dead_caron => Some("ˇ".to_owned()), pub fn keystroke_underlying_dead_key(keysym: Keysym) -> Option<String> {
Keysym::dead_cedilla => Some("¸".to_owned()), match keysym {
Keysym::dead_ogonek => Some("˛".to_owned()), Keysym::dead_grave => Some("`".to_owned()),
Keysym::dead_iota => Some("ͅ".to_owned()), Keysym::dead_acute => Some("´".to_owned()),
Keysym::dead_voiced_sound => Some("゙".to_owned()), Keysym::dead_circumflex => Some("^".to_owned()),
Keysym::dead_semivoiced_sound => Some("゚".to_owned()), Keysym::dead_tilde => Some("~".to_owned()),
Keysym::dead_belowdot => Some("̣̣".to_owned()), Keysym::dead_macron => Some("¯".to_owned()),
Keysym::dead_hook => Some("̡".to_owned()), Keysym::dead_breve => Some("˘".to_owned()),
Keysym::dead_horn => Some("̛".to_owned()), Keysym::dead_abovedot => Some("˙".to_owned()),
Keysym::dead_stroke => Some("̶̶".to_owned()), Keysym::dead_diaeresis => Some("¨".to_owned()),
Keysym::dead_abovecomma => Some("̓̓".to_owned()), Keysym::dead_abovering => Some("˚".to_owned()),
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()), Keysym::dead_doubleacute => Some("˝".to_owned()),
Keysym::dead_doublegrave => Some("̏".to_owned()), Keysym::dead_caron => Some("ˇ".to_owned()),
Keysym::dead_belowring => Some("˳".to_owned()), Keysym::dead_cedilla => Some("¸".to_owned()),
Keysym::dead_belowmacron => Some("̱".to_owned()), Keysym::dead_ogonek => Some("˛".to_owned()),
Keysym::dead_belowcircumflex => Some("ꞈ".to_owned()), Keysym::dead_iota => Some("ͅ".to_owned()),
Keysym::dead_belowtilde => Some("̰".to_owned()), Keysym::dead_voiced_sound => Some("゙".to_owned()),
Keysym::dead_belowbreve => Some("̮".to_owned()), Keysym::dead_semivoiced_sound => Some("゚".to_owned()),
Keysym::dead_belowdiaeresis => Some("̤".to_owned()), Keysym::dead_belowdot => Some("̣̣".to_owned()),
Keysym::dead_invertedbreve => Some("̯".to_owned()), Keysym::dead_hook => Some("̡".to_owned()),
Keysym::dead_belowcomma => Some("̦".to_owned()), Keysym::dead_horn => Some("̛".to_owned()),
Keysym::dead_currency => None, Keysym::dead_stroke => Some("̶̶".to_owned()),
Keysym::dead_lowline => None, Keysym::dead_abovecomma => Some("̓̓".to_owned()),
Keysym::dead_aboveverticalline => None, Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
Keysym::dead_belowverticalline => None, Keysym::dead_doublegrave => Some("̏".to_owned()),
Keysym::dead_longsolidusoverlay => None, Keysym::dead_belowring => Some("˳".to_owned()),
Keysym::dead_a => None, Keysym::dead_belowmacron => Some("̱".to_owned()),
Keysym::dead_A => None, Keysym::dead_belowcircumflex => Some("ꞈ".to_owned()),
Keysym::dead_e => None, Keysym::dead_belowtilde => Some("̰".to_owned()),
Keysym::dead_E => None, Keysym::dead_belowbreve => Some("̮".to_owned()),
Keysym::dead_i => None, Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
Keysym::dead_I => None, Keysym::dead_invertedbreve => Some("̯".to_owned()),
Keysym::dead_o => None, Keysym::dead_belowcomma => Some("̦".to_owned()),
Keysym::dead_O => None, Keysym::dead_currency => None,
Keysym::dead_u => None, Keysym::dead_lowline => None,
Keysym::dead_U => None, Keysym::dead_aboveverticalline => None,
Keysym::dead_small_schwa => Some("ə".to_owned()), Keysym::dead_belowverticalline => None,
Keysym::dead_capital_schwa => Some("Ə".to_owned()), Keysym::dead_longsolidusoverlay => None,
Keysym::dead_greek => None, Keysym::dead_a => None,
_ => None, Keysym::dead_A => None,
} Keysym::dead_e => None,
Keysym::dead_E => None,
Keysym::dead_i => None,
Keysym::dead_I => None,
Keysym::dead_o => None,
Keysym::dead_O => None,
Keysym::dead_u => None,
Keysym::dead_U => None,
Keysym::dead_small_schwa => Some("ə".to_owned()),
Keysym::dead_capital_schwa => Some("Ə".to_owned()),
Keysym::dead_greek => None,
_ => None,
}
}
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(super) fn modifiers_from_xkb(keymap_state: &State) -> gpui::Modifiers {
let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
let alt = keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
let control = keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
let platform = keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
gpui::Modifiers {
shift,
alt,
control,
platform,
function: false,
} }
} }
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
impl crate::Modifiers { pub(super) fn capslock_from_xkb(keymap_state: &State) -> gpui::Capslock {
pub(super) fn from_xkb(keymap_state: &State) -> Self { let on = keymap_state.mod_name_is_active(xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE);
let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE); gpui::Capslock { on }
let alt = keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
let control =
keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
let platform =
keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
Self {
shift,
alt,
control,
platform,
function: false,
}
}
}
#[cfg(any(feature = "wayland", feature = "x11"))]
impl crate::Capslock {
pub(super) fn from_xkb(keymap_state: &State) -> Self {
let on = keymap_state.mod_name_is_active(xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE);
Self { on }
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{Point, px}; use gpui::{Point, px};
#[test] #[test]
fn test_is_within_click_distance() { fn test_is_within_click_distance() {
@@ -1,22 +1,17 @@
use crate::{
Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight,
GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, SUBPIXEL_VARIANTS_X,
SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point,
size,
};
use anyhow::{Context as _, Ok, Result}; use anyhow::{Context as _, Ok, Result};
use collections::HashMap; use collections::HashMap;
use cosmic_text::{ use cosmic_text::{
Attrs, AttrsList, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures, Attrs, AttrsList, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures,
FontSystem, ShapeBuffer, ShapeLine, FontSystem, ShapeBuffer, ShapeLine,
}; };
use gpui::{
Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, GlyphId, LineLayout,
Pixels, PlatformTextSystem, RenderGlyphParams, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y,
ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, size,
};
use itertools::Itertools; use itertools::Itertools;
use parking_lot::RwLock; use parking_lot::RwLock;
use pathfinder_geometry::{
rect::{RectF, RectI},
vector::{Vector2F, Vector2I},
};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, sync::Arc};
use swash::{ use swash::{
@@ -58,7 +53,7 @@ struct LoadedFont {
impl CosmicTextSystem { impl CosmicTextSystem {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
// todo(linux) make font loading non-blocking // todo(linux) make font loading non-blocking
let mut font_system = FontSystem::new(); let font_system = FontSystem::new();
Self(RwLock::new(CosmicTextSystemState { Self(RwLock::new(CosmicTextSystemState {
font_system, font_system,
@@ -227,7 +222,7 @@ impl CosmicTextSystemState {
features: &FontFeatures, features: &FontFeatures,
) -> Result<SmallVec<[FontId; 4]>> { ) -> Result<SmallVec<[FontId; 4]>> {
// TODO: Determine the proper system UI font. // TODO: Determine the proper system UI font.
let name = crate::text_system::font_name_with_fallbacks(name, "IBM Plex Sans"); let name = gpui::font_name_with_fallbacks(name, "IBM Plex Sans");
let families = self let families = self
.font_system .font_system
@@ -261,7 +256,7 @@ impl CosmicTextSystemState {
loaded_font_ids.push(font_id); loaded_font_ids.push(font_id);
self.loaded_fonts.push(LoadedFont { self.loaded_fonts.push(LoadedFont {
font, font,
features: features.try_into()?, features: cosmic_font_features(features)?,
is_known_emoji_font: check_is_known_emoji_font(&postscript_name), is_known_emoji_font: check_is_known_emoji_font(&postscript_name),
}); });
} }
@@ -324,7 +319,7 @@ impl CosmicTextSystemState {
) -> Result<swash::scale::image::Image> { ) -> Result<swash::scale::image::Image> {
let loaded_font = &self.loaded_fonts[params.font_id.0]; let loaded_font = &self.loaded_fonts[params.font_id.0];
let font_ref = loaded_font.font.as_swash(); let font_ref = loaded_font.font.as_swash();
let pixel_size = params.font_size.0; let pixel_size = f32::from(params.font_size);
let subpixel_offset = Vector::new( let subpixel_offset = Vector::new(
params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor, params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor,
@@ -428,7 +423,7 @@ impl CosmicTextSystemState {
let mut layout_lines = Vec::with_capacity(1); let mut layout_lines = Vec::with_capacity(1);
line.layout_to_buffer( line.layout_to_buffer(
&mut self.scratch, &mut self.scratch,
font_size.0, f32::from(font_size),
None, // We do our own wrapping None, // We do our own wrapping
cosmic_text::Wrap::None, cosmic_text::Wrap::None,
None, None,
@@ -484,93 +479,28 @@ impl CosmicTextSystemState {
} }
} }
impl TryFrom<&FontFeatures> for CosmicFontFeatures { fn cosmic_font_features(features: &FontFeatures) -> Result<CosmicFontFeatures> {
type Error = anyhow::Error; let mut result = CosmicFontFeatures::new();
for feature in features.0.iter() {
let name_bytes: [u8; 4] = feature
.0
.as_bytes()
.try_into()
.context("Incorrect feature flag format")?;
fn try_from(features: &FontFeatures) -> Result<Self> { let tag = cosmic_text::FeatureTag::new(&name_bytes);
let mut result = CosmicFontFeatures::new();
for feature in features.0.iter() {
let name_bytes: [u8; 4] = feature
.0
.as_bytes()
.try_into()
.context("Incorrect feature flag format")?;
let tag = cosmic_text::FeatureTag::new(&name_bytes); result.set(tag, feature.1);
result.set(tag, feature.1);
}
Ok(result)
} }
Ok(result)
} }
impl From<RectF> for Bounds<f32> { fn font_into_properties(font: &gpui::Font) -> font_kit::properties::Properties {
fn from(rect: RectF) -> Self {
Bounds {
origin: point(rect.origin_x(), rect.origin_y()),
size: size(rect.width(), rect.height()),
}
}
}
impl From<RectI> for Bounds<DevicePixels> {
fn from(rect: RectI) -> Self {
Bounds {
origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
}
}
}
impl From<Vector2I> for Size<DevicePixels> {
fn from(value: Vector2I) -> Self {
size(value.x().into(), value.y().into())
}
}
impl From<RectI> for Bounds<i32> {
fn from(rect: RectI) -> Self {
Bounds {
origin: point(rect.origin_x(), rect.origin_y()),
size: size(rect.width(), rect.height()),
}
}
}
impl From<Point<u32>> for Vector2I {
fn from(size: Point<u32>) -> Self {
Vector2I::new(size.x as i32, size.y as i32)
}
}
impl From<Vector2F> for Size<f32> {
fn from(vec: Vector2F) -> Self {
size(vec.x(), vec.y())
}
}
impl From<FontWeight> for cosmic_text::Weight {
fn from(value: FontWeight) -> Self {
cosmic_text::Weight(value.0 as u16)
}
}
impl From<FontStyle> for cosmic_text::Style {
fn from(style: FontStyle) -> Self {
match style {
FontStyle::Normal => cosmic_text::Style::Normal,
FontStyle::Italic => cosmic_text::Style::Italic,
FontStyle::Oblique => cosmic_text::Style::Oblique,
}
}
}
fn font_into_properties(font: &crate::Font) -> font_kit::properties::Properties {
font_kit::properties::Properties { font_kit::properties::Properties {
style: match font.style { style: match font.style {
crate::FontStyle::Normal => font_kit::properties::Style::Normal, gpui::FontStyle::Normal => font_kit::properties::Style::Normal,
crate::FontStyle::Italic => font_kit::properties::Style::Italic, gpui::FontStyle::Italic => font_kit::properties::Style::Italic,
crate::FontStyle::Oblique => font_kit::properties::Style::Oblique, gpui::FontStyle::Oblique => font_kit::properties::Style::Oblique,
}, },
weight: font_kit::properties::Weight(font.weight.0), weight: font_kit::properties::Weight(font.weight.0),
stretch: Default::default(), stretch: Default::default(),
+47
View File
@@ -0,0 +1,47 @@
mod client;
mod clipboard;
mod cursor;
mod display;
mod serial;
mod window;
/// Contains Types for configuring layer_shell surfaces.
pub mod layer_shell;
pub(crate) use client::*;
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
use gpui::CursorStyle;
pub(super) fn to_shape(style: CursorStyle) -> Shape {
match style {
CursorStyle::Arrow => Shape::Default,
CursorStyle::IBeam => Shape::Text,
CursorStyle::Crosshair => Shape::Crosshair,
CursorStyle::ClosedHand => Shape::Grabbing,
CursorStyle::OpenHand => Shape::Grab,
CursorStyle::PointingHand => Shape::Pointer,
CursorStyle::ResizeLeft => Shape::WResize,
CursorStyle::ResizeRight => Shape::EResize,
CursorStyle::ResizeLeftRight => Shape::EwResize,
CursorStyle::ResizeUp => Shape::NResize,
CursorStyle::ResizeDown => Shape::SResize,
CursorStyle::ResizeUpDown => Shape::NsResize,
CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize,
CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize,
CursorStyle::ResizeColumn => Shape::ColResize,
CursorStyle::ResizeRow => Shape::RowResize,
CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
CursorStyle::OperationNotAllowed => Shape::NotAllowed,
CursorStyle::DragLink => Shape::Alias,
CursorStyle::DragCopy => Shape::Copy,
CursorStyle::ContextualMenu => Shape::ContextMenu,
CursorStyle::None => {
#[cfg(debug_assertions)]
panic!("CursorStyle::None should be handled separately in the client");
#[cfg(not(debug_assertions))]
Shape::Default
}
}
}
@@ -73,32 +73,29 @@ use super::{
window::{ImeInput, WaylandWindowStatePtr}, window::{ImeInput, WaylandWindowStatePtr},
}; };
use crate::{ use crate::linux::{
AnyWindowHandle, Bounds, Capslock, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId, DOUBLE_CLICK_INTERVAL, LinuxClient, LinuxCommon, LinuxKeyboardLayout, ResultExt as _,
FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, SCROLL_LINES, capslock_from_xkb, cursor_style_to_icon_names, get_xkb_compose_state,
LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, is_within_click_distance, keystroke_from_xkb, keystroke_underlying_dead_key,
MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, modifiers_from_xkb, open_uri_internal, read_fd, reveal_path_internal,
PlatformInput, PlatformKeyboardLayout, Point, ResultExt as _, SCROLL_LINES, ScrollDelta, wayland::{
ScrollWheelEvent, Size, TouchPhase, WindowParams, point, profiler, px, size, clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
}; cursor::Cursor,
use crate::{ serial::{SerialKind, SerialTracker},
SharedString, to_shape,
platform::linux::{ window::WaylandWindow,
LinuxClient, get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
reveal_path_internal,
wayland::{
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
cursor::Cursor,
serial::{SerialKind, SerialTracker},
window::WaylandWindow,
},
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
}, },
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
}; };
use crate::{ use gpui::{
TaskTiming, AnyWindowHandle, Bounds, Capslock, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
platform::{PlatformWindow, wgpu::WgpuContext}, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
Pixels, PlatformDisplay, PlatformInput, PlatformKeyboardLayout, PlatformWindow, Point,
ScrollDelta, ScrollWheelEvent, SharedString, Size, TaskTiming, TouchPhase, WindowParams, point,
profiler, px, size,
}; };
use gpui_wgpu::WgpuContext;
/// Used to convert evdev scancode to xkb scancode /// Used to convert evdev scancode to xkb scancode
const MIN_KEYCODE: u32 = 8; const MIN_KEYCODE: u32 = 8;
@@ -303,7 +300,7 @@ impl WaylandClientStatePtr {
pub fn enable_ime(&self) { pub fn enable_ime(&self) {
let client = self.get_client(); let client = self.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
let Some(mut text_input) = state.text_input.take() else { let Some(text_input) = state.text_input.take() else {
return; return;
}; };
@@ -313,10 +310,10 @@ impl WaylandClientStatePtr {
drop(state); drop(state);
if let Some(area) = window.get_ime_area() { if let Some(area) = window.get_ime_area() {
text_input.set_cursor_rectangle( text_input.set_cursor_rectangle(
area.origin.x.0 as i32, f32::from(area.origin.x) as i32,
area.origin.y.0 as i32, f32::from(area.origin.y) as i32,
area.size.width.0 as i32, f32::from(area.size.width) as i32,
area.size.height.0 as i32, f32::from(area.size.height) as i32,
); );
} }
state = client.borrow_mut(); state = client.borrow_mut();
@@ -337,17 +334,17 @@ impl WaylandClientStatePtr {
pub fn update_ime_position(&self, bounds: Bounds<Pixels>) { pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {
let client = self.get_client(); let client = self.get_client();
let mut state = client.borrow_mut(); let state = client.borrow_mut();
if state.composing || state.text_input.is_none() || state.pre_edit_text.is_some() { if state.composing || state.text_input.is_none() || state.pre_edit_text.is_some() {
return; return;
} }
let text_input = state.text_input.as_ref().unwrap(); let text_input = state.text_input.as_ref().unwrap();
text_input.set_cursor_rectangle( text_input.set_cursor_rectangle(
bounds.origin.x.0 as i32, bounds.origin.x.as_f32() as i32,
bounds.origin.y.0 as i32, bounds.origin.y.as_f32() as i32,
bounds.size.width.0 as i32, bounds.size.width.as_f32() as i32,
bounds.size.height.0 as i32, bounds.size.height.as_f32() as i32,
); );
text_input.commit(); text_input.commit();
} }
@@ -382,7 +379,7 @@ impl WaylandClientStatePtr {
} }
pub fn drop_window(&self, surface_id: &ObjectId) { pub fn drop_window(&self, surface_id: &ObjectId) {
let mut client = self.get_client(); let client = self.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
let closed_window = state.windows.remove(surface_id).unwrap(); let closed_window = state.windows.remove(surface_id).unwrap();
if let Some(window) = state.mouse_focused_window.take() if let Some(window) = state.mouse_focused_window.take()
@@ -456,8 +453,7 @@ impl WaylandClient {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let conn = Connection::connect_to_env().unwrap(); let conn = Connection::connect_to_env().unwrap();
let (globals, mut event_queue) = let (globals, event_queue) = registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
let qh = event_queue.handle(); let qh = event_queue.handle();
let mut seat: Option<wl_seat::WlSeat> = None; let mut seat: Option<wl_seat::WlSeat> = None;
@@ -540,7 +536,7 @@ impl WaylandClient {
.as_ref() .as_ref()
.map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ())); .map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ()));
let mut cursor = Cursor::new(&conn, &globals, 24); let cursor = Cursor::new(&conn, &globals, 24);
handle handle
.insert_source(XDPEventSource::new(&common.background_executor), { .insert_source(XDPEventSource::new(&common.background_executor), {
@@ -572,7 +568,7 @@ impl WaylandClient {
}) })
.unwrap(); .unwrap();
let mut state = Rc::new(RefCell::new(WaylandClientState { let state = Rc::new(RefCell::new(WaylandClientState {
serial_tracker: SerialTracker::new(), serial_tracker: SerialTracker::new(),
globals, globals,
gpu_context, gpu_context,
@@ -673,7 +669,7 @@ impl LinuxClient for WaylandClient {
.outputs .outputs
.iter() .iter()
.find_map(|(object_id, output)| { .find_map(|(object_id, output)| {
(object_id.protocol_id() == id.0).then(|| { (object_id.protocol_id() == u32::from(id)).then(|| {
Rc::new(WaylandDisplay { Rc::new(WaylandDisplay {
id: object_id.clone(), id: object_id.clone(),
name: output.name.clone(), name: output.name.clone(),
@@ -695,7 +691,7 @@ impl LinuxClient for WaylandClient {
#[cfg(feature = "screen-capture")] #[cfg(feature = "screen-capture")]
fn screen_capture_sources( fn screen_capture_sources(
&self, &self,
) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>> ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>>
{ {
// TODO: Get screen capture working on wayland. Be sure to try window resizing as that may // TODO: Get screen capture working on wayland. Be sure to try window resizing as that may
// be tricky. // be tricky.
@@ -754,7 +750,7 @@ impl LinuxClient for WaylandClient {
.expect("window is focused by pointer"); .expect("window is focused by pointer");
wl_pointer.set_cursor(serial, None, 0, 0); wl_pointer.set_cursor(serial, None, 0, 0);
} else if let Some(cursor_shape_device) = &state.cursor_shape_device { } else if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, style.to_shape()); cursor_shape_device.set_shape(serial, to_shape(style));
} else if let Some(focused_window) = &state.mouse_focused_window { } else if let Some(focused_window) = &state.mouse_focused_window {
// cursor-shape-v1 isn't supported, set the cursor using a surface. // cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state let wl_pointer = state
@@ -762,9 +758,12 @@ impl LinuxClient for WaylandClient {
.clone() .clone()
.expect("window is focused by pointer"); .expect("window is focused by pointer");
let scale = focused_window.primary_output_scale(); let scale = focused_window.primary_output_scale();
state state.cursor.set_icon(
.cursor &wl_pointer,
.set_icon(&wl_pointer, serial, style.to_icon_names(), scale); serial,
cursor_style_to_icon_names(style),
scale,
);
} }
} }
} }
@@ -826,7 +825,7 @@ impl LinuxClient for WaylandClient {
.log_err(); .log_err();
} }
fn write_to_primary(&self, item: crate::ClipboardItem) { fn write_to_primary(&self, item: gpui::ClipboardItem) {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let (Some(primary_selection_manager), Some(primary_selection)) = ( let (Some(primary_selection_manager), Some(primary_selection)) = (
state.globals.primary_selection_manager.clone(), state.globals.primary_selection_manager.clone(),
@@ -846,7 +845,7 @@ impl LinuxClient for WaylandClient {
} }
} }
fn write_to_clipboard(&self, item: crate::ClipboardItem) { fn write_to_clipboard(&self, item: gpui::ClipboardItem) {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let (Some(data_device_manager), Some(data_device)) = ( let (Some(data_device_manager), Some(data_device)) = (
state.globals.data_device_manager.clone(), state.globals.data_device_manager.clone(),
@@ -866,11 +865,11 @@ impl LinuxClient for WaylandClient {
} }
} }
fn read_from_primary(&self) -> Option<crate::ClipboardItem> { fn read_from_primary(&self) -> Option<gpui::ClipboardItem> {
self.0.borrow_mut().clipboard.read_primary() self.0.borrow_mut().clipboard.read_primary()
} }
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> { fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
self.0.borrow_mut().clipboard.read() self.0.borrow_mut().clipboard.read()
} }
@@ -914,7 +913,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
_: &Connection, _: &Connection,
qh: &QueueHandle<Self>, qh: &QueueHandle<Self>,
) { ) {
let mut client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
match event { match event {
@@ -1002,7 +1001,7 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
} }
pub(crate) fn get_window( pub(crate) fn get_window(
mut state: &mut RefMut<WaylandClientState>, state: &mut RefMut<WaylandClientState>,
surface_id: &ObjectId, surface_id: &ObjectId,
) -> Option<WaylandWindowStatePtr> { ) -> Option<WaylandWindowStatePtr> {
state.windows.get(surface_id).cloned() state.windows.get(surface_id).cloned()
@@ -1017,7 +1016,7 @@ impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
_: &Connection, _: &Connection,
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
let mut client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
let Some(window) = get_window(&mut state, &surface.id()) else { let Some(window) = get_window(&mut state, &surface.id()) else {
@@ -1040,10 +1039,10 @@ impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
_: &Connection, _: &Connection,
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
let mut client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
let Some(mut in_progress_output) = state.in_progress_outputs.get_mut(&output.id()) else { let Some(in_progress_output) = state.in_progress_outputs.get_mut(&output.id()) else {
return; return;
}; };
@@ -1257,7 +1256,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
_: &Connection, _: &Connection,
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
let mut client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
match event { match event {
wl_keyboard::Event::RepeatInfo { rate, delay } => { wl_keyboard::Event::RepeatInfo { rate, delay } => {
@@ -1332,9 +1331,9 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
let old_layout = let old_layout =
keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE); keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group); keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
state.modifiers = Modifiers::from_xkb(keymap_state); state.modifiers = modifiers_from_xkb(keymap_state);
let keymap_state = state.keymap_state.as_mut().unwrap(); let keymap_state = state.keymap_state.as_mut().unwrap();
state.capslock = Capslock::from_xkb(keymap_state); state.capslock = capslock_from_xkb(keymap_state);
let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent { let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers: state.modifiers, modifiers: state.modifiers,
@@ -1370,14 +1369,14 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
match key_state { match key_state {
wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => { wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
let mut keystroke = let mut keystroke =
Keystroke::from_xkb(keymap_state, state.modifiers, keycode); keystroke_from_xkb(keymap_state, state.modifiers, keycode);
if let Some(mut compose) = state.compose_state.take() { if let Some(mut compose) = state.compose_state.take() {
compose.feed(keysym); compose.feed(keysym);
match compose.status() { match compose.status() {
xkb::Status::Composing => { xkb::Status::Composing => {
keystroke.key_char = None; keystroke.key_char = None;
state.pre_edit_text = state.pre_edit_text =
compose.utf8().or(Keystroke::underlying_dead_key(keysym)); compose.utf8().or(keystroke_underlying_dead_key(keysym));
let pre_edit = let pre_edit =
state.pre_edit_text.clone().unwrap_or(String::default()); state.pre_edit_text.clone().unwrap_or(String::default());
drop(state); drop(state);
@@ -1394,7 +1393,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
} }
xkb::Status::Cancelled => { xkb::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take(); let pre_edit = state.pre_edit_text.take();
let new_pre_edit = Keystroke::underlying_dead_key(keysym); let new_pre_edit = keystroke_underlying_dead_key(keysym);
state.pre_edit_text = new_pre_edit.clone(); state.pre_edit_text = new_pre_edit.clone();
drop(state); drop(state);
if let Some(pre_edit) = pre_edit { if let Some(pre_edit) = pre_edit {
@@ -1432,8 +1431,8 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
prefer_character_input: false, prefer_character_input: false,
}); });
move |event_timestamp, _metadata, this| { move |event_timestamp, _metadata, this| {
let mut client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let state = client.borrow();
let is_repeating = id == state.repeat.current_id let is_repeating = id == state.repeat.current_id
&& state.repeat.current_keycode.is_some() && state.repeat.current_keycode.is_some()
&& state.keyboard_focused_window.is_some(); && state.keyboard_focused_window.is_some();
@@ -1459,7 +1458,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
} }
wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => { wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
let input = PlatformInput::KeyUp(KeyUpEvent { let input = PlatformInput::KeyUp(KeyUpEvent {
keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode), keystroke: keystroke_from_xkb(keymap_state, state.modifiers, keycode),
}); });
if state.repeat.current_keycode == Some(keycode) { if state.repeat.current_keycode == Some(keycode) {
@@ -1538,10 +1537,10 @@ impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
window.handle_ime(ImeInput::SetMarkedText(text)); window.handle_ime(ImeInput::SetMarkedText(text));
if let Some(area) = window.get_ime_area() { if let Some(area) = window.get_ime_area() {
text_input.set_cursor_rectangle( text_input.set_cursor_rectangle(
area.origin.x.0 as i32, f32::from(area.origin.x) as i32,
area.origin.y.0 as i32, f32::from(area.origin.y) as i32,
area.size.width.0 as i32, f32::from(area.size.width) as i32,
area.size.height.0 as i32, f32::from(area.size.height) as i32,
); );
if last_serial == serial { if last_serial == serial {
text_input.commit(); text_input.commit();
@@ -1587,7 +1586,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
_: &Connection, _: &Connection,
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
let mut client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
match event { match event {
@@ -1616,12 +1615,15 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
.expect("window is focused by pointer"); .expect("window is focused by pointer");
wl_pointer.set_cursor(serial, None, 0, 0); wl_pointer.set_cursor(serial, None, 0, 0);
} else if let Some(cursor_shape_device) = &state.cursor_shape_device { } else if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, style.to_shape()); cursor_shape_device.set_shape(serial, to_shape(style));
} else { } else {
let scale = window.primary_output_scale(); let scale = window.primary_output_scale();
state state.cursor.set_icon(
.cursor wl_pointer,
.set_icon(wl_pointer, serial, style.to_icon_names(), scale); serial,
cursor_style_to_icon_names(style),
scale,
);
} }
} }
drop(state); drop(state);
@@ -1662,7 +1664,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
state.cursor_style = Some(default_style); state.cursor_style = Some(default_style);
if let Some(cursor_shape_device) = &state.cursor_shape_device { if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, default_style.to_shape()); cursor_shape_device.set_shape(serial, to_shape(default_style));
} else { } else {
// cursor-shape-v1 isn't supported, set the cursor using a surface. // cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state let wl_pointer = state
@@ -1673,7 +1675,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
state.cursor.set_icon( state.cursor.set_icon(
&wl_pointer, &wl_pointer,
serial, serial,
default_style.to_icon_names(), cursor_style_to_icon_names(default_style),
scale, scale,
); );
} }
@@ -2043,7 +2045,7 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
let input = PlatformInput::FileDrop(FileDropEvent::Entered { let input = PlatformInput::FileDrop(FileDropEvent::Entered {
position, position,
paths: crate::ExternalPaths(paths), paths: gpui::ExternalPaths(paths),
}); });
let client = this.get_client(); let client = this.get_client();
@@ -2151,7 +2153,7 @@ impl Dispatch<wl_data_source::WlDataSource, ()> for WaylandClientStatePtr {
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
let client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let state = client.borrow_mut();
match event { match event {
wl_data_source::Event::Send { mime_type, fd } => { wl_data_source::Event::Send { mime_type, fd } => {
@@ -2237,7 +2239,7 @@ impl Dispatch<zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, ()>
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
let client = this.get_client(); let client = this.get_client();
let mut state = client.borrow_mut(); let state = client.borrow_mut();
match event { match event {
zwp_primary_selection_source_v1::Event::Send { mime_type, fd } => { zwp_primary_selection_source_v1::Event::Send { mime_type, fd } => {
@@ -10,10 +10,8 @@ use strum::IntoEnumIterator;
use wayland_client::{Connection, protocol::wl_data_offer::WlDataOffer}; use wayland_client::{Connection, protocol::wl_data_offer::WlDataOffer};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1; use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use crate::{ use crate::linux::{WaylandClientStatePtr, platform::read_fd};
ClipboardEntry, ClipboardItem, Image, ImageFormat, WaylandClientStatePtr, hash, use gpui::{ClipboardEntry, ClipboardItem, Image, ImageFormat, hash};
platform::linux::platform::read_fd,
};
/// Text mime types that we'll offer to other programs. /// Text mime types that we'll offer to other programs.
pub(crate) const TEXT_MIME_TYPES: [&str; 3] = pub(crate) const TEXT_MIME_TYPES: [&str; 3] =
@@ -241,7 +239,7 @@ impl Clipboard {
calloop::Mode::Level, calloop::Mode::Level,
), ),
move |_, file, _| { move |_, file, _| {
let mut file = unsafe { file.get_mut() }; let file = unsafe { file.get_mut() };
loop { loop {
match file.write(&bytes[written..]) { match file.write(&bytes[written..]) {
Ok(n) if written + n == bytes.len() => { Ok(n) if written + n == bytes.len() => {
@@ -1,5 +1,5 @@
use crate::Globals; use crate::linux::Globals;
use crate::platform::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning}; use crate::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
use anyhow::{Context as _, anyhow}; use anyhow::{Context as _, anyhow};
use util::ResultExt; use util::ResultExt;
@@ -95,7 +95,7 @@ impl Cursor {
&mut self, &mut self,
wl_pointer: &WlPointer, wl_pointer: &WlPointer,
serial_id: u32, serial_id: u32,
mut cursor_icon_names: &[&str], cursor_icon_names: &[&str],
scale: i32, scale: i32,
) { ) {
self.set_scaled_size(self.size * scale as u32); self.set_scaled_size(self.size * scale as u32);
@@ -104,9 +104,9 @@ impl Cursor {
log::warn!("Wayland: Unable to load cursor themes"); log::warn!("Wayland: Unable to load cursor themes");
return; return;
}; };
let mut theme = &mut loaded_theme.theme; let theme = &mut loaded_theme.theme;
let mut buffer: &CursorImageBuffer; let buffer: &CursorImageBuffer;
'outer: { 'outer: {
for cursor_icon_name in cursor_icon_names { for cursor_icon_name in cursor_icon_names {
if let Some(cursor) = theme.get_cursor(cursor_icon_name) { if let Some(cursor) = theme.get_cursor(cursor_icon_name) {
@@ -7,7 +7,7 @@ use anyhow::Context as _;
use uuid::Uuid; use uuid::Uuid;
use wayland_backend::client::ObjectId; use wayland_backend::client::ObjectId;
use crate::{Bounds, DisplayId, Pixels, PlatformDisplay}; use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct WaylandDisplay { pub(crate) struct WaylandDisplay {
@@ -25,7 +25,7 @@ impl Hash for WaylandDisplay {
impl PlatformDisplay for WaylandDisplay { impl PlatformDisplay for WaylandDisplay {
fn id(&self) -> DisplayId { fn id(&self) -> DisplayId {
DisplayId(self.id.protocol_id()) DisplayId::new(self.id.protocol_id())
} }
fn uuid(&self) -> anyhow::Result<Uuid> { fn uuid(&self) -> anyhow::Result<Uuid> {
@@ -0,0 +1,26 @@
pub use gpui::layer_shell::*;
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
pub(crate) fn wayland_layer(layer: Layer) -> zwlr_layer_shell_v1::Layer {
match layer {
Layer::Background => zwlr_layer_shell_v1::Layer::Background,
Layer::Bottom => zwlr_layer_shell_v1::Layer::Bottom,
Layer::Top => zwlr_layer_shell_v1::Layer::Top,
Layer::Overlay => zwlr_layer_shell_v1::Layer::Overlay,
}
}
pub(crate) fn wayland_anchor(anchor: Anchor) -> zwlr_layer_surface_v1::Anchor {
zwlr_layer_surface_v1::Anchor::from_bits_truncate(anchor.bits())
}
pub(crate) fn wayland_keyboard_interactivity(
value: KeyboardInteractivity,
) -> zwlr_layer_surface_v1::KeyboardInteractivity {
match value {
KeyboardInteractivity::None => zwlr_layer_surface_v1::KeyboardInteractivity::None,
KeyboardInteractivity::Exclusive => zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive,
KeyboardInteractivity::OnDemand => zwlr_layer_surface_v1::KeyboardInteractivity::OnDemand,
}
}
@@ -24,27 +24,22 @@ use wayland_protocols::{
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1; use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1;
use crate::{ use crate::linux::wayland::{display::WaylandDisplay, serial::SerialKind};
AnyWindowHandle, Bounds, Decorations, DevicePixels, Globals, GpuSpecs, Modifiers, Output, use crate::linux::{Globals, Output, WaylandClientStatePtr, get_window};
Pixels, PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions, use gpui::{
ResizeEdge, Size, Tiling, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance, AnyWindowHandle, Bounds, Capslock, Decorations, DevicePixels, GpuSpecs, Modifiers, Pixels,
WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams, get_window, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
layer_shell::LayerShellNotSupportedError, px, size, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, Scene, Size, Tiling,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls,
WindowDecorations, WindowKind, WindowParams, layer_shell::LayerShellNotSupportedError, px,
size,
}; };
use crate::{ use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
Capslock,
platform::{
PlatformAtlas, PlatformInputHandler, PlatformWindow,
linux::wayland::{display::WaylandDisplay, serial::SerialKind},
wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig},
},
};
use crate::{WindowKind, scene::Scene};
#[derive(Default)] #[derive(Default)]
pub(crate) struct Callbacks { pub(crate) struct Callbacks {
request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>, request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>, input: Option<Box<dyn FnMut(gpui::PlatformInput) -> gpui::DispatchEventResult>>,
active_status_change: Option<Box<dyn FnMut(bool)>>, active_status_change: Option<Box<dyn FnMut(bool)>>,
hover_status_change: Option<Box<dyn FnMut(bool)>>, hover_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>, resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
@@ -144,34 +139,37 @@ impl WaylandSurfaceState {
let layer_surface = layer_shell.get_layer_surface( let layer_surface = layer_shell.get_layer_surface(
&surface, &surface,
None, None,
options.layer.into(), super::layer_shell::wayland_layer(options.layer),
options.namespace.clone(), options.namespace.clone(),
&globals.qh, &globals.qh,
surface.id(), surface.id(),
); );
let width = params.bounds.size.width.0; let width = f32::from(params.bounds.size.width);
let height = params.bounds.size.height.0; let height = f32::from(params.bounds.size.height);
layer_surface.set_size(width as u32, height as u32); layer_surface.set_size(width as u32, height as u32);
layer_surface.set_anchor(options.anchor.into()); layer_surface.set_anchor(super::layer_shell::wayland_anchor(options.anchor));
layer_surface.set_keyboard_interactivity(options.keyboard_interactivity.into()); layer_surface.set_keyboard_interactivity(
super::layer_shell::wayland_keyboard_interactivity(options.keyboard_interactivity),
);
if let Some(margin) = options.margin { if let Some(margin) = options.margin {
layer_surface.set_margin( layer_surface.set_margin(
margin.0.0 as i32, f32::from(margin.0) as i32,
margin.1.0 as i32, f32::from(margin.1) as i32,
margin.2.0 as i32, f32::from(margin.2) as i32,
margin.3.0 as i32, f32::from(margin.3) as i32,
) )
} }
if let Some(exclusive_zone) = options.exclusive_zone { if let Some(exclusive_zone) = options.exclusive_zone {
layer_surface.set_exclusive_zone(exclusive_zone.0 as i32); layer_surface.set_exclusive_zone(f32::from(exclusive_zone) as i32);
} }
if let Some(exclusive_edge) = options.exclusive_edge { if let Some(exclusive_edge) = options.exclusive_edge {
layer_surface.set_exclusive_edge(exclusive_edge.into()); layer_surface
.set_exclusive_edge(super::layer_shell::wayland_anchor(exclusive_edge));
} }
return Ok(WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { return Ok(WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState {
@@ -208,7 +206,7 @@ impl WaylandSurfaceState {
}; };
if let Some(size) = params.window_min_size { if let Some(size) = params.window_min_size {
toplevel.set_min_size(size.width.0 as i32, size.height.0 as i32); toplevel.set_min_size(f32::from(size.width) as i32, f32::from(size.height) as i32);
} }
// Attempt to set up window decorations based on the requested configuration // Attempt to set up window decorations based on the requested configuration
@@ -335,8 +333,8 @@ impl WaylandWindowState {
}; };
let config = WgpuSurfaceConfig { let config = WgpuSurfaceConfig {
size: Size { size: Size {
width: DevicePixels(options.bounds.size.width.0 as i32), width: DevicePixels(f32::from(options.bounds.size.width) as i32),
height: DevicePixels(options.bounds.size.height.0 as i32), height: DevicePixels(f32::from(options.bounds.size.height) as i32),
}, },
transparent: true, transparent: true,
}; };
@@ -618,7 +616,7 @@ impl WaylandWindowStatePtr {
state.inset(), state.inset(),
state.tiling, state.tiling,
) )
.map(|v| v.0 as i32) .map(|v| f32::from(v) as i32)
.map_size(|v| if v <= 0 { 1 } else { v }); .map_size(|v| if v <= 0 { 1 } else { v });
state.surface_state.set_geometry( state.surface_state.set_geometry(
@@ -642,7 +640,7 @@ impl WaylandWindowStatePtr {
match mode { match mode {
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => { WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
self.state.borrow_mut().decorations = WindowDecorations::Server; self.state.borrow_mut().decorations = WindowDecorations::Server;
if let Some(mut appearance_changed) = if let Some(appearance_changed) =
self.callbacks.borrow_mut().appearance_changed.as_mut() self.callbacks.borrow_mut().appearance_changed.as_mut()
{ {
appearance_changed(); appearance_changed();
@@ -651,7 +649,7 @@ impl WaylandWindowStatePtr {
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => { WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
self.state.borrow_mut().decorations = WindowDecorations::Client; self.state.borrow_mut().decorations = WindowDecorations::Client;
// Update background to be transparent // Update background to be transparent
if let Some(mut appearance_changed) = if let Some(appearance_changed) =
self.callbacks.borrow_mut().appearance_changed.as_mut() self.callbacks.borrow_mut().appearance_changed.as_mut()
{ {
appearance_changed(); appearance_changed();
@@ -680,7 +678,7 @@ impl WaylandWindowStatePtr {
height, height,
states, states,
} => { } => {
let mut size = if width == 0 || height == 0 { let size = if width == 0 || height == 0 {
None None
} else { } else {
Some(size(px(width as f32), px(height as f32))) Some(size(px(width as f32), px(height as f32)))
@@ -787,7 +785,7 @@ impl WaylandWindowStatePtr {
height, height,
serial, serial,
} => { } => {
let mut size = if width == 0 || height == 0 { let size = if width == 0 || height == 0 {
None None
} else { } else {
Some(size(px(width as f32), px(height as f32))) Some(size(px(width as f32), px(height as f32)))
@@ -933,7 +931,8 @@ impl WaylandWindowStatePtr {
{ {
let state = self.state.borrow(); let state = self.state.borrow();
if let Some(viewport) = &state.viewport { if let Some(viewport) = &state.viewport {
viewport.set_destination(size.width.0 as i32, size.height.0 as i32); viewport
.set_destination(f32::from(size.width) as i32, f32::from(size.height) as i32);
} }
} }
} }
@@ -1108,7 +1107,7 @@ impl PlatformWindow for WaylandWindow {
state.inset(), state.inset(),
state.tiling, state.tiling,
) )
.map(|v| v.0 as i32) .map(|v| f32::from(v) as i32)
.map_size(|v| if v <= 0 { 1 } else { v }); .map_size(|v| if v <= 0 { 1 } else { v });
state.surface_state.set_geometry( state.surface_state.set_geometry(
@@ -1252,7 +1251,7 @@ impl PlatformWindow for WaylandWindow {
} }
fn toggle_fullscreen(&self) { fn toggle_fullscreen(&self) {
let mut state = self.borrow(); let state = self.borrow();
if let Some(toplevel) = state.surface_state.toplevel() { if let Some(toplevel) = state.surface_state.toplevel() {
if !state.fullscreen { if !state.fullscreen {
toplevel.set_fullscreen(None); toplevel.set_fullscreen(None);
@@ -1270,7 +1269,7 @@ impl PlatformWindow for WaylandWindow {
self.0.callbacks.borrow_mut().request_frame = Some(callback); self.0.callbacks.borrow_mut().request_frame = Some(callback);
} }
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) { fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>) {
self.0.callbacks.borrow_mut().input = Some(callback); self.0.callbacks.borrow_mut().input = Some(callback);
} }
@@ -1327,8 +1326,8 @@ impl PlatformWindow for WaylandWindow {
toplevel.show_window_menu( toplevel.show_window_menu(
&state.globals.seat, &state.globals.seat,
serial, serial,
position.x.0 as i32, f32::from(position.x) as i32,
position.y.0 as i32, f32::from(position.y) as i32,
); );
} }
} }
@@ -1341,7 +1340,7 @@ impl PlatformWindow for WaylandWindow {
} }
} }
fn start_window_resize(&self, edge: crate::ResizeEdge) { fn start_window_resize(&self, edge: gpui::ResizeEdge) {
let state = self.borrow(); let state = self.borrow();
if let Some(toplevel) = state.surface_state.toplevel() { if let Some(toplevel) = state.surface_state.toplevel() {
toplevel.resize( toplevel.resize(
@@ -1408,8 +1407,8 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
let opaque = !state.is_transparent(); let opaque = !state.is_transparent();
state.renderer.update_transparency(!opaque); state.renderer.update_transparency(!opaque);
let mut opaque_area = state.window_bounds.map(|v| v.0 as i32); let opaque_area = state.window_bounds.map(|v| f32::from(v) as i32);
opaque_area.inset(state.inset().0 as i32); opaque_area.inset(f32::from(state.inset()) as i32);
let region = state let region = state
.globals .globals
@@ -1454,7 +1453,11 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
region.destroy(); region.destroy();
} }
impl WindowDecorations { pub(crate) trait WindowDecorationsExt {
fn to_xdg(self) -> zxdg_toplevel_decoration_v1::Mode;
}
impl WindowDecorationsExt for WindowDecorations {
fn to_xdg(self) -> zxdg_toplevel_decoration_v1::Mode { fn to_xdg(self) -> zxdg_toplevel_decoration_v1::Mode {
match self { match self {
WindowDecorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide, WindowDecorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide,
@@ -1463,7 +1466,11 @@ impl WindowDecorations {
} }
} }
impl ResizeEdge { pub(crate) trait ResizeEdgeWaylandExt {
fn to_xdg(self) -> xdg_toplevel::ResizeEdge;
}
impl ResizeEdgeWaylandExt for ResizeEdge {
fn to_xdg(self) -> xdg_toplevel::ResizeEdge { fn to_xdg(self) -> xdg_toplevel::ResizeEdge {
match self { match self {
ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top, ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,
File renamed without changes.
Loaded 100 of 178 files, more files were not shown because too many files have changed in this diff. Show more