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

3179
gpui/src/app.rs Normal file

File diff suppressed because it is too large Load diff

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
}
}

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()
}
}

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,
}

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
}
}

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
gpui/src/elements/flex.rs Normal file
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
gpui/src/elements/label.rs Normal file
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
}
}

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
gpui/src/elements/mod.rs Normal file
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)))
}

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
gpui/src/elements/svg.rs Normal file
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
}
}

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
}
}

298
gpui/src/fonts.rs Normal file
View 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);
}
}

View file

@ -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;

View file

@ -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
View 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
View file

@ -0,0 +1 @@
pub struct Scene;

77
gpui/src/util.rs Normal file
View 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)
);
}
}