Merge branch 'gpui2' into marshall/gpui2-playground

This commit is contained in:
Marshall Bowers 2023-10-06 13:45:11 -04:00
commit 7fd35d68bb
34 changed files with 2710 additions and 620 deletions

View file

@ -7,7 +7,7 @@ description = "The next version of Zed's GPU-accelerated UI framework"
publish = false publish = false
[features] [features]
test = ["backtrace", "dhat", "env_logger", "collections/test-support"] test = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
[lib] [lib]
path = "src/gpui3.rs" path = "src/gpui3.rs"
@ -66,6 +66,7 @@ dhat = "0.3"
env_logger.workspace = true env_logger.workspace = true
png = "0.16" png = "0.16"
simplelog = "0.9" simplelog = "0.9"
util = { path = "../util", features = ["test-support"] }
[build-dependencies] [build-dependencies]
bindgen = "0.65.1" bindgen = "0.65.1"

View file

@ -50,7 +50,11 @@ fn generate_shader_bindings() -> PathBuf {
"ScaledContentMask".into(), "ScaledContentMask".into(),
"Uniforms".into(), "Uniforms".into(),
"AtlasTile".into(), "AtlasTile".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"QuadInputIndex".into(), "QuadInputIndex".into(),
"Underline".into(),
"UnderlineInputIndex".into(),
"Quad".into(), "Quad".into(),
"SpriteInputIndex".into(), "SpriteInputIndex".into(),
"MonochromeSprite".into(), "MonochromeSprite".into(),

View file

@ -8,9 +8,9 @@ pub use model_context::*;
use refineable::Refineable; use refineable::Refineable;
use crate::{ use crate::{
current_platform, image_cache::ImageCache, AssetSource, Context, Executor, LayoutId, current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, LayoutId,
MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle, MainThread, MainThreadOnly, Platform, PlatformDisplayLinker, RootView, SvgRenderer, Task,
TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId, TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
@ -51,18 +51,19 @@ impl App {
http_client: Arc<dyn HttpClient>, http_client: Arc<dyn HttpClient>,
) -> Self { ) -> Self {
let executor = platform.executor(); let executor = platform.executor();
let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new(); let entities = EntityMap::new();
let unit_entity = entities.insert(entities.reserve(), ()); let unit_entity = entities.insert(entities.reserve(), ());
Self(Arc::new_cyclic(|this| { Self(Arc::new_cyclic(|this| {
Mutex::new(AppContext { Mutex::new(AppContext {
this: this.clone(), this: this.clone(),
text_system: Arc::new(TextSystem::new(platform.text_system())),
pending_updates: 0,
display_linker: platform.display_linker(),
next_frame_callbacks: Default::default(),
platform: MainThreadOnly::new(platform, executor.clone()), platform: MainThreadOnly::new(platform, executor.clone()),
executor, executor,
text_system,
svg_renderer: SvgRenderer::new(asset_source), svg_renderer: SvgRenderer::new(asset_source),
image_cache: ImageCache::new(http_client), image_cache: ImageCache::new(http_client),
pending_updates: 0,
text_style_stack: Vec::new(), text_style_stack: Vec::new(),
state_stacks_by_type: HashMap::default(), state_stacks_by_type: HashMap::default(),
unit_entity, unit_entity,
@ -90,12 +91,15 @@ impl App {
} }
type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>; type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
pub struct AppContext { pub struct AppContext {
this: Weak<Mutex<AppContext>>, this: Weak<Mutex<AppContext>>,
platform: MainThreadOnly<dyn Platform>, platform: MainThreadOnly<dyn Platform>,
text_system: Arc<TextSystem>, text_system: Arc<TextSystem>,
pending_updates: usize, pending_updates: usize,
pub(crate) display_linker: Arc<dyn PlatformDisplayLinker>,
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
pub(crate) executor: Executor, pub(crate) executor: Executor,
pub(crate) svg_renderer: SvgRenderer, pub(crate) svg_renderer: SvgRenderer,
pub(crate) image_cache: ImageCache, pub(crate) image_cache: ImageCache,
@ -145,7 +149,6 @@ impl AppContext {
} }
fn flush_effects(&mut self) { fn flush_effects(&mut self) {
dbg!("flush effects");
while let Some(effect) = self.pending_effects.pop_front() { while let Some(effect) = self.pending_effects.pop_front() {
match effect { match effect {
Effect::Notify(entity_id) => self.apply_notify_effect(entity_id), Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),

View file

@ -60,9 +60,19 @@ pub struct AsyncWindowContext {
} }
impl AsyncWindowContext { impl AsyncWindowContext {
pub fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self { pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
Self { app, window } Self { app, window }
} }
pub fn update<R>(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
self.app.update_window(self.window, update)
}
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
self.app
.update_window(self.window, |cx| cx.on_next_frame(f))
.ok();
}
} }
impl Context for AsyncWindowContext { impl Context for AsyncWindowContext {

View file

@ -1,4 +1,6 @@
use super::{Layout, LayoutId, Pixels, Point, Result, ViewContext}; use crate::Bounds;
use super::{LayoutId, Pixels, Point, Result, ViewContext};
pub(crate) use smallvec::SmallVec; pub(crate) use smallvec::SmallVec;
pub trait Element: 'static { pub trait Element: 'static {
@ -13,7 +15,7 @@ pub trait Element: 'static {
fn paint( fn paint(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
state: &mut Self::State, state: &mut Self::State,
frame_state: &mut Self::FrameState, frame_state: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>, cx: &mut ViewContext<Self::State>,
@ -90,7 +92,7 @@ enum ElementRenderPhase<S> {
frame_state: S, frame_state: S,
}, },
Painted { Painted {
layout: Layout, bounds: Bounds<Pixels>,
frame_state: S, frame_state: S,
}, },
} }
@ -130,24 +132,23 @@ impl<E: Element> ElementObject<E::State> for RenderedElement<E> {
layout_id, layout_id,
mut frame_state, mut frame_state,
} => { } => {
let mut layout = cx.layout(layout_id)?.clone(); let mut bounds = cx.layout_bounds(layout_id)?.clone();
offset.map(|offset| layout.bounds.origin += offset); offset.map(|offset| bounds.origin += offset);
self.element self.element.paint(bounds, state, &mut frame_state, cx)?;
.paint(layout.clone(), state, &mut frame_state, cx)?;
ElementRenderPhase::Painted { ElementRenderPhase::Painted {
layout, bounds,
frame_state, frame_state,
} }
} }
ElementRenderPhase::Painted { ElementRenderPhase::Painted {
layout, bounds,
mut frame_state, mut frame_state,
} => { } => {
self.element self.element
.paint(layout.clone(), state, &mut frame_state, cx)?; .paint(bounds.clone(), state, &mut frame_state, cx)?;
ElementRenderPhase::Painted { ElementRenderPhase::Painted {
layout, bounds,
frame_state, frame_state,
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point, AnyElement, Bounds, Element, LayoutId, Overflow, ParentElement, Pixels, Point, Refineable,
Refineable, RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext, RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -40,34 +40,28 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
fn paint( fn paint(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
state: &mut S, state: &mut S,
child_layouts: &mut Self::FrameState, child_layouts: &mut Self::FrameState,
cx: &mut ViewContext<S>, cx: &mut ViewContext<S>,
) -> Result<()> { ) -> Result<()> {
let Layout { order, bounds } = layout;
let style = self.computed_style(); let style = self.computed_style();
style.paint(order, bounds, cx); cx.stack(0, |cx| style.paint(bounds, cx));
// // todo!("support only one dimension being hidden")
let overflow = &style.overflow; let overflow = &style.overflow;
// if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
// cx.clip(layout.bounds, style.corner_radii, || )
// }
style.apply_text_style(cx, |cx| { style.apply_text_style(cx, |cx| {
style.apply_overflow(layout.bounds, cx, |cx| { cx.stack(1, |cx| {
self.paint_children(overflow, state, cx) style.apply_overflow(bounds, cx, |cx| self.paint_children(overflow, state, cx))
}) })
})?; })?;
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx); self.handle_scroll(bounds, style.overflow.clone(), child_layouts, cx);
// todo!("enable inspector") // todo!("enable inspector")
// if cx.is_inspector_enabled() { // if cx.is_inspector_enabled() {
// self.paint_inspector(parent_origin, layout, cx); // self.paint_inspector(parent_origin, layout, cx);
// } // }
// //
Ok(()) Ok(())
} }
} }
@ -142,7 +136,6 @@ impl<S: 'static> Div<S> {
fn handle_scroll( fn handle_scroll(
&mut self, &mut self,
_order: u32,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
overflow: Point<Overflow>, overflow: Point<Overflow>,
child_layout_ids: &[LayoutId], child_layout_ids: &[LayoutId],
@ -151,8 +144,8 @@ impl<S: 'static> Div<S> {
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll { if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
let mut scroll_max = Point::default(); let mut scroll_max = Point::default();
for child_layout_id in child_layout_ids { for child_layout_id in child_layout_ids {
if let Some(child_layout) = cx.layout(*child_layout_id).log_err() { if let Some(child_bounds) = cx.layout_bounds(*child_layout_id).log_err() {
scroll_max = scroll_max.max(&child_layout.bounds.lower_right()); scroll_max = scroll_max.max(&child_bounds.lower_right());
} }
} }
scroll_max -= bounds.size; scroll_max -= bounds.size;

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
BorrowWindow, Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled, BorrowWindow, Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers,
ViewContext, Styled, ViewContext,
}; };
use futures::FutureExt; use futures::FutureExt;
use refineable::RefinementCascade; use refineable::RefinementCascade;
@ -54,16 +54,14 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
fn paint( fn paint(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
_: &mut Self::State, _: &mut Self::State,
_: &mut Self::FrameState, _: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>, cx: &mut ViewContext<Self::State>,
) -> Result<()> { ) -> Result<()> {
let style = self.computed_style(); let style = self.computed_style();
let order = layout.order;
let bounds = layout.bounds;
style.paint(order, bounds, cx); style.paint(bounds, cx);
if let Some(uri) = self.uri.clone() { if let Some(uri) = self.uri.clone() {
let image_future = cx.image_cache.get(uri); let image_future = cx.image_cache.get(uri);
@ -72,15 +70,14 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
.now_or_never() .now_or_never()
.and_then(ResultExt::log_err) .and_then(ResultExt::log_err)
{ {
let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size()); let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?; cx.stack(1, |cx| {
cx.paint_image(bounds, corner_radii, data, self.grayscale)
})?;
} else { } else {
cx.spawn(|view, mut cx| async move { cx.spawn(|_, mut cx| async move {
if image_future.await.log_err().is_some() { if image_future.await.log_err().is_some() {
view.update(&mut cx, |_, cx| { cx.on_next_frame(|cx| cx.notify());
cx.notify();
})
.ok();
} }
}) })
.detach() .detach()

View file

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

View file

@ -1,4 +1,4 @@
use crate::{Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled}; use crate::{Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers, Styled};
use refineable::RefinementCascade; use refineable::RefinementCascade;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -41,7 +41,7 @@ impl<S: 'static> Element for Svg<S> {
fn paint( fn paint(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
_: &mut Self::State, _: &mut Self::State,
_: &mut Self::FrameState, _: &mut Self::FrameState,
cx: &mut crate::ViewContext<S>, cx: &mut crate::ViewContext<S>,
@ -51,7 +51,7 @@ impl<S: 'static> Element for Svg<S> {
{ {
let fill_color = self.computed_style().fill.and_then(|fill| fill.color()); let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) { if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
cx.paint_svg(layout.bounds, layout.order, path.clone(), fill_color)?; cx.paint_svg(bounds, path.clone(), fill_color)?;
} }
Ok(()) Ok(())
} }

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
AnyElement, Element, IntoAnyElement, Layout, LayoutId, Line, Pixels, Result, Size, ViewContext, AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Result, Size, ViewContext,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{marker::PhantomData, sync::Arc}; use std::{marker::PhantomData, sync::Arc};
@ -94,7 +94,7 @@ impl<S: 'static> Element for Text<S> {
fn paint<'a>( fn paint<'a>(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
_: &mut Self::State, _: &mut Self::State,
frame_state: &mut Self::FrameState, frame_state: &mut Self::FrameState,
cx: &mut ViewContext<S>, cx: &mut ViewContext<S>,
@ -111,8 +111,7 @@ impl<S: 'static> Element for Text<S> {
} }
// todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder."); // todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
let visible_bounds = layout.bounds; line.paint(bounds, bounds, line_height, cx)?;
line.paint(&layout, visible_bounds, line_height, cx)?;
Ok(()) Ok(())
} }

View file

@ -2,7 +2,7 @@ use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
use refineable::Refineable; use refineable::Refineable;
use std::{ use std::{
cmp, cmp, fmt,
ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}, ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign},
}; };
@ -128,7 +128,7 @@ impl<T: Clone + Debug> Clone for Point<T> {
} }
} }
#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div, Hash)] #[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash)]
#[refineable(debug)] #[refineable(debug)]
#[repr(C)] #[repr(C)]
pub struct Size<T: Clone + Debug> { pub struct Size<T: Clone + Debug> {
@ -199,11 +199,17 @@ 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<T: Eq + Debug + Clone> Eq for Size<T> {}
impl From<Size<Option<Pixels>>> for Size<Option<f32>> { impl<T: Clone + Debug> Debug for Size<T> {
fn from(size: Size<Option<Pixels>>) -> Self { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Size {{ {:?} × {:?} }}", self.width, self.height)
}
}
impl From<Size<Pixels>> for Size<GlobalPixels> {
fn from(size: Size<Pixels>) -> Self {
Size { Size {
width: size.width.map(|p| p.0 as f32), width: GlobalPixels(size.width.0),
height: size.height.map(|p| p.0 as f32), height: GlobalPixels(size.height.0),
} }
} }
} }
@ -257,6 +263,26 @@ impl<T: Clone + Debug + Sub<Output = T>> Bounds<T> {
} }
} }
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
pub fn intersects(&self, other: &Bounds<T>) -> bool {
let my_lower_right = self.lower_right();
let their_lower_right = other.lower_right();
self.origin.x < their_lower_right.x
&& my_lower_right.x > other.origin.x
&& self.origin.y < their_lower_right.y
&& my_lower_right.y > other.origin.y
}
pub fn dilate(&mut self, amount: T) {
self.origin.x = self.origin.x.clone() - amount.clone();
self.origin.y = self.origin.y.clone() - amount.clone();
let double_amount = amount.clone() + amount;
self.size.width = self.size.width.clone() + double_amount.clone();
self.size.height = self.size.height.clone() + double_amount;
}
}
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> { impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
pub fn intersect(&self, other: &Self) -> Self { pub fn intersect(&self, other: &Self) -> Self {
let upper_left = self.origin.max(&other.origin); let upper_left = self.origin.max(&other.origin);
@ -316,6 +342,13 @@ impl<T: Clone + Debug + Add<T, Output = T>> Bounds<T> {
y: self.origin.y.clone() + self.size.height.clone(), y: self.origin.y.clone() + self.size.height.clone(),
} }
} }
pub fn lower_left(&self) -> Point<T> {
Point {
x: self.origin.x.clone(),
y: self.origin.y.clone() + self.size.height.clone(),
}
}
} }
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> { impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
@ -448,6 +481,17 @@ impl Edges<AbsoluteLength> {
} }
} }
impl Edges<Pixels> {
pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
Edges {
top: self.top.scale(factor),
right: self.right.scale(factor),
bottom: self.bottom.scale(factor),
left: self.left.scale(factor),
}
}
}
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(debug)]
#[repr(C)] #[repr(C)]
@ -459,8 +503,8 @@ pub struct Corners<T: Clone + Debug> {
} }
impl Corners<AbsoluteLength> { impl Corners<AbsoluteLength> {
pub fn to_pixels(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> Corners<Pixels> { pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = bounds.size.width.max(bounds.size.height) / 2.; let max = size.width.max(size.height) / 2.;
Corners { Corners {
top_left: self.top_left.to_pixels(rem_size).min(max), top_left: self.top_left.to_pixels(rem_size).min(max),
top_right: self.top_right.to_pixels(rem_size).min(max), top_right: self.top_right.to_pixels(rem_size).min(max),
@ -587,7 +631,7 @@ impl From<f32> for Pixels {
} }
impl Debug for Pixels { impl Debug for Pixels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} px", self.0) write!(f, "{} px", self.0)
} }
} }
@ -622,8 +666,8 @@ impl DevicePixels {
} }
} }
impl std::fmt::Debug for DevicePixels { impl fmt::Debug for DevicePixels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} px (device)", self.0) write!(f, "{} px (device)", self.0)
} }
} }
@ -681,7 +725,7 @@ impl ScaledPixels {
impl Eq for ScaledPixels {} impl Eq for ScaledPixels {}
impl Debug for ScaledPixels { impl Debug for ScaledPixels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} px (scaled)", self.0) write!(f, "{} px (scaled)", self.0)
} }
} }
@ -698,6 +742,34 @@ impl From<DevicePixels> for ScaledPixels {
} }
} }
impl From<ScaledPixels> for f64 {
fn from(scaled_pixels: ScaledPixels) -> Self {
scaled_pixels.0 as f64
}
}
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct GlobalPixels(pub(crate) f32);
impl Debug for GlobalPixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} px (global coordinate space)", self.0)
}
}
impl From<GlobalPixels> for f64 {
fn from(global_pixels: GlobalPixels) -> Self {
global_pixels.0 as f64
}
}
impl From<f64> for GlobalPixels {
fn from(global_pixels: f64) -> Self {
GlobalPixels(global_pixels as f32)
}
}
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)] #[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
pub struct Rems(f32); pub struct Rems(f32);
@ -710,7 +782,7 @@ impl Mul<Pixels> for Rems {
} }
impl Debug for Rems { impl Debug for Rems {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} rem", self.0) write!(f, "{} rem", self.0)
} }
} }
@ -778,7 +850,7 @@ impl DefiniteLength {
} }
impl Debug for DefiniteLength { impl Debug for DefiniteLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
DefiniteLength::Absolute(length) => Debug::fmt(length, f), DefiniteLength::Absolute(length) => Debug::fmt(length, f),
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32), DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
@ -818,7 +890,7 @@ pub enum Length {
} }
impl Debug for Length { impl Debug for Length {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Length::Definite(definite_length) => write!(f, "{:?}", definite_length), Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
Length::Auto => write!(f, "auto"), Length::Auto => write!(f, "auto"),
@ -964,3 +1036,42 @@ impl<T: IsZero + Debug + Clone> IsZero for Corners<T> {
&& self.bottom_left.is_zero() && self.bottom_left.is_zero()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bounds_intersects() {
let bounds1 = Bounds {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
width: 5.0,
height: 5.0,
},
};
let bounds2 = Bounds {
origin: Point { x: 4.0, y: 4.0 },
size: Size {
width: 5.0,
height: 5.0,
},
};
let bounds3 = Bounds {
origin: Point { x: 10.0, y: 10.0 },
size: Size {
width: 5.0,
height: 5.0,
},
};
// Test Case 1: Intersecting bounds
assert_eq!(bounds1.intersects(&bounds2), true);
// Test Case 2: Non-Intersecting bounds
assert_eq!(bounds1.intersects(&bounds3), false);
// Test Case 3: Bounds intersecting with themselves
assert_eq!(bounds1.intersects(&bounds1), true);
}
}

View file

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

View file

@ -5,10 +5,10 @@ mod mac;
#[cfg(any(test, feature = "test"))] #[cfg(any(test, feature = "test"))]
mod test; mod test;
use crate::image_cache::RenderImageParams;
use crate::{ use crate::{
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlyphId, Pixels, AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlobalPixels,
Point, RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
ShapedLine, SharedString, Size,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use async_task::Runnable; use async_task::Runnable;
@ -16,7 +16,6 @@ use futures::channel::oneshot;
use seahash::SeaHasher; use seahash::SeaHasher;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::c_void;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::{ use std::{
any::Any, any::Any,
@ -27,7 +26,6 @@ use std::{
str::FromStr, str::FromStr,
sync::Arc, sync::Arc,
}; };
use uuid::Uuid;
pub use events::*; pub use events::*;
pub use keystroke::*; pub use keystroke::*;
@ -44,6 +42,7 @@ pub(crate) fn current_platform() -> Arc<dyn Platform> {
pub trait Platform: 'static { pub trait Platform: 'static {
fn executor(&self) -> Executor; fn executor(&self) -> Executor;
fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker>;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>; fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>); fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
@ -54,8 +53,8 @@ pub trait Platform: 'static {
fn hide_other_apps(&self); fn hide_other_apps(&self);
fn unhide_other_apps(&self); fn unhide_other_apps(&self);
fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>; fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>; fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn main_window(&self) -> Option<AnyWindowHandle>; fn main_window(&self) -> Option<AnyWindowHandle>;
fn open_window( fn open_window(
&self, &self,
@ -97,23 +96,22 @@ pub trait Platform: 'static {
fn delete_credentials(&self, url: &str) -> Result<()>; fn delete_credentials(&self, url: &str) -> Result<()>;
} }
pub trait PlatformScreen: Debug { pub trait PlatformDisplay: Debug {
fn id(&self) -> Option<ScreenId>; fn id(&self) -> DisplayId;
fn handle(&self) -> PlatformScreenHandle;
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
fn bounds(&self) -> Bounds<Pixels>; fn bounds(&self) -> Bounds<GlobalPixels>;
fn content_bounds(&self) -> Bounds<Pixels>;
} }
pub struct PlatformScreenHandle(pub *mut c_void); #[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct DisplayId(pub(crate) u32);
impl Debug for PlatformScreenHandle { impl Debug for DisplayId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PlatformScreenHandle({:p})", self.0) write!(f, "DisplayId({})", self.0)
} }
} }
unsafe impl Send for PlatformScreenHandle {} unsafe impl Send for DisplayId {}
pub trait PlatformWindow { pub trait PlatformWindow {
fn bounds(&self) -> WindowBounds; fn bounds(&self) -> WindowBounds;
@ -121,7 +119,7 @@ pub trait PlatformWindow {
fn scale_factor(&self) -> f32; fn scale_factor(&self) -> f32;
fn titlebar_height(&self) -> Pixels; fn titlebar_height(&self) -> Pixels;
fn appearance(&self) -> WindowAppearance; fn appearance(&self) -> WindowAppearance;
fn screen(&self) -> Rc<dyn PlatformScreen>; fn display(&self) -> Rc<dyn PlatformDisplay>;
fn mouse_position(&self) -> Point<Pixels>; fn mouse_position(&self) -> Point<Pixels>;
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>); fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
@ -158,6 +156,16 @@ pub trait PlatformDispatcher: Send + Sync {
fn dispatch_on_main_thread(&self, task: Runnable); fn dispatch_on_main_thread(&self, task: Runnable);
} }
pub trait PlatformDisplayLinker: Send + Sync {
fn set_output_callback(
&self,
display_id: DisplayId,
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
);
fn start(&self, display_id: DisplayId);
fn stop(&self, display_id: DisplayId);
}
pub trait PlatformTextSystem: Send + Sync { pub trait PlatformTextSystem: Send + Sync {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>; fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
fn all_font_families(&self) -> Vec<String>; fn all_font_families(&self) -> Vec<String>;
@ -266,9 +274,6 @@ pub trait PlatformInputHandler {
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>; fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
} }
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScreenId(pub(crate) Uuid);
#[derive(Debug)] #[derive(Debug)]
pub struct WindowOptions { pub struct WindowOptions {
pub bounds: WindowBounds, pub bounds: WindowBounds,
@ -278,7 +283,7 @@ pub struct WindowOptions {
pub show: bool, pub show: bool,
pub kind: WindowKind, pub kind: WindowKind,
pub is_movable: bool, pub is_movable: bool,
pub screen: Option<PlatformScreenHandle>, pub display_id: Option<DisplayId>,
} }
impl Default for WindowOptions { impl Default for WindowOptions {
@ -295,7 +300,7 @@ impl Default for WindowOptions {
show: true, show: true,
kind: WindowKind::Normal, kind: WindowKind::Normal,
is_movable: true, is_movable: true,
screen: None, display_id: None,
} }
} }
} }
@ -332,7 +337,7 @@ pub enum WindowBounds {
Fullscreen, Fullscreen,
#[default] #[default]
Maximized, Maximized,
Fixed(Bounds<Pixels>), Fixed(Bounds<GlobalPixels>),
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]

View file

@ -1,17 +1,18 @@
///! Macos screen have a y axis that goings up from the bottom of the screen and ///! Macos screen have a y axis that goings up from the bottom of the screen and
///! an origin at the bottom left of the main display. ///! an origin at the bottom left of the main display.
mod dispatcher; mod dispatcher;
mod display;
mod display_linker;
mod events; mod events;
mod metal_atlas; mod metal_atlas;
mod metal_renderer; mod metal_renderer;
mod open_type; mod open_type;
mod platform; mod platform;
mod screen;
mod text_system; mod text_system;
mod window; mod window;
mod window_appearence; mod window_appearence;
use crate::{px, size, Pixels, Size}; use crate::{px, size, GlobalPixels, Pixels, Size};
use anyhow::anyhow; use anyhow::anyhow;
use cocoa::{ use cocoa::{
base::{id, nil}, base::{id, nil},
@ -31,9 +32,10 @@ use std::{
}; };
pub use dispatcher::*; pub use dispatcher::*;
pub use display::*;
pub use display_linker::*;
pub use metal_atlas::*; pub use metal_atlas::*;
pub use platform::*; pub use platform::*;
pub use screen::*;
pub use text_system::*; pub use text_system::*;
pub use window::*; pub use window::*;
@ -119,23 +121,33 @@ pub trait NSRectExt {
fn intersects(&self, other: Self) -> bool; fn intersects(&self, other: Self) -> bool;
} }
impl NSRectExt for NSRect { impl From<NSRect> for Size<Pixels> {
fn size(&self) -> Size<Pixels> { fn from(rect: NSRect) -> Self {
size(px(self.size.width as f32), px(self.size.height as f32)) let NSSize { width, height } = rect.size;
} size(width.into(), height.into())
fn intersects(&self, other: Self) -> bool {
self.size.width > 0.
&& self.size.height > 0.
&& other.size.width > 0.
&& other.size.height > 0.
&& self.origin.x <= other.origin.x + other.size.width
&& self.origin.x + self.size.width >= other.origin.x
&& self.origin.y <= other.origin.y + other.size.height
&& self.origin.y + self.size.height >= other.origin.y
} }
} }
impl From<NSRect> for Size<GlobalPixels> {
fn from(rect: NSRect) -> Self {
let NSSize { width, height } = rect.size;
size(width.into(), height.into())
}
}
// impl NSRectExt for NSRect {
// fn intersects(&self, other: Self) -> bool {
// self.size.width > 0.
// && self.size.height > 0.
// && other.size.width > 0.
// && other.size.height > 0.
// && self.origin.x <= other.origin.x + other.size.width
// && self.origin.x + self.size.width >= other.origin.x
// && self.origin.y <= other.origin.y + other.size.height
// && self.origin.y + self.size.height >= other.origin.y
// }
// }
// todo! // todo!
#[allow(unused)] #[allow(unused)]
unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> { unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {

View file

@ -0,0 +1,101 @@
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
use core_graphics::{
display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
geometry::{CGPoint, CGRect, CGSize},
};
use std::any::Any;
#[derive(Debug)]
pub struct MacDisplay(pub(crate) CGDirectDisplayID);
unsafe impl Send for MacDisplay {}
impl MacDisplay {
/// Get the screen with the given UUID.
pub fn find_by_id(id: DisplayId) -> Option<Self> {
Self::all().find(|screen| screen.id() == id)
}
/// Get the primary screen - the one with the menu bar, and whose bottom left
/// corner is at the origin of the AppKit coordinate system.
pub fn primary() -> Self {
Self::all().next().unwrap()
}
pub fn all() -> impl Iterator<Item = Self> {
unsafe {
let mut display_count: u32 = 0;
let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count);
if result == 0 {
let mut displays = Vec::with_capacity(display_count as usize);
CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count);
displays.set_len(display_count as usize);
displays.into_iter().map(|display| MacDisplay(display))
} else {
panic!("Failed to get active display list");
}
}
}
}
/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
///
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
/// with the Y axis pointing upwards.
///
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
/// screen, with the Y axis pointing downwards.
pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
Bounds {
origin: point(
GlobalPixels(rect.origin.x as f32),
GlobalPixels(
primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
),
),
size: size(
GlobalPixels(rect.size.width as f32),
GlobalPixels(rect.size.height as f32),
),
}
}
/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
///
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
/// with the Y axis pointing upwards.
///
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
/// screen, with the Y axis pointing downwards.
pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
let primary_screen_height = MacDisplay::primary().bounds().size.height;
CGRect::new(
&CGPoint::new(
bounds.origin.x.into(),
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
),
&CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
)
}
impl PlatformDisplay for MacDisplay {
fn id(&self) -> DisplayId {
DisplayId(self.0)
}
fn as_any(&self) -> &dyn Any {
self
}
fn bounds(&self) -> Bounds<GlobalPixels> {
unsafe {
let native_bounds = CGDisplayBounds(self.0);
display_bounds_from_native(native_bounds)
}
}
}

