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 Osiewicz 2026-02-19 18:57:49 +01:00 committed by GitHub
parent da7b8f2939
commit bc31ad4a8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
178 changed files with 3114 additions and 3204 deletions

166
Cargo.lock generated
View file

@ -3438,6 +3438,7 @@ dependencies = [
"db",
"fs",
"gpui",
"gpui_platform",
"language",
"log",
"node_runtime",
@ -5242,6 +5243,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"gpui_platform",
"gpui_tokio",
"http_client",
"indoc",
@ -5779,6 +5781,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"gpui_platform",
"gpui_tokio",
"handlebars 4.5.0",
"language",
@ -5814,7 +5817,7 @@ dependencies = [
name = "eval_utils"
version = "0.1.0"
dependencies = [
"gpui",
"gpui_platform",
"serde",
"smol",
]
@ -5931,7 +5934,7 @@ dependencies = [
"env_logger 0.11.8",
"extension",
"fs",
"gpui",
"gpui_platform",
"language",
"log",
"reqwest_client",
@ -6498,6 +6501,7 @@ version = "0.1.0"
dependencies = [
"fs",
"gpui",
"gpui_platform",
]
[[package]]
@ -7348,16 +7352,11 @@ name = "gpui"
version = "0.2.2"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
"ashpd",
"async-task",
"backtrace",
"bindgen 0.71.1",
"bitflags 2.10.0",
"block",
"bytemuck",
"calloop",
"calloop-wayland-source",
"cbindgen",
"chrono",
"circular-buffer",
@ -7369,21 +7368,19 @@ dependencies = [
"core-graphics 0.24.0",
"core-text",
"core-video",
"cosmic-text",
"ctor",
"derive_more 0.99.20",
"embed-resource",
"env_logger 0.11.8",
"etagere",
"filedescriptor",
"foreign-types 0.5.0",
"futures 0.3.31",
"gpui_macros",
"gpui_platform",
"http_client",
"image",
"inventory",
"itertools 0.14.0",
"libc",
"log",
"lyon",
"mach2 0.5.0",
@ -7394,8 +7391,6 @@ dependencies = [
"objc",
"objc2",
"objc2-metal",
"oo7",
"open",
"parking",
"parking_lot",
"pathfinder_geometry",
@ -7411,7 +7406,6 @@ dependencies = [
"scheduler",
"schemars",
"seahash",
"semver",
"serde",
"serde_json",
"slotmap",
@ -7421,7 +7415,6 @@ dependencies = [
"stacksafe",
"strum 0.27.2",
"sum_tree",
"swash",
"taffy",
"thiserror 2.0.17",
"unicode-segmentation",
@ -7430,17 +7423,50 @@ dependencies = [
"util_macros",
"uuid",
"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-client",
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-plasma",
"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",
"x11rb",
"xkbcommon",
@ -7449,6 +7475,47 @@ dependencies = [
"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]]
name = "gpui_macros"
version = "0.1.0"
@ -7460,6 +7527,16 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "gpui_platform"
version = "0.1.0"
dependencies = [
"gpui",
"gpui_linux",
"gpui_macos",
"gpui_windows",
]
[[package]]
name = "gpui_tokio"
version = "0.1.0"
@ -7470,6 +7547,49 @@ dependencies = [
"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]]
name = "grid"
version = "0.18.0"
@ -9534,6 +9654,7 @@ dependencies = [
"cpal",
"futures 0.3.31",
"gpui",
"gpui_platform",
"gpui_tokio",
"http_client_tls",
"image",
@ -9786,6 +9907,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"gpui_platform",
"language",
"languages",
"linkify",
@ -12736,6 +12858,7 @@ dependencies = [
"client",
"futures 0.3.31",
"gpui",
"gpui_platform",
"http_client",
"language",
"node_runtime",
@ -13702,6 +13825,7 @@ dependencies = [
"git2",
"git_hosting_providers",
"gpui",
"gpui_platform",
"gpui_tokio",
"http_client",
"image",
@ -15911,6 +16035,7 @@ dependencies = [
"editor",
"fuzzy",
"gpui",
"gpui_platform",
"indoc",
"language",
"log",
@ -20724,7 +20849,7 @@ name = "worktree_benchmarks"
version = "0.1.0"
dependencies = [
"fs",
"gpui",
"gpui_platform",
"settings",
"worktree",
]
@ -21140,6 +21265,7 @@ dependencies = [
"git_ui",
"go_to_line",
"gpui",
"gpui_platform",
"gpui_tokio",
"http_client",
"image",

View file

@ -83,7 +83,12 @@ members = [
"crates/go_to_line",
"crates/google_ai",
"crates/gpui",
"crates/gpui_linux",
"crates/gpui_macos",
"crates/gpui_macros",
"crates/gpui_platform",
"crates/gpui_wgpu",
"crates/gpui_windows",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
@ -321,7 +326,12 @@ git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
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_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" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
@ -819,6 +829,8 @@ codegen-units = 16
# (without this cargo will compile ~400 crates twice)
[profile.dev.build-override]
codegen-units = 16
split-debuginfo = "unpacked"
debug = true
[profile.dev.package]
# proc-macros start

View file

@ -13,7 +13,6 @@ path = "src/component_preview.rs"
[features]
default = []
preview = []
test-support = ["db/test-support"]
[dependencies]
@ -39,7 +38,9 @@ ui_input.workspace = true
uuid.workspace = true
workspace.workspace = true
[dev-dependencies]
gpui_platform = { workspace = true, features = ["screen-capture"] }
[[example]]
name = "component_preview"
path = "examples/component_preview.rs"
required-features = ["preview"]

View file

@ -1,18 +1,143 @@
//! Component Preview Example
//!
//! Run with: `cargo run -p component_preview --example component_preview --features="preview"`
//!
//! To use this in other projects, add the following to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! component_preview = { path = "../component_preview", features = ["preview"] }
//!
//! [[example]]
//! name = "component_preview"
//! path = "examples/component_preview.rs"
//! ```
//! Run with: `cargo run -p component_preview --example component_preview"`
use fs::RealFs;
use gpui::{AppContext as _, 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 component_preview::{ComponentPreview, init};
actions!(zed, [Quit]);
fn quit(_: &Quit, cx: &mut App) {
cx.quit();
}
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);
});
}

View file

@ -1,4 +1,3 @@
mod component_preview_example;
mod persistence;
use client::UserStore;
@ -20,9 +19,6 @@ use workspace::{
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) {
workspace::register_serializable_item::<ComponentPreview>(cx);
@ -85,13 +81,13 @@ impl From<SharedString> for PreviewEntry {
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
enum PreviewPage {
pub enum PreviewPage {
#[default]
AllComponents,
Component(ComponentId),
}
struct ComponentPreview {
pub struct ComponentPreview {
active_page: PreviewPage,
reset_key: usize,
component_list: ListState,

View file

@ -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);
});
}

View file

@ -27,6 +27,7 @@ extension.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
gpui_platform.workspace = true
gpui_tokio.workspace = true
indoc.workspace = true
language.workspace = true

View file

@ -31,7 +31,7 @@ use collections::HashSet;
use edit_prediction::EditPredictionStore;
use futures::channel::mpsc;
use futures::{SinkExt as _, StreamExt as _};
use gpui::{AppContext as _, Application, BackgroundExecutor, Task};
use gpui::{AppContext as _, BackgroundExecutor, Task};
use zeta_prompt::ZetaFormat;
use reqwest_client::ReqwestClient;
@ -851,7 +851,7 @@ fn main() {
}
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| {
let app_state = Arc::new(headless::init(cx));

View file

@ -38,6 +38,7 @@ extension.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
gpui_platform.workspace = true
gpui_tokio.workspace = true
handlebars.workspace = true
language.workspace = true

View file

@ -18,7 +18,7 @@ use collections::{HashMap, HashSet};
use extension::ExtensionHostProxy;
use futures::future;
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 language::LanguageRegistry;
use language_model::{ConfiguredModel, LanguageModel, LanguageModelRegistry, SelectedModel};
@ -114,7 +114,7 @@ fn main() {
let languages: HashSet<String> = args.languages.into_iter().collect();
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);
app.run(move |cx| {

View file

@ -13,6 +13,6 @@ path = "src/eval_utils.rs"
doctest = false
[dependencies]
gpui.workspace = true
gpui_platform.workspace = true
serde.workspace = true
smol.workspace = true

View file

@ -82,7 +82,7 @@ pub fn eval<P>(
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 evalf = Arc::new(evalf);
// Warm the cache once

View file

@ -19,7 +19,7 @@ cloud_api_types.workspace = true
env_logger.workspace = true
extension.workspace = true
fs.workspace = true
gpui.workspace = true
gpui_platform.workspace = true
language.workspace = true
log.workspace = true
reqwest_client.workspace = true

View file

@ -35,7 +35,7 @@ async fn main() -> Result<()> {
env_logger::init();
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 mut wasm_store = WasmStore::new(&engine)?;

View file

@ -6,7 +6,8 @@ edition.workspace = true
[dependencies]
fs.workspace = true
gpui = {workspace = true, features = ["windows-manifest"]}
gpui.workspace = true
gpui_platform.workspace = true
[lints]
workspace = true

View file

@ -1,12 +1,14 @@
use fs::Fs;
use gpui::{AppContext, Application};
use gpui::AppContext;
use gpui_platform::headless;
fn main() {
let Some(path_to_read) = std::env::args().nth(1) else {
println!("Expected path to read as 1st argument.");
return;
};
let _ = Application::headless().run(|cx| {
let _ = headless().run(|cx| {
let fs = fs::RealFs::new(None, cx.background_executor().clone());
cx.background_spawn(async move {
let timer = std::time::Instant::now();

View file

@ -31,37 +31,8 @@ leak-detection = ["backtrace"]
runtime_shaders = []
wayland = [
"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 = [
"scap",
]
@ -76,7 +47,7 @@ anyhow.workspace = true
async-task = "4.7"
backtrace = { workspace = true, optional = true }
bitflags = { workspace = true, optional = true }
bytemuck = { version = "1", optional = true }
collections.workspace = true
ctor.workspace = true
derive_more.workspace = true
@ -107,7 +78,6 @@ usvg = { version = "0.45.0", default-features = false }
util_macros.workspace = true
schemars.workspace = true
seahash = "4.1"
semver.workspace = true
serde.workspace = true
serde_json.workspace = true
slotmap.workspace = true
@ -122,7 +92,6 @@ util.workspace = true
uuid.workspace = true
waker-fn = "1.2.0"
lyon = "1.0"
libc.workspace = true
pin-project = "1.1.10"
circular-buffer.workspace = true
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]
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]
rand.workspace = true
windows.workspace = true
windows-core.workspace = true
windows-numerics = "0.2"
windows-registry = "0.5"
windows = { version = "0.61", features = ["Win32_Foundation"] }
[dev-dependencies]
backtrace.workspace = true
collections = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
gpui_platform.workspace = true
http_client = { workspace = true, features = ["test-support"] }
lyon = { version = "1.0", features = ["extra"] }
pretty_assertions.workspace = true
@ -233,17 +143,17 @@ scheduler = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
util = { workspace = true, features = ["test-support"] }
[target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = "3.0"
windows-registry = "0.5"
[target.'cfg(target_os = "macos")'.build-dependencies]
bindgen = "0.71"
cbindgen = { version = "0.28.0", default-features = false }
naga.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
naga.workspace = true
[[example]]

View file

@ -1,463 +1,20 @@
#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
#![cfg_attr(not(target_os = "macos"), allow(unused))]
use std::env;
fn main() {
let target = env::var("CARGO_CFG_TARGET_OS");
println!("cargo::rustc-check-cfg=cfg(gles)");
match target.as_deref() {
Ok("macos") => {
#[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(all(target_os = "windows", feature = "windows-manifest"))]
embed_resource();
}
#[cfg(target_os = "windows")]
mod windows {
use std::{
ffi::OsString,
fs,
io::Write,
path::{Path, PathBuf},
process::{self, Command},
};
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");
}
#[cfg(all(target_os = "windows", 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();
}

View file

@ -2,10 +2,11 @@ use std::time::Duration;
use anyhow::Result;
use gpui::{
Animation, AnimationExt as _, App, Application, AssetSource, Bounds, Context, SharedString,
Transformation, Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage,
prelude::*, px, size, svg,
Animation, AnimationExt as _, App, AssetSource, Bounds, Context, SharedString, Transformation,
Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage, prelude::*, px,
size, svg,
};
use gpui_platform::application;
struct Assets {}
@ -101,21 +102,19 @@ impl Render for AnimationExample {
}
fn main() {
Application::new()
.with_assets(Assets {})
.run(|cx: &mut App| {
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
None,
size(px(300.), px(300.)),
cx,
))),
..Default::default()
};
cx.open_window(options, |_, cx| {
cx.activate(false);
cx.new(|_| AnimationExample {})
})
.unwrap();
});
application().with_assets(Assets {}).run(|cx: &mut App| {
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
None,
size(px(300.), px(300.)),
cx,
))),
..Default::default()
};
cx.open_window(options, |_, cx| {
cx.activate(false);
cx.new(|_| AnimationExample {})
})
.unwrap();
});
}

View file

@ -1,10 +1,11 @@
use std::{ops::Range, rc::Rc, time::Duration};
use gpui::{
App, Application, Bounds, Context, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point,
Render, SharedString, UniformListScrollHandle, Window, WindowBounds, WindowOptions, canvas,
div, point, prelude::*, px, rgb, size, uniform_list,
App, Bounds, Context, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Render,
SharedString, UniformListScrollHandle, Window, WindowBounds, WindowOptions, canvas, div, point,
prelude::*, px, rgb, size, uniform_list,
};
use gpui_platform::application;
const TOTAL_ITEMS: usize = 10000;
const SCROLLBAR_THUMB_WIDTH: Pixels = px(8.);
@ -447,7 +448,7 @@ impl Render for DataTable {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.open_window(
WindowOptions {
focus: true,

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds,
WindowOptions, div, prelude::*, px, rgb, size,
App, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds, WindowOptions, div,
prelude::*, px, rgb, size,
};
use gpui_platform::application;
#[derive(Clone, Copy)]
struct DragInfo {
@ -121,7 +122,7 @@ impl Render for DragDrop {
}
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);
cx.open_window(
WindowOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString,
Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window,
WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
};
use gpui_platform::application;
actions!(example, [Tab, TabPrev, Quit]);
@ -192,7 +193,7 @@ impl Render for Example {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.bind_keys([
KeyBinding::new("tab", Tab, None),
KeyBinding::new("shift-tab", TabPrev, None),

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;
struct GifViewer {
@ -24,7 +25,7 @@ impl Render for GifViewer {
fn main() {
env_logger::init();
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
let gif_path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/image/black-cat-typing.gif");

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, ColorSpace, Context, Half, Render, Window, WindowOptions, canvas,
div, linear_color_stop, linear_gradient, point, prelude::*, px, size,
App, Bounds, ColorSpace, Context, Half, Render, Window, WindowOptions, canvas, div,
linear_color_stop, linear_gradient, point, prelude::*, px, size,
};
use gpui_platform::application;
struct GradientViewer {
color_space: ColorSpace,
@ -243,7 +244,7 @@ impl Render for GradientViewer {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.open_window(
WindowOptions {
focus: true,

View file

@ -1,7 +1,7 @@
use gpui::{
App, Application, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*,
px, rgb, size,
App, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
};
use gpui_platform::application;
// https://en.wikipedia.org/wiki/Holy_grail_(web_design)
struct HolyGrailExample {}
@ -65,7 +65,7 @@ impl Render for HolyGrailExample {
}
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);
cx.open_window(
WindowOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div,
prelude::*, px, rgb, size,
App, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div, prelude::*, px,
rgb, size,
};
use gpui_platform::application;
struct HelloWorld {
text: SharedString,
@ -87,7 +88,7 @@ impl Render for HelloWorld {
}
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);
cx.open_window(
WindowOptions {

View file

@ -4,10 +4,11 @@ use std::sync::Arc;
use anyhow::Result;
use gpui::{
App, AppContext, Application, AssetSource, Bounds, Context, ImageSource, KeyBinding, Menu,
MenuItem, Point, SharedString, SharedUri, TitlebarOptions, Window, WindowBounds, WindowOptions,
actions, div, img, prelude::*, px, rgb, size,
App, AppContext, AssetSource, Bounds, Context, ImageSource, KeyBinding, Menu, MenuItem, Point,
SharedString, SharedUri, TitlebarOptions, Window, WindowBounds, WindowOptions, actions, div,
img, prelude::*, px, rgb, size,
};
use gpui_platform::application;
use reqwest_client::ReqwestClient;
struct Assets {
@ -150,7 +151,7 @@ fn main() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
Application::new()
application()
.with_assets(Assets {
base: manifest_dir.join("examples"),
})

View file

@ -1,10 +1,11 @@
use futures::FutureExt;
use gpui::{
App, AppContext, Application, Asset as _, AssetLogger, Bounds, ClickEvent, Context, ElementId,
Entity, ImageAssetLoader, ImageCache, ImageCacheProvider, KeyBinding, Menu, MenuItem,
App, AppContext, Asset as _, AssetLogger, Bounds, ClickEvent, Context, ElementId, Entity,
ImageAssetLoader, ImageCache, ImageCacheProvider, KeyBinding, Menu, MenuItem,
RetainAllImageCache, SharedString, TitlebarOptions, Window, WindowBounds, WindowOptions,
actions, div, hash, image_cache, img, prelude::*, px, rgb, size,
};
use gpui_platform::application;
use reqwest_client::ReqwestClient;
use std::{collections::HashMap, sync::Arc};
@ -247,7 +248,7 @@ actions!(image, [Quit]);
fn main() {
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();
cx.set_http_client(Arc::new(http_client));

View file

@ -1,11 +1,12 @@
use std::{path::Path, sync::Arc, time::Duration};
use gpui::{
Animation, AnimationExt, App, Application, Asset, AssetLogger, AssetSource, Bounds, Context,
Hsla, ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, RenderImage,
Animation, AnimationExt, App, Asset, AssetLogger, AssetSource, Bounds, Context, Hsla,
ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, RenderImage,
Resource, SharedString, Window, WindowBounds, WindowOptions, black, div, img, prelude::*,
pulsating_between, px, red, size,
};
use gpui_platform::application;
struct Assets {}
@ -193,21 +194,19 @@ impl Render for ImageLoadingExample {
fn main() {
env_logger::init();
Application::new()
.with_assets(Assets {})
.run(|cx: &mut App| {
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
None,
size(px(300.), px(300.)),
cx,
))),
..Default::default()
};
cx.open_window(options, |_, cx| {
cx.activate(false);
cx.new(|_| ImageLoadingExample {})
})
.unwrap();
});
application().with_assets(Assets {}).run(|cx: &mut App| {
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
None,
size(px(300.), px(300.)),
cx,
))),
..Default::default()
};
cx.open_window(options, |_, cx| {
cx.activate(false);
cx.new(|_| ImageLoadingExample {})
})
.unwrap();
});
}

View file

@ -1,13 +1,14 @@
use std::ops::Range;
use gpui::{
App, Application, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler,
Entity, EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, Keystroke,
LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
App, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler, Entity,
EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, Keystroke, LayoutId,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
ShapedLine, SharedString, Style, TextRun, UTF16Selection, UnderlineStyle, Window, WindowBounds,
WindowOptions, actions, black, div, fill, hsla, opaque_grey, point, prelude::*, px, relative,
rgb, rgba, size, white, yellow,
};
use gpui_platform::application;
use unicode_segmentation::*;
actions!(
@ -682,7 +683,7 @@ impl Render for InputExample {
}
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);
cx.bind_keys([
KeyBinding::new("backspace", Backspace, None),

View file

@ -11,10 +11,10 @@ mod example {
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use gpui::{
App, Application, Bounds, Context, FontWeight, Size, Window, WindowBackgroundAppearance,
WindowBounds, WindowKind, WindowOptions, div, layer_shell::*, point, prelude::*, px, rems,
rgba, white,
App, Bounds, Context, FontWeight, Size, Window, WindowBackgroundAppearance, WindowBounds,
WindowKind, WindowOptions, div, layer_shell::*, point, prelude::*, px, rems, rgba, white,
};
use gpui_platform::application;
struct LayerShellExample;
@ -60,7 +60,7 @@ mod example {
}
pub fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.open_window(
WindowOptions {
titlebar: None,

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, MousePressureEvent, PressureStage, Window, WindowBounds,
WindowOptions, div, prelude::*, px, rgb, size,
App, Bounds, Context, MousePressureEvent, PressureStage, Window, WindowBounds, WindowOptions,
div, prelude::*, px, rgb, size,
};
use gpui_platform::application;
struct MousePressureExample {
pressure_stage: PressureStage,
@ -44,7 +45,7 @@ impl MousePressureExample {
}
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);
cx.open_window(

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, FocusHandle, KeyBinding, Window, WindowBounds,
WindowOptions, actions, div, prelude::*, px, rgb, size,
App, Bounds, Context, FocusHandle, KeyBinding, Window, WindowBounds, WindowOptions, actions,
div, prelude::*, px, rgb, size,
};
use gpui_platform::application;
actions!(example, [CloseWindow]);
@ -35,7 +36,7 @@ impl Render for ExampleWindow {
}
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);
cx.bind_keys([KeyBinding::new("cmd-w", CloseWindow, None)]);

View file

@ -2,9 +2,10 @@ use std::{fs, path::PathBuf};
use anyhow::Result;
use gpui::{
App, Application, AssetSource, Bounds, BoxShadow, ClickEvent, Context, SharedString, Task,
Window, WindowBounds, WindowOptions, div, hsla, img, point, prelude::*, px, rgb, size, svg,
App, AssetSource, Bounds, BoxShadow, ClickEvent, Context, SharedString, Task, Window,
WindowBounds, WindowOptions, div, hsla, img, point, prelude::*, px, rgb, size, svg,
};
use gpui_platform::application;
struct Assets {
base: PathBuf,
@ -156,7 +157,7 @@ impl Render for HelloWorld {
}
fn main() {
Application::new()
application()
.with_assets(Assets {
base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"),
})

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 {
count: usize,
@ -11,7 +12,7 @@ struct Change {
impl EventEmitter<Change> for Counter {}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
let subscriber = cx.new(|cx: &mut Context<Counter>| {
cx.subscribe(&counter, |subscriber, _emitter, event, _cx| {

View file

@ -1,8 +1,9 @@
use gpui::{
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
PathStyle, Pixels, Point, Render, StrokeOptions, Window, WindowOptions, canvas, div,
linear_color_stop, linear_gradient, point, prelude::*, px, quad, rgb, size,
Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
Point, Render, StrokeOptions, Window, WindowOptions, canvas, div, linear_color_stop,
linear_gradient, point, prelude::*, px, quad, rgb, size,
};
use gpui_platform::application;
struct PaintingViewer {
default_lines: Vec<(Path<Pixels>, Background)>,
@ -445,7 +446,7 @@ impl Render for PaintingViewer {
}
fn main() {
Application::new().run(|cx| {
application().run(|cx| {
cx.open_window(
WindowOptions {
focus: true,

View file

@ -1,8 +1,9 @@
use gpui::{
Application, Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render,
TitlebarOptions, Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop,
linear_gradient, point, prelude::*, px, rgb, size,
Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render, TitlebarOptions,
Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop, linear_gradient, point,
prelude::*, px, rgb, size,
};
use gpui_platform::application;
const DEFAULT_WINDOW_WIDTH: Pixels = px(1024.0);
const DEFAULT_WINDOW_HEIGHT: Pixels = px(768.0);
@ -69,7 +70,7 @@ impl Render for PaintingViewer {
}
fn main() {
Application::new().run(|cx| {
application().run(|cx| {
cx.open_window(
WindowOptions {
titlebar: Some(TitlebarOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, AppContext, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div,
linear_color_stop, linear_gradient, pattern_slash, prelude::*, px, rgb, size,
App, AppContext, Bounds, Context, Window, WindowBounds, WindowOptions, div, linear_color_stop,
linear_gradient, pattern_slash, prelude::*, px, rgb, size,
};
use gpui_platform::application;
struct PatternExample;
@ -99,7 +100,7 @@ impl Render for PatternExample {
}
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);
cx.open_window(
WindowOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Context, Corner, Div, Hsla, Stateful, Window, WindowOptions, anchored,
deferred, div, prelude::*, px,
App, Context, Corner, Div, Hsla, Stateful, Window, WindowOptions, anchored, deferred, div,
prelude::*, px,
};
use gpui_platform::application;
/// An example show use deferred to create a floating layers.
struct HelloWorld {
@ -161,7 +162,7 @@ impl Render for HelloWorld {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.open_window(WindowOptions::default(), |_, cx| {
cx.new(|_| HelloWorld {
open: false,

View file

@ -1,7 +1,5 @@
use gpui::{
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
size,
};
use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size};
use gpui_platform::application;
struct Scrollable {}
@ -45,7 +43,7 @@ impl Render for Scrollable {
}
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);
cx.open_window(
WindowOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window,
WindowOptions, actions, div, prelude::*, rgb,
App, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window, WindowOptions,
actions, div, prelude::*, rgb,
};
use gpui_platform::application;
struct SetMenus;
@ -20,7 +21,7 @@ impl Render for SetMenus {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.set_global(AppState::new());
// Bring the menu bar to the foreground (so you can see the menu bar)

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, BoxShadow, Context, Div, SharedString, Window, WindowBounds,
WindowOptions, div, hsla, point, prelude::*, px, relative, rgb, size,
App, Bounds, BoxShadow, Context, Div, SharedString, Window, WindowBounds, WindowOptions, div,
hsla, point, prelude::*, px, relative, rgb, size,
};
use gpui_platform::application;
struct Shadow {}
@ -569,7 +570,7 @@ impl Render for Shadow {
}
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);
cx.open_window(
WindowOptions {

View file

@ -3,9 +3,10 @@ use std::path::PathBuf;
use anyhow::Result;
use gpui::{
App, Application, AssetSource, Bounds, Context, SharedString, Window, WindowBounds,
WindowOptions, div, prelude::*, px, rgb, size, svg,
App, AssetSource, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div,
prelude::*, px, rgb, size, svg,
};
use gpui_platform::application;
struct Assets {
base: PathBuf,
@ -68,7 +69,7 @@ impl Render for SvgExample {
}
fn main() {
Application::new()
application()
.with_assets(Assets {
base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"),
})

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString,
Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window,
WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
};
use gpui_platform::application;
actions!(example, [Tab, TabPrev]);
@ -178,7 +179,7 @@ impl Render for Example {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.bind_keys([
KeyBinding::new("tab", Tab, None),
KeyBinding::new("shift-tab", TabPrev, None),

View file

@ -7,9 +7,10 @@
//! Run tests: cargo test -p gpui --example testing --features test-support
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,
};
use gpui_platform::application;
actions!(counter, [Increment, Decrement]);
@ -176,7 +177,7 @@ impl Render for Counter {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.bind_keys([
gpui::KeyBinding::new("up", Increment, Some("Counter")),
gpui::KeyBinding::new("down", Decrement, Some("Counter")),

View file

@ -4,10 +4,11 @@ use std::{
};
use gpui::{
AbsoluteLength, App, Application, Context, DefiniteLength, ElementId, Global, Hsla, Menu,
SharedString, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds,
colors::DefaultColors, div, point, prelude::*, px, relative, rgb, size,
AbsoluteLength, App, Context, DefiniteLength, ElementId, Global, Hsla, Menu, SharedString,
TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds, colors::DefaultColors,
div, point, prelude::*, px, relative, rgb, size,
};
use gpui_platform::application;
use std::iter;
#[derive(Clone, Debug)]
@ -298,7 +299,7 @@ impl Render for TextExample {
}
fn main() {
Application::new().run(|cx: &mut App| {
application().run(|cx: &mut App| {
cx.set_menus(vec![Menu {
name: "GPUI Typography".into(),
items: vec![],

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds,
WindowOptions, div, prelude::*, px, size,
App, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds, WindowOptions,
div, prelude::*, px, size,
};
use gpui_platform::application;
struct HelloWorld {}
@ -81,7 +82,7 @@ impl Render for HelloWorld {
}
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);
cx.open_window(
WindowOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, TextOverflow, Window, WindowBounds, WindowOptions, div,
prelude::*, px, size,
App, Bounds, Context, TextOverflow, Window, WindowBounds, WindowOptions, div, prelude::*, px,
size,
};
use gpui_platform::application;
struct HelloWorld {}
@ -108,7 +109,7 @@ impl Render for HelloWorld {
}
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);
cx.open_window(
WindowOptions {

View file

@ -2,10 +2,8 @@
//! handle deep hierarchies (even though it cannot just yet!).
use std::sync::LazyLock;
use gpui::{
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
size,
};
use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size};
use gpui_platform::application;
struct Tree {}
@ -32,7 +30,7 @@ impl Render for Tree {
}
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);
cx.open_window(
WindowOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
rgb, size, uniform_list,
App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
uniform_list,
};
use gpui_platform::application;
struct UniformListExample {}
@ -36,7 +37,7 @@ impl Render for UniformListExample {
}
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);
cx.open_window(
WindowOptions {

View file

@ -1,7 +1,8 @@
use gpui::{
App, Application, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Window, WindowBounds,
WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size,
App, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Window, WindowBounds, WindowKind,
WindowOptions, actions, div, prelude::*, px, rgb, size,
};
use gpui_platform::application;
struct SubWindow {
custom_titlebar: bool,
@ -306,7 +307,7 @@ impl Render for WindowDemo {
actions!(window, [Quit]);
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);
cx.open_window(

View file

@ -1,8 +1,9 @@
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::*,
px, rgb,
};
use gpui_platform::application;
struct WindowContent {
text: SharedString,
@ -68,7 +69,7 @@ fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> Window
}
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
let size = Size {
width: px(350.),

View file

@ -1,9 +1,10 @@
use gpui::{
App, Application, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton,
Pixels, Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds,
WindowDecorations, WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size,
transparent_black, white,
App, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton, Pixels,
Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black,
white,
};
use gpui_platform::application;
struct WindowShadow {}
@ -203,7 +204,7 @@ fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> O
}
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);
cx.open_window(
WindowOptions {

View file

@ -7,7 +7,7 @@
//! # struct Counter {
//! # 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 });
//! // ...
//! });
@ -22,7 +22,7 @@
//! # struct Counter {
//! # 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 });
//! // Call `update` to access the model's state.
//! counter.update(cx, |counter: &mut Counter, _cx: &mut Context<Counter>| {
@ -42,7 +42,7 @@
//! # struct Counter {
//! # 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 });
//! counter.update(cx, |counter, cx| {
//! counter.count += 1;
@ -60,7 +60,7 @@
//! # struct Counter {
//! # 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 second_counter = cx.new(|cx: &mut Context<Counter>| {
@ -114,7 +114,7 @@
//! # increment: usize,
//! # }
//! # 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 second_counter = cx.new(|cx: &mut Context<Counter>| {

View file

@ -46,7 +46,7 @@ use crate::{
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem,
ThermalState, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus,
hash, init_app_menus,
};
mod async_context;
@ -132,25 +132,10 @@ pub struct Application(Rc<AppCell>);
/// Represents an application before it is fully launched. Once your app is
/// configured, you'll start the app with `App::run`.
impl Application {
/// Builds an app with the given asset source.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
#[cfg(any(test, feature = "test-support"))]
log::info!("GPUI was compiled in test mode");
/// Builds an app with a caller-provided platform implementation.
pub fn with_platform(platform: Rc<dyn Platform>) -> Self {
Self(App::new_app(
current_platform(false),
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),
platform,
Arc::new(()),
Arc::new(NullHttpClient),
))

View file

@ -42,15 +42,18 @@ impl VisualTestAppContext {
///
/// 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.
pub fn new() -> Self {
Self::with_asset_source(Arc::new(()))
pub fn new(platform: Rc<dyn Platform>) -> Self {
Self::with_asset_source(platform, Arc::new(()))
}
/// Creates a new `VisualTestAppContext` with a custom asset source.
///
/// Use this when you need SVG icons to render properly in visual tests.
/// 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
let seed = std::env::var("SEED")
.ok()
@ -59,7 +62,7 @@ impl VisualTestAppContext {
// Create a visual test platform that combines real Mac rendering
// 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
let dispatcher = platform.dispatcher().clone();
@ -391,12 +394,6 @@ impl VisualTestAppContext {
}
}
impl Default for VisualTestAppContext {
fn default() -> Self {
Self::new()
}
}
impl AppContext for VisualTestAppContext {
fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
let mut app = self.app.borrow_mut();
@ -476,112 +473,3 @@ impl AppContext for VisualTestAppContext {
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());
}
}

View file

@ -33,9 +33,10 @@ impl AssetSource for () {
pub struct ImageId(pub usize);
#[derive(PartialEq, Eq, Hash, Clone)]
pub(crate) struct RenderImageParams {
pub(crate) image_id: ImageId,
pub(crate) frame_index: usize,
#[expect(missing_docs)]
pub struct RenderImageParams {
pub image_id: ImageId,
pub frame_index: usize,
}
/// A cached and processed image, in BGRA format

View file

@ -23,7 +23,7 @@ pub fn rgba(hex: u32) -> Rgba {
}
/// 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);
if color[3] > 0 {
let a = color[3] as f32 / 255.;

View file

@ -1592,7 +1592,7 @@ impl<T: Clone + Debug + Default + PartialEq + Display + Add<T, Output = T>> Disp
impl Size<DevicePixels> {
/// 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(
px(self.width.0 as f32 / scale_factor),
px(self.height.0 as f32 / scale_factor),
@ -1602,7 +1602,7 @@ impl Size<DevicePixels> {
impl Size<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(
DevicePixels((self.width.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`.
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.
///
/// # Returns
@ -2964,9 +2969,14 @@ impl From<usize> for DevicePixels {
/// display resolutions.
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, DivAssign, PartialEq)]
#[repr(transparent)]
pub struct ScaledPixels(pub(crate) f32);
pub struct ScaledPixels(pub f32);
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.
///
/// # Returns

View file

@ -5,7 +5,8 @@
#![allow(unused_mut)] // False positives in platform specific code
extern crate self as gpui;
#[doc(hidden)]
pub static GPUI_MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");
#[macro_use]
mod action;
mod app;
@ -32,9 +33,11 @@ mod keymap;
mod path_builder;
mod platform;
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"))]
mod queue;
#[expect(missing_docs)]
pub mod queue;
mod scene;
mod shared_string;
mod shared_uri;
@ -94,7 +97,7 @@ pub use path_builder::*;
pub use platform::*;
pub use profiler::*;
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub(crate) use queue::{PriorityQueueReceiver, PriorityQueueSender};
pub use queue::{PriorityQueueReceiver, PriorityQueueSender};
pub use refineable::*;
pub use scene::*;
pub use shared_string::*;

View file

@ -557,7 +557,7 @@ impl Deref for MouseExitEvent {
/// A collection of paths from the platform, such as from a file drop.
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
pub struct ExternalPaths(pub SmallVec<[PathBuf; 2]>);
impl ExternalPaths {
/// Convert this collection of paths into a slice.

View file

@ -262,7 +262,7 @@ impl KeyBindingContextPredicate {
/// Eval a predicate against a set of contexts, arranged from lowest to highest.
#[allow(unused)]
pub(crate) fn eval(&self, contexts: &[KeyContext]) -> bool {
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
self.eval_inner(contexts, contexts)
}

View file

@ -2,17 +2,9 @@ mod app_menu;
mod keyboard;
mod keystroke;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
mod linux;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(all(
any(target_os = "linux", target_os = "freebsd"),
any(feature = "wayland", feature = "x11")
))]
mod wgpu;
#[cfg(all(target_os = "linux", feature = "wayland"))]
#[expect(missing_docs)]
pub mod layer_shell;
#[cfg(any(test, feature = "test-support"))]
mod test;
@ -20,14 +12,21 @@ mod test;
#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
mod visual_test;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(all(
feature = "screen-capture",
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::{
Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
@ -69,17 +68,8 @@ pub use app_menu::*;
pub use keyboard::*;
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"))]
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"))]
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")))]
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.
/// 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"))]
#[inline]
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 foreground_executor(&self) -> ForegroundExecutor;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
@ -182,16 +129,10 @@ pub(crate) trait Platform: 'static {
None
}
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool;
#[cfg(not(feature = "screen-capture"))]
fn is_screen_capture_supported(&self) -> bool {
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(
&self,
) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
@ -370,6 +311,19 @@ pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
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 {
fn from(id: DisplayId) -> Self {
id.0
@ -482,13 +436,16 @@ impl Tiling {
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub(crate) struct RequestFrameOptions {
pub(crate) require_presentation: bool,
/// Force refresh of all rendering states when true
pub(crate) force_render: bool,
#[expect(missing_docs)]
pub struct RequestFrameOptions {
/// Whether a presentation is required.
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 is_maximized(&self) -> bool;
fn window_bounds(&self) -> WindowBounds;
@ -558,7 +515,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
#[cfg(target_os = "windows")]
fn get_raw_handle(&self) -> windows::HWND;
fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
// Linux specific methods
fn inner_window_bounds(&self) -> WindowBounds {
@ -603,17 +560,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
pub type RunnableVariant = Runnable<RunnableMeta>;
#[doc(hidden)]
pub struct TimerResolutionGuard {
cleanup: Option<Box<dyn FnOnce() + Send>>,
}
impl Drop for TimerResolutionGuard {
fn drop(&mut self) {
if let Some(cleanup) = self.cleanup.take() {
cleanup();
}
}
}
pub type TimerResolutionGuard = util::Deferred<Box<dyn FnOnce() + Send>>;
/// 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.
@ -632,7 +579,7 @@ pub trait PlatformDispatcher: Send + Sync {
}
fn increase_timer_resolution(&self) -> TimerResolutionGuard {
TimerResolutionGuard { cleanup: None }
util::defer(Box::new(|| {}))
}
#[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 all_font_names(&self) -> Vec<String>;
fn font_id(&self, descriptor: &Font) -> Result<FontId>;
@ -660,8 +608,10 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
-> TextRenderingMode;
}
pub(crate) struct NoopTextSystem;
#[expect(missing_docs)]
pub struct NoopTextSystem;
#[expect(missing_docs)]
impl NoopTextSystem {
#[allow(dead_code)]
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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/// Compute gamma correction ratios for subpixel text rendering.
#[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] = [
[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
@ -824,7 +775,8 @@ pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub(crate) enum AtlasKey {
#[expect(missing_docs)]
pub enum AtlasKey {
Glyph(RenderGlyphParams),
Svg(RenderSvgParams),
Image(RenderImageParams),
@ -838,7 +790,8 @@ impl AtlasKey {
),
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 {
AtlasKey::Glyph(params) => {
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>(
&self,
key: &AtlasKey,
@ -882,9 +836,10 @@ pub(crate) trait PlatformAtlas: Send + Sync {
fn remove(&self, key: &AtlasKey);
}
struct AtlasTextureList<T> {
textures: Vec<Option<T>>,
free_list: Vec<usize>,
#[doc(hidden)]
pub struct AtlasTextureList<T> {
pub textures: Vec<Option<T>>,
pub free_list: Vec<usize>,
}
impl<T> Default for AtlasTextureList<T> {
@ -906,32 +861,40 @@ impl<T> ops::Index<usize> for AtlasTextureList<T> {
impl<T> AtlasTextureList<T> {
#[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.textures.drain(..)
}
#[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()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub(crate) struct AtlasTile {
pub(crate) texture_id: AtlasTextureId,
pub(crate) tile_id: TileId,
pub(crate) padding: u32,
pub(crate) bounds: Bounds<DevicePixels>,
#[expect(missing_docs)]
pub struct AtlasTile {
/// The texture this tile belongs to.
pub texture_id: AtlasTextureId,
/// 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)]
#[repr(C)]
pub(crate) struct AtlasTextureId {
#[expect(missing_docs)]
pub struct AtlasTextureId {
// We use u32 instead of usize for Metal Shader Language compatibility
pub(crate) index: u32,
pub(crate) kind: AtlasTextureKind,
/// The index of this texture in the atlas.
pub index: u32,
/// The kind of content stored in this texture.
pub kind: AtlasTextureKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -943,7 +906,8 @@ pub(crate) struct AtlasTextureId {
),
allow(dead_code)
)]
pub(crate) enum AtlasTextureKind {
#[expect(missing_docs)]
pub enum AtlasTextureKind {
Monochrome = 0,
Polychrome = 1,
Subpixel = 2,
@ -951,7 +915,8 @@ pub(crate) enum AtlasTextureKind {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub(crate) struct TileId(pub(crate) u32);
#[expect(missing_docs)]
pub struct TileId(pub u32);
impl From<etagere::AllocId> for TileId {
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,
handler: Box<dyn InputHandler>,
}
#[expect(missing_docs)]
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
@ -982,7 +949,7 @@ impl PlatformInputHandler {
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
.update(|window, cx| {
self.handler
@ -993,7 +960,7 @@ impl PlatformInputHandler {
}
#[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
.update(|window, cx| self.handler.marked_text_range(window, cx))
.ok()
@ -1004,7 +971,7 @@ impl PlatformInputHandler {
any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
allow(dead_code)
)]
fn text_for_range(
pub fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted: &mut Option<Range<usize>>,
@ -1018,7 +985,7 @@ impl PlatformInputHandler {
.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
.update(|window, cx| {
self.handler
@ -1047,13 +1014,13 @@ impl PlatformInputHandler {
}
#[cfg_attr(target_os = "windows", allow(dead_code))]
fn unmark_text(&mut self) {
pub fn unmark_text(&mut self) {
self.cx
.update(|window, cx| self.handler.unmark_text(window, cx))
.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
.update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
.ok()
@ -1061,11 +1028,11 @@ impl PlatformInputHandler {
}
#[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()
}
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);
}
@ -1091,7 +1058,7 @@ impl PlatformInputHandler {
}
#[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)
}
}
@ -1268,7 +1235,8 @@ pub struct WindowOptions {
),
allow(dead_code)
)]
pub(crate) struct WindowParams {
#[expect(missing_docs)]
pub struct WindowParams {
pub bounds: Bounds<Pixels>,
/// The titlebar configuration of the window
@ -1523,8 +1491,9 @@ impl PromptButton {
PromptButton::Cancel(label.into())
}
/// Returns true if this button is a cancel button.
#[allow(dead_code)]
pub(crate) fn is_cancel(&self) -> bool {
pub fn is_cancel(&self) -> bool {
matches!(self, PromptButton::Cancel(_))
}
@ -1642,7 +1611,8 @@ pub enum CursorStyle {
/// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {
entries: Vec<ClipboardEntry>,
/// The entries in this clipboard item.
pub entries: Vec<ClipboardEntry>,
}
/// Either a ClipboardString or a ClipboardImage
@ -1842,7 +1812,7 @@ pub struct Image {
/// The raw image bytes
pub bytes: Vec<u8>,
/// The unique ID for the image
id: u64,
pub id: u64,
}
impl Hash for Image {
@ -1960,8 +1930,10 @@ impl Image {
/// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardString {
pub(crate) text: String,
pub(crate) metadata: Option<String>,
/// The text content.
pub text: String,
/// Optional metadata associated with this clipboard string.
pub metadata: Option<String>,
}
impl ClipboardString {
@ -2001,7 +1973,8 @@ impl ClipboardString {
}
#[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();
text.hash(&mut hasher);
hasher.finish()

View file

@ -265,7 +265,8 @@ impl Keystroke {
impl KeybindingKeystroke {
#[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 {
inner,
display_modifiers,

View file

@ -1,6 +1,5 @@
use bitflags::bitflags;
use thiserror::Error;
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
use crate::Pixels;
@ -22,17 +21,6 @@ pub enum Layer {
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! {
/// 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
@ -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.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum KeyboardInteractivity {
@ -72,16 +54,6 @@ pub enum KeyboardInteractivity {
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.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LayerShellOptions {

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;

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
}
}
}
}

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>);
}

View file

@ -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;
}

View file

@ -15,7 +15,7 @@ use std::sync::atomic::{self, AtomicBool};
/// `scap_default_target_source` should be used instead on Wayland, since `scap_screen_sources`
/// won't return any results.
#[allow(dead_code)]
pub(crate) fn scap_screen_sources(
pub fn scap_screen_sources(
foreground_executor: &ForegroundExecutor,
) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
let (sources_tx, sources_rx) = oneshot::channel();

View file

@ -15,11 +15,6 @@ use std::{
rc::{Rc, Weak},
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.
pub(crate) struct TestPlatform {
@ -39,8 +34,6 @@ pub(crate) struct TestPlatform {
pub opened_url: RefCell<Option<String>>,
pub text_system: Arc<dyn PlatformTextSystem>,
pub expect_restart: RefCell<Option<oneshot::Sender<Option<PathBuf>>>>,
#[cfg(target_os = "windows")]
bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
weak: Weak<Self>,
}
@ -95,16 +88,6 @@ pub(crate) struct TestPrompts {
impl TestPlatform {
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);
Rc::new_cyclic(|weak| TestPlatform {
@ -123,8 +106,6 @@ impl TestPlatform {
current_find_pasteboard_item: Mutex::new(None),
weak: weak.clone(),
opened_url: Default::default(),
#[cfg(target_os = "windows")]
bitmap_factory,
text_system,
})
}
@ -288,12 +269,10 @@ impl Platform for TestPlatform {
Some(self.active_display.clone())
}
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool {
true
}
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(
&self,
) -> 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;
impl PlatformKeyboardLayout for TestKeyboardLayout {

View file

@ -32,7 +32,7 @@ pub(crate) struct TestWindowState {
}
#[derive(Clone)]
pub(crate) struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
pub struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
impl HasWindowHandle for TestWindow {
fn window_handle(
@ -51,7 +51,7 @@ impl HasDisplayHandle for TestWindow {
}
impl TestWindow {
pub fn new(
pub(crate) fn new(
handle: AnyWindowHandle,
params: WindowParams,
platform: Weak<TestPlatform>,

View file

@ -9,7 +9,7 @@
use crate::ScreenCaptureSource;
use crate::{
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap,
MacPlatform, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, Task,
TestDispatcher, WindowAppearance, WindowParams,
};
@ -33,7 +33,7 @@ pub struct VisualTestPlatform {
dispatcher: TestDispatcher,
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
mac_platform: MacPlatform,
platform: Rc<dyn Platform>,
clipboard: Mutex<Option<ClipboardItem>>,
find_pasteboard: Mutex<Option<ClipboardItem>>,
}
@ -42,20 +42,18 @@ impl VisualTestPlatform {
/// Creates a new VisualTestPlatform with the given random seed.
///
/// 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 arc_dispatcher = Arc::new(dispatcher.clone());
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
let mac_platform = MacPlatform::new(false);
Self {
dispatcher,
background_executor,
foreground_executor,
mac_platform,
platform,
clipboard: Mutex::new(None),
find_pasteboard: Mutex::new(None),
}
@ -77,7 +75,7 @@ impl Platform for VisualTestPlatform {
}
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()>) {
@ -97,31 +95,29 @@ impl Platform for VisualTestPlatform {
fn unhide_other_apps(&self) {}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
self.mac_platform.displays()
self.platform.displays()
}
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.mac_platform.primary_display()
self.platform.primary_display()
}
fn active_window(&self) -> Option<AnyWindowHandle> {
self.mac_platform.active_window()
self.platform.active_window()
}
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 {
self.mac_platform.is_screen_capture_supported()
self.platform.is_screen_capture_supported()
}
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(
&self,
) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
self.mac_platform.screen_capture_sources()
self.platform.screen_capture_sources()
}
fn open_window(
@ -129,15 +125,15 @@ impl Platform for VisualTestPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> {
self.mac_platform.open_window(handle, options)
self.platform.open_window(handle, options)
}
fn window_appearance(&self) -> WindowAppearance {
self.mac_platform.window_appearance()
self.platform.window_appearance()
}
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>)>) {}
@ -170,11 +166,11 @@ impl Platform for VisualTestPlatform {
}
fn reveal_path(&self, path: &Path) {
self.mac_platform.reveal_path(path)
self.platform.reveal_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()>) {}
@ -196,19 +192,19 @@ impl Platform for VisualTestPlatform {
fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
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> {
self.mac_platform.path_for_auxiliary_executable(name)
self.platform.path_for_auxiliary_executable(name)
}
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 {
self.mac_platform.should_auto_hide_scrollbars()
self.platform.should_auto_hide_scrollbars()
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
@ -242,11 +238,11 @@ impl Platform for VisualTestPlatform {
}
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
self.mac_platform.keyboard_layout()
self.platform.keyboard_layout()
}
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()>) {}

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::*;

View file

@ -30,7 +30,8 @@ pub struct 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
.iter()
.filter_map(|t| match t.timings.upgrade() {
@ -245,19 +246,24 @@ impl ProfilingCollector {
// Allow 20mb of task timing entries
const MAX_TASK_TIMINGS: usize = (20 * 1024 * 1024) / core::mem::size_of::<TaskTiming>();
pub(crate) type TaskTimings = circular_buffer::CircularBuffer<MAX_TASK_TIMINGS, TaskTiming>;
pub(crate) type GuardedTaskTimings = spin::Mutex<ThreadTimings>;
#[doc(hidden)]
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 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());
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 thread_name = current_thread.name();
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_id: ThreadId,
pub timings: Box<TaskTimings>,
@ -285,7 +292,7 @@ pub(crate) struct 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 {
thread_name,
thread_id,
@ -310,8 +317,9 @@ impl Drop for ThreadTimings {
}
}
#[doc(hidden)]
#[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| {
let mut timings = timings.lock();

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>>,
}
@ -95,7 +96,7 @@ impl<T> PriorityQueueSender<T> {
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)?;
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>>,
rand: SmallRng,
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> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -137,11 +140,12 @@ impl<T: fmt::Debug> fmt::Debug for SendError<T> {
}
#[derive(Debug)]
pub(crate) struct RecvError;
#[doc(hidden)]
pub struct RecvError;
#[allow(dead_code)]
impl<T> PriorityQueueReceiver<T> {
pub(crate) fn new() -> (PriorityQueueSender<T>, Self) {
pub fn new() -> (PriorityQueueSender<T>, Self) {
let state = PriorityQueueState {
queues: parking_lot::Mutex::new(PriorityQueues {
high_priority: VecDeque::new(),
@ -175,7 +179,7 @@ impl<T> PriorityQueueReceiver<T> {
/// # Errors
///
/// 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)
}
@ -187,13 +191,13 @@ impl<T> PriorityQueueReceiver<T> {
/// # Errors
///
/// 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())
}
/// 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.
pub(crate) fn try_iter(self) -> TryIter<T> {
pub fn try_iter(self) -> TryIter<T> {
TryIter {
receiver: self,
ended: false,
@ -202,7 +206,7 @@ impl<T> PriorityQueueReceiver<T> {
/// Returns an iterator over the elements of the queue
/// 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)
}
@ -261,8 +265,8 @@ impl<T> Drop for PriorityQueueReceiver<T> {
}
}
/// If None is returned the sender disconnected
pub(crate) struct Iter<T>(PriorityQueueReceiver<T>);
#[doc(hidden)]
pub struct Iter<T>(PriorityQueueReceiver<T>);
impl<T> Iterator for Iter<T> {
type Item = T;
@ -272,8 +276,8 @@ impl<T> Iterator for Iter<T> {
}
impl<T> FusedIterator for Iter<T> {}
/// If None is returned there are no more elements in the queue
pub(crate) struct TryIter<T> {
#[doc(hidden)]
pub struct TryIter<T> {
receiver: PriorityQueueReceiver<T>,
ended: bool,
}

View file

@ -16,25 +16,29 @@ use std::{
};
#[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)]
pub(crate) struct Scene {
#[expect(missing_docs)]
pub struct Scene {
pub(crate) paint_operations: Vec<PaintOperation>,
primitive_bounds: BoundsTree<ScaledPixels>,
layer_stack: Vec<DrawOrder>,
pub(crate) shadows: Vec<Shadow>,
pub(crate) quads: Vec<Quad>,
pub(crate) paths: Vec<Path<ScaledPixels>>,
pub(crate) underlines: Vec<Underline>,
pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
pub(crate) subpixel_sprites: Vec<SubpixelSprite>,
pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
pub(crate) surfaces: Vec<PaintSurface>,
pub shadows: Vec<Shadow>,
pub quads: Vec<Quad>,
pub paths: Vec<Path<ScaledPixels>>,
pub underlines: Vec<Underline>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub subpixel_sprites: Vec<SubpixelSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
pub surfaces: Vec<PaintSurface>,
}
#[expect(missing_docs)]
impl Scene {
pub fn clear(&mut self) {
self.paint_operations.clear();
@ -151,7 +155,7 @@ impl Scene {
),
allow(dead_code)
)]
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
BatchIterator {
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
@ -200,7 +204,8 @@ pub(crate) enum PaintOperation {
}
#[derive(Clone)]
pub(crate) enum Primitive {
#[expect(missing_docs)]
pub enum Primitive {
Shadow(Shadow),
Quad(Quad),
Path(Path<ScaledPixels>),
@ -211,6 +216,7 @@ pub(crate) enum Primitive {
Surface(PaintSurface),
}
#[expect(missing_docs)]
impl Primitive {
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
match self {
@ -453,7 +459,8 @@ impl<'a> Iterator for BatchIterator<'a> {
),
allow(dead_code)
)]
pub(crate) enum PrimitiveBatch {
#[expect(missing_docs)]
pub enum PrimitiveBatch {
Shadows(Range<usize>),
Quads(Range<usize>),
Paths(Range<usize>),
@ -476,7 +483,8 @@ pub(crate) enum PrimitiveBatch {
#[derive(Default, Debug, Clone)]
#[repr(C)]
pub(crate) struct Quad {
#[expect(missing_docs)]
pub struct Quad {
pub order: DrawOrder,
pub border_style: BorderStyle,
pub bounds: Bounds<ScaledPixels>,
@ -495,7 +503,8 @@ impl From<Quad> for Primitive {
#[derive(Debug, Clone)]
#[repr(C)]
pub(crate) struct Underline {
#[expect(missing_docs)]
pub struct Underline {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>,
@ -513,7 +522,8 @@ impl From<Underline> for Primitive {
#[derive(Debug, Clone)]
#[repr(C)]
pub(crate) struct Shadow {
#[expect(missing_docs)]
pub struct Shadow {
pub order: DrawOrder,
pub blur_radius: ScaledPixels,
pub bounds: Bounds<ScaledPixels>,
@ -644,7 +654,8 @@ impl Default for TransformationMatrix {
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct MonochromeSprite {
#[expect(missing_docs)]
pub struct MonochromeSprite {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>,
@ -662,7 +673,8 @@ impl From<MonochromeSprite> for Primitive {
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct SubpixelSprite {
#[expect(missing_docs)]
pub struct SubpixelSprite {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>,
@ -680,7 +692,8 @@ impl From<SubpixelSprite> for Primitive {
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct PolychromeSprite {
#[expect(missing_docs)]
pub struct PolychromeSprite {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub grayscale: bool,
@ -698,7 +711,8 @@ impl From<PolychromeSprite> for Primitive {
}
#[derive(Clone, Debug)]
pub(crate) struct PaintSurface {
#[expect(missing_docs)]
pub struct PaintSurface {
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@ -713,17 +727,19 @@ impl From<PaintSurface> for Primitive {
}
#[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.
#[derive(Clone, Debug)]
#[expect(missing_docs)]
pub struct Path<P: Clone + Debug + Default + PartialEq> {
pub(crate) id: PathId,
pub(crate) order: DrawOrder,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,
pub(crate) color: Background,
pub id: PathId,
pub order: DrawOrder,
pub bounds: Bounds<P>,
pub content_mask: ContentMask<P>,
pub vertices: Vec<PathVertex<P>>,
pub color: Background,
start: Point<P>,
current: Point<P>,
contour_count: usize,
@ -847,7 +863,8 @@ where
T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
{
#[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)
}
}
@ -860,12 +877,14 @@ impl From<Path<ScaledPixels>> for Primitive {
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
pub(crate) xy_position: Point<P>,
pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>,
#[expect(missing_docs)]
pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
pub xy_position: Point<P>,
pub st_position: Point<f32>,
pub content_mask: ContentMask<P>,
}
#[expect(missing_docs)]
impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex {

View file

@ -14,9 +14,10 @@ use std::{
pub const SMOOTH_SVG_SCALE_FACTOR: f32 = 2.;
#[derive(Clone, PartialEq, Hash, Eq)]
pub(crate) struct RenderSvgParams {
pub(crate) path: SharedString,
pub(crate) size: Size<DevicePixels>,
#[expect(missing_docs)]
pub struct RenderSvgParams {
pub path: SharedString,
pub size: Size<DevicePixels>,
}
#[derive(Clone)]

View file

@ -41,14 +41,15 @@ pub struct FontId(pub usize);
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
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 =
if cfg!(target_os = "windows") || cfg!(target_os = "linux") {
1
} else {
SUBPIXEL_VARIANTS_X
};
/// Number of subpixel glyph variants along the Y axis.
pub const SUBPIXEL_VARIANTS_Y: u8 = if cfg!(target_os = "windows") || cfg!(target_os = "linux") {
1
} else {
SUBPIXEL_VARIANTS_X
};
/// The GPUI text rendering sub system.
pub struct TextSystem {
@ -799,17 +800,18 @@ impl TextRun {
/// An identifier for a specific glyph, as returned by [`WindowTextSystem::layout_line`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct GlyphId(pub(crate) u32);
pub struct GlyphId(pub u32);
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct RenderGlyphParams {
pub(crate) font_id: FontId,
pub(crate) glyph_id: GlyphId,
pub(crate) font_size: Pixels,
pub(crate) subpixel_variant: Point<u8>,
pub(crate) scale_factor: f32,
pub(crate) is_emoji: bool,
pub(crate) subpixel_rendering: bool,
#[expect(missing_docs)]
pub struct RenderGlyphParams {
pub font_id: FontId,
pub glyph_id: GlyphId,
pub font_size: Pixels,
pub subpixel_variant: Point<u8>,
pub scale_factor: f32,
pub is_emoji: bool,
pub subpixel_rendering: bool,
}
impl Eq for RenderGlyphParams {}
@ -884,32 +886,32 @@ impl Font {
pub struct FontMetrics {
/// The number of font units that make up the "em square",
/// 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.
pub(crate) ascent: f32,
pub ascent: f32,
/// 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.
pub(crate) line_gap: f32,
pub line_gap: f32,
/// The suggested position of the underline.
pub(crate) underline_position: f32,
pub underline_position: f32,
/// 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.
pub(crate) cap_height: f32,
pub cap_height: f32,
/// 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.
/// 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 {
@ -954,8 +956,9 @@ impl FontMetrics {
}
}
/// Maps well-known virtual font names to their concrete equivalents.
#[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"
// in a derived font name. They are essentially indistinguishable from IBM Plex/Lilex,
// 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)]
pub(crate) fn font_name_with_fallbacks_shared<'a>(
pub fn font_name_with_fallbacks_shared<'a>(
name: &'a SharedString,
system: &'a SharedString,
) -> &'a SharedString {

View file

@ -594,9 +594,10 @@ impl LineLayoutCache {
/// A run of text with a single font.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[expect(missing_docs)]
pub struct FontRun {
pub(crate) len: usize,
pub(crate) font_id: FontId,
pub len: usize,
pub font_id: FontId,
}
trait AsCacheKeyRef {

View file

@ -59,7 +59,8 @@ mod prompts;
use crate::util::atomic_incr_if_not_zero;
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,
/// additional-to-main-Zed windows, like the settings and rules library windows.
@ -1447,7 +1448,8 @@ impl Window {
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct DispatchEventResult {
#[expect(missing_docs)]
pub struct DispatchEventResult {
pub propagate: bool,
pub default_prevented: bool,
}

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 }

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1,4 @@
#![cfg(any(target_os = "linux", target_os = "freebsd"))]
mod linux;
pub use linux::current_platform;

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!(),
}
}

View file

@ -11,7 +11,7 @@ use std::{
time::{Duration, Instant},
};
use crate::{
use gpui::{
GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, PriorityQueueReceiver,
PriorityQueueSender, RunnableVariant, THREAD_TIMINGS, TaskTiming, ThreadTaskTimings, profiler,
};
@ -39,8 +39,7 @@ impl LinuxDispatcher {
let mut background_threads = (0..thread_count)
.map(|i| {
let mut receiver: PriorityQueueReceiver<RunnableVariant> =
background_receiver.clone();
let receiver: PriorityQueueReceiver<RunnableVariant> = background_receiver.clone();
std::thread::Builder::new()
.name(format!("Worker-{i}"))
.spawn(move || {
@ -140,12 +139,12 @@ impl 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();
ThreadTaskTimings::convert(&global_timings)
}
fn get_current_thread_timings(&self) -> crate::ThreadTaskTimings {
fn get_current_thread_timings(&self) -> gpui::ThreadTaskTimings {
THREAD_TIMINGS.with(|timings| {
let timings = timings.lock();
let thread_name = timings.thread_name.clone();
@ -158,7 +157,7 @@ impl PlatformDispatcher for LinuxDispatcher {
vec.extend_from_slice(s1);
vec.extend_from_slice(s2);
crate::ThreadTaskTimings {
gpui::ThreadTaskTimings {
thread_name,
thread_id: std::thread::current().id(),
timings: vec,
@ -232,7 +231,7 @@ impl<T> PriorityQueueCalloopSender<T> {
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);
if res.is_ok() {
self.ping.ping();
@ -312,7 +311,7 @@ impl<T> calloop::EventSource for PriorityQueueCalloopReceiver<T> {
.process_events(readiness, token, |(), &mut ()| {
let mut is_empty = true;
let mut receiver = self.receiver.clone();
let receiver = self.receiver.clone();
for runnable in receiver.try_iter() {
match runnable {
Ok(r) => {
@ -429,11 +428,11 @@ mod tests {
}
// running 1 test
// test platform::linux::dispatcher::tests::tomato ... FAILED
// test linux::dispatcher::tests::tomato ... FAILED
// failures:
// ---- platform::linux::dispatcher::tests::tomato stdout ----
// ---- linux::dispatcher::tests::tomato stdout ----
// [crates/gpui/src/platform/linux/dispatcher.rs:262:9]
// returning 1 tasks to process
// [crates/gpui/src/platform/linux/dispatcher.rs:480:75] evt = Msg(
@ -441,6 +440,6 @@ mod tests {
// )
// 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
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

View file

@ -4,11 +4,10 @@ use std::rc::Rc;
use calloop::{EventLoop, LoopHandle};
use util::ResultExt;
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, LinuxKeyboardLayout, PlatformDisplay,
PlatformKeyboardLayout, WindowParams,
use crate::linux::{LinuxClient, LinuxCommon, LinuxKeyboardLayout};
use gpui::{
AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, PlatformKeyboardLayout,
PlatformWindow, WindowParams,
};
pub struct HeadlessClientState {
@ -65,17 +64,11 @@ impl LinuxClient for HeadlessClient {
None
}
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool {
false
}
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(
&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!(
"Headless mode does not support screen capture."
)))
@ -109,15 +102,15 @@ impl LinuxClient for HeadlessClient {
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
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
None
}

View file

@ -1,4 +1,4 @@
use crate::{PlatformKeyboardLayout, SharedString};
use gpui::{PlatformKeyboardLayout, SharedString};
#[derive(Clone)]
pub(crate) struct LinuxKeyboardLayout {

View file

@ -21,15 +21,15 @@ use util::command::{new_command, new_std_command};
#[cfg(any(feature = "wayland", feature = "x11"))]
use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::{
use crate::linux::{LinuxDispatcher, PriorityQueueCalloopReceiver};
use gpui::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
PlatformWindow, PriorityQueueCalloopReceiver, Result, RunnableVariant, Task, ThermalState,
WindowAppearance, WindowParams,
ForegroundExecutor, Keymap, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform,
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
PlatformWindow, Result, RunnableVariant, Task, ThermalState, WindowAppearance, WindowParams,
};
#[cfg(any(feature = "wayland", feature = "x11"))]
use crate::{Pixels, Point, px};
use gpui::{Pixels, Point, px};
#[cfg(any(feature = "wayland", feature = "x11"))]
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 with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
@ -99,11 +99,21 @@ pub trait LinuxClient {
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool;
fn is_screen_capture_supported(&self) -> bool {
false
}
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(
&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(
&self,
@ -156,7 +166,7 @@ impl LinuxCommon {
let (main_sender, main_receiver) = PriorityQueueCalloopReceiver::new();
#[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")))]
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 {
self.with_common(|common| common.background_executor.clone())
self.inner
.with_common(|common| common.background_executor.clone())
}
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> {
self.with_common(|common| common.text_system.clone())
self.inner.with_common(|common| common.text_system.clone())
}
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
self.keyboard_layout()
self.inner.keyboard_layout()
}
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()>) {
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()>) {}
@ -215,20 +232,22 @@ impl<P: LinuxClient + 'static> Platform for P {
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
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 {
fun();
}
}
fn quit(&self) {
self.with_common(|common| common.signal.stop());
self.inner.with_common(|common| common.signal.stop());
}
fn compositor_name(&self) -> &'static str {
self.compositor_name()
self.inner.compositor_name()
}
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>> {
self.primary_display()
self.inner.primary_display()
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
self.displays()
self.inner.displays()
}
#[cfg(feature = "screen-capture")]
fn is_screen_capture_supported(&self) -> bool {
self.is_screen_capture_supported()
self.inner.is_screen_capture_supported()
}
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(
&self,
) -> oneshot::Receiver<Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>> {
self.screen_capture_sources()
) -> oneshot::Receiver<Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>> {
self.inner.screen_capture_sources()
}
fn active_window(&self) -> Option<AnyWindowHandle> {
self.active_window()
self.inner.active_window()
}
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
self.window_stack()
self.inner.window_stack()
}
fn open_window(
@ -330,15 +349,16 @@ impl<P: LinuxClient + 'static> Platform for P {
handle: AnyWindowHandle,
options: WindowParams,
) -> anyhow::Result<Box<dyn PlatformWindow>> {
self.open_window(handle, options)
self.inner.open_window(handle, options)
}
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>)>) {
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(
@ -351,7 +371,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let _ = (done_tx.send(Ok(None)), options);
#[cfg(any(feature = "wayland", feature = "x11"))]
let identifier = self.window_identifier();
let identifier = self.inner.window_identifier();
#[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor()
@ -366,7 +386,7 @@ impl<P: LinuxClient + 'static> Platform for P {
.identifier(identifier.await)
.modal(true)
.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)
.directory(options.directories)
.send()
@ -411,7 +431,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let _ = (done_tx.send(Ok(None)), directory, suggested_name);
#[cfg(any(feature = "wayland", feature = "x11"))]
let identifier = self.window_identifier();
let identifier = self.inner.window_identifier();
#[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor()
@ -468,7 +488,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
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) {
@ -489,31 +509,31 @@ impl<P: LinuxClient + 'static> Platform for P {
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| {
self.inner.with_common(|common| {
common.callbacks.quit = Some(callback);
});
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| {
self.inner.with_common(|common| {
common.callbacks.reopen = Some(callback);
});
}
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);
});
}
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);
});
}
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);
});
}
@ -525,13 +545,13 @@ impl<P: LinuxClient + 'static> Platform for P {
}
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();
})
}
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) {
@ -545,11 +565,11 @@ impl<P: LinuxClient + 'static> Platform for P {
}
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 {
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<()>> {
@ -619,7 +639,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
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<()>> {
@ -627,19 +647,19 @@ impl<P: LinuxClient + 'static> Platform for P {
}
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) {
self.write_to_clipboard(item)
self.inner.write_to_clipboard(item)
}
fn read_from_primary(&self) -> Option<ClipboardItem> {
self.read_from_primary()
self.inner.read_from_primary()
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.read_from_clipboard()
self.inner.read_from_clipboard()
}
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"))]
pub(super) const DEFAULT_CURSOR_ICON_NAME: &str = "left_ptr";
impl CursorStyle {
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(super) fn to_icon_names(self) -> &'static [&'static str] {
// Based on cursor names from chromium:
// https://github.com/chromium/chromium/blob/d3069cf9c973dc3627fa75f64085c6a86c8f41bf/ui/base/cursor/cursor_factory.cc#L113
match self {
CursorStyle::Arrow => &[DEFAULT_CURSOR_ICON_NAME],
CursorStyle::IBeam => &["text", "xterm"],
CursorStyle::Crosshair => &["crosshair", "cross"],
CursorStyle::ClosedHand => &["closedhand", "grabbing", "hand2"],
CursorStyle::OpenHand => &["openhand", "grab", "hand1"],
CursorStyle::PointingHand => &["pointer", "hand", "hand2"],
CursorStyle::ResizeLeft => &["w-resize", "left_side"],
CursorStyle::ResizeRight => &["e-resize", "right_side"],
CursorStyle::ResizeLeftRight => &["ew-resize", "sb_h_double_arrow"],
CursorStyle::ResizeUp => &["n-resize", "top_side"],
CursorStyle::ResizeDown => &["s-resize", "bottom_side"],
CursorStyle::ResizeUpDown => &["sb_v_double_arrow", "ns-resize"],
CursorStyle::ResizeUpLeftDownRight => &["size_fdiag", "bd_double_arrow", "nwse-resize"],
CursorStyle::ResizeUpRightDownLeft => &["size_bdiag", "nesw-resize", "fd_double_arrow"],
CursorStyle::ResizeColumn => &["col-resize", "sb_h_double_arrow"],
CursorStyle::ResizeRow => &["row-resize", "sb_v_double_arrow"],
CursorStyle::IBeamCursorForVerticalLayout => &["vertical-text"],
CursorStyle::OperationNotAllowed => &["not-allowed", "crossed_circle"],
CursorStyle::DragLink => &["alias"],
CursorStyle::DragCopy => &["copy"],
CursorStyle::ContextualMenu => &["context-menu"],
CursorStyle::None => {
#[cfg(debug_assertions)]
panic!("CursorStyle::None should be handled separately in the client");
#[cfg(not(debug_assertions))]
&[DEFAULT_CURSOR_ICON_NAME]
}
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(super) fn cursor_style_to_icon_names(style: CursorStyle) -> &'static [&'static str] {
// Based on cursor names from chromium:
// https://github.com/chromium/chromium/blob/d3069cf9c973dc3627fa75f64085c6a86c8f41bf/ui/base/cursor/cursor_factory.cc#L113
match style {
CursorStyle::Arrow => &[DEFAULT_CURSOR_ICON_NAME],
CursorStyle::IBeam => &["text", "xterm"],
CursorStyle::Crosshair => &["crosshair", "cross"],
CursorStyle::ClosedHand => &["closedhand", "grabbing", "hand2"],
CursorStyle::OpenHand => &["openhand", "grab", "hand1"],
CursorStyle::PointingHand => &["pointer", "hand", "hand2"],
CursorStyle::ResizeLeft => &["w-resize", "left_side"],
CursorStyle::ResizeRight => &["e-resize", "right_side"],
CursorStyle::ResizeLeftRight => &["ew-resize", "sb_h_double_arrow"],
CursorStyle::ResizeUp => &["n-resize", "top_side"],
CursorStyle::ResizeDown => &["s-resize", "bottom_side"],
CursorStyle::ResizeUpDown => &["sb_v_double_arrow", "ns-resize"],
CursorStyle::ResizeUpLeftDownRight => &["size_fdiag", "bd_double_arrow", "nwse-resize"],
CursorStyle::ResizeUpRightDownLeft => &["size_bdiag", "nesw-resize", "fd_double_arrow"],
CursorStyle::ResizeColumn => &["col-resize", "sb_h_double_arrow"],
CursorStyle::ResizeRow => &["row-resize", "sb_v_double_arrow"],
CursorStyle::IBeamCursorForVerticalLayout => &["vertical-text"],
CursorStyle::OperationNotAllowed => &["not-allowed", "crossed_circle"],
CursorStyle::DragLink => &["alias"],
CursorStyle::DragCopy => &["copy"],
CursorStyle::ContextualMenu => &["context-menu"],
CursorStyle::None => {
#[cfg(debug_assertions)]
panic!("CursorStyle::None should be handled separately in the client");
#[cfg(not(debug_assertions))]
&[DEFAULT_CURSOR_ICON_NAME]
}
}
}
@ -856,222 +874,214 @@ fn guess_ascii(keycode: Keycode, shift: bool) -> Option<char> {
}
#[cfg(any(feature = "wayland", feature = "x11"))]
impl crate::Keystroke {
pub(super) fn from_xkb(
state: &State,
mut modifiers: crate::Modifiers,
keycode: Keycode,
) -> Self {
let key_utf32 = state.key_get_utf32(keycode);
let key_utf8 = state.key_get_utf8(keycode);
let key_sym = state.key_get_one_sym(keycode);
pub(super) fn keystroke_from_xkb(
state: &State,
mut modifiers: gpui::Modifiers,
keycode: Keycode,
) -> gpui::Keystroke {
let key_utf32 = state.key_get_utf32(keycode);
let key_utf8 = state.key_get_utf8(keycode);
let key_sym = state.key_get_one_sym(keycode);
let key = match key_sym {
Keysym::Return => "enter".to_owned(),
Keysym::Prior => "pageup".to_owned(),
Keysym::Next => "pagedown".to_owned(),
Keysym::ISO_Left_Tab => "tab".to_owned(),
Keysym::KP_Prior => "pageup".to_owned(),
Keysym::KP_Next => "pagedown".to_owned(),
Keysym::XF86_Back => "back".to_owned(),
Keysym::XF86_Forward => "forward".to_owned(),
Keysym::XF86_Cut => "cut".to_owned(),
Keysym::XF86_Copy => "copy".to_owned(),
Keysym::XF86_Paste => "paste".to_owned(),
Keysym::XF86_New => "new".to_owned(),
Keysym::XF86_Open => "open".to_owned(),
Keysym::XF86_Save => "save".to_owned(),
let key = match key_sym {
Keysym::Return => "enter".to_owned(),
Keysym::Prior => "pageup".to_owned(),
Keysym::Next => "pagedown".to_owned(),
Keysym::ISO_Left_Tab => "tab".to_owned(),
Keysym::KP_Prior => "pageup".to_owned(),
Keysym::KP_Next => "pagedown".to_owned(),
Keysym::XF86_Back => "back".to_owned(),
Keysym::XF86_Forward => "forward".to_owned(),
Keysym::XF86_Cut => "cut".to_owned(),
Keysym::XF86_Copy => "copy".to_owned(),
Keysym::XF86_Paste => "paste".to_owned(),
Keysym::XF86_New => "new".to_owned(),
Keysym::XF86_Open => "open".to_owned(),
Keysym::XF86_Save => "save".to_owned(),
Keysym::comma => ",".to_owned(),
Keysym::period => ".".to_owned(),
Keysym::less => "<".to_owned(),
Keysym::greater => ">".to_owned(),
Keysym::slash => "/".to_owned(),
Keysym::question => "?".to_owned(),
Keysym::comma => ",".to_owned(),
Keysym::period => ".".to_owned(),
Keysym::less => "<".to_owned(),
Keysym::greater => ">".to_owned(),
Keysym::slash => "/".to_owned(),
Keysym::question => "?".to_owned(),
Keysym::semicolon => ";".to_owned(),
Keysym::colon => ":".to_owned(),
Keysym::apostrophe => "'".to_owned(),
Keysym::quotedbl => "\"".to_owned(),
Keysym::semicolon => ";".to_owned(),
Keysym::colon => ":".to_owned(),
Keysym::apostrophe => "'".to_owned(),
Keysym::quotedbl => "\"".to_owned(),
Keysym::bracketleft => "[".to_owned(),
Keysym::braceleft => "{".to_owned(),
Keysym::bracketright => "]".to_owned(),
Keysym::braceright => "}".to_owned(),
Keysym::backslash => "\\".to_owned(),
Keysym::bar => "|".to_owned(),
Keysym::bracketleft => "[".to_owned(),
Keysym::braceleft => "{".to_owned(),
Keysym::bracketright => "]".to_owned(),
Keysym::braceright => "}".to_owned(),
Keysym::backslash => "\\".to_owned(),
Keysym::bar => "|".to_owned(),
Keysym::grave => "`".to_owned(),
Keysym::asciitilde => "~".to_owned(),
Keysym::exclam => "!".to_owned(),
Keysym::at => "@".to_owned(),
Keysym::numbersign => "#".to_owned(),
Keysym::dollar => "$".to_owned(),
Keysym::percent => "%".to_owned(),
Keysym::asciicircum => "^".to_owned(),
Keysym::ampersand => "&".to_owned(),
Keysym::asterisk => "*".to_owned(),
Keysym::parenleft => "(".to_owned(),
Keysym::parenright => ")".to_owned(),
Keysym::minus => "-".to_owned(),
Keysym::underscore => "_".to_owned(),
Keysym::equal => "=".to_owned(),
Keysym::plus => "+".to_owned(),
Keysym::space => "space".to_owned(),
Keysym::BackSpace => "backspace".to_owned(),
Keysym::Tab => "tab".to_owned(),
Keysym::Delete => "delete".to_owned(),
Keysym::Escape => "escape".to_owned(),
Keysym::grave => "`".to_owned(),
Keysym::asciitilde => "~".to_owned(),
Keysym::exclam => "!".to_owned(),
Keysym::at => "@".to_owned(),
Keysym::numbersign => "#".to_owned(),
Keysym::dollar => "$".to_owned(),
Keysym::percent => "%".to_owned(),
Keysym::asciicircum => "^".to_owned(),
Keysym::ampersand => "&".to_owned(),
Keysym::asterisk => "*".to_owned(),
Keysym::parenleft => "(".to_owned(),
Keysym::parenright => ")".to_owned(),
Keysym::minus => "-".to_owned(),
Keysym::underscore => "_".to_owned(),
Keysym::equal => "=".to_owned(),
Keysym::plus => "+".to_owned(),
Keysym::space => "space".to_owned(),
Keysym::BackSpace => "backspace".to_owned(),
Keysym::Tab => "tab".to_owned(),
Keysym::Delete => "delete".to_owned(),
Keysym::Escape => "escape".to_owned(),
Keysym::Left => "left".to_owned(),
Keysym::Right => "right".to_owned(),
Keysym::Up => "up".to_owned(),
Keysym::Down => "down".to_owned(),
Keysym::Home => "home".to_owned(),
Keysym::End => "end".to_owned(),
Keysym::Insert => "insert".to_owned(),
Keysym::Left => "left".to_owned(),
Keysym::Right => "right".to_owned(),
Keysym::Up => "up".to_owned(),
Keysym::Down => "down".to_owned(),
Keysym::Home => "home".to_owned(),
Keysym::End => "end".to_owned(),
Keysym::Insert => "insert".to_owned(),
_ => {
let name = xkb::keysym_get_name(key_sym).to_lowercase();
if key_sym.is_keypad_key() {
name.replace("kp_", "")
} else if let Some(key) = key_utf8.chars().next()
&& key_utf8.len() == 1
&& key.is_ascii()
_ => {
let name = xkb::keysym_get_name(key_sym).to_lowercase();
if key_sym.is_keypad_key() {
name.replace("kp_", "")
} else if let Some(key) = key_utf8.chars().next()
&& key_utf8.len() == 1
&& 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_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())
{
((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)
((key_utf32 as u8 + 0x40) as char)
.to_ascii_lowercase()
.to_string()
} else {
name
}
}
};
if modifiers.shift {
// 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;
} else if let Some(key_en) = guess_ascii(keycode, modifiers.shift) {
String::from(key_en)
} else {
name
}
}
};
// Ignore control characters (and DEL) for the purposes of key_char
let key_char =
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
Self {
modifiers,
key,
key_char,
if modifiers.shift {
// 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;
}
}
/**
* Returns which symbol the dead key represents
* <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
*/
pub fn underlying_dead_key(keysym: Keysym) -> Option<String> {
match keysym {
Keysym::dead_grave => Some("`".to_owned()),
Keysym::dead_acute => Some("´".to_owned()),
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()),
Keysym::dead_diaeresis => Some("¨".to_owned()),
Keysym::dead_abovering => Some("˚".to_owned()),
Keysym::dead_doubleacute => Some("˝".to_owned()),
Keysym::dead_caron => Some("ˇ".to_owned()),
Keysym::dead_cedilla => Some("¸".to_owned()),
Keysym::dead_ogonek => Some("˛".to_owned()),
Keysym::dead_iota => Some("ͅ".to_owned()),
Keysym::dead_voiced_sound => Some("".to_owned()),
Keysym::dead_semivoiced_sound => Some("".to_owned()),
Keysym::dead_belowdot => Some("̣̣".to_owned()),
Keysym::dead_hook => Some("̡".to_owned()),
Keysym::dead_horn => Some("̛".to_owned()),
Keysym::dead_stroke => Some("̶̶".to_owned()),
Keysym::dead_abovecomma => Some("̓̓".to_owned()),
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
Keysym::dead_doublegrave => Some("̏".to_owned()),
Keysym::dead_belowring => Some("˳".to_owned()),
Keysym::dead_belowmacron => Some("̱".to_owned()),
Keysym::dead_belowcircumflex => Some("".to_owned()),
Keysym::dead_belowtilde => Some("̰".to_owned()),
Keysym::dead_belowbreve => Some("̮".to_owned()),
Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
Keysym::dead_invertedbreve => Some("̯".to_owned()),
Keysym::dead_belowcomma => Some("̦".to_owned()),
Keysym::dead_currency => None,
Keysym::dead_lowline => None,
Keysym::dead_aboveverticalline => None,
Keysym::dead_belowverticalline => None,
Keysym::dead_longsolidusoverlay => None,
Keysym::dead_a => 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,
}
// Ignore control characters (and DEL) for the purposes of key_char
let key_char =
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
gpui::Keystroke {
modifiers,
key,
key_char,
}
}
/**
* Returns which symbol the dead key represents
* <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
*/
#[cfg(any(feature = "wayland", feature = "x11"))]
pub fn keystroke_underlying_dead_key(keysym: Keysym) -> Option<String> {
match keysym {
Keysym::dead_grave => Some("`".to_owned()),
Keysym::dead_acute => Some("´".to_owned()),
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()),
Keysym::dead_diaeresis => Some("¨".to_owned()),
Keysym::dead_abovering => Some("˚".to_owned()),
Keysym::dead_doubleacute => Some("˝".to_owned()),
Keysym::dead_caron => Some("ˇ".to_owned()),
Keysym::dead_cedilla => Some("¸".to_owned()),
Keysym::dead_ogonek => Some("˛".to_owned()),
Keysym::dead_iota => Some("ͅ".to_owned()),
Keysym::dead_voiced_sound => Some("".to_owned()),
Keysym::dead_semivoiced_sound => Some("".to_owned()),
Keysym::dead_belowdot => Some("̣̣".to_owned()),
Keysym::dead_hook => Some("̡".to_owned()),
Keysym::dead_horn => Some("̛".to_owned()),
Keysym::dead_stroke => Some("̶̶".to_owned()),
Keysym::dead_abovecomma => Some("̓̓".to_owned()),
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
Keysym::dead_doublegrave => Some("̏".to_owned()),
Keysym::dead_belowring => Some("˳".to_owned()),
Keysym::dead_belowmacron => Some("̱".to_owned()),
Keysym::dead_belowcircumflex => Some("".to_owned()),
Keysym::dead_belowtilde => Some("̰".to_owned()),
Keysym::dead_belowbreve => Some("̮".to_owned()),
Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
Keysym::dead_invertedbreve => Some("̯".to_owned()),
Keysym::dead_belowcomma => Some("̦".to_owned()),
Keysym::dead_currency => None,
Keysym::dead_lowline => None,
Keysym::dead_aboveverticalline => None,
Keysym::dead_belowverticalline => None,
Keysym::dead_longsolidusoverlay => None,
Keysym::dead_a => 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"))]
impl crate::Modifiers {
pub(super) fn from_xkb(keymap_state: &State) -> Self {
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);
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 }
}
pub(super) fn capslock_from_xkb(keymap_state: &State) -> gpui::Capslock {
let on = keymap_state.mod_name_is_active(xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE);
gpui::Capslock { on }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Point, px};
use gpui::{Point, px};
#[test]
fn test_is_within_click_distance() {

View file

@ -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 collections::HashMap;
use cosmic_text::{
Attrs, AttrsList, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures,
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 parking_lot::RwLock;
use pathfinder_geometry::{
rect::{RectF, RectI},
vector::{Vector2F, Vector2I},
};
use smallvec::SmallVec;
use std::{borrow::Cow, sync::Arc};
use swash::{
@ -58,7 +53,7 @@ struct LoadedFont {
impl CosmicTextSystem {
pub(crate) fn new() -> Self {
// todo(linux) make font loading non-blocking
let mut font_system = FontSystem::new();
let font_system = FontSystem::new();
Self(RwLock::new(CosmicTextSystemState {
font_system,
@ -227,7 +222,7 @@ impl CosmicTextSystemState {
features: &FontFeatures,
) -> Result<SmallVec<[FontId; 4]>> {
// 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
.font_system
@ -261,7 +256,7 @@ impl CosmicTextSystemState {
loaded_font_ids.push(font_id);
self.loaded_fonts.push(LoadedFont {
font,
features: features.try_into()?,
features: cosmic_font_features(features)?,
is_known_emoji_font: check_is_known_emoji_font(&postscript_name),
});
}
@ -324,7 +319,7 @@ impl CosmicTextSystemState {
) -> Result<swash::scale::image::Image> {
let loaded_font = &self.loaded_fonts[params.font_id.0];
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(
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);
line.layout_to_buffer(
&mut self.scratch,
font_size.0,
f32::from(font_size),
None, // We do our own wrapping
cosmic_text::Wrap::None,
None,
@ -484,93 +479,28 @@ impl CosmicTextSystemState {
}
}
impl TryFrom<&FontFeatures> for CosmicFontFeatures {
type Error = anyhow::Error;
fn cosmic_font_features(features: &FontFeatures) -> Result<CosmicFontFeatures> {
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 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);
let tag = cosmic_text::FeatureTag::new(&name_bytes);
result.set(tag, feature.1);
}
Ok(result)
result.set(tag, feature.1);
}
Ok(result)
}
impl From<RectF> for Bounds<f32> {
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 {
fn font_into_properties(font: &gpui::Font) -> font_kit::properties::Properties {
font_kit::properties::Properties {
style: match font.style {
crate::FontStyle::Normal => font_kit::properties::Style::Normal,
crate::FontStyle::Italic => font_kit::properties::Style::Italic,
crate::FontStyle::Oblique => font_kit::properties::Style::Oblique,
gpui::FontStyle::Normal => font_kit::properties::Style::Normal,
gpui::FontStyle::Italic => font_kit::properties::Style::Italic,
gpui::FontStyle::Oblique => font_kit::properties::Style::Oblique,
},
weight: font_kit::properties::Weight(font.weight.0),
stretch: Default::default(),

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
}
}
}

View file

@ -73,32 +73,29 @@ use super::{
window::{ImeInput, WaylandWindowStatePtr},
};
use crate::{
AnyWindowHandle, Bounds, Capslock, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId,
FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
PlatformInput, PlatformKeyboardLayout, Point, ResultExt as _, SCROLL_LINES, ScrollDelta,
ScrollWheelEvent, Size, TouchPhase, WindowParams, point, profiler, px, size,
};
use crate::{
SharedString,
platform::linux::{
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},
use crate::linux::{
DOUBLE_CLICK_INTERVAL, LinuxClient, LinuxCommon, LinuxKeyboardLayout, ResultExt as _,
SCROLL_LINES, capslock_from_xkb, cursor_style_to_icon_names, get_xkb_compose_state,
is_within_click_distance, keystroke_from_xkb, keystroke_underlying_dead_key,
modifiers_from_xkb, open_uri_internal, read_fd, reveal_path_internal,
wayland::{
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
cursor::Cursor,
serial::{SerialKind, SerialTracker},
to_shape,
window::WaylandWindow,
},
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
};
use crate::{
TaskTiming,
platform::{PlatformWindow, wgpu::WgpuContext},
use gpui::{
AnyWindowHandle, Bounds, Capslock, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
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
const MIN_KEYCODE: u32 = 8;
@ -303,7 +300,7 @@ impl WaylandClientStatePtr {
pub fn enable_ime(&self) {
let client = self.get_client();
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;
};
@ -313,10 +310,10 @@ impl WaylandClientStatePtr {
drop(state);
if let Some(area) = window.get_ime_area() {
text_input.set_cursor_rectangle(
area.origin.x.0 as i32,
area.origin.y.0 as i32,
area.size.width.0 as i32,
area.size.height.0 as i32,
f32::from(area.origin.x) as i32,
f32::from(area.origin.y) as i32,
f32::from(area.size.width) as i32,
f32::from(area.size.height) as i32,
);
}
state = client.borrow_mut();
@ -337,17 +334,17 @@ impl WaylandClientStatePtr {
pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {
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() {
return;
}
let text_input = state.text_input.as_ref().unwrap();
text_input.set_cursor_rectangle(
bounds.origin.x.0 as i32,
bounds.origin.y.0 as i32,
bounds.size.width.0 as i32,
bounds.size.height.0 as i32,
bounds.origin.x.as_f32() as i32,
bounds.origin.y.as_f32() as i32,
bounds.size.width.as_f32() as i32,
bounds.size.height.as_f32() as i32,
);
text_input.commit();
}
@ -382,7 +379,7 @@ impl WaylandClientStatePtr {
}
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 closed_window = state.windows.remove(surface_id).unwrap();
if let Some(window) = state.mouse_focused_window.take()
@ -456,8 +453,7 @@ impl WaylandClient {
pub(crate) fn new() -> Self {
let conn = Connection::connect_to_env().unwrap();
let (globals, mut event_queue) =
registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
let (globals, event_queue) = registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
let qh = event_queue.handle();
let mut seat: Option<wl_seat::WlSeat> = None;
@ -540,7 +536,7 @@ impl WaylandClient {
.as_ref()
.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
.insert_source(XDPEventSource::new(&common.background_executor), {
@ -572,7 +568,7 @@ impl WaylandClient {
})
.unwrap();
let mut state = Rc::new(RefCell::new(WaylandClientState {
let state = Rc::new(RefCell::new(WaylandClientState {
serial_tracker: SerialTracker::new(),
globals,
gpu_context,
@ -673,7 +669,7 @@ impl LinuxClient for WaylandClient {
.outputs
.iter()
.find_map(|(object_id, output)| {
(object_id.protocol_id() == id.0).then(|| {
(object_id.protocol_id() == u32::from(id)).then(|| {
Rc::new(WaylandDisplay {
id: object_id.clone(),
name: output.name.clone(),
@ -695,7 +691,7 @@ impl LinuxClient for WaylandClient {
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(
&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
// be tricky.
@ -754,7 +750,7 @@ impl LinuxClient for WaylandClient {
.expect("window is focused by pointer");
wl_pointer.set_cursor(serial, None, 0, 0);
} 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 {
// cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state
@ -762,9 +758,12 @@ impl LinuxClient for WaylandClient {
.clone()
.expect("window is focused by pointer");
let scale = focused_window.primary_output_scale();
state
.cursor
.set_icon(&wl_pointer, serial, style.to_icon_names(), scale);
state.cursor.set_icon(
&wl_pointer,
serial,
cursor_style_to_icon_names(style),
scale,
);
}
}
}
@ -826,7 +825,7 @@ impl LinuxClient for WaylandClient {
.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 (Some(primary_selection_manager), Some(primary_selection)) = (
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 (Some(data_device_manager), Some(data_device)) = (
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()
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
self.0.borrow_mut().clipboard.read()
}
@ -914,7 +913,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
_: &Connection,
qh: &QueueHandle<Self>,
) {
let mut client = this.get_client();
let client = this.get_client();
let mut state = client.borrow_mut();
match event {
@ -1002,7 +1001,7 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
}
pub(crate) fn get_window(
mut state: &mut RefMut<WaylandClientState>,
state: &mut RefMut<WaylandClientState>,
surface_id: &ObjectId,
) -> Option<WaylandWindowStatePtr> {
state.windows.get(surface_id).cloned()
@ -1017,7 +1016,7 @@ impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
_: &Connection,
_: &QueueHandle<Self>,
) {
let mut client = this.get_client();
let client = this.get_client();
let mut state = client.borrow_mut();
let Some(window) = get_window(&mut state, &surface.id()) else {
@ -1040,10 +1039,10 @@ impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
_: &Connection,
_: &QueueHandle<Self>,
) {
let mut client = this.get_client();
let client = this.get_client();
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;
};
@ -1257,7 +1256,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
_: &Connection,
_: &QueueHandle<Self>,
) {
let mut client = this.get_client();
let client = this.get_client();
let mut state = client.borrow_mut();
match event {
wl_keyboard::Event::RepeatInfo { rate, delay } => {
@ -1332,9 +1331,9 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
let old_layout =
keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
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();
state.capslock = Capslock::from_xkb(keymap_state);
state.capslock = capslock_from_xkb(keymap_state);
let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers: state.modifiers,
@ -1370,14 +1369,14 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
match key_state {
wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
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() {
compose.feed(keysym);
match compose.status() {
xkb::Status::Composing => {
keystroke.key_char = None;
state.pre_edit_text =
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
compose.utf8().or(keystroke_underlying_dead_key(keysym));
let pre_edit =
state.pre_edit_text.clone().unwrap_or(String::default());
drop(state);
@ -1394,7 +1393,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
}
xkb::Status::Cancelled => {
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();
drop(state);
if let Some(pre_edit) = pre_edit {
@ -1432,8 +1431,8 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
prefer_character_input: false,
});
move |event_timestamp, _metadata, this| {
let mut client = this.get_client();
let mut state = client.borrow_mut();
let client = this.get_client();
let state = client.borrow();
let is_repeating = id == state.repeat.current_id
&& state.repeat.current_keycode.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() => {
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) {
@ -1538,10 +1537,10 @@ impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
window.handle_ime(ImeInput::SetMarkedText(text));
if let Some(area) = window.get_ime_area() {
text_input.set_cursor_rectangle(
area.origin.x.0 as i32,
area.origin.y.0 as i32,
area.size.width.0 as i32,
area.size.height.0 as i32,
f32::from(area.origin.x) as i32,
f32::from(area.origin.y) as i32,
f32::from(area.size.width) as i32,
f32::from(area.size.height) as i32,
);
if last_serial == serial {
text_input.commit();
@ -1587,7 +1586,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
_: &Connection,
_: &QueueHandle<Self>,
) {
let mut client = this.get_client();
let client = this.get_client();
let mut state = client.borrow_mut();
match event {
@ -1616,12 +1615,15 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
.expect("window is focused by pointer");
wl_pointer.set_cursor(serial, None, 0, 0);
} 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 {
let scale = window.primary_output_scale();
state
.cursor
.set_icon(wl_pointer, serial, style.to_icon_names(), scale);
state.cursor.set_icon(
wl_pointer,
serial,
cursor_style_to_icon_names(style),
scale,
);
}
}
drop(state);
@ -1662,7 +1664,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
state.cursor_style = Some(default_style);
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 {
// cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state
@ -1673,7 +1675,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
state.cursor.set_icon(
&wl_pointer,
serial,
default_style.to_icon_names(),
cursor_style_to_icon_names(default_style),
scale,
);
}
@ -2043,7 +2045,7 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
let input = PlatformInput::FileDrop(FileDropEvent::Entered {
position,
paths: crate::ExternalPaths(paths),
paths: gpui::ExternalPaths(paths),
});
let client = this.get_client();
@ -2151,7 +2153,7 @@ impl Dispatch<wl_data_source::WlDataSource, ()> for WaylandClientStatePtr {
_: &QueueHandle<Self>,
) {
let client = this.get_client();
let mut state = client.borrow_mut();
let state = client.borrow_mut();
match event {
wl_data_source::Event::Send { mime_type, fd } => {
@ -2237,7 +2239,7 @@ impl Dispatch<zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, ()>
_: &QueueHandle<Self>,
) {
let client = this.get_client();
let mut state = client.borrow_mut();
let state = client.borrow_mut();
match event {
zwp_primary_selection_source_v1::Event::Send { mime_type, fd } => {

View file

@ -10,10 +10,8 @@ use strum::IntoEnumIterator;
use wayland_client::{Connection, protocol::wl_data_offer::WlDataOffer};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use crate::{
ClipboardEntry, ClipboardItem, Image, ImageFormat, WaylandClientStatePtr, hash,
platform::linux::platform::read_fd,
};
use crate::linux::{WaylandClientStatePtr, platform::read_fd};
use gpui::{ClipboardEntry, ClipboardItem, Image, ImageFormat, hash};
/// Text mime types that we'll offer to other programs.
pub(crate) const TEXT_MIME_TYPES: [&str; 3] =
@ -241,7 +239,7 @@ impl Clipboard {
calloop::Mode::Level,
),
move |_, file, _| {
let mut file = unsafe { file.get_mut() };
let file = unsafe { file.get_mut() };
loop {
match file.write(&bytes[written..]) {
Ok(n) if written + n == bytes.len() => {

View file

@ -1,5 +1,5 @@
use crate::Globals;
use crate::platform::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
use crate::linux::Globals;
use crate::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
use anyhow::{Context as _, anyhow};
use util::ResultExt;
@ -95,7 +95,7 @@ impl Cursor {
&mut self,
wl_pointer: &WlPointer,
serial_id: u32,
mut cursor_icon_names: &[&str],
cursor_icon_names: &[&str],
scale: i32,
) {
self.set_scaled_size(self.size * scale as u32);
@ -104,9 +104,9 @@ impl Cursor {
log::warn!("Wayland: Unable to load cursor themes");
return;
};
let mut theme = &mut loaded_theme.theme;
let theme = &mut loaded_theme.theme;
let mut buffer: &CursorImageBuffer;
let buffer: &CursorImageBuffer;
'outer: {
for cursor_icon_name in cursor_icon_names {
if let Some(cursor) = theme.get_cursor(cursor_icon_name) {

View file

@ -7,7 +7,7 @@ use anyhow::Context as _;
use uuid::Uuid;
use wayland_backend::client::ObjectId;
use crate::{Bounds, DisplayId, Pixels, PlatformDisplay};
use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay};
#[derive(Debug, Clone)]
pub(crate) struct WaylandDisplay {
@ -25,7 +25,7 @@ impl Hash for WaylandDisplay {
impl PlatformDisplay for WaylandDisplay {
fn id(&self) -> DisplayId {
DisplayId(self.id.protocol_id())
DisplayId::new(self.id.protocol_id())
}
fn uuid(&self) -> anyhow::Result<Uuid> {

View file

@ -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,
}
}

View file

@ -24,27 +24,22 @@ use wayland_protocols::{
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1;
use crate::{
AnyWindowHandle, Bounds, Decorations, DevicePixels, Globals, GpuSpecs, Modifiers, Output,
Pixels, PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
ResizeEdge, Size, Tiling, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams, get_window,
layer_shell::LayerShellNotSupportedError, px, size,
use crate::linux::wayland::{display::WaylandDisplay, serial::SerialKind};
use crate::linux::{Globals, Output, WaylandClientStatePtr, get_window};
use gpui::{
AnyWindowHandle, Bounds, Capslock, Decorations, DevicePixels, GpuSpecs, Modifiers, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, Scene, Size, Tiling,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls,
WindowDecorations, WindowKind, WindowParams, layer_shell::LayerShellNotSupportedError, px,
size,
};
use crate::{
Capslock,
platform::{
PlatformAtlas, PlatformInputHandler, PlatformWindow,
linux::wayland::{display::WaylandDisplay, serial::SerialKind},
wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig},
},
};
use crate::{WindowKind, scene::Scene};
use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
#[derive(Default)]
pub(crate) struct Callbacks {
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)>>,
hover_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
@ -144,34 +139,37 @@ impl WaylandSurfaceState {
let layer_surface = layer_shell.get_layer_surface(
&surface,
None,
options.layer.into(),
super::layer_shell::wayland_layer(options.layer),
options.namespace.clone(),
&globals.qh,
surface.id(),
);
let width = params.bounds.size.width.0;
let height = params.bounds.size.height.0;
let width = f32::from(params.bounds.size.width);
let height = f32::from(params.bounds.size.height);
layer_surface.set_size(width as u32, height as u32);
layer_surface.set_anchor(options.anchor.into());
layer_surface.set_keyboard_interactivity(options.keyboard_interactivity.into());
layer_surface.set_anchor(super::layer_shell::wayland_anchor(options.anchor));
layer_surface.set_keyboard_interactivity(
super::layer_shell::wayland_keyboard_interactivity(options.keyboard_interactivity),
);
if let Some(margin) = options.margin {
layer_surface.set_margin(
margin.0.0 as i32,
margin.1.0 as i32,
margin.2.0 as i32,
margin.3.0 as i32,
f32::from(margin.0) as i32,
f32::from(margin.1) as i32,
f32::from(margin.2) as i32,
f32::from(margin.3) as i32,
)
}
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 {
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 {
@ -208,7 +206,7 @@ impl WaylandSurfaceState {
};
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
@ -335,8 +333,8 @@ impl WaylandWindowState {
};
let config = WgpuSurfaceConfig {
size: Size {
width: DevicePixels(options.bounds.size.width.0 as i32),
height: DevicePixels(options.bounds.size.height.0 as i32),
width: DevicePixels(f32::from(options.bounds.size.width) as i32),
height: DevicePixels(f32::from(options.bounds.size.height) as i32),
},
transparent: true,
};
@ -618,7 +616,7 @@ impl WaylandWindowStatePtr {
state.inset(),
state.tiling,
)
.map(|v| v.0 as i32)
.map(|v| f32::from(v) as i32)
.map_size(|v| if v <= 0 { 1 } else { v });
state.surface_state.set_geometry(
@ -642,7 +640,7 @@ impl WaylandWindowStatePtr {
match mode {
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
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()
{
appearance_changed();
@ -651,7 +649,7 @@ impl WaylandWindowStatePtr {
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
self.state.borrow_mut().decorations = WindowDecorations::Client;
// Update background to be transparent
if let Some(mut appearance_changed) =
if let Some(appearance_changed) =
self.callbacks.borrow_mut().appearance_changed.as_mut()
{
appearance_changed();
@ -680,7 +678,7 @@ impl WaylandWindowStatePtr {
height,
states,
} => {
let mut size = if width == 0 || height == 0 {
let size = if width == 0 || height == 0 {
None
} else {
Some(size(px(width as f32), px(height as f32)))
@ -787,7 +785,7 @@ impl WaylandWindowStatePtr {
height,
serial,
} => {
let mut size = if width == 0 || height == 0 {
let size = if width == 0 || height == 0 {
None
} else {
Some(size(px(width as f32), px(height as f32)))
@ -933,7 +931,8 @@ impl WaylandWindowStatePtr {
{
let state = self.state.borrow();
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.tiling,
)
.map(|v| v.0 as i32)
.map(|v| f32::from(v) as i32)
.map_size(|v| if v <= 0 { 1 } else { v });
state.surface_state.set_geometry(
@ -1252,7 +1251,7 @@ impl PlatformWindow for WaylandWindow {
}
fn toggle_fullscreen(&self) {
let mut state = self.borrow();
let state = self.borrow();
if let Some(toplevel) = state.surface_state.toplevel() {
if !state.fullscreen {
toplevel.set_fullscreen(None);
@ -1270,7 +1269,7 @@ impl PlatformWindow for WaylandWindow {
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);
}
@ -1327,8 +1326,8 @@ impl PlatformWindow for WaylandWindow {
toplevel.show_window_menu(
&state.globals.seat,
serial,
position.x.0 as i32,
position.y.0 as i32,
f32::from(position.x) 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();
if let Some(toplevel) = state.surface_state.toplevel() {
toplevel.resize(
@ -1408,8 +1407,8 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
let opaque = !state.is_transparent();
state.renderer.update_transparency(!opaque);
let mut opaque_area = state.window_bounds.map(|v| v.0 as i32);
opaque_area.inset(state.inset().0 as i32);
let opaque_area = state.window_bounds.map(|v| f32::from(v) as i32);
opaque_area.inset(f32::from(state.inset()) as i32);
let region = state
.globals
@ -1454,7 +1453,11 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
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 {
match self {
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 {
match self {
ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,

Some files were not shown because too many files have changed in this diff Show more