Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/Cargo.toml             |  31 +
crates/gpui3/src/app.rs             | 217 +++++++
crates/gpui3/src/arc_cow.rs         |   0 
crates/gpui3/src/color.rs           | 240 ++++++++
crates/gpui3/src/element.rs         | 275 ++++++++++
crates/gpui3/src/elements.rs        |   8 
crates/gpui3/src/elements/div.rs    |  38 +
crates/gpui3/src/elements/editor.rs |  61 ++
crates/gpui3/src/fonts.rs           | 393 ++++++++++++++
crates/gpui3/src/geometry.rs        | 374 +++++++++++++
crates/gpui3/src/gpui3.rs           | 141 +++++
crates/gpui3/src/platform.rs        | 131 ++++
crates/gpui3/src/platform/test.rs   |  23 
crates/gpui3/src/renderer.rs        | 164 +++++
crates/gpui3/src/scene.rs           | 141 +++++
crates/gpui3/src/shader.frag.wgsl   |   1 
crates/gpui3/src/shader.vert.wgsl   |   0 
crates/gpui3/src/style.rs           | 345 ++++++++++++
crates/gpui3/src/taffy.rs           | 236 ++++++++
crates/gpui3/src/text.rs            | 852 +++++++++++++++++++++++++++++++
crates/gpui3/src/window.rs          | 251 +++++++++
21 files changed, 3,922 insertions(+)

Detailed changes

crates/gpui3/Cargo.toml 🔗

@@ -0,0 +1,31 @@
+[package]
+name = "gpui3"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+test = []
+
+[lib]
+path = "src/gpui3.rs"
+
+[dependencies]
+anyhow.workspace = true
+bytemuck = "1.14.0"
+derive_more.workspace = true
+font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
+itertools = "0.11.0"
+log.workspace = true
+parking_lot.workspace = true
+plane-split = "0.18.0"
+raw-window-handle = "0.5.2"
+refineable = { path = "../refineable" }
+rust-embed.workspace = true
+schemars = "0.8"
+serde.workspace = true
+simplelog = "0.9"
+slotmap = "1.0.6"
+smallvec.workspace = true
+taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
+util = { path = "../util" }
+wgpu = "0.17.0"

crates/gpui3/src/app.rs 🔗

@@ -0,0 +1,217 @@
+use anyhow::{anyhow, Result};
+use slotmap::SlotMap;
+use std::{any::Any, marker::PhantomData, rc::Rc, sync::Arc};
+
+use crate::FontCache;
+
+use super::{
+    platform::Platform,
+    window::{Window, WindowHandle, WindowId},
+    Context, LayoutId, Reference, View, WindowContext,
+};
+
+pub struct AppContext {
+    platform: Rc<dyn Platform>,
+    font_cache: Arc<FontCache>,
+    pub(crate) entities: SlotMap<EntityId, Option<Box<dyn Any>>>,
+    pub(crate) windows: SlotMap<WindowId, Option<Window>>,
+    // We recycle this memory across layout requests.
+    pub(crate) layout_id_buffer: Vec<LayoutId>,
+}
+
+impl AppContext {
+    pub fn new(platform: Rc<dyn Platform>) -> Self {
+        let font_cache = Arc::new(FontCache::new(platform.font_system()));
+        AppContext {
+            platform,
+            font_cache,
+            entities: SlotMap::with_key(),
+            windows: SlotMap::with_key(),
+            layout_id_buffer: Default::default(),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn test() -> Self {
+        Self::new(Rc::new(super::TestPlatform::new()))
+    }
+
+    pub fn platform(&self) -> &Rc<dyn Platform> {
+        &self.platform
+    }
+
+    pub fn font_cache(&self) -> &Arc<FontCache> {
+        &self.font_cache
+    }
+
+    pub fn open_window<S: 'static>(
+        &mut self,
+        options: crate::WindowOptions,
+        build_root_view: impl FnOnce(&mut WindowContext) -> View<S>,
+    ) -> WindowHandle<S> {
+        let id = self.windows.insert(None);
+        let handle = WindowHandle::new(id);
+        self.platform.open_window(handle.into(), options);
+
+        let mut window = Window::new(id);
+        let root_view = build_root_view(&mut WindowContext::mutable(self, &mut window));
+        window.root_view.replace(Box::new(root_view));
+
+        self.windows.get_mut(id).unwrap().replace(window);
+        handle
+    }
+
+    pub(crate) fn update_window<R>(
+        &mut self,
+        window_id: WindowId,
+        update: impl FnOnce(&mut WindowContext) -> R,
+    ) -> Result<R> {
+        let mut window = self
+            .windows
+            .get_mut(window_id)
+            .ok_or_else(|| anyhow!("window not found"))?
+            .take()
+            .unwrap();
+
+        let result = update(&mut WindowContext::mutable(self, &mut window));
+
+        self.windows
+            .get_mut(window_id)
+            .ok_or_else(|| anyhow!("window not found"))?
+            .replace(window);
+
+        Ok(result)
+    }
+}
+
+impl Context for AppContext {
+    type EntityContext<'a, 'w, T: 'static> = ModelContext<'a, T>;
+
+    fn entity<T: 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Handle<T> {
+        let id = self.entities.insert(None);
+        let entity = Box::new(build_entity(&mut ModelContext::mutable(self, id)));
+        self.entities.get_mut(id).unwrap().replace(entity);
+
+        Handle::new(id)
+    }
+
+    fn update_entity<T: 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> R {
+        let mut entity = self
+            .entities
+            .get_mut(handle.id)
+            .unwrap()
+            .take()
+            .unwrap()
+            .downcast::<T>()
+            .unwrap();
+
+        let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id));
+        self.entities.get_mut(handle.id).unwrap().replace(entity);
+        result
+    }
+}
+
+pub struct ModelContext<'a, T> {
+    app: Reference<'a, AppContext>,
+    entity_type: PhantomData<T>,
+    entity_id: EntityId,
+}
+
+impl<'a, T: '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,
+        }
+    }
+
+    fn immutable(app: &'a AppContext, entity_id: EntityId) -> Self {
+        Self {
+            app: Reference::Immutable(app),
+            entity_type: PhantomData,
+            entity_id,
+        }
+    }
+
+    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
+    }
+}
+
+impl<'a, T: 'static> Context for ModelContext<'a, T> {
+    type EntityContext<'b, 'c, U: 'static> = ModelContext<'b, U>;
+
+    fn entity<U: 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U,
+    ) -> Handle<U> {
+        self.app.entity(build_entity)
+    }
+
+    fn update_entity<U: 'static, R>(
+        &mut self,
+        handle: &Handle<U>,
+        update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
+    ) -> R {
+        self.app.update_entity(handle, update)
+    }
+}
+
+pub struct Handle<T> {
+    pub(crate) id: EntityId,
+    pub(crate) entity_type: PhantomData<T>,
+}
+
+slotmap::new_key_type! { pub struct EntityId; }
+
+impl<T: 'static> Handle<T> {
+    fn new(id: EntityId) -> Self {
+        Self {
+            id,
+            entity_type: PhantomData,
+        }
+    }
+
+    /// 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,
+    ) -> R {
+        cx.update_entity(self, update)
+    }
+}
+
+impl<T> Clone for Handle<T> {
+    fn clone(&self) -> Self {
+        Self {
+            id: self.id,
+            entity_type: PhantomData,
+        }
+    }
+}

crates/gpui3/src/color.rs 🔗

@@ -0,0 +1,240 @@
+#![allow(dead_code)]
+
+use bytemuck::{Pod, Zeroable};
+use serde::de::{self, Deserialize, Deserializer, Visitor};
+use std::fmt;
+use std::num::ParseIntError;
+
+pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
+    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)]
+#[repr(C)]
+pub struct Hsla {
+    pub h: f32,
+    pub s: f32,
+    pub l: f32,
+    pub a: f32,
+}
+
+impl Eq for Hsla {}
+unsafe impl Zeroable for Hsla {}
+unsafe impl Pod 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))
+    }
+}

crates/gpui3/src/element.rs 🔗

@@ -0,0 +1,275 @@
+use super::{Handle, Layout, LayoutId, Pixels, Point, Result, ViewContext, WindowContext};
+use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
+
+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 child(self, child: impl IntoAnyElement<S>) -> Self;
+}
+
+trait ElementObject<S> {
+    fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId>;
+    fn paint(
+        &mut self,
+        parent_origin: super::Point<Pixels>,
+        state: &mut S,
+        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,
+        parent_origin: Point<Pixels>,
+        state: &mut E::State,
+        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)?;
+                layout.bounds.origin += parent_origin;
+                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,
+        parent_origin: Point<Pixels>,
+        state: &mut S,
+        cx: &mut ViewContext<S>,
+    ) -> Result<()> {
+        self.0.paint(parent_origin, state, 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
+    }
+}
+
+#[derive(Clone)]
+pub struct View<S> {
+    state: Handle<S>,
+    render: Rc<dyn Fn(&mut S, &mut ViewContext<S>) -> AnyElement<S>>,
+}
+
+pub fn view<S: 'static, E: Element<State = S>>(
+    state: Handle<S>,
+    render: impl 'static + Fn(&mut S, &mut ViewContext<S>) -> E,
+) -> View<S> {
+    View {
+        state,
+        render: Rc::new(move |state, cx| render(state, cx).into_any()),
+    }
+}
+
+impl<S: 'static> View<S> {
+    pub fn into_any<ParentState>(self) -> AnyView<ParentState> {
+        AnyView {
+            view: Rc::new(RefCell::new(self)),
+            parent_state_type: PhantomData,
+        }
+    }
+}
+
+impl<S: 'static> Element for View<S> {
+    type State = ();
+    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: Layout,
+        _: &mut Self::State,
+        element: &mut Self::FrameState,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()> {
+        self.state.update(cx, |state, cx| {
+            element.paint(layout.bounds.origin, state, cx)
+        })
+    }
+}
+
+trait ViewObject {
+    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: 'static> ViewObject for View<S> {
+    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: Layout,
+        element: &mut dyn Any,
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        self.state.update(cx, |state, cx| {
+            element
+                .downcast_mut::<AnyElement<S>>()
+                .unwrap()
+                .paint(layout.bounds.origin, state, cx)
+        })
+    }
+}
+
+pub struct AnyView<S> {
+    view: Rc<RefCell<dyn ViewObject>>,
+    parent_state_type: PhantomData<S>,
+}
+
+impl<S: 'static> Element for AnyView<S> {
+    type State = S;
+    type FrameState = Box<dyn Any>;
+
+    fn layout(
+        &mut self,
+        _: &mut Self::State,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<(LayoutId, Self::FrameState)> {
+        self.view.borrow_mut().layout(cx)
+    }
+
+    fn paint(
+        &mut self,
+        layout: Layout,
+        _: &mut Self::State,
+        element: &mut Self::FrameState,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()> {
+        self.view.borrow_mut().paint(layout, element, cx)
+    }
+}
+
+impl<S> Clone for AnyView<S> {
+    fn clone(&self) -> Self {
+        Self {
+            view: self.view.clone(),
+            parent_state_type: PhantomData,
+        }
+    }
+}

crates/gpui3/src/elements.rs 🔗

@@ -0,0 +1,8 @@
+pub mod div;
+pub mod editor;
+
+use super::*;
+use std::marker::PhantomData;
+
+pub use div::div;
+pub use editor::field;

crates/gpui3/src/elements/div.rs 🔗

@@ -0,0 +1,38 @@
+use super::{
+    Element, IntoAnyElement, Layout, LayoutId, ParentElement, PhantomData, Result, ViewContext,
+};
+
+pub struct Div<S>(PhantomData<S>);
+
+impl<S: 'static> Element for Div<S> {
+    type State = S;
+    type FrameState = ();
+
+    fn layout(
+        &mut self,
+        _state: &mut Self::State,
+        _cx: &mut ViewContext<Self::State>,
+    ) -> Result<(LayoutId, Self::FrameState)> {
+        todo!()
+    }
+
+    fn paint(
+        &mut self,
+        _layout: Layout,
+        _state: &mut Self::State,
+        _frame_state: &mut Self::FrameState,
+        _cx: &mut ViewContext<Self::State>,
+    ) -> Result<()> {
+        todo!()
+    }
+}
+
+impl<S> ParentElement<S> for Div<S> {
+    fn child(self, _child: impl IntoAnyElement<S>) -> Self {
+        todo!()
+    }
+}
+
+pub fn div<S>() -> Div<S> {
+    todo!()
+}

crates/gpui3/src/elements/editor.rs 🔗

@@ -0,0 +1,61 @@
+use super::{Element, Handle, Layout, LayoutId, Result, SharedString, ViewContext};
+use std::marker::PhantomData;
+
+pub fn field<S>(editor: Handle<Editor>) -> EditorElement<S> {
+    EditorElement {
+        editor,
+        field: true,
+        placeholder_text: None,
+        parent_state: PhantomData,
+    }
+}
+
+pub struct EditorElement<S> {
+    editor: Handle<Editor>,
+    field: bool,
+    placeholder_text: Option<SharedString>,
+    parent_state: PhantomData<S>,
+}
+
+impl<S> EditorElement<S> {
+    pub fn field(mut self) -> Self {
+        self.field = true;
+        self
+    }
+
+    pub fn placeholder_text(mut self, text: impl Into<SharedString>) -> Self {
+        self.placeholder_text = Some(text.into());
+        self
+    }
+}
+
+impl<S: 'static> Element for EditorElement<S> {
+    type State = S;
+    type FrameState = ();
+
+    fn layout(
+        &mut self,
+        _: &mut Self::State,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<(LayoutId, Self::FrameState)> {
+        self.editor.update(cx, |_editor, _cx| todo!())
+    }
+
+    fn paint(
+        &mut self,
+        _layout: Layout,
+        _state: &mut Self::State,
+        _frame_state: &mut Self::FrameState,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()> {
+        self.editor.update(cx, |_editor, _cx| todo!())
+    }
+}
+
+pub struct Editor {}
+
+impl Editor {
+    pub fn new(_: &mut ViewContext<Self>) -> Self {
+        Editor {}
+    }
+}

crates/gpui3/src/fonts.rs 🔗

@@ -0,0 +1,393 @@
+use crate::{px, Bounds, LineWrapper, Pixels, PlatformFontSystem, Result, Size};
+use anyhow::anyhow;
+pub use font_kit::properties::{
+    Properties as FontProperties, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight,
+};
+use parking_lot::{RwLock, RwLockUpgradableReadGuard};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::{
+    collections::HashMap,
+    ops::{Deref, DerefMut},
+    sync::Arc,
+};
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct FontFamilyId(usize);
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct FontId(pub usize);
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
+pub struct FontFeatures {
+    pub calt: Option<bool>,
+    pub case: Option<bool>,
+    pub cpsp: Option<bool>,
+    pub frac: Option<bool>,
+    pub liga: Option<bool>,
+    pub onum: Option<bool>,
+    pub ordn: Option<bool>,
+    pub pnum: Option<bool>,
+    pub ss01: Option<bool>,
+    pub ss02: Option<bool>,
+    pub ss03: Option<bool>,
+    pub ss04: Option<bool>,
+    pub ss05: Option<bool>,
+    pub ss06: Option<bool>,
+    pub ss07: Option<bool>,
+    pub ss08: Option<bool>,
+    pub ss09: Option<bool>,
+    pub ss10: Option<bool>,
+    pub ss11: Option<bool>,
+    pub ss12: Option<bool>,
+    pub ss13: Option<bool>,
+    pub ss14: Option<bool>,
+    pub ss15: Option<bool>,
+    pub ss16: Option<bool>,
+    pub ss17: Option<bool>,
+    pub ss18: Option<bool>,
+    pub ss19: Option<bool>,
+    pub ss20: Option<bool>,
+    pub subs: Option<bool>,
+    pub sups: Option<bool>,
+    pub swsh: Option<bool>,
+    pub titl: Option<bool>,
+    pub tnum: Option<bool>,
+    pub zero: Option<bool>,
+}
+
+#[allow(non_camel_case_types)]
+#[derive(Deserialize)]
+enum WeightJson {
+    thin,
+    extra_light,
+    light,
+    normal,
+    medium,
+    semibold,
+    bold,
+    extra_bold,
+    black,
+}
+
+struct Family {
+    name: Arc<str>,
+    font_features: FontFeatures,
+    font_ids: Vec<FontId>,
+}
+
+pub struct FontCache(RwLock<FontCacheState>);
+
+pub struct FontCacheState {
+    font_system: Arc<dyn PlatformFontSystem>,
+    families: Vec<Family>,
+    default_family: Option<FontFamilyId>,
+    font_selections: HashMap<FontFamilyId, HashMap<(FontWeight, FontStyle), FontId>>,
+    metrics: HashMap<FontId, FontMetrics>,
+    wrapper_pool: HashMap<(FontId, Pixels), Vec<LineWrapper>>,
+}
+
+unsafe impl Send for FontCache {}
+
+impl FontCache {
+    pub fn new(fonts: Arc<dyn PlatformFontSystem>) -> Self {
+        Self(RwLock::new(FontCacheState {
+            font_system: fonts,
+            families: Default::default(),
+            default_family: None,
+            font_selections: Default::default(),
+            metrics: Default::default(),
+            wrapper_pool: Default::default(),
+        }))
+    }
+
+    pub fn family_name(&self, family_id: FontFamilyId) -> Result<Arc<str>> {
+        self.0
+            .read()
+            .families
+            .get(family_id.0)
+            .ok_or_else(|| anyhow!("invalid family id"))
+            .map(|family| family.name.clone())
+    }
+
+    pub fn load_family(&self, names: &[&str], features: &FontFeatures) -> Result<FontFamilyId> {
+        for name in names {
+            let state = self.0.upgradable_read();
+
+            if let Some(ix) = state
+                .families
+                .iter()
+                .position(|f| f.name.as_ref() == *name && f.font_features == *features)
+            {
+                return Ok(FontFamilyId(ix));
+            }
+
+            let mut state = RwLockUpgradableReadGuard::upgrade(state);
+
+            if let Ok(font_ids) = state.font_system.load_family(name, features) {
+                if font_ids.is_empty() {
+                    continue;
+                }
+
+                let family_id = FontFamilyId(state.families.len());
+                for font_id in &font_ids {
+                    if state.font_system.glyph_for_char(*font_id, 'm').is_none() {
+                        return Err(anyhow!("font must contain a glyph for the 'm' character"));
+                    }
+                }
+
+                state.families.push(Family {
+                    name: Arc::from(*name),
+                    font_features: features.clone(),
+                    font_ids,
+                });
+                return Ok(family_id);
+            }
+        }
+
+        Err(anyhow!(
+            "could not find a non-empty font family matching one of the given names"
+        ))
+    }
+
+    /// Returns an arbitrary font family that is available on the system.
+    pub fn known_existing_family(&self) -> FontFamilyId {
+        if let Some(family_id) = self.0.read().default_family {
+            return family_id;
+        }
+
+        let default_family = self
+            .load_family(
+                &["Courier", "Helvetica", "Arial", "Verdana"],
+                &Default::default(),
+            )
+            .unwrap_or_else(|_| {
+                let all_family_names = self.0.read().font_system.all_families();
+                let all_family_names: Vec<_> = all_family_names
+                    .iter()
+                    .map(|string| string.as_str())
+                    .collect();
+                self.load_family(&all_family_names, &Default::default())
+                    .expect("could not load any default font family")
+            });
+
+        self.0.write().default_family = Some(default_family);
+        default_family
+    }
+
+    pub fn default_font(&self, family_id: FontFamilyId) -> FontId {
+        self.select_font(family_id, Default::default(), Default::default())
+            .unwrap()
+    }
+
+    pub fn select_font(
+        &self,
+        family_id: FontFamilyId,
+        weight: FontWeight,
+        style: FontStyle,
+    ) -> Result<FontId> {
+        let inner = self.0.upgradable_read();
+        if let Some(font_id) = inner
+            .font_selections
+            .get(&family_id)
+            .and_then(|fonts| fonts.get(&(weight, style)))
+        {
+            Ok(*font_id)
+        } else {
+            let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
+            let family = &inner.families[family_id.0];
+            let font_id = inner
+                .font_system
+                .select_font(&family.font_ids, weight, style)
+                .unwrap_or(family.font_ids[0]);
+            inner
+                .font_selections
+                .entry(family_id)
+                .or_default()
+                .insert((weight, style), font_id);
+            Ok(font_id)
+        }
+    }
+
+    pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
+    where
+        F: FnOnce(&FontMetrics) -> T,
+        T: 'static,
+    {
+        let state = self.0.upgradable_read();
+        if let Some(metrics) = state.metrics.get(&font_id) {
+            f(metrics)
+        } else {
+            let metrics = state.font_system.font_metrics(font_id);
+            let metric = f(&metrics);
+            let mut state = RwLockUpgradableReadGuard::upgrade(state);
+            state.metrics.insert(font_id, metrics);
+            metric
+        }
+    }
+
+    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
+        let bounding_box = self.metric(font_id, |m| m.bounding_box);
+
+        let width = px(bounding_box.size.width) * self.em_size(font_id, font_size);
+        let height = px(bounding_box.size.height) * self.em_size(font_id, font_size);
+        Size { width, height }
+    }
+
+    pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        let glyph_id;
+        let bounds;
+        {
+            let state = self.0.read();
+            glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
+            bounds = state
+                .font_system
+                .typographic_bounds(font_id, glyph_id)
+                .unwrap();
+        }
+        bounds.size.width * self.em_size(font_id, font_size)
+    }
+
+    pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        let glyph_id;
+        let advance;
+        {
+            let state = self.0.read();
+            glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
+            advance = state.font_system.advance(font_id, glyph_id).unwrap();
+        }
+        advance.x * self.em_size(font_id, font_size)
+    }
+
+    pub fn line_height(&self, font_size: Pixels) -> Pixels {
+        (font_size * 1.618).round()
+    }
+
+    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        self.em_size(font_id, font_size) * self.metric(font_id, |m| m.cap_height)
+    }
+
+    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        self.em_size(font_id, font_size) * self.metric(font_id, |m| m.x_height)
+    }
+
+    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        self.em_size(font_id, font_size) * self.metric(font_id, |m| m.ascent)
+    }
+
+    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        self.em_size(font_id, font_size) * self.metric(font_id, |m| -m.descent)
+    }
+
+    pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        font_size / self.metric(font_id, |m| m.units_per_em as f32)
+    }
+
+    pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+        let line_height = self.line_height(font_size);
+        let ascent = self.ascent(font_id, font_size);
+        let descent = self.descent(font_id, font_size);
+        let padding_top = (line_height - ascent - descent) / 2.;
+        padding_top + ascent
+    }
+
+    pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: Pixels) -> LineWrapperHandle {
+        let mut state = self.0.write();
+        let wrappers = state.wrapper_pool.entry((font_id, font_size)).or_default();
+        let wrapper = wrappers
+            .pop()
+            .unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
+        LineWrapperHandle {
+            wrapper: Some(wrapper),
+            font_cache: self.clone(),
+        }
+    }
+}
+
+pub struct LineWrapperHandle {
+    wrapper: Option<LineWrapper>,
+    font_cache: Arc<FontCache>,
+}
+
+impl Drop for LineWrapperHandle {
+    fn drop(&mut self) {
+        let mut state = self.font_cache.0.write();
+        let wrapper = self.wrapper.take().unwrap();
+        state
+            .wrapper_pool
+            .get_mut(&(wrapper.font_id, 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()
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct FontMetrics {
+    pub units_per_em: u32,
+    pub ascent: f32,
+    pub descent: f32,
+    pub line_gap: f32,
+    pub underline_position: f32,
+    pub underline_thickness: f32,
+    pub cap_height: f32,
+    pub x_height: f32,
+    pub bounding_box: Bounds<f32>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{FontStyle, FontWeight, Platform, TestPlatform};
+
+    #[test]
+    fn test_select_font() {
+        let platform = TestPlatform::new();
+        let fonts = FontCache::new(platform.font_system());
+        let arial = fonts
+            .load_family(
+                &["Arial"],
+                &FontFeatures {
+                    calt: Some(false),
+                    ..Default::default()
+                },
+            )
+            .unwrap();
+        let arial_regular = fonts
+            .select_font(arial, FontWeight::default(), FontStyle::default())
+            .unwrap();
+        let arial_italic = fonts
+            .select_font(arial, FontWeight::default(), FontStyle::Italic)
+            .unwrap();
+        let arial_bold = fonts
+            .select_font(arial, FontWeight::BOLD, FontStyle::default())
+            .unwrap();
+        assert_ne!(arial_regular, arial_italic);
+        assert_ne!(arial_regular, arial_bold);
+        assert_ne!(arial_italic, arial_bold);
+
+        let arial_with_calt = fonts
+            .load_family(
+                &["Arial"],
+                &FontFeatures {
+                    calt: Some(true),
+                    ..Default::default()
+                },
+            )
+            .unwrap();
+        assert_ne!(arial_with_calt, arial);
+    }
+}

crates/gpui3/src/geometry.rs 🔗

@@ -0,0 +1,374 @@
+use bytemuck::{Pod, Zeroable};
+use core::fmt::Debug;
+use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
+use refineable::Refineable;
+use std::ops::{Add, Mul};
+
+#[derive(Refineable, Default, Add, AddAssign, Sub, Mul, Div, 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 }
+    }
+}
+
+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)]
+#[refineable(debug)]
+pub struct Size<T: Clone + Debug> {
+    pub width: T,
+    pub height: T,
+}
+
+impl Size<Length> {
+    pub fn full() -> Self {
+        Self {
+            width: relative(1.),
+            height: relative(1.),
+        }
+    }
+}
+
+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)]
+pub struct Bounds<T: Clone + Debug> {
+    pub origin: Point<T>,
+    pub size: Size<T>,
+}
+
+impl<T: Clone + Debug + Copy + Add<T, Output = T>> Bounds<T> {
+    pub fn upper_right(&self) -> Point<T> {
+        Point {
+            x: self.origin.x + self.size.width,
+            y: self.origin.y,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
+
+#[derive(Refineable, Clone, Default, Debug)]
+#[refineable(debug)]
+pub struct Edges<T: Clone + Debug> {
+    pub top: T,
+    pub right: T,
+    pub bottom: T,
+    pub left: T,
+}
+
+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),
+        }
+    }
+}
+
+impl Edges<Pixels> {
+    pub fn is_empty(&self) -> bool {
+        self.top == px(0.) && self.right == px(0.) && self.bottom == px(0.) && self.left == px(0.)
+    }
+}
+
+#[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 Pixels {
+    type Output = Pixels;
+
+    fn mul(self, rhs: Pixels) -> Self::Output {
+        Pixels(self.0 * rhs.0)
+    }
+}
+
+impl Pixels {
+    pub fn round(&self) -> Self {
+        Self(self.0.round())
+    }
+}
+
+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);
+    }
+}
+
+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
+    }
+}
+
+#[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 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 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<T: From<DefiniteLength>>(fraction: f32) -> T {
+    DefiniteLength::Fraction(fraction).into()
+}
+
+pub fn rems(rems: f32) -> Rems {
+    Rems(rems).into()
+}
+
+pub fn px(pixels: f32) -> Pixels {
+    Pixels(pixels).into()
+}
+
+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())
+    }
+}