View file

@ -0,0 +1,276 @@
use std::{
ffi::c_void,
mem,
sync::{Arc, Weak},
};
use crate::{DisplayId, PlatformDisplayLinker};
use collections::HashMap;
use parking_lot::Mutex;
pub use sys::CVTimeStamp as VideoTimestamp;
pub struct MacDisplayLinker {
links: Mutex<HashMap<DisplayId, MacDisplayLink>>,
}
struct MacDisplayLink {
system_link: Mutex<sys::DisplayLink>,
_output_callback: Arc<OutputCallback>,
}
unsafe impl Send for MacDisplayLink {}
impl MacDisplayLinker {
pub fn new() -> Self {
MacDisplayLinker {
links: Default::default(),
}
}
}
type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>>;
impl PlatformDisplayLinker for MacDisplayLinker {
fn set_output_callback(
&self,
display_id: DisplayId,
output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
) {
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
let callback = Arc::new(Mutex::new(output_callback));
let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
self.links.lock().insert(
display_id,
MacDisplayLink {
_output_callback: callback,
system_link: Mutex::new(system_link),
},
);
} else {
log::warn!("DisplayLink could not be obtained for {:?}", display_id);
return;
}
}
fn start(&self, display_id: DisplayId) {
if let Some(link) = self.links.lock().get_mut(&display_id) {
unsafe {
link.system_link.lock().start();
}
} else {
log::warn!("No DisplayLink callback registered for {:?}", display_id)
}
}
fn stop(&self, display_id: DisplayId) {
if let Some(link) = self.links.lock().get_mut(&display_id) {
unsafe {
link.system_link.lock().stop();
}
} else {
log::warn!("No DisplayLink callback registered for {:?}", display_id)
}
}
}
unsafe extern "C" fn trampoline(
_display_link_out: *mut sys::CVDisplayLink,
current_time: *const sys::CVTimeStamp,
output_time: *const sys::CVTimeStamp,
_flags_in: i64,
_flags_out: *mut i64,
user_data: *mut c_void,
) -> i32 {
if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
let output_callback: Weak<OutputCallback> =
Weak::from_raw(user_data as *mut OutputCallback);
if let Some(output_callback) = output_callback.upgrade() {
(output_callback.lock())(current_time, output_time)
}
mem::forget(output_callback);
}
0
}
mod sys {
//! Derived from display-link crate under the fololwing license:
//! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
#![allow(dead_code, non_upper_case_globals)]
use foreign_types::{foreign_type, ForeignType};
use std::{
ffi::c_void,
fmt::{Debug, Formatter, Result},
};
#[derive(Debug)]
pub enum CVDisplayLink {}
foreign_type! {
type CType = CVDisplayLink;
fn drop = CVDisplayLinkRelease;
fn clone = CVDisplayLinkRetain;
pub struct DisplayLink;
pub struct DisplayLinkRef;
}
impl Debug for DisplayLink {
fn fmt(&self, formatter: &mut Formatter) -> Result {
formatter
.debug_tuple("DisplayLink")
.field(&self.as_ptr())
.finish()
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CVTimeStamp {
pub version: u32,
pub video_time_scale: i32,
pub video_time: i64,
pub host_time: u64,
pub rate_scalar: f64,
pub video_refresh_period: i64,
pub smpte_time: CVSMPTETime,
pub flags: u64,
pub reserved: u64,
}
pub type CVTimeStampFlags = u64;
pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
kCVTimeStampTopField | kCVTimeStampBottomField;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CVSMPTETime {
pub subframes: i16,
pub subframe_divisor: i16,
pub counter: u32,
pub time_type: u32,
pub flags: u32,
pub hours: i16,
pub minutes: i16,
pub seconds: i16,
pub frames: i16,
}
pub type CVSMPTETimeType = u32;
pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
pub type CVSMPTETimeFlags = u32;
pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
display_link_out: *mut CVDisplayLink,
// A pointer to the current timestamp. This represents the timestamp when the callback is called.
current_time: *const CVTimeStamp,
// A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
output_time: *const CVTimeStamp,
// Unused
flags_in: i64,
// Unused
flags_out: *mut i64,
// A pointer to app-defined data.
display_link_context: *mut c_void,
) -> i32;
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "CoreVideo", kind = "framework")]
#[allow(improper_ctypes)]
extern "C" {
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
display_link_out: *mut *mut CVDisplayLink,
) -> i32;
pub fn CVDisplayLinkCreateWithCGDisplay(
display_id: u32,
display_link_out: *mut *mut CVDisplayLink,
) -> i32;
pub fn CVDisplayLinkSetOutputCallback(
display_link: &mut DisplayLinkRef,
callback: CVDisplayLinkOutputCallback,
user_info: *mut c_void,
) -> i32;
pub fn CVDisplayLinkSetCurrentCGDisplay(
display_link: &mut DisplayLinkRef,
display_id: u32,
) -> i32;
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
}
impl DisplayLink {
/// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
pub unsafe fn new() -> Option<Self> {
let mut display_link: *mut CVDisplayLink = 0 as _;
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
if code == 0 {
Some(DisplayLink::from_ptr(display_link))
} else {
None
}
}
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
pub unsafe fn on_display(display_id: u32) -> Option<Self> {
let mut display_link: *mut CVDisplayLink = 0 as _;
let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
if code == 0 {
Some(DisplayLink::from_ptr(display_link))
} else {
None
}
}
}
impl DisplayLinkRef {
/// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
pub unsafe fn set_output_callback(
&mut self,
callback: CVDisplayLinkOutputCallback,
user_info: *mut c_void,
) {
assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
}
/// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
pub unsafe fn set_current_display(&mut self, display_id: u32) {
assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
}
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
pub unsafe fn start(&mut self) {
assert_eq!(CVDisplayLinkStart(self), 0);
}
/// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
pub unsafe fn stop(&mut self) {
assert_eq!(CVDisplayLinkStop(self), 0);
}
}
}

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite, point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
Quad, Scene, Size, PrimitiveBatch, Quad, Scene, Shadow, Size, Underline,
}; };
use cocoa::{ use cocoa::{
base::{NO, YES}, base::{NO, YES},
@ -17,7 +17,9 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
pub struct MetalRenderer { pub struct MetalRenderer {
layer: metal::MetalLayer, layer: metal::MetalLayer,
command_queue: CommandQueue, command_queue: CommandQueue,
shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer, unit_vertices: metal::Buffer,
@ -82,6 +84,14 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged, MTLResourceOptions::StorageModeManaged,
); );
let shadows_pipeline_state = build_pipeline_state(
&device,
&library,
"shadows",
"shadow_vertex",
"shadow_fragment",
PIXEL_FORMAT,
);
let quads_pipeline_state = build_pipeline_state( let quads_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
@ -90,6 +100,14 @@ impl MetalRenderer {
"quad_fragment", "quad_fragment",
PIXEL_FORMAT, PIXEL_FORMAT,
); );
let underlines_pipeline_state = build_pipeline_state(
&device,
&library,
"underlines",
"underline_vertex",
"underline_fragment",
PIXEL_FORMAT,
);
let monochrome_sprites_pipeline_state = build_pipeline_state( let monochrome_sprites_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
@ -113,7 +131,9 @@ impl MetalRenderer {
Self { Self {
layer, layer,
command_queue, command_queue,
shadows_pipeline_state,
quads_pipeline_state, quads_pipeline_state,
underlines_pipeline_state,
monochrome_sprites_pipeline_state, monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state, polychrome_sprites_pipeline_state,
unit_vertices, unit_vertices,
@ -131,8 +151,6 @@ impl MetalRenderer {
} }
pub fn draw(&mut self, scene: &mut Scene) { pub fn draw(&mut self, scene: &mut Scene) {
dbg!("draw scene");
let layer = self.layer.clone(); let layer = self.layer.clone();
let viewport_size = layer.drawable_size(); let viewport_size = layer.drawable_size();
let viewport_size: Size<DevicePixels> = size( let viewport_size: Size<DevicePixels> = size(
@ -174,18 +192,28 @@ impl MetalRenderer {
}); });
let mut instance_offset = 0; let mut instance_offset = 0;
for layer in scene.layers() { for batch in scene.batches() {
for batch in layer.batches() {
match batch { match batch {
crate::PrimitiveBatch::Quads(quads) => { PrimitiveBatch::Shadows(shadows) => {
self.draw_quads( self.draw_shadows(
quads, shadows,
&mut instance_offset, &mut instance_offset,
viewport_size, viewport_size,
command_encoder, command_encoder,
); );
} }
crate::PrimitiveBatch::MonochromeSprites { PrimitiveBatch::Quads(quads) => {
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
}
PrimitiveBatch::Underlines(underlines) => {
self.draw_underlines(
underlines,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
PrimitiveBatch::MonochromeSprites {
texture_id, texture_id,
sprites, sprites,
} => { } => {
@ -197,7 +225,7 @@ impl MetalRenderer {
command_encoder, command_encoder,
); );
} }
crate::PrimitiveBatch::PolychromeSprites { PrimitiveBatch::PolychromeSprites {
texture_id, texture_id,
sprites, sprites,
} => { } => {
@ -211,7 +239,6 @@ impl MetalRenderer {
} }
} }
} }
}
command_encoder.end_encoding(); command_encoder.end_encoding();
@ -225,6 +252,66 @@ impl MetalRenderer {
drawable.present(); drawable.present();
} }
fn draw_shadows(
&mut self,
shadows: &[Shadow],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if shadows.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
command_encoder.set_vertex_buffer(
ShadowInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
ShadowInputIndex::Shadows as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
ShadowInputIndex::Shadows as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
ShadowInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
shadows.as_ptr() as *const u8,
buffer_contents,
shadow_bytes_len,
);
}
let next_offset = *offset + shadow_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
shadows.len() as u64,
);
*offset = next_offset;
}
fn draw_quads( fn draw_quads(
&mut self, &mut self,
quads: &[Quad], quads: &[Quad],
@ -281,6 +368,66 @@ impl MetalRenderer {
*offset = next_offset; *offset = next_offset;
} }
fn draw_underlines(
&mut self,
underlines: &[Underline],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if underlines.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
UnderlineInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Underline>() * underlines.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
underlines.as_ptr() as *const u8,
buffer_contents,
quad_bytes_len,
);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
underlines.len() as u64,
);
*offset = next_offset;
}
fn draw_monochrome_sprites( fn draw_monochrome_sprites(
&mut self, &mut self,
texture_id: AtlasTextureId, texture_id: AtlasTextureId,
@ -464,6 +611,13 @@ fn align_offset(offset: &mut usize) {
*offset = ((*offset + 255) / 256) * 256; *offset = ((*offset + 255) / 256) * 256;
} }
#[repr(C)]
enum ShadowInputIndex {
Vertices = 0,
Shadows = 1,
ViewportSize = 2,
}
#[repr(C)] #[repr(C)]
enum QuadInputIndex { enum QuadInputIndex {
Vertices = 0, Vertices = 0,
@ -471,6 +625,13 @@ enum QuadInputIndex {
ViewportSize = 2, ViewportSize = 2,
} }
#[repr(C)]
enum UnderlineInputIndex {
Vertices = 0,
Underlines = 1,
ViewportSize = 2,
}
#[repr(C)] #[repr(C)]
enum SpriteInputIndex { enum SpriteInputIndex {
Vertices = 0, Vertices = 0,

View file

@ -1,8 +1,9 @@
use super::BoolExt; use super::BoolExt;
use crate::{ use crate::{
AnyWindowHandle, ClipboardItem, CursorStyle, Event, Executor, MacDispatcher, MacScreen, AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher,
MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
PlatformWindow, Result, ScreenId, SemanticVersion, WindowOptions, PlatformDisplay, PlatformDisplayLinker, PlatformTextSystem, PlatformWindow, Result,
SemanticVersion, WindowOptions,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use block::ConcreteBlock; use block::ConcreteBlock;
@ -347,6 +348,10 @@ impl Platform for MacPlatform {
self.0.lock().executor.clone() self.0.lock().executor.clone()
} }
fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker> {
Arc::new(MacDisplayLinker::new())
}
fn text_system(&self) -> Arc<dyn PlatformTextSystem> { fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.0.lock().text_system.clone() self.0.lock().text_system.clone()
} }
@ -455,21 +460,21 @@ impl Platform for MacPlatform {
} }
} }
fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> { fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
MacScreen::all() MacDisplay::all()
.into_iter() .into_iter()
.map(|screen| Rc::new(screen) as Rc<_>) .map(|screen| Rc::new(screen) as Rc<_>)
.collect() .collect()
} }
fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>> {
MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
}
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> { // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
// Box::new(StatusItem::add(self.fonts())) // Box::new(StatusItem::add(self.fonts()))
// } // }
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
}
fn main_window(&self) -> Option<AnyWindowHandle> { fn main_window(&self) -> Option<AnyWindowHandle> {
MacWindow::main_window() MacWindow::main_window()
} }
@ -736,6 +741,32 @@ impl Platform for MacPlatform {
} }
} }
// fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
// self.0.lock().menu_command = Some(callback);
// }
// fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
// self.0.lock().will_open_menu = Some(callback);
// }
// fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
// self.0.lock().validate_menu_command = Some(callback);
// }
// fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
// unsafe {
// let app: id = msg_send![APP_CLASS, sharedApplication];
// let mut state = self.0.lock();
// let actions = &mut state.menu_actions;
// app.setMainMenu_(self.create_menu_bar(
// menus,
// app.delegate(),
// actions,
// keystroke_matcher,
// ));
// }
// }
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
let state = self.0.lock(); let state = self.0.lock();
unsafe { unsafe {
@ -773,32 +804,6 @@ impl Platform for MacPlatform {
} }
} }
// fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
// self.0.lock().menu_command = Some(callback);
// }
// fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
// self.0.lock().will_open_menu = Some(callback);
// }
// fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
// self.0.lock().validate_menu_command = Some(callback);
// }
// fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
// unsafe {
// let app: id = msg_send![APP_CLASS, sharedApplication];
// let mut state = self.0.lock();
// let actions = &mut state.menu_actions;
// app.setMainMenu_(self.create_menu_bar(
// menus,
// app.delegate(),
// actions,
// keystroke_matcher,
// ));
// }
// }
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
let url = CFString::from(url); let url = CFString::from(url);
let username = CFString::from(username); let username = CFString::from(username);

