Merge branch 'chat'

This commit is contained in:
Antonio Scandurra
2021-09-07 19:21:03 +02:00
105 changed files with 11897 additions and 5996 deletions
+956 -606
View File
File diff suppressed because it is too large Load Diff
+111 -64
View File
@@ -5,11 +5,15 @@ mod container;
mod empty;
mod event_handler;
mod flex;
mod hook;
mod label;
mod line_box;
mod list;
mod mouse_event_handler;
mod overlay;
mod stack;
mod svg;
mod text;
mod uniform_list;
pub use crate::presenter::ChildView;
@@ -20,26 +24,35 @@ pub use container::*;
pub use empty::*;
pub use event_handler::*;
pub use flex::*;
pub use hook::*;
pub use label::*;
pub use line_box::*;
pub use list::*;
pub use mouse_event_handler::*;
pub use overlay::*;
pub use stack::*;
pub use svg::*;
pub use text::*;
pub use uniform_list::*;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AfterLayoutContext, DebugContext, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
json, DebugContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use core::panic;
use json::ToJson;
use std::{any::Any, borrow::Cow, mem};
use std::{
any::Any,
borrow::Cow,
cell::RefCell,
mem,
ops::{Deref, DerefMut},
rc::Rc,
};
trait AnyElement {
fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F;
fn after_layout(&mut self, _: &mut AfterLayoutContext) {}
fn paint(&mut self, origin: Vector2F, cx: &mut PaintContext);
fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext);
fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool;
fn debug(&self, cx: &DebugContext) -> serde_json::Value;
@@ -57,16 +70,10 @@ pub trait Element {
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState);
fn after_layout(
&mut self,
size: Vector2F,
layout: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
);
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState;
@@ -96,20 +103,20 @@ pub trait Element {
where
Self: 'static + Sized,
{
ElementBox {
ElementBox(ElementRc {
name: None,
element: Box::new(Lifecycle::Init { element: self }),
}
element: Rc::new(RefCell::new(Lifecycle::Init { element: self })),
})
}
fn named(self, name: impl Into<Cow<'static, str>>) -> ElementBox
where
Self: 'static + Sized,
{
ElementBox {
ElementBox(ElementRc {
name: Some(name.into()),
element: Box::new(Lifecycle::Init { element: self }),
}
element: Rc::new(RefCell::new(Lifecycle::Init { element: self })),
})
}
}
@@ -132,9 +139,12 @@ pub enum Lifecycle<T: Element> {
paint: T::PaintState,
},
}
pub struct ElementBox {
pub struct ElementBox(ElementRc);
#[derive(Clone)]
pub struct ElementRc {
name: Option<Cow<'static, str>>,
element: Box<dyn AnyElement>,
element: Rc<RefCell<dyn AnyElement>>,
}
impl<T: Element> AnyElement for Lifecycle<T> {
@@ -161,40 +171,49 @@ impl<T: Element> AnyElement for Lifecycle<T> {
result
}
fn after_layout(&mut self, cx: &mut AfterLayoutContext) {
if let Lifecycle::PostLayout {
element,
size,
layout,
..
} = self
{
element.after_layout(*size, layout, cx);
} else {
panic!("invalid element lifecycle state");
}
}
fn paint(&mut self, origin: Vector2F, cx: &mut PaintContext) {
*self = if let Lifecycle::PostLayout {
mut element,
constraint,
size,
mut layout,
} = mem::take(self)
{
let bounds = RectF::new(origin, size);
let paint = element.paint(bounds, &mut layout, cx);
fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
*self = match mem::take(self) {
Lifecycle::PostLayout {
mut element,
constraint,
size,
mut layout,
} => {
let bounds = RectF::new(origin, size);
let visible_bounds = visible_bounds
.intersection(bounds)
.unwrap_or_else(|| RectF::new(bounds.origin(), Vector2F::default()));
let paint = element.paint(bounds, visible_bounds, &mut layout, cx);
Lifecycle::PostPaint {
element,
constraint,
bounds,
layout,
paint,
}
}
Lifecycle::PostPaint {
element,
mut element,
constraint,
bounds,
layout,
paint,
mut layout,
..
} => {
let bounds = RectF::new(origin, bounds.size());
let visible_bounds = visible_bounds
.intersection(bounds)
.unwrap_or_else(|| RectF::new(bounds.origin(), Vector2F::default()));
let paint = element.paint(bounds, visible_bounds, &mut layout, cx);
Lifecycle::PostPaint {
element,
constraint,
bounds,
layout,
paint,
}
}
} else {
panic!("invalid element lifecycle state");
};
_ => panic!("invalid element lifecycle state"),
}
}
fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool {
@@ -264,32 +283,51 @@ impl<T: Element> Default for Lifecycle<T> {
}
impl ElementBox {
pub fn metadata<T: 'static>(&self) -> Option<&T> {
let element = unsafe { &*self.0.element.as_ptr() };
element.metadata().and_then(|m| m.downcast_ref())
}
}
impl Into<ElementRc> for ElementBox {
fn into(self) -> ElementRc {
self.0
}
}
impl Deref for ElementBox {
type Target = ElementRc;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ElementBox {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl ElementRc {
pub fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
self.element.layout(constraint, cx)
self.element.borrow_mut().layout(constraint, cx)
}
pub fn after_layout(&mut self, cx: &mut AfterLayoutContext) {
self.element.after_layout(cx);
}
pub fn paint(&mut self, origin: Vector2F, cx: &mut PaintContext) {
self.element.paint(origin, cx);
pub fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
self.element.borrow_mut().paint(origin, visible_bounds, cx);
}
pub fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool {
self.element.dispatch_event(event, cx)
self.element.borrow_mut().dispatch_event(event, cx)
}
pub fn size(&self) -> Vector2F {
self.element.size()
}
pub fn metadata(&self) -> Option<&dyn Any> {
self.element.metadata()
self.element.borrow().size()
}
pub fn debug(&self, cx: &DebugContext) -> json::Value {
let mut value = self.element.debug(cx);
let mut value = self.element.borrow().debug(cx);
if let Some(name) = &self.name {
if let json::Value::Object(map) = &mut value {
@@ -302,6 +340,15 @@ impl ElementBox {
value
}
pub fn with_metadata<T, F, R>(&self, f: F) -> R
where
T: 'static,
F: FnOnce(Option<&T>) -> R,
{
let element = self.element.borrow();
f(element.metadata().and_then(|m| m.downcast_ref()))
}
}
pub trait ParentElement<'a>: Extend<ElementBox> + Sized {
+11 -15
View File
@@ -1,9 +1,10 @@
use crate::{
json, AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext,
LayoutContext, PaintContext, SizeConstraint,
geometry::{rect::RectF, vector::Vector2F},
json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
use json::ToJson;
use pathfinder_geometry::vector::Vector2F;
use serde_json::json;
pub struct Align {
@@ -51,18 +52,10 @@ impl Element for Align {
(size, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
self.child.after_layout(cx);
}
fn paint(
&mut self,
bounds: pathfinder_geometry::rect::RectF,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
@@ -72,8 +65,11 @@ impl Element for Align {
let child_center = self.child.size() / 2.;
let child_target = child_center + child_center * self.alignment;
self.child
.paint(bounds.origin() - (child_target - my_target), cx);
self.child.paint(
bounds.origin() - (child_target - my_target),
visible_bounds,
cx,
);
}
fn dispatch_event(
+5 -14
View File
@@ -9,13 +9,11 @@ use pathfinder_geometry::{
vector::{vec2f, Vector2F},
};
pub struct Canvas<F>(F)
where
F: FnMut(RectF, &mut PaintContext);
pub struct Canvas<F>(F);
impl<F> Canvas<F>
where
F: FnMut(RectF, &mut PaintContext),
F: FnMut(RectF, RectF, &mut PaintContext),
{
pub fn new(f: F) -> Self {
Self(f)
@@ -24,7 +22,7 @@ where
impl<F> Element for Canvas<F>
where
F: FnMut(RectF, &mut PaintContext),
F: FnMut(RectF, RectF, &mut PaintContext),
{
type LayoutState = ();
type PaintState = ();
@@ -50,18 +48,11 @@ where
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
self.0(bounds, cx)
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
_: &mut crate::AfterLayoutContext,
) {
self.0(bounds, visible_bounds, cx)
}
fn dispatch_event(
+4 -12
View File
@@ -3,8 +3,8 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext,
LayoutContext, PaintContext, SizeConstraint,
json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
pub struct ConstrainedBox {
@@ -67,22 +67,14 @@ impl Element for ConstrainedBox {
(size, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
self.child.after_layout(cx);
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
self.child.paint(bounds.origin(), cx);
self.child.paint(bounds.origin(), visible_bounds, cx);
}
fn dispatch_event(
+76 -22
View File
@@ -10,8 +10,7 @@ use crate::{
},
json::ToJson,
scene::{self, Border, Quad},
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
#[derive(Clone, Debug, Default, Deserialize)]
@@ -167,18 +166,10 @@ impl Element for Container {
(child_size + size_buffer, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
self.child.after_layout(cx);
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
@@ -208,7 +199,7 @@ impl Element for Container {
self.style.border.left_width(),
self.style.border.top_width(),
);
self.child.paint(child_origin, cx);
self.child.paint(child_origin, visible_bounds, cx);
}
fn dispatch_event(
@@ -251,15 +242,11 @@ impl ToJson for ContainerStyle {
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default)]
pub struct Margin {
#[serde(default)]
top: f32,
#[serde(default)]
left: f32,
#[serde(default)]
bottom: f32,
#[serde(default)]
right: f32,
}
@@ -282,18 +269,85 @@ impl ToJson for Margin {
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default)]
pub struct Padding {
#[serde(default)]
top: f32,
#[serde(default)]
left: f32,
#[serde(default)]
bottom: f32,
#[serde(default)]
right: f32,
}
impl<'de> Deserialize<'de> for Padding {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let spacing = Spacing::deserialize(deserializer)?;
Ok(match spacing {
Spacing::Uniform(size) => Padding {
top: size,
left: size,
bottom: size,
right: size,
},
Spacing::Specific {
top,
left,
bottom,
right,
} => Padding {
top,
left,
bottom,
right,
},
})
}
}
impl<'de> Deserialize<'de> for Margin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let spacing = Spacing::deserialize(deserializer)?;
Ok(match spacing {
Spacing::Uniform(size) => Margin {
top: size,
left: size,
bottom: size,
right: size,
},
Spacing::Specific {
top,
left,
bottom,
right,
} => Margin {
top,
left,
bottom,
right,
},
})
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Spacing {
Uniform(f32),
Specific {
#[serde(default)]
top: f32,
#[serde(default)]
left: f32,
#[serde(default)]
bottom: f32,
#[serde(default)]
right: f32,
},
}
impl ToJson for Padding {
fn to_json(&self) -> serde_json::Value {
let mut value = json!({});
+2 -6
View File
@@ -6,9 +6,7 @@ use crate::{
json::{json, ToJson},
DebugContext,
};
use crate::{
AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use crate::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint};
pub struct Empty;
@@ -41,12 +39,10 @@ impl Element for Empty {
(vec2f(x, y), ())
}
fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
}
fn paint(
&mut self,
_: RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut PaintContext,
) -> Self::PaintState {
+4 -12
View File
@@ -2,8 +2,8 @@ use pathfinder_geometry::rect::RectF;
use serde_json::json;
use crate::{
geometry::vector::Vector2F, AfterLayoutContext, DebugContext, Element, ElementBox, Event,
EventContext, LayoutContext, PaintContext, SizeConstraint,
geometry::vector::Vector2F, DebugContext, Element, ElementBox, Event, EventContext,
LayoutContext, PaintContext, SizeConstraint,
};
pub struct EventHandler {
@@ -41,22 +41,14 @@ impl Element for EventHandler {
(size, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
self.child.after_layout(cx);
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
self.child.paint(bounds.origin(), cx);
self.child.paint(bounds.origin(), visible_bounds, cx);
}
fn dispatch_event(
+159 -62
View File
@@ -2,8 +2,8 @@ use std::{any::Any, f32::INFINITY};
use crate::{
json::{self, ToJson, Value},
AfterLayoutContext, Axis, DebugContext, Element, ElementBox, Event, EventContext,
LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint, Vector2FExt,
};
use pathfinder_geometry::{
rect::RectF,
@@ -32,11 +32,46 @@ impl Flex {
Self::new(Axis::Vertical)
}
fn child_flex<'b>(child: &ElementBox) -> Option<f32> {
child
.metadata()
.and_then(|d| d.downcast_ref::<FlexParentData>())
.map(|data| data.flex)
fn layout_flex_children(
&mut self,
expanded: bool,
constraint: SizeConstraint,
remaining_space: &mut f32,
remaining_flex: &mut f32,
cross_axis_max: &mut f32,
cx: &mut LayoutContext,
) {
let cross_axis = self.axis.invert();
for child in &mut self.children {
if let Some(metadata) = child.metadata::<FlexParentData>() {
if metadata.expanded != expanded {
continue;
}
let flex = metadata.flex;
let child_max = if *remaining_flex == 0.0 {
*remaining_space
} else {
let space_per_flex = *remaining_space / *remaining_flex;
space_per_flex * flex
};
let child_min = if expanded { child_max } else { 0. };
let child_constraint = match self.axis {
Axis::Horizontal => SizeConstraint::new(
vec2f(child_min, constraint.min.y()),
vec2f(child_max, constraint.max.y()),
),
Axis::Vertical => SizeConstraint::new(
vec2f(constraint.min.x(), child_min),
vec2f(constraint.max.x(), child_max),
),
};
let child_size = child.layout(child_constraint, cx);
*remaining_space -= child_size.along(self.axis);
*remaining_flex -= flex;
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
}
}
}
}
@@ -47,7 +82,7 @@ impl Extend<ElementBox> for Flex {
}
impl Element for Flex {
type LayoutState = ();
type LayoutState = bool;
type PaintState = ();
fn layout(
@@ -55,14 +90,14 @@ impl Element for Flex {
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let mut total_flex = 0.0;
let mut total_flex = None;
let mut fixed_space = 0.0;
let cross_axis = self.axis.invert();
let mut cross_axis_max: f32 = 0.0;
for child in &mut self.children {
if let Some(flex) = Self::child_flex(&child) {
total_flex += flex;
if let Some(metadata) = child.metadata::<FlexParentData>() {
*total_flex.get_or_insert(0.) += metadata.flex;
} else {
let child_constraint = match self.axis {
Axis::Horizontal => SizeConstraint::new(
@@ -80,37 +115,28 @@ impl Element for Flex {
}
}
let mut size = if total_flex > 0.0 {
let mut size = if let Some(mut remaining_flex) = total_flex {
if constraint.max_along(self.axis).is_infinite() {
panic!("flex contains flexible children but has an infinite constraint along the flex axis");
}
let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
let mut remaining_flex = total_flex;
for child in &mut self.children {
if let Some(flex) = Self::child_flex(&child) {
let child_max = if remaining_flex == 0.0 {
remaining_space
} else {
let space_per_flex = remaining_space / remaining_flex;
space_per_flex * flex
};
let child_constraint = match self.axis {
Axis::Horizontal => SizeConstraint::new(
vec2f(0.0, constraint.min.y()),
vec2f(child_max, constraint.max.y()),
),
Axis::Vertical => SizeConstraint::new(
vec2f(constraint.min.x(), 0.0),
vec2f(constraint.max.x(), child_max),
),
};
let child_size = child.layout(child_constraint, cx);
remaining_space -= child_size.along(self.axis);
remaining_flex -= flex;
cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
}
}
self.layout_flex_children(
false,
constraint,
&mut remaining_space,
&mut remaining_flex,
&mut cross_axis_max,
cx,
);
self.layout_flex_children(
true,
constraint,
&mut remaining_space,
&mut remaining_flex,
&mut cross_axis_max,
cx,
);
match self.axis {
Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
@@ -126,39 +152,44 @@ impl Element for Flex {
if constraint.min.x().is_finite() {
size.set_x(size.x().max(constraint.min.x()));
}
if constraint.min.y().is_finite() {
size.set_y(size.y().max(constraint.min.y()));
}
(size, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
for child in &mut self.children {
child.after_layout(cx);
let mut overflowing = false;
if size.x() > constraint.max.x() {
size.set_x(constraint.max.x());
overflowing = true;
}
if size.y() > constraint.max.y() {
size.set_y(constraint.max.y());
overflowing = true;
}
(size, overflowing)
}
fn paint(
&mut self,
bounds: RectF,
_: &mut Self::LayoutState,
visible_bounds: RectF,
overflowing: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
if *overflowing {
cx.scene.push_layer(Some(bounds));
}
let mut child_origin = bounds.origin();
for child in &mut self.children {
child.paint(child_origin, cx);
child.paint(child_origin, visible_bounds, cx);
match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
}
}
if *overflowing {
cx.scene.pop_layer();
}
}
fn dispatch_event(
@@ -194,6 +225,7 @@ impl Element for Flex {
struct FlexParentData {
flex: f32,
expanded: bool,
}
pub struct Expanded {
@@ -204,7 +236,10 @@ pub struct Expanded {
impl Expanded {
pub fn new(flex: f32, child: ElementBox) -> Self {
Expanded {
metadata: FlexParentData { flex },
metadata: FlexParentData {
flex,
expanded: true,
},
child,
}
}
@@ -223,22 +258,14 @@ impl Element for Expanded {
(size, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
self.child.after_layout(cx);
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
self.child.paint(bounds.origin(), cx)
self.child.paint(bounds.origin(), visible_bounds, cx)
}
fn dispatch_event(
@@ -270,3 +297,73 @@ impl Element for Expanded {
})
}
}
pub struct Flexible {
metadata: FlexParentData,
child: ElementBox,
}
impl Flexible {
pub fn new(flex: f32, child: ElementBox) -> Self {
Flexible {
metadata: FlexParentData {
flex,
expanded: false,
},
child,
}
}
}
impl Element for Flexible {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, cx);
(size, ())
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
self.child.paint(bounds.origin(), visible_bounds, cx)
}
fn dispatch_event(
&mut self,
event: &Event,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
self.child.dispatch_event(event, cx)
}
fn metadata(&self) -> Option<&dyn Any> {
Some(&self.metadata)
}
fn debug(
&self,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &DebugContext,
) -> Value {
json!({
"type": "Flexible",
"flex": self.metadata.flex,
"child": self.child.debug(cx)
})
}
}
+79
View File
@@ -0,0 +1,79 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
pub struct Hook {
child: ElementBox,
after_layout: Option<Box<dyn FnMut(Vector2F, &mut LayoutContext)>>,
}
impl Hook {
pub fn new(child: ElementBox) -> Self {
Self {
child,
after_layout: None,
}
}
pub fn on_after_layout(
mut self,
f: impl 'static + FnMut(Vector2F, &mut LayoutContext),
) -> Self {
self.after_layout = Some(Box::new(f));
self
}
}
impl Element for Hook {
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, cx);
if let Some(handler) = self.after_layout.as_mut() {
handler(size, cx);
}
(size, ())
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) {
self.child.paint(bounds.origin(), visible_bounds, cx);
}
fn dispatch_event(
&mut self,
event: &Event,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
self.child.dispatch_event(event, cx)
}
fn debug(
&self,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &DebugContext,
) -> serde_json::Value {
json!({
"type": "Hooks",
"child": self.child.debug(cx),
})
}
}
+112 -103
View File
@@ -1,15 +1,12 @@
use crate::{
color::Color,
font_cache::FamilyId,
fonts::{FontId, TextStyle},
fonts::TextStyle,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{ToJson, Value},
text_layout::Line,
AfterLayoutContext, DebugContext, Element, Event, EventContext, FontCache, LayoutContext,
PaintContext, SizeConstraint,
text_layout::{Line, RunStyle},
DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use serde::Deserialize;
use serde_json::json;
@@ -17,37 +14,32 @@ use smallvec::{smallvec, SmallVec};
pub struct Label {
text: String,
family_id: FamilyId,
font_size: f32,
style: LabelStyle,
highlight_indices: Vec<usize>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
pub struct LabelStyle {
pub text: TextStyle,
pub highlight_text: Option<TextStyle>,
}
impl Label {
pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
Self {
impl From<TextStyle> for LabelStyle {
fn from(text: TextStyle) -> Self {
LabelStyle {
text,
family_id,
font_size,
highlight_indices: Default::default(),
style: Default::default(),
highlight_text: None,
}
}
}
pub fn with_style(mut self, style: &LabelStyle) -> Self {
self.style = style.clone();
self
}
pub fn with_default_color(mut self, color: Color) -> Self {
self.style.text.color = color;
self
impl Label {
pub fn new(text: String, style: impl Into<LabelStyle>) -> Self {
Self {
text,
highlight_indices: Default::default(),
style: style.into(),
}
}
pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
@@ -55,47 +47,52 @@ impl Label {
self
}
fn compute_runs(
&self,
font_cache: &FontCache,
font_id: FontId,
) -> SmallVec<[(usize, FontId, Color); 8]> {
fn compute_runs(&self) -> SmallVec<[(usize, RunStyle); 8]> {
let font_id = self.style.text.font_id;
if self.highlight_indices.is_empty() {
return smallvec![(self.text.len(), font_id, self.style.text.color)];
return smallvec![(
self.text.len(),
RunStyle {
font_id,
color: self.style.text.color,
underline: self.style.text.underline,
}
)];
}
let highlight_font_id = self
.style
.highlight_text
.as_ref()
.and_then(|style| {
font_cache
.select_font(self.family_id, &style.font_properties)
.ok()
})
.unwrap_or(font_id);
.map_or(font_id, |style| style.font_id);
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
let mut runs = SmallVec::new();
let highlight_style = self
.style
.highlight_text
.as_ref()
.unwrap_or(&self.style.text);
for (char_ix, c) in self.text.char_indices() {
let mut font_id = font_id;
let mut color = self.style.text.color;
let mut underline = self.style.text.underline;
if let Some(highlight_ix) = highlight_indices.peek() {
if char_ix == *highlight_ix {
font_id = highlight_font_id;
color = self
.style
.highlight_text
.as_ref()
.unwrap_or(&self.style.text)
.color;
color = highlight_style.color;
underline = highlight_style.underline;
highlight_indices.next();
}
}
let push_new_run = if let Some((last_len, last_font_id, last_color)) = runs.last_mut() {
if font_id == *last_font_id && color == *last_color {
let last_run: Option<&mut (usize, RunStyle)> = runs.last_mut();
let push_new_run = if let Some((last_len, last_style)) = last_run {
if font_id == last_style.font_id
&& color == last_style.color
&& underline == last_style.underline
{
*last_len += c.len_utf8();
false
} else {
@@ -106,7 +103,14 @@ impl Label {
};
if push_new_run {
runs.push((c.len_utf8(), font_id, color));
runs.push((
c.len_utf8(),
RunStyle {
font_id,
color,
underline,
},
));
}
}
@@ -123,37 +127,31 @@ impl Element for Label {
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let font_id = cx
.font_cache
.select_font(self.family_id, &self.style.text.font_properties)
.unwrap();
let runs = self.compute_runs(&cx.font_cache, font_id);
let line =
cx.text_layout_cache
.layout_str(self.text.as_str(), self.font_size, runs.as_slice());
let runs = self.compute_runs();
let line = cx.text_layout_cache.layout_str(
self.text.as_str(),
self.style.text.font_size,
runs.as_slice(),
);
let size = vec2f(
line.width().max(constraint.min.x()).min(constraint.max.x()),
cx.font_cache.line_height(font_id, self.font_size).ceil(),
cx.font_cache
.line_height(self.style.text.font_id, self.style.text.font_size)
.ceil(),
);
(size, line)
}
fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
line: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
line.paint(
bounds.origin(),
RectF::new(vec2f(0., 0.), bounds.size()),
cx,
)
line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
}
fn dispatch_event(
@@ -172,15 +170,13 @@ impl Element for Label {
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &DebugContext,
_: &DebugContext,
) -> Value {
json!({
"type": "Label",
"bounds": bounds.to_json(),
"text": &self.text,
"highlight_indices": self.highlight_indices,
"font_family": cx.font_cache.family_name(self.family_id).unwrap(),
"font_size": self.font_size,
"style": self.style.to_json(),
})
}
@@ -200,52 +196,65 @@ impl ToJson for LabelStyle {
#[cfg(test)]
mod tests {
use super::*;
use crate::color::Color;
use crate::fonts::{Properties as FontProperties, Weight};
#[crate::test(self)]
fn test_layout_label_with_highlights(cx: &mut crate::MutableAppContext) {
let menlo = cx.font_cache().load_family(&["Menlo"]).unwrap();
let menlo_regular = cx
.font_cache()
.select_font(menlo, &FontProperties::new())
.unwrap();
let menlo_bold = cx
.font_cache()
.select_font(menlo, FontProperties::new().weight(Weight::BOLD))
.unwrap();
let black = Color::black();
let red = Color::new(255, 0, 0, 255);
let default_style = TextStyle::new(
"Menlo",
12.,
Default::default(),
false,
Color::black(),
cx.font_cache(),
)
.unwrap();
let highlight_style = TextStyle::new(
"Menlo",
12.,
*FontProperties::new().weight(Weight::BOLD),
false,
Color::new(255, 0, 0, 255),
cx.font_cache(),
)
.unwrap();
let label = Label::new(
".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(),
LabelStyle {
text: default_style.clone(),
highlight_text: Some(highlight_style.clone()),
},
)
.with_highlights(vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
]);
let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0)
.with_style(&LabelStyle {
text: TextStyle {
color: black,
font_properties: Default::default(),
},
highlight_text: Some(TextStyle {
color: red,
font_properties: *FontProperties::new().weight(Weight::BOLD),
}),
})
.with_highlights(vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
]);
let runs = label.compute_runs(cx.font_cache().as_ref(), menlo_regular);
let default_run_style = RunStyle {
font_id: default_style.font_id,
color: default_style.color,
underline: default_style.underline,
};
let highlight_run_style = RunStyle {
font_id: highlight_style.font_id,
color: highlight_style.color,
underline: highlight_style.underline,
};
let runs = label.compute_runs();
assert_eq!(
runs.as_slice(),
&[
(".α".len(), menlo_regular, black),
("βγ".len(), menlo_bold, red),
("δ".len(), menlo_regular, black),
("ε".len(), menlo_bold, red),
(".ⓐ".len(), menlo_regular, black),
("ⓑⓒ".len(), menlo_bold, red),
("ⓓⓔ.abcde.".len(), menlo_regular, black),
(".α".len(), default_run_style),
("βγ".len(), highlight_run_style),
("δ".len(), default_run_style),
("ε".len(), highlight_run_style),
(".ⓐ".len(), default_run_style),
("ⓑⓒ".len(), highlight_run_style),
("ⓓⓔ.abcde.".len(), default_run_style),
]
);
}
+29 -50
View File
@@ -1,30 +1,22 @@
use crate::{
font_cache::FamilyId,
fonts::Properties,
fonts::TextStyle,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext,
PaintContext, SizeConstraint,
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
pub struct LineBox {
child: ElementBox,
family_id: FamilyId,
font_size: f32,
font_properties: Properties,
style: TextStyle,
}
impl LineBox {
pub fn new(family_id: FamilyId, font_size: f32, child: ElementBox) -> Self {
Self {
child,
family_id,
font_size,
font_properties: Properties::default(),
}
pub fn new(child: ElementBox, style: TextStyle) -> Self {
Self { child, style }
}
}
@@ -37,52 +29,41 @@ impl Element for LineBox {
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
match cx
let line_height = cx
.font_cache
.select_font(self.family_id, &self.font_properties)
{
Ok(font_id) => {
let line_height = cx.font_cache.line_height(font_id, self.font_size);
let character_height = cx.font_cache.ascent(font_id, self.font_size)
+ cx.font_cache.descent(font_id, self.font_size);
let child_max = vec2f(constraint.max.x(), character_height);
let child_size = self.child.layout(
SizeConstraint::new(constraint.min.min(child_max), child_max),
cx,
);
let size = vec2f(child_size.x(), line_height);
(size, (line_height - character_height) / 2.)
}
Err(error) => {
log::error!("can't find font for LineBox: {}", error);
(constraint.min, 0.0)
}
}
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
self.child.after_layout(cx);
.line_height(self.style.font_id, self.style.font_size);
let character_height = cx
.font_cache
.ascent(self.style.font_id, self.style.font_size)
+ cx.font_cache
.descent(self.style.font_id, self.style.font_size);
let child_max = vec2f(constraint.max.x(), character_height);
let child_size = self.child.layout(
SizeConstraint::new(constraint.min.min(child_max), child_max),
cx,
);
let size = vec2f(child_size.x(), line_height);
(size, (line_height - character_height) / 2.)
}
fn paint(
&mut self,
bounds: pathfinder_geometry::rect::RectF,
bounds: RectF,
visible_bounds: RectF,
padding_top: &mut f32,
cx: &mut PaintContext,
) -> Self::PaintState {
self.child
.paint(bounds.origin() + vec2f(0., *padding_top), cx);
self.child.paint(
bounds.origin() + vec2f(0., *padding_top),
visible_bounds,
cx,
);
}
fn dispatch_event(
&mut self,
event: &Event,
_: pathfinder_geometry::rect::RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
@@ -99,9 +80,7 @@ impl Element for LineBox {
) -> serde_json::Value {
json!({
"bounds": bounds.to_json(),
"font_family": cx.font_cache.family_name(self.family_id).unwrap(),
"font_size": self.font_size,
"font_properties": self.font_properties.to_json(),
"style": self.style.to_json(),
"child": self.child.debug(cx),
})
}
+864
View File
@@ -0,0 +1,864 @@
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::json,
sum_tree::{self, Bias, SumTree},
DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
pub struct List {
state: ListState,
}
#[derive(Clone)]
pub struct ListState(Rc<RefCell<StateInner>>);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Orientation {
Top,
Bottom,
}
struct StateInner {
last_layout_width: Option<f32>,
render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> ElementBox>,
rendered_range: Range<usize>,
items: SumTree<ListItem>,
logical_scroll_top: Option<ListOffset>,
orientation: Orientation,
overdraw: f32,
scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut EventContext)>>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ListOffset {
item_ix: usize,
offset_in_item: f32,
}
#[derive(Clone)]
enum ListItem {
Unrendered,
Rendered(ElementRc),
Removed(f32),
}
impl std::fmt::Debug for ListItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unrendered => write!(f, "Unrendered"),
Self::Rendered(_) => f.debug_tuple("Rendered").finish(),
Self::Removed(height) => f.debug_tuple("Removed").field(height).finish(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
struct ListItemSummary {
count: usize,
rendered_count: usize,
unrendered_count: usize,
height: f32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct Count(usize);
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct RenderedCount(usize);
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct UnrenderedCount(usize);
#[derive(Clone, Debug, Default)]
struct Height(f32);
impl List {
pub fn new(state: ListState) -> Self {
Self { state }
}
}
impl Element for List {
type LayoutState = ListOffset;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let state = &mut *self.state.0.borrow_mut();
let size = constraint.max;
let mut item_constraint = constraint;
item_constraint.min.set_y(0.);
item_constraint.max.set_y(f32::INFINITY);
if cx.refreshing || state.last_layout_width != Some(size.x()) {
state.rendered_range = 0..0;
state.items = SumTree::from_iter(
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
&(),
)
}
let old_items = state.items.clone();
let mut new_items = SumTree::new();
let mut rendered_items = VecDeque::new();
let mut rendered_height = 0.;
let mut scroll_top = state
.logical_scroll_top
.unwrap_or_else(|| match state.orientation {
Orientation::Top => ListOffset {
item_ix: 0,
offset_in_item: 0.,
},
Orientation::Bottom => ListOffset {
item_ix: state.items.summary().count,
offset_in_item: 0.,
},
});
// Render items after the scroll top, including those in the trailing overdraw.
let mut cursor = old_items.cursor::<Count, ()>();
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
for (ix, item) in cursor.by_ref().enumerate() {
if rendered_height - scroll_top.offset_in_item >= size.y() + state.overdraw {
break;
}
let element = state.render_item(scroll_top.item_ix + ix, item, item_constraint, cx);
rendered_height += element.size().y();
rendered_items.push_back(ListItem::Rendered(element));
}
// Prepare to start walking upward from the item at the scroll top.
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
// If the rendered items do not fill the visible region, then adjust
// the scroll top upward.
if rendered_height - scroll_top.offset_in_item < size.y() {
while rendered_height < size.y() {
cursor.prev(&());
if let Some(item) = cursor.item() {
let element =
state.render_item(cursor.seek_start().0, item, item_constraint, cx);
rendered_height += element.size().y();
rendered_items.push_front(ListItem::Rendered(element));
} else {
break;
}
}
scroll_top = ListOffset {
item_ix: cursor.seek_start().0,
offset_in_item: rendered_height - size.y(),
};
match state.orientation {
Orientation::Top => {
scroll_top.offset_in_item = scroll_top.offset_in_item.max(0.);
state.logical_scroll_top = Some(scroll_top);
}
Orientation::Bottom => {
scroll_top = ListOffset {
item_ix: cursor.seek_start().0,
offset_in_item: rendered_height - size.y(),
};
state.logical_scroll_top = None;
}
};
}
// Render items in the leading overdraw.
let mut leading_overdraw = scroll_top.offset_in_item;
while leading_overdraw < state.overdraw {
cursor.prev(&());
if let Some(item) = cursor.item() {
let element = state.render_item(cursor.seek_start().0, item, item_constraint, cx);
leading_overdraw += element.size().y();
rendered_items.push_front(ListItem::Rendered(element));
} else {
break;
}
}
let new_rendered_range =
cursor.seek_start().0..(cursor.seek_start().0 + rendered_items.len());
let mut cursor = old_items.cursor::<Count, ()>();
if state.rendered_range.start < new_rendered_range.start {
new_items.push_tree(
cursor.slice(&Count(state.rendered_range.start), Bias::Right, &()),
&(),
);
let remove_to = state.rendered_range.end.min(new_rendered_range.start);
while cursor.seek_start().0 < remove_to {
new_items.push(cursor.item().unwrap().remove(), &());
cursor.next(&());
}
}
new_items.push_tree(
cursor.slice(&Count(new_rendered_range.start), Bias::Right, &()),
&(),
);
new_items.extend(rendered_items, &());
cursor.seek(&Count(new_rendered_range.end), Bias::Right, &());
if new_rendered_range.end < state.rendered_range.start {
new_items.push_tree(
cursor.slice(&Count(state.rendered_range.start), Bias::Right, &()),
&(),
);
}
while cursor.seek_start().0 < state.rendered_range.end {
new_items.push(cursor.item().unwrap().remove(), &());
cursor.next(&());
}
new_items.push_tree(cursor.suffix(&()), &());
state.items = new_items;
state.rendered_range = new_rendered_range;
state.last_layout_width = Some(size.x());
(size, scroll_top)
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
scroll_top: &mut ListOffset,
cx: &mut PaintContext,
) {
cx.scene.push_layer(Some(bounds));
let state = &mut *self.state.0.borrow_mut();
for (mut element, origin) in state.visible_elements(bounds, scroll_top) {
element.paint(origin, visible_bounds, cx);
}
cx.scene.pop_layer();
}
fn dispatch_event(
&mut self,
event: &Event,
bounds: RectF,
scroll_top: &mut ListOffset,
_: &mut (),
cx: &mut EventContext,
) -> bool {
let mut handled = false;
let mut state = self.state.0.borrow_mut();
for (mut element, _) in state.visible_elements(bounds, scroll_top) {
handled = element.dispatch_event(event, cx) || handled;
}
match event {
Event::ScrollWheel {
position,
delta,
precise,
} => {
if bounds.contains_point(*position) {
if state.scroll(scroll_top, bounds.height(), *delta, *precise, cx) {
handled = true;
}
}
}
_ => {}
}
handled
}
fn debug(
&self,
bounds: RectF,
scroll_top: &Self::LayoutState,
_: &(),
cx: &DebugContext,
) -> serde_json::Value {
let state = self.state.0.borrow_mut();
let visible_elements = state
.visible_elements(bounds, scroll_top)
.map(|e| e.0.debug(cx))
.collect::<Vec<_>>();
let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
json!({
"visible_range": visible_range,
"visible_elements": visible_elements,
"scroll_top": state.logical_scroll_top.map(|top| (top.item_ix, top.offset_in_item)),
})
}
}
impl ListState {
pub fn new<F>(
element_count: usize,
orientation: Orientation,
overdraw: f32,
render_item: F,
) -> Self
where
F: 'static + FnMut(usize, &mut LayoutContext) -> ElementBox,
{
let mut items = SumTree::new();
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
Self(Rc::new(RefCell::new(StateInner {
last_layout_width: None,
render_item: Box::new(render_item),
rendered_range: 0..0,
items,
logical_scroll_top: None,
orientation,
overdraw,
scroll_handler: None,
})))
}
pub fn reset(&self, element_count: usize) {
let state = &mut *self.0.borrow_mut();
state.rendered_range = 0..0;
state.logical_scroll_top = None;
state.items = SumTree::new();
state
.items
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
}
pub fn splice(&self, old_range: Range<usize>, count: usize) {
let state = &mut *self.0.borrow_mut();
if let Some(ListOffset {
item_ix,
offset_in_item,
}) = state.logical_scroll_top.as_mut()
{
if old_range.contains(item_ix) {
*item_ix = old_range.start;
*offset_in_item = 0.;
} else if old_range.end <= *item_ix {
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
}
}
let new_end = old_range.start + count;
if old_range.start < state.rendered_range.start {
state.rendered_range.start =
new_end + state.rendered_range.start.saturating_sub(old_range.end);
}
if old_range.start < state.rendered_range.end {
state.rendered_range.end =
new_end + state.rendered_range.end.saturating_sub(old_range.end);
}
let mut old_heights = state.items.cursor::<Count, ()>();
let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
new_heights.push_tree(old_heights.suffix(&()), &());
drop(old_heights);
state.items = new_heights;
}
pub fn set_scroll_handler(
&mut self,
handler: impl FnMut(Range<usize>, &mut EventContext) + 'static,
) {
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
}
}
impl StateInner {
fn render_item(
&mut self,
ix: usize,
existing_item: &ListItem,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> ElementRc {
if let ListItem::Rendered(element) = existing_item {
element.clone()
} else {
let mut element = (self.render_item)(ix, cx);
element.layout(constraint, cx);
element.into()
}
}
fn visible_range(&self, height: f32, scroll_top: &ListOffset) -> Range<usize> {
let mut cursor = self.items.cursor::<Count, Height>();
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
let start_y = cursor.sum_start().0 + scroll_top.offset_in_item;
let mut cursor = cursor.swap_dimensions();
cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
scroll_top.item_ix..cursor.sum_start().0 + 1
}
fn visible_elements<'a>(
&'a self,
bounds: RectF,
scroll_top: &ListOffset,
) -> impl Iterator<Item = (ElementRc, Vector2F)> + 'a {
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
let mut cursor = self.items.cursor::<Count, ()>();
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
std::iter::from_fn(move || {
while let Some(item) = cursor.item() {
if item_origin.y() > bounds.max_y() {
break;
}
if let ListItem::Rendered(element) = item {
let result = (element.clone(), item_origin);
item_origin.set_y(item_origin.y() + element.size().y());
cursor.next(&());
return Some(result);
}
cursor.next(&());
}
None
})
}
fn scroll(
&mut self,
scroll_top: &ListOffset,
height: f32,
mut delta: Vector2F,
precise: bool,
cx: &mut EventContext,
) -> bool {
if !precise {
delta *= 20.;
}
let scroll_max = (self.items.summary().height - height).max(0.);
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y())
.max(0.)
.min(scroll_max);
if self.orientation == Orientation::Bottom && new_scroll_top == scroll_max {
self.logical_scroll_top = None;
} else {
let mut cursor = self.items.cursor::<Height, Count>();
cursor.seek(&Height(new_scroll_top), Bias::Right, &());
let item_ix = cursor.sum_start().0;
let offset_in_item = new_scroll_top - cursor.seek_start().0;
self.logical_scroll_top = Some(ListOffset {
item_ix,
offset_in_item,
});
}
if self.scroll_handler.is_some() {
let visible_range = self.visible_range(height, scroll_top);
self.scroll_handler.as_mut().unwrap()(visible_range, cx);
}
cx.notify();
true
}
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> f32 {
let mut cursor = self.items.cursor::<Count, Height>();
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
cursor.sum_start().0 + logical_scroll_top.offset_in_item
}
}
impl ListItem {
fn remove(&self) -> Self {
match self {
ListItem::Unrendered => ListItem::Unrendered,
ListItem::Rendered(element) => ListItem::Removed(element.size().y()),
ListItem::Removed(height) => ListItem::Removed(*height),
}
}
}
impl sum_tree::Item for ListItem {
type Summary = ListItemSummary;
fn summary(&self) -> Self::Summary {
match self {
ListItem::Unrendered => ListItemSummary {
count: 1,
rendered_count: 0,
unrendered_count: 1,
height: 0.,
},
ListItem::Rendered(element) => ListItemSummary {
count: 1,
rendered_count: 1,
unrendered_count: 0,
height: element.size().y(),
},
ListItem::Removed(height) => ListItemSummary {
count: 1,
rendered_count: 0,
unrendered_count: 1,
height: *height,
},
}
}
}
impl sum_tree::Summary for ListItemSummary {
type Context = ();
fn add_summary(&mut self, summary: &Self, _: &()) {
self.count += summary.count;
self.rendered_count += summary.rendered_count;
self.unrendered_count += summary.unrendered_count;
self.height += summary.height;
}
}
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for ListItemSummary {
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
sum_tree::Summary::add_summary(self, summary, &());
}
}
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
self.0 += summary.count;
}
}
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
self.0 += summary.rendered_count;
}
}
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
self.0 += summary.unrendered_count;
}
}
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
self.0 += summary.height;
}
}
impl<'a> sum_tree::SeekDimension<'a, ListItemSummary> for Height {
fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering {
self.0.partial_cmp(&other.0).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::vector::vec2f;
use rand::prelude::*;
use std::env;
#[crate::test(self)]
fn test_layout(cx: &mut crate::MutableAppContext) {
let mut presenter = cx.build_presenter(0, 0.);
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
let elements = elements.clone();
move |ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed()
}
});
let mut list = List::new(state.clone());
let (size, _) = list.layout(constraint, &mut presenter.build_layout_context(false, cx));
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary(),
ListItemSummary {
count: 3,
rendered_count: 3,
unrendered_count: 0,
height: 150.
}
);
state.0.borrow_mut().scroll(
&ListOffset {
item_ix: 0,
offset_in_item: 0.,
},
40.,
vec2f(0., 54.),
true,
&mut presenter.build_event_context(cx),
);
let (_, logical_scroll_top) =
list.layout(constraint, &mut presenter.build_layout_context(false, cx));
assert_eq!(
logical_scroll_top,
ListOffset {
item_ix: 2,
offset_in_item: 4.
}
);
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
elements.borrow_mut().push((5, 60.));
state.splice(1..2, 2);
state.splice(4..4, 1);
assert_eq!(
state.0.borrow().items.summary(),
ListItemSummary {
count: 5,
rendered_count: 2,
unrendered_count: 3,
height: 120.
}
);
let (size, logical_scroll_top) =
list.layout(constraint, &mut presenter.build_layout_context(false, cx));
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary(),
ListItemSummary {
count: 5,
rendered_count: 5,
unrendered_count: 0,
height: 270.
}
);
assert_eq!(
logical_scroll_top,
ListOffset {
item_ix: 3,
offset_in_item: 4.
}
);
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
}
#[crate::test(self, iterations = 10000, seed = 0)]
fn test_random(cx: &mut crate::MutableAppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
let mut presenter = cx.build_presenter(0, 0.);
let mut next_id = 0;
let elements = Rc::new(RefCell::new(
(0..rng.gen_range(0..=20))
.map(|_| {
let id = next_id;
next_id += 1;
(id, rng.gen_range(0..=200) as f32 / 2.0)
})
.collect::<Vec<_>>(),
));
let orientation = *[Orientation::Top, Orientation::Bottom]
.choose(&mut rng)
.unwrap();
let overdraw = rng.gen_range(1..=100) as f32;
let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
let elements = elements.clone();
move |ix, _| {
let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed()
}
});
let mut width = rng.gen_range(0..=2000) as f32 / 2.;
let mut height = rng.gen_range(0..=2000) as f32 / 2.;
log::info!("orientation: {:?}", orientation);
log::info!("overdraw: {}", overdraw);
log::info!("elements: {:?}", elements.borrow());
log::info!("size: ({:?}, {:?})", width, height);
log::info!("==================");
let mut last_logical_scroll_top = None;
for _ in 0..operations {
match rng.gen_range(0..=100) {
0..=29 if last_logical_scroll_top.is_some() => {
let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
log::info!(
"Scrolling by {:?}, previous scroll top: {:?}",
delta,
last_logical_scroll_top.unwrap()
);
state.0.borrow_mut().scroll(
last_logical_scroll_top.as_ref().unwrap(),
height,
delta,
true,
&mut presenter.build_event_context(cx),
);
}
30..=34 => {
width = rng.gen_range(0..=2000) as f32 / 2.;
log::info!("changing width: {:?}", width);
}
35..=54 => {
height = rng.gen_range(0..=1000) as f32 / 2.;
log::info!("changing height: {:?}", height);
}
_ => {
let mut elements = elements.borrow_mut();
let end_ix = rng.gen_range(0..=elements.len());
let start_ix = rng.gen_range(0..=end_ix);
let new_elements = (0..rng.gen_range(0..10))
.map(|_| {
let id = next_id;
next_id += 1;
(id, rng.gen_range(0..=200) as f32 / 2.)
})
.collect::<Vec<_>>();
log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
state.splice(start_ix..end_ix, new_elements.len());
elements.splice(start_ix..end_ix, new_elements);
for (ix, item) in state.0.borrow().items.cursor::<(), ()>().enumerate() {
if let ListItem::Rendered(element) = item {
let (expected_id, _) = elements[ix];
element.with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id);
});
}
}
}
}
let mut list = List::new(state.clone());
let (size, logical_scroll_top) = list.layout(
SizeConstraint::new(vec2f(0., 0.), vec2f(width, height)),
&mut presenter.build_layout_context(false, cx),
);
assert_eq!(size, vec2f(width, height));
last_logical_scroll_top = Some(logical_scroll_top);
let state = state.0.borrow();
log::info!("items {:?}", state.items.items(&()));
let scroll_top = state.scroll_top(&logical_scroll_top);
let rendered_top = (scroll_top - overdraw).max(0.);
let rendered_bottom = scroll_top + height + overdraw;
let mut item_top = 0.;
log::info!(
"rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
rendered_top,
rendered_bottom,
scroll_top,
);
let mut first_rendered_element_top = None;
let mut last_rendered_element_bottom = None;
assert_eq!(state.items.summary().count, elements.borrow().len());
for (ix, item) in state.items.cursor::<(), ()>().enumerate() {
match item {
ListItem::Unrendered => {
let item_bottom = item_top;
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
item_top = item_bottom;
}
ListItem::Removed(height) => {
let (id, expected_height) = elements.borrow()[ix];
assert_eq!(
*height, expected_height,
"element {} height didn't match",
id
);
let item_bottom = item_top + height;
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
item_top = item_bottom;
}
ListItem::Rendered(element) => {
let (expected_id, expected_height) = elements.borrow()[ix];
element.with_metadata(|metadata: Option<&usize>| {
assert_eq!(*metadata.unwrap(), expected_id);
});
assert_eq!(element.size().y(), expected_height);
let item_bottom = item_top + element.size().y();
first_rendered_element_top.get_or_insert(item_top);
last_rendered_element_bottom = Some(item_bottom);
assert!(item_bottom > rendered_top || item_top < rendered_bottom);
item_top = item_bottom;
}
}
}
match orientation {
Orientation::Top => {
if let Some(first_rendered_element_top) = first_rendered_element_top {
assert!(first_rendered_element_top <= scroll_top);
}
}
Orientation::Bottom => {
if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
assert!(last_rendered_element_bottom >= scroll_top + height);
}
}
}
}
}
struct TestElement {
id: usize,
size: Vector2F,
}
impl TestElement {
fn new(id: usize, height: f32) -> Self {
Self {
id,
size: vec2f(100., height),
}
}
}
impl Element for TestElement {
type LayoutState = ();
type PaintState = ();
fn layout(&mut self, _: SizeConstraint, _: &mut LayoutContext) -> (Vector2F, ()) {
(self.size, ())
}
fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut PaintContext) {
todo!()
}
fn dispatch_event(
&mut self,
_: &Event,
_: RectF,
_: &mut (),
_: &mut (),
_: &mut EventContext,
) -> bool {
todo!()
}
fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
self.id.into()
}
fn metadata(&self) -> Option<&dyn std::any::Any> {
Some(&self.id)
}
}
}
+85 -27
View File
@@ -1,42 +1,69 @@
use std::ops::DerefMut;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
AfterLayoutContext, AppContext, DebugContext, Element, ElementBox, Event, EventContext,
LayoutContext, PaintContext, SizeConstraint, ValueHandle,
platform::CursorStyle,
CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateHandle, ElementStateId,
Event, EventContext, LayoutContext, MutableAppContext, PaintContext, SizeConstraint,
};
use serde_json::json;
pub struct MouseEventHandler {
state: ValueHandle<MouseState>,
state: ElementStateHandle<MouseState>,
child: ElementBox,
cursor_style: Option<CursorStyle>,
mouse_down_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
click_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
}
#[derive(Clone, Copy, Debug, Default)]
#[derive(Default)]
pub struct MouseState {
pub hovered: bool,
pub clicked: bool,
prev_drag_position: Option<Vector2F>,
cursor_style_handle: Option<CursorStyleHandle>,
}
impl MouseEventHandler {
pub fn new<Tag, F>(id: usize, cx: &AppContext, render_child: F) -> Self
pub fn new<Tag, F, C, Id>(id: Id, cx: &mut C, render_child: F) -> Self
where
Tag: 'static,
F: FnOnce(MouseState) -> ElementBox,
F: FnOnce(&MouseState, &mut C) -> ElementBox,
C: DerefMut<Target = MutableAppContext>,
Id: Into<ElementStateId>,
{
let state_handle = cx.value::<Tag, _>(id);
let state = state_handle.read(cx.as_ref(), |state| *state);
let child = render_child(state);
let state_handle = cx.element_state::<Tag, _>(id.into());
let child = state_handle.update(cx, |state, cx| render_child(state, cx));
Self {
state: state_handle,
child,
cursor_style: None,
mouse_down_handler: None,
click_handler: None,
drag_handler: None,
}
}
pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
self.cursor_style = Some(cursor);
self
}
pub fn on_mouse_down(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
self.mouse_down_handler = Some(Box::new(handler));
self
}
pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
self.click_handler = Some(Box::new(handler));
self
}
pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self {
self.drag_handler = Some(Box::new(handler));
self
}
}
impl Element for MouseEventHandler {
@@ -51,22 +78,14 @@ impl Element for MouseEventHandler {
(self.child.layout(constraint, cx), ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
self.child.after_layout(cx);
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
self.child.paint(bounds.origin(), cx);
self.child.paint(bounds.origin(), visible_bounds, cx);
}
fn dispatch_event(
@@ -77,33 +96,58 @@ impl Element for MouseEventHandler {
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
let cursor_style = self.cursor_style;
let mouse_down_handler = self.mouse_down_handler.as_mut();
let click_handler = self.click_handler.as_mut();
let drag_handler = self.drag_handler.as_mut();
let handled_in_child = self.child.dispatch_event(event, cx);
self.state.update(cx, |state, cx| match event {
Event::MouseMoved { position } => {
let mouse_in = bounds.contains_point(*position);
if state.hovered != mouse_in {
state.hovered = mouse_in;
cx.notify();
true
} else {
handled_in_child
Event::MouseMoved {
position,
left_mouse_down,
} => {
if !left_mouse_down {
let mouse_in = bounds.contains_point(*position);
if state.hovered != mouse_in {
state.hovered = mouse_in;
if let Some(cursor_style) = cursor_style {
if !state.clicked {
if state.hovered {
state.cursor_style_handle =
Some(cx.set_cursor_style(cursor_style));
} else {
state.cursor_style_handle = None;
}
}
}
cx.notify();
return true;
}
}
handled_in_child
}
Event::LeftMouseDown { position, .. } => {
if !handled_in_child && bounds.contains_point(*position) {
state.clicked = true;
state.prev_drag_position = Some(*position);
cx.notify();
if let Some(handler) = mouse_down_handler {
handler(cx);
}
true
} else {
handled_in_child
}
}
Event::LeftMouseUp { position, .. } => {
state.prev_drag_position = None;
if !handled_in_child && state.clicked {
state.clicked = false;
if !state.hovered {
state.cursor_style_handle = None;
}
cx.notify();
if let Some(handler) = click_handler {
if bounds.contains_point(*position) {
@@ -115,6 +159,20 @@ impl Element for MouseEventHandler {
handled_in_child
}
}
Event::LeftMouseDragged { position, .. } => {
if !handled_in_child && state.clicked {
let prev_drag_position = state.prev_drag_position.replace(*position);
if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
let delta = *position - prev_position;
if !delta.is_zero() {
(handler)(delta, cx);
}
}
true
} else {
handled_in_child
}
}
_ => handled_in_child,
})
}
+63
View File
@@ -0,0 +1,63 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
pub struct Overlay {
child: ElementBox,
}
impl Overlay {
pub fn new(child: ElementBox) -> Self {
Self { child }
}
}
impl Element for Overlay {
type LayoutState = Vector2F;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, cx);
(Vector2F::zero(), size)
}
fn paint(
&mut self,
bounds: RectF,
_: RectF,
size: &mut Self::LayoutState,
cx: &mut PaintContext,
) {
let bounds = RectF::new(bounds.origin(), *size);
cx.scene.push_stacking_context(None);
self.child.paint(bounds.origin(), bounds, cx);
cx.scene.pop_stacking_context();
}
fn dispatch_event(
&mut self,
event: &Event,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
self.child.dispatch_event(event, cx)
}
fn debug(
&self,
_: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &DebugContext,
) -> serde_json::Value {
self.child.debug(cx)
}
}
+4 -14
View File
@@ -1,8 +1,8 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext,
PaintContext, SizeConstraint,
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
pub struct Stack {
@@ -33,26 +33,16 @@ impl Element for Stack {
(size, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
for child in &mut self.children {
child.after_layout(cx);
}
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
for child in &mut self.children {
cx.scene.push_layer(None);
child.paint(bounds.origin(), cx);
child.paint(bounds.origin(), visible_bounds, cx);
cx.scene.pop_layer();
}
}
+8 -6
View File
@@ -8,8 +8,7 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
scene, AfterLayoutContext, DebugContext, Element, Event, EventContext, LayoutContext,
PaintContext, SizeConstraint,
scene, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
pub struct Svg {
@@ -66,10 +65,13 @@ impl Element for Svg {
}
}
fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
}
fn paint(&mut self, bounds: RectF, svg: &mut Self::LayoutState, cx: &mut PaintContext) {
fn paint(
&mut self,
bounds: RectF,
_visible_bounds: RectF,
svg: &mut Self::LayoutState,
cx: &mut PaintContext,
) {
if let Some(svg) = svg.clone() {
cx.scene.push_icon(scene::Icon {
bounds,
+131
View File
@@ -0,0 +1,131 @@
use crate::{
color::Color,
fonts::TextStyle,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{ToJson, Value},
text_layout::{Line, ShapedBoundary},
DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use serde_json::json;
pub struct Text {
text: String,
style: TextStyle,
}
pub struct LayoutState {
lines: Vec<(Line, Vec<ShapedBoundary>)>,
line_height: f32,
}
impl Text {
pub fn new(text: String, style: TextStyle) -> Self {
Self { text, style }
}
pub fn with_default_color(mut self, color: Color) -> Self {
self.style.color = color;
self
}
}
impl Element for Text {
type LayoutState = LayoutState;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let font_id = self.style.font_id;
let line_height = cx.font_cache.line_height(font_id, self.style.font_size);
let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size);
let mut lines = Vec::new();
let mut line_count = 0;
let mut max_line_width = 0_f32;
for line in self.text.lines() {
let shaped_line = cx.text_layout_cache.layout_str(
line,
self.style.font_size,
&[(line.len(), self.style.to_run())],
);
let wrap_boundaries = wrapper
.wrap_shaped_line(line, &shaped_line, constraint.max.x())
.collect::<Vec<_>>();
max_line_width = max_line_width.max(shaped_line.width());
line_count += wrap_boundaries.len() + 1;
lines.push((shaped_line, wrap_boundaries));
}
let size = vec2f(
max_line_width
.ceil()
.max(constraint.min.x())
.min(constraint.max.x()),
(line_height * line_count as f32).ceil(),
);
(size, LayoutState { lines, line_height })
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
let mut origin = bounds.origin();
for (line, wrap_boundaries) in &layout.lines {
let wrapped_line_boundaries = RectF::new(
origin,
vec2f(
bounds.width(),
(wrap_boundaries.len() + 1) as f32 * layout.line_height,
),
);
if wrapped_line_boundaries.intersects(visible_bounds) {
line.paint_wrapped(
origin,
visible_bounds,
layout.line_height,
wrap_boundaries.iter().copied(),
cx,
);
}
origin.set_y(wrapped_line_boundaries.max_y());
}
}
fn dispatch_event(
&mut self,
_: &Event,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
_: &mut EventContext,
) -> bool {
false
}
fn debug(
&self,
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
_: &DebugContext,
) -> Value {
json!({
"type": "Text",
"bounds": bounds.to_json(),
"text": &self.text,
"style": self.style.to_json(),
})
}
}
+11 -21
View File
@@ -1,13 +1,11 @@
use super::{
AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use super::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint};
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{self, json},
AppContext, ElementBox,
ElementBox, MutableAppContext,
};
use json::ToJson;
use parking_lot::Mutex;
@@ -40,7 +38,7 @@ pub struct LayoutState {
pub struct UniformList<F>
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
{
state: UniformListState,
item_count: usize,
@@ -49,7 +47,7 @@ where
impl<F> UniformList<F>
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
{
pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self {
Self {
@@ -62,13 +60,13 @@ where
fn scroll(
&self,
_: Vector2F,
delta: Vector2F,
mut delta: Vector2F,
precise: bool,
scroll_max: f32,
cx: &mut EventContext,
) -> bool {
if !precise {
todo!("still need to handle non-precise scroll events from a mouse wheel");
delta *= 20.;
}
let mut state = self.state.0.lock();
@@ -104,7 +102,7 @@ where
impl<F> Element for UniformList<F>
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
{
type LayoutState = LayoutState;
type PaintState = ();
@@ -152,6 +150,8 @@ where
for item in &mut items {
item.layout(item_constraint, cx);
}
} else {
size = constraint.min;
}
(
@@ -164,20 +164,10 @@ where
)
}
fn after_layout(
&mut self,
_: Vector2F,
layout: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
for item in &mut layout.items {
item.after_layout(cx);
}
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
@@ -187,7 +177,7 @@ where
bounds.origin() - vec2f(0.0, self.state.scroll_top() % layout.item_height);
for item in &mut layout.items {
item.paint(item_origin, cx);
item.paint(item_origin, visible_bounds, cx);
item_origin += vec2f(0.0, layout.item_height);
}
+10 -4
View File
@@ -122,9 +122,14 @@ impl Deterministic {
smol::pin!(future);
let unparker = self.parker.lock().unparker();
let waker = waker_fn(move || {
unparker.unpark();
});
let woken = Arc::new(AtomicBool::new(false));
let waker = {
let woken = woken.clone();
waker_fn(move || {
woken.store(true, SeqCst);
unparker.unpark();
})
};
let mut cx = Context::from_waker(&waker);
let mut trace = Trace::default();
@@ -166,10 +171,11 @@ impl Deterministic {
&& state.scheduled_from_background.is_empty()
&& state.spawned_from_foreground.is_empty()
{
if state.forbid_parking {
if state.forbid_parking && !woken.load(SeqCst) {
panic!("deterministic executor parked after a call to forbid_parking");
}
drop(state);
woken.store(false, SeqCst);
self.parker.lock().park();
}
+65 -7
View File
@@ -2,10 +2,16 @@ use crate::{
fonts::{FontId, Metrics, Properties},
geometry::vector::{vec2f, Vector2F},
platform,
text_layout::LineWrapper,
};
use anyhow::{anyhow, Result};
use ordered_float::OrderedFloat;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use std::{collections::HashMap, sync::Arc};
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::Arc,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FamilyId(usize);
@@ -22,6 +28,12 @@ pub struct FontCacheState {
families: Vec<Family>,
font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
metrics: HashMap<FontId, Metrics>,
wrapper_pool: HashMap<(FontId, OrderedFloat<f32>), Vec<LineWrapper>>,
}
pub struct LineWrapperHandle {
wrapper: Option<LineWrapper>,
font_cache: Arc<FontCache>,
}
unsafe impl Send for FontCache {}
@@ -30,9 +42,10 @@ impl FontCache {
pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
Self(RwLock::new(FontCacheState {
fonts,
families: Vec::new(),
font_selections: HashMap::new(),
metrics: HashMap::new(),
families: Default::default(),
font_selections: Default::default(),
metrics: Default::default(),
wrapper_pool: Default::default(),
}))
}
@@ -134,9 +147,13 @@ impl FontCache {
}
pub fn em_width(&self, font_id: FontId, font_size: f32) -> f32 {
let state = self.0.read();
let glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
let bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap();
let glyph_id;
let bounds;
{
let state = self.0.read();
glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap();
}
self.scale_metric(bounds.width(), font_id, font_size)
}
@@ -160,6 +177,47 @@ impl FontCache {
pub fn scale_metric(&self, metric: f32, font_id: FontId, font_size: f32) -> f32 {
metric * font_size / self.metric(font_id, |m| m.units_per_em as f32)
}
pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: f32) -> LineWrapperHandle {
let mut state = self.0.write();
let wrappers = state
.wrapper_pool
.entry((font_id, OrderedFloat(font_size)))
.or_default();
let wrapper = wrappers
.pop()
.unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.fonts.clone()));
LineWrapperHandle {
wrapper: Some(wrapper),
font_cache: self.clone(),
}
}
}
impl Drop for LineWrapperHandle {
fn drop(&mut self) {
let mut state = self.font_cache.0.write();
let wrapper = self.wrapper.take().unwrap();
state
.wrapper_pool
.get_mut(&(wrapper.font_id, OrderedFloat(wrapper.font_size)))
.unwrap()
.push(wrapper);
}
}
impl Deref for LineWrapperHandle {
type Target = LineWrapper;
fn deref(&self) -> &Self::Target {
self.wrapper.as_ref().unwrap()
}
}
impl DerefMut for LineWrapperHandle {
fn deref_mut(&mut self) -> &mut Self::Target {
self.wrapper.as_mut().unwrap()
}
}
#[cfg(test)]
+152 -41
View File
@@ -1,23 +1,38 @@
use crate::{
color::Color,
json::{json, ToJson},
text_layout::RunStyle,
FontCache,
};
use anyhow::anyhow;
pub use font_kit::{
metrics::Metrics,
properties::{Properties, Stretch, Style, Weight},
};
use serde::{de, Deserialize};
use serde_json::Value;
use std::{cell::RefCell, sync::Arc};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FontId(pub usize);
pub type GlyphId = u32;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[derive(Clone, Debug)]
pub struct TextStyle {
pub color: Color,
pub font_family_name: Arc<str>,
pub font_id: FontId,
pub font_size: f32,
pub font_properties: Properties,
pub underline: bool,
}
#[derive(Clone, Debug, Default)]
pub struct HighlightStyle {
pub color: Color,
pub font_properties: Properties,
pub underline: bool,
}
#[allow(non_camel_case_types)]
@@ -34,12 +49,102 @@ enum WeightJson {
black,
}
thread_local! {
static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
}
#[derive(Deserialize)]
struct TextStyleJson {
color: Color,
family: String,
weight: Option<WeightJson>,
size: f32,
#[serde(default)]
italic: bool,
#[serde(default)]
underline: bool,
}
#[derive(Deserialize)]
struct HighlightStyleJson {
color: Color,
weight: Option<WeightJson>,
#[serde(default)]
italic: bool,
#[serde(default)]
underline: bool,
}
impl TextStyle {
pub fn new(
font_family_name: impl Into<Arc<str>>,
font_size: f32,
font_properties: Properties,
underline: bool,
color: Color,
font_cache: &FontCache,
) -> anyhow::Result<Self> {
let font_family_name = font_family_name.into();
let family_id = font_cache.load_family(&[&font_family_name])?;
let font_id = font_cache.select_font(family_id, &font_properties)?;
Ok(Self {
color,
font_family_name,
font_id,
font_size,
font_properties,
underline,
})
}
pub fn to_run(&self) -> RunStyle {
RunStyle {
font_id: self.font_id,
color: self.color,
underline: self.underline,
}
}
fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
FONT_CACHE.with(|font_cache| {
if let Some(font_cache) = font_cache.borrow().as_ref() {
let font_properties = properties_from_json(json.weight, json.italic);
Self::new(
json.family,
json.size,
font_properties,
json.underline,
json.color,
font_cache,
)
} else {
Err(anyhow!(
"TextStyle can only be deserialized within a call to with_font_cache"
))
}
})
}
}
impl HighlightStyle {
fn from_json(json: HighlightStyleJson) -> Self {
let font_properties = properties_from_json(json.weight, json.italic);
Self {
color: json.color,
font_properties,
underline: json.underline,
}
}
}
impl From<Color> for HighlightStyle {
fn from(color: Color) -> Self {
Self {
color,
font_properties: Default::default(),
underline: false,
}
}
}
impl<'de> Deserialize<'de> for TextStyle {
@@ -47,26 +152,8 @@ impl<'de> Deserialize<'de> for TextStyle {
where
D: serde::Deserializer<'de>,
{
let json = Value::deserialize(deserializer)?;
if json.is_object() {
let style_json: TextStyleJson =
serde_json::from_value(json).map_err(de::Error::custom)?;
Ok(style_json.into())
} else {
Ok(Self {
color: serde_json::from_value(json).map_err(de::Error::custom)?,
font_properties: Properties::new(),
})
}
}
}
impl From<Color> for TextStyle {
fn from(color: Color) -> Self {
Self {
color,
font_properties: Default::default(),
}
Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
.map_err(|e| de::Error::custom(e))?)
}
}
@@ -74,36 +161,48 @@ impl ToJson for TextStyle {
fn to_json(&self) -> Value {
json!({
"color": self.color.to_json(),
"font_family": self.font_family_name.as_ref(),
"font_properties": self.font_properties.to_json(),
})
}
}
impl Into<TextStyle> for TextStyleJson {
fn into(self) -> TextStyle {
let weight = match self.weight.unwrap_or(WeightJson::normal) {
WeightJson::thin => Weight::THIN,
WeightJson::extra_light => Weight::EXTRA_LIGHT,
WeightJson::light => Weight::LIGHT,
WeightJson::normal => Weight::NORMAL,
WeightJson::medium => Weight::MEDIUM,
WeightJson::semibold => Weight::SEMIBOLD,
WeightJson::bold => Weight::BOLD,
WeightJson::extra_bold => Weight::EXTRA_BOLD,
WeightJson::black => Weight::BLACK,
};
let style = if self.italic {
Style::Italic
impl<'de> Deserialize<'de> for HighlightStyle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let json = serde_json::Value::deserialize(deserializer)?;
if json.is_object() {
Ok(Self::from_json(
serde_json::from_value(json).map_err(de::Error::custom)?,
))
} else {
Style::Normal
};
TextStyle {
color: self.color,
font_properties: *Properties::new().weight(weight).style(style),
Ok(Self {
color: serde_json::from_value(json).map_err(de::Error::custom)?,
font_properties: Properties::new(),
underline: false,
})
}
}
}
fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
let weight = match weight.unwrap_or(WeightJson::normal) {
WeightJson::thin => Weight::THIN,
WeightJson::extra_light => Weight::EXTRA_LIGHT,
WeightJson::light => Weight::LIGHT,
WeightJson::normal => Weight::NORMAL,
WeightJson::medium => Weight::MEDIUM,
WeightJson::semibold => Weight::SEMIBOLD,
WeightJson::bold => Weight::BOLD,
WeightJson::extra_bold => Weight::EXTRA_BOLD,
WeightJson::black => Weight::BLACK,
};
let style = if italic { Style::Italic } else { Style::Normal };
*Properties::new().weight(weight).style(style)
}
impl ToJson for Properties {
fn to_json(&self) -> crate::json::Value {
json!({
@@ -155,3 +254,15 @@ impl ToJson for Stretch {
json!(self.0)
}
}
pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
where
F: FnOnce() -> T,
{
FONT_CACHE.with(|cache| {
*cache.borrow_mut() = Some(font_cache);
let result = callback();
cache.borrow_mut().take();
result
})
}
+56 -58
View File
@@ -2,9 +2,12 @@ use anyhow::anyhow;
use std::{
any::Any,
collections::{HashMap, HashSet},
fmt::Debug,
};
use tree_sitter::{Language, Node, Parser};
use crate::{Action, AnyAction};
extern "C" {
fn tree_sitter_context_predicate() -> Language;
}
@@ -24,8 +27,7 @@ pub struct Keymap(Vec<Binding>);
pub struct Binding {
keystrokes: Vec<Keystroke>,
action: String,
action_arg: Option<Box<dyn ActionArg>>,
action: Box<dyn AnyAction>,
context: Option<ContextPredicate>,
}
@@ -70,10 +72,7 @@ where
pub enum MatchResult {
None,
Pending,
Action {
name: String,
arg: Option<Box<dyn Any>>,
},
Action(Box<dyn AnyAction>),
}
impl Matcher {
@@ -117,10 +116,7 @@ impl Matcher {
{
if binding.keystrokes.len() == pending.keystrokes.len() {
self.pending.remove(&view_id);
return MatchResult::Action {
name: binding.action.clone(),
arg: binding.action_arg.as_ref().map(|arg| (*arg).boxed_clone()),
};
return MatchResult::Action(binding.action.boxed_clone());
} else {
retain_pending = true;
pending.context = Some(cx.clone());
@@ -153,19 +149,26 @@ impl Keymap {
}
}
pub mod menu {
use crate::action;
action!(SelectPrev);
action!(SelectNext);
}
impl Default for Keymap {
fn default() -> Self {
Self(vec![
Binding::new("up", "menu:select_prev", Some("menu")),
Binding::new("ctrl-p", "menu:select_prev", Some("menu")),
Binding::new("down", "menu:select_next", Some("menu")),
Binding::new("ctrl-n", "menu:select_next", Some("menu")),
Binding::new("up", menu::SelectPrev, Some("menu")),
Binding::new("ctrl-p", menu::SelectPrev, Some("menu")),
Binding::new("down", menu::SelectNext, Some("menu")),
Binding::new("ctrl-n", menu::SelectNext, Some("menu")),
])
}
}
impl Binding {
pub fn new<S: Into<String>>(keystrokes: &str, action: S, context: Option<&str>) -> Self {
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
let context = if let Some(context) = context {
Some(ContextPredicate::parse(context).unwrap())
} else {
@@ -177,16 +180,10 @@ impl Binding {
.split_whitespace()
.map(|key| Keystroke::parse(key).unwrap())
.collect(),
action: action.into(),
action_arg: None,
action: Box::new(action),
context,
}
}
pub fn with_arg<T: 'static + Any + Clone>(mut self, arg: T) -> Self {
self.action_arg = Some(Box::new(arg));
self
}
}
impl Keystroke {
@@ -328,6 +325,8 @@ impl ContextPredicate {
#[cfg(test)]
mod tests {
use crate::action;
use super::*;
#[test]
@@ -417,15 +416,31 @@ mod tests {
#[test]
fn test_matcher() -> anyhow::Result<()> {
action!(A, &'static str);
action!(B);
action!(Ab);
impl PartialEq for A {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for A {}
impl Debug for A {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "A({:?})", &self.0)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ActionArg {
a: &'static str,
}
let keymap = Keymap(vec![
Binding::new("a", "a", Some("a")).with_arg(ActionArg { a: "b" }),
Binding::new("b", "b", Some("a")),
Binding::new("a b", "a_b", Some("a || b")),
Binding::new("a", A("x"), Some("a")),
Binding::new("b", B, Some("a")),
Binding::new("a b", Ab, Some("a || b")),
]);
let mut ctx_a = Context::default();
@@ -437,57 +452,40 @@ mod tests {
let mut matcher = Matcher::new(keymap);
// Basic match
assert_eq!(
matcher.test_keystroke("a", 1, &ctx_a),
Some(("a".to_string(), Some(ActionArg { a: "b" })))
);
assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
// Multi-keystroke match
assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
assert_eq!(
matcher.test_keystroke::<()>("b", 1, &ctx_b),
Some(("a_b".to_string(), None))
);
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
// Failed matches don't interfere with matching subsequent keys
assert_eq!(matcher.test_keystroke::<()>("x", 1, &ctx_a), None);
assert_eq!(
matcher.test_keystroke("a", 1, &ctx_a),
Some(("a".to_string(), Some(ActionArg { a: "b" })))
);
assert_eq!(matcher.test_keystroke::<A>("x", 1, &ctx_a), None);
assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
// Pending keystrokes are cleared when the context changes
assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
assert_eq!(
matcher.test_keystroke::<()>("b", 1, &ctx_a),
Some(("b".to_string(), None))
);
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke("b", 1, &ctx_a), Some(B));
let mut ctx_c = Context::default();
ctx_c.set.insert("c".into());
// Pending keystrokes are maintained per-view
assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke::<()>("a", 2, &ctx_c), None);
assert_eq!(
matcher.test_keystroke::<()>("b", 1, &ctx_b),
Some(("a_b".to_string(), None))
);
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke::<A>("a", 2, &ctx_c), None);
assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
Ok(())
}
impl Matcher {
fn test_keystroke<A: Any + Clone>(
&mut self,
keystroke: &str,
view_id: usize,
cx: &Context,
) -> Option<(String, Option<A>)> {
if let MatchResult::Action { name, arg } =
fn test_keystroke<A>(&mut self, keystroke: &str, view_id: usize, cx: &Context) -> Option<A>
where
A: Action + Debug + Eq,
{
if let MatchResult::Action(action) =
self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx)
{
Some((name, arg.and_then(|arg| arg.downcast_ref::<A>().cloned())))
Some(*action.boxed_clone_as_any().downcast().unwrap())
} else {
None
}
+4 -3
View File
@@ -1,11 +1,13 @@
mod app;
pub use app::*;
mod assets;
pub mod sum_tree;
#[cfg(test)]
mod test;
pub use assets::*;
pub mod elements;
pub mod font_cache;
pub mod views;
pub use font_cache::FontCache;
mod clipboard;
pub use clipboard::ClipboardItem;
@@ -17,7 +19,7 @@ pub use scene::{Border, Quad, Scene};
pub mod text_layout;
pub use text_layout::TextLayoutCache;
mod util;
pub use elements::{Element, ElementBox};
pub use elements::{Element, ElementBox, ElementRc};
pub mod executor;
pub use executor::Task;
pub mod color;
@@ -28,6 +30,5 @@ pub use gpui_macros::test;
pub use platform::FontSystem;
pub use platform::{Event, PathPromptOptions, Platform, PromptLevel};
pub use presenter::{
AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
SizeConstraint, Vector2FExt,
Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
};
+36 -14
View File
@@ -8,16 +8,16 @@ pub mod current {
}
use crate::{
color::Color,
executor,
fonts::{FontId, GlyphId, Metrics as FontMetrics, Properties as FontProperties},
geometry::{
rect::{RectF, RectI},
vector::Vector2F,
vector::{vec2f, Vector2F},
},
text_layout::LineLayout,
ClipboardItem, Menu, Scene,
text_layout::{LineLayout, RunStyle},
AnyAction, ClipboardItem, Menu, Scene,
};
use anyhow::Result;
use async_task::Runnable;
pub use event::Event;
use std::{
@@ -26,6 +26,7 @@ use std::{
rc::Rc,
sync::Arc,
};
use time::UtcOffset;
pub trait Platform: Send + Sync {
fn dispatcher(&self) -> Arc<dyn Dispatcher>;
@@ -45,8 +46,12 @@ pub trait Platform: Send + Sync {
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn open_url(&self, url: &str);
fn write_credentials(&self, url: &str, username: &str, password: &[u8]);
fn read_credentials(&self, url: &str) -> Option<(String, Vec<u8>)>;
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
fn set_cursor_style(&self, style: CursorStyle);
fn local_timezone(&self) -> UtcOffset;
}
pub(crate) trait ForegroundPlatform {
@@ -56,7 +61,7 @@ pub(crate) trait ForegroundPlatform {
fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>);
fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>);
fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn AnyAction)>);
fn set_menus(&self, menus: Vec<Menu>);
fn prompt_for_paths(
&self,
@@ -78,7 +83,7 @@ pub trait Dispatcher: Send + Sync {
pub trait Window: WindowContext {
fn as_any_mut(&mut self) -> &mut dyn Any;
fn on_event(&mut self, callback: Box<dyn FnMut(Event)>);
fn on_resize(&mut self, callback: Box<dyn FnMut(&mut dyn WindowContext)>);
fn on_resize(&mut self, callback: Box<dyn FnMut()>);
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
fn prompt(
&self,
@@ -92,12 +97,15 @@ pub trait Window: WindowContext {
pub trait WindowContext {
fn size(&self) -> Vector2F;
fn scale_factor(&self) -> f32;
fn titlebar_height(&self) -> f32;
fn present_scene(&mut self, scene: Scene);
}
pub struct WindowOptions<'a> {
pub bounds: RectF,
pub title: Option<&'a str>,
pub titlebar_appears_transparent: bool,
pub traffic_light_position: Option<Vector2F>,
}
pub struct PathPromptOptions {
@@ -112,7 +120,15 @@ pub enum PromptLevel {
Critical,
}
#[derive(Copy, Clone, Debug)]
pub enum CursorStyle {
Arrow,
ResizeLeftRight,
PointingHand,
}
pub trait FontSystem: Send + Sync {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>>;
fn select_font(
&self,
@@ -130,11 +146,17 @@ pub trait FontSystem: Send + Sync {
subpixel_shift: Vector2F,
scale_factor: f32,
) -> Option<(RectI, Vec<u8>)>;
fn layout_line(
&self,
text: &str,
font_size: f32,
runs: &[(usize, FontId, Color)],
) -> LineLayout;
fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout;
fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize>;
}
impl<'a> Default for WindowOptions<'a> {
fn default() -> Self {
Self {
bounds: RectF::new(Default::default(), vec2f(1024.0, 768.0)),
title: Default::default(),
titlebar_appears_transparent: Default::default(),
traffic_light_position: Default::default(),
}
}
}
+1
View File
@@ -24,5 +24,6 @@ pub enum Event {
},
MouseMoved {
position: Vector2F,
left_mouse_down: bool,
},
}
+3 -2
View File
@@ -6,8 +6,8 @@ use cocoa::appkit::{
NSUpArrowFunctionKey as ARROW_UP_KEY,
};
use cocoa::{
appkit::{NSEvent as _, NSEventModifierFlags, NSEventType},
base::{id, YES},
appkit::{NSEvent, NSEventModifierFlags, NSEventType},
base::{id, nil, YES},
foundation::NSString as _,
};
use std::{ffi::CStr, os::raw::c_char};
@@ -116,6 +116,7 @@ impl Event {
native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32,
),
left_mouse_down: NSEvent::pressedMouseButtons(nil) & 1 != 0,
}),
_ => None,
}
+79 -48
View File
@@ -1,5 +1,4 @@
use crate::{
color::Color,
fonts::{FontId, GlyphId, Metrics, Properties},
geometry::{
rect::{RectF, RectI},
@@ -7,7 +6,7 @@ use crate::{
vector::{vec2f, vec2i, Vector2F},
},
platform,
text_layout::{Glyph, LineLayout, Run},
text_layout::{Glyph, LineLayout, Run, RunStyle},
};
use cocoa::appkit::{CGFloat, CGPoint};
use core_foundation::{
@@ -21,9 +20,12 @@ use core_graphics::{
base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform,
};
use core_text::{line::CTLine, string_attributes::kCTFontAttributeName};
use font_kit::{canvas::RasterizationOptions, hinting::HintingOptions, source::SystemSource};
use font_kit::{
canvas::RasterizationOptions, handle::Handle, hinting::HintingOptions, source::SystemSource,
sources::mem::MemSource,
};
use parking_lot::RwLock;
use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void};
use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
#[allow(non_upper_case_globals)]
const kCGImageAlphaOnly: u32 = 7;
@@ -31,20 +33,26 @@ const kCGImageAlphaOnly: u32 = 7;
pub struct FontSystem(RwLock<FontSystemState>);
struct FontSystemState {
source: SystemSource,
memory_source: MemSource,
system_source: SystemSource,
fonts: Vec<font_kit::font::Font>,
}
impl FontSystem {
pub fn new() -> Self {
Self(RwLock::new(FontSystemState {
source: SystemSource::new(),
memory_source: MemSource::empty(),
system_source: SystemSource::new(),
fonts: Vec::new(),
}))
}
}
impl platform::FontSystem for FontSystem {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
self.0.write().add_fonts(fonts)
}
fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>> {
self.0.write().load_family(name)
}
@@ -78,12 +86,7 @@ impl platform::FontSystem for FontSystem {
.rasterize_glyph(font_id, font_size, glyph_id, subpixel_shift, scale_factor)
}
fn layout_line(
&self,
text: &str,
font_size: f32,
runs: &[(usize, FontId, Color)],
) -> LineLayout {
fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
self.0.read().layout_line(text, font_size, runs)
}
@@ -93,9 +96,23 @@ impl platform::FontSystem for FontSystem {
}
impl FontSystemState {
fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
self.memory_source.add_fonts(
fonts
.iter()
.map(|bytes| Handle::from_memory(bytes.clone(), 0)),
)?;
Ok(())
}
fn load_family(&mut self, name: &str) -> anyhow::Result<Vec<FontId>> {
let mut font_ids = Vec::new();
for font in self.source.select_family_by_name(name)?.fonts() {
let family = self
.memory_source
.select_family_by_name(name)
.or_else(|_| self.system_source.select_family_by_name(name))?;
for font in family.fonts() {
let font = font.load()?;
font_ids.push(FontId(self.fonts.len()));
self.fonts.push(font);
@@ -187,12 +204,7 @@ impl FontSystemState {
}
}
fn layout_line(
&self,
text: &str,
font_size: f32,
runs: &[(usize, FontId, Color)],
) -> LineLayout {
fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
let font_id_attr_name = CFString::from_static_string("zed_font_id");
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
@@ -204,20 +216,20 @@ impl FontSystemState {
let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
let font_runs = runs
.iter()
.filter_map(|(len, font_id, _)| {
.filter_map(|(len, style)| {
let mut last_run = last_run.borrow_mut();
if let Some((last_len, last_font_id)) = last_run.as_mut() {
if font_id == last_font_id {
if style.font_id == *last_font_id {
*last_len += *len;
None
} else {
let result = (*last_len, *last_font_id);
*last_len = *len;
*last_font_id = *font_id;
*last_font_id = style.font_id;
Some(result)
}
} else {
*last_run = Some((*len, *font_id));
*last_run = Some((*len, style.font_id));
None
}
})
@@ -392,9 +404,8 @@ extern "C" {
#[cfg(test)]
mod tests {
use crate::MutableAppContext;
use super::*;
use crate::MutableAppContext;
use font_kit::properties::{Style, Weight};
use platform::FontSystem as _;
@@ -403,13 +414,25 @@ mod tests {
// This is failing intermittently on CI and we don't have time to figure it out
let fonts = FontSystem::new();
let menlo = fonts.load_family("Menlo").unwrap();
let menlo_regular = fonts.select_font(&menlo, &Properties::new()).unwrap();
let menlo_italic = fonts
.select_font(&menlo, &Properties::new().style(Style::Italic))
.unwrap();
let menlo_bold = fonts
.select_font(&menlo, &Properties::new().weight(Weight::BOLD))
.unwrap();
let menlo_regular = RunStyle {
font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
color: Default::default(),
underline: false,
};
let menlo_italic = RunStyle {
font_id: fonts
.select_font(&menlo, &Properties::new().style(Style::Italic))
.unwrap(),
color: Default::default(),
underline: false,
};
let menlo_bold = RunStyle {
font_id: fonts
.select_font(&menlo, &Properties::new().weight(Weight::BOLD))
.unwrap(),
color: Default::default(),
underline: false,
};
assert_ne!(menlo_regular, menlo_italic);
assert_ne!(menlo_regular, menlo_bold);
assert_ne!(menlo_italic, menlo_bold);
@@ -417,18 +440,14 @@ mod tests {
let line = fonts.layout_line(
"hello world",
16.0,
&[
(2, menlo_bold, Default::default()),
(4, menlo_italic, Default::default()),
(5, menlo_regular, Default::default()),
],
&[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
);
assert_eq!(line.runs.len(), 3);
assert_eq!(line.runs[0].font_id, menlo_bold);
assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
assert_eq!(line.runs[0].glyphs.len(), 2);
assert_eq!(line.runs[1].font_id, menlo_italic);
assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
assert_eq!(line.runs[1].glyphs.len(), 4);
assert_eq!(line.runs[2].font_id, menlo_regular);
assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
assert_eq!(line.runs[2].glyphs.len(), 5);
}
@@ -436,18 +455,26 @@ mod tests {
fn test_glyph_offsets() -> anyhow::Result<()> {
let fonts = FontSystem::new();
let zapfino = fonts.load_family("Zapfino")?;
let zapfino_regular = fonts.select_font(&zapfino, &Properties::new())?;
let zapfino_regular = RunStyle {
font_id: fonts.select_font(&zapfino, &Properties::new())?,
color: Default::default(),
underline: false,
};
let menlo = fonts.load_family("Menlo")?;
let menlo_regular = fonts.select_font(&menlo, &Properties::new())?;
let menlo_regular = RunStyle {
font_id: fonts.select_font(&menlo, &Properties::new())?,
color: Default::default(),
underline: false,
};
let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
let line = fonts.layout_line(
text,
16.0,
&[
(9, zapfino_regular, Color::default()),
(13, menlo_regular, Color::default()),
(text.len() - 22, zapfino_regular, Color::default()),
(9, zapfino_regular),
(13, menlo_regular),
(text.len() - 22, zapfino_regular),
],
);
assert_eq!(
@@ -513,15 +540,19 @@ mod tests {
fn test_layout_line_bom_char() {
let fonts = FontSystem::new();
let font_ids = fonts.load_family("Helvetica").unwrap();
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
let style = RunStyle {
font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
color: Default::default(),
underline: false,
};
let line = "\u{feff}";
let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]);
let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
assert_eq!(layout.len, line.len());
assert!(layout.runs.is_empty());
let line = "a\u{feff}b";
let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]);
let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
assert_eq!(layout.len, line.len());
assert_eq!(layout.runs.len(), 1);
assert_eq!(layout.runs[0].glyphs.len(), 2);
+45 -20
View File
@@ -1,5 +1,11 @@
use super::{BoolExt as _, Dispatcher, FontSystem, Window};
use crate::{executor, keymap::Keystroke, platform, ClipboardItem, Event, Menu, MenuItem};
use crate::{
executor,
keymap::Keystroke,
platform::{self, CursorStyle},
AnyAction, ClipboardItem, Event, Menu, MenuItem,
};
use anyhow::{anyhow, Result};
use block::ConcreteBlock;
use cocoa::{
appkit::{
@@ -27,7 +33,6 @@ use objc::{
};
use ptr::null_mut;
use std::{
any::Any,
cell::{Cell, RefCell},
convert::TryInto,
ffi::{c_void, CStr},
@@ -38,6 +43,7 @@ use std::{
slice, str,
sync::Arc,
};
use time::UtcOffset;
const MAC_PLATFORM_IVAR: &'static str = "platform";
static mut APP_CLASS: *const Class = ptr::null();
@@ -90,10 +96,10 @@ pub struct MacForegroundPlatformState {
become_active: Option<Box<dyn FnMut()>>,
resign_active: Option<Box<dyn FnMut()>>,
event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
menu_command: Option<Box<dyn FnMut(&str, Option<&dyn Any>)>>,
menu_command: Option<Box<dyn FnMut(&dyn AnyAction)>>,
open_files: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
finish_launching: Option<Box<dyn FnOnce() -> ()>>,
menu_actions: Vec<(String, Option<Box<dyn Any>>)>,
menu_actions: Vec<Box<dyn AnyAction>>,
}
impl MacForegroundPlatform {
@@ -121,7 +127,6 @@ impl MacForegroundPlatform {
name,
keystroke,
action,
arg,
} => {
if let Some(keystroke) = keystroke {
let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| {
@@ -162,7 +167,7 @@ impl MacForegroundPlatform {
let tag = state.menu_actions.len() as NSInteger;
let _: () = msg_send![item, setTag: tag];
state.menu_actions.push((action.to_string(), arg));
state.menu_actions.push(action);
}
}
@@ -215,7 +220,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
}
}
fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>) {
fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn AnyAction)>) {
self.0.borrow_mut().menu_command = Some(callback);
}
@@ -465,7 +470,7 @@ impl platform::Platform for MacPlatform {
}
}
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) {
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
let url = CFString::from(url);
let username = CFString::from(username);
let password = CFData::from_buffer(password);
@@ -498,12 +503,13 @@ impl platform::Platform for MacPlatform {
}
if status != errSecSuccess {
panic!("{} password failed: {}", verb, status);
return Err(anyhow!("{} password failed: {}", verb, status));
}
}
Ok(())
}
fn read_credentials(&self, url: &str) -> Option<(String, Vec<u8>)> {
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
let url = CFString::from(url);
let cf_true = CFBoolean::true_value().as_CFTypeRef();
@@ -521,27 +527,46 @@ impl platform::Platform for MacPlatform {
let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
match status {
security::errSecSuccess => {}
security::errSecItemNotFound | security::errSecUserCanceled => return None,
_ => panic!("reading password failed: {}", status),
security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
_ => return Err(anyhow!("reading password failed: {}", status)),
}
let result = CFType::wrap_under_create_rule(result)
.downcast::<CFDictionary>()
.expect("keychain item was not a dictionary");
.ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
let username = result
.find(kSecAttrAccount as *const _)
.expect("account was missing from keychain item");
.ok_or_else(|| anyhow!("account was missing from keychain item"))?;
let username = CFType::wrap_under_get_rule(*username)
.downcast::<CFString>()
.expect("account was not a string");
.ok_or_else(|| anyhow!("account was not a string"))?;
let password = result
.find(kSecValueData as *const _)
.expect("password was missing from keychain item");
.ok_or_else(|| anyhow!("password was missing from keychain item"))?;
let password = CFType::wrap_under_get_rule(*password)
.downcast::<CFData>()
.expect("password was not a string");
.ok_or_else(|| anyhow!("password was not a string"))?;
Some((username.to_string(), password.bytes().to_vec()))
Ok(Some((username.to_string(), password.bytes().to_vec())))
}
}
fn set_cursor_style(&self, style: CursorStyle) {
unsafe {
let cursor: id = match style {
CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
};
let _: () = msg_send![cursor, set];
}
}
fn local_timezone(&self) -> UtcOffset {
unsafe {
let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
}
}
}
@@ -623,8 +648,8 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
if let Some(mut callback) = platform.menu_command.take() {
let tag: NSInteger = msg_send![item, tag];
let index = tag as usize;
if let Some((action, arg)) = platform.menu_actions.get(index) {
callback(action, arg.as_ref().map(Box::as_ref));
if let Some(action) = platform.menu_actions.get(index) {
callback(action.as_ref());
}
platform.menu_command = Some(callback);
}
+60 -33
View File
@@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, vec2i, Vector2F},
},
platform,
scene::Layer,
scene::{Glyph, Icon, Layer, Quad, Shadow},
Scene,
};
use cocoa::foundation::NSUInteger;
@@ -142,7 +142,7 @@ impl Renderer {
let mut sprites = Vec::new();
let mut vertices = Vec::<shaders::GPUIPathVertex>::new();
let mut current_atlas_id = None;
for (layer_id, layer) in scene.layers().iter().enumerate() {
for (layer_id, layer) in scene.layers().enumerate() {
for path in layer.paths() {
let origin = path.bounds.origin() * scene.scale_factor();
let size = (path.bounds.size() * scene.scale_factor()).ceil();
@@ -283,12 +283,24 @@ impl Renderer {
zfar: 1.0,
});
let scale_factor = scene.scale_factor();
let mut path_sprites = path_sprites.into_iter().peekable();
for (layer_id, layer) in scene.layers().iter().enumerate() {
for (layer_id, layer) in scene.layers().enumerate() {
self.clip(scene, layer, drawable_size, command_encoder);
self.render_shadows(scene, layer, offset, drawable_size, command_encoder);
self.render_quads(scene, layer, offset, drawable_size, command_encoder);
self.render_shadows(
layer.shadows(),
scale_factor,
offset,
drawable_size,
command_encoder,
);
self.render_quads(
layer.quads(),
scale_factor,
offset,
drawable_size,
command_encoder,
);
self.render_path_sprites(
layer_id,
&mut path_sprites,
@@ -296,7 +308,21 @@ impl Renderer {
drawable_size,
command_encoder,
);
self.render_sprites(scene, layer, offset, drawable_size, command_encoder);
self.render_sprites(
layer.glyphs(),
layer.icons(),
scale_factor,
offset,
drawable_size,
command_encoder,
);
self.render_quads(
layer.underlines(),
scale_factor,
offset,
drawable_size,
command_encoder,
);
}
command_encoder.end_encoding();
@@ -324,18 +350,18 @@ impl Renderer {
fn render_shadows(
&mut self,
scene: &Scene,
layer: &Layer,
shadows: &[Shadow],
scale_factor: f32,
offset: &mut usize,
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if layer.shadows().is_empty() {
if shadows.is_empty() {
return;
}
align_offset(offset);
let next_offset = *offset + layer.shadows().len() * mem::size_of::<shaders::GPUIShadow>();
let next_offset = *offset + shadows.len() * mem::size_of::<shaders::GPUIShadow>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
@@ -365,12 +391,12 @@ impl Renderer {
(self.instances.contents() as *mut u8).offset(*offset as isize)
as *mut shaders::GPUIShadow
};
for (ix, shadow) in layer.shadows().iter().enumerate() {
let shape_bounds = shadow.bounds * scene.scale_factor();
for (ix, shadow) in shadows.iter().enumerate() {
let shape_bounds = shadow.bounds * scale_factor;
let shader_shadow = shaders::GPUIShadow {
origin: shape_bounds.origin().to_float2(),
size: shape_bounds.size().to_float2(),
corner_radius: shadow.corner_radius * scene.scale_factor(),
corner_radius: shadow.corner_radius * scale_factor,
sigma: shadow.sigma,
color: shadow.color.to_uchar4(),
};
@@ -383,24 +409,24 @@ impl Renderer {
metal::MTLPrimitiveType::Triangle,
0,
6,
layer.shadows().len() as u64,
shadows.len() as u64,
);
*offset = next_offset;
}
fn render_quads(
&mut self,
scene: &Scene,
layer: &Layer,
quads: &[Quad],
scale_factor: f32,
offset: &mut usize,
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if layer.quads().is_empty() {
if quads.is_empty() {
return;
}
align_offset(offset);
let next_offset = *offset + layer.quads().len() * mem::size_of::<shaders::GPUIQuad>();
let next_offset = *offset + quads.len() * mem::size_of::<shaders::GPUIQuad>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
@@ -430,9 +456,9 @@ impl Renderer {
(self.instances.contents() as *mut u8).offset(*offset as isize)
as *mut shaders::GPUIQuad
};
for (ix, quad) in layer.quads().iter().enumerate() {
let bounds = quad.bounds * scene.scale_factor();
let border_width = quad.border.width * scene.scale_factor();
for (ix, quad) in quads.iter().enumerate() {
let bounds = quad.bounds * scale_factor;
let border_width = quad.border.width * scale_factor;
let shader_quad = shaders::GPUIQuad {
origin: bounds.origin().round().to_float2(),
size: bounds.size().round().to_float2(),
@@ -445,7 +471,7 @@ impl Renderer {
border_bottom: border_width * (quad.border.bottom as usize as f32),
border_left: border_width * (quad.border.left as usize as f32),
border_color: quad.border.color.to_uchar4(),
corner_radius: quad.corner_radius * scene.scale_factor(),
corner_radius: quad.corner_radius * scale_factor,
};
unsafe {
*(buffer_contents.offset(ix as isize)) = shader_quad;
@@ -456,35 +482,36 @@ impl Renderer {
metal::MTLPrimitiveType::Triangle,
0,
6,
layer.quads().len() as u64,
quads.len() as u64,
);
*offset = next_offset;
}
fn render_sprites(
&mut self,
scene: &Scene,
layer: &Layer,
glyphs: &[Glyph],
icons: &[Icon],
scale_factor: f32,
offset: &mut usize,
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if layer.glyphs().is_empty() && layer.icons().is_empty() {
if glyphs.is_empty() && icons.is_empty() {
return;
}
let mut sprites_by_atlas = HashMap::new();
for glyph in layer.glyphs() {
for glyph in glyphs {
if let Some(sprite) = self.sprite_cache.render_glyph(
glyph.font_id,
glyph.font_size,
glyph.id,
glyph.origin,
scene.scale_factor(),
scale_factor,
) {
// Snap sprite to pixel grid.
let origin = (glyph.origin * scene.scale_factor()).floor() + sprite.offset.to_f32();
let origin = (glyph.origin * scale_factor).floor() + sprite.offset.to_f32();
sprites_by_atlas
.entry(sprite.atlas_id)
.or_insert_with(Vec::new)
@@ -499,9 +526,9 @@ impl Renderer {
}
}
for icon in layer.icons() {
let origin = icon.bounds.origin() * scene.scale_factor();
let target_size = icon.bounds.size() * scene.scale_factor();
for icon in icons {
let origin = icon.bounds.origin() * scale_factor;
let target_size = icon.bounds.size() * scale_factor;
let source_size = (target_size * 2.).ceil().to_i32();
let sprite =
+98 -18
View File
@@ -8,13 +8,14 @@ use crate::{
use block::ConcreteBlock;
use cocoa::{
appkit::{
NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
NSViewWidthSizable, NSWindow, NSWindowStyleMask,
CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
},
base::{id, nil},
foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString},
quartzcore::AutoresizingMask,
};
use core_graphics::display::CGRect;
use ctor::ctor;
use foreign_types::ForeignType as _;
use objc::{
@@ -65,6 +66,10 @@ unsafe fn build_classes() {
sel!(sendEvent:),
send_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidResize:),
window_did_resize as extern "C" fn(&Object, Sel, id),
);
decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
decl.register()
};
@@ -129,7 +134,7 @@ struct WindowState {
id: usize,
native_window: id,
event_callback: Option<Box<dyn FnMut(Event)>>,
resize_callback: Option<Box<dyn FnMut(&mut dyn platform::WindowContext)>>,
resize_callback: Option<Box<dyn FnMut()>>,
close_callback: Option<Box<dyn FnOnce()>>,
synthetic_drag_counter: usize,
executor: Rc<executor::Foreground>,
@@ -138,6 +143,7 @@ struct WindowState {
command_queue: metal::CommandQueue,
last_fresh_keydown: Option<(Keystroke, String)>,
layer: id,
traffic_light_position: Option<Vector2F>,
}
impl Window {
@@ -153,11 +159,15 @@ impl Window {
let pool = NSAutoreleasePool::new(nil);
let frame = options.bounds.to_ns_rect();
let style_mask = NSWindowStyleMask::NSClosableWindowMask
let mut style_mask = NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSTitledWindowMask;
if options.titlebar_appears_transparent {
style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
}
let native_window: id = msg_send![WINDOW_CLASS, alloc];
let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
frame,
@@ -199,12 +209,14 @@ impl Window {
command_queue: device.new_command_queue(),
last_fresh_keydown: None,
layer,
traffic_light_position: options.traffic_light_position,
})));
(*native_window).set_ivar(
WINDOW_STATE_IVAR,
Rc::into_raw(window.0.clone()) as *const c_void,
);
native_window.setDelegate_(native_window);
(*native_view).set_ivar(
WINDOW_STATE_IVAR,
Rc::into_raw(window.0.clone()) as *const c_void,
@@ -213,6 +225,9 @@ impl Window {
if let Some(title) = options.title.as_ref() {
native_window.setTitle_(NSString::alloc(nil).init_str(title));
}
if options.titlebar_appears_transparent {
native_window.setTitlebarAppearsTransparent_(YES);
}
native_window.setAcceptsMouseMovedEvents_(YES);
native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
@@ -235,6 +250,7 @@ impl Window {
native_window.center();
native_window.makeKeyAndOrderFront_(nil);
window.0.borrow().move_traffic_light();
pool.drain();
window
@@ -272,7 +288,7 @@ impl platform::Window for Window {
self.0.as_ref().borrow_mut().event_callback = Some(callback);
}
fn on_resize(&mut self, callback: Box<dyn FnMut(&mut dyn platform::WindowContext)>) {
fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
self.0.as_ref().borrow_mut().resize_callback = Some(callback);
}
@@ -329,6 +345,56 @@ impl platform::WindowContext for Window {
fn present_scene(&mut self, scene: Scene) {
self.0.as_ref().borrow_mut().present_scene(scene);
}
fn titlebar_height(&self) -> f32 {
self.0.as_ref().borrow().titlebar_height()
}
}
impl WindowState {
fn move_traffic_light(&self) {
if let Some(traffic_light_position) = self.traffic_light_position {
let titlebar_height = self.titlebar_height();
unsafe {
let close_button: id = msg_send![
self.native_window,
standardWindowButton: NSWindowButton::NSWindowCloseButton
];
let min_button: id = msg_send![
self.native_window,
standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
];
let zoom_button: id = msg_send![
self.native_window,
standardWindowButton: NSWindowButton::NSWindowZoomButton
];
let mut close_button_frame: CGRect = msg_send![close_button, frame];
let mut min_button_frame: CGRect = msg_send![min_button, frame];
let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
let mut origin = vec2f(
traffic_light_position.x(),
titlebar_height
- traffic_light_position.y()
- close_button_frame.size.height as f32,
);
let button_spacing =
(min_button_frame.origin.x - close_button_frame.origin.x) as f32;
close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
let _: () = msg_send![close_button, setFrame: close_button_frame];
origin.set_x(origin.x() + button_spacing);
min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
let _: () = msg_send![min_button, setFrame: min_button_frame];
origin.set_x(origin.x() + button_spacing);
zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
}
}
}
}
impl platform::WindowContext for WindowState {
@@ -345,6 +411,14 @@ impl platform::WindowContext for WindowState {
}
}
fn titlebar_height(&self) -> f32 {
unsafe {
let frame = NSWindow::frame(self.native_window);
let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
(frame.size.height - content_layout_rect.size.height) as f32
}
}
fn present_scene(&mut self, scene: Scene) {
self.scene_to_render = Some(scene);
unsafe {
@@ -442,6 +516,11 @@ extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
}
}
extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
let window_state = unsafe { get_window_state(this) };
window_state.as_ref().borrow().move_traffic_light();
}
extern "C" fn close_window(this: &Object, _: Sel) {
unsafe {
let close_callback = {
@@ -469,24 +548,24 @@ extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
let window_state = unsafe { get_window_state(this) };
let mut window_state = window_state.as_ref().borrow_mut();
let mut window_state_borrow = window_state.as_ref().borrow_mut();
unsafe {
let _: () =
msg_send![window_state.layer, setContentsScale: window_state.scale_factor() as f64];
let _: () = msg_send![window_state_borrow.layer, setContentsScale: window_state_borrow.scale_factor() as f64];
}
if let Some(mut callback) = window_state.resize_callback.take() {
callback(&mut *window_state);
window_state.resize_callback = Some(callback);
if let Some(mut callback) = window_state_borrow.resize_callback.take() {
drop(window_state_borrow);
callback();
window_state.as_ref().borrow_mut().resize_callback = Some(callback);
};
}
extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
let window_state = unsafe { get_window_state(this) };
let mut window_state = window_state.as_ref().borrow_mut();
let mut window_state_borrow = window_state.as_ref().borrow_mut();
if window_state.size() == vec2f(size.width as f32, size.height as f32) {
if window_state_borrow.size() == vec2f(size.width as f32, size.height as f32) {
return;
}
@@ -494,19 +573,20 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
}
let scale_factor = window_state.scale_factor() as f64;
let scale_factor = window_state_borrow.scale_factor() as f64;
let drawable_size: NSSize = NSSize {
width: size.width * scale_factor,
height: size.height * scale_factor,
};
unsafe {
let _: () = msg_send![window_state.layer, setDrawableSize: drawable_size];
let _: () = msg_send![window_state_borrow.layer, setDrawableSize: drawable_size];
}
if let Some(mut callback) = window_state.resize_callback.take() {
callback(&mut *window_state);
window_state.resize_callback = Some(callback);
if let Some(mut callback) = window_state_borrow.resize_callback.take() {
drop(window_state_borrow);
callback();
window_state.borrow_mut().resize_callback = Some(callback);
};
}
+26 -7
View File
@@ -1,4 +1,6 @@
use crate::ClipboardItem;
use super::CursorStyle;
use crate::{AnyAction, ClipboardItem};
use anyhow::Result;
use parking_lot::Mutex;
use pathfinder_geometry::vector::Vector2F;
use std::{
@@ -8,11 +10,13 @@ use std::{
rc::Rc,
sync::Arc,
};
use time::UtcOffset;
pub struct Platform {
dispatcher: Arc<dyn super::Dispatcher>,
fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
cursor: Mutex<CursorStyle>,
}
#[derive(Default)]
@@ -27,7 +31,7 @@ pub struct Window {
scale_factor: f32,
current_scene: Option<crate::Scene>,
event_handlers: Vec<Box<dyn FnMut(super::Event)>>,
resize_handlers: Vec<Box<dyn FnMut(&mut dyn super::WindowContext)>>,
resize_handlers: Vec<Box<dyn FnMut()>>,
close_handlers: Vec<Box<dyn FnOnce()>>,
pub(crate) last_prompt: RefCell<Option<Box<dyn FnOnce(usize)>>>,
}
@@ -62,7 +66,7 @@ impl super::ForegroundPlatform for ForegroundPlatform {
unimplemented!()
}
fn on_menu_command(&self, _: Box<dyn FnMut(&str, Option<&dyn Any>)>) {}
fn on_menu_command(&self, _: Box<dyn FnMut(&dyn AnyAction)>) {}
fn set_menus(&self, _: Vec<crate::Menu>) {}
@@ -84,6 +88,7 @@ impl Platform {
dispatcher: Arc::new(Dispatcher),
fonts: Arc::new(super::current::FontSystem::new()),
current_clipboard_item: Default::default(),
cursor: Mutex::new(CursorStyle::Arrow),
}
}
}
@@ -124,10 +129,20 @@ impl super::Platform for Platform {
fn open_url(&self, _: &str) {}
fn write_credentials(&self, _: &str, _: &str, _: &[u8]) {}
fn write_credentials(&self, _: &str, _: &str, _: &[u8]) -> Result<()> {
Ok(())
}
fn read_credentials(&self, _: &str) -> Option<(String, Vec<u8>)> {
None
fn read_credentials(&self, _: &str) -> Result<Option<(String, Vec<u8>)>> {
Ok(None)
}
fn set_cursor_style(&self, style: CursorStyle) {
*self.cursor.lock() = style;
}
fn local_timezone(&self) -> UtcOffset {
UtcOffset::UTC
}
}
@@ -164,6 +179,10 @@ impl super::WindowContext for Window {
self.scale_factor
}
fn titlebar_height(&self) -> f32 {
24.
}
fn present_scene(&mut self, scene: crate::Scene) {
self.current_scene = Some(scene);
}
@@ -178,7 +197,7 @@ impl super::Window for Window {
self.event_handlers.push(callback);
}
fn on_resize(&mut self, callback: Box<dyn FnMut(&mut dyn super::WindowContext)>) {
fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
self.resize_handlers.push(callback);
}
+151 -87
View File
@@ -2,16 +2,18 @@ use crate::{
app::{AppContext, MutableAppContext, WindowInvalidation},
elements::Element,
font_cache::FontCache,
geometry::rect::RectF,
json::{self, ToJson},
platform::Event,
text_layout::TextLayoutCache,
AssetCache, ElementBox, Scene,
Action, AnyAction, AssetCache, ElementBox, Entity, FontSystem, ModelHandle, ReadModel,
ReadView, Scene, View, ViewHandle,
};
use pathfinder_geometry::vector::{vec2f, Vector2F};
use serde_json::json;
use std::{
any::Any,
collections::{HashMap, HashSet},
ops::{Deref, DerefMut},
sync::Arc,
};
@@ -23,24 +25,27 @@ pub struct Presenter {
text_layout_cache: TextLayoutCache,
asset_cache: Arc<AssetCache>,
last_mouse_moved_event: Option<Event>,
titlebar_height: f32,
}
impl Presenter {
pub fn new(
window_id: usize,
titlebar_height: f32,
font_cache: Arc<FontCache>,
text_layout_cache: TextLayoutCache,
asset_cache: Arc<AssetCache>,
cx: &MutableAppContext,
cx: &mut MutableAppContext,
) -> Self {
Self {
window_id,
rendered_views: cx.render_views(window_id),
rendered_views: cx.render_views(window_id, titlebar_height),
parents: HashMap::new(),
font_cache,
text_layout_cache,
asset_cache,
last_mouse_moved_event: None,
titlebar_height,
}
}
@@ -55,15 +60,37 @@ impl Presenter {
path
}
pub fn invalidate(&mut self, mut invalidation: WindowInvalidation, cx: &AppContext) {
pub fn invalidate(&mut self, mut invalidation: WindowInvalidation, cx: &mut MutableAppContext) {
for view_id in invalidation.removed {
invalidation.updated.remove(&view_id);
self.rendered_views.remove(&view_id);
self.parents.remove(&view_id);
}
for view_id in invalidation.updated {
self.rendered_views
.insert(view_id, cx.render_view(self.window_id, view_id).unwrap());
self.rendered_views.insert(
view_id,
cx.render_view(self.window_id, view_id, self.titlebar_height, false)
.unwrap(),
);
}
}
pub fn refresh(
&mut self,
invalidation: Option<WindowInvalidation>,
cx: &mut MutableAppContext,
) {
if let Some(invalidation) = invalidation {
for view_id in invalidation.removed {
self.rendered_views.remove(&view_id);
self.parents.remove(&view_id);
}
}
for (view_id, view) in &mut self.rendered_views {
*view = cx
.render_view(self.window_id, *view_id, self.titlebar_height, true)
.unwrap();
}
}
@@ -71,13 +98,13 @@ impl Presenter {
&mut self,
window_size: Vector2F,
scale_factor: f32,
refreshing: bool,
cx: &mut MutableAppContext,
) -> Scene {
let mut scene = Scene::new(scale_factor);
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
self.layout(window_size, cx);
self.after_layout(cx);
self.layout(window_size, refreshing, cx);
let mut paint_cx = PaintContext {
scene: &mut scene,
font_cache: &self.font_cache,
@@ -85,7 +112,11 @@ impl Presenter {
rendered_views: &mut self.rendered_views,
app: cx.as_ref(),
};
paint_cx.paint(root_view_id, Vector2F::zero());
paint_cx.paint(
root_view_id,
Vector2F::zero(),
RectF::new(Vector2F::zero(), window_size),
);
self.text_layout_cache.finish_frame();
if let Some(event) = self.last_mouse_moved_event.clone() {
@@ -98,67 +129,76 @@ impl Presenter {
scene
}
fn layout(&mut self, size: Vector2F, cx: &mut MutableAppContext) {
fn layout(&mut self, size: Vector2F, refreshing: bool, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
let mut layout_ctx = LayoutContext {
rendered_views: &mut self.rendered_views,
parents: &mut self.parents,
font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache,
asset_cache: &self.asset_cache,
view_stack: Vec::new(),
app: cx,
};
layout_ctx.layout(root_view_id, SizeConstraint::strict(size));
self.build_layout_context(refreshing, cx)
.layout(root_view_id, SizeConstraint::strict(size));
}
}
fn after_layout(&mut self, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
let mut layout_cx = AfterLayoutContext {
rendered_views: &mut self.rendered_views,
font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache,
app: cx,
};
layout_cx.after_layout(root_view_id);
pub fn build_layout_context<'a>(
&'a mut self,
refreshing: bool,
cx: &'a mut MutableAppContext,
) -> LayoutContext<'a> {
LayoutContext {
rendered_views: &mut self.rendered_views,
parents: &mut self.parents,
refreshing,
font_cache: &self.font_cache,
font_system: cx.platform().fonts(),
text_layout_cache: &self.text_layout_cache,
asset_cache: &self.asset_cache,
view_stack: Vec::new(),
app: cx,
}
}
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
if matches!(event, Event::MouseMoved { .. }) {
self.last_mouse_moved_event = Some(event.clone());
match event {
Event::MouseMoved { .. } => {
self.last_mouse_moved_event = Some(event.clone());
}
Event::LeftMouseDragged { position } => {
self.last_mouse_moved_event = Some(Event::MouseMoved {
position,
left_mouse_down: true,
});
}
_ => {}
}
let mut event_cx = EventContext {
rendered_views: &mut self.rendered_views,
actions: Default::default(),
font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache,
view_stack: Default::default(),
invalidated_views: Default::default(),
app: cx,
};
let mut event_cx = self.build_event_context(cx);
event_cx.dispatch_event(root_view_id, &event);
let invalidated_views = event_cx.invalidated_views;
let actions = event_cx.actions;
let dispatch_directives = event_cx.dispatched_actions;
for view_id in invalidated_views {
cx.notify_view(self.window_id, view_id);
}
for action in actions {
cx.dispatch_action_any(
self.window_id,
&action.path,
action.name,
action.arg.as_ref(),
);
for directive in dispatch_directives {
cx.dispatch_action_any(self.window_id, &directive.path, directive.action.as_ref());
}
}
}
pub fn build_event_context<'a>(
&'a mut self,
cx: &'a mut MutableAppContext,
) -> EventContext<'a> {
EventContext {
rendered_views: &mut self.rendered_views,
dispatched_actions: Default::default(),
font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache,
view_stack: Default::default(),
invalidated_views: Default::default(),
app: cx,
}
}
pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
cx.root_view_id(self.window_id)
.and_then(|root_view_id| self.rendered_views.get(&root_view_id))
@@ -172,20 +212,21 @@ impl Presenter {
}
}
pub struct ActionToDispatch {
pub struct DispatchDirective {
pub path: Vec<usize>,
pub name: &'static str,
pub arg: Box<dyn Any>,
pub action: Box<dyn AnyAction>,
}
pub struct LayoutContext<'a> {
rendered_views: &'a mut HashMap<usize, ElementBox>,
parents: &'a mut HashMap<usize, usize>,
pub font_cache: &'a FontCache,
view_stack: Vec<usize>,
pub refreshing: bool,
pub font_cache: &'a Arc<FontCache>,
pub font_system: Arc<dyn FontSystem>,
pub text_layout_cache: &'a TextLayoutCache,
pub asset_cache: &'a AssetCache,
pub app: &'a mut MutableAppContext,
view_stack: Vec<usize>,
}
impl<'a> LayoutContext<'a> {
@@ -202,19 +243,29 @@ impl<'a> LayoutContext<'a> {
}
}
pub struct AfterLayoutContext<'a> {
rendered_views: &'a mut HashMap<usize, ElementBox>,
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a TextLayoutCache,
pub app: &'a mut MutableAppContext,
impl<'a> Deref for LayoutContext<'a> {
type Target = MutableAppContext;
fn deref(&self) -> &Self::Target {
self.app
}
}
impl<'a> AfterLayoutContext<'a> {
fn after_layout(&mut self, view_id: usize) {
if let Some(mut view) = self.rendered_views.remove(&view_id) {
view.after_layout(self);
self.rendered_views.insert(view_id, view);
}
impl<'a> DerefMut for LayoutContext<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.app
}
}
impl<'a> ReadView for LayoutContext<'a> {
fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
self.app.read_view(handle)
}
}
impl<'a> ReadModel for LayoutContext<'a> {
fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
self.app.read_model(handle)
}
}
@@ -227,9 +278,9 @@ pub struct PaintContext<'a> {
}
impl<'a> PaintContext<'a> {
fn paint(&mut self, view_id: usize, origin: Vector2F) {
fn paint(&mut self, view_id: usize, origin: Vector2F, visible_bounds: RectF) {
if let Some(mut tree) = self.rendered_views.remove(&view_id) {
tree.paint(origin, self);
tree.paint(origin, visible_bounds, self);
self.rendered_views.insert(view_id, tree);
}
}
@@ -237,7 +288,7 @@ impl<'a> PaintContext<'a> {
pub struct EventContext<'a> {
rendered_views: &'a mut HashMap<usize, ElementBox>,
actions: Vec<ActionToDispatch>,
dispatched_actions: Vec<DispatchDirective>,
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a TextLayoutCache,
pub app: &'a mut MutableAppContext,
@@ -258,17 +309,31 @@ impl<'a> EventContext<'a> {
}
}
pub fn dispatch_action<A: 'static + Any>(&mut self, name: &'static str, arg: A) {
self.actions.push(ActionToDispatch {
pub fn dispatch_action<A: Action>(&mut self, action: A) {
self.dispatched_actions.push(DispatchDirective {
path: self.view_stack.clone(),
name,
arg: Box::new(arg),
action: Box::new(action),
});
}
pub fn notify(&mut self) {
self.invalidated_views
.insert(*self.view_stack.last().unwrap());
if let Some(view_id) = self.view_stack.last() {
self.invalidated_views.insert(*view_id);
}
}
}
impl<'a> Deref for EventContext<'a> {
type Target = MutableAppContext;
fn deref(&self) -> &Self::Target {
self.app
}
}
impl<'a> DerefMut for EventContext<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.app
}
}
@@ -352,6 +417,13 @@ impl SizeConstraint {
Axis::Vertical => self.max.y(),
}
}
pub fn min_along(&self, axis: Axis) -> f32 {
match axis {
Axis::Horizontal => self.min.x(),
Axis::Vertical => self.min.y(),
}
}
}
impl ToJson for SizeConstraint {
@@ -386,28 +458,20 @@ impl Element for ChildView {
(size, ())
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
cx: &mut AfterLayoutContext,
) {
cx.after_layout(self.view_id);
}
fn paint(
&mut self,
bounds: pathfinder_geometry::rect::RectF,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
cx.paint(self.view_id, bounds.origin());
cx.paint(self.view_id, bounds.origin(), visible_bounds);
}
fn dispatch_event(
&mut self,
event: &Event,
_: pathfinder_geometry::rect::RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
@@ -417,7 +481,7 @@ impl Element for ChildView {
fn debug(
&self,
bounds: pathfinder_geometry::rect::RectF,
bounds: RectF,
_: &Self::LayoutState,
_: &Self::PaintState,
cx: &DebugContext,
+81 -9
View File
@@ -11,6 +11,11 @@ use crate::{
pub struct Scene {
scale_factor: f32,
stacking_contexts: Vec<StackingContext>,
active_stacking_context_stack: Vec<usize>,
}
struct StackingContext {
layers: Vec<Layer>,
active_layer_stack: Vec<usize>,
}
@@ -19,6 +24,7 @@ pub struct Scene {
pub struct Layer {
clip_bounds: Option<RectF>,
quads: Vec<Quad>,
underlines: Vec<Quad>,
shadows: Vec<Shadow>,
glyphs: Vec<Glyph>,
icons: Vec<Icon>,
@@ -120,10 +126,11 @@ pub struct PathVertex {
impl Scene {
pub fn new(scale_factor: f32) -> Self {
let stacking_context = StackingContext::new(None);
Scene {
scale_factor,
layers: vec![Layer::new(None)],
active_layer_stack: vec![0],
stacking_contexts: vec![stacking_context],
active_stacking_context_stack: vec![0],
}
}
@@ -131,25 +138,38 @@ impl Scene {
self.scale_factor
}
pub fn layers(&self) -> &[Layer] {
self.layers.as_slice()
pub fn layers(&self) -> impl Iterator<Item = &Layer> {
self.stacking_contexts.iter().flat_map(|s| &s.layers)
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context_stack
.push(self.stacking_contexts.len());
self.stacking_contexts
.push(StackingContext::new(clip_bounds))
}
pub fn pop_stacking_context(&mut self) {
self.active_stacking_context_stack.pop();
assert!(!self.active_stacking_context_stack.is_empty());
}
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
let ix = self.layers.len();
self.layers.push(Layer::new(clip_bounds));
self.active_layer_stack.push(ix);
self.active_stacking_context().push_layer(clip_bounds);
}
pub fn pop_layer(&mut self) {
assert!(self.active_layer_stack.len() > 1);
self.active_layer_stack.pop();
self.active_stacking_context().pop_layer();
}
pub fn push_quad(&mut self, quad: Quad) {
self.active_layer().push_quad(quad)
}
pub fn push_underline(&mut self, underline: Quad) {
self.active_layer().push_underline(underline)
}
pub fn push_shadow(&mut self, shadow: Shadow) {
self.active_layer().push_shadow(shadow)
}
@@ -166,9 +186,52 @@ impl Scene {
self.active_layer().push_path(path);
}
fn active_stacking_context(&mut self) -> &mut StackingContext {
let ix = *self.active_stacking_context_stack.last().unwrap();
&mut self.stacking_contexts[ix]
}
fn active_layer(&mut self) -> &mut Layer {
self.active_stacking_context().active_layer()
}
}
impl StackingContext {
fn new(clip_bounds: Option<RectF>) -> Self {
Self {
layers: vec![Layer::new(clip_bounds)],
active_layer_stack: vec![0],
}
}
fn active_layer(&mut self) -> &mut Layer {
&mut self.layers[*self.active_layer_stack.last().unwrap()]
}
fn push_layer(&mut self, clip_bounds: Option<RectF>) {
let parent_clip_bounds = self.active_layer().clip_bounds();
let clip_bounds = clip_bounds
.map(|clip_bounds| {
clip_bounds
.intersection(parent_clip_bounds.unwrap_or(clip_bounds))
.unwrap_or_else(|| {
if !clip_bounds.is_empty() {
log::warn!("specified clip bounds are disjoint from parent layer");
}
RectF::default()
})
})
.or(parent_clip_bounds);
let ix = self.layers.len();
self.layers.push(Layer::new(clip_bounds));
self.active_layer_stack.push(ix);
}
fn pop_layer(&mut self) {
self.active_layer_stack.pop().unwrap();
assert!(!self.active_layer_stack.is_empty());
}
}
impl Layer {
@@ -176,6 +239,7 @@ impl Layer {
Self {
clip_bounds,
quads: Vec::new(),
underlines: Vec::new(),
shadows: Vec::new(),
glyphs: Vec::new(),
icons: Vec::new(),
@@ -195,6 +259,14 @@ impl Layer {
self.quads.as_slice()
}
fn push_underline(&mut self, underline: Quad) {
self.underlines.push(underline);
}
pub fn underlines(&self) -> &[Quad] {
self.underlines.as_slice()
}
fn push_shadow(&mut self, shadow: Shadow) {
self.shadows.push(shadow);
}
+995
View File
@@ -0,0 +1,995 @@
mod cursor;
use arrayvec::ArrayVec;
pub use cursor::Cursor;
pub use cursor::FilterCursor;
use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc};
#[cfg(test)]
const TREE_BASE: usize = 2;
#[cfg(not(test))]
const TREE_BASE: usize = 6;
pub trait Item: Clone {
type Summary: Summary;
fn summary(&self) -> Self::Summary;
}
pub trait KeyedItem: Item {
type Key: for<'a> Dimension<'a, Self::Summary> + Ord;
fn key(&self) -> Self::Key;
}
pub trait Summary: Default + Clone + fmt::Debug {
type Context;
fn add_summary(&mut self, summary: &Self, cx: &Self::Context);
}
pub trait Dimension<'a, S: Summary>: Clone + fmt::Debug + Default {
fn add_summary(&mut self, _summary: &'a S, _: &S::Context);
}
impl<'a, T: Summary> Dimension<'a, T> for () {
fn add_summary(&mut self, _: &'a T, _: &T::Context) {}
}
pub trait SeekDimension<'a, T: Summary>: Dimension<'a, T> {
fn cmp(&self, other: &Self, cx: &T::Context) -> Ordering;
}
impl<'a, S: Summary, T: Dimension<'a, S> + Ord> SeekDimension<'a, S> for T {
fn cmp(&self, other: &Self, _ctx: &S::Context) -> Ordering {
Ord::cmp(self, other)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub enum Bias {
Left,
Right,
}
impl PartialOrd for Bias {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Bias {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Left, Self::Left) => Ordering::Equal,
(Self::Left, Self::Right) => Ordering::Less,
(Self::Right, Self::Right) => Ordering::Equal,
(Self::Right, Self::Left) => Ordering::Greater,
}
}
}
#[derive(Debug, Clone)]
pub struct SumTree<T: Item>(Arc<Node<T>>);
impl<T: Item> SumTree<T> {
pub fn new() -> Self {
SumTree(Arc::new(Node::Leaf {
summary: T::Summary::default(),
items: ArrayVec::new(),
item_summaries: ArrayVec::new(),
}))
}
pub fn from_item(item: T, cx: &<T::Summary as Summary>::Context) -> Self {
let mut tree = Self::new();
tree.push(item, cx);
tree
}
pub fn from_iter<I: IntoIterator<Item = T>>(
iter: I,
cx: &<T::Summary as Summary>::Context,
) -> Self {
let mut tree = Self::new();
tree.extend(iter, cx);
tree
}
#[allow(unused)]
pub fn items(&self, cx: &<T::Summary as Summary>::Context) -> Vec<T> {
let mut items = Vec::new();
let mut cursor = self.cursor::<(), ()>();
cursor.next(cx);
while let Some(item) = cursor.item() {
items.push(item.clone());
cursor.next(cx);
}
items
}
pub fn cursor<'a, S, U>(&'a self) -> Cursor<T, S, U>
where
S: Dimension<'a, T::Summary>,
U: Dimension<'a, T::Summary>,
{
Cursor::new(self)
}
pub fn filter<'a, F, U>(
&'a self,
filter_node: F,
cx: &<T::Summary as Summary>::Context,
) -> FilterCursor<F, T, U>
where
F: Fn(&T::Summary) -> bool,
U: Dimension<'a, T::Summary>,
{
FilterCursor::new(self, filter_node, cx)
}
#[allow(dead_code)]
pub fn first(&self) -> Option<&T> {
self.leftmost_leaf().0.items().first()
}
pub fn last(&self) -> Option<&T> {
self.rightmost_leaf().0.items().last()
}
pub fn update_last(&mut self, f: impl FnOnce(&mut T), cx: &<T::Summary as Summary>::Context) {
self.update_last_recursive(f, cx);
}
fn update_last_recursive(
&mut self,
f: impl FnOnce(&mut T),
cx: &<T::Summary as Summary>::Context,
) -> Option<T::Summary> {
match Arc::make_mut(&mut self.0) {
Node::Internal {
summary,
child_summaries,
child_trees,
..
} => {
let last_summary = child_summaries.last_mut().unwrap();
let last_child = child_trees.last_mut().unwrap();
*last_summary = last_child.update_last_recursive(f, cx).unwrap();
*summary = sum(child_summaries.iter(), cx);
Some(summary.clone())
}
Node::Leaf {
summary,
items,
item_summaries,
} => {
if let Some((item, item_summary)) = items.last_mut().zip(item_summaries.last_mut())
{
(f)(item);
*item_summary = item.summary();
*summary = sum(item_summaries.iter(), cx);
Some(summary.clone())
} else {
None
}
}
}
}
pub fn extent<'a, D: Dimension<'a, T::Summary>>(
&'a self,
cx: &<T::Summary as Summary>::Context,
) -> D {
let mut extent = D::default();
match self.0.as_ref() {
Node::Internal { summary, .. } | Node::Leaf { summary, .. } => {
extent.add_summary(summary, cx);
}
}
extent
}
pub fn summary(&self) -> T::Summary {
match self.0.as_ref() {
Node::Internal { summary, .. } => summary.clone(),
Node::Leaf { summary, .. } => summary.clone(),
}
}
pub fn is_empty(&self) -> bool {
match self.0.as_ref() {
Node::Internal { .. } => false,
Node::Leaf { items, .. } => items.is_empty(),
}
}
pub fn extend<I>(&mut self, iter: I, cx: &<T::Summary as Summary>::Context)
where
I: IntoIterator<Item = T>,
{
let mut leaf: Option<Node<T>> = None;
for item in iter {
if leaf.is_some() && leaf.as_ref().unwrap().items().len() == 2 * TREE_BASE {
self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), cx);
}
if leaf.is_none() {
leaf = Some(Node::Leaf::<T> {
summary: T::Summary::default(),
items: ArrayVec::new(),
item_summaries: ArrayVec::new(),
});
}
if let Some(Node::Leaf {
summary,
items,
item_summaries,
}) = leaf.as_mut()
{
let item_summary = item.summary();
summary.add_summary(&item_summary, cx);
items.push(item);
item_summaries.push(item_summary);
} else {
unreachable!()
}
}
if leaf.is_some() {
self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), cx);
}
}
pub fn push(&mut self, item: T, cx: &<T::Summary as Summary>::Context) {
let summary = item.summary();
self.push_tree(
SumTree(Arc::new(Node::Leaf {
summary: summary.clone(),
items: ArrayVec::from_iter(Some(item)),
item_summaries: ArrayVec::from_iter(Some(summary)),
})),
cx,
);
}
pub fn push_tree(&mut self, other: Self, cx: &<T::Summary as Summary>::Context) {
if !other.0.is_leaf() || other.0.items().len() > 0 {
if self.0.height() < other.0.height() {
for tree in other.0.child_trees() {
self.push_tree(tree.clone(), cx);
}
} else if let Some(split_tree) = self.push_tree_recursive(other, cx) {
*self = Self::from_child_trees(self.clone(), split_tree, cx);
}
}
}
fn push_tree_recursive(
&mut self,
other: SumTree<T>,
cx: &<T::Summary as Summary>::Context,
) -> Option<SumTree<T>> {
match Arc::make_mut(&mut self.0) {
Node::Internal {
height,
summary,
child_summaries,
child_trees,
..
} => {
let other_node = other.0.clone();
summary.add_summary(other_node.summary(), cx);
let height_delta = *height - other_node.height();
let mut summaries_to_append = ArrayVec::<T::Summary, { 2 * TREE_BASE }>::new();
let mut trees_to_append = ArrayVec::<SumTree<T>, { 2 * TREE_BASE }>::new();
if height_delta == 0 {
summaries_to_append.extend(other_node.child_summaries().iter().cloned());
trees_to_append.extend(other_node.child_trees().iter().cloned());
} else if height_delta == 1 && !other_node.is_underflowing() {
summaries_to_append.push(other_node.summary().clone());
trees_to_append.push(other)
} else {
let tree_to_append = child_trees
.last_mut()
.unwrap()
.push_tree_recursive(other, cx);
*child_summaries.last_mut().unwrap() =
child_trees.last().unwrap().0.summary().clone();
if let Some(split_tree) = tree_to_append {
summaries_to_append.push(split_tree.0.summary().clone());
trees_to_append.push(split_tree);
}
}
let child_count = child_trees.len() + trees_to_append.len();
if child_count > 2 * TREE_BASE {
let left_summaries: ArrayVec<_, { 2 * TREE_BASE }>;
let right_summaries: ArrayVec<_, { 2 * TREE_BASE }>;
let left_trees;
let right_trees;
let midpoint = (child_count + child_count % 2) / 2;
{
let mut all_summaries = child_summaries
.iter()
.chain(summaries_to_append.iter())
.cloned();
left_summaries = all_summaries.by_ref().take(midpoint).collect();
right_summaries = all_summaries.collect();
let mut all_trees =
child_trees.iter().chain(trees_to_append.iter()).cloned();
left_trees = all_trees.by_ref().take(midpoint).collect();
right_trees = all_trees.collect();
}
*summary = sum(left_summaries.iter(), cx);
*child_summaries = left_summaries;
*child_trees = left_trees;
Some(SumTree(Arc::new(Node::Internal {
height: *height,
summary: sum(right_summaries.iter(), cx),
child_summaries: right_summaries,
child_trees: right_trees,
})))
} else {
child_summaries.extend(summaries_to_append);
child_trees.extend(trees_to_append);
None
}
}
Node::Leaf {
summary,
items,
item_summaries,
} => {
let other_node = other.0;
let child_count = items.len() + other_node.items().len();
if child_count > 2 * TREE_BASE {
let left_items;
let right_items;
let left_summaries;
let right_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>;
let midpoint = (child_count + child_count % 2) / 2;
{
let mut all_items = items.iter().chain(other_node.items().iter()).cloned();
left_items = all_items.by_ref().take(midpoint).collect();
right_items = all_items.collect();
let mut all_summaries = item_summaries
.iter()
.chain(other_node.child_summaries())
.cloned();
left_summaries = all_summaries.by_ref().take(midpoint).collect();
right_summaries = all_summaries.collect();
}
*items = left_items;
*item_summaries = left_summaries;
*summary = sum(item_summaries.iter(), cx);
Some(SumTree(Arc::new(Node::Leaf {
items: right_items,
summary: sum(right_summaries.iter(), cx),
item_summaries: right_summaries,
})))
} else {
summary.add_summary(other_node.summary(), cx);
items.extend(other_node.items().iter().cloned());
item_summaries.extend(other_node.child_summaries().iter().cloned());
None
}
}
}
}
fn from_child_trees(
left: SumTree<T>,
right: SumTree<T>,
cx: &<T::Summary as Summary>::Context,
) -> Self {
let height = left.0.height() + 1;
let mut child_summaries = ArrayVec::new();
child_summaries.push(left.0.summary().clone());
child_summaries.push(right.0.summary().clone());
let mut child_trees = ArrayVec::new();
child_trees.push(left);
child_trees.push(right);
SumTree(Arc::new(Node::Internal {
height,
summary: sum(child_summaries.iter(), cx),
child_summaries,
child_trees,
}))
}
fn leftmost_leaf(&self) -> &Self {
match *self.0 {
Node::Leaf { .. } => self,
Node::Internal {
ref child_trees, ..
} => child_trees.first().unwrap().leftmost_leaf(),
}
}
fn rightmost_leaf(&self) -> &Self {
match *self.0 {
Node::Leaf { .. } => self,
Node::Internal {
ref child_trees, ..
} => child_trees.last().unwrap().rightmost_leaf(),
}
}
}
impl<T: KeyedItem> SumTree<T> {
pub fn insert_or_replace(&mut self, item: T, cx: &<T::Summary as Summary>::Context) -> bool {
let mut replaced = false;
*self = {
let mut cursor = self.cursor::<T::Key, ()>();
let mut new_tree = cursor.slice(&item.key(), Bias::Left, cx);
if cursor
.item()
.map_or(false, |cursor_item| cursor_item.key() == item.key())
{
cursor.next(cx);
replaced = true;
}
new_tree.push(item, cx);
new_tree.push_tree(cursor.suffix(cx), cx);
new_tree
};
replaced
}
pub fn edit(
&mut self,
mut edits: Vec<Edit<T>>,
cx: &<T::Summary as Summary>::Context,
) -> Vec<T> {
if edits.is_empty() {
return Vec::new();
}
let mut removed = Vec::new();
edits.sort_unstable_by_key(|item| item.key());
*self = {
let mut cursor = self.cursor::<T::Key, ()>();
let mut new_tree = SumTree::new();
let mut buffered_items = Vec::new();
cursor.seek(&T::Key::default(), Bias::Left, cx);
for edit in edits {
let new_key = edit.key();
let mut old_item = cursor.item();
if old_item
.as_ref()
.map_or(false, |old_item| old_item.key() < new_key)
{
new_tree.extend(buffered_items.drain(..), cx);
let slice = cursor.slice(&new_key, Bias::Left, cx);
new_tree.push_tree(slice, cx);
old_item = cursor.item();
}
if let Some(old_item) = old_item {
if old_item.key() == new_key {
removed.push(old_item.clone());
cursor.next(cx);
}
}
match edit {
Edit::Insert(item) => {
buffered_items.push(item);
}
Edit::Remove(_) => {}
}
}
new_tree.extend(buffered_items, cx);
new_tree.push_tree(cursor.suffix(cx), cx);
new_tree
};
removed
}
pub fn get(&self, key: &T::Key, cx: &<T::Summary as Summary>::Context) -> Option<&T> {
let mut cursor = self.cursor::<T::Key, ()>();
if cursor.seek(key, Bias::Left, cx) {
cursor.item()
} else {
None
}
}
}
impl<T: Item> Default for SumTree<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub enum Node<T: Item> {
Internal {
height: u8,
summary: T::Summary,
child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }>,
},
Leaf {
summary: T::Summary,
items: ArrayVec<T, { 2 * TREE_BASE }>,
item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
},
}
impl<T: Item> Node<T> {
fn is_leaf(&self) -> bool {
match self {
Node::Leaf { .. } => true,
_ => false,
}
}
fn height(&self) -> u8 {
match self {
Node::Internal { height, .. } => *height,
Node::Leaf { .. } => 0,
}
}
fn summary(&self) -> &T::Summary {
match self {
Node::Internal { summary, .. } => summary,
Node::Leaf { summary, .. } => summary,
}
}
fn child_summaries(&self) -> &[T::Summary] {
match self {
Node::Internal {
child_summaries, ..
} => child_summaries.as_slice(),
Node::Leaf { item_summaries, .. } => item_summaries.as_slice(),
}
}
fn child_trees(&self) -> &ArrayVec<SumTree<T>, { 2 * TREE_BASE }> {
match self {
Node::Internal { child_trees, .. } => child_trees,
Node::Leaf { .. } => panic!("Leaf nodes have no child trees"),
}
}
fn items(&self) -> &ArrayVec<T, { 2 * TREE_BASE }> {
match self {
Node::Leaf { items, .. } => items,
Node::Internal { .. } => panic!("Internal nodes have no items"),
}
}
fn is_underflowing(&self) -> bool {
match self {
Node::Internal { child_trees, .. } => child_trees.len() < TREE_BASE,
Node::Leaf { items, .. } => items.len() < TREE_BASE,
}
}
}
#[derive(Debug)]
pub enum Edit<T: KeyedItem> {
Insert(T),
Remove(T::Key),
}
impl<T: KeyedItem> Edit<T> {
fn key(&self) -> T::Key {
match self {
Edit::Insert(item) => item.key(),
Edit::Remove(key) => key.clone(),
}
}
}
fn sum<'a, T, I>(iter: I, cx: &T::Context) -> T
where
T: 'a + Summary,
I: Iterator<Item = &'a T>,
{
let mut sum = T::default();
for value in iter {
sum.add_summary(value, cx);
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{distributions, prelude::*};
use std::cmp;
use std::ops::Add;
#[test]
fn test_extend_and_push_tree() {
let mut tree1 = SumTree::new();
tree1.extend(0..20, &());
let mut tree2 = SumTree::new();
tree2.extend(50..100, &());
tree1.push_tree(tree2, &());
assert_eq!(
tree1.items(&()),
(0..20).chain(50..100).collect::<Vec<u8>>()
);
}
#[crate::test(self, iterations = 100)]
fn test_random(mut rng: StdRng) {
let rng = &mut rng;
let mut tree = SumTree::<u8>::new();
let count = rng.gen_range(0..10);
tree.extend(rng.sample_iter(distributions::Standard).take(count), &());
for _ in 0..5 {
let splice_end = rng.gen_range(0..tree.extent::<Count>(&()).0 + 1);
let splice_start = rng.gen_range(0..splice_end + 1);
let count = rng.gen_range(0..3);
let tree_end = tree.extent::<Count>(&());
let new_items = rng
.sample_iter(distributions::Standard)
.take(count)
.collect::<Vec<u8>>();
let mut reference_items = tree.items(&());
reference_items.splice(splice_start..splice_end, new_items.clone());
tree = {
let mut cursor = tree.cursor::<Count, ()>();
let mut new_tree = cursor.slice(&Count(splice_start), Bias::Right, &());
new_tree.extend(new_items, &());
cursor.seek(&Count(splice_end), Bias::Right, &());
new_tree.push_tree(cursor.slice(&tree_end, Bias::Right, &()), &());
new_tree
};
assert_eq!(tree.items(&()), reference_items);
let mut filter_cursor = tree.filter::<_, Count>(|summary| summary.contains_even, &());
let mut reference_filter = tree
.items(&())
.into_iter()
.enumerate()
.filter(|(_, item)| (item & 1) == 0);
while let Some(actual_item) = filter_cursor.item() {
let (reference_index, reference_item) = reference_filter.next().unwrap();
assert_eq!(actual_item, &reference_item);
assert_eq!(filter_cursor.start().0, reference_index);
filter_cursor.next(&());
}
assert!(reference_filter.next().is_none());
let mut pos = rng.gen_range(0..tree.extent::<Count>(&()).0 + 1);
let mut before_start = false;
let mut cursor = tree.cursor::<Count, Count>();
cursor.seek(&Count(pos), Bias::Right, &());
for i in 0..10 {
assert_eq!(cursor.sum_start().0, pos);
if pos > 0 {
assert_eq!(cursor.prev_item().unwrap(), &reference_items[pos - 1]);
} else {
assert_eq!(cursor.prev_item(), None);
}
if pos < reference_items.len() && !before_start {
assert_eq!(cursor.item().unwrap(), &reference_items[pos]);
} else {
assert_eq!(cursor.item(), None);
}
if i < 5 {
cursor.next(&());
if pos < reference_items.len() {
pos += 1;
before_start = false;
}
} else {
cursor.prev(&());
if pos == 0 {
before_start = true;
}
pos = pos.saturating_sub(1);
}
}
}
for _ in 0..10 {
let end = rng.gen_range(0..tree.extent::<Count>(&()).0 + 1);
let start = rng.gen_range(0..end + 1);
let start_bias = if rng.gen() { Bias::Left } else { Bias::Right };
let end_bias = if rng.gen() { Bias::Left } else { Bias::Right };
let mut cursor = tree.cursor::<Count, ()>();
cursor.seek(&Count(start), start_bias, &());
let slice = cursor.slice(&Count(end), end_bias, &());
cursor.seek(&Count(start), start_bias, &());
let summary = cursor.summary::<Sum>(&Count(end), end_bias, &());
assert_eq!(summary, slice.summary().sum);
}
}
#[test]
fn test_cursor() {
// Empty tree
let tree = SumTree::<u8>::new();
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(
cursor.slice(&Count(0), Bias::Right, &()).items(&()),
Vec::<u8>::new()
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.sum_start(), &Sum(0));
// Single-element tree
let mut tree = SumTree::<u8>::new();
tree.extend(vec![1], &());
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(
cursor.slice(&Count(0), Bias::Right, &()).items(&()),
Vec::<u8>::new()
);
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.sum_start(), &Sum(0));
cursor.next(&());
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.sum_start(), &Sum(1));
cursor.prev(&());
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.sum_start(), &Sum(0));
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(cursor.slice(&Count(1), Bias::Right, &()).items(&()), [1]);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.sum_start(), &Sum(1));
cursor.seek(&Count(0), Bias::Right, &());
assert_eq!(
cursor
.slice(&tree.extent::<Count>(&()), Bias::Right, &())
.items(&()),
[1]
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.sum_start(), &Sum(1));
// Multiple-element tree
let mut tree = SumTree::new();
tree.extend(vec![1, 2, 3, 4, 5, 6], &());
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(cursor.slice(&Count(2), Bias::Right, &()).items(&()), [1, 2]);
assert_eq!(cursor.item(), Some(&3));
assert_eq!(cursor.prev_item(), Some(&2));
assert_eq!(cursor.sum_start(), &Sum(3));
cursor.next(&());
assert_eq!(cursor.item(), Some(&4));
assert_eq!(cursor.prev_item(), Some(&3));
assert_eq!(cursor.sum_start(), &Sum(6));
cursor.next(&());
assert_eq!(cursor.item(), Some(&5));
assert_eq!(cursor.prev_item(), Some(&4));
assert_eq!(cursor.sum_start(), &Sum(10));
cursor.next(&());
assert_eq!(cursor.item(), Some(&6));
assert_eq!(cursor.prev_item(), Some(&5));
assert_eq!(cursor.sum_start(), &Sum(15));
cursor.next(&());
cursor.next(&());
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&6));
assert_eq!(cursor.sum_start(), &Sum(21));
cursor.prev(&());
assert_eq!(cursor.item(), Some(&6));
assert_eq!(cursor.prev_item(), Some(&5));
assert_eq!(cursor.sum_start(), &Sum(15));
cursor.prev(&());
assert_eq!(cursor.item(), Some(&5));
assert_eq!(cursor.prev_item(), Some(&4));
assert_eq!(cursor.sum_start(), &Sum(10));
cursor.prev(&());
assert_eq!(cursor.item(), Some(&4));
assert_eq!(cursor.prev_item(), Some(&3));
assert_eq!(cursor.sum_start(), &Sum(6));
cursor.prev(&());
assert_eq!(cursor.item(), Some(&3));
assert_eq!(cursor.prev_item(), Some(&2));
assert_eq!(cursor.sum_start(), &Sum(3));
cursor.prev(&());
assert_eq!(cursor.item(), Some(&2));
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.sum_start(), &Sum(1));
cursor.prev(&());
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.sum_start(), &Sum(0));
cursor.prev(&());
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.sum_start(), &Sum(0));
cursor.next(&());
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.sum_start(), &Sum(0));
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(
cursor
.slice(&tree.extent::<Count>(&()), Bias::Right, &())
.items(&()),
tree.items(&())
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&6));
assert_eq!(cursor.sum_start(), &Sum(21));
cursor.seek(&Count(3), Bias::Right, &());
assert_eq!(
cursor
.slice(&tree.extent::<Count>(&()), Bias::Right, &())
.items(&()),
[4, 5, 6]
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&6));
assert_eq!(cursor.sum_start(), &Sum(21));
// Seeking can bias left or right
cursor.seek(&Count(1), Bias::Left, &());
assert_eq!(cursor.item(), Some(&1));
cursor.seek(&Count(1), Bias::Right, &());
assert_eq!(cursor.item(), Some(&2));
// Slicing without resetting starts from where the cursor is parked at.
cursor.seek(&Count(1), Bias::Right, &());
assert_eq!(
cursor.slice(&Count(3), Bias::Right, &()).items(&()),
vec![2, 3]
);
assert_eq!(
cursor.slice(&Count(6), Bias::Left, &()).items(&()),
vec![4, 5]
);
assert_eq!(
cursor.slice(&Count(6), Bias::Right, &()).items(&()),
vec![6]
);
}
#[test]
fn test_edit() {
let mut tree = SumTree::<u8>::new();
let removed = tree.edit(vec![Edit::Insert(1), Edit::Insert(2), Edit::Insert(0)], &());
assert_eq!(tree.items(&()), vec![0, 1, 2]);
assert_eq!(removed, Vec::<u8>::new());
assert_eq!(tree.get(&0, &()), Some(&0));
assert_eq!(tree.get(&1, &()), Some(&1));
assert_eq!(tree.get(&2, &()), Some(&2));
assert_eq!(tree.get(&4, &()), None);
let removed = tree.edit(vec![Edit::Insert(2), Edit::Insert(4), Edit::Remove(0)], &());
assert_eq!(tree.items(&()), vec![1, 2, 4]);
assert_eq!(removed, vec![0, 2]);
assert_eq!(tree.get(&0, &()), None);
assert_eq!(tree.get(&1, &()), Some(&1));
assert_eq!(tree.get(&2, &()), Some(&2));
assert_eq!(tree.get(&4, &()), Some(&4));
}
#[derive(Clone, Default, Debug)]
pub struct IntegersSummary {
count: Count,
sum: Sum,
contains_even: bool,
max: u8,
}
#[derive(Ord, PartialOrd, Default, Eq, PartialEq, Clone, Debug)]
struct Count(usize);
#[derive(Ord, PartialOrd, Default, Eq, PartialEq, Clone, Debug)]
struct Sum(usize);
impl Item for u8 {
type Summary = IntegersSummary;
fn summary(&self) -> Self::Summary {
IntegersSummary {
count: Count(1),
sum: Sum(*self as usize),
contains_even: (*self & 1) == 0,
max: *self,
}
}
}
impl KeyedItem for u8 {
type Key = u8;
fn key(&self) -> Self::Key {
*self
}
}
impl Summary for IntegersSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
self.count.0 += &other.count.0;
self.sum.0 += &other.sum.0;
self.contains_even |= other.contains_even;
self.max = cmp::max(self.max, other.max);
}
}
impl<'a> Dimension<'a, IntegersSummary> for u8 {
fn add_summary(&mut self, summary: &IntegersSummary, _: &()) {
*self = summary.max;
}
}
impl<'a> Dimension<'a, IntegersSummary> for Count {
fn add_summary(&mut self, summary: &IntegersSummary, _: &()) {
self.0 += summary.count.0;
}
}
impl<'a> Dimension<'a, IntegersSummary> for Sum {
fn add_summary(&mut self, summary: &IntegersSummary, _: &()) {
self.0 += summary.sum.0;
}
}
impl<'a> Add<&'a Self> for Sum {
type Output = Self;
fn add(mut self, other: &Self) -> Self {
self.0 += other.0;
self
}
}
}
+702
View File
@@ -0,0 +1,702 @@
use super::*;
use arrayvec::ArrayVec;
use std::{cmp::Ordering, sync::Arc};
#[derive(Clone)]
struct StackEntry<'a, T: Item, S, U> {
tree: &'a SumTree<T>,
index: usize,
seek_dimension: S,
sum_dimension: U,
}
impl<'a, T, S, U> StackEntry<'a, T, S, U>
where
T: Item,
S: SeekDimension<'a, T::Summary>,
U: SeekDimension<'a, T::Summary>,
{
fn swap_dimensions(self) -> StackEntry<'a, T, U, S> {
StackEntry {
tree: self.tree,
index: self.index,
seek_dimension: self.sum_dimension,
sum_dimension: self.seek_dimension,
}
}
}
#[derive(Clone)]
pub struct Cursor<'a, T: Item, S, U> {
tree: &'a SumTree<T>,
stack: ArrayVec<StackEntry<'a, T, S, U>, 16>,
seek_dimension: S,
sum_dimension: U,
did_seek: bool,
at_end: bool,
}
impl<'a, T, S, U> Cursor<'a, T, S, U>
where
T: Item,
S: Dimension<'a, T::Summary>,
U: Dimension<'a, T::Summary>,
{
pub fn new(tree: &'a SumTree<T>) -> Self {
Self {
tree,
stack: ArrayVec::new(),
seek_dimension: S::default(),
sum_dimension: U::default(),
did_seek: false,
at_end: false,
}
}
fn reset(&mut self) {
self.did_seek = false;
self.at_end = false;
self.stack.truncate(0);
self.seek_dimension = S::default();
self.sum_dimension = U::default();
}
pub fn seek_start(&self) -> &S {
&self.seek_dimension
}
pub fn seek_end(&self, cx: &<T::Summary as Summary>::Context) -> S {
if let Some(item_summary) = self.item_summary() {
let mut end = self.seek_start().clone();
end.add_summary(item_summary, cx);
end
} else {
self.seek_start().clone()
}
}
pub fn sum_start(&self) -> &U {
&self.sum_dimension
}
pub fn sum_end(&self, cx: &<T::Summary as Summary>::Context) -> U {
if let Some(item_summary) = self.item_summary() {
let mut end = self.sum_start().clone();
end.add_summary(item_summary, cx);
end
} else {
self.sum_start().clone()
}
}
pub fn item(&self) -> Option<&'a T> {
assert!(self.did_seek, "Must seek before calling this method");
if let Some(entry) = self.stack.last() {
match *entry.tree.0 {
Node::Leaf { ref items, .. } => {
if entry.index == items.len() {
None
} else {
Some(&items[entry.index])
}
}
_ => unreachable!(),
}
} else {
None
}
}
pub fn item_summary(&self) -> Option<&'a T::Summary> {
assert!(self.did_seek, "Must seek before calling this method");
if let Some(entry) = self.stack.last() {
match *entry.tree.0 {
Node::Leaf {
ref item_summaries, ..
} => {
if entry.index == item_summaries.len() {
None
} else {
Some(&item_summaries[entry.index])
}
}
_ => unreachable!(),
}
} else {
None
}
}
pub fn prev_item(&self) -> Option<&'a T> {
assert!(self.did_seek, "Must seek before calling this method");
if let Some(entry) = self.stack.last() {
if entry.index == 0 {
if let Some(prev_leaf) = self.prev_leaf() {
Some(prev_leaf.0.items().last().unwrap())
} else {
None
}
} else {
match *entry.tree.0 {
Node::Leaf { ref items, .. } => Some(&items[entry.index - 1]),
_ => unreachable!(),
}
}
} else if self.at_end {
self.tree.last()
} else {
None
}
}
fn prev_leaf(&self) -> Option<&'a SumTree<T>> {
for entry in self.stack.iter().rev().skip(1) {
if entry.index != 0 {
match *entry.tree.0 {
Node::Internal {
ref child_trees, ..
} => return Some(child_trees[entry.index - 1].rightmost_leaf()),
Node::Leaf { .. } => unreachable!(),
};
}
}
None
}
pub fn prev(&mut self, cx: &<T::Summary as Summary>::Context) {
assert!(self.did_seek, "Must seek before calling this method");
if self.at_end {
self.seek_dimension = S::default();
self.sum_dimension = U::default();
self.descend_to_last_item(self.tree, cx);
self.at_end = false;
} else {
while let Some(entry) = self.stack.pop() {
if entry.index > 0 {
let new_index = entry.index - 1;
if let Some(StackEntry {
seek_dimension,
sum_dimension,
..
}) = self.stack.last()
{
self.seek_dimension = seek_dimension.clone();
self.sum_dimension = sum_dimension.clone();
} else {
self.seek_dimension = S::default();
self.sum_dimension = U::default();
}
match entry.tree.0.as_ref() {
Node::Internal {
child_trees,
child_summaries,
..
} => {
for summary in &child_summaries[0..new_index] {
self.seek_dimension.add_summary(summary, cx);
self.sum_dimension.add_summary(summary, cx);
}
self.stack.push(StackEntry {
tree: entry.tree,
index: new_index,
seek_dimension: self.seek_dimension.clone(),
sum_dimension: self.sum_dimension.clone(),
});
self.descend_to_last_item(&child_trees[new_index], cx);
}
Node::Leaf { item_summaries, .. } => {
for item_summary in &item_summaries[0..new_index] {
self.seek_dimension.add_summary(item_summary, cx);
self.sum_dimension.add_summary(item_summary, cx);
}
self.stack.push(StackEntry {
tree: entry.tree,
index: new_index,
seek_dimension: self.seek_dimension.clone(),
sum_dimension: self.sum_dimension.clone(),
});
}
}
break;
}
}
}
}
pub fn next(&mut self, cx: &<T::Summary as Summary>::Context) {
self.next_internal(|_| true, cx)
}
fn next_internal<F>(&mut self, filter_node: F, cx: &<T::Summary as Summary>::Context)
where
F: Fn(&T::Summary) -> bool,
{
let mut descend = false;
if self.stack.is_empty() && !self.at_end {
self.stack.push(StackEntry {
tree: self.tree,
index: 0,
seek_dimension: S::default(),
sum_dimension: U::default(),
});
descend = true;
self.did_seek = true;
}
while self.stack.len() > 0 {
let new_subtree = {
let entry = self.stack.last_mut().unwrap();
match entry.tree.0.as_ref() {
Node::Internal {
child_trees,
child_summaries,
..
} => {
if !descend {
entry.seek_dimension = self.seek_dimension.clone();
entry.sum_dimension = self.sum_dimension.clone();
entry.index += 1;
}
while entry.index < child_summaries.len() {
let next_summary = &child_summaries[entry.index];
if filter_node(next_summary) {
break;
} else {
self.seek_dimension.add_summary(next_summary, cx);
self.sum_dimension.add_summary(next_summary, cx);
}
entry.index += 1;
}
child_trees.get(entry.index)
}
Node::Leaf { item_summaries, .. } => {
if !descend {
let item_summary = &item_summaries[entry.index];
self.seek_dimension.add_summary(item_summary, cx);
entry.seek_dimension.add_summary(item_summary, cx);
self.sum_dimension.add_summary(item_summary, cx);
entry.sum_dimension.add_summary(item_summary, cx);
entry.index += 1;
}
loop {
if let Some(next_item_summary) = item_summaries.get(entry.index) {
if filter_node(next_item_summary) {
return;
} else {
self.seek_dimension.add_summary(next_item_summary, cx);
entry.seek_dimension.add_summary(next_item_summary, cx);
self.sum_dimension.add_summary(next_item_summary, cx);
entry.sum_dimension.add_summary(next_item_summary, cx);
entry.index += 1;
}
} else {
break None;
}
}
}
}
};
if let Some(subtree) = new_subtree {
descend = true;
self.stack.push(StackEntry {
tree: subtree,
index: 0,
seek_dimension: self.seek_dimension.clone(),
sum_dimension: self.sum_dimension.clone(),
});
} else {
descend = false;
self.stack.pop();
}
}
self.at_end = self.stack.is_empty();
debug_assert!(self.stack.is_empty() || self.stack.last().unwrap().tree.0.is_leaf());
}
fn descend_to_last_item(
&mut self,
mut subtree: &'a SumTree<T>,
cx: &<T::Summary as Summary>::Context,
) {
self.did_seek = true;
loop {
match subtree.0.as_ref() {
Node::Internal {
child_trees,
child_summaries,
..
} => {
for summary in &child_summaries[0..child_summaries.len() - 1] {
self.seek_dimension.add_summary(summary, cx);
self.sum_dimension.add_summary(summary, cx);
}
self.stack.push(StackEntry {
tree: subtree,
index: child_trees.len() - 1,
seek_dimension: self.seek_dimension.clone(),
sum_dimension: self.sum_dimension.clone(),
});
subtree = child_trees.last().unwrap();
}
Node::Leaf { item_summaries, .. } => {
let last_index = item_summaries.len().saturating_sub(1);
for item_summary in &item_summaries[0..last_index] {
self.seek_dimension.add_summary(item_summary, cx);
self.sum_dimension.add_summary(item_summary, cx);
}
self.stack.push(StackEntry {
tree: subtree,
index: last_index,
seek_dimension: self.seek_dimension.clone(),
sum_dimension: self.sum_dimension.clone(),
});
break;
}
}
}
}
}
impl<'a, T, S, U> Cursor<'a, T, S, U>
where
T: Item,
S: SeekDimension<'a, T::Summary>,
U: Dimension<'a, T::Summary>,
{
pub fn seek(&mut self, pos: &S, bias: Bias, cx: &<T::Summary as Summary>::Context) -> bool {
self.reset();
self.seek_internal::<()>(Some(pos), bias, &mut SeekAggregate::None, cx)
}
pub fn seek_forward(
&mut self,
pos: &S,
bias: Bias,
cx: &<T::Summary as Summary>::Context,
) -> bool {
self.seek_internal::<()>(Some(pos), bias, &mut SeekAggregate::None, cx)
}
pub fn slice(
&mut self,
end: &S,
bias: Bias,
cx: &<T::Summary as Summary>::Context,
) -> SumTree<T> {
let mut slice = SeekAggregate::Slice(SumTree::new());
self.seek_internal::<()>(Some(end), bias, &mut slice, cx);
if let SeekAggregate::Slice(slice) = slice {
slice
} else {
unreachable!()
}
}
pub fn suffix(&mut self, cx: &<T::Summary as Summary>::Context) -> SumTree<T> {
let mut slice = SeekAggregate::Slice(SumTree::new());
self.seek_internal::<()>(None, Bias::Right, &mut slice, cx);
if let SeekAggregate::Slice(slice) = slice {
slice
} else {
unreachable!()
}
}
pub fn summary<D>(&mut self, end: &S, bias: Bias, cx: &<T::Summary as Summary>::Context) -> D
where
D: Dimension<'a, T::Summary>,
{
let mut summary = SeekAggregate::Summary(D::default());
self.seek_internal(Some(end), bias, &mut summary, cx);
if let SeekAggregate::Summary(summary) = summary {
summary
} else {
unreachable!()
}
}
fn seek_internal<D>(
&mut self,
target: Option<&S>,
bias: Bias,
aggregate: &mut SeekAggregate<T, D>,
cx: &<T::Summary as Summary>::Context,
) -> bool
where
D: Dimension<'a, T::Summary>,
{
if let Some(target) = target {
debug_assert!(
target.cmp(&self.seek_dimension, cx) >= Ordering::Equal,
"cannot seek backward from {:?} to {:?}",
self.seek_dimension,
target
);
}
if !self.did_seek {
self.did_seek = true;
self.stack.push(StackEntry {
tree: self.tree,
index: 0,
seek_dimension: Default::default(),
sum_dimension: Default::default(),
});
}
let mut ascending = false;
'outer: while let Some(entry) = self.stack.last_mut() {
match *entry.tree.0 {
Node::Internal {
ref child_summaries,
ref child_trees,
..
} => {
if ascending {
entry.index += 1;
}
for (child_tree, child_summary) in child_trees[entry.index..]
.iter()
.zip(&child_summaries[entry.index..])
{
let mut child_end = self.seek_dimension.clone();
child_end.add_summary(&child_summary, cx);
let comparison =
target.map_or(Ordering::Greater, |t| t.cmp(&child_end, cx));
if comparison == Ordering::Greater
|| (comparison == Ordering::Equal && bias == Bias::Right)
{
self.seek_dimension = child_end;
self.sum_dimension.add_summary(child_summary, cx);
match aggregate {
SeekAggregate::None => {}
SeekAggregate::Slice(slice) => {
slice.push_tree(child_tree.clone(), cx);
}
SeekAggregate::Summary(summary) => {
summary.add_summary(child_summary, cx);
}
}
entry.index += 1;
entry.seek_dimension = self.seek_dimension.clone();
entry.sum_dimension = self.sum_dimension.clone();
} else {
self.stack.push(StackEntry {
tree: child_tree,
index: 0,
seek_dimension: self.seek_dimension.clone(),
sum_dimension: self.sum_dimension.clone(),
});
ascending = false;
continue 'outer;
}
}
}
Node::Leaf {
ref items,
ref item_summaries,
..
} => {
let mut slice_items = ArrayVec::<T, { 2 * TREE_BASE }>::new();
let mut slice_item_summaries = ArrayVec::<T::Summary, { 2 * TREE_BASE }>::new();
let mut slice_items_summary = match aggregate {
SeekAggregate::Slice(_) => Some(T::Summary::default()),
_ => None,
};
for (item, item_summary) in items[entry.index..]
.iter()
.zip(&item_summaries[entry.index..])
{
let mut child_end = self.seek_dimension.clone();
child_end.add_summary(item_summary, cx);
let comparison =
target.map_or(Ordering::Greater, |t| t.cmp(&child_end, cx));
if comparison == Ordering::Greater
|| (comparison == Ordering::Equal && bias == Bias::Right)
{
self.seek_dimension = child_end;
self.sum_dimension.add_summary(item_summary, cx);
match aggregate {
SeekAggregate::None => {}
SeekAggregate::Slice(_) => {
slice_items.push(item.clone());
slice_item_summaries.push(item_summary.clone());
slice_items_summary
.as_mut()
.unwrap()
.add_summary(item_summary, cx);
}
SeekAggregate::Summary(summary) => {
summary.add_summary(item_summary, cx);
}
}
entry.index += 1;
} else {
if let SeekAggregate::Slice(slice) = aggregate {
slice.push_tree(
SumTree(Arc::new(Node::Leaf {
summary: slice_items_summary.unwrap(),
items: slice_items,
item_summaries: slice_item_summaries,
})),
cx,
);
}
break 'outer;
}
}
if let SeekAggregate::Slice(slice) = aggregate {
if !slice_items.is_empty() {
slice.push_tree(
SumTree(Arc::new(Node::Leaf {
summary: slice_items_summary.unwrap(),
items: slice_items,
item_summaries: slice_item_summaries,
})),
cx,
);
}
}
}
}
self.stack.pop();
ascending = true;
}
self.at_end = self.stack.is_empty();
debug_assert!(self.stack.is_empty() || self.stack.last().unwrap().tree.0.is_leaf());
let mut end = self.seek_dimension.clone();
if bias == Bias::Left {
if let Some(summary) = self.item_summary() {
end.add_summary(summary, cx);
}
}
target.map_or(false, |t| t.cmp(&end, cx) == Ordering::Equal)
}
}
impl<'a, T, S, Seek, Sum> Iterator for Cursor<'a, T, Seek, Sum>
where
T: Item<Summary = S>,
S: Summary<Context = ()>,
Seek: Dimension<'a, T::Summary>,
Sum: Dimension<'a, T::Summary>,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if !self.did_seek {
self.next(&());
}
if let Some(item) = self.item() {
self.next(&());
Some(item)
} else {
None
}
}
}
impl<'a, T, S, U> Cursor<'a, T, S, U>
where
T: Item,
S: SeekDimension<'a, T::Summary>,
U: SeekDimension<'a, T::Summary>,
{
pub fn swap_dimensions(self) -> Cursor<'a, T, U, S> {
Cursor {
tree: self.tree,
stack: self
.stack
.into_iter()
.map(StackEntry::swap_dimensions)
.collect(),
seek_dimension: self.sum_dimension,
sum_dimension: self.seek_dimension,
did_seek: self.did_seek,
at_end: self.at_end,
}
}
}
pub struct FilterCursor<'a, F: Fn(&T::Summary) -> bool, T: Item, U> {
cursor: Cursor<'a, T, (), U>,
filter_node: F,
}
impl<'a, F, T, U> FilterCursor<'a, F, T, U>
where
F: Fn(&T::Summary) -> bool,
T: Item,
U: Dimension<'a, T::Summary>,
{
pub fn new(
tree: &'a SumTree<T>,
filter_node: F,
cx: &<T::Summary as Summary>::Context,
) -> Self {
let mut cursor = tree.cursor::<(), U>();
cursor.next_internal(&filter_node, cx);
Self {
cursor,
filter_node,
}
}
pub fn start(&self) -> &U {
self.cursor.sum_start()
}
pub fn item(&self) -> Option<&'a T> {
self.cursor.item()
}
pub fn next(&mut self, cx: &<T::Summary as Summary>::Context) {
self.cursor.next_internal(&self.filter_node, cx);
}
}
impl<'a, F, T, S, U> Iterator for FilterCursor<'a, F, T, U>
where
F: Fn(&T::Summary) -> bool,
T: Item<Summary = S>,
S: Summary<Context = ()>,
U: Dimension<'a, T::Summary>,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.item() {
self.cursor.next_internal(&self.filter_node, &());
Some(item)
} else {
None
}
}
}
enum SeekAggregate<T: Item, D> {
None,
Slice(SumTree<T>),
Summary(D),
}
+3 -1
View File
@@ -2,5 +2,7 @@ use ctor::ctor;
#[ctor]
fn init_logger() {
env_logger::init();
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
}
+504 -24
View File
@@ -5,7 +5,7 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
platform, scene, PaintContext,
platform, scene, FontSystem, PaintContext,
};
use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@@ -14,6 +14,7 @@ use std::{
borrow::Borrow,
collections::HashMap,
hash::{Hash, Hasher},
iter,
sync::Arc,
};
@@ -23,6 +24,13 @@ pub struct TextLayoutCache {
fonts: Arc<dyn platform::FontSystem>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct RunStyle {
pub color: Color,
pub font_id: FontId,
pub underline: bool,
}
impl TextLayoutCache {
pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
Self {
@@ -43,7 +51,7 @@ impl TextLayoutCache {
&'a self,
text: &'a str,
font_size: f32,
runs: &'a [(usize, FontId, Color)],
runs: &'a [(usize, RunStyle)],
) -> Line {
let key = &CacheKeyRef {
text,
@@ -94,7 +102,7 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
struct CacheKeyValue {
text: String,
font_size: OrderedFloat<f32>,
runs: SmallVec<[(usize, FontId, Color); 1]>,
runs: SmallVec<[(usize, RunStyle); 1]>,
}
impl CacheKey for CacheKeyValue {
@@ -119,11 +127,11 @@ impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone)]
struct CacheKeyRef<'a> {
text: &'a str,
font_size: OrderedFloat<f32>,
runs: &'a [(usize, FontId, Color)],
runs: &'a [(usize, RunStyle)],
}
impl<'a> CacheKey for CacheKeyRef<'a> {
@@ -132,10 +140,34 @@ impl<'a> CacheKey for CacheKeyRef<'a> {
}
}
impl<'a> PartialEq for CacheKeyRef<'a> {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
&& self.font_size == other.font_size
&& self.runs.len() == other.runs.len()
&& self.runs.iter().zip(other.runs.iter()).all(
|((len_a, style_a), (len_b, style_b))| {
len_a == len_b && style_a.font_id == style_b.font_id
},
)
}
}
impl<'a> Hash for CacheKeyRef<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.text.hash(state);
self.font_size.hash(state);
for (len, style_id) in self.runs {
len.hash(state);
style_id.font_id.hash(state);
}
}
}
#[derive(Default, Debug)]
pub struct Line {
layout: Arc<LineLayout>,
color_runs: SmallVec<[(u32, Color); 32]>,
style_runs: SmallVec<[(u32, Color, bool); 32]>,
}
#[derive(Default, Debug)]
@@ -162,12 +194,16 @@ pub struct Glyph {
}
impl Line {
fn new(layout: Arc<LineLayout>, runs: &[(usize, FontId, Color)]) -> Self {
let mut color_runs = SmallVec::new();
for (len, _, color) in runs {
color_runs.push((*len as u32, *color));
fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
let mut style_runs = SmallVec::new();
for (len, style) in runs {
style_runs.push((*len as u32, style.color, style.underline));
}
Self { layout, color_runs }
Self { layout, style_runs }
}
pub fn runs(&self) -> &[Run] {
&self.layout.runs
}
pub fn width(&self) -> f32 {
@@ -200,13 +236,20 @@ impl Line {
}
}
pub fn paint(&self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
let padding_top = (visible_bounds.height() - self.layout.ascent - self.layout.descent) / 2.;
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
pub fn paint(
&self,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
cx: &mut PaintContext,
) {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
let mut color_runs = self.color_runs.iter();
let mut color_end = 0;
let mut style_runs = self.style_runs.iter();
let mut run_end = 0;
let mut color = Color::black();
let mut underline_start = None;
for run in &self.layout.runs {
let max_glyph_width = cx
@@ -215,7 +258,7 @@ impl Line {
.x();
for glyph in &run.glyphs {
let glyph_origin = baseline_origin + glyph.position;
let glyph_origin = origin + baseline_offset + glyph.position;
if glyph_origin.x() + max_glyph_width < visible_bounds.origin().x() {
continue;
@@ -224,6 +267,96 @@ impl Line {
break;
}
if glyph.index >= run_end {
if let Some((run_len, run_color, run_underlined)) = style_runs.next() {
if let Some(underline_origin) = underline_start {
if !*run_underlined || *run_color != color {
cx.scene.push_underline(scene::Quad {
bounds: RectF::from_points(
underline_origin,
glyph_origin + vec2f(0., 1.),
),
background: Some(color),
border: Default::default(),
corner_radius: 0.,
});
underline_start = None;
}
}
if *run_underlined {
underline_start.get_or_insert(glyph_origin);
}
run_end += *run_len as usize;
color = *run_color;
} else {
run_end = self.layout.len;
color = Color::black();
if let Some(underline_origin) = underline_start.take() {
cx.scene.push_underline(scene::Quad {
bounds: RectF::from_points(
underline_origin,
glyph_origin + vec2f(0., 1.),
),
background: Some(color),
border: Default::default(),
corner_radius: 0.,
});
}
}
}
cx.scene.push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_origin,
color,
});
}
}
if let Some(underline_start) = underline_start.take() {
let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.);
cx.scene.push_underline(scene::Quad {
bounds: RectF::from_points(underline_start, line_end + vec2f(0., 1.)),
background: Some(color),
border: Default::default(),
corner_radius: 0.,
});
}
}
pub fn paint_wrapped(
&self,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
boundaries: impl IntoIterator<Item = ShapedBoundary>,
cx: &mut PaintContext,
) {
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
let mut boundaries = boundaries.into_iter().peekable();
let mut color_runs = self.style_runs.iter();
let mut color_end = 0;
let mut color = Color::black();
let mut glyph_origin = vec2f(0., 0.);
let mut prev_position = 0.;
for run in &self.layout.runs {
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
if boundaries.peek().map_or(false, |b| b.glyph_ix == glyph_ix) {
boundaries.next();
glyph_origin = vec2f(0., glyph_origin.y() + line_height);
} else {
glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
}
prev_position = glyph.position.x();
if glyph.index >= color_end {
if let Some(next_run) = color_runs.next() {
color_end += next_run.0 as usize;
@@ -234,14 +367,361 @@ impl Line {
}
}
cx.scene.push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: origin + glyph_origin,
color,
});
let glyph_bounds = RectF::new(
origin + glyph_origin,
cx.font_cache
.bounding_box(run.font_id, self.layout.font_size),
);
if glyph_bounds.intersects(visible_bounds) {
cx.scene.push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_bounds.origin() + baseline_origin,
color,
});
}
}
}
}
}
impl Run {
pub fn glyphs(&self) -> &[Glyph] {
&self.glyphs
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Boundary {
pub ix: usize,
pub next_indent: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ShapedBoundary {
pub run_ix: usize,
pub glyph_ix: usize,
}
impl Boundary {
fn new(ix: usize, next_indent: u32) -> Self {
Self { ix, next_indent }
}
}
pub struct LineWrapper {
font_system: Arc<dyn FontSystem>,
pub(crate) font_id: FontId,
pub(crate) font_size: f32,
cached_ascii_char_widths: [f32; 128],
cached_other_char_widths: HashMap<char, f32>,
}
impl LineWrapper {
pub const MAX_INDENT: u32 = 256;
pub fn new(font_id: FontId, font_size: f32, font_system: Arc<dyn FontSystem>) -> Self {
Self {
font_system,
font_id,
font_size,
cached_ascii_char_widths: [f32::NAN; 128],
cached_other_char_widths: HashMap::new(),
}
}
pub fn wrap_line<'a>(
&'a mut self,
line: &'a str,
wrap_width: f32,
) -> impl Iterator<Item = Boundary> + 'a {
let mut width = 0.0;
let mut first_non_whitespace_ix = None;
let mut indent = None;
let mut last_candidate_ix = 0;
let mut last_candidate_width = 0.0;
let mut last_wrap_ix = 0;
let mut prev_c = '\0';
let mut char_indices = line.char_indices();
iter::from_fn(move || {
while let Some((ix, c)) = char_indices.next() {
if c == '\n' {
continue;
}
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
last_candidate_ix = ix;
last_candidate_width = width;
}
if c != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(ix);
}
let char_width = self.width_for_char(c);
width += char_width;
if width > wrap_width && ix > last_wrap_ix {
if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
{
indent = Some(
Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
);
}
if last_candidate_ix > 0 {
last_wrap_ix = last_candidate_ix;
width -= last_candidate_width;
last_candidate_ix = 0;
} else {
last_wrap_ix = ix;
width = char_width;
}
let indent_width =
indent.map(|indent| indent as f32 * self.width_for_char(' '));
width += indent_width.unwrap_or(0.);
return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
}
prev_c = c;
}
None
})
}
pub fn wrap_shaped_line<'a>(
&'a mut self,
str: &'a str,
line: &'a Line,
wrap_width: f32,
) -> impl Iterator<Item = ShapedBoundary> + 'a {
let mut first_non_whitespace_ix = None;
let mut last_candidate_ix = None;
let mut last_candidate_x = 0.0;
let mut last_wrap_ix = ShapedBoundary {
run_ix: 0,
glyph_ix: 0,
};
let mut last_wrap_x = 0.;
let mut prev_c = '\0';
let mut glyphs = line
.runs()
.iter()
.enumerate()
.flat_map(move |(run_ix, run)| {
run.glyphs()
.iter()
.enumerate()
.map(move |(glyph_ix, glyph)| {
let character = str[glyph.index..].chars().next().unwrap();
(
ShapedBoundary { run_ix, glyph_ix },
character,
glyph.position.x(),
)
})
})
.peekable();
iter::from_fn(move || {
while let Some((ix, c, x)) = glyphs.next() {
if c == '\n' {
continue;
}
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
last_candidate_ix = Some(ix);
last_candidate_x = x;
}
if c != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(ix);
}
let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
let width = next_x - last_wrap_x;
if width > wrap_width && ix > last_wrap_ix {
if let Some(last_candidate_ix) = last_candidate_ix.take() {
last_wrap_ix = last_candidate_ix;
last_wrap_x = last_candidate_x;
} else {
last_wrap_ix = ix;
last_wrap_x = x;
}
return Some(last_wrap_ix);
}
prev_c = c;
}
None
})
}
fn is_boundary(&self, prev: char, next: char) -> bool {
(prev == ' ') && (next != ' ')
}
#[inline(always)]
fn width_for_char(&mut self, c: char) -> f32 {
if (c as u32) < 128 {
let mut width = self.cached_ascii_char_widths[c as usize];
if width.is_nan() {
width = self.compute_width_for_char(c);
self.cached_ascii_char_widths[c as usize] = width;
}
width
} else {
let mut width = self
.cached_other_char_widths
.get(&c)
.copied()
.unwrap_or(f32::NAN);
if width.is_nan() {
width = self.compute_width_for_char(c);
self.cached_other_char_widths.insert(c, width);
}
width
}
}
fn compute_width_for_char(&self, c: char) -> f32 {
self.font_system
.layout_line(
&c.to_string(),
self.font_size,
&[(
1,
RunStyle {
font_id: self.font_id,
color: Default::default(),
underline: false,
},
)],
)
.width
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fonts::{Properties, Weight};
#[crate::test(self)]
fn test_wrap_line(cx: &mut crate::MutableAppContext) {
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let family = font_cache.load_family(&["Courier"]).unwrap();
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
assert_eq!(
wrapper
.wrap_line("aa bbb cccc ddddd eeee", 72.0)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(12, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
.collect::<Vec<_>>(),
&[
Boundary::new(4, 0),
Boundary::new(11, 0),
Boundary::new(18, 0)
],
);
assert_eq!(
wrapper.wrap_line(" aaaaaaa", 72.).collect::<Vec<_>>(),
&[
Boundary::new(7, 5),
Boundary::new(9, 5),
Boundary::new(11, 5),
]
);
assert_eq!(
wrapper
.wrap_line(" ", 72.)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 0),
Boundary::new(21, 0)
]
);
assert_eq!(
wrapper
.wrap_line(" aaaaaaaaaaaaaa", 72.)
.collect::<Vec<_>>(),
&[
Boundary::new(7, 0),
Boundary::new(14, 3),
Boundary::new(18, 3),
Boundary::new(22, 3),
]
);
}
#[crate::test(self)]
fn test_wrap_shaped_line(cx: &mut crate::MutableAppContext) {
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let text_layout_cache = TextLayoutCache::new(font_system.clone());
let family = font_cache.load_family(&["Helvetica"]).unwrap();
let font_id = font_cache.select_font(family, &Default::default()).unwrap();
let normal = RunStyle {
font_id,
color: Default::default(),
underline: false,
};
let bold = RunStyle {
font_id: font_cache
.select_font(
family,
&Properties {
weight: Weight::BOLD,
..Default::default()
},
)
.unwrap(),
color: Default::default(),
underline: false,
};
let text = "aa bbb cccc ddddd eeee";
let line = text_layout_cache.layout_str(
text,
16.0,
&[(4, normal), (5, bold), (6, normal), (1, bold), (7, normal)],
);
let mut wrapper = LineWrapper::new(font_id, 16., font_system);
assert_eq!(
wrapper
.wrap_shaped_line(&text, &line, 72.0)
.collect::<Vec<_>>(),
&[
ShapedBoundary {
run_ix: 1,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 2,
glyph_ix: 3
},
ShapedBoundary {
run_ix: 4,
glyph_ix: 2
}
],
);
}
}
+7
View File
@@ -0,0 +1,7 @@
mod select;
pub use select::{ItemType, Select, SelectStyle};
pub fn init(cx: &mut super::MutableAppContext) {
select::init(cx);
}
+169
View File
@@ -0,0 +1,169 @@
use crate::{
action, elements::*, AppContext, Entity, MutableAppContext, RenderContext, View, ViewContext,
WeakViewHandle,
};
pub struct Select {
handle: WeakViewHandle<Self>,
render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> ElementBox>,
selected_item_ix: usize,
item_count: usize,
is_open: bool,
list_state: UniformListState,
build_style: Option<Box<dyn FnMut(&mut MutableAppContext) -> SelectStyle>>,
}
#[derive(Clone, Default)]
pub struct SelectStyle {
pub header: ContainerStyle,
pub menu: ContainerStyle,
}
pub enum ItemType {
Header,
Selected,
Unselected,
}
action!(ToggleSelect);
action!(SelectItem, usize);
pub enum Event {}
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Select::toggle);
cx.add_action(Select::select_item);
}
impl Select {
pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> ElementBox>(
item_count: usize,
cx: &mut ViewContext<Self>,
render_item: F,
) -> Self {
Self {
handle: cx.handle().downgrade(),
render_item: Box::new(render_item),
selected_item_ix: 0,
item_count,
is_open: false,
list_state: UniformListState::default(),
build_style: Default::default(),
}
}
pub fn with_style(
mut self,
f: impl 'static + FnMut(&mut MutableAppContext) -> SelectStyle,
) -> Self {
self.build_style = Some(Box::new(f));
self
}
pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
self.item_count = count;
cx.notify();
}
fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext<Self>) {
self.is_open = !self.is_open;
cx.notify();
}
fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext<Self>) {
self.selected_item_ix = action.0;
self.is_open = false;
cx.notify();
}
pub fn selected_index(&self) -> usize {
self.selected_item_ix
}
}
impl Entity for Select {
type Event = Event;
}
impl View for Select {
fn ui_name() -> &'static str {
"Select"
}
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
if self.item_count == 0 {
return Empty::new().boxed();
}
enum Header {}
enum Item {}
let style = if let Some(build_style) = self.build_style.as_mut() {
(build_style)(cx)
} else {
Default::default()
};
let mut result = Flex::column().with_child(
MouseEventHandler::new::<Header, _, _, _>(self.handle.id(), cx, |mouse_state, cx| {
Container::new((self.render_item)(
self.selected_item_ix,
ItemType::Header,
mouse_state.hovered,
cx,
))
.with_style(&style.header)
.boxed()
})
.on_click(move |cx| cx.dispatch_action(ToggleSelect))
.boxed(),
);
if self.is_open {
let handle = self.handle.clone();
result.add_child(
Overlay::new(
Container::new(
ConstrainedBox::new(
UniformList::new(
self.list_state.clone(),
self.item_count,
move |mut range, items, mut cx| {
let handle = handle.upgrade(cx).unwrap();
let this = handle.read(cx);
let selected_item_ix = this.selected_item_ix;
range.end = range.end.min(this.item_count);
items.extend(range.map(|ix| {
MouseEventHandler::new::<Item, _, _, _>(
(handle.id(), ix),
&mut cx,
|mouse_state, cx| {
(handle.read(*cx).render_item)(
ix,
if ix == selected_item_ix {
ItemType::Selected
} else {
ItemType::Unselected
},
mouse_state.hovered,
cx,
)
},
)
.on_click(move |cx| cx.dispatch_action(SelectItem(ix)))
.boxed()
}))
},
)
.boxed(),
)
.with_max_height(200.)
.boxed(),
)
.with_style(&style.menu)
.boxed(),
)
.boxed(),
)
}
result.boxed()
}
}