WIP
This commit is contained in:
parent
a015c61337
commit
356bc41752
36 changed files with 14097 additions and 4 deletions
3179
gpui/src/app.rs
Normal file
3179
gpui/src/app.rs
Normal file
File diff suppressed because it is too large
Load diff
68
gpui/src/elements/align.rs
Normal file
68
gpui/src/elements/align.rs
Normal 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
gpui/src/elements/constrained_box.rs
Normal file
67
gpui/src/elements/constrained_box.rs
Normal 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
gpui/src/elements/container.rs
Normal file
358
gpui/src/elements/container.rs
Normal 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
gpui/src/elements/empty.rs
Normal file
45
gpui/src/elements/empty.rs
Normal 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
gpui/src/elements/event_handler.rs
Normal file
69
gpui/src/elements/event_handler.rs
Normal 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
gpui/src/elements/flex.rs
Normal file
201
gpui/src/elements/flex.rs
Normal 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
gpui/src/elements/label.rs
Normal file
154
gpui/src/elements/label.rs
Normal 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
gpui/src/elements/line_box.rs
Normal file
84
gpui/src/elements/line_box.rs
Normal 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
gpui/src/elements/mod.rs
Normal file
80
gpui/src/elements/mod.rs
Normal 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
gpui/src/elements/stack.rs
Normal file
65
gpui/src/elements/stack.rs
Normal 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
gpui/src/elements/svg.rs
Normal file
81
gpui/src/elements/svg.rs
Normal 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
gpui/src/elements/uniform_list.rs
Normal file
226
gpui/src/elements/uniform_list.rs
Normal 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
|
||||
}
|
||||
}
|
298
gpui/src/fonts.rs
Normal file
298
gpui/src/fonts.rs
Normal file
|
@ -0,0 +1,298 @@
|
|||
use crate::geometry::vector::{vec2f, Vector2F};
|
||||
use anyhow::{anyhow, Result};
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
|
||||
pub use font_kit::properties::{Properties, Weight};
|
||||
use font_kit::{
|
||||
font::Font, loaders::core_text::NativeFont, metrics::Metrics, source::SystemSource,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
pub type GlyphId = u32;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FamilyId(usize);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FontId(usize);
|
||||
|
||||
pub struct FontCache(RwLock<FontCacheState>);
|
||||
|
||||
pub struct FontCacheState {
|
||||
source: SystemSource,
|
||||
families: Vec<Family>,
|
||||
fonts: Vec<Arc<Font>>,
|
||||
font_names: Vec<Arc<String>>,
|
||||
font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
|
||||
metrics: HashMap<FontId, Metrics>,
|
||||
native_fonts: HashMap<(FontId, OrderedFloat<f32>), NativeFont>,
|
||||
fonts_by_name: HashMap<Arc<String>, FontId>,
|
||||
emoji_font_id: Option<FontId>,
|
||||
}
|
||||
|
||||
unsafe impl Send for FontCache {}
|
||||
|
||||
struct Family {
|
||||
name: String,
|
||||
font_ids: Vec<FontId>,
|
||||
}
|
||||
|
||||
impl FontCache {
|
||||
pub fn new() -> Self {
|
||||
Self(RwLock::new(FontCacheState {
|
||||
source: SystemSource::new(),
|
||||
families: Vec::new(),
|
||||
fonts: Vec::new(),
|
||||
font_names: Vec::new(),
|
||||
font_selections: HashMap::new(),
|
||||
metrics: HashMap::new(),
|
||||
native_fonts: HashMap::new(),
|
||||
fonts_by_name: HashMap::new(),
|
||||
emoji_font_id: None,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn load_family(&self, names: &[&str]) -> Result<FamilyId> {
|
||||
for name in names {
|
||||
let state = self.0.upgradable_read();
|
||||
|
||||
if let Some(ix) = state.families.iter().position(|f| f.name == *name) {
|
||||
return Ok(FamilyId(ix));
|
||||
}
|
||||
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
|
||||
if let Ok(handle) = state.source.select_family_by_name(name) {
|
||||
if handle.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let family_id = FamilyId(state.families.len());
|
||||
let mut font_ids = Vec::new();
|
||||
for font in handle.fonts() {
|
||||
let font = font.load()?;
|
||||
if font.glyph_for_char('m').is_none() {
|
||||
return Err(anyhow!("font must contain a glyph for the 'm' character"));
|
||||
}
|
||||
font_ids.push(push_font(&mut state, font));
|
||||
}
|
||||
|
||||
state.families.push(Family {
|
||||
name: String::from(*name),
|
||||
font_ids,
|
||||
});
|
||||
return Ok(family_id);
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"could not find a non-empty font family matching one of the given names"
|
||||
))
|
||||
}
|
||||
|
||||
pub fn default_font(&self, family_id: FamilyId) -> FontId {
|
||||
self.select_font(family_id, &Properties::default()).unwrap()
|
||||
}
|
||||
|
||||
pub fn select_font(&self, family_id: FamilyId, properties: &Properties) -> Result<FontId> {
|
||||
let inner = self.0.upgradable_read();
|
||||
if let Some(font_id) = inner
|
||||
.font_selections
|
||||
.get(&family_id)
|
||||
.and_then(|f| f.get(properties))
|
||||
{
|
||||
Ok(*font_id)
|
||||
} else {
|
||||
let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
|
||||
let family = &inner.families[family_id.0];
|
||||
let candidates = family
|
||||
.font_ids
|
||||
.iter()
|
||||
.map(|font_id| inner.fonts[font_id.0].properties())
|
||||
.collect::<Vec<_>>();
|
||||
let idx = font_kit::matching::find_best_match(&candidates, properties)?;
|
||||
let font_id = family.font_ids[idx];
|
||||
|
||||
inner
|
||||
.font_selections
|
||||
.entry(family_id)
|
||||
.or_default()
|
||||
.insert(properties.clone(), font_id);
|
||||
Ok(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font(&self, font_id: FontId) -> Arc<Font> {
|
||||
self.0.read().fonts[font_id.0].clone()
|
||||
}
|
||||
|
||||
pub fn font_name(&self, font_id: FontId) -> Arc<String> {
|
||||
self.0.read().font_names[font_id.0].clone()
|
||||
}
|
||||
|
||||
pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&Metrics) -> T,
|
||||
T: 'static,
|
||||
{
|
||||
let state = self.0.upgradable_read();
|
||||
if let Some(metrics) = state.metrics.get(&font_id) {
|
||||
f(metrics)
|
||||
} else {
|
||||
let metrics = state.fonts[font_id.0].metrics();
|
||||
let metric = f(&metrics);
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
state.metrics.insert(font_id, metrics);
|
||||
metric
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_emoji(&self, font_id: FontId) -> bool {
|
||||
self.0
|
||||
.read()
|
||||
.emoji_font_id
|
||||
.map_or(false, |emoji_font_id| emoji_font_id == font_id)
|
||||
}
|
||||
|
||||
pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F {
|
||||
let bounding_box = self.metric(font_id, |m| m.bounding_box);
|
||||
let width = self.scale_metric(bounding_box.width(), font_id, font_size);
|
||||
let height = self.scale_metric(bounding_box.height(), font_id, font_size);
|
||||
vec2f(width, height)
|
||||
}
|
||||
|
||||
pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 {
|
||||
let bounding_box = self.metric(font_id, |m| m.bounding_box);
|
||||
self.scale_metric(bounding_box.height(), font_id, font_size)
|
||||
}
|
||||
|
||||
pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 {
|
||||
self.scale_metric(self.metric(font_id, |m| m.cap_height), font_id, font_size)
|
||||
}
|
||||
|
||||
pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 {
|
||||
self.scale_metric(self.metric(font_id, |m| m.ascent), font_id, font_size)
|
||||
}
|
||||
|
||||
pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 {
|
||||
self.scale_metric(self.metric(font_id, |m| m.descent), font_id, font_size)
|
||||
}
|
||||
|
||||
// pub fn render_emoji(&self, glyph_id: GlyphId, font_size: f32) -> Result<Pattern> {
|
||||
// let key = (glyph_id, OrderedFloat(font_size));
|
||||
|
||||
// {
|
||||
// if let Some(image) = self.0.read().emoji_images.get(&key) {
|
||||
// return Ok(image.clone());
|
||||
// }
|
||||
// }
|
||||
|
||||
// let font_id = self.emoji_font_id()?;
|
||||
// let bounding_box = self.bounding_box(font_id, font_size);
|
||||
// let width = (4.0 * bounding_box.x()) as usize;
|
||||
// let height = (4.0 * bounding_box.y()) as usize;
|
||||
// let mut ctx = CGContext::create_bitmap_context(
|
||||
// None,
|
||||
// width,
|
||||
// height,
|
||||
// 8,
|
||||
// width * 4,
|
||||
// &CGColorSpace::create_device_rgb(),
|
||||
// kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault,
|
||||
// );
|
||||
// ctx.scale(4.0, 4.0);
|
||||
|
||||
// let native_font = self.native_font(font_id, font_size);
|
||||
// let glyph = glyph_id.0 as CGGlyph;
|
||||
// let glyph_bounds = native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph]);
|
||||
// let position = CGPoint::new(glyph_bounds.origin.x, -glyph_bounds.origin.y);
|
||||
|
||||
// native_font.draw_glyphs(&[glyph], &[position], ctx.clone());
|
||||
|
||||
// ctx.flush();
|
||||
|
||||
// let image = Pattern::from_image(Image::new(
|
||||
// vec2i(ctx.width() as i32, ctx.height() as i32),
|
||||
// Arc::new(u8_slice_to_color_slice(&ctx.data()).into()),
|
||||
// ));
|
||||
// self.0.write().emoji_images.insert(key, image.clone());
|
||||
|
||||
// Ok(image)
|
||||
// }
|
||||
|
||||
fn emoji_font_id(&self) -> Result<FontId> {
|
||||
let state = self.0.upgradable_read();
|
||||
|
||||
if let Some(font_id) = state.emoji_font_id {
|
||||
Ok(font_id)
|
||||
} else {
|
||||
let handle = state.source.select_family_by_name("Apple Color Emoji")?;
|
||||
let font = handle
|
||||
.fonts()
|
||||
.first()
|
||||
.ok_or(anyhow!("no fonts in Apple Color Emoji font family"))?
|
||||
.load()?;
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
let font_id = push_font(&mut state, font);
|
||||
state.emoji_font_id = Some(font_id);
|
||||
Ok(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
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 native_font(&self, font_id: FontId, size: f32) -> NativeFont {
|
||||
let native_key = (font_id, OrderedFloat(size));
|
||||
|
||||
let state = self.0.upgradable_read();
|
||||
if let Some(native_font) = state.native_fonts.get(&native_key).cloned() {
|
||||
native_font
|
||||
} else {
|
||||
let native_font = state.fonts[font_id.0]
|
||||
.native_font()
|
||||
.clone_with_font_size(size as f64);
|
||||
RwLockUpgradableReadGuard::upgrade(state)
|
||||
.native_fonts
|
||||
.insert(native_key, native_font.clone());
|
||||
native_font
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font_id_for_native_font(&self, native_font: NativeFont) -> FontId {
|
||||
let postscript_name = native_font.postscript_name();
|
||||
let state = self.0.upgradable_read();
|
||||
if let Some(font_id) = state.fonts_by_name.get(&postscript_name) {
|
||||
*font_id
|
||||
} else {
|
||||
push_font(&mut RwLockUpgradableReadGuard::upgrade(state), unsafe {
|
||||
Font::from_native_font(native_font.clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_font(state: &mut FontCacheState, font: Font) -> FontId {
|
||||
let font_id = FontId(state.fonts.len());
|
||||
let name = Arc::new(font.postscript_name().unwrap());
|
||||
if *name == "AppleColorEmoji" {
|
||||
state.emoji_font_id = Some(font_id);
|
||||
}
|
||||
state.fonts.push(Arc::new(font));
|
||||
state.font_names.push(name.clone());
|
||||
state.fonts_by_name.insert(name, font_id);
|
||||
font_id
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_render_emoji() {
|
||||
let ctx = FontCache::new();
|
||||
let _ = ctx.render_emoji(0, 16.0);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,17 @@
|
|||
mod app;
|
||||
pub mod elements;
|
||||
pub mod executor;
|
||||
mod fonts;
|
||||
pub mod keymap;
|
||||
pub mod platform;
|
||||
mod presenter;
|
||||
mod scene;
|
||||
mod util;
|
||||
|
||||
pub use app::*;
|
||||
pub use elements::Element;
|
||||
pub use pathfinder_color as color;
|
||||
pub use pathfinder_geometry as geometry;
|
||||
pub use platform::Event;
|
||||
pub use presenter::*;
|
||||
use scene::Scene;
|
||||
|
|
|
@ -9,7 +9,7 @@ pub mod current {
|
|||
use crate::{executor, geometry::rect::RectF};
|
||||
use anyhow::Result;
|
||||
use async_task::Runnable;
|
||||
use event::Event;
|
||||
pub use event::Event;
|
||||
use std::{path::PathBuf, rc::Rc, sync::Arc};
|
||||
|
||||
pub trait Runner {
|
||||
|
|
370
gpui/src/presenter.rs
Normal file
370
gpui/src/presenter.rs
Normal file
|
@ -0,0 +1,370 @@
|
|||
use crate::{
|
||||
app::{AppContext, MutableAppContext, WindowInvalidation},
|
||||
elements::Element,
|
||||
platform::Event,
|
||||
Scene,
|
||||
};
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use std::{any::Any, collections::HashMap, rc::Rc};
|
||||
|
||||
pub struct Presenter {
|
||||
window_id: usize,
|
||||
rendered_views: HashMap<usize, Box<dyn Element>>,
|
||||
parents: HashMap<usize, usize>,
|
||||
font_cache: Rc<FontCache>,
|
||||
text_layout_cache: LayoutCache,
|
||||
asset_cache: Rc<AssetCache>,
|
||||
}
|
||||
|
||||
impl Presenter {
|
||||
pub fn new(
|
||||
window_id: usize,
|
||||
font_cache: Rc<FontCache>,
|
||||
asset_cache: Rc<AssetCache>,
|
||||
app: &MutableAppContext,
|
||||
) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
rendered_views: app.render_views(window_id).unwrap(),
|
||||
parents: HashMap::new(),
|
||||
font_cache,
|
||||
text_layout_cache: LayoutCache::new(),
|
||||
asset_cache,
|
||||
}
|
||||
}
|
||||
|
||||
fn invalidate(&mut self, invalidation: WindowInvalidation, app: &AppContext) {
|
||||
for view_id in invalidation.updated {
|
||||
self.rendered_views
|
||||
.insert(view_id, app.render_view(self.window_id, view_id).unwrap());
|
||||
}
|
||||
for view_id in invalidation.removed {
|
||||
self.rendered_views.remove(&view_id);
|
||||
self.parents.remove(&view_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_scene(
|
||||
&mut self,
|
||||
window_size: Vector2F,
|
||||
scale_factor: f32,
|
||||
app: &mut MutableAppContext,
|
||||
) -> Scene {
|
||||
self.layout(window_size, app.ctx());
|
||||
self.after_layout(app);
|
||||
let scene = self.paint(window_size, scale_factor, app.ctx());
|
||||
self.text_layout_cache.finish_frame();
|
||||
scene
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vector2F, app: &AppContext) {
|
||||
if let Some(root_view_id) = app.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(),
|
||||
};
|
||||
layout_ctx.layout(root_view_id, SizeConstraint::strict(size), app);
|
||||
}
|
||||
}
|
||||
|
||||
fn after_layout(&mut self, app: &mut MutableAppContext) {
|
||||
if let Some(root_view_id) = app.root_view_id(self.window_id) {
|
||||
let mut ctx = AfterLayoutContext {
|
||||
rendered_views: &mut self.rendered_views,
|
||||
font_cache: &self.font_cache,
|
||||
text_layout_cache: &self.text_layout_cache,
|
||||
};
|
||||
ctx.after_layout(root_view_id, app);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self, size: Vector2F, scale_factor: f32, app: &AppContext) -> Scene {
|
||||
// let mut canvas = Canvas::new(size * scale_factor).get_context_2d(self.font_context.clone());
|
||||
// canvas.scale(scale_factor);
|
||||
|
||||
// if let Some(root_view_id) = app.root_view_id(self.window_id) {
|
||||
// let mut paint_ctx = PaintContext {
|
||||
// canvas: &mut canvas,
|
||||
// font_cache: &self.font_cache,
|
||||
// text_layout_cache: &self.text_layout_cache,
|
||||
// rendered_views: &mut self.rendered_views,
|
||||
// };
|
||||
// paint_ctx.paint(root_view_id, Vector2F::zero(), app);
|
||||
// }
|
||||
|
||||
// canvas.into_canvas().into_scene()
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn responder_chain(&self, app: &AppContext) -> Option<Vec<usize>> {
|
||||
app.focused_view_id(self.window_id).map(|mut view_id| {
|
||||
let mut chain = vec![view_id];
|
||||
while let Some(parent_id) = self.parents.get(&view_id) {
|
||||
view_id = *parent_id;
|
||||
chain.push(view_id);
|
||||
}
|
||||
chain.reverse();
|
||||
chain
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dispatch_event(
|
||||
&self,
|
||||
event: Event,
|
||||
app: &AppContext,
|
||||
) -> Vec<(usize, &'static str, Box<dyn Any>)> {
|
||||
let mut event_ctx = EventContext {
|
||||
rendered_views: &self.rendered_views,
|
||||
actions: Vec::new(),
|
||||
font_cache: &self.font_cache,
|
||||
text_layout_cache: &self.text_layout_cache,
|
||||
view_stack: Vec::new(),
|
||||
};
|
||||
if let Some(root_view_id) = app.root_view_id(self.window_id) {
|
||||
event_ctx.dispatch_event_on_view(root_view_id, &event, app);
|
||||
}
|
||||
event_ctx.actions
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayoutContext<'a> {
|
||||
rendered_views: &'a mut HashMap<usize, Box<dyn Element>>,
|
||||
parents: &'a mut HashMap<usize, usize>,
|
||||
pub font_cache: &'a FontCache,
|
||||
pub text_layout_cache: &'a LayoutCache,
|
||||
pub asset_cache: &'a AssetCache,
|
||||
view_stack: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<'a> LayoutContext<'a> {
|
||||
fn layout(&mut self, view_id: usize, constraint: SizeConstraint, app: &AppContext) -> Vector2F {
|
||||
if let Some(parent_id) = self.view_stack.last() {
|
||||
self.parents.insert(view_id, *parent_id);
|
||||
}
|
||||
self.view_stack.push(view_id);
|
||||
let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
|
||||
let size = rendered_view.layout(constraint, self, app);
|
||||
self.rendered_views.insert(view_id, rendered_view);
|
||||
self.view_stack.pop();
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AfterLayoutContext<'a> {
|
||||
rendered_views: &'a mut HashMap<usize, Box<dyn Element>>,
|
||||
pub font_cache: &'a FontCache,
|
||||
pub text_layout_cache: &'a LayoutCache,
|
||||
}
|
||||
|
||||
impl<'a> AfterLayoutContext<'a> {
|
||||
fn after_layout(&mut self, view_id: usize, app: &mut MutableAppContext) {
|
||||
if let Some(mut view) = self.rendered_views.remove(&view_id) {
|
||||
view.after_layout(self, app);
|
||||
self.rendered_views.insert(view_id, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PaintContext<'a> {
|
||||
rendered_views: &'a mut HashMap<usize, Box<dyn Element>>,
|
||||
// pub canvas: &'a mut CanvasRenderingContext2D,
|
||||
pub font_cache: &'a FontCache,
|
||||
pub text_layout_cache: &'a LayoutCache,
|
||||
}
|
||||
|
||||
impl<'a> PaintContext<'a> {
|
||||
fn paint(&mut self, view_id: usize, origin: Vector2F, app: &AppContext) {
|
||||
if let Some(mut tree) = self.rendered_views.remove(&view_id) {
|
||||
tree.paint(origin, self, app);
|
||||
self.rendered_views.insert(view_id, tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventContext<'a> {
|
||||
rendered_views: &'a HashMap<usize, Box<dyn Element>>,
|
||||
actions: Vec<(usize, &'static str, Box<dyn Any>)>,
|
||||
pub font_cache: &'a FontCache,
|
||||
pub text_layout_cache: &'a LayoutCache,
|
||||
view_stack: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<'a> EventContext<'a> {
|
||||
pub fn dispatch_event_on_view(
|
||||
&mut self,
|
||||
view_id: usize,
|
||||
event: &Event,
|
||||
app: &AppContext,
|
||||
) -> bool {
|
||||
if let Some(element) = self.rendered_views.get(&view_id) {
|
||||
self.view_stack.push(view_id);
|
||||
let result = element.dispatch_event(event, self, app);
|
||||
self.view_stack.pop();
|
||||
result
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_action<A: 'static + Any>(&mut self, name: &'static str, arg: A) {
|
||||
self.actions
|
||||
.push((*self.view_stack.last().unwrap(), name, Box::new(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Axis {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
pub fn invert(self) -> Self {
|
||||
match self {
|
||||
Self::Horizontal => Self::Vertical,
|
||||
Self::Vertical => Self::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Vector2FExt {
|
||||
fn along(self, axis: Axis) -> f32;
|
||||
}
|
||||
|
||||
impl Vector2FExt for Vector2F {
|
||||
fn along(self, axis: Axis) -> f32 {
|
||||
match axis {
|
||||
Axis::Horizontal => self.x(),
|
||||
Axis::Vertical => self.y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct SizeConstraint {
|
||||
pub min: Vector2F,
|
||||
pub max: Vector2F,
|
||||
}
|
||||
|
||||
impl SizeConstraint {
|
||||
pub fn new(min: Vector2F, max: Vector2F) -> Self {
|
||||
Self { min, max }
|
||||
}
|
||||
|
||||
pub fn strict(size: Vector2F) -> Self {
|
||||
Self {
|
||||
min: size,
|
||||
max: size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strict_along(axis: Axis, max: f32) -> Self {
|
||||
match axis {
|
||||
Axis::Horizontal => Self {
|
||||
min: vec2f(max, 0.0),
|
||||
max: vec2f(max, f32::INFINITY),
|
||||
},
|
||||
Axis::Vertical => Self {
|
||||
min: vec2f(0.0, max),
|
||||
max: vec2f(f32::INFINITY, max),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_along(&self, axis: Axis) -> f32 {
|
||||
match axis {
|
||||
Axis::Horizontal => self.max.x(),
|
||||
Axis::Vertical => self.max.y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChildView {
|
||||
view_id: usize,
|
||||
size: Option<Vector2F>,
|
||||
origin: Option<Vector2F>,
|
||||
}
|
||||
|
||||
impl ChildView {
|
||||
pub fn new(view_id: usize) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
size: None,
|
||||
origin: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for ChildView {
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
ctx: &mut LayoutContext,
|
||||
app: &AppContext,
|
||||
) -> Vector2F {
|
||||
let size = ctx.layout(self.view_id, constraint, app);
|
||||
self.size = Some(size);
|
||||
size
|
||||
}
|
||||
|
||||
fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) {
|
||||
ctx.after_layout(self.view_id, app);
|
||||
}
|
||||
|
||||
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
|
||||
self.origin = Some(origin);
|
||||
ctx.paint(self.view_id, origin, app);
|
||||
}
|
||||
|
||||
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool {
|
||||
ctx.dispatch_event_on_view(self.view_id, event, app)
|
||||
}
|
||||
|
||||
fn size(&self) -> Option<Vector2F> {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// #[test]
|
||||
// fn test_responder_chain() {
|
||||
// let settings = settings_rx(None);
|
||||
// let mut app = App::new().unwrap();
|
||||
// let workspace = app.add_model(|ctx| Workspace::new(Vec::new(), ctx));
|
||||
// let (window_id, workspace_view) =
|
||||
// app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
|
||||
|
||||
// let invalidations = Rc::new(RefCell::new(Vec::new()));
|
||||
// let invalidations_ = invalidations.clone();
|
||||
// app.on_window_invalidated(window_id, move |invalidation, _| {
|
||||
// invalidations_.borrow_mut().push(invalidation)
|
||||
// });
|
||||
|
||||
// let active_pane_id = workspace_view.update(&mut app, |view, ctx| {
|
||||
// ctx.focus(view.active_pane());
|
||||
// view.active_pane().id()
|
||||
// });
|
||||
|
||||
// app.update(|app| {
|
||||
// let mut presenter = Presenter::new(
|
||||
// window_id,
|
||||
// Rc::new(FontCache::new()),
|
||||
// Rc::new(AssetCache::new()),
|
||||
// app,
|
||||
// );
|
||||
// for invalidation in invalidations.borrow().iter().cloned() {
|
||||
// presenter.update(vec2f(1024.0, 768.0), 2.0, Some(invalidation), app);
|
||||
// }
|
||||
|
||||
// assert_eq!(
|
||||
// presenter.responder_chain(app.ctx()).unwrap(),
|
||||
// vec![workspace_view.id(), active_pane_id]
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
}
|
1
gpui/src/scene.rs
Normal file
1
gpui/src/scene.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub struct Scene;
|
77
gpui/src/util.rs
Normal file
77
gpui/src/util.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use rand::prelude::*;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
pub fn pre_inc(value: &mut usize) -> usize {
|
||||
*value += 1;
|
||||
*value
|
||||
}
|
||||
|
||||
pub fn post_inc(value: &mut usize) -> usize {
|
||||
let prev = *value;
|
||||
*value += 1;
|
||||
prev
|
||||
}
|
||||
|
||||
pub fn find_insertion_index<'a, F, T, E>(slice: &'a [T], mut f: F) -> Result<usize, E>
|
||||
where
|
||||
F: FnMut(&'a T) -> Result<Ordering, E>,
|
||||
{
|
||||
use Ordering::*;
|
||||
|
||||
let s = slice;
|
||||
let mut size = s.len();
|
||||
if size == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
let mut base = 0usize;
|
||||
while size > 1 {
|
||||
let half = size / 2;
|
||||
let mid = base + half;
|
||||
// mid is always in [0, size), that means mid is >= 0 and < size.
|
||||
// mid >= 0: by definition
|
||||
// mid < size: mid = size / 2 + size / 4 + size / 8 ...
|
||||
let cmp = f(unsafe { s.get_unchecked(mid) })?;
|
||||
base = if cmp == Greater { base } else { mid };
|
||||
size -= half;
|
||||
}
|
||||
// base is always in [0, size) because base <= mid.
|
||||
let cmp = f(unsafe { s.get_unchecked(base) })?;
|
||||
if cmp == Equal {
|
||||
Ok(base)
|
||||
} else {
|
||||
Ok(base + (cmp == Less) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RandomCharIter<T: Rng>(T);
|
||||
|
||||
impl<T: Rng> RandomCharIter<T> {
|
||||
pub fn new(rng: T) -> Self {
|
||||
Self(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Rng> Iterator for RandomCharIter<T> {
|
||||
type Item = char;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.0.gen_bool(1.0 / 5.0) {
|
||||
Some('\n')
|
||||
} else {
|
||||
Some(self.0.gen_range(b'a', b'z' + 1).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_find_insertion_index() {
|
||||
assert_eq!(
|
||||
find_insertion_index(&[0, 4, 8], |probe| Ok::<Ordering, ()>(probe.cmp(&2))),
|
||||
Ok(1)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue