This commit is contained in:
Nathan Sobo
2021-03-09 21:00:51 -07:00
parent a015c61337
commit 356bc41752
36 changed files with 14097 additions and 4 deletions
+68
View File
@@ -0,0 +1,68 @@
use crate::{
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
use pathfinder_geometry::vector::{vec2f, Vector2F};
pub struct Align {
child: Box<dyn Element>,
alignment: Vector2F,
size: Option<Vector2F>,
}
impl Align {
pub fn new(child: Box<dyn Element>) -> Self {
Self {
child,
alignment: Vector2F::zero(),
size: None,
}
}
pub fn top_center(mut self) -> Self {
self.alignment = vec2f(0.0, -1.0);
self
}
}
impl Element for Align {
fn layout(
&mut self,
mut constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
let mut size = constraint.max;
constraint.min = Vector2F::zero();
let child_size = self.child.layout(constraint, ctx, app);
if size.x().is_infinite() {
size.set_x(child_size.x());
}
if size.y().is_infinite() {
size.set_y(child_size.y());
}
self.size = Some(size);
size
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
self.child.after_layout(ctx, app);
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
let self_center = self.size.unwrap() / 2.0;
let self_target = self_center + self_center * self.alignment;
let child_center = self.child.size().unwrap() / 2.0;
let child_target = child_center + child_center * self.alignment;
let origin = origin - (child_target - self_target);
self.child.paint(origin, ctx, app);
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
self.child.dispatch_event(event, ctx, app)
}
fn size(&self) -> Option<Vector2F> {
self.size
}
}
+67
View File
@@ -0,0 +1,67 @@
use crate::{
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
use pathfinder_geometry::vector::Vector2F;
pub struct ConstrainedBox {
child: Box<dyn Element>,
constraint: SizeConstraint,
}
impl ConstrainedBox {
pub fn new(child: Box<dyn Element>) -> Self {
Self {
child,
constraint: SizeConstraint {
min: Vector2F::zero(),
max: Vector2F::splat(f32::INFINITY),
},
}
}
pub fn with_max_width(mut self, max_width: f32) -> Self {
self.constraint.max.set_x(max_width);
self
}
pub fn with_max_height(mut self, max_height: f32) -> Self {
self.constraint.max.set_y(max_height);
self
}
pub fn with_height(mut self, height: f32) -> Self {
self.constraint.min.set_y(height);
self.constraint.max.set_y(height);
self
}
}
impl Element for ConstrainedBox {
fn layout(
&mut self,
mut constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
constraint.min = constraint.min.max(self.constraint.min);
constraint.max = constraint.max.min(self.constraint.max);
self.child.layout(constraint, ctx, app)
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
self.child.after_layout(ctx, app);
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
self.child.paint(origin, ctx, app);
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
self.child.dispatch_event(event, ctx, app)
}
fn size(&self) -> Option<Vector2F> {
self.child.size()
}
}
+358
View File
@@ -0,0 +1,358 @@
use crate::{
color::ColorU,
geometry::vector::{vec2f, Vector2F},
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
pub struct Container {
margin: Margin,
padding: Padding,
overdraw: Overdraw,
background_color: Option<ColorU>,
border: Border,
corner_radius: f32,
shadow: Option<Shadow>,
child: Box<dyn Element>,
size: Option<Vector2F>,
origin: Option<Vector2F>,
}
impl Container {
pub fn new(child: Box<dyn Element>) -> Self {
Self {
margin: Margin::default(),
padding: Padding::default(),
overdraw: Overdraw::default(),
background_color: None,
border: Border::default(),
corner_radius: 0.0,
shadow: None,
child,
size: None,
origin: None,
}
}
pub fn with_margin_top(mut self, margin: f32) -> Self {
self.margin.top = margin;
self
}
pub fn with_uniform_padding(mut self, padding: f32) -> Self {
self.padding = Padding {
top: padding,
left: padding,
bottom: padding,
right: padding,
};
self
}
pub fn with_padding_right(mut self, padding: f32) -> Self {
self.padding.right = padding;
self
}
pub fn with_background_color(mut self, color: impl Into<ColorU>) -> Self {
self.background_color = Some(color.into());
self
}
pub fn with_border(mut self, border: Border) -> Self {
self.border = border;
self
}
pub fn with_overdraw_bottom(mut self, overdraw: f32) -> Self {
self.overdraw.bottom = overdraw;
self
}
pub fn with_corner_radius(mut self, radius: f32) -> Self {
self.corner_radius = radius;
self
}
pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: impl Into<ColorU>) -> Self {
self.shadow = Some(Shadow {
offset,
blur,
color: color.into(),
});
self
}
fn margin_size(&self) -> Vector2F {
vec2f(
self.margin.left + self.margin.right,
self.margin.top + self.margin.bottom,
)
}
fn padding_size(&self) -> Vector2F {
vec2f(
self.padding.left + self.padding.right,
self.padding.top + self.padding.bottom,
)
}
fn border_size(&self) -> Vector2F {
let mut x = 0.0;
if self.border.left {
x += self.border.width;
}
if self.border.right {
x += self.border.width;
}
let mut y = 0.0;
if self.border.top {
y += self.border.width;
}
if self.border.bottom {
y += self.border.width;
}
vec2f(x, y)
}
}
impl Element for Container {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
let size_buffer = self.margin_size() + self.padding_size() + self.border_size();
let child_constraint = SizeConstraint {
min: (constraint.min - size_buffer).max(Vector2F::zero()),
max: (constraint.max - size_buffer).max(Vector2F::zero()),
};
let child_size = self.child.layout(child_constraint, ctx, app);
let size = child_size + size_buffer;
self.size = Some(size);
size
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
self.child.after_layout(ctx, app);
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
// self.origin = Some(origin);
// let canvas = &mut ctx.canvas;
// let size = self.size.unwrap() - self.margin_size()
// + vec2f(self.overdraw.right, self.overdraw.bottom);
// let origin = origin + vec2f(self.margin.left, self.margin.top)
// - vec2f(self.overdraw.left, self.overdraw.top);
// let rect = RectF::new(origin, size);
// let mut path = Path2D::new();
// if self.corner_radius > 0.0 {
// path.move_to(rect.upper_right() - vec2f(self.corner_radius, 0.0));
// path.arc_to(
// rect.upper_right(),
// rect.upper_right() + vec2f(0.0, self.corner_radius),
// self.corner_radius,
// );
// path.line_to(rect.lower_right() - vec2f(0.0, self.corner_radius));
// path.arc_to(
// rect.lower_right(),
// rect.lower_right() - vec2f(self.corner_radius, 0.0),
// self.corner_radius,
// );
// path.line_to(rect.lower_left() + vec2f(self.corner_radius, 0.0));
// path.arc_to(
// rect.lower_left(),
// rect.lower_left() - vec2f(0.0, self.corner_radius),
// self.corner_radius,
// );
// path.line_to(origin + vec2f(0.0, self.corner_radius));
// path.arc_to(
// origin,
// origin + vec2f(self.corner_radius, 0.0),
// self.corner_radius,
// );
// path.close_path();
// } else {
// path.rect(rect);
// }
// canvas.save();
// if let Some(shadow) = self.shadow.as_ref() {
// canvas.set_shadow_offset(shadow.offset);
// canvas.set_shadow_blur(shadow.blur);
// canvas.set_shadow_color(shadow.color);
// }
// if let Some(background_color) = self.background_color {
// canvas.set_fill_style(FillStyle::Color(background_color));
// canvas.fill_path(path.clone(), FillRule::Winding);
// }
// canvas.set_line_width(self.border.width);
// canvas.set_stroke_style(FillStyle::Color(self.border.color));
// let border_rect = rect.contract(self.border.width / 2.0);
// // For now, we ignore the corner radius unless we draw a border on all sides.
// // This could be improved.
// if self.border.all_sides() {
// let mut path = Path2D::new();
// path.rect(border_rect);
// canvas.stroke_path(path);
// } else {
// canvas.set_line_cap(LineCap::Square);
// if self.border.top {
// let mut path = Path2D::new();
// path.move_to(border_rect.origin());
// path.line_to(border_rect.upper_right());
// canvas.stroke_path(path);
// }
// if self.border.left {
// let mut path = Path2D::new();
// path.move_to(border_rect.origin());
// path.line_to(border_rect.lower_left());
// canvas.stroke_path(path);
// }
// if self.border.bottom {
// let mut path = Path2D::new();
// path.move_to(border_rect.lower_left());
// path.line_to(border_rect.lower_right());
// canvas.stroke_path(path);
// }
// if self.border.right {
// let mut path = Path2D::new();
// path.move_to(border_rect.upper_right());
// path.line_to(border_rect.lower_right());
// canvas.stroke_path(path);
// }
// }
// canvas.restore();
// let mut child_origin = origin + vec2f(self.padding.left, self.padding.top);
// if self.border.left {
// child_origin.set_x(child_origin.x() + self.border.width);
// }
// if self.border.top {
// child_origin.set_y(child_origin.y() + self.border.width);
// }
// self.child.paint(child_origin, ctx, app);
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
self.child.dispatch_event(event, ctx, app)
}
fn size(&self) -> Option<Vector2F> {
self.size
}
}
#[derive(Default)]
pub struct Margin {
top: f32,
left: f32,
bottom: f32,
right: f32,
}
#[derive(Default)]
pub struct Padding {
top: f32,
left: f32,
bottom: f32,
right: f32,
}
#[derive(Default)]
pub struct Overdraw {
top: f32,
left: f32,
bottom: f32,
right: f32,
}
#[derive(Default)]
pub struct Border {
width: f32,
color: ColorU,
pub top: bool,
pub left: bool,
pub bottom: bool,
pub right: bool,
}
impl Border {
pub fn new(width: f32, color: impl Into<ColorU>) -> Self {
Self {
width,
color: color.into(),
top: false,
left: false,
bottom: false,
right: false,
}
}
pub fn all(width: f32, color: impl Into<ColorU>) -> Self {
Self {
width,
color: color.into(),
top: true,
left: true,
bottom: true,
right: true,
}
}
pub fn top(width: f32, color: impl Into<ColorU>) -> Self {
let mut border = Self::new(width, color);
border.top = true;
border
}
pub fn left(width: f32, color: impl Into<ColorU>) -> Self {
let mut border = Self::new(width, color);
border.left = true;
border
}
pub fn bottom(width: f32, color: impl Into<ColorU>) -> Self {
let mut border = Self::new(width, color);
border.bottom = true;
border
}
pub fn right(width: f32, color: impl Into<ColorU>) -> Self {
let mut border = Self::new(width, color);
border.right = true;
border
}
pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
self.top = top;
self.left = left;
self.bottom = bottom;
self.right = right;
self
}
fn all_sides(&self) -> bool {
self.top && self.left && self.bottom && self.right
}
}
#[derive(Default)]
pub struct Shadow {
offset: Vector2F,
blur: f32,
color: ColorU,
}
+45
View File
@@ -0,0 +1,45 @@
use crate::{
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
use pathfinder_geometry::vector::Vector2F;
pub struct Empty {
size: Option<Vector2F>,
origin: Option<Vector2F>,
}
impl Empty {
pub fn new() -> Self {
Self {
size: None,
origin: None,
}
}
}
impl Element for Empty {
fn layout(
&mut self,
constraint: SizeConstraint,
_: &mut LayoutContext,
_: &AppContext,
) -> Vector2F {
self.size = Some(constraint.min);
constraint.max
}
fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {}
fn paint(&mut self, origin: Vector2F, _: &mut PaintContext, _: &AppContext) {
self.origin = Some(origin);
}
fn dispatch_event(&self, _: &Event, _: &mut EventContext, _: &AppContext) -> bool {
false
}
fn size(&self) -> Option<Vector2F> {
self.size
}
}
+69
View File
@@ -0,0 +1,69 @@
use super::try_rect;
use crate::{
geometry::vector::Vector2F, AfterLayoutContext, AppContext, Element, Event, EventContext,
LayoutContext, MutableAppContext, PaintContext, SizeConstraint,
};
use std::cell::RefCell;
pub struct EventHandler {
child: Box<dyn Element>,
mouse_down: Option<RefCell<Box<dyn FnMut(&mut EventContext, &AppContext) -> bool>>>,
origin: Option<Vector2F>,
}
impl EventHandler {
pub fn new(child: Box<dyn Element>) -> Self {
Self {
child,
mouse_down: None,
origin: None,
}
}
pub fn on_mouse_down<F>(mut self, callback: F) -> Self
where
F: 'static + FnMut(&mut EventContext, &AppContext) -> bool,
{
self.mouse_down = Some(RefCell::new(Box::new(callback)));
self
}
}
impl Element for EventHandler {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
self.child.layout(constraint, ctx, app)
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
self.child.after_layout(ctx, app);
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
self.origin = Some(origin);
self.child.paint(origin, ctx, app);
}
fn size(&self) -> Option<Vector2F> {
self.child.size()
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
match event {
Event::LeftMouseDown { position, .. } => {
if let Some(callback) = self.mouse_down.as_ref() {
let rect = try_rect(self.origin, self.size()).unwrap();
if rect.contains_point(*position) {
return callback.borrow_mut()(ctx, app);
}
}
false
}
_ => false,
}
}
}
+201
View File
@@ -0,0 +1,201 @@
use crate::{
AfterLayoutContext, AppContext, Axis, Element, Event, EventContext, LayoutContext,
MutableAppContext, PaintContext, SizeConstraint, Vector2FExt,
};
use pathfinder_geometry::vector::{vec2f, Vector2F};
use std::any::Any;
pub struct Flex {
axis: Axis,
children: Vec<Box<dyn Element>>,
size: Option<Vector2F>,
origin: Option<Vector2F>,
}
impl Flex {
pub fn new(axis: Axis) -> Self {
Self {
axis,
children: Default::default(),
size: None,
origin: None,
}
}
pub fn row() -> Self {
Self::new(Axis::Horizontal)
}
pub fn column() -> Self {
Self::new(Axis::Vertical)
}
fn child_flex<'b>(child: &dyn Element) -> Option<f32> {
child
.parent_data()
.and_then(|d| d.downcast_ref::<FlexParentData>())
.map(|data| data.flex)
}
}
impl Extend<Box<dyn Element>> for Flex {
fn extend<T: IntoIterator<Item = Box<dyn Element>>>(&mut self, children: T) {
self.children.extend(children);
}
}
impl Element for Flex {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
let mut total_flex = 0.0;
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.as_ref()) {
total_flex += flex;
} else {
let child_constraint =
SizeConstraint::strict_along(cross_axis, constraint.max_along(cross_axis));
let size = child.layout(child_constraint, ctx, app);
fixed_space += size.along(self.axis);
cross_axis_max = cross_axis_max.max(size.along(cross_axis));
}
}
let mut size = if total_flex > 0.0 {
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 {
let space_per_flex = remaining_space / remaining_flex;
if let Some(flex) = Self::child_flex(child.as_ref()) {
let child_max = space_per_flex * flex;
let child_constraint = match self.axis {
Axis::Horizontal => SizeConstraint::new(
vec2f(0.0, constraint.max.y()),
vec2f(child_max, constraint.max.y()),
),
Axis::Vertical => SizeConstraint::new(
vec2f(constraint.max.x(), 0.0),
vec2f(constraint.max.x(), child_max),
),
};
let child_size = child.layout(child_constraint, ctx, app);
remaining_space -= child_size.along(self.axis);
remaining_flex -= flex;
cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
}
}
match self.axis {
Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
}
} else {
match self.axis {
Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
Axis::Vertical => vec2f(cross_axis_max, fixed_space),
}
};
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()));
}
self.size = Some(size);
size
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
for child in &mut self.children {
child.after_layout(ctx, app);
}
}
fn paint(&mut self, mut origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
self.origin = Some(origin);
for child in &mut self.children {
child.paint(origin, ctx, app);
match self.axis {
Axis::Horizontal => origin += vec2f(child.size().unwrap().x(), 0.0),
Axis::Vertical => origin += vec2f(0.0, child.size().unwrap().y()),
}
}
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
let mut handled = false;
for child in &self.children {
if child.dispatch_event(event, ctx, app) {
handled = true;
}
}
handled
}
fn size(&self) -> Option<Vector2F> {
self.size
}
}
struct FlexParentData {
flex: f32,
}
pub struct Expanded {
parent_data: FlexParentData,
child: Box<dyn Element>,
}
impl Expanded {
pub fn new(flex: f32, child: Box<dyn Element>) -> Self {
Expanded {
parent_data: FlexParentData { flex },
child,
}
}
}
impl Element for Expanded {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
self.child.layout(constraint, ctx, app)
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
self.child.after_layout(ctx, app);
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
self.child.paint(origin, ctx, app);
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
self.child.dispatch_event(event, ctx, app)
}
fn size(&self) -> Option<Vector2F> {
self.child.size()
}
fn parent_data(&self) -> Option<&dyn Any> {
Some(&self.parent_data)
}
}
+154
View File
@@ -0,0 +1,154 @@
use crate::{
color::ColorU,
fonts::{FamilyId, Properties},
geometry::vector::{vec2f, Vector2F},
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
use std::{ops::Range, sync::Arc};
pub struct Label {
text: String,
family_id: FamilyId,
font_properties: Properties,
font_size: f32,
highlights: Option<Highlights>,
layout_line: Option<Arc<Line>>,
colors: Option<Vec<(Range<usize>, ColorU)>>,
size: Option<Vector2F>,
}
pub struct Highlights {
color: ColorU,
indices: Vec<usize>,
font_properties: Properties,
}
impl Label {
pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
Self {
text,
family_id,
font_properties: Properties::new(),
font_size,
highlights: None,
layout_line: None,
colors: None,
size: None,
}
}
pub fn with_highlights(
mut self,
color: ColorU,
font_properties: Properties,
indices: Vec<usize>,
) -> Self {
self.highlights = Some(Highlights {
color,
font_properties,
indices,
});
self
}
}
impl Element for Label {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
_: &AppContext,
) -> Vector2F {
let font_id = ctx
.font_cache
.select_font(self.family_id, &self.font_properties)
.unwrap();
let text_len = self.text.chars().count();
let mut styles;
let mut colors;
if let Some(highlights) = self.highlights.as_ref() {
styles = Vec::new();
colors = Vec::new();
let highlight_font_id = ctx
.font_cache
.select_font(self.family_id, &highlights.font_properties)
.unwrap_or(font_id);
let mut pending_highlight: Option<Range<usize>> = None;
for ix in &highlights.indices {
if let Some(pending_highlight) = pending_highlight.as_mut() {
if *ix == pending_highlight.end {
pending_highlight.end += 1;
} else {
styles.push((pending_highlight.clone(), highlight_font_id));
colors.push((pending_highlight.clone(), highlights.color));
styles.push((pending_highlight.end..*ix, font_id));
colors.push((pending_highlight.end..*ix, ColorU::black()));
*pending_highlight = *ix..*ix + 1;
}
} else {
styles.push((0..*ix, font_id));
colors.push((0..*ix, ColorU::black()));
pending_highlight = Some(*ix..*ix + 1);
}
}
if let Some(pending_highlight) = pending_highlight.as_mut() {
styles.push((pending_highlight.clone(), highlight_font_id));
colors.push((pending_highlight.clone(), highlights.color));
if text_len > pending_highlight.end {
styles.push((pending_highlight.end..text_len, font_id));
colors.push((pending_highlight.end..text_len, ColorU::black()));
}
} else {
styles.push((0..text_len, font_id));
colors.push((0..text_len, ColorU::black()));
}
} else {
styles = vec![(0..text_len, font_id)];
colors = vec![(0..text_len, ColorU::black())];
}
self.colors = Some(colors);
let layout_line = ctx.text_layout_cache.layout_str(
self.text.as_str(),
self.font_size,
styles.as_slice(),
ctx.font_cache,
);
let size = vec2f(
layout_line
.width
.max(constraint.min.x())
.min(constraint.max.x()),
ctx.font_cache.line_height(font_id, self.font_size).ceil(),
);
self.layout_line = Some(layout_line);
self.size = Some(size);
size
}
fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, _: &AppContext) {
// ctx.canvas.set_fill_style(FillStyle::Color(ColorU::black()));
// self.layout_line.as_ref().unwrap().paint(
// origin,
// RectF::new(origin, self.size.unwrap()),
// self.colors.as_ref().unwrap(),
// ctx.canvas,
// ctx.font_cache,
// );
}
fn size(&self) -> Option<Vector2F> {
self.size
}
fn dispatch_event(&self, _: &Event, _: &mut EventContext, _: &AppContext) -> bool {
false
}
}
+84
View File
@@ -0,0 +1,84 @@
use super::{AppContext, Element, MutableAppContext};
use crate::{
fonts::{FamilyId, FontId, Properties},
geometry::vector::{vec2f, Vector2F},
AfterLayoutContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
pub struct LineBox {
child: Box<dyn Element>,
family_id: FamilyId,
font_size: f32,
font_properties: Properties,
font_id: Option<FontId>,
size: Option<Vector2F>,
}
impl LineBox {
pub fn new(family_id: FamilyId, font_size: f32, child: Box<dyn Element>) -> Self {
Self {
child,
family_id,
font_size,
font_properties: Properties::default(),
font_id: None,
size: None,
}
}
}
impl Element for LineBox {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
match ctx
.font_cache
.select_font(self.family_id, &self.font_properties)
{
Ok(font_id) => {
self.font_id = Some(font_id);
let line_height = ctx.font_cache.bounding_box(font_id, self.font_size).y();
let child_max = vec2f(
constraint.max.x(),
ctx.font_cache.ascent(font_id, self.font_size)
- ctx.font_cache.descent(font_id, self.font_size),
);
let child_size = self.child.layout(
SizeConstraint::new(constraint.min.min(child_max), child_max),
ctx,
app,
);
let size = vec2f(child_size.x(), line_height);
self.size = Some(size);
size
}
Err(error) => {
log::error!("can't layout LineBox: {}", error);
self.size = Some(constraint.min);
constraint.min
}
}
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
self.child.after_layout(ctx, app);
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
if let Some(font_id) = self.font_id {
let descent = ctx.font_cache.descent(font_id, self.font_size);
self.child.paint(origin + vec2f(0.0, -descent), ctx, app);
}
}
fn size(&self) -> Option<Vector2F> {
self.size
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
self.child.dispatch_event(event, ctx, app)
}
}
+80
View File
@@ -0,0 +1,80 @@
mod align;
mod constrained_box;
mod container;
mod empty;
mod event_handler;
mod flex;
mod label;
mod line_box;
mod stack;
mod svg;
mod uniform_list;
pub use align::*;
pub use constrained_box::*;
pub use container::*;
pub use empty::*;
pub use event_handler::*;
pub use flex::*;
pub use label::*;
pub use line_box::*;
pub use stack::*;
pub use svg::*;
pub use uniform_list::*;
use crate::{
AfterLayoutContext, AppContext, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use std::any::Any;
pub trait Element {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F;
fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext);
fn size(&self) -> Option<Vector2F>;
fn parent_data(&self) -> Option<&dyn Any> {
None
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool;
fn boxed(self) -> Box<dyn Element> {
Box::new(self)
}
}
pub trait ParentElement<'a>: Extend<Box<dyn Element>> + Sized {
fn add_children(&mut self, children: impl IntoIterator<Item = Box<dyn Element>>) {
self.extend(children);
}
fn add_child(&mut self, child: Box<dyn Element>) {
self.add_childen(Some(child));
}
fn with_children(mut self, children: impl IntoIterator<Item = Box<dyn Element>>) -> Self {
self.add_children(children);
self
}
fn with_child(self, child: Box<dyn Element>) -> Self {
self.with_children(Some(child))
}
}
impl<'a, T> ParentElement<'a> for T where T: Extend<Box<dyn Element>> {}
pub fn try_rect(origin: Option<Vector2F>, size: Option<Vector2F>) -> Option<RectF> {
origin.and_then(|origin| size.map(|size| RectF::new(origin, size)))
}
+65
View File
@@ -0,0 +1,65 @@
use crate::{
geometry::vector::Vector2F, AfterLayoutContext, AppContext, Element, Event, EventContext,
LayoutContext, MutableAppContext, PaintContext, SizeConstraint,
};
pub struct Stack {
children: Vec<Box<dyn Element>>,
size: Option<Vector2F>,
}
impl Stack {
pub fn new() -> Self {
Stack {
children: Vec::new(),
size: None,
}
}
}
impl Element for Stack {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
let mut size = constraint.min;
for child in &mut self.children {
size = size.max(child.layout(constraint, ctx, app));
}
self.size = Some(size);
size
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
for child in &mut self.children {
child.after_layout(ctx, app);
}
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
for child in &mut self.children {
child.paint(origin, ctx, app);
}
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
for child in self.children.iter().rev() {
if child.dispatch_event(event, ctx, app) {
return true;
}
}
false
}
fn size(&self) -> Option<Vector2F> {
self.size
}
}
impl Extend<Box<dyn Element>> for Stack {
fn extend<T: IntoIterator<Item = Box<dyn Element>>>(&mut self, children: T) {
self.children.extend(children)
}
}
+81
View File
@@ -0,0 +1,81 @@
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
use std::rc::Rc;
pub struct Svg {
path: String,
// tree: Option<Rc<usvg::Tree>>,
size: Option<Vector2F>,
}
impl Svg {
pub fn new(path: String) -> Self {
Self {
path,
// tree: None,
size: None,
}
}
}
impl Element for Svg {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
_: &AppContext,
) -> Vector2F {
// let size;
// match ctx.asset_cache.svg(&self.path) {
// Ok(tree) => {
// size = if constraint.max.x().is_infinite() && constraint.max.y().is_infinite() {
// let rect = usvg_rect_to_euclid_rect(&tree.svg_node().view_box.rect);
// rect.size()
// } else {
// let max_size = constraint.max;
// let svg_size = usvg_rect_to_euclid_rect(&tree.svg_node().view_box.rect).size();
// if max_size.x().is_infinite()
// || max_size.x() / max_size.y() > svg_size.x() / svg_size.y()
// {
// vec2f(svg_size.x() * max_size.y() / svg_size.y(), max_size.y())
// } else {
// vec2f(max_size.x(), svg_size.y() * max_size.x() / svg_size.x())
// }
// };
// self.tree = Some(tree);
// }
// Err(error) => {
// log::error!("{}", error);
// size = constraint.min;
// }
// };
// self.size = Some(size);
// size
todo!()
}
fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, _: &AppContext) {
if let Some(tree) = self.tree.as_ref() {
ctx.canvas
.draw_svg(tree, RectF::new(origin, self.size.unwrap()));
}
}
fn size(&self) -> Option<Vector2F> {
self.size
}
fn dispatch_event(&self, _: &Event, _: &mut EventContext, _: &AppContext) -> bool {
false
}
}
+226
View File
@@ -0,0 +1,226 @@
use super::{
try_rect, AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext,
MutableAppContext, PaintContext, SizeConstraint,
};
use crate::geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
};
use parking_lot::Mutex;
use std::{cmp, ops::Range, sync::Arc};
#[derive(Clone)]
pub struct UniformListState(Arc<Mutex<StateInner>>);
struct StateInner {
scroll_top: f32,
scroll_to: Option<usize>,
}
impl UniformListState {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(StateInner {
scroll_top: 0.0,
scroll_to: None,
})))
}
pub fn scroll_to(&self, item_ix: usize) {
self.0.lock().scroll_to = Some(item_ix);
}
}
pub struct UniformList<F, G>
where
F: Fn(Range<usize>, &AppContext) -> G,
G: Iterator<Item = Box<dyn Element>>,
{
state: UniformListState,
item_count: usize,
build_items: F,
scroll_max: Option<f32>,
items: Vec<Box<dyn Element>>,
origin: Option<Vector2F>,
size: Option<Vector2F>,
}
impl<F, G> UniformList<F, G>
where
F: Fn(Range<usize>, &AppContext) -> G,
G: Iterator<Item = Box<dyn Element>>,
{
pub fn new(state: UniformListState, item_count: usize, build_items: F) -> Self {
Self {
state,
item_count,
build_items,
scroll_max: None,
items: Default::default(),
origin: None,
size: None,
}
}
fn scroll(
&self,
position: Vector2F,
delta: Vector2F,
precise: bool,
ctx: &mut EventContext,
_: &AppContext,
) -> bool {
if !self.rect().unwrap().contains_point(position) {
return false;
}
if !precise {
todo!("still need to handle non-precise scroll events from a mouse wheel");
}
let mut state = self.state.0.lock();
state.scroll_top = (state.scroll_top - delta.y())
.max(0.0)
.min(self.scroll_max.unwrap());
ctx.dispatch_action("uniform_list:scroll", state.scroll_top);
true
}
fn autoscroll(&mut self, list_height: f32, item_height: f32) {
let mut state = self.state.0.lock();
let scroll_max = self.item_count as f32 * item_height - list_height;
if state.scroll_top > scroll_max {
state.scroll_top = scroll_max;
}
if let Some(item_ix) = state.scroll_to.take() {
let item_top = item_ix as f32 * item_height;
let item_bottom = item_top + item_height;
if item_top < state.scroll_top {
state.scroll_top = item_top;
} else if item_bottom > (state.scroll_top + list_height) {
state.scroll_top = item_bottom - list_height;
}
}
}
fn scroll_top(&self) -> f32 {
self.state.0.lock().scroll_top
}
fn rect(&self) -> Option<RectF> {
try_rect(self.origin, self.size)
}
}
impl<F, G> Element for UniformList<F, G>
where
F: Fn(Range<usize>, &AppContext) -> G,
G: Iterator<Item = Box<dyn Element>>,
{
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F {
if constraint.max.y().is_infinite() {
unimplemented!(
"UniformList does not support being rendered with an unconstrained height"
);
}
let mut size = constraint.max;
let mut item_constraint =
SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY));
let first_item = (self.build_items)(0..1, app).next();
if let Some(first_item) = first_item {
let mut item_size = first_item.layout(item_constraint, ctx, app);
item_size.set_x(size.x());
item_constraint.min = item_size;
item_constraint.max = item_size;
let scroll_height = self.item_count as f32 * item_size.y();
if scroll_height < size.y() {
size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
}
self.autoscroll(size.y(), item_size.y());
let start = cmp::min(
(self.scroll_top() / item_size.y()) as usize,
self.item_count,
);
let end = cmp::min(
self.item_count,
start + (size.y() / item_size.y()).ceil() as usize + 1,
);
self.items.clear();
self.items.extend((self.build_items)(start..end, app));
self.scroll_max = Some(item_size.y() * self.item_count as f32 - size.y());
for item in &mut self.items {
item.layout(item_constraint, ctx, app);
}
}
self.size = Some(size);
size
}
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
for item in &mut self.items {
item.after_layout(ctx, app);
}
}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
// self.origin = Some(origin);
// if let Some(item) = self.items.first() {
// ctx.canvas.save();
// let mut clip_path = Path2D::new();
// clip_path.rect(RectF::new(origin, self.size.unwrap()));
// ctx.canvas.clip_path(clip_path, FillRule::Winding);
// let item_height = item.size().unwrap().y();
// let mut item_origin = origin - vec2f(0.0, self.state.0.lock().scroll_top % item_height);
// for item in &mut self.items {
// item.paint(item_origin, ctx, app);
// item_origin += vec2f(0.0, item_height);
// }
// ctx.canvas.restore();
// }
}
fn size(&self) -> Option<Vector2F> {
self.size
}
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
let mut handled = false;
for item in &self.items {
if item.dispatch_event(event, ctx, app) {
handled = true;
}
}
match event {
Event::ScrollWheel {
position,
delta,
precise,
} => {
if self.scroll(*position, *delta, *precise, ctx, app) {
handled = true;
}
}
_ => {}
}
handled
}
}