Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/elements/text.rs               |   2 
crates/gpui3/src/geometry.rs                    |  59 +++
crates/gpui3/src/gpui3.rs                       |   1 
crates/gpui3/src/platform.rs                    |  16 
crates/gpui3/src/platform/mac.rs                |   2 
crates/gpui3/src/platform/mac/metal_renderer.rs |  46 +
crates/gpui3/src/platform/mac/shaders.metal     |  49 ++
crates/gpui3/src/platform/mac/sprite.rs         |   1 
crates/gpui3/src/platform/mac/window.rs         |  17 
crates/gpui3/src/scene.rs                       | 225 +++++++++--
crates/gpui3/src/style.rs                       |  23 
crates/gpui3/src/text_system/line.rs            | 333 +++++++++++++++++++
crates/gpui3/src/window.rs                      |  16 
13 files changed, 694 insertions(+), 96 deletions(-)

Detailed changes

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

@@ -3,7 +3,7 @@ use crate::{
 };
 use parking_lot::Mutex;
 use std::{marker::PhantomData, sync::Arc};
-use util::arc_cow::ArcCow;
+use util::{arc_cow::ArcCow, ResultExt};
 
 impl<S: 'static> IntoAnyElement<S> for ArcCow<'static, str> {
     fn into_any(self) -> AnyElement<S> {

crates/gpui3/src/geometry.rs 🔗

@@ -31,10 +31,10 @@ impl<T: Clone + Debug> Point<T> {
 
 impl<T, Rhs> Mul<Rhs> for Point<T>
 where
-    T: Mul<Rhs, Output = Rhs> + Clone + Debug,
+    T: Mul<Rhs, Output = T> + Clone + Debug,
     Rhs: Clone + Debug,
 {
-    type Output = Point<Rhs>;
+    type Output = Point<T>;
 
     fn mul(self, rhs: Rhs) -> Self::Output {
         Point {
@@ -105,7 +105,7 @@ impl<T: Clone + Debug> Clone for Point<T> {
 unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
 unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
 
-#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div)]
+#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div, Hash)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Size<T: Clone + Debug> {
@@ -129,6 +129,23 @@ impl<T: Clone + Debug> Size<T> {
     }
 }
 
+impl<T: Clone + Debug + Ord> Size<T> {
+    pub fn max(&self, other: &Self) -> Self {
+        Size {
+            width: if self.width >= other.width {
+                self.width.clone()
+            } else {
+                other.width.clone()
+            },
+            height: if self.height >= other.height {
+                self.height.clone()
+            } else {
+                other.height.clone()
+            },
+        }
+    }
+}
+
 impl<T, Rhs> Mul<Rhs> for Size<T>
 where
     T: Mul<Rhs, Output = Rhs> + Debug + Clone,
@@ -151,6 +168,8 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
     }
 }
 
+impl<T: Eq + Debug + Clone> Eq for Size<T> {}
+
 impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
     fn from(val: Size<Option<Pixels>>) -> Self {
         Size {
@@ -202,6 +221,7 @@ unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Bounds<T> {}
 impl<T, Rhs> Mul<Rhs> for Bounds<T>
 where
     T: Mul<Rhs, Output = Rhs> + Clone + Debug,
+    Point<T>: Mul<Rhs, Output = Point<Rhs>>,
     Rhs: Clone + Debug,
 {
     type Output = Bounds<Rhs>;
@@ -522,11 +542,30 @@ impl From<Pixels> for f64 {
 }
 
 #[derive(
-    Clone, Copy, Debug, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd,
+    Add,
+    AddAssign,
+    Clone,
+    Copy,
+    Debug,
+    Default,
+    Div,
+    Eq,
+    Hash,
+    Ord,
+    PartialEq,
+    PartialOrd,
+    Sub,
+    SubAssign,
 )]
 #[repr(transparent)]
 pub struct DevicePixels(pub(crate) u32);
 
+impl DevicePixels {
+    pub fn to_bytes(&self, bytes_per_pixel: u8) -> u32 {
+        self.0 * bytes_per_pixel as u32
+    }
+}
+
 unsafe impl bytemuck::Pod for DevicePixels {}
 unsafe impl bytemuck::Zeroable for DevicePixels {}
 
@@ -542,6 +581,18 @@ impl From<u32> for DevicePixels {
     }
 }
 
+impl From<DevicePixels> for u64 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as u64
+    }
+}
+
+impl From<u64> for DevicePixels {
+    fn from(val: u64) -> Self {
+        DevicePixels(val as u32)
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
 pub struct Rems(f32);
 

crates/gpui3/src/gpui3.rs 🔗

@@ -23,6 +23,7 @@ pub use elements::*;
 pub use executor::*;
 pub use geometry::*;
 pub use gpui3_macros::*;
+
 pub use platform::*;
 pub use refineable::*;
 pub use scene::*;

crates/gpui3/src/platform.rs 🔗

@@ -6,8 +6,8 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, Font, FontId, FontMetrics, GlyphId, LineLayout, Pixels, Point, Result,
-    Scene, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, LineLayout,
+    MonochromeSprite, Pixels, Point, Result, Scene, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -122,7 +122,7 @@ pub trait PlatformWindow {
     fn screen(&self) -> Rc<dyn PlatformScreen>;
     fn mouse_position(&self) -> Point<Pixels>;
     fn as_any_mut(&mut self) -> &mut dyn Any;
-    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
+    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
     fn prompt(
         &self,
         level: WindowPromptLevel,
@@ -180,7 +180,15 @@ pub trait PlatformTextSystem: Send + Sync {
     ) -> Vec<usize>;
 }
 
-pub trait InputHandler {
+pub trait PlatformSpriteSystem<Key> {
+    fn get_or_insert_with(
+        &self,
+        key: Key,
+        build: impl FnOnce() -> (Size<DevicePixels>, Vec<u8>),
+    ) -> MonochromeSprite;
+}
+
+pub trait PlatformInputHandler {
     fn selected_text_range(&self) -> Option<Range<usize>>;
     fn marked_text_range(&self) -> Option<Range<usize>>;
     fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;

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

@@ -6,6 +6,7 @@ mod metal_renderer;
 mod open_type;
 mod platform;
 mod screen;
+mod sprite;
 mod text_system;
 mod window;
 mod window_appearence;
@@ -32,6 +33,7 @@ use std::{
 pub use dispatcher::*;
 pub use platform::*;
 pub use screen::*;
+pub use sprite::*;
 pub use text_system::*;
 pub use window::*;
 

crates/gpui3/src/platform/mac/metal_renderer.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{point, size, DevicePixels, Quad, Scene, Size};
+use crate::{point, size, DevicePixels, MonochromeSprite, Quad, Scene, Size};
 use bytemuck::{Pod, Zeroable};
 use cocoa::{
     base::{NO, YES},
@@ -102,9 +102,7 @@ impl MetalRenderer {
         &*self.layer
     }
 
-    pub fn draw(&mut self, scene: &Scene) {
-        dbg!(scene);
-
+    pub fn draw(&mut self, scene: &mut Scene) {
         let layer = self.layer.clone();
         let viewport_size = layer.drawable_size();
         let viewport_size: Size<DevicePixels> = size(
@@ -159,21 +157,35 @@ impl MetalRenderer {
             zfar: 1.0,
         });
 
-        let mut buffer_offset = 0;
+        let mut instance_offset = 0;
         for layer in scene.layers() {
-            self.draw_quads(
-                &layer.quads,
-                &mut buffer_offset,
-                viewport_size,
-                command_encoder,
-            );
+            for batch in layer.batches() {
+                match batch {
+                    crate::PrimitiveBatch::Quads(quads) => {
+                        self.draw_quads(
+                            quads,
+                            &mut instance_offset,
+                            viewport_size,
+                            command_encoder,
+                        );
+                    }
+                    crate::PrimitiveBatch::Sprites(sprites) => {
+                        self.draw_monochrome_sprites(
+                            sprites,
+                            &mut instance_offset,
+                            viewport_size,
+                            command_encoder,
+                        );
+                    }
+                }
+            }
         }
 
         command_encoder.end_encoding();
 
         self.instances.did_modify_range(NSRange {
             location: 0,
-            length: buffer_offset as NSUInteger,
+            length: instance_offset as NSUInteger,
         });
 
         command_buffer.commit();
@@ -238,6 +250,16 @@ impl MetalRenderer {
         );
         *offset = next_offset;
     }
+
+    fn draw_monochrome_sprites(
+        &mut self,
+        monochrome: &[MonochromeSprite],
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        todo!()
+    }
 }
 
 fn build_pipeline_state(

crates/gpui3/src/platform/mac/shaders.metal 🔗

@@ -198,3 +198,52 @@ float4 to_device_position(float2 pixel_position, float2 viewport_size) {
                     float2(-1., 1.),
                 0., 1.);
 }
+
+// struct SpriteFragmentInput {
+//     float4 position [[position]];
+//     float2 atlas_position;
+//     float4 color [[flat]];
+//     uchar compute_winding [[flat]];
+// };
+
+// vertex SpriteFragmentInput sprite_vertex(
+//     uint unit_vertex_id [[vertex_id]],
+//     uint sprite_id [[instance_id]],
+//     constant float2 *unit_vertices
+//     [[buffer(GPUISpriteVertexInputIndexVertices)]], constant GPUISprite
+//     *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]], constant float2
+//     *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
+//     constant float2 *atlas_size
+//     [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
+// ) {
+//     float2 unit_vertex = unit_vertices[unit_vertex_id];
+//     GPUISprite sprite = sprites[sprite_id];
+//     float2 position = unit_vertex * sprite.target_size + sprite.origin;
+//     float4 device_position = to_device_position(position, *viewport_size);
+//     float2 atlas_position = (unit_vertex * sprite.source_size +
+//     sprite.atlas_origin) / *atlas_size;
+
+//     return SpriteFragmentInput {
+//         device_position,
+//         atlas_position,
+//         coloru_to_colorf(sprite.color),
+//         sprite.compute_winding
+//     };
+// }
+
+// fragment float4 sprite_fragment(
+//     SpriteFragmentInput input [[stage_in]],
+//     texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
+// ) {
+//     constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
+//     float4 color = input.color;
+//     float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
+//     float mask;
+//     if (input.compute_winding) {
+//         mask = 1. - abs(1. - fmod(sample.r, 2.));
+//     } else {
+//         mask = sample.a;
+//     }
+//     color.a *= mask;
+//     return color;
+// }

crates/gpui3/src/platform/mac/window.rs 🔗

@@ -1,8 +1,8 @@
 use super::{ns_string, MetalRenderer, NSRange};
 use crate::{
-    point, px, size, AnyWindowHandle, Bounds, Event, InputHandler, KeyDownEvent, Keystroke,
-    MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent,
-    MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen, PlatformWindow,
+    point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen, Modifiers,
+    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt,
+    Pixels, Platform, PlatformDispatcher, PlatformInputHandler, PlatformScreen, PlatformWindow,
     Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
     WindowPromptLevel,
 };
@@ -292,7 +292,7 @@ struct MacWindowState {
     should_close_callback: Option<Box<dyn FnMut() -> bool>>,
     close_callback: Option<Box<dyn FnOnce()>>,
     appearance_changed_callback: Option<Box<dyn FnMut()>>,
-    input_handler: Option<Box<dyn InputHandler>>,
+    input_handler: Option<Box<dyn PlatformInputHandler>>,
     pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
     last_key_equivalent: Option<KeyDownEvent>,
     synthetic_drag_counter: usize,
@@ -671,7 +671,7 @@ impl PlatformWindow for MacWindow {
         self
     }
 
-    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
+    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
         self.0.as_ref().lock().input_handler = Some(input_handler);
     }
 
@@ -1357,9 +1357,8 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
     unsafe {
         let window_state = get_window_state(this);
         let mut window_state = window_state.as_ref().lock();
-        if let Some(scene) = window_state.scene_to_render.take() {
-            dbg!("render", &scene);
-            window_state.renderer.draw(&scene);
+        if let Some(mut scene) = window_state.scene_to_render.take() {
+            window_state.renderer.draw(&mut scene);
         }
     }
 }
@@ -1580,7 +1579,7 @@ async fn synthetic_drag(
 
 fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
 where
-    F: FnOnce(&mut dyn InputHandler) -> R,
+    F: FnOnce(&mut dyn PlatformInputHandler) -> R,
 {
     let window_state = unsafe { get_window_state(window) };
     let mut lock = window_state.as_ref().lock();

crates/gpui3/src/scene.rs 🔗

@@ -1,82 +1,201 @@
-use std::mem;
+use std::{iter::Peekable, mem};
 
 use super::{Bounds, Hsla, Pixels, Point};
-use crate::{Corners, Edges, FontId, GlyphId};
+use crate::{Corners, DevicePixels, Edges};
 use bytemuck::{Pod, Zeroable};
-use collections::BTreeMap;
 
 // Exported to metal
 pub type PointF = Point<f32>;
+pub type StackingOrder = SmallVec<[u32; 16]>;
 
 #[derive(Debug)]
 pub struct Scene {
-    layers: BTreeMap<u32, SceneLayer>,
-    pub(crate) scale_factor: f32,
-}
-
-#[derive(Default, Debug)]
-pub struct SceneLayer {
-    pub quads: Vec<Quad>,
-    pub symbol: Vec<Symbol>,
+    scale_factor: f32,
+    pub(crate) layers: BTreeMap<StackingOrder, SceneLayer>,
 }
 
 impl Scene {
     pub fn new(scale_factor: f32) -> Scene {
         Scene {
-            layers: Default::default(),
             scale_factor,
+            layers: BTreeMap::new(),
         }
     }
 
     pub fn take(&mut self) -> Scene {
         Scene {
-            layers: mem::take(&mut self.layers),
             scale_factor: self.scale_factor,
+            layers: mem::take(&mut self.layers),
         }
     }
 
-    pub fn insert(&mut self, primitive: impl Into<Primitive>) {
-        let mut primitive = primitive.into();
-        primitive.scale(self.scale_factor);
-        let layer = self.layers.entry(primitive.order()).or_default();
+    pub fn insert(&mut self, order: StackingOrder, primitive: impl Into<Primitive>) {
+        let layer = self.layers.entry(order).or_default();
+
+        let primitive = primitive.into();
         match primitive {
-            Primitive::Quad(quad) => layer.quads.push(quad),
-            Primitive::MonochromeGlyph(glyph) => layer.symbol.push(glyph),
+            Primitive::Quad(mut quad) => {
+                quad.scale(self.scale_factor);
+                layer.quads.push(quad);
+            }
+            Primitive::Sprite(mut sprite) => {
+                sprite.scale(self.scale_factor);
+                layer.sprites.push(sprite);
+            }
         }
     }
 
-    pub fn layers(&self) -> impl Iterator<Item = &SceneLayer> {
-        self.layers.values()
+    pub(crate) fn layers(&mut self) -> impl Iterator<Item = &mut SceneLayer> {
+        self.layers.values_mut()
     }
 }
 
-#[derive(Clone, Debug)]
-pub enum Primitive {
-    Quad(Quad),
-    MonochromeGlyph(Symbol),
+#[derive(Debug, Default)]
+pub(crate) struct SceneLayer {
+    pub quads: Vec<Quad>,
+    pub sprites: Vec<MonochromeSprite>,
+}
+
+impl SceneLayer {
+    pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
+        self.quads.sort_unstable_by(|a, b| a.order.cmp(&b.order));
+        self.sprites.sort_unstable_by(|a, b| a.order.cmp(&b.order));
+
+        BatchIterator::new(
+            &self.quads,
+            self.quads.iter().peekable(),
+            &self.sprites,
+            self.sprites.iter().peekable(),
+        )
+    }
 }
 
-impl Primitive {
-    pub fn order(&self) -> u32 {
-        match self {
-            Primitive::Quad(quad) => quad.order,
-            Primitive::MonochromeGlyph(glyph) => glyph.order,
+struct BatchIterator<'a, Q, S>
+where
+    Q: Iterator<Item = &'a Quad>,
+    S: Iterator<Item = &'a MonochromeSprite>,
+{
+    next_batch_kind: Option<PrimitiveKind>,
+    quads: &'a [Quad],
+    sprites: &'a [MonochromeSprite],
+    quads_start: usize,
+    sprites_start: usize,
+    quads_iter: Peekable<Q>,
+    sprites_iter: Peekable<S>,
+}
+
+impl<'a, Q: 'a, S: 'a> Iterator for BatchIterator<'a, Q, S>
+where
+    Q: Iterator<Item = &'a Quad>,
+    S: Iterator<Item = &'a MonochromeSprite>,
+{
+    type Item = PrimitiveBatch<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if let Some(batch_kind) = self.next_batch_kind.take() {
+            match batch_kind {
+                PrimitiveKind::Quad => {
+                    let max_order = self
+                        .next_order(Some(PrimitiveKind::Quad))
+                        .unwrap_or(u32::MAX);
+                    let quads_start = self.quads_start;
+                    let quads_end = quads_start
+                        + self
+                            .quads_iter
+                            .by_ref()
+                            .take_while(|quad| quad.order <= max_order)
+                            .count();
+                    self.quads_start = quads_end;
+                    Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
+                }
+                PrimitiveKind::Sprite => {
+                    let max_order = self
+                        .next_order(Some(PrimitiveKind::Sprite))
+                        .unwrap_or(u32::MAX);
+                    let sprites_start = self.sprites_start;
+                    let sprites_end = sprites_start
+                        + self
+                            .sprites_iter
+                            .by_ref()
+                            .take_while(|sprite| sprite.order <= max_order)
+                            .count();
+                    self.sprites_start = sprites_end;
+                    Some(PrimitiveBatch::Sprites(
+                        &self.sprites[sprites_start..sprites_end],
+                    ))
+                }
+            }
+        } else {
+            None
         }
     }
+}
+
+impl<'a, Q: 'a, S: 'a> BatchIterator<'a, Q, S>
+where
+    Q: Iterator<Item = &'a Quad>,
+    S: Iterator<Item = &'a MonochromeSprite>,
+{
+    fn new(
+        quads: &'a [Quad],
+        quads_iter: Peekable<Q>,
+        sprites: &'a [MonochromeSprite],
+        sprites_iter: Peekable<S>,
+    ) -> Self {
+        let mut this = Self {
+            quads,
+            quads_start: 0,
+            quads_iter,
+            sprites,
+            sprites_start: 0,
+            sprites_iter,
+            next_batch_kind: None,
+        };
+        this.next_order(None); // Called for its side effect of setting this.next_batch_kind
+        this
+    }
 
-    pub fn scale(&mut self, factor: f32) {
-        match self {
-            Primitive::Quad(quad) => {
-                quad.scale(factor);
+    fn next_order(&mut self, exclude_kind: Option<PrimitiveKind>) -> Option<u32> {
+        let mut next_order = u32::MAX;
+
+        if exclude_kind != Some(PrimitiveKind::Quad) {
+            if let Some(next_quad) = self.quads_iter.peek() {
+                self.next_batch_kind = Some(PrimitiveKind::Quad);
+                next_order = next_quad.order;
             }
-            Primitive::MonochromeGlyph(glyph) => {
-                glyph.scale(factor);
+        }
+
+        if exclude_kind != Some(PrimitiveKind::Sprite) {
+            if let Some(next_sprite) = self.sprites_iter.peek() {
+                if next_sprite.order < next_order {
+                    self.next_batch_kind = Some(PrimitiveKind::Sprite);
+                    next_order = next_sprite.order;
+                }
             }
         }
+
+        (next_order < u32::MAX).then_some(next_order)
     }
 }
 
-#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum PrimitiveKind {
+    Quad,
+    Sprite,
+}
+
+#[derive(Clone, Debug)]
+pub enum Primitive {
+    Quad(Quad),
+    Sprite(MonochromeSprite),
+}
+
+pub enum PrimitiveBatch<'a> {
+    Quads(&'a [Quad]),
+    Sprites(&'a [MonochromeSprite]),
+}
+
+#[derive(Debug, Copy, Clone, Zeroable, Pod)]
 #[repr(C)]
 pub struct Quad {
     pub order: u32,
@@ -119,26 +238,32 @@ impl From<Quad> for Primitive {
     }
 }
 
-#[derive(Debug, Clone, Copy)]
+#[derive(Clone, Debug)]
 #[repr(C)]
-pub struct Symbol {
+pub struct MonochromeSprite {
     pub order: u32,
-    pub origin: Point<Pixels>,
-    pub font_id: FontId,
-    pub font_size: Pixels,
-    pub id: GlyphId,
-    pub color: Hsla,
+    pub bounds: Bounds<Pixels>,
+    pub atlas_id: AtlasId,
+    pub tile_id: TileId,
+    pub bounds_in_atlas: Bounds<DevicePixels>,
+    pub color: Option<Hsla>,
 }
 
-impl Symbol {
+impl MonochromeSprite {
     pub fn scale(&mut self, factor: f32) {
-        self.font_size *= factor;
-        self.origin *= factor;
+        self.bounds *= factor;
     }
 }
 
-impl From<Symbol> for Primitive {
-    fn from(glyph: Symbol) -> Self {
-        Primitive::MonochromeGlyph(glyph)
+impl From<MonochromeSprite> for Primitive {
+    fn from(sprite: MonochromeSprite) -> Self {
+        Primitive::Sprite(sprite)
     }
 }
+
+#[derive(Copy, Clone, Debug)]
+pub struct AtlasId(pub(crate) usize);
+
+use collections::BTreeMap;
+use etagere::AllocId as TileId;
+use smallvec::SmallVec;

crates/gpui3/src/style.rs 🔗

@@ -185,16 +185,19 @@ impl Style {
 
         let background_color = self.fill.as_ref().and_then(Fill::color);
         if background_color.is_some() || self.is_border_visible() {
-            cx.scene().insert(Quad {
-                order,
-                bounds,
-                clip_bounds: bounds, // todo!
-                clip_corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
-                background: background_color.unwrap_or_default(),
-                border_color: self.border_color.unwrap_or_default(),
-                corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
-                border_widths: self.border_widths.map(|length| length.to_pixels(rem_size)),
-            });
+            cx.scene().insert(
+                todo!(),
+                Quad {
+                    order,
+                    bounds,
+                    clip_bounds: bounds, // todo!
+                    clip_corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
+                    background: background_color.unwrap_or_default(),
+                    border_color: self.border_color.unwrap_or_default(),
+                    corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
+                    border_widths: self.border_widths.map(|length| length.to_pixels(rem_size)),
+                },
+            );
         }
     }
 

crates/gpui3/src/text_system/line.rs 🔗

@@ -0,0 +1,333 @@
+use crate::{
+    black, point, px, Bounds, FontId, Hsla, Layout, LineLayout, Pixels, Point, Run, RunStyle,
+    ShapedBoundary, UnderlineStyle, WindowContext,
+};
+use anyhow::Result;
+use smallvec::SmallVec;
+use std::sync::Arc;
+
+#[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,
+}
+
+impl Line {
+    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
+        let mut style_runs = SmallVec::new();
+        for (len, style) in runs {
+            style_runs.push(StyleRun {
+                len: *len as u32,
+                color: style.color,
+                underline: style.underline.clone().unwrap_or_default(),
+            });
+        }
+        Self { layout, style_runs }
+    }
+
+    pub fn runs(&self) -> &[Run] {
+        &self.layout.runs
+    }
+
+    pub fn width(&self) -> Pixels {
+        self.layout.width
+    }
+
+    pub fn font_size(&self) -> Pixels {
+        self.layout.font_size
+    }
+
+    pub fn x_for_index(&self, index: usize) -> Pixels {
+        for run in &self.layout.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return glyph.position.x;
+                }
+            }
+        }
+        self.layout.width
+    }
+
+    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
+        for run in &self.layout.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return Some(run.font_id);
+                }
+            }
+        }
+
+        None
+    }
+
+    pub fn len(&self) -> usize {
+        self.layout.len
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.layout.len == 0
+    }
+
+    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
+        if x >= self.layout.width {
+            None
+        } else {
+            for run in self.layout.runs.iter().rev() {
+                for glyph in run.glyphs.iter().rev() {
+                    if glyph.position.x <= x {
+                        return Some(glyph.index);
+                    }
+                }
+            }
+            Some(0)
+        }
+    }
+
+    // todo!
+    pub fn paint(
+        &self,
+        layout: &Layout,
+        visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        let origin = layout.bounds.origin;
+        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;
+        let text_system = cx.text_system().clone();
+
+        for run in &self.layout.runs {
+            text_system.with_font(run.font_id, |system, font| {
+                let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width;
+
+                for glyph in &run.glyphs {
+                    let glyph_origin = origin + baseline_offset + glyph.position;
+                    if glyph_origin.x > visible_bounds.upper_right().x {
+                        break;
+                    }
+
+                    let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
+                    if glyph.index >= run_end {
+                        if let Some(style_run) = style_runs.next() {
+                            if let Some((_, underline_style)) = &mut underline {
+                                if style_run.underline != *underline_style {
+                                    finished_underline = underline.take();
+                                }
+                            }
+                            if style_run.underline.thickness > px(0.) {
+                                underline.get_or_insert((
+                                    point(
+                                        glyph_origin.x,
+                                        origin.y
+                                            + baseline_offset.y
+                                            + (self.layout.descent * 0.618),
+                                    ),
+                                    UnderlineStyle {
+                                        color: style_run.underline.color,
+                                        thickness: style_run.underline.thickness,
+                                        squiggly: style_run.underline.squiggly,
+                                    },
+                                ));
+                            }
+
+                            run_end += style_run.len as usize;
+                            color = style_run.color;
+                        } else {
+                            run_end = self.layout.len;
+                            finished_underline = underline.take();
+                        }
+                    }
+
+                    if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
+                        continue;
+                    }
+
+                    if let Some((_underline_origin, _underline_style)) = finished_underline {
+                        todo!()
+                        // cx.scene().insert(Underline {
+                        //     origin: underline_origin,
+                        //     width: glyph_origin.x - underline_origin.x,
+                        //     thickness: underline_style.thickness.into(),
+                        //     color: underline_style.color.unwrap(),
+                        //     squiggly: underline_style.squiggly,
+                        // });
+                    }
+
+                    if glyph.is_emoji {
+                        todo!()
+                        // 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 {
+                        todo!()
+                        // cx.scene().insert(Symbol {
+                        //     order: layout.order,
+                        //     origin,
+                        //     font_id: run.font_id,
+                        //     font_size: self.layout.font_size,
+                        //     id: glyph.id,
+                        //     color,
+                        // });
+                    }
+                }
+
+                anyhow::Ok(())
+            })??;
+        }
+
+        if let Some((_underline_start, _underline_style)) = underline.take() {
+            let _line_end_x = origin.x + self.layout.width;
+            // cx.scene().push_underline(Underline {
+            //     origin: underline_start,
+            //     width: line_end_x - underline_start.x,
+            //     color: underline_style.color,
+            //     thickness: underline_style.thickness.into(),
+            //     squiggly: underline_style.squiggly,
+            // });
+        }
+
+        Ok(())
+    }
+
+    pub fn paint_wrapped(
+        &self,
+        origin: Point<Pixels>,
+        _visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        boundaries: &[ShapedBoundary],
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
+        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
+
+        let mut boundaries = boundaries.into_iter().peekable();
+        let mut color_runs = self.style_runs.iter();
+        let mut style_run_end = 0;
+        let mut _color = black(); // todo!
+        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
+
+        let mut glyph_origin = origin;
+        let mut prev_position = px(0.);
+        for (run_ix, run) in self.layout.runs.iter().enumerate() {
+            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
+                glyph_origin.x += glyph.position.x - prev_position;
+
+                if boundaries
+                    .peek()
+                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
+                {
+                    boundaries.next();
+                    if let Some((_underline_origin, _underline_style)) = underline.take() {
+                        // cx.scene().push_underline(Underline {
+                        //     origin: underline_origin,
+                        //     width: glyph_origin.x - underline_origin.x,
+                        //     thickness: underline_style.thickness.into(),
+                        //     color: underline_style.color.unwrap(),
+                        //     squiggly: underline_style.squiggly,
+                        // });
+                    }
+
+                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
+                }
+                prev_position = glyph.position.x;
+
+                let mut finished_underline = None;
+                if glyph.index >= style_run_end {
+                    if let Some(style_run) = color_runs.next() {
+                        style_run_end += style_run.len as usize;
+                        _color = style_run.color;
+                        if let Some((_, underline_style)) = &mut underline {
+                            if style_run.underline != *underline_style {
+                                finished_underline = underline.take();
+                            }
+                        }
+                        if style_run.underline.thickness > px(0.) {
+                            underline.get_or_insert((
+                                glyph_origin
+                                    + point(
+                                        px(0.),
+                                        baseline_offset.y + (self.layout.descent * 0.618),
+                                    ),
+                                UnderlineStyle {
+                                    color: Some(
+                                        style_run.underline.color.unwrap_or(style_run.color),
+                                    ),
+                                    thickness: style_run.underline.thickness,
+                                    squiggly: style_run.underline.squiggly,
+                                },
+                            ));
+                        }
+                    } else {
+                        style_run_end = self.layout.len;
+                        _color = black();
+                        finished_underline = underline.take();
+                    }
+                }
+
+                if let Some((_underline_origin, _underline_style)) = finished_underline {
+                    // cx.scene().push_underline(Underline {
+                    //     origin: underline_origin,
+                    //     width: glyph_origin.x - underline_origin.x,
+                    //     thickness: underline_style.thickness.into(),
+                    //     color: underline_style.color.unwrap(),
+                    //     squiggly: underline_style.squiggly,
+                    // });
+                }
+
+                cx.text_system().with_font(run.font_id, |system, font| {
+                    let _glyph_bounds = Bounds {
+                        origin: glyph_origin,
+                        size: system.bounding_box(font, self.layout.font_size)?.size,
+                    };
+                    // if glyph_bounds.intersects(visible_bounds) {
+                    //     if glyph.is_emoji {
+                    //         cx.scene().push_image_glyph(scene::ImageGlyph {
+                    //             font_id: run.font_id,
+                    //             font_size: self.layout.font_size,
+                    //             id: glyph.id,
+                    //             origin: glyph_bounds.origin() + baseline_offset,
+                    //         });
+                    //     } else {
+                    //         cx.scene().push_glyph(scene::Glyph {
+                    //             font_id: run.font_id,
+                    //             font_size: self.layout.font_size,
+                    //             id: glyph.id,
+                    //             origin: glyph_bounds.origin() + baseline_offset,
+                    //             color,
+                    //         });
+                    //     }
+                    // }
+                    anyhow::Ok(())
+                })??;
+            }
+        }
+
+        if let Some((_underline_origin, _underline_style)) = underline.take() {
+            // let line_end_x = glyph_origin.x + self.layout.width - prev_position;
+            // cx.scene().push_underline(Underline {
+            //     origin: underline_origin,
+            //     width: line_end_x - underline_origin.x,
+            //     thickness: underline_style.thickness.into(),
+            //     color: underline_style.color,
+            //     squiggly: underline_style.squiggly,
+            // });
+        }
+
+        Ok(())
+    }
+}

crates/gpui3/src/window.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
     LayoutId, MainThread, MainThreadOnly, Pixels, PlatformWindow, Point, Reference, Scene, Size,
-    StackContext, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
+    StackContext, StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
 };
 use anyhow::Result;
 use futures::Future;
@@ -19,7 +19,7 @@ pub struct Window {
     layout_engine: TaffyLayoutEngine,
     pub(crate) root_view: Option<AnyView<()>>,
     mouse_position: Point<Pixels>,
-    z_index_stack: SmallVec<[u32; 8]>,
+    current_stacking_order: StackingOrder,
     pub(crate) scene: Scene,
     pub(crate) dirty: bool,
 }
@@ -58,7 +58,7 @@ impl Window {
             layout_engine: TaffyLayoutEngine::new(),
             root_view: None,
             mouse_position,
-            z_index_stack: SmallVec::new(),
+            current_stacking_order: SmallVec::new(),
             scene: Scene::new(scale_factor),
             dirty: true,
         }
@@ -129,13 +129,17 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         &mut self.window.scene
     }
 
-    pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
-        self.window.z_index_stack.push(z_index);
+    pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+        self.window.current_stacking_order.push(order);
         let result = f(self);
-        self.window.z_index_stack.pop();
+        self.window.current_stacking_order.pop();
         result
     }
 
+    pub fn current_stack_order(&self) -> StackingOrder {
+        self.window.current_stacking_order.clone()
+    }
+
     pub fn run_on_main<R>(
         &self,
         f: impl FnOnce(&mut MainThread<WindowContext>) -> R + Send + 'static,