crates/gpui3/src/gpui3.rs 🔗

@@ -0,0 +1,141 @@
+mod app;
+mod color;
+mod element;
+mod elements;
+mod fonts;
+mod geometry;
+mod platform;
+mod renderer;
+mod scene;
+mod style;
+mod taffy;
+mod text;
+mod window;
+
+use anyhow::Result;
+pub use app::*;
+pub use color::*;
+pub use element::*;
+pub use elements::*;
+pub use fonts::*;
+pub use geometry::*;
+pub use platform::*;
+pub use scene::*;
+use std::ops::{Deref, DerefMut};
+pub use style::*;
+pub use taffy::LayoutId;
+use taffy::TaffyLayoutEngine;
+use text::*;
+pub use util::arc_cow::ArcCow;
+pub use window::*;
+
+pub trait Context {
+    type EntityContext<'a, 'w, T: 'static>;
+
+    fn entity<T: 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Handle<T>;
+
+    fn update_entity<T: 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> R;
+}
+
+#[derive(Clone, Eq, PartialEq)]
+pub struct SharedString(ArcCow<'static, str>);
+
+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,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    struct Workspace {
+        left_panel: AnyView<Self>,
+    }
+
+    fn workspace(cx: &mut WindowContext) -> View<Workspace> {
+        let workspace = cx.entity(|cx| Workspace {
+            left_panel: collab_panel(cx).into_any(),
+        });
+        view(workspace, |workspace, cx| {
+            div().child(workspace.left_panel.clone())
+        })
+    }
+
+    struct CollabPanel {
+        filter_editor: Handle<editor::Editor>,
+    }
+
+    fn collab_panel(cx: &mut WindowContext) -> View<CollabPanel> {
+        let panel = cx.entity(|cx| CollabPanel::new(cx));
+        view(panel, |panel, cx| {
+            div().child(div()).child(
+                field(panel.filter_editor.clone()).placeholder_text("Search channels, contacts"),
+            )
+        })
+    }
+
+    impl CollabPanel {
+        fn new(cx: &mut ViewContext<Self>) -> Self {
+            Self {
+                filter_editor: cx.entity(|cx| editor::Editor::new(cx)),
+            }
+        }
+    }
+
+    struct Editor {}
+
+    impl Editor {
+        pub fn new(cx: &mut ViewContext<Self>) -> Self {
+            Self {}
+        }
+    }
+
+    #[test]
+    fn test() {
+        let mut cx = AppContext::test();
+
+        cx.open_window(WindowOptions::default(), |cx| workspace(cx));
+    }
+}

crates/gpui3/src/platform.rs 🔗

@@ -0,0 +1,131 @@
+#[cfg(any(test, feature = "test"))]
+mod test;
+use crate::{
+    AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
+    LineLayout, Pixels, Point, RunStyle, SharedString,
+};
+use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
+use std::sync::Arc;
+
+#[cfg(any(test, feature = "test"))]
+pub use test::*;
+
+pub trait Platform {
+    fn font_system(&self) -> Arc<dyn PlatformFontSystem>;
+
+    fn open_window(
+        &self,
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+    ) -> Box<dyn PlatformWindow>;
+}
+
+pub trait PlatformWindow: HasRawWindowHandle + HasRawDisplayHandle {}
+
+pub trait PlatformFontSystem: Send + Sync {
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
+    fn all_families(&self) -> Vec<String>;
+    fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
+    fn select_font(
+        &self,
+        font_ids: &[FontId],
+        weight: FontWeight,
+        style: FontStyle,
+    ) -> anyhow::Result<FontId>;
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
+    fn typographic_bounds(
+        &self,
+        font_id: FontId,
+        glyph_id: GlyphId,
+    ) -> anyhow::Result<Bounds<Pixels>>;
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Point<Pixels>>;
+    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, RunStyle)]) -> LineLayout;
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize>;
+}
+
+#[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,
+}
+
+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,
+        }
+    }
+}
+
+
+#[derive(Debug, Default)]
+pub struct TitlebarOptions {
+    pub title: Option<SharedString>,
+    pub appears_transparent: bool,
+    pub traffic_light_position: Option<Point<f32>>,
+}
+
+#[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<f32>),
+}

crates/gpui3/src/platform/test.rs 🔗

@@ -0,0 +1,23 @@
+use super::Platform;
+
+pub struct TestPlatform;
+
+impl TestPlatform {
+    pub fn new() -> Self {
+        TestPlatform
+    }
+}
+
+impl Platform for TestPlatform {
+    fn font_system(&self) -> std::sync::Arc<dyn crate::PlatformFontSystem> {
+        todo!()
+    }
+
+    fn open_window(
+        &self,
+        handle: crate::AnyWindowHandle,
+        options: crate::WindowOptions,
+    ) -> Box<dyn crate::PlatformWindow> {
+        todo!()
+    }
+}

crates/gpui3/src/renderer.rs 🔗

