Merge branch 'gpui2' into marshall/gpui2-playground

This commit is contained in:
Marshall Bowers 2023-10-02 18:36:22 -04:00
commit d14dc35efe
79 changed files with 19378 additions and 371 deletions

834
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,8 @@ members = [
"crates/gpui_macros",
"crates/gpui2",
"crates/gpui2_macros",
"crates/gpui3",
"crates/gpui3_macros",
"crates/install_cli",
"crates/journal",
"crates/language",
@ -65,6 +67,7 @@ members = [
"crates/sqlez_macros",
"crates/feature_flags",
"crates/storybook",
"crates/storybook2",
"crates/sum_tree",
"crates/terminal",
"crates/text",

View File

@ -3607,7 +3607,7 @@ impl<V> BorrowWindowContext for EventContext<'_, '_, '_, V> {
}
}
pub(crate) enum Reference<'a, T> {
pub enum Reference<'a, T> {
Immutable(&'a T),
Mutable(&'a mut T),
}

View File

@ -1,12 +1,12 @@
use std::fmt::Debug;
use super::scene::{Path, PathVertex};
use crate::{color::Color, json::ToJson};
use derive_more::Neg;
pub use pathfinder_geometry::*;
use rect::RectF;
use refineable::Refineable;
use serde::{Deserialize, Deserializer};
use serde_json::json;
use std::fmt::Debug;
use vector::{vec2f, Vector2F};
pub struct PathBuilder {
@ -167,6 +167,15 @@ pub struct Size<T: Clone + Default + Debug> {
pub height: T,
}
impl Size<Length> {
pub fn full() -> Self {
Self {
width: relative(1.),
height: relative(1.),
}
}
}
impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
where
S: Into<T>,
@ -194,8 +203,8 @@ where
impl Size<DefiniteLength> {
pub fn zero() -> Self {
Self {
width: pixels(0.).into(),
height: pixels(0.).into(),
width: px(0.),
height: px(0.),
}
}
@ -235,17 +244,6 @@ pub struct Edges<T: Clone + Default + Debug> {
pub left: T,
}
impl<T: Clone + Default + Debug> Edges<T> {
pub fn uniform(value: T) -> Self {
Self {
top: value.clone(),
right: value.clone(),
bottom: value.clone(),
left: value.clone(),
}
}
}
impl Edges<Length> {
pub fn auto() -> Self {
Self {
@ -258,10 +256,10 @@ impl Edges<Length> {
pub fn zero() -> Self {
Self {
top: pixels(0.).into(),
right: pixels(0.).into(),
bottom: pixels(0.).into(),
left: pixels(0.).into(),
top: px(0.),
right: px(0.),
bottom: px(0.),
left: px(0.),
}
}
@ -281,10 +279,10 @@ impl Edges<Length> {
impl Edges<DefiniteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.).into(),
right: pixels(0.).into(),
bottom: pixels(0.).into(),
left: pixels(0.).into(),
top: px(0.),
right: px(0.),
bottom: px(0.),
left: px(0.),
}
}
@ -301,10 +299,10 @@ impl Edges<DefiniteLength> {
impl Edges<AbsoluteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.),
right: pixels(0.),
bottom: pixels(0.),
left: pixels(0.),
top: px(0.),
right: px(0.),
bottom: px(0.),
left: px(0.),
}
}
@ -333,7 +331,7 @@ impl Edges<f32> {
}
}
#[derive(Clone, Copy, Neg)]
#[derive(Clone, Copy)]
pub enum AbsoluteLength {
Pixels(f32),
Rems(f32),
@ -371,7 +369,7 @@ impl Default for AbsoluteLength {
}
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
#[derive(Clone, Copy, Neg)]
#[derive(Clone, Copy)]
pub enum DefiniteLength {
Absolute(AbsoluteLength),
Relative(f32), // 0. to 1.
@ -415,7 +413,7 @@ impl Default for DefiniteLength {
}
/// A length that can be defined in pixels, rems, percent of parent, or auto.
#[derive(Clone, Copy, Neg)]
#[derive(Clone, Copy)]
pub enum Length {
Definite(DefiniteLength),
Auto,
@ -430,16 +428,20 @@ impl std::fmt::Debug for Length {
}
}
pub fn relative(fraction: f32) -> DefiniteLength {
DefiniteLength::Relative(fraction)
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
DefiniteLength::Relative(fraction).into()
}
pub fn rems(rems: f32) -> AbsoluteLength {
AbsoluteLength::Rems(rems)
pub fn rems<T: From<AbsoluteLength>>(rems: f32) -> T {
AbsoluteLength::Rems(rems).into()
}
pub fn pixels(pixels: f32) -> AbsoluteLength {
AbsoluteLength::Pixels(pixels)
pub fn px<T: From<AbsoluteLength>>(pixels: f32) -> T {
AbsoluteLength::Pixels(pixels).into()
}
pub fn pixels<T: From<AbsoluteLength>>(pixels: f32) -> T {
AbsoluteLength::Pixels(pixels).into()
}
pub fn auto() -> Length {

View File

@ -22,8 +22,8 @@ use std::{
};
pub struct TextLayoutCache {
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
prev_frame: Mutex<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
curr_frame: RwLock<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
fonts: Arc<dyn platform::FontSystem>,
}
@ -56,7 +56,7 @@ impl TextLayoutCache {
font_size: f32,
runs: &'a [(usize, RunStyle)],
) -> Line {
let key = &CacheKeyRef {
let key = &BorrowedCacheKey {
text,
font_size: OrderedFloat(font_size),
runs,
@ -72,7 +72,7 @@ impl TextLayoutCache {
Line::new(layout, runs)
} else {
let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
let key = CacheKeyValue {
let key = OwnedCacheKey {
text: text.into(),
font_size: OrderedFloat(font_size),
runs: SmallVec::from(runs),
@ -84,7 +84,7 @@ impl TextLayoutCache {
}
trait CacheKey {
fn key(&self) -> CacheKeyRef;
fn key(&self) -> BorrowedCacheKey;
}
impl<'a> PartialEq for (dyn CacheKey + 'a) {
@ -102,15 +102,15 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
}
#[derive(Eq)]
struct CacheKeyValue {
struct OwnedCacheKey {
text: String,
font_size: OrderedFloat<f32>,
runs: SmallVec<[(usize, RunStyle); 1]>,
}
impl CacheKey for CacheKeyValue {
fn key(&self) -> CacheKeyRef {
CacheKeyRef {
impl CacheKey for OwnedCacheKey {
fn key(&self) -> BorrowedCacheKey {
BorrowedCacheKey {
text: self.text.as_str(),
font_size: self.font_size,
runs: self.runs.as_slice(),
@ -118,38 +118,38 @@ impl CacheKey for CacheKeyValue {
}
}
impl PartialEq for CacheKeyValue {
impl PartialEq for OwnedCacheKey {
fn eq(&self, other: &Self) -> bool {
self.key().eq(&other.key())
}
}
impl Hash for CacheKeyValue {
impl Hash for OwnedCacheKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key().hash(state);
}
}
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
impl<'a> Borrow<dyn CacheKey + 'a> for OwnedCacheKey {
fn borrow(&self) -> &(dyn CacheKey + 'a) {
self as &dyn CacheKey
}
}
#[derive(Copy, Clone)]
struct CacheKeyRef<'a> {
struct BorrowedCacheKey<'a> {
text: &'a str,
font_size: OrderedFloat<f32>,
runs: &'a [(usize, RunStyle)],
}
impl<'a> CacheKey for CacheKeyRef<'a> {
fn key(&self) -> CacheKeyRef {
impl<'a> CacheKey for BorrowedCacheKey<'a> {
fn key(&self) -> BorrowedCacheKey {
*self
}
}
impl<'a> PartialEq for CacheKeyRef<'a> {
impl<'a> PartialEq for BorrowedCacheKey<'a> {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
&& self.font_size == other.font_size
@ -162,7 +162,7 @@ impl<'a> PartialEq for CacheKeyRef<'a> {
}
}
impl<'a> Hash for CacheKeyRef<'a> {
impl<'a> Hash for BorrowedCacheKey<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.text.hash(state);
self.font_size.hash(state);

View File

@ -12,11 +12,21 @@ use parking_lot::Mutex;
use std::sync::Arc;
use util::arc_cow::ArcCow;
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
impl<V: 'static> IntoElement<V> for ArcCow<'static, str> {
type Element = Text;
fn into_element(self) -> Self::Element {
Text { text: self.into() }
Text { text: self }
}
}
impl<V: 'static> IntoElement<V> for &'static str {
type Element = Text;
fn into_element(self) -> Self::Element {
Text {
text: ArcCow::from(self),
}
}
}

View File

@ -6,6 +6,7 @@ pub mod interactive;
pub mod style;
pub mod view;
pub mod view_context;
pub mod view_handle;
pub use color::*;
pub use element::{AnyElement, Element, IntoElement, Layout, ParentElement};

View File

@ -22,6 +22,8 @@ use gpui2_macros::styleable_helpers;
use refineable::{Refineable, RefinementCascade};
use std::sync::Arc;
pub type StyleCascade = RefinementCascade<Style>;
#[derive(Clone, Refineable, Debug)]
#[refineable(debug)]
pub struct Style {
@ -129,7 +131,7 @@ impl Style {
color: self.text_color.map(Into::into),
font_family: self.font_family.clone(),
font_size: self.font_size.map(|size| size * cx.rem_size()),
font_weight: self.font_weight,
font_weight: self.font_weight.map(Into::into),
font_style: self.font_style,
underline: None,
})

View File

@ -3,7 +3,10 @@ use std::{any::TypeId, rc::Rc};
use crate::{element::LayoutId, style::Style};
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams};
use gpui::{
geometry::Size, scene::EventHandler, AnyWindowHandle, BorrowWindowContext, EventContext,
Layout, MeasureParams, WindowContext,
};
pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext};
#[derive(Deref, DerefMut)]
@ -77,3 +80,35 @@ impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> {
.computed_layout(layout_id)
}
}
impl<'a, 'b, 'c, V: 'static> BorrowWindowContext for ViewContext<'a, 'b, 'c, V> {
type Result<T> = T;
fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
where
F: FnOnce(&WindowContext) -> T,
{
self.legacy_cx.read_window(window, f)
}
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
self.legacy_cx.read_window_optional(window, f)
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
where
F: FnOnce(&mut WindowContext) -> T,
{
self.legacy_cx.update_window(window, f)
}
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
self.legacy_cx.update_window_optional(window, f)
}
}

View File

@ -0,0 +1,60 @@
use crate::{style::Style, Element, IntoElement, ViewContext};
use gpui::{
geometry::{Point, Size},
taffy::style::Overflow,
AnyElement, View, ViewHandle,
};
impl<ParentView: 'static, ChildView: View> Element<ParentView> for ViewHandle<ChildView> {
type PaintState = AnyElement<ChildView>;
fn layout(
&mut self,
_: &mut ParentView,
cx: &mut ViewContext<ParentView>,
) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
where
Self: Sized,
{
let layout_id = cx.add_layout_node(
Style {
overflow: Point {
x: Overflow::Hidden,
y: Overflow::Hidden,
},
size: Size::full(),
..Default::default()
},
None,
)?;
let element = self.update(cx, |view, cx| view.render(cx));
Ok((layout_id, element))
}
fn paint(
&mut self,
_: &mut ParentView,
parent_origin: gpui::geometry::vector::Vector2F,
layout: &gpui::Layout,
element: &mut AnyElement<ChildView>,
cx: &mut ViewContext<ParentView>,
) where
Self: Sized,
{
self.update(cx, |view, cx| {
let bounds = layout.bounds + parent_origin;
element.layout(gpui::SizeConstraint::strict(bounds.size()), view, cx);
cx.paint_layer(Some(layout.bounds), |cx| {
element.paint(bounds.origin(), bounds, view, cx);
});
})
}
}
impl<ParentView: 'static, ChildView: View> IntoElement<ParentView> for ViewHandle<ChildView> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}

87
crates/gpui3/Cargo.toml Normal file
View File

@ -0,0 +1,87 @@
[package]
name = "gpui3"
version = "0.1.0"
edition = "2021"
authors = ["Nathan Sobo <nathan@zed.dev>"]
description = "The next version of Zed's GPU-accelerated UI framework"
publish = false
[features]
test = ["backtrace", "dhat", "env_logger", "collections/test-support"]
[lib]
path = "src/gpui3.rs"
doctest = false
[dependencies]
collections = { path = "../collections" }
gpui_macros = { path = "../gpui_macros" }
gpui3_macros = { path = "../gpui3_macros" }
util = { path = "../util" }
sum_tree = { path = "../sum_tree" }
sqlez = { path = "../sqlez" }
async-task = "4.0.3"
backtrace = { version = "0.3", optional = true }
ctor.workspace = true
derive_more.workspace = true
dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true }
etagere = "0.2"
futures.workspace = true
image = "0.23"
itertools = "0.10"
lazy_static.workspace = true
log.workspace = true
num_cpus = "1.13"
ordered-float.workspace = true
parking = "2.0.0"
parking_lot.workspace = true
pathfinder_geometry = "0.5"
postage.workspace = true
rand.workspace = true
refineable.workspace = true
resvg = "0.14"
seahash = "4.1"
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
smallvec.workspace = true
smol.workspace = true
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
thiserror.workspace = true
time.workspace = true
tiny-skia = "0.5"
usvg = { version = "0.14", features = [] }
uuid = { version = "1.1.2", features = ["v4"] }
waker-fn = "1.1.0"
slotmap = "1.0.6"
bytemuck = { version = "1.14.0", features = ["derive"] }
schemars.workspace = true
plane-split = "0.18.0"
bitflags = "2.4.0"
[dev-dependencies]
backtrace = "0.3"
collections = { path = "../collections", features = ["test-support"] }
dhat = "0.3"
env_logger.workspace = true
png = "0.16"
simplelog = "0.9"
[build-dependencies]
bindgen = "0.65.1"
cbindgen = "0.26.0"
[target.'cfg(target_os = "macos")'.dependencies]
media = { path = "../media" }
anyhow.workspace = true
block = "0.1"
cocoa = "0.24"
core-foundation = { version = "0.9.3", features = ["with-uuid"] }
core-graphics = "0.22.3"
core-text = "19.2"
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
foreign-types = "0.3"
log.workspace = true
metal = "0.21.0"
objc = "0.2"

115
crates/gpui3/build.rs Normal file
View File

@ -0,0 +1,115 @@
use std::{
env,
path::{Path, PathBuf},
process::{self, Command},
};
use cbindgen::Config;
fn main() {
generate_dispatch_bindings();
let header_path = generate_shader_bindings();
compile_metal_shaders(&header_path);
}
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")
.allowlist_var("_dispatch_main_q")
.allowlist_function("dispatch_async_f")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.layout_tests(false)
.generate()
.expect("unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("dispatch_sys.rs"))
.expect("couldn't write dispatch bindings");
}
fn generate_shader_bindings() -> PathBuf {
let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let mut config = Config::default();
config.include_guard = Some("SCENE_H".into());
config.language = cbindgen::Language::C;
config.export.include.extend([
"Bounds".into(),
"Corners".into(),
"Edges".into(),
"Size".into(),
"Pixels".into(),
"PointF".into(),
"Hsla".into(),
"Quad".into(),
"QuadInputIndex".into(),
"QuadUniforms".into(),
]);
config.no_includes = true;
config.enumeration.prefix_with_name = true;
cbindgen::Builder::new()
.with_src(crate_dir.join("src/scene.rs"))
.with_src(crate_dir.join("src/geometry.rs"))
.with_src(crate_dir.join("src/color.rs"))
.with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file(&output_path);
output_path
}
fn compile_metal_shaders(header_path: &Path) {
let shader_path = "./src/platform/mac/shaders.metal";
let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
println!("cargo:rerun-if-changed={}", header_path.display());
println!("cargo:rerun-if-changed={}", shader_path);
let output = Command::new("xcrun")
.args([
"-sdk",
"macosx",
"metal",
"-gline-tables-only",
"-mmacosx-version-min=10.15.7",
"-MO",
"-c",
shader_path,
"-include",
&header_path.to_str().unwrap(),
"-o",
])
.arg(&air_output_path)
.output()
.unwrap();
if !output.status.success() {
eprintln!(
"metal shader compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
let output = Command::new("xcrun")
.args(["-sdk", "macosx", "metallib"])
.arg(air_output_path)
.arg("-o")
.arg(metallib_output_path)
.output()
.unwrap();
if !output.status.success() {
eprintln!(
"metallib compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
}

343
crates/gpui3/src/app.rs Normal file
View File

@ -0,0 +1,343 @@
mod async_context;
mod entity_map;
mod model_context;
pub use async_context::*;
pub use entity_map::*;
pub use model_context::*;
use refineable::Refineable;
use crate::{
current_platform, run_on_main, spawn_on_main, Context, LayoutId, MainThread, MainThreadOnly,
Platform, PlatformDispatcher, RootView, TextStyle, TextStyleRefinement, TextSystem, Window,
WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, VecDeque};
use futures::Future;
use parking_lot::Mutex;
use slotmap::SlotMap;
use smallvec::SmallVec;
use std::{
any::{type_name, Any, TypeId},
mem,
sync::{Arc, Weak},
};
use util::ResultExt;
#[derive(Clone)]
pub struct App(Arc<Mutex<MainThread<AppContext>>>);
impl App {
pub fn production() -> Self {
Self::new(current_platform())
}
#[cfg(any(test, feature = "test"))]
pub fn test() -> Self {
Self::new(Arc::new(super::TestPlatform::new()))
}
fn new(platform: Arc<dyn Platform>) -> Self {
let dispatcher = platform.dispatcher();
let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new();
let unit_entity = entities.redeem(entities.reserve(), ());
Self(Arc::new_cyclic(|this| {
Mutex::new(MainThread::new(AppContext {
this: this.clone(),
platform: MainThreadOnly::new(platform, dispatcher.clone()),
dispatcher,
text_system,
pending_updates: 0,
text_style_stack: Vec::new(),
state_stacks_by_type: HashMap::default(),
unit_entity,
entities,
windows: SlotMap::with_key(),
pending_effects: Default::default(),
observers: Default::default(),
layout_id_buffer: Default::default(),
}))
}))
}
pub fn run<F>(self, on_finish_launching: F)
where
F: 'static + FnOnce(&mut MainThread<AppContext>),
{
let this = self.clone();
let platform = self.0.lock().platform.clone();
platform.borrow_on_main_thread().run(Box::new(move || {
let cx = &mut *this.0.lock();
on_finish_launching(cx);
}));
}
}
type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
pub struct AppContext {
this: Weak<Mutex<MainThread<AppContext>>>,
platform: MainThreadOnly<dyn Platform>,
dispatcher: Arc<dyn PlatformDispatcher>,
text_system: Arc<TextSystem>,
pending_updates: usize,
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
pub(crate) unit_entity: Handle<()>,
pub(crate) entities: EntityMap,
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
pub(crate) pending_effects: VecDeque<Effect>,
pub(crate) observers: HashMap<EntityId, Handlers>,
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
}
impl AppContext {
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
self.pending_updates += 1;
let result = update(self);
if self.pending_updates == 1 {
self.flush_effects();
}
self.pending_updates -= 1;
result
}
pub(crate) fn update_window<R>(
&mut self,
id: WindowId,
update: impl FnOnce(&mut WindowContext) -> R,
) -> Result<R> {
self.update(|cx| {
let mut window = cx
.windows
.get_mut(id)
.ok_or_else(|| anyhow!("window not found"))?
.take()
.unwrap();
let result = update(&mut WindowContext::mutable(cx, &mut window));
cx.windows
.get_mut(id)
.ok_or_else(|| anyhow!("window not found"))?
.replace(window);
Ok(result)
})
}
fn flush_effects(&mut self) {
while let Some(effect) = self.pending_effects.pop_front() {
match effect {
Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),
}
}
let dirty_window_ids = self
.windows
.iter()
.filter_map(|(window_id, window)| {
let window = window.as_ref().unwrap();
if window.dirty {
Some(window_id)
} else {
None
}
})
.collect::<Vec<_>>();
for dirty_window_id in dirty_window_ids {
self.update_window(dirty_window_id, |cx| cx.draw())
.unwrap()
.log_err();
}
}
fn apply_notify_effect(&mut self, updated_entity: EntityId) {
if let Some(mut handlers) = self.observers.remove(&updated_entity) {
handlers.retain(|handler| handler(self));
if let Some(new_handlers) = self.observers.remove(&updated_entity) {
handlers.extend(new_handlers);
}
self.observers.insert(updated_entity, handlers);
}
}
pub fn to_async(&self) -> AsyncContext {
AsyncContext(unsafe { mem::transmute(self.this.clone()) })
}
pub fn run_on_main<R>(
&self,
f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
) -> impl Future<Output = R>
where
R: Send + 'static,
{
let this = self.this.upgrade().unwrap();
run_on_main(self.dispatcher.clone(), move || {
let cx = &mut *this.lock();
cx.update(|cx| {
f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) })
})
})
}
pub fn spawn_on_main<F, R>(
&self,
f: impl FnOnce(&mut MainThread<AppContext>) -> F + Send + 'static,
) -> impl Future<Output = R>
where
F: Future<Output = R> + 'static,
R: Send + 'static,
{
let this = self.this.upgrade().unwrap();
spawn_on_main(self.dispatcher.clone(), move || {
let cx = &mut *this.lock();
cx.update(|cx| {
f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) })
})
})
}
pub fn text_system(&self) -> &Arc<TextSystem> {
&self.text_system
}
pub fn text_style(&self) -> TextStyle {
let mut style = TextStyle::default();
for refinement in &self.text_style_stack {
style.refine(refinement);
}
style
}
pub fn state<S: 'static>(&self) -> &S {
self.state_stacks_by_type
.get(&TypeId::of::<S>())
.and_then(|stack| stack.last())
.and_then(|any_state| any_state.downcast_ref::<S>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
.unwrap()
}
pub fn state_mut<S: 'static>(&mut self) -> &mut S {
self.state_stacks_by_type
.get_mut(&TypeId::of::<S>())
.and_then(|stack| stack.last_mut())
.and_then(|any_state| any_state.downcast_mut::<S>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
.unwrap()
}
pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
self.text_style_stack.push(text_style);
}
pub(crate) fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
pub(crate) fn push_state<T: Send + Sync + 'static>(&mut self, state: T) {
self.state_stacks_by_type
.entry(TypeId::of::<T>())
.or_default()
.push(Box::new(state));
}
pub(crate) fn pop_state<T: 'static>(&mut self) {
self.state_stacks_by_type
.get_mut(&TypeId::of::<T>())
.and_then(|stack| stack.pop())
.expect("state stack underflow");
}
}
impl Context for AppContext {
type EntityContext<'a, 'w, T: Send + Sync + 'static> = ModelContext<'a, T>;
type Result<T> = T;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Handle<T> {
let slot = self.entities.reserve();
let entity = build_entity(&mut ModelContext::mutable(self, slot.id));
self.entities.redeem(slot, entity)
}
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> R {
let mut entity = self.entities.lease(handle);
let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id));
self.entities.end_lease(entity);
result
}
}
impl MainThread<AppContext> {
fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
self.0.update(|cx| {
update(unsafe {
std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
})
})
}
pub(crate) fn update_window<R>(
&mut self,
id: WindowId,
update: impl FnOnce(&mut WindowContext) -> R,
) -> Result<R> {
self.0.update_window(id, |cx| {
update(unsafe {
std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
})
})
}
pub(crate) fn platform(&self) -> &dyn Platform {
self.platform.borrow_on_main_thread()
}
pub fn activate(&mut self, ignoring_other_apps: bool) {
self.platform().activate(ignoring_other_apps);
}
pub fn open_window<S: 'static + Send + Sync>(
&mut self,
options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut WindowContext) -> RootView<S> + Send + 'static,
) -> WindowHandle<S> {
self.update(|cx| {
let id = cx.windows.insert(None);
let handle = WindowHandle::new(id);
let mut window = Window::new(handle.into(), options, cx);
let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
window.root_view.replace(root_view.into_any());
cx.windows.get_mut(id).unwrap().replace(window);
handle
})
}
}
pub(crate) enum Effect {
Notify(EntityId),
}
#[cfg(test)]
mod tests {
use super::AppContext;
#[test]
fn test_app_context_send_sync() {
// This will not compile if `AppContext` does not implement `Send`
fn assert_send<T: Send>() {}
assert_send::<AppContext>();
}
}

View File

@ -0,0 +1,52 @@
use crate::{AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, WindowContext};
use anyhow::anyhow;
use parking_lot::Mutex;
use std::sync::Weak;
#[derive(Clone)]
pub struct AsyncContext(pub(crate) Weak<Mutex<AppContext>>);
impl Context for AsyncContext {
type EntityContext<'a, 'b, T: 'static + Send + Sync> = ModelContext<'a, T>;
type Result<T> = Result<T>;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Result<Handle<T>> {
let app = self
.0
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.lock();
Ok(lock.entity(build_entity))
}
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> Result<R> {
let app = self
.0
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.lock();
Ok(lock.update_entity(handle, update))
}
}
impl AsyncContext {
pub fn update_window<T>(
&self,
handle: AnyWindowHandle,
update: impl FnOnce(&mut WindowContext) -> T + Send + Sync,
) -> Result<T> {
let app = self
.0
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut app_context = app.lock();
app_context.update_window(handle.id, update)
}
}

View File

@ -0,0 +1,175 @@
use crate::Context;
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use parking_lot::{Mutex, RwLock};
use slotmap::{SecondaryMap, SlotMap};
use std::{
any::Any,
marker::PhantomData,
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak,
},
};
slotmap::new_key_type! { pub struct EntityId; }
#[derive(Deref, DerefMut)]
pub struct Lease<T> {
#[deref]
#[deref_mut]
entity: Box<T>,
pub id: EntityId,
}
pub(crate) struct EntityMap {
ref_counts: Arc<RwLock<RefCounts>>,
entities: Arc<Mutex<SecondaryMap<EntityId, Box<dyn Any + Send + Sync>>>>,
}
impl EntityMap {
pub fn new() -> Self {
Self {
ref_counts: Arc::new(RwLock::new(SlotMap::with_key())),
entities: Arc::new(Mutex::new(SecondaryMap::new())),
}
}
pub fn reserve<T: 'static + Send + Sync>(&self) -> Slot<T> {
let id = self.ref_counts.write().insert(1.into());
Slot(Handle::new(id, Arc::downgrade(&self.ref_counts)))
}
pub fn redeem<T: 'static + Any + Send + Sync>(&self, slot: Slot<T>, entity: T) -> Handle<T> {
let handle = slot.0;
self.entities.lock().insert(handle.id, Box::new(entity));
handle
}
pub fn lease<T: 'static + Send + Sync>(&self, handle: &Handle<T>) -> Lease<T> {
let id = handle.id;
let entity = self
.entities
.lock()
.remove(id)
.expect("Circular entity lease. Is the entity already being updated?")
.downcast::<T>()
.unwrap();
Lease { id, entity }
}
pub fn end_lease<T: 'static + Send + Sync>(&mut self, lease: Lease<T>) {
self.entities.lock().insert(lease.id, lease.entity);
}
pub fn weak_handle<T: 'static + Send + Sync>(&self, id: EntityId) -> WeakHandle<T> {
WeakHandle {
id,
entity_type: PhantomData,
ref_counts: Arc::downgrade(&self.ref_counts),
}
}
}
#[derive(Deref, DerefMut)]
pub struct Slot<T: Send + Sync + 'static>(Handle<T>);
pub struct Handle<T: Send + Sync> {
pub(crate) id: EntityId,
entity_type: PhantomData<T>,
ref_counts: Weak<RwLock<RefCounts>>,
}
type RefCounts = SlotMap<EntityId, AtomicUsize>;
impl<T: 'static + Send + Sync> Handle<T> {
pub fn new(id: EntityId, ref_counts: Weak<RwLock<RefCounts>>) -> Self {
Self {
id,
entity_type: PhantomData,
ref_counts,
}
}
pub fn downgrade(&self) -> WeakHandle<T> {
WeakHandle {
id: self.id,
entity_type: self.entity_type,
ref_counts: self.ref_counts.clone(),
}
}
/// Update the entity referenced by this handle with the given function.
///
/// The update function receives a context appropriate for its environment.
/// When updating in an `AppContext`, it receives a `ModelContext`.
/// When updating an a `WindowContext`, it receives a `ViewContext`.
pub fn update<C: Context, R>(
&self,
cx: &mut C,
update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
) -> C::Result<R> {
cx.update_entity(self, update)
}
}
impl<T: Send + Sync> Clone for Handle<T> {
fn clone(&self) -> Self {
Self {
id: self.id,
entity_type: PhantomData,
ref_counts: self.ref_counts.clone(),
}
}
}
impl<T: Send + Sync> Drop for Handle<T> {
fn drop(&mut self) {
if let Some(ref_counts) = self.ref_counts.upgrade() {
if let Some(count) = ref_counts.read().get(self.id) {
let prev_count = count.fetch_sub(1, SeqCst);
assert_ne!(prev_count, 0, "Detected over-release of a handle.");
}
}
}
}
pub struct WeakHandle<T> {
pub(crate) id: EntityId,
pub(crate) entity_type: PhantomData<T>,
pub(crate) ref_counts: Weak<RwLock<RefCounts>>,
}
impl<T: Send + Sync + 'static> WeakHandle<T> {
pub fn upgrade(&self, _: &impl Context) -> Option<Handle<T>> {
let ref_counts = self.ref_counts.upgrade()?;
ref_counts.read().get(self.id).unwrap().fetch_add(1, SeqCst);
Some(Handle {
id: self.id,
entity_type: self.entity_type,
ref_counts: self.ref_counts.clone(),
})
}
/// Update the entity referenced by this handle with the given function if
/// the referenced entity still exists. Returns an error if the entity has
/// been released.
///
/// The update function receives a context appropriate for its environment.
/// When updating in an `AppContext`, it receives a `ModelContext`.
/// When updating an a `WindowContext`, it receives a `ViewContext`.
pub fn update<C: Context, R>(
&self,
cx: &mut C,
update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
) -> Result<R>
where
Result<C::Result<R>>: crate::Flatten<R>,
{
crate::Flatten::flatten(
self.upgrade(cx)
.ok_or_else(|| anyhow!("entity release"))
.map(|this| cx.update_entity(&this, update)),
)
}
}

View File

@ -0,0 +1,87 @@
use crate::{AppContext, Context, Effect, EntityId, Handle, Reference, WeakHandle};
use std::{marker::PhantomData, sync::Arc};
pub struct ModelContext<'a, T> {
app: Reference<'a, AppContext>,
entity_type: PhantomData<T>,
entity_id: EntityId,
}
impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
pub(crate) fn mutable(app: &'a mut AppContext, entity_id: EntityId) -> Self {
Self {
app: Reference::Mutable(app),
entity_type: PhantomData,
entity_id,
}
}
// todo!
// fn update<R>(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R {
// let mut entity = self
// .app
// .entities
// .get_mut(self.entity_id)
// .unwrap()
// .take()
// .unwrap();
// let result = update(entity.downcast_mut::<T>().unwrap(), self);
// self.app
// .entities
// .get_mut(self.entity_id)
// .unwrap()
// .replace(entity);
// result
// }
pub fn handle(&self) -> WeakHandle<T> {
self.app.entities.weak_handle(self.entity_id)
}
pub fn observe<E: Send + Sync + 'static>(
&mut self,
handle: &Handle<E>,
on_notify: impl Fn(&mut T, Handle<E>, &mut ModelContext<'_, T>) + Send + Sync + 'static,
) {
let this = self.handle();
let handle = handle.downgrade();
self.app
.observers
.entry(handle.id)
.or_default()
.push(Arc::new(move |cx| {
if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
this.update(cx, |this, cx| on_notify(this, handle, cx));
true
} else {
false
}
}));
}
pub fn notify(&mut self) {
self.app
.pending_effects
.push_back(Effect::Notify(self.entity_id));
}
}
impl<'a, T: 'static> Context for ModelContext<'a, T> {
type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>;
type Result<U> = U;
fn entity<U: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U,
) -> Handle<U> {
self.app.entity(build_entity)
}
fn update_entity<U: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<U>,
update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
) -> R {
self.app.update_entity(handle, update)
}
}

View File

238
crates/gpui3/src/color.rs Normal file
View File