View file

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

View file

@ -11,6 +11,10 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size); constant Size_DevicePixels *atlas_size);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds, float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii); Corners_ScaledPixels corner_radii);
float gaussian(float x, float sigma);
float2 erf(float2 x);
float blur_along_x(float x, float y, float sigma, float corner,
float2 half_size);
struct QuadVertexOutput { struct QuadVertexOutput {
float4 position [[position]]; float4 position [[position]];
@ -29,8 +33,8 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
[[buffer(QuadInputIndex_ViewportSize)]]) { [[buffer(QuadInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
Quad quad = quads[quad_id]; Quad quad = quads[quad_id];
float4 device_position = to_device_position(unit_vertex, quad.bounds, float4 device_position = to_device_position(
quad.clip_bounds, viewport_size); unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size);
float4 background_color = hsla_to_rgba(quad.background); float4 background_color = hsla_to_rgba(quad.background);
float4 border_color = hsla_to_rgba(quad.border_color); float4 border_color = hsla_to_rgba(quad.border_color);
return QuadVertexOutput{device_position, background_color, border_color, return QuadVertexOutput{device_position, background_color, border_color,
@ -109,6 +113,133 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
return color * float4(1., 1., 1., saturate(0.5 - distance)); return color * float4(1., 1., 1., saturate(0.5 - distance));
} }
struct ShadowVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint shadow_id [[flat]];
};
vertex ShadowVertexOutput shadow_vertex(
uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
constant Size_DevicePixels *viewport_size
[[buffer(ShadowInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Shadow shadow = shadows[shadow_id];
float margin = 3. * shadow.blur_radius;
// Set the bounds of the shadow and adjust its size based on the shadow's
// spread radius to achieve the spreading effect
Bounds_ScaledPixels bounds = shadow.bounds;
bounds.origin.x -= margin;
bounds.origin.y -= margin;
bounds.size.width += 2. * margin;
bounds.size.height += 2. * margin;
float4 device_position = to_device_position(
unit_vertex, bounds, shadow.content_mask.bounds, viewport_size);
float4 color = hsla_to_rgba(shadow.color);
return ShadowVertexOutput{
device_position,
color,
shadow_id,
};
}
fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
constant Shadow *shadows
[[buffer(ShadowInputIndex_Shadows)]]) {
Shadow shadow = shadows[input.shadow_id];
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
float2 half_size = size / 2.;
float2 center = origin + half_size;
float2 point = input.position.xy - center;
float corner_radius;
if (point.x < 0.) {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_left;
} else {
corner_radius = shadow.corner_radii.bottom_left;
}
} else {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_right;
} else {
corner_radius = shadow.corner_radii.bottom_right;
}
}
// The signal is only non-zero in a limited range, so don't waste samples
float low = point.y - half_size.y;
float high = point.y + half_size.y;
float start = clamp(-3. * shadow.blur_radius, low, high);
float end = clamp(3. * shadow.blur_radius, low, high);
// Accumulate samples (we can get away with surprisingly few samples)
float step = (end - start) / 4.;
float y = start + step * 0.5;
float alpha = 0.;
for (int i = 0; i < 4; i++) {
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
corner_radius, half_size) *
gaussian(y, shadow.blur_radius) * step;
y += step;
}
return input.color * float4(1., 1., 1., alpha);
}
struct UnderlineVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint underline_id [[flat]];
};
vertex UnderlineVertexOutput underline_vertex(
uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
constant Size_DevicePixels *viewport_size
[[buffer(ShadowInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Underline underline = underlines[underline_id];
float4 device_position =
to_device_position(unit_vertex, underline.bounds,
underline.content_mask.bounds, viewport_size);
float4 color = hsla_to_rgba(underline.color);
return UnderlineVertexOutput{device_position, color, underline_id};
}
fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
constant Underline *underlines
[[buffer(UnderlineInputIndex_Underlines)]]) {
Underline underline = underlines[input.underline_id];
if (underline.wavy) {
float half_thickness = underline.thickness * 0.5;
float2 origin =
float2(underline.bounds.origin.x, underline.bounds.origin.y);
float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
float2(0., 0.5);
float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
float amplitude = 1. / (2. * underline.thickness);
float sine = sin(st.x * frequency) * amplitude;
float dSine = cos(st.x * frequency) * amplitude * frequency;
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
float distance_in_pixels = distance * underline.bounds.size.height;
float distance_from_top_border = distance_in_pixels - half_thickness;
float distance_from_bottom_border = distance_in_pixels + half_thickness;
float alpha = saturate(
0.5 - max(-distance_from_bottom_border, distance_from_top_border));
return input.color * float4(1., 1., 1., alpha);
} else {
return input.color;
}
}
struct MonochromeSpriteVertexOutput { struct MonochromeSpriteVertexOutput {
float4 position [[position]]; float4 position [[position]];
float2 tile_position; float2 tile_position;
@ -127,9 +258,10 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
MonochromeSprite sprite = sprites[sprite_id]; MonochromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask. // Don't apply content mask at the vertex level because we don't have time
float4 device_position = to_device_position( // to make sampling from the texture match the mask.
unit_vertex, sprite.bounds, sprite.bounds, viewport_size); float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color); float4 color = hsla_to_rgba(sprite.color);
return MonochromeSpriteVertexOutput{device_position, tile_position, color, return MonochromeSpriteVertexOutput{device_position, tile_position, color,
@ -145,11 +277,8 @@ fragment float4 monochrome_sprite_fragment(
min_filter::linear); min_filter::linear);
float4 sample = float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position); atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float clip_distance = quad_sdf( float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
input.position.xy, Corners_ScaledPixels{0., 0., 0., 0.});
sprite.content_mask.bounds,
Corners_ScaledPixels { 0., 0., 0., 0. }
);
float4 color = input.color; float4 color = input.color;
color.a *= sample.a * saturate(0.5 - clip_distance); color.a *= sample.a * saturate(0.5 - clip_distance);
return color; return color;
@ -172,9 +301,10 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
PolychromeSprite sprite = sprites[sprite_id]; PolychromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask. // Don't apply content mask at the vertex level because we don't have time
float4 device_position = to_device_position( // to make sampling from the texture match the mask.
unit_vertex, sprite.bounds, sprite.bounds, viewport_size); float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
return PolychromeSpriteVertexOutput{device_position, tile_position, return PolychromeSpriteVertexOutput{device_position, tile_position,
sprite_id}; sprite_id};
@ -189,8 +319,10 @@ fragment float4 polychrome_sprite_fragment(
min_filter::linear); min_filter::linear);
float4 sample = float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position); atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float quad_distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); float quad_distance =
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, Corners_ScaledPixels { 0., 0., 0., 0. }); quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
Corners_ScaledPixels{0., 0., 0., 0.});
float distance = max(quad_distance, clip_distance); float distance = max(quad_distance, clip_distance);
float4 color = sample; float4 color = sample;
@ -307,3 +439,27 @@ float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
return distance; return distance;
} }
// A standard gaussian function, used for weighting samples
float gaussian(float x, float sigma) {
return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
}
// This approximates the error function, needed for the gaussian integral
float2 erf(float2 x) {
float2 s = sign(x);
float2 a = abs(x);
x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
x *= x;
return s - s / (x * x);
}
float blur_along_x(float x, float y, float sigma, float corner,
float2 half_size) {
float delta = min(half_size.y - corner - abs(y), 0.);
float curved =
half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
float2 integral =
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
return integral.y - integral.x;
}