@@ -0,0 +1,164 @@
+use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
+
+use super::{Scene, Size};
+
+pub struct Renderer {
+    device: wgpu::Device,
+    queue: wgpu::Queue,
+    surface: wgpu::Surface,
+    surface_config: wgpu::SurfaceConfiguration,
+    pipeline: wgpu::RenderPipeline,
+    vertex_buffer: wgpu::Buffer,
+    vertex_count: u32,
+}
+
+pub trait Window: HasRawWindowHandle + HasRawDisplayHandle {
+    fn inner_size(&self) -> Size<u32>;
+}
+
+impl Renderer {
+    pub async fn new<W>(window: &W) -> Self
+    where
+        W: Window,
+    {
+        let instance = wgpu::Instance::new(Default::default());
+        let surface = unsafe { instance.create_surface(window).unwrap() };
+
+        let adapter = instance
+            .request_adapter(&wgpu::RequestAdapterOptions::default())
+            .await
+            .unwrap();
+
+        let (device, queue) = adapter
+            .request_device(&wgpu::DeviceDescriptor::default(), None)
+            .await
+            .unwrap();
+
+        let surface_config = wgpu::SurfaceConfiguration {
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+            format: wgpu::TextureFormat::Bgra8UnormSrgb,
+            width: window.inner_size().width,
+            height: window.inner_size().height,
+
+            // "FIFO" mode renders frames in queue synced with the display's refresh rate.
+            // Avoids screen tearing but may not offer the lowest latency. Ideal when image
+            // quality takes priority over input latency.
+            present_mode: wgpu::PresentMode::Fifo,
+
+            // Use the Premultiplied alpha mode. With premultiplication, the color components
+            // are multiplied by the alpha value before storage or blending, meaning calculations
+            // with colors already factor in the influence of alpha. This typically results
+            // in better performance and avoids a separate multiplication operation during blending.
+            alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
+
+            // Specify the color formats for the views the surface can have.
+            // In this case, the format is BGRA (blue, green, red, alpha) with unsigned
+            // normalised integers in the 8-bit range and the color space is sRGB (standard RGB).
+            // sRGB is the standard color space for displaying images and video on digital displays,
+            // as it optimises color accuracy and consistency.
+            view_formats: vec![wgpu::TextureFormat::Bgra8UnormSrgb],
+        };
+
+        surface.configure(&device, &surface_config);
+
+        let vs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+            label: Some("Vertex Shader"),
+            source: wgpu::ShaderSource::Wgsl(include_str!("shader.vert.wgsl").into()),
+        });
+
+        let fs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+            label: Some("Fragment Shader"),
+            source: wgpu::ShaderSource::Wgsl(include_str!("shader.frag.wgsl").into()),
+        });
+
+        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+            label: Some("Render Pipeline Layout"),
+            bind_group_layouts: &[],
+            push_constant_ranges: &[],
+        });
+
+        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+            label: Some("Vertex Buffer"),
+            size: 0,
+            usage: wgpu::BufferUsages::VERTEX,
+            mapped_at_creation: false,
+        });
+
+        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+            label: Some("Render Pipeline"),
+            layout: Some(&pipeline_layout),
+            vertex: wgpu::VertexState {
+                module: &vs_module,
+                entry_point: "main",
+                buffers: &[],
+            },
+            fragment: Some(wgpu::FragmentState {
+                module: &fs_module,
+                entry_point: "main",
+                targets: &[Some(wgpu::ColorTargetState {
+                    format: surface_config.format,
+                    blend: Some(wgpu::BlendState::REPLACE),
+                    write_mask: wgpu::ColorWrites::ALL,
+                })],
+            }),
+            primitive: wgpu::PrimitiveState {
+                topology: wgpu::PrimitiveTopology::TriangleStrip,
+                ..Default::default()
+            },
+            depth_stencil: None,
+            multisample: wgpu::MultisampleState::default(),
+            multiview: None,
+        });
+
+        Self {
+            device,
+            queue,
+            surface,
+            surface_config,
+            pipeline,
+            vertex_buffer,
+            vertex_count: 0,
+        }
+    }
+
+    pub fn render(&mut self, scene: &Scene) {
+        let frame = self.surface.get_current_texture().unwrap();
+        let view = frame
+            .texture
+            .create_view(&wgpu::TextureViewDescriptor::default());
+
+        self.queue.write_buffer(
+            &self.vertex_buffer,
+            0,
+            bytemuck::cast_slice(&scene.opaque_primitives().quads),
+        );
+        self.vertex_count = scene.opaque_primitives().quads.len() as u32;
+
+        let mut encoder = self
+            .device
+            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
+                label: Some("Render Encoder"),
+            });
+
+        {
+            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                label: Some("Render Pass"),
+                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                    view: &view,
+                    resolve_target: None,
+                    ops: wgpu::Operations {
+                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
+                        store: true,
+                    },
+                })],
+                depth_stencil_attachment: None,
+            });
+
+            render_pass.set_pipeline(&self.pipeline);
+            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
+            render_pass.draw(0..self.vertex_count, 0..1);
+        }
+
+        self.queue.submit(std::iter::once(encoder.finish()));
+    }
+}

crates/gpui3/src/scene.rs 🔗

@@ -0,0 +1,141 @@
+use super::{Bounds, Hsla, Pixels, Point};
+use bytemuck::{Pod, Zeroable};
+use plane_split::BspSplitter;
+
+pub struct Scene {
+    opaque_primitives: PrimitiveBatch,
+    transparent_primitives: slotmap::SlotMap<slotmap::DefaultKey, Primitive>,
+    splitter: BspSplitter<slotmap::DefaultKey>,
+}
+
+impl Scene {
+    pub fn new() -> Scene {
+        Scene {
+            opaque_primitives: PrimitiveBatch::default(),
+            transparent_primitives: slotmap::SlotMap::new(),
+            splitter: BspSplitter::new(),
+        }
+    }
+
+    pub fn insert(&mut self, primitive: impl Into<Primitive>, is_transparent: bool) {
+        if is_transparent {
+            self.transparent_primitives.insert(primitive.into());
+        } else {
+            match primitive.into() {
+                Primitive::Quad(quad) => self.opaque_primitives.quads.push(quad),
+                Primitive::Glyph(glyph) => self.opaque_primitives.glyphs.push(glyph),
+                Primitive::Underline(underline) => {
+                    self.opaque_primitives.underlines.push(underline)
+                }
+            }
+        }
+    }
+
+    pub fn opaque_primitives(&self) -> &PrimitiveBatch {
+        &self.opaque_primitives
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum Primitive {
+    Quad(Quad),
+    Glyph(Glyph),
+    Underline(Underline),
+}
+
+impl Primitive {
+    pub fn is_transparent(&self) -> bool {
+        match self {
+            Primitive::Quad(quad) => {
+                quad.background.is_transparent() && quad.border_color.is_transparent()
+            }
+            Primitive::Glyph(glyph) => glyph.color.is_transparent(),
+            Primitive::Underline(underline) => underline.color.is_transparent(),
+        }
+    }
+}
+
+#[derive(Default)]
+pub struct PrimitiveBatch {
+    pub quads: Vec<Quad>,
+    pub glyphs: Vec<Glyph>,
+    pub underlines: Vec<Underline>,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub struct Quad {
+    pub order: f32,
+    pub bounds: Bounds<Pixels>,
+    pub background: Hsla,
+    pub border_color: Hsla,
+    pub corner_radius: Pixels,
+    pub border_left: Pixels,
+    pub border_right: Pixels,
+    pub border_top: Pixels,
+    pub border_bottom: 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()
+    }
+}
+
+unsafe impl Zeroable for Quad {}
+
+unsafe impl Pod for Quad {}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct GlyphId(u32);
+
+#[derive(Clone, Debug)]
+pub struct Glyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub color: Hsla,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
+impl From<Quad> for Primitive {
+    fn from(quad: Quad) -> Self {
+        Primitive::Quad(quad)
+    }
+}
+
+impl From<Glyph> for Primitive {
+    fn from(glyph: Glyph) -> Self {
+        Primitive::Glyph(glyph)
+    }
+}
+
+#[derive(Copy, Clone, Default, Debug)]
+#[repr(C)]
+pub struct Underline {
+    pub origin: Point<Pixels>,
+    pub width: Pixels,
+    pub thickness: Pixels,
+    pub color: Hsla,
+    pub squiggly: bool,
+}
+
+unsafe impl Zeroable for Underline {}
+
+unsafe impl Pod for Underline {}
+
+impl From<Underline> for Primitive {
+    fn from(underline: Underline) -> Self {
+        Primitive::Underline(underline)
+    }
+}

crates/gpui3/src/style.rs 🔗

@@ -0,0 +1,345 @@
+use super::{
+    rems, AbsoluteLength, Bounds, DefiniteLength, Edges, EdgesRefinement, FontStyle, FontWeight,
+    Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString, Size,
+    SizeRefinement, ViewContext, WindowContext,
+};
+use crate::{FontCache};
+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: CornerRadii,
+
+    /// The color of text within this element. Cascades to children unless overridden.
+    pub text_color: Option<Hsla>,
+
+    /// The font size in rems.
+    pub font_size: Option<Rems>,
+
+    pub font_family: Option<SharedString>,
+
+    pub font_weight: Option<FontWeight>,
+
+    pub font_style: Option<FontStyle>,
+}
+
+#[derive(Refineable, Clone, Debug)]
+#[refineable(debug)]
+pub struct TextStyle {
+    pub color: Hsla,
+    pub font_family: SharedString,
+    pub font_size: Rems,
+    pub font_weight: FontWeight,
+    pub font_style: FontStyle,
+    pub underline: Option<UnderlineStyle>,
+}
+
+impl TextStyle {
+    pub fn highlight(mut self, style: HighlightStyle, _font_cache: &FontCache) -> 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_id: todo!(),
+            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_color.is_none()
+            && self.font_size.is_none()
+            && self.font_family.is_none()
+            && self.font_weight.is_none()
+            && self.font_style.is_none()
+        {
+            return None;
+        }
+
+        Some(TextStyleRefinement {
+            color: self.text_color,
+            font_family: self.font_family.clone(),
+            font_size: self.font_size,
+            font_weight: self.font_weight,
+            font_style: self.font_style,
+            underline: None,
+        })
+    }
+
+    /// Paints the background of an element styled with this style.
+    pub fn paint_background<V: 'static>(&self, _bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
+        let _rem_size = cx.rem_size();
+        if let Some(_color) = self.fill.as_ref().and_then(Fill::color) {
+            todo!();
+        }
+    }
+
+    /// Paints the foreground of an element styled with this style.
+    pub fn paint_foreground<V: 'static>(&self, _bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
+        let rem_size = cx.rem_size();
+
+        if let Some(_color) = self.border_color {
+            let border = self.border_widths.to_pixels(rem_size);
+            if !border.is_empty() {
+                todo!();
+            }
+        }
+    }
+}
+
+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: CornerRadii::default(),
+            text_color: None,
+            font_size: Some(rems(1.)),
+            font_family: None,
+            font_weight: None,
+            font_style: None,
+        }
+    }
+}
+
+#[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)
+    }
+}
+
+#[derive(Clone, Refineable, Default, Debug)]
+#[refineable(debug)]
+pub struct CornerRadii {
+    top_left: AbsoluteLength,
+    top_right: AbsoluteLength,
+    bottom_left: AbsoluteLength,
+    bottom_right: AbsoluteLength,
+}
+
+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()
+        }
+    }
+}

crates/gpui3/src/taffy.rs 🔗

@@ -0,0 +1,236 @@
+use super::{
+    AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
+    Style,
+};
+use std::fmt::Debug;
+pub use taffy::tree::NodeId as LayoutId;
+pub use taffy::*;
+pub struct TaffyLayoutEngine(Taffy);
+
+impl TaffyLayoutEngine {
+    pub fn new() -> Self {
+        TaffyLayoutEngine(Taffy::new())
+    }
+
+    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.0.new_leaf(style)?)
+        } else {
+            Ok(self.0.new_with_children(style, children)?)
+        }
+    }
+
+    pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
+        Ok(self.0.layout(id).map(Into::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<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
+where
+    S: Into<T>,
+{
+    fn from(value: taffy::geometry::Size<S>) -> Self {
+        Self {
+            width: value.width.into(),
+            height: value.height.into(),
+        }
+    }
+}
+
+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(),
+            },
+        }
+    }
+}
+
+impl From<f32> for Pixels {
+    fn from(pixels: f32) -> Self {
+        Pixels(pixels)
+    }
+}

crates/gpui3/src/text.rs 🔗

@@ -0,0 +1,852 @@
+use crate::{black, px};
+
+use super::{
+    point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformFontSystem, Point, UnderlineStyle,
+    WindowContext,
+};
+use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
+use smallvec::SmallVec;
+use std::{
+    borrow::Borrow,
+    collections::HashMap,
+    hash::{Hash, Hasher},
+    iter,
+    sync::Arc,
+};
+
+pub struct TextLayoutCache {
+    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    fonts: Arc<dyn PlatformFontSystem>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct RunStyle {
+    pub color: Hsla,
+    pub font_id: FontId,
+    pub underline: Option<UnderlineStyle>,
+}
+
+impl TextLayoutCache {
+    pub fn new(fonts: Arc<dyn PlatformFontSystem>) -> Self {
+        Self {
+            prev_frame: Mutex::new(HashMap::new()),
+            curr_frame: RwLock::new(HashMap::new()),
+            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_str<'a>(
+        &'a self,
+        text: &'a str,
+        font_size: Pixels,
+        runs: &'a [(usize, RunStyle)],
+    ) -> 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 Line::new(layout.clone(), runs);
+        }
+
+        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());
+            Line::new(layout, runs)
+        } else {
+            let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
+            let key = CacheKeyValue {
+                text: text.into(),
+                font_size,
+                runs: SmallVec::from(runs),
+            };
+            curr_frame.insert(key, layout.clone());
+            Line::new(layout, runs)
+        }
+    }
+}
+
+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, RunStyle); 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)]
+struct CacheKeyRef<'a> {
+    text: &'a str,
+    font_size: Pixels,
+    runs: &'a [(usize, RunStyle)],
+}
+
+impl<'a> CacheKey for CacheKeyRef<'a> {
+    fn key(&self) -> CacheKeyRef {
+        *self
+    }
+}
+
+impl<'a> PartialEq for CacheKeyRef<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        self.text == other.text
+            && self.font_size == other.font_size
+            && self.runs.len() == other.runs.len()
+            && self.runs.iter().zip(other.runs.iter()).all(
+                |((len_a, style_a), (len_b, style_b))| {
+                    len_a == len_b && style_a.font_id == style_b.font_id
+                },
+            )
+    }
+}
+
+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, style_id) in self.runs {
+            len.hash(state);
+            style_id.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(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>,
+}
+
+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)
+        }
+    }
+
+    pub fn paint(
+        &self,
+        origin: Point<Pixels>,
+        visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        cx: &mut WindowContext,
+    ) {
+        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 {
+            let max_glyph_width = cx
+                .font_cache()
+                .bounding_box(run.font_id, self.layout.font_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,
+                    // });
+                }
+
+                // todo!()
+                // 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,
+                //     });
+                // }
+            }
+        }
+
+        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,
+            // });
+        }
+    }
+
+    pub fn paint_wrapped(
+        &self,
+        origin: Point<Pixels>,
+        _visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        boundaries: &[ShapedBoundary],
+        cx: &mut WindowContext,
+    ) {
+        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();
+        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,
+                    // });
+                }
+
+                let _glyph_bounds = Bounds {
+                    origin: glyph_origin,
+                    size: cx
+                        .font_cache()
+                        .bounding_box(run.font_id, self.layout.font_size),
+                };
+                // todo!()
+                // 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,
+                //         });
+                //     }
+                // }
+            }
+        }
+
+        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,
+            // });
+        }
+    }
+}
+
+impl Run {
+    pub fn glyphs(&self) -> &[Glyph] {
+        &self.glyphs
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct Boundary {
+    pub ix: usize,
+    pub next_indent: u32,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ShapedBoundary {
+    pub run_ix: usize,
+    pub glyph_ix: usize,
+}
+
+impl Boundary {
+    fn new(ix: usize, next_indent: u32) -> Self {
+        Self { ix, next_indent }
+    }
+}
+
+pub struct LineWrapper {
+    font_system: Arc<dyn PlatformFontSystem>,
+    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,
+        font_system: Arc<dyn PlatformFontSystem>,
+    ) -> Self {
+        Self {
+            font_system,
+            font_id,
+            font_size,
+            cached_ascii_char_widths: [None; 128],
+            cached_other_char_widths: HashMap::new(),
+        }
+    }
+
+    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.font_system
+            .layout_line(
+                &c.to_string(),
+                self.font_size,
+                &[(
+                    1,
+                    RunStyle {
+                        font_id: self.font_id,
+                        color: Default::default(),
+                        underline: Default::default(),
+                    },
+                )],
+            )
+            .width
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{AppContext, FontWeight};
+
+    #[test]
+    fn test_wrap_line() {
+        let cx = AppContext::test();
+
+        let font_cache = cx.font_cache().clone();
+        let font_system = cx.platform().font_system();
+        let family = font_cache
+            .load_family(&["Courier"], &Default::default())
+            .unwrap();
+        let font_id = font_cache
+            .select_font(family, Default::default(), Default::default())
+            .unwrap();
+
+        let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
+        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() {
+        let cx = AppContext::test();
+        let font_cache = cx.font_cache().clone();
+        let font_system = cx.platform().font_system();
+        let text_layout_cache = TextLayoutCache::new(font_system.clone());
+
+        let family = font_cache
+            .load_family(&["Helvetica"], &Default::default())
+            .unwrap();
+        let font_id = font_cache
+            .select_font(family, Default::default(), Default::default())
+            .unwrap();
+        let normal = RunStyle {
+            font_id,
+            color: Default::default(),
+            underline: Default::default(),
+        };
+        let bold = RunStyle {
+            font_id: font_cache
+                .select_font(family, FontWeight::BOLD, Default::default())
+                .unwrap(),
+            color: Default::default(),
+            underline: Default::default(),
+        };
+
+        let text = "aa bbb cccc ddddd eeee";
+        let line = text_layout_cache.layout_str(
+            text,
+            px(16.),
+            &[
+                (4, normal.clone()),
+                (5, bold.clone()),
+                (6, normal.clone()),
+                (1, bold),
+                (7, normal),
+            ],
+        );
+
+        let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
+        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
+                }
+            ],
+        );
+    }
+}