@ -0,0 +1,238 @@
#![allow(dead_code)]
use bytemuck::{Pod, Zeroable};
use serde::de::{self, Deserialize, Deserializer, Visitor};
use std::fmt;
use std::num::ParseIntError;
pub fn rgb(hex: u32) -> Rgba {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
let b = (hex & 0xFF) as f32 / 255.0;
Rgba { r, g, b, a: 1.0 }.into()
}
#[derive(Clone, Copy, Default, Debug)]
pub struct Rgba {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Rgba {
pub fn blend(&self, other: Rgba) -> Self {
if other.a >= 1.0 {
return other;
} else if other.a <= 0.0 {
return *self;
} else {
return Rgba {
r: (self.r * (1.0 - other.a)) + (other.r * other.a),
g: (self.g * (1.0 - other.a)) + (other.g * other.a),
b: (self.b * (1.0 - other.a)) + (other.b * other.a),
a: self.a,
};
}
}
}
struct RgbaVisitor;
impl<'de> Visitor<'de> for RgbaVisitor {
type Value = Rgba;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
if value.len() == 7 || value.len() == 9 {
let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
let a = if value.len() == 9 {
u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
} else {
1.0
};
Ok(Rgba { r, g, b, a })
} else {
Err(E::custom(
"Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
))
}
}
}
impl<'de> Deserialize<'de> for Rgba {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(RgbaVisitor)
}
}
impl From<Hsla> for Rgba {
fn from(color: Hsla) -> Self {
let h = color.h;
let s = color.s;
let l = color.l;
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
let m = l - c / 2.0;
let cm = c + m;
let xm = x + m;
let (r, g, b) = match (h * 6.0).floor() as i32 {
0 | 6 => (cm, xm, m),
1 => (xm, cm, m),
2 => (m, cm, xm),
3 => (m, xm, cm),
4 => (xm, m, cm),
_ => (cm, m, xm),
};
Rgba {
r,
g,
b,
a: color.a,
}
}
}
impl TryFrom<&'_ str> for Rgba {
type Error = ParseIntError;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
let a = if value.len() > 7 {
u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
} else {
1.0
};
Ok(Rgba { r, g, b, a })
}
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
pub struct Hsla {
pub h: f32,
pub s: f32,
pub l: f32,
pub a: f32,
}
impl Eq for Hsla {}
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla {
h: h.clamp(0., 1.),
s: s.clamp(0., 1.),
l: l.clamp(0., 1.),
a: a.clamp(0., 1.),
}
}
pub fn black() -> Hsla {
Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 1.,
}
}
impl Hsla {
/// Returns true if the HSLA color is fully transparent, false otherwise.
pub fn is_transparent(&self) -> bool {
self.a == 0.0
}
/// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
///
/// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
/// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
/// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
///
/// Assumptions:
/// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
/// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
/// - RGB color components are contained in the range [0, 1].
/// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
pub fn blend(self, other: Hsla) -> Hsla {
let alpha = other.a;
if alpha >= 1.0 {
return other;
} else if alpha <= 0.0 {
return self;
} else {
let converted_self = Rgba::from(self);
let converted_other = Rgba::from(other);
let blended_rgb = converted_self.blend(converted_other);
return Hsla::from(blended_rgb);
}
}
/// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
/// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
pub fn fade_out(&mut self, factor: f32) {
self.a *= 1.0 - factor.clamp(0., 1.);
}
}
impl From<Rgba> for Hsla {
fn from(color: Rgba) -> Self {
let r = color.r;
let g = color.g;
let b = color.b;
let max = r.max(g.max(b));
let min = r.min(g.min(b));
let delta = max - min;
let l = (max + min) / 2.0;
let s = if l == 0.0 || l == 1.0 {
0.0
} else if l < 0.5 {
delta / (2.0 * l)
} else {
delta / (2.0 - 2.0 * l)
};
let h = if delta == 0.0 {
0.0
} else if max == r {
((g - b) / delta).rem_euclid(6.0) / 6.0
} else if max == g {
((b - r) / delta + 2.0) / 6.0
} else {
((r - g) / delta + 4.0) / 6.0
};
Hsla {
h,
s,
l,
a: color.a,
}
}
}
impl<'de> Deserialize<'de> for Hsla {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// First, deserialize it into Rgba
let rgba = Rgba::deserialize(deserializer)?;
// Then, use the From<Rgba> for Hsla implementation to convert it
Ok(Hsla::from(rgba))
}
}

166
crates/gpui3/src/element.rs Normal file
View File

@ -0,0 +1,166 @@
use super::{Layout, LayoutId, Pixels, Point, Result, ViewContext};
pub(crate) use smallvec::SmallVec;
pub trait Element: 'static {
type State;
type FrameState;
fn layout(
&mut self,
state: &mut Self::State,
cx: &mut ViewContext<Self::State>,
) -> Result<(LayoutId, Self::FrameState)>;
fn paint(
&mut self,
layout: Layout,
state: &mut Self::State,
frame_state: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>,
) -> Result<()>;
}
pub trait ParentElement<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]>;
fn child(mut self, child: impl IntoAnyElement<S>) -> Self
where
Self: Sized,
{
self.children_mut().push(child.into_any());
self
}
fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<S>>) -> Self
where
Self: Sized,
{
self.children_mut()
.extend(iter.into_iter().map(|item| item.into_any()));
self
}
}
trait ElementObject<S> {
fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId>;
fn paint(
&mut self,
state: &mut S,
offset: Option<Point<Pixels>>,
cx: &mut ViewContext<S>,
) -> Result<()>;
}
struct RenderedElement<E: Element> {
element: E,
phase: ElementRenderPhase<E::FrameState>,
}
#[derive(Default)]
enum ElementRenderPhase<S> {
#[default]
Rendered,
LayoutRequested {
layout_id: LayoutId,
frame_state: S,
},
Painted {
layout: Layout,
frame_state: S,
},
}
/// Internal struct that wraps an element to store Layout and FrameState after the element is rendered.
/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for
/// improved usability.
impl<E: Element> RenderedElement<E> {
fn new(element: E) -> Self {
RenderedElement {
element,
phase: ElementRenderPhase::Rendered,
}
}
}
impl<E: Element> ElementObject<E::State> for RenderedElement<E> {
fn layout(&mut self, state: &mut E::State, cx: &mut ViewContext<E::State>) -> Result<LayoutId> {
let (layout_id, frame_state) = self.element.layout(state, cx)?;
self.phase = ElementRenderPhase::LayoutRequested {
layout_id,
frame_state,
};
Ok(layout_id)
}
fn paint(
&mut self,
state: &mut E::State,
offset: Option<Point<Pixels>>,
cx: &mut ViewContext<E::State>,
) -> Result<()> {
self.phase = match std::mem::take(&mut self.phase) {
ElementRenderPhase::Rendered => panic!("must call layout before paint"),
ElementRenderPhase::LayoutRequested {
layout_id,
mut frame_state,
} => {
let mut layout = cx.layout(layout_id)?.clone();
offset.map(|offset| layout.bounds.origin += offset);
self.element
.paint(layout.clone(), state, &mut frame_state, cx)?;
ElementRenderPhase::Painted {
layout,
frame_state,
}
}
ElementRenderPhase::Painted {
layout,
mut frame_state,
} => {
self.element
.paint(layout.clone(), state, &mut frame_state, cx)?;
ElementRenderPhase::Painted {
layout,
frame_state,
}
}
};
Ok(())
}
}
pub struct AnyElement<S>(Box<dyn ElementObject<S>>);
impl<S> AnyElement<S> {
pub fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId> {
self.0.layout(state, cx)
}
pub fn paint(
&mut self,
state: &mut S,
offset: Option<Point<Pixels>>,
cx: &mut ViewContext<S>,
) -> Result<()> {
self.0.paint(state, offset, cx)
}
}
pub trait IntoAnyElement<S> {
fn into_any(self) -> AnyElement<S>;
}
impl<E: Element> IntoAnyElement<E::State> for E {
fn into_any(self) -> AnyElement<E::State> {
AnyElement(Box::new(RenderedElement::new(self)))
}
}
impl<S> IntoAnyElement<S> for AnyElement<S> {
fn into_any(self) -> AnyElement<S> {
self
}
}

View File

@ -0,0 +1,11 @@
mod div;
mod img;
mod stateless;
mod svg;
mod text;
pub use div::*;
pub use img::*;
pub use stateless::*;
pub use svg::*;
pub use text::*;

View File

@ -0,0 +1,297 @@
use crate::{
AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
Refineable, RefinementCascade, Result, StackContext, Style, StyleHelpers, Styled, ViewContext,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::sync::Arc;
use util::ResultExt;
pub struct Div<S: 'static> {
styles: RefinementCascade<Style>,
// handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<S>; 2]>,
scroll_state: Option<ScrollState>,
}
pub fn div<S>() -> Div<S> {
Div {
styles: Default::default(),
// handlers: Default::default(),
children: Default::default(),
scroll_state: None,
}
}
impl<S: 'static + Send + Sync> Element for Div<S> {
type State = S;
type FrameState = Vec<LayoutId>;
fn layout(
&mut self,
view: &mut S,
cx: &mut ViewContext<S>,
) -> Result<(LayoutId, Self::FrameState)> {
let style = self.computed_style();
let child_layout_ids = if let Some(text_style) = style.text_style(cx) {
cx.with_text_style(text_style.clone(), |cx| self.layout_children(view, cx))?
} else {
self.layout_children(view, cx)?
};
Ok((
cx.request_layout(style.into(), child_layout_ids.clone())?,
child_layout_ids,
))
}
fn paint(
&mut self,
layout: Layout,
state: &mut S,
child_layouts: &mut Self::FrameState,
cx: &mut ViewContext<S>,
) -> Result<()> {
let Layout { order, bounds } = layout;
let style = self.computed_style();
style.paint(order, bounds, cx);
let overflow = &style.overflow;
// // todo!("support only one dimension being hidden")
// if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
// cx.scene().push_layer(Some(bounds));
// pop_layer = true;
// }
if let Some(text_style) = style.text_style(cx) {
cx.with_text_style(text_style.clone(), |cx| {
self.paint_children(overflow, state, cx)
})?;
} else {
self.paint_children(overflow, state, cx)?;
}
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
// todo!("enable inspector")
// if cx.is_inspector_enabled() {
// self.paint_inspector(parent_origin, layout, cx);
// }
//
Ok(())
}
}
impl<S: 'static> Div<S> {
pub fn overflow_hidden(mut self) -> Self {
self.declared_style().overflow.x = Some(Overflow::Hidden);
self.declared_style().overflow.y = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_x(mut self) -> Self {
self.declared_style().overflow.x = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_y(mut self) -> Self {
self.declared_style().overflow.y = Some(Overflow::Hidden);
self
}
pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.x = Some(Overflow::Scroll);
self.declared_style().overflow.y = Some(Overflow::Scroll);
self
}
pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.x = Some(Overflow::Scroll);
self
}
pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.y = Some(Overflow::Scroll);
self
}
fn scroll_offset(&self, overflow: &Point<Overflow>) -> Point<Pixels> {
let mut offset = Point::default();
if overflow.y == Overflow::Scroll {
offset.y = self.scroll_state.as_ref().unwrap().y();
}
if overflow.x == Overflow::Scroll {
offset.x = self.scroll_state.as_ref().unwrap().x();
}
offset
}
fn layout_children(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> Result<Vec<LayoutId>> {
self.children
.iter_mut()
.map(|child| child.layout(view, cx))
.collect::<Result<Vec<LayoutId>>>()
}
fn paint_children(
&mut self,
overflow: &Point<Overflow>,
state: &mut S,
cx: &mut ViewContext<S>,
) -> Result<()> {
let scroll_offset = self.scroll_offset(overflow);
for child in &mut self.children {
child.paint(state, Some(scroll_offset), cx)?;
}
Ok(())
}
fn handle_scroll(
&mut self,
_order: u32,
bounds: Bounds<Pixels>,
overflow: Point<Overflow>,
child_layout_ids: &[LayoutId],
cx: &mut ViewContext<S>,
) {
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
let mut scroll_max = Point::default();
for child_layout_id in child_layout_ids {
if let Some(child_layout) = cx.layout(*child_layout_id).log_err() {
scroll_max = scroll_max.max(&child_layout.bounds.lower_right());
}
}
scroll_max -= bounds.size;
// todo!("handle scroll")
// let scroll_state = self.scroll_state.as_ref().unwrap().clone();
// cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
// if bounds.contains_point(event.position) {
// let scroll_delta = match event.delta {
// ScrollDelta::Pixels(delta) => delta,
// ScrollDelta::Lines(delta) => cx.text_style().font_size * delta,
// };
// if overflow.x == Overflow::Scroll {
// scroll_state.set_x(
// (scroll_state.x() - scroll_delta.x())
// .max(px(0.))
// .min(scroll_max.x),
// );
// }
// if overflow.y == Overflow::Scroll {
// scroll_state.set_y(
// (scroll_state.y() - scroll_delta.y())
// .max(px(0.))
// .min(scroll_max.y),
// );
// }
// cx.repaint();
// } else {
// cx.bubble_event();
// }
// })
}
}
// fn paint_inspector(
// &self,
// parent_origin: Point<Pixels>,
// layout: &Layout,
// cx: &mut ViewContext<V>,
// ) {
// let style = self.styles.merged();
// let bounds = layout.bounds;
// let hovered = bounds.contains_point(cx.mouse_position());
// if hovered {
// let rem_size = cx.rem_size();
// // cx.scene().push_quad(scene::Quad {
// // bounds,
// // background: Some(hsla(0., 0., 1., 0.05).into()),
// // border: gpui::Border {
// // color: hsla(0., 0., 1., 0.2).into(),
// // top: 1.,
// // right: 1.,
// // bottom: 1.,
// // left: 1.,
// // },
// // corner_radii: CornerRadii::default()
// // .refined(&style.corner_radii)
// // .to_gpui(bounds.size(), rem_size),
// // })
// }
// // let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left));
// // cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| {
// // if bounds.contains_point(event.position) {
// // if event.is_down {
// // pressed.set(true);
// // } else if pressed.get() {
// // pressed.set(false);
// // eprintln!("clicked div {:?} {:#?}", bounds, style);
// // }
// // }
// // });
// // let hovered = Cell::new(hovered);
// // cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| {
// // cx.bubble_event();
// // let hovered_now = bounds.contains_point(event.position);
// // if hovered.get() != hovered_now {
// // hovered.set(hovered_now);
// // cx.repaint();
// // }
// // });
// }
//
}
impl<V> Styled for Div<V> {
type Style = Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
&mut self.styles
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
self.styles.base()
}
}
impl<V> StyleHelpers for Div<V> {}
// impl<V> Interactive<V> for Div<V> {
// fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
// &mut self.handlers
// }
// }
impl<V: 'static> ParentElement<V> for Div<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
#[derive(Default, Clone)]
pub struct ScrollState(Arc<Mutex<Point<Pixels>>>);
impl ScrollState {
pub fn x(&self) -> Pixels {
self.0.lock().x
}
pub fn set_x(&self, value: Pixels) {
self.0.lock().x = value;
}
pub fn y(&self) -> Pixels {
self.0.lock().y
}
pub fn set_y(&self, value: Pixels) {
self.0.lock().y = value;
}
}

View File

@ -0,0 +1,105 @@
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
style::{Style, StyleHelpers, Styleable},
ViewContext,
};
use anyhow::Result;
use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
use refineable::{CascadeSlot, Refineable, RefinementCascade};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
pub struct Hoverable<E: Styleable> {
hovered: Rc<Cell<bool>>,
cascade_slot: CascadeSlot,
hovered_style: <E::Style as Refineable>::Refinement,
child: E,
}
pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
Hoverable {
hovered: Rc::new(Cell::new(false)),
cascade_slot: child.style_cascade().reserve(),
hovered_style: Default::default(),
child,
}
}
impl<E: Styleable> Styleable for Hoverable<E> {
type Style = E::Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
self.child.style_cascade()
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.hovered_style
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
type PaintState = E::PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
Ok(self.child.layout(view, cx)?)
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let bounds = layout.bounds + parent_origin;
self.hovered.set(bounds.contains_point(cx.mouse_position()));
let slot = self.cascade_slot;
let style = self.hovered.get().then_some(self.hovered_style.clone());
self.style_cascade().set(slot, style);
let hovered = self.hovered.clone();
cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
cx.bubble_event();
if bounds.contains_point(cx.mouse_position()) != hovered.get() {
cx.repaint();
}
});
self.child
.paint(view, parent_origin, layout, paint_state, cx);
}
}
impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
self.child.interaction_handlers()
}
}
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
self.child.children_mut()
}
}
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}

View File

@ -0,0 +1,103 @@
use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
use refineable::RefinementCascade;
use std::marker::PhantomData;
use util::arc_cow::ArcCow;
pub struct Img<S> {
style: RefinementCascade<Style>,
uri: Option<ArcCow<'static, str>>,
state_type: PhantomData<S>,
}
pub fn img<S>() -> Img<S> {
Img {
style: RefinementCascade::default(),
uri: None,
state_type: PhantomData,
}
}
impl<S> Img<S> {
pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
self.uri = Some(uri.into());
self
}
}
impl<S: 'static> Element for Img<S> {
type State = S;
type FrameState = ();
fn layout(
&mut self,
_: &mut Self::State,
cx: &mut crate::ViewContext<Self::State>,
) -> anyhow::Result<(LayoutId, Self::FrameState)>
where
Self: Sized,
{
let style = self.computed_style();
let layout_id = cx.request_layout(style, [])?;
Ok((layout_id, ()))
}
fn paint(
&mut self,
layout: Layout,
_: &mut Self::State,
_: &mut Self::FrameState,
cx: &mut crate::ViewContext<Self::State>,
) -> Result<()> {
let style = self.computed_style();
let order = layout.order;
let bounds = layout.bounds;
style.paint(order, bounds, cx);
// if let Some(uri) = &self.uri {
// let image_future = cx.image_cache.get(uri.clone());
// if let Some(data) = image_future
// .clone()
// .now_or_never()
// .and_then(ResultExt::log_err)
// {
// let rem_size = cx.rem_size();
// cx.scene().push_image(scene::Image {
// bounds,
// border: gpui::Border {
// color: style.border_color.unwrap_or_default().into(),
// top: style.border_widths.top.to_pixels(rem_size),
// right: style.border_widths.right.to_pixels(rem_size),
// bottom: style.border_widths.bottom.to_pixels(rem_size),
// left: style.border_widths.left.to_pixels(rem_size),
// },
// corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
// grayscale: false,
// data,
// })
// } else {
// cx.spawn(|this, mut cx| async move {
// if image_future.await.log_err().is_some() {
// this.update(&mut cx, |_, cx| cx.notify()).ok();
// }
// })
// .detach();
// }
// }
Ok(())
}
}
impl<S> Styled for Img<S> {
type Style = Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
&mut self.style
}
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
self.style.base()
}
}
impl<S> StyleHelpers for Img<S> {}

View File

@ -0,0 +1,108 @@
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
style::{Style, StyleHelpers, Styleable},
ViewContext,
};
use anyhow::Result;
use gpui::{geometry::vector::Vector2F, platform::MouseButtonEvent, LayoutId};
use refineable::{CascadeSlot, Refineable, RefinementCascade};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
pub struct Pressable<E: Styleable> {
pressed: Rc<Cell<bool>>,
pressed_style: <E::Style as Refineable>::Refinement,
cascade_slot: CascadeSlot,
child: E,
}
pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
Pressable {
pressed: Rc::new(Cell::new(false)),
pressed_style: Default::default(),
cascade_slot: child.style_cascade().reserve(),
child,
}
}
impl<E: Styleable> Styleable for Pressable<E> {
type Style = E::Style;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.pressed_style
}
fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
self.child.style_cascade()
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
type PaintState = E::PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
self.child.layout(view, cx)
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let slot = self.cascade_slot;
let style = self.pressed.get().then_some(self.pressed_style.clone());
self.style_cascade().set(slot, style);
let pressed = self.pressed.clone();
let bounds = layout.bounds + parent_origin;
cx.on_event(layout.order, move |_view, event: &MouseButtonEvent, cx| {
if event.is_down {
if bounds.contains_point(event.position) {
pressed.set(true);
cx.repaint();
}
} else if pressed.get() {
pressed.set(false);
cx.repaint();
}
});
self.child
.paint(view, parent_origin, layout, paint_state, cx);
}
}
impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
self.child.interaction_handlers()
}
}
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
self.child.children_mut()
}
}
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}

View File

@ -0,0 +1,30 @@
use crate::Element;
use std::marker::PhantomData;
pub struct Stateless<E: Element<State = ()>, S> {
element: E,
parent_state_type: PhantomData<S>,
}
impl<E: Element<State = ()>, S: Send + Sync + 'static> Element for Stateless<E, S> {
type State = S;
type FrameState = E::FrameState;
fn layout(
&mut self,
_: &mut Self::State,
cx: &mut crate::ViewContext<Self::State>,
) -> anyhow::Result<(crate::LayoutId, Self::FrameState)> {
cx.erase_state(|cx| self.element.layout(&mut (), cx))
}
fn paint(
&mut self,
layout: crate::Layout,
_: &mut Self::State,
frame_state: &mut Self::FrameState,
cx: &mut crate::ViewContext<Self::State>,
) -> anyhow::Result<()> {
cx.erase_state(|cx| self.element.paint(layout, &mut (), frame_state, cx))
}
}

View File

@ -0,0 +1,82 @@
use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
use refineable::RefinementCascade;
use std::{borrow::Cow, marker::PhantomData};
pub struct Svg<S> {
path: Option<Cow<'static, str>>,
style: RefinementCascade<Style>,
state_type: PhantomData<S>,
}
pub fn svg<S>() -> Svg<S> {
Svg {
path: None,
style: RefinementCascade::<Style>::default(),
state_type: PhantomData,
}
}
impl<S> Svg<S> {
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
self.path = Some(path.into());
self
}
}
impl<S: 'static> Element for Svg<S> {
type State = S;
type FrameState = ();
fn layout(
&mut self,
_: &mut S,
cx: &mut crate::ViewContext<S>,
) -> anyhow::Result<(LayoutId, Self::FrameState)>
where
Self: Sized,
{
let style = self.computed_style();
Ok((cx.request_layout(style, [])?, ()))
}
fn paint(
&mut self,
_layout: Layout,
_: &mut Self::State,
_: &mut Self::FrameState,
_cx: &mut crate::ViewContext<S>,
) -> Result<()>
where
Self: Sized,
{
// todo!
// let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
// if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
// if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
// let icon = scene::Icon {
// bounds: layout.bounds + parent_origin,
// svg: svg_tree,
// path: path.clone(),
// color: Rgba::from(fill_color).into(),
// };
// cx.scene().push_icon(icon);
// }
// }
Ok(())
}
}
impl<S> Styled for Svg<S> {
type Style = Style;
fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
&mut self.style
}
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
self.style.base()
}
}
impl<S> StyleHelpers for Svg<S> {}

View File

@ -0,0 +1,121 @@
use crate::{
AnyElement, Element, IntoAnyElement, Layout, LayoutId, Line, Pixels, Result, Size, ViewContext,
};
use parking_lot::Mutex;
use std::{marker::PhantomData, sync::Arc};
use util::{arc_cow::ArcCow, ResultExt};
impl<S: 'static> IntoAnyElement<S> for ArcCow<'static, str> {
fn into_any(self) -> AnyElement<S> {
Text {
text: self,
state_type: PhantomData,
}
.into_any()
}
}
impl<V: 'static> IntoAnyElement<V> for &'static str {
fn into_any(self) -> AnyElement<V> {
Text {
text: ArcCow::from(self),
state_type: PhantomData,
}
.into_any()
}
}
pub struct Text<S> {
text: ArcCow<'static, str>,
state_type: PhantomData<S>,
}
impl<S: 'static> Element for Text<S> {
type State = S;
type FrameState = Arc<Mutex<Option<TextLayout>>>;
fn layout(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> Result<(LayoutId, Self::FrameState)> {
dbg!("layout text");
let text_system = cx.text_system().clone();
let text_style = cx.text_style();
let font_size = text_style.font_size * cx.rem_size();
let line_height = text_style
.line_height
.to_pixels(font_size.into(), cx.rem_size());
let text = self.text.clone();
let paint_state = Arc::new(Mutex::new(None));
let rem_size = cx.rem_size();
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let frame_state = paint_state.clone();
move |_, _| {
dbg!("starting measurement");
let Some(line_layout) = text_system
.layout_line(
text.as_ref(),
font_size,
&[(text.len(), text_style.to_run())],
)
.log_err()
else {
return Size::default();
};
dbg!("bbbb");
let size = Size {
width: line_layout.width(),
height: line_height,
};
frame_state.lock().replace(TextLayout {
line: Arc::new(line_layout),
line_height,
});
dbg!(size)
}
});
dbg!("got to end of text layout");
Ok((layout_id?, paint_state))
}
fn paint<'a>(
&mut self,
layout: Layout,
_: &mut Self::State,
paint_state: &mut Self::FrameState,
cx: &mut ViewContext<S>,
) -> Result<()> {
let bounds = layout.bounds;
let line;
let line_height;
{
let paint_state = paint_state.lock();
let paint_state = paint_state
.as_ref()
.expect("measurement has not been performed");
line = paint_state.line.clone();
line_height = paint_state.line_height;
}
let _text_style = cx.text_style();
// todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
let visible_bounds = bounds;
line.paint(bounds.origin, visible_bounds, line_height, cx)?;
Ok(())
}
}
pub struct TextLayout {
line: Arc<Line>,
line_height: Pixels,
}

1095
crates/gpui3/src/executor.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,723 @@
use bytemuck::{Pod, Zeroable};
use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
use refineable::Refineable;
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign};
#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
#[refineable(debug)]
#[repr(C)]
pub struct Point<T: Clone + Debug> {
pub x: T,
pub y: T,
}
pub fn point<T: Clone + Debug>(x: T, y: T) -> Point<T> {
Point { x, y }
}
impl<T: Clone + Debug> Point<T> {
pub fn new(x: T, y: T) -> Self {
Self { x, y }
}
pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Point<U> {
Point {
x: f(self.x.clone()),
y: f(self.y.clone()),
}
}
}
impl<T, Rhs> Mul<Rhs> for Point<T>
where
T: Mul<Rhs, Output = Rhs> + Clone + Debug,
Rhs: Clone + Debug,
{
type Output = Point<Rhs>;
fn mul(self, rhs: Rhs) -> Self::Output {
Point {
x: self.x * rhs.clone(),
y: self.y * rhs,
}
}
}
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Point<T> {
fn mul_assign(&mut self, rhs: S) {
self.x = self.x.clone() * rhs.clone();
self.y = self.y.clone() * rhs;
}
}
impl<T: Clone + Debug + Sub<Output = T>> SubAssign<Size<T>> for Point<T> {
fn sub_assign(&mut self, rhs: Size<T>) {
self.x = self.x.clone() - rhs.width;
self.y = self.y.clone() - rhs.height;
}
}
impl<T: Clone + Debug + Add<Output = T> + Copy> AddAssign<T> for Point<T> {
fn add_assign(&mut self, rhs: T) {
self.x = self.x.clone() + rhs;
self.y = self.y.clone() + rhs;
}
}
impl<T: Clone + Debug + Div<S, Output = T>, S: Clone> Div<S> for Point<T> {
type Output = Self;
fn div(self, rhs: S) -> Self::Output {
Self {
x: self.x / rhs.clone(),
y: self.y / rhs,
}
}
}
impl<T: Clone + Debug + std::cmp::PartialOrd> Point<T> {
pub fn max(&self, other: &Self) -> Self {
Point {
x: if self.x >= other.x {
self.x.clone()
} else {
other.x.clone()
},
y: if self.y >= other.y {
self.y.clone()
} else {
other.y.clone()
},
}
}
}
impl<T: Clone + Debug> Clone for Point<T> {
fn clone(&self) -> Self {
Self {
x: self.x.clone(),
y: self.y.clone(),
}
}
}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div)]
#[refineable(debug)]
#[repr(C)]
pub struct Size<T: Clone + Debug> {
pub width: T,
pub height: T,
}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Size<T> {}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Size<T> {}
pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
Size { width, height }
}
impl<T: Clone + Debug> Size<T> {
pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Size<U> {
Size {
width: f(self.width.clone()),
height: f(self.height.clone()),
}
}
}
impl<T, Rhs> Mul<Rhs> for Size<T>
where
T: Mul<Rhs, Output = Rhs> + Debug + Clone,
Rhs: Debug + Clone,
{
type Output = Size<Rhs>;
fn mul(self, rhs: Rhs) -> Self::Output {
Size {
width: self.width * rhs.clone(),
height: self.height * rhs,
}
}
}
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
fn mul_assign(&mut self, rhs: S) {
self.width = self.width.clone() * rhs.clone();
self.height = self.height.clone() * rhs;
}
}
impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
fn from(val: Size<Option<Pixels>>) -> Self {
Size {
width: val.width.map(|p| p.0 as f32),
height: val.height.map(|p| p.0 as f32),
}
}
}
impl Size<Length> {
pub fn full() -> Self {
Self {
width: relative(1.).into(),
height: relative(1.).into(),
}
}
}
impl Size<DefiniteLength> {
pub fn zero() -> Self {
Self {
width: px(0.).into(),
height: px(0.).into(),
}
}
}
impl Size<Length> {
pub fn auto() -> Self {
Self {
width: Length::Auto,
height: Length::Auto,
}
}
}
#[derive(Refineable, Clone, Default, Debug, PartialEq)]
#[refineable(debug)]
#[repr(C)]
pub struct Bounds<T: Clone + Debug> {
pub origin: Point<T>,
pub size: Size<T>,
}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Bounds<T> {}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Bounds<T> {}
// Bounds<f32> * Pixels = Bounds<Pixels>
impl<T, Rhs> Mul<Rhs> for Bounds<T>
where
T: Mul<Rhs, Output = Rhs> + Clone + Debug,
Rhs: Clone + Debug,
{
type Output = Bounds<Rhs>;
fn mul(self, rhs: Rhs) -> Self::Output {
Bounds {
origin: self.origin * rhs.clone(),
size: self.size * rhs,
}
}
}
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Bounds<T> {
fn mul_assign(&mut self, rhs: S) {
self.origin *= rhs.clone();
self.size *= rhs;
}
}
impl<T: Clone + Debug + Div<S, Output = T>, S: Clone> Div<S> for Bounds<T>
where
Size<T>: Div<S, Output = Size<T>>,
{
type Output = Self;
fn div(self, rhs: S) -> Self {
Self {
origin: self.origin / rhs.clone(),
size: self.size / rhs,
}
}
}
impl<T: Clone + Debug + Add<T, Output = T>> Bounds<T> {
pub fn upper_right(&self) -> Point<T> {
Point {
x: self.origin.x.clone() + self.size.width.clone(),
y: self.origin.y.clone(),
}
}
pub fn lower_right(&self) -> Point<T> {
Point {
x: self.origin.x.clone() + self.size.width.clone(),
y: self.origin.y.clone() + self.size.height.clone(),
}
}
}
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
pub fn contains_point(&self, point: Point<T>) -> bool {
point.x >= self.origin.x
&& point.x <= self.origin.x.clone() + self.size.width.clone()
&& point.y >= self.origin.y
&& point.y <= self.origin.y.clone() + self.size.height.clone()
}
pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Bounds<U> {
Bounds {
origin: self.origin.map(&f),
size: self.size.map(f),
}
}
}
impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
#[derive(Refineable, Clone, Default, Debug)]
#[refineable(debug)]
#[repr(C)]
pub struct Edges<T: Clone + Debug> {
pub top: T,
pub right: T,
pub bottom: T,
pub left: T,
}
impl<T: Clone + Debug + Mul<Output = T>> Mul for Edges<T> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
top: self.top.clone() * rhs.top,
right: self.right.clone() * rhs.right,
bottom: self.bottom.clone() * rhs.bottom,
left: self.left.clone() * rhs.left,
}
}
}
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Edges<T> {
fn mul_assign(&mut self, rhs: S) {
self.top = self.top.clone() * rhs.clone();
self.right = self.right.clone() * rhs.clone();
self.bottom = self.bottom.clone() * rhs.clone();
self.left = self.left.clone() * rhs.clone();
}
}
impl<T: Clone + Debug + Copy> Copy for Edges<T> {}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Edges<T> {}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Edges<T> {}
impl<T: Clone + Debug> Edges<T> {
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Edges<U> {
Edges {
top: f(&self.top),
right: f(&self.right),
bottom: f(&self.bottom),
left: f(&self.left),
}
}
pub fn any<F: Fn(&T) -> bool>(&self, predicate: F) -> bool {
predicate(&self.top)
|| predicate(&self.right)
|| predicate(&self.bottom)
|| predicate(&self.left)
}
}
impl Edges<Length> {
pub fn auto() -> Self {
Self {
top: Length::Auto,
right: Length::Auto,
bottom: Length::Auto,
left: Length::Auto,
}
}
pub fn zero() -> Self {
Self {
top: px(0.).into(),
right: px(0.).into(),
bottom: px(0.).into(),
left: px(0.).into(),
}
}
}
impl Edges<DefiniteLength> {
pub fn zero() -> Self {
Self {
top: px(0.).into(),
right: px(0.).into(),
bottom: px(0.).into(),
left: px(0.).into(),
}
}
}
impl Edges<AbsoluteLength> {
pub fn zero() -> Self {
Self {
top: px(0.).into(),
right: px(0.).into(),
bottom: px(0.).into(),
left: px(0.).into(),
}
}
pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
Edges {
top: self.top.to_pixels(rem_size),
right: self.right.to_pixels(rem_size),
bottom: self.bottom.to_pixels(rem_size),
left: self.left.to_pixels(rem_size),
}
}
}
#[derive(Refineable, Clone, Default, Debug)]
#[refineable(debug)]
#[repr(C)]
pub struct Corners<T: Clone + Debug> {
pub top_left: T,
pub top_right: T,
pub bottom_right: T,
pub bottom_left: T,
}
impl<T: Clone + Debug> Corners<T> {
pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Corners<U> {
Corners {
top_left: f(&self.top_left),
top_right: f(&self.top_right),
bottom_right: f(&self.bottom_right),
bottom_left: f(&self.bottom_left),
}
}
}
impl<T: Clone + Debug + Mul<Output = T>> Mul for Corners<T> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
top_left: self.top_left.clone() * rhs.top_left,
top_right: self.top_right.clone() * rhs.top_right,
bottom_right: self.bottom_right.clone() * rhs.bottom_right,
bottom_left: self.bottom_left.clone() * rhs.bottom_left,
}
}
}
impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Corners<T> {
fn mul_assign(&mut self, rhs: S) {
self.top_left = self.top_left.clone() * rhs.clone();
self.top_right = self.top_right.clone() * rhs.clone();
self.bottom_right = self.bottom_right.clone() * rhs.clone();
self.bottom_left = self.bottom_left.clone() * rhs;
}
}
impl<T: Clone + Debug + Copy> Copy for Corners<T> {}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Corners<T> {}
unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Corners<T> {}
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Pixels(pub(crate) f32);
impl Mul<f32> for Pixels {
type Output = Pixels;
fn mul(self, other: f32) -> Pixels {
Pixels(self.0 * other)
}
}
impl Mul<Pixels> for f32 {
type Output = Pixels;
fn mul(self, rhs: Pixels) -> Self::Output {
Pixels(self * rhs.0)
}
}
impl Pixels {
pub fn round(&self) -> Self {
Self(self.0.round())
}
pub fn to_device_pixels(&self, scale: f32) -> DevicePixels {
DevicePixels((self.0 * scale).ceil() as u32)
}
}
impl Mul<Pixels> for Pixels {
type Output = Pixels;
fn mul(self, rhs: Pixels) -> Self::Output {
Pixels(self.0 * rhs.0)
}
}
impl Eq for Pixels {}
impl Ord for Pixels {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.partial_cmp(&other.0).unwrap()
}
}
impl std::hash::Hash for Pixels {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl From<f64> for Pixels {
fn from(val: f64) -> Self {
Pixels(val as f32)
}
}
impl From<f32> for Pixels {
fn from(val: f32) -> Self {
Pixels(val)
}
}
unsafe impl bytemuck::Pod for Pixels {}
unsafe impl bytemuck::Zeroable for Pixels {}
impl Debug for Pixels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} px", self.0)
}
}
impl From<Pixels> for f32 {
fn from(pixels: Pixels) -> Self {
pixels.0
}
}
impl From<&Pixels> for f32 {
fn from(pixels: &Pixels) -> Self {
pixels.0
}
}
impl From<Pixels> for f64 {
fn from(pixels: Pixels) -> Self {
pixels.0 as f64
}
}
#[derive(
Clone, Copy, Debug, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd,
)]
#[repr(transparent)]
pub struct DevicePixels(pub(crate) u32);
unsafe impl bytemuck::Pod for DevicePixels {}
unsafe impl bytemuck::Zeroable for DevicePixels {}
impl From<DevicePixels> for u32 {
fn from(device_pixels: DevicePixels) -> Self {
device_pixels.0
}
}
impl From<u32> for DevicePixels {
fn from(val: u32) -> Self {
DevicePixels(val)
}
}
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
pub struct Rems(f32);
impl Mul<Pixels> for Rems {
type Output = Pixels;
fn mul(self, other: Pixels) -> Pixels {
Pixels(self.0 * other.0)
}
}
impl Debug for Rems {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} rem", self.0)
}
}
#[derive(Clone, Copy, Debug)]
pub enum AbsoluteLength {
Pixels(Pixels),
Rems(Rems),
}
impl AbsoluteLength {
pub fn is_zero(&self) -> bool {
match self {
AbsoluteLength::Pixels(px) => px.0 == 0.,
AbsoluteLength::Rems(rems) => rems.0 == 0.,
}
}
}
impl From<Pixels> for AbsoluteLength {
fn from(pixels: Pixels) -> Self {
AbsoluteLength::Pixels(pixels)
}
}
impl From<Rems> for AbsoluteLength {
fn from(rems: Rems) -> Self {
AbsoluteLength::Rems(rems)
}
}
impl AbsoluteLength {
pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
match self {
AbsoluteLength::Pixels(pixels) => *pixels,
AbsoluteLength::Rems(rems) => *rems * rem_size,
}
}
}
impl Default for AbsoluteLength {
fn default() -> Self {
px(0.).into()
}
}
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
#[derive(Clone, Copy)]
pub enum DefiniteLength {
Absolute(AbsoluteLength),
/// A fraction of the parent's size between 0 and 1.
Fraction(f32),
}
impl DefiniteLength {
pub fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
match self {
DefiniteLength::Absolute(size) => size.to_pixels(rem_size),
DefiniteLength::Fraction(fraction) => match base_size {
AbsoluteLength::Pixels(px) => px * *fraction,
AbsoluteLength::Rems(rems) => rems * rem_size * *fraction,
},
}
}
}
impl Debug for DefiniteLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
}
}
}
impl From<Pixels> for DefiniteLength {
fn from(pixels: Pixels) -> Self {
Self::Absolute(pixels.into())
}
}
impl From<Rems> for DefiniteLength {
fn from(rems: Rems) -> Self {
Self::Absolute(rems.into())
}
}
impl From<AbsoluteLength> for DefiniteLength {
fn from(length: AbsoluteLength) -> Self {
Self::Absolute(length)
}
}
impl Default for DefiniteLength {
fn default() -> Self {
Self::Absolute(AbsoluteLength::default())
}
}
/// A length that can be defined in pixels, rems, percent of parent, or auto.
#[derive(Clone, Copy)]
pub enum Length {
Definite(DefiniteLength),
Auto,
}
impl Debug for Length {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
Length::Auto => write!(f, "auto"),
}
}
}
pub fn relative(fraction: f32) -> DefiniteLength {
DefiniteLength::Fraction(fraction).into()
}
/// Returns the Golden Ratio, i.e. `~(1.0 + sqrt(5.0)) / 2.0`.
pub fn phi() -> DefiniteLength {
relative(1.61803398875)
}
pub fn rems(rems: f32) -> Rems {
Rems(rems)
}
pub fn px(pixels: f32) -> Pixels {
Pixels(pixels)
}
pub fn auto() -> Length {
Length::Auto
}
impl From<Pixels> for Length {
fn from(pixels: Pixels) -> Self {
Self::Definite(pixels.into())
}
}
impl From<Rems> for Length {
fn from(rems: Rems) -> Self {
Self::Definite(rems.into())
}
}
impl From<DefiniteLength> for Length {
fn from(length: DefiniteLength) -> Self {
Self::Definite(length)
}
}
impl From<AbsoluteLength> for Length {
fn from(length: AbsoluteLength) -> Self {
Self::Definite(length.into())
}
}
impl Default for Length {
fn default() -> Self {
Self::Definite(DefiniteLength::default())
}
}
impl From<()> for Length {
fn from(_: ()) -> Self {
Self::Definite(DefiniteLength::default())
}
}

