diff --git a/Cargo.lock b/Cargo.lock index 16dd7a31b..1af7ced01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -467,6 +467,36 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.9.0", + "block", + "cocoa-foundation", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.9.0", + "block", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "libc", + "objc", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -541,6 +571,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -554,8 +594,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", "foreign-types", "libc", ] @@ -567,7 +620,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", "libc", ] @@ -1554,7 +1618,7 @@ checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ "bitflags 2.9.0", "block", - "core-graphics-types", + "core-graphics-types 0.1.3", "foreign-types", "log", "objc", @@ -2142,6 +2206,20 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "planeshift" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.0", + "block", + "cocoa", + "core-graphics 0.24.0", + "euclid", + "image", + "objc", + "winit", +] + [[package]] name = "png" version = "0.17.16" @@ -3761,7 +3839,7 @@ dependencies = [ "block", "bytemuck", "cfg_aliases", - "core-graphics-types", + "core-graphics-types 0.1.3", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -4109,8 +4187,8 @@ dependencies = [ "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "cursor-icon", "dpi", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 9636b3fb8..aeadc8297 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ members = [ "sparse_strips/vello_hybrid/examples/webgl", "sparse_strips/vello_hybrid/examples/winit", "sparse_strips/vello_toy", + + "planeshift", ] [workspace.package] diff --git a/planeshift/Cargo.toml b/planeshift/Cargo.toml new file mode 100644 index 000000000..9a58be96b --- /dev/null +++ b/planeshift/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "planeshift" +version = "0.1.0" +description = "TODO" +categories = ["gui"] +keywords = ["windowing", "compositor"] +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +[package.metadata.docs.rs] +all-features = true +# There are no platform specific docs. +default-target = "x86_64-unknown-linux-gnu" +targets = [] + +[features] +default = ["enable-winit"] +enable-winit = ["dep:winit"] + +[lints] +workspace = true + +[dependencies] +bitflags = "2.9.0" +euclid = "0.22.11" +image = { workspace = true } +winit = { workspace = true, optional = true } + +[target.'cfg(target_os = "macos")'.dependencies] +block = "0.1" +cocoa = "0.26.0" +core-graphics = "0.24.0" +objc = "0.2" diff --git a/planeshift/README.md b/planeshift/README.md new file mode 100644 index 000000000..63114b386 --- /dev/null +++ b/planeshift/README.md @@ -0,0 +1,49 @@ +
+ +# Planeshift + +[![Apache 2.0 or MIT license.](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](#license) +\ +[![Linebender Zulip chat.](https://img.shields.io/badge/Linebender-%23vello-blue?logo=Zulip)](https://xi.zulipchat.com/#narrow/channel/197075-vello) +[![GitHub Actions CI status.](https://img.shields.io/github/actions/workflow/status/linebender/vello/ci.yml?logo=github&label=CI)](https://github.com/linebender/vello/actions) + +
+ +## Minimum supported Rust Version (MSRV) + +This version of Planeshift has been verified to compile with **Rust 1.85** and later. + +Future versions of Planeshift might increase the Rust version requirement. +It will not be treated as a breaking change and as such can even happen with small patch releases. + +
+Click here if compiling fails. + +As time has passed, some of Planeshift's dependencies could have released versions with a higher Rust requirement. +If you encounter a compilation issue due to a dependency and don't want to upgrade your Rust toolchain, then you could downgrade the dependency. + +```sh +# Use the problematic dependency's name and version +cargo update -p package_name --precise 0.1.1 +``` + +
+ +## Community + +Discussion of Planeshift development happens in the [Linebender Zulip](https://xi.zulipchat.com/), specifically the [#vello stream](https://xi.zulipchat.com/#narrow/channel/197075-vello). +All public content can be read without logging in. + +Contributions are welcome by pull request. +The [Rust code of conduct] applies. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +[Rust code of conduct]: https://www.rust-lang.org/policies/code-of-conduct diff --git a/planeshift/src/backend.rs b/planeshift/src/backend.rs new file mode 100644 index 000000000..b1323da28 --- /dev/null +++ b/planeshift/src/backend.rs @@ -0,0 +1,105 @@ +// Copyright 2018 the Vello Authors and The Pathfinder Project Developers +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use image::RgbaImage; + +#[cfg(feature = "enable-winit")] +use winit::window::Window; + +use crate::Rect; +use crate::{Connection, ConnectionError, LayerContainerInfo}; +use crate::{LayerGeometryInfo, LayerId, LayerMap, LayerSurfaceInfo, LayerTreeInfo, Promise}; + +// Backend definition + +pub trait Backend: Sized { + type NativeConnection; + type Host; + + // Constructor + fn new(connection: Connection) -> Result; + + // Transactions + fn begin_transaction(&self); + fn end_transaction( + &mut self, + promise: &Promise<()>, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + surface_component: &LayerMap, + ); + + // Layer creation and destruction + fn add_container_layer(&mut self, new_layer: LayerId); + fn add_surface_layer(&mut self, new_layer: LayerId); + fn delete_layer(&mut self, layer: LayerId); + + // Layer tree management + fn insert_before( + &mut self, + parent: LayerId, + new_child: LayerId, + reference: Option, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ); + fn remove_from_superlayer( + &mut self, + layer: LayerId, + parent: LayerId, + tree_component: &LayerMap, + geometry_component: &LayerMap, + ); + + // Native hosting + unsafe fn host_layer( + &mut self, + layer: LayerId, + host: Self::Host, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ); + fn unhost_layer(&mut self, layer: LayerId); + + // Geometry + fn set_layer_bounds( + &mut self, + layer: LayerId, + old_bounds: &Rect, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ); + + // Miscellaneous layer flags + fn set_layer_surface_options( + &mut self, + layer: LayerId, + surface_component: &LayerMap, + ); + + // Screenshots + fn screenshot_hosted_layer( + &mut self, + layer: LayerId, + transaction_promise: &Promise<()>, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + surface_component: &LayerMap, + ) -> Promise; + + // `winit` integration + #[cfg(feature = "enable-winit")] + fn host_layer_in_window( + &mut self, + layer: LayerId, + window: &Window, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) -> Result<(), ()>; +} diff --git a/planeshift/src/backends/alternate.rs b/planeshift/src/backends/alternate.rs new file mode 100644 index 000000000..b4dca02c6 --- /dev/null +++ b/planeshift/src/backends/alternate.rs @@ -0,0 +1,317 @@ +// Copyright 2018 the Vello Authors and The Pathfinder Project Developers +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! A backend that tries one backend, and if it fails, tries the other. +//! +//! If backend A fails to initialize, then it tries to initialize backend B. Note that more than +//! two backends can be chained together by making backend A or backend B itself a `Chain`. + +use image::RgbaImage; + +#[cfg(feature = "enable-winit")] +use winit::window::Window; + +use crate::Rect; +use crate::{Connection, ConnectionError, LayerContainerInfo}; +use crate::{LayerGeometryInfo, LayerId, LayerMap, LayerSurfaceInfo, LayerTreeInfo, Promise}; + +pub enum Backend +where + A: crate::Backend, + B: crate::Backend, +{ + A(A), + B(B), +} + +impl crate::Backend for Backend +where + A: crate::Backend, + B: crate::Backend, +{ + type NativeConnection = NativeConnection; + type Host = Host; + + // Constructor + + fn new(connection: Connection) -> Result { + match connection { + Connection::Native(NativeConnection::A(native_connection)) => { + Ok(Backend::A(A::new(Connection::Native(native_connection))?)) + } + Connection::Native(NativeConnection::B(native_connection)) => { + Ok(Backend::B(B::new(Connection::Native(native_connection))?)) + } + } + } + + // Transactions + + fn begin_transaction(&self) { + match *self { + Backend::A(ref this) => this.begin_transaction(), + Backend::B(ref this) => this.begin_transaction(), + } + } + + fn end_transaction( + &mut self, + promise: &Promise<()>, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + surface_component: &LayerMap, + ) { + match *self { + Backend::A(ref mut this) => this.end_transaction( + promise, + tree_component, + container_component, + geometry_component, + surface_component, + ), + Backend::B(ref mut this) => this.end_transaction( + promise, + tree_component, + container_component, + geometry_component, + surface_component, + ), + } + } + + // Layer creation and destruction + + fn add_container_layer(&mut self, new_layer: LayerId) { + match *self { + Backend::A(ref mut this) => this.add_container_layer(new_layer), + Backend::B(ref mut this) => this.add_container_layer(new_layer), + } + } + + fn add_surface_layer(&mut self, new_layer: LayerId) { + match *self { + Backend::A(ref mut this) => this.add_surface_layer(new_layer), + Backend::B(ref mut this) => this.add_surface_layer(new_layer), + } + } + + fn delete_layer(&mut self, layer: LayerId) { + match *self { + Backend::A(ref mut this) => this.delete_layer(layer), + Backend::B(ref mut this) => this.delete_layer(layer), + } + } + + // Layer tree management + + fn insert_before( + &mut self, + parent: LayerId, + new_child: LayerId, + reference: Option, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) { + match *self { + Backend::A(ref mut this) => this.insert_before( + parent, + new_child, + reference, + tree_component, + container_component, + geometry_component, + ), + Backend::B(ref mut this) => this.insert_before( + parent, + new_child, + reference, + tree_component, + container_component, + geometry_component, + ), + } + } + + fn remove_from_superlayer( + &mut self, + layer: LayerId, + parent: LayerId, + tree_component: &LayerMap, + geometry_component: &LayerMap, + ) { + match *self { + Backend::A(ref mut this) => { + this.remove_from_superlayer(layer, parent, tree_component, geometry_component) + } + Backend::B(ref mut this) => { + this.remove_from_superlayer(layer, parent, tree_component, geometry_component) + } + } + } + + // Native hosting + + unsafe fn host_layer( + &mut self, + layer: LayerId, + host: Self::Host, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) { + match *self { + Backend::A(ref mut this) => match host { + Host::A(host) => unsafe { + this.host_layer( + layer, + host, + tree_component, + container_component, + geometry_component, + ) + }, + Host::B(_) => panic!("host_layer(): mismatched backend and host"), + }, + Backend::B(ref mut this) => match host { + Host::B(host) => unsafe { + this.host_layer( + layer, + host, + tree_component, + container_component, + geometry_component, + ) + }, + Host::A(_) => panic!("host_layer(): mismatched backend and host"), + }, + } + } + + fn unhost_layer(&mut self, layer: LayerId) { + match *self { + Backend::A(ref mut this) => this.unhost_layer(layer), + Backend::B(ref mut this) => this.unhost_layer(layer), + } + } + + // Geometry + + fn set_layer_bounds( + &mut self, + layer: LayerId, + old_bounds: &Rect, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) { + match *self { + Backend::A(ref mut this) => this.set_layer_bounds( + layer, + old_bounds, + tree_component, + container_component, + geometry_component, + ), + Backend::B(ref mut this) => this.set_layer_bounds( + layer, + old_bounds, + tree_component, + container_component, + geometry_component, + ), + } + } + + // Miscellaneous layer flags + + fn set_layer_surface_options( + &mut self, + layer: LayerId, + surface_component: &LayerMap, + ) { + match *self { + Backend::A(ref mut this) => this.set_layer_surface_options(layer, surface_component), + Backend::B(ref mut this) => this.set_layer_surface_options(layer, surface_component), + } + } + + // Screenshots + + fn screenshot_hosted_layer( + &mut self, + layer: LayerId, + transaction_promise: &Promise<()>, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + surface_component: &LayerMap, + ) -> Promise { + match *self { + Backend::A(ref mut this) => this.screenshot_hosted_layer( + layer, + transaction_promise, + tree_component, + container_component, + geometry_component, + surface_component, + ), + Backend::B(ref mut this) => this.screenshot_hosted_layer( + layer, + transaction_promise, + tree_component, + container_component, + geometry_component, + surface_component, + ), + } + } + + // `winit` integration + + #[cfg(feature = "enable-winit")] + fn host_layer_in_window( + &mut self, + layer: LayerId, + window: &Window, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) -> Result<(), ()> { + match *self { + Backend::A(ref mut this) => this.host_layer_in_window( + layer, + window, + tree_component, + container_component, + geometry_component, + ), + Backend::B(ref mut this) => this.host_layer_in_window( + layer, + window, + tree_component, + container_component, + geometry_component, + ), + } + } +} + +pub enum NativeConnection +where + A: crate::Backend, + B: crate::Backend, +{ + A(A::NativeConnection), + B(B::NativeConnection), +} + +pub enum Host +where + A: crate::Backend, + B: crate::Backend, +{ + A(A::Host), + B(B::Host), +} diff --git a/planeshift/src/backends/core_animation.rs b/planeshift/src/backends/core_animation.rs new file mode 100644 index 000000000..87cf49ff5 --- /dev/null +++ b/planeshift/src/backends/core_animation.rs @@ -0,0 +1,404 @@ +// Copyright 2018 the Vello Authors and The Pathfinder Project Developers +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Core Animation native system implementation. + +use block::ConcreteBlock; +use cocoa::base::{NO, YES, id, nil}; +use cocoa::foundation::{NSPoint, NSRect, NSSize}; +use cocoa::quartzcore::{CALayer, transaction}; +use core_graphics::base::CGFloat; +use core_graphics::geometry::{CG_ZERO_POINT, CGPoint, CGRect, CGSize}; +use core_graphics::window::{self, CGWindowID, kCGWindowImageBestResolution}; +use core_graphics::window::{kCGWindowImageBoundsIgnoreFraming, kCGWindowListOptionAll}; +use image::RgbaImage; +use objc::{msg_send, sel, sel_impl}; +use std::sync::Mutex; + +#[cfg(feature = "enable-winit")] +use winit::{ + raw_window_handle::{HasWindowHandle, RawWindowHandle}, + window::Window, +}; + +use crate::{Connection, ConnectionError, LayerContainerInfo}; +use crate::{LayerGeometryInfo, LayerId, LayerMap, LayerParent, LayerSurfaceInfo, LayerTreeInfo}; +use crate::{Promise, Rect, SurfaceOptions}; + +pub struct Backend { + native_component: LayerMap, +} + +impl crate::Backend for Backend { + type NativeConnection = (); + type Host = id; + + fn new(_connection: Connection) -> Result { + Ok(Backend { + native_component: LayerMap::new(), + }) + } + + fn begin_transaction(&self) { + transaction::begin(); + + // Disable implicit animations. + transaction::set_disable_actions(true); + } + + fn end_transaction( + &mut self, + promise: &Promise<()>, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + ) { + let promise = Mutex::new(Some((*promise).clone())); + transaction::set_completion_block(ConcreteBlock::new(move || { + (*promise.lock().unwrap()).take().unwrap().resolve(()) + })); + + transaction::commit(); + } + + fn add_container_layer(&mut self, new_layer: LayerId) { + let layer = CALayer::new(); + layer.set_anchor_point(&CG_ZERO_POINT); + + self.native_component.add( + new_layer, + NativeInfo { + host: nil, + core_animation_layer: layer, + }, + ); + } + + fn add_surface_layer(&mut self, new_layer: LayerId) { + self.add_container_layer(new_layer); + } + + fn delete_layer(&mut self, layer: LayerId) { + self.native_component.remove_if_present(layer); + } + + fn insert_before( + &mut self, + parent: LayerId, + new_child: LayerId, + reference: Option, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) { + let parent = &self.native_component[parent].core_animation_layer; + let new_core_animation_child = &self.native_component[new_child].core_animation_layer; + match reference { + None => parent.add_sublayer(new_core_animation_child), + Some(reference) => { + let reference = &self.native_component[reference].core_animation_layer; + parent.insert_sublayer_below(new_core_animation_child, reference); + } + } + + self.update_layer_subtree_bounds( + new_child, + tree_component, + container_component, + geometry_component, + ); + } + + fn remove_from_superlayer( + &mut self, + layer: LayerId, + _: LayerId, + _: &LayerMap, + _: &LayerMap, + ) { + self.native_component[layer] + .core_animation_layer + .remove_from_superlayer() + } + + // Increases the reference count of `hosting_view`. + unsafe fn host_layer( + &mut self, + layer: LayerId, + host: Self::Host, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) { + let native_component = &mut self.native_component[layer]; + debug_assert_eq!(native_component.host, nil); + + let core_animation_layer = &native_component.core_animation_layer; + let _: id = msg_send![host, retain]; + let _: () = msg_send![host, setLayer:core_animation_layer.id()]; + let _: () = msg_send![host, setWantsLayer:YES]; + + native_component.host = host; + + self.update_layer_subtree_bounds( + layer, + tree_component, + container_component, + geometry_component, + ); + } + + fn unhost_layer(&mut self, layer: LayerId) { + let native_component = &mut self.native_component[layer]; + debug_assert_ne!(native_component.host, nil); + + unsafe { + let _: () = msg_send![native_component.host, setWantsLayer:NO]; + let _: () = msg_send![native_component.host, setLayer:nil]; + let _: id = msg_send![native_component.host, release]; + } + + native_component.host = nil; + } + + fn set_layer_bounds( + &mut self, + layer: LayerId, + _: &Rect, + tree_component: &LayerMap, + _: &LayerMap, + geometry_component: &LayerMap, + ) { + self.update_layer_bounds(layer, tree_component, geometry_component); + } + + fn set_layer_surface_options( + &mut self, + layer: LayerId, + surface_component: &LayerMap, + ) { + let surface_options = surface_component[layer].options; + + let core_animation_layer = &mut self.native_component[layer].core_animation_layer; + let opaque = surface_options.contains(SurfaceOptions::OPAQUE); + core_animation_layer.set_opaque(opaque); + core_animation_layer.set_contents_opaque(opaque); + } + + // Screenshots + + fn screenshot_hosted_layer( + &mut self, + layer: LayerId, + transaction_promise: &Promise<()>, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + ) -> Promise { + let result_promise = Promise::new(); + let result_promise_to_return = result_promise.clone(); + + let hosting_view = self.native_component[layer].host as usize; + transaction_promise.then(Box::new(move |()| { + let hosting_view: id = hosting_view as id; + let image; + unsafe { + let view_bounds: NSRect = msg_send![hosting_view, bounds]; + let mut view_frame: NSRect = + msg_send![hosting_view, convertRect:view_bounds toView:nil]; + + let window: id = msg_send![hosting_view, window]; + let window_id: CGWindowID = msg_send![window, windowNumber]; + + let window_frame: NSRect = msg_send![window, frame]; + view_frame.origin.x += window_frame.origin.x; + view_frame.origin.y += window_frame.origin.y; + + let screen: id = msg_send![window, screen]; + let screen_frame: NSRect = msg_send![screen, frame]; + let screen_rect = CGRect::new( + &CGPoint::new( + view_frame.origin.x, + screen_frame.size.height - view_frame.origin.y - view_frame.size.height, + ), + &CGSize::new(view_frame.size.width, view_frame.size.height), + ); + + image = window::create_image( + screen_rect, + kCGWindowListOptionAll, + window_id, + kCGWindowImageBoundsIgnoreFraming | kCGWindowImageBestResolution, + ) + .unwrap(); + } + + let (width, height) = ( + u32::try_from(image.width()).unwrap(), + u32::try_from(image.height()).unwrap(), + ); + let mut data = image.data().bytes().to_vec(); + data.chunks_mut(4).for_each(|pixel| pixel.swap(0, 2)); + result_promise.resolve(RgbaImage::from_vec(width, height, data).unwrap()); + })); + + result_promise_to_return + } + + // `winit` integration + + #[cfg(feature = "enable-winit")] + fn host_layer_in_window( + &mut self, + layer: LayerId, + window: &Window, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) -> Result<(), ()> { + let nsview = match window.window_handle().unwrap().as_raw() { + RawWindowHandle::AppKit(handle) => handle.ns_view.cast().as_ptr(), + _ => panic!("Unsupported platform."), + }; + unsafe { + self.host_layer( + layer, + nsview, + tree_component, + container_component, + geometry_component, + ); + } + Ok(()) + } +} + +impl Backend { + fn hosting_view(&self, layer: LayerId, tree_component: &LayerMap) -> Option { + match tree_component.get(layer) { + None => None, + Some(LayerTreeInfo { + parent: LayerParent::Layer(parent_layer), + .. + }) => self.hosting_view(*parent_layer, tree_component), + Some(LayerTreeInfo { + parent: LayerParent::NativeHost, + .. + }) => Some(self.native_component[layer].host), + } + } + + fn update_layer_bounds_with_hosting_view( + &mut self, + layer: LayerId, + hosting_view: id, + geometry_component: &LayerMap, + ) { + let new_bounds: Rect = match geometry_component.get(layer) { + None => return, + Some(geometry_info) => geometry_info.bounds.to_f64(), + }; + + let new_appkit_bounds = NSRect::new( + NSPoint::new(new_bounds.origin.x, new_bounds.origin.y), + NSSize::new(new_bounds.size.width, new_bounds.size.height), + ); + let new_appkit_bounds: NSRect = + unsafe { msg_send![hosting_view, convertRectFromBacking:new_appkit_bounds] }; + + let new_core_animation_bounds = CGRect::new( + &CG_ZERO_POINT, + &CGSize::new(new_appkit_bounds.size.width, new_appkit_bounds.size.height), + ); + + let core_animation_layer = &self.native_component[layer].core_animation_layer; + core_animation_layer.set_bounds(&new_core_animation_bounds); + core_animation_layer.set_position(&CGPoint::new( + new_appkit_bounds.origin.x, + new_appkit_bounds.origin.y, + )); + } + + fn update_layer_subtree_bounds_with_hosting_view( + &mut self, + layer: LayerId, + hosting_view: id, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) { + self.update_layer_bounds_with_hosting_view(layer, hosting_view, geometry_component); + + if let Some(container_info) = container_component.get(layer) { + let mut maybe_kid = container_info.first_child; + while let Some(kid) = maybe_kid { + self.update_layer_subtree_bounds_with_hosting_view( + kid, + hosting_view, + tree_component, + container_component, + geometry_component, + ); + maybe_kid = tree_component[kid].next_sibling; + } + } + } + + fn update_layer_subtree_bounds( + &mut self, + layer: LayerId, + tree_component: &LayerMap, + container_component: &LayerMap, + geometry_component: &LayerMap, + ) { + if let Some(hosting_view) = self.hosting_view(layer, tree_component) { + self.update_layer_subtree_bounds_with_hosting_view( + layer, + hosting_view, + tree_component, + container_component, + geometry_component, + ) + } + } + + fn update_layer_bounds( + &mut self, + layer: LayerId, + tree_component: &LayerMap, + geometry_component: &LayerMap, + ) { + if let Some(hosting_view) = self.hosting_view(layer, tree_component) { + self.update_layer_bounds_with_hosting_view(layer, hosting_view, geometry_component) + } + } +} + +struct NativeInfo { + host: id, + core_animation_layer: CALayer, +} + +pub type LayerNativeHost = id; + +impl Default for NativeInfo { + fn default() -> NativeInfo { + NativeInfo { + host: nil, + core_animation_layer: CALayer::new(), + } + } +} + +impl Drop for NativeInfo { + fn drop(&mut self) { + unsafe { + if !std::ptr::eq(self.host, nil) { + let _: id = msg_send![self.host, release]; + self.host = nil; + } + } + } +} diff --git a/planeshift/src/backends/dummy.rs b/planeshift/src/backends/dummy.rs new file mode 100644 index 000000000..ddda71ece --- /dev/null +++ b/planeshift/src/backends/dummy.rs @@ -0,0 +1,140 @@ +// Copyright 2018 the Vello Authors and The Pathfinder Project Developers +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Dummy implementation that integrates with nothing + +use image::RgbaImage; + +#[cfg(feature = "enable-winit")] +use winit::window::Window; + +use crate::{Connection, ConnectionError, LayerContainerInfo}; +use crate::{LayerGeometryInfo, LayerId, LayerMap, LayerSurfaceInfo, LayerTreeInfo}; +use crate::{Promise, Rect}; + +pub struct Backend { + native_component: LayerMap<()>, +} + +impl crate::Backend for Backend { + type NativeConnection = (); + type Host = (); + + fn new(_connection: Connection) -> Result { + Ok(Backend { + native_component: LayerMap::new(), + }) + } + + fn begin_transaction(&self) { + unimplemented!(); + } + + fn end_transaction( + &mut self, + _promise: &Promise<()>, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + ) { + unimplemented!(); + } + + fn add_container_layer(&mut self, new_layer: LayerId) { + self.native_component.add(new_layer, ()); + } + + fn add_surface_layer(&mut self, new_layer: LayerId) { + self.add_container_layer(new_layer); + } + + fn delete_layer(&mut self, layer: LayerId) { + self.native_component.remove_if_present(layer); + } + + fn insert_before( + &mut self, + _parent: LayerId, + _new_child: LayerId, + _reference: Option, + _tree_component: &LayerMap, + _container_component: &LayerMap, + _geometry_component: &LayerMap, + ) { + unimplemented!(); + } + + fn remove_from_superlayer( + &mut self, + _layer: LayerId, + _: LayerId, + _: &LayerMap, + _: &LayerMap, + ) { + unimplemented!(); + } + + // Increases the reference count of `hosting_view`. + unsafe fn host_layer( + &mut self, + _layer: LayerId, + _host: Self::Host, + _tree_component: &LayerMap, + _container_component: &LayerMap, + _geometry_component: &LayerMap, + ) { + unimplemented!(); + } + + fn unhost_layer(&mut self, _layer: LayerId) { + unimplemented!(); + } + + fn set_layer_bounds( + &mut self, + _layer: LayerId, + _: &Rect, + _tree_component: &LayerMap, + _: &LayerMap, + _geometry_component: &LayerMap, + ) { + unimplemented!(); + } + + fn set_layer_surface_options( + &mut self, + _layer: LayerId, + _surface_component: &LayerMap, + ) { + unimplemented!(); + } + + // Screenshots + + fn screenshot_hosted_layer( + &mut self, + _layer: LayerId, + _transaction_promise: &Promise<()>, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + _: &LayerMap, + ) -> Promise { + unimplemented!() + } + + // `winit` integration + + #[cfg(feature = "enable-winit")] + fn host_layer_in_window( + &mut self, + _layer: LayerId, + _window: &Window, + _tree_component: &LayerMap, + _container_component: &LayerMap, + _geometry_component: &LayerMap, + ) -> Result<(), ()> { + unimplemented!(); + } +} diff --git a/planeshift/src/backends/mod.rs b/planeshift/src/backends/mod.rs new file mode 100644 index 000000000..ca96b422b --- /dev/null +++ b/planeshift/src/backends/mod.rs @@ -0,0 +1,31 @@ +// Copyright 2018 the Vello Authors and The Pathfinder Project Developers +// SPDX-License-Identifier: Apache-2.0 OR MIT + +pub mod dummy; + +pub use dummy as default; + +/* +TODO: All of this ... +#[cfg(target_os = "macos")] +pub use self::core_animation as default; +#[cfg(target_family = "windows")] +pub use self::direct_composition as default; +#[cfg(target_os = "linux")] +pub use self::wayland as default; + +#[cfg(target_os = "macos")] +#[path = "core-animation.rs"] +pub mod core_animation; +#[cfg(target_family = "windows")] +#[path = "direct-composition.rs"] +pub mod direct_composition; +#[cfg(any(target_os = "linux"))] +pub mod wayland; +*/ + +#[cfg(target_vendor = "apple")] +pub mod core_animation; + +// Special backends +pub mod alternate; diff --git a/planeshift/src/lib.rs b/planeshift/src/lib.rs new file mode 100644 index 000000000..b71c79f64 --- /dev/null +++ b/planeshift/src/lib.rs @@ -0,0 +1,745 @@ +// Copyright 2018 the Vello Authors and The Pathfinder Project Developers +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![expect( + dead_code, + missing_debug_implementations, + missing_docs, + reason = "Deferred" +)] +#![expect( + clippy::mem_replace_option_with_none, + clippy::mem_replace_with_default, + clippy::missing_assert_message, + clippy::missing_safety_doc, + clippy::new_without_default, + clippy::semicolon_if_nothing_returned, + clippy::use_self, + reason = "Deferred" +)] +#![cfg_attr( + feature = "enable-winit", + expect(clippy::result_unit_err, reason = "Deferred") +)] +#![cfg_attr(target_vendor = "apple", expect(unexpected_cfgs, reason = "Deferred"))] + +use image::RgbaImage; +use std::fmt::{self, Debug, Formatter}; +use std::mem; +use std::ops::{Index, IndexMut}; +use std::sync::{Arc, Mutex}; + +#[cfg(feature = "enable-winit")] +use winit::window::Window; + +use crate::backend::Backend; + +pub mod backend; +pub mod backends; + +pub type Rect = euclid::Rect; + +/// Manages all the layers. +pub struct LayerContext +where + B: Backend, +{ + next_layer_id: LayerId, + transaction: Option, + + tree_component: LayerMap, + container_component: LayerMap, + geometry_component: LayerMap, + surface_component: LayerMap, + + backend: B, +} + +/// A unique identifier for a layer. +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] +pub struct LayerId(pub u32); + +#[doc(hidden)] +#[derive(Debug)] +pub struct LayerMap(pub Vec>); + +// Public structures + +/// A connection to the OS display server. +pub enum Connection { + /// A native connection. + Native(N), +} + +bitflags::bitflags! { + /// Specifies the type of GPU surface or surfaces to be allocated for a surface layer. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + pub struct SurfaceOptions: u8 { + /// The layer is opaque. + /// + /// The OS may be able to optimize composition of opaque layers, for example by not + /// composing any content underneath them + const OPAQUE = 0x01; + + /// The surface includes a 24-bit depth or Z-buffer. + const DEPTH = 0x02; + + /// The surface includes an 8-bit stencil buffer. + const STENCIL = 0x04; + } +} + +/// Represents the result of a pending operation. +/// +/// This is similar to a Rust future, but it always uses the native OS event loop for dispatch. +/// Note that it is your responsibility to pump the OS event loop. (If using `winit`, this is the +/// `EventLoop` object.) +/// +/// Use the `then` method to attach handlers. +#[derive(Clone)] +pub struct Promise(Arc>>) +where + T: 'static + Clone + Send; + +// Components + +#[doc(hidden)] +pub struct LayerTreeInfo { + parent: LayerParent, + prev_sibling: Option, + next_sibling: Option, +} + +#[doc(hidden)] +pub struct LayerContainerInfo { + first_child: Option, + last_child: Option, +} + +#[doc(hidden)] +pub struct LayerGeometryInfo { + bounds: Rect, +} + +#[doc(hidden)] +pub struct LayerSurfaceInfo { + options: SurfaceOptions, +} + +// Other data structures + +#[derive(PartialEq, Debug)] +pub enum LayerParent { + Layer(LayerId), + NativeHost, +} + +struct PromiseData +where + T: Clone + Send, +{ + on_fulfilled: Vec>, + on_rejected: Vec>, + result: PromiseResult, +} + +enum PromiseResult +where + T: Clone + Send, +{ + Pending, + Resolved(T), + Rejected, +} + +// Public API for the context + +impl LayerContext +where + B: Backend, +{ + // Core functions + + /// Creates a layer context from a connection to the display server. + /// + /// This method allows you to specify a backend explicitly. + pub fn with_backend_connection( + connection: Connection, + ) -> Result, ConnectionError> { + Ok(LayerContext { + backend: Backend::new(connection)?, + + next_layer_id: LayerId(0), + transaction: None, + + tree_component: LayerMap::new(), + container_component: LayerMap::new(), + geometry_component: LayerMap::new(), + surface_component: LayerMap::new(), + }) + } + + // Transactions + + /// Opens a new atomic transaction. + /// + /// All layer manipulations must take place between calls to `begin_transaction` and + /// `end_transaction`, unless otherwise specified. The layer context will panic otherwise. + /// + /// Transactions may be nested. No operations happen until the final `end_transaction` call is + /// issued. + pub fn begin_transaction(&mut self) { + match self.transaction { + None => { + self.transaction = Some(TransactionInfo { + level: 1, + promise: Promise::new(), + }); + self.backend.begin_transaction(); + } + Some(ref mut transaction) => { + transaction.level += 1; + } + } + } + + /// Ends the current transaction and submits it to the display server. + /// + /// This method is *not* synchronous; it merely flushes the pending operations the server, + /// ensuring that they will complete in finite time. + pub fn end_transaction(&mut self) { + { + let transaction = self + .transaction + .as_mut() + .expect("end_transaction(): Not in a transaction!"); + transaction.level -= 1; + if transaction.level > 0 { + return; + } + } + + // If we got here, we're done with the transaction. + let transaction = self.transaction.take().unwrap(); + self.backend.end_transaction( + &transaction.promise, + &self.tree_component, + &self.container_component, + &self.geometry_component, + &self.surface_component, + ); + } + + /// Returns true if a transaction is in process and false otherwise. + /// + /// In other words, this returns true if and only if `begin_transaction` has been called + /// without a matching `end_transaction`. + #[inline] + fn in_transaction(&self) -> bool { + self.transaction.is_some() + } + + // Layer tree management system + + /// Creates a new container layer and returns its ID. + /// + /// Container layers, as their name implies, contain other layers. They are invisible and + /// cannot be rendered to. OpenGL contexts also cannot be attached to them. + /// + /// Initially, the newly-created layer is off-screen, with neither position nor size. + pub fn add_container_layer(&mut self) -> LayerId { + debug_assert!(self.in_transaction()); + + let layer = self.next_layer_id; + self.next_layer_id.0 += 1; + + self.container_component.add( + layer, + LayerContainerInfo { + first_child: None, + last_child: None, + }, + ); + self.backend.add_container_layer(layer); + layer + } + + /// Creates a new surface layer and returns its ID. + /// + /// Surface layers can be rendered to, and OpenGL contexts can be attached to them. They may + /// not contain other layers; therefore, they must be leaves of the layer tree. + /// + /// Initially, the newly-created layer is off-screen, with neither position nor size. + pub fn add_surface_layer(&mut self) -> LayerId { + debug_assert!(self.in_transaction()); + + let layer = self.next_layer_id; + self.next_layer_id.0 += 1; + + self.surface_component.add( + layer, + LayerSurfaceInfo { + options: SurfaceOptions::empty(), + }, + ); + + self.backend.add_surface_layer(layer); + layer + } + + /// Returns the parent of the given layer, if it is on-screen. + pub fn parent_of(&self, layer: LayerId) -> Option<&LayerParent> { + self.tree_component.get(layer).map(|info| &info.parent) + } + + /// Adds a layer to a container layer, optionally before a specific sibling. + /// + /// The specified parent layer must be a container layer. The new child layer must be + /// off-screen (i.e. not in the tree). + /// + /// If `reference` is specified, it must name an immediate child of the given parent layer. The + /// new child layer will be added before that reference in the parent's child list. If + /// `reference` is `None`, then the new child is added to the end of the parent's child list. + pub fn insert_before( + &mut self, + parent: LayerId, + new_child: LayerId, + reference: Option, + ) { + debug_assert!(self.in_transaction()); + + if let Some(reference) = reference { + debug_assert_eq!(self.parent_of(reference), Some(&LayerParent::Layer(parent))); + } + + let new_prev_sibling = match reference { + Some(reference) => self.tree_component[reference].prev_sibling, + None => self.container_component[parent].last_child, + }; + + self.tree_component.add( + new_child, + LayerTreeInfo { + parent: LayerParent::Layer(parent), + prev_sibling: new_prev_sibling, + next_sibling: reference, + }, + ); + + match reference { + Some(reference) => self.tree_component[reference].next_sibling = Some(new_child), + None => self.container_component[parent].last_child = Some(new_child), + } + + if self.tree_component[new_child].prev_sibling.is_none() { + self.container_component[parent].first_child = Some(new_child) + } + + self.backend.insert_before( + parent, + new_child, + reference, + &self.tree_component, + &self.container_component, + &self.geometry_component, + ); + } + + /// Adds a layer to the end of a container layer's child list. + /// + /// This is equivalent to `insert_before` with `reference` set to `None`. + #[inline] + pub fn append_child(&mut self, parent: LayerId, new_child: LayerId) { + self.insert_before(parent, new_child, None) + } + + #[inline] + pub unsafe fn host_layer(&mut self, host: B::Host, layer: LayerId) { + debug_assert!(self.in_transaction()); + + self.tree_component.add( + layer, + LayerTreeInfo { + parent: LayerParent::NativeHost, + prev_sibling: None, + next_sibling: None, + }, + ); + + unsafe { + self.backend.host_layer( + layer, + host, + &self.tree_component, + &self.container_component, + &self.geometry_component, + ); + } + } + + pub fn remove_from_parent(&mut self, old_child: LayerId) { + debug_assert!(self.in_transaction()); + + let old_tree = self.tree_component.take(old_child); + match old_tree.parent { + LayerParent::NativeHost => self.backend.unhost_layer(old_child), + + LayerParent::Layer(parent_layer) => { + self.backend.remove_from_superlayer( + old_child, + parent_layer, + &self.tree_component, + &self.geometry_component, + ); + + match old_tree.prev_sibling { + None => { + self.container_component[parent_layer].first_child = old_tree.next_sibling + } + Some(prev_sibling) => { + self.tree_component[prev_sibling].next_sibling = old_tree.next_sibling + } + } + match old_tree.next_sibling { + None => { + self.container_component[parent_layer].last_child = old_tree.prev_sibling + } + Some(next_sibling) => { + self.tree_component[next_sibling].prev_sibling = old_tree.prev_sibling + } + } + } + } + } + + /// Deletes a layer and destroys all graphics resources associated with it. + /// + /// The layer must be offscreen (i.e. removed from the tree) first. + pub fn delete_layer(&mut self, layer: LayerId) { + debug_assert!(self.in_transaction()); + + // TODO(pcwalton): Use a free list to recycle IDs. + debug_assert!(self.parent_of(layer).is_none()); + + self.tree_component.remove_if_present(layer); + self.container_component.remove_if_present(layer); + self.geometry_component.remove_if_present(layer); + self.surface_component.remove_if_present(layer); + + self.backend.delete_layer(layer); + } + + // Geometry system + + /// Returns the boundaries of the layer relative to its parent. + /// + /// The rectangle origin specifies the top left corner of the layer. + pub fn layer_bounds(&self, layer: LayerId) -> Rect { + debug_assert!(self.in_transaction()); + + match self.geometry_component.get(layer) { + None => Rect::zero(), + Some(geometry) => geometry.bounds, + } + } + + /// Sets the boundaries of the layer relative to its parent. + /// + /// The rectangle origin specifies the top left corner of the layer. + /// + /// If this call causes the size of the layer to change, it may cause associated GPU resources + /// to be reallocated. + pub fn set_layer_bounds(&mut self, layer: LayerId, new_bounds: &Rect) { + debug_assert!(self.in_transaction()); + + let old_bounds = mem::replace( + &mut self.geometry_component.get_mut_default(layer).bounds, + *new_bounds, + ); + + self.backend.set_layer_bounds( + layer, + &old_bounds, + &self.tree_component, + &self.container_component, + &self.geometry_component, + ); + } + + // Miscellaneous layer flags + + /// Sets options for this surface layer. + /// + /// Any GL contexts attached to this layer must have the same surface options as the layer + /// itself. + /// + /// The `layer` parameter must refer to a surface layer, not a container layer. + pub fn set_layer_surface_options(&mut self, layer: LayerId, surface_options: SurfaceOptions) { + debug_assert!(self.in_transaction()); + + self.surface_component[layer].options = surface_options; + self.backend + .set_layer_surface_options(layer, &self.surface_component); + } + + // Screenshots + + pub fn screenshot_hosted_layer(&mut self, layer: LayerId) -> Promise { + debug_assert!(self.in_transaction()); + assert_eq!(self.tree_component[layer].parent, LayerParent::NativeHost); + + let transaction_promise = self.transaction.as_ref().unwrap().promise.clone(); + self.backend.screenshot_hosted_layer( + layer, + &transaction_promise, + &self.tree_component, + &self.container_component, + &self.geometry_component, + &self.surface_component, + ) + } + + // `winit` integration + + #[cfg(feature = "enable-winit")] + pub fn host_layer_in_window(&mut self, layer: LayerId, window: &Window) -> Result<(), ()> { + debug_assert!(self.in_transaction()); + + self.tree_component.add( + layer, + LayerTreeInfo { + parent: LayerParent::NativeHost, + prev_sibling: None, + next_sibling: None, + }, + ); + + self.backend.host_layer_in_window( + layer, + window, + &self.tree_component, + &self.container_component, + &self.geometry_component, + ) + } +} + +impl LayerContext { + #[inline] + pub fn new( + connection: Connection<::NativeConnection>, + ) -> Result, ConnectionError> { + LayerContext::with_backend_connection(connection) + } +} + +// Errors + +pub struct ConnectionError { + #[cfg(feature = "enable-winit")] + window_builder: Option<()>, // TODO +} + +impl Debug for ConnectionError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> { + "ConnectionError".fmt(formatter) + } +} + +impl ConnectionError { + #[inline] + pub fn new() -> ConnectionError { + ConnectionError { + #[cfg(feature = "enable-winit")] + window_builder: None, + } + } +} + +// Promise infrastructure + +impl Promise +where + T: 'static + Clone + Send, +{ + fn new() -> Promise { + Promise(Arc::new(Mutex::new(PromiseData { + on_fulfilled: vec![], + on_rejected: vec![], + result: PromiseResult::Pending, + }))) + } + + fn all(promises: Vec>) -> Promise> { + let result_promise = Promise::new(); + let all = Arc::new(Mutex::new(All { + result_promise: result_promise.clone(), + promises, + results: vec![], + })); + wait(all); + return result_promise; + + fn wait(all: Arc>>) + where + T: 'static + Clone + Send, + { + let next_promise; + { + let mut all = all.lock().unwrap(); + if all.results.len() == all.promises.len() { + let results = mem::replace(&mut all.results, vec![]); + all.result_promise.resolve(results); + return; + } + next_promise = all.promises[all.results.len()].clone(); + } + + next_promise.then(Box::new(move |result| { + all.lock().unwrap().results.push(result); + wait(all.clone()); + })); + } + + struct All + where + T: 'static + Clone + Send, + { + result_promise: Promise>, + promises: Vec>, + results: Vec, + } + } + + pub fn then(&self, mut on_fulfilled: Box) { + let mut this = self.0.lock().unwrap(); + match this.result { + PromiseResult::Rejected => {} + PromiseResult::Resolved(ref result) => on_fulfilled((*result).clone()), + PromiseResult::Pending => this.on_fulfilled.push(on_fulfilled), + } + } + + pub fn or_else(&self, mut on_rejected: Box) { + let mut this = self.0.lock().unwrap(); + match this.result { + PromiseResult::Rejected => on_rejected(), + PromiseResult::Resolved(_) => {} + PromiseResult::Pending => this.on_rejected.push(on_rejected), + } + } + + fn resolve(&self, result: T) { + let mut this = self.0.lock().unwrap(); + this.result = PromiseResult::Resolved(result.clone()); + for mut on_fulfilled in this.on_fulfilled.drain(..) { + on_fulfilled(result.clone()) + } + } + + fn reject(&self) { + let mut this = self.0.lock().unwrap(); + this.result = PromiseResult::Rejected; + for mut on_rejected in this.on_rejected.drain(..) { + on_rejected() + } + } +} + +struct TransactionInfo { + level: u32, + promise: Promise<()>, +} + +// Entity-component system infrastructure + +impl LayerMap { + #[inline] + fn new() -> LayerMap { + LayerMap(vec![]) + } + + fn add(&mut self, layer_id: LayerId, element: T) { + while self.0.len() <= (layer_id.0 as usize) { + self.0.push(None) + } + debug_assert!(self.0[layer_id.0 as usize].is_none()); + self.0[layer_id.0 as usize] = Some(element); + } + + fn has(&self, layer_id: LayerId) -> bool { + (layer_id.0 as usize) < self.0.len() && self.0[layer_id.0 as usize].is_some() + } + + fn take(&mut self, layer_id: LayerId) -> T { + debug_assert!(self.has(layer_id)); + mem::replace(&mut self.0[layer_id.0 as usize], None).unwrap() + } + + fn remove(&mut self, layer_id: LayerId) { + drop(self.take(layer_id)) + } + + fn remove_if_present(&mut self, layer_id: LayerId) { + if self.has(layer_id) { + self.remove(layer_id) + } + } + + fn get(&self, layer_id: LayerId) -> Option<&T> { + if (layer_id.0 as usize) >= self.0.len() { + None + } else { + self.0[layer_id.0 as usize].as_ref() + } + } + + fn get_mut(&mut self, layer_id: LayerId) -> Option<&mut T> { + if (layer_id.0 as usize) >= self.0.len() { + None + } else { + self.0[layer_id.0 as usize].as_mut() + } + } +} + +impl LayerMap +where + T: Default, +{ + fn get_mut_default(&mut self, layer_id: LayerId) -> &mut T { + while self.0.len() <= (layer_id.0 as usize) { + self.0.push(None) + } + if self.0[layer_id.0 as usize].is_none() { + self.0[layer_id.0 as usize] = Some(T::default()); + } + self.0[layer_id.0 as usize].as_mut().unwrap() + } +} + +impl Index for LayerMap { + type Output = T; + + #[inline] + fn index(&self, layer_id: LayerId) -> &T { + self.0[layer_id.0 as usize].as_ref().unwrap() + } +} + +impl IndexMut for LayerMap { + #[inline] + fn index_mut(&mut self, layer_id: LayerId) -> &mut T { + self.0[layer_id.0 as usize].as_mut().unwrap() + } +} + +// Specific component infrastructure + +impl Default for LayerGeometryInfo { + fn default() -> LayerGeometryInfo { + LayerGeometryInfo { + bounds: Rect::zero(), + } + } +}