crates/gpui3/src/window.rs 🔗

@@ -0,0 +1,251 @@
+use super::{
+    px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference, Style,
+    TaffyLayoutEngine,
+};
+use anyhow::Result;
+use derive_more::{Deref, DerefMut};
+use std::{
+    any::{Any, TypeId},
+    marker::PhantomData,
+};
+
+pub struct AnyWindow {}
+
+pub struct Window {
+    id: WindowId,
+    rem_size: Pixels,
+    layout_engine: TaffyLayoutEngine,
+    pub(crate) root_view: Option<Box<dyn Any>>,
+}
+
+impl Window {
+    pub fn new(id: WindowId) -> Window {
+        Window {
+            id,
+            layout_engine: TaffyLayoutEngine::new(),
+            rem_size: px(16.),
+            root_view: None,
+        }
+    }
+}
+
+#[derive(Deref, DerefMut)]
+pub struct WindowContext<'a, 'b> {
+    #[deref]
+    #[deref_mut]
+    app: Reference<'a, AppContext>,
+    window: Reference<'b, 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(crate) fn immutable(app: &'a AppContext, window: &'w Window) -> Self {
+        Self {
+            app: Reference::Immutable(app),
+            window: Reference::Immutable(window),
+        }
+    }
+
+    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 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
+    }
+
+    fn update_window<R>(
+        &mut self,
+        window_id: WindowId,
+        update: impl FnOnce(&mut WindowContext) -> R,
+    ) -> Result<R> {
+        if window_id == self.window.id {
+            Ok(update(self))
+        } else {
+            self.app.update_window(window_id, update)
+        }
+    }
+}
+
+impl Context for WindowContext<'_, '_> {
+    type EntityContext<'a, 'w, T: 'static> = ViewContext<'a, 'w, T>;
+
+    fn entity<T: 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Handle<T> {
+        let id = self.entities.insert(None);
+        let entity = Box::new(build_entity(&mut ViewContext::mutable(
+            &mut *self.app,
+            &mut self.window,
+            id,
+        )));
+        self.entities.get_mut(id).unwrap().replace(entity);
+
+        Handle {
+            id,
+            entity_type: PhantomData,
+        }
+    }
+
+    fn update_entity<T: 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> R {
+        let mut entity = self
+            .app
+            .entities
+            .get_mut(handle.id)
+            .unwrap()
+            .take()
+            .unwrap()
+            .downcast::<T>()
+            .unwrap();
+
+        let result = update(
+            &mut *entity,
+            &mut ViewContext::mutable(&mut *self.app, &mut *self.window, handle.id),
+        );
+
+        self.app
+            .entities
+            .get_mut(handle.id)
+            .unwrap()
+            .replace(entity);
+
+        result
+    }
+}
+
+#[derive(Deref, DerefMut)]
+pub struct ViewContext<'a, 'w, T> {
+    #[deref]
+    #[deref_mut]
+    window_cx: WindowContext<'a, 'w>,
+    entity_type: PhantomData<T>,
+    entity_id: EntityId,
+}
+
+impl<'a, 'w, T: 'static> ViewContext<'a, 'w, T> {
+    // fn update<R>(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R {
+
+    //     self.window_cx.update_entity(handle, update)
+
+    //     let mut entity = self.window_cx.app.entities.remove(&self.entity_id).unwrap();
+    //     let result = update(entity.downcast_mut::<T>().unwrap(), self);
+    //     self.window_cx
+    //         .app
+    //         .entities
+    //         .insert(self.entity_id, Box::new(entity));
+    //     result
+    // }
+
+    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,
+        }
+    }
+
+    fn immutable(app: &'a AppContext, window: &'w Window, entity_id: EntityId) -> Self {
+        Self {
+            window_cx: WindowContext::immutable(app, window),
+            entity_id,
+            entity_type: PhantomData,
+        }
+    }
+}
+
+impl<'a, 'w, T: 'static> Context for ViewContext<'a, 'w, T> {
+    type EntityContext<'b, 'c, U: 'static> = ViewContext<'b, 'c, U>;
+
+    fn entity<T2: 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T2>) -> T2,
+    ) -> Handle<T2> {
+        self.window_cx.entity(build_entity)
+    }
+
+    fn update_entity<U: '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)
+    }
+}
+
+// #[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>(),
+        }
+    }
+}
+
+pub struct AnyWindowHandle {
+    id: WindowId,
+    state_type: TypeId,
+}
+
+#[derive(Clone)]
+pub struct Layout {
+    pub order: u32,
+    pub bounds: Bounds<Pixels>,
+}