Merge branch 'gpui2' into marshall/gpui2-playground
This commit is contained in:
commit
7fd35d68bb
34 changed files with 2710 additions and 620 deletions
|
@ -7,7 +7,7 @@ description = "The next version of Zed's GPU-accelerated UI framework"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test = ["backtrace", "dhat", "env_logger", "collections/test-support"]
|
test = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/gpui3.rs"
|
path = "src/gpui3.rs"
|
||||||
|
@ -66,6 +66,7 @@ dhat = "0.3"
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
png = "0.16"
|
png = "0.16"
|
||||||
simplelog = "0.9"
|
simplelog = "0.9"
|
||||||
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.65.1"
|
bindgen = "0.65.1"
|
||||||
|
|
|
@ -50,7 +50,11 @@ fn generate_shader_bindings() -> PathBuf {
|
||||||
"ScaledContentMask".into(),
|
"ScaledContentMask".into(),
|
||||||
"Uniforms".into(),
|
"Uniforms".into(),
|
||||||
"AtlasTile".into(),
|
"AtlasTile".into(),
|
||||||
|
"ShadowInputIndex".into(),
|
||||||
|
"Shadow".into(),
|
||||||
"QuadInputIndex".into(),
|
"QuadInputIndex".into(),
|
||||||
|
"Underline".into(),
|
||||||
|
"UnderlineInputIndex".into(),
|
||||||
"Quad".into(),
|
"Quad".into(),
|
||||||
"SpriteInputIndex".into(),
|
"SpriteInputIndex".into(),
|
||||||
"MonochromeSprite".into(),
|
"MonochromeSprite".into(),
|
||||||
|
|
|
@ -8,9 +8,9 @@ pub use model_context::*;
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, image_cache::ImageCache, AssetSource, Context, Executor, LayoutId,
|
current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, LayoutId,
|
||||||
MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle,
|
MainThread, MainThreadOnly, Platform, PlatformDisplayLinker, RootView, SvgRenderer, Task,
|
||||||
TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
|
TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
|
@ -51,18 +51,19 @@ impl App {
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let executor = platform.executor();
|
let executor = platform.executor();
|
||||||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
|
||||||
let entities = EntityMap::new();
|
let entities = EntityMap::new();
|
||||||
let unit_entity = entities.insert(entities.reserve(), ());
|
let unit_entity = entities.insert(entities.reserve(), ());
|
||||||
Self(Arc::new_cyclic(|this| {
|
Self(Arc::new_cyclic(|this| {
|
||||||
Mutex::new(AppContext {
|
Mutex::new(AppContext {
|
||||||
this: this.clone(),
|
this: this.clone(),
|
||||||
|
text_system: Arc::new(TextSystem::new(platform.text_system())),
|
||||||
|
pending_updates: 0,
|
||||||
|
display_linker: platform.display_linker(),
|
||||||
|
next_frame_callbacks: Default::default(),
|
||||||
platform: MainThreadOnly::new(platform, executor.clone()),
|
platform: MainThreadOnly::new(platform, executor.clone()),
|
||||||
executor,
|
executor,
|
||||||
text_system,
|
|
||||||
svg_renderer: SvgRenderer::new(asset_source),
|
svg_renderer: SvgRenderer::new(asset_source),
|
||||||
image_cache: ImageCache::new(http_client),
|
image_cache: ImageCache::new(http_client),
|
||||||
pending_updates: 0,
|
|
||||||
text_style_stack: Vec::new(),
|
text_style_stack: Vec::new(),
|
||||||
state_stacks_by_type: HashMap::default(),
|
state_stacks_by_type: HashMap::default(),
|
||||||
unit_entity,
|
unit_entity,
|
||||||
|
@ -90,12 +91,15 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
|
type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
|
||||||
|
type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
|
||||||
|
|
||||||
pub struct AppContext {
|
pub struct AppContext {
|
||||||
this: Weak<Mutex<AppContext>>,
|
this: Weak<Mutex<AppContext>>,
|
||||||
platform: MainThreadOnly<dyn Platform>,
|
platform: MainThreadOnly<dyn Platform>,
|
||||||
text_system: Arc<TextSystem>,
|
text_system: Arc<TextSystem>,
|
||||||
pending_updates: usize,
|
pending_updates: usize,
|
||||||
|
pub(crate) display_linker: Arc<dyn PlatformDisplayLinker>,
|
||||||
|
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
||||||
pub(crate) executor: Executor,
|
pub(crate) executor: Executor,
|
||||||
pub(crate) svg_renderer: SvgRenderer,
|
pub(crate) svg_renderer: SvgRenderer,
|
||||||
pub(crate) image_cache: ImageCache,
|
pub(crate) image_cache: ImageCache,
|
||||||
|
@ -145,7 +149,6 @@ impl AppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush_effects(&mut self) {
|
fn flush_effects(&mut self) {
|
||||||
dbg!("flush effects");
|
|
||||||
while let Some(effect) = self.pending_effects.pop_front() {
|
while let Some(effect) = self.pending_effects.pop_front() {
|
||||||
match effect {
|
match effect {
|
||||||
Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),
|
Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),
|
||||||
|
|
|
@ -60,9 +60,19 @@ pub struct AsyncWindowContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncWindowContext {
|
impl AsyncWindowContext {
|
||||||
pub fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
|
pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
|
||||||
Self { app, window }
|
Self { app, window }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update<R>(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
|
||||||
|
self.app.update_window(self.window, update)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
|
||||||
|
self.app
|
||||||
|
.update_window(self.window, |cx| cx.on_next_frame(f))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for AsyncWindowContext {
|
impl Context for AsyncWindowContext {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use super::{Layout, LayoutId, Pixels, Point, Result, ViewContext};
|
use crate::Bounds;
|
||||||
|
|
||||||
|
use super::{LayoutId, Pixels, Point, Result, ViewContext};
|
||||||
pub(crate) use smallvec::SmallVec;
|
pub(crate) use smallvec::SmallVec;
|
||||||
|
|
||||||
pub trait Element: 'static {
|
pub trait Element: 'static {
|
||||||
|
@ -13,7 +15,7 @@ pub trait Element: 'static {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
frame_state: &mut Self::FrameState,
|
frame_state: &mut Self::FrameState,
|
||||||
cx: &mut ViewContext<Self::State>,
|
cx: &mut ViewContext<Self::State>,
|
||||||
|
@ -90,7 +92,7 @@ enum ElementRenderPhase<S> {
|
||||||
frame_state: S,
|
frame_state: S,
|
||||||
},
|
},
|
||||||
Painted {
|
Painted {
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
frame_state: S,
|
frame_state: S,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -130,24 +132,23 @@ impl<E: Element> ElementObject<E::State> for RenderedElement<E> {
|
||||||
layout_id,
|
layout_id,
|
||||||
mut frame_state,
|
mut frame_state,
|
||||||
} => {
|
} => {
|
||||||
let mut layout = cx.layout(layout_id)?.clone();
|
let mut bounds = cx.layout_bounds(layout_id)?.clone();
|
||||||
offset.map(|offset| layout.bounds.origin += offset);
|
offset.map(|offset| bounds.origin += offset);
|
||||||
self.element
|
self.element.paint(bounds, state, &mut frame_state, cx)?;
|
||||||
.paint(layout.clone(), state, &mut frame_state, cx)?;
|
|
||||||
ElementRenderPhase::Painted {
|
ElementRenderPhase::Painted {
|
||||||
layout,
|
bounds,
|
||||||
frame_state,
|
frame_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementRenderPhase::Painted {
|
ElementRenderPhase::Painted {
|
||||||
layout,
|
bounds,
|
||||||
mut frame_state,
|
mut frame_state,
|
||||||
} => {
|
} => {
|
||||||
self.element
|
self.element
|
||||||
.paint(layout.clone(), state, &mut frame_state, cx)?;
|
.paint(bounds.clone(), state, &mut frame_state, cx)?;
|
||||||
ElementRenderPhase::Painted {
|
ElementRenderPhase::Painted {
|
||||||
layout,
|
bounds,
|
||||||
frame_state,
|
frame_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
|
AnyElement, Bounds, Element, LayoutId, Overflow, ParentElement, Pixels, Point, Refineable,
|
||||||
Refineable, RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
|
RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -40,34 +40,28 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
child_layouts: &mut Self::FrameState,
|
child_layouts: &mut Self::FrameState,
|
||||||
cx: &mut ViewContext<S>,
|
cx: &mut ViewContext<S>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Layout { order, bounds } = layout;
|
|
||||||
|
|
||||||
let style = self.computed_style();
|
let style = self.computed_style();
|
||||||
style.paint(order, bounds, cx);
|
cx.stack(0, |cx| style.paint(bounds, cx));
|
||||||
|
|
||||||
// // todo!("support only one dimension being hidden")
|
|
||||||
let overflow = &style.overflow;
|
let overflow = &style.overflow;
|
||||||
// if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
|
|
||||||
// cx.clip(layout.bounds, style.corner_radii, || )
|
|
||||||
// }
|
|
||||||
|
|
||||||
style.apply_text_style(cx, |cx| {
|
style.apply_text_style(cx, |cx| {
|
||||||
style.apply_overflow(layout.bounds, cx, |cx| {
|
cx.stack(1, |cx| {
|
||||||
self.paint_children(overflow, state, cx)
|
style.apply_overflow(bounds, cx, |cx| self.paint_children(overflow, state, cx))
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
|
self.handle_scroll(bounds, style.overflow.clone(), child_layouts, cx);
|
||||||
|
|
||||||
// todo!("enable inspector")
|
// todo!("enable inspector")
|
||||||
// if cx.is_inspector_enabled() {
|
// if cx.is_inspector_enabled() {
|
||||||
// self.paint_inspector(parent_origin, layout, cx);
|
// self.paint_inspector(parent_origin, layout, cx);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +136,6 @@ impl<S: 'static> Div<S> {
|
||||||
|
|
||||||
fn handle_scroll(
|
fn handle_scroll(
|
||||||
&mut self,
|
&mut self,
|
||||||
_order: u32,
|
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
overflow: Point<Overflow>,
|
overflow: Point<Overflow>,
|
||||||
child_layout_ids: &[LayoutId],
|
child_layout_ids: &[LayoutId],
|
||||||
|
@ -151,8 +144,8 @@ impl<S: 'static> Div<S> {
|
||||||
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
|
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
|
||||||
let mut scroll_max = Point::default();
|
let mut scroll_max = Point::default();
|
||||||
for child_layout_id in child_layout_ids {
|
for child_layout_id in child_layout_ids {
|
||||||
if let Some(child_layout) = cx.layout(*child_layout_id).log_err() {
|
if let Some(child_bounds) = cx.layout_bounds(*child_layout_id).log_err() {
|
||||||
scroll_max = scroll_max.max(&child_layout.bounds.lower_right());
|
scroll_max = scroll_max.max(&child_bounds.lower_right());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scroll_max -= bounds.size;
|
scroll_max -= bounds.size;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
BorrowWindow, Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled,
|
BorrowWindow, Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers,
|
||||||
ViewContext,
|
Styled, ViewContext,
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use refineable::RefinementCascade;
|
use refineable::RefinementCascade;
|
||||||
|
@ -54,16 +54,14 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
_: &mut Self::FrameState,
|
_: &mut Self::FrameState,
|
||||||
cx: &mut ViewContext<Self::State>,
|
cx: &mut ViewContext<Self::State>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let style = self.computed_style();
|
let style = self.computed_style();
|
||||||
let order = layout.order;
|
|
||||||
let bounds = layout.bounds;
|
|
||||||
|
|
||||||
style.paint(order, bounds, cx);
|
style.paint(bounds, cx);
|
||||||
|
|
||||||
if let Some(uri) = self.uri.clone() {
|
if let Some(uri) = self.uri.clone() {
|
||||||
let image_future = cx.image_cache.get(uri);
|
let image_future = cx.image_cache.get(uri);
|
||||||
|
@ -72,15 +70,14 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
|
||||||
.now_or_never()
|
.now_or_never()
|
||||||
.and_then(ResultExt::log_err)
|
.and_then(ResultExt::log_err)
|
||||||
{
|
{
|
||||||
let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size());
|
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||||
cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?;
|
cx.stack(1, |cx| {
|
||||||
|
cx.paint_image(bounds, corner_radii, data, self.grayscale)
|
||||||
|
})?;
|
||||||
} else {
|
} else {
|
||||||
cx.spawn(|view, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
if image_future.await.log_err().is_some() {
|
if image_future.await.log_err().is_some() {
|
||||||
view.update(&mut cx, |_, cx| {
|
cx.on_next_frame(|cx| cx.notify());
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach()
|
.detach()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::Element;
|
use crate::{Bounds, Element, Pixels};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub struct Stateless<E: Element<State = ()>, S> {
|
pub struct Stateless<E: Element<State = ()>, S> {
|
||||||
|
@ -20,11 +20,11 @@ impl<E: Element<State = ()>, S: Send + Sync + 'static> Element for Stateless<E,
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: crate::Layout,
|
bounds: Bounds<Pixels>,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
frame_state: &mut Self::FrameState,
|
frame_state: &mut Self::FrameState,
|
||||||
cx: &mut crate::ViewContext<Self::State>,
|
cx: &mut crate::ViewContext<Self::State>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
cx.erase_state(|cx| self.element.paint(layout, &mut (), frame_state, cx))
|
cx.erase_state(|cx| self.element.paint(bounds, &mut (), frame_state, cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled};
|
use crate::{Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers, Styled};
|
||||||
use refineable::RefinementCascade;
|
use refineable::RefinementCascade;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ impl<S: 'static> Element for Svg<S> {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
_: &mut Self::FrameState,
|
_: &mut Self::FrameState,
|
||||||
cx: &mut crate::ViewContext<S>,
|
cx: &mut crate::ViewContext<S>,
|
||||||
|
@ -51,7 +51,7 @@ impl<S: 'static> Element for Svg<S> {
|
||||||
{
|
{
|
||||||
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
||||||
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
||||||
cx.paint_svg(layout.bounds, layout.order, path.clone(), fill_color)?;
|
cx.paint_svg(bounds, path.clone(), fill_color)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyElement, Element, IntoAnyElement, Layout, LayoutId, Line, Pixels, Result, Size, ViewContext,
|
AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Result, Size, ViewContext,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{marker::PhantomData, sync::Arc};
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
|
@ -94,7 +94,7 @@ impl<S: 'static> Element for Text<S> {
|
||||||
|
|
||||||
fn paint<'a>(
|
fn paint<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
frame_state: &mut Self::FrameState,
|
frame_state: &mut Self::FrameState,
|
||||||
cx: &mut ViewContext<S>,
|
cx: &mut ViewContext<S>,
|
||||||
|
@ -111,8 +111,7 @@ impl<S: 'static> Element for Text<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
|
// todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
|
||||||
let visible_bounds = layout.bounds;
|
line.paint(bounds, bounds, line_height, cx)?;
|
||||||
line.paint(&layout, visible_bounds, line_height, cx)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use core::fmt::Debug;
|
||||||
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
|
use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp, fmt,
|
||||||
ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign},
|
ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ impl<T: Clone + Debug> Clone for Point<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div, Hash)]
|
#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash)]
|
||||||
#[refineable(debug)]
|
#[refineable(debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Size<T: Clone + Debug> {
|
pub struct Size<T: Clone + Debug> {
|
||||||
|
@ -199,11 +199,17 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
|
||||||
|
|
||||||
impl<T: Eq + Debug + Clone> Eq for Size<T> {}
|
impl<T: Eq + Debug + Clone> Eq for Size<T> {}
|
||||||
|
|
||||||
impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
|
impl<T: Clone + Debug> Debug for Size<T> {
|
||||||
fn from(size: Size<Option<Pixels>>) -> Self {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Size {{ {:?} × {:?} }}", self.width, self.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Size<Pixels>> for Size<GlobalPixels> {
|
||||||
|
fn from(size: Size<Pixels>) -> Self {
|
||||||
Size {
|
Size {
|
||||||
width: size.width.map(|p| p.0 as f32),
|
width: GlobalPixels(size.width.0),
|
||||||
height: size.height.map(|p| p.0 as f32),
|
height: GlobalPixels(size.height.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,6 +263,26 @@ impl<T: Clone + Debug + Sub<Output = T>> Bounds<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
|
||||||
|
pub fn intersects(&self, other: &Bounds<T>) -> bool {
|
||||||
|
let my_lower_right = self.lower_right();
|
||||||
|
let their_lower_right = other.lower_right();
|
||||||
|
|
||||||
|
self.origin.x < their_lower_right.x
|
||||||
|
&& my_lower_right.x > other.origin.x
|
||||||
|
&& self.origin.y < their_lower_right.y
|
||||||
|
&& my_lower_right.y > other.origin.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dilate(&mut self, amount: T) {
|
||||||
|
self.origin.x = self.origin.x.clone() - amount.clone();
|
||||||
|
self.origin.y = self.origin.y.clone() - amount.clone();
|
||||||
|
let double_amount = amount.clone() + amount;
|
||||||
|
self.size.width = self.size.width.clone() + double_amount.clone();
|
||||||
|
self.size.height = self.size.height.clone() + double_amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
|
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
|
||||||
pub fn intersect(&self, other: &Self) -> Self {
|
pub fn intersect(&self, other: &Self) -> Self {
|
||||||
let upper_left = self.origin.max(&other.origin);
|
let upper_left = self.origin.max(&other.origin);
|
||||||
|
@ -316,6 +342,13 @@ impl<T: Clone + Debug + Add<T, Output = T>> Bounds<T> {
|
||||||
y: self.origin.y.clone() + self.size.height.clone(),
|
y: self.origin.y.clone() + self.size.height.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lower_left(&self) -> Point<T> {
|
||||||
|
Point {
|
||||||
|
x: self.origin.x.clone(),
|
||||||
|
y: self.origin.y.clone() + self.size.height.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
|
impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
|
||||||
|
@ -448,6 +481,17 @@ impl Edges<AbsoluteLength> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Edges<Pixels> {
|
||||||
|
pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
|
||||||
|
Edges {
|
||||||
|
top: self.top.scale(factor),
|
||||||
|
right: self.right.scale(factor),
|
||||||
|
bottom: self.bottom.scale(factor),
|
||||||
|
left: self.left.scale(factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
|
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
|
||||||
#[refineable(debug)]
|
#[refineable(debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -459,8 +503,8 @@ pub struct Corners<T: Clone + Debug> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Corners<AbsoluteLength> {
|
impl Corners<AbsoluteLength> {
|
||||||
pub fn to_pixels(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
|
pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
|
||||||
let max = bounds.size.width.max(bounds.size.height) / 2.;
|
let max = size.width.max(size.height) / 2.;
|
||||||
Corners {
|
Corners {
|
||||||
top_left: self.top_left.to_pixels(rem_size).min(max),
|
top_left: self.top_left.to_pixels(rem_size).min(max),
|
||||||
top_right: self.top_right.to_pixels(rem_size).min(max),
|
top_right: self.top_right.to_pixels(rem_size).min(max),
|
||||||
|
@ -587,7 +631,7 @@ impl From<f32> for Pixels {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Pixels {
|
impl Debug for Pixels {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{} px", self.0)
|
write!(f, "{} px", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -622,8 +666,8 @@ impl DevicePixels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for DevicePixels {
|
impl fmt::Debug for DevicePixels {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{} px (device)", self.0)
|
write!(f, "{} px (device)", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -681,7 +725,7 @@ impl ScaledPixels {
|
||||||
impl Eq for ScaledPixels {}
|
impl Eq for ScaledPixels {}
|
||||||
|
|
||||||
impl Debug for ScaledPixels {
|
impl Debug for ScaledPixels {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{} px (scaled)", self.0)
|
write!(f, "{} px (scaled)", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -698,6 +742,34 @@ impl From<DevicePixels> for ScaledPixels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ScaledPixels> for f64 {
|
||||||
|
fn from(scaled_pixels: ScaledPixels) -> Self {
|
||||||
|
scaled_pixels.0 as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct GlobalPixels(pub(crate) f32);
|
||||||
|
|
||||||
|
impl Debug for GlobalPixels {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{} px (global coordinate space)", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GlobalPixels> for f64 {
|
||||||
|
fn from(global_pixels: GlobalPixels) -> Self {
|
||||||
|
global_pixels.0 as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f64> for GlobalPixels {
|
||||||
|
fn from(global_pixels: f64) -> Self {
|
||||||
|
GlobalPixels(global_pixels as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
|
#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
|
||||||
pub struct Rems(f32);
|
pub struct Rems(f32);
|
||||||
|
|
||||||
|
@ -710,7 +782,7 @@ impl Mul<Pixels> for Rems {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Rems {
|
impl Debug for Rems {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{} rem", self.0)
|
write!(f, "{} rem", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -778,7 +850,7 @@ impl DefiniteLength {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for DefiniteLength {
|
impl Debug for DefiniteLength {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
|
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
|
||||||
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
|
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
|
||||||
|
@ -818,7 +890,7 @@ pub enum Length {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Length {
|
impl Debug for Length {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
|
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
|
||||||
Length::Auto => write!(f, "auto"),
|
Length::Auto => write!(f, "auto"),
|
||||||
|
@ -964,3 +1036,42 @@ impl<T: IsZero + Debug + Clone> IsZero for Corners<T> {
|
||||||
&& self.bottom_left.is_zero()
|
&& self.bottom_left.is_zero()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bounds_intersects() {
|
||||||
|
let bounds1 = Bounds {
|
||||||
|
origin: Point { x: 0.0, y: 0.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 5.0,
|
||||||
|
height: 5.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let bounds2 = Bounds {
|
||||||
|
origin: Point { x: 4.0, y: 4.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 5.0,
|
||||||
|
height: 5.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let bounds3 = Bounds {
|
||||||
|
origin: Point { x: 10.0, y: 10.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 5.0,
|
||||||
|
height: 5.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test Case 1: Intersecting bounds
|
||||||
|
assert_eq!(bounds1.intersects(&bounds2), true);
|
||||||
|
|
||||||
|
// Test Case 2: Non-Intersecting bounds
|
||||||
|
assert_eq!(bounds1.intersects(&bounds3), false);
|
||||||
|
|
||||||
|
// Test Case 3: Bounds intersecting with themselves
|
||||||
|
assert_eq!(bounds1.intersects(&bounds1), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub use elements::*;
|
||||||
pub use executor::*;
|
pub use executor::*;
|
||||||
pub use geometry::*;
|
pub use geometry::*;
|
||||||
pub use gpui3_macros::*;
|
pub use gpui3_macros::*;
|
||||||
|
pub use image_cache::*;
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
pub use refineable::*;
|
pub use refineable::*;
|
||||||
pub use scene::*;
|
pub use scene::*;
|
||||||
|
|
|
@ -5,10 +5,10 @@ mod mac;
|
||||||
#[cfg(any(test, feature = "test"))]
|
#[cfg(any(test, feature = "test"))]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use crate::image_cache::RenderImageParams;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlyphId, Pixels,
|
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlobalPixels,
|
||||||
Point, RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size,
|
GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
|
||||||
|
ShapedLine, SharedString, Size,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -16,7 +16,6 @@ use futures::channel::oneshot;
|
||||||
use seahash::SeaHasher;
|
use seahash::SeaHasher;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ffi::c_void;
|
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
@ -27,7 +26,6 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
pub use events::*;
|
pub use events::*;
|
||||||
pub use keystroke::*;
|
pub use keystroke::*;
|
||||||
|
@ -44,6 +42,7 @@ pub(crate) fn current_platform() -> Arc<dyn Platform> {
|
||||||
|
|
||||||
pub trait Platform: 'static {
|
pub trait Platform: 'static {
|
||||||
fn executor(&self) -> Executor;
|
fn executor(&self) -> Executor;
|
||||||
|
fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker>;
|
||||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
||||||
|
|
||||||
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
|
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
|
||||||
|
@ -54,8 +53,8 @@ pub trait Platform: 'static {
|
||||||
fn hide_other_apps(&self);
|
fn hide_other_apps(&self);
|
||||||
fn unhide_other_apps(&self);
|
fn unhide_other_apps(&self);
|
||||||
|
|
||||||
fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||||
fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>;
|
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||||
fn main_window(&self) -> Option<AnyWindowHandle>;
|
fn main_window(&self) -> Option<AnyWindowHandle>;
|
||||||
fn open_window(
|
fn open_window(
|
||||||
&self,
|
&self,
|
||||||
|
@ -97,23 +96,22 @@ pub trait Platform: 'static {
|
||||||
fn delete_credentials(&self, url: &str) -> Result<()>;
|
fn delete_credentials(&self, url: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PlatformScreen: Debug {
|
pub trait PlatformDisplay: Debug {
|
||||||
fn id(&self) -> Option<ScreenId>;
|
fn id(&self) -> DisplayId;
|
||||||
fn handle(&self) -> PlatformScreenHandle;
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
fn bounds(&self) -> Bounds<Pixels>;
|
fn bounds(&self) -> Bounds<GlobalPixels>;
|
||||||
fn content_bounds(&self) -> Bounds<Pixels>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PlatformScreenHandle(pub *mut c_void);
|
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||||
|
pub struct DisplayId(pub(crate) u32);
|
||||||
|
|
||||||
impl Debug for PlatformScreenHandle {
|
impl Debug for DisplayId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "PlatformScreenHandle({:p})", self.0)
|
write!(f, "DisplayId({})", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for PlatformScreenHandle {}
|
unsafe impl Send for DisplayId {}
|
||||||
|
|
||||||
pub trait PlatformWindow {
|
pub trait PlatformWindow {
|
||||||
fn bounds(&self) -> WindowBounds;
|
fn bounds(&self) -> WindowBounds;
|
||||||
|
@ -121,7 +119,7 @@ pub trait PlatformWindow {
|
||||||
fn scale_factor(&self) -> f32;
|
fn scale_factor(&self) -> f32;
|
||||||
fn titlebar_height(&self) -> Pixels;
|
fn titlebar_height(&self) -> Pixels;
|
||||||
fn appearance(&self) -> WindowAppearance;
|
fn appearance(&self) -> WindowAppearance;
|
||||||
fn screen(&self) -> Rc<dyn PlatformScreen>;
|
fn display(&self) -> Rc<dyn PlatformDisplay>;
|
||||||
fn mouse_position(&self) -> Point<Pixels>;
|
fn mouse_position(&self) -> Point<Pixels>;
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
|
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
|
||||||
|
@ -158,6 +156,16 @@ pub trait PlatformDispatcher: Send + Sync {
|
||||||
fn dispatch_on_main_thread(&self, task: Runnable);
|
fn dispatch_on_main_thread(&self, task: Runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait PlatformDisplayLinker: Send + Sync {
|
||||||
|
fn set_output_callback(
|
||||||
|
&self,
|
||||||
|
display_id: DisplayId,
|
||||||
|
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
|
||||||
|
);
|
||||||
|
fn start(&self, display_id: DisplayId);
|
||||||
|
fn stop(&self, display_id: DisplayId);
|
||||||
|
}
|
||||||
|
|
||||||
pub trait PlatformTextSystem: Send + Sync {
|
pub trait PlatformTextSystem: Send + Sync {
|
||||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
|
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
|
||||||
fn all_font_families(&self) -> Vec<String>;
|
fn all_font_families(&self) -> Vec<String>;
|
||||||
|
@ -266,9 +274,6 @@ pub trait PlatformInputHandler {
|
||||||
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
|
fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub struct ScreenId(pub(crate) Uuid);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WindowOptions {
|
pub struct WindowOptions {
|
||||||
pub bounds: WindowBounds,
|
pub bounds: WindowBounds,
|
||||||
|
@ -278,7 +283,7 @@ pub struct WindowOptions {
|
||||||
pub show: bool,
|
pub show: bool,
|
||||||
pub kind: WindowKind,
|
pub kind: WindowKind,
|
||||||
pub is_movable: bool,
|
pub is_movable: bool,
|
||||||
pub screen: Option<PlatformScreenHandle>,
|
pub display_id: Option<DisplayId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WindowOptions {
|
impl Default for WindowOptions {
|
||||||
|
@ -295,7 +300,7 @@ impl Default for WindowOptions {
|
||||||
show: true,
|
show: true,
|
||||||
kind: WindowKind::Normal,
|
kind: WindowKind::Normal,
|
||||||
is_movable: true,
|
is_movable: true,
|
||||||
screen: None,
|
display_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +337,7 @@ pub enum WindowBounds {
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
#[default]
|
#[default]
|
||||||
Maximized,
|
Maximized,
|
||||||
Fixed(Bounds<Pixels>),
|
Fixed(Bounds<GlobalPixels>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
///! Macos screen have a y axis that goings up from the bottom of the screen and
|
///! Macos screen have a y axis that goings up from the bottom of the screen and
|
||||||
///! an origin at the bottom left of the main display.
|
///! an origin at the bottom left of the main display.
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
|
mod display;
|
||||||
|
mod display_linker;
|
||||||
mod events;
|
mod events;
|
||||||
mod metal_atlas;
|
mod metal_atlas;
|
||||||
mod metal_renderer;
|
mod metal_renderer;
|
||||||
mod open_type;
|
mod open_type;
|
||||||
mod platform;
|
mod platform;
|
||||||
mod screen;
|
|
||||||
mod text_system;
|
mod text_system;
|
||||||
mod window;
|
mod window;
|
||||||
mod window_appearence;
|
mod window_appearence;
|
||||||
|
|
||||||
use crate::{px, size, Pixels, Size};
|
use crate::{px, size, GlobalPixels, Pixels, Size};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
|
@ -31,9 +32,10 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use dispatcher::*;
|
pub use dispatcher::*;
|
||||||
|
pub use display::*;
|
||||||
|
pub use display_linker::*;
|
||||||
pub use metal_atlas::*;
|
pub use metal_atlas::*;
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
pub use screen::*;
|
|
||||||
pub use text_system::*;
|
pub use text_system::*;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
|
@ -119,23 +121,33 @@ pub trait NSRectExt {
|
||||||
fn intersects(&self, other: Self) -> bool;
|
fn intersects(&self, other: Self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NSRectExt for NSRect {
|
impl From<NSRect> for Size<Pixels> {
|
||||||
fn size(&self) -> Size<Pixels> {
|
fn from(rect: NSRect) -> Self {
|
||||||
size(px(self.size.width as f32), px(self.size.height as f32))
|
let NSSize { width, height } = rect.size;
|
||||||
}
|
size(width.into(), height.into())
|
||||||
|
|
||||||
fn intersects(&self, other: Self) -> bool {
|
|
||||||
self.size.width > 0.
|
|
||||||
&& self.size.height > 0.
|
|
||||||
&& other.size.width > 0.
|
|
||||||
&& other.size.height > 0.
|
|
||||||
&& self.origin.x <= other.origin.x + other.size.width
|
|
||||||
&& self.origin.x + self.size.width >= other.origin.x
|
|
||||||
&& self.origin.y <= other.origin.y + other.size.height
|
|
||||||
&& self.origin.y + self.size.height >= other.origin.y
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NSRect> for Size<GlobalPixels> {
|
||||||
|
fn from(rect: NSRect) -> Self {
|
||||||
|
let NSSize { width, height } = rect.size;
|
||||||
|
size(width.into(), height.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl NSRectExt for NSRect {
|
||||||
|
// fn intersects(&self, other: Self) -> bool {
|
||||||
|
// self.size.width > 0.
|
||||||
|
// && self.size.height > 0.
|
||||||
|
// && other.size.width > 0.
|
||||||
|
// && other.size.height > 0.
|
||||||
|
// && self.origin.x <= other.origin.x + other.size.width
|
||||||
|
// && self.origin.x + self.size.width >= other.origin.x
|
||||||
|
// && self.origin.y <= other.origin.y + other.size.height
|
||||||
|
// && self.origin.y + self.size.height >= other.origin.y
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// todo!
|
// todo!
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
|
unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
|
||||||
|
|
101
crates/gpui3/src/platform/mac/display.rs
Normal file
101
crates/gpui3/src/platform/mac/display.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
276
crates/gpui3/src/platform/mac/display_linker.rs
Normal file
276
crates/gpui3/src/platform/mac/display_linker.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
|
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
|
||||||
Quad, Scene, Size,
|
PrimitiveBatch, Quad, Scene, Shadow, Size, Underline,
|
||||||
};
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
base::{NO, YES},
|
base::{NO, YES},
|
||||||
|
@ -17,7 +17,9 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
|
||||||
pub struct MetalRenderer {
|
pub struct MetalRenderer {
|
||||||
layer: metal::MetalLayer,
|
layer: metal::MetalLayer,
|
||||||
command_queue: CommandQueue,
|
command_queue: CommandQueue,
|
||||||
|
shadows_pipeline_state: metal::RenderPipelineState,
|
||||||
quads_pipeline_state: metal::RenderPipelineState,
|
quads_pipeline_state: metal::RenderPipelineState,
|
||||||
|
underlines_pipeline_state: metal::RenderPipelineState,
|
||||||
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
|
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||||
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
|
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||||
unit_vertices: metal::Buffer,
|
unit_vertices: metal::Buffer,
|
||||||
|
@ -82,6 +84,14 @@ impl MetalRenderer {
|
||||||
MTLResourceOptions::StorageModeManaged,
|
MTLResourceOptions::StorageModeManaged,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let shadows_pipeline_state = build_pipeline_state(
|
||||||
|
&device,
|
||||||
|
&library,
|
||||||
|
"shadows",
|
||||||
|
"shadow_vertex",
|
||||||
|
"shadow_fragment",
|
||||||
|
PIXEL_FORMAT,
|
||||||
|
);
|
||||||
let quads_pipeline_state = build_pipeline_state(
|
let quads_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
&library,
|
&library,
|
||||||
|
@ -90,6 +100,14 @@ impl MetalRenderer {
|
||||||
"quad_fragment",
|
"quad_fragment",
|
||||||
PIXEL_FORMAT,
|
PIXEL_FORMAT,
|
||||||
);
|
);
|
||||||
|
let underlines_pipeline_state = build_pipeline_state(
|
||||||
|
&device,
|
||||||
|
&library,
|
||||||
|
"underlines",
|
||||||
|
"underline_vertex",
|
||||||
|
"underline_fragment",
|
||||||
|
PIXEL_FORMAT,
|
||||||
|
);
|
||||||
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
&library,
|
&library,
|
||||||
|
@ -113,7 +131,9 @@ impl MetalRenderer {
|
||||||
Self {
|
Self {
|
||||||
layer,
|
layer,
|
||||||
command_queue,
|
command_queue,
|
||||||
|
shadows_pipeline_state,
|
||||||
quads_pipeline_state,
|
quads_pipeline_state,
|
||||||
|
underlines_pipeline_state,
|
||||||
monochrome_sprites_pipeline_state,
|
monochrome_sprites_pipeline_state,
|
||||||
polychrome_sprites_pipeline_state,
|
polychrome_sprites_pipeline_state,
|
||||||
unit_vertices,
|
unit_vertices,
|
||||||
|
@ -131,8 +151,6 @@ impl MetalRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &mut Scene) {
|
pub fn draw(&mut self, scene: &mut Scene) {
|
||||||
dbg!("draw scene");
|
|
||||||
|
|
||||||
let layer = self.layer.clone();
|
let layer = self.layer.clone();
|
||||||
let viewport_size = layer.drawable_size();
|
let viewport_size = layer.drawable_size();
|
||||||
let viewport_size: Size<DevicePixels> = size(
|
let viewport_size: Size<DevicePixels> = size(
|
||||||
|
@ -174,41 +192,50 @@ impl MetalRenderer {
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut instance_offset = 0;
|
let mut instance_offset = 0;
|
||||||
for layer in scene.layers() {
|
for batch in scene.batches() {
|
||||||
for batch in layer.batches() {
|
match batch {
|
||||||
match batch {
|
PrimitiveBatch::Shadows(shadows) => {
|
||||||
crate::PrimitiveBatch::Quads(quads) => {
|
self.draw_shadows(
|
||||||
self.draw_quads(
|
shadows,
|
||||||
quads,
|
&mut instance_offset,
|
||||||
&mut instance_offset,
|
viewport_size,
|
||||||
viewport_size,
|
command_encoder,
|
||||||
command_encoder,
|
);
|
||||||
);
|
}
|
||||||
}
|
PrimitiveBatch::Quads(quads) => {
|
||||||
crate::PrimitiveBatch::MonochromeSprites {
|
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,
|
||||||
|
} => {
|
||||||
|
self.draw_monochrome_sprites(
|
||||||
texture_id,
|
texture_id,
|
||||||
sprites,
|
sprites,
|
||||||
} => {
|
&mut instance_offset,
|
||||||
self.draw_monochrome_sprites(
|
viewport_size,
|
||||||
texture_id,
|
command_encoder,
|
||||||
sprites,
|
);
|
||||||
&mut instance_offset,
|
}
|
||||||
viewport_size,
|
PrimitiveBatch::PolychromeSprites {
|
||||||
command_encoder,
|
texture_id,
|
||||||
);
|
sprites,
|
||||||
}
|
} => {
|
||||||
crate::PrimitiveBatch::PolychromeSprites {
|
self.draw_polychrome_sprites(
|
||||||
texture_id,
|
texture_id,
|
||||||
sprites,
|
sprites,
|
||||||
} => {
|
&mut instance_offset,
|
||||||
self.draw_polychrome_sprites(
|
viewport_size,
|
||||||
texture_id,
|
command_encoder,
|
||||||
sprites,
|
);
|
||||||
&mut instance_offset,
|
|
||||||
viewport_size,
|
|
||||||
command_encoder,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +252,66 @@ impl MetalRenderer {
|
||||||
drawable.present();
|
drawable.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_shadows(
|
||||||
|
&mut self,
|
||||||
|
shadows: &[Shadow],
|
||||||
|
offset: &mut usize,
|
||||||
|
viewport_size: Size<DevicePixels>,
|
||||||
|
command_encoder: &metal::RenderCommandEncoderRef,
|
||||||
|
) {
|
||||||
|
if shadows.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
align_offset(offset);
|
||||||
|
|
||||||
|
command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
ShadowInputIndex::Vertices as u64,
|
||||||
|
Some(&self.unit_vertices),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
ShadowInputIndex::Shadows as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder.set_fragment_buffer(
|
||||||
|
ShadowInputIndex::Shadows as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
ShadowInputIndex::ViewportSize as u64,
|
||||||
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
|
||||||
|
let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
|
||||||
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
shadows.as_ptr() as *const u8,
|
||||||
|
buffer_contents,
|
||||||
|
shadow_bytes_len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_offset = *offset + shadow_bytes_len;
|
||||||
|
assert!(
|
||||||
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
|
"instance buffer exhausted"
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.draw_primitives_instanced(
|
||||||
|
metal::MTLPrimitiveType::Triangle,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
shadows.len() as u64,
|
||||||
|
);
|
||||||
|
*offset = next_offset;
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_quads(
|
fn draw_quads(
|
||||||
&mut self,
|
&mut self,
|
||||||
quads: &[Quad],
|
quads: &[Quad],
|
||||||
|
@ -281,6 +368,66 @@ impl MetalRenderer {
|
||||||
*offset = next_offset;
|
*offset = next_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_underlines(
|
||||||
|
&mut self,
|
||||||
|
underlines: &[Underline],
|
||||||
|
offset: &mut usize,
|
||||||
|
viewport_size: Size<DevicePixels>,
|
||||||
|
command_encoder: &metal::RenderCommandEncoderRef,
|
||||||
|
) {
|
||||||
|
if underlines.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
align_offset(offset);
|
||||||
|
|
||||||
|
command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
UnderlineInputIndex::Vertices as u64,
|
||||||
|
Some(&self.unit_vertices),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
UnderlineInputIndex::Underlines as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder.set_fragment_buffer(
|
||||||
|
UnderlineInputIndex::Underlines as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
UnderlineInputIndex::ViewportSize as u64,
|
||||||
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
|
||||||
|
let quad_bytes_len = mem::size_of::<Underline>() * underlines.len();
|
||||||
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
underlines.as_ptr() as *const u8,
|
||||||
|
buffer_contents,
|
||||||
|
quad_bytes_len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_offset = *offset + quad_bytes_len;
|
||||||
|
assert!(
|
||||||
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
|
"instance buffer exhausted"
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.draw_primitives_instanced(
|
||||||
|
metal::MTLPrimitiveType::Triangle,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
underlines.len() as u64,
|
||||||
|
);
|
||||||
|
*offset = next_offset;
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_monochrome_sprites(
|
fn draw_monochrome_sprites(
|
||||||
&mut self,
|
&mut self,
|
||||||
texture_id: AtlasTextureId,
|
texture_id: AtlasTextureId,
|
||||||
|
@ -464,6 +611,13 @@ fn align_offset(offset: &mut usize) {
|
||||||
*offset = ((*offset + 255) / 256) * 256;
|
*offset = ((*offset + 255) / 256) * 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
enum ShadowInputIndex {
|
||||||
|
Vertices = 0,
|
||||||
|
Shadows = 1,
|
||||||
|
ViewportSize = 2,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
enum QuadInputIndex {
|
enum QuadInputIndex {
|
||||||
Vertices = 0,
|
Vertices = 0,
|
||||||
|
@ -471,6 +625,13 @@ enum QuadInputIndex {
|
||||||
ViewportSize = 2,
|
ViewportSize = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
enum UnderlineInputIndex {
|
||||||
|
Vertices = 0,
|
||||||
|
Underlines = 1,
|
||||||
|
ViewportSize = 2,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
enum SpriteInputIndex {
|
enum SpriteInputIndex {
|
||||||
Vertices = 0,
|
Vertices = 0,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use super::BoolExt;
|
use super::BoolExt;
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, ClipboardItem, CursorStyle, Event, Executor, MacDispatcher, MacScreen,
|
AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher,
|
||||||
MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem,
|
MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
|
||||||
PlatformWindow, Result, ScreenId, SemanticVersion, WindowOptions,
|
PlatformDisplay, PlatformDisplayLinker, PlatformTextSystem, PlatformWindow, Result,
|
||||||
|
SemanticVersion, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
|
@ -347,6 +348,10 @@ impl Platform for MacPlatform {
|
||||||
self.0.lock().executor.clone()
|
self.0.lock().executor.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker> {
|
||||||
|
Arc::new(MacDisplayLinker::new())
|
||||||
|
}
|
||||||
|
|
||||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
||||||
self.0.lock().text_system.clone()
|
self.0.lock().text_system.clone()
|
||||||
}
|
}
|
||||||
|
@ -455,21 +460,21 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||||
MacScreen::all()
|
MacDisplay::all()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|screen| Rc::new(screen) as Rc<_>)
|
.map(|screen| Rc::new(screen) as Rc<_>)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>> {
|
|
||||||
MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
|
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
|
||||||
// Box::new(StatusItem::add(self.fonts()))
|
// Box::new(StatusItem::add(self.fonts()))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||||
|
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
|
||||||
|
}
|
||||||
|
|
||||||
fn main_window(&self) -> Option<AnyWindowHandle> {
|
fn main_window(&self) -> Option<AnyWindowHandle> {
|
||||||
MacWindow::main_window()
|
MacWindow::main_window()
|
||||||
}
|
}
|
||||||
|
@ -736,6 +741,32 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
||||||
|
// self.0.lock().menu_command = Some(callback);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
|
||||||
|
// self.0.lock().will_open_menu = Some(callback);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
||||||
|
// self.0.lock().validate_menu_command = Some(callback);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
|
||||||
|
// unsafe {
|
||||||
|
// let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
|
// let mut state = self.0.lock();
|
||||||
|
// let actions = &mut state.menu_actions;
|
||||||
|
// app.setMainMenu_(self.create_menu_bar(
|
||||||
|
// menus,
|
||||||
|
// app.delegate(),
|
||||||
|
// actions,
|
||||||
|
// keystroke_matcher,
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||||
let state = self.0.lock();
|
let state = self.0.lock();
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -773,32 +804,6 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
|
||||||
// self.0.lock().menu_command = Some(callback);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
|
|
||||||
// self.0.lock().will_open_menu = Some(callback);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
|
||||||
// self.0.lock().validate_menu_command = Some(callback);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
|
|
||||||
// unsafe {
|
|
||||||
// let app: id = msg_send![APP_CLASS, sharedApplication];
|
|
||||||
// let mut state = self.0.lock();
|
|
||||||
// let actions = &mut state.menu_actions;
|
|
||||||
// app.setMainMenu_(self.create_menu_bar(
|
|
||||||
// menus,
|
|
||||||
// app.delegate(),
|
|
||||||
// actions,
|
|
||||||
// keystroke_matcher,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
|
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
|
||||||
let url = CFString::from(url);
|
let url = CFString::from(url);
|
||||||
let username = CFString::from(username);
|
let username = CFString::from(username);
|
||||||
|
|
|
@ -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()) }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,6 +11,10 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||||
constant Size_DevicePixels *atlas_size);
|
constant Size_DevicePixels *atlas_size);
|
||||||
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||||
Corners_ScaledPixels corner_radii);
|
Corners_ScaledPixels corner_radii);
|
||||||
|
float gaussian(float x, float sigma);
|
||||||
|
float2 erf(float2 x);
|
||||||
|
float blur_along_x(float x, float y, float sigma, float corner,
|
||||||
|
float2 half_size);
|
||||||
|
|
||||||
struct QuadVertexOutput {
|
struct QuadVertexOutput {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
|
@ -29,8 +33,8 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
||||||
[[buffer(QuadInputIndex_ViewportSize)]]) {
|
[[buffer(QuadInputIndex_ViewportSize)]]) {
|
||||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
Quad quad = quads[quad_id];
|
Quad quad = quads[quad_id];
|
||||||
float4 device_position = to_device_position(unit_vertex, quad.bounds,
|
float4 device_position = to_device_position(
|
||||||
quad.clip_bounds, viewport_size);
|
unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size);
|
||||||
float4 background_color = hsla_to_rgba(quad.background);
|
float4 background_color = hsla_to_rgba(quad.background);
|
||||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||||
return QuadVertexOutput{device_position, background_color, border_color,
|
return QuadVertexOutput{device_position, background_color, border_color,
|
||||||
|
@ -109,6 +113,133 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
|
||||||
return color * float4(1., 1., 1., saturate(0.5 - distance));
|
return color * float4(1., 1., 1., saturate(0.5 - distance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ShadowVertexOutput {
|
||||||
|
float4 position [[position]];
|
||||||
|
float4 color [[flat]];
|
||||||
|
uint shadow_id [[flat]];
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex ShadowVertexOutput shadow_vertex(
|
||||||
|
uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
|
||||||
|
constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
|
||||||
|
constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
|
||||||
|
constant Size_DevicePixels *viewport_size
|
||||||
|
[[buffer(ShadowInputIndex_ViewportSize)]]) {
|
||||||
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
|
Shadow shadow = shadows[shadow_id];
|
||||||
|
|
||||||
|
float margin = 3. * shadow.blur_radius;
|
||||||
|
// Set the bounds of the shadow and adjust its size based on the shadow's
|
||||||
|
// spread radius to achieve the spreading effect
|
||||||
|
Bounds_ScaledPixels bounds = shadow.bounds;
|
||||||
|
bounds.origin.x -= margin;
|
||||||
|
bounds.origin.y -= margin;
|
||||||
|
bounds.size.width += 2. * margin;
|
||||||
|
bounds.size.height += 2. * margin;
|
||||||
|
|
||||||
|
float4 device_position = to_device_position(
|
||||||
|
unit_vertex, bounds, shadow.content_mask.bounds, viewport_size);
|
||||||
|
float4 color = hsla_to_rgba(shadow.color);
|
||||||
|
|
||||||
|
return ShadowVertexOutput{
|
||||||
|
device_position,
|
||||||
|
color,
|
||||||
|
shadow_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
|
||||||
|
constant Shadow *shadows
|
||||||
|
[[buffer(ShadowInputIndex_Shadows)]]) {
|
||||||
|
Shadow shadow = shadows[input.shadow_id];
|
||||||
|
|
||||||
|
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
|
||||||
|
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
|
||||||
|
float2 half_size = size / 2.;
|
||||||
|
float2 center = origin + half_size;
|
||||||
|
float2 point = input.position.xy - center;
|
||||||
|
float corner_radius;
|
||||||
|
if (point.x < 0.) {
|
||||||
|
if (point.y < 0.) {
|
||||||
|
corner_radius = shadow.corner_radii.top_left;
|
||||||
|
} else {
|
||||||
|
corner_radius = shadow.corner_radii.bottom_left;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (point.y < 0.) {
|
||||||
|
corner_radius = shadow.corner_radii.top_right;
|
||||||
|
} else {
|
||||||
|
corner_radius = shadow.corner_radii.bottom_right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The signal is only non-zero in a limited range, so don't waste samples
|
||||||
|
float low = point.y - half_size.y;
|
||||||
|
float high = point.y + half_size.y;
|
||||||
|
float start = clamp(-3. * shadow.blur_radius, low, high);
|
||||||
|
float end = clamp(3. * shadow.blur_radius, low, high);
|
||||||
|
|
||||||
|
// Accumulate samples (we can get away with surprisingly few samples)
|
||||||
|
float step = (end - start) / 4.;
|
||||||
|
float y = start + step * 0.5;
|
||||||
|
float alpha = 0.;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
|
||||||
|
corner_radius, half_size) *
|
||||||
|
gaussian(y, shadow.blur_radius) * step;
|
||||||
|
y += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.color * float4(1., 1., 1., alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UnderlineVertexOutput {
|
||||||
|
float4 position [[position]];
|
||||||
|
float4 color [[flat]];
|
||||||
|
uint underline_id [[flat]];
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex UnderlineVertexOutput underline_vertex(
|
||||||
|
uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
|
||||||
|
constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
|
||||||
|
constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
|
||||||
|
constant Size_DevicePixels *viewport_size
|
||||||
|
[[buffer(ShadowInputIndex_ViewportSize)]]) {
|
||||||
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
|
Underline underline = underlines[underline_id];
|
||||||
|
float4 device_position =
|
||||||
|
to_device_position(unit_vertex, underline.bounds,
|
||||||
|
underline.content_mask.bounds, viewport_size);
|
||||||
|
float4 color = hsla_to_rgba(underline.color);
|
||||||
|
return UnderlineVertexOutput{device_position, color, underline_id};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
|
||||||
|
constant Underline *underlines
|
||||||
|
[[buffer(UnderlineInputIndex_Underlines)]]) {
|
||||||
|
Underline underline = underlines[input.underline_id];
|
||||||
|
if (underline.wavy) {
|
||||||
|
float half_thickness = underline.thickness * 0.5;
|
||||||
|
float2 origin =
|
||||||
|
float2(underline.bounds.origin.x, underline.bounds.origin.y);
|
||||||
|
float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
|
||||||
|
float2(0., 0.5);
|
||||||
|
float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
|
||||||
|
float amplitude = 1. / (2. * underline.thickness);
|
||||||
|
float sine = sin(st.x * frequency) * amplitude;
|
||||||
|
float dSine = cos(st.x * frequency) * amplitude * frequency;
|
||||||
|
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
|
||||||
|
float distance_in_pixels = distance * underline.bounds.size.height;
|
||||||
|
float distance_from_top_border = distance_in_pixels - half_thickness;
|
||||||
|
float distance_from_bottom_border = distance_in_pixels + half_thickness;
|
||||||
|
float alpha = saturate(
|
||||||
|
0.5 - max(-distance_from_bottom_border, distance_from_top_border));
|
||||||
|
return input.color * float4(1., 1., 1., alpha);
|
||||||
|
} else {
|
||||||
|
return input.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct MonochromeSpriteVertexOutput {
|
struct MonochromeSpriteVertexOutput {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
float2 tile_position;
|
float2 tile_position;
|
||||||
|
@ -127,9 +258,10 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||||
|
|
||||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
MonochromeSprite sprite = sprites[sprite_id];
|
MonochromeSprite sprite = sprites[sprite_id];
|
||||||
// Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask.
|
// Don't apply content mask at the vertex level because we don't have time
|
||||||
float4 device_position = to_device_position(
|
// to make sampling from the texture match the mask.
|
||||||
unit_vertex, sprite.bounds, sprite.bounds, viewport_size);
|
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
|
||||||
|
sprite.bounds, viewport_size);
|
||||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
float4 color = hsla_to_rgba(sprite.color);
|
float4 color = hsla_to_rgba(sprite.color);
|
||||||
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
|
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
|
||||||
|
@ -145,11 +277,8 @@ fragment float4 monochrome_sprite_fragment(
|
||||||
min_filter::linear);
|
min_filter::linear);
|
||||||
float4 sample =
|
float4 sample =
|
||||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
float clip_distance = quad_sdf(
|
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||||
input.position.xy,
|
Corners_ScaledPixels{0., 0., 0., 0.});
|
||||||
sprite.content_mask.bounds,
|
|
||||||
Corners_ScaledPixels { 0., 0., 0., 0. }
|
|
||||||
);
|
|
||||||
float4 color = input.color;
|
float4 color = input.color;
|
||||||
color.a *= sample.a * saturate(0.5 - clip_distance);
|
color.a *= sample.a * saturate(0.5 - clip_distance);
|
||||||
return color;
|
return color;
|
||||||
|
@ -172,9 +301,10 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
|
||||||
|
|
||||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
PolychromeSprite sprite = sprites[sprite_id];
|
PolychromeSprite sprite = sprites[sprite_id];
|
||||||
// Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask.
|
// Don't apply content mask at the vertex level because we don't have time
|
||||||
float4 device_position = to_device_position(
|
// to make sampling from the texture match the mask.
|
||||||
unit_vertex, sprite.bounds, sprite.bounds, viewport_size);
|
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
|
||||||
|
sprite.bounds, viewport_size);
|
||||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
return PolychromeSpriteVertexOutput{device_position, tile_position,
|
return PolychromeSpriteVertexOutput{device_position, tile_position,
|
||||||
sprite_id};
|
sprite_id};
|
||||||
|
@ -189,8 +319,10 @@ fragment float4 polychrome_sprite_fragment(
|
||||||
min_filter::linear);
|
min_filter::linear);
|
||||||
float4 sample =
|
float4 sample =
|
||||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
float quad_distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
|
float quad_distance =
|
||||||
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, Corners_ScaledPixels { 0., 0., 0., 0. });
|
quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
|
||||||
|
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||||
|
Corners_ScaledPixels{0., 0., 0., 0.});
|
||||||
float distance = max(quad_distance, clip_distance);
|
float distance = max(quad_distance, clip_distance);
|
||||||
|
|
||||||
float4 color = sample;
|
float4 color = sample;
|
||||||
|
@ -307,3 +439,27 @@ float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||||
|
|
||||||
return distance;
|
return distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A standard gaussian function, used for weighting samples
|
||||||
|
float gaussian(float x, float sigma) {
|
||||||
|
return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This approximates the error function, needed for the gaussian integral
|
||||||
|
float2 erf(float2 x) {
|
||||||
|
float2 s = sign(x);
|
||||||
|
float2 a = abs(x);
|
||||||
|
x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
|
||||||
|
x *= x;
|
||||||
|
return s - s / (x * x);
|
||||||
|
}
|
||||||
|
|
||||||
|
float blur_along_x(float x, float y, float sigma, float corner,
|
||||||
|
float2 half_size) {
|
||||||
|
float delta = min(half_size.y - corner - abs(y), 0.);
|
||||||
|
float curved =
|
||||||
|
half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
|
||||||
|
float2 integral =
|
||||||
|
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
|
||||||
|
return integral.y - integral.x;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{ns_string, MetalRenderer, NSRange};
|
use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, size, AnyWindowHandle, Bounds, Event, Executor, KeyDownEvent, Keystroke, MacScreen,
|
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor,
|
||||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent,
|
GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||||
NSRectExt, Pixels, PlatformAtlas, PlatformInputHandler, PlatformScreen, PlatformWindow, Point,
|
MouseDownEvent, MouseMovedEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
|
||||||
Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
|
||||||
WindowPromptLevel,
|
WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -14,7 +14,9 @@ use cocoa::{
|
||||||
NSWindowStyleMask, NSWindowTitleVisibility,
|
NSWindowStyleMask, NSWindowTitleVisibility,
|
||||||
},
|
},
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
|
foundation::{
|
||||||
|
NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use core_graphics::display::CGRect;
|
use core_graphics::display::CGRect;
|
||||||
use ctor::ctor;
|
use ctor::ctor;
|
||||||
|
@ -365,7 +367,7 @@ impl MacWindowState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = self.frame();
|
let frame = self.frame();
|
||||||
let screen_size = self.native_window.screen().visibleFrame().size();
|
let screen_size = self.native_window.screen().visibleFrame().into();
|
||||||
if frame.size == screen_size {
|
if frame.size == screen_size {
|
||||||
WindowBounds::Maximized
|
WindowBounds::Maximized
|
||||||
} else {
|
} else {
|
||||||
|
@ -374,10 +376,10 @@ impl MacWindowState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame(&self) -> Bounds<Pixels> {
|
fn frame(&self) -> Bounds<GlobalPixels> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let frame = NSWindow::frame(self.native_window);
|
let frame = NSWindow::frame(self.native_window);
|
||||||
MacScreen::screen_bounds_from_native(frame)
|
display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,15 +443,33 @@ impl MacWindow {
|
||||||
msg_send![PANEL_CLASS, alloc]
|
msg_send![PANEL_CLASS, alloc]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let display = options
|
||||||
|
.display_id
|
||||||
|
.and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
|
||||||
|
.unwrap_or_else(|| MacDisplay::primary());
|
||||||
|
|
||||||
|
let mut target_screen = nil;
|
||||||
|
let screens = NSScreen::screens(nil);
|
||||||
|
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
||||||
|
for i in 0..count {
|
||||||
|
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
||||||
|
let device_description = NSScreen::deviceDescription(screen);
|
||||||
|
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
||||||
|
let screen_number = device_description.objectForKey_(screen_number_key);
|
||||||
|
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
|
||||||
|
if screen_number as u32 == display.id().0 {
|
||||||
|
target_screen = screen;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
|
let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
|
||||||
NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
|
NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
|
||||||
style_mask,
|
style_mask,
|
||||||
NSBackingStoreBuffered,
|
NSBackingStoreBuffered,
|
||||||
NO,
|
NO,
|
||||||
options
|
target_screen,
|
||||||
.screen
|
|
||||||
.map(|screen| MacScreen::from_handle(screen).native_screen)
|
|
||||||
.unwrap_or(nil),
|
|
||||||
);
|
);
|
||||||
assert!(!native_window.is_null());
|
assert!(!native_window.is_null());
|
||||||
|
|
||||||
|
@ -462,13 +482,13 @@ impl MacWindow {
|
||||||
native_window.setFrame_display_(screen.visibleFrame(), YES);
|
native_window.setFrame_display_(screen.visibleFrame(), YES);
|
||||||
}
|
}
|
||||||
WindowBounds::Fixed(bounds) => {
|
WindowBounds::Fixed(bounds) => {
|
||||||
let bounds = MacScreen::screen_bounds_to_native(bounds);
|
let display_bounds = display.bounds();
|
||||||
let screen_bounds = screen.visibleFrame();
|
let frame = if bounds.intersects(&display_bounds) {
|
||||||
if bounds.intersects(screen_bounds) {
|
display_bounds_to_native(bounds)
|
||||||
native_window.setFrame_display_(bounds, YES);
|
|
||||||
} else {
|
} else {
|
||||||
native_window.setFrame_display_(screen_bounds, YES);
|
display_bounds_to_native(display_bounds)
|
||||||
}
|
};
|
||||||
|
native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,11 +669,18 @@ impl PlatformWindow for MacWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen(&self) -> Rc<dyn PlatformScreen> {
|
fn display(&self) -> Rc<dyn PlatformDisplay> {
|
||||||
unsafe {
|
unsafe {
|
||||||
Rc::new(MacScreen {
|
let screen = self.0.lock().native_window.screen();
|
||||||
native_screen: self.0.as_ref().lock().native_window.screen(),
|
let device_description: id = msg_send![screen, deviceDescription];
|
||||||
})
|
let screen_number: id = NSDictionary::valueForKey_(
|
||||||
|
device_description,
|
||||||
|
NSString::alloc(nil).init_str("NSScreenNumber"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
|
||||||
|
|
||||||
|
Rc::new(MacDisplay(screen_number))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::Platform;
|
use super::Platform;
|
||||||
use crate::{Executor, ScreenId};
|
use crate::{DisplayId, Executor};
|
||||||
|
|
||||||
pub struct TestPlatform;
|
pub struct TestPlatform;
|
||||||
|
|
||||||
|
@ -15,6 +15,10 @@ impl Platform for TestPlatform {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn display_linker(&self) -> std::sync::Arc<dyn crate::PlatformDisplayLinker> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
|
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
@ -47,11 +51,11 @@ impl Platform for TestPlatform {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
|
fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen_by_id(&self, _id: ScreenId) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> {
|
fn display(&self, _id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,27 @@
|
||||||
use std::{iter::Peekable, mem, slice};
|
use crate::{
|
||||||
|
AtlasTextureId, AtlasTile, Bounds, Corners, Edges, Hsla, Point, ScaledContentMask, ScaledPixels,
|
||||||
use super::{Bounds, Hsla, Point};
|
};
|
||||||
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
|
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
|
use etagere::euclid::{Point3D, Vector3D};
|
||||||
|
use plane_split::{BspSplitter, Polygon as BspPolygon};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use std::{iter::Peekable, mem, slice};
|
||||||
|
|
||||||
// Exported to metal
|
// Exported to metal
|
||||||
pub type PointF = Point<f32>;
|
pub type PointF = Point<f32>;
|
||||||
pub type LayerId = SmallVec<[u32; 16]>;
|
pub type StackingOrder = SmallVec<[u32; 16]>;
|
||||||
|
pub type LayerId = u32;
|
||||||
|
pub type DrawOrder = u32;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
pub(crate) scale_factor: f32,
|
pub(crate) scale_factor: f32,
|
||||||
pub(crate) layers: BTreeMap<LayerId, SceneLayer>,
|
pub(crate) layers: BTreeMap<StackingOrder, LayerId>,
|
||||||
|
pub shadows: Vec<Shadow>,
|
||||||
|
pub quads: Vec<Quad>,
|
||||||
|
pub underlines: Vec<Underline>,
|
||||||
|
pub monochrome_sprites: Vec<MonochromeSprite>,
|
||||||
|
pub polychrome_sprites: Vec<PolychromeSprite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
|
@ -20,6 +29,11 @@ impl Scene {
|
||||||
Scene {
|
Scene {
|
||||||
scale_factor,
|
scale_factor,
|
||||||
layers: BTreeMap::new(),
|
layers: BTreeMap::new(),
|
||||||
|
shadows: Vec::new(),
|
||||||
|
quads: Vec::new(),
|
||||||
|
underlines: Vec::new(),
|
||||||
|
monochrome_sprites: Vec::new(),
|
||||||
|
polychrome_sprites: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,47 +41,125 @@ impl Scene {
|
||||||
Scene {
|
Scene {
|
||||||
scale_factor: self.scale_factor,
|
scale_factor: self.scale_factor,
|
||||||
layers: mem::take(&mut self.layers),
|
layers: mem::take(&mut self.layers),
|
||||||
|
shadows: mem::take(&mut self.shadows),
|
||||||
|
quads: mem::take(&mut self.quads),
|
||||||
|
underlines: mem::take(&mut self.underlines),
|
||||||
|
monochrome_sprites: mem::take(&mut self.monochrome_sprites),
|
||||||
|
polychrome_sprites: mem::take(&mut self.polychrome_sprites),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, stacking_order: LayerId, primitive: impl Into<Primitive>) {
|
pub fn insert(&mut self, layer_id: StackingOrder, primitive: impl Into<Primitive>) {
|
||||||
let layer = self.layers.entry(stacking_order).or_default();
|
let next_id = self.layers.len() as LayerId;
|
||||||
|
let layer_id = *self.layers.entry(layer_id).or_insert(next_id);
|
||||||
let primitive = primitive.into();
|
let primitive = primitive.into();
|
||||||
match primitive {
|
match primitive {
|
||||||
Primitive::Quad(quad) => {
|
Primitive::Shadow(mut shadow) => {
|
||||||
layer.quads.push(quad);
|
shadow.order = layer_id;
|
||||||
|
self.shadows.push(shadow);
|
||||||
}
|
}
|
||||||
Primitive::MonochromeSprite(sprite) => {
|
Primitive::Quad(mut quad) => {
|
||||||
layer.monochrome_sprites.push(sprite);
|
quad.order = layer_id;
|
||||||
|
self.quads.push(quad);
|
||||||
}
|
}
|
||||||
Primitive::PolychromeSprite(sprite) => {
|
Primitive::Underline(mut underline) => {
|
||||||
layer.polychrome_sprites.push(sprite);
|
underline.order = layer_id;
|
||||||
|
self.underlines.push(underline);
|
||||||
|
}
|
||||||
|
Primitive::MonochromeSprite(mut sprite) => {
|
||||||
|
sprite.order = layer_id;
|
||||||
|
self.monochrome_sprites.push(sprite);
|
||||||
|
}
|
||||||
|
Primitive::PolychromeSprite(mut sprite) => {
|
||||||
|
sprite.order = layer_id;
|
||||||
|
self.polychrome_sprites.push(sprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn layers(&mut self) -> impl Iterator<Item = &mut SceneLayer> {
|
pub(crate) fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||||
self.layers.values_mut()
|
// Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
|
||||||
}
|
let mut layer_z_values = vec![0.; self.layers.len()];
|
||||||
}
|
for (ix, layer_id) in self.layers.values().enumerate() {
|
||||||
|
layer_z_values[*layer_id as usize] = ix as f32 / self.layers.len() as f32;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
// Add all primitives to the BSP splitter to determine draw order
|
||||||
pub(crate) struct SceneLayer {
|
// todo!("reuse the same splitter")
|
||||||
pub quads: Vec<Quad>,
|
let mut splitter = BspSplitter::new();
|
||||||
pub monochrome_sprites: Vec<MonochromeSprite>,
|
|
||||||
pub polychrome_sprites: Vec<PolychromeSprite>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SceneLayer {
|
for (ix, shadow) in self.shadows.iter().enumerate() {
|
||||||
pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
|
let z = layer_z_values[shadow.order as LayerId as usize];
|
||||||
|
splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ix, quad) in self.quads.iter().enumerate() {
|
||||||
|
let z = layer_z_values[quad.order as LayerId as usize];
|
||||||
|
splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ix, underline) in self.underlines.iter().enumerate() {
|
||||||
|
let z = layer_z_values[underline.order as LayerId as usize];
|
||||||
|
splitter.add(
|
||||||
|
underline
|
||||||
|
.bounds
|
||||||
|
.to_bsp_polygon(z, (PrimitiveKind::Underline, ix)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() {
|
||||||
|
let z = layer_z_values[monochrome_sprite.order as LayerId as usize];
|
||||||
|
splitter.add(
|
||||||
|
monochrome_sprite
|
||||||
|
.bounds
|
||||||
|
.to_bsp_polygon(z, (PrimitiveKind::MonochromeSprite, ix)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ix, polychrome_sprite) in self.polychrome_sprites.iter().enumerate() {
|
||||||
|
let z = layer_z_values[polychrome_sprite.order as LayerId as usize];
|
||||||
|
splitter.add(
|
||||||
|
polychrome_sprite
|
||||||
|
.bounds
|
||||||
|
.to_bsp_polygon(z, (PrimitiveKind::PolychromeSprite, ix)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort all polygons, then reassign the order field of each primitive to `draw_order`
|
||||||
|
// We need primitives to be repr(C), hence the weird reuse of the order field for two different types.
|
||||||
|
for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() {
|
||||||
|
match polygon.anchor {
|
||||||
|
(PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder,
|
||||||
|
(PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
|
||||||
|
(PrimitiveKind::Underline, ix) => {
|
||||||
|
self.underlines[ix].order = draw_order as DrawOrder
|
||||||
|
}
|
||||||
|
(PrimitiveKind::MonochromeSprite, ix) => {
|
||||||
|
self.monochrome_sprites[ix].order = draw_order as DrawOrder
|
||||||
|
}
|
||||||
|
(PrimitiveKind::PolychromeSprite, ix) => {
|
||||||
|
self.polychrome_sprites[ix].order = draw_order as DrawOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the primitives
|
||||||
|
self.shadows.sort_unstable();
|
||||||
self.quads.sort_unstable();
|
self.quads.sort_unstable();
|
||||||
|
self.underlines.sort_unstable();
|
||||||
self.monochrome_sprites.sort_unstable();
|
self.monochrome_sprites.sort_unstable();
|
||||||
self.polychrome_sprites.sort_unstable();
|
self.polychrome_sprites.sort_unstable();
|
||||||
|
|
||||||
BatchIterator {
|
BatchIterator {
|
||||||
|
shadows: &self.shadows,
|
||||||
|
shadows_start: 0,
|
||||||
|
shadows_iter: self.shadows.iter().peekable(),
|
||||||
quads: &self.quads,
|
quads: &self.quads,
|
||||||
quads_start: 0,
|
quads_start: 0,
|
||||||
quads_iter: self.quads.iter().peekable(),
|
quads_iter: self.quads.iter().peekable(),
|
||||||
|
underlines: &self.underlines,
|
||||||
|
underlines_start: 0,
|
||||||
|
underlines_iter: self.underlines.iter().peekable(),
|
||||||
monochrome_sprites: &self.monochrome_sprites,
|
monochrome_sprites: &self.monochrome_sprites,
|
||||||
monochrome_sprites_start: 0,
|
monochrome_sprites_start: 0,
|
||||||
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
|
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
|
||||||
|
@ -82,6 +174,12 @@ struct BatchIterator<'a> {
|
||||||
quads: &'a [Quad],
|
quads: &'a [Quad],
|
||||||
quads_start: usize,
|
quads_start: usize,
|
||||||
quads_iter: Peekable<slice::Iter<'a, Quad>>,
|
quads_iter: Peekable<slice::Iter<'a, Quad>>,
|
||||||
|
shadows: &'a [Shadow],
|
||||||
|
shadows_start: usize,
|
||||||
|
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
|
||||||
|
underlines: &'a [Underline],
|
||||||
|
underlines_start: usize,
|
||||||
|
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
|
||||||
monochrome_sprites: &'a [MonochromeSprite],
|
monochrome_sprites: &'a [MonochromeSprite],
|
||||||
monochrome_sprites_start: usize,
|
monochrome_sprites_start: usize,
|
||||||
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
|
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
|
||||||
|
@ -94,50 +192,92 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||||
type Item = PrimitiveBatch<'a>;
|
type Item = PrimitiveBatch<'a>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut kinds_and_orders = [
|
let mut orders_and_kinds = [
|
||||||
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
|
|
||||||
(
|
(
|
||||||
PrimitiveKind::MonochromeSprite,
|
self.shadows_iter.peek().map(|s| s.order),
|
||||||
self.monochrome_sprites_iter.peek().map(|s| s.order),
|
PrimitiveKind::Shadow,
|
||||||
|
),
|
||||||
|
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
|
||||||
|
(
|
||||||
|
self.underlines_iter.peek().map(|u| u.order),
|
||||||
|
PrimitiveKind::Underline,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
self.monochrome_sprites_iter.peek().map(|s| s.order),
|
||||||
|
PrimitiveKind::MonochromeSprite,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
PrimitiveKind::PolychromeSprite,
|
|
||||||
self.polychrome_sprites_iter.peek().map(|s| s.order),
|
self.polychrome_sprites_iter.peek().map(|s| s.order),
|
||||||
|
PrimitiveKind::PolychromeSprite,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX));
|
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
|
||||||
|
|
||||||
let first = kinds_and_orders[0];
|
let first = orders_and_kinds[0];
|
||||||
let second = kinds_and_orders[1];
|
let second = orders_and_kinds[1];
|
||||||
let (batch_kind, max_order) = if first.1.is_some() {
|
let (batch_kind, max_order) = if first.0.is_some() {
|
||||||
(first.0, second.1.unwrap_or(u32::MAX))
|
(first.1, second.0.unwrap_or(u32::MAX))
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
match batch_kind {
|
match batch_kind {
|
||||||
|
PrimitiveKind::Shadow => {
|
||||||
|
let shadows_start = self.shadows_start;
|
||||||
|
let mut shadows_end = shadows_start;
|
||||||
|
while self
|
||||||
|
.shadows_iter
|
||||||
|
.next_if(|shadow| shadow.order <= max_order)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
shadows_end += 1;
|
||||||
|
}
|
||||||
|
self.shadows_start = shadows_end;
|
||||||
|
Some(PrimitiveBatch::Shadows(
|
||||||
|
&self.shadows[shadows_start..shadows_end],
|
||||||
|
))
|
||||||
|
}
|
||||||
PrimitiveKind::Quad => {
|
PrimitiveKind::Quad => {
|
||||||
let quads_start = self.quads_start;
|
let quads_start = self.quads_start;
|
||||||
let quads_end = quads_start
|
let mut quads_end = quads_start;
|
||||||
+ self
|
while self
|
||||||
.quads_iter
|
.quads_iter
|
||||||
.by_ref()
|
.next_if(|quad| quad.order <= max_order)
|
||||||
.take_while(|quad| quad.order <= max_order)
|
.is_some()
|
||||||
.count();
|
{
|
||||||
|
quads_end += 1;
|
||||||
|
}
|
||||||
self.quads_start = quads_end;
|
self.quads_start = quads_end;
|
||||||
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
|
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
|
||||||
}
|
}
|
||||||
|
PrimitiveKind::Underline => {
|
||||||
|
let underlines_start = self.underlines_start;
|
||||||
|
let mut underlines_end = underlines_start;
|
||||||
|
while self
|
||||||
|
.underlines_iter
|
||||||
|
.next_if(|underline| underline.order <= max_order)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
underlines_end += 1;
|
||||||
|
}
|
||||||
|
self.underlines_start = underlines_end;
|
||||||
|
Some(PrimitiveBatch::Underlines(
|
||||||
|
&self.underlines[underlines_start..underlines_end],
|
||||||
|
))
|
||||||
|
}
|
||||||
PrimitiveKind::MonochromeSprite => {
|
PrimitiveKind::MonochromeSprite => {
|
||||||
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
|
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
|
||||||
let sprites_start = self.monochrome_sprites_start;
|
let sprites_start = self.monochrome_sprites_start;
|
||||||
let sprites_end = sprites_start
|
let mut sprites_end = sprites_start;
|
||||||
+ self
|
while self
|
||||||
.monochrome_sprites_iter
|
.monochrome_sprites_iter
|
||||||
.by_ref()
|
.next_if(|sprite| {
|
||||||
.take_while(|sprite| {
|
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
||||||
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
})
|
||||||
})
|
.is_some()
|
||||||
.count();
|
{
|
||||||
|
sprites_end += 1;
|
||||||
|
}
|
||||||
self.monochrome_sprites_start = sprites_end;
|
self.monochrome_sprites_start = sprites_end;
|
||||||
Some(PrimitiveBatch::MonochromeSprites {
|
Some(PrimitiveBatch::MonochromeSprites {
|
||||||
texture_id,
|
texture_id,
|
||||||
|
@ -147,14 +287,16 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||||
PrimitiveKind::PolychromeSprite => {
|
PrimitiveKind::PolychromeSprite => {
|
||||||
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
|
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
|
||||||
let sprites_start = self.polychrome_sprites_start;
|
let sprites_start = self.polychrome_sprites_start;
|
||||||
let sprites_end = sprites_start
|
let mut sprites_end = self.polychrome_sprites_start;
|
||||||
+ self
|
while self
|
||||||
.polychrome_sprites_iter
|
.polychrome_sprites_iter
|
||||||
.by_ref()
|
.next_if(|sprite| {
|
||||||
.take_while(|sprite| {
|
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
||||||
sprite.order <= max_order && sprite.tile.texture_id == texture_id
|
})
|
||||||
})
|
.is_some()
|
||||||
.count();
|
{
|
||||||
|
sprites_end += 1;
|
||||||
|
}
|
||||||
self.polychrome_sprites_start = sprites_end;
|
self.polychrome_sprites_start = sprites_end;
|
||||||
Some(PrimitiveBatch::PolychromeSprites {
|
Some(PrimitiveBatch::PolychromeSprites {
|
||||||
texture_id,
|
texture_id,
|
||||||
|
@ -165,22 +307,30 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||||
pub enum PrimitiveKind {
|
pub enum PrimitiveKind {
|
||||||
|
Shadow,
|
||||||
|
#[default]
|
||||||
Quad,
|
Quad,
|
||||||
|
Underline,
|
||||||
MonochromeSprite,
|
MonochromeSprite,
|
||||||
PolychromeSprite,
|
PolychromeSprite,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Primitive {
|
pub enum Primitive {
|
||||||
|
Shadow(Shadow),
|
||||||
Quad(Quad),
|
Quad(Quad),
|
||||||
|
Underline(Underline),
|
||||||
MonochromeSprite(MonochromeSprite),
|
MonochromeSprite(MonochromeSprite),
|
||||||
PolychromeSprite(PolychromeSprite),
|
PolychromeSprite(PolychromeSprite),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) enum PrimitiveBatch<'a> {
|
pub(crate) enum PrimitiveBatch<'a> {
|
||||||
|
Shadows(&'a [Shadow]),
|
||||||
Quads(&'a [Quad]),
|
Quads(&'a [Quad]),
|
||||||
|
Underlines(&'a [Underline]),
|
||||||
MonochromeSprites {
|
MonochromeSprites {
|
||||||
texture_id: AtlasTextureId,
|
texture_id: AtlasTextureId,
|
||||||
sprites: &'a [MonochromeSprite],
|
sprites: &'a [MonochromeSprite],
|
||||||
|
@ -191,35 +341,18 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Quad {
|
pub struct Quad {
|
||||||
pub order: u32,
|
pub order: u32, // Initially a LayerId, then a DrawOrder.
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub clip_bounds: Bounds<ScaledPixels>,
|
pub content_mask: ScaledContentMask,
|
||||||
pub clip_corner_radii: Corners<ScaledPixels>,
|
|
||||||
pub background: Hsla,
|
pub background: Hsla,
|
||||||
pub border_color: Hsla,
|
pub border_color: Hsla,
|
||||||
pub corner_radii: Corners<ScaledPixels>,
|
pub corner_radii: Corners<ScaledPixels>,
|
||||||
pub border_widths: Edges<ScaledPixels>,
|
pub border_widths: Edges<ScaledPixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Quad {
|
|
||||||
pub fn vertices(&self) -> impl Iterator<Item = Point<ScaledPixels>> {
|
|
||||||
let x1 = self.bounds.origin.x;
|
|
||||||
let y1 = self.bounds.origin.y;
|
|
||||||
let x2 = x1 + self.bounds.size.width;
|
|
||||||
let y2 = y1 + self.bounds.size.height;
|
|
||||||
[
|
|
||||||
Point::new(x1, y1),
|
|
||||||
Point::new(x2, y1),
|
|
||||||
Point::new(x2, y2),
|
|
||||||
Point::new(x1, y2),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Quad {
|
impl Ord for Quad {
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
self.order.cmp(&other.order)
|
self.order.cmp(&other.order)
|
||||||
|
@ -238,6 +371,64 @@ impl From<Quad> for Primitive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Underline {
|
||||||
|
pub order: u32,
|
||||||
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
|
pub content_mask: ScaledContentMask,
|
||||||
|
pub thickness: ScaledPixels,
|
||||||
|
pub color: Hsla,
|
||||||
|
pub wavy: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Underline {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.order.cmp(&other.order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Underline {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Underline> for Primitive {
|
||||||
|
fn from(underline: Underline) -> Self {
|
||||||
|
Primitive::Underline(underline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Shadow {
|
||||||
|
pub order: u32,
|
||||||
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
|
pub corner_radii: Corners<ScaledPixels>,
|
||||||
|
pub content_mask: ScaledContentMask,
|
||||||
|
pub color: Hsla,
|
||||||
|
pub blur_radius: ScaledPixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Shadow {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.order.cmp(&other.order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Shadow {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Shadow> for Primitive {
|
||||||
|
fn from(shadow: Shadow) -> Self {
|
||||||
|
Primitive::Shadow(shadow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct MonochromeSprite {
|
pub struct MonochromeSprite {
|
||||||
|
@ -303,3 +494,76 @@ impl From<PolychromeSprite> for Primitive {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct AtlasId(pub(crate) usize);
|
pub struct AtlasId(pub(crate) usize);
|
||||||
|
|
||||||
|
impl Bounds<ScaledPixels> {
|
||||||
|
fn to_bsp_polygon<A: Copy>(&self, z: f32, anchor: A) -> BspPolygon<A> {
|
||||||
|
let upper_left = self.origin;
|
||||||
|
let upper_right = self.upper_right();
|
||||||
|
let lower_right = self.lower_right();
|
||||||
|
let lower_left = self.lower_left();
|
||||||
|
|
||||||
|
BspPolygon::from_points(
|
||||||
|
[
|
||||||
|
Point3D::new(upper_left.x.into(), upper_left.y.into(), z as f64),
|
||||||
|
Point3D::new(upper_right.x.into(), upper_right.y.into(), z as f64),
|
||||||
|
Point3D::new(lower_right.x.into(), lower_right.y.into(), z as f64),
|
||||||
|
Point3D::new(lower_left.x.into(), lower_left.y.into(), z as f64),
|
||||||
|
],
|
||||||
|
anchor,
|
||||||
|
)
|
||||||
|
.expect("Polygon should not be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{point, size};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use smallvec::smallvec;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scene() {
|
||||||
|
let mut scene = Scene::new(1.0);
|
||||||
|
assert_eq!(scene.layers.len(), 0);
|
||||||
|
|
||||||
|
scene.insert(smallvec![1], quad());
|
||||||
|
scene.insert(smallvec![2], shadow());
|
||||||
|
scene.insert(smallvec![3], quad());
|
||||||
|
|
||||||
|
let mut batches_count = 0;
|
||||||
|
for _ in scene.batches() {
|
||||||
|
batches_count += 1;
|
||||||
|
}
|
||||||
|
assert_eq!(batches_count, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quad() -> Quad {
|
||||||
|
Quad {
|
||||||
|
order: 0,
|
||||||
|
bounds: Bounds {
|
||||||
|
origin: point(ScaledPixels(0.), ScaledPixels(0.)),
|
||||||
|
size: size(ScaledPixels(100.), ScaledPixels(100.)),
|
||||||
|
},
|
||||||
|
content_mask: Default::default(),
|
||||||
|
background: Default::default(),
|
||||||
|
border_color: Default::default(),
|
||||||
|
corner_radii: Default::default(),
|
||||||
|
border_widths: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadow() -> Shadow {
|
||||||
|
Shadow {
|
||||||
|
order: Default::default(),
|
||||||
|
bounds: Bounds {
|
||||||
|
origin: point(ScaledPixels(0.), ScaledPixels(0.)),
|
||||||
|
size: size(ScaledPixels(100.), ScaledPixels(100.)),
|
||||||
|
},
|
||||||
|
corner_radii: Default::default(),
|
||||||
|
content_mask: Default::default(),
|
||||||
|
color: Default::default(),
|
||||||
|
blur_radius: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
|
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
|
||||||
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
|
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
|
||||||
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle,
|
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, Shadow,
|
||||||
SharedString, Size, SizeRefinement, ViewContext, WindowContext,
|
SharedString, Size, SizeRefinement, ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
use smallvec::SmallVec;
|
||||||
pub use taffy::style::{
|
pub use taffy::style::{
|
||||||
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
|
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
|
||||||
Overflow, Position,
|
Overflow, Position,
|
||||||
|
@ -89,10 +90,21 @@ pub struct Style {
|
||||||
#[refineable]
|
#[refineable]
|
||||||
pub corner_radii: Corners<AbsoluteLength>,
|
pub corner_radii: Corners<AbsoluteLength>,
|
||||||
|
|
||||||
|
/// Box Shadow of the element
|
||||||
|
pub box_shadow: SmallVec<[BoxShadow; 2]>,
|
||||||
|
|
||||||
/// TEXT
|
/// TEXT
|
||||||
pub text: TextStyleRefinement,
|
pub text: TextStyleRefinement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct BoxShadow {
|
||||||
|
pub color: Hsla,
|
||||||
|
pub offset: Point<Pixels>,
|
||||||
|
pub blur_radius: Pixels,
|
||||||
|
pub spread_radius: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Refineable, Clone, Debug)]
|
#[derive(Refineable, Clone, Debug)]
|
||||||
#[refineable(debug)]
|
#[refineable(debug)]
|
||||||
pub struct TextStyle {
|
pub struct TextStyle {
|
||||||
|
@ -229,32 +241,55 @@ impl Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paints the background of an element styled with this style.
|
/// Paints the background of an element styled with this style.
|
||||||
pub fn paint<V: 'static>(&self, order: u32, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
||||||
let rem_size = cx.rem_size();
|
let rem_size = cx.rem_size();
|
||||||
let scale = cx.scale_factor();
|
let scale = cx.scale_factor();
|
||||||
|
|
||||||
|
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,
|
||||||
|
Shadow {
|
||||||
|
order: 0,
|
||||||
|
bounds: shadow_bounds.scale(scale),
|
||||||
|
content_mask: content_mask.scale(scale),
|
||||||
|
corner_radii: self
|
||||||
|
.corner_radii
|
||||||
|
.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);
|
let background_color = self.fill.as_ref().and_then(Fill::color);
|
||||||
if background_color.is_some() || self.is_border_visible() {
|
if background_color.is_some() || self.is_border_visible() {
|
||||||
let layer_id = cx.current_layer_id();
|
let content_mask = cx.content_mask();
|
||||||
cx.scene().insert(
|
cx.stack(1, |cx| {
|
||||||
layer_id,
|
let order = cx.current_stacking_order();
|
||||||
Quad {
|
cx.scene().insert(
|
||||||
order,
|
order,
|
||||||
bounds: bounds.scale(scale),
|
Quad {
|
||||||
clip_bounds: bounds.scale(scale), // todo!
|
order: 0,
|
||||||
clip_corner_radii: self
|
bounds: bounds.scale(scale),
|
||||||
.corner_radii
|
content_mask: content_mask.scale(scale),
|
||||||
.map(|length| length.to_pixels(rem_size).scale(scale)),
|
background: background_color.unwrap_or_default(),
|
||||||
background: background_color.unwrap_or_default(),
|
border_color: self.border_color.unwrap_or_default(),
|
||||||
border_color: self.border_color.unwrap_or_default(),
|
corner_radii: self
|
||||||
corner_radii: self
|
.corner_radii
|
||||||
.corner_radii
|
.to_pixels(bounds.size, rem_size)
|
||||||
.map(|length| length.to_pixels(rem_size).scale(scale)),
|
.scale(scale),
|
||||||
border_widths: self
|
border_widths: self.border_widths.to_pixels(rem_size).scale(scale),
|
||||||
.border_widths
|
},
|
||||||
.map(|length| length.to_pixels(rem_size).scale(scale)),
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,6 +333,7 @@ impl Default for Style {
|
||||||
fill: None,
|
fill: None,
|
||||||
border_color: None,
|
border_color: None,
|
||||||
corner_radii: Corners::default(),
|
corner_radii: Corners::default(),
|
||||||
|
box_shadow: Default::default(),
|
||||||
text: TextStyleRefinement::default(),
|
text: TextStyleRefinement::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,7 +344,7 @@ impl Default for Style {
|
||||||
pub struct UnderlineStyle {
|
pub struct UnderlineStyle {
|
||||||
pub thickness: Pixels,
|
pub thickness: Pixels,
|
||||||
pub color: Option<Hsla>,
|
pub color: Option<Hsla>,
|
||||||
pub squiggly: bool,
|
pub wavy: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent,
|
self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
|
||||||
Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement,
|
FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement,
|
||||||
|
Styled, TextStyleRefinement,
|
||||||
};
|
};
|
||||||
|
use smallvec::smallvec;
|
||||||
|
|
||||||
pub trait StyleHelpers: Sized + Styled<Style = Style> {
|
pub trait StyleHelpers: Styled<Style = Style> {
|
||||||
gpui3_macros::style_helpers!();
|
gpui3_macros::style_helpers!();
|
||||||
|
|
||||||
fn h(mut self, height: Length) -> Self {
|
fn h(mut self, height: Length) -> Self {
|
||||||
|
@ -147,6 +149,103 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shadow(mut self) -> Self {
|
||||||
|
self.declared_style().box_shadow = Some(smallvec![
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.1),
|
||||||
|
offset: point(px(0.), px(1.)),
|
||||||
|
blur_radius: px(3.),
|
||||||
|
spread_radius: px(0.),
|
||||||
|
},
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.1),
|
||||||
|
offset: point(px(0.), px(1.)),
|
||||||
|
blur_radius: px(2.),
|
||||||
|
spread_radius: px(-1.),
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadow_none(mut self) -> Self {
|
||||||
|
self.declared_style().box_shadow = Some(Default::default());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadow_sm(mut self) -> Self {
|
||||||
|
self.declared_style().box_shadow = Some(smallvec![BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.05),
|
||||||
|
offset: point(px(0.), px(1.)),
|
||||||
|
blur_radius: px(2.),
|
||||||
|
spread_radius: px(0.),
|
||||||
|
}]);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadow_md(mut self) -> Self {
|
||||||
|
self.declared_style().box_shadow = Some(smallvec![
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0.5, 0., 0., 1.0),
|
||||||
|
offset: point(px(0.), px(4.)),
|
||||||
|
blur_radius: px(6.),
|
||||||
|
spread_radius: px(-1.),
|
||||||
|
},
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.1),
|
||||||
|
offset: point(px(0.), px(2.)),
|
||||||
|
blur_radius: px(4.),
|
||||||
|
spread_radius: px(-2.),
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadow_lg(mut self) -> Self {
|
||||||
|
self.declared_style().box_shadow = Some(smallvec![
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.1),
|
||||||
|
offset: point(px(0.), px(10.)),
|
||||||
|
blur_radius: px(15.),
|
||||||
|
spread_radius: px(-3.),
|
||||||
|
},
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.1),
|
||||||
|
offset: point(px(0.), px(4.)),
|
||||||
|
blur_radius: px(6.),
|
||||||
|
spread_radius: px(-4.),
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadow_xl(mut self) -> Self {
|
||||||
|
self.declared_style().box_shadow = Some(smallvec![
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.1),
|
||||||
|
offset: point(px(0.), px(20.)),
|
||||||
|
blur_radius: px(25.),
|
||||||
|
spread_radius: px(-5.),
|
||||||
|
},
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.1),
|
||||||
|
offset: point(px(0.), px(8.)),
|
||||||
|
blur_radius: px(10.),
|
||||||
|
spread_radius: px(-6.),
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadow_2xl(mut self) -> Self {
|
||||||
|
self.declared_style().box_shadow = Some(smallvec![BoxShadow {
|
||||||
|
color: hsla(0., 0., 0., 0.25),
|
||||||
|
offset: point(px(0.), px(25.)),
|
||||||
|
blur_radius: px(50.),
|
||||||
|
spread_radius: px(-12.),
|
||||||
|
}]);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
|
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
|
||||||
let style: &mut StyleRefinement = self.declared_style();
|
let style: &mut StyleRefinement = self.declared_style();
|
||||||
&mut style.text
|
&mut style.text
|
||||||
|
@ -206,6 +305,69 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text_decoration_none(mut self) -> Self {
|
||||||
|
self.text_style()
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.underline = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.color = Some(color.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_solid(mut self) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.wavy = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_wavy(mut self) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.wavy = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_0(mut self) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.thickness = px(0.);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_1(mut self) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.thickness = px(1.);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_2(mut self) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.thickness = px(2.);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_4(mut self) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.thickness = px(4.);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_decoration_8(mut self) -> Self {
|
||||||
|
let style = self.text_style().get_or_insert_with(Default::default);
|
||||||
|
let underline = style.underline.get_or_insert_with(Default::default);
|
||||||
|
underline.thickness = px(8.);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn font(mut self, family_name: impl Into<SharedString>) -> Self {
|
fn font(mut self, family_name: impl Into<SharedString>) -> Self {
|
||||||
self.text_style()
|
self.text_style()
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_with(Default::default)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use super::{
|
use super::{
|
||||||
AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
|
AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Result, Size, Style,
|
||||||
Style,
|
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -14,7 +13,7 @@ use taffy::{
|
||||||
pub struct TaffyLayoutEngine {
|
pub struct TaffyLayoutEngine {
|
||||||
taffy: Taffy,
|
taffy: Taffy,
|
||||||
children_to_parents: HashMap<LayoutId, LayoutId>,
|
children_to_parents: HashMap<LayoutId, LayoutId>,
|
||||||
absolute_layouts: HashMap<LayoutId, Layout>,
|
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaffyLayoutEngine {
|
impl TaffyLayoutEngine {
|
||||||
|
@ -22,7 +21,7 @@ impl TaffyLayoutEngine {
|
||||||
TaffyLayoutEngine {
|
TaffyLayoutEngine {
|
||||||
taffy: Taffy::new(),
|
taffy: Taffy::new(),
|
||||||
children_to_parents: HashMap::default(),
|
children_to_parents: HashMap::default(),
|
||||||
absolute_layouts: HashMap::default(),
|
absolute_layout_bounds: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,19 +126,24 @@ impl TaffyLayoutEngine {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
|
pub fn layout_bounds(&mut self, id: LayoutId) -> Result<Bounds<Pixels>> {
|
||||||
if let Some(layout) = self.absolute_layouts.get(&id).cloned() {
|
if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
|
||||||
return Ok(layout);
|
return Ok(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut relative_layout: Layout = self.taffy.layout(id.into()).map(Into::into)?;
|
let layout = self.taffy.layout(id.into())?;
|
||||||
if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
|
let mut bounds = Bounds {
|
||||||
let parent_layout = self.layout(parent_id)?;
|
origin: layout.location.into(),
|
||||||
relative_layout.bounds.origin += parent_layout.bounds.origin;
|
size: layout.size.into(),
|
||||||
}
|
};
|
||||||
self.absolute_layouts.insert(id, relative_layout.clone());
|
|
||||||
|
|
||||||
Ok(relative_layout)
|
if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
|
||||||
|
let parent_bounds = self.layout_bounds(parent_id)?;
|
||||||
|
bounds.origin += parent_bounds.origin;
|
||||||
|
}
|
||||||
|
self.absolute_layout_bounds.insert(id, bounds);
|
||||||
|
|
||||||
|
Ok(bounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,15 +424,3 @@ impl From<Pixels> for AvailableSpace {
|
||||||
AvailableSpace::Definite(pixels)
|
AvailableSpace::Definite(pixels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&taffy::tree::Layout> for Layout {
|
|
||||||
fn from(layout: &taffy::tree::Layout) -> Self {
|
|
||||||
Layout {
|
|
||||||
order: layout.order,
|
|
||||||
bounds: Bounds {
|
|
||||||
origin: layout.location.into(),
|
|
||||||
size: layout.size.into(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
|
black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine,
|
||||||
ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
|
ShapedRun, UnderlineStyle, WindowContext,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -90,15 +90,14 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo!
|
|
||||||
pub fn paint(
|
pub fn paint(
|
||||||
&self,
|
&self,
|
||||||
layout: &Layout,
|
bounds: Bounds<Pixels>,
|
||||||
visible_bounds: Bounds<Pixels>,
|
visible_bounds: Bounds<Pixels>, // todo!("use clipping")
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let origin = layout.bounds.origin;
|
let origin = bounds.origin;
|
||||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||||
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
|
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
|
||||||
|
|
||||||
|
@ -135,9 +134,11 @@ impl Line {
|
||||||
origin.y + baseline_offset.y + (self.layout.descent * 0.618),
|
origin.y + baseline_offset.y + (self.layout.descent * 0.618),
|
||||||
),
|
),
|
||||||
UnderlineStyle {
|
UnderlineStyle {
|
||||||
color: style_run.underline.color,
|
color: Some(
|
||||||
|
style_run.underline.color.unwrap_or(style_run.color),
|
||||||
|
),
|
||||||
thickness: style_run.underline.thickness,
|
thickness: style_run.underline.thickness,
|
||||||
squiggly: style_run.underline.squiggly,
|
wavy: style_run.underline.wavy,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -154,22 +155,19 @@ impl Line {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_underline_origin, _underline_style)) = finished_underline {
|
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||||
todo!()
|
cx.paint_underline(
|
||||||
|
underline_origin,
|
||||||
|
glyph_origin.x - underline_origin.x,
|
||||||
|
&underline_style,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if glyph.is_emoji {
|
if glyph.is_emoji {
|
||||||
cx.paint_emoji(
|
cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
|
||||||
glyph_origin,
|
|
||||||
layout.order,
|
|
||||||
run.font_id,
|
|
||||||
glyph.id,
|
|
||||||
self.layout.font_size,
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
cx.paint_glyph(
|
cx.paint_glyph(
|
||||||
glyph_origin,
|
glyph_origin,
|
||||||
layout.order,
|
|
||||||
run.font_id,
|
run.font_id,
|
||||||
glyph.id,
|
glyph.id,
|
||||||
self.layout.font_size,
|
self.layout.font_size,
|
||||||
|
@ -179,15 +177,13 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_underline_start, _underline_style)) = underline.take() {
|
if let Some((underline_start, underline_style)) = underline.take() {
|
||||||
let _line_end_x = origin.x + self.layout.width;
|
let line_end_x = origin.x + self.layout.width;
|
||||||
// cx.scene().push_underline(Underline {
|
cx.paint_underline(
|
||||||
// origin: underline_start,
|
underline_start,
|
||||||
// width: line_end_x - underline_start.x,
|
line_end_x - underline_start.x,
|
||||||
// color: underline_style.color,
|
&underline_style,
|
||||||
// thickness: underline_style.thickness.into(),
|
)?;
|
||||||
// squiggly: underline_style.squiggly,
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -196,7 +192,7 @@ impl Line {
|
||||||
pub fn paint_wrapped(
|
pub fn paint_wrapped(
|
||||||
&self,
|
&self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
_visible_bounds: Bounds<Pixels>,
|
_visible_bounds: Bounds<Pixels>, // todo!("use clipping")
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
boundaries: &[ShapedBoundary],
|
boundaries: &[ShapedBoundary],
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
|
@ -221,14 +217,12 @@ impl Line {
|
||||||
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
|
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
|
||||||
{
|
{
|
||||||
boundaries.next();
|
boundaries.next();
|
||||||
if let Some((_underline_origin, _underline_style)) = underline.take() {
|
if let Some((underline_origin, underline_style)) = underline.take() {
|
||||||
// cx.scene().push_underline(Underline {
|
cx.paint_underline(
|
||||||
// origin: underline_origin,
|
underline_origin,
|
||||||
// width: glyph_origin.x - underline_origin.x,
|
glyph_origin.x - underline_origin.x,
|
||||||
// thickness: underline_style.thickness.into(),
|
&underline_style,
|
||||||
// color: underline_style.color.unwrap(),
|
)?;
|
||||||
// squiggly: underline_style.squiggly,
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glyph_origin = point(origin.x, glyph_origin.y + line_height);
|
glyph_origin = point(origin.x, glyph_origin.y + line_height);
|
||||||
|
@ -257,7 +251,7 @@ impl Line {
|
||||||
style_run.underline.color.unwrap_or(style_run.color),
|
style_run.underline.color.unwrap_or(style_run.color),
|
||||||
),
|
),
|
||||||
thickness: style_run.underline.thickness,
|
thickness: style_run.underline.thickness,
|
||||||
squiggly: style_run.underline.squiggly,
|
wavy: style_run.underline.wavy,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -268,14 +262,12 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_underline_origin, _underline_style)) = finished_underline {
|
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||||
// cx.scene().push_underline(Underline {
|
cx.paint_underline(
|
||||||
// origin: underline_origin,
|
underline_origin,
|
||||||
// width: glyph_origin.x - underline_origin.x,
|
glyph_origin.x - underline_origin.x,
|
||||||
// thickness: underline_style.thickness.into(),
|
&underline_style,
|
||||||
// color: underline_style.color.unwrap(),
|
)?;
|
||||||
// squiggly: underline_style.squiggly,
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_system = cx.text_system();
|
let text_system = cx.text_system();
|
||||||
|
@ -306,15 +298,13 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_underline_origin, _underline_style)) = underline.take() {
|
if let Some((underline_origin, underline_style)) = underline.take() {
|
||||||
// let line_end_x = glyph_origin.x + self.layout.width - prev_position;
|
let line_end_x = glyph_origin.x + self.layout.width - prev_position;
|
||||||
// cx.scene().push_underline(Underline {
|
cx.paint_underline(
|
||||||
// origin: underline_origin,
|
underline_origin,
|
||||||
// width: line_end_x - underline_origin.x,
|
line_end_x - underline_origin.x,
|
||||||
// thickness: underline_style.thickness.into(),
|
&underline_style,
|
||||||
// color: underline_style.color,
|
)?;
|
||||||
// squiggly: underline_style.squiggly,
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyElement, Element, Handle, IntoAnyElement, Layout, LayoutId, Result, ViewContext,
|
AnyElement, Bounds, Element, Handle, IntoAnyElement, LayoutId, Pixels, Result, ViewContext,
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use std::{any::Any, marker::PhantomData, sync::Arc};
|
use std::{any::Any, marker::PhantomData, sync::Arc};
|
||||||
|
@ -67,7 +67,7 @@ impl<S: Send + Sync + 'static, P: Send + 'static> Element for View<S, P> {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Layout,
|
_: Bounds<Pixels>,
|
||||||
_: &mut Self::State,
|
_: &mut Self::State,
|
||||||
element: &mut Self::FrameState,
|
element: &mut Self::FrameState,
|
||||||
cx: &mut ViewContext<Self::State>,
|
cx: &mut ViewContext<Self::State>,
|
||||||
|
@ -81,7 +81,7 @@ trait ViewObject: Send + 'static {
|
||||||
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
|
fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
element: &mut dyn Any,
|
element: &mut dyn Any,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
@ -97,7 +97,12 @@ impl<S: Send + Sync + 'static, P: Send + 'static> ViewObject for View<S, P> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, _: Layout, element: &mut dyn Any, cx: &mut WindowContext) -> Result<()> {
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
element: &mut dyn Any,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Result<()> {
|
||||||
self.state.update(cx, |state, cx| {
|
self.state.update(cx, |state, cx| {
|
||||||
let element = element.downcast_mut::<AnyElement<S>>().unwrap();
|
let element = element.downcast_mut::<AnyElement<S>>().unwrap();
|
||||||
element.paint(state, None, cx)
|
element.paint(state, None, cx)
|
||||||
|
@ -124,12 +129,12 @@ impl<S: 'static> Element for AnyView<S> {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
_: &mut (),
|
_: &mut (),
|
||||||
element: &mut Box<dyn Any>,
|
element: &mut Box<dyn Any>,
|
||||||
cx: &mut ViewContext<Self::State>,
|
cx: &mut ViewContext<Self::State>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.view.lock().paint(layout, element.as_mut(), cx)
|
self.view.lock().paint(bounds, element.as_mut(), cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
|
||||||
BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId,
|
AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
|
||||||
GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly,
|
Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
|
||||||
MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference,
|
MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
|
||||||
RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style,
|
PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
|
||||||
TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
|
SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
|
||||||
|
WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -16,13 +17,14 @@ pub struct AnyWindow {}
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
||||||
|
pub(crate) display_id: DisplayId, // todo!("make private again?")
|
||||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||||
rem_size: Pixels,
|
rem_size: Pixels,
|
||||||
content_size: Size<Pixels>,
|
content_size: Size<Pixels>,
|
||||||
layout_engine: TaffyLayoutEngine,
|
layout_engine: TaffyLayoutEngine,
|
||||||
pub(crate) root_view: Option<AnyView<()>>,
|
pub(crate) root_view: Option<AnyView<()>>,
|
||||||
mouse_position: Point<Pixels>,
|
mouse_position: Point<Pixels>,
|
||||||
current_layer_id: LayerId,
|
current_stacking_order: StackingOrder,
|
||||||
content_mask_stack: Vec<ContentMask>,
|
content_mask_stack: Vec<ContentMask>,
|
||||||
pub(crate) scene: Scene,
|
pub(crate) scene: Scene,
|
||||||
pub(crate) dirty: bool,
|
pub(crate) dirty: bool,
|
||||||
|
@ -35,6 +37,7 @@ impl Window {
|
||||||
cx: &mut MainThread<AppContext>,
|
cx: &mut MainThread<AppContext>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let platform_window = cx.platform().open_window(handle, options);
|
let platform_window = cx.platform().open_window(handle, options);
|
||||||
|
let display_id = platform_window.display().id();
|
||||||
let sprite_atlas = platform_window.sprite_atlas();
|
let sprite_atlas = platform_window.sprite_atlas();
|
||||||
let mouse_position = platform_window.mouse_position();
|
let mouse_position = platform_window.mouse_position();
|
||||||
let content_size = platform_window.content_size();
|
let content_size = platform_window.content_size();
|
||||||
|
@ -46,6 +49,12 @@ impl Window {
|
||||||
cx.update_window(handle, |cx| {
|
cx.update_window(handle, |cx| {
|
||||||
cx.window.scene = Scene::new(scale_factor);
|
cx.window.scene = Scene::new(scale_factor);
|
||||||
cx.window.content_size = content_size;
|
cx.window.content_size = content_size;
|
||||||
|
cx.window.display_id = cx
|
||||||
|
.window
|
||||||
|
.platform_window
|
||||||
|
.borrow_on_main_thread()
|
||||||
|
.display()
|
||||||
|
.id();
|
||||||
cx.window.dirty = true;
|
cx.window.dirty = true;
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
@ -57,13 +66,14 @@ impl Window {
|
||||||
Window {
|
Window {
|
||||||
handle,
|
handle,
|
||||||
platform_window,
|
platform_window,
|
||||||
|
display_id,
|
||||||
sprite_atlas,
|
sprite_atlas,
|
||||||
rem_size: px(16.),
|
rem_size: px(16.),
|
||||||
content_size,
|
content_size,
|
||||||
layout_engine: TaffyLayoutEngine::new(),
|
layout_engine: TaffyLayoutEngine::new(),
|
||||||
root_view: None,
|
root_view: None,
|
||||||
mouse_position,
|
mouse_position,
|
||||||
current_layer_id: SmallVec::new(),
|
current_stacking_order: SmallVec::new(),
|
||||||
content_mask_stack: Vec::new(),
|
content_mask_stack: Vec::new(),
|
||||||
scene: Scene::new(scale_factor),
|
scene: Scene::new(scale_factor),
|
||||||
dirty: true,
|
dirty: true,
|
||||||
|
@ -89,7 +99,7 @@ impl ContentMask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Default, Clone, Debug, PartialEq, Eq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ScaledContentMask {
|
pub struct ScaledContentMask {
|
||||||
bounds: Bounds<ScaledPixels>,
|
bounds: Bounds<ScaledPixels>,
|
||||||
|
@ -133,6 +143,45 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
AsyncWindowContext::new(self.app.to_async(), self.window.handle)
|
AsyncWindowContext::new(self.app.to_async(), self.window.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
|
||||||
|
let f = Box::new(f);
|
||||||
|
let display_id = self.window.display_id;
|
||||||
|
let async_cx = self.to_async();
|
||||||
|
let app_cx = self.app_mut();
|
||||||
|
match app_cx.next_frame_callbacks.entry(display_id) {
|
||||||
|
collections::hash_map::Entry::Occupied(mut entry) => {
|
||||||
|
if entry.get().is_empty() {
|
||||||
|
app_cx.display_linker.start(display_id);
|
||||||
|
}
|
||||||
|
entry.get_mut().push(f);
|
||||||
|
}
|
||||||
|
collections::hash_map::Entry::Vacant(entry) => {
|
||||||
|
app_cx.display_linker.set_output_callback(
|
||||||
|
display_id,
|
||||||
|
Box::new(move |_current_time, _output_time| {
|
||||||
|
let _ = async_cx.update(|cx| {
|
||||||
|
let callbacks = cx
|
||||||
|
.next_frame_callbacks
|
||||||
|
.get_mut(&display_id)
|
||||||
|
.unwrap()
|
||||||
|
.drain(..)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for callback in callbacks {
|
||||||
|
callback(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() {
|
||||||
|
cx.display_linker.stop(display_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app_cx.display_linker.start(display_id);
|
||||||
|
entry.insert(vec![f]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn<Fut, R>(
|
pub fn spawn<Fut, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static,
|
f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static,
|
||||||
|
@ -176,11 +225,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
.request_measured_layout(style, rem_size, measure)
|
.request_measured_layout(style, rem_size, measure)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
|
pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Result<Bounds<Pixels>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.window
|
.window
|
||||||
.layout_engine
|
.layout_engine
|
||||||
.layout(layout_id)
|
.layout_bounds(layout_id)
|
||||||
.map(Into::into)?)
|
.map(Into::into)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,20 +250,51 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
self.window.current_layer_id.push(order);
|
self.window.current_stacking_order.push(order);
|
||||||
let result = f(self);
|
let result = f(self);
|
||||||
self.window.current_layer_id.pop();
|
self.window.current_stacking_order.pop();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_layer_id(&self) -> LayerId {
|
pub fn current_stacking_order(&self) -> StackingOrder {
|
||||||
self.window.current_layer_id.clone()
|
self.window.current_stacking_order.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paint_underline(
|
||||||
|
&mut self,
|
||||||
|
origin: Point<Pixels>,
|
||||||
|
width: Pixels,
|
||||||
|
style: &UnderlineStyle,
|
||||||
|
) -> Result<()> {
|
||||||
|
let scale_factor = self.scale_factor();
|
||||||
|
let height = if style.wavy {
|
||||||
|
style.thickness * 3.
|
||||||
|
} else {
|
||||||
|
style.thickness
|
||||||
|
};
|
||||||
|
let bounds = Bounds {
|
||||||
|
origin,
|
||||||
|
size: size(width, height),
|
||||||
|
};
|
||||||
|
let content_mask = self.content_mask();
|
||||||
|
let layer_id = self.current_stacking_order();
|
||||||
|
self.window.scene.insert(
|
||||||
|
layer_id,
|
||||||
|
Underline {
|
||||||
|
order: 0,
|
||||||
|
bounds: bounds.scale(scale_factor),
|
||||||
|
content_mask: content_mask.scale(scale_factor),
|
||||||
|
thickness: style.thickness.scale(scale_factor),
|
||||||
|
color: style.color.unwrap_or_default(),
|
||||||
|
wavy: style.wavy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_glyph(
|
pub fn paint_glyph(
|
||||||
&mut self,
|
&mut self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
order: u32,
|
|
||||||
font_id: FontId,
|
font_id: FontId,
|
||||||
glyph_id: GlyphId,
|
glyph_id: GlyphId,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
|
@ -237,7 +317,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
|
|
||||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||||
if !raster_bounds.is_zero() {
|
if !raster_bounds.is_zero() {
|
||||||
let layer_id = self.current_layer_id();
|
let layer_id = self.current_stacking_order();
|
||||||
let tile =
|
let tile =
|
||||||
self.window
|
self.window
|
||||||
.sprite_atlas
|
.sprite_atlas
|
||||||
|
@ -254,7 +334,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
self.window.scene.insert(
|
self.window.scene.insert(
|
||||||
layer_id,
|
layer_id,
|
||||||
MonochromeSprite {
|
MonochromeSprite {
|
||||||
order,
|
order: 0,
|
||||||
bounds,
|
bounds,
|
||||||
content_mask,
|
content_mask,
|
||||||
color,
|
color,
|
||||||
|
@ -268,7 +348,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
pub fn paint_emoji(
|
pub fn paint_emoji(
|
||||||
&mut self,
|
&mut self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
order: u32,
|
|
||||||
font_id: FontId,
|
font_id: FontId,
|
||||||
glyph_id: GlyphId,
|
glyph_id: GlyphId,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
|
@ -287,7 +366,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
|
|
||||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||||
if !raster_bounds.is_zero() {
|
if !raster_bounds.is_zero() {
|
||||||
let layer_id = self.current_layer_id();
|
let layer_id = self.current_stacking_order();
|
||||||
let tile =
|
let tile =
|
||||||
self.window
|
self.window
|
||||||
.sprite_atlas
|
.sprite_atlas
|
||||||
|
@ -304,7 +383,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
self.window.scene.insert(
|
self.window.scene.insert(
|
||||||
layer_id,
|
layer_id,
|
||||||
PolychromeSprite {
|
PolychromeSprite {
|
||||||
order,
|
order: 0,
|
||||||
bounds,
|
bounds,
|
||||||
corner_radii: Default::default(),
|
corner_radii: Default::default(),
|
||||||
content_mask,
|
content_mask,
|
||||||
|
@ -319,7 +398,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
pub fn paint_svg(
|
pub fn paint_svg(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
order: u32,
|
|
||||||
path: SharedString,
|
path: SharedString,
|
||||||
color: Hsla,
|
color: Hsla,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
@ -333,7 +411,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
.map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
|
.map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let layer_id = self.current_layer_id();
|
let layer_id = self.current_stacking_order();
|
||||||
let tile =
|
let tile =
|
||||||
self.window
|
self.window
|
||||||
.sprite_atlas
|
.sprite_atlas
|
||||||
|
@ -346,7 +424,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
self.window.scene.insert(
|
self.window.scene.insert(
|
||||||
layer_id,
|
layer_id,
|
||||||
MonochromeSprite {
|
MonochromeSprite {
|
||||||
order,
|
order: 0,
|
||||||
bounds,
|
bounds,
|
||||||
content_mask,
|
content_mask,
|
||||||
color,
|
color,
|
||||||
|
@ -361,7 +439,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
corner_radii: Corners<Pixels>,
|
corner_radii: Corners<Pixels>,
|
||||||
order: u32,
|
|
||||||
data: Arc<ImageData>,
|
data: Arc<ImageData>,
|
||||||
grayscale: bool,
|
grayscale: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
@ -369,7 +446,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
let bounds = bounds.scale(scale_factor);
|
let bounds = bounds.scale(scale_factor);
|
||||||
let params = RenderImageParams { image_id: data.id };
|
let params = RenderImageParams { image_id: data.id };
|
||||||
|
|
||||||
let layer_id = self.current_layer_id();
|
let order = self.current_stacking_order();
|
||||||
let tile = self
|
let tile = self
|
||||||
.window
|
.window
|
||||||
.sprite_atlas
|
.sprite_atlas
|
||||||
|
@ -380,9 +457,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
let corner_radii = corner_radii.scale(scale_factor);
|
let corner_radii = corner_radii.scale(scale_factor);
|
||||||
|
|
||||||
self.window.scene.insert(
|
self.window.scene.insert(
|
||||||
layer_id,
|
order,
|
||||||
PolychromeSprite {
|
PolychromeSprite {
|
||||||
order,
|
order: 0,
|
||||||
bounds,
|
bounds,
|
||||||
content_mask,
|
content_mask,
|
||||||
corner_radii,
|
corner_radii,
|
||||||
|
@ -401,12 +478,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
|
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
|
||||||
let available_space = cx.window.content_size.map(Into::into);
|
let available_space = cx.window.content_size.map(Into::into);
|
||||||
|
|
||||||
let started_at = std::time::Instant::now();
|
|
||||||
cx.window
|
cx.window
|
||||||
.layout_engine
|
.layout_engine
|
||||||
.compute_layout(root_layout_id, available_space)?;
|
.compute_layout(root_layout_id, available_space)?;
|
||||||
println!("compute_layout took {:?}", started_at.elapsed());
|
let layout = cx.window.layout_engine.layout_bounds(root_layout_id)?;
|
||||||
let layout = cx.window.layout_engine.layout(root_layout_id)?;
|
|
||||||
|
|
||||||
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
|
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
|
||||||
cx.window.root_view = Some(root_view);
|
cx.window.root_view = Some(root_view);
|
||||||
|
@ -573,6 +648,20 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
|
||||||
self.entities.weak_handle(self.entity_id)
|
self.entities.weak_handle(self.entity_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
self.window.current_stacking_order.push(order);
|
||||||
|
let result = f(self);
|
||||||
|
self.window.current_stacking_order.pop();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut S, &mut ViewContext<S>) + Send + 'static) {
|
||||||
|
let entity = self.handle();
|
||||||
|
self.window_cx.on_next_frame(move |cx| {
|
||||||
|
entity.update(cx, f).ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn observe<E: Send + Sync + 'static>(
|
pub fn observe<E: Send + Sync + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Handle<E>,
|
handle: &Handle<E>,
|
||||||
|
@ -726,9 +815,3 @@ pub struct AnyWindowHandle {
|
||||||
pub(crate) id: WindowId,
|
pub(crate) id: WindowId,
|
||||||
state_type: TypeId,
|
state_type: TypeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Layout {
|
|
||||||
pub order: u32,
|
|
||||||
pub bounds: Bounds<Pixels>,
|
|
||||||
}
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl CollabPanel {
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.take(10)
|
.take(5)
|
||||||
.flatten(),
|
.flatten(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -168,7 +168,8 @@ impl CollabPanel {
|
||||||
.uri(avatar_uri)
|
.uri(avatar_uri)
|
||||||
.size_3p5()
|
.size_3p5()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.fill(theme.middle.positive.default.foreground),
|
.fill(theme.middle.positive.default.foreground)
|
||||||
|
.shadow_md(),
|
||||||
)
|
)
|
||||||
.child(label),
|
.child(label),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
use gpui3::{
|
use gpui3::{
|
||||||
BorrowAppContext, Element, Hsla, Layout, LayoutId, Result, ViewContext, WindowContext,
|
BorrowAppContext, Bounds, Element, Hsla, LayoutId, Pixels, Result, ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ impl<E: Element> Element for Themed<E> {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout,
|
bounds: Bounds<Pixels>,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
frame_state: &mut Self::FrameState,
|
frame_state: &mut Self::FrameState,
|
||||||
cx: &mut ViewContext<Self::State>,
|
cx: &mut ViewContext<Self::State>,
|
||||||
|
@ -171,7 +171,7 @@ impl<E: Element> Element for Themed<E> {
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
cx.with_state(self.theme.clone(), |cx| {
|
cx.with_state(self.theme.clone(), |cx| {
|
||||||
self.child.paint(layout, state, frame_state, cx)
|
self.child.paint(bounds, state, frame_state, cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
mod rose_pine_dawn;
|
mod rose_pine;
|
||||||
|
|
||||||
pub use rose_pine_dawn::*;
|
pub use rose_pine::*;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::theme::Theme;
|
use crate::theme::Theme;
|
||||||
use gpui3::serde_json::{self, json};
|
use gpui3::serde_json::{self, json};
|
||||||
|
|
||||||
pub fn rose_pine_dawn() -> Theme {
|
pub fn rose_pine() -> Theme {
|
||||||
serde_json::from_value(json! {
|
serde_json::from_value(json! {
|
||||||
{
|
{
|
||||||
"name": "Rosé Pine",
|
"name": "Rosé Pine",
|
||||||
|
@ -843,3 +843,844 @@ pub fn rose_pine_dawn() -> Theme {
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rose_pine_dawn() -> Theme {
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"name": "Rosé Pine Dawn",
|
||||||
|
"is_light": true,
|
||||||
|
"ramps": {},
|
||||||
|
"lowest": {
|
||||||
|
"base": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dcd8d8",
|
||||||
|
"border": "#dcd6d5",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#dcd6d5",
|
||||||
|
"border": "#dcd6d5",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#efe6df",
|
||||||
|
"border": "#dcd6d5",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#c1bac1",
|
||||||
|
"border": "#a9a3b0",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dcd8d8",
|
||||||
|
"border": "#d0cccf",
|
||||||
|
"foreground": "#938fa3"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#c7c0c5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dcd8d8",
|
||||||
|
"border": "#dcd6d5",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#dcd6d5",
|
||||||
|
"border": "#dcd6d5",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#efe6df",
|
||||||
|
"border": "#dcd6d5",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#c1bac1",
|
||||||
|
"border": "#a9a3b0",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dcd8d8",
|
||||||
|
"border": "#d0cccf",
|
||||||
|
"foreground": "#938fa3"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#c7c0c5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"default": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#e5e0df",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#d4d0d2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#dbd5d4",
|
||||||
|
"border": "#dbd3d1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#f6f1eb",
|
||||||
|
"foreground": "#b1abb5"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#d6d1d1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accent": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dde9eb",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#c3d7db",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#b6cfd3",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#a3c3c9",
|
||||||
|
"border": "#8db6bd",
|
||||||
|
"foreground": "#06090a"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dde9eb",
|
||||||
|
"border": "#d0e0e3",
|
||||||
|
"foreground": "#72a5ae"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#06090a",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#a8c7cd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"positive": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dbeee7",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#bee0d5",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#b0dacb",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#9bd0bf",
|
||||||
|
"border": "#82c6b1",
|
||||||
|
"foreground": "#060a09"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dbeee7",
|
||||||
|
"border": "#cde7de",
|
||||||
|
"foreground": "#63b89f"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#060a09",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#a1d4c3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"default": {
|
||||||
|
"background": "#ffebd6",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#ffdab7",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#fed2a6",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#fbc891",
|
||||||
|
"border": "#f7bc77",
|
||||||
|
"foreground": "#330704"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#ffebd6",
|
||||||
|
"border": "#ffe2c7",
|
||||||
|
"foreground": "#f1ac57"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#330704",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#fccb97"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"negative": {
|
||||||
|
"default": {
|
||||||
|
"background": "#f1dfe3",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#e6c6cd",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#e0bac2",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#d8a8b3",
|
||||||
|
"border": "#ce94a3",
|
||||||
|
"foreground": "#0b0708"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#f1dfe3",
|
||||||
|
"border": "#ecd2d8",
|
||||||
|
"foreground": "#c17b8e"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#0b0708",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#dbadb8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"middle": {
|
||||||
|
"base": {
|
||||||
|
"default": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#e5e0df",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#d4d0d2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#dbd5d4",
|
||||||
|
"border": "#dbd3d1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#f6f1eb",
|
||||||
|
"foreground": "#b1abb5"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#d6d1d1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"default": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#e5e0df",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#d4d0d2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#dbd5d4",
|
||||||
|
"border": "#dbd3d1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#f6f1eb",
|
||||||
|
"foreground": "#b1abb5"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#d6d1d1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"default": {
|
||||||
|
"background": "#faf4ed",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#fdf8f1",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#fdf8f2",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#e6e1e0",
|
||||||
|
"border": "#d0cccf",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#faf4ed",
|
||||||
|
"border": "#fcf6ef",
|
||||||
|
"foreground": "#efe6df"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#ede9e5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accent": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dde9eb",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#c3d7db",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#b6cfd3",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#a3c3c9",
|
||||||
|
"border": "#8db6bd",
|
||||||
|
"foreground": "#06090a"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dde9eb",
|
||||||
|
"border": "#d0e0e3",
|
||||||
|
"foreground": "#72a5ae"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#06090a",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#a8c7cd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"positive": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dbeee7",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#bee0d5",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#b0dacb",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#9bd0bf",
|
||||||
|
"border": "#82c6b1",
|
||||||
|
"foreground": "#060a09"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dbeee7",
|
||||||
|
"border": "#cde7de",
|
||||||
|
"foreground": "#63b89f"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#060a09",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#a1d4c3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"default": {
|
||||||
|
"background": "#ffebd6",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#ffdab7",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#fed2a6",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#fbc891",
|
||||||
|
"border": "#f7bc77",
|
||||||
|
"foreground": "#330704"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#ffebd6",
|
||||||
|
"border": "#ffe2c7",
|
||||||
|
"foreground": "#f1ac57"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#330704",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#fccb97"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"negative": {
|
||||||
|
"default": {
|
||||||
|
"background": "#f1dfe3",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#e6c6cd",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#e0bac2",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#d8a8b3",
|
||||||
|
"border": "#ce94a3",
|
||||||
|
"foreground": "#0b0708"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#f1dfe3",
|
||||||
|
"border": "#ecd2d8",
|
||||||
|
"foreground": "#c17b8e"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#0b0708",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#dbadb8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"highest": {
|
||||||
|
"base": {
|
||||||
|
"default": {
|
||||||
|
"background": "#faf4ed",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#fdf8f1",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#fdf8f2",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#e6e1e0",
|
||||||
|
"border": "#d0cccf",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#faf4ed",
|
||||||
|
"border": "#fcf6ef",
|
||||||
|
"foreground": "#efe6df"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#ede9e5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"default": {
|
||||||
|
"background": "#faf4ed",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#fdf8f1",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#fdf8f2",
|
||||||
|
"border": "#fdf8f1",
|
||||||
|
"foreground": "#706c8c"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#e6e1e0",
|
||||||
|
"border": "#d0cccf",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#faf4ed",
|
||||||
|
"border": "#fcf6ef",
|
||||||
|
"foreground": "#efe6df"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#ede9e5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"default": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#e5e0df",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#d4d0d2",
|
||||||
|
"border": "#e5e0df",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#dbd5d4",
|
||||||
|
"border": "#dbd3d1",
|
||||||
|
"foreground": "#575279"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#fef9f2",
|
||||||
|
"border": "#f6f1eb",
|
||||||
|
"foreground": "#b1abb5"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#575279",
|
||||||
|
"border": "#faf4ed",
|
||||||
|
"foreground": "#d6d1d1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accent": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dde9eb",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#c3d7db",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#b6cfd3",
|
||||||
|
"border": "#c3d7db",
|
||||||
|
"foreground": "#57949f"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#a3c3c9",
|
||||||
|
"border": "#8db6bd",
|
||||||
|
"foreground": "#06090a"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dde9eb",
|
||||||
|
"border": "#d0e0e3",
|
||||||
|
"foreground": "#72a5ae"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#06090a",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#a8c7cd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"positive": {
|
||||||
|
"default": {
|
||||||
|
"background": "#dbeee7",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#bee0d5",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#b0dacb",
|
||||||
|
"border": "#bee0d5",
|
||||||
|
"foreground": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#9bd0bf",
|
||||||
|
"border": "#82c6b1",
|
||||||
|
"foreground": "#060a09"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#dbeee7",
|
||||||
|
"border": "#cde7de",
|
||||||
|
"foreground": "#63b89f"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#060a09",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#a1d4c3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"default": {
|
||||||
|
"background": "#ffebd6",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#ffdab7",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#fed2a6",
|
||||||
|
"border": "#ffdab7",
|
||||||
|
"foreground": "#e99d35"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#fbc891",
|
||||||
|
"border": "#f7bc77",
|
||||||
|
"foreground": "#330704"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#ffebd6",
|
||||||
|
"border": "#ffe2c7",
|
||||||
|
"foreground": "#f1ac57"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#330704",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#fccb97"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"negative": {
|
||||||
|
"default": {
|
||||||
|
"background": "#f1dfe3",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"background": "#e6c6cd",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"background": "#e0bac2",
|
||||||
|
"border": "#e6c6cd",
|
||||||
|
"foreground": "#b4647a"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"background": "#d8a8b3",
|
||||||
|
"border": "#ce94a3",
|
||||||
|
"foreground": "#0b0708"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"background": "#f1dfe3",
|
||||||
|
"border": "#ecd2d8",
|
||||||
|
"foreground": "#c17b8e"
|
||||||
|
},
|
||||||
|
"inverted": {
|
||||||
|
"background": "#0b0708",
|
||||||
|
"border": "#ffffff",
|
||||||
|
"foreground": "#dbadb8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"popover_shadow": {
|
||||||
|
"blur": 4,
|
||||||
|
"color": "#2c2a4d33",
|
||||||
|
"offset": [
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modal_shadow": {
|
||||||
|
"blur": 16,
|
||||||
|
"color": "#2c2a4d33",
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"players": {
|
||||||
|
"0": {
|
||||||
|
"selection": "#57949f3d",
|
||||||
|
"cursor": "#57949f"
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"selection": "#3eaa8e3d",
|
||||||
|
"cursor": "#3eaa8e"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"selection": "#7c697f3d",
|
||||||
|
"cursor": "#7c697f"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"selection": "#907aa93d",
|
||||||
|
"cursor": "#907aa9"
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"selection": "#907aa93d",
|
||||||
|
"cursor": "#907aa9"
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"selection": "#2a69833d",
|
||||||
|
"cursor": "#2a6983"
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"selection": "#b4647a3d",
|
||||||
|
"cursor": "#b4647a"
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"selection": "#e99d353d",
|
||||||
|
"cursor": "#e99d35"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syntax": {
|
||||||
|
"comment": {
|
||||||
|
"color": "#9893a5"
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
"color": "#286983"
|
||||||
|
},
|
||||||
|
"punctuation": {
|
||||||
|
"color": "#797593"
|
||||||
|
},
|
||||||
|
"variable": {
|
||||||
|
"color": "#575279"
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
"color": "#ea9d34"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"color": "#56949f"
|
||||||
|
},
|
||||||
|
"type.builtin": {
|
||||||
|
"color": "#56949f"
|
||||||
|
},
|
||||||
|
"boolean": {
|
||||||
|
"color": "#d7827e"
|
||||||
|
},
|
||||||
|
"function": {
|
||||||
|
"color": "#d7827e"
|
||||||
|
},
|
||||||
|
"keyword": {
|
||||||
|
"color": "#286983"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"color": "#56949f"
|
||||||
|
},
|
||||||
|
"function.method": {
|
||||||
|
"color": "#d7827e"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"color": "#ea9d34"
|
||||||
|
},
|
||||||
|
"link_text": {
|
||||||
|
"color": "#56949f",
|
||||||
|
"italic": false
|
||||||
|
},
|
||||||
|
"link_uri": {
|
||||||
|
"color": "#d7827e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_family": {
|
||||||
|
"neutral": {
|
||||||
|
"low": 39.80392156862745,
|
||||||
|
"high": 95.49019607843137,
|
||||||
|
"range": 55.686274509803916,
|
||||||
|
"scaling_value": 1.7957746478873242
|
||||||
|
},
|
||||||
|
"red": {
|
||||||
|
"low": 0,
|
||||||
|
"high": 100,
|
||||||
|
"range": 100,
|
||||||
|
"scaling_value": 1
|
||||||
|
},
|
||||||
|
"orange": {
|
||||||
|
"low": 0,
|
||||||
|
"high": 100,
|
||||||
|
"range": 100,
|
||||||
|
"scaling_value": 1
|
||||||
|
},
|
||||||
|
"yellow": {
|
||||||
|
"low": 8.823529411764707,
|
||||||
|
"high": 100,
|
||||||
|
"range": 91.17647058823529,
|
||||||
|
"scaling_value": 1.0967741935483872
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"low": 0,
|
||||||
|
"high": 100,
|
||||||
|
"range": 100,
|
||||||
|
"scaling_value": 1
|
||||||
|
},
|
||||||
|
"cyan": {
|
||||||
|
"low": 0,
|
||||||
|
"high": 100,
|
||||||
|
"range": 100,
|
||||||
|
"scaling_value": 1
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"low": 0,
|
||||||
|
"high": 100,
|
||||||
|
"range": 100,
|
||||||
|
"scaling_value": 1
|
||||||
|
},
|
||||||
|
"violet": {
|
||||||
|
"low": 0,
|
||||||
|
"high": 100,
|
||||||
|
"range": 100,
|
||||||
|
"scaling_value": 1
|
||||||
|
},
|
||||||
|
"magenta": {
|
||||||
|
"low": 0,
|
||||||
|
"high": 100,
|
||||||
|
"range": 100,
|
||||||
|
"scaling_value": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
}
|
|
@ -28,9 +28,8 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
|
||||||
let theme = rose_pine_dawn();
|
|
||||||
|
|
||||||
themed(rose_pine_dawn(), cx, |cx| {
|
themed(rose_pine_dawn(), cx, |cx| {
|
||||||
|
let theme = theme(cx);
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.v_stack()
|
.v_stack()
|
||||||
|
@ -201,7 +200,13 @@ impl Titlebar {
|
||||||
// .fill(theme.lowest.base.hovered.background)
|
// .fill(theme.lowest.base.hovered.background)
|
||||||
// .active()
|
// .active()
|
||||||
// .fill(theme.lowest.base.pressed.background)
|
// .fill(theme.lowest.base.pressed.background)
|
||||||
.child(div().text_sm().child("branch")),
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.text_decoration_1()
|
||||||
|
.text_decoration_wavy()
|
||||||
|
.child("branch"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue