Merge branch 'chat'
This commit is contained in:
+956
-606
File diff suppressed because it is too large
Load Diff
+111
-64
@@ -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
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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!({});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,6 @@ pub enum Event {
|
||||
},
|
||||
MouseMoved {
|
||||
position: Vector2F,
|
||||
left_mouse_down: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
mod select;
|
||||
|
||||
pub use select::{ItemType, Select, SelectStyle};
|
||||
|
||||
pub fn init(cx: &mut super::MutableAppContext) {
|
||||
select::init(cx);
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user