View file

@ -1,10 +1,10 @@
use super::{ns_string, MetalRenderer, NSRange}; use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
use crate::{ use crate::{
point, px, size, AnyWindowHandle, Bounds, Event, Executor, KeyDownEvent, Keystroke, MacScreen, display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor,
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
NSRectExt, Pixels, PlatformAtlas, PlatformInputHandler, PlatformScreen, PlatformWindow, Point, MouseDownEvent, MouseMovedEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
WindowPromptLevel, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
}; };
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
@ -14,7 +14,9 @@ use cocoa::{
NSWindowStyleMask, NSWindowTitleVisibility, NSWindowStyleMask, NSWindowTitleVisibility,
}, },
base::{id, nil}, base::{id, nil},
foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger}, foundation::{
NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
},
}; };
use core_graphics::display::CGRect; use core_graphics::display::CGRect;
use ctor::ctor; use ctor::ctor;
@ -365,7 +367,7 @@ impl MacWindowState {
} }
let frame = self.frame(); let frame = self.frame();
let screen_size = self.native_window.screen().visibleFrame().size(); let screen_size = self.native_window.screen().visibleFrame().into();
if frame.size == screen_size { if frame.size == screen_size {
WindowBounds::Maximized WindowBounds::Maximized
} else { } else {
@ -374,10 +376,10 @@ impl MacWindowState {
} }
} }
fn frame(&self) -> Bounds<Pixels> { fn frame(&self) -> Bounds<GlobalPixels> {
unsafe { unsafe {
let frame = NSWindow::frame(self.native_window); let frame = NSWindow::frame(self.native_window);
MacScreen::screen_bounds_from_native(frame) display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
} }
} }
@ -441,15 +443,33 @@ impl MacWindow {
msg_send![PANEL_CLASS, alloc] msg_send![PANEL_CLASS, alloc]
} }
}; };
let display = options
.display_id
.and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
.unwrap_or_else(|| MacDisplay::primary());
let mut target_screen = nil;
let screens = NSScreen::screens(nil);
let count: u64 = cocoa::foundation::NSArray::count(screens);
for i in 0..count {
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
let device_description = NSScreen::deviceDescription(screen);
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
let screen_number = device_description.objectForKey_(screen_number_key);
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
if screen_number as u32 == display.id().0 {
target_screen = screen;
break;
}
}
let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_( let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)), NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
style_mask, style_mask,
NSBackingStoreBuffered, NSBackingStoreBuffered,
NO, NO,
options target_screen,
.screen
.map(|screen| MacScreen::from_handle(screen).native_screen)
.unwrap_or(nil),
); );
assert!(!native_window.is_null()); assert!(!native_window.is_null());
@ -462,13 +482,13 @@ impl MacWindow {
native_window.setFrame_display_(screen.visibleFrame(), YES); native_window.setFrame_display_(screen.visibleFrame(), YES);
} }
WindowBounds::Fixed(bounds) => { WindowBounds::Fixed(bounds) => {
let bounds = MacScreen::screen_bounds_to_native(bounds); let display_bounds = display.bounds();
let screen_bounds = screen.visibleFrame(); let frame = if bounds.intersects(&display_bounds) {
if bounds.intersects(screen_bounds) { display_bounds_to_native(bounds)
native_window.setFrame_display_(bounds, YES);
} else { } else {
native_window.setFrame_display_(screen_bounds, YES); display_bounds_to_native(display_bounds)
} };
native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
} }
} }
@ -649,11 +669,18 @@ impl PlatformWindow for MacWindow {
} }
} }
fn screen(&self) -> Rc<dyn PlatformScreen> { fn display(&self) -> Rc<dyn PlatformDisplay> {
unsafe { unsafe {
Rc::new(MacScreen { let screen = self.0.lock().native_window.screen();
native_screen: self.0.as_ref().lock().native_window.screen(), let device_description: id = msg_send![screen, deviceDescription];
}) let screen_number: id = NSDictionary::valueForKey_(
device_description,
NSString::alloc(nil).init_str("NSScreenNumber"),
);
let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
Rc::new(MacDisplay(screen_number))
} }
} }

View file

@ -1,5 +1,5 @@
use super::Platform; use super::Platform;
use crate::{Executor, ScreenId}; use crate::{DisplayId, Executor};
pub struct TestPlatform; pub struct TestPlatform;
@ -15,6 +15,10 @@ impl Platform for TestPlatform {
unimplemented!() unimplemented!()
} }
fn display_linker(&self) -> std::sync::Arc<dyn crate::PlatformDisplayLinker> {
unimplemented!()
}
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> { fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
unimplemented!() unimplemented!()
} }
@ -47,11 +51,11 @@ impl Platform for TestPlatform {
unimplemented!() unimplemented!()
} }
fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> { fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
unimplemented!() unimplemented!()
} }
fn screen_by_id(&self, _id: ScreenId) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> { fn display(&self, _id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
unimplemented!() unimplemented!()
} }

View file

