Compare commits
2 Commits
branch-dif
...
accessibil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c4337b29 | ||
|
|
020f8169e3 |
186
Cargo.lock
generated
186
Cargo.lock
generated
@@ -2,6 +2,30 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "accesskit"
|
||||
version = "0.19.0"
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_consumer"
|
||||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_macos"
|
||||
version = "0.20.0"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"hashbrown 0.15.2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "activity_indicator"
|
||||
version = "0.1.0"
|
||||
@@ -2046,12 +2070,12 @@ dependencies = [
|
||||
"log",
|
||||
"mint",
|
||||
"naga",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit 0.3.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
"objc2-quartz-core",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-metal 0.3.1",
|
||||
"objc2-quartz-core 0.3.1",
|
||||
"objc2-ui-kit",
|
||||
"raw-window-handle",
|
||||
"slab",
|
||||
@@ -2117,13 +2141,22 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
||||
dependencies = [
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3898,6 +3931,10 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "current_commit_sha"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "cursor-icon"
|
||||
version = "1.1.0"
|
||||
@@ -4440,7 +4477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6156,6 +6193,8 @@ dependencies = [
|
||||
name = "gpui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_macos",
|
||||
"anyhow",
|
||||
"as-raw-xcb-connection",
|
||||
"ashpd",
|
||||
@@ -6179,6 +6218,7 @@ dependencies = [
|
||||
"core-video",
|
||||
"cosmic-text",
|
||||
"ctor",
|
||||
"current_commit_sha",
|
||||
"derive_more",
|
||||
"embed-resource",
|
||||
"env_logger 0.11.8",
|
||||
@@ -6200,8 +6240,8 @@ dependencies = [
|
||||
"naga",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"objc2",
|
||||
"objc2-metal",
|
||||
"objc2 0.6.1",
|
||||
"objc2-metal 0.3.1",
|
||||
"oo7",
|
||||
"open",
|
||||
"parking",
|
||||
@@ -9368,6 +9408,22 @@ dependencies = [
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
|
||||
dependencies = [
|
||||
"objc-sys",
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.1"
|
||||
@@ -9377,6 +9433,22 @@ dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.5.1",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.3.1"
|
||||
@@ -9384,10 +9456,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-quartz-core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9398,7 +9482,19 @@ checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-image"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9407,6 +9503,18 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.5.1",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.1"
|
||||
@@ -9414,10 +9522,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.3.1"
|
||||
@@ -9425,11 +9545,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2",
|
||||
"block2 0.6.1",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9439,10 +9572,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-metal 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9452,10 +9585,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-quartz-core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -18108,9 +18241,9 @@ dependencies = [
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-metal 0.3.1",
|
||||
"object",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
@@ -18554,6 +18687,7 @@ dependencies = [
|
||||
"command_palette",
|
||||
"component",
|
||||
"copilot",
|
||||
"current_commit_sha",
|
||||
"dap",
|
||||
"dap_adapters",
|
||||
"db",
|
||||
|
||||
@@ -34,6 +34,7 @@ members = [
|
||||
"crates/context_server",
|
||||
"crates/copilot",
|
||||
"crates/credentials_provider",
|
||||
"crates/current_commit_sha",
|
||||
"crates/dap",
|
||||
"crates/dap_adapters",
|
||||
"crates/db",
|
||||
@@ -232,6 +233,7 @@ cli = { path = "crates/cli" }
|
||||
client = { path = "crates/client" }
|
||||
clock = { path = "crates/clock" }
|
||||
collab = { path = "crates/collab" }
|
||||
current_commit_sha = { path = "crates/current_commit_sha" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
@@ -380,6 +382,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
accesskit = { path = "../accesskit/common" }
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||
any_vec = "0.14"
|
||||
|
||||
127
accesskit.md
Normal file
127
accesskit.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Adding AccessKit to your immediate mode UI framework
|
||||
|
||||
AccessKit is a cool rust project that provides a cross-platform library for interfacing with accessibility APIs.
|
||||
|
||||
It's a great project, but when setting out to add it to GPUI, Zed's UI framework, I had trouble figuring out how to even start.
|
||||
|
||||
So here's the tutorial I wish I had when I started:
|
||||
|
||||
## Step 0: Acquiring AccessKit
|
||||
|
||||
AccessKit is available on [crates.io](https://crates.io/crates/accesskit)
|
||||
|
||||
If you're using macOS, you'll also need to bundle your app.
|
||||
|
||||
## Step 1: Turning it On
|
||||
|
||||
AccessKit works by creating an "Adapter" object that does the work of communicating
|
||||
with the platform. This interface is in a seperate, platform-specific crate.
|
||||
At Zed, we're going to build our own accesskit-winit style general adapter, so we'll use
|
||||
it's API as a model for the rest of this document. But first, let's make the macOS adapter
|
||||
|
||||
First, you need to have some way of implementing `accesskit::ActionHandler`, that works for your
|
||||
framework. In gpui, we have a `MacWindowState` struct behind an `Arc<Mutex>` with a callback pointing
|
||||
up to the general puprose framework, so let's use `Arc::new_cyclic` to capture a pointer to that struct in
|
||||
the adapter:
|
||||
|
||||
```rs
|
||||
|
||||
struct MacWindow(Arc<Mutex<MacWindowState>>);
|
||||
|
||||
struct MacWindowState {
|
||||
//...
|
||||
accesskit_adapter: accesskit_macos::Adapter,
|
||||
accesskit_action_handler: Option<Box<dyn FnMut(ActionRequest)>>
|
||||
}
|
||||
|
||||
struct MacActionHandler(Weak<Mutex<MacWindowState>>);
|
||||
|
||||
impl accesskit::ActionHandler for MacActionHandler {
|
||||
fn do_action(&mut self, request: accesskit::ActionRequest) {
|
||||
if let Some(this) = self.0.upgrade() {
|
||||
let mut lock = this.lock();
|
||||
if let Some(mut callback) = lock.accesskit_action_handler_callback.take() {
|
||||
drop(lock);
|
||||
callback(request);
|
||||
this.lock().accesskit_action_handler_callback = Some(callback);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Later
|
||||
let window = MacWindow(Arc::new_cyclic(|weak| {
|
||||
Mutex::new(MacWindowState {
|
||||
//....
|
||||
accesskit_action_handler: None,
|
||||
acceskit_adapter: accesskit_macos::Adapter::new(
|
||||
native_view as *mut _,
|
||||
focus,
|
||||
MacActionHandler(weak.clone()),
|
||||
)
|
||||
})
|
||||
}))
|
||||
|
||||
// Later still
|
||||
|
||||
impl PlatformWindow for MacWindow {
|
||||
//.....
|
||||
fn on_accesskit_action(&self, callback: Box<dyn FnMut(accesskit::ActionRequest)>) {
|
||||
self.0.lock().accesskit_action_handler_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then we can handle it in the UI framework with just a little bit of wiring:
|
||||
|
||||
```rs
|
||||
// in gpui::Window::new(handle)
|
||||
let handle: AnyWindowHandle;
|
||||
let mut window = cx.platform.open_window(/*...*/);
|
||||
platform_window.on_accesskit_action({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move |action| {
|
||||
handle
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
window.dispatch_accessibility_action(action, cx)
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
QUESTIONS:
|
||||
- re: `update_view_focus_state` on macOS, is this for the window focus???
|
||||
|
||||
|
||||
|
||||
-----
|
||||
|
||||
- Nodes: these are the accesibility units
|
||||
- NodeID: Used for references (just a number)
|
||||
- Generate the tree, and then publish minimal updates to the tree as you render
|
||||
- use the `Node` struct to create and provide data
|
||||
- `Role` struct is important for Aria stuff
|
||||
- `TreeUpdate` consists of nodes that have changed (NodeId, Node)
|
||||
- `TreeUpdate.focus` -> The element that is currently focused
|
||||
- Winit specific: `Adapter` The thing that we need to create to interface with accessibility
|
||||
- These "adapters" are per-platform, and since we're not using winit, we need to pull in each individual
|
||||
adapter (e.g.https://crates.io/crates/accesskit_macos)
|
||||
- `update_if_active` is very important, RESEARCH THIS
|
||||
- Actually testing on macOS:
|
||||
- 1 use voice over to see how it reads
|
||||
- 2 use accessibility inspector to read the data structures
|
||||
- Only works if you've bundled
|
||||
- Core issue: Bidirectional communication based on AcccesKit NodeIds
|
||||
- Core issue: Need minimal acceskit tree updates, with stable node IDs
|
||||
- for GPUI, essentially use Winit's approach to abstracting AcceskitAdapters
|
||||
- OR look at how Glazier adopted AccessKit: https://github.com/linebender/glazier
|
||||
- GOAL: Get the winit simple example of accesskit working
|
||||
- Issue: AccessKit panics, rather than fails silently. This causes issues for things like dangling NodeIds
|
||||
- !!!
|
||||
|
||||
- KitTest
|
||||
-
|
||||
14
crates/current_commit_sha/Cargo.toml
Normal file
14
crates/current_commit_sha/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "current_commit_sha"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/current_commit_sha.rs"
|
||||
|
||||
[dependencies]
|
||||
29
crates/current_commit_sha/src/current_commit_sha.rs
Normal file
29
crates/current_commit_sha/src/current_commit_sha.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::process::Command;
|
||||
|
||||
pub fn generate_current_commit_sha(prefix: &'static str, repo_root: &str, print_warning: bool) {
|
||||
println!("cargo:rerun-if-changed={repo_root}/.git/logs/HEAD");
|
||||
if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
|
||||
if output.status.success() {
|
||||
let git_sha = String::from_utf8_lossy(&output.stdout);
|
||||
let git_sha = git_sha.trim();
|
||||
|
||||
println!("cargo:rustc-env={prefix}_COMMIT_SHA={git_sha}");
|
||||
|
||||
if print_warning {
|
||||
if let Ok(build_profile) = std::env::var("PROFILE") {
|
||||
if build_profile == "release" {
|
||||
// This is currently the best way to make `cargo build ...`'s build script
|
||||
// to print something to stdout without extra verbosity.
|
||||
println!(
|
||||
"cargo:warning=Info: using '{git_sha}' hash for {prefix}_COMMIT_SHA env var"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_commit_sha(prefix: &'static str) -> Option<String> {
|
||||
std::env::var(format!("{prefix}_COMMIT_SHA")).ok()
|
||||
}
|
||||
@@ -75,6 +75,7 @@ path = "src/gpui.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
accesskit.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-task = "4.7"
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
@@ -84,6 +85,7 @@ blade-util = { workspace = true, optional = true }
|
||||
bytemuck = { version = "1", optional = true }
|
||||
collections.workspace = true
|
||||
ctor.workspace = true
|
||||
current_commit_sha.workspace = true
|
||||
derive_more.workspace = true
|
||||
etagere = "0.2"
|
||||
futures.workspace = true
|
||||
@@ -143,6 +145,7 @@ objc2 = { version = "0.6", optional = true }
|
||||
objc2-metal = { version = "0.3", optional = true }
|
||||
#TODO: replace with "objc2"
|
||||
metal.workspace = true
|
||||
accesskit_macos = { path = "../../../accesskit/platforms/macos"}
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies]
|
||||
pathfinder_geometry = "0.5"
|
||||
@@ -244,6 +247,8 @@ naga.workspace = true
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
|
||||
naga.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
current_commit_sha.workspace = true
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
|
||||
@@ -12,6 +12,8 @@ fn main() {
|
||||
#[cfg(any(not(target_os = "macos"), feature = "macos-blade"))]
|
||||
check_wgsl_shaders();
|
||||
|
||||
current_commit_sha::generate_current_commit_sha("GPUI", "../../", false);
|
||||
|
||||
match target.as_deref() {
|
||||
Ok("macos") => {
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
60
crates/gpui/examples/accessibility.rs
Normal file
60
crates/gpui/examples/accessibility.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use gpui::{
|
||||
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
|
||||
rgb, size,
|
||||
};
|
||||
|
||||
struct HelloWorld {}
|
||||
|
||||
impl Render for HelloWorld {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.bg(rgb(0x505050))
|
||||
.size(px(500.0))
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
.border_1()
|
||||
.border_color(rgb(0x0000ff))
|
||||
.text_xl()
|
||||
.text_color(rgb(0xffffff))
|
||||
.child(format!("Hello, world!"))
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
div().child("Button label"), // .aria_role(accesskit::Role::Label),
|
||||
)
|
||||
.flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.id("button")
|
||||
.size_8()
|
||||
.bg(gpui::red())
|
||||
.border_1()
|
||||
.border_dashed()
|
||||
.rounded_md()
|
||||
.border_color(gpui::white())
|
||||
// .aria_role(accesskit::Role::Button)
|
||||
.on_click(|_, _, _| println!("button clicked")),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|_, cx| cx.new(|_| HelloWorld {}),
|
||||
)
|
||||
.unwrap();
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
100
crates/gpui/src/accessibility.rs
Normal file
100
crates/gpui/src/accessibility.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
// pub const NULL_NODE_ID: accesskit::NodeId = accesskit::NodeId(0);
|
||||
|
||||
// pub fn null_node() -> accesskit::Node {
|
||||
// let node = accesskit::Node::new(accesskit::Role::Window);
|
||||
// node
|
||||
// }
|
||||
|
||||
use std::mem;
|
||||
|
||||
use collections::HashMap;
|
||||
|
||||
use crate::{DispatchTree, EntityId, FocusId};
|
||||
|
||||
pub struct AccessibilityData {
|
||||
next_node_id: u64,
|
||||
entity_ids: HashMap<EntityId, accesskit::NodeId>,
|
||||
focus_ids: HashMap<FocusId, accesskit::NodeId>,
|
||||
last_nodes: HashMap<accesskit::NodeId, accesskit::Node>,
|
||||
current_nodes: HashMap<accesskit::NodeId, accesskit::Node>,
|
||||
node_stack: Vec<accesskit::NodeId>,
|
||||
}
|
||||
|
||||
impl AccessibilityData {
|
||||
pub fn new() -> Self {
|
||||
AccessibilityData {
|
||||
next_node_id: 0,
|
||||
entity_ids: HashMap::default(),
|
||||
last_nodes: HashMap::default(),
|
||||
current_nodes: HashMap::default(),
|
||||
focus_ids: HashMap::default(),
|
||||
node_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This API should work better,
|
||||
pub fn node_id_for_entity(&mut self, entity_id: EntityId) -> accesskit::NodeId {
|
||||
*self
|
||||
.entity_ids
|
||||
.entry(entity_id)
|
||||
.or_insert_with(|| accesskit::NodeId(util::post_inc(&mut self.next_node_id)))
|
||||
}
|
||||
|
||||
/// Get the nearest ancestor node ID for a given focus id
|
||||
pub fn nearest_node_id(
|
||||
&mut self,
|
||||
focus_id: FocusId,
|
||||
dispatch_tree: &DispatchTree,
|
||||
) -> Option<accesskit::NodeId> {
|
||||
let mut focus_path = dispatch_tree.focus_path(focus_id);
|
||||
focus_path.reverse();
|
||||
for focus_id in focus_path.into_iter() {
|
||||
if let Some(node_id) = self.focus_ids.get(&focus_id) {
|
||||
return Some(*node_id);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn push_node(&mut self, node_id: accesskit::NodeId) {
|
||||
debug_assert!(self.current_nodes.get(&node_id).is_some());
|
||||
self.node_stack.push(node_id);
|
||||
}
|
||||
|
||||
pub fn pop_node(&mut self) {
|
||||
if let Some(child_id) = self.node_stack.pop() {
|
||||
if let Some(parent_id) = self.node_stack.last_mut() {
|
||||
let parent_node = self
|
||||
.current_nodes
|
||||
.get_mut(parent_id)
|
||||
.expect("Nodes must be inserted before using");
|
||||
parent_node.push_child(child_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_node(&mut self, node_id: accesskit::NodeId, node: accesskit::Node) {
|
||||
self.current_nodes.insert(node_id, node);
|
||||
}
|
||||
|
||||
pub fn updated_nodes(&mut self) -> Vec<(accesskit::NodeId, accesskit::Node)> {
|
||||
debug_assert!(self.node_stack.is_empty());
|
||||
|
||||
let mut changed_ids = Vec::new();
|
||||
|
||||
for id in self.current_nodes.keys() {
|
||||
if let Some(node) = self.last_nodes.get(id) {
|
||||
if self.current_nodes[id] != *node {
|
||||
changed_ids.push((*id, self.current_nodes[id].clone()));
|
||||
}
|
||||
} else {
|
||||
changed_ids.push((*id, self.current_nodes[id].clone()));
|
||||
}
|
||||
}
|
||||
mem::swap(&mut self.current_nodes, &mut self.last_nodes);
|
||||
self.current_nodes.clear();
|
||||
|
||||
changed_ids
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use std::{
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
num::NonZeroU64,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc, Weak,
|
||||
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
|
||||
|
||||
@@ -117,6 +117,11 @@ impl<T: IntoElement> FluentBuilder for T {}
|
||||
/// An object that can be drawn to the screen. This is the trait that distinguishes "views" from
|
||||
/// other entities. Views are `Entity`'s which `impl Render` and drawn to the screen.
|
||||
pub trait Render: 'static + Sized {
|
||||
/// The accessibility node for this view, if nescessary.
|
||||
fn accessibility_node(&self, _window: &mut Window, _cx: &mut Context<Self>) -> accesskit::Node {
|
||||
accesskit::Node::new(accesskit::Role::GenericContainer)
|
||||
}
|
||||
|
||||
/// Render this view into an element tree.
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
mod action;
|
||||
mod app;
|
||||
|
||||
mod accessibility;
|
||||
mod arena;
|
||||
mod asset_cache;
|
||||
mod assets;
|
||||
|
||||
@@ -38,6 +38,7 @@ use crate::{
|
||||
ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SvgSize, Task, TaskLabel, Window,
|
||||
hash, point, px, size,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot;
|
||||
@@ -437,6 +438,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
||||
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_accesskit_action(&self, callback: Box<dyn FnMut(accesskit::ActionRequest)>);
|
||||
fn draw(&self, scene: &Scene);
|
||||
fn completed_frame(&self) {}
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
@@ -471,6 +473,9 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
|
||||
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>);
|
||||
|
||||
fn accesskit_active(&self) -> bool;
|
||||
fn accesskit_update(&self, tree: accesskit::TreeUpdate);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
None
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
|
||||
platform::PlatformInputHandler, point, px, size,
|
||||
};
|
||||
use accesskit::ActivationHandler;
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
appkit::{
|
||||
@@ -336,6 +337,7 @@ struct MacWindowState {
|
||||
native_view: NonNull<Object>,
|
||||
display_link: Option<DisplayLink>,
|
||||
renderer: renderer::Renderer,
|
||||
accesskit_adapter: accesskit_macos::Adapter,
|
||||
request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
|
||||
event_callback: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
|
||||
activate_callback: Option<Box<dyn FnMut(bool)>>,
|
||||
@@ -344,6 +346,7 @@ struct MacWindowState {
|
||||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||
close_callback: Option<Box<dyn FnOnce()>>,
|
||||
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
||||
accesskit_action_handler_callback: Option<Box<dyn FnMut(accesskit::ActionRequest)>>,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
last_key_equivalent: Option<KeyDownEvent>,
|
||||
synthetic_drag_counter: usize,
|
||||
@@ -499,6 +502,21 @@ impl MacWindowState {
|
||||
}
|
||||
}
|
||||
|
||||
struct MacActionHandler(Weak<Mutex<MacWindowState>>);
|
||||
|
||||
impl accesskit::ActionHandler for MacActionHandler {
|
||||
fn do_action(&mut self, request: accesskit::ActionRequest) {
|
||||
if let Some(this) = self.0.upgrade() {
|
||||
let mut lock = this.lock();
|
||||
if let Some(mut callback) = lock.accesskit_action_handler_callback.take() {
|
||||
drop(lock);
|
||||
callback(request);
|
||||
this.lock().accesskit_action_handler_callback = Some(callback);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MacWindowState {}
|
||||
|
||||
pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
|
||||
@@ -603,43 +621,51 @@ impl MacWindow {
|
||||
let native_view = NSView::init(native_view);
|
||||
assert!(!native_view.is_null());
|
||||
|
||||
let mut window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||
handle,
|
||||
executor,
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
display_link: None,
|
||||
renderer: renderer::new_renderer(
|
||||
renderer_context,
|
||||
native_window as *mut _,
|
||||
native_view as *mut _,
|
||||
bounds.size.map(|pixels| pixels.0),
|
||||
false,
|
||||
),
|
||||
request_frame_callback: None,
|
||||
event_callback: None,
|
||||
activate_callback: None,
|
||||
resize_callback: None,
|
||||
moved_callback: None,
|
||||
should_close_callback: None,
|
||||
close_callback: None,
|
||||
appearance_changed_callback: None,
|
||||
input_handler: None,
|
||||
last_key_equivalent: None,
|
||||
synthetic_drag_counter: 0,
|
||||
traffic_light_position: titlebar
|
||||
.as_ref()
|
||||
.and_then(|titlebar| titlebar.traffic_light_position),
|
||||
transparent_titlebar: titlebar
|
||||
.as_ref()
|
||||
.map_or(true, |titlebar| titlebar.appears_transparent),
|
||||
previous_modifiers_changed_event: None,
|
||||
keystroke_for_do_command: None,
|
||||
do_command_handled: None,
|
||||
external_files_dragged: false,
|
||||
first_mouse: false,
|
||||
fullscreen_restore_bounds: Bounds::default(),
|
||||
})));
|
||||
let mut window = Self(Arc::new_cyclic(|weak| {
|
||||
Mutex::new(MacWindowState {
|
||||
handle,
|
||||
executor,
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
display_link: None,
|
||||
renderer: renderer::new_renderer(
|
||||
renderer_context,
|
||||
native_window as *mut _,
|
||||
native_view as *mut _,
|
||||
bounds.size.map(|pixels| pixels.0),
|
||||
false,
|
||||
),
|
||||
accesskit_adapter: accesskit_macos::Adapter::new(
|
||||
native_view as *mut _,
|
||||
focus,
|
||||
MacActionHandler(weak.clone()),
|
||||
),
|
||||
request_frame_callback: None,
|
||||
event_callback: None,
|
||||
activate_callback: None,
|
||||
resize_callback: None,
|
||||
moved_callback: None,
|
||||
should_close_callback: None,
|
||||
close_callback: None,
|
||||
appearance_changed_callback: None,
|
||||
accesskit_action_handler_callback: None,
|
||||
input_handler: None,
|
||||
last_key_equivalent: None,
|
||||
synthetic_drag_counter: 0,
|
||||
traffic_light_position: titlebar
|
||||
.as_ref()
|
||||
.and_then(|titlebar| titlebar.traffic_light_position),
|
||||
transparent_titlebar: titlebar
|
||||
.as_ref()
|
||||
.map_or(true, |titlebar| titlebar.appears_transparent),
|
||||
previous_modifiers_changed_event: None,
|
||||
keystroke_for_do_command: None,
|
||||
do_command_handled: None,
|
||||
external_files_dragged: false,
|
||||
first_mouse: false,
|
||||
fullscreen_restore_bounds: Bounds::default(),
|
||||
})
|
||||
}));
|
||||
|
||||
(*native_window).set_ivar(
|
||||
WINDOW_STATE_IVAR,
|
||||
@@ -721,6 +747,8 @@ impl MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
window.activate_accessibility();
|
||||
|
||||
if focus && show {
|
||||
native_window.makeKeyAndOrderFront_(nil);
|
||||
} else if show {
|
||||
@@ -775,6 +803,19 @@ impl MacWindow {
|
||||
window_handles
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_accessibility(&self) {
|
||||
// We'll send the first tree update on the next frame draw
|
||||
struct NoActivation;
|
||||
impl ActivationHandler for NoActivation {
|
||||
fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let mut window = self.0.lock();
|
||||
window.accesskit_adapter.focus(&mut NoActivation);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MacWindow {
|
||||
@@ -1138,6 +1179,21 @@ impl PlatformWindow for MacWindow {
|
||||
self.0.lock().appearance_changed_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_accesskit_action(&self, callback: Box<dyn FnMut(accesskit::ActionRequest)>) {
|
||||
self.0.lock().accesskit_action_handler_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn accesskit_active(&self) -> bool {
|
||||
self.0.lock().accesskit_adapter.active()
|
||||
}
|
||||
|
||||
fn accesskit_update(&self, update: accesskit::TreeUpdate) {
|
||||
dbg!(&update);
|
||||
if let Some(events) = self.0.lock().accesskit_adapter.update(update) {
|
||||
events.raise();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &crate::Scene) {
|
||||
let mut this = self.0.lock();
|
||||
this.renderer.draw(scene);
|
||||
|
||||
@@ -254,6 +254,8 @@ impl PlatformWindow for TestWindow {
|
||||
|
||||
fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
|
||||
|
||||
fn on_accesskit_action(&self, _callback: Box<dyn FnMut(accesskit::ActionRequest)>) {}
|
||||
|
||||
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
|
||||
|
||||
fn draw(&self, _scene: &crate::Scene) {}
|
||||
@@ -284,6 +286,12 @@ impl PlatformWindow for TestWindow {
|
||||
fn gpu_specs(&self) -> Option<GpuSpecs> {
|
||||
None
|
||||
}
|
||||
|
||||
fn accesskit_active(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn accesskit_update(&self, _update: accesskit::TreeUpdate) {}
|
||||
}
|
||||
|
||||
pub(crate) struct TestAtlasState {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId,
|
||||
Entity, EntityId, GlobalElementId, IntoElement, LayoutId, PaintIndex, Pixels,
|
||||
PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, WeakEntity,
|
||||
PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, WeakEntity, element,
|
||||
};
|
||||
use crate::{Empty, Window};
|
||||
use anyhow::Result;
|
||||
@@ -26,7 +26,7 @@ struct ViewCacheKey {
|
||||
}
|
||||
|
||||
impl<V: Render> Element for Entity<V> {
|
||||
type RequestLayoutState = AnyElement;
|
||||
type RequestLayoutState = (AnyElement, accesskit::Node);
|
||||
type PrepaintState = ();
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
@@ -39,30 +39,39 @@ impl<V: Render> Element for Entity<V> {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut element = self.update(cx, |view, cx| view.render(window, cx).into_any_element());
|
||||
let layout_id = window.with_rendered_view(self.entity_id(), |window| {
|
||||
element.request_layout(window, cx)
|
||||
let node_id = window.accessibility.node_id_for_entity(self.entity_id);
|
||||
let (mut node, mut element) = self.update(cx, |view, cx| {
|
||||
let node = view.accessibility_node(window, cx);
|
||||
let element = view.render(window, cx).into_any_element();
|
||||
(node, element)
|
||||
});
|
||||
(layout_id, element)
|
||||
let layout_id = window.with_rendered_view(self.entity_id(), |window| {
|
||||
window.with_accessibility_node(node_id, node.clone(), |window| {
|
||||
element.request_layout(window, cx)
|
||||
})
|
||||
});
|
||||
(layout_id, (element, node))
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
(element, node): &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
window.set_view_id(self.entity_id());
|
||||
window.with_rendered_view(self.entity_id(), |window| element.prepaint(window, cx));
|
||||
window.with_rendered_view(self.entity_id(), |window| {
|
||||
element.prepaint(window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
(element, node): &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -75,6 +84,7 @@ impl<V: Render> Element for Entity<V> {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyView {
|
||||
entity: AnyEntity,
|
||||
accessibility_node: fn(&AnyView, &mut Window, &mut App) -> accesskit::Node,
|
||||
render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
|
||||
cached_style: Option<Rc<StyleRefinement>>,
|
||||
}
|
||||
@@ -84,6 +94,7 @@ impl<V: Render> From<Entity<V>> for AnyView {
|
||||
AnyView {
|
||||
entity: value.into_any(),
|
||||
render: any_view::render::<V>,
|
||||
accessibility_node: any_view::accessibility_node::<V>,
|
||||
cached_style: None,
|
||||
}
|
||||
}
|
||||
@@ -103,6 +114,7 @@ impl AnyView {
|
||||
AnyWeakView {
|
||||
entity: self.entity.downgrade(),
|
||||
render: self.render,
|
||||
accessibility_node: self.accessibility_node,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +126,7 @@ impl AnyView {
|
||||
Err(entity) => Err(Self {
|
||||
entity,
|
||||
render: self.render,
|
||||
accessibility_node: self.accessibility_node,
|
||||
cached_style: self.cached_style,
|
||||
}),
|
||||
}
|
||||
@@ -296,6 +309,7 @@ impl IntoElement for AnyView {
|
||||
pub struct AnyWeakView {
|
||||
entity: AnyWeakEntity,
|
||||
render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
|
||||
accessibility_node: fn(&AnyView, &mut Window, &mut App) -> accesskit::Node,
|
||||
}
|
||||
|
||||
impl AnyWeakView {
|
||||
@@ -305,6 +319,7 @@ impl AnyWeakView {
|
||||
Some(AnyView {
|
||||
entity,
|
||||
render: self.render,
|
||||
accessibility_node: self.accessibility_node,
|
||||
cached_style: None,
|
||||
})
|
||||
}
|
||||
@@ -315,6 +330,7 @@ impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
|
||||
AnyWeakView {
|
||||
entity: view.into(),
|
||||
render: any_view::render::<V>,
|
||||
accessibility_node: any_view::accessibility_node::<V>,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,6 +360,14 @@ mod any_view {
|
||||
let view = view.clone().downcast::<V>().unwrap();
|
||||
view.update(cx, |view, cx| view.render(window, cx).into_any_element())
|
||||
}
|
||||
pub(crate) fn accessibility_node<V: 'static + Render>(
|
||||
view: &AnyView,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> accesskit::Node {
|
||||
let view = view.clone().downcast::<V>().unwrap();
|
||||
view.update(cx, |view, cx| view.accessibility_node(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
/// A view that renders nothing
|
||||
|
||||
@@ -13,8 +13,9 @@ use crate::{
|
||||
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
||||
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
|
||||
point, prelude::*, px, size, transparent_black,
|
||||
accessibility::AccessibilityData, point, prelude::*, px, size, transparent_black,
|
||||
};
|
||||
use accesskit::Tree;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -31,7 +32,7 @@ use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
cmp,
|
||||
cmp, env,
|
||||
fmt::{Debug, Display},
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
@@ -648,6 +649,7 @@ pub struct Window {
|
||||
pub(crate) pending_input_observers: SubscriberSet<(), AnyObserver>,
|
||||
prompt: Option<RenderablePromptHandle>,
|
||||
pub(crate) client_inset: Option<Pixels>,
|
||||
pub(crate) accessibility: AccessibilityData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -877,6 +879,16 @@ impl Window {
|
||||
.unwrap_or(DispatchEventResult::default())
|
||||
})
|
||||
});
|
||||
platform_window.on_accesskit_action({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move |action| {
|
||||
handle
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
window.dispatch_accessibility_action(action, cx)
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(app_id) = app_id {
|
||||
platform_window.set_app_id(&app_id);
|
||||
@@ -935,6 +947,7 @@ impl Window {
|
||||
prompt: None,
|
||||
client_inset: None,
|
||||
image_cache_stack: Vec::new(),
|
||||
accessibility: AccessibilityData::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1619,11 +1632,45 @@ impl Window {
|
||||
debug_assert!(self.rendered_entity_stack.is_empty());
|
||||
self.record_entities_accessed(cx);
|
||||
self.reset_cursor_style(cx);
|
||||
|
||||
if self.platform_window.accesskit_active() {
|
||||
let update = self.accesskit_tree_diff();
|
||||
self.platform_window.accesskit_update(update);
|
||||
}
|
||||
|
||||
self.refreshing = false;
|
||||
self.invalidator.set_phase(DrawPhase::None);
|
||||
self.needs_present.set(true);
|
||||
}
|
||||
|
||||
fn accesskit_tree_diff(&mut self) -> accesskit::TreeUpdate {
|
||||
let root = self.root.clone().unwrap();
|
||||
let root_id = self.accessibility.node_id_for_entity(root.entity_id());
|
||||
|
||||
let focus_node_id = self
|
||||
.focus
|
||||
.as_ref()
|
||||
.and_then(|focus_id| {
|
||||
self.accessibility
|
||||
.nearest_node_id(*focus_id, &self.rendered_frame.dispatch_tree)
|
||||
})
|
||||
.unwrap_or(root_id);
|
||||
|
||||
let new_nodes = self.accessibility.updated_nodes();
|
||||
|
||||
accesskit::TreeUpdate {
|
||||
focus: focus_node_id,
|
||||
nodes: new_nodes,
|
||||
tree: Some(Tree {
|
||||
root: root_id,
|
||||
toolkit_name: Some("GPUI".to_string()),
|
||||
toolkit_version: current_commit_sha::current_commit_sha("GPUI")
|
||||
.or_else(|| env::var("CARGO_PKG_VERSION").ok())
|
||||
.or_else(|| Some("<unknown>".to_string())),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn record_entities_accessed(&mut self, cx: &mut App) {
|
||||
let mut entities_ref = cx.entities.accessed_entities.borrow_mut();
|
||||
let mut entities = mem::take(entities_ref.deref_mut());
|
||||
@@ -2877,10 +2924,13 @@ impl Window {
|
||||
pub(crate) fn with_rendered_view<R>(
|
||||
&mut self,
|
||||
id: EntityId,
|
||||
node: accesskit::Node,
|
||||
f: impl FnOnce(&mut Self) -> R,
|
||||
) -> R {
|
||||
self.rendered_entity_stack.push(id);
|
||||
let node_id = self.accessibility.node_id_for_entity(id);
|
||||
let result = f(self);
|
||||
self.accessibility.pop_node();
|
||||
self.rendered_entity_stack.pop();
|
||||
result
|
||||
}
|
||||
@@ -3597,6 +3647,28 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_accessibility_action(
|
||||
&mut self,
|
||||
action_request: accesskit::ActionRequest,
|
||||
_cx: &mut App,
|
||||
) {
|
||||
dbg!(action_request);
|
||||
}
|
||||
|
||||
/// Run the given closure with a given accessibility node.
|
||||
pub fn with_accessibility_node<R>(
|
||||
&mut self,
|
||||
node_id: accesskit::NodeId,
|
||||
node: accesskit::Node,
|
||||
f: impl FnOnce(&mut Window) -> R,
|
||||
) -> R {
|
||||
node.push_child(item);
|
||||
self.accessibility.push_node(node_id, node);
|
||||
let result = f(self);
|
||||
self.accessibility.pop_node();
|
||||
result
|
||||
}
|
||||
|
||||
/// Register the given handler to be invoked whenever the global of the given type
|
||||
/// is updated.
|
||||
pub fn observe_global<G: Global>(
|
||||
|
||||
@@ -154,6 +154,9 @@ winresource = "0.1"
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
ashpd.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
current_commit_sha.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
dap = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
|
||||
@@ -18,29 +16,11 @@ fn main() {
|
||||
}
|
||||
|
||||
// Populate git sha environment variable if git is available
|
||||
println!("cargo:rerun-if-changed=../../.git/logs/HEAD");
|
||||
current_commit_sha::generate_current_commit_sha("ZED", "../../", true);
|
||||
println!(
|
||||
"cargo:rustc-env=TARGET={}",
|
||||
std::env::var("TARGET").unwrap()
|
||||
);
|
||||
if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
|
||||
if output.status.success() {
|
||||
let git_sha = String::from_utf8_lossy(&output.stdout);
|
||||
let git_sha = git_sha.trim();
|
||||
|
||||
println!("cargo:rustc-env=ZED_COMMIT_SHA={git_sha}");
|
||||
|
||||
if let Ok(build_profile) = std::env::var("PROFILE") {
|
||||
if build_profile == "release" {
|
||||
// This is currently the best way to make `cargo build ...`'s build script
|
||||
// to print something to stdout without extra verbosity.
|
||||
println!(
|
||||
"cargo:warning=Info: using '{git_sha}' hash for ZED_COMMIT_SHA env var"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
|
||||
@@ -55,6 +55,8 @@ use zed::{
|
||||
open_paths_with_positions,
|
||||
};
|
||||
|
||||
use crate::zed::component_preview;
|
||||
|
||||
#[cfg(feature = "mimalloc")]
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
@@ -660,7 +662,7 @@ fn main() {
|
||||
|
||||
let app_state = app_state.clone();
|
||||
|
||||
crate::zed::component_preview::init(app_state.clone(), cx);
|
||||
component_preview::init(app_state.clone(), cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
while let Some(urls) = open_rx.next().await {
|
||||
@@ -1034,6 +1036,10 @@ struct Args {
|
||||
#[cfg(target_os = "windows")]
|
||||
#[arg(hide = true)]
|
||||
dock_action: Option<usize>,
|
||||
|
||||
#[arg(long)]
|
||||
#[arg(hide = true)]
|
||||
component_preview: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
13
tes.md
Normal file
13
tes.md
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
See Miro for current: https://miro.com/welcomeonboard/OXl3SFBwOUFManhuN25tV1dkU2E0UVgzYnVIcHRRVWZkT0RJY1JqMm1PRmVmSTU3MlcweTRsRG9tb0dHWml3dlh5NTN6aSthcVlMSW9ZSlcvZWlxUVQ1NkhveXdJUjRzaVBhanFqRld5MFhOd2Fjcmh5MTlqR1diRHRGbzhNSHl0R2lncW1vRmFBVnlLcVJzTmdFdlNRPT0hdjE=?share_link_id=220464759562
|
||||
|
||||
Monetization follow ups:
|
||||
- For new user flows:
|
||||
- Create transition from free to trial
|
||||
- Transition trial to free instead of "no plan"
|
||||
- Transition from cancel-pro to free instead of "no plan"
|
||||
- For existing users:
|
||||
- Move from "no plan" to free plan
|
||||
- UI in general:
|
||||
- "Subscribe to free" button should not exist, free plan is the default
|
||||
-
|
||||
Reference in New Issue
Block a user