Detailed changes
@@ -0,0 +1,64 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug executable 'zed'",
+ "cargo": {
+ "args": [
+ "build",
+ "--bin=zed",
+ "--package=zed"
+ ],
+ "filter": {
+ "name": "zed",
+ "kind": "bin"
+ }
+ },
+ "args": [],
+ "cwd": "${workspaceFolder}"
+ },
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug unit tests in executable 'zed'",
+ "cargo": {
+ "args": [
+ "test",
+ "--no-run",
+ "--bin=zed",
+ "--package=zed"
+ ],
+ "filter": {
+ "name": "zed",
+ "kind": "bin"
+ }
+ },
+ "args": [],
+ "cwd": "${workspaceFolder}"
+ },
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug unit tests in library 'gpui'",
+ "cargo": {
+ "args": [
+ "test",
+ "--no-run",
+ "--lib",
+ "--package=gpui"
+ ],
+ "filter": {
+ "name": "gpui",
+ "kind": "lib"
+ }
+ },
+ "args": [],
+ "cwd": "${workspaceFolder}"
+ }
+ ]
+}
@@ -9,6 +9,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
[[package]]
name = "anyhow"
version = "1.0.38"
@@ -44,7 +53,7 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
dependencies = [
- "async-task",
+ "async-task 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"concurrent-queue",
"fastrand",
"futures-lite",
@@ -126,12 +135,28 @@ version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
+[[package]]
+name = "async-task"
+version = "4.0.3"
+source = "git+https://github.com/zedit-io/async-task?rev=341b57d6de98cdfd7b418567b8de2022ca993a6e#341b57d6de98cdfd7b418567b8de2022ca993a6e"
+
[[package]]
name = "atomic-waker"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
[[package]]
name = "autocfg"
version = "1.0.1"
@@ -144,6 +169,29 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+[[package]]
+name = "bindgen"
+version = "0.57.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "clap",
+ "env_logger",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
[[package]]
name = "bitflags"
version = "1.2.1"
@@ -174,7 +222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
dependencies = [
"async-channel",
- "async-task",
+ "async-task 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"atomic-waker",
"fastrand",
"futures-lite",
@@ -193,6 +241,15 @@ version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+[[package]]
+name = "cexpr"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
+dependencies = [
+ "nom",
+]
+
[[package]]
name = "cfg-if"
version = "0.1.10"
@@ -218,6 +275,32 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "clang-sys"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f54d78e30b388d4815220c8dd03fea5656b6c6d32adb59e89061552a102f8da1"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
[[package]]
name = "cocoa"
version = "0.24.0"
@@ -329,6 +412,16 @@ dependencies = [
"loom",
]
+[[package]]
+name = "ctor"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19"
+dependencies = [
+ "quote",
+ "syn",
+]
+
[[package]]
name = "dirs"
version = "3.0.1"
@@ -349,6 +442,19 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "env_logger"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
[[package]]
name = "event-listener"
version = "2.5.1"
@@ -457,17 +563,27 @@ dependencies = [
"wasi 0.9.0+wasi-snapshot-preview1",
]
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
[[package]]
name = "gpui"
version = "0.1.0"
dependencies = [
"anyhow",
+ "async-task 4.0.3 (git+https://github.com/zedit-io/async-task?rev=341b57d6de98cdfd7b418567b8de2022ca993a6e)",
+ "bindgen",
"cocoa",
"core-foundation",
"core-text",
+ "ctor",
"foreign-types 0.5.0",
"log",
"metal",
+ "num_cpus",
"objc",
"pathfinder_color",
"pathfinder_geometry",
@@ -475,6 +591,21 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
[[package]]
name = "instant"
version = "0.1.9"
@@ -490,12 +621,28 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
[[package]]
name = "libc"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
+[[package]]
+name = "libloading"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi",
+]
+
[[package]]
name = "log"
version = "0.4.14"
@@ -555,6 +702,16 @@ dependencies = [
"socket2",
]
+[[package]]
+name = "nom"
+version = "5.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
+dependencies = [
+ "memchr",
+ "version_check",
+]
+
[[package]]
name = "num-integer"
version = "0.1.44"
@@ -574,6 +731,16 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
[[package]]
name = "objc"
version = "0.2.7"
@@ -633,6 +800,12 @@ dependencies = [
"rustc_version",
]
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
[[package]]
name = "pin-project-lite"
version = "0.2.4"
@@ -717,6 +890,12 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
[[package]]
name = "rustc_version"
version = "0.2.3"
@@ -747,6 +926,12 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+[[package]]
+name = "shlex"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
+
[[package]]
name = "signal-hook"
version = "0.3.6"
@@ -806,6 +991,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
[[package]]
name = "syn"
version = "1.0.60"
@@ -826,6 +1017,15 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
[[package]]
name = "thread_local"
version = "1.1.3"
@@ -856,6 +1056,12 @@ dependencies = [
"regex",
]
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
[[package]]
name = "unicode-xid"
version = "0.2.1"
@@ -868,6 +1074,18 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d"
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
[[package]]
name = "waker-fn"
version = "1.1.0"
@@ -895,6 +1113,15 @@ dependencies = [
"cc",
]
+[[package]]
+name = "which"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -5,11 +5,17 @@ name = "gpui"
version = "0.1.0"
[dependencies]
+async-task = {git = "https://github.com/zedit-io/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
+ctor = "0.1"
+num_cpus = "1.13"
pathfinder_color = "0.5"
pathfinder_geometry = "0.5"
smol = "1.2"
tree-sitter = "0.17"
+[build-dependencies]
+bindgen = "0.57"
+
[target.'cfg(target_os = "macos")'.dependencies]
anyhow = "1"
cocoa = "0.24"
@@ -0,0 +1,23 @@
+use std::{env, path::PathBuf};
+
+fn main() {
+ generate_dispatch_bindings();
+}
+
+fn generate_dispatch_bindings() {
+ println!("cargo:rustc-link-lib=framework=System");
+ println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
+
+ let bindings = bindgen::Builder::default()
+ .header("src/platform/mac/dispatch.h")
+ .whitelist_var("_dispatch_main_q")
+ .whitelist_function("dispatch_async_f")
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+ .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 bindings");
+}
@@ -0,0 +1,114 @@
+// #[cfg(not(test))]
+use anyhow::{anyhow, Result};
+use async_task::Runnable;
+use smol::prelude::*;
+use smol::{channel, Executor};
+use std::rc::Rc;
+use std::sync::Arc;
+use std::{marker::PhantomData, thread};
+
+use crate::platform;
+
+pub enum Foreground {
+ Platform {
+ dispatcher: Arc<dyn platform::Dispatcher>,
+ _not_send_or_sync: PhantomData<Rc<()>>,
+ },
+ Test(smol::LocalExecutor<'static>),
+}
+
+pub enum ForegroundTask<T> {
+ Platform(async_task::Task<T>),
+ Test(smol::Task<T>),
+}
+
+pub struct Background {
+ executor: Arc<smol::Executor<'static>>,
+ _stop: channel::Sender<()>,
+}
+
+pub type BackgroundTask<T> = smol::Task<T>;
+
+impl Foreground {
+ pub fn platform(dispatcher: Arc<dyn platform::Dispatcher>) -> Result<Self> {
+ if dispatcher.is_main_thread() {
+ Ok(Self::Platform {
+ dispatcher,
+ _not_send_or_sync: PhantomData,
+ })
+ } else {
+ Err(anyhow!("must be constructed on main thread"))
+ }
+ }
+
+ pub fn test() -> Self {
+ Self::Test(smol::LocalExecutor::new())
+ }
+
+ pub fn spawn<T: 'static>(
+ &self,
+ future: impl Future<Output = T> + 'static,
+ ) -> ForegroundTask<T> {
+ match self {
+ Self::Platform { dispatcher, .. } => {
+ let dispatcher = dispatcher.clone();
+ let schedule = move |runnable: Runnable| dispatcher.run_on_main_thread(runnable);
+ let (runnable, task) = async_task::spawn_local(future, schedule);
+ runnable.schedule();
+ ForegroundTask::Platform(task)
+ }
+ Self::Test(executor) => ForegroundTask::Test(executor.spawn(future)),
+ }
+ }
+
+ pub async fn run<T>(&self, future: impl Future<Output = T>) -> T {
+ match self {
+ Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"),
+ Self::Test(executor) => executor.run(future).await,
+ }
+ }
+}
+
+impl<T> ForegroundTask<T> {
+ pub fn detach(self) {
+ match self {
+ Self::Platform(task) => task.detach(),
+ Self::Test(task) => task.detach(),
+ }
+ }
+
+ pub async fn cancel(self) -> Option<T> {
+ match self {
+ Self::Platform(task) => task.cancel().await,
+ Self::Test(task) => task.cancel().await,
+ }
+ }
+}
+
+impl Background {
+ pub fn new() -> Self {
+ let executor = Arc::new(Executor::new());
+ let stop = channel::unbounded::<()>();
+
+ for i in 0..num_cpus::get() {
+ let executor = executor.clone();
+ let stop = stop.1.clone();
+ thread::Builder::new()
+ .name(format!("background-executor-{}", i))
+ .spawn(move || smol::block_on(executor.run(stop.recv())))
+ .unwrap();
+ }
+
+ Self {
+ executor,
+ _stop: stop.0,
+ }
+ }
+
+ pub fn spawn<T>(&self, future: impl Send + Future<Output = T> + 'static) -> BackgroundTask<T>
+ where
+ T: 'static + Send,
+ {
+ self.executor.spawn(future)
+ }
+}
@@ -1,3 +1,4 @@
+pub mod executor;
pub mod keymap;
pub mod platform;
@@ -1,176 +1,39 @@
-use super::Event;
-pub use cocoa::foundation::NSSize;
-use cocoa::{
- base::{id, nil},
- foundation::{NSArray, NSAutoreleasePool, NSString},
-};
-use objc::{
- class,
- declare::ClassDecl,
- msg_send,
- runtime::{Class, Object, Sel},
- sel, sel_impl,
-};
-use std::{
- ffi::CStr,
- os::raw::{c_char, c_void},
- path::PathBuf,
-};
+use super::{BoolExt as _, Dispatcher, Window};
+use crate::{executor, platform};
+use anyhow::Result;
+use cocoa::base::id;
+use objc::{class, msg_send, sel, sel_impl};
+use std::{rc::Rc, sync::Arc};
-#[derive(Default)]
pub struct App {
- finish_launching_callback: Option<Box<dyn FnOnce()>>,
- become_active_callback: Option<Box<dyn FnMut()>>,
- resign_active_callback: Option<Box<dyn FnMut()>>,
- event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
- open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
+ dispatcher: Arc<Dispatcher>,
}
-const RUST_WRAPPER_IVAR_NAME: &'static str = "rustWrapper";
-
-impl super::App for App {
- fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
- self.finish_launching_callback = Some(Box::new(callback));
- self
- }
-
- fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
- self.become_active_callback = Some(Box::new(callback));
- self
- }
-
- fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
- self.resign_active_callback = Some(Box::new(callback));
- self
- }
-
- fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
- self.event_callback = Some(Box::new(callback));
- self
- }
-
- fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
- self.open_files_callback = Some(Box::new(callback));
- self
- }
-
- fn run(self) {
- unsafe {
- let self_ptr = Box::into_raw(Box::new(self));
-
- let pool = NSAutoreleasePool::new(nil);
- let app: id = msg_send![build_app_class(), sharedApplication];
- (*app).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
- let app_delegate: id = msg_send![build_app_delegate_class(), new];
- (*app_delegate).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
- let _: () = msg_send![app, setDelegate: app_delegate];
- let _: () = msg_send![app, run];
- let _: () = msg_send![pool, drain];
-
- // App is done running when we get here, so we can reinstantiate the Box and drop it.
- Box::from_raw(self_ptr);
+impl App {
+ pub fn new() -> Self {
+ Self {
+ dispatcher: Arc::new(Dispatcher),
}
}
}
-fn build_app_class() -> *const Class {
- unsafe {
- let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
- decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
- decl.add_method(
- sel!(sendEvent:),
- send_event as extern "C" fn(&Object, Sel, id),
- );
- decl.register()
- }
-}
-
-fn build_app_delegate_class() -> *const Class {
- unsafe {
- let superclass = class!(NSResponder);
- let mut decl = ClassDecl::new("GPUIApplicationDelegate", superclass).unwrap();
- decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
- decl.add_method(
- sel!(applicationDidFinishLaunching:),
- did_finish_launching as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(applicationDidBecomeActive:),
- did_become_active as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(applicationDidResignActive:),
- did_resign_active as extern "C" fn(&Object, Sel, id),
- );
- decl.add_method(
- sel!(application:openFiles:),
- open_files as extern "C" fn(&Object, Sel, id, id),
- );
- decl.register()
+impl platform::App for App {
+ fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
+ self.dispatcher.clone()
}
-}
-
-unsafe fn get_app(object: &Object) -> &mut App {
- let wrapper_ptr: *mut c_void = *object.get_ivar(RUST_WRAPPER_IVAR_NAME);
- &mut *(wrapper_ptr as *mut App)
-}
-extern "C" fn send_event(this: &Object, _sel: Sel, native_event: id) {
- let event = unsafe { Event::from_native(native_event, None) };
-
- if let Some(event) = event {
- let app = unsafe { get_app(this) };
- if let Some(callback) = app.event_callback.as_mut() {
- if callback(event) {
- return;
- }
+ fn activate(&self, ignoring_other_apps: bool) {
+ unsafe {
+ let app: id = msg_send![class!(NSApplication), sharedApplication];
+ let _: () = msg_send![app, activateIgnoringOtherApps: ignoring_other_apps.to_objc()];
}
}
- unsafe {
- let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
- }
-}
-
-extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
- let app = unsafe { get_app(this) };
- if let Some(callback) = app.finish_launching_callback.take() {
- callback();
- }
-}
-
-extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
- let app = unsafe { get_app(this) };
- if let Some(callback) = app.become_active_callback.as_mut() {
- callback();
- }
-}
-
-extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
- let app = unsafe { get_app(this) };
- if let Some(callback) = app.resign_active_callback.as_mut() {
- callback();
- }
-}
-
-extern "C" fn open_files(this: &Object, _: Sel, _: id, paths: id) {
- let paths = unsafe {
- (0..paths.count())
- .into_iter()
- .filter_map(|i| {
- let path = paths.objectAtIndex(i);
- match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
- Ok(string) => Some(PathBuf::from(string)),
- Err(err) => {
- log::error!("error converting path to string: {}", err);
- None
- }
- }
- })
- .collect::<Vec<_>>()
- };
- let app = unsafe { get_app(this) };
- if let Some(callback) = app.open_files_callback.as_mut() {
- callback(paths);
+ fn open_window(
+ &self,
+ options: platform::WindowOptions,
+ executor: Rc<executor::Foreground>,
+ ) -> Result<Rc<dyn platform::Window>> {
+ Ok(Rc::new(Window::open(options, executor)?))
}
}
@@ -0,0 +1 @@
+#include <dispatch/dispatch.h>
@@ -0,0 +1,43 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use async_task::Runnable;
+use objc::{
+ class, msg_send,
+ runtime::{BOOL, YES},
+ sel, sel_impl,
+};
+use std::ffi::c_void;
+
+use crate::platform;
+
+include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
+
+pub fn dispatch_get_main_queue() -> dispatch_queue_t {
+ unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
+}
+
+pub struct Dispatcher;
+
+impl platform::Dispatcher for Dispatcher {
+ fn is_main_thread(&self) -> bool {
+ let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
+ is_main_thread == YES
+ }
+
+ fn run_on_main_thread(&self, runnable: Runnable) {
+ unsafe {
+ dispatch_async_f(
+ dispatch_get_main_queue(),
+ runnable.into_raw() as *mut c_void,
+ Some(trampoline),
+ );
+ }
+
+ extern "C" fn trampoline(runnable: *mut c_void) {
+ let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
+ task.run();
+ }
+ }
+}
@@ -1,5 +1,4 @@
-use super::Event;
-use crate::{geometry::vector::vec2f, keymap::Keystroke};
+use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event};
use cocoa::appkit::{
NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY,
NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY,
@@ -0,0 +1,26 @@
+use cocoa::foundation::{NSPoint, NSRect, NSSize};
+use pathfinder_geometry::{rect::RectF, vector::Vector2F};
+pub trait Vector2FExt {
+ fn to_ns_point(&self) -> NSPoint;
+ fn to_ns_size(&self) -> NSSize;
+}
+
+pub trait RectFExt {
+ fn to_ns_rect(&self) -> NSRect;
+}
+
+impl Vector2FExt for Vector2F {
+ fn to_ns_point(&self) -> NSPoint {
+ NSPoint::new(self.x() as f64, self.y() as f64)
+ }
+
+ fn to_ns_size(&self) -> NSSize {
+ NSSize::new(self.x() as f64, self.y() as f64)
+ }
+}
+
+impl RectFExt for RectF {
+ fn to_ns_rect(&self) -> NSRect {
+ NSRect::new(self.origin().to_ns_point(), self.size().to_ns_size())
+ }
+}
@@ -1,8 +1,34 @@
-use super::*;
-
mod app;
+mod dispatcher;
mod event;
+mod geometry;
+mod runner;
+mod window;
+
+use crate::platform;
+pub use app::App;
+use cocoa::base::{BOOL, NO, YES};
+pub use dispatcher::Dispatcher;
+pub use runner::Runner;
+use window::Window;
+
+pub fn app() -> impl platform::App {
+ App::new()
+}
+
+pub fn runner() -> impl platform::Runner {
+ Runner::new()
+}
+trait BoolExt {
+ fn to_objc(self) -> BOOL;
+}
-pub fn app() -> impl App {
- app::App::default()
+impl BoolExt for bool {
+ fn to_objc(self) -> BOOL {
+ if self {
+ YES
+ } else {
+ NO
+ }
+ }
}
@@ -0,0 +1,188 @@
+use crate::platform::Event;
+use cocoa::{
+ appkit::NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
+ base::{id, nil},
+ foundation::{NSArray, NSAutoreleasePool, NSString},
+};
+use ctor::ctor;
+use objc::{
+ class,
+ declare::ClassDecl,
+ msg_send,
+ runtime::{Class, Object, Sel},
+ sel, sel_impl,
+};
+use std::{
+ ffi::CStr,
+ os::raw::{c_char, c_void},
+ path::PathBuf,
+ ptr,
+};
+
+const RUNNER_IVAR: &'static str = "runner";
+static mut APP_CLASS: *const Class = ptr::null();
+static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
+
+#[ctor]
+unsafe fn build_classes() {
+ APP_CLASS = {
+ let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
+ decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
+ decl.add_method(
+ sel!(sendEvent:),
+ send_event as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.register()
+ };
+
+ APP_DELEGATE_CLASS = {
+ let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
+ decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
+ decl.add_method(
+ sel!(applicationDidFinishLaunching:),
+ did_finish_launching as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(applicationDidBecomeActive:),
+ did_become_active as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(applicationDidResignActive:),
+ did_resign_active as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(application:openFiles:),
+ open_files as extern "C" fn(&mut Object, Sel, id, id),
+ );
+ decl.register()
+ }
+}
+
+#[derive(Default)]
+pub struct Runner {
+ finish_launching_callback: Option<Box<dyn FnOnce()>>,
+ become_active_callback: Option<Box<dyn FnMut()>>,
+ resign_active_callback: Option<Box<dyn FnMut()>>,
+ event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+ open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
+}
+
+impl Runner {
+ pub fn new() -> Self {
+ Default::default()
+ }
+}
+
+impl crate::platform::Runner for Runner {
+ fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
+ self.finish_launching_callback = Some(Box::new(callback));
+ self
+ }
+
+ fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
+ log::info!("become active");
+ self.become_active_callback = Some(Box::new(callback));
+ self
+ }
+
+ fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
+ self.resign_active_callback = Some(Box::new(callback));
+ self
+ }
+
+ fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
+ self.event_callback = Some(Box::new(callback));
+ self
+ }
+
+ fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
+ self.open_files_callback = Some(Box::new(callback));
+ self
+ }
+
+ fn run(self) {
+ unsafe {
+ let self_ptr = Box::into_raw(Box::new(self));
+
+ let pool = NSAutoreleasePool::new(nil);
+ let app: id = msg_send![APP_CLASS, sharedApplication];
+ let _: () = msg_send![
+ app,
+ setActivationPolicy: NSApplicationActivationPolicyRegular
+ ];
+ (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
+ let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
+ (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
+ let _: () = msg_send![app, setDelegate: app_delegate];
+ let _: () = msg_send![app, run];
+ let _: () = msg_send![pool, drain];
+ // The Runner is done running when we get here, so we can reinstantiate the Box and drop it.
+ Box::from_raw(self_ptr);
+ }
+ }
+}
+
+unsafe fn get_runner(object: &mut Object) -> &mut Runner {
+ let runner_ptr: *mut c_void = *object.get_ivar(RUNNER_IVAR);
+ &mut *(runner_ptr as *mut Runner)
+}
+
+extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
+ let event = unsafe { Event::from_native(native_event, None) };
+
+ if let Some(event) = event {
+ let runner = unsafe { get_runner(this) };
+ if let Some(callback) = runner.event_callback.as_mut() {
+ if callback(event) {
+ return;
+ }
+ }
+ }
+
+ unsafe {
+ let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
+ }
+}
+
+extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
+ let runner = unsafe { get_runner(this) };
+ if let Some(callback) = runner.finish_launching_callback.take() {
+ callback();
+ }
+}
+
+extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
+ let runner = unsafe { get_runner(this) };
+ if let Some(callback) = runner.become_active_callback.as_mut() {
+ callback();
+ }
+}
+
+extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
+ let runner = unsafe { get_runner(this) };
+ if let Some(callback) = runner.resign_active_callback.as_mut() {
+ callback();
+ }
+}
+
+extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
+ let paths = unsafe {
+ (0..paths.count())
+ .into_iter()
+ .filter_map(|i| {
+ let path = paths.objectAtIndex(i);
+ match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
+ Ok(string) => Some(PathBuf::from(string)),
+ Err(err) => {
+ log::error!("error converting path to string: {}", err);
+ None
+ }
+ }
+ })
+ .collect::<Vec<_>>()
+ };
+ let runner = unsafe { get_runner(this) };
+ if let Some(callback) = runner.open_files_callback.as_mut() {
+ callback(paths);
+ }
+}
@@ -0,0 +1,353 @@
+use crate::{
+ executor,
+ geometry::vector::Vector2F,
+ platform::{self, Event},
+};
+use anyhow::{anyhow, Result};
+use cocoa::{
+ appkit::{
+ NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable,
+ NSWindow, NSWindowStyleMask,
+ },
+ base::{id, nil},
+ foundation::{NSAutoreleasePool, NSSize, NSString},
+};
+use ctor::ctor;
+use objc::{
+ class,
+ declare::ClassDecl,
+ msg_send,
+ runtime::{Class, Object, Sel, BOOL, NO, YES},
+ sel, sel_impl,
+};
+use smol::Timer;
+use std::{
+ cell::{Cell, RefCell},
+ ffi::c_void,
+ mem, ptr,
+ rc::Rc,
+ time::{Duration, Instant},
+};
+
+use super::geometry::RectFExt;
+
+const WINDOW_STATE_IVAR: &'static str = "windowState";
+
+static mut WINDOW_CLASS: *const Class = ptr::null();
+static mut VIEW_CLASS: *const Class = ptr::null();
+static mut DELEGATE_CLASS: *const Class = ptr::null();
+
+#[ctor]
+unsafe fn build_classes() {
+ WINDOW_CLASS = {
+ let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).unwrap();
+ decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+ decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
+ decl.add_method(
+ sel!(canBecomeMainWindow),
+ yes as extern "C" fn(&Object, Sel) -> BOOL,
+ );
+ decl.add_method(
+ sel!(canBecomeKeyWindow),
+ yes as extern "C" fn(&Object, Sel) -> BOOL,
+ );
+ decl.add_method(
+ sel!(sendEvent:),
+ send_event as extern "C" fn(&Object, Sel, id),
+ );
+ decl.register()
+ };
+
+ VIEW_CLASS = {
+ let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
+ decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+ decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
+ decl.add_method(
+ sel!(keyDown:),
+ handle_view_event as extern "C" fn(&Object, Sel, id),
+ );
+ 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!(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.register()
+ };
+
+ DELEGATE_CLASS = {
+ let mut decl = ClassDecl::new("GPUIWindowDelegate", class!(NSObject)).unwrap();
+ decl.add_method(
+ sel!(dealloc),
+ dealloc_delegate as extern "C" fn(&Object, Sel),
+ );
+ decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+ decl.add_method(
+ sel!(windowDidResize:),
+ window_did_resize as extern "C" fn(&Object, Sel, id),
+ );
+ decl.register()
+ };
+}
+
+pub struct Window(Rc<WindowState>);
+
+struct WindowState {
+ native_window: id,
+ event_callback: RefCell<Option<Box<dyn FnMut(Event) -> bool>>>,
+ resize_callback: RefCell<Option<Box<dyn FnMut(NSSize, f64)>>>,
+ synthetic_drag_counter: Cell<usize>,
+ executor: Rc<executor::Foreground>,
+}
+
+impl Window {
+ pub fn open(
+ options: platform::WindowOptions,
+ executor: Rc<executor::Foreground>,
+ ) -> Result<Self> {
+ unsafe {
+ let pool = NSAutoreleasePool::new(nil);
+
+ let frame = options.bounds.to_ns_rect();
+ let style_mask = NSWindowStyleMask::NSClosableWindowMask
+ | NSWindowStyleMask::NSMiniaturizableWindowMask
+ | NSWindowStyleMask::NSResizableWindowMask
+ | NSWindowStyleMask::NSTitledWindowMask;
+
+ let native_window: id = msg_send![WINDOW_CLASS, alloc];
+ let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
+ frame,
+ style_mask,
+ NSBackingStoreBuffered,
+ NO,
+ );
+
+ if native_window == nil {
+ return Err(anyhow!("window returned nil from initializer"));
+ }
+
+ let delegate: id = msg_send![DELEGATE_CLASS, alloc];
+ let delegate = delegate.init();
+ if native_window == nil {
+ return Err(anyhow!("delegate returned nil from initializer"));
+ }
+ native_window.setDelegate_(delegate);
+
+ let native_view: id = msg_send![VIEW_CLASS, alloc];
+ let native_view = NSView::init(native_view);
+ if native_view == nil {
+ return Err(anyhow!("view return nil from initializer"));
+ }
+
+ let window = Self(Rc::new(WindowState {
+ native_window,
+ event_callback: RefCell::new(None),
+ resize_callback: RefCell::new(None),
+ synthetic_drag_counter: Cell::new(0),
+ executor,
+ }));
+
+ (*native_window).set_ivar(
+ WINDOW_STATE_IVAR,
+ Rc::into_raw(window.0.clone()) as *const c_void,
+ );
+ (*native_view).set_ivar(
+ WINDOW_STATE_IVAR,
+ Rc::into_raw(window.0.clone()) as *const c_void,
+ );
+ (*delegate).set_ivar(
+ WINDOW_STATE_IVAR,
+ Rc::into_raw(window.0.clone()) as *const c_void,
+ );
+
+ if let Some(title) = options.title.as_ref() {
+ native_window.setTitle_(NSString::alloc(nil).init_str(title));
+ }
+ native_window.setAcceptsMouseMovedEvents_(YES);
+
+ native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
+ native_view.setWantsBestResolutionOpenGLSurface_(YES);
+
+ // From winit crate: On Mojave, views automatically become layer-backed shortly after
+ // being added to a native_window. Changing the layer-backedness of a view breaks the
+ // association between the view and its associated OpenGL context. To work around this,
+ // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
+ // itself and break the association with its context.
+ native_view.setWantsLayer(YES);
+
+ native_view.layer().setBackgroundColor_(
+ msg_send![class!(NSColor), colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0],
+ );
+
+ native_window.setContentView_(native_view.autorelease());
+ native_window.makeFirstResponder_(native_view);
+
+ native_window.center();
+ native_window.makeKeyAndOrderFront_(nil);
+
+ pool.drain();
+
+ Ok(window)
+ }
+ }
+
+ pub fn zoom(&self) {
+ unsafe {
+ self.0.native_window.performZoom_(nil);
+ }
+ }
+
+ pub fn size(&self) -> NSSize {
+ self.0.size()
+ }
+
+ pub fn backing_scale_factor(&self) -> f64 {
+ self.0.backing_scale_factor()
+ }
+
+ pub fn on_event<F: 'static + FnMut(Event) -> bool>(&mut self, callback: F) {
+ *self.0.event_callback.borrow_mut() = Some(Box::new(callback));
+ }
+
+ pub fn on_resize<F: 'static + FnMut(NSSize, f64)>(&mut self, callback: F) {
+ *self.0.resize_callback.borrow_mut() = Some(Box::new(callback));
+ }
+}
+
+impl Drop for Window {
+ fn drop(&mut self) {
+ unsafe {
+ self.0.native_window.close();
+ let _: () = msg_send![self.0.native_window.delegate(), release];
+ }
+ }
+}
+
+impl platform::Window for Window {}
+
+impl WindowState {
+ fn size(&self) -> NSSize {
+ let view_frame = unsafe { NSView::frame(self.native_window.contentView()) };
+ view_frame.size
+ }
+
+ fn backing_scale_factor(&self) -> f64 {
+ unsafe {
+ let screen: id = msg_send![self.native_window, screen];
+ NSScreen::backingScaleFactor(screen)
+ }
+ }
+
+ fn next_synthetic_drag_id(&self) -> usize {
+ let next_id = self.synthetic_drag_counter.get() + 1;
+ self.synthetic_drag_counter.set(next_id);
+ next_id
+ }
+}
+
+unsafe fn window_state(object: &Object) -> Rc<WindowState> {
+ let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+ let rc1 = Rc::from_raw(raw as *mut WindowState);
+ let rc2 = rc1.clone();
+ mem::forget(rc1);
+ rc2
+}
+
+unsafe fn drop_window_state(object: &Object) {
+ let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+ Rc::from_raw(raw as *mut WindowState);
+}
+
+extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
+ YES
+}
+
+extern "C" fn dealloc_window(this: &Object, _: Sel) {
+ unsafe {
+ drop_window_state(this);
+ let () = msg_send![super(this, class!(NSWindow)), dealloc];
+ }
+}
+
+extern "C" fn dealloc_view(this: &Object, _: Sel) {
+ unsafe {
+ drop_window_state(this);
+ let () = msg_send![super(this, class!(NSView)), dealloc];
+ }
+}
+
+extern "C" fn dealloc_delegate(this: &Object, _: Sel) {
+ unsafe {
+ let raw: *mut c_void = *this.get_ivar(WINDOW_STATE_IVAR);
+ Rc::from_raw(raw as *mut WindowState);
+ let () = msg_send![super(this, class!(NSObject)), dealloc];
+ }
+}
+
+extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
+ let window = unsafe { window_state(this) };
+
+ let event = unsafe { Event::from_native(native_event, Some(window.size().height as f32)) };
+
+ if let Some(event) = event {
+ match event {
+ Event::LeftMouseDragged { position } => schedule_synthetic_drag(&window, position),
+ Event::LeftMouseUp { .. } => {
+ window.next_synthetic_drag_id();
+ }
+ _ => {}
+ }
+
+ if let Some(callback) = window.event_callback.borrow_mut().as_mut() {
+ if callback(event) {
+ return;
+ }
+ }
+ }
+}
+
+extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
+ unsafe {
+ let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
+ }
+}
+
+extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
+ let window = unsafe { window_state(this) };
+ let size = window.size();
+ let scale_factor = window.backing_scale_factor();
+ if let Some(callback) = window.resize_callback.borrow_mut().as_mut() {
+ callback(size, scale_factor);
+ }
+ drop(window);
+}
+
+fn schedule_synthetic_drag(window_state: &Rc<WindowState>, position: Vector2F) {
+ let drag_id = window_state.next_synthetic_drag_id();
+ let weak_window_state = Rc::downgrade(window_state);
+ let instant = Instant::now() + Duration::from_millis(16);
+ window_state
+ .executor
+ .spawn(async move {
+ Timer::at(instant).await;
+ if let Some(window_state) = weak_window_state.upgrade() {
+ if window_state.synthetic_drag_counter.get() == drag_id {
+ if let Some(callback) = window_state.event_callback.borrow_mut().as_mut() {
+ schedule_synthetic_drag(&window_state, position);
+ callback(Event::LeftMouseDragged { position });
+ }
+ }
+ }
+ })
+ .detach();
+}
@@ -6,11 +6,13 @@ pub mod current {
pub use super::mac::*;
}
-use std::path::PathBuf;
-
+use crate::{executor, geometry::rect::RectF};
+use anyhow::Result;
+use async_task::Runnable;
use event::Event;
+use std::{path::PathBuf, rc::Rc, sync::Arc};
-pub trait App {
+pub trait Runner {
fn on_finish_launching<F: 'static + FnOnce()>(self, callback: F) -> Self where;
fn on_become_active<F: 'static + FnMut()>(self, callback: F) -> Self;
fn on_resign_active<F: 'static + FnMut()>(self, callback: F) -> Self;
@@ -18,3 +20,25 @@ pub trait App {
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
fn run(self);
}
+
+pub trait App {
+ fn dispatcher(&self) -> Arc<dyn Dispatcher>;
+ fn activate(&self, ignoring_other_apps: bool);
+ fn open_window(
+ &self,
+ options: WindowOptions,
+ executor: Rc<executor::Foreground>,
+ ) -> Result<Rc<dyn Window>>;
+}
+
+pub trait Dispatcher: Send + Sync {
+ fn is_main_thread(&self) -> bool;
+ fn run_on_main_thread(&self, task: Runnable);
+}
+
+pub trait Window {}
+
+pub struct WindowOptions<'a> {
+ pub bounds: RectF,
+ pub title: Option<&'a str>,
+}
@@ -1,14 +1,41 @@
-use std::fs;
-
use fs::OpenOptions;
-use gpui::platform::{current as platform, App as _};
+use gpui::{
+ executor,
+ geometry::{rect::RectF, vector::vec2f},
+ platform::{current as platform, App as _, Runner as _, WindowOptions},
+};
use log::LevelFilter;
use simplelog::SimpleLogger;
+use std::{fs, mem, rc::Rc, sync::Arc};
fn main() {
init_logger();
- platform::app()
- .on_finish_launching(|| log::info!("finish launching"))
+
+ let platform = Arc::new(platform::app());
+
+ let foreground = Rc::new(
+ executor::Foreground::platform(platform.dispatcher())
+ .expect("could not foreground create executor"),
+ );
+
+ platform::runner()
+ .on_finish_launching(move || {
+ log::info!("finish launching");
+ if stdout_is_a_pty() {
+ platform.activate(true);
+ }
+ let window = platform
+ .open_window(
+ WindowOptions {
+ bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)),
+ title: Some("Zed"),
+ },
+ foreground,
+ )
+ .expect("error opening window");
+
+ mem::forget(window); // Leak window for now so it doesn't close
+ })
.run();
}