@ -1,18 +1,27 @@
use std::{iter::Peekable, mem, slice}; use crate::{
AtlasTextureId, AtlasTile, Bounds, Corners, Edges, Hsla, Point, ScaledContentMask, ScaledPixels,
use super::{Bounds, Hsla, Point}; };
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
use collections::BTreeMap; use collections::BTreeMap;
use etagere::euclid::{Point3D, Vector3D};
use plane_split::{BspSplitter, Polygon as BspPolygon};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{iter::Peekable, mem, slice};
// Exported to metal // Exported to metal
pub type PointF = Point<f32>; pub type PointF = Point<f32>;
pub type LayerId = SmallVec<[u32; 16]>; pub type StackingOrder = SmallVec<[u32; 16]>;
pub type LayerId = u32;
pub type DrawOrder = u32;
#[derive(Debug)] #[derive(Debug)]
pub struct Scene { pub struct Scene {
pub(crate) scale_factor: f32, pub(crate) scale_factor: f32,
pub(crate) layers: BTreeMap<LayerId, SceneLayer>, pub(crate) layers: BTreeMap<StackingOrder, LayerId>,
pub shadows: Vec<Shadow>,
pub quads: Vec<Quad>,
pub underlines: Vec<Underline>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
} }
impl Scene { impl Scene {
@ -20,6 +29,11 @@ impl Scene {
Scene { Scene {
scale_factor, scale_factor,
layers: BTreeMap::new(), layers: BTreeMap::new(),
shadows: Vec::new(),
quads: Vec::new(),
underlines: Vec::new(),
monochrome_sprites: Vec::new(),
polychrome_sprites: Vec::new(),
} }
} }
@ -27,47 +41,125 @@ impl Scene {
Scene { Scene {
scale_factor: self.scale_factor, scale_factor: self.scale_factor,
layers: mem::take(&mut self.layers), layers: mem::take(&mut self.layers),
shadows: mem::take(&mut self.shadows),
quads: mem::take(&mut self.quads),
underlines: mem::take(&mut self.underlines),
monochrome_sprites: mem::take(&mut self.monochrome_sprites),
polychrome_sprites: mem::take(&mut self.polychrome_sprites),
} }
} }
pub fn insert(&mut self, stacking_order: LayerId, primitive: impl Into<Primitive>) { pub fn insert(&mut self, layer_id: StackingOrder, primitive: impl Into<Primitive>) {
let layer = self.layers.entry(stacking_order).or_default(); let next_id = self.layers.len() as LayerId;
let layer_id = *self.layers.entry(layer_id).or_insert(next_id);
let primitive = primitive.into(); let primitive = primitive.into();
match primitive { match primitive {
Primitive::Quad(quad) => { Primitive::Shadow(mut shadow) => {
layer.quads.push(quad); shadow.order = layer_id;
self.shadows.push(shadow);
} }
Primitive::MonochromeSprite(sprite) => { Primitive::Quad(mut quad) => {
layer.monochrome_sprites.push(sprite); quad.order = layer_id;
self.quads.push(quad);
} }
Primitive::PolychromeSprite(sprite) => { Primitive::Underline(mut underline) => {
layer.polychrome_sprites.push(sprite); underline.order = layer_id;
self.underlines.push(underline);
}
Primitive::MonochromeSprite(mut sprite) => {
sprite.order = layer_id;
self.monochrome_sprites.push(sprite);
}
Primitive::PolychromeSprite(mut sprite) => {
sprite.order = layer_id;
self.polychrome_sprites.push(sprite);
} }
} }
} }
pub(crate) fn layers(&mut self) -> impl Iterator<Item = &mut SceneLayer> { pub(crate) fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
self.layers.values_mut() // Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
let mut layer_z_values = vec![0.; self.layers.len()];
for (ix, layer_id) in self.layers.values().enumerate() {
layer_z_values[*layer_id as usize] = ix as f32 / self.layers.len() as f32;
} }
}
#[derive(Debug, Default)] // Add all primitives to the BSP splitter to determine draw order
pub(crate) struct SceneLayer { // todo!("reuse the same splitter")
pub quads: Vec<Quad>, let mut splitter = BspSplitter::new();
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
}
impl SceneLayer { for (ix, shadow) in self.shadows.iter().enumerate() {
pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> { let z = layer_z_values[shadow.order as LayerId as usize];
splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
}
for (ix, quad) in self.quads.iter().enumerate() {
let z = layer_z_values[quad.order as LayerId as usize];
splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
}
for (ix, underline) in self.underlines.iter().enumerate() {
let z = layer_z_values[underline.order as LayerId as usize];
splitter.add(
underline
.bounds
.to_bsp_polygon(z, (PrimitiveKind::Underline, ix)),
);
}
for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() {
let z = layer_z_values[monochrome_sprite.order as LayerId as usize];
splitter.add(
monochrome_sprite
.bounds
.to_bsp_polygon(z, (PrimitiveKind::MonochromeSprite, ix)),
);
}
for (ix, polychrome_sprite) in self.polychrome_sprites.iter().enumerate() {
let z = layer_z_values[polychrome_sprite.order as LayerId as usize];
splitter.add(
polychrome_sprite
.bounds
.to_bsp_polygon(z, (PrimitiveKind::PolychromeSprite, ix)),
);
}
// Sort all polygons, then reassign the order field of each primitive to `draw_order`
// We need primitives to be repr(C), hence the weird reuse of the order field for two different types.
for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() {
match polygon.anchor {
(PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Underline, ix) => {
self.underlines[ix].order = draw_order as DrawOrder
}
(PrimitiveKind::MonochromeSprite, ix) => {
self.monochrome_sprites[ix].order = draw_order as DrawOrder
}
(PrimitiveKind::PolychromeSprite, ix) => {
self.polychrome_sprites[ix].order = draw_order as DrawOrder
}
}
}
// Sort the primitives
self.shadows.sort_unstable();
self.quads.sort_unstable(); self.quads.sort_unstable();
self.underlines.sort_unstable();
self.monochrome_sprites.sort_unstable(); self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable(); self.polychrome_sprites.sort_unstable();
BatchIterator { BatchIterator {
shadows: &self.shadows,
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
quads: &self.quads, quads: &self.quads,
quads_start: 0, quads_start: 0,
quads_iter: self.quads.iter().peekable(), quads_iter: self.quads.iter().peekable(),
underlines: &self.underlines,
underlines_start: 0,
underlines_iter: self.underlines.iter().peekable(),
monochrome_sprites: &self.monochrome_sprites, monochrome_sprites: &self.monochrome_sprites,
monochrome_sprites_start: 0, monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
@ -82,6 +174,12 @@ struct BatchIterator<'a> {
quads: &'a [Quad], quads: &'a [Quad],
quads_start: usize, quads_start: usize,
quads_iter: Peekable<slice::Iter<'a, Quad>>, quads_iter: Peekable<slice::Iter<'a, Quad>>,
shadows: &'a [Shadow],
shadows_start: usize,
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
underlines: &'a [Underline],
underlines_start: usize,
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
monochrome_sprites: &'a [MonochromeSprite], monochrome_sprites: &'a [MonochromeSprite],
monochrome_sprites_start: usize, monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>, monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
@ -94,50 +192,92 @@ impl<'a> Iterator for BatchIterator<'a> {
type Item = PrimitiveBatch<'a>; type Item = PrimitiveBatch<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut kinds_and_orders = [ let mut orders_and_kinds = [
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
( (
PrimitiveKind::MonochromeSprite, self.shadows_iter.peek().map(|s| s.order),
self.monochrome_sprites_iter.peek().map(|s| s.order), PrimitiveKind::Shadow,
),
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
(
self.underlines_iter.peek().map(|u| u.order),
PrimitiveKind::Underline,
),
(
self.monochrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::MonochromeSprite,
), ),
( (
PrimitiveKind::PolychromeSprite,
self.polychrome_sprites_iter.peek().map(|s| s.order), self.polychrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::PolychromeSprite,
), ),
]; ];
kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX)); orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
let first = kinds_and_orders[0]; let first = orders_and_kinds[0];
let second = kinds_and_orders[1]; let second = orders_and_kinds[1];
let (batch_kind, max_order) = if first.1.is_some() { let (batch_kind, max_order) = if first.0.is_some() {
(first.0, second.1.unwrap_or(u32::MAX)) (first.1, second.0.unwrap_or(u32::MAX))
} else { } else {
return None; return None;
}; };
match batch_kind { match batch_kind {
PrimitiveKind::Shadow => {
let shadows_start = self.shadows_start;
let mut shadows_end = shadows_start;
while self
.shadows_iter
.next_if(|shadow| shadow.order <= max_order)
.is_some()
{
shadows_end += 1;
}
self.shadows_start = shadows_end;
Some(PrimitiveBatch::Shadows(
&self.shadows[shadows_start..shadows_end],
))
}
PrimitiveKind::Quad => { PrimitiveKind::Quad => {
let quads_start = self.quads_start; let quads_start = self.quads_start;
let quads_end = quads_start let mut quads_end = quads_start;
+ self while self
.quads_iter .quads_iter
.by_ref() .next_if(|quad| quad.order <= max_order)
.take_while(|quad| quad.order <= max_order) .is_some()
.count(); {
quads_end += 1;
}
self.quads_start = quads_end; self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end])) Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
} }
PrimitiveKind::Underline => {
let underlines_start = self.underlines_start;
let mut underlines_end = underlines_start;
while self
.underlines_iter
.next_if(|underline| underline.order <= max_order)
.is_some()
{
underlines_end += 1;
}
self.underlines_start = underlines_end;
Some(PrimitiveBatch::Underlines(
&self.underlines[underlines_start..underlines_end],
))
}
PrimitiveKind::MonochromeSprite => { PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id; let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start; let sprites_start = self.monochrome_sprites_start;
let sprites_end = sprites_start let mut sprites_end = sprites_start;
+ self while self
.monochrome_sprites_iter .monochrome_sprites_iter
.by_ref() .next_if(|sprite| {
.take_while(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id sprite.order <= max_order && sprite.tile.texture_id == texture_id
}) })
.count(); .is_some()
{
sprites_end += 1;
}
self.monochrome_sprites_start = sprites_end; self.monochrome_sprites_start = sprites_end;
Some(PrimitiveBatch::MonochromeSprites { Some(PrimitiveBatch::MonochromeSprites {
texture_id, texture_id,
@ -147,14 +287,16 @@ impl<'a> Iterator for BatchIterator<'a> {
PrimitiveKind::PolychromeSprite => { PrimitiveKind::PolychromeSprite => {
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id; let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.polychrome_sprites_start; let sprites_start = self.polychrome_sprites_start;
let sprites_end = sprites_start let mut sprites_end = self.polychrome_sprites_start;
+ self while self
.polychrome_sprites_iter .polychrome_sprites_iter
.by_ref() .next_if(|sprite| {
.take_while(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id sprite.order <= max_order && sprite.tile.texture_id == texture_id
}) })
.count(); .is_some()
{
sprites_end += 1;
}
self.polychrome_sprites_start = sprites_end; self.polychrome_sprites_start = sprites_end;
Some(PrimitiveBatch::PolychromeSprites { Some(PrimitiveBatch::PolychromeSprites {
texture_id, texture_id,
@ -165,22 +307,30 @@ impl<'a> Iterator for BatchIterator<'a> {
} }
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
pub enum PrimitiveKind { pub enum PrimitiveKind {
Shadow,
#[default]
Quad, Quad,
Underline,
MonochromeSprite, MonochromeSprite,
PolychromeSprite, PolychromeSprite,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Primitive { pub enum Primitive {
Shadow(Shadow),
Quad(Quad), Quad(Quad),
Underline(Underline),
MonochromeSprite(MonochromeSprite), MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite), PolychromeSprite(PolychromeSprite),
} }
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> { pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
Quads(&'a [Quad]), Quads(&'a [Quad]),
Underlines(&'a [Underline]),
MonochromeSprites { MonochromeSprites {
texture_id: AtlasTextureId, texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite], sprites: &'a [MonochromeSprite],
@ -191,35 +341,18 @@ pub(crate) enum PrimitiveBatch<'a> {
}, },
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)] #[repr(C)]
pub struct Quad { pub struct Quad {
pub order: u32, pub order: u32, // Initially a LayerId, then a DrawOrder.
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
pub clip_bounds: Bounds<ScaledPixels>, pub content_mask: ScaledContentMask,
pub clip_corner_radii: Corners<ScaledPixels>,
pub background: Hsla, pub background: Hsla,
pub border_color: Hsla, pub border_color: Hsla,
pub corner_radii: Corners<ScaledPixels>, pub corner_radii: Corners<ScaledPixels>,
pub border_widths: Edges<ScaledPixels>, pub border_widths: Edges<ScaledPixels>,
} }
impl Quad {
pub fn vertices(&self) -> impl Iterator<Item = Point<ScaledPixels>> {
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()
}
}
impl Ord for Quad { impl Ord for Quad {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order) self.order.cmp(&other.order)
@ -238,6 +371,64 @@ impl From<Quad> for Primitive {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Underline {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ScaledContentMask,
pub thickness: ScaledPixels,
pub color: Hsla,
pub wavy: bool,
}
impl Ord for Underline {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Underline {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Underline> for Primitive {
fn from(underline: Underline) -> Self {
Primitive::Underline(underline)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Shadow {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ScaledContentMask,
pub color: Hsla,
pub blur_radius: ScaledPixels,
}
impl Ord for Shadow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Shadow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Shadow> for Primitive {
fn from(shadow: Shadow) -> Self {
Primitive::Shadow(shadow)
}
}
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)] #[repr(C)]
pub struct MonochromeSprite { pub struct MonochromeSprite {
@ -303,3 +494,76 @@ impl From<PolychromeSprite> for Primitive {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct AtlasId(pub(crate) usize); pub struct AtlasId(pub(crate) usize);
impl Bounds<ScaledPixels> {
fn to_bsp_polygon<A: Copy>(&self, z: f32, anchor: A) -> BspPolygon<A> {
let upper_left = self.origin;
let upper_right = self.upper_right();
let lower_right = self.lower_right();
let lower_left = self.lower_left();
BspPolygon::from_points(
[
Point3D::new(upper_left.x.into(), upper_left.y.into(), z as f64),
Point3D::new(upper_right.x.into(), upper_right.y.into(), z as f64),
Point3D::new(lower_right.x.into(), lower_right.y.into(), z as f64),
Point3D::new(lower_left.x.into(), lower_left.y.into(), z as f64),
],
anchor,
)
.expect("Polygon should not be empty")
}
}
#[cfg(test)]
mod tests {
use crate::{point, size};
use super::*;
use smallvec::smallvec;
#[test]
fn test_scene() {
let mut scene = Scene::new(1.0);
assert_eq!(scene.layers.len(), 0);
scene.insert(smallvec![1], quad());
scene.insert(smallvec![2], shadow());
scene.insert(smallvec![3], quad());
let mut batches_count = 0;
for _ in scene.batches() {
batches_count += 1;
}
assert_eq!(batches_count, 3);
}
fn quad() -> Quad {
Quad {
order: 0,
bounds: Bounds {
origin: point(ScaledPixels(0.), ScaledPixels(0.)),
size: size(ScaledPixels(100.), ScaledPixels(100.)),
},
content_mask: Default::default(),
background: Default::default(),
border_color: Default::default(),
corner_radii: Default::default(),
border_widths: Default::default(),
}
}
fn shadow() -> Shadow {
Shadow {
order: Default::default(),
bounds: Bounds {
origin: point(ScaledPixels(0.), ScaledPixels(0.)),
size: size(ScaledPixels(100.), ScaledPixels(100.)),
},
corner_radii: Default::default(),
content_mask: Default::default(),
color: Default::default(),
blur_radius: Default::default(),
}
}
}

View file

@ -1,10 +1,11 @@
use crate::{ use crate::{
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, Shadow,
SharedString, Size, SizeRefinement, ViewContext, WindowContext, SharedString, Size, SizeRefinement, ViewContext, WindowContext,
}; };
use refineable::Refineable; use refineable::Refineable;
use smallvec::SmallVec;
pub use taffy::style::{ pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent, AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position, Overflow, Position,
@ -89,10 +90,21 @@ pub struct Style {
#[refineable] #[refineable]
pub corner_radii: Corners<AbsoluteLength>, pub corner_radii: Corners<AbsoluteLength>,
/// Box Shadow of the element
pub box_shadow: SmallVec<[BoxShadow; 2]>,
/// TEXT /// TEXT
pub text: TextStyleRefinement, pub text: TextStyleRefinement,
} }
#[derive(Clone, Debug)]
pub struct BoxShadow {
pub color: Hsla,
pub offset: Point<Pixels>,
pub blur_radius: Pixels,
pub spread_radius: Pixels,
}
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(debug)]
pub struct TextStyle { pub struct TextStyle {
@ -229,32 +241,55 @@ impl Style {
} }
/// Paints the background of an element styled with this style. /// Paints the background of an element styled with this style.
pub fn paint<V: 'static>(&self, order: u32, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) { pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
let scale = cx.scale_factor(); let scale = cx.scale_factor();
let background_color = self.fill.as_ref().and_then(Fill::color); for shadow in &self.box_shadow {
if background_color.is_some() || self.is_border_visible() { let content_mask = cx.content_mask();
let layer_id = cx.current_layer_id(); let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset;
shadow_bounds.dilate(shadow.spread_radius);
cx.stack(0, |cx| {
let layer_id = cx.current_stacking_order();
cx.scene().insert( cx.scene().insert(
layer_id, layer_id,
Quad { Shadow {
order, order: 0,
bounds: bounds.scale(scale), bounds: shadow_bounds.scale(scale),
clip_bounds: bounds.scale(scale), // todo! content_mask: content_mask.scale(scale),
clip_corner_radii: self corner_radii: self
.corner_radii .corner_radii
.map(|length| length.to_pixels(rem_size).scale(scale)), .to_pixels(shadow_bounds.size, rem_size)
.scale(scale),
color: shadow.color,
blur_radius: shadow.blur_radius.scale(scale),
},
);
})
}
let background_color = self.fill.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() {
let content_mask = cx.content_mask();
cx.stack(1, |cx| {
let order = cx.current_stacking_order();
cx.scene().insert(
order,
Quad {
order: 0,
bounds: bounds.scale(scale),
content_mask: content_mask.scale(scale),
background: background_color.unwrap_or_default(), background: background_color.unwrap_or_default(),
border_color: self.border_color.unwrap_or_default(), border_color: self.border_color.unwrap_or_default(),
corner_radii: self corner_radii: self
.corner_radii .corner_radii
.map(|length| length.to_pixels(rem_size).scale(scale)), .to_pixels(bounds.size, rem_size)
border_widths: self .scale(scale),
.border_widths border_widths: self.border_widths.to_pixels(rem_size).scale(scale),
.map(|length| length.to_pixels(rem_size).scale(scale)),
}, },
); );
});
} }
} }
@ -298,6 +333,7 @@ impl Default for Style {
fill: None, fill: None,
border_color: None, border_color: None,
corner_radii: Corners::default(), corner_radii: Corners::default(),
box_shadow: Default::default(),
text: TextStyleRefinement::default(), text: TextStyleRefinement::default(),
} }
} }
@ -308,7 +344,7 @@ impl Default for Style {
pub struct UnderlineStyle { pub struct UnderlineStyle {
pub thickness: Pixels, pub thickness: Pixels,
pub color: Option<Hsla>, pub color: Option<Hsla>,
pub squiggly: bool, pub wavy: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -1,9 +1,11 @@
use crate::{ use crate::{
self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent, self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement, FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement,
Styled, TextStyleRefinement,
}; };
use smallvec::smallvec;
pub trait StyleHelpers: Sized + Styled<Style = Style> { pub trait StyleHelpers: Styled<Style = Style> {
gpui3_macros::style_helpers!(); gpui3_macros::style_helpers!();
fn h(mut self, height: Length) -> Self { fn h(mut self, height: Length) -> Self {
@ -147,6 +149,103 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
self self
} }
fn shadow(mut self) -> Self {
self.declared_style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(1.)),
blur_radius: px(3.),
spread_radius: px(0.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(-1.),
}
]);
self
}
fn shadow_none(mut self) -> Self {
self.declared_style().box_shadow = Some(Default::default());
self
}
fn shadow_sm(mut self) -> Self {
self.declared_style().box_shadow = Some(smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.05),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
}]);
self
}
fn shadow_md(mut self) -> Self {
self.declared_style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0.5, 0., 0., 1.0),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-1.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(2.)),
blur_radius: px(4.),
spread_radius: px(-2.),
}
]);
self
}
fn shadow_lg(mut self) -> Self {
self.declared_style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(10.)),
blur_radius: px(15.),
spread_radius: px(-3.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-4.),
}
]);
self
}
fn shadow_xl(mut self) -> Self {
self.declared_style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(20.)),
blur_radius: px(25.),
spread_radius: px(-5.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(8.)),
blur_radius: px(10.),
spread_radius: px(-6.),
}
]);
self
}
fn shadow_2xl(mut self) -> Self {
self.declared_style().box_shadow = Some(smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.25),
offset: point(px(0.), px(25.)),
blur_radius: px(50.),
spread_radius: px(-12.),
}]);
self
}
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> { fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.declared_style(); let style: &mut StyleRefinement = self.declared_style();
&mut style.text &mut style.text
@ -206,6 +305,69 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
self self
} }
fn text_decoration_none(mut self) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
.underline = None;
self
}
fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.color = Some(color.into());
self
}
fn text_decoration_solid(mut self) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.wavy = false;
self
}
fn text_decoration_wavy(mut self) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.wavy = true;
self
}
fn text_decoration_0(mut self) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(0.);
self
}
fn text_decoration_1(mut self) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(1.);
self
}
fn text_decoration_2(mut self) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(2.);
self
}
fn text_decoration_4(mut self) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(4.);
self
}
fn text_decoration_8(mut self) -> Self {
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(8.);
self
}
fn font(mut self, family_name: impl Into<SharedString>) -> Self { fn font(mut self, family_name: impl Into<SharedString>) -> Self {
self.text_style() self.text_style()
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)

View file

@ -1,6 +1,5 @@
use super::{ use super::{
AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size, AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Result, Size, Style,
Style,
}; };
use collections::HashMap; use collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
@ -14,7 +13,7 @@ use taffy::{
pub struct TaffyLayoutEngine { pub struct TaffyLayoutEngine {
taffy: Taffy, taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>, children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layouts: HashMap<LayoutId, Layout>, absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
} }
impl TaffyLayoutEngine { impl TaffyLayoutEngine {
@ -22,7 +21,7 @@ impl TaffyLayoutEngine {
TaffyLayoutEngine { TaffyLayoutEngine {
taffy: Taffy::new(), taffy: Taffy::new(),
children_to_parents: HashMap::default(), children_to_parents: HashMap::default(),
absolute_layouts: HashMap::default(), absolute_layout_bounds: HashMap::default(),
} }
} }
@ -127,19 +126,24 @@ impl TaffyLayoutEngine {
Ok(()) Ok(())
} }
pub fn layout(&mut self, id: LayoutId) -> Result<Layout> { pub fn layout_bounds(&mut self, id: LayoutId) -> Result<Bounds<Pixels>> {
if let Some(layout) = self.absolute_layouts.get(&id).cloned() { if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
return Ok(layout); return Ok(layout);
} }
let mut relative_layout: Layout = self.taffy.layout(id.into()).map(Into::into)?; let layout = self.taffy.layout(id.into())?;
if let Some(parent_id) = self.children_to_parents.get(&id).copied() { let mut bounds = Bounds {
let parent_layout = self.layout(parent_id)?; origin: layout.location.into(),
relative_layout.bounds.origin += parent_layout.bounds.origin; size: layout.size.into(),
} };
self.absolute_layouts.insert(id, relative_layout.clone());
Ok(relative_layout) if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
let parent_bounds = self.layout_bounds(parent_id)?;
bounds.origin += parent_bounds.origin;
}
self.absolute_layout_bounds.insert(id, bounds);
Ok(bounds)
} }
} }
@ -420,15 +424,3 @@ impl From<Pixels> for AvailableSpace {
AvailableSpace::Definite(pixels) AvailableSpace::Definite(pixels)
} }
} }
impl From<&taffy::tree::Layout> for Layout {
fn from(layout: &taffy::tree::Layout) -> Self {
Layout {
order: layout.order,
bounds: Bounds {
origin: layout.location.into(),
size: layout.size.into(),
},
}
}
}

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary, black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine,
ShapedLine, ShapedRun, UnderlineStyle, WindowContext, ShapedRun, UnderlineStyle, WindowContext,
}; };
use anyhow::Result; use anyhow::Result;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -90,15 +90,14 @@ impl Line {
} }
} }
// todo!
pub fn paint( pub fn paint(
&self, &self,
layout: &Layout, bounds: Bounds<Pixels>,
visible_bounds: Bounds<Pixels>, visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels, line_height: Pixels,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Result<()> { ) -> Result<()> {
let origin = layout.bounds.origin; let origin = bounds.origin;
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.; let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.ascent); let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
@ -135,9 +134,11 @@ impl Line {
origin.y + baseline_offset.y + (self.layout.descent * 0.618), origin.y + baseline_offset.y + (self.layout.descent * 0.618),
), ),
UnderlineStyle { UnderlineStyle {
color: style_run.underline.color, color: Some(
style_run.underline.color.unwrap_or(style_run.color),
),
thickness: style_run.underline.thickness, thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly, wavy: style_run.underline.wavy,
}, },
)); ));
} }
@ -154,22 +155,19 @@ impl Line {
continue; continue;
} }
if let Some((_underline_origin, _underline_style)) = finished_underline { if let Some((underline_origin, underline_style)) = finished_underline {
todo!() cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
} }
if glyph.is_emoji { if glyph.is_emoji {
cx.paint_emoji( cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
glyph_origin,
layout.order,
run.font_id,
glyph.id,
self.layout.font_size,
)?;
} else { } else {
cx.paint_glyph( cx.paint_glyph(
glyph_origin, glyph_origin,
layout.order,
run.font_id, run.font_id,
glyph.id, glyph.id,
self.layout.font_size, self.layout.font_size,
@ -179,15 +177,13 @@ impl Line {
} }
} }
if let Some((_underline_start, _underline_style)) = underline.take() { if let Some((underline_start, underline_style)) = underline.take() {
let _line_end_x = origin.x + self.layout.width; let line_end_x = origin.x + self.layout.width;
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_start, underline_start,
// width: line_end_x - underline_start.x, line_end_x - underline_start.x,
// color: underline_style.color, &underline_style,
// thickness: underline_style.thickness.into(), )?;
// squiggly: underline_style.squiggly,
// });
} }
Ok(()) Ok(())
@ -196,7 +192,7 @@ impl Line {
pub fn paint_wrapped( pub fn paint_wrapped(
&self, &self,
origin: Point<Pixels>, origin: Point<Pixels>,
_visible_bounds: Bounds<Pixels>, _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels, line_height: Pixels,
boundaries: &[ShapedBoundary], boundaries: &[ShapedBoundary],
cx: &mut WindowContext, cx: &mut WindowContext,
@ -221,14 +217,12 @@ impl Line {
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix) .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
{ {
boundaries.next(); boundaries.next();
if let Some((_underline_origin, _underline_style)) = underline.take() { if let Some((underline_origin, underline_style)) = underline.take() {
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_origin, underline_origin,
// width: glyph_origin.x - underline_origin.x, glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(), &underline_style,
// color: underline_style.color.unwrap(), )?;
// squiggly: underline_style.squiggly,
// });
} }
glyph_origin = point(origin.x, glyph_origin.y + line_height); glyph_origin = point(origin.x, glyph_origin.y + line_height);
@ -257,7 +251,7 @@ impl Line {
style_run.underline.color.unwrap_or(style_run.color), style_run.underline.color.unwrap_or(style_run.color),
), ),
thickness: style_run.underline.thickness, thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly, wavy: style_run.underline.wavy,
}, },
)); ));
} }
@ -268,14 +262,12 @@ impl Line {
} }
} }
if let Some((_underline_origin, _underline_style)) = finished_underline { if let Some((underline_origin, underline_style)) = finished_underline {
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_origin, underline_origin,
// width: glyph_origin.x - underline_origin.x, glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(), &underline_style,
// color: underline_style.color.unwrap(), )?;
// squiggly: underline_style.squiggly,
// });
} }
let text_system = cx.text_system(); let text_system = cx.text_system();
@ -306,15 +298,13 @@ impl Line {
} }
} }
if let Some((_underline_origin, _underline_style)) = underline.take() { if let Some((underline_origin, underline_style)) = underline.take() {
// let line_end_x = glyph_origin.x + self.layout.width - prev_position; let line_end_x = glyph_origin.x + self.layout.width - prev_position;
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_origin, underline_origin,
// width: line_end_x - underline_origin.x, line_end_x - underline_origin.x,
// thickness: underline_style.thickness.into(), &underline_style,
// color: underline_style.color, )?;
// squiggly: underline_style.squiggly,
// });
} }
Ok(()) Ok(())