207
crates/gpui3/src/gpui3.rs Normal file
View File

@ -0,0 +1,207 @@
mod app;
mod color;
mod element;
mod elements;
mod executor;
mod geometry;
mod platform;
mod scene;
mod style;
mod style_helpers;
mod styled;
mod taffy;
mod text_system;
mod util;
mod view;
mod window;
pub use anyhow::Result;
pub use app::*;
pub use color::*;
pub use element::*;
pub use elements::*;
pub use executor::*;
pub use geometry::*;
pub use gpui3_macros::*;
pub use platform::*;
pub use refineable::*;
pub use scene::*;
pub use serde;
pub use serde_json;
pub use smallvec;
pub use smol::Timer;
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
pub use style::*;
pub use style_helpers::*;
pub use styled::*;
use taffy::TaffyLayoutEngine;
pub use taffy::{AvailableSpace, LayoutId};
pub use text_system::*;
pub use util::arc_cow::ArcCow;
pub use view::*;
pub use window::*;
pub trait Context {
type EntityContext<'a, 'w, T: 'static + Send + Sync>;
type Result<T>;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Self::Result<Handle<T>>;
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> Self::Result<R>;
}
#[repr(transparent)]
pub struct MainThread<T>(T);
impl<T> MainThread<T> {
fn new(value: T) -> Self {
Self(value)
}
}
impl<T> Deref for MainThread<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for MainThread<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub trait StackContext {
fn app(&mut self) -> &mut AppContext;
fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.app().push_text_style(style);
let result = f(self);
self.app().pop_text_style();
result
}
fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.app().push_state(state);
let result = f(self);
self.app().pop_state::<T>();
result
}
}
pub trait Flatten<T> {
fn flatten(self) -> Result<T>;
}
impl<T> Flatten<T> for Result<Result<T>> {
fn flatten(self) -> Result<T> {
self?
}
}
impl<T> Flatten<T> for Result<T> {
fn flatten(self) -> Result<T> {
self
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct SharedString(ArcCow<'static, str>);
impl Default for SharedString {
fn default() -> Self {
Self(ArcCow::Owned("".into()))
}
}
impl AsRef<str> for SharedString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Debug for SharedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
fn from(value: T) -> Self {
Self(value.into())
}
}
pub enum Reference<'a, T> {
Immutable(&'a T),
Mutable(&'a mut T),
}
impl<'a, T> Deref for Reference<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Reference::Immutable(target) => target,
Reference::Mutable(target) => target,
}
}
}
impl<'a, T> DerefMut for Reference<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Reference::Immutable(_) => {
panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
}
Reference::Mutable(target) => target,
}
}
}
pub(crate) struct MainThreadOnly<T: ?Sized> {
dispatcher: Arc<dyn PlatformDispatcher>,
value: Arc<T>,
}
impl<T: ?Sized> Clone for MainThreadOnly<T> {
fn clone(&self) -> Self {
Self {
dispatcher: self.dispatcher.clone(),
value: self.value.clone(),
}
}
}
/// Allows a value to be accessed only on the main thread, allowing a non-`Send` type
/// to become `Send`.
impl<T: 'static + ?Sized> MainThreadOnly<T> {
pub(crate) fn new(value: Arc<T>, dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher, value }
}
pub(crate) fn borrow_on_main_thread(&self) -> &T {
assert!(self.dispatcher.is_main_thread());
&self.value
}
}
unsafe impl<T: ?Sized> Send for MainThreadOnly<T> {}

View File

@ -0,0 +1,399 @@
mod events;
mod keystroke;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(any(test, feature = "test"))]
mod test;
use crate::{
AnyWindowHandle, Bounds, Font, FontId, FontMetrics, GlyphId, LineLayout, Pixels, Point, Result,
Scene, SharedString, Size,
};
use anyhow::anyhow;
use async_task::Runnable;
use futures::channel::oneshot;
use seahash::SeaHasher;
use serde::{Deserialize, Serialize};
use std::ffi::c_void;
use std::hash::{Hash, Hasher};
use std::{
any::Any,
fmt::{self, Debug, Display},
ops::Range,
path::{Path, PathBuf},
rc::Rc,
str::FromStr,
sync::Arc,
};
use uuid::Uuid;
pub use events::*;
pub use keystroke::*;
#[cfg(target_os = "macos")]
pub use mac::*;
#[cfg(any(test, feature = "test"))]
pub use test::*;
pub use time::UtcOffset;
#[cfg(target_os = "macos")]
pub(crate) fn current_platform() -> Arc<dyn Platform> {
Arc::new(MacPlatform::new())
}
pub trait Platform: 'static {
fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
fn quit(&self);
fn restart(&self);
fn activate(&self, ignoring_other_apps: bool);
fn hide(&self);
fn hide_other_apps(&self);
fn unhide_other_apps(&self);
fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>;
fn main_window(&self) -> Option<AnyWindowHandle>;
fn open_window(
&self,
handle: AnyWindowHandle,
options: WindowOptions,
) -> Box<dyn PlatformWindow>;
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
fn open_url(&self, url: &str);
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
fn reveal_path(&self, path: &Path);
fn on_become_active(&self, callback: Box<dyn FnMut()>);
fn on_resign_active(&self, callback: Box<dyn FnMut()>);
fn on_quit(&self, callback: Box<dyn FnMut()>);
fn on_reopen(&self, callback: Box<dyn FnMut()>);
fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
fn os_name(&self) -> &'static str;
fn os_version(&self) -> Result<SemanticVersion>;
fn app_version(&self) -> Result<SemanticVersion>;
fn app_path(&self) -> Result<PathBuf>;
fn local_timezone(&self) -> UtcOffset;
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
fn set_cursor_style(&self, style: CursorStyle);
fn should_auto_hide_scrollbars(&self) -> bool;
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
fn delete_credentials(&self, url: &str) -> Result<()>;
}
pub trait PlatformScreen: Debug {
fn id(&self) -> Option<ScreenId>;
fn handle(&self) -> PlatformScreenHandle;
fn as_any(&self) -> &dyn Any;
fn bounds(&self) -> Bounds<Pixels>;
fn content_bounds(&self) -> Bounds<Pixels>;
}
pub struct PlatformScreenHandle(pub *mut c_void);
impl Debug for PlatformScreenHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PlatformScreenHandle({:p})", self.0)
}
}
unsafe impl Send for PlatformScreenHandle {}
pub trait PlatformWindow {
fn bounds(&self) -> WindowBounds;
fn content_size(&self) -> Size<Pixels>;
fn scale_factor(&self) -> f32;
fn titlebar_height(&self) -> Pixels;
fn appearance(&self) -> WindowAppearance;
fn screen(&self) -> Rc<dyn PlatformScreen>;
fn mouse_position(&self) -> Point<Pixels>;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
fn prompt(
&self,
level: WindowPromptLevel,
msg: &str,
answers: &[&str],
) -> oneshot::Receiver<usize>;
fn activate(&self);
fn set_title(&mut self, title: &str);
fn set_edited(&mut self, edited: bool);
fn show_character_palette(&self);
fn minimize(&self);
fn zoom(&self);
fn toggle_full_screen(&self);
fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
fn on_moved(&self, callback: Box<dyn FnMut()>);
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 is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
fn draw(&self, scene: Scene);
}
pub trait PlatformDispatcher: Send + Sync {
fn is_main_thread(&self) -> bool;
fn run_on_main_thread(&self, task: Runnable);
}
pub trait PlatformTextSystem: Send + Sync {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
fn all_font_families(&self) -> Vec<String>;
fn font_id(&self, descriptor: &Font) -> Result<FontId>;
fn font_metrics(&self, font_id: FontId) -> FontMetrics;
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
fn rasterize_glyph(
&self,
font_id: FontId,
font_size: f32,
glyph_id: GlyphId,
subpixel_shift: Point<Pixels>,
scale_factor: f32,
options: RasterizationOptions,
) -> Option<(Bounds<u32>, Vec<u8>)>;
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
fn wrap_line(
&self,
text: &str,
font_id: FontId,
font_size: Pixels,
width: Pixels,
) -> Vec<usize>;
}
pub trait InputHandler {
fn selected_text_range(&self) -> Option<Range<usize>>;
fn marked_text_range(&self) -> Option<Range<usize>>;
fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
);
fn unmark_text(&mut self);
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScreenId(pub(crate) Uuid);
#[derive(Copy, Clone, Debug)]
pub enum RasterizationOptions {
Alpha,
Bgra,
}
#[derive(Debug)]
pub struct WindowOptions {
pub bounds: WindowBounds,
pub titlebar: Option<TitlebarOptions>,
pub center: bool,
pub focus: bool,
pub show: bool,
pub kind: WindowKind,
pub is_movable: bool,
pub screen: Option<PlatformScreenHandle>,
}
impl Default for WindowOptions {
fn default() -> Self {
Self {
bounds: WindowBounds::default(),
titlebar: Some(TitlebarOptions {
title: Default::default(),
appears_transparent: Default::default(),
traffic_light_position: Default::default(),
}),
center: false,
focus: true,
show: true,
kind: WindowKind::Normal,
is_movable: true,
screen: None,
}
}
}
#[derive(Debug, Default)]
pub struct TitlebarOptions {
pub title: Option<SharedString>,
pub appears_transparent: bool,
pub traffic_light_position: Option<Point<Pixels>>,
}
#[derive(Copy, Clone, Debug)]
pub enum Appearance {
Light,
VibrantLight,
Dark,
VibrantDark,
}
impl Default for Appearance {
fn default() -> Self {
Self::Light
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WindowKind {
Normal,
PopUp,
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum WindowBounds {
Fullscreen,
#[default]
Maximized,
Fixed(Bounds<Pixels>),
}
#[derive(Copy, Clone, Debug)]
pub enum WindowAppearance {
Light,
VibrantLight,
Dark,
VibrantDark,
}
impl Default for WindowAppearance {
fn default() -> Self {
Self::Light
}
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum WindowPromptLevel {
#[default]
Info,
Warning,
Critical,
}
#[derive(Copy, Clone, Debug)]
pub struct PathPromptOptions {
pub files: bool,
pub directories: bool,
pub multiple: bool,
}
#[derive(Copy, Clone, Debug)]
pub enum PromptLevel {
Info,
Warning,
Critical,
}
#[derive(Copy, Clone, Debug)]
pub enum CursorStyle {
Arrow,
ResizeLeftRight,
ResizeUpDown,
PointingHand,
IBeam,
}
impl Default for CursorStyle {
fn default() -> Self {
Self::Arrow
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct SemanticVersion {
major: usize,
minor: usize,
patch: usize,
}
impl FromStr for SemanticVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut components = s.trim().split('.');
let major = components
.next()
.ok_or_else(|| anyhow!("missing major version number"))?
.parse()?;
let minor = components
.next()
.ok_or_else(|| anyhow!("missing minor version number"))?
.parse()?;
let patch = components
.next()
.ok_or_else(|| anyhow!("missing patch version number"))?
.parse()?;
Ok(Self {
major,
minor,
patch,
})
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {
pub(crate) text: String,
pub(crate) metadata: Option<String>,
}
impl ClipboardItem {
pub fn new(text: String) -> Self {
Self {
text,
metadata: None,
}
}
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
self
}
pub fn text(&self) -> &String {
&self.text
}
pub fn metadata<T>(&self) -> Option<T>
where
T: for<'a> Deserialize<'a>,
{
self.metadata
.as_ref()
.and_then(|m| serde_json::from_str(m).ok())
}
pub(crate) fn text_hash(text: &str) -> u64 {
let mut hasher = SeaHasher::new();
text.hash(&mut hasher);
hasher.finish()
}
}

View File

@ -0,0 +1,204 @@
use crate::{point, Keystroke, Modifiers, Pixels, Point};
use std::{any::Any, ops::Deref};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeyDownEvent {
pub keystroke: Keystroke,
pub is_held: bool,
}
#[derive(Clone, Debug)]
pub struct KeyUpEvent {
pub keystroke: Keystroke,
}
#[derive(Clone, Debug, Default)]
pub struct ModifiersChangedEvent {
pub modifiers: Modifiers,
}
impl Deref for ModifiersChangedEvent {
type Target = Modifiers;
fn deref(&self) -> &Self::Target {
&self.modifiers
}
}
/// The phase of a touch motion event.
/// Based on the winit enum of the same name,
#[derive(Clone, Copy, Debug)]
pub enum TouchPhase {
Started,
Moved,
Ended,
}
#[derive(Clone, Copy, Debug)]
pub enum ScrollDelta {
Pixels(Point<Pixels>),
Lines(Point<f32>),
}
impl Default for ScrollDelta {
fn default() -> Self {
Self::Lines(Default::default())
}
}
impl ScrollDelta {
pub fn precise(&self) -> bool {
match self {
ScrollDelta::Pixels(_) => true,
ScrollDelta::Lines(_) => false,
}
}
pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
match self {
ScrollDelta::Pixels(delta) => *delta,
ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ScrollWheelEvent {
pub position: Point<Pixels>,
pub delta: ScrollDelta,
pub modifiers: Modifiers,
/// If the platform supports returning the phase of a scroll wheel event, it will be stored here
pub phase: Option<TouchPhase>,
}
impl Deref for ScrollWheelEvent {
type Target = Modifiers;
fn deref(&self) -> &Self::Target {
&self.modifiers
}
}
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum NavigationDirection {
Back,
Forward,
}
impl Default for NavigationDirection {
fn default() -> Self {
Self::Back
}
}
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum MouseButton {
Left,
Right,
Middle,
Navigate(NavigationDirection),
}
impl MouseButton {
pub fn all() -> Vec<Self> {
vec![
MouseButton::Left,
MouseButton::Right,
MouseButton::Middle,
MouseButton::Navigate(NavigationDirection::Back),
MouseButton::Navigate(NavigationDirection::Forward),
]
}
}
impl Default for MouseButton {
fn default() -> Self {
Self::Left
}
}
#[derive(Clone, Debug, Default)]
pub struct MouseDownEvent {
pub button: MouseButton,
pub position: Point<Pixels>,
pub modifiers: Modifiers,
pub click_count: usize,
}
#[derive(Clone, Debug, Default)]
pub struct MouseUpEvent {
pub button: MouseButton,
pub position: Point<Pixels>,
pub modifiers: Modifiers,
pub click_count: usize,
}
#[derive(Clone, Debug, Default)]
pub struct MouseUp {
pub button: MouseButton,
pub position: Point<Pixels>,
pub modifiers: Modifiers,
pub click_count: usize,
}
#[derive(Clone, Debug, Default)]
pub struct MouseMovedEvent {
pub position: Point<Pixels>,
pub pressed_button: Option<MouseButton>,
pub modifiers: Modifiers,
}
#[derive(Clone, Debug, Default)]
pub struct MouseExitedEvent {
pub position: Point<Pixels>,
pub pressed_button: Option<MouseButton>,
pub modifiers: Modifiers,
}
impl Deref for MouseExitedEvent {
type Target = Modifiers;
fn deref(&self) -> &Self::Target {
&self.modifiers
}
}
#[derive(Clone, Debug)]
pub enum Event {
KeyDown(KeyDownEvent),
KeyUp(KeyUpEvent),
ModifiersChanged(ModifiersChangedEvent),
MouseDown(MouseDownEvent),
MouseUp(MouseUpEvent),
MouseMoved(MouseMovedEvent),
MouseExited(MouseExitedEvent),
ScrollWheel(ScrollWheelEvent),
}
impl Event {
pub fn position(&self) -> Option<Point<Pixels>> {
match self {
Event::KeyDown { .. } => None,
Event::KeyUp { .. } => None,
Event::ModifiersChanged { .. } => None,
Event::MouseDown(event) => Some(event.position),
Event::MouseUp(event) => Some(event.position),
Event::MouseMoved(event) => Some(event.position),
Event::MouseExited(event) => Some(event.position),
Event::ScrollWheel(event) => Some(event.position),
}
}
pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
match self {
Event::KeyDown { .. } => None,
Event::KeyUp { .. } => None,
Event::ModifiersChanged { .. } => None,
Event::MouseDown(event) => Some(event),
Event::MouseUp(event) => Some(event),
Event::MouseMoved(event) => Some(event),
Event::MouseExited(event) => Some(event),
Event::ScrollWheel(event) => Some(event),
}
}
}

View File

@ -0,0 +1,121 @@
use anyhow::anyhow;
use serde::Deserialize;
use std::fmt::Write;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Modifiers {
pub control: bool,
pub alt: bool,
pub shift: bool,
pub command: bool,
pub function: bool,
}
impl Modifiers {
pub fn modified(&self) -> bool {
self.control || self.alt || self.shift || self.command || self.function
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Hash)]
pub struct Keystroke {
pub key: String,
pub modifiers: Modifiers,
}
impl Keystroke {
pub fn parse(source: &str) -> anyhow::Result<Self> {
let mut control = false;
let mut alt = false;
let mut shift = false;
let mut command = false;
let mut function = false;
let mut key = None;
let mut components = source.split('-').peekable();
while let Some(component) = components.next() {
match component {
"ctrl" => control = true,
"alt" => alt = true,
"shift" => shift = true,
"cmd" => command = true,
"fn" => function = true,
_ => {
if let Some(component) = components.peek() {
if component.is_empty() && source.ends_with('-') {
key = Some(String::from("-"));
break;
} else {
return Err(anyhow!("Invalid keystroke `{}`", source));
}
} else {
key = Some(String::from(component));
}
}
}
}
let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
Ok(Keystroke {
modifiers: Modifiers {
control,
alt,
shift,
command,
function,
},
key,
})
}
pub fn modified(&self) -> bool {
self.modifiers.modified()
}
}
impl std::fmt::Display for Keystroke {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Modifiers {
control,
alt,
shift,
command,
function,
} = self.modifiers;
if control {
f.write_char('^')?;
}
if alt {
f.write_char('⎇')?;
}
if command {
f.write_char('⌘')?;
}
if shift {
f.write_char('⇧')?;
}
if function {
f.write_char('𝙛')?;
}
let key = match self.key.as_str() {
"backspace" => '⌫',
"up" => '↑',
"down" => '↓',
"left" => '←',
"right" => '→',
"tab" => '⇥',
"escape" => '⎋',
key => {
if key.len() == 1 {
key.chars().next().unwrap().to_ascii_uppercase()
} else {
return f.write_str(key);
}
}
};
f.write_char(key)
}
}

View File

@ -0,0 +1,151 @@
///! Macos screen have a y axis that goings up from the bottom of the screen and
///! an origin at the bottom left of the main display.
mod dispatcher;
mod events;
mod metal_renderer;
mod open_type;
mod platform;
mod screen;
mod text_system;
mod window;
mod window_appearence;
use crate::{px, size, Pixels, Size};
use anyhow::anyhow;
use cocoa::{
base::{id, nil},
foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger, NSURL},
};
use metal_renderer::*;
use objc::{
msg_send,
runtime::{BOOL, NO, YES},
sel, sel_impl,
};
use std::{
ffi::{c_char, CStr, OsStr},
ops::Range,
os::unix::prelude::OsStrExt,
path::PathBuf,
};
pub use dispatcher::*;
pub use platform::*;
pub use screen::*;
pub use text_system::*;
pub use window::*;
trait BoolExt {
fn to_objc(self) -> BOOL;
}
impl BoolExt for bool {
fn to_objc(self) -> BOOL {
if self {
YES
} else {
NO
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
struct NSRange {
pub location: NSUInteger,
pub length: NSUInteger,
}
impl NSRange {
fn invalid() -> Self {
Self {
location: NSNotFound as NSUInteger,
length: 0,
}
}
fn is_valid(&self) -> bool {
self.location != NSNotFound as NSUInteger
}
fn to_range(self) -> Option<Range<usize>> {
if self.is_valid() {
let start = self.location as usize;
let end = start + self.length as usize;
Some(start..end)
} else {
None
}
}
}
impl From<Range<usize>> for NSRange {
fn from(range: Range<usize>) -> Self {
NSRange {
location: range.start as NSUInteger,
length: range.len() as NSUInteger,
}
}
}
unsafe impl objc::Encode for NSRange {
fn encode() -> objc::Encoding {
let encoding = format!(
"{{NSRange={}{}}}",
NSUInteger::encode().as_str(),
NSUInteger::encode().as_str()
);
unsafe { objc::Encoding::from_str(&encoding) }
}
}
unsafe fn ns_string(string: &str) -> id {
NSString::alloc(nil).init_str(string).autorelease()
}
impl From<NSSize> for Size<Pixels> {
fn from(value: NSSize) -> Self {
Size {
width: px(value.width as f32),
height: px(value.height as f32),
}
}
}
pub trait NSRectExt {
fn size(&self) -> Size<Pixels>;
fn intersects(&self, other: Self) -> bool;
}
impl NSRectExt for NSRect {
fn size(&self) -> Size<Pixels> {
size(px(self.size.width as f32), px(self.size.height as f32))
}
fn intersects(&self, other: Self) -> bool {
self.size.width > 0.
&& self.size.height > 0.
&& other.size.width > 0.
&& other.size.height > 0.
&& self.origin.x <= other.origin.x + other.size.width
&& self.origin.x + self.size.width >= other.origin.x
&& self.origin.y <= other.origin.y + other.size.height
&& self.origin.y + self.size.height >= other.origin.y
}
}
// todo!
#[allow(unused)]
unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
let path: *mut c_char = msg_send![url, fileSystemRepresentation];
if path.is_null() {
Err(anyhow!(
"url is not a file path: {}",
CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
))
} else {
Ok(PathBuf::from(OsStr::from_bytes(
CStr::from_ptr(path).to_bytes(),
)))
}
}

View File

@ -0,0 +1 @@
#include <dispatch/dispatch.h>

View File

@ -0,0 +1,42 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::PlatformDispatcher;
use async_task::Runnable;
use objc::{
class, msg_send,
runtime::{BOOL, YES},
sel, sel_impl,
};
use std::ffi::c_void;
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 MacDispatcher;
impl PlatformDispatcher for MacDispatcher {
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();
}
}
}

View File

@ -0,0 +1,356 @@
use crate::{
point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
MouseButton, MouseDownEvent, MouseExitedEvent, MouseMovedEvent, MouseUpEvent,
NavigationDirection, Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
};
use cocoa::{
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
base::{id, YES},
foundation::NSString as _,
};
use core_graphics::{
event::{CGEvent, CGEventFlags, CGKeyCode},
event_source::{CGEventSource, CGEventSourceStateID},
};
use ctor::ctor;
use foreign_types::ForeignType;
use objc::{class, msg_send, sel, sel_impl};
use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
const BACKSPACE_KEY: u16 = 0x7f;
const SPACE_KEY: u16 = b' ' as u16;
const ENTER_KEY: u16 = 0x0d;
const NUMPAD_ENTER_KEY: u16 = 0x03;
const ESCAPE_KEY: u16 = 0x1b;
const TAB_KEY: u16 = 0x09;
const SHIFT_TAB_KEY: u16 = 0x19;
static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
#[ctor]
unsafe fn build_event_source() {
let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
EVENT_SOURCE = source.as_ptr();
mem::forget(source);
}
// todo!
#[allow(unused)]
pub fn key_to_native(key: &str) -> Cow<str> {
use cocoa::appkit::*;
let code = match key {
"space" => SPACE_KEY,
"backspace" => BACKSPACE_KEY,
"up" => NSUpArrowFunctionKey,
"down" => NSDownArrowFunctionKey,
"left" => NSLeftArrowFunctionKey,
"right" => NSRightArrowFunctionKey,
"pageup" => NSPageUpFunctionKey,
"pagedown" => NSPageDownFunctionKey,
"home" => NSHomeFunctionKey,
"end" => NSEndFunctionKey,
"delete" => NSDeleteFunctionKey,
"f1" => NSF1FunctionKey,
"f2" => NSF2FunctionKey,
"f3" => NSF3FunctionKey,
"f4" => NSF4FunctionKey,
"f5" => NSF5FunctionKey,
"f6" => NSF6FunctionKey,
"f7" => NSF7FunctionKey,
"f8" => NSF8FunctionKey,
"f9" => NSF9FunctionKey,
"f10" => NSF10FunctionKey,
"f11" => NSF11FunctionKey,
"f12" => NSF12FunctionKey,
_ => return Cow::Borrowed(key),
};
Cow::Owned(String::from_utf16(&[code]).unwrap())
}
unsafe fn read_modifiers(native_event: id) -> Modifiers {
let modifiers = native_event.modifierFlags();
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
Modifiers {
control,
alt,
shift,
command,
function,
}
}
impl Event {
pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
let event_type = native_event.eventType();
// Filter out event types that aren't in the NSEventType enum.
// See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
match event_type as u64 {
0 | 21 | 32 | 33 | 35 | 36 | 37 => {
return None;
}
_ => {}
}
match event_type {
NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
modifiers: read_modifiers(native_event),
})),
NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
keystroke: parse_keystroke(native_event),
is_held: native_event.isARepeat() == YES,
})),
NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
keystroke: parse_keystroke(native_event),
})),
NSEventType::NSLeftMouseDown
| NSEventType::NSRightMouseDown
| NSEventType::NSOtherMouseDown => {
let button = match native_event.buttonNumber() {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Navigate(NavigationDirection::Back),
4 => MouseButton::Navigate(NavigationDirection::Forward),
// Other mouse buttons aren't tracked currently
_ => return None,
};
window_height.map(|window_height| {
Self::MouseDown(MouseDownEvent {
button,
position: point(
px(native_event.locationInWindow().x as f32),
// MacOS screen coordinates are relative to bottom left
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
click_count: native_event.clickCount() as usize,
})
})
}
NSEventType::NSLeftMouseUp
| NSEventType::NSRightMouseUp
| NSEventType::NSOtherMouseUp => {
let button = match native_event.buttonNumber() {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Navigate(NavigationDirection::Back),
4 => MouseButton::Navigate(NavigationDirection::Forward),
// Other mouse buttons aren't tracked currently
_ => return None,
};
window_height.map(|window_height| {
Self::MouseUp(MouseUpEvent {
button,
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
click_count: native_event.clickCount() as usize,
})
})
}
NSEventType::NSScrollWheel => window_height.map(|window_height| {
let phase = match native_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
Some(TouchPhase::Started)
}
NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
_ => Some(TouchPhase::Moved),
};
let raw_data = point(
native_event.scrollingDeltaX() as f32,
native_event.scrollingDeltaY() as f32,
);
let delta = if native_event.hasPreciseScrollingDeltas() == YES {
ScrollDelta::Pixels(raw_data.map(px))
} else {
ScrollDelta::Lines(raw_data)
};
Self::ScrollWheel(ScrollWheelEvent {
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
delta,
phase,
modifiers: read_modifiers(native_event),
})
}),
NSEventType::NSLeftMouseDragged
| NSEventType::NSRightMouseDragged
| NSEventType::NSOtherMouseDragged => {
let pressed_button = match native_event.buttonNumber() {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Navigate(NavigationDirection::Back),
4 => MouseButton::Navigate(NavigationDirection::Forward),
// Other mouse buttons aren't tracked currently
_ => return None,
};
window_height.map(|window_height| {
Self::MouseMoved(MouseMovedEvent {
pressed_button: Some(pressed_button),
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
})
})
}
NSEventType::NSMouseMoved => window_height.map(|window_height| {
Self::MouseMoved(MouseMovedEvent {
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
pressed_button: None,
modifiers: read_modifiers(native_event),
})
}),
NSEventType::NSMouseExited => window_height.map(|window_height| {
Self::MouseExited(MouseExitedEvent {
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
pressed_button: None,
modifiers: read_modifiers(native_event),
})
}),
_ => None,
}
}
}
unsafe fn parse_keystroke(native_event: id) -> Keystroke {
use cocoa::appkit::*;
let mut chars_ignoring_modifiers =
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
.to_str()
.unwrap()
.to_string();
let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
let modifiers = native_event.modifierFlags();
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
&& first_char.map_or(true, |ch| {
!(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
});
#[allow(non_upper_case_globals)]
let key = match first_char {
Some(SPACE_KEY) => "space".to_string(),
Some(BACKSPACE_KEY) => "backspace".to_string(),
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
Some(ESCAPE_KEY) => "escape".to_string(),
Some(TAB_KEY) => "tab".to_string(),
Some(SHIFT_TAB_KEY) => "tab".to_string(),
Some(NSUpArrowFunctionKey) => "up".to_string(),
Some(NSDownArrowFunctionKey) => "down".to_string(),
Some(NSLeftArrowFunctionKey) => "left".to_string(),
Some(NSRightArrowFunctionKey) => "right".to_string(),
Some(NSPageUpFunctionKey) => "pageup".to_string(),
Some(NSPageDownFunctionKey) => "pagedown".to_string(),
Some(NSHomeFunctionKey) => "home".to_string(),
Some(NSEndFunctionKey) => "end".to_string(),
Some(NSDeleteFunctionKey) => "delete".to_string(),
Some(NSF1FunctionKey) => "f1".to_string(),
Some(NSF2FunctionKey) => "f2".to_string(),
Some(NSF3FunctionKey) => "f3".to_string(),
Some(NSF4FunctionKey) => "f4".to_string(),
Some(NSF5FunctionKey) => "f5".to_string(),
Some(NSF6FunctionKey) => "f6".to_string(),
Some(NSF7FunctionKey) => "f7".to_string(),
Some(NSF8FunctionKey) => "f8".to_string(),
Some(NSF9FunctionKey) => "f9".to_string(),
Some(NSF10FunctionKey) => "f10".to_string(),
Some(NSF11FunctionKey) => "f11".to_string(),
Some(NSF12FunctionKey) => "f12".to_string(),
_ => {
let mut chars_ignoring_modifiers_and_shift =
chars_for_modified_key(native_event.keyCode(), false, false);
// Honor ⌘ when Dvorak-QWERTY is used.
let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
if command && chars_ignoring_modifiers_and_shift != chars_with_cmd {
chars_ignoring_modifiers =
chars_for_modified_key(native_event.keyCode(), true, shift);
chars_ignoring_modifiers_and_shift = chars_with_cmd;
}
if shift {
if chars_ignoring_modifiers_and_shift
== chars_ignoring_modifiers.to_ascii_lowercase()
{
chars_ignoring_modifiers_and_shift
} else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
shift = false;
chars_ignoring_modifiers
} else {
chars_ignoring_modifiers
}
} else {
chars_ignoring_modifiers
}
}
};
Keystroke {
modifiers: Modifiers {
control,
alt,
shift,
command,
function,
},
key,
}
}
fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
// Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
// always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
// an event with the given flags instead lets us access `characters`, which always
// returns a valid string.
let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
mem::forget(source);
let mut flags = CGEventFlags::empty();
if cmd {
flags |= CGEventFlags::CGEventFlagCommand;
}
if shift {
flags |= CGEventFlags::CGEventFlagShift;
}
event.set_flags(flags);
unsafe {
let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
CStr::from_ptr(event.characters().UTF8String())
.to_str()
.unwrap()
.to_string()
}
}

View File

@ -0,0 +1,294 @@
use crate::{point, size, DevicePixels, Quad, Scene, Size};
use bytemuck::{Pod, Zeroable};
use cocoa::{
base::{NO, YES},
foundation::NSUInteger,
quartzcore::AutoresizingMask,
};
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl};
use std::{ffi::c_void, mem, ptr};
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
pub struct MetalRenderer {
device: metal::Device,
layer: metal::MetalLayer,
command_queue: CommandQueue,
quad_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
instances: metal::Buffer,
}
impl MetalRenderer {
pub fn new(is_opaque: bool) -> Self {
const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
device
} else {
log::error!("unable to access a compatible graphics device");
std::process::exit(1);
};
let layer = metal::MetalLayer::new();
layer.set_device(&device);
layer.set_pixel_format(PIXEL_FORMAT);
layer.set_presents_with_transaction(true);
layer.set_opaque(is_opaque);
unsafe {
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
let _: () = msg_send![
&*layer,
setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
| AutoresizingMask::HEIGHT_SIZABLE
];
}
let library = device
.new_library_with_data(SHADERS_METALLIB)
.expect("error building metal library");
fn to_float2_bits(point: crate::PointF) -> u64 {
unsafe {
let mut output = mem::transmute::<_, u32>(point.y.to_bits()) as u64;
output <<= 32;
output |= mem::transmute::<_, u32>(point.x.to_bits()) as u64;
output
}
}
let unit_vertices = [
to_float2_bits(point(0., 0.)),
to_float2_bits(point(1., 0.)),
to_float2_bits(point(0., 1.)),
to_float2_bits(point(0., 1.)),
to_float2_bits(point(1., 0.)),
to_float2_bits(point(1., 1.)),
];
let unit_vertices = device.new_buffer_with_data(
unit_vertices.as_ptr() as *const c_void,
(unit_vertices.len() * mem::size_of::<u64>()) as u64,
MTLResourceOptions::StorageModeManaged,
);
let instances = device.new_buffer(
INSTANCE_BUFFER_SIZE as u64,
MTLResourceOptions::StorageModeManaged,
);
let quad_pipeline_state = build_pipeline_state(
&device,
&library,
"quad",
"quad_vertex",
"quad_fragment",
PIXEL_FORMAT,
);
let command_queue = device.new_command_queue();
Self {
device,
layer,
command_queue,
quad_pipeline_state,
unit_vertices,
instances,
}
}
pub fn layer(&self) -> &metal::MetalLayerRef {
&*self.layer
}
pub fn draw(&mut self, scene: &Scene) {
dbg!(scene);
let layer = self.layer.clone();
let viewport_size = layer.drawable_size();
let viewport_size: Size<DevicePixels> = size(
(viewport_size.width.ceil() as u32).into(),
(viewport_size.height.ceil() as u32).into(),
);
let drawable = if let Some(drawable) = layer.next_drawable() {
drawable
} else {
log::error!(
"failed to retrieve next drawable, drawable size: {:?}",
viewport_size
);
return;
};
let command_queue = self.command_queue.clone();
let command_buffer = command_queue.new_command_buffer();
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let depth_texture_desc = metal::TextureDescriptor::new();
depth_texture_desc.set_pixel_format(metal::MTLPixelFormat::Depth32Float);
depth_texture_desc.set_storage_mode(metal::MTLStorageMode::Private);
depth_texture_desc.set_usage(metal::MTLTextureUsage::RenderTarget);
depth_texture_desc.set_width(u32::from(viewport_size.width) as u64);
depth_texture_desc.set_height(u32::from(viewport_size.height) as u64);
let depth_texture = self.device.new_texture(&depth_texture_desc);
let depth_attachment = render_pass_descriptor.depth_attachment().unwrap();
depth_attachment.set_texture(Some(&depth_texture));
depth_attachment.set_clear_depth(1.);
depth_attachment.set_store_action(metal::MTLStoreAction::Store);
let color_attachment = render_pass_descriptor
.color_attachments()
.object_at(0)
.unwrap();
color_attachment.set_texture(Some(drawable.texture()));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store);
let alpha = if self.layer.is_opaque() { 1. } else { 0. };
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
command_encoder.set_viewport(metal::MTLViewport {
originX: 0.0,
originY: 0.0,
width: u32::from(viewport_size.width) as f64,
height: u32::from(viewport_size.height) as f64,
znear: 0.0,
zfar: 1.0,
});
let mut buffer_offset = 0;
for layer in scene.layers() {
self.draw_quads(
&layer.quads,
&mut buffer_offset,
viewport_size,
command_encoder,
);
}
command_encoder.end_encoding();
self.instances.did_modify_range(NSRange {
location: 0,
length: buffer_offset as NSUInteger,
});
command_buffer.commit();
command_buffer.wait_until_completed();
drawable.present();
}
fn draw_quads(
&mut self,
quads: &[Quad],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if quads.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
command_encoder.set_vertex_buffer(
QuadInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
let quad_uniforms = QuadUniforms { viewport_size };
let quad_uniform_bytes = bytemuck::bytes_of(&quad_uniforms);
command_encoder.set_vertex_bytes(
QuadInputIndex::Uniforms as u64,
quad_uniform_bytes.len() as u64,
quad_uniform_bytes.as_ptr() as *const c_void,
);
let quad_bytes = bytemuck::cast_slice(quads);
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(quad_bytes.as_ptr(), buffer_contents, quad_bytes.len());
}
let next_offset = *offset + quad_bytes.len();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
quads.len() as u64,
);
*offset = next_offset;
}
}
fn build_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
label: &str,
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
.expect("error locating vertex function");
let fragment_fn = library
.get_function(fragment_fn_name, None)
.expect("error locating fragment function");
let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);
color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
// descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Depth32Float);
device
.new_render_pipeline_state(&descriptor)
.expect("could not create render pipeline state")
}
// Align to multiples of 256 make Metal happy.
fn align_offset(offset: &mut usize) {
*offset = ((*offset + 255) / 256) * 256;
}
#[repr(C)]
enum QuadInputIndex {
Vertices = 0,
Quads = 1,
Uniforms = 2,
}
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
pub(crate) struct QuadUniforms {
viewport_size: Size<DevicePixels>,
}

View File

@ -0,0 +1,394 @@
#![allow(unused, non_upper_case_globals)]
use crate::FontFeatures;
use cocoa::appkit::CGFloat;
use core_foundation::{base::TCFType, number::CFNumber};
use core_graphics::geometry::CGAffineTransform;
use core_text::{
font::{CTFont, CTFontRef},
font_descriptor::{
CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
},
};
use font_kit::font::Font;
use std::ptr;
const kCaseSensitiveLayoutOffSelector: i32 = 1;
const kCaseSensitiveLayoutOnSelector: i32 = 0;
const kCaseSensitiveLayoutType: i32 = 33;
const kCaseSensitiveSpacingOffSelector: i32 = 3;
const kCaseSensitiveSpacingOnSelector: i32 = 2;
const kCharacterAlternativesType: i32 = 17;
const kCommonLigaturesOffSelector: i32 = 3;
const kCommonLigaturesOnSelector: i32 = 2;
const kContextualAlternatesOffSelector: i32 = 1;
const kContextualAlternatesOnSelector: i32 = 0;
const kContextualAlternatesType: i32 = 36;
const kContextualLigaturesOffSelector: i32 = 19;
const kContextualLigaturesOnSelector: i32 = 18;
const kContextualSwashAlternatesOffSelector: i32 = 5;
const kContextualSwashAlternatesOnSelector: i32 = 4;
const kDefaultLowerCaseSelector: i32 = 0;
const kDefaultUpperCaseSelector: i32 = 0;
const kDiagonalFractionsSelector: i32 = 2;
const kFractionsType: i32 = 11;
const kHistoricalLigaturesOffSelector: i32 = 21;
const kHistoricalLigaturesOnSelector: i32 = 20;
const kHojoCharactersSelector: i32 = 12;
const kInferiorsSelector: i32 = 2;
const kJIS2004CharactersSelector: i32 = 11;
const kLigaturesType: i32 = 1;
const kLowerCasePetiteCapsSelector: i32 = 2;
const kLowerCaseSmallCapsSelector: i32 = 1;
const kLowerCaseType: i32 = 37;
const kLowerCaseNumbersSelector: i32 = 0;
const kMathematicalGreekOffSelector: i32 = 11;
const kMathematicalGreekOnSelector: i32 = 10;
const kMonospacedNumbersSelector: i32 = 0;
const kNLCCharactersSelector: i32 = 13;
const kNoFractionsSelector: i32 = 0;
const kNormalPositionSelector: i32 = 0;
const kNoStyleOptionsSelector: i32 = 0;
const kNumberCaseType: i32 = 21;
const kNumberSpacingType: i32 = 6;
const kOrdinalsSelector: i32 = 3;
const kProportionalNumbersSelector: i32 = 1;
const kQuarterWidthTextSelector: i32 = 4;
const kScientificInferiorsSelector: i32 = 4;
const kSlashedZeroOffSelector: i32 = 5;
const kSlashedZeroOnSelector: i32 = 4;
const kStyleOptionsType: i32 = 19;
const kStylisticAltEighteenOffSelector: i32 = 37;
const kStylisticAltEighteenOnSelector: i32 = 36;
const kStylisticAltEightOffSelector: i32 = 17;
const kStylisticAltEightOnSelector: i32 = 16;
const kStylisticAltElevenOffSelector: i32 = 23;
const kStylisticAltElevenOnSelector: i32 = 22;
const kStylisticAlternativesType: i32 = 35;
const kStylisticAltFifteenOffSelector: i32 = 31;
const kStylisticAltFifteenOnSelector: i32 = 30;
const kStylisticAltFiveOffSelector: i32 = 11;
const kStylisticAltFiveOnSelector: i32 = 10;
const kStylisticAltFourOffSelector: i32 = 9;
const kStylisticAltFourOnSelector: i32 = 8;
const kStylisticAltFourteenOffSelector: i32 = 29;
const kStylisticAltFourteenOnSelector: i32 = 28;
const kStylisticAltNineOffSelector: i32 = 19;
const kStylisticAltNineOnSelector: i32 = 18;
const kStylisticAltNineteenOffSelector: i32 = 39;
const kStylisticAltNineteenOnSelector: i32 = 38;
const kStylisticAltOneOffSelector: i32 = 3;
const kStylisticAltOneOnSelector: i32 = 2;
const kStylisticAltSevenOffSelector: i32 = 15;
const kStylisticAltSevenOnSelector: i32 = 14;
const kStylisticAltSeventeenOffSelector: i32 = 35;
const kStylisticAltSeventeenOnSelector: i32 = 34;
const kStylisticAltSixOffSelector: i32 = 13;
const kStylisticAltSixOnSelector: i32 = 12;
const kStylisticAltSixteenOffSelector: i32 = 33;
const kStylisticAltSixteenOnSelector: i32 = 32;
const kStylisticAltTenOffSelector: i32 = 21;
const kStylisticAltTenOnSelector: i32 = 20;
const kStylisticAltThirteenOffSelector: i32 = 27;
const kStylisticAltThirteenOnSelector: i32 = 26;
const kStylisticAltThreeOffSelector: i32 = 7;
const kStylisticAltThreeOnSelector: i32 = 6;
const kStylisticAltTwelveOffSelector: i32 = 25;
const kStylisticAltTwelveOnSelector: i32 = 24;
const kStylisticAltTwentyOffSelector: i32 = 41;
const kStylisticAltTwentyOnSelector: i32 = 40;
const kStylisticAltTwoOffSelector: i32 = 5;
const kStylisticAltTwoOnSelector: i32 = 4;
const kSuperiorsSelector: i32 = 1;
const kSwashAlternatesOffSelector: i32 = 3;
const kSwashAlternatesOnSelector: i32 = 2;
const kTitlingCapsSelector: i32 = 4;
const kTypographicExtrasType: i32 = 14;
const kVerticalFractionsSelector: i32 = 1;
const kVerticalPositionType: i32 = 10;
pub fn apply_features(font: &mut Font, features: FontFeatures) {
// See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
// for a reference implementation.
toggle_open_type_feature(
font,
features.calt(),
kContextualAlternatesType,
kContextualAlternatesOnSelector,
kContextualAlternatesOffSelector,
);
toggle_open_type_feature(
font,
features.case(),
kCaseSensitiveLayoutType,
kCaseSensitiveLayoutOnSelector,
kCaseSensitiveLayoutOffSelector,
);
toggle_open_type_feature(
font,
features.cpsp(),
kCaseSensitiveLayoutType,
kCaseSensitiveSpacingOnSelector,
kCaseSensitiveSpacingOffSelector,
);
toggle_open_type_feature(
font,
features.frac(),
kFractionsType,
kDiagonalFractionsSelector,
kNoFractionsSelector,
);
toggle_open_type_feature(
font,
features.liga(),
kLigaturesType,
kCommonLigaturesOnSelector,
kCommonLigaturesOffSelector,
);
toggle_open_type_feature(
font,
features.onum(),
kNumberCaseType,
kLowerCaseNumbersSelector,
2,
);
toggle_open_type_feature(
font,
features.ordn(),
kVerticalPositionType,
kOrdinalsSelector,
kNormalPositionSelector,
);
toggle_open_type_feature(
font,
features.pnum(),
kNumberSpacingType,
kProportionalNumbersSelector,
4,
);
toggle_open_type_feature(
font,
features.ss01(),
kStylisticAlternativesType,
kStylisticAltOneOnSelector,
kStylisticAltOneOffSelector,
);
toggle_open_type_feature(
font,
features.ss02(),
kStylisticAlternativesType,
kStylisticAltTwoOnSelector,
kStylisticAltTwoOffSelector,
);
toggle_open_type_feature(
font,
features.ss03(),
kStylisticAlternativesType,
kStylisticAltThreeOnSelector,
kStylisticAltThreeOffSelector,
);
toggle_open_type_feature(
font,
features.ss04(),
kStylisticAlternativesType,
kStylisticAltFourOnSelector,
kStylisticAltFourOffSelector,
);
toggle_open_type_feature(
font,
features.ss05(),
kStylisticAlternativesType,
kStylisticAltFiveOnSelector,
kStylisticAltFiveOffSelector,
);
toggle_open_type_feature(
font,
features.ss06(),
kStylisticAlternativesType,
kStylisticAltSixOnSelector,
kStylisticAltSixOffSelector,
);
toggle_open_type_feature(
font,
features.ss07(),
kStylisticAlternativesType,
kStylisticAltSevenOnSelector,
kStylisticAltSevenOffSelector,
);
toggle_open_type_feature(
font,
features.ss08(),
kStylisticAlternativesType,
kStylisticAltEightOnSelector,
kStylisticAltEightOffSelector,
);
toggle_open_type_feature(
font,
features.ss09(),
kStylisticAlternativesType,
kStylisticAltNineOnSelector,
kStylisticAltNineOffSelector,
);
toggle_open_type_feature(
font,
features.ss10(),
kStylisticAlternativesType,
kStylisticAltTenOnSelector,
kStylisticAltTenOffSelector,
);
toggle_open_type_feature(
font,
features.ss11(),
kStylisticAlternativesType,
kStylisticAltElevenOnSelector,
kStylisticAltElevenOffSelector,
);
toggle_open_type_feature(
font,
features.ss12(),
kStylisticAlternativesType,
kStylisticAltTwelveOnSelector,
kStylisticAltTwelveOffSelector,
);
toggle_open_type_feature(
font,
features.ss13(),
kStylisticAlternativesType,
kStylisticAltThirteenOnSelector,
kStylisticAltThirteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss14(),
kStylisticAlternativesType,
kStylisticAltFourteenOnSelector,
kStylisticAltFourteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss15(),
kStylisticAlternativesType,
kStylisticAltFifteenOnSelector,
kStylisticAltFifteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss16(),
kStylisticAlternativesType,
kStylisticAltSixteenOnSelector,
kStylisticAltSixteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss17(),
kStylisticAlternativesType,
kStylisticAltSeventeenOnSelector,
kStylisticAltSeventeenOffSelector,
);
toggle_open_type_feature(
font,
features.ss18(),
kStylisticAlternativesType,
kStylisticAltEighteenOnSelector,
kStylisticAltEighteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss19(),
kStylisticAlternativesType,
kStylisticAltNineteenOnSelector,
kStylisticAltNineteenOffSelector,
);
toggle_open_type_feature(
font,
features.ss20(),
kStylisticAlternativesType,
kStylisticAltTwentyOnSelector,
kStylisticAltTwentyOffSelector,
);
toggle_open_type_feature(
font,
features.subs(),
kVerticalPositionType,
kInferiorsSelector,
kNormalPositionSelector,
);
toggle_open_type_feature(
font,
features.sups(),
kVerticalPositionType,
kSuperiorsSelector,
kNormalPositionSelector,
);
toggle_open_type_feature(
font,
features.swsh(),
kContextualAlternatesType,
kSwashAlternatesOnSelector,
kSwashAlternatesOffSelector,
);
toggle_open_type_feature(
font,
features.titl(),
kStyleOptionsType,
kTitlingCapsSelector,
kNoStyleOptionsSelector,
);
toggle_open_type_feature(
font,
features.tnum(),
kNumberSpacingType,
kMonospacedNumbersSelector,
4,
);
toggle_open_type_feature(
font,
features.zero(),
kTypographicExtrasType,
kSlashedZeroOnSelector,
kSlashedZeroOffSelector,
);
}
fn toggle_open_type_feature(
font: &mut Font,
enabled: Option<bool>,
type_identifier: i32,
on_selector_identifier: i32,
off_selector_identifier: i32,
) {
if let Some(enabled) = enabled {
let native_font = font.native_font();
unsafe {
let selector_identifier = if enabled {
on_selector_identifier
} else {
off_selector_identifier
};
let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
native_font.copy_descriptor().as_concrete_TypeRef(),
CFNumber::from(type_identifier).as_concrete_TypeRef(),
CFNumber::from(selector_identifier).as_concrete_TypeRef(),
);
let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
let new_font = CTFontCreateCopyWithAttributes(
font.native_font().as_concrete_TypeRef(),
0.0,
ptr::null(),
new_descriptor.as_concrete_TypeRef(),
);
let new_font = CTFont::wrap_under_create_rule(new_font);
*font = Font::from_native_font(new_font);
}
}
}
#[link(name = "CoreText", kind = "framework")]
extern "C" {
fn CTFontCreateCopyWithAttributes(
font: CTFontRef,
size: CGFloat,
matrix: *const CGAffineTransform,
attributes: CTFontDescriptorRef,
) -> CTFontRef;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
use super::ns_string;
use crate::{point, px, size, Bounds, Pixels, PlatformScreen, PlatformScreenHandle, ScreenId};
use cocoa::{
appkit::NSScreen,
base::{id, nil},
foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
};
use core_foundation::{
number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
};
use core_graphics::display::CGDirectDisplayID;
use objc::runtime::Object;
use std::{any::Any, ffi::c_void};
use uuid::Uuid;
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
}
#[derive(Debug)]
pub struct MacScreen {
pub(crate) native_screen: id,
}
unsafe impl Send for MacScreen {}
impl MacScreen {
pub(crate) fn from_handle(handle: PlatformScreenHandle) -> Self {
Self {
native_screen: handle.0 as *mut Object,
}
}
/// Get the screen with the given UUID.
pub fn find_by_id(id: ScreenId) -> Option<Self> {
Self::all().find(|screen| screen.id() == Some(id))
}
/// Get the primary screen - the one with the menu bar, and whose bottom left
/// corner is at the origin of the AppKit coordinate system.
fn primary() -> Self {
Self::all().next().unwrap()
}
pub fn all() -> impl Iterator<Item = Self> {
unsafe {
let native_screens = NSScreen::screens(nil);
(0..NSArray::count(native_screens)).map(move |ix| MacScreen {
native_screen: native_screens.objectAtIndex(ix),
})
}
}
/// Convert the given rectangle in screen coordinates from GPUI's
/// coordinate system to the AppKit coordinate system.
///
/// In GPUI's coordinates, the origin is at the top left of the primary screen, with
/// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
/// bottom left of the primary screen, with the Y axis pointing upward.
pub(crate) fn screen_bounds_to_native(bounds: Bounds<Pixels>) -> NSRect {
let primary_screen_height =
px(unsafe { Self::primary().native_screen.frame().size.height } as f32);
NSRect::new(
NSPoint::new(
bounds.origin.x.into(),
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
),
NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
)
}
/// Convert the given rectangle in screen coordinates from the AppKit
/// coordinate system to GPUI's coordinate system.
///
/// In GPUI's coordinates, the origin is at the top left of the primary screen, with
/// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
/// bottom left of the primary screen, with the Y axis pointing upward.
pub(crate) fn screen_bounds_from_native(rect: NSRect) -> Bounds<Pixels> {
let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
Bounds {
origin: point(
px(rect.origin.x as f32),
px((primary_screen_height - rect.origin.y - rect.size.height) as f32),
),
size: size(px(rect.size.width as f32), px(rect.size.height as f32)),
}
}
}
impl PlatformScreen for MacScreen {
fn id(&self) -> Option<ScreenId> {
unsafe {
// This approach is similar to that which winit takes
// https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
let device_description = self.native_screen.deviceDescription();
let key = ns_string("NSScreenNumber");
let device_id_obj = device_description.objectForKey_(key);
if device_id_obj.is_null() {
// Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
// to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
return None;
}
let mut device_id: u32 = 0;
CFNumberGetValue(
device_id_obj as CFNumberRef,
kCFNumberIntType,
(&mut device_id) as *mut _ as *mut c_void,
);
let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
if cfuuid.is_null() {
return None;
}
let bytes = CFUUIDGetUUIDBytes(cfuuid);
Some(ScreenId(Uuid::from_bytes([
bytes.byte0,
bytes.byte1,
bytes.byte2,
bytes.byte3,
bytes.byte4,
bytes.byte5,
bytes.byte6,
bytes.byte7,
bytes.byte8,
bytes.byte9,
bytes.byte10,
bytes.byte11,
bytes.byte12,
bytes.byte13,
bytes.byte14,
bytes.byte15,
])))
}
}
fn handle(&self) -> PlatformScreenHandle {
PlatformScreenHandle(self.native_screen as *mut c_void)
}
fn as_any(&self) -> &dyn Any {
self
}
fn bounds(&self) -> Bounds<Pixels> {
unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) }
}
fn content_bounds(&self) -> Bounds<Pixels> {
unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) }
}
}

View File

@ -0,0 +1,180 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
float4 hsla_to_rgba(Hsla hsla);
float4 to_device_position(float2 pixel_position, float2 viewport_size);
struct QuadVertexOutput {
float4 position [[position]];
float4 background_color;
float4 border_color;
uint quad_id;
};
vertex QuadVertexOutput quad_vertex(
uint unit_vertex_id [[vertex_id]],
uint quad_id [[instance_id]],
constant float2 *unit_vertices [[buffer(QuadInputIndex_Vertices)]],
constant Quad *quads [[buffer(QuadInputIndex_Quads)]],
constant QuadUniforms *uniforms [[buffer(QuadInputIndex_Uniforms)]]
) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Quad quad = quads[quad_id];
float2 position_2d = unit_vertex * float2(quad.bounds.size.width, quad.bounds.size.height) + float2(quad.bounds.origin.x, quad.bounds.origin.y);
position_2d.x = max(quad.clip_bounds.origin.x, position_2d.x);
position_2d.x = min(quad.clip_bounds.origin.x + quad.clip_bounds.size.width, position_2d.x);
position_2d.y = max(quad.clip_bounds.origin.y, position_2d.y);
position_2d.y = min(quad.clip_bounds.origin.y + quad.clip_bounds.size.height, position_2d.y);
float2 viewport_size = float2((float)uniforms->viewport_size.width, (float)uniforms->viewport_size.height);
float4 device_position = to_device_position(position_2d, viewport_size);
float4 background_color = hsla_to_rgba(quad.background);
float4 border_color = hsla_to_rgba(quad.border_color);
return QuadVertexOutput {
device_position,
background_color,
border_color,
quad_id
};
}
float quad_sdf(float2 point, Bounds_Pixels bounds, Corners_Pixels corner_radii) {
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
float2 center_to_point = point - center;
float corner_radius;
if (center_to_point.x < 0.) {
if (center_to_point.y < 0.) {
corner_radius = corner_radii.top_left;
} else {
corner_radius = corner_radii.bottom_left;
}
} else {
if (center_to_point.y < 0.) {
corner_radius = corner_radii.top_right;
} else {
corner_radius = corner_radii.bottom_right;
}
}
float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
float distance = length(max(0., rounded_edge_to_point))
+ min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y))
- corner_radius;
return distance;
}
fragment float4 quad_fragment(
QuadVertexOutput input [[stage_in]],
constant Quad *quads [[buffer(QuadInputIndex_Quads)]]
) {
Quad quad = quads[input.quad_id];
float2 half_size = float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
float2 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
float2 center_to_point = input.position.xy - center;
float corner_radius;
if (center_to_point.x < 0.) {
if (center_to_point.y < 0.) {
corner_radius = quad.corner_radii.top_left;
} else {
corner_radius = quad.corner_radii.bottom_left;
}
} else {
if (center_to_point.y < 0.) {
corner_radius = quad.corner_radii.top_right;
} else {
corner_radius = quad.corner_radii.bottom_right;
}
}
float2 rounded_edge_to_point = fabs(center_to_point) - half_size + corner_radius;
float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left : quad.border_widths.right;
float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top : quad.border_widths.bottom;
float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
float border_width;
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
border_width = 0.;
} else if (point_to_inset_corner.y > point_to_inset_corner.x) {
border_width = horizontal_border;
} else {
border_width = vertical_border;
}
float4 color;
if (border_width == 0.) {
color = input.background_color;
} else {
float inset_distance = distance + border_width;
// Decrease border's opacity as we move inside the background.
input.border_color.a *= 1. - saturate(0.5 - inset_distance);
// Alpha-blend the border and the background.
float output_alpha = quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
float3 premultiplied_border_rgb = input.border_color.rgb * quad.border_color.a;
float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
color = float4(premultiplied_output_rgb, output_alpha);
}
float clip_distance = quad_sdf(input.position.xy, quad.clip_bounds, quad.clip_corner_radii);
return color * float4(1., 1., 1., saturate(0.5 - distance) * saturate(0.5 - clip_distance));
}
float4 hsla_to_rgba(Hsla hsla) {
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
float s = hsla.s;
float l = hsla.l;
float a = hsla.a;
float c = (1.0 - fabs(2.0*l - 1.0)) * s;
float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
float m = l - c/2.0;
float r = 0.0;
float g = 0.0;
float b = 0.0;
if (h >= 0.0 && h < 1.0) {
r = c;
g = x;
b = 0.0;
} else if (h >= 1.0 && h < 2.0) {
r = x;
g = c;
b = 0.0;
} else if (h >= 2.0 && h < 3.0) {
r = 0.0;
g = c;
b = x;
} else if (h >= 3.0 && h < 4.0) {
r = 0.0;
g = x;
b = c;
} else if (h >= 4.0 && h < 5.0) {
r = x;
g = 0.0;
b = c;
} else {
r = c;
g = 0.0;
b = x;
}
float4 rgba;
rgba.x = (r + m);
rgba.y = (g + m);
rgba.z = (b + m);
rgba.w = a;
return rgba;
}
float4 to_device_position(float2 pixel_position, float2 viewport_size) {
return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
}

View File

@ -0,0 +1,754 @@
use crate::{
point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, Run,
SharedString, Size,
};
use cocoa::appkit::{CGFloat, CGPoint};
use collections::HashMap;
use core_foundation::{
array::CFIndex,
attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
base::{CFRange, TCFType},
string::CFString,
};
use core_graphics::{
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
color_space::CGColorSpace,
context::CGContext,
};
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
use font_kit::{
font::Font as FontKitFont,
handle::Handle,
hinting::HintingOptions,
metrics::Metrics,
properties::{Style as FontkitStyle, Weight as FontkitWeight},
source::SystemSource,
sources::mem::MemSource,
};
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use pathfinder_geometry::{
rect::{RectF, RectI},
transform2d::Transform2F,
vector::{Vector2F, Vector2I},
};
use smallvec::SmallVec;
use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
use super::open_type;
#[allow(non_upper_case_globals)]
const kCGImageAlphaOnly: u32 = 7;
pub struct MacTextSystem(RwLock<MacTextSystemState>);
struct MacTextSystemState {
memory_source: MemSource,
system_source: SystemSource,
fonts: Vec<FontKitFont>,
font_selections: HashMap<Font, FontId>,
font_ids_by_postscript_name: HashMap<String, FontId>,
font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
postscript_names_by_font_id: HashMap<FontId, String>,
}
impl MacTextSystem {
pub fn new() -> Self {
Self(RwLock::new(MacTextSystemState {
memory_source: MemSource::empty(),
system_source: SystemSource::new(),
fonts: Vec::new(),
font_selections: HashMap::default(),
font_ids_by_postscript_name: HashMap::default(),
font_ids_by_family_name: HashMap::default(),
postscript_names_by_font_id: HashMap::default(),
}))
}
}
impl Default for MacTextSystem {
fn default() -> Self {
Self::new()
}
}
impl PlatformTextSystem for MacTextSystem {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
self.0.write().add_fonts(fonts)
}
fn all_font_families(&self) -> Vec<String> {
self.0
.read()
.system_source
.all_families()
.expect("core text should never return an error")
}
fn font_id(&self, font: &Font) -> Result<FontId> {
let lock = self.0.upgradable_read();
if let Some(font_id) = lock.font_selections.get(font) {
Ok(*font_id)
} else {
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
{
font_ids.as_slice()
} else {
let font_ids = lock.load_family(&font.family, font.features)?;
lock.font_ids_by_family_name
.insert(font.family.clone(), font_ids);
lock.font_ids_by_family_name[&font.family].as_ref()
};
let candidate_properties = candidates
.iter()
.map(|font_id| lock.fonts[font_id.0].properties())
.collect::<SmallVec<[_; 4]>>();
let ix = font_kit::matching::find_best_match(
&candidate_properties,
&font_kit::properties::Properties {
style: font.style.into(),
weight: font.weight.into(),
stretch: Default::default(),
},
)?;
Ok(candidates[ix])
}
}
fn font_metrics(&self, font_id: FontId) -> FontMetrics {
self.0.read().fonts[font_id.0].metrics().into()
}
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
Ok(self.0.read().fonts[font_id.0]
.typographic_bounds(glyph_id.into())?
.into())
}
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
self.0.read().advance(font_id, glyph_id)
}
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
self.0.read().glyph_for_char(font_id, ch)
}
fn rasterize_glyph(
&self,
font_id: FontId,
font_size: f32,
glyph_id: GlyphId,
subpixel_shift: Point<Pixels>,
scale_factor: f32,
options: RasterizationOptions,
) -> Option<(Bounds<u32>, Vec<u8>)> {
self.0.read().rasterize_glyph(
font_id,
font_size,
glyph_id,
subpixel_shift,
scale_factor,
options,
)
}
fn layout_line(
&self,
text: &str,
font_size: Pixels,
font_runs: &[(usize, FontId)],
) -> LineLayout {
self.0.write().layout_line(text, font_size, font_runs)
}
fn wrap_line(
&self,
text: &str,
font_id: FontId,
font_size: Pixels,
width: Pixels,
) -> Vec<usize> {
self.0.read().wrap_line(text, font_id, font_size, width)
}
}
impl MacTextSystemState {
fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
self.memory_source.add_fonts(
fonts
.iter()
.map(|bytes| Handle::from_memory(bytes.clone(), 0)),
)?;
Ok(())
}
fn load_family(
&mut self,
name: &SharedString,
features: FontFeatures,
) -> Result<SmallVec<[FontId; 4]>> {
let mut font_ids = SmallVec::new();
let family = self
.memory_source
.select_family_by_name(name.as_ref())
.or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
for font in family.fonts() {
let mut font = font.load()?;
open_type::apply_features(&mut font, features);
let font_id = FontId(self.fonts.len());
font_ids.push(font_id);
let postscript_name = font.postscript_name().unwrap();
self.font_ids_by_postscript_name
.insert(postscript_name.clone(), font_id);
self.postscript_names_by_font_id
.insert(font_id, postscript_name);
self.fonts.push(font);
}
Ok(font_ids)
}
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
}
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
}
fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
let postscript_name = requested_font.postscript_name();
if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
*font_id
} else {
let font_id = FontId(self.fonts.len());
self.font_ids_by_postscript_name
.insert(postscript_name.clone(), font_id);
self.postscript_names_by_font_id
.insert(font_id, postscript_name);
self.fonts
.push(font_kit::font::Font::from_core_graphics_font(
requested_font.copy_to_CGFont(),
));
font_id
}
}
fn is_emoji(&self, font_id: FontId) -> bool {
self.postscript_names_by_font_id
.get(&font_id)
.map_or(false, |postscript_name| {
postscript_name == "AppleColorEmoji"
})
}
fn rasterize_glyph(
&self,
font_id: FontId,
font_size: f32,
glyph_id: GlyphId,
subpixel_shift: Point<Pixels>,
scale_factor: f32,
options: RasterizationOptions,
) -> Option<(Bounds<u32>, Vec<u8>)> {
let font = &self.fonts[font_id.0];
let scale = Transform2F::from_scale(scale_factor);
let glyph_bounds = font
.raster_bounds(
glyph_id.into(),
font_size,
scale,
HintingOptions::None,
font_kit::canvas::RasterizationOptions::GrayscaleAa,
)
.ok()?;
if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
None
} else {
// Make room for subpixel variants.
let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
let cx_bounds = RectI::new(
glyph_bounds.origin(),
glyph_bounds.size() + Vector2I::from(subpixel_padding),
);
let mut bytes;
let cx;
match options {
RasterizationOptions::Alpha => {
bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _),
cx_bounds.width() as usize,
cx_bounds.height() as usize,
8,
cx_bounds.width() as usize,
&CGColorSpace::create_device_gray(),
kCGImageAlphaOnly,
);
}
RasterizationOptions::Bgra => {
bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _),
cx_bounds.width() as usize,
cx_bounds.height() as usize,
8,
cx_bounds.width() as usize * 4,
&CGColorSpace::create_device_rgb(),
kCGImageAlphaPremultipliedLast,
);
}
}
// Move the origin to bottom left and account for scaling, this
// makes drawing text consistent with the font-kit's raster_bounds.
cx.translate(
-glyph_bounds.origin_x() as CGFloat,
(glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
);
cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
cx.set_allows_font_subpixel_positioning(true);
cx.set_should_subpixel_position_fonts(true);
cx.set_allows_font_subpixel_quantization(false);
cx.set_should_subpixel_quantize_fonts(false);
font.native_font()
.clone_with_font_size(font_size as CGFloat)
.draw_glyphs(
&[u32::from(glyph_id) as CGGlyph],
&[CGPoint::new(
(f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
(f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
)],
cx,
);
if let RasterizationOptions::Bgra = options {
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
for pixel in bytes.chunks_exact_mut(4) {
pixel.swap(0, 2);
let a = pixel[3] as f32 / 255.;
pixel[0] = (pixel[0] as f32 / a) as u8;
pixel[1] = (pixel[1] as f32 / a) as u8;
pixel[2] = (pixel[2] as f32 / a) as u8;
}
}
Some((cx_bounds.into(), bytes))
}
}
fn layout_line(
&mut self,
text: &str,
font_size: Pixels,
font_runs: &[(usize, FontId)],
) -> LineLayout {
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
let mut string = CFMutableAttributedString::new();
{
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
let utf16_line_len = string.char_len() as usize;
let mut ix_converter = StringIndexConverter::new(text);
for (run_len, font_id) in font_runs {
let utf8_end = ix_converter.utf8_ix + run_len;
let utf16_start = ix_converter.utf16_ix;
if utf16_start >= utf16_line_len {
break;
}
ix_converter.advance_to_utf8_ix(utf8_end);
let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
let cf_range =
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
let font: &FontKitFont = &self.fonts[font_id.0];
unsafe {
string.set_attribute(
cf_range,
kCTFontAttributeName,
&font.native_font().clone_with_font_size(font_size.into()),
);
}
if utf16_end == utf16_line_len {
break;
}
}
}
// Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
let mut runs = Vec::new();
for run in line.glyph_runs().into_iter() {
let attributes = run.attributes().unwrap();
let font = unsafe {
attributes
.get(kCTFontAttributeName)
.downcast::<CTFont>()
.unwrap()
};
let font_id = self.id_for_native_font(font);
let mut ix_converter = StringIndexConverter::new(text);
let mut glyphs = Vec::new();
for ((glyph_id, position), glyph_utf16_ix) in run
.glyphs()
.iter()
.zip(run.positions().iter())
.zip(run.string_indices().iter())
{
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
glyphs.push(Glyph {
id: (*glyph_id).into(),
position: point(position.x as f32, position.y as f32).map(px),
index: ix_converter.utf8_ix,
is_emoji: self.is_emoji(font_id),
});
}
runs.push(Run { font_id, glyphs })
}
let typographic_bounds = line.get_typographic_bounds();
LineLayout {
width: typographic_bounds.width.into(),
ascent: typographic_bounds.ascent.into(),
descent: typographic_bounds.descent.into(),
runs,
font_size,
len: text.len(),
}
}
fn wrap_line(
&self,
text: &str,
font_id: FontId,
font_size: Pixels,
width: Pixels,
) -> Vec<usize> {
let mut string = CFMutableAttributedString::new();
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
let font = &self.fonts[font_id.0];
unsafe {
string.set_attribute(
cf_range,
kCTFontAttributeName,
&font.native_font().clone_with_font_size(font_size.into()),
);
let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
let mut ix_converter = StringIndexConverter::new(text);
let mut break_indices = Vec::new();
while ix_converter.utf8_ix < text.len() {
let utf16_len = CTTypesetterSuggestLineBreak(
typesetter,
ix_converter.utf16_ix as isize,
width.into(),
) as usize;
ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
if ix_converter.utf8_ix >= text.len() {
break;
}
break_indices.push(ix_converter.utf8_ix as usize);
}
break_indices
}
}
}
#[derive(Clone)]
struct StringIndexConverter<'a> {
text: &'a str,
utf8_ix: usize,
utf16_ix: usize,
}
impl<'a> StringIndexConverter<'a> {
fn new(text: &'a str) -> Self {
Self {
text,
utf8_ix: 0,
utf16_ix: 0,
}
}
fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
if self.utf8_ix + ix >= utf8_target {
self.utf8_ix += ix;
return;
}
self.utf16_ix += c.len_utf16();
}
self.utf8_ix = self.text.len();
}
fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
if self.utf16_ix >= utf16_target {
self.utf8_ix += ix;
return;
}
self.utf16_ix += c.len_utf16();
}
self.utf8_ix = self.text.len();
}
}
#[repr(C)]
pub struct __CFTypesetter(c_void);
pub type CTTypesetterRef = *const __CFTypesetter;
#[link(name = "CoreText", kind = "framework")]
extern "C" {
fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
fn CTTypesetterSuggestLineBreak(
typesetter: CTTypesetterRef,
start_index: CFIndex,
width: f64,
) -> CFIndex;
}
impl From<Metrics> for FontMetrics {
fn from(metrics: Metrics) -> Self {
FontMetrics {
units_per_em: metrics.units_per_em,
ascent: metrics.ascent,
descent: metrics.descent,
line_gap: metrics.line_gap,
underline_position: metrics.underline_position,
underline_thickness: metrics.underline_thickness,
cap_height: metrics.cap_height,
x_height: metrics.x_height,
bounding_box: metrics.bounding_box.into(),
}
}
}
impl From<RectF> for Bounds<f32> {
fn from(rect: RectF) -> Self {
Bounds {
origin: point(rect.origin_x(), rect.origin_y()),
size: size(rect.width(), rect.height()),
}
}
}
impl From<RectI> for Bounds<u32> {
fn from(rect: RectI) -> Self {
Bounds {
origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
size: size(rect.width() as u32, rect.height() as u32),
}
}
}
impl From<Point<u32>> for Vector2I {
fn from(size: Point<u32>) -> Self {
Vector2I::new(size.x as i32, size.y as i32)
}
}
impl From<Vector2F> for Size<f32> {
fn from(vec: Vector2F) -> Self {
size(vec.x(), vec.y())
}
}
impl From<FontWeight> for FontkitWeight {
fn from(value: FontWeight) -> Self {
FontkitWeight(value.0)
}
}
impl From<FontStyle> for FontkitStyle {
fn from(style: FontStyle) -> Self {
match style {
FontStyle::Normal => FontkitStyle::Normal,
FontStyle::Italic => FontkitStyle::Italic,
FontStyle::Oblique => FontkitStyle::Oblique,
}
}
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate::AppContext;
// use font_kit::properties::{Style, Weight};
// use platform::FontSystem as _;
// #[crate::test(self, retries = 5)]
// fn test_layout_str(_: &mut AppContext) {
// // This is failing intermittently on CI and we don't have time to figure it out
// let fonts = FontSystem::new();
// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
// let menlo_regular = RunStyle {
// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// let menlo_italic = RunStyle {
// font_id: fonts
// .select_font(&menlo, Properties::new().style(Style::Italic))
// .unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// let menlo_bold = RunStyle {
// font_id: fonts
// .select_font(&menlo, Properties::new().weight(Weight::BOLD))
// .unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// assert_ne!(menlo_regular, menlo_italic);
// assert_ne!(menlo_regular, menlo_bold);
// assert_ne!(menlo_italic, menlo_bold);
// let line = fonts.layout_line(
// "hello world",
// 16.0,
// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
// );
// assert_eq!(line.runs.len(), 3);
// assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
// assert_eq!(line.runs[0].glyphs.len(), 2);
// assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
// assert_eq!(line.runs[1].glyphs.len(), 4);
// assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
// assert_eq!(line.runs[2].glyphs.len(), 5);
// }
// #[test]
// fn test_glyph_offsets() -> crate::Result<()> {
// let fonts = FontSystem::new();
// let zapfino = fonts.load_family("Zapfino", &Default::default())?;
// let zapfino_regular = RunStyle {
// font_id: fonts.select_font(&zapfino, &Properties::new())?,
// color: Default::default(),
// underline: Default::default(),
// };
// let menlo = fonts.load_family("Menlo", &Default::default())?;
// let menlo_regular = RunStyle {
// font_id: fonts.select_font(&menlo, &Properties::new())?,
// color: Default::default(),
// underline: Default::default(),
// };
// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
// let line = fonts.layout_line(
// text,
// 16.0,
// &[
// (9, zapfino_regular),
// (13, menlo_regular),
// (text.len() - 22, zapfino_regular),
// ],
// );
// assert_eq!(
// line.runs
// .iter()
// .flat_map(|r| r.glyphs.iter())
// .map(|g| g.index)
// .collect::<Vec<_>>(),
// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
// );
// Ok(())
// }
// #[test]
// #[ignore]
// fn test_rasterize_glyph() {
// use std::{fs::File, io::BufWriter, path::Path};
// let fonts = FontSystem::new();
// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
// const VARIANTS: usize = 1;
// for i in 0..VARIANTS {
// let variant = i as f32 / VARIANTS as f32;
// let (bounds, bytes) = fonts
// .rasterize_glyph(
// font_id,
// 16.0,
// glyph_id,
// vec2f(variant, variant),
// 2.,
// RasterizationOptions::Alpha,
// )
// .unwrap();
// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
// let path = Path::new(&name);
// let file = File::create(path).unwrap();
// let w = &mut BufWriter::new(file);
// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
// encoder.set_color(png::ColorType::Grayscale);
// encoder.set_depth(png::BitDepth::Eight);
// let mut writer = encoder.write_header().unwrap();
// writer.write_image_data(&bytes).unwrap();
// }
// }
// #[test]
// fn test_wrap_line() {
// let fonts = FontSystem::new();
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
// let line = "one two three four five\n";
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
// assert_eq!(
// wrap_boundaries,
// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
// );
// }
// #[test]
// fn test_layout_line_bom_char() {
// let fonts = FontSystem::new();
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
// let style = RunStyle {
// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
// color: Default::default(),
// underline: Default::default(),
// };
// let line = "\u{feff}";
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
// assert_eq!(layout.len, line.len());
// assert!(layout.runs.is_empty());
// let line = "a\u{feff}b";
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
// assert_eq!(layout.len, line.len());
// assert_eq!(layout.runs.len(), 1);
// assert_eq!(layout.runs[0].glyphs.len(), 2);
// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
// // There's no glyph for \u{feff}
// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
// }
// }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
use crate::WindowAppearance;
use cocoa::{
appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
base::id,
foundation::NSString,
};
use objc::{msg_send, sel, sel_impl};
use std::ffi::CStr;
impl WindowAppearance {
pub unsafe fn from_native(appearance: id) -> Self {
let name: id = msg_send![appearance, name];
if name == NSAppearanceNameVibrantLight {
Self::VibrantLight
} else if name == NSAppearanceNameVibrantDark {
Self::VibrantDark
} else if name == NSAppearanceNameAqua {
Self::Light
} else if name == NSAppearanceNameDarkAqua {
Self::Dark
} else {
println!(
"unknown appearance: {:?}",
CStr::from_ptr(name.UTF8String())
);
Self::Light
}
}
}
#[link(name = "AppKit", kind = "framework")]
extern "C" {
pub static NSAppearanceNameAqua: id;
pub static NSAppearanceNameDarkAqua: id;
}

View File

@ -0,0 +1,172 @@
use super::Platform;
use crate::ScreenId;
pub struct TestPlatform;
impl TestPlatform {
pub fn new() -> Self {
TestPlatform
}
}
// todo!("implement out what our tests needed in GPUI 1")
impl Platform for TestPlatform {
fn dispatcher(&self) -> std::sync::Arc<dyn crate::PlatformDispatcher> {
unimplemented!()
}
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
unimplemented!()
}
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
unimplemented!()
}
fn quit(&self) {
unimplemented!()
}
fn restart(&self) {
unimplemented!()
}
fn activate(&self, _ignoring_other_apps: bool) {
unimplemented!()
}
fn hide(&self) {
unimplemented!()
}
fn hide_other_apps(&self) {
unimplemented!()
}
fn unhide_other_apps(&self) {
unimplemented!()
}
fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
unimplemented!()
}
fn screen_by_id(&self, _id: ScreenId) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> {
unimplemented!()
}
fn main_window(&self) -> Option<crate::AnyWindowHandle> {
unimplemented!()
}
fn open_window(
&self,
_handle: crate::AnyWindowHandle,
_options: crate::WindowOptions,
) -> Box<dyn crate::PlatformWindow> {
unimplemented!()
}
fn open_url(&self, _url: &str) {
unimplemented!()
}
fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
unimplemented!()
}
fn prompt_for_paths(
&self,
_options: crate::PathPromptOptions,
) -> futures::channel::oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
unimplemented!()
}
fn prompt_for_new_path(
&self,
_directory: &std::path::Path,
) -> futures::channel::oneshot::Receiver<Option<std::path::PathBuf>> {
unimplemented!()
}
fn reveal_path(&self, _path: &std::path::Path) {
unimplemented!()
}
fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_quit(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn on_event(&self, _callback: Box<dyn FnMut(crate::Event) -> bool>) {
unimplemented!()
}
fn os_name(&self) -> &'static str {
unimplemented!()
}
fn os_version(&self) -> anyhow::Result<crate::SemanticVersion> {
unimplemented!()
}
fn app_version(&self) -> anyhow::Result<crate::SemanticVersion> {
unimplemented!()
}
fn app_path(&self) -> anyhow::Result<std::path::PathBuf> {
unimplemented!()
}
fn local_timezone(&self) -> time::UtcOffset {
unimplemented!()
}
fn path_for_auxiliary_executable(&self, _name: &str) -> anyhow::Result<std::path::PathBuf> {
unimplemented!()
}
fn set_cursor_style(&self, _style: crate::CursorStyle) {
unimplemented!()
}
fn should_auto_hide_scrollbars(&self) -> bool {
unimplemented!()
}
fn write_to_clipboard(&self, _item: crate::ClipboardItem) {
unimplemented!()
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
unimplemented!()
}
fn write_credentials(
&self,
_url: &str,
_username: &str,
_password: &[u8],
) -> anyhow::Result<()> {
unimplemented!()
}
fn read_credentials(&self, _url: &str) -> anyhow::Result<Option<(String, Vec<u8>)>> {
unimplemented!()
}
fn delete_credentials(&self, _url: &str) -> anyhow::Result<()> {
unimplemented!()
}
}

121
crates/gpui3/src/scene.rs Normal file
View File

@ -0,0 +1,121 @@
use std::mem;
use super::{Bounds, Hsla, Pixels, Point};
use crate::{Corners, Edges};
use bytemuck::{Pod, Zeroable};
use collections::BTreeMap;
// Exported to metal
pub type PointF = Point<f32>;
#[derive(Debug)]
pub struct Scene {
layers: BTreeMap<u32, SceneLayer>,
pub(crate) scale_factor: f32,
}
#[derive(Default, Debug)]
pub struct SceneLayer {
pub quads: Vec<Quad>,
}
impl Scene {
pub fn new(scale_factor: f32) -> Scene {
Scene {
layers: Default::default(),
scale_factor,
}
}
pub fn take(&mut self) -> Scene {
Scene {
layers: mem::take(&mut self.layers),
scale_factor: self.scale_factor,
}
}
pub fn insert(&mut self, primitive: impl Into<Primitive>) {
let mut primitive = primitive.into();
primitive.scale(self.scale_factor);
let layer = self.layers.entry(primitive.order()).or_default();
match primitive {
Primitive::Quad(quad) => layer.quads.push(quad),
}
}
pub fn layers(&self) -> impl Iterator<Item = &SceneLayer> {
self.layers.values()
}
}
#[derive(Clone, Debug)]
pub enum Primitive {
Quad(Quad),
}
impl Primitive {
pub fn order(&self) -> u32 {
match self {
Primitive::Quad(quad) => quad.order,
}
}
pub fn is_transparent(&self) -> bool {
match self {
Primitive::Quad(quad) => {
quad.background.is_transparent() && quad.border_color.is_transparent()
}
}
}
pub fn scale(&mut self, factor: f32) {
match self {
Primitive::Quad(quad) => {
quad.scale(factor);
}
}
}
}
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
pub struct Quad {
pub order: u32,
pub bounds: Bounds<Pixels>,
pub clip_bounds: Bounds<Pixels>,
pub clip_corner_radii: Corners<Pixels>,
pub background: Hsla,
pub border_color: Hsla,
pub corner_radii: Corners<Pixels>,
pub border_widths: Edges<Pixels>,
}
impl Quad {
pub fn vertices(&self) -> impl Iterator<Item = Point<Pixels>> {
let x1 = self.bounds.origin.x;
let y1 = self.bounds.origin.y;
let x2 = x1 + self.bounds.size.width;
let y2 = y1 + self.bounds.size.height;
[
Point::new(x1, y1),
Point::new(x2, y1),
Point::new(x2, y2),
Point::new(x1, y2),
]
.into_iter()
}
pub fn scale(&mut self, factor: f32) {
self.bounds *= factor;
self.clip_bounds *= factor;
self.clip_corner_radii *= factor;
self.corner_radii *= factor;
self.border_widths *= factor;
}
}
impl From<Quad> for Primitive {
fn from(quad: Quad) -> Self {
Primitive::Quad(quad)
}
}

338
crates/gpui3/src/style.rs Normal file
View File

@ -0,0 +1,338 @@
use crate::{
phi, rems, AbsoluteLength, Bounds, Corners, CornersRefinement, DefiniteLength, Edges,
EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point,
PointRefinement, Quad, Rems, Result, RunStyle, SharedString, Size, SizeRefinement, ViewContext,
WindowContext,
};
use refineable::Refineable;
pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
};
#[derive(Clone, Refineable, Debug)]
#[refineable(debug)]
pub struct Style {
/// What layout strategy should be used?
pub display: Display,
// Overflow properties
/// How children overflowing their container should affect layout
#[refineable]
pub overflow: Point<Overflow>,
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
pub scrollbar_width: f32,
// Position properties
/// What should the `position` value of this struct use as a base offset?
pub position: Position,
/// How should the position of this element be tweaked relative to the layout defined?
#[refineable]
pub inset: Edges<Length>,
// Size properies
/// Sets the initial size of the item
#[refineable]
pub size: Size<Length>,
/// Controls the minimum size of the item
#[refineable]
pub min_size: Size<Length>,
/// Controls the maximum size of the item
#[refineable]
pub max_size: Size<Length>,
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
pub aspect_ratio: Option<f32>,
// Spacing Properties
/// How large should the margin be on each side?
#[refineable]
pub margin: Edges<Length>,
/// How large should the padding be on each side?
#[refineable]
pub padding: Edges<DefiniteLength>,
/// How large should the border be on each side?
#[refineable]
pub border_widths: Edges<AbsoluteLength>,
// Alignment properties
/// How this node's children aligned in the cross/block axis?
pub align_items: Option<AlignItems>,
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
pub align_self: Option<AlignSelf>,
/// How should content contained within this item be aligned in the cross/block axis
pub align_content: Option<AlignContent>,
/// How should contained within this item be aligned in the main/inline axis
pub justify_content: Option<JustifyContent>,
/// How large should the gaps between items in a flex container be?
#[refineable]
pub gap: Size<DefiniteLength>,
// Flexbox properies
/// Which direction does the main axis flow in?
pub flex_direction: FlexDirection,
/// Should elements wrap, or stay in a single line?
pub flex_wrap: FlexWrap,
/// Sets the initial main axis size of the item
pub flex_basis: Length,
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
pub flex_grow: f32,
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
pub flex_shrink: f32,
/// The fill color of this element
pub fill: Option<Fill>,
/// The border color of this element
pub border_color: Option<Hsla>,
/// The radius of the corners of this element
#[refineable]
pub corner_radii: Corners<AbsoluteLength>,
/// TEXT
pub text: TextStyleRefinement,
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
pub struct TextStyle {
pub color: Hsla,
pub font_family: SharedString,
pub font_features: FontFeatures,
pub font_size: Rems,
pub line_height: DefiniteLength,
pub font_weight: FontWeight,
pub font_style: FontStyle,
pub underline: Option<UnderlineStyle>,
}
impl Default for TextStyle {
fn default() -> Self {
TextStyle {
color: Hsla::default(),
font_family: SharedString::default(),
font_features: FontFeatures::default(),
font_size: rems(1.),
line_height: phi(),
font_weight: FontWeight::default(),
font_style: FontStyle::default(),
underline: None,
}
}
}
impl TextStyle {
pub fn highlight(mut self, style: HighlightStyle) -> Result<Self> {
if let Some(weight) = style.font_weight {
self.font_weight = weight;
}
if let Some(style) = style.font_style {
self.font_style = style;
}
if let Some(color) = style.color {
self.color = self.color.blend(color);
}
if let Some(factor) = style.fade_out {
self.color.fade_out(factor);
}
if let Some(underline) = style.underline {
self.underline = Some(underline);
}
Ok(self)
}
pub fn to_run(&self) -> RunStyle {
RunStyle {
font: Font {
family: self.font_family.clone(),
features: Default::default(),
weight: self.font_weight,
style: self.font_style,
},
color: self.color,
underline: self.underline.clone(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HighlightStyle {
pub color: Option<Hsla>,
pub font_weight: Option<FontWeight>,
pub font_style: Option<FontStyle>,
pub underline: Option<UnderlineStyle>,
pub fade_out: Option<f32>,
}
impl Eq for HighlightStyle {}
impl Style {
pub fn text_style(&self, _cx: &WindowContext) -> Option<&TextStyleRefinement> {
if self.text.is_some() {
Some(&self.text)
} else {
None
}
}
/// Paints the background of an element styled with this style.
pub fn paint<V: 'static>(&self, order: u32, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size();
let background_color = self.fill.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() {
cx.scene().insert(Quad {
order,
bounds,
clip_bounds: bounds, // todo!
clip_corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
background: background_color.unwrap_or_default(),
border_color: self.border_color.unwrap_or_default(),
corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
border_widths: self.border_widths.map(|length| length.to_pixels(rem_size)),
});
}
}
fn is_border_visible(&self) -> bool {
self.border_color
.map_or(false, |color| !color.is_transparent())
&& self.border_widths.any(|length| !length.is_zero())
}
}
impl Default for Style {
fn default() -> Self {
Style {
display: Display::Block,
overflow: Point {
x: Overflow::Visible,
y: Overflow::Visible,
},
scrollbar_width: 0.0,
position: Position::Relative,
inset: Edges::auto(),
margin: Edges::<Length>::zero(),
padding: Edges::<DefiniteLength>::zero(),
border_widths: Edges::<AbsoluteLength>::zero(),
size: Size::auto(),
min_size: Size::auto(),
max_size: Size::auto(),
aspect_ratio: None,
gap: Size::zero(),
// Aligment
align_items: None,
align_self: None,
align_content: None,
justify_content: None,
// Flexbox
flex_direction: FlexDirection::Row,
flex_wrap: FlexWrap::NoWrap,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Length::Auto,
fill: None,
border_color: None,
corner_radii: Corners::default(),
text: TextStyleRefinement::default(),
}
}
}
#[derive(Refineable, Clone, Default, Debug, PartialEq, Eq)]
#[refineable(debug)]
pub struct UnderlineStyle {
pub thickness: Pixels,
pub color: Option<Hsla>,
pub squiggly: bool,
}
#[derive(Clone, Debug)]
pub enum Fill {
Color(Hsla),
}
impl Fill {
pub fn color(&self) -> Option<Hsla> {
match self {
Fill::Color(color) => Some(*color),
}
}
}
impl Default for Fill {
fn default() -> Self {
Self::Color(Hsla::default())
}
}
impl From<Hsla> for Fill {
fn from(color: Hsla) -> Self {
Self::Color(color)
}
}
impl From<TextStyle> for HighlightStyle {
fn from(other: TextStyle) -> Self {
Self::from(&other)
}
}
impl From<&TextStyle> for HighlightStyle {
fn from(other: &TextStyle) -> Self {
Self {
color: Some(other.color),
font_weight: Some(other.font_weight),
font_style: Some(other.font_style),
underline: other.underline.clone(),
fade_out: None,
}
}
}
impl HighlightStyle {
pub fn highlight(&mut self, other: HighlightStyle) {
match (self.color, other.color) {
(Some(self_color), Some(other_color)) => {
self.color = Some(Hsla::blend(other_color, self_color));
}
(None, Some(other_color)) => {
self.color = Some(other_color);
}
_ => {}
}
if other.font_weight.is_some() {
self.font_weight = other.font_weight;
}
if other.font_style.is_some() {
self.font_style = other.font_style;
}
if other.underline.is_some() {
self.underline = other.underline;
}
match (other.fade_out, self.fade_out) {
(Some(source_fade), None) => self.fade_out = Some(source_fade),
(Some(source_fade), Some(dest_fade)) => {
self.fade_out = Some((dest_fade * (1. + source_fade)).clamp(0., 1.));
}
_ => {}
}
}
}
impl From<Hsla> for HighlightStyle {
fn from(color: Hsla) -> Self {
Self {
color: Some(color),
..Default::default()
}
}
}

View File

@ -0,0 +1,308 @@
use crate::{
self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent,
Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement,
};
pub trait StyleHelpers: Styled<Style = Style> {
gpui3_macros::style_helpers!();
fn h(mut self, height: Length) -> Self
where
Self: Sized,
{
self.declared_style().size.height = Some(height);
self
}
/// size_{n}: Sets width & height to {n}
///
/// Example:
/// size_1: Sets width & height to 1
fn size(mut self, size: Length) -> Self
where
Self: Sized,
{
self.declared_style().size.height = Some(size);
self.declared_style().size.width = Some(size);
self
}
fn full(mut self) -> Self
where
Self: Sized,
{
self.declared_style().size.width = Some(relative(1.).into());
self.declared_style().size.height = Some(relative(1.).into());
self
}
fn relative(mut self) -> Self
where
Self: Sized,
{
self.declared_style().position = Some(Position::Relative);
self
}
fn absolute(mut self) -> Self
where
Self: Sized,
{
self.declared_style().position = Some(Position::Absolute);
self
}
fn block(mut self) -> Self
where
Self: Sized,
{
self.declared_style().display = Some(Display::Block);
self
}
fn flex(mut self) -> Self
where
Self: Sized,
{
self.declared_style().display = Some(Display::Flex);
self
}
fn flex_col(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_direction = Some(FlexDirection::Column);
self
}
fn flex_row(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_direction = Some(FlexDirection::Row);
self
}
fn flex_1(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(relative(0.).into());
self
}
fn flex_auto(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(Length::Auto);
self
}
fn flex_initial(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(0.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(Length::Auto);
self
}
fn flex_none(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(0.);
self.declared_style().flex_shrink = Some(0.);
self
}
fn grow(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self
}
fn items_start(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::FlexStart);
self
}
fn items_end(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::FlexEnd);
self
}
fn items_center(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::Center);
self
}
fn justify_between(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
self
}
fn justify_center(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::Center);
self
}
fn justify_start(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::Start);
self
}
fn justify_end(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::End);
self
}
fn justify_around(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
self
}
fn fill<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
Self: Sized,
{
self.declared_style().fill = Some(fill.into());
self
}
fn border_color<C>(mut self, border_color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.declared_style().border_color = Some(border_color.into());
self
}
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.declared_style();
&mut style.text
}
fn text_color(mut self, color: impl Into<Hsla>) -> Self
where
Self: Sized,
{
self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
self
}
fn text_xs(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(0.75));
self
}
fn text_sm(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(0.875));
self
}
fn text_base(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.0));
self
}
fn text_lg(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.125));
self
}
fn text_xl(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.25));
self
}
fn text_2xl(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.5));
self
}
fn text_3xl(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_size = Some(rems(1.875));
self
}
fn font(mut self, family_name: impl Into<SharedString>) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.font_family = Some(family_name.into());
self
}
}

View File

@ -0,0 +1,26 @@
use crate::{Refineable, RefinementCascade};
pub trait Styled {
type Style: Refineable + Default;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
fn computed_style(&mut self) -> Self::Style {
Self::Style::from_refinement(&self.style_cascade().merged())
}
// fn hover(self) -> Hoverable<Self>
// where
// Self: Sized,
// {
// hoverable(self)
// }
// fn active(self) -> Pressable<Self>
// where
// Self: Sized,
// {
// pressable(self)
// }
}

383
crates/gpui3/src/taffy.rs Normal file
View File

@ -0,0 +1,383 @@
use super::{
AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
Style,
};
use collections::HashMap;
use std::fmt::Debug;
use taffy::{
geometry::Size as TaffySize,
style::AvailableSpace as TaffyAvailableSpace,
tree::{Measurable, MeasureFunc, NodeId},
Taffy,
};
pub struct TaffyLayoutEngine {
taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layouts: HashMap<LayoutId, Layout>,
}
impl TaffyLayoutEngine {
pub fn new() -> Self {
TaffyLayoutEngine {
taffy: Taffy::new(),
children_to_parents: HashMap::default(),
absolute_layouts: HashMap::default(),
}
}
pub fn request_layout(
&mut self,
style: Style,
rem_size: Pixels,
children: &[LayoutId],
) -> Result<LayoutId> {
let style = style.to_taffy(rem_size);
if children.is_empty() {
Ok(self.taffy.new_leaf(style)?.into())
} else {
let parent_id = self
.taffy
// This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
.new_with_children(style, unsafe { std::mem::transmute(children) })?
.into();
for child_id in children {
self.children_to_parents.insert(*child_id, parent_id);
}
Ok(parent_id)
}
}
pub fn request_measured_layout(
&mut self,
style: Style,
rem_size: Pixels,
measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
+ Send
+ Sync
+ 'static,
) -> Result<LayoutId> {
let style = style.to_taffy(rem_size);
let measurable = Box::new(Measureable(measure)) as Box<dyn Measurable>;
Ok(self
.taffy
.new_leaf_with_measure(style, MeasureFunc::Boxed(measurable))?
.into())
}
pub fn compute_layout(
&mut self,
id: LayoutId,
available_space: Size<AvailableSpace>,
) -> Result<()> {
self.taffy
.compute_layout(id.into(), available_space.into())?;
Ok(())
}
pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
if let Some(layout) = self.absolute_layouts.get(&id).cloned() {
return Ok(layout);
}
let mut relative_layout: Layout = self.taffy.layout(id.into()).map(Into::into)?;
if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
let parent_layout = self.layout(parent_id)?;
relative_layout.bounds.origin += parent_layout.bounds.origin;
}
self.absolute_layouts.insert(id, relative_layout.clone());
Ok(relative_layout)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[repr(transparent)]
pub struct LayoutId(NodeId);
impl std::hash::Hash for LayoutId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
u64::from(self.0).hash(state);
}
}
impl From<NodeId> for LayoutId {
fn from(node_id: NodeId) -> Self {
Self(node_id)
}
}
impl From<LayoutId> for NodeId {
fn from(layout_id: LayoutId) -> NodeId {
layout_id.0
}
}
struct Measureable<F>(F);
impl<F> taffy::tree::Measurable for Measureable<F>
where
F: Send + Sync + Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>,
{
fn measure(
&self,
known_dimensions: TaffySize<Option<f32>>,
available_space: TaffySize<TaffyAvailableSpace>,
) -> TaffySize<f32> {
let known_dimensions: Size<Option<f32>> = known_dimensions.into();
let known_dimensions: Size<Option<Pixels>> = known_dimensions.map(|d| d.map(Into::into));
let available_space = available_space.into();
let size = (self.0)(known_dimensions, available_space);
size.into()
}
}
trait ToTaffy<Output> {
fn to_taffy(&self, rem_size: Pixels) -> Output;
}
impl ToTaffy<taffy::style::Style> for Style {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
taffy::style::Style {
display: self.display,
overflow: self.overflow.clone().into(),
scrollbar_width: self.scrollbar_width,
position: self.position,
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
min_size: self.min_size.to_taffy(rem_size),
max_size: self.max_size.to_taffy(rem_size),
aspect_ratio: self.aspect_ratio,
margin: self.margin.to_taffy(rem_size),
padding: self.padding.to_taffy(rem_size),
border: self.border_widths.to_taffy(rem_size),
align_items: self.align_items,
align_self: self.align_self,
align_content: self.align_content,
justify_content: self.justify_content,
gap: self.gap.to_taffy(rem_size),
flex_direction: self.flex_direction,
flex_wrap: self.flex_wrap,
flex_basis: self.flex_basis.to_taffy(rem_size),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
..Default::default() // Ignore grid properties for now
}
}
}
// impl ToTaffy for Bounds<Length> {
// type Output = taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto>;
// fn to_taffy(
// &self,
// rem_size: Pixels,
// ) -> taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto> {
// taffy::prelude::Bounds {
// origin: self.origin.to_taffy(rem_size),
// size: self.size.to_taffy(rem_size),
// }
// }
// }
impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
match self {
Length::Definite(length) => length.to_taffy(rem_size),
Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
}
}
}
impl ToTaffy<taffy::style::Dimension> for Length {
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
match self {
Length::Definite(length) => length.to_taffy(rem_size),
Length::Auto => taffy::prelude::Dimension::Auto,
}
}
}
impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => {
taffy::style::LengthPercentage::Length(pixels.into())
}
AbsoluteLength::Rems(rems) => {
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => {
taffy::style::LengthPercentage::Percent(*fraction)
}
}
}
}
impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => {
taffy::style::LengthPercentageAuto::Length(pixels.into())
}
AbsoluteLength::Rems(rems) => {
taffy::style::LengthPercentageAuto::Length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => {
taffy::style::LengthPercentageAuto::Percent(*fraction)
}
}
}
}
impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::Length(pixels.into()),
AbsoluteLength::Rems(rems) => {
taffy::style::Dimension::Length((*rems * rem_size).into())
}
},
DefiniteLength::Fraction(fraction) => taffy::style::Dimension::Percent(*fraction),
}
}
}
impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
match self {
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(pixels.into()),
AbsoluteLength::Rems(rems) => {
taffy::style::LengthPercentage::Length((*rems * rem_size).into())
}
}
}
}
impl<T, T2: Clone + Debug> From<taffy::geometry::Point<T>> for Point<T2>
where
T: Into<T2>,
{
fn from(point: taffy::geometry::Point<T>) -> Point<T2> {
Point {
x: point.x.into(),
y: point.y.into(),
}
}
}
impl<T: Clone + Debug, T2> Into<taffy::geometry::Point<T2>> for Point<T>
where
T: Into<T2>,
{
fn into(self) -> taffy::geometry::Point<T2> {
taffy::geometry::Point {
x: self.x.into(),
y: self.y.into(),
}
}
}
impl<T: ToTaffy<U> + Clone + Debug, U> ToTaffy<taffy::geometry::Size<U>> for Size<T> {
fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Size<U> {
taffy::geometry::Size {
width: self.width.to_taffy(rem_size).into(),
height: self.height.to_taffy(rem_size).into(),
}
}
}
impl<T, U> ToTaffy<taffy::geometry::Rect<U>> for Edges<T>
where
T: ToTaffy<U> + Clone + Debug,
{
fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Rect<U> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size).into(),
right: self.right.to_taffy(rem_size).into(),
bottom: self.bottom.to_taffy(rem_size).into(),
left: self.left.to_taffy(rem_size).into(),
}
}
}
impl<T: Into<U>, U: Clone + Debug> From<TaffySize<T>> for Size<U> {
fn from(taffy_size: taffy::geometry::Size<T>) -> Self {
Size {
width: taffy_size.width.into(),
height: taffy_size.height.into(),
}
}
}
impl<T: Into<U> + Clone + Debug, U> From<Size<T>> for taffy::geometry::Size<U> {
fn from(size: Size<T>) -> Self {
taffy::geometry::Size {
width: size.width.into(),
height: size.height.into(),
}
}
}
// impl From<TaffySize<Option<f32>>> for Size<Option<Pixels>> {
// fn from(value: TaffySize<Option<f32>>) -> Self {
// Self {
// width: value.width.map(Into::into),
// height: value.height.map(Into::into),
// }
// }
// }
#[derive(Copy, Clone, Debug)]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
Definite(Pixels),
/// The amount of space available is indefinite and the node should be laid out under a min-content constraint
MinContent,
/// The amount of space available is indefinite and the node should be laid out under a max-content constraint
MaxContent,
}
impl From<AvailableSpace> for TaffyAvailableSpace {
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
match space {
AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
}
}
}
impl From<TaffyAvailableSpace> for AvailableSpace {
fn from(space: TaffyAvailableSpace) -> AvailableSpace {
match space {
TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
}
}
}
impl From<Pixels> for AvailableSpace {
fn from(pixels: Pixels) -> Self {
AvailableSpace::Definite(pixels)
}
}
impl From<&taffy::tree::Layout> for Layout {
fn from(layout: &taffy::tree::Layout) -> Self {
Layout {
order: layout.order,
bounds: Bounds {
origin: layout.location.into(),
size: layout.size.into(),
},
}
}
}

View File

@ -0,0 +1,477 @@
mod font_features;
mod line_wrapper;
mod text_layout_cache;
use anyhow::anyhow;
pub use font_features::*;
use line_wrapper::*;
pub use text_layout_cache::*;
use crate::{
px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle,
};
use collections::HashMap;
use core::fmt;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use std::{
fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
sync::Arc,
};
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
pub struct FontId(pub usize);
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
pub struct FontFamilyId(pub usize);
pub struct TextSystem {
text_layout_cache: Arc<TextLayoutCache>,
platform_text_system: Arc<dyn PlatformTextSystem>,
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
font_metrics: RwLock<HashMap<Font, FontMetrics>>,
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
}
impl TextSystem {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
TextSystem {
text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
platform_text_system,
font_metrics: RwLock::new(HashMap::default()),
font_ids_by_font: RwLock::new(HashMap::default()),
fonts_by_font_id: RwLock::new(HashMap::default()),
wrapper_pool: Mutex::new(HashMap::default()),
font_runs_pool: Default::default(),
}
}
pub fn font_id(&self, font: &Font) -> Result<FontId> {
let font_id = self.font_ids_by_font.read().get(font).copied();
if let Some(font_id) = font_id {
Ok(font_id)
} else {
let font_id = self.platform_text_system.font_id(font)?;
self.font_ids_by_font.write().insert(font.clone(), font_id);
self.fonts_by_font_id.write().insert(font_id, font.clone());
Ok(font_id)
}
}
pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> {
self.fonts_by_font_id
.read()
.get(&font_id)
.ok_or_else(|| anyhow!("font not found"))
.map(|font| f(self, font))
}
pub fn bounding_box(&self, font: &Font, font_size: Pixels) -> Result<Bounds<Pixels>> {
self.read_metrics(&font, |metrics| metrics.bounding_box(font_size))
}
pub fn typographic_bounds(
&self,
font: &Font,
font_size: Pixels,
character: char,
) -> Result<Bounds<Pixels>> {
let font_id = self.font_id(font)?;
let glyph_id = self
.platform_text_system
.glyph_for_char(font_id, character)
.ok_or_else(|| anyhow!("glyph not found for character '{}'", character))?;
let bounds = self
.platform_text_system
.typographic_bounds(font_id, glyph_id)?;
self.read_metrics(font, |metrics| {
(bounds / metrics.units_per_em as f32 * font_size.0).map(px)
})
}
pub fn advance(&self, font: &Font, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
let font_id = self.font_id(font)?;
let glyph_id = self
.platform_text_system
.glyph_for_char(font_id, ch)
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
let result =
self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32;
Ok(result * font_size)
}
pub fn units_per_em(&self, font: &Font) -> Result<u32> {
self.read_metrics(font, |metrics| metrics.units_per_em as u32)
}
pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.cap_height(font_size))
}
pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.x_height(font_size))
}
pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.ascent(font_size))
}
pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.descent(font_size))
}
pub fn baseline_offset(
&self,
font: &Font,
font_size: Pixels,
line_height: Pixels,
) -> Result<Pixels> {
let ascent = self.ascent(font, font_size)?;
let descent = self.descent(font, font_size)?;
let padding_top = (line_height - ascent - descent) / 2.;
Ok(padding_top + ascent)
}
fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
let lock = self.font_metrics.upgradable_read();
if let Some(metrics) = lock.get(font) {
Ok(read(metrics))
} else {
let font_id = self.platform_text_system.font_id(&font)?;
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
let metrics = lock
.entry(font.clone())
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
Ok(read(metrics))
}
}
pub fn layout_line(
&self,
text: &str,
font_size: Pixels,
runs: &[(usize, RunStyle)],
) -> Result<Line> {
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
dbg!("got font runs from pool");
let mut last_font: Option<&Font> = None;
for (len, style) in runs {
dbg!(len);
if let Some(last_font) = last_font.as_ref() {
dbg!("a");
if **last_font == style.font {
dbg!("b");
font_runs.last_mut().unwrap().0 += len;
dbg!("c");
continue;
}
dbg!("d");
}
dbg!("e");
last_font = Some(&style.font);
dbg!("f");
font_runs.push((*len, self.font_id(&style.font)?));
dbg!("g");
}
dbg!("built font runs");
let layout = self
.text_layout_cache
.layout_line(text, font_size, &font_runs);
font_runs.clear();
self.font_runs_pool.lock().push(font_runs);
Ok(Line::new(layout.clone(), runs))
}
pub fn finish_frame(&self) {
self.text_layout_cache.finish_frame()
}
pub fn line_wrapper(
self: &Arc<Self>,
font: Font,
font_size: Pixels,
) -> Result<LineWrapperHandle> {
let lock = &mut self.wrapper_pool.lock();
let font_id = self.font_id(&font)?;
let wrappers = lock
.entry(FontIdWithSize { font_id, font_size })
.or_default();
let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| {
Ok(LineWrapper::new(
font_id,
font_size,
self.platform_text_system.clone(),
))
})?;
Ok(LineWrapperHandle {
wrapper: Some(wrapper),
text_system: self.clone(),
})
}
}
#[derive(Hash, Eq, PartialEq)]
struct FontIdWithSize {
font_id: FontId,
font_size: Pixels,
}
pub struct LineWrapperHandle {
wrapper: Option<LineWrapper>,
text_system: Arc<TextSystem>,
}
impl Drop for LineWrapperHandle {
fn drop(&mut self) {
let mut state = self.text_system.wrapper_pool.lock();
let wrapper = self.wrapper.take().unwrap();
state
.get_mut(&FontIdWithSize {
font_id: wrapper.font_id.clone(),
font_size: wrapper.font_size,
})
.unwrap()
.push(wrapper);
}
}
impl Deref for LineWrapperHandle {
type Target = LineWrapper;
fn deref(&self) -> &Self::Target {
self.wrapper.as_ref().unwrap()
}
}
impl DerefMut for LineWrapperHandle {
fn deref_mut(&mut self) -> &mut Self::Target {
self.wrapper.as_mut().unwrap()
}
}
/// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0,
/// with 400.0 as normal.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct FontWeight(pub f32);
impl Default for FontWeight {
#[inline]
fn default() -> FontWeight {
FontWeight::NORMAL
}
}
impl Hash for FontWeight {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
}
}
impl Eq for FontWeight {}
impl FontWeight {
/// Thin weight (100), the thinnest value.
pub const THIN: FontWeight = FontWeight(100.0);
/// Extra light weight (200).
pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
/// Light weight (300).
pub const LIGHT: FontWeight = FontWeight(300.0);
/// Normal (400).
pub const NORMAL: FontWeight = FontWeight(400.0);
/// Medium weight (500, higher than normal).
pub const MEDIUM: FontWeight = FontWeight(500.0);
/// Semibold weight (600).
pub const SEMIBOLD: FontWeight = FontWeight(600.0);
/// Bold weight (700).
pub const BOLD: FontWeight = FontWeight(700.0);
/// Extra-bold weight (800).
pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
/// Black weight (900), the thickest value.
pub const BLACK: FontWeight = FontWeight(900.0);
}
/// Allows italic or oblique faces to be selected.
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub enum FontStyle {
/// A face that is neither italic not obliqued.
Normal,
/// A form that is generally cursive in nature.
Italic,
/// A typically-sloped version of the regular face.
Oblique,
}
impl Default for FontStyle {
fn default() -> FontStyle {
FontStyle::Normal
}
}
impl Display for FontStyle {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self, f)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RunStyle {
pub font: Font,
pub color: Hsla,
pub underline: Option<UnderlineStyle>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct GlyphId(u32);
impl From<GlyphId> for u32 {
fn from(value: GlyphId) -> Self {
value.0
}
}
impl From<u16> for GlyphId {
fn from(num: u16) -> Self {
GlyphId(num as u32)
}
}
impl From<u32> for GlyphId {
fn from(num: u32) -> Self {
GlyphId(num)
}
}
#[derive(Clone, Debug)]
pub struct Glyph {
pub id: GlyphId,
pub position: Point<Pixels>,
pub index: usize,
pub is_emoji: bool,
}
#[derive(Default, Debug)]
pub struct LineLayout {
pub font_size: Pixels,
pub width: Pixels,
pub ascent: Pixels,
pub descent: Pixels,
pub runs: Vec<Run>,
pub len: usize,
}
#[derive(Debug)]
pub struct Run {
pub font_id: FontId,
pub glyphs: Vec<Glyph>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Font {
pub family: SharedString,
pub features: FontFeatures,
pub weight: FontWeight,
pub style: FontStyle,
}
pub fn font(family: impl Into<SharedString>) -> Font {
Font {
family: family.into(),
features: FontFeatures::default(),
weight: FontWeight::default(),
style: FontStyle::default(),
}
}
impl Font {
pub fn bold(mut self) -> Self {
self.weight = FontWeight::BOLD;
self
}
}
/// A struct for storing font metrics.
/// It is used to define the measurements of a typeface.
#[derive(Clone, Copy, Debug)]
pub struct FontMetrics {
/// The number of font units that make up the "em square",
/// a scalable grid for determining the size of a typeface.
pub(crate) units_per_em: u32,
/// The vertical distance from the baseline of the font to the top of the glyph covers.
pub(crate) ascent: f32,
/// The vertical distance from the baseline of the font to the bottom of the glyph covers.
pub(crate) descent: f32,
/// The recommended additional space to add between lines of type.
pub(crate) line_gap: f32,
/// The suggested position of the underline.
pub(crate) underline_position: f32,
/// The suggested thickness of the underline.
pub(crate) underline_thickness: f32,
/// The height of a capital letter measured from the baseline of the font.
pub(crate) cap_height: f32,
/// The height of a lowercase x.
pub(crate) x_height: f32,
/// The outer limits of the area that the font covers.
pub(crate) bounding_box: Bounds<f32>,
}
impl FontMetrics {
/// Returns the vertical distance from the baseline of the font to the top of the glyph covers in pixels.
pub fn ascent(&self, font_size: Pixels) -> Pixels {
Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
}
/// Returns the vertical distance from the baseline of the font to the bottom of the glyph covers in pixels.
pub fn descent(&self, font_size: Pixels) -> Pixels {
Pixels((self.descent / self.units_per_em as f32) * font_size.0)
}
/// Returns the recommended additional space to add between lines of type in pixels.
pub fn line_gap(&self, font_size: Pixels) -> Pixels {
Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
}
/// Returns the suggested position of the underline in pixels.
pub fn underline_position(&self, font_size: Pixels) -> Pixels {
Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
}
/// Returns the suggested thickness of the underline in pixels.
pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
}
/// Returns the height of a capital letter measured from the baseline of the font in pixels.
pub fn cap_height(&self, font_size: Pixels) -> Pixels {
Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
}
/// Returns the height of a lowercase x in pixels.
pub fn x_height(&self, font_size: Pixels) -> Pixels {
Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
}
/// Returns the outer limits of the area that the font covers in pixels.
pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
(self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
}
}

View File

@ -0,0 +1,162 @@
use schemars::{
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
macro_rules! create_definitions {
($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FontFeatures {
enabled: u64,
disabled: u64,
}
impl FontFeatures {
$(
pub fn $name(&self) -> Option<bool> {
if (self.enabled & (1 << $idx)) != 0 {
Some(true)
} else if (self.disabled & (1 << $idx)) != 0 {
Some(false)
} else {
None
}
}
)*
}
impl std::fmt::Debug for FontFeatures {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("FontFeatures");
$(
if let Some(value) = self.$name() {
debug.field(stringify!($name), &value);
};
)*
debug.finish()
}
}
impl<'de> serde::Deserialize<'de> for FontFeatures {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{MapAccess, Visitor};
use std::fmt;
struct FontFeaturesVisitor;
impl<'de> Visitor<'de> for FontFeaturesVisitor {
type Value = FontFeatures;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map of font features")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut enabled: u64 = 0;
let mut disabled: u64 = 0;
while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? {
let idx = match key.as_str() {
$(stringify!($name) => $idx,)*
_ => continue,
};
match value {
Some(true) => enabled |= 1 << idx,
Some(false) => disabled |= 1 << idx,
None => {}
};
}
Ok(FontFeatures { enabled, disabled })
}
}
let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
Ok(features)
}
}
impl serde::Serialize for FontFeatures {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
$(
let feature = stringify!($name);
if let Some(value) = self.$name() {
map.serialize_entry(feature, &value)?;
}
)*
map.end()
}
}
impl JsonSchema for FontFeatures {
fn schema_name() -> String {
"FontFeatures".into()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema {
let mut schema = SchemaObject::default();
let properties = &mut schema.object().properties;
let feature_schema = Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
..Default::default()
});
$(
properties.insert(stringify!($name).to_owned(), feature_schema.clone());
)*
schema.into()
}
}
};
}
create_definitions!(
(calt, 0),
(case, 1),
(cpsp, 2),
(frac, 3),
(liga, 4),
(onum, 5),
(ordn, 6),
(pnum, 7),
(ss01, 8),
(ss02, 9),
(ss03, 10),
(ss04, 11),
(ss05, 12),
(ss06, 13),
(ss07, 14),
(ss08, 15),
(ss09, 16),
(ss10, 17),
(ss11, 18),
(ss12, 19),
(ss13, 20),
(ss14, 21),
(ss15, 22),
(ss16, 23),
(ss17, 24),
(ss18, 25),
(ss19, 26),
(ss20, 27),
(subs, 28),
(sups, 29),
(swsh, 30),
(titl, 31),
(tnum, 32),
(zero, 33)
);

View File

@ -0,0 +1,329 @@
use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary};
use collections::HashMap;
use std::{iter, sync::Arc};
pub struct LineWrapper {
platform_text_system: Arc<dyn PlatformTextSystem>,
pub(crate) font_id: FontId,
pub(crate) font_size: Pixels,
cached_ascii_char_widths: [Option<Pixels>; 128],
cached_other_char_widths: HashMap<char, Pixels>,
}
impl LineWrapper {
pub const MAX_INDENT: u32 = 256;
pub fn new(
font_id: FontId,
font_size: Pixels,
text_system: Arc<dyn PlatformTextSystem>,
) -> Self {
Self {
platform_text_system: text_system,
font_id,
font_size,
cached_ascii_char_widths: [None; 128],
cached_other_char_widths: HashMap::default(),
}
}
pub fn wrap_line<'a>(
&'a mut self,
line: &'a str,
wrap_width: Pixels,
) -> impl Iterator<Item = Boundary> + 'a {
let mut width = px(0.);
let mut first_non_whitespace_ix = None;
let mut indent = None;
let mut last_candidate_ix = 0;
let mut last_candidate_width = px(0.);
let mut last_wrap_ix = 0;
let mut prev_c = '\0';
let mut char_indices = line.char_indices();
iter::from_fn(move || {
for (ix, c) in char_indices.by_ref() {
if c == '\n' {
continue;
}
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
last_candidate_ix = ix;
last_candidate_width = width;
}
if c != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(ix);
}
let char_width = self.width_for_char(c);
width += char_width;
if width > wrap_width && ix > last_wrap_ix {
if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
{
indent = Some(
Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
);
}
if last_candidate_ix > 0 {
last_wrap_ix = last_candidate_ix;
width -= last_candidate_width;
last_candidate_ix = 0;
} else {
last_wrap_ix = ix;
width = char_width;
}
if let Some(indent) = indent {
width += self.width_for_char(' ') * indent as f32;
}
return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
}
prev_c = c;
}
None
})
}
pub fn wrap_shaped_line<'a>(
&'a mut self,
str: &'a str,
line: &'a Line,
wrap_width: Pixels,
) -> impl Iterator<Item = ShapedBoundary> + 'a {
let mut first_non_whitespace_ix = None;
let mut last_candidate_ix = None;
let mut last_candidate_x = px(0.);
let mut last_wrap_ix = ShapedBoundary {
run_ix: 0,
glyph_ix: 0,
};
let mut last_wrap_x = px(0.);
let mut prev_c = '\0';
let mut glyphs = line
.runs()
.iter()
.enumerate()
.flat_map(move |(run_ix, run)| {
run.glyphs()
.iter()
.enumerate()
.map(move |(glyph_ix, glyph)| {
let character = str[glyph.index..].chars().next().unwrap();
(
ShapedBoundary { run_ix, glyph_ix },
character,
glyph.position.x,
)
})
})
.peekable();
iter::from_fn(move || {
while let Some((ix, c, x)) = glyphs.next() {
if c == '\n' {
continue;
}
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
last_candidate_ix = Some(ix);
last_candidate_x = x;
}
if c != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(ix);
}
let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
let width = next_x - last_wrap_x;
if width > wrap_width && ix > last_wrap_ix {
if let Some(last_candidate_ix) = last_candidate_ix.take() {
last_wrap_ix = last_candidate_ix;
last_wrap_x = last_candidate_x;
} else {
last_wrap_ix = ix;
last_wrap_x = x;
}
return Some(last_wrap_ix);
}
prev_c = c;
}
None
})
}
fn is_boundary(&self, prev: char, next: char) -> bool {
(prev == ' ') && (next != ' ')
}
#[inline(always)]
fn width_for_char(&mut self, c: char) -> Pixels {
if (c as u32) < 128 {
if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
cached_width
} else {
let width = self.compute_width_for_char(c);
self.cached_ascii_char_widths[c as usize] = Some(width);
width
}
} else {
if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
*cached_width
} else {
let width = self.compute_width_for_char(c);
self.cached_other_char_widths.insert(c, width);
width
}
}
}
fn compute_width_for_char(&self, c: char) -> Pixels {
self.platform_text_system
.layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)])
.width
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Boundary {
pub ix: usize,
pub next_indent: u32,
}
impl Boundary {
fn new(ix: usize, next_indent: u32) -> Self {
Self { ix, next_indent }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{font, App, RunStyle};
#[test]
fn test_wrap_line() {
App::test().run(|cx| {
let text_system = cx.text_system().clone();
let mut wrapper = LineWrapper::new(
text_system.font_id(&font("Courier")).unwrap(),
px(16.),
text_system.platform_text_system.clone(),
);
assert_eq!(
wrapper
.wrap_line("aa bbb cccc ddddd eeee", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(12, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
.collect::<Vec<_>>(),
&[
Boundary::new(4, 0),
Boundary::new(11, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaa", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 5),
Boundary::new(9, 5),
Boundary::new(11, 5),
]
);
assert_eq!(
wrapper
.wrap_line(" ", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 0),
Boundary::new(21, 0)
]
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaaaaaaaaa", px(72.))
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 3),
Boundary::new(18, 3),
Boundary::new(22, 3),
]
);
});
}
// todo! repeat this test
#[test]
fn test_wrap_shaped_line() {
App::test().run(|cx| {
let text_system = cx.text_system().clone();
let normal = RunStyle {
font: font("Helvetica"),
color: Default::default(),
underline: Default::default(),
};
let bold = RunStyle {
font: font("Helvetica").bold(),
color: Default::default(),
underline: Default::default(),
};
let text = "aa bbb cccc ddddd eeee";
let line = text_system
.layout_line(
text,
px(16.),
&[
(4, normal.clone()),
(5, bold.clone()),
(6, normal.clone()),
(1, bold.clone()),
(7, normal.clone()),
],
)
.unwrap();
let mut wrapper = LineWrapper::new(
text_system.font_id(&normal.font).unwrap(),
px(16.),
text_system.platform_text_system.clone(),
);
assert_eq!(
wrapper
.wrap_shaped_line(text, &line, px(72.))
.collect::<Vec<_>>(),
&[
ShapedBoundary {
run_ix: 1,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 2,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 4,
glyph_ix: 2
}
],
);
});
}
}

View File

@ -0,0 +1,478 @@
use crate::{
black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
Run, RunStyle, UnderlineStyle, WindowContext,
};
use anyhow::Result;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use std::{
borrow::Borrow,
collections::HashMap,
hash::{Hash, Hasher},
sync::Arc,
};
pub(crate) struct TextLayoutCache {
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
platform_text_system: Arc<dyn PlatformTextSystem>,
}
impl TextLayoutCache {
pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
Self {
prev_frame: Mutex::new(HashMap::new()),
curr_frame: RwLock::new(HashMap::new()),
platform_text_system: fonts,
}
}
pub fn finish_frame(&self) {
let mut prev_frame = self.prev_frame.lock();
let mut curr_frame = self.curr_frame.write();
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
curr_frame.clear();
}
pub fn layout_line<'a>(
&'a self,
text: &'a str,
font_size: Pixels,
runs: &[(usize, FontId)],
) -> Arc<LineLayout> {
dbg!("layout line");
let key = &CacheKeyRef {
text,
font_size,
runs,
} as &dyn CacheKey;
let curr_frame = self.curr_frame.upgradable_read();
if let Some(layout) = curr_frame.get(key) {
return layout.clone();
}
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
curr_frame.insert(key, layout.clone());
layout
} else {
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
let key = CacheKeyValue {
text: text.into(),
font_size,
runs: SmallVec::from(runs),
};
curr_frame.insert(key, layout.clone());
layout
}
}
}
trait CacheKey {
fn key(&self) -> CacheKeyRef;
}
impl<'a> PartialEq for (dyn CacheKey + 'a) {
fn eq(&self, other: &dyn CacheKey) -> bool {
self.key() == other.key()
}
}
impl<'a> Eq for (dyn CacheKey + 'a) {}
impl<'a> Hash for (dyn CacheKey + 'a) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key().hash(state)
}
}
#[derive(Eq)]
struct CacheKeyValue {
text: String,
font_size: Pixels,
runs: SmallVec<[(usize, FontId); 1]>,
}
impl CacheKey for CacheKeyValue {
fn key(&self) -> CacheKeyRef {
CacheKeyRef {
text: self.text.as_str(),
font_size: self.font_size,
runs: self.runs.as_slice(),
}
}
}
impl PartialEq for CacheKeyValue {
fn eq(&self, other: &Self) -> bool {
self.key().eq(&other.key())
}
}
impl Hash for CacheKeyValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key().hash(state);
}
}
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
fn borrow(&self) -> &(dyn CacheKey + 'a) {
self as &dyn CacheKey
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
struct CacheKeyRef<'a> {
text: &'a str,
font_size: Pixels,
runs: &'a [(usize, FontId)],
}
impl<'a> CacheKey for CacheKeyRef<'a> {
fn key(&self) -> CacheKeyRef {
*self
}
}
impl<'a> Hash for CacheKeyRef<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.text.hash(state);
self.font_size.hash(state);
for (len, font_id) in self.runs {
len.hash(state);
font_id.hash(state);
}
}
}
#[derive(Default, Debug, Clone)]
pub struct Line {
layout: Arc<LineLayout>,
style_runs: SmallVec<[StyleRun; 32]>,
}
#[derive(Debug, Clone)]
struct StyleRun {
len: u32,
color: Hsla,
underline: UnderlineStyle,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ShapedBoundary {
pub run_ix: usize,
pub glyph_ix: usize,
}
impl Line {
pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
let mut style_runs = SmallVec::new();
for (len, style) in runs {
style_runs.push(StyleRun {
len: *len as u32,
color: style.color,
underline: style.underline.clone().unwrap_or_default(),
});
}
Self { layout, style_runs }
}
pub fn runs(&self) -> &[Run] {
&self.layout.runs
}
pub fn width(&self) -> Pixels {
self.layout.width
}
pub fn font_size(&self) -> Pixels {
self.layout.font_size
}
pub fn x_for_index(&self, index: usize) -> Pixels {
for run in &self.layout.runs {
for glyph in &run.glyphs {
if glyph.index >= index {
return glyph.position.x;
}
}
}
self.layout.width
}
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
for run in &self.layout.runs {
for glyph in &run.glyphs {
if glyph.index >= index {
return Some(run.font_id);
}
}
}
None
}
pub fn len(&self) -> usize {
self.layout.len
}
pub fn is_empty(&self) -> bool {
self.layout.len == 0
}
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
if x >= self.layout.width {
None
} else {
for run in self.layout.runs.iter().rev() {
for glyph in run.glyphs.iter().rev() {
if glyph.position.x <= x {
return Some(glyph.index);
}
}
}
Some(0)
}
}
// todo!
pub fn paint(
&self,
origin: Point<Pixels>,
visible_bounds: Bounds<Pixels>,
line_height: Pixels,
cx: &mut WindowContext,
) -> Result<()> {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
let mut style_runs = self.style_runs.iter();
let mut run_end = 0;
let mut color = black();
let mut underline = None;
for run in &self.layout.runs {
cx.text_system().with_font(run.font_id, |system, font| {
let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width;
for glyph in &run.glyphs {
let glyph_origin = origin + baseline_offset + glyph.position;
if glyph_origin.x > visible_bounds.upper_right().x {
break;
}
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end {
if let Some(style_run) = style_runs.next() {
if let Some((_, underline_style)) = &mut underline {
if style_run.underline != *underline_style {
finished_underline = underline.take();
}
}
if style_run.underline.thickness > px(0.) {
underline.get_or_insert((
point(
glyph_origin.x,
origin.y
+ baseline_offset.y
+ (self.layout.descent * 0.618),
),
UnderlineStyle {
color: style_run.underline.color,
thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly,
},
));
}
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = self.layout.len;
finished_underline = underline.take();
}
}
if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
continue;
}
if let Some((_underline_origin, _underline_style)) = finished_underline {
// cx.scene().insert(Underline {
// origin: underline_origin,
// width: glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(),
// color: underline_style.color.unwrap(),
// squiggly: underline_style.squiggly,
// });
}
// if glyph.is_emoji {
// cx.scene().push_image_glyph(scene::ImageGlyph {
// font_id: run.font_id,
// font_size: self.layout.font_size,
// id: glyph.id,
// origin: glyph_origin,
// });
// } else {
// cx.scene().push_glyph(scene::Glyph {
// font_id: run.font_id,
// font_size: self.layout.font_size,
// id: glyph.id,
// origin: glyph_origin,
// color,
// });
// }
}
anyhow::Ok(())
})??;
}
if let Some((_underline_start, _underline_style)) = underline.take() {
let _line_end_x = origin.x + self.layout.width;
// cx.scene().push_underline(Underline {
// origin: underline_start,
// width: line_end_x - underline_start.x,
// color: underline_style.color,
// thickness: underline_style.thickness.into(),
// squiggly: underline_style.squiggly,
// });
}
Ok(())
}
pub fn paint_wrapped(
&self,
origin: Point<Pixels>,
_visible_bounds: Bounds<Pixels>,
line_height: Pixels,
boundaries: &[ShapedBoundary],
cx: &mut WindowContext,
) -> Result<()> {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
let mut boundaries = boundaries.into_iter().peekable();
let mut color_runs = self.style_runs.iter();
let mut style_run_end = 0;
let mut _color = black(); // todo!
let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut glyph_origin = origin;
let mut prev_position = px(0.);
for (run_ix, run) in self.layout.runs.iter().enumerate() {
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_position;
if boundaries
.peek()
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
{
boundaries.next();
if let Some((_underline_origin, _underline_style)) = underline.take() {
// cx.scene().push_underline(Underline {
// origin: underline_origin,
// width: glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(),
// color: underline_style.color.unwrap(),
// squiggly: underline_style.squiggly,
// });
}
glyph_origin = point(origin.x, glyph_origin.y + line_height);
}
prev_position = glyph.position.x;
let mut finished_underline = None;
if glyph.index >= style_run_end {
if let Some(style_run) = color_runs.next() {
style_run_end += style_run.len as usize;
_color = style_run.color;
if let Some((_, underline_style)) = &mut underline {
if style_run.underline != *underline_style {
finished_underline = underline.take();
}
}
if style_run.underline.thickness > px(0.) {
underline.get_or_insert((
glyph_origin
+ point(
px(0.),
baseline_offset.y + (self.layout.descent * 0.618),
),
UnderlineStyle {
color: Some(
style_run.underline.color.unwrap_or(style_run.color),
),
thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly,
},
));
}
} else {
style_run_end = self.layout.len;
_color = black();
finished_underline = underline.take();
}
}
if let Some((_underline_origin, _underline_style)) = finished_underline {
// cx.scene().push_underline(Underline {
// origin: underline_origin,
// width: glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(),
// color: underline_style.color.unwrap(),
// squiggly: underline_style.squiggly,
// });
}
cx.text_system().with_font(run.font_id, |system, font| {
let _glyph_bounds = Bounds {
origin: glyph_origin,
size: system.bounding_box(font, self.layout.font_size)?.size,
};
// if glyph_bounds.intersects(visible_bounds) {
// if glyph.is_emoji {
// cx.scene().push_image_glyph(scene::ImageGlyph {
// font_id: run.font_id,
// font_size: self.layout.font_size,
// id: glyph.id,
// origin: glyph_bounds.origin() + baseline_offset,
// });
// } else {
// cx.scene().push_glyph(scene::Glyph {
// font_id: run.font_id,
// font_size: self.layout.font_size,
// id: glyph.id,
// origin: glyph_bounds.origin() + baseline_offset,
// color,
// });
// }
// }
anyhow::Ok(())
})??;
}
}
if let Some((_underline_origin, _underline_style)) = underline.take() {
// let line_end_x = glyph_origin.x + self.layout.width - prev_position;
// cx.scene().push_underline(Underline {
// origin: underline_origin,
// width: line_end_x - underline_origin.x,
// thickness: underline_style.thickness.into(),
// color: underline_style.color,
// squiggly: underline_style.squiggly,
// });
}
Ok(())
}
}
impl Run {
pub fn glyphs(&self) -> &[Glyph] {
&self.glyphs
}
}

49
crates/gpui3/src/util.rs Normal file
View File

@ -0,0 +1,49 @@
use smol::future::FutureExt;
use std::{future::Future, time::Duration};
pub use util::*;
pub fn post_inc(value: &mut usize) -> usize {
let prev = *value;
*value += 1;
prev
}
pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
where
F: Future<Output = T>,
{
let timer = async {
smol::Timer::after(timeout).await;
Err(())
};
let future = async move { Ok(f.await) };
timer.race(future).await
}
#[cfg(any(test, feature = "test"))]
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
#[cfg(any(test, feature = "test"))]
impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use backtrace::{BacktraceFmt, BytesOrWideString};
let cwd = std::env::current_dir().unwrap();
let cwd = cwd.parent().unwrap();
let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
std::fmt::Display::fmt(&path, fmt)
};
let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
for frame in self.0.frames() {
let mut formatted_frame = fmt.frame();
if frame
.symbols()
.iter()
.any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd)))
{
formatted_frame.backtrace_frame(frame)?;
}
}
fmt.finish()
}
}

143
crates/gpui3/src/view.rs Normal file
View File

@ -0,0 +1,143 @@
use parking_lot::Mutex;
use crate::{
AnyElement, Element, Handle, IntoAnyElement, Layout, LayoutId, Result, ViewContext,
WindowContext,
};
use std::{any::Any, marker::PhantomData, sync::Arc};
pub struct View<S: Send + Sync, P> {
state: Handle<S>,
render: Arc<dyn Fn(&mut S, &mut ViewContext<S>) -> AnyElement<S> + Send + Sync + 'static>,
parent_state_type: PhantomData<P>,
}
impl<S: 'static + Send + Sync, P: 'static + Send> View<S, P> {
pub fn into_any(self) -> AnyView<P> {
AnyView {
view: Arc::new(Mutex::new(self)),
parent_state_type: PhantomData,
}
}
}
impl<S: Send + Sync, P> Clone for View<S, P> {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
render: self.render.clone(),
parent_state_type: PhantomData,
}
}
}
pub type RootView<S> = View<S, ()>;
pub fn view<S, P, E>(
state: Handle<S>,
render: impl Fn(&mut S, &mut ViewContext<S>) -> E + Send + Sync + 'static,
) -> View<S, P>
where
S: 'static + Send + Sync,
P: 'static,
E: Element<State = S>,
{
View {
state,
render: Arc::new(move |state, cx| render(state, cx).into_any()),
parent_state_type: PhantomData,
}
}
impl<S: Send + Sync + 'static, P: Send + 'static> Element for View<S, P> {
type State = P;
type FrameState = AnyElement<S>;
fn layout(
&mut self,
_: &mut Self::State,
cx: &mut ViewContext<Self::State>,
) -> Result<(LayoutId, Self::FrameState)> {
self.state.update(cx, |state, cx| {
let mut element = (self.render)(state, cx);
let layout_id = element.layout(state, cx)?;
Ok((layout_id, element))
})
}
fn paint(
&mut self,
_: Layout,
_: &mut Self::State,
element: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>,
) -> Result<()> {
self.state
.update(cx, |state, cx| element.paint(state, None, cx))
}
}
trait ViewObject: Send + 'static {
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
fn paint(
&mut self,
layout: Layout,
element: &mut dyn Any,
cx: &mut WindowContext,
) -> Result<()>;
}
impl<S: Send + Sync + 'static, P: Send + 'static> ViewObject for View<S, P> {
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)> {
self.state.update(cx, |state, cx| {
let mut element = (self.render)(state, cx);
let layout_id = element.layout(state, cx)?;
let element = Box::new(element) as Box<dyn Any>;
Ok((layout_id, element))
})
}
fn paint(&mut self, _: Layout, element: &mut dyn Any, cx: &mut WindowContext) -> Result<()> {
self.state.update(cx, |state, cx| {
let element = element.downcast_mut::<AnyElement<S>>().unwrap();
element.paint(state, None, cx)
})
}
}
pub struct AnyView<S> {
view: Arc<Mutex<dyn ViewObject>>,
parent_state_type: PhantomData<S>,
}
impl<S: 'static> Element for AnyView<S> {
type State = ();
type FrameState = Box<dyn Any>;
fn layout(
&mut self,
_: &mut Self::State,
cx: &mut ViewContext<Self::State>,
) -> Result<(LayoutId, Self::FrameState)> {
self.view.lock().layout(cx)
}
fn paint(
&mut self,
layout: Layout,
_: &mut (),
element: &mut Box<dyn Any>,
cx: &mut ViewContext<Self::State>,
) -> Result<()> {
self.view.lock().paint(layout, element.as_mut(), cx)
}
}
impl<S> Clone for AnyView<S> {
fn clone(&self) -> Self {
Self {
view: self.view.clone(),
parent_state_type: PhantomData,
}
}
}

403
crates/gpui3/src/window.rs Normal file
View File

@ -0,0 +1,403 @@
use crate::{
px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
LayoutId, MainThread, MainThreadOnly, Pixels, PlatformWindow, Point, Reference, Scene, Size,
StackContext, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
};
use anyhow::Result;
use futures::Future;
use std::{any::TypeId, marker::PhantomData, mem, sync::Arc};
use util::ResultExt;
pub struct AnyWindow {}
pub struct Window {
handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
rem_size: Pixels,
content_size: Size<Pixels>,
layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView<()>>,
mouse_position: Point<Pixels>,
pub(crate) scene: Scene,
pub(crate) dirty: bool,
}
impl Window {
pub fn new(
handle: AnyWindowHandle,
options: WindowOptions,
cx: &mut MainThread<AppContext>,
) -> Self {
let platform_window = cx.platform().open_window(handle, options);
let mouse_position = platform_window.mouse_position();
let content_size = platform_window.content_size();
let scale_factor = platform_window.scale_factor();
platform_window.on_resize(Box::new({
let handle = handle;
let cx = cx.to_async();
move |content_size, scale_factor| {
cx.update_window(handle, |cx| {
cx.window.scene = Scene::new(scale_factor);
cx.window.content_size = content_size;
cx.window.dirty = true;
})
.log_err();
}
}));
let platform_window =
MainThreadOnly::new(Arc::new(platform_window), cx.platform().dispatcher());
Window {
handle,
platform_window,
rem_size: px(16.),
content_size,
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
mouse_position,
scene: Scene::new(scale_factor),
dirty: true,
}
}
}
pub struct WindowContext<'a, 'w> {
app: Reference<'a, AppContext>,
window: Reference<'w, Window>,
}
impl<'a, 'w> WindowContext<'a, 'w> {
pub(crate) fn mutable(app: &'a mut AppContext, window: &'w mut Window) -> Self {
Self {
app: Reference::Mutable(app),
window: Reference::Mutable(window),
}
}
pub fn notify(&mut self) {
self.window.dirty = true;
}
pub fn request_layout(
&mut self,
style: Style,
children: impl IntoIterator<Item = LayoutId>,
) -> Result<LayoutId> {
self.app.layout_id_buffer.clear();
self.app.layout_id_buffer.extend(children.into_iter());
let rem_size = self.rem_size();
self.window
.layout_engine
.request_layout(style, rem_size, &self.app.layout_id_buffer)
}
pub fn request_measured_layout<
F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync + 'static,
>(
&mut self,
style: Style,
rem_size: Pixels,
measure: F,
) -> Result<LayoutId> {
self.window
.layout_engine
.request_measured_layout(style, rem_size, measure)
}
pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
Ok(self
.window
.layout_engine
.layout(layout_id)
.map(Into::into)?)
}
pub fn rem_size(&self) -> Pixels {
self.window.rem_size
}
pub fn mouse_position(&self) -> Point<Pixels> {
self.window.mouse_position
}
pub fn scene(&mut self) -> &mut Scene {
&mut self.window.scene
}
pub fn run_on_main<R>(
&self,
f: impl FnOnce(&mut MainThread<WindowContext>) -> R + Send + 'static,
) -> impl Future<Output = Result<R>>
where
R: Send + 'static,
{
let id = self.window.handle.id;
self.app.run_on_main(move |cx| {
cx.update_window(id, |cx| {
f(unsafe {
mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
})
})
})
}
pub(crate) fn draw(&mut self) -> Result<()> {
let unit_entity = self.unit_entity.clone();
self.update_entity(&unit_entity, |_, cx| {
let mut root_view = cx.window.root_view.take().unwrap();
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
let available_space = cx.window.content_size.map(Into::into);
dbg!("computing layout");
cx.window
.layout_engine
.compute_layout(root_layout_id, available_space)?;
dbg!("asking for layout");
let layout = cx.window.layout_engine.layout(root_layout_id)?;
dbg!("painting root view");
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
cx.window.root_view = Some(root_view);
let scene = cx.window.scene.take();
let _ = cx.run_on_main(|cx| {
cx.window
.platform_window
.borrow_on_main_thread()
.draw(scene);
});
cx.window.dirty = false;
Ok(())
})
}
}
impl MainThread<WindowContext<'_, '_>> {
// todo!("implement other methods that use platform window")
fn platform_window(&self) -> &dyn PlatformWindow {
self.window.platform_window.borrow_on_main_thread().as_ref()
}
}
impl Context for WindowContext<'_, '_> {
type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
type Result<T> = T;
fn entity<T: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
) -> Handle<T> {
let slot = self.app.entities.reserve();
let entity = build_entity(&mut ViewContext::mutable(
&mut *self.app,
&mut self.window,
slot.id,
));
self.entities.redeem(slot, entity)
}
fn update_entity<T: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<T>,
update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
) -> R {
let mut entity = self.entities.lease(handle);
let result = update(
&mut *entity,
&mut ViewContext::mutable(&mut *self.app, &mut *self.window, handle.id),
);
self.entities.end_lease(entity);
result
}
}
impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> {
type Target = AppContext;
fn deref(&self) -> &Self::Target {
&self.app
}
}
impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.app
}
}
impl<S> StackContext for ViewContext<'_, '_, S> {
fn app(&mut self) -> &mut AppContext {
&mut *self.window_cx.app
}
fn with_text_style<F, R>(&mut self, style: crate::TextStyleRefinement, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.window_cx.app.push_text_style(style);
let result = f(self);
self.window_cx.app.pop_text_style();
result
}
fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.window_cx.app.push_state(state);
let result = f(self);
self.window_cx.app.pop_state::<T>();
result
}
}
pub struct ViewContext<'a, 'w, S> {
window_cx: WindowContext<'a, 'w>,
entity_type: PhantomData<S>,
entity_id: EntityId,
}
impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
fn mutable(app: &'a mut AppContext, window: &'w mut Window, entity_id: EntityId) -> Self {
Self {
window_cx: WindowContext::mutable(app, window),
entity_id,
entity_type: PhantomData,
}
}
pub fn handle(&self) -> WeakHandle<S> {
self.entities.weak_handle(self.entity_id)
}
pub fn observe<E: Send + Sync + 'static>(
&mut self,
handle: &Handle<E>,
on_notify: impl Fn(&mut S, Handle<E>, &mut ViewContext<'_, '_, S>) + Send + Sync + 'static,
) {
let this = self.handle();
let handle = handle.downgrade();
let window_handle = self.window.handle;
self.app
.observers
.entry(handle.id)
.or_default()
.push(Arc::new(move |cx| {
cx.update_window(window_handle.id, |cx| {
if let Some(handle) = handle.upgrade(cx) {
this.update(cx, |this, cx| on_notify(this, handle, cx))
.is_ok()
} else {
false
}
})
.unwrap_or(false)
}));
}
pub fn notify(&mut self) {
self.window_cx.notify();
self.window_cx
.app
.pending_effects
.push_back(Effect::Notify(self.entity_id));
}
pub(crate) fn erase_state<R>(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R {
let entity_id = self.unit_entity.id;
let mut cx = ViewContext::mutable(
&mut *self.window_cx.app,
&mut *self.window_cx.window,
entity_id,
);
f(&mut cx)
}
}
impl<'a, 'w, S> Context for ViewContext<'a, 'w, S> {
type EntityContext<'b, 'c, U: 'static + Send + Sync> = ViewContext<'b, 'c, U>;
type Result<U> = U;
fn entity<T2: Send + Sync + 'static>(
&mut self,
build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T2>) -> T2,
) -> Handle<T2> {
self.window_cx.entity(build_entity)
}
fn update_entity<U: Send + Sync + 'static, R>(
&mut self,
handle: &Handle<U>,
update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
) -> R {
self.window_cx.update_entity(handle, update)
}
}
impl<'a, 'w, S: 'static> std::ops::Deref for ViewContext<'a, 'w, S> {
type Target = WindowContext<'a, 'w>;
fn deref(&self) -> &Self::Target {
&self.window_cx
}
}
impl<'a, 'w, S: 'static> std::ops::DerefMut for ViewContext<'a, 'w, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.window_cx
}
}
// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
slotmap::new_key_type! { pub struct WindowId; }
#[derive(PartialEq, Eq)]
pub struct WindowHandle<S> {
id: WindowId,
state_type: PhantomData<S>,
}
impl<S> Copy for WindowHandle<S> {}
impl<S> Clone for WindowHandle<S> {
fn clone(&self) -> Self {
WindowHandle {
id: self.id,
state_type: PhantomData,
}
}
}
impl<S> WindowHandle<S> {
pub fn new(id: WindowId) -> Self {
WindowHandle {
id,
state_type: PhantomData,
}
}
}
impl<S: 'static> Into<AnyWindowHandle> for WindowHandle<S> {
fn into(self) -> AnyWindowHandle {
AnyWindowHandle {
id: self.id,
state_type: TypeId::of::<S>(),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct AnyWindowHandle {
pub(crate) id: WindowId,
state_type: TypeId,
}
#[derive(Clone)]
pub struct Layout {
pub order: u32,
pub bounds: Bounds<Pixels>,
}

View File

@ -0,0 +1,14 @@
[package]
name = "gpui3_macros"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/gpui3_macros.rs"
proc-macro = true
[dependencies]
syn = "1.0.72"
quote = "1.0.9"
proc-macro2 = "1.0.66"

View File

@ -0,0 +1,50 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, GenericParam};
pub fn derive_element(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let type_name = ast.ident;
let mut state_type = quote! { () };
for param in &ast.generics.params {
if let GenericParam::Type(type_param) = param {
let type_ident = &type_param.ident;
state_type = quote! {#type_ident};
}
}
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let gen = quote! {
impl #impl_generics gpui3::Element for #type_name #ty_generics
#where_clause
{
type State = #state_type;
type FrameState = gpui3::AnyElement<#state_type>;
fn layout(
&mut self,
state: &mut #state_type,
cx: &mut gpui3::ViewContext<V>,
) -> anyhow::Result<(gpui3::LayoutId, Self::FrameState)> {
let mut rendered_element = self.render(state, cx).into_element().into_any();
let layout_id = rendered_element.layout(state, cx)?;
Ok((layout_id, rendered_element))
}
fn paint(
&mut self,
layout: &gpui3::Layout,
state: &mut #state_type,
rendered_element: &mut Self::FrameState,
cx: &mut gpui3::ViewContext<V>,
) {
rendered_element.paint(layout.origin, state, cx);
}
}
};
gen.into()
}

View File

@ -0,0 +1,14 @@
use proc_macro::TokenStream;
mod derive_element;
mod style_helpers;
#[proc_macro]
pub fn style_helpers(args: TokenStream) -> TokenStream {
style_helpers::style_helpers(args)
}
#[proc_macro_derive(Element, attributes(element_crate))]
pub fn derive_element(input: TokenStream) -> TokenStream {
derive_element::derive_element(input)
}

View File

@ -0,0 +1,326 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
struct StyleableMacroInput;
impl Parse for StyleableMacroInput {
fn parse(_input: ParseStream) -> Result<Self> {
Ok(StyleableMacroInput)
}
}
pub fn style_helpers(input: TokenStream) -> TokenStream {
let _ = parse_macro_input!(input as StyleableMacroInput);
let methods = generate_methods();
let output = quote! {
#(#methods)*
};
output.into()
}
fn generate_methods() -> Vec<TokenStream2> {
let mut methods = Vec::new();
for (prefix, auto_allowed, fields) in box_prefixes() {
for (suffix, length_tokens) in box_suffixes() {
if auto_allowed || suffix != "auto" {
let method = generate_method(prefix, suffix, &fields, length_tokens);
methods.push(method);
}
}
}
for (prefix, fields) in corner_prefixes() {
for (suffix, radius_tokens) in corner_suffixes() {
let method = generate_method(prefix, suffix, &fields, radius_tokens);
methods.push(method);
}
}
for (prefix, fields) in border_prefixes() {
for (suffix, width_tokens) in border_suffixes() {
let method = generate_method(prefix, suffix, &fields, width_tokens);
methods.push(method);
}
}
methods
}
fn generate_method(
prefix: &'static str,
suffix: &'static str,
fields: &Vec<TokenStream2>,
length_tokens: TokenStream2,
) -> TokenStream2 {
let method_name = if suffix.is_empty() {
format_ident!("{}", prefix)
} else {
format_ident!("{}_{}", prefix, suffix)
};
let field_assignments = fields
.iter()
.map(|field_tokens| {
quote! {
style.#field_tokens = Some(gpui3::#length_tokens.into());
}
})
.collect::<Vec<_>>();
let method = quote! {
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
let style = self.declared_style();
#(#field_assignments)*
self
}
};
method
}
fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
vec![
("w", true, vec![quote! { size.width }]),
("h", true, vec![quote! { size.height }]),
(
"size",
true,
vec![quote! {size.width}, quote! {size.height}],
),
("min_w", false, vec![quote! { min_size.width }]),
("min_h", false, vec![quote! { min_size.height }]),
("max_w", false, vec![quote! { max_size.width }]),
("max_h", false, vec![quote! { max_size.height }]),
(
"m",
true,
vec![
quote! { margin.top },
quote! { margin.bottom },
quote! { margin.left },
quote! { margin.right },
],
),
("mt", true, vec![quote! { margin.top }]),
("mb", true, vec![quote! { margin.bottom }]),
(
"my",
true,
vec![quote! { margin.top }, quote! { margin.bottom }],
),
(
"mx",
true,
vec![quote! { margin.left }, quote! { margin.right }],
),
("ml", true, vec![quote! { margin.left }]),
("mr", true, vec![quote! { margin.right }]),
(
"p",
false,
vec![
quote! { padding.top },
quote! { padding.bottom },
quote! { padding.left },
quote! { padding.right },
],
),
("pt", false, vec![quote! { padding.top }]),
("pb", false, vec![quote! { padding.bottom }]),
(
"px",
false,
vec![quote! { padding.left }, quote! { padding.right }],
),
(
"py",
false,
vec![quote! { padding.top }, quote! { padding.bottom }],
),
("pl", false, vec![quote! { padding.left }]),
("pr", false, vec![quote! { padding.right }]),
("top", true, vec![quote! { inset.top }]),
("bottom", true, vec![quote! { inset.bottom }]),
("left", true, vec![quote! { inset.left }]),
("right", true, vec![quote! { inset.right }]),
(
"gap",
false,
vec![quote! { gap.width }, quote! { gap.height }],
),
("gap_x", false, vec![quote! { gap.width }]),
("gap_y", false, vec![quote! { gap.height }]),
]
}
fn box_suffixes() -> Vec<(&'static str, TokenStream2)> {
vec![
("0", quote! { px(0.) }),
("0p5", quote! { rems(0.125) }),
("1", quote! { rems(0.25) }),
("1p5", quote! { rems(0.375) }),
("2", quote! { rems(0.5) }),
("2p5", quote! { rems(0.625) }),
("3", quote! { rems(0.75) }),
("3p5", quote! { rems(0.875) }),
("4", quote! { rems(1.) }),
("5", quote! { rems(1.25) }),
("6", quote! { rems(1.5) }),
("7", quote! { rems(1.75) }),
("8", quote! { rems(2.0) }),
("9", quote! { rems(2.25) }),
("10", quote! { rems(2.5) }),
("11", quote! { rems(2.75) }),
("12", quote! { rems(3.) }),
("16", quote! { rems(4.) }),
("20", quote! { rems(5.) }),
("24", quote! { rems(6.) }),
("32", quote! { rems(8.) }),
("40", quote! { rems(10.) }),
("48", quote! { rems(12.) }),
("56", quote! { rems(14.) }),
("64", quote! { rems(16.) }),
("72", quote! { rems(18.) }),
("80", quote! { rems(20.) }),
("96", quote! { rems(24.) }),
("auto", quote! { auto() }),
("px", quote! { px(1.) }),
("full", quote! { relative(1.) }),
("1_2", quote! { relative(0.5) }),
("1_3", quote! { relative(1./3.) }),
("2_3", quote! { relative(2./3.) }),
("1_4", quote! { relative(0.25) }),
("2_4", quote! { relative(0.5) }),
("3_4", quote! { relative(0.75) }),
("1_5", quote! { relative(0.2) }),
("2_5", quote! { relative(0.4) }),
("3_5", quote! { relative(0.6) }),
("4_5", quote! { relative(0.8) }),
("1_6", quote! { relative(1./6.) }),
("5_6", quote! { relative(5./6.) }),
("1_12", quote! { relative(1./12.) }),
// ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
// ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
// ("screen", quote! { DefiniteLength::Vh(100.0) }),
]
}
fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
vec![
(
"rounded",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
quote! { corner_radii.bottom_left },
],
),
(
"rounded_t",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
],
),
(
"rounded_b",
vec![
quote! { corner_radii.bottom_left },
quote! { corner_radii.bottom_right },
],
),
(
"rounded_r",
vec![
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
],
),
(
"rounded_l",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.bottom_left },
],
),
("rounded_tl", vec![quote! { corner_radii.top_left }]),
("rounded_tr", vec![quote! { corner_radii.top_right }]),
("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
("rounded_br", vec![quote! { corner_radii.bottom_right }]),
]
}
fn corner_suffixes() -> Vec<(&'static str, TokenStream2)> {
vec![
("none", quote! { px(0.) }),
("sm", quote! { rems(0.125) }),
("md", quote! { rems(0.25) }),
("lg", quote! { rems(0.5) }),
("xl", quote! { rems(0.75) }),
("2xl", quote! { rems(1.) }),
("3xl", quote! { rems(1.5) }),
("full", quote! { px(9999.) }),
]
}
fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
vec![
(
"border",
vec![
quote! { border_widths.top },
quote! { border_widths.right },
quote! { border_widths.bottom },
quote! { border_widths.left },
],
),
("border_t", vec![quote! { border_widths.top }]),
("border_b", vec![quote! { border_widths.bottom }]),
("border_r", vec![quote! { border_widths.right }]),
("border_l", vec![quote! { border_widths.left }]),
(
"border_x",
vec![
quote! { border_widths.left },
quote! { border_widths.right },
],
),
(
"border_y",
vec![
quote! { border_widths.top },
quote! { border_widths.bottom },
],
),
]
}
fn border_suffixes() -> Vec<(&'static str, TokenStream2)> {
vec![
("", quote! { px(1.) }),
("0", quote! { px(0.) }),
("1", quote! { px(1.) }),
("2", quote! { px(2.) }),
("3", quote! { px(3.) }),
("4", quote! { px(4.) }),
("5", quote! { px(5.) }),
("6", quote! { px(6.) }),
("7", quote! { px(7.) }),
("8", quote! { px(8.) }),
("9", quote! { px(9.) }),
("10", quote! { px(10.) }),
("11", quote! { px(11.) }),
("12", quote! { px(12.) }),
("16", quote! { px(16.) }),
("20", quote! { px(20.) }),
("24", quote! { px(24.) }),
("32", quote! { px(32.) }),
]
}

View File

@ -35,7 +35,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
let field_visibilities: Vec<_> = fields.iter().map(|f| &f.vis).collect();
let wrapped_types: Vec<_> = fields.iter().map(|f| get_wrapper_type(f, &f.ty)).collect();
// Create trait bound that each wrapped type must implement Clone & Default
// Create trait bound that each wrapped type must implement Clone // & Default
let type_param_bounds: Vec<_> = wrapped_types
.iter()
.map(|ty| {
@ -51,13 +51,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
lifetimes: None,
path: parse_quote!(Clone),
}));
punctuated.push_punct(syn::token::Add::default());
punctuated.push_value(TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path: parse_quote!(Default),
}));
// punctuated.push_punct(syn::token::Add::default());
// punctuated.push_value(TypeParamBound::Trait(TraitBound {
// paren_token: None,
// modifier: syn::TraitBoundModifier::None,
// lifetimes: None,
// path: parse_quote!(Default),
// }));
punctuated
},
})
@ -161,7 +162,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
};
let gen = quote! {
#[derive(Default, Clone)]
#[derive(Clone)]
pub struct #refinement_ident #impl_generics {
#( #field_visibilities #field_names: #wrapped_types ),*
}
@ -186,6 +187,16 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
}
}
impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics
#where_clause
{
fn default() -> Self {
#refinement_ident {
#( #field_names: Default::default() ),*
}
}
}
impl #impl_generics #refinement_ident #ty_generics
#where_clause
{

View File

@ -17,6 +17,12 @@ pub trait Refineable: Clone {
{
Self::default().refined(refinement)
}
fn from_cascade(cascade: &RefinementCascade<Self>) -> Self
where
Self: Default + Sized,
{
Self::default().refined(&cascade.merged())
}
}
pub struct RefinementCascade<S: Refineable>(Vec<Option<S::Refinement>>);

View File

@ -8,15 +8,20 @@ publish = false
name = "storybook"
path = "src/storybook.rs"
[features]
test-support = []
[dependencies]
anyhow.workspace = true
clap = { version = "4.4", features = ["derive", "string"] }
chrono = "0.4"
derive_more.workspace = true
fs = { path = "../fs" }
futures.workspace = true
gpui2 = { path = "../gpui2" }
itertools = "0.11.0"
log.workspace = true
refineable = { path = "../refineable" }
rust-embed.workspace = true
serde.workspace = true
settings = { path = "../settings" }

View File

@ -0,0 +1,5 @@
fn main() {
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
// TODO: We shouldn't depend on WebRTC in editor
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
}

2919
crates/storybook2/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
[package]
name = "storybook2"
version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "storybook"
path = "src/storybook2.rs"
[dependencies]
anyhow.workspace = true
# TODO: Remove after diagnosing stack overflow.
backtrace-on-stack-overflow = "0.3.0"
gpui3 = { path = "../gpui3" }
log.workspace = true
rust-embed.workspace = true
serde.workspace = true
settings = { path = "../settings" }
simplelog = "0.9"
theme = { path = "../theme" }
util = { path = "../util" }
[dev-dependencies]
gpui3 = { path = "../gpui3", features = ["test"] }

View File

@ -0,0 +1,5 @@
fn main() {
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
// TODO: We shouldn't depend on WebRTC in editor
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
}

View File

@ -0,0 +1,72 @@
Much of element styling is now handled by an external engine.
How do I make an element hover.
There's a hover style.
Hoverable needs to wrap another element. That element can be styled.
```rs
struct Hoverable<E: Element> {
}
impl<V> Element<V> for Hoverable {
}
```
```rs
#[derive(Styled, Interactive)]
pub struct Div {
declared_style: StyleRefinement,
interactions: Interactions
}
pub trait Styled {
fn declared_style(&mut self) -> &mut StyleRefinement;
fn compute_style(&mut self) -> Style {
Style::default().refine(self.declared_style())
}
// All the tailwind classes, modifying self.declared_style()
}
impl Style {
pub fn paint_background<V>(layout: Layout, cx: &mut PaintContext<V>);
pub fn paint_foreground<V>(layout: Layout, cx: &mut PaintContext<V>);
}
pub trait Interactive<V> {
fn interactions(&mut self) -> &mut Interactions<V>;
fn on_click(self, )
}
struct Interactions<V> {
click: SmallVec<[<Rc<dyn Fn(&mut V, &dyn Any, )>; 1]>,
}
```
```rs
trait Stylable {
type Style;
fn with_style(self, style: Self::Style) -> Self;
}
```

View File

@ -0,0 +1,176 @@
use crate::theme::{theme, Theme};
use gpui3::{
div, img, svg, view, AppContext, ArcCow, Context, Element, IntoAnyElement, ParentElement,
ScrollState, StyleHelpers, View, ViewContext, WindowContext,
};
pub struct CollabPanel {
scroll_state: ScrollState,
}
pub fn collab_panel<S: 'static>(cx: &mut WindowContext) -> View<CollabPanel, S> {
view(cx.entity(|cx| CollabPanel::new(cx)), CollabPanel::render)
}
impl CollabPanel {
fn new(_: &mut AppContext) -> Self {
CollabPanel {
scroll_state: ScrollState::default(),
}
}
}
impl CollabPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
let theme = theme(cx);
// Panel
div()
.w_64()
.h_full()
.flex()
.flex_col()
.font("Zed Sans Extended")
.text_color(theme.middle.base.default.foreground)
.border_color(theme.middle.base.default.border)
.border()
.fill(theme.middle.base.default.background)
.child(
div()
.w_full()
.flex()
.flex_col()
.overflow_y_scroll(self.scroll_state.clone())
// List Container
.child(
div()
.fill(theme.lowest.base.default.background)
.pb_1()
.border_color(theme.lowest.base.default.border)
.border_b()
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
// .group()
// List Section Header
.child(self.list_section_header("#CRDB", true, theme))
// List Item Large
.child(self.list_item(
"http://github.com/maxbrunsfeld.png?s=50",
"maxbrunsfeld",
theme,
)),
)
.child(
div()
.py_2()
.flex()
.flex_col()
.child(self.list_section_header("CHANNELS", true, theme)),
)
.child(
div()
.py_2()
.flex()
.flex_col()
.child(self.list_section_header("CONTACTS", true, theme))
.children(
std::iter::repeat_with(|| {
vec![
self.list_item(
"http://github.com/as-cii.png?s=50",
"as-cii",
theme,
),
self.list_item(
"http://github.com/nathansobo.png?s=50",
"nathansobo",
theme,
),
self.list_item(
"http://github.com/maxbrunsfeld.png?s=50",
"maxbrunsfeld",
theme,
),
]
})
.take(10)
.flatten(),
),
),
)
.child(
div()
.h_7()
.px_2()
.border_t()
.border_color(theme.middle.variant.default.border)
.flex()
.items_center()
.child(
div()
.text_sm()
.text_color(theme.middle.variant.default.foreground)
.child("Find..."),
),
)
}
fn list_section_header(
&self,
label: impl IntoAnyElement<Self>,
expanded: bool,
theme: &Theme,
) -> impl Element<State = Self> {
div()
.h_7()
.px_2()
.flex()
.justify_between()
.items_center()
.child(div().flex().gap_1().text_sm().child(label))
.child(
div().flex().h_full().gap_1().items_center().child(
svg()
.path(if expanded {
"icons/radix/caret-down.svg"
} else {
"icons/radix/caret-up.svg"
})
.w_3p5()
.h_3p5()
.fill(theme.middle.variant.default.foreground),
),
)
}
fn list_item(
&self,
avatar_uri: impl Into<ArcCow<'static, str>>,
label: impl IntoAnyElement<Self>,
theme: &Theme,
) -> impl Element<State = Self> {
div()
.h_7()
.px_2()
.flex()
.items_center()
// .hover()
// .fill(theme.lowest.variant.hovered.background)
// .active()
// .fill(theme.lowest.variant.pressed.background)
.child(
div()
.flex()
.items_center()
.gap_1()
.text_sm()
.child(
img()
.uri(avatar_uri)
.size_3p5()
.rounded_full()
.fill(theme.middle.positive.default.foreground),
)
.child(label),
)
}
}

