Detailed changes
@@ -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"
@@ -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,
+ }
+ }
+}
@@ -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))
+ }
+}
@@ -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,
+ }
+ }
+}
@@ -0,0 +1,8 @@
+pub mod div;
+pub mod editor;
+
+use super::*;
+use std::marker::PhantomData;
+
+pub use div::div;
+pub use editor::field;
@@ -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!()
+}
@@ -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 {}
+ }
+}
@@ -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);
+ }
+}
@@ -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())
+ }
+}
@@ -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));
+ }
+}
@@ -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>),
+}
@@ -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!()
+ }
+}
@@ -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()));
+ }
+}
@@ -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)
+ }
+}
@@ -0,0 +1 @@
+
@@ -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()
+ }
+ }
+}
@@ -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)
+ }
+}
@@ -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
+ }
+ ],
+ );
+ }
+}
@@ -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>,
+}