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
[features]
test = ["backtrace", "dhat", "env_logger", "collections/test-support"]
test = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
[lib]
path = "src/gpui3.rs"
@ -66,6 +66,7 @@ dhat = "0.3"
env_logger.workspace = true
png = "0.16"
simplelog = "0.9"
util = { path = "../util", features = ["test-support"] }
[build-dependencies]
bindgen = "0.65.1"

View file

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

View file

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

View file

@ -60,9 +60,19 @@ pub struct AsyncWindowContext {
}
impl AsyncWindowContext {
pub fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
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 {

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use crate::Element;
use crate::{Bounds, Element, Pixels};
use std::marker::PhantomData;
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(
&mut self,
layout: crate::Layout,
bounds: Bounds<Pixels>,
_: &mut Self::State,
frame_state: &mut Self::FrameState,
cx: &mut crate::ViewContext<Self::State>,
) -> anyhow::Result<()> {
cx.erase_state(|cx| self.element.paint(layout, &mut (), frame_state, cx))
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 std::marker::PhantomData;
@ -41,7 +41,7 @@ impl<S: 'static> Element for Svg<S> {
fn paint(
&mut self,
layout: Layout,
bounds: Bounds<Pixels>,
_: &mut Self::State,
_: &mut Self::FrameState,
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());
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(())
}

View file

@ -1,5 +1,5 @@
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 std::{marker::PhantomData, sync::Arc};
@ -94,7 +94,7 @@ impl<S: 'static> Element for Text<S> {
fn paint<'a>(
&mut self,
layout: Layout,
bounds: Bounds<Pixels>,
_: &mut Self::State,
frame_state: &mut Self::FrameState,
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.");
let visible_bounds = layout.bounds;
line.paint(&layout, visible_bounds, line_height, cx)?;
line.paint(bounds, bounds, line_height, cx)?;
Ok(())
}

View file

@ -2,7 +2,7 @@ use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
use refineable::Refineable;
use std::{
cmp,
cmp, fmt,
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)]
#[repr(C)]
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 From<Size<Option<Pixels>>> for Size<Option<f32>> {
fn from(size: Size<Option<Pixels>>) -> Self {
impl<T: Clone + Debug> Debug for Size<T> {
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 {
width: size.width.map(|p| p.0 as f32),
height: size.height.map(|p| p.0 as f32),
width: GlobalPixels(size.width.0),
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> {
pub fn intersect(&self, other: &Self) -> Self {
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(),
}
}
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> {
@ -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)]
#[refineable(debug)]
#[repr(C)]
@ -459,8 +503,8 @@ pub struct Corners<T: Clone + Debug> {
}
impl Corners<AbsoluteLength> {
pub fn to_pixels(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = bounds.size.width.max(bounds.size.height) / 2.;
pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = size.width.max(size.height) / 2.;
Corners {
top_left: self.top_left.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 {
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)
}
}
@ -622,8 +666,8 @@ impl DevicePixels {
}
}
impl std::fmt::Debug for DevicePixels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for DevicePixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} px (device)", self.0)
}
}
@ -681,7 +725,7 @@ impl ScaledPixels {
impl Eq 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)
}
}
@ -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)]
pub struct Rems(f32);
@ -710,7 +782,7 @@ impl Mul<Pixels> 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)
}
}
@ -778,7 +850,7 @@ impl 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 {
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
@ -818,7 +890,7 @@ pub enum 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 {
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
Length::Auto => write!(f, "auto"),
@ -964,3 +1036,42 @@ impl<T: IsZero + Debug + Clone> IsZero for Corners<T> {
&& 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 geometry::*;
pub use gpui3_macros::*;
pub use image_cache::*;
pub use platform::*;
pub use refineable::*;
pub use scene::*;

View file

@ -5,10 +5,10 @@ mod mac;
#[cfg(any(test, feature = "test"))]
mod test;
use crate::image_cache::RenderImageParams;
use crate::{
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlyphId, Pixels,
Point, RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size,
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlobalPixels,
GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
ShapedLine, SharedString, Size,
};
use anyhow::anyhow;
use async_task::Runnable;
@ -16,7 +16,6 @@ use futures::channel::oneshot;
use seahash::SeaHasher;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::ffi::c_void;
use std::hash::{Hash, Hasher};
use std::{
any::Any,
@ -27,7 +26,6 @@ use std::{
str::FromStr,
sync::Arc,
};
use uuid::Uuid;
pub use events::*;
pub use keystroke::*;
@ -44,6 +42,7 @@ pub(crate) fn current_platform() -> Arc<dyn Platform> {
pub trait Platform: 'static {
fn executor(&self) -> Executor;
fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker>;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
@ -54,8 +53,8 @@ pub trait Platform: 'static {
fn hide_other_apps(&self);
fn unhide_other_apps(&self);
fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>;
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn main_window(&self) -> Option<AnyWindowHandle>;
fn open_window(
&self,
@ -97,23 +96,22 @@ pub trait Platform: 'static {
fn delete_credentials(&self, url: &str) -> Result<()>;
}
pub trait PlatformScreen: Debug {
fn id(&self) -> Option<ScreenId>;
fn handle(&self) -> PlatformScreenHandle;
pub trait PlatformDisplay: Debug {
fn id(&self) -> DisplayId;
fn as_any(&self) -> &dyn Any;
fn bounds(&self) -> Bounds<Pixels>;
fn content_bounds(&self) -> Bounds<Pixels>;
fn bounds(&self) -> Bounds<GlobalPixels>;
}
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 {
write!(f, "PlatformScreenHandle({:p})", self.0)
write!(f, "DisplayId({})", self.0)
}
}
unsafe impl Send for PlatformScreenHandle {}
unsafe impl Send for DisplayId {}
pub trait PlatformWindow {
fn bounds(&self) -> WindowBounds;
@ -121,7 +119,7 @@ pub trait PlatformWindow {
fn scale_factor(&self) -> f32;
fn titlebar_height(&self) -> Pixels;
fn appearance(&self) -> WindowAppearance;
fn screen(&self) -> Rc<dyn PlatformScreen>;
fn display(&self) -> Rc<dyn PlatformDisplay>;
fn mouse_position(&self) -> Point<Pixels>;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
@ -158,6 +156,16 @@ pub trait PlatformDispatcher: Send + Sync {
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 {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
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>>;
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScreenId(pub(crate) Uuid);
#[derive(Debug)]
pub struct WindowOptions {
pub bounds: WindowBounds,
@ -278,7 +283,7 @@ pub struct WindowOptions {
pub show: bool,
pub kind: WindowKind,
pub is_movable: bool,
pub screen: Option<PlatformScreenHandle>,
pub display_id: Option<DisplayId>,
}
impl Default for WindowOptions {
@ -295,7 +300,7 @@ impl Default for WindowOptions {
show: true,
kind: WindowKind::Normal,
is_movable: true,
screen: None,
display_id: None,
}
}
}
@ -332,7 +337,7 @@ pub enum WindowBounds {
Fullscreen,
#[default]
Maximized,
Fixed(Bounds<Pixels>),
Fixed(Bounds<GlobalPixels>),
}
#[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
///! an origin at the bottom left of the main display.
mod dispatcher;
mod display;
mod display_linker;
mod events;
mod metal_atlas;
mod metal_renderer;
mod open_type;
mod platform;
mod screen;
mod text_system;
mod window;
mod window_appearence;
use crate::{px, size, Pixels, Size};
use crate::{px, size, GlobalPixels, Pixels, Size};
use anyhow::anyhow;
use cocoa::{
base::{id, nil},
@ -31,9 +32,10 @@ use std::{
};
pub use dispatcher::*;
pub use display::*;
pub use display_linker::*;
pub use metal_atlas::*;
pub use platform::*;
pub use screen::*;
pub use text_system::*;
pub use window::*;
@ -119,23 +121,33 @@ pub trait NSRectExt {
fn intersects(&self, other: Self) -> bool;
}
impl NSRectExt for NSRect {
fn size(&self) -> Size<Pixels> {
size(px(self.size.width as f32), px(self.size.height as f32))
}
fn intersects(&self, other: Self) -> bool {
self.size.width > 0.
&& self.size.height > 0.
&& other.size.width > 0.
&& other.size.height > 0.
&& self.origin.x <= other.origin.x + other.size.width
&& self.origin.x + self.size.width >= other.origin.x
&& self.origin.y <= other.origin.y + other.size.height
&& self.origin.y + self.size.height >= other.origin.y
impl From<NSRect> for Size<Pixels> {
fn from(rect: NSRect) -> Self {
let NSSize { width, height } = rect.size;
size(width.into(), height.into())
}
}
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!
#[allow(unused)]
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::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
Quad, Scene, Size,
PrimitiveBatch, Quad, Scene, Shadow, Size, Underline,
};
use cocoa::{
base::{NO, YES},
@ -17,7 +17,9 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
pub struct MetalRenderer {
layer: metal::MetalLayer,
command_queue: CommandQueue,
shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
@ -82,6 +84,14 @@ impl MetalRenderer {
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(
&device,
&library,
@ -90,6 +100,14 @@ impl MetalRenderer {
"quad_fragment",
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(
&device,
&library,
@ -113,7 +131,9 @@ impl MetalRenderer {
Self {
layer,
command_queue,
shadows_pipeline_state,
quads_pipeline_state,
underlines_pipeline_state,
monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state,
unit_vertices,
@ -131,8 +151,6 @@ impl MetalRenderer {
}
pub fn draw(&mut self, scene: &mut Scene) {
dbg!("draw scene");
let layer = self.layer.clone();
let viewport_size = layer.drawable_size();
let viewport_size: Size<DevicePixels> = size(
@ -174,18 +192,28 @@ impl MetalRenderer {
});
let mut instance_offset = 0;
for layer in scene.layers() {
for batch in layer.batches() {
for batch in scene.batches() {
match batch {
crate::PrimitiveBatch::Quads(quads) => {
self.draw_quads(
quads,
PrimitiveBatch::Shadows(shadows) => {
self.draw_shadows(
shadows,
&mut instance_offset,
viewport_size,
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,
sprites,
} => {
@ -197,7 +225,7 @@ impl MetalRenderer {
command_encoder,
);
}
crate::PrimitiveBatch::PolychromeSprites {
PrimitiveBatch::PolychromeSprites {
texture_id,
sprites,
} => {
@ -211,7 +239,6 @@ impl MetalRenderer {
}
}
}
}
command_encoder.end_encoding();
@ -225,6 +252,66 @@ impl MetalRenderer {
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(
&mut self,
quads: &[Quad],
@ -281,6 +368,66 @@ impl MetalRenderer {
*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(
&mut self,
texture_id: AtlasTextureId,
@ -464,6 +611,13 @@ fn align_offset(offset: &mut usize) {
*offset = ((*offset + 255) / 256) * 256;
}
#[repr(C)]
enum ShadowInputIndex {
Vertices = 0,
Shadows = 1,
ViewportSize = 2,
}
#[repr(C)]
enum QuadInputIndex {
Vertices = 0,
@ -471,6 +625,13 @@ enum QuadInputIndex {
ViewportSize = 2,
}
#[repr(C)]
enum UnderlineInputIndex {
Vertices = 0,
Underlines = 1,
ViewportSize = 2,
}
#[repr(C)]
enum SpriteInputIndex {
Vertices = 0,

View file

@ -1,8 +1,9 @@
use super::BoolExt;
use crate::{
AnyWindowHandle, ClipboardItem, CursorStyle, Event, Executor, MacDispatcher, MacScreen,
MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem,
PlatformWindow, Result, ScreenId, SemanticVersion, WindowOptions,
AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher,
MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
PlatformDisplay, PlatformDisplayLinker, PlatformTextSystem, PlatformWindow, Result,
SemanticVersion, WindowOptions,
};
use anyhow::anyhow;
use block::ConcreteBlock;
@ -347,6 +348,10 @@ impl Platform for MacPlatform {
self.0.lock().executor.clone()
}
fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker> {
Arc::new(MacDisplayLinker::new())
}
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.0.lock().text_system.clone()
}
@ -455,21 +460,21 @@ impl Platform for MacPlatform {
}
}
fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
MacScreen::all()
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
MacDisplay::all()
.into_iter()
.map(|screen| Rc::new(screen) as Rc<_>)
.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> {
// 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> {
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> {
let state = self.0.lock();
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<()> {
let url = CFString::from(url);
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);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
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 {
float4 position [[position]];
@ -29,8 +33,8 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
[[buffer(QuadInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Quad quad = quads[quad_id];
float4 device_position = to_device_position(unit_vertex, quad.bounds,
quad.clip_bounds, viewport_size);
float4 device_position = to_device_position(
unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size);
float4 background_color = hsla_to_rgba(quad.background);
float4 border_color = hsla_to_rgba(quad.border_color);
return QuadVertexOutput{device_position, background_color, border_color,
@ -109,6 +113,133 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
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 {
float4 position [[position]];
float2 tile_position;
@ -127,9 +258,10 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_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.
float4 device_position = to_device_position(
unit_vertex, sprite.bounds, sprite.bounds, viewport_size);
// Don't apply content mask at the vertex level because we don't have time
// to make sampling from the texture match the mask.
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);
float4 color = hsla_to_rgba(sprite.color);
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
@ -145,11 +277,8 @@ fragment float4 monochrome_sprite_fragment(
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float clip_distance = quad_sdf(
input.position.xy,
sprite.content_mask.bounds,
Corners_ScaledPixels { 0., 0., 0., 0. }
);
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
Corners_ScaledPixels{0., 0., 0., 0.});
float4 color = input.color;
color.a *= sample.a * saturate(0.5 - clip_distance);
return color;
@ -172,9 +301,10 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_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.
float4 device_position = to_device_position(
unit_vertex, sprite.bounds, sprite.bounds, viewport_size);
// Don't apply content mask at the vertex level because we don't have time
// to make sampling from the texture match the mask.
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);
return PolychromeSpriteVertexOutput{device_position, tile_position,
sprite_id};
@ -189,8 +319,10 @@ fragment float4 polychrome_sprite_fragment(
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float quad_distance = 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 quad_distance =
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);
float4 color = sample;
@ -307,3 +439,27 @@ float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
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::{
point, px, size, AnyWindowHandle, Bounds, Event, Executor, KeyDownEvent, Keystroke, MacScreen,
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent,
NSRectExt, Pixels, PlatformAtlas, PlatformInputHandler, PlatformScreen, PlatformWindow, Point,
Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
WindowPromptLevel,
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor,
GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
MouseDownEvent, MouseMovedEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
};
use block::ConcreteBlock;
use cocoa::{
@ -14,7 +14,9 @@ use cocoa::{
NSWindowStyleMask, NSWindowTitleVisibility,
},
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 ctor::ctor;
@ -365,7 +367,7 @@ impl MacWindowState {
}
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 {
WindowBounds::Maximized
} else {
@ -374,10 +376,10 @@ impl MacWindowState {
}
}
fn frame(&self) -> Bounds<Pixels> {
fn frame(&self) -> Bounds<GlobalPixels> {
unsafe {
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]
}
};
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_(
NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
style_mask,
NSBackingStoreBuffered,
NO,
options
.screen
.map(|screen| MacScreen::from_handle(screen).native_screen)
.unwrap_or(nil),
target_screen,
);
assert!(!native_window.is_null());
@ -462,13 +482,13 @@ impl MacWindow {
native_window.setFrame_display_(screen.visibleFrame(), YES);
}
WindowBounds::Fixed(bounds) => {
let bounds = MacScreen::screen_bounds_to_native(bounds);
let screen_bounds = screen.visibleFrame();
if bounds.intersects(screen_bounds) {
native_window.setFrame_display_(bounds, YES);
let display_bounds = display.bounds();
let frame = if bounds.intersects(&display_bounds) {
display_bounds_to_native(bounds)
} 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 {
Rc::new(MacScreen {
native_screen: self.0.as_ref().lock().native_window.screen(),
})
let screen = self.0.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 crate::{Executor, ScreenId};
use crate::{DisplayId, Executor};
pub struct TestPlatform;
@ -15,6 +15,10 @@ impl Platform for TestPlatform {
unimplemented!()
}
fn display_linker(&self) -> std::sync::Arc<dyn crate::PlatformDisplayLinker> {
unimplemented!()
}
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
unimplemented!()
}
@ -47,11 +51,11 @@ impl Platform for TestPlatform {
unimplemented!()
}
fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
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!()
}

View file

@ -1,18 +1,27 @@
use std::{iter::Peekable, mem, slice};
use super::{Bounds, Hsla, Point};
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
use crate::{
AtlasTextureId, AtlasTile, Bounds, Corners, Edges, Hsla, Point, ScaledContentMask, ScaledPixels,
};
use collections::BTreeMap;
use etagere::euclid::{Point3D, Vector3D};
use plane_split::{BspSplitter, Polygon as BspPolygon};
use smallvec::SmallVec;
use std::{iter::Peekable, mem, slice};
// Exported to metal
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)]
pub struct Scene {
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 {
@ -20,6 +29,11 @@ impl Scene {
Scene {
scale_factor,
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 {
scale_factor: self.scale_factor,
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>) {
let layer = self.layers.entry(stacking_order).or_default();
pub fn insert(&mut self, layer_id: StackingOrder, primitive: impl Into<Primitive>) {
let next_id = self.layers.len() as LayerId;
let layer_id = *self.layers.entry(layer_id).or_insert(next_id);
let primitive = primitive.into();
match primitive {
Primitive::Quad(quad) => {
layer.quads.push(quad);
Primitive::Shadow(mut shadow) => {
shadow.order = layer_id;
self.shadows.push(shadow);
}
Primitive::MonochromeSprite(sprite) => {
layer.monochrome_sprites.push(sprite);
Primitive::Quad(mut quad) => {
quad.order = layer_id;
self.quads.push(quad);
}
Primitive::PolychromeSprite(sprite) => {
layer.polychrome_sprites.push(sprite);
Primitive::Underline(mut underline) => {
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> {
self.layers.values_mut()
pub(crate) fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
// 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)]
pub(crate) struct SceneLayer {
pub quads: Vec<Quad>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
}
// Add all primitives to the BSP splitter to determine draw order
// todo!("reuse the same splitter")
let mut splitter = BspSplitter::new();
impl SceneLayer {
pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
for (ix, shadow) in self.shadows.iter().enumerate() {
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.underlines.sort_unstable();
self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable();
BatchIterator {
shadows: &self.shadows,
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
quads: &self.quads,
quads_start: 0,
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_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
@ -82,6 +174,12 @@ struct BatchIterator<'a> {
quads: &'a [Quad],
quads_start: usize,
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_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
@ -94,50 +192,92 @@ impl<'a> Iterator for BatchIterator<'a> {
type Item = PrimitiveBatch<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut kinds_and_orders = [
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
let mut orders_and_kinds = [
(
PrimitiveKind::MonochromeSprite,
self.monochrome_sprites_iter.peek().map(|s| s.order),
self.shadows_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),
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 second = kinds_and_orders[1];
let (batch_kind, max_order) = if first.1.is_some() {
(first.0, second.1.unwrap_or(u32::MAX))
let first = orders_and_kinds[0];
let second = orders_and_kinds[1];
let (batch_kind, max_order) = if first.0.is_some() {
(first.1, second.0.unwrap_or(u32::MAX))
} else {
return None;
};
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 => {
let quads_start = self.quads_start;
let quads_end = quads_start
+ self
let mut quads_end = quads_start;
while self
.quads_iter
.by_ref()
.take_while(|quad| quad.order <= max_order)
.count();
.next_if(|quad| quad.order <= max_order)
.is_some()
{
quads_end += 1;
}
self.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 => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start;
let sprites_end = sprites_start
+ self
let mut sprites_end = sprites_start;
while self
.monochrome_sprites_iter
.by_ref()
.take_while(|sprite| {
.next_if(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id
})
.count();
.is_some()
{
sprites_end += 1;
}
self.monochrome_sprites_start = sprites_end;
Some(PrimitiveBatch::MonochromeSprites {
texture_id,
@ -147,14 +287,16 @@ impl<'a> Iterator for BatchIterator<'a> {
PrimitiveKind::PolychromeSprite => {
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.polychrome_sprites_start;
let sprites_end = sprites_start
+ self
let mut sprites_end = self.polychrome_sprites_start;
while self
.polychrome_sprites_iter
.by_ref()
.take_while(|sprite| {
.next_if(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id
})
.count();
.is_some()
{
sprites_end += 1;
}
self.polychrome_sprites_start = sprites_end;
Some(PrimitiveBatch::PolychromeSprites {
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 {
Shadow,
#[default]
Quad,
Underline,
MonochromeSprite,
PolychromeSprite,
}
#[derive(Clone, Debug)]
pub enum Primitive {
Shadow(Shadow),
Quad(Quad),
Underline(Underline),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
}
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
Quads(&'a [Quad]),
Underlines(&'a [Underline]),
MonochromeSprites {
texture_id: AtlasTextureId,
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)]
pub struct Quad {
pub order: u32,
pub order: u32, // Initially a LayerId, then a DrawOrder.
pub bounds: Bounds<ScaledPixels>,
pub clip_bounds: Bounds<ScaledPixels>,
pub clip_corner_radii: Corners<ScaledPixels>,
pub content_mask: ScaledContentMask,
pub background: Hsla,
pub border_color: Hsla,
pub corner_radii: Corners<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 {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
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)]
#[repr(C)]
pub struct MonochromeSprite {
@ -303,3 +494,76 @@ impl From<PolychromeSprite> for Primitive {
#[derive(Copy, Clone, Debug)]
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::{
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
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,
};
use refineable::Refineable;
use smallvec::SmallVec;
pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
@ -89,10 +90,21 @@ pub struct Style {
#[refineable]
pub corner_radii: Corners<AbsoluteLength>,
/// Box Shadow of the element
pub box_shadow: SmallVec<[BoxShadow; 2]>,
/// TEXT
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)]
#[refineable(debug)]
pub struct TextStyle {
@ -229,32 +241,55 @@ impl 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 scale = cx.scale_factor();
let background_color = self.fill.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() {
let layer_id = cx.current_layer_id();
for shadow in &self.box_shadow {
let content_mask = cx.content_mask();
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(
layer_id,
Quad {
order,
bounds: bounds.scale(scale),
clip_bounds: bounds.scale(scale), // todo!
clip_corner_radii: self
Shadow {
order: 0,
bounds: shadow_bounds.scale(scale),
content_mask: content_mask.scale(scale),
corner_radii: self
.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(),
border_color: self.border_color.unwrap_or_default(),
corner_radii: self
.corner_radii
.map(|length| length.to_pixels(rem_size).scale(scale)),
border_widths: self
.border_widths
.map(|length| length.to_pixels(rem_size).scale(scale)),
.to_pixels(bounds.size, rem_size)
.scale(scale),
border_widths: self.border_widths.to_pixels(rem_size).scale(scale),
},
);
});
}
}
@ -298,6 +333,7 @@ impl Default for Style {
fill: None,
border_color: None,
corner_radii: Corners::default(),
box_shadow: Default::default(),
text: TextStyleRefinement::default(),
}
}
@ -308,7 +344,7 @@ impl Default for Style {
pub struct UnderlineStyle {
pub thickness: Pixels,
pub color: Option<Hsla>,
pub squiggly: bool,
pub wavy: bool,
}
#[derive(Clone, Debug)]

View file

@ -1,9 +1,11 @@
use crate::{
self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent,
Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement,
self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
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!();
fn h(mut self, height: Length) -> Self {
@ -147,6 +149,103 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
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> {
let style: &mut StyleRefinement = self.declared_style();
&mut style.text
@ -206,6 +305,69 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
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 {
self.text_style()
.get_or_insert_with(Default::default)

View file

@ -1,6 +1,5 @@
use super::{
AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
Style,
AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Result, Size, Style,
};
use collections::HashMap;
use std::fmt::Debug;
@ -14,7 +13,7 @@ use taffy::{
pub struct TaffyLayoutEngine {
taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layouts: HashMap<LayoutId, Layout>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
}
impl TaffyLayoutEngine {
@ -22,7 +21,7 @@ impl TaffyLayoutEngine {
TaffyLayoutEngine {
taffy: Taffy::new(),
children_to_parents: HashMap::default(),
absolute_layouts: HashMap::default(),
absolute_layout_bounds: HashMap::default(),
}
}
@ -127,19 +126,24 @@ impl TaffyLayoutEngine {
Ok(())
}
pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
if let Some(layout) = self.absolute_layouts.get(&id).cloned() {
pub fn layout_bounds(&mut self, id: LayoutId) -> Result<Bounds<Pixels>> {
if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
return Ok(layout);
}
let mut relative_layout: Layout = self.taffy.layout(id.into()).map(Into::into)?;
if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
let parent_layout = self.layout(parent_id)?;
relative_layout.bounds.origin += parent_layout.bounds.origin;
}
self.absolute_layouts.insert(id, relative_layout.clone());
let layout = self.taffy.layout(id.into())?;
let mut bounds = Bounds {
origin: layout.location.into(),
size: layout.size.into(),
};
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)
}
}
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::{
black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine,
ShapedRun, UnderlineStyle, WindowContext,
};
use anyhow::Result;
use smallvec::SmallVec;
@ -90,15 +90,14 @@ impl Line {
}
}
// todo!
pub fn paint(
&self,
layout: &Layout,
visible_bounds: Bounds<Pixels>,
bounds: Bounds<Pixels>,
visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels,
cx: &mut WindowContext,
) -> Result<()> {
let origin = layout.bounds.origin;
let origin = bounds.origin;
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
@ -135,9 +134,11 @@ impl Line {
origin.y + baseline_offset.y + (self.layout.descent * 0.618),
),
UnderlineStyle {
color: style_run.underline.color,
color: Some(
style_run.underline.color.unwrap_or(style_run.color),
),
thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly,
wavy: style_run.underline.wavy,
},
));
}
@ -154,22 +155,19 @@ impl Line {
continue;
}
if let Some((_underline_origin, _underline_style)) = finished_underline {
todo!()
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
if glyph.is_emoji {
cx.paint_emoji(
glyph_origin,
layout.order,
run.font_id,
glyph.id,
self.layout.font_size,
)?;
cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
} else {
cx.paint_glyph(
glyph_origin,
layout.order,
run.font_id,
glyph.id,
self.layout.font_size,
@ -179,15 +177,13 @@ impl Line {
}
}
if let Some((_underline_start, _underline_style)) = underline.take() {
let _line_end_x = origin.x + self.layout.width;
// cx.scene().push_underline(Underline {
// origin: underline_start,
// width: line_end_x - underline_start.x,
// color: underline_style.color,
// thickness: underline_style.thickness.into(),
// squiggly: underline_style.squiggly,
// });
if let Some((underline_start, underline_style)) = underline.take() {
let line_end_x = origin.x + self.layout.width;
cx.paint_underline(
underline_start,
line_end_x - underline_start.x,
&underline_style,
)?;
}
Ok(())
@ -196,7 +192,7 @@ impl Line {
pub fn paint_wrapped(
&self,
origin: Point<Pixels>,
_visible_bounds: Bounds<Pixels>,
_visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels,
boundaries: &[ShapedBoundary],
cx: &mut WindowContext,
@ -221,14 +217,12 @@ impl Line {
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
{
boundaries.next();
if let Some((_underline_origin, _underline_style)) = underline.take() {
// cx.scene().push_underline(Underline {
// origin: underline_origin,
// width: glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(),
// color: underline_style.color.unwrap(),
// squiggly: underline_style.squiggly,
// });
if let Some((underline_origin, underline_style)) = underline.take() {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
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),
),
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 {
// cx.scene().push_underline(Underline {
// origin: underline_origin,
// width: glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(),
// color: underline_style.color.unwrap(),
// squiggly: underline_style.squiggly,
// });
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
let text_system = cx.text_system();
@ -306,15 +298,13 @@ impl Line {
}
}
if let Some((_underline_origin, _underline_style)) = underline.take() {
// let line_end_x = glyph_origin.x + self.layout.width - prev_position;
// cx.scene().push_underline(Underline {
// origin: underline_origin,
// width: line_end_x - underline_origin.x,
// thickness: underline_style.thickness.into(),
// color: underline_style.color,
// squiggly: underline_style.squiggly,
// });
if let Some((underline_origin, underline_style)) = underline.take() {
let line_end_x = glyph_origin.x + self.layout.width - prev_position;
cx.paint_underline(
underline_origin,
line_end_x - underline_origin.x,
&underline_style,
)?;
}
Ok(())

View file

@ -1,7 +1,7 @@
use parking_lot::Mutex;
use crate::{
AnyElement, Element, Handle, IntoAnyElement, Layout, LayoutId, Result, ViewContext,
AnyElement, Bounds, Element, Handle, IntoAnyElement, LayoutId, Pixels, Result, ViewContext,
WindowContext,
};
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(
&mut self,
_: Layout,
_: Bounds<Pixels>,
_: &mut Self::State,
element: &mut Self::FrameState,
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 paint(
&mut self,
layout: Layout,
bounds: Bounds<Pixels>,
element: &mut dyn Any,
cx: &mut WindowContext,
) -> 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| {
let element = element.downcast_mut::<AnyElement<S>>().unwrap();
element.paint(state, None, cx)
@ -124,12 +129,12 @@ impl<S: 'static> Element for AnyView<S> {
fn paint(
&mut self,
layout: Layout,
bounds: Bounds<Pixels>,
_: &mut (),
element: &mut Box<dyn Any>,
cx: &mut ViewContext<Self::State>,
) -> 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::{
image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId,
GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly,
MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference,
RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style,
TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use smallvec::SmallVec;
@ -16,13 +17,14 @@ pub struct AnyWindow {}
pub struct Window {
handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
pub(crate) display_id: DisplayId, // todo!("make private again?")
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
content_size: Size<Pixels>,
layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView<()>>,
mouse_position: Point<Pixels>,
current_layer_id: LayerId,
current_stacking_order: StackingOrder,
content_mask_stack: Vec<ContentMask>,
pub(crate) scene: Scene,
pub(crate) dirty: bool,
@ -35,6 +37,7 @@ impl Window {
cx: &mut MainThread<AppContext>,
) -> Self {
let platform_window = cx.platform().open_window(handle, options);
let display_id = platform_window.display().id();
let sprite_atlas = platform_window.sprite_atlas();
let mouse_position = platform_window.mouse_position();
let content_size = platform_window.content_size();
@ -46,6 +49,12 @@ impl Window {
cx.update_window(handle, |cx| {
cx.window.scene = Scene::new(scale_factor);
cx.window.content_size = content_size;
cx.window.display_id = cx
.window
.platform_window
.borrow_on_main_thread()
.display()
.id();
cx.window.dirty = true;
})
.log_err();
@ -57,13 +66,14 @@ impl Window {
Window {
handle,
platform_window,
display_id,
sprite_atlas,
rem_size: px(16.),
content_size,
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
mouse_position,
current_layer_id: SmallVec::new(),
current_stacking_order: SmallVec::new(),
content_mask_stack: Vec::new(),
scene: Scene::new(scale_factor),
dirty: true,
@ -89,7 +99,7 @@ impl ContentMask {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Default, Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct ScaledContentMask {
bounds: Bounds<ScaledPixels>,
@ -133,6 +143,45 @@ impl<'a, 'w> WindowContext<'a, 'w> {
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>(
&mut self,
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)
}
pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Result<Bounds<Pixels>> {
Ok(self
.window
.layout_engine
.layout(layout_id)
.layout_bounds(layout_id)
.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 {
self.window.current_layer_id.push(order);
self.window.current_stacking_order.push(order);
let result = f(self);
self.window.current_layer_id.pop();
self.window.current_stacking_order.pop();
result
}
pub fn current_layer_id(&self) -> LayerId {
self.window.current_layer_id.clone()
pub fn current_stacking_order(&self) -> StackingOrder {
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(
&mut self,
origin: Point<Pixels>,
order: u32,
font_id: FontId,
glyph_id: GlyphId,
font_size: Pixels,
@ -237,7 +317,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let raster_bounds = self.text_system().raster_bounds(&params)?;
if !raster_bounds.is_zero() {
let layer_id = self.current_layer_id();
let layer_id = self.current_stacking_order();
let tile =
self.window
.sprite_atlas
@ -254,7 +334,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.scene.insert(
layer_id,
MonochromeSprite {
order,
order: 0,
bounds,
content_mask,
color,
@ -268,7 +348,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
pub fn paint_emoji(
&mut self,
origin: Point<Pixels>,
order: u32,
font_id: FontId,
glyph_id: GlyphId,
font_size: Pixels,
@ -287,7 +366,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let raster_bounds = self.text_system().raster_bounds(&params)?;
if !raster_bounds.is_zero() {
let layer_id = self.current_layer_id();
let layer_id = self.current_stacking_order();
let tile =
self.window
.sprite_atlas
@ -304,7 +383,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.scene.insert(
layer_id,
PolychromeSprite {
order,
order: 0,
bounds,
corner_radii: Default::default(),
content_mask,
@ -319,7 +398,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
pub fn paint_svg(
&mut self,
bounds: Bounds<Pixels>,
order: u32,
path: SharedString,
color: Hsla,
) -> Result<()> {
@ -333,7 +411,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
.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 =
self.window
.sprite_atlas
@ -346,7 +424,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.scene.insert(
layer_id,
MonochromeSprite {
order,
order: 0,
bounds,
content_mask,
color,
@ -361,7 +439,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
&mut self,
bounds: Bounds<Pixels>,
corner_radii: Corners<Pixels>,
order: u32,
data: Arc<ImageData>,
grayscale: bool,
) -> Result<()> {
@ -369,7 +446,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let bounds = bounds.scale(scale_factor);
let params = RenderImageParams { image_id: data.id };
let layer_id = self.current_layer_id();
let order = self.current_stacking_order();
let tile = self
.window
.sprite_atlas
@ -380,9 +457,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let corner_radii = corner_radii.scale(scale_factor);
self.window.scene.insert(
layer_id,
PolychromeSprite {
order,
PolychromeSprite {
order: 0,
bounds,
content_mask,
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 available_space = cx.window.content_size.map(Into::into);
let started_at = std::time::Instant::now();
cx.window
.layout_engine
.compute_layout(root_layout_id, available_space)?;
println!("compute_layout took {:?}", started_at.elapsed());
let layout = cx.window.layout_engine.layout(root_layout_id)?;
let layout = cx.window.layout_engine.layout_bounds(root_layout_id)?;
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
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)
}
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>(
&mut self,
handle: &Handle<E>,
@ -726,9 +815,3 @@ pub struct AnyWindowHandle {
pub(crate) id: WindowId,
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(),
),
),
@ -168,7 +168,8 @@ impl CollabPanel {
.uri(avatar_uri)
.size_3p5()
.rounded_full()
.fill(theme.middle.positive.default.foreground),
.fill(theme.middle.positive.default.foreground)
.shadow_md(),
)
.child(label),
)

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use std::{collections::HashMap, fmt};
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};
@ -162,7 +162,7 @@ impl<E: Element> Element for Themed<E> {
fn paint(
&mut self,
layout: Layout,
bounds: Bounds<Pixels>,
state: &mut Self::State,
frame_state: &mut Self::FrameState,
cx: &mut ViewContext<Self::State>,
@ -171,7 +171,7 @@ impl<E: Element> Element for Themed<E> {
Self: Sized,
{
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 gpui3::serde_json::{self, json};
pub fn rose_pine_dawn() -> Theme {
pub fn rose_pine() -> Theme {
serde_json::from_value(json! {
{
"name": "Rosé Pine",
@ -843,3 +843,844 @@ pub fn rose_pine_dawn() -> Theme {
})
.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> {
let theme = rose_pine_dawn();
themed(rose_pine_dawn(), cx, |cx| {
let theme = theme(cx);
div()
.size_full()
.v_stack()
@ -201,7 +200,13 @@ impl Titlebar {
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
.child(div().text_sm().child("branch")),
.child(
div()
.text_sm()
.text_decoration_1()
.text_decoration_wavy()
.child("branch"),
),
),
)
}