View File

@ -0,0 +1,97 @@
use gpui2::{
elements::div, interactive::Interactive, platform::MouseButton, style::StyleHelpers, ArcCow,
Element, EventContext, IntoElement, ParentElement, ViewContext,
};
use std::{marker::PhantomData, rc::Rc};
struct ButtonHandlers<V, D> {
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
}
impl<V, D> Default for ButtonHandlers<V, D> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(Element)]
pub struct Button<V: 'static, D: 'static> {
handlers: ButtonHandlers<V, D>,
label: Option<ArcCow<'static, str>>,
icon: Option<ArcCow<'static, str>>,
data: Rc<D>,
view_type: PhantomData<V>,
}
// Impl block for buttons without data.
// See below for an impl block for any button.
impl<V: 'static> Button<V, ()> {
fn new() -> Self {
Self {
handlers: ButtonHandlers::default(),
label: None,
icon: None,
data: Rc::new(()),
view_type: PhantomData,
}
}
pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
Button {
handlers: ButtonHandlers::default(),
label: self.label,
icon: self.icon,
data: Rc::new(data),
view_type: PhantomData,
}
}
}
// Impl block for button regardless of its data type.
impl<V: 'static, D: 'static> Button<V, D> {
pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
self.label = Some(label.into());
self
}
pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
self.icon = Some(icon.into());
self
}
pub fn on_click(
mut self,
handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers.click = Some(Rc::new(handler));
self
}
}
pub fn button<V>() -> Button<V, ()> {
Button::new()
}
impl<V: 'static, D: 'static> Button<V, D> {
fn render(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> impl IntoElement<V> + Interactive<V> {
// let colors = &cx.theme::<Theme>().colors;
let button = div()
// .fill(colors.error(0.5))
.h_4()
.children(self.label.clone());
if let Some(handler) = self.handlers.click.clone() {
let data = self.data.clone();
button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
handler(view, data.as_ref(), cx)
})
} else {
button
}
}
}

View File

@ -0,0 +1,75 @@
#![allow(dead_code, unused_variables)]
use gpui3::{Bounds, WindowBounds, WindowOptions};
use log::LevelFilter;
use simplelog::SimpleLogger;
mod collab_panel;
mod theme;
mod themes;
mod workspace;
// gpui2::actions! {
// storybook,
// [ToggleInspector]
// }
fn main() {
// unsafe { backtrace_on_stack_overflow::enable() };
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
gpui3::App::production().run(|cx| {
let window = cx.open_window(
WindowOptions {
bounds: WindowBounds::Fixed(Bounds {
size: gpui3::Size {
width: 800_f32.into(),
height: 600_f32.into(),
},
..Default::default()
}),
..Default::default()
},
|cx| workspace(cx),
);
cx.activate(true);
});
}
use rust_embed::RustEmbed;
use workspace::workspace;
#[derive(RustEmbed)]
#[folder = "../../assets"]
#[include = "themes/**/*"]
#[include = "fonts/**/*"]
#[include = "icons/**/*"]
#[exclude = "*.DS_Store"]
pub struct Assets;
// impl AssetSource for Assets {
// fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
// Self::get(path)
// .map(|f| f.data)
// .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
// }
// fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
// Self::iter().filter(|p| p.starts_with(path)).collect()
// }
// }
// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
// let font_paths = Assets.list("fonts");
// let mut embedded_fonts = Vec::new();
// for font_path in &font_paths {
// if font_path.ends_with(".ttf") {
// let font_path = &*font_path;
// let font_bytes = Assets.load(font_path).unwrap().to_vec();
// embedded_fonts.push(Arc::from(font_bytes));
// }
// }
// platform.fonts().add_fonts(&embedded_fonts).unwrap();
// }

View File

@ -0,0 +1,193 @@
use gpui3::{Element, Hsla, Layout, LayoutId, Result, StackContext, ViewContext, WindowContext};
use serde::{de::Visitor, Deserialize, Deserializer};
use std::{collections::HashMap, fmt};
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Theme {
pub name: String,
pub is_light: bool,
pub lowest: Layer,
pub middle: Layer,
pub highest: Layer,
pub popover_shadow: Shadow,
pub modal_shadow: Shadow,
#[serde(deserialize_with = "deserialize_player_colors")]
pub players: Vec<PlayerColors>,
#[serde(deserialize_with = "deserialize_syntax_colors")]
pub syntax: HashMap<String, Hsla>,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Layer {
pub base: StyleSet,
pub variant: StyleSet,
pub on: StyleSet,
pub accent: StyleSet,
pub positive: StyleSet,
pub warning: StyleSet,
pub negative: StyleSet,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct StyleSet {
#[serde(rename = "default")]
pub default: ContainerColors,
pub hovered: ContainerColors,
pub pressed: ContainerColors,
pub active: ContainerColors,
pub disabled: ContainerColors,
pub inverted: ContainerColors,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct ContainerColors {
pub background: Hsla,
pub foreground: Hsla,
pub border: Hsla,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct PlayerColors {
pub selection: Hsla,
pub cursor: Hsla,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Shadow {
pub blur: u8,
pub color: Hsla,
pub offset: Vec<u8>,
}
fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
where
D: Deserializer<'de>,
{
struct PlayerArrayVisitor;
impl<'de> Visitor<'de> for PlayerArrayVisitor {
type Value = Vec<PlayerColors>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an object with integer keys")
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
let mut players = Vec::with_capacity(8);
while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
if key < 8 {
players.push(value);
} else {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(key as u64),
&"a key in range 0..7",
));
}
}
Ok(players)
}
}
deserializer.deserialize_map(PlayerArrayVisitor)
}
fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct ColorWrapper {
color: Hsla,
}
struct SyntaxVisitor;
impl<'de> Visitor<'de> for SyntaxVisitor {
type Value = HashMap<String, Hsla>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map with keys and objects with a single color field as values")
}
fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut result = HashMap::new();
while let Some(key) = map.next_key()? {
let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
result.insert(key, wrapper.color);
}
Ok(result)
}
}
deserializer.deserialize_map(SyntaxVisitor)
}
pub fn themed<E, F>(theme: Theme, cx: &mut ViewContext<E::State>, build_child: F) -> Themed<E>
where
E: Element,
F: FnOnce(&mut ViewContext<E::State>) -> E,
{
let child = cx.with_state(theme.clone(), |cx| build_child(cx));
Themed { theme, child }
}
pub struct Themed<E> {
pub(crate) theme: Theme,
pub(crate) child: E,
}
impl<E: Element> Element for Themed<E> {
type State = E::State;
type FrameState = E::FrameState;
fn layout(
&mut self,
state: &mut E::State,
cx: &mut ViewContext<E::State>,
) -> anyhow::Result<(LayoutId, Self::FrameState)>
where
Self: Sized,
{
cx.with_state(self.theme.clone(), |cx| self.child.layout(state, cx))
}
fn paint(
&mut self,
layout: Layout,
state: &mut Self::State,
frame_state: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>,
) -> Result<()>
where
Self: Sized,
{
cx.with_state(self.theme.clone(), |cx| {
self.child.paint(layout, state, frame_state, cx)
})
}
}
// fn preferred_theme<V: 'static>(cx: &AppContext) -> Theme {
// settings::get::<ThemeSettings>(cx)
// .theme
// .deserialized_base_theme
// .lock()
// .get_or_insert_with(|| {
// let theme: Theme =
// serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
// .unwrap();
// Box::new(theme)
// })
// .downcast_ref::<Theme>()
// .unwrap()
// .clone()
// }
pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
cx.state()
}

View File

@ -0,0 +1,3 @@
mod rose_pine_dawn;
pub use rose_pine_dawn::*;

View File

@ -0,0 +1,845 @@
use crate::theme::Theme;
use gpui3::serde_json::{self, json};
pub fn rose_pine_dawn() -> Theme {
serde_json::from_value(json! {
{
"name": "Rosé Pine",
"is_light": false,
"ramps": {},
"lowest": {
"base": {
"default": {
"background": "#292739",
"border": "#423f55",
"foreground": "#e0def4"
},
"hovered": {
"background": "#423f55",
"border": "#423f55",
"foreground": "#e0def4"
},
"pressed": {
"background": "#4e4b63",
"border": "#423f55",
"foreground": "#e0def4"
},
"active": {
"background": "#47445b",
"border": "#36334a",
"foreground": "#e0def4"
},
"disabled": {
"background": "#292739",
"border": "#353347",
"foreground": "#2f2b43"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#4b4860"
}
},
"variant": {
"default": {
"background": "#292739",
"border": "#423f55",
"foreground": "#75718e"
},
"hovered": {
"background": "#423f55",
"border": "#423f55",
"foreground": "#75718e"
},
"pressed": {
"background": "#4e4b63",
"border": "#423f55",
"foreground": "#75718e"
},
"active": {
"background": "#47445b",
"border": "#36334a",
"foreground": "#e0def4"
},
"disabled": {
"background": "#292739",
"border": "#353347",
"foreground": "#2f2b43"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#4b4860"
}
},
"on": {
"default": {
"background": "#1d1b2a",
"border": "#232132",
"foreground": "#e0def4"
},
"hovered": {
"background": "#232132",
"border": "#232132",
"foreground": "#e0def4"
},
"pressed": {
"background": "#2f2d40",
"border": "#232132",
"foreground": "#e0def4"
},
"active": {
"background": "#403e53",
"border": "#504d65",
"foreground": "#e0def4"
},
"disabled": {
"background": "#1d1b2a",
"border": "#1e1c2c",
"foreground": "#3b384f"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#3b394e"
}
},
"accent": {
"default": {
"background": "#2f3739",
"border": "#435255",
"foreground": "#9cced7"
},
"hovered": {
"background": "#435255",
"border": "#435255",
"foreground": "#9cced7"
},
"pressed": {
"background": "#4e6164",
"border": "#435255",
"foreground": "#9cced7"
},
"active": {
"background": "#5d757a",
"border": "#6e8f94",
"foreground": "#fbfdfd"
},
"disabled": {
"background": "#2f3739",
"border": "#3a4446",
"foreground": "#85aeb5"
},
"inverted": {
"background": "#fbfdfd",
"border": "#171717",
"foreground": "#587074"
}
},
"positive": {
"default": {
"background": "#182e23",
"border": "#254839",
"foreground": "#5dc2a3"
},
"hovered": {
"background": "#254839",
"border": "#254839",
"foreground": "#5dc2a3"
},
"pressed": {
"background": "#2c5645",
"border": "#254839",
"foreground": "#5dc2a3"
},
"active": {
"background": "#356b57",
"border": "#40836c",
"foreground": "#f9fdfb"
},
"disabled": {
"background": "#182e23",
"border": "#1e3b2e",
"foreground": "#4ea287"
},
"inverted": {
"background": "#f9fdfb",
"border": "#000e00",
"foreground": "#326552"
}
},
"warning": {
"default": {
"background": "#50341a",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"hovered": {
"background": "#6d4d2b",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"pressed": {
"background": "#7e5a34",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"active": {
"background": "#946e41",
"border": "#b0854f",
"foreground": "#fffcf9"
},
"disabled": {
"background": "#50341a",
"border": "#5e4023",
"foreground": "#d2a263"
},
"inverted": {
"background": "#fffcf9",
"border": "#2c1600",
"foreground": "#8e683c"
}
},
"negative": {
"default": {
"background": "#431820",
"border": "#612834",
"foreground": "#ea6f92"
},
"hovered": {
"background": "#612834",
"border": "#612834",
"foreground": "#ea6f92"
},
"pressed": {
"background": "#71303f",
"border": "#612834",
"foreground": "#ea6f92"
},
"active": {
"background": "#883c4f",
"border": "#a44961",
"foreground": "#fff9fa"
},
"disabled": {
"background": "#431820",
"border": "#52202a",
"foreground": "#c75c79"
},
"inverted": {
"background": "#fff9fa",
"border": "#230000",
"foreground": "#82384a"
}
}
},
"middle": {
"base": {
"default": {
"background": "#1d1b2a",
"border": "#232132",
"foreground": "#e0def4"
},
"hovered": {
"background": "#232132",
"border": "#232132",
"foreground": "#e0def4"
},
"pressed": {
"background": "#2f2d40",
"border": "#232132",
"foreground": "#e0def4"
},
"active": {
"background": "#403e53",
"border": "#504d65",
"foreground": "#e0def4"
},
"disabled": {
"background": "#1d1b2a",
"border": "#1e1c2c",
"foreground": "#3b384f"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#3b394e"
}
},
"variant": {
"default": {
"background": "#1d1b2a",
"border": "#232132",
"foreground": "#75718e"
},
"hovered": {
"background": "#232132",
"border": "#232132",
"foreground": "#75718e"
},
"pressed": {
"background": "#2f2d40",
"border": "#232132",
"foreground": "#75718e"
},
"active": {
"background": "#403e53",
"border": "#504d65",
"foreground": "#e0def4"
},
"disabled": {
"background": "#1d1b2a",
"border": "#1e1c2c",
"foreground": "#3b384f"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#3b394e"
}
},
"on": {
"default": {
"background": "#191724",
"border": "#1c1a29",
"foreground": "#e0def4"
},
"hovered": {
"background": "#1c1a29",
"border": "#1c1a29",
"foreground": "#e0def4"
},
"pressed": {
"background": "#1d1b2b",
"border": "#1c1a29",
"foreground": "#e0def4"
},
"active": {
"background": "#222031",
"border": "#353347",
"foreground": "#e0def4"
},
"disabled": {
"background": "#191724",
"border": "#1a1826",
"foreground": "#4e4b63"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#1f1d2e"
}
},
"accent": {
"default": {
"background": "#2f3739",
"border": "#435255",
"foreground": "#9cced7"
},
"hovered": {
"background": "#435255",
"border": "#435255",
"foreground": "#9cced7"
},
"pressed": {
"background": "#4e6164",
"border": "#435255",
"foreground": "#9cced7"
},
"active": {
"background": "#5d757a",
"border": "#6e8f94",
"foreground": "#fbfdfd"
},
"disabled": {
"background": "#2f3739",
"border": "#3a4446",
"foreground": "#85aeb5"
},
"inverted": {
"background": "#fbfdfd",
"border": "#171717",
"foreground": "#587074"
}
},
"positive": {
"default": {
"background": "#182e23",
"border": "#254839",
"foreground": "#5dc2a3"
},
"hovered": {
"background": "#254839",
"border": "#254839",
"foreground": "#5dc2a3"
},
"pressed": {
"background": "#2c5645",
"border": "#254839",
"foreground": "#5dc2a3"
},
"active": {
"background": "#356b57",
"border": "#40836c",
"foreground": "#f9fdfb"
},
"disabled": {
"background": "#182e23",
"border": "#1e3b2e",
"foreground": "#4ea287"
},
"inverted": {
"background": "#f9fdfb",
"border": "#000e00",
"foreground": "#326552"
}
},
"warning": {
"default": {
"background": "#50341a",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"hovered": {
"background": "#6d4d2b",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"pressed": {
"background": "#7e5a34",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"active": {
"background": "#946e41",
"border": "#b0854f",
"foreground": "#fffcf9"
},
"disabled": {
"background": "#50341a",
"border": "#5e4023",
"foreground": "#d2a263"
},
"inverted": {
"background": "#fffcf9",
"border": "#2c1600",
"foreground": "#8e683c"
}
},
"negative": {
"default": {
"background": "#431820",
"border": "#612834",
"foreground": "#ea6f92"
},
"hovered": {
"background": "#612834",
"border": "#612834",
"foreground": "#ea6f92"
},
"pressed": {
"background": "#71303f",
"border": "#612834",
"foreground": "#ea6f92"
},
"active": {
"background": "#883c4f",
"border": "#a44961",
"foreground": "#fff9fa"
},
"disabled": {
"background": "#431820",
"border": "#52202a",
"foreground": "#c75c79"
},
"inverted": {
"background": "#fff9fa",
"border": "#230000",
"foreground": "#82384a"
}
}
},
"highest": {
"base": {
"default": {
"background": "#191724",
"border": "#1c1a29",
"foreground": "#e0def4"
},
"hovered": {
"background": "#1c1a29",
"border": "#1c1a29",
"foreground": "#e0def4"
},
"pressed": {
"background": "#1d1b2b",
"border": "#1c1a29",
"foreground": "#e0def4"
},
"active": {
"background": "#222031",
"border": "#353347",
"foreground": "#e0def4"
},
"disabled": {
"background": "#191724",
"border": "#1a1826",
"foreground": "#4e4b63"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#1f1d2e"
}
},
"variant": {
"default": {
"background": "#191724",
"border": "#1c1a29",
"foreground": "#75718e"
},
"hovered": {
"background": "#1c1a29",
"border": "#1c1a29",
"foreground": "#75718e"
},
"pressed": {
"background": "#1d1b2b",
"border": "#1c1a29",
"foreground": "#75718e"
},
"active": {
"background": "#222031",
"border": "#353347",
"foreground": "#e0def4"
},
"disabled": {
"background": "#191724",
"border": "#1a1826",
"foreground": "#4e4b63"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#1f1d2e"
}
},
"on": {
"default": {
"background": "#1d1b2a",
"border": "#232132",
"foreground": "#e0def4"
},
"hovered": {
"background": "#232132",
"border": "#232132",
"foreground": "#e0def4"
},
"pressed": {
"background": "#2f2d40",
"border": "#232132",
"foreground": "#e0def4"
},
"active": {
"background": "#403e53",
"border": "#504d65",
"foreground": "#e0def4"
},
"disabled": {
"background": "#1d1b2a",
"border": "#1e1c2c",
"foreground": "#3b384f"
},
"inverted": {
"background": "#e0def4",
"border": "#191724",
"foreground": "#3b394e"
}
},
"accent": {
"default": {
"background": "#2f3739",
"border": "#435255",
"foreground": "#9cced7"
},
"hovered": {
"background": "#435255",
"border": "#435255",
"foreground": "#9cced7"
},
"pressed": {
"background": "#4e6164",
"border": "#435255",
"foreground": "#9cced7"
},
"active": {
"background": "#5d757a",
"border": "#6e8f94",
"foreground": "#fbfdfd"
},
"disabled": {
"background": "#2f3739",
"border": "#3a4446",
"foreground": "#85aeb5"
},
"inverted": {
"background": "#fbfdfd",
"border": "#171717",
"foreground": "#587074"
}
},
"positive": {
"default": {
"background": "#182e23",
"border": "#254839",
"foreground": "#5dc2a3"
},
"hovered": {
"background": "#254839",
"border": "#254839",
"foreground": "#5dc2a3"
},
"pressed": {
"background": "#2c5645",
"border": "#254839",
"foreground": "#5dc2a3"
},
"active": {
"background": "#356b57",
"border": "#40836c",
"foreground": "#f9fdfb"
},
"disabled": {
"background": "#182e23",
"border": "#1e3b2e",
"foreground": "#4ea287"
},
"inverted": {
"background": "#f9fdfb",
"border": "#000e00",
"foreground": "#326552"
}
},
"warning": {
"default": {
"background": "#50341a",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"hovered": {
"background": "#6d4d2b",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"pressed": {
"background": "#7e5a34",
"border": "#6d4d2b",
"foreground": "#f5c177"
},
"active": {
"background": "#946e41",
"border": "#b0854f",
"foreground": "#fffcf9"
},
"disabled": {
"background": "#50341a",
"border": "#5e4023",
"foreground": "#d2a263"
},
"inverted": {
"background": "#fffcf9",
"border": "#2c1600",
"foreground": "#8e683c"
}
},
"negative": {
"default": {
"background": "#431820",
"border": "#612834",
"foreground": "#ea6f92"
},
"hovered": {
"background": "#612834",
"border": "#612834",
"foreground": "#ea6f92"
},
"pressed": {
"background": "#71303f",
"border": "#612834",
"foreground": "#ea6f92"
},
"active": {
"background": "#883c4f",
"border": "#a44961",
"foreground": "#fff9fa"
},
"disabled": {
"background": "#431820",
"border": "#52202a",
"foreground": "#c75c79"
},
"inverted": {
"background": "#fff9fa",
"border": "#230000",
"foreground": "#82384a"
}
}
},
"popover_shadow": {
"blur": 4,
"color": "#00000033",
"offset": [
1,
2
]
},
"modal_shadow": {
"blur": 16,
"color": "#00000033",
"offset": [
0,
2
]
},
"players": {
"0": {
"selection": "#9cced73d",
"cursor": "#9cced7"
},
"1": {
"selection": "#5dc2a33d",
"cursor": "#5dc2a3"
},
"2": {
"selection": "#9d76913d",
"cursor": "#9d7691"
},
"3": {
"selection": "#c4a7e63d",
"cursor": "#c4a7e6"
},
"4": {
"selection": "#c4a7e63d",
"cursor": "#c4a7e6"
},
"5": {
"selection": "#32748f3d",
"cursor": "#32748f"
},
"6": {
"selection": "#ea6f923d",
"cursor": "#ea6f92"
},
"7": {
"selection": "#f5c1773d",
"cursor": "#f5c177"
}
},
"syntax": {
"comment": {
"color": "#6e6a86"
},
"operator": {
"color": "#31748f"
},
"punctuation": {
"color": "#908caa"
},
"variable": {
"color": "#e0def4"
},
"string": {
"color": "#f6c177"
},
"type": {
"color": "#9ccfd8"
},
"type.builtin": {
"color": "#9ccfd8"
},
"boolean": {
"color": "#ebbcba"
},
"function": {
"color": "#ebbcba"
},
"keyword": {
"color": "#31748f"
},
"tag": {
"color": "#9ccfd8"
},
"function.method": {
"color": "#ebbcba"
},
"title": {
"color": "#f6c177"
},
"link_text": {
"color": "#9ccfd8",
"italic": false
},
"link_uri": {
"color": "#ebbcba"
}
},
"color_family": {
"neutral": {
"low": 11.568627450980392,
"high": 91.37254901960785,
"range": 79.80392156862746,
"scaling_value": 1.2530712530712529
},
"red": {
"low": 6.862745098039216,
"high": 100,
"range": 93.13725490196079,
"scaling_value": 1.0736842105263158
},
"orange": {
"low": 5.490196078431373,
"high": 100,
"range": 94.50980392156863,
"scaling_value": 1.058091286307054
},
"yellow": {
"low": 8.627450980392156,
"high": 100,
"range": 91.37254901960785,
"scaling_value": 1.094420600858369
},
"green": {
"low": 2.7450980392156863,
"high": 100,
"range": 97.25490196078431,
"scaling_value": 1.028225806451613
},
"cyan": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
},
"blue": {
"low": 9.019607843137255,
"high": 100,
"range": 90.98039215686275,
"scaling_value": 1.0991379310344827
},
"violet": {
"low": 5.490196078431373,
"high": 100,
"range": 94.50980392156863,
"scaling_value": 1.058091286307054
},
"magenta": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
}
}
}
})
.unwrap()
}

View File

@ -0,0 +1,473 @@
use crate::{
collab_panel::{collab_panel, CollabPanel},
theme::theme,
themes::rose_pine_dawn,
};
use gpui3::{
div, img, svg, view, Context, Element, ParentElement, RootView, StyleHelpers, View,
ViewContext, WindowContext,
};
pub struct Workspace {
left_panel: View<CollabPanel, Self>,
right_panel: View<CollabPanel, Self>,
}
pub fn workspace(cx: &mut WindowContext) -> RootView<Workspace> {
view(cx.entity(|cx| Workspace::new(cx)), Workspace::render)
}
impl Workspace {
fn new(cx: &mut ViewContext<Self>) -> Self {
Self {
left_panel: collab_panel(cx),
right_panel: collab_panel(cx),
}
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
let theme = rose_pine_dawn();
div()
.font("Helvetica")
.text_base()
.size_full()
.fill(theme.middle.positive.default.background)
.child("Hello world")
// TODO: Implement style.
//.size_full().fill(gpui3::hsla(0.83, 1., 0.5, 1.))
// TODO: Debug font not font.
//.child("Is this thing on?")
// themed(rose_pine_dawn(), cx, |cx| {
// div()
// .size_full()
// .flex()
// .flex_col()
// .font("Zed Sans Extended")
// .gap_0()
// .justify_start()
// .items_start()
// .text_color(theme.lowest.base.default.foreground)
// // .fill(theme.middle.base.default.background)
// .fill(gpui3::hsla(0.83, 1., 0.5, 1.))
// .child(titlebar(cx))
// .child(
// div()
// .flex_1()
// .w_full()
// .flex()
// .flex_row()
// .overflow_hidden()
// .child(self.left_panel.clone())
// .child(div().h_full().flex_1())
// .child(self.right_panel.clone()),
// )
// .child(statusbar::statusbar(cx))
// })
}
}
struct Titlebar;
pub fn titlebar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
let ref mut this = Titlebar;
let theme = theme(cx);
div()
.flex()
.items_center()
.justify_between()
.w_full()
.h_8()
.fill(theme.lowest.base.default.background)
.child(this.left_group(cx))
.child(this.right_group(cx))
}
impl Titlebar {
fn render<V: 'static + Send + Sync>(
&mut self,
cx: &mut ViewContext<V>,
) -> impl Element<State = V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.justify_between()
.w_full()
.h_8()
.fill(theme.lowest.base.default.background)
.child(self.left_group(cx))
.child(self.right_group(cx))
}
fn left_group<S: 'static + Send + Sync>(
&mut self,
cx: &mut ViewContext<S>,
) -> impl Element<State = S> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_4()
.px_2()
// === Traffic Lights === //
.child(
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.w_3()
.h_3()
.rounded_full()
.fill(theme.lowest.positive.default.foreground),
)
.child(
div()
.w_3()
.h_3()
.rounded_full()
.fill(theme.lowest.warning.default.foreground),
)
.child(
div()
.w_3()
.h_3()
.rounded_full()
.fill(theme.lowest.negative.default.foreground),
),
)
// === Project Info === //
.child(
div()
.flex()
.items_center()
.gap_1()
.child(
div()
.h_full()
.flex()
.items_center()
.justify_center()
.px_2()
.rounded_md()
// .hover()
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(div().text_sm().child("project")),
)
.child(
div()
.h_full()
.flex()
.items_center()
.justify_center()
.px_2()
.rounded_md()
.text_color(theme.lowest.variant.default.foreground)
// .hover()
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(div().text_sm().child("branch")),
),
)
}
fn right_group<S: 'static + Send + Sync>(
&mut self,
cx: &mut ViewContext<S>,
) -> impl Element<State = S> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_3()
.px_2()
// === Actions === //
.child(
div().child(
div().flex().items_center().gap_1().child(
div().size_4().flex().items_center().justify_center().child(
svg()
.path("icons/exit.svg")
.size_4()
.fill(theme.lowest.base.default.foreground),
),
),
),
)
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
// === Comms === //
.child(
div().child(
div()
.flex()
.items_center()
.gap_px()
.child(
div()
.px_2()
.py_1()
.rounded_md()
.h_full()
.flex()
.items_center()
.justify_center()
// .hover()
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/microphone.svg")
.size_3p5()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.px_2()
.py_1()
.rounded_md()
.h_full()
.flex()
.items_center()
.justify_center()
// .hover()
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/radix/speaker-loud.svg")
.size_3p5()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.px_2()
.py_1()
.rounded_md()
.h_full()
.flex()
.items_center()
.justify_center()
// .hover()
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/radix/desktop.svg")
.size_3p5()
.fill(theme.lowest.base.default.foreground),
),
),
),
)
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
// User Group
.child(
div().child(
div()
.px_1()
.py_1()
.flex()
.items_center()
.justify_center()
.rounded_md()
.gap_0p5()
// .hover()
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(
img()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size_4()
.rounded_md()
.fill(theme.middle.on.default.foreground),
)
.child(
svg()
.path("icons/caret_down_8.svg")
.w_2()
.h_2()
.fill(theme.lowest.variant.default.foreground),
),
),
)
}
}
// ================================================================================ //
mod statusbar {
use super::*;
pub fn statusbar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
let theme = theme(cx);
div()
.flex()
.items_center()
.justify_between()
.w_full()
.h_8()
.fill(theme.lowest.base.default.background)
// .child(left_group(cx))
// .child(right_group(cx))
}
fn left_group<V: 'static + Send + Sync>(cx: &mut ViewContext<V>) -> impl Element<State = V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_4()
.px_2()
// === Tools === //
.child(
div()
.flex()
.items_center()
.gap_1()
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/project.svg")
.w_4()
.h_4()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/conversations.svg")
.w_4()
.h_4()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/file_icons/notebook.svg")
.w_4()
.h_4()
.fill(theme.lowest.accent.default.foreground),
),
),
)
// === Diagnostics === //
.child(
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.h_full()
.flex()
.items_center()
.justify_center()
.gap_0p5()
.px_1()
.text_color(theme.lowest.variant.default.foreground)
// .hover()
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/error.svg")
.w_4()
.h_4()
.fill(theme.lowest.negative.default.foreground),
)
.child(div().text_sm().child("2")),
)
.child(
div()
.text_sm()
.text_color(theme.lowest.variant.default.foreground)
.child("Something is wrong"),
),
)
}
fn right_group<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_4()
.px_2()
// === Tools === //
.child(
div()
.flex()
.items_center()
.gap_1()
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/check_circle.svg")
.w_4()
.h_4()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/copilot.svg")
.w_4()
.h_4()
.fill(theme.lowest.accent.default.foreground),
),
),
)
}
}

View File

@ -1,4 +1,7 @@
use std::sync::Arc;
use std::{
fmt::{self, Debug},
sync::Arc,
};
#[derive(PartialEq, Eq)]
pub enum ArcCow<'a, T: ?Sized> {
@ -72,3 +75,12 @@ impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
}
}
}
impl<'a, T: ?Sized + Debug> Debug for ArcCow<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ArcCow::Borrowed(borrowed) => Debug::fmt(borrowed, f),
ArcCow::Owned(owned) => Debug::fmt(&**owned, f),
}
}
}

View File

@ -139,6 +139,7 @@ tree-sitter-nu.workspace = true
url = "2.2"
urlencoding = "2.1.2"
uuid = { version = "1.1.2", features = ["v4"] }
owning_ref = "0.4.1"
[dev-dependencies]
call = { path = "../call", features = ["test-support"] }