Merge branch 'gpui2' into marshall/gpui2-playground
This commit is contained in:
@@ -47,9 +47,11 @@ fn generate_shader_bindings() -> PathBuf {
|
||||
"Pixels".into(),
|
||||
"PointF".into(),
|
||||
"Hsla".into(),
|
||||
"ScaledContentMask".into(),
|
||||
"ContentMask".into(),
|
||||
"Uniforms".into(),
|
||||
"AtlasTile".into(),
|
||||
"PathRasterizationInputIndex".into(),
|
||||
"PathVertex_ScaledPixels".into(),
|
||||
"ShadowInputIndex".into(),
|
||||
"Shadow".into(),
|
||||
"QuadInputIndex".into(),
|
||||
@@ -59,6 +61,7 @@ fn generate_shader_bindings() -> PathBuf {
|
||||
"SpriteInputIndex".into(),
|
||||
"MonochromeSprite".into(),
|
||||
"PolychromeSprite".into(),
|
||||
"PathSprite".into(),
|
||||
]);
|
||||
config.no_includes = true;
|
||||
config.enumeration.prefix_with_name = true;
|
||||
|
||||
@@ -22,10 +22,12 @@ pub trait Element: 'static {
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait ParentElement<S> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]>;
|
||||
pub trait ParentElement {
|
||||
type State;
|
||||
|
||||
fn child(mut self, child: impl IntoAnyElement<S>) -> Self
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]>;
|
||||
|
||||
fn child(mut self, child: impl IntoAnyElement<Self::State>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -33,7 +35,7 @@ pub trait ParentElement<S> {
|
||||
self
|
||||
}
|
||||
|
||||
fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<S>>) -> Self
|
||||
fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<Self::State>>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
mod div;
|
||||
mod hoverable;
|
||||
mod img;
|
||||
mod stateless;
|
||||
mod svg;
|
||||
mod text;
|
||||
|
||||
pub use div::*;
|
||||
pub use hoverable::*;
|
||||
pub use img::*;
|
||||
pub use stateless::*;
|
||||
pub use svg::*;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
AnyElement, Bounds, Element, LayoutId, Overflow, ParentElement, Pixels, Point, Refineable,
|
||||
RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
|
||||
AnyElement, Bounds, Element, Interactive, LayoutId, MouseEventListeners, Overflow,
|
||||
ParentElement, Pixels, Point, Refineable, RefinementCascade, Result, Style, Styled,
|
||||
ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
@@ -9,7 +10,7 @@ use util::ResultExt;
|
||||
|
||||
pub struct Div<S: 'static> {
|
||||
styles: RefinementCascade<Style>,
|
||||
// handlers: InteractionHandlers<V>,
|
||||
listeners: MouseEventListeners<S>,
|
||||
children: SmallVec<[AnyElement<S>; 2]>,
|
||||
scroll_state: Option<ScrollState>,
|
||||
}
|
||||
@@ -17,7 +18,7 @@ pub struct Div<S: 'static> {
|
||||
pub fn div<S>() -> Div<S> {
|
||||
Div {
|
||||
styles: Default::default(),
|
||||
// handlers: Default::default(),
|
||||
listeners: Default::default(),
|
||||
children: Default::default(),
|
||||
scroll_state: None,
|
||||
}
|
||||
@@ -42,7 +43,7 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut S,
|
||||
child_layouts: &mut Self::FrameState,
|
||||
child_layout_ids: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> Result<()> {
|
||||
let style = self.computed_style();
|
||||
@@ -52,10 +53,13 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
|
||||
let overflow = &style.overflow;
|
||||
style.apply_text_style(cx, |cx| {
|
||||
cx.stack(z_index + 1, |cx| {
|
||||
style.apply_overflow(bounds, cx, |cx| self.paint_children(overflow, state, cx))
|
||||
style.apply_overflow(bounds, cx, |cx| {
|
||||
self.listeners.paint(bounds, cx);
|
||||
self.paint_children(overflow, state, cx)
|
||||
})
|
||||
})
|
||||
})?;
|
||||
self.handle_scroll(bounds, style.overflow.clone(), child_layouts, cx);
|
||||
self.handle_scroll(bounds, style.overflow.clone(), child_layout_ids, cx);
|
||||
|
||||
// todo!("enable inspector")
|
||||
// if cx.is_inspector_enabled() {
|
||||
@@ -251,16 +255,16 @@ impl<V> Styled for Div<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> StyleHelpers for Div<V> {}
|
||||
impl<V: Send + Sync + 'static> Interactive<V> for Div<V> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<V> {
|
||||
&mut self.listeners
|
||||
}
|
||||
}
|
||||
|
||||
// impl<V> Interactive<V> for Div<V> {
|
||||
// fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
// &mut self.handlers
|
||||
// }
|
||||
// }
|
||||
impl<S: 'static> ParentElement for Div<S> {
|
||||
type State = S;
|
||||
|
||||
impl<V: 'static> ParentElement<V> for Div<V> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +1,97 @@
|
||||
use crate::{
|
||||
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
||||
interactive::{InteractionHandlers, Interactive},
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
ViewContext,
|
||||
AnyElement, Bounds, DispatchPhase, Element, Interactive, MouseEventListeners, MouseMoveEvent,
|
||||
ParentElement, Pixels, Styled, ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
|
||||
use refineable::{CascadeSlot, Refineable, RefinementCascade};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
};
|
||||
|
||||
pub struct Hoverable<E: Styleable> {
|
||||
hovered: Rc<Cell<bool>>,
|
||||
pub struct Hoverable<E: Styled> {
|
||||
hovered: Arc<AtomicBool>,
|
||||
cascade_slot: CascadeSlot,
|
||||
hovered_style: <E::Style as Refineable>::Refinement,
|
||||
child: E,
|
||||
}
|
||||
|
||||
pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
|
||||
Hoverable {
|
||||
hovered: Rc::new(Cell::new(false)),
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
hovered_style: Default::default(),
|
||||
child,
|
||||
impl<E: Styled> Hoverable<E> {
|
||||
pub fn new(mut child: E) -> Self {
|
||||
Self {
|
||||
hovered: Arc::new(AtomicBool::new(false)),
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
hovered_style: Default::default(),
|
||||
child,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable> Styleable for Hoverable<E> {
|
||||
impl<E> Styled for Hoverable<E>
|
||||
where
|
||||
E: Styled,
|
||||
{
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
&mut self.hovered_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
|
||||
type PaintState = E::PaintState;
|
||||
impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Hoverable<E> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<S> {
|
||||
self.child.listeners()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element + Styled> Element for Hoverable<E> {
|
||||
type State = E::State;
|
||||
type FrameState = E::FrameState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(self.child.layout(view, cx)?)
|
||||
state: &mut Self::State,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<(crate::LayoutId, Self::FrameState)> {
|
||||
Ok(self.child.layout(state, cx)?)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
paint_state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
self.hovered.set(bounds.contains_point(cx.mouse_position()));
|
||||
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::State,
|
||||
frame_state: &mut Self::FrameState,
|
||||
cx: &mut ViewContext<Self::State>,
|
||||
) -> Result<()> {
|
||||
let hovered = bounds.contains_point(cx.mouse_position());
|
||||
let slot = self.cascade_slot;
|
||||
let style = self.hovered.get().then_some(self.hovered_style.clone());
|
||||
let style = hovered.then_some(self.hovered_style.clone());
|
||||
self.style_cascade().set(slot, style);
|
||||
self.hovered.store(hovered, SeqCst);
|
||||
|
||||
let hovered = self.hovered.clone();
|
||||
cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
|
||||
cx.bubble_event();
|
||||
if bounds.contains_point(cx.mouse_position()) != hovered.get() {
|
||||
cx.repaint();
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
if bounds.contains_point(event.position) != hovered.load(SeqCst) {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.child
|
||||
.paint(view, parent_origin, layout, paint_state, cx);
|
||||
self.child.paint(bounds, state, frame_state, cx)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
|
||||
impl<E: ParentElement + Styled> ParentElement for Hoverable<E> {
|
||||
type State = E::State;
|
||||
|
||||
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
self.child.interaction_handlers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
BorrowWindow, Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers,
|
||||
Styled, ViewContext,
|
||||
BorrowWindow, Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, Styled,
|
||||
ViewContext,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use refineable::RefinementCascade;
|
||||
@@ -98,5 +98,3 @@ impl<S> Styled for Img<S> {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StyleHelpers for Img<S> {}
|
||||
|
||||
@@ -85,8 +85,6 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
|
||||
|
||||
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
self.child.interaction_handlers()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers, Styled};
|
||||
use crate::{Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, Styled};
|
||||
use refineable::RefinementCascade;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
@@ -68,5 +68,3 @@ impl<S> Styled for Svg<S> {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StyleHelpers for Svg<S> {}
|
||||
|
||||
@@ -34,6 +34,83 @@ pub enum TouchPhase {
|
||||
Ended,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseDownEvent {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseUpEvent {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Navigate(NavigationDirection),
|
||||
}
|
||||
|
||||
impl MouseButton {
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
MouseButton::Left,
|
||||
MouseButton::Right,
|
||||
MouseButton::Middle,
|
||||
MouseButton::Navigate(NavigationDirection::Back),
|
||||
MouseButton::Navigate(NavigationDirection::Forward),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MouseButton {
|
||||
fn default() -> Self {
|
||||
Self::Left
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum NavigationDirection {
|
||||
Back,
|
||||
Forward,
|
||||
}
|
||||
|
||||
impl Default for NavigationDirection {
|
||||
fn default() -> Self {
|
||||
Self::Back
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseMoveEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ScrollWheelEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub delta: ScrollDelta,
|
||||
pub modifiers: Modifiers,
|
||||
pub touch_phase: TouchPhase,
|
||||
}
|
||||
|
||||
impl Deref for ScrollWheelEvent {
|
||||
type Target = Modifiers;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.modifiers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ScrollDelta {
|
||||
Pixels(Point<Pixels>),
|
||||
@@ -63,99 +140,13 @@ impl ScrollDelta {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScrollWheelEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub delta: ScrollDelta,
|
||||
pub modifiers: Modifiers,
|
||||
/// If the platform supports returning the phase of a scroll wheel event, it will be stored here
|
||||
pub phase: Option<TouchPhase>,
|
||||
}
|
||||
|
||||
impl Deref for ScrollWheelEvent {
|
||||
type Target = Modifiers;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.modifiers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum NavigationDirection {
|
||||
Back,
|
||||
Forward,
|
||||
}
|
||||
|
||||
impl Default for NavigationDirection {
|
||||
fn default() -> Self {
|
||||
Self::Back
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Navigate(NavigationDirection),
|
||||
}
|
||||
|
||||
impl MouseButton {
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
MouseButton::Left,
|
||||
MouseButton::Right,
|
||||
MouseButton::Middle,
|
||||
MouseButton::Navigate(NavigationDirection::Back),
|
||||
MouseButton::Navigate(NavigationDirection::Forward),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MouseButton {
|
||||
fn default() -> Self {
|
||||
Self::Left
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseDownEvent {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseUpEvent {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseUp {
|
||||
pub button: MouseButton,
|
||||
pub position: Point<Pixels>,
|
||||
pub modifiers: Modifiers,
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseMovedEvent {
|
||||
pub struct MouseExitEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseExitedEvent {
|
||||
pub position: Point<Pixels>,
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
impl Deref for MouseExitedEvent {
|
||||
impl Deref for MouseExitEvent {
|
||||
type Target = Modifiers;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -170,8 +161,8 @@ pub enum Event {
|
||||
ModifiersChanged(ModifiersChangedEvent),
|
||||
MouseDown(MouseDownEvent),
|
||||
MouseUp(MouseUpEvent),
|
||||
MouseMoved(MouseMovedEvent),
|
||||
MouseExited(MouseExitedEvent),
|
||||
MouseMoved(MouseMoveEvent),
|
||||
MouseExited(MouseExitEvent),
|
||||
ScrollWheel(ScrollWheelEvent),
|
||||
}
|
||||
|
||||
@@ -289,6 +289,12 @@ impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bound
|
||||
let lower_right = self.lower_right().min(&other.lower_right());
|
||||
Self::from_corners(upper_left, lower_right)
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
let top_left = self.origin.min(&other.origin);
|
||||
let bottom_right = self.lower_right().max(&other.lower_right());
|
||||
Bounds::from_corners(top_left, bottom_right)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rhs> Mul<Rhs> for Bounds<T>
|
||||
|
||||
@@ -3,9 +3,11 @@ mod assets;
|
||||
mod color;
|
||||
mod element;
|
||||
mod elements;
|
||||
mod events;
|
||||
mod executor;
|
||||
mod geometry;
|
||||
mod image_cache;
|
||||
mod interactive;
|
||||
mod platform;
|
||||
mod scene;
|
||||
mod style;
|
||||
@@ -24,10 +26,12 @@ pub use assets::*;
|
||||
pub use color::*;
|
||||
pub use element::*;
|
||||
pub use elements::*;
|
||||
pub use events::*;
|
||||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use gpui3_macros::*;
|
||||
pub use image_cache::*;
|
||||
pub use interactive::*;
|
||||
pub use platform::*;
|
||||
pub use refineable::*;
|
||||
pub use scene::*;
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
use crate::{
|
||||
Bounds, DispatchPhase, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
ScrollWheelEvent, ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait Interactive<S: 'static + Send + Sync> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<S>;
|
||||
|
||||
fn on_mouse_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.listeners()
|
||||
.mouse_down
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.contains_point(event.position)
|
||||
{
|
||||
handler(view, event, cx)
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_mouse_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.listeners()
|
||||
.mouse_up
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.contains_point(event.position)
|
||||
{
|
||||
handler(view, event, cx)
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_mouse_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.listeners()
|
||||
.mouse_down
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& event.button == button
|
||||
&& !bounds.contains_point(event.position)
|
||||
{
|
||||
handler(view, event, cx)
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_mouse_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.listeners()
|
||||
.mouse_up
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& event.button == button
|
||||
&& !bounds.contains_point(event.position)
|
||||
{
|
||||
handler(view, event, cx);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_click(
|
||||
self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, (&MouseDownEvent, &MouseUpEvent), &mut ViewContext<S>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let down_event = Arc::new(Mutex::new(None));
|
||||
self.on_mouse_down(button, {
|
||||
let down_event = down_event.clone();
|
||||
move |_, event, _| {
|
||||
down_event.lock().replace(event.clone());
|
||||
}
|
||||
})
|
||||
.on_mouse_up_out(button, {
|
||||
let down_event = down_event.clone();
|
||||
move |_, _, _| {
|
||||
down_event.lock().take();
|
||||
}
|
||||
})
|
||||
.on_mouse_up(button, move |view, event, cx| {
|
||||
if let Some(down_event) = down_event.lock().take() {
|
||||
handler(view, (&down_event, event), cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_mouse_move(
|
||||
mut self,
|
||||
handler: impl Fn(&mut S, &MouseMoveEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.listeners()
|
||||
.mouse_move
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_scroll_wheel(
|
||||
mut self,
|
||||
handler: impl Fn(&mut S, &ScrollWheelEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.listeners()
|
||||
.scroll_wheel
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
type MouseDownHandler<V> = Arc<
|
||||
dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
type MouseUpHandler<V> = Arc<
|
||||
dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
type MouseMoveHandler<V> = Arc<
|
||||
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
type ScrollWheelHandler<V> = Arc<
|
||||
dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
pub struct MouseEventListeners<V: 'static> {
|
||||
mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
|
||||
mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
|
||||
mouse_move: SmallVec<[MouseMoveHandler<V>; 2]>,
|
||||
scroll_wheel: SmallVec<[ScrollWheelHandler<V>; 2]>,
|
||||
}
|
||||
|
||||
impl<S: Send + Sync + 'static> MouseEventListeners<S> {
|
||||
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<S>) {
|
||||
for handler in self.mouse_down.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &MouseDownEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
for handler in self.mouse_up.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
for handler in self.mouse_move.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &MouseMoveEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for handler in self.scroll_wheel.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &ScrollWheelEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for MouseEventListeners<V> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mouse_down: Default::default(),
|
||||
mouse_up: Default::default(),
|
||||
mouse_move: Default::default(),
|
||||
scroll_wheel: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
mod events;
|
||||
mod keystroke;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac;
|
||||
@@ -6,9 +5,9 @@ mod mac;
|
||||
mod test;
|
||||
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlobalPixels,
|
||||
GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
|
||||
ShapedLine, SharedString, Size,
|
||||
AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics,
|
||||
GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams,
|
||||
Result, Scene, ShapedLine, SharedString, Size,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_task::Runnable;
|
||||
@@ -27,7 +26,6 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub use events::*;
|
||||
pub use keystroke::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use mac::*;
|
||||
@@ -40,7 +38,7 @@ pub(crate) fn current_platform() -> Arc<dyn Platform> {
|
||||
Arc::new(MacPlatform::new())
|
||||
}
|
||||
|
||||
pub trait Platform: 'static {
|
||||
pub(crate) trait Platform: 'static {
|
||||
fn executor(&self) -> Executor;
|
||||
fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker>;
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
||||
@@ -113,7 +111,7 @@ impl Debug for DisplayId {
|
||||
|
||||
unsafe impl Send for DisplayId {}
|
||||
|
||||
pub trait PlatformWindow {
|
||||
pub(crate) trait PlatformWindow {
|
||||
fn bounds(&self) -> WindowBounds;
|
||||
fn content_size(&self) -> Size<Pixels>;
|
||||
fn scale_factor(&self) -> f32;
|
||||
@@ -194,11 +192,17 @@ pub enum AtlasKey {
|
||||
}
|
||||
|
||||
impl AtlasKey {
|
||||
pub fn is_monochrome(&self) -> bool {
|
||||
pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
|
||||
match self {
|
||||
AtlasKey::Glyph(params) => !params.is_emoji,
|
||||
AtlasKey::Svg(_) => true,
|
||||
AtlasKey::Image(_) => false,
|
||||
AtlasKey::Glyph(params) => {
|
||||
if params.is_emoji {
|
||||
AtlasTextureKind::Polychrome
|
||||
} else {
|
||||
AtlasTextureKind::Monochrome
|
||||
}
|
||||
}
|
||||
AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
|
||||
AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,9 +243,21 @@ pub struct AtlasTile {
|
||||
pub(crate) bounds: Bounds<DevicePixels>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct AtlasTextureId(pub(crate) u32); // We use u32 instead of usize for Metal Shader Language compatibility
|
||||
pub(crate) struct AtlasTextureId {
|
||||
// We use u32 instead of usize for Metal Shader Language compatibility
|
||||
pub(crate) index: u32,
|
||||
pub(crate) kind: AtlasTextureKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(C)]
|
||||
pub(crate) enum AtlasTextureKind {
|
||||
Monochrome = 0,
|
||||
Polychrome = 1,
|
||||
Path = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(C)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
|
||||
MouseButton, MouseDownEvent, MouseExitedEvent, MouseMovedEvent, MouseUpEvent,
|
||||
NavigationDirection, Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
|
||||
MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
|
||||
Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
|
||||
@@ -161,10 +161,10 @@ impl Event {
|
||||
NSEventType::NSScrollWheel => window_height.map(|window_height| {
|
||||
let phase = match native_event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
Some(TouchPhase::Started)
|
||||
TouchPhase::Started
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
|
||||
_ => Some(TouchPhase::Moved),
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => TouchPhase::Moved,
|
||||
};
|
||||
|
||||
let raw_data = point(
|
||||
@@ -184,7 +184,7 @@ impl Event {
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
delta,
|
||||
phase,
|
||||
touch_phase: phase,
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
}),
|
||||
@@ -202,7 +202,7 @@ impl Event {
|
||||
};
|
||||
|
||||
window_height.map(|window_height| {
|
||||
Self::MouseMoved(MouseMovedEvent {
|
||||
Self::MouseMoved(MouseMoveEvent {
|
||||
pressed_button: Some(pressed_button),
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
@@ -213,7 +213,7 @@ impl Event {
|
||||
})
|
||||
}
|
||||
NSEventType::NSMouseMoved => window_height.map(|window_height| {
|
||||
Self::MouseMoved(MouseMovedEvent {
|
||||
Self::MouseMoved(MouseMoveEvent {
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
@@ -223,7 +223,7 @@ impl Event {
|
||||
})
|
||||
}),
|
||||
NSEventType::NSMouseExited => window_height.map(|window_height| {
|
||||
Self::MouseExited(MouseExitedEvent {
|
||||
Self::MouseExited(MouseExitEvent {
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size,
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Point, Size,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use metal::Device;
|
||||
use parking_lot::Mutex;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||
|
||||
@@ -16,19 +16,43 @@ impl MetalAtlas {
|
||||
pub fn new(device: Device) -> Self {
|
||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||
device: AssertSend(device),
|
||||
textures: Default::default(),
|
||||
monochrome_textures: Default::default(),
|
||||
polychrome_textures: Default::default(),
|
||||
path_textures: Default::default(),
|
||||
tiles_by_key: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn texture(&self, id: AtlasTextureId) -> metal::Texture {
|
||||
self.0.lock().textures[id.0 as usize].metal_texture.clone()
|
||||
pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
|
||||
self.0.lock().texture(id).metal_texture.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn allocate(
|
||||
&self,
|
||||
size: Size<DevicePixels>,
|
||||
texture_kind: AtlasTextureKind,
|
||||
) -> AtlasTile {
|
||||
self.0.lock().allocate(size, texture_kind)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||
let mut lock = self.0.lock();
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.path_textures,
|
||||
};
|
||||
for texture in textures {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MetalAtlasState {
|
||||
device: AssertSend<Device>,
|
||||
textures: Vec<MetalAtlasTexture>,
|
||||
monochrome_textures: Vec<MetalAtlasTexture>,
|
||||
polychrome_textures: Vec<MetalAtlasTexture>,
|
||||
path_textures: Vec<MetalAtlasTexture>,
|
||||
tiles_by_key: HashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
@@ -43,37 +67,50 @@ impl PlatformAtlas for MetalAtlas {
|
||||
return Ok(tile.clone());
|
||||
} else {
|
||||
let (size, bytes) = build()?;
|
||||
let tile = lock
|
||||
.textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| {
|
||||
if texture.monochrome == key.is_monochrome() {
|
||||
texture.upload(size, &bytes)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
let texture = lock.push_texture(size, key.is_monochrome());
|
||||
texture.upload(size, &bytes)
|
||||
})
|
||||
.ok_or_else(|| anyhow!("could not allocate in new texture"))?;
|
||||
let tile = lock.allocate(size, key.texture_kind());
|
||||
let texture = lock.texture(tile.texture_id);
|
||||
texture.upload(tile.bounds, &bytes);
|
||||
lock.tiles_by_key.insert(key.clone(), tile.clone());
|
||||
Ok(tile)
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&self) {
|
||||
self.0.lock().tiles_by_key.clear();
|
||||
let mut lock = self.0.lock();
|
||||
lock.tiles_by_key.clear();
|
||||
for texture in &mut lock.monochrome_textures {
|
||||
texture.clear();
|
||||
}
|
||||
for texture in &mut lock.polychrome_textures {
|
||||
texture.clear();
|
||||
}
|
||||
for texture in &mut lock.path_textures {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetalAtlasState {
|
||||
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.unwrap_or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
&mut self,
|
||||
min_size: Size<DevicePixels>,
|
||||
monochrome: bool,
|
||||
kind: AtlasTextureKind,
|
||||
) -> &mut MetalAtlasTexture {
|
||||
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
|
||||
width: DevicePixels(1024),
|
||||
@@ -84,21 +121,50 @@ impl MetalAtlasState {
|
||||
let texture_descriptor = metal::TextureDescriptor::new();
|
||||
texture_descriptor.set_width(size.width.into());
|
||||
texture_descriptor.set_height(size.height.into());
|
||||
if monochrome {
|
||||
texture_descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm);
|
||||
} else {
|
||||
texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
|
||||
let pixel_format;
|
||||
let usage;
|
||||
match kind {
|
||||
AtlasTextureKind::Monochrome => {
|
||||
pixel_format = metal::MTLPixelFormat::A8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Polychrome => {
|
||||
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Path => {
|
||||
pixel_format = metal::MTLPixelFormat::R16Float;
|
||||
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
}
|
||||
texture_descriptor.set_pixel_format(pixel_format);
|
||||
texture_descriptor.set_usage(usage);
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
let textures = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
let atlas_texture = MetalAtlasTexture {
|
||||
id: AtlasTextureId(self.textures.len() as u32),
|
||||
id: AtlasTextureId {
|
||||
index: textures.len() as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
metal_texture: AssertSend(metal_texture),
|
||||
monochrome,
|
||||
};
|
||||
self.textures.push(atlas_texture);
|
||||
self.textures.last_mut().unwrap()
|
||||
textures.push(atlas_texture);
|
||||
textures.last_mut().unwrap()
|
||||
}
|
||||
|
||||
fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
|
||||
let textures = match id.kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,11 +172,14 @@ struct MetalAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
monochrome: bool,
|
||||
}
|
||||
|
||||
impl MetalAtlasTexture {
|
||||
fn upload(&mut self, size: Size<DevicePixels>, bytes: &[u8]) -> Option<AtlasTile> {
|
||||
fn clear(&mut self) {
|
||||
self.allocator.clear();
|
||||
}
|
||||
|
||||
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
|
||||
let allocation = self.allocator.allocate(size.into())?;
|
||||
let tile = AtlasTile {
|
||||
texture_id: self.id,
|
||||
@@ -120,20 +189,22 @@ impl MetalAtlasTexture {
|
||||
size,
|
||||
},
|
||||
};
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
|
||||
let region = metal::MTLRegion::new_2d(
|
||||
tile.bounds.origin.x.into(),
|
||||
tile.bounds.origin.y.into(),
|
||||
tile.bounds.size.width.into(),
|
||||
tile.bounds.size.height.into(),
|
||||
bounds.origin.x.into(),
|
||||
bounds.origin.y.into(),
|
||||
bounds.size.width.into(),
|
||||
bounds.size.height.into(),
|
||||
);
|
||||
self.metal_texture.replace_region(
|
||||
region,
|
||||
0,
|
||||
bytes.as_ptr() as *const _,
|
||||
u32::from(tile.bounds.size.width.to_bytes(self.bytes_per_pixel())) as u64,
|
||||
u32::from(bounds.size.width.to_bytes(self.bytes_per_pixel())) as u64,
|
||||
);
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
use crate::{
|
||||
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
|
||||
PrimitiveBatch, Quad, Scene, Shadow, Size, Underline,
|
||||
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
||||
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
|
||||
Quad, ScaledPixels, Scene, Shadow, Size, Underline,
|
||||
};
|
||||
use cocoa::{
|
||||
base::{NO, YES},
|
||||
foundation::NSUInteger,
|
||||
quartzcore::AutoresizingMask,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
use smallvec::SmallVec;
|
||||
use std::{ffi::c_void, mem, ptr, sync::Arc};
|
||||
|
||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||
const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
|
||||
|
||||
pub struct MetalRenderer {
|
||||
pub(crate) struct MetalRenderer {
|
||||
layer: metal::MetalLayer,
|
||||
command_queue: CommandQueue,
|
||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
||||
path_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
shadows_pipeline_state: metal::RenderPipelineState,
|
||||
quads_pipeline_state: metal::RenderPipelineState,
|
||||
underlines_pipeline_state: metal::RenderPipelineState,
|
||||
@@ -29,8 +34,6 @@ pub struct MetalRenderer {
|
||||
|
||||
impl MetalRenderer {
|
||||
pub fn new(is_opaque: bool) -> Self {
|
||||
const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
|
||||
|
||||
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
|
||||
device
|
||||
} else {
|
||||
@@ -40,7 +43,7 @@ impl MetalRenderer {
|
||||
|
||||
let layer = metal::MetalLayer::new();
|
||||
layer.set_device(&device);
|
||||
layer.set_pixel_format(PIXEL_FORMAT);
|
||||
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||
layer.set_presents_with_transaction(true);
|
||||
layer.set_opaque(is_opaque);
|
||||
unsafe {
|
||||
@@ -84,13 +87,29 @@ impl MetalRenderer {
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
);
|
||||
|
||||
let paths_rasterization_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
"paths_rasterization",
|
||||
"path_rasterization_vertex",
|
||||
"path_rasterization_fragment",
|
||||
MTLPixelFormat::R16Float,
|
||||
);
|
||||
let path_sprites_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
"path_sprites",
|
||||
"path_sprite_vertex",
|
||||
"path_sprite_fragment",
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let shadows_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
"shadows",
|
||||
"shadow_vertex",
|
||||
"shadow_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let quads_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
@@ -98,7 +117,7 @@ impl MetalRenderer {
|
||||
"quads",
|
||||
"quad_vertex",
|
||||
"quad_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let underlines_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
@@ -106,7 +125,7 @@ impl MetalRenderer {
|
||||
"underlines",
|
||||
"underline_vertex",
|
||||
"underline_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
@@ -114,7 +133,7 @@ impl MetalRenderer {
|
||||
"monochrome_sprites",
|
||||
"monochrome_sprite_vertex",
|
||||
"monochrome_sprite_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
@@ -122,7 +141,7 @@ impl MetalRenderer {
|
||||
"polychrome_sprites",
|
||||
"polychrome_sprite_vertex",
|
||||
"polychrome_sprite_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
|
||||
let command_queue = device.new_command_queue();
|
||||
@@ -131,6 +150,8 @@ impl MetalRenderer {
|
||||
Self {
|
||||
layer,
|
||||
command_queue,
|
||||
paths_rasterization_pipeline_state,
|
||||
path_sprites_pipeline_state,
|
||||
shadows_pipeline_state,
|
||||
quads_pipeline_state,
|
||||
underlines_pipeline_state,
|
||||
@@ -150,7 +171,7 @@ impl MetalRenderer {
|
||||
&self.sprite_atlas
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &mut Scene) {
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
let layer = self.layer.clone();
|
||||
let viewport_size = layer.drawable_size();
|
||||
let viewport_size: Size<DevicePixels> = size(
|
||||
@@ -168,6 +189,9 @@ impl MetalRenderer {
|
||||
};
|
||||
let command_queue = self.command_queue.clone();
|
||||
let command_buffer = command_queue.new_command_buffer();
|
||||
let mut instance_offset = 0;
|
||||
|
||||
let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
|
||||
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
let color_attachment = render_pass_descriptor
|
||||
@@ -190,8 +214,6 @@ impl MetalRenderer {
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
});
|
||||
|
||||
let mut instance_offset = 0;
|
||||
for batch in scene.batches() {
|
||||
match batch {
|
||||
PrimitiveBatch::Shadows(shadows) => {
|
||||
@@ -205,6 +227,15 @@ impl MetalRenderer {
|
||||
PrimitiveBatch::Quads(quads) => {
|
||||
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
|
||||
}
|
||||
PrimitiveBatch::Paths(paths) => {
|
||||
self.draw_paths(
|
||||
paths,
|
||||
&path_tiles,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
);
|
||||
}
|
||||
PrimitiveBatch::Underlines(underlines) => {
|
||||
self.draw_underlines(
|
||||
underlines,
|
||||
@@ -248,10 +279,97 @@ impl MetalRenderer {
|
||||
});
|
||||
|
||||
command_buffer.commit();
|
||||
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
||||
command_buffer.wait_until_completed();
|
||||
drawable.present();
|
||||
}
|
||||
|
||||
fn rasterize_paths(
|
||||
&mut self,
|
||||
paths: &[Path<ScaledPixels>],
|
||||
offset: &mut usize,
|
||||
command_buffer: &metal::CommandBufferRef,
|
||||
) -> HashMap<PathId, AtlasTile> {
|
||||
let mut tiles = HashMap::default();
|
||||
let mut vertices_by_texture_id = HashMap::default();
|
||||
for path in paths {
|
||||
let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
|
||||
|
||||
let tile = self
|
||||
.sprite_atlas
|
||||
.allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
|
||||
vertices_by_texture_id
|
||||
.entry(tile.texture_id)
|
||||
.or_insert(Vec::new())
|
||||
.extend(path.vertices.iter().map(|vertex| PathVertex {
|
||||
xy_position: vertex.xy_position - path.bounds.origin
|
||||
+ tile.bounds.origin.map(Into::into),
|
||||
st_position: vertex.st_position,
|
||||
content_mask: ContentMask {
|
||||
bounds: tile.bounds.map(Into::into),
|
||||
},
|
||||
}));
|
||||
tiles.insert(path.id, tile);
|
||||
}
|
||||
|
||||
for (texture_id, vertices) in vertices_by_texture_id {
|
||||
align_offset(offset);
|
||||
let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
|
||||
assert!(
|
||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||
"instance buffer exhausted"
|
||||
);
|
||||
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
let color_attachment = render_pass_descriptor
|
||||
.color_attachments()
|
||||
.object_at(0)
|
||||
.unwrap();
|
||||
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
color_attachment.set_texture(Some(&texture));
|
||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
|
||||
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
PathRasterizationInputIndex::Vertices as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
let texture_size = Size {
|
||||
width: DevicePixels::from(texture.width()),
|
||||
height: DevicePixels::from(texture.height()),
|
||||
};
|
||||
command_encoder.set_vertex_bytes(
|
||||
PathRasterizationInputIndex::AtlasTextureSize as u64,
|
||||
mem::size_of_val(&texture_size) as u64,
|
||||
&texture_size as *const Size<DevicePixels> as *const _,
|
||||
);
|
||||
|
||||
let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
vertices.as_ptr() as *const u8,
|
||||
buffer_contents,
|
||||
vertices_bytes_len,
|
||||
);
|
||||
}
|
||||
|
||||
command_encoder.draw_primitives(
|
||||
metal::MTLPrimitiveType::Triangle,
|
||||
0,
|
||||
vertices.len() as u64,
|
||||
);
|
||||
command_encoder.end_encoding();
|
||||
*offset = next_offset;
|
||||
}
|
||||
|
||||
tiles
|
||||
}
|
||||
|
||||
fn draw_shadows(
|
||||
&mut self,
|
||||
shadows: &[Shadow],
|
||||
@@ -368,6 +486,112 @@ impl MetalRenderer {
|
||||
*offset = next_offset;
|
||||
}
|
||||
|
||||
fn draw_paths(
|
||||
&mut self,
|
||||
paths: &[Path<ScaledPixels>],
|
||||
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) {
|
||||
if paths.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Vertices as u64,
|
||||
Some(&self.unit_vertices),
|
||||
0,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::ViewportSize as u64,
|
||||
mem::size_of_val(&viewport_size) as u64,
|
||||
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||
);
|
||||
|
||||
let mut prev_texture_id = None;
|
||||
let mut sprites = SmallVec::<[_; 1]>::new();
|
||||
let mut paths_and_tiles = paths
|
||||
.into_iter()
|
||||
.map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
|
||||
.peekable();
|
||||
|
||||
loop {
|
||||
if let Some((path, tile)) = paths_and_tiles.peek() {
|
||||
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
|
||||
prev_texture_id = Some(tile.texture_id);
|
||||
sprites.push(PathSprite {
|
||||
bounds: Bounds {
|
||||
origin: path.bounds.origin.map(|p| p.floor()),
|
||||
size: tile.bounds.size.map(Into::into),
|
||||
},
|
||||
color: path.color,
|
||||
tile: (*tile).clone(),
|
||||
});
|
||||
paths_and_tiles.next();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if sprites.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
align_offset(offset);
|
||||
let texture_id = prev_texture_id.take().unwrap();
|
||||
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
DevicePixels(texture.width() as i32),
|
||||
DevicePixels(texture.height() as i32),
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::AtlasTextureSize as u64,
|
||||
mem::size_of_val(&texture_size) as u64,
|
||||
&texture_size as *const Size<DevicePixels> as *const _,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder
|
||||
.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||
|
||||
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
|
||||
let buffer_contents =
|
||||
unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
sprites.as_ptr() as *const u8,
|
||||
buffer_contents,
|
||||
sprite_bytes_len,
|
||||
);
|
||||
}
|
||||
|
||||
let next_offset = *offset + sprite_bytes_len;
|
||||
assert!(
|
||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||
"instance buffer exhausted"
|
||||
);
|
||||
|
||||
command_encoder.draw_primitives_instanced(
|
||||
metal::MTLPrimitiveType::Triangle,
|
||||
0,
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
sprites.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_underlines(
|
||||
&mut self,
|
||||
underlines: &[Underline],
|
||||
@@ -441,7 +665,7 @@ impl MetalRenderer {
|
||||
}
|
||||
align_offset(offset);
|
||||
|
||||
let texture = self.sprite_atlas.texture(texture_id);
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
DevicePixels(texture.width() as i32),
|
||||
DevicePixels(texture.height() as i32),
|
||||
@@ -512,7 +736,7 @@ impl MetalRenderer {
|
||||
}
|
||||
align_offset(offset);
|
||||
|
||||
let texture = self.sprite_atlas.texture(texture_id);
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
DevicePixels(texture.width() as i32),
|
||||
DevicePixels(texture.height() as i32),
|
||||
@@ -640,3 +864,17 @@ enum SpriteInputIndex {
|
||||
AtlasTextureSize = 3,
|
||||
AtlasTexture = 4,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
enum PathRasterizationInputIndex {
|
||||
Vertices = 0,
|
||||
AtlasTextureSize = 1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct PathSprite {
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub color: Hsla,
|
||||
pub tile: AtlasTile,
|
||||
}
|
||||
|
||||
@@ -923,7 +923,7 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
|
||||
if let Some(event) = Event::from_native(native_event, None) {
|
||||
let platform = get_foreground_platform(this);
|
||||
if let Some(callback) = platform.0.lock().event.as_mut() {
|
||||
if callback(event) {
|
||||
if !callback(event) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +336,94 @@ fragment float4 polychrome_sprite_fragment(
|
||||
return color;
|
||||
}
|
||||
|
||||
struct PathRasterizationVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
float clip_rect_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct PathRasterizationFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
};
|
||||
|
||||
vertex PathRasterizationVertexOutput path_rasterization_vertex(
|
||||
uint vertex_id [[vertex_id]],
|
||||
constant PathVertex_ScaledPixels *vertices
|
||||
[[buffer(PathRasterizationInputIndex_Vertices)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
|
||||
PathVertex_ScaledPixels v = vertices[vertex_id];
|
||||
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
|
||||
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
|
||||
return PathRasterizationVertexOutput{
|
||||
float4(vertex_position / viewport_size * float2(2., -2.) +
|
||||
float2(-1., 1.),
|
||||
0., 1.),
|
||||
float2(v.st_position.x, v.st_position.y),
|
||||
{v.xy_position.x - v.content_mask.bounds.origin.x,
|
||||
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
|
||||
v.xy_position.x,
|
||||
v.xy_position.y - v.content_mask.bounds.origin.y,
|
||||
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
|
||||
v.xy_position.y}};
|
||||
}
|
||||
|
||||
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
|
||||
[[stage_in]]) {
|
||||
float2 dx = dfdx(input.st_position);
|
||||
float2 dy = dfdy(input.st_position);
|
||||
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
|
||||
(2. * input.st_position.x) * dy.x - dy.y);
|
||||
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
|
||||
float distance = f / length(gradient);
|
||||
float alpha = saturate(0.5 - distance);
|
||||
return float4(alpha, 0., 0., 1.);
|
||||
}
|
||||
|
||||
struct PathSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
uint sprite_id [[flat]];
|
||||
};
|
||||
|
||||
vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
PathSprite sprite = sprites[sprite_id];
|
||||
// Don't apply content mask because it was already accounted for when
|
||||
// rasterizing the path.
|
||||
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
|
||||
sprite.bounds, viewport_size);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return PathSpriteVertexOutput{device_position, tile_position, color,
|
||||
sprite_id};
|
||||
}
|
||||
|
||||
fragment float4 path_sprite_fragment(
|
||||
PathSpriteVertexOutput input [[stage_in]],
|
||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
PathSprite sprite = sprites[input.sprite_id];
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
||||
float4 color = input.color;
|
||||
color.a *= mask;
|
||||
return color;
|
||||
}
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla) {
|
||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||
float s = hsla.s;
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NS
|
||||
use crate::{
|
||||
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor,
|
||||
GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseMovedEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
|
||||
PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
|
||||
WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
|
||||
};
|
||||
@@ -911,7 +911,7 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, scene: crate::Scene) {
|
||||
fn draw(&self, scene: Scene) {
|
||||
let mut this = self.0.lock();
|
||||
this.scene_to_render = Some(scene);
|
||||
unsafe {
|
||||
@@ -1141,7 +1141,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||
|
||||
match &event {
|
||||
Event::MouseMoved(
|
||||
event @ MouseMovedEvent {
|
||||
event @ MouseMoveEvent {
|
||||
pressed_button: Some(_),
|
||||
..
|
||||
},
|
||||
@@ -1395,8 +1395,8 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let window_state = get_window_state(this);
|
||||
let mut window_state = window_state.as_ref().lock();
|
||||
if let Some(mut scene) = window_state.scene_to_render.take() {
|
||||
window_state.renderer.draw(&mut scene);
|
||||
if let Some(scene) = window_state.scene_to_render.take() {
|
||||
window_state.renderer.draw(&scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1596,7 +1596,7 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
|
||||
async fn synthetic_drag(
|
||||
window_state: Weak<Mutex<MacWindowState>>,
|
||||
drag_id: usize,
|
||||
event: MouseMovedEvent,
|
||||
event: MouseMoveEvent,
|
||||
) {
|
||||
loop {
|
||||
Timer::after(Duration::from_millis(16)).await;
|
||||
|
||||
+371
-111
@@ -1,58 +1,162 @@
|
||||
use crate::{
|
||||
AtlasTextureId, AtlasTile, Bounds, Corners, Edges, Hsla, Point, ScaledContentMask, ScaledPixels,
|
||||
point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point,
|
||||
ScaledPixels, StackingOrder,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
use etagere::euclid::{Point3D, Vector3D};
|
||||
use plane_split::{BspSplitter, Polygon as BspPolygon};
|
||||
use smallvec::SmallVec;
|
||||
use std::{iter::Peekable, mem, slice};
|
||||
use std::{fmt::Debug, iter::Peekable, mem, slice};
|
||||
|
||||
// Exported to metal
|
||||
pub type PointF = Point<f32>;
|
||||
pub type StackingOrder = SmallVec<[u32; 16]>;
|
||||
pub(crate) type PointF = Point<f32>;
|
||||
#[allow(non_camel_case_types, unused)]
|
||||
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
||||
|
||||
pub type LayerId = u32;
|
||||
|
||||
pub type DrawOrder = u32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Scene {
|
||||
pub(crate) scale_factor: f32,
|
||||
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>,
|
||||
pub(crate) struct SceneBuilder {
|
||||
layers_by_order: BTreeMap<StackingOrder, LayerId>,
|
||||
splitter: BspSplitter<(PrimitiveKind, usize)>,
|
||||
shadows: Vec<Shadow>,
|
||||
quads: Vec<Quad>,
|
||||
paths: Vec<Path<ScaledPixels>>,
|
||||
underlines: Vec<Underline>,
|
||||
monochrome_sprites: Vec<MonochromeSprite>,
|
||||
polychrome_sprites: Vec<PolychromeSprite>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub fn new(scale_factor: f32) -> Scene {
|
||||
Scene {
|
||||
scale_factor,
|
||||
layers: BTreeMap::new(),
|
||||
impl SceneBuilder {
|
||||
pub fn new() -> SceneBuilder {
|
||||
SceneBuilder {
|
||||
layers_by_order: BTreeMap::new(),
|
||||
splitter: BspSplitter::new(),
|
||||
shadows: Vec::new(),
|
||||
quads: Vec::new(),
|
||||
paths: Vec::new(),
|
||||
underlines: Vec::new(),
|
||||
monochrome_sprites: Vec::new(),
|
||||
polychrome_sprites: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> Scene {
|
||||
pub fn build(&mut self) -> Scene {
|
||||
// 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_by_order.len()];
|
||||
for (ix, layer_id) in self.layers_by_order.values().enumerate() {
|
||||
layer_z_values[*layer_id as usize] = ix as f32 / self.layers_by_order.len() as f32;
|
||||
}
|
||||
self.layers_by_order.clear();
|
||||
|
||||
// Add all primitives to the BSP splitter to determine draw order
|
||||
self.splitter.reset();
|
||||
|
||||
for (ix, shadow) in self.shadows.iter().enumerate() {
|
||||
let z = layer_z_values[shadow.order as LayerId as usize];
|
||||
self.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];
|
||||
self.splitter
|
||||
.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
|
||||
}
|
||||
|
||||
for (ix, path) in self.paths.iter().enumerate() {
|
||||
let z = layer_z_values[path.order as LayerId as usize];
|
||||
self.splitter
|
||||
.add(path.bounds.to_bsp_polygon(z, (PrimitiveKind::Path, ix)));
|
||||
}
|
||||
|
||||
for (ix, underline) in self.underlines.iter().enumerate() {
|
||||
let z = layer_z_values[underline.order as LayerId as usize];
|
||||
self.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];
|
||||
self.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];
|
||||
self.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 self
|
||||
.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::Path, ix) => self.paths[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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.shadows.sort_unstable();
|
||||
self.quads.sort_unstable();
|
||||
self.paths.sort_unstable();
|
||||
self.underlines.sort_unstable();
|
||||
self.monochrome_sprites.sort_unstable();
|
||||
self.polychrome_sprites.sort_unstable();
|
||||
|
||||
Scene {
|
||||
scale_factor: self.scale_factor,
|
||||
layers: mem::take(&mut self.layers),
|
||||
shadows: mem::take(&mut self.shadows),
|
||||
quads: mem::take(&mut self.quads),
|
||||
paths: mem::take(&mut self.paths),
|
||||
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, layer_id: StackingOrder, primitive: impl Into<Primitive>) {
|
||||
let next_id = self.layers.len() as LayerId;
|
||||
let layer_id = *self.layers.entry(layer_id).or_insert(next_id);
|
||||
pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
|
||||
let primitive = primitive.into();
|
||||
let clipped_bounds = primitive
|
||||
.bounds()
|
||||
.intersect(&primitive.content_mask().bounds);
|
||||
if clipped_bounds.size.width <= ScaledPixels(0.)
|
||||
|| clipped_bounds.size.height <= ScaledPixels(0.)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
|
||||
*layer_id
|
||||
} else {
|
||||
let next_id = self.layers_by_order.len() as LayerId;
|
||||
self.layers_by_order.insert(order.clone(), next_id);
|
||||
next_id
|
||||
};
|
||||
|
||||
match primitive {
|
||||
Primitive::Shadow(mut shadow) => {
|
||||
shadow.order = layer_id;
|
||||
@@ -62,6 +166,11 @@ impl Scene {
|
||||
quad.order = layer_id;
|
||||
self.quads.push(quad);
|
||||
}
|
||||
Primitive::Path(mut path) => {
|
||||
path.order = layer_id;
|
||||
path.id = PathId(self.paths.len());
|
||||
self.paths.push(path);
|
||||
}
|
||||
Primitive::Underline(mut underline) => {
|
||||
underline.order = layer_id;
|
||||
self.underlines.push(underline);
|
||||
@@ -76,80 +185,24 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||
// Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
|
||||
let mut layer_z_values = vec![0.; self.layers.len()];
|
||||
for (ix, layer_id) in self.layers.values().enumerate() {
|
||||
layer_z_values[*layer_id as usize] = ix as f32 / self.layers.len() as f32;
|
||||
}
|
||||
pub(crate) struct Scene {
|
||||
pub shadows: Vec<Shadow>,
|
||||
pub quads: Vec<Quad>,
|
||||
pub paths: Vec<Path<ScaledPixels>>,
|
||||
pub underlines: Vec<Underline>,
|
||||
pub monochrome_sprites: Vec<MonochromeSprite>,
|
||||
pub polychrome_sprites: Vec<PolychromeSprite>,
|
||||
}
|
||||
|
||||
// Add all primitives to the BSP splitter to determine draw order
|
||||
// todo!("reuse the same splitter")
|
||||
let mut splitter = BspSplitter::new();
|
||||
|
||||
for (ix, shadow) in self.shadows.iter().enumerate() {
|
||||
let z = layer_z_values[shadow.order as LayerId as usize];
|
||||
splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
|
||||
}
|
||||
|
||||
for (ix, quad) in self.quads.iter().enumerate() {
|
||||
let z = layer_z_values[quad.order as LayerId as usize];
|
||||
splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
|
||||
}
|
||||
|
||||
for (ix, underline) in self.underlines.iter().enumerate() {
|
||||
let z = layer_z_values[underline.order as LayerId as usize];
|
||||
splitter.add(
|
||||
underline
|
||||
.bounds
|
||||
.to_bsp_polygon(z, (PrimitiveKind::Underline, ix)),
|
||||
);
|
||||
}
|
||||
|
||||
for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() {
|
||||
let z = layer_z_values[monochrome_sprite.order as LayerId as usize];
|
||||
splitter.add(
|
||||
monochrome_sprite
|
||||
.bounds
|
||||
.to_bsp_polygon(z, (PrimitiveKind::MonochromeSprite, ix)),
|
||||
);
|
||||
}
|
||||
|
||||
for (ix, polychrome_sprite) in self.polychrome_sprites.iter().enumerate() {
|
||||
let z = layer_z_values[polychrome_sprite.order as LayerId as usize];
|
||||
splitter.add(
|
||||
polychrome_sprite
|
||||
.bounds
|
||||
.to_bsp_polygon(z, (PrimitiveKind::PolychromeSprite, ix)),
|
||||
);
|
||||
}
|
||||
|
||||
// Sort all polygons, then reassign the order field of each primitive to `draw_order`
|
||||
// We need primitives to be repr(C), hence the weird reuse of the order field for two different types.
|
||||
for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() {
|
||||
match polygon.anchor {
|
||||
(PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder,
|
||||
(PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
|
||||
(PrimitiveKind::Underline, ix) => {
|
||||
self.underlines[ix].order = draw_order as DrawOrder
|
||||
}
|
||||
(PrimitiveKind::MonochromeSprite, ix) => {
|
||||
self.monochrome_sprites[ix].order = draw_order as DrawOrder
|
||||
}
|
||||
(PrimitiveKind::PolychromeSprite, ix) => {
|
||||
self.polychrome_sprites[ix].order = draw_order as DrawOrder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the primitives
|
||||
self.shadows.sort_unstable();
|
||||
self.quads.sort_unstable();
|
||||
self.underlines.sort_unstable();
|
||||
self.monochrome_sprites.sort_unstable();
|
||||
self.polychrome_sprites.sort_unstable();
|
||||
impl Scene {
|
||||
#[allow(dead_code)]
|
||||
pub fn paths(&self) -> &[Path<ScaledPixels>] {
|
||||
&self.paths
|
||||
}
|
||||
|
||||
pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||
BatchIterator {
|
||||
shadows: &self.shadows,
|
||||
shadows_start: 0,
|
||||
@@ -157,6 +210,9 @@ impl Scene {
|
||||
quads: &self.quads,
|
||||
quads_start: 0,
|
||||
quads_iter: self.quads.iter().peekable(),
|
||||
paths: &self.paths,
|
||||
paths_start: 0,
|
||||
paths_iter: self.paths.iter().peekable(),
|
||||
underlines: &self.underlines,
|
||||
underlines_start: 0,
|
||||
underlines_iter: self.underlines.iter().peekable(),
|
||||
@@ -171,12 +227,15 @@ impl Scene {
|
||||
}
|
||||
|
||||
struct BatchIterator<'a> {
|
||||
quads: &'a [Quad],
|
||||
quads_start: usize,
|
||||
quads_iter: Peekable<slice::Iter<'a, Quad>>,
|
||||
shadows: &'a [Shadow],
|
||||
shadows_start: usize,
|
||||
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
|
||||
quads: &'a [Quad],
|
||||
quads_start: usize,
|
||||
quads_iter: Peekable<slice::Iter<'a, Quad>>,
|
||||
paths: &'a [Path<ScaledPixels>],
|
||||
paths_start: usize,
|
||||
paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
|
||||
underlines: &'a [Underline],
|
||||
underlines_start: usize,
|
||||
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
|
||||
@@ -198,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
PrimitiveKind::Shadow,
|
||||
),
|
||||
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
|
||||
(self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
|
||||
(
|
||||
self.underlines_iter.peek().map(|u| u.order),
|
||||
PrimitiveKind::Underline,
|
||||
@@ -250,6 +310,19 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
self.quads_start = quads_end;
|
||||
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
|
||||
}
|
||||
PrimitiveKind::Path => {
|
||||
let paths_start = self.paths_start;
|
||||
let mut paths_end = paths_start;
|
||||
while self
|
||||
.paths_iter
|
||||
.next_if(|path| path.order <= max_order)
|
||||
.is_some()
|
||||
{
|
||||
paths_end += 1;
|
||||
}
|
||||
self.paths_start = paths_end;
|
||||
Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
|
||||
}
|
||||
PrimitiveKind::Underline => {
|
||||
let underlines_start = self.underlines_start;
|
||||
let mut underlines_end = underlines_start;
|
||||
@@ -312,24 +385,50 @@ pub enum PrimitiveKind {
|
||||
Shadow,
|
||||
#[default]
|
||||
Quad,
|
||||
Path,
|
||||
Underline,
|
||||
MonochromeSprite,
|
||||
PolychromeSprite,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Primitive {
|
||||
Shadow(Shadow),
|
||||
Quad(Quad),
|
||||
Path(Path<ScaledPixels>),
|
||||
Underline(Underline),
|
||||
MonochromeSprite(MonochromeSprite),
|
||||
PolychromeSprite(PolychromeSprite),
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
|
||||
match self {
|
||||
Primitive::Shadow(shadow) => &shadow.bounds,
|
||||
Primitive::Quad(quad) => &quad.bounds,
|
||||
Primitive::Path(path) => &path.bounds,
|
||||
Primitive::Underline(underline) => &underline.bounds,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
|
||||
match self {
|
||||
Primitive::Shadow(shadow) => &shadow.content_mask,
|
||||
Primitive::Quad(quad) => &quad.content_mask,
|
||||
Primitive::Path(path) => &path.content_mask,
|
||||
Primitive::Underline(underline) => &underline.content_mask,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PrimitiveBatch<'a> {
|
||||
Shadows(&'a [Shadow]),
|
||||
Quads(&'a [Quad]),
|
||||
Paths(&'a [Path<ScaledPixels>]),
|
||||
Underlines(&'a [Underline]),
|
||||
MonochromeSprites {
|
||||
texture_id: AtlasTextureId,
|
||||
@@ -346,7 +445,7 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
pub struct Quad {
|
||||
pub order: u32, // Initially a LayerId, then a DrawOrder.
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ScaledContentMask,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub background: Hsla,
|
||||
pub border_color: Hsla,
|
||||
pub corner_radii: Corners<ScaledPixels>,
|
||||
@@ -376,7 +475,7 @@ impl From<Quad> for Primitive {
|
||||
pub struct Underline {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ScaledContentMask,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub thickness: ScaledPixels,
|
||||
pub color: Hsla,
|
||||
pub wavy: bool,
|
||||
@@ -406,7 +505,7 @@ pub struct Shadow {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub corner_radii: Corners<ScaledPixels>,
|
||||
pub content_mask: ScaledContentMask,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub color: Hsla,
|
||||
pub blur_radius: ScaledPixels,
|
||||
}
|
||||
@@ -434,7 +533,7 @@ impl From<Shadow> for Primitive {
|
||||
pub struct MonochromeSprite {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ScaledContentMask,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub color: Hsla,
|
||||
pub tile: AtlasTile,
|
||||
}
|
||||
@@ -465,7 +564,7 @@ impl From<MonochromeSprite> for Primitive {
|
||||
pub struct PolychromeSprite {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ScaledContentMask,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub corner_radii: Corners<ScaledPixels>,
|
||||
pub tile: AtlasTile,
|
||||
pub grayscale: bool,
|
||||
@@ -492,6 +591,167 @@ impl From<PolychromeSprite> for Primitive {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PathId(pub(crate) usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Path<P: Clone + Debug> {
|
||||
pub(crate) id: PathId,
|
||||
order: u32,
|
||||
pub(crate) bounds: Bounds<P>,
|
||||
pub(crate) content_mask: ContentMask<P>,
|
||||
pub(crate) vertices: Vec<PathVertex<P>>,
|
||||
pub(crate) color: Hsla,
|
||||
start: Point<P>,
|
||||
current: Point<P>,
|
||||
contour_count: usize,
|
||||
}
|
||||
|
||||
impl Path<Pixels> {
|
||||
pub fn new(start: Point<Pixels>) -> Self {
|
||||
Self {
|
||||
id: PathId(0),
|
||||
order: 0,
|
||||
vertices: Vec::new(),
|
||||
start,
|
||||
current: start,
|
||||
bounds: Bounds {
|
||||
origin: start,
|
||||
size: Default::default(),
|
||||
},
|
||||
content_mask: Default::default(),
|
||||
color: Default::default(),
|
||||
contour_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
||||
Path {
|
||||
id: self.id,
|
||||
order: self.order,
|
||||
bounds: self.bounds.scale(factor),
|
||||
content_mask: self.content_mask.scale(factor),
|
||||
vertices: self
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| vertex.scale(factor))
|
||||
.collect(),
|
||||
start: self.start.map(|start| start.scale(factor)),
|
||||
current: self.current.scale(factor),
|
||||
contour_count: self.contour_count,
|
||||
color: self.color,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
if self.contour_count > 1 {
|
||||
self.push_triangle(
|
||||
(self.start, self.current, to),
|
||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
||||
);
|
||||
}
|
||||
self.current = to;
|
||||
}
|
||||
|
||||
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
if self.contour_count > 1 {
|
||||
self.push_triangle(
|
||||
(self.start, self.current, to),
|
||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
||||
);
|
||||
}
|
||||
|
||||
self.push_triangle(
|
||||
(self.current, ctrl, to),
|
||||
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
|
||||
);
|
||||
self.current = to;
|
||||
}
|
||||
|
||||
fn push_triangle(
|
||||
&mut self,
|
||||
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
|
||||
st: (Point<f32>, Point<f32>, Point<f32>),
|
||||
) {
|
||||
self.bounds = self
|
||||
.bounds
|
||||
.union(&Bounds {
|
||||
origin: xy.0,
|
||||
size: Default::default(),
|
||||
})
|
||||
.union(&Bounds {
|
||||
origin: xy.1,
|
||||
size: Default::default(),
|
||||
})
|
||||
.union(&Bounds {
|
||||
origin: xy.2,
|
||||
size: Default::default(),
|
||||
});
|
||||
|
||||
self.vertices.push(PathVertex {
|
||||
xy_position: xy.0,
|
||||
st_position: st.0,
|
||||
content_mask: Default::default(),
|
||||
});
|
||||
self.vertices.push(PathVertex {
|
||||
xy_position: xy.1,
|
||||
st_position: st.1,
|
||||
content_mask: Default::default(),
|
||||
});
|
||||
self.vertices.push(PathVertex {
|
||||
xy_position: xy.2,
|
||||
st_position: st.2,
|
||||
content_mask: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Path<ScaledPixels> {}
|
||||
|
||||
impl PartialEq for Path<ScaledPixels> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.order == other.order
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Path<ScaledPixels> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.order.cmp(&other.order)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Path<ScaledPixels> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Path<ScaledPixels>> for Primitive {
|
||||
fn from(path: Path<ScaledPixels>) -> Self {
|
||||
Primitive::Path(path)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct PathVertex<P: Clone + Debug> {
|
||||
pub(crate) xy_position: Point<P>,
|
||||
pub(crate) st_position: Point<f32>,
|
||||
pub(crate) content_mask: ContentMask<P>,
|
||||
}
|
||||
|
||||
impl PathVertex<Pixels> {
|
||||
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
|
||||
PathVertex {
|
||||
xy_position: self.xy_position.scale(factor),
|
||||
st_position: self.st_position,
|
||||
content_mask: self.content_mask.scale(factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtlasId(pub(crate) usize);
|
||||
|
||||
@@ -524,15 +784,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_scene() {
|
||||
let mut scene = Scene::new(1.0);
|
||||
assert_eq!(scene.layers.len(), 0);
|
||||
let mut scene = SceneBuilder::new();
|
||||
assert_eq!(scene.layers_by_order.len(), 0);
|
||||
|
||||
scene.insert(smallvec![1], quad());
|
||||
scene.insert(smallvec![2], shadow());
|
||||
scene.insert(smallvec![3], quad());
|
||||
scene.insert(&smallvec![1].into(), quad());
|
||||
scene.insert(&smallvec![2].into(), shadow());
|
||||
scene.insert(&smallvec![3].into(), quad());
|
||||
|
||||
let mut batches_count = 0;
|
||||
for _ in scene.batches() {
|
||||
for _ in scene.build().batches() {
|
||||
batches_count += 1;
|
||||
}
|
||||
assert_eq!(batches_count, 3);
|
||||
|
||||
+15
-42
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
|
||||
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
|
||||
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, Shadow,
|
||||
SharedString, Size, SizeRefinement, ViewContext, WindowContext,
|
||||
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString,
|
||||
Size, SizeRefinement, ViewContext, WindowContext,
|
||||
};
|
||||
use refineable::Refineable;
|
||||
use smallvec::SmallVec;
|
||||
@@ -245,51 +245,24 @@ impl Style {
|
||||
/// Paints the background of an element styled with this style.
|
||||
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
||||
let rem_size = cx.rem_size();
|
||||
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),
|
||||
},
|
||||
);
|
||||
})
|
||||
}
|
||||
cx.stack(0, |cx| {
|
||||
cx.paint_shadows(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
&self.box_shadow,
|
||||
);
|
||||
});
|
||||
|
||||
let background_color = self.fill.as_ref().and_then(Fill::color);
|
||||
if background_color.is_some() || self.is_border_visible() {
|
||||
let content_mask = cx.content_mask();
|
||||
cx.stack(1, |cx| {
|
||||
let order = cx.current_stacking_order();
|
||||
cx.scene().insert(
|
||||
order,
|
||||
Quad {
|
||||
order: 0,
|
||||
bounds: bounds.scale(scale),
|
||||
content_mask: content_mask.scale(scale),
|
||||
background: background_color.unwrap_or_default(),
|
||||
border_color: self.border_color.unwrap_or_default(),
|
||||
corner_radii: self
|
||||
.corner_radii
|
||||
.to_pixels(bounds.size, rem_size)
|
||||
.scale(scale),
|
||||
border_widths: self.border_widths.to_pixels(rem_size).scale(scale),
|
||||
},
|
||||
cx.paint_quad(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
background_color.unwrap_or_default(),
|
||||
self.border_widths.to_pixels(rem_size),
|
||||
self.border_color.unwrap_or_default(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,3 +360,5 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styled<Style = Style>> StyleHelpers for E {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Refineable, RefinementCascade};
|
||||
use crate::{Hoverable, Refineable, RefinementCascade};
|
||||
|
||||
pub trait Styled {
|
||||
type Style: Refineable + Default;
|
||||
@@ -10,12 +10,12 @@ pub trait Styled {
|
||||
Self::Style::from_refinement(&self.style_cascade().merged())
|
||||
}
|
||||
|
||||
// fn hover(self) -> Hoverable<Self>
|
||||
// where
|
||||
// Self: Sized,
|
||||
// {
|
||||
// hoverable(self)
|
||||
// }
|
||||
fn hover(self) -> Hoverable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Hoverable::new(self)
|
||||
}
|
||||
|
||||
// fn active(self) -> Pressable<Self>
|
||||
// where
|
||||
|
||||
+249
-55
@@ -1,18 +1,46 @@
|
||||
use crate::{
|
||||
image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
|
||||
AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
|
||||
Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
|
||||
MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
|
||||
PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
|
||||
SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
|
||||
WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
AvailableSpace, BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId,
|
||||
Edges, Effect, Element, EntityId, Event, FontId, GlyphId, Handle, Hsla, ImageData, IsZero,
|
||||
LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels,
|
||||
PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
|
||||
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style,
|
||||
TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
|
||||
SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use smallvec::SmallVec;
|
||||
use std::{any::TypeId, borrow::Cow, future::Future, marker::PhantomData, mem, sync::Arc};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::Cow,
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct AnyWindow {}
|
||||
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)]
|
||||
pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum DispatchPhase {
|
||||
/// After the capture phase comes the bubble phase, in which event handlers are
|
||||
/// invoked front to back. This is the phase you'll usually want to use for event handlers.
|
||||
#[default]
|
||||
Bubble,
|
||||
/// During the initial capture phase, event handlers are invoked back to front. This phase
|
||||
/// is used for special purposes such as clearing the "pressed" state for click events. If
|
||||
/// you stop event propagation during this phase, you need to know what you're doing. Handlers
|
||||
/// outside of the immediate region may rely on detecting non-local events during this phase.
|
||||
Capture,
|
||||
}
|
||||
|
||||
type MouseEventHandler =
|
||||
Arc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
|
||||
|
||||
pub struct Window {
|
||||
handle: AnyWindowHandle,
|
||||
@@ -23,10 +51,13 @@ pub struct Window {
|
||||
content_size: Size<Pixels>,
|
||||
layout_engine: TaffyLayoutEngine,
|
||||
pub(crate) root_view: Option<AnyView<()>>,
|
||||
mouse_position: Point<Pixels>,
|
||||
current_stacking_order: StackingOrder,
|
||||
content_mask_stack: Vec<ContentMask>,
|
||||
pub(crate) scene: Scene,
|
||||
content_mask_stack: Vec<ContentMask<Pixels>>,
|
||||
mouse_event_handlers: HashMap<TypeId, Vec<(StackingOrder, MouseEventHandler)>>,
|
||||
propagate_event: bool,
|
||||
mouse_position: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
pub(crate) scene_builder: SceneBuilder,
|
||||
pub(crate) dirty: bool,
|
||||
}
|
||||
|
||||
@@ -43,11 +74,11 @@ impl Window {
|
||||
let content_size = platform_window.content_size();
|
||||
let scale_factor = platform_window.scale_factor();
|
||||
platform_window.on_resize(Box::new({
|
||||
let handle = handle;
|
||||
let cx = cx.to_async();
|
||||
move |content_size, scale_factor| {
|
||||
cx.update_window(handle, |cx| {
|
||||
cx.window.scene = Scene::new(scale_factor);
|
||||
cx.window.scale_factor = scale_factor;
|
||||
cx.window.scene_builder = SceneBuilder::new();
|
||||
cx.window.content_size = content_size;
|
||||
cx.window.display_id = cx
|
||||
.window
|
||||
@@ -61,6 +92,15 @@ impl Window {
|
||||
}
|
||||
}));
|
||||
|
||||
platform_window.on_event({
|
||||
let cx = cx.to_async();
|
||||
Box::new(move |event| {
|
||||
cx.update_window(handle, |cx| cx.dispatch_event(event))
|
||||
.log_err()
|
||||
.unwrap_or(true)
|
||||
})
|
||||
});
|
||||
|
||||
let platform_window = MainThreadOnly::new(Arc::new(platform_window), cx.executor.clone());
|
||||
|
||||
Window {
|
||||
@@ -72,23 +112,27 @@ impl Window {
|
||||
content_size,
|
||||
layout_engine: TaffyLayoutEngine::new(),
|
||||
root_view: None,
|
||||
mouse_position,
|
||||
current_stacking_order: SmallVec::new(),
|
||||
current_stacking_order: StackingOrder(SmallVec::new()),
|
||||
content_mask_stack: Vec::new(),
|
||||
scene: Scene::new(scale_factor),
|
||||
mouse_event_handlers: HashMap::default(),
|
||||
propagate_event: true,
|
||||
mouse_position,
|
||||
scale_factor,
|
||||
scene_builder: SceneBuilder::new(),
|
||||
dirty: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ContentMask {
|
||||
pub bounds: Bounds<Pixels>,
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub struct ContentMask<P: Clone + Debug> {
|
||||
pub bounds: Bounds<P>,
|
||||
}
|
||||
|
||||
impl ContentMask {
|
||||
pub fn scale(&self, factor: f32) -> ScaledContentMask {
|
||||
ScaledContentMask {
|
||||
impl ContentMask<Pixels> {
|
||||
pub fn scale(&self, factor: f32) -> ContentMask<ScaledPixels> {
|
||||
ContentMask {
|
||||
bounds: self.bounds.scale(factor),
|
||||
}
|
||||
}
|
||||
@@ -99,12 +143,6 @@ impl ContentMask {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub struct ScaledContentMask {
|
||||
bounds: Bounds<ScaledPixels>,
|
||||
}
|
||||
|
||||
pub struct WindowContext<'a, 'w> {
|
||||
app: Reference<'a, AppContext>,
|
||||
window: Reference<'w, Window>,
|
||||
@@ -234,19 +272,36 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f32 {
|
||||
self.window.scene.scale_factor
|
||||
self.window.scale_factor
|
||||
}
|
||||
|
||||
pub fn rem_size(&self) -> Pixels {
|
||||
self.window.rem_size
|
||||
}
|
||||
|
||||
pub fn mouse_position(&self) -> Point<Pixels> {
|
||||
self.window.mouse_position
|
||||
pub fn stop_propagation(&mut self) {
|
||||
self.window.propagate_event = false;
|
||||
}
|
||||
|
||||
pub fn scene(&mut self) -> &mut Scene {
|
||||
&mut self.window.scene
|
||||
pub fn on_mouse_event<Event: 'static>(
|
||||
&mut self,
|
||||
handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + Send + Sync + 'static,
|
||||
) {
|
||||
let order = self.window.current_stacking_order.clone();
|
||||
self.window
|
||||
.mouse_event_handlers
|
||||
.entry(TypeId::of::<Event>())
|
||||
.or_default()
|
||||
.push((
|
||||
order,
|
||||
Arc::new(move |event: &dyn Any, phase, cx| {
|
||||
handler(event.downcast_ref().unwrap(), phase, cx)
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn mouse_position(&self) -> Point<Pixels> {
|
||||
self.window.mouse_position
|
||||
}
|
||||
|
||||
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
@@ -256,8 +311,68 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn current_stacking_order(&self) -> StackingOrder {
|
||||
self.window.current_stacking_order.clone()
|
||||
pub fn paint_shadows(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
corner_radii: Corners<Pixels>,
|
||||
shadows: &[BoxShadow],
|
||||
) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let content_mask = self.content_mask();
|
||||
let window = &mut *self.window;
|
||||
for shadow in shadows {
|
||||
let mut shadow_bounds = bounds;
|
||||
shadow_bounds.origin += shadow.offset;
|
||||
shadow_bounds.dilate(shadow.spread_radius);
|
||||
window.scene_builder.insert(
|
||||
&window.current_stacking_order,
|
||||
Shadow {
|
||||
order: 0,
|
||||
bounds: shadow_bounds.scale(scale_factor),
|
||||
content_mask: content_mask.scale(scale_factor),
|
||||
corner_radii: corner_radii.scale(scale_factor),
|
||||
color: shadow.color,
|
||||
blur_radius: shadow.blur_radius.scale(scale_factor),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint_quad(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
corner_radii: Corners<Pixels>,
|
||||
background: impl Into<Hsla>,
|
||||
border_widths: Edges<Pixels>,
|
||||
border_color: impl Into<Hsla>,
|
||||
) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let content_mask = self.content_mask();
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.scene_builder.insert(
|
||||
&window.current_stacking_order,
|
||||
Quad {
|
||||
order: 0,
|
||||
bounds: bounds.scale(scale_factor),
|
||||
content_mask: content_mask.scale(scale_factor),
|
||||
background: background.into(),
|
||||
border_color: border_color.into(),
|
||||
corner_radii: corner_radii.scale(scale_factor),
|
||||
border_widths: border_widths.scale(scale_factor),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let content_mask = self.content_mask();
|
||||
path.content_mask = content_mask;
|
||||
path.color = color.into();
|
||||
let window = &mut *self.window;
|
||||
window
|
||||
.scene_builder
|
||||
.insert(&window.current_stacking_order, path.scale(scale_factor));
|
||||
}
|
||||
|
||||
pub fn paint_underline(
|
||||
@@ -277,9 +392,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
size: size(width, height),
|
||||
};
|
||||
let content_mask = self.content_mask();
|
||||
let layer_id = self.current_stacking_order();
|
||||
self.window.scene.insert(
|
||||
layer_id,
|
||||
let window = &mut *self.window;
|
||||
window.scene_builder.insert(
|
||||
&window.current_stacking_order,
|
||||
Underline {
|
||||
order: 0,
|
||||
bounds: bounds.scale(scale_factor),
|
||||
@@ -317,7 +432,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
|
||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||
if !raster_bounds.is_zero() {
|
||||
let layer_id = self.current_stacking_order();
|
||||
let tile =
|
||||
self.window
|
||||
.sprite_atlas
|
||||
@@ -330,9 +444,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
size: tile.bounds.size.map(Into::into),
|
||||
};
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
|
||||
self.window.scene.insert(
|
||||
layer_id,
|
||||
let window = &mut *self.window;
|
||||
window.scene_builder.insert(
|
||||
&window.current_stacking_order,
|
||||
MonochromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -366,7 +480,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
|
||||
let raster_bounds = self.text_system().raster_bounds(¶ms)?;
|
||||
if !raster_bounds.is_zero() {
|
||||
let layer_id = self.current_stacking_order();
|
||||
let tile =
|
||||
self.window
|
||||
.sprite_atlas
|
||||
@@ -379,9 +492,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
size: tile.bounds.size.map(Into::into),
|
||||
};
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
let window = &mut *self.window;
|
||||
|
||||
self.window.scene.insert(
|
||||
layer_id,
|
||||
window.scene_builder.insert(
|
||||
&window.current_stacking_order,
|
||||
PolychromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -411,7 +525,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
.map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
|
||||
};
|
||||
|
||||
let layer_id = self.current_stacking_order();
|
||||
let tile =
|
||||
self.window
|
||||
.sprite_atlas
|
||||
@@ -421,8 +534,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
})?;
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
|
||||
self.window.scene.insert(
|
||||
layer_id,
|
||||
let window = &mut *self.window;
|
||||
window.scene_builder.insert(
|
||||
&window.current_stacking_order,
|
||||
MonochromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -446,7 +560,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
let bounds = bounds.scale(scale_factor);
|
||||
let params = RenderImageParams { image_id: data.id };
|
||||
|
||||
let order = self.current_stacking_order();
|
||||
let tile = self
|
||||
.window
|
||||
.sprite_atlas
|
||||
@@ -456,8 +569,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
let corner_radii = corner_radii.scale(scale_factor);
|
||||
|
||||
self.window.scene.insert(
|
||||
order,
|
||||
let window = &mut *self.window;
|
||||
window.scene_builder.insert(
|
||||
&window.current_stacking_order,
|
||||
PolychromeSprite {
|
||||
order: 0,
|
||||
bounds,
|
||||
@@ -467,13 +581,17 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
grayscale,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn draw(&mut self) -> Result<()> {
|
||||
let unit_entity = self.unit_entity.clone();
|
||||
self.update_entity(&unit_entity, |view, cx| {
|
||||
cx.window
|
||||
.mouse_event_handlers
|
||||
.values_mut()
|
||||
.for_each(Vec::clear);
|
||||
|
||||
let mut root_view = cx.window.root_view.take().unwrap();
|
||||
let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
|
||||
let available_space = cx.window.content_size.map(Into::into);
|
||||
@@ -485,7 +603,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
|
||||
root_view.paint(layout, &mut (), &mut frame_state, cx)?;
|
||||
cx.window.root_view = Some(root_view);
|
||||
let scene = cx.window.scene.take();
|
||||
let scene = cx.window.scene_builder.build();
|
||||
|
||||
cx.run_on_main(view, |_, cx| {
|
||||
cx.window
|
||||
@@ -499,6 +617,59 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn dispatch_event(&mut self, event: Event) -> bool {
|
||||
if let Some(any_mouse_event) = event.mouse_event() {
|
||||
if let Some(MouseMoveEvent { position, .. }) = any_mouse_event.downcast_ref() {
|
||||
self.window.mouse_position = *position;
|
||||
}
|
||||
|
||||
if let Some(mut handlers) = self
|
||||
.window
|
||||
.mouse_event_handlers
|
||||
.remove(&any_mouse_event.type_id())
|
||||
{
|
||||
// Because handlers may add other handlers, we sort every time.
|
||||
handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
// Handlers may set this to false by calling `stop_propagation`
|
||||
self.window.propagate_event = true;
|
||||
|
||||
// Capture phase, events bubble from back to front. Handlers for this phase are used for
|
||||
// special purposes, such as detecting events outside of a given Bounds.
|
||||
for (_, handler) in &handlers {
|
||||
handler(any_mouse_event, DispatchPhase::Capture, self);
|
||||
if !self.window.propagate_event {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Bubble phase, where most normal handlers do their work.
|
||||
if self.window.propagate_event {
|
||||
for (_, handler) in handlers.iter().rev() {
|
||||
handler(any_mouse_event, DispatchPhase::Bubble, self);
|
||||
if !self.window.propagate_event {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just in case any handlers added new handlers, which is weird, but possible.
|
||||
handlers.extend(
|
||||
self.window
|
||||
.mouse_event_handlers
|
||||
.get_mut(&any_mouse_event.type_id())
|
||||
.into_iter()
|
||||
.flat_map(|handlers| handlers.drain(..)),
|
||||
);
|
||||
self.window
|
||||
.mouse_event_handlers
|
||||
.insert(any_mouse_event.type_id(), handlers);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for WindowContext<'_, '_> {
|
||||
@@ -557,7 +728,11 @@ pub trait BorrowWindow: BorrowAppContext {
|
||||
fn window(&self) -> &Window;
|
||||
fn window_mut(&mut self) -> &mut Window;
|
||||
|
||||
fn with_content_mask<R>(&mut self, mask: ContentMask, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
fn with_content_mask<R>(
|
||||
&mut self,
|
||||
mask: ContentMask<Pixels>,
|
||||
f: impl FnOnce(&mut Self) -> R,
|
||||
) -> R {
|
||||
let mask = mask.intersect(&self.content_mask());
|
||||
self.window_mut().content_mask_stack.push(mask);
|
||||
let result = f(self);
|
||||
@@ -565,7 +740,7 @@ pub trait BorrowWindow: BorrowAppContext {
|
||||
result
|
||||
}
|
||||
|
||||
fn content_mask(&self) -> ContentMask {
|
||||
fn content_mask(&self) -> ContentMask<Pixels> {
|
||||
self.window()
|
||||
.content_mask_stack
|
||||
.last()
|
||||
@@ -727,6 +902,18 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_mouse_event<Event: 'static>(
|
||||
&mut self,
|
||||
handler: impl Fn(&mut S, &Event, DispatchPhase, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
) {
|
||||
let handle = self.handle().upgrade(self).unwrap();
|
||||
self.window_cx.on_mouse_event(move |event, phase, cx| {
|
||||
handle.update(cx, |view, cx| {
|
||||
handler(view, event, phase, cx);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn erase_state<R>(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R {
|
||||
let entity_id = self.unit_entity.id;
|
||||
let mut cx = ViewContext::mutable(
|
||||
@@ -815,3 +1002,10 @@ pub struct AnyWindowHandle {
|
||||
pub(crate) id: WindowId,
|
||||
state_type: TypeId,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
impl From<SmallVec<[u32; 16]>> for StackingOrder {
|
||||
fn from(small_vec: SmallVec<[u32; 16]>) -> Self {
|
||||
StackingOrder(small_vec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use gpui3::{
|
||||
div, img, svg, view, AppContext, Context, Element, IntoAnyElement, ParentElement, ScrollState,
|
||||
SharedString, StyleHelpers, View, ViewContext, WindowContext,
|
||||
div, img, svg, view, AppContext, Context, Element, Interactive, IntoAnyElement, MouseButton,
|
||||
ParentElement, ScrollState, SharedString, StyleHelpers, Styled, View, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use ui::{theme, Theme};
|
||||
|
||||
@@ -44,6 +45,9 @@ impl CollabPanel {
|
||||
// List Container
|
||||
.child(
|
||||
div()
|
||||
.on_click(MouseButton::Left, |_, _, _| {
|
||||
dbg!("click!");
|
||||
})
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.pb_1()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
@@ -126,6 +130,8 @@ impl CollabPanel {
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.active.background)
|
||||
.child(div().flex().gap_1().text_sm().child(label))
|
||||
.child(
|
||||
div().flex().h_full().gap_1().items_center().child(
|
||||
|
||||
Reference in New Issue
Block a user