View file

@ -1,7 +1,7 @@
use parking_lot::Mutex; use parking_lot::Mutex;
use crate::{ use crate::{
AnyElement, Element, Handle, IntoAnyElement, Layout, LayoutId, Result, ViewContext, AnyElement, Bounds, Element, Handle, IntoAnyElement, LayoutId, Pixels, Result, ViewContext,
WindowContext, WindowContext,
}; };
use std::{any::Any, marker::PhantomData, sync::Arc}; use std::{any::Any, marker::PhantomData, sync::Arc};
@ -67,7 +67,7 @@ impl<S: Send + Sync + 'static, P: Send + 'static> Element for View<S, P> {
fn paint( fn paint(
&mut self, &mut self,
_: Layout, _: Bounds<Pixels>,
_: &mut Self::State, _: &mut Self::State,
element: &mut Self::FrameState, element: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>, cx: &mut ViewContext<Self::State>,
@ -81,7 +81,7 @@ trait ViewObject: Send + 'static {
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>; fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
fn paint( fn paint(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
element: &mut dyn Any, element: &mut dyn Any,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Result<()>; ) -> Result<()>;
@ -97,7 +97,12 @@ impl<S: Send + Sync + 'static, P: Send + 'static> ViewObject for View<S, P> {
}) })
} }
fn paint(&mut self, _: Layout, element: &mut dyn Any, cx: &mut WindowContext) -> Result<()> { fn paint(
&mut self,
_: Bounds<Pixels>,
element: &mut dyn Any,
cx: &mut WindowContext,
) -> Result<()> {
self.state.update(cx, |state, cx| { self.state.update(cx, |state, cx| {
let element = element.downcast_mut::<AnyElement<S>>().unwrap(); let element = element.downcast_mut::<AnyElement<S>>().unwrap();
element.paint(state, None, cx) element.paint(state, None, cx)
@ -124,12 +129,12 @@ impl<S: 'static> Element for AnyView<S> {
fn paint( fn paint(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
_: &mut (), _: &mut (),
element: &mut Box<dyn Any>, element: &mut Box<dyn Any>,
cx: &mut ViewContext<Self::State>, cx: &mut ViewContext<Self::State>,
) -> Result<()> { ) -> Result<()> {
self.view.lock().paint(layout, element.as_mut(), cx) self.view.lock().paint(bounds, element.as_mut(), cx)
} }
} }

View file

@ -1,10 +1,11 @@
use crate::{ use crate::{
image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace, image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
}; };
use anyhow::Result; use anyhow::Result;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -16,13 +17,14 @@ pub struct AnyWindow {}
pub struct Window { pub struct Window {
handle: AnyWindowHandle, handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>, platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
pub(crate) display_id: DisplayId, // todo!("make private again?")
sprite_atlas: Arc<dyn PlatformAtlas>, sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels, rem_size: Pixels,
content_size: Size<Pixels>, content_size: Size<Pixels>,
layout_engine: TaffyLayoutEngine, layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView<()>>, pub(crate) root_view: Option<AnyView<()>>,
mouse_position: Point<Pixels>, mouse_position: Point<Pixels>,
current_layer_id: LayerId, current_stacking_order: StackingOrder,
content_mask_stack: Vec<ContentMask>, content_mask_stack: Vec<ContentMask>,
pub(crate) scene: Scene, pub(crate) scene: Scene,
pub(crate) dirty: bool, pub(crate) dirty: bool,
@ -35,6 +37,7 @@ impl Window {
cx: &mut MainThread<AppContext>, cx: &mut MainThread<AppContext>,
) -> Self { ) -> Self {
let platform_window = cx.platform().open_window(handle, options); let platform_window = cx.platform().open_window(handle, options);
let display_id = platform_window.display().id();
let sprite_atlas = platform_window.sprite_atlas(); let sprite_atlas = platform_window.sprite_atlas();
let mouse_position = platform_window.mouse_position(); let mouse_position = platform_window.mouse_position();
let content_size = platform_window.content_size(); let content_size = platform_window.content_size();
@ -46,6 +49,12 @@ impl Window {
cx.update_window(handle, |cx| { cx.update_window(handle, |cx| {
cx.window.scene = Scene::new(scale_factor); cx.window.scene = Scene::new(scale_factor);
cx.window.content_size = content_size; cx.window.content_size = content_size;
cx.window.display_id = cx
.window
.platform_window
.borrow_on_main_thread()
.display()
.id();
cx.window.dirty = true; cx.window.dirty = true;
}) })
.log_err(); .log_err();
@ -57,13 +66,14 @@ impl Window {
Window { Window {
handle, handle,
platform_window, platform_window,
display_id,
sprite_atlas, sprite_atlas,
rem_size: px(16.), rem_size: px(16.),
content_size, content_size,
layout_engine: TaffyLayoutEngine::new(), layout_engine: TaffyLayoutEngine::new(),
root_view: None, root_view: None,
mouse_position, mouse_position,
current_layer_id: SmallVec::new(), current_stacking_order: SmallVec::new(),
content_mask_stack: Vec::new(), content_mask_stack: Vec::new(),
scene: Scene::new(scale_factor), scene: Scene::new(scale_factor),
dirty: true, dirty: true,
@ -89,7 +99,7 @@ impl ContentMask {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Default, Clone, Debug, PartialEq, Eq)]
#[repr(C)] #[repr(C)]
pub struct ScaledContentMask { pub struct ScaledContentMask {
bounds: Bounds<ScaledPixels>, bounds: Bounds<ScaledPixels>,
@ -133,6 +143,45 @@ impl<'a, 'w> WindowContext<'a, 'w> {
AsyncWindowContext::new(self.app.to_async(), self.window.handle) AsyncWindowContext::new(self.app.to_async(), self.window.handle)
} }
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
let f = Box::new(f);
let display_id = self.window.display_id;
let async_cx = self.to_async();
let app_cx = self.app_mut();
match app_cx.next_frame_callbacks.entry(display_id) {
collections::hash_map::Entry::Occupied(mut entry) => {
if entry.get().is_empty() {
app_cx.display_linker.start(display_id);
}
entry.get_mut().push(f);
}
collections::hash_map::Entry::Vacant(entry) => {
app_cx.display_linker.set_output_callback(
display_id,
Box::new(move |_current_time, _output_time| {
let _ = async_cx.update(|cx| {
let callbacks = cx
.next_frame_callbacks
.get_mut(&display_id)
.unwrap()
.drain(..)
.collect::<Vec<_>>();
for callback in callbacks {
callback(cx);
}
if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() {
cx.display_linker.stop(display_id);
}
});
}),
);
app_cx.display_linker.start(display_id);
entry.insert(vec![f]);
}
}
}
pub fn spawn<Fut, R>( pub fn spawn<Fut, R>(
&mut self, &mut self,
f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static, f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static,
@ -176,11 +225,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
.request_measured_layout(style, rem_size, measure) .request_measured_layout(style, rem_size, measure)
} }
pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> { pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Result<Bounds<Pixels>> {
Ok(self Ok(self
.window .window
.layout_engine .layout_engine
.layout(layout_id) .layout_bounds(layout_id)
.map(Into::into)?) .map(Into::into)?)
} }
@ -201,20 +250,51 @@ impl<'a, 'w> WindowContext<'a, 'w> {
} }
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R { pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
self.window.current_layer_id.push(order); self.window.current_stacking_order.push(order);
let result = f(self); let result = f(self);
self.window.current_layer_id.pop(); self.window.current_stacking_order.pop();
result result
} }
pub fn current_layer_id(&self) -> LayerId { pub fn current_stacking_order(&self) -> StackingOrder {
self.window.current_layer_id.clone() self.window.current_stacking_order.clone()
}
pub fn paint_underline(
&mut self,
origin: Point<Pixels>,
width: Pixels,
style: &UnderlineStyle,
) -> Result<()> {
let scale_factor = self.scale_factor();
let height = if style.wavy {
style.thickness * 3.
} else {
style.thickness
};
let bounds = Bounds {
origin,
size: size(width, height),
};
let content_mask = self.content_mask();
let layer_id = self.current_stacking_order();
self.window.scene.insert(
layer_id,
Underline {
order: 0,
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
thickness: style.thickness.scale(scale_factor),
color: style.color.unwrap_or_default(),
wavy: style.wavy,
},
);
Ok(())
} }
pub fn paint_glyph( pub fn paint_glyph(
&mut self, &mut self,
origin: Point<Pixels>, origin: Point<Pixels>,
order: u32,
font_id: FontId, font_id: FontId,
glyph_id: GlyphId, glyph_id: GlyphId,
font_size: Pixels, font_size: Pixels,
@ -237,7 +317,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let raster_bounds = self.text_system().raster_bounds(&params)?; let raster_bounds = self.text_system().raster_bounds(&params)?;
if !raster_bounds.is_zero() { if !raster_bounds.is_zero() {
let layer_id = self.current_layer_id(); let layer_id = self.current_stacking_order();
let tile = let tile =
self.window self.window
.sprite_atlas .sprite_atlas
@ -254,7 +334,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.scene.insert( self.window.scene.insert(
layer_id, layer_id,
MonochromeSprite { MonochromeSprite {
order, order: 0,
bounds, bounds,
content_mask, content_mask,
color, color,
@ -268,7 +348,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
pub fn paint_emoji( pub fn paint_emoji(
&mut self, &mut self,
origin: Point<Pixels>, origin: Point<Pixels>,
order: u32,
font_id: FontId, font_id: FontId,
glyph_id: GlyphId, glyph_id: GlyphId,
font_size: Pixels, font_size: Pixels,
@ -287,7 +366,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let raster_bounds = self.text_system().raster_bounds(&params)?; let raster_bounds = self.text_system().raster_bounds(&params)?;
if !raster_bounds.is_zero() { if !raster_bounds.is_zero() {
let layer_id = self.current_layer_id(); let layer_id = self.current_stacking_order();
let tile = let tile =
self.window self.window
.sprite_atlas .sprite_atlas
@ -304,7 +383,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.scene.insert( self.window.scene.insert(
layer_id, layer_id,
PolychromeSprite { PolychromeSprite {
order, order: 0,
bounds, bounds,
corner_radii: Default::default(), corner_radii: Default::default(),
content_mask, content_mask,
@ -319,7 +398,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
pub fn paint_svg( pub fn paint_svg(
&mut self, &mut self,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
order: u32,
path: SharedString, path: SharedString,
color: Hsla, color: Hsla,
) -> Result<()> { ) -> Result<()> {
@ -333,7 +411,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
.map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)), .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
}; };
let layer_id = self.current_layer_id(); let layer_id = self.current_stacking_order();
let tile = let tile =
self.window self.window
.sprite_atlas .sprite_atlas
@ -346,7 +424,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.scene.insert( self.window.scene.insert(
layer_id, layer_id,
MonochromeSprite { MonochromeSprite {
order, order: 0,
bounds, bounds,
content_mask, content_mask,
color, color,
@ -361,7 +439,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
&mut self, &mut self,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
corner_radii: Corners<Pixels>, corner_radii: Corners<Pixels>,
order: u32,
data: Arc<ImageData>, data: Arc<ImageData>,
grayscale: bool, grayscale: bool,
) -> Result<()> { ) -> Result<()> {
@ -369,7 +446,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let bounds = bounds.scale(scale_factor); let bounds = bounds.scale(scale_factor);
let params = RenderImageParams { image_id: data.id }; let params = RenderImageParams { image_id: data.id };
let layer_id = self.current_layer_id(); let order = self.current_stacking_order();
let tile = self let tile = self
.window .window
.sprite_atlas .sprite_atlas
@ -380,9 +457,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let corner_radii = corner_radii.scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor);
self.window.scene.insert( self.window.scene.insert(
layer_id,
PolychromeSprite {
order, order,
PolychromeSprite {
order: 0,
bounds, bounds,
content_mask, content_mask,
corner_radii, corner_radii,
@ -401,12 +478,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?; let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
let available_space = cx.window.content_size.map(Into::into); let available_space = cx.window.content_size.map(Into::into);
let started_at = std::time::Instant::now();
cx.window cx.window
.layout_engine .layout_engine
.compute_layout(root_layout_id, available_space)?; .compute_layout(root_layout_id, available_space)?;
println!("compute_layout took {:?}", started_at.elapsed()); let layout = cx.window.layout_engine.layout_bounds(root_layout_id)?;
let layout = cx.window.layout_engine.layout(root_layout_id)?;
root_view.paint(layout, &mut (), &mut frame_state, cx)?; root_view.paint(layout, &mut (), &mut frame_state, cx)?;
cx.window.root_view = Some(root_view); cx.window.root_view = Some(root_view);
@ -573,6 +648,20 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
self.entities.weak_handle(self.entity_id) self.entities.weak_handle(self.entity_id)
} }
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.current_stacking_order.pop();
result
}
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut S, &mut ViewContext<S>) + Send + 'static) {
let entity = self.handle();
self.window_cx.on_next_frame(move |cx| {
entity.update(cx, f).ok();
});
}
pub fn observe<E: Send + Sync + 'static>( pub fn observe<E: Send + Sync + 'static>(
&mut self, &mut self,
handle: &Handle<E>, handle: &Handle<E>,
@ -726,9 +815,3 @@ pub struct AnyWindowHandle {
pub(crate) id: WindowId, pub(crate) id: WindowId,
state_type: TypeId, state_type: TypeId,
} }
#[derive(Clone)]
pub struct Layout {
pub order: u32,
pub bounds: Bounds<Pixels>,
}

View file

@ -92,7 +92,7 @@ impl CollabPanel {
), ),
] ]
}) })
.take(10) .take(5)
.flatten(), .flatten(),
), ),
), ),
@ -168,7 +168,8 @@ impl CollabPanel {
.uri(avatar_uri) .uri(avatar_uri)
.size_3p5() .size_3p5()
.rounded_full() .rounded_full()
.fill(theme.middle.positive.default.foreground), .fill(theme.middle.positive.default.foreground)
.shadow_md(),
) )
.child(label), .child(label),
) )

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use std::{collections::HashMap, fmt}; use std::{collections::HashMap, fmt};
use gpui3::{ use gpui3::{
BorrowAppContext, Element, Hsla, Layout, LayoutId, Result, ViewContext, WindowContext, BorrowAppContext, Bounds, Element, Hsla, LayoutId, Pixels, Result, ViewContext, WindowContext,
}; };
use serde::{de::Visitor, Deserialize, Deserializer}; use serde::{de::Visitor, Deserialize, Deserializer};
@ -162,7 +162,7 @@ impl<E: Element> Element for Themed<E> {
fn paint( fn paint(
&mut self, &mut self,
layout: Layout, bounds: Bounds<Pixels>,
state: &mut Self::State, state: &mut Self::State,
frame_state: &mut Self::FrameState, frame_state: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>, cx: &mut ViewContext<Self::State>,
@ -171,7 +171,7 @@ impl<E: Element> Element for Themed<E> {
Self: Sized, Self: Sized,
{ {
cx.with_state(self.theme.clone(), |cx| { cx.with_state(self.theme.clone(), |cx| {
self.child.paint(layout, state, frame_state, cx) self.child.paint(bounds, state, frame_state, cx)
}) })
} }
} }

View file

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

View file

@ -1,7 +1,7 @@
use crate::theme::Theme; use crate::theme::Theme;
use gpui3::serde_json::{self, json}; use gpui3::serde_json::{self, json};
pub fn rose_pine_dawn() -> Theme { pub fn rose_pine() -> Theme {
serde_json::from_value(json! { serde_json::from_value(json! {
{ {
"name": "Rosé Pine", "name": "Rosé Pine",
@ -843,3 +843,844 @@ pub fn rose_pine_dawn() -> Theme {
}) })
.unwrap() .unwrap()
} }
pub fn rose_pine_dawn() -> Theme {
serde_json::from_value(json!({
"name": "Rosé Pine Dawn",
"is_light": true,
"ramps": {},
"lowest": {
"base": {
"default": {
"background": "#dcd8d8",
"border": "#dcd6d5",
"foreground": "#575279"
},
"hovered": {
"background": "#dcd6d5",
"border": "#dcd6d5",
"foreground": "#575279"
},
"pressed": {
"background": "#efe6df",
"border": "#dcd6d5",
"foreground": "#575279"
},
"active": {
"background": "#c1bac1",
"border": "#a9a3b0",
"foreground": "#575279"
},
"disabled": {
"background": "#dcd8d8",
"border": "#d0cccf",
"foreground": "#938fa3"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#c7c0c5"
}
},
"variant": {
"default": {
"background": "#dcd8d8",
"border": "#dcd6d5",
"foreground": "#706c8c"
},
"hovered": {
"background": "#dcd6d5",
"border": "#dcd6d5",
"foreground": "#706c8c"
},
"pressed": {
"background": "#efe6df",
"border": "#dcd6d5",
"foreground": "#706c8c"
},
"active": {
"background": "#c1bac1",
"border": "#a9a3b0",
"foreground": "#575279"
},
"disabled": {
"background": "#dcd8d8",
"border": "#d0cccf",
"foreground": "#938fa3"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#c7c0c5"
}
},
"on": {
"default": {
"background": "#fef9f2",
"border": "#e5e0df",
"foreground": "#575279"
},
"hovered": {
"background": "#e5e0df",
"border": "#e5e0df",
"foreground": "#575279"
},
"pressed": {
"background": "#d4d0d2",
"border": "#e5e0df",
"foreground": "#575279"
},
"active": {
"background": "#dbd5d4",
"border": "#dbd3d1",
"foreground": "#575279"
},
"disabled": {
"background": "#fef9f2",
"border": "#f6f1eb",
"foreground": "#b1abb5"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#d6d1d1"
}
},
"accent": {
"default": {
"background": "#dde9eb",
"border": "#c3d7db",
"foreground": "#57949f"
},
"hovered": {
"background": "#c3d7db",
"border": "#c3d7db",
"foreground": "#57949f"
},
"pressed": {
"background": "#b6cfd3",
"border": "#c3d7db",
"foreground": "#57949f"
},
"active": {
"background": "#a3c3c9",
"border": "#8db6bd",
"foreground": "#06090a"
},
"disabled": {
"background": "#dde9eb",
"border": "#d0e0e3",
"foreground": "#72a5ae"
},
"inverted": {
"background": "#06090a",
"border": "#ffffff",
"foreground": "#a8c7cd"
}
},
"positive": {
"default": {
"background": "#dbeee7",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"hovered": {
"background": "#bee0d5",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"pressed": {
"background": "#b0dacb",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"active": {
"background": "#9bd0bf",
"border": "#82c6b1",
"foreground": "#060a09"
},
"disabled": {
"background": "#dbeee7",
"border": "#cde7de",
"foreground": "#63b89f"
},
"inverted": {
"background": "#060a09",
"border": "#ffffff",
"foreground": "#a1d4c3"
}
},
"warning": {
"default": {
"background": "#ffebd6",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"hovered": {
"background": "#ffdab7",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"pressed": {
"background": "#fed2a6",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"active": {
"background": "#fbc891",
"border": "#f7bc77",
"foreground": "#330704"
},
"disabled": {
"background": "#ffebd6",
"border": "#ffe2c7",
"foreground": "#f1ac57"
},
"inverted": {
"background": "#330704",
"border": "#ffffff",
"foreground": "#fccb97"
}
},
"negative": {
"default": {
"background": "#f1dfe3",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"hovered": {
"background": "#e6c6cd",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"pressed": {
"background": "#e0bac2",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"active": {
"background": "#d8a8b3",
"border": "#ce94a3",
"foreground": "#0b0708"
},
"disabled": {
"background": "#f1dfe3",
"border": "#ecd2d8",
"foreground": "#c17b8e"
},
"inverted": {
"background": "#0b0708",
"border": "#ffffff",
"foreground": "#dbadb8"
}
}
},
"middle": {
"base": {
"default": {
"background": "#fef9f2",
"border": "#e5e0df",
"foreground": "#575279"
},
"hovered": {
"background": "#e5e0df",
"border": "#e5e0df",
"foreground": "#575279"
},
"pressed": {
"background": "#d4d0d2",
"border": "#e5e0df",
"foreground": "#575279"
},
"active": {
"background": "#dbd5d4",
"border": "#dbd3d1",
"foreground": "#575279"
},
"disabled": {
"background": "#fef9f2",
"border": "#f6f1eb",
"foreground": "#b1abb5"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#d6d1d1"
}
},
"variant": {
"default": {
"background": "#fef9f2",
"border": "#e5e0df",
"foreground": "#706c8c"
},
"hovered": {
"background": "#e5e0df",
"border": "#e5e0df",
"foreground": "#706c8c"
},
"pressed": {
"background": "#d4d0d2",
"border": "#e5e0df",
"foreground": "#706c8c"
},
"active": {
"background": "#dbd5d4",
"border": "#dbd3d1",
"foreground": "#575279"
},
"disabled": {
"background": "#fef9f2",
"border": "#f6f1eb",
"foreground": "#b1abb5"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#d6d1d1"
}
},
"on": {
"default": {
"background": "#faf4ed",
"border": "#fdf8f1",
"foreground": "#575279"
},
"hovered": {
"background": "#fdf8f1",
"border": "#fdf8f1",
"foreground": "#575279"
},
"pressed": {
"background": "#fdf8f2",
"border": "#fdf8f1",
"foreground": "#575279"
},
"active": {
"background": "#e6e1e0",
"border": "#d0cccf",
"foreground": "#575279"
},
"disabled": {
"background": "#faf4ed",
"border": "#fcf6ef",
"foreground": "#efe6df"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#ede9e5"
}
},
"accent": {
"default": {
"background": "#dde9eb",
"border": "#c3d7db",
"foreground": "#57949f"
},
"hovered": {
"background": "#c3d7db",
"border": "#c3d7db",
"foreground": "#57949f"
},
"pressed": {
"background": "#b6cfd3",
"border": "#c3d7db",
"foreground": "#57949f"
},
"active": {
"background": "#a3c3c9",
"border": "#8db6bd",
"foreground": "#06090a"
},
"disabled": {
"background": "#dde9eb",
"border": "#d0e0e3",
"foreground": "#72a5ae"
},
"inverted": {
"background": "#06090a",
"border": "#ffffff",
"foreground": "#a8c7cd"
}
},
"positive": {
"default": {
"background": "#dbeee7",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"hovered": {
"background": "#bee0d5",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"pressed": {
"background": "#b0dacb",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"active": {
"background": "#9bd0bf",
"border": "#82c6b1",
"foreground": "#060a09"
},
"disabled": {
"background": "#dbeee7",
"border": "#cde7de",
"foreground": "#63b89f"
},
"inverted": {
"background": "#060a09",
"border": "#ffffff",
"foreground": "#a1d4c3"
}
},
"warning": {
"default": {
"background": "#ffebd6",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"hovered": {
"background": "#ffdab7",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"pressed": {
"background": "#fed2a6",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"active": {
"background": "#fbc891",
"border": "#f7bc77",
"foreground": "#330704"
},
"disabled": {
"background": "#ffebd6",
"border": "#ffe2c7",
"foreground": "#f1ac57"
},
"inverted": {
"background": "#330704",
"border": "#ffffff",
"foreground": "#fccb97"
}
},
"negative": {
"default": {
"background": "#f1dfe3",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"hovered": {
"background": "#e6c6cd",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"pressed": {
"background": "#e0bac2",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"active": {
"background": "#d8a8b3",
"border": "#ce94a3",
"foreground": "#0b0708"
},
"disabled": {
"background": "#f1dfe3",
"border": "#ecd2d8",
"foreground": "#c17b8e"
},
"inverted": {
"background": "#0b0708",
"border": "#ffffff",
"foreground": "#dbadb8"
}
}
},
"highest": {
"base": {
"default": {
"background": "#faf4ed",
"border": "#fdf8f1",
"foreground": "#575279"
},
"hovered": {
"background": "#fdf8f1",
"border": "#fdf8f1",
"foreground": "#575279"
},
"pressed": {
"background": "#fdf8f2",
"border": "#fdf8f1",
"foreground": "#575279"
},
"active": {
"background": "#e6e1e0",
"border": "#d0cccf",
"foreground": "#575279"
},
"disabled": {
"background": "#faf4ed",
"border": "#fcf6ef",
"foreground": "#efe6df"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#ede9e5"
}
},
"variant": {
"default": {
"background": "#faf4ed",
"border": "#fdf8f1",
"foreground": "#706c8c"
},
"hovered": {
"background": "#fdf8f1",
"border": "#fdf8f1",
"foreground": "#706c8c"
},
"pressed": {
"background": "#fdf8f2",
"border": "#fdf8f1",
"foreground": "#706c8c"
},
"active": {
"background": "#e6e1e0",
"border": "#d0cccf",
"foreground": "#575279"
},
"disabled": {
"background": "#faf4ed",
"border": "#fcf6ef",
"foreground": "#efe6df"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#ede9e5"
}
},
"on": {
"default": {
"background": "#fef9f2",
"border": "#e5e0df",
"foreground": "#575279"
},
"hovered": {
"background": "#e5e0df",
"border": "#e5e0df",
"foreground": "#575279"
},
"pressed": {
"background": "#d4d0d2",
"border": "#e5e0df",
"foreground": "#575279"
},
"active": {
"background": "#dbd5d4",
"border": "#dbd3d1",
"foreground": "#575279"
},
"disabled": {
"background": "#fef9f2",
"border": "#f6f1eb",
"foreground": "#b1abb5"
},
"inverted": {
"background": "#575279",
"border": "#faf4ed",
"foreground": "#d6d1d1"
}
},
"accent": {
"default": {
"background": "#dde9eb",
"border": "#c3d7db",
"foreground": "#57949f"
},
"hovered": {
"background": "#c3d7db",
"border": "#c3d7db",
"foreground": "#57949f"
},
"pressed": {
"background": "#b6cfd3",
"border": "#c3d7db",
"foreground": "#57949f"
},
"active": {
"background": "#a3c3c9",
"border": "#8db6bd",
"foreground": "#06090a"
},
"disabled": {
"background": "#dde9eb",
"border": "#d0e0e3",
"foreground": "#72a5ae"
},
"inverted": {
"background": "#06090a",
"border": "#ffffff",
"foreground": "#a8c7cd"
}
},
"positive": {
"default": {
"background": "#dbeee7",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"hovered": {
"background": "#bee0d5",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"pressed": {
"background": "#b0dacb",
"border": "#bee0d5",
"foreground": "#3eaa8e"
},
"active": {
"background": "#9bd0bf",
"border": "#82c6b1",
"foreground": "#060a09"
},
"disabled": {
"background": "#dbeee7",
"border": "#cde7de",
"foreground": "#63b89f"
},
"inverted": {
"background": "#060a09",
"border": "#ffffff",
"foreground": "#a1d4c3"
}
},
"warning": {
"default": {
"background": "#ffebd6",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"hovered": {
"background": "#ffdab7",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"pressed": {
"background": "#fed2a6",
"border": "#ffdab7",
"foreground": "#e99d35"
},
"active": {
"background": "#fbc891",
"border": "#f7bc77",
"foreground": "#330704"
},
"disabled": {
"background": "#ffebd6",
"border": "#ffe2c7",
"foreground": "#f1ac57"
},
"inverted": {
"background": "#330704",
"border": "#ffffff",
"foreground": "#fccb97"
}
},
"negative": {
"default": {
"background": "#f1dfe3",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"hovered": {
"background": "#e6c6cd",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"pressed": {
"background": "#e0bac2",
"border": "#e6c6cd",
"foreground": "#b4647a"
},
"active": {
"background": "#d8a8b3",
"border": "#ce94a3",
"foreground": "#0b0708"
},
"disabled": {
"background": "#f1dfe3",
"border": "#ecd2d8",
"foreground": "#c17b8e"
},
"inverted": {
"background": "#0b0708",
"border": "#ffffff",
"foreground": "#dbadb8"
}
}
},
"popover_shadow": {
"blur": 4,
"color": "#2c2a4d33",
"offset": [
1,
2
]
},
"modal_shadow": {
"blur": 16,
"color": "#2c2a4d33",
"offset": [
0,
2
]
},
"players": {
"0": {
"selection": "#57949f3d",
"cursor": "#57949f"
},
"1": {
"selection": "#3eaa8e3d",
"cursor": "#3eaa8e"
},
"2": {
"selection": "#7c697f3d",
"cursor": "#7c697f"
},
"3": {
"selection": "#907aa93d",
"cursor": "#907aa9"
},
"4": {
"selection": "#907aa93d",
"cursor": "#907aa9"
},
"5": {
"selection": "#2a69833d",
"cursor": "#2a6983"
},
"6": {
"selection": "#b4647a3d",
"cursor": "#b4647a"
},
"7": {
"selection": "#e99d353d",
"cursor": "#e99d35"
}
},
"syntax": {
"comment": {
"color": "#9893a5"
},
"operator": {
"color": "#286983"
},
"punctuation": {
"color": "#797593"
},
"variable": {
"color": "#575279"
},
"string": {
"color": "#ea9d34"
},
"type": {
"color": "#56949f"
},
"type.builtin": {
"color": "#56949f"
},
"boolean": {
"color": "#d7827e"
},
"function": {
"color": "#d7827e"
},
"keyword": {
"color": "#286983"
},
"tag": {
"color": "#56949f"
},
"function.method": {
"color": "#d7827e"
},
"title": {
"color": "#ea9d34"
},
"link_text": {
"color": "#56949f",
"italic": false
},
"link_uri": {
"color": "#d7827e"
}
},
"color_family": {
"neutral": {
"low": 39.80392156862745,
"high": 95.49019607843137,
"range": 55.686274509803916,
"scaling_value": 1.7957746478873242
},
"red": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
},
"orange": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
},
"yellow": {
"low": 8.823529411764707,
"high": 100,
"range": 91.17647058823529,
"scaling_value": 1.0967741935483872
},
"green": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
},
"cyan": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
},
"blue": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
},
"violet": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
},
"magenta": {
"low": 0,
"high": 100,
"range": 100,
"scaling_value": 1
}
}
}))
.unwrap()
}

View file

@ -28,9 +28,8 @@ impl Workspace {
} }
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
let theme = rose_pine_dawn();
themed(rose_pine_dawn(), cx, |cx| { themed(rose_pine_dawn(), cx, |cx| {
let theme = theme(cx);
div() div()
.size_full() .size_full()
.v_stack() .v_stack()
@ -201,7 +200,13 @@ impl Titlebar {
// .fill(theme.lowest.base.hovered.background) // .fill(theme.lowest.base.hovered.background)
// .active() // .active()
// .fill(theme.lowest.base.pressed.background) // .fill(theme.lowest.base.pressed.background)
.child(div().text_sm().child("branch")), .child(
div()
.text_sm()
.text_decoration_1()
.text_decoration_wavy()
.child("branch"),
),
), ),
) )
} }