Introduce gpui2 and storybook crates (#2919)

This PR splits `crates/gpui/playground` with two new crates: `gpui2` and
`storybook`.

GPUI 2 re-exports most of `gpui`, but makes some adjustments. I want to
keep the scope focused. This isn't literally version 2.0 of GPUI or
anything. It's just a convenient way to make a create with fewer
dependencies where we can iterate quickly on aspects of GPUI's design.
Most of the focus is on improving our approach to element layout and
styling.

The `storybook` crate is pretty empty for now, but it's where I intend
to start rebuilding interfaces. I welcome anyone else to join me in
this, though until I get one interface fully built, buyer beware. You
may need to pair on it with me.
This commit is contained in:
Nathan Sobo 2023-09-11 09:19:43 -06:00 committed by GitHub
commit c3a3543ebb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
111 changed files with 5059 additions and 3252 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
**/target
**/cargo-target
/zed.xcworkspace
.DS_Store
/plugins/bin

75
Cargo.lock generated
View file

@ -3163,6 +3163,7 @@ dependencies = [
"sqlez",
"sum_tree",
"taffy",
"thiserror",
"time 0.3.27",
"tiny-skia",
"usvg",
@ -3171,6 +3172,36 @@ dependencies = [
"waker-fn",
]
[[package]]
name = "gpui2"
version = "0.1.0"
dependencies = [
"anyhow",
"derive_more",
"futures 0.3.28",
"gpui",
"gpui2_macros",
"log",
"parking_lot 0.11.2",
"refineable",
"rust-embed",
"serde",
"settings",
"simplelog",
"smallvec",
"theme",
"util",
]
[[package]]
name = "gpui2_macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "gpui_macros"
version = "0.1.0"
@ -5231,33 +5262,6 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "playground"
version = "0.1.0"
dependencies = [
"anyhow",
"derive_more",
"gpui",
"log",
"parking_lot 0.11.2",
"playground_macros",
"refineable",
"serde",
"simplelog",
"smallvec",
"taffy",
"util",
]
[[package]]
name = "playground_macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "plist"
version = "1.5.0"
@ -7367,6 +7371,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "storybook"
version = "0.1.0"
dependencies = [
"anyhow",
"gpui2",
"log",
"rust-embed",
"serde",
"settings",
"simplelog",
"theme",
"util",
]
[[package]]
name = "stringprep"
version = "0.1.3"
@ -7566,7 +7585,7 @@ dependencies = [
[[package]]
name = "taffy"
version = "0.3.11"
source = "git+https://github.com/DioxusLabs/taffy?rev=dab541d6104d58e2e10ce90c4a1dad0b703160cd#dab541d6104d58e2e10ce90c4a1dad0b703160cd"
source = "git+https://github.com/DioxusLabs/taffy?rev=4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e#4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e"
dependencies = [
"arrayvec 0.7.4",
"grid",

View file

@ -32,9 +32,9 @@ members = [
"crates/git",
"crates/go_to_line",
"crates/gpui",
"crates/gpui/playground",
"crates/gpui/playground_macros",
"crates/gpui_macros",
"crates/gpui2",
"crates/gpui2_macros",
"crates/install_cli",
"crates/journal",
"crates/language",
@ -63,6 +63,7 @@ members = [
"crates/sqlez",
"crates/sqlez_macros",
"crates/feature_flags",
"crates/storybook",
"crates/sum_tree",
"crates/terminal",
"crates/text",

View file

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 740 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -2434,14 +2434,14 @@ fn render_tree_branch(
let cap_height = row_style.cap_height(font_cache);
let baseline_offset = row_style.baseline_offset(font_cache) + (size.y() - line_height) / 2.;
Canvas::new(move |scene, bounds, _, _, _| {
scene.paint_layer(None, |scene| {
Canvas::new(move |bounds, _, _, cx| {
cx.paint_layer(None, |cx| {
let start_x = bounds.min_x() + (bounds.width() / 2.) - (branch_style.width / 2.);
let end_x = bounds.max_x();
let start_y = bounds.min_y();
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
scene.push_quad(gpui::Quad {
cx.scene().push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, start_y),
vec2f(
@ -2453,7 +2453,7 @@ fn render_tree_branch(
border: gpui::Border::default(),
corner_radii: (0.).into(),
});
scene.push_quad(gpui::Quad {
cx.scene().push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, end_y),
vec2f(end_x, end_y + branch_style.width),

View file

@ -13,8 +13,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle,
WeakViewHandle,
};
use picker::PickerEvent;
use project::{Project, RepositoryEntry};
@ -1165,19 +1165,18 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
&mut self,
constraint: gpui::SizeConstraint,
_: &mut CollabTitlebarItem,
_: &mut LayoutContext<CollabTitlebarItem>,
_: &mut ViewContext<CollabTitlebarItem>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut CollabTitlebarItem,
_: &mut PaintContext<CollabTitlebarItem>,
cx: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState {
let mut path = PathBuilder::new();
path.reset(bounds.lower_left());
@ -1188,7 +1187,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
path.curve_to(bounds.lower_right(), bounds.upper_right());
path.line_to(bounds.lower_left());
scene.push_path(path.build(self.color, None));
cx.scene().push_path(path.build(self.color, None));
}
fn rect_for_text_range(

View file

@ -7,7 +7,7 @@ use gpui::{
},
json::ToJson,
serde_json::{self, json},
AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
AnyElement, Axis, Element, View, ViewContext,
};
pub(crate) struct FacePile<V: View> {
@ -32,7 +32,7 @@ impl<V: View> Element<V> for FacePile<V> {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
@ -53,12 +53,11 @@ impl<V: View> Element<V> for FacePile<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@ -69,9 +68,10 @@ impl<V: View> Element<V> for FacePile<V> {
let size = face.size();
origin_x -= size.x();
let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
scene.paint_layer(None, |scene| {
face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx);
});
cx.scene().push_layer(None);
face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
cx.scene().pop_layer();
origin_x += self.overlap;
}

View file

@ -32,8 +32,8 @@ use gpui::{
json::{self, ToJson},
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache},
AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, MouseRegion, Quad,
SizeConstraint, ViewContext, WindowContext,
};
use itertools::Itertools;
use json::json;
@ -131,7 +131,6 @@ impl EditorElement {
}
fn attach_mouse_handlers(
scene: &mut SceneBuilder,
position_map: &Arc<PositionMap>,
has_popovers: bool,
visible_bounds: RectF,
@ -141,124 +140,124 @@ impl EditorElement {
cx: &mut ViewContext<Editor>,
) {
enum EditorElementMouseHandlers {}
scene.push_mouse_region(
MouseRegion::new::<EditorElementMouseHandlers>(
cx.view_id(),
cx.view_id(),
visible_bounds,
)
.on_down(MouseButton::Left, {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_down(
editor,
event.platform_event,
position_map.as_ref(),
text_bounds,
gutter_bounds,
cx,
) {
cx.propagate_event();
let view_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::new::<EditorElementMouseHandlers>(view_id, view_id, visible_bounds)
.on_down(MouseButton::Left, {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_down(
editor,
event.platform_event,
position_map.as_ref(),
text_bounds,
gutter_bounds,
cx,
) {
cx.propagate_event();
}
}
}
})
.on_down(MouseButton::Right, {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_right_down(
editor,
event.position,
position_map.as_ref(),
text_bounds,
cx,
) {
cx.propagate_event();
})
.on_down(MouseButton::Right, {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_right_down(
editor,
event.position,
position_map.as_ref(),
text_bounds,
cx,
) {
cx.propagate_event();
}
}
}
})
.on_up(MouseButton::Left, {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_up(
editor,
event.position,
event.cmd,
event.shift,
event.alt,
position_map.as_ref(),
text_bounds,
cx,
) {
cx.propagate_event()
}
}
})
.on_drag(MouseButton::Left, {
let position_map = position_map.clone();
move |event, editor, cx| {
if event.end {
return;
})
.on_up(MouseButton::Left, {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_up(
editor,
event.position,
event.cmd,
event.shift,
event.alt,
position_map.as_ref(),
text_bounds,
cx,
) {
cx.propagate_event()
}
}
})
.on_drag(MouseButton::Left, {
let position_map = position_map.clone();
move |event, editor, cx| {
if event.end {
return;
}
if !Self::mouse_dragged(
editor,
event.platform_event,
position_map.as_ref(),
text_bounds,
cx,
) {
cx.propagate_event()
if !Self::mouse_dragged(
editor,
event.platform_event,
position_map.as_ref(),
text_bounds,
cx,
) {
cx.propagate_event()
}
}
}
})
.on_move({
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_moved(
editor,
event.platform_event,
&position_map,
text_bounds,
cx,
) {
cx.propagate_event()
})
.on_move({
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_moved(
editor,
event.platform_event,
&position_map,
text_bounds,
cx,
) {
cx.propagate_event()
}
}
}
})
.on_move_out(move |_, editor: &mut Editor, cx| {
if has_popovers {
hide_hover(editor, cx);
}
})
.on_scroll({
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::scroll(
editor,
event.position,
*event.delta.raw(),
event.delta.precise(),
&position_map,
bounds,
cx,
) {
cx.propagate_event()
})
.on_move_out(move |_, editor: &mut Editor, cx| {
if has_popovers {
hide_hover(editor, cx);
}
}
}),
})
.on_scroll({
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::scroll(
editor,
event.position,
*event.delta.raw(),
event.delta.precise(),
&position_map,
bounds,
cx,
) {
cx.propagate_event()
}
}
}),
);
enum GutterHandlers {}
scene.push_mouse_region(
MouseRegion::new::<GutterHandlers>(cx.view_id(), cx.view_id() + 1, gutter_bounds)
.on_hover(|hover, editor: &mut Editor, cx| {
let view_id = cx.view_id();
let region_id = cx.view_id() + 1;
cx.scene().push_mouse_region(
MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
|hover, editor: &mut Editor, cx| {
editor.gutter_hover(
&GutterHover {
hovered: hover.started,
},
cx,
);
}),
},
),
)
}
@ -528,24 +527,24 @@ impl EditorElement {
fn paint_background(
&self,
scene: &mut SceneBuilder,
gutter_bounds: RectF,
text_bounds: RectF,
layout: &LayoutState,
cx: &mut ViewContext<Editor>,
) {
let bounds = gutter_bounds.union_rect(text_bounds);
let scroll_top =
layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: gutter_bounds,
background: Some(self.style.gutter_background),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: Default::default(),
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: text_bounds,
background: Some(self.style.background),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: Default::default(),
});
@ -570,10 +569,10 @@ impl EditorElement {
bounds.width(),
layout.position_map.line_height * (end_row - start_row + 1) as f32,
);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(origin, size),
background: Some(self.style.active_line_background),
border: Border::default(),
border: Border::default().into(),
corner_radii: Default::default(),
});
}
@ -590,10 +589,10 @@ impl EditorElement {
bounds.width(),
layout.position_map.line_height * highlighted_rows.len() as f32,
);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(origin, size),
background: Some(self.style.highlighted_line_background),
border: Border::default(),
border: Border::default().into(),
corner_radii: Default::default(),
});
}
@ -617,13 +616,13 @@ impl EditorElement {
} else {
self.style.wrap_guide
};
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(
vec2f(x, text_bounds.origin_y()),
vec2f(1., text_bounds.height()),
),
background: Some(color),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: Default::default(),
});
}
@ -632,12 +631,11 @@ impl EditorElement {
fn paint_gutter(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) {
let line_height = layout.position_map.line_height;
@ -650,7 +648,7 @@ impl EditorElement {
);
if show_gutter {
Self::paint_diff_hunks(scene, bounds, layout, cx);
Self::paint_diff_hunks(bounds, layout, cx);
}
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
@ -661,7 +659,7 @@ impl EditorElement {
ix as f32 * line_height - (scroll_top % line_height),
);
line.paint(scene, line_origin, visible_bounds, line_height, cx);
line.paint(line_origin, visible_bounds, line_height, cx);
}
}
@ -678,7 +676,7 @@ impl EditorElement {
let indicator_origin = bounds.origin() + position + centering_offset;
indicator.paint(scene, indicator_origin, visible_bounds, editor, cx);
indicator.paint(indicator_origin, visible_bounds, editor, cx);
}
}
@ -687,22 +685,11 @@ impl EditorElement {
let mut y = *row as f32 * line_height - scroll_top;
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
y += (line_height - indicator.size().y()) / 2.;
indicator.paint(
scene,
bounds.origin() + vec2f(x, y),
visible_bounds,
editor,
cx,
);
indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx);
}
}
fn paint_diff_hunks(
scene: &mut SceneBuilder,
bounds: RectF,
layout: &mut LayoutState,
cx: &mut ViewContext<Editor>,
) {
fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut ViewContext<Editor>) {
let diff_style = &theme::current(cx).editor.diff.clone();
let line_height = layout.position_map.line_height;
@ -721,10 +708,10 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: highlight_bounds,
background: Some(diff_style.modified),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: (1. * line_height).into(),
});
@ -754,10 +741,10 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: highlight_bounds,
background: Some(diff_style.deleted),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: (1. * line_height).into(),
});
@ -776,10 +763,10 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: highlight_bounds,
background: Some(color),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: (diff_style.corner_radius * line_height).into(),
});
}
@ -787,12 +774,11 @@ impl EditorElement {
fn paint_text(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) {
let style = &self.style;
let scroll_position = layout.position_map.snapshot.scroll_position();
@ -804,9 +790,9 @@ impl EditorElement {
let line_end_overshoot = 0.15 * layout.position_map.line_height;
let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
scene.push_layer(Some(bounds));
cx.scene().push_layer(Some(bounds));
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds,
style: if !editor.link_go_to_definition_state.definitions.is_empty() {
CursorStyle::PointingHand
@ -819,7 +805,6 @@ impl EditorElement {
self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
for (id, range, color) in layout.fold_ranges.iter() {
self.paint_highlighted_range(
scene,
range.clone(),
*color,
fold_corner_radius,
@ -829,6 +814,7 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
cx,
);
for bound in range_to_bounds(
@ -840,7 +826,7 @@ impl EditorElement {
line_end_overshoot,
&layout.position_map,
) {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: bound,
style: CursorStyle::PointingHand,
});
@ -851,8 +837,9 @@ impl EditorElement {
.to_point(&layout.position_map.snapshot.display_snapshot)
.row;
scene.push_mouse_region(
MouseRegion::new::<FoldMarkers>(cx.view_id(), *id as usize, bound)
let view_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
.on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
editor.unfold_at(&UnfoldAt { buffer_row }, cx)
})
@ -864,7 +851,6 @@ impl EditorElement {
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(
scene,
range.clone(),
*color,
0.,
@ -874,6 +860,7 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
cx,
);
}
@ -891,7 +878,6 @@ impl EditorElement {
for selection in selections {
self.paint_highlighted_range(
scene,
selection.range.clone(),
selection_style.selection,
corner_radius,
@ -901,6 +887,7 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
cx,
);
if selection.is_local && !selection.range.is_empty() {
@ -980,7 +967,6 @@ impl EditorElement {
layout,
row,
scroll_top,
scene,
content_origin,
scroll_left,
visible_text_bounds,
@ -992,14 +978,14 @@ impl EditorElement {
}
}
scene.paint_layer(Some(bounds), |scene| {
for cursor in cursors {
cursor.paint(scene, content_origin, cx);
}
});
cx.scene().push_layer(Some(bounds));
for cursor in cursors {
cursor.paint(content_origin, cx);
}
cx.scene().pop_layer();
if let Some((position, context_menu)) = layout.context_menu.as_mut() {
scene.push_stacking_context(None, None);
cx.scene().push_stacking_context(None, None);
let cursor_row_layout =
&layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
@ -1019,18 +1005,17 @@ impl EditorElement {
}
context_menu.paint(
scene,
list_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
editor,
cx,
);
scene.pop_stacking_context();
cx.scene().pop_stacking_context();
}
if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
scene.push_stacking_context(None, None);
cx.scene().push_stacking_context(None, None);
// This is safe because we check on layout whether the required row is available
let hovered_row_layout =
@ -1061,7 +1046,6 @@ impl EditorElement {
}
hover_popover.paint(
scene,
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
editor,
@ -1083,7 +1067,6 @@ impl EditorElement {
}
hover_popover.paint(
scene,
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
editor,
@ -1094,10 +1077,10 @@ impl EditorElement {
}
}
scene.pop_stacking_context();
cx.scene().pop_stacking_context();
}
scene.pop_layer();
cx.scene().pop_layer();
}
fn scrollbar_left(&self, bounds: &RectF) -> f32 {
@ -1106,11 +1089,10 @@ impl EditorElement {
fn paint_scrollbar(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
layout: &mut LayoutState,
cx: &mut ViewContext<Editor>,
editor: &Editor,
cx: &mut ViewContext<Editor>,
) {
enum ScrollbarMouseHandlers {}
if layout.mode != EditorMode::Full {
@ -1147,9 +1129,9 @@ impl EditorElement {
let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
if layout.show_scrollbars {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: track_bounds,
border: style.track.border,
border: style.track.border.into(),
background: style.track.background_color,
..Default::default()
});
@ -1177,10 +1159,10 @@ impl EditorElement {
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(color),
border,
border: border.into(),
corner_radii: style.thumb.corner_radii.into(),
})
};
@ -1237,29 +1219,30 @@ impl EditorElement {
left: true,
};
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(color),
border,
border: border.into(),
corner_radii: style.thumb.corner_radii.into(),
})
}
}
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: thumb_bounds,
border: style.thumb.border,
border: style.thumb.border.into(),
background: style.thumb.background_color,
corner_radii: style.thumb.corner_radii.into(),
});
}
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: track_bounds,
style: CursorStyle::Arrow,
});
scene.push_mouse_region(
MouseRegion::new::<ScrollbarMouseHandlers>(cx.view_id(), cx.view_id(), track_bounds)
let region_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
.on_move(move |event, editor: &mut Editor, cx| {
if event.pressed_button.is_none() {
editor.scroll_manager.show_scrollbar(cx);
@ -1305,7 +1288,6 @@ impl EditorElement {
#[allow(clippy::too_many_arguments)]
fn paint_highlighted_range(
&self,
scene: &mut SceneBuilder,
range: Range<DisplayPoint>,
color: Color,
corner_radius: f32,
@ -1315,6 +1297,7 @@ impl EditorElement {
scroll_top: f32,
scroll_left: f32,
bounds: RectF,
cx: &mut ViewContext<Editor>,
) {
let start_row = layout.visible_display_row_range.start;
let end_row = layout.visible_display_row_range.end;
@ -1358,18 +1341,17 @@ impl EditorElement {
.collect(),
};
highlighted_range.paint(bounds, scene);
highlighted_range.paint(bounds, cx);
}
}
fn paint_blocks(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) {
let scroll_position = layout.position_map.snapshot.scroll_position();
let scroll_left = scroll_position.x() * layout.position_map.em_width;
@ -1384,9 +1366,7 @@ impl EditorElement {
if !matches!(block.style, BlockStyle::Sticky) {
origin += vec2f(-scroll_left, 0.);
}
block
.element
.paint(scene, origin, visible_bounds, editor, cx);
block.element.paint(origin, visible_bounds, editor, cx);
}
}
@ -1690,7 +1670,7 @@ impl EditorElement {
style: &EditorStyle,
line_layouts: &[LineWithInvisibles],
editor: &mut Editor,
cx: &mut LayoutContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> (f32, Vec<BlockLayout>) {
let mut block_id = 0;
let scroll_x = snapshot.scroll_anchor.offset.x();
@ -2022,7 +2002,6 @@ impl LineWithInvisibles {
layout: &LayoutState,
row: u32,
scroll_top: f32,
scene: &mut SceneBuilder,
content_origin: Vector2F,
scroll_left: f32,
visible_text_bounds: RectF,
@ -2035,7 +2014,6 @@ impl LineWithInvisibles {
let line_y = row as f32 * line_height - scroll_top;
self.line.paint(
scene,
content_origin + vec2f(-scroll_left, line_y),
visible_text_bounds,
line_height,
@ -2049,7 +2027,6 @@ impl LineWithInvisibles {
scroll_left,
line_y,
row,
scene,
visible_bounds,
line_height,
whitespace_setting,
@ -2065,7 +2042,6 @@ impl LineWithInvisibles {
scroll_left: f32,
line_y: f32,
row: u32,
scene: &mut SceneBuilder,
visible_bounds: RectF,
line_height: f32,
whitespace_setting: ShowWhitespaceSetting,
@ -2097,7 +2073,7 @@ impl LineWithInvisibles {
continue;
}
}
invisible_symbol.paint(scene, origin, visible_bounds, line_height, cx);
invisible_symbol.paint(origin, visible_bounds, line_height, cx);
}
}
}
@ -2116,7 +2092,7 @@ impl Element<Editor> for EditorElement {
&mut self,
constraint: SizeConstraint,
editor: &mut Editor,
cx: &mut LayoutContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max;
if size.x().is_infinite() {
@ -2590,15 +2566,14 @@ impl Element<Editor> for EditorElement {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
cx.scene().push_layer(Some(visible_bounds));
let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
let text_bounds = RectF::new(
@ -2607,7 +2582,6 @@ impl Element<Editor> for EditorElement {
);
Self::attach_mouse_handlers(
scene,
&layout.position_map,
layout.hover_popovers.is_some(),
visible_bounds,
@ -2617,20 +2591,19 @@ impl Element<Editor> for EditorElement {
cx,
);
self.paint_background(scene, gutter_bounds, text_bounds, layout);
self.paint_background(gutter_bounds, text_bounds, layout, cx);
if layout.gutter_size.x() > 0. {
self.paint_gutter(scene, gutter_bounds, visible_bounds, layout, editor, cx);
self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
}
self.paint_text(scene, text_bounds, visible_bounds, layout, editor, cx);
self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
scene.push_layer(Some(bounds));
cx.scene().push_layer(Some(bounds));
if !layout.blocks.is_empty() {
self.paint_blocks(scene, bounds, visible_bounds, layout, editor, cx);
self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
}
self.paint_scrollbar(scene, bounds, layout, cx, &editor);
scene.pop_layer();
scene.pop_layer();
self.paint_scrollbar(bounds, layout, &editor, cx);
cx.scene().pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(
@ -2873,7 +2846,7 @@ impl Cursor {
)
}
pub fn paint(&self, scene: &mut SceneBuilder, origin: Vector2F, cx: &mut WindowContext) {
pub fn paint(&self, origin: Vector2F, cx: &mut WindowContext) {
let bounds = match self.shape {
CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)),
CursorShape::Block | CursorShape::Hollow => RectF::new(
@ -2888,14 +2861,14 @@ impl Cursor {
//Draw background or border quad
if matches!(self.shape, CursorShape::Hollow) {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: None,
border: Border::all(1., self.color),
border: Border::all(1., self.color).into(),
corner_radii: Default::default(),
});
} else {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(self.color),
border: Default::default(),
@ -2904,7 +2877,7 @@ impl Cursor {
}
if let Some(block_text) = &self.block_text {
block_text.paint(scene, self.origin + origin, bounds, self.line_height, cx);
block_text.paint(self.origin + origin, bounds, self.line_height, cx);
}
}
@ -2929,17 +2902,17 @@ pub struct HighlightedRangeLine {
}
impl HighlightedRange {
pub fn paint(&self, bounds: RectF, scene: &mut SceneBuilder) {
pub fn paint(&self, bounds: RectF, cx: &mut WindowContext) {
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
self.paint_lines(
self.start_y + self.line_height,
&self.lines[1..],
bounds,
scene,
cx,
);
} else {
self.paint_lines(self.start_y, &self.lines, bounds, scene);
self.paint_lines(self.start_y, &self.lines, bounds, cx);
}
}
@ -2948,7 +2921,7 @@ impl HighlightedRange {
start_y: f32,
lines: &[HighlightedRangeLine],
bounds: RectF,
scene: &mut SceneBuilder,
cx: &mut WindowContext,
) {
if lines.is_empty() {
return;
@ -3046,7 +3019,7 @@ impl HighlightedRange {
}
path.line_to(first_top_right - top_curve_width);
scene.push_path(path.build(self.color, Some(bounds)));
cx.scene().push_path(path.build(self.color, Some(bounds)));
}
}
@ -3204,18 +3177,10 @@ mod tests {
Point::new(5, 6)..Point::new(6, 0),
]);
});
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
editor,
&mut layout_cx,
cx,
)
});
assert_eq!(state.selections.len(), 1);
@ -3296,18 +3261,10 @@ mod tests {
DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
]);
});
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
editor,
&mut layout_cx,
cx,
)
});
@ -3363,18 +3320,10 @@ mod tests {
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (size, mut state) = editor.update(cx, |editor, cx| {
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
editor,
&mut layout_cx,
cx,
)
});
@ -3389,17 +3338,9 @@ mod tests {
);
// Don't panic.
let mut scene = SceneBuilder::new(1.0);
let bounds = RectF::new(Default::default(), size);
editor.update(cx, |editor, cx| {
element.paint(
&mut scene,
bounds,
bounds,
&mut state,
editor,
&mut PaintContext::new(cx),
);
element.paint(bounds, bounds, &mut state, editor, cx);
});
}
@ -3567,18 +3508,10 @@ mod tests {
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
editor.set_wrap_width(Some(editor_width), cx);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(editor_width, 500.), vec2f(editor_width, 500.)),
editor,
&mut layout_cx,
cx,
)
});

View file

@ -691,15 +691,15 @@ impl InfoPopover {
.with_highlights(rendered_content.highlights.clone())
.with_custom_runs(
rendered_content.region_ranges.clone(),
move |ix, bounds, scene, _| {
move |ix, bounds, cx| {
region_id += 1;
let region = regions[ix].clone();
if let Some(url) = region.link_url {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds,
style: CursorStyle::PointingHand,
});
scene.push_mouse_region(
cx.scene().push_mouse_region(
MouseRegion::new::<Self>(view_id, region_id, bounds)
.on_click::<Editor, _>(
MouseButton::Left,
@ -708,7 +708,7 @@ impl InfoPopover {
);
}
if region.code {
scene.push_quad(gpui::Quad {
cx.scene().push_quad(gpui::Quad {
bounds,
background: Some(code_span_background_color),
border: Default::default(),

View file

@ -48,7 +48,8 @@ serde_derive.workspace = true
serde_json.workspace = true
smallvec.workspace = true
smol.workspace = true
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] }
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
thiserror.workspace = true
time.workspace = true
tiny-skia = "0.5"
usvg = { version = "0.14", features = [] }

View file

@ -42,29 +42,28 @@ impl<V: View> gpui::Element<V> for CornersElement {
&mut self,
constraint: gpui::SizeConstraint,
_: &mut V,
_: &mut gpui::LayoutContext<V>,
_: &mut gpui::ViewContext<V>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
_: pathfinder_geometry::rect::RectF,
_: &mut Self::LayoutState,
_: &mut V,
_: &mut gpui::PaintContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> Self::PaintState {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(Color::white()),
..Default::default()
});
scene.push_layer(None);
cx.scene().push_layer(None);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
background: Some(Color::red()),
border: Default::default(),
@ -74,7 +73,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
background: Some(Color::green()),
border: Default::default(),
@ -84,7 +83,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
background: Some(Color::blue()),
border: Default::default(),
@ -94,7 +93,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
background: Some(Color::yellow()),
border: Default::default(),
@ -104,7 +103,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_shadow(Shadow {
cx.scene().push_shadow(Shadow {
bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
corner_radii: gpui::scene::CornerRadii {
bottom_right: 20.,
@ -114,8 +113,8 @@ impl<V: View> gpui::Element<V> for CornersElement {
color: Color::black(),
});
scene.push_layer(None);
scene.push_quad(Quad {
cx.scene().push_layer(None);
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
background: Some(Color::red()),
border: Default::default(),
@ -125,8 +124,8 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.pop_layer();
scene.pop_layer();
cx.scene().pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(

View file

@ -62,12 +62,12 @@ impl gpui::View for TextView {
},
)
.with_highlights(vec![(17..26, underline), (34..40, underline)])
.with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, scene, _| {
scene.push_cursor_region(CursorRegion {
.with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, cx| {
cx.scene().push_cursor_region(CursorRegion {
bounds,
style: CursorStyle::PointingHand,
});
scene.push_mouse_region(
cx.scene().push_mouse_region(
MouseRegion::new::<Self>(view_id, ix, bounds).on_click::<Self, _>(
MouseButton::Left,
move |_, _, _| {

View file

@ -1,26 +0,0 @@
[package]
name = "playground"
version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "playground"
path = "src/playground.rs"
[dependencies]
anyhow.workspace = true
derive_more.workspace = true
gpui = { path = ".." }
log.workspace = true
playground_macros = { path = "../playground_macros" }
parking_lot.workspace = true
refineable.workspace = true
serde.workspace = true
simplelog = "0.9"
smallvec.workspace = true
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] }
util = { path = "../../util" }
[dev-dependencies]
gpui = { path = "..", features = ["test-support"] }

View file

@ -1,108 +0,0 @@
use crate::{
element::{AnyElement, Element, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
layout_context::LayoutContext,
paint_context::PaintContext,
style::{Style, StyleHelpers, StyleRefinement, Styleable},
};
use anyhow::Result;
use gpui::LayoutId;
use smallvec::SmallVec;
pub struct Div<V: 'static> {
style: StyleRefinement,
handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<V>; 2]>,
}
pub fn div<V>() -> Div<V> {
Div {
style: Default::default(),
handlers: Default::default(),
children: Default::default(),
}
}
impl<V: 'static> Element<V> for Div<V> {
type Layout = ();
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, ()>>
where
Self: Sized,
{
let children = self
.children
.iter_mut()
.map(|child| child.layout(view, cx))
.collect::<Result<Vec<LayoutId>>>()?;
cx.add_layout_node(self.style(), (), children)
}
fn paint(&mut self, view: &mut V, layout: &mut Layout<V, ()>, cx: &mut PaintContext<V>)
where
Self: Sized,
{
let style = self.style();
style.paint_background::<V, Self>(layout, cx);
for child in &mut self.children {
child.paint(view, cx);
}
}
}
impl<V> Styleable for Div<V> {
type Style = Style;
fn declared_style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl<V> StyleHelpers for Div<V> {}
impl<V> Interactive<V> for Div<V> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
&mut self.handlers
}
}
impl<V: 'static> ParentElement<V> for Div<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
#[test]
fn test() {
// let elt = div().w_auto();
}
// trait Element<V: 'static> {
// type Style;
// fn layout()
// }
// trait Stylable<V: 'static>: Element<V> {
// type Style;
// fn with_style(self, style: Self::Style) -> Self;
// }
// pub struct HoverStyle<S> {
// default: S,
// hovered: S,
// }
// struct Hover<V: 'static, C: Stylable<V>> {
// child: C,
// style: HoverStyle<C::Style>,
// }
// impl<V: 'static, C: Stylable<V>> Hover<V, C> {
// fn new(child: C, style: HoverStyle<C::Style>) -> Self {
// Self { child, style }
// }
// }

View file

@ -1,158 +0,0 @@
use anyhow::Result;
use derive_more::{Deref, DerefMut};
use gpui::{geometry::rect::RectF, EngineLayout};
use smallvec::SmallVec;
use std::marker::PhantomData;
use util::ResultExt;
pub use crate::layout_context::LayoutContext;
pub use crate::paint_context::PaintContext;
type LayoutId = gpui::LayoutId;
pub trait Element<V: 'static>: 'static {
type Layout;
fn layout(
&mut self,
view: &mut V,
cx: &mut LayoutContext<V>,
) -> Result<Layout<V, Self::Layout>>
where
Self: Sized;
fn paint(
&mut self,
view: &mut V,
layout: &mut Layout<V, Self::Layout>,
cx: &mut PaintContext<V>,
) where
Self: Sized;
fn into_any(self) -> AnyElement<V>
where
Self: 'static + Sized,
{
AnyElement(Box::new(ElementState {
element: self,
layout: None,
}))
}
}
/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
trait ElementStateObject<V> {
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId>;
fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>);
}
/// A wrapper around an element that stores its layout state.
struct ElementState<V: 'static, E: Element<V>> {
element: E,
layout: Option<Layout<V, E::Layout>>,
}
/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
impl<V, E: Element<V>> ElementStateObject<V> for ElementState<V, E> {
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
let layout = self.element.layout(view, cx)?;
let layout_id = layout.id;
self.layout = Some(layout);
Ok(layout_id)
}
fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
let layout = self.layout.as_mut().expect("paint called before layout");
if layout.engine_layout.is_none() {
layout.engine_layout = cx.computed_layout(layout.id).log_err()
}
self.element.paint(view, layout, cx)
}
}
/// A dynamic element.
pub struct AnyElement<V>(Box<dyn ElementStateObject<V>>);
impl<V> AnyElement<V> {
pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
self.0.layout(view, cx)
}
pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
self.0.paint(view, cx)
}
}
#[derive(Deref, DerefMut)]
pub struct Layout<V, D> {
id: LayoutId,
engine_layout: Option<EngineLayout>,
#[deref]
#[deref_mut]
element_data: D,
view_type: PhantomData<V>,
}
impl<V: 'static, D> Layout<V, D> {
pub fn new(id: LayoutId, element_data: D) -> Self {
Self {
id,
engine_layout: None,
element_data: element_data,
view_type: PhantomData,
}
}
pub fn bounds(&mut self, cx: &mut PaintContext<V>) -> RectF {
self.engine_layout(cx).bounds
}
pub fn order(&mut self, cx: &mut PaintContext<V>) -> u32 {
self.engine_layout(cx).order
}
fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout {
self.engine_layout
.get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default())
}
}
impl<V: 'static> Layout<V, Option<AnyElement<V>>> {
pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
let mut element = self.element_data.take().unwrap();
element.paint(view, cx);
self.element_data = Some(element);
}
}
pub trait ParentElement<V: 'static> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
fn child(mut self, child: impl IntoElement<V>) -> Self
where
Self: Sized,
{
self.children_mut().push(child.into_element().into_any());
self
}
fn children<I, E>(mut self, children: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoElement<V>,
Self: Sized,
{
self.children_mut().extend(
children
.into_iter()
.map(|child| child.into_element().into_any()),
);
self
}
}
pub trait IntoElement<V: 'static> {
type Element: Element<V>;
fn into_element(self) -> Self::Element;
}

View file

@ -1,76 +0,0 @@
use crate::{
element::{Element, Layout},
layout_context::LayoutContext,
paint_context::PaintContext,
style::{StyleRefinement, Styleable},
};
use anyhow::Result;
use gpui::platform::MouseMovedEvent;
use refineable::Refineable;
use std::{cell::Cell, marker::PhantomData};
pub struct Hoverable<V: 'static, E: Element<V> + Styleable> {
hovered: Cell<bool>,
child_style: StyleRefinement,
hovered_style: StyleRefinement,
child: E,
view_type: PhantomData<V>,
}
pub fn hoverable<V, E: Element<V> + Styleable>(mut child: E) -> Hoverable<V, E> {
Hoverable {
hovered: Cell::new(false),
child_style: child.declared_style().clone(),
hovered_style: Default::default(),
child,
view_type: PhantomData,
}
}
impl<V, E: Element<V> + Styleable> Styleable for Hoverable<V, E> {
type Style = E::Style;
fn declared_style(&mut self) -> &mut crate::style::StyleRefinement {
self.child.declared_style()
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
type Layout = E::Layout;
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, Self::Layout>>
where
Self: Sized,
{
self.child.layout(view, cx)
}
fn paint(
&mut self,
view: &mut V,
layout: &mut Layout<V, Self::Layout>,
cx: &mut PaintContext<V>,
) where
Self: Sized,
{
if self.hovered.get() {
// If hovered, refine the child's style with this element's style.
self.child.declared_style().refine(&self.hovered_style);
} else {
// Otherwise, set the child's style back to its original style.
*self.child.declared_style() = self.child_style.clone();
}
let bounds = layout.bounds(cx);
let order = layout.order(cx);
self.hovered.set(bounds.contains_point(cx.mouse_position()));
let was_hovered = self.hovered.clone();
cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {
let is_hovered = bounds.contains_point(event.position);
if is_hovered != was_hovered.get() {
was_hovered.set(is_hovered);
cx.repaint();
}
});
}
}

View file

@ -1,34 +0,0 @@
use gpui::{platform::MouseMovedEvent, EventContext};
use smallvec::SmallVec;
use std::rc::Rc;
pub trait Interactive<V: 'static> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
fn on_mouse_move<H>(mut self, handler: H) -> Self
where
H: 'static + Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>),
Self: Sized,
{
self.interaction_handlers()
.mouse_moved
.push(Rc::new(move |view, event, hit_test, cx| {
handler(view, event, hit_test, cx);
cx.bubble
}));
self
}
}
pub struct InteractionHandlers<V: 'static> {
mouse_moved:
SmallVec<[Rc<dyn Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>) -> bool>; 2]>,
}
impl<V> Default for InteractionHandlers<V> {
fn default() -> Self {
Self {
mouse_moved: Default::default(),
}
}
}

View file

@ -1,54 +0,0 @@
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
pub use gpui::LayoutContext as LegacyLayoutContext;
use gpui::{RenderContext, ViewContext};
pub use taffy::tree::NodeId;
use crate::{element::Layout, style::Style};
#[derive(Deref, DerefMut)]
pub struct LayoutContext<'a, 'b, 'c, 'd, V> {
#[deref]
#[deref_mut]
pub(crate) legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>,
}
impl<'a, 'b, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, '_, '_, V> {
fn text_style(&self) -> gpui::fonts::TextStyle {
self.legacy_cx.text_style()
}
fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
self.legacy_cx.push_text_style(style)
}
fn pop_text_style(&mut self) {
self.legacy_cx.pop_text_style()
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> {
pub fn new(legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>) -> Self {
Self { legacy_cx }
}
pub fn add_layout_node<D>(
&mut self,
style: Style,
element_data: D,
children: impl IntoIterator<Item = NodeId>,
) -> Result<Layout<V, D>> {
let rem_size = self.rem_pixels();
let id = self
.legacy_cx
.layout_engine()
.ok_or_else(|| anyhow!("no layout engine"))?
.add_node(style.to_taffy(rem_size), children)?;
Ok(Layout::new(id, element_data))
}
}

View file

@ -1,71 +0,0 @@
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use gpui::{scene::EventHandler, EngineLayout, EventContext, LayoutId, RenderContext, ViewContext};
pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
use std::{any::TypeId, rc::Rc};
pub use taffy::tree::NodeId;
#[derive(Deref, DerefMut)]
pub struct PaintContext<'a, 'b, 'c, 'd, V> {
#[deref]
#[deref_mut]
pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
pub(crate) scene: &'d mut gpui::SceneBuilder,
}
impl<'a, 'b, V> RenderContext<'a, 'b, V> for PaintContext<'a, 'b, '_, '_, V> {
fn text_style(&self) -> gpui::fonts::TextStyle {
self.legacy_cx.text_style()
}
fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
self.legacy_cx.push_text_style(style)
}
fn pop_text_style(&mut self) {
self.legacy_cx.pop_text_style()
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
pub fn new(
legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
scene: &'d mut gpui::SceneBuilder,
) -> Self {
Self { legacy_cx, scene }
}
pub fn on_event<E: 'static>(
&mut self,
order: u32,
handler: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
) {
let view = self.weak_handle();
self.scene.event_handlers.push(EventHandler {
order,
handler: Rc::new(move |event, window_cx| {
if let Some(view) = view.upgrade(window_cx) {
view.update(window_cx, |view, view_cx| {
let mut event_cx = EventContext::new(view_cx);
handler(view, event.downcast_ref().unwrap(), &mut event_cx);
event_cx.bubble
})
} else {
true
}
}),
event_type: TypeId::of::<E>(),
})
}
pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<EngineLayout> {
self.layout_engine()
.ok_or_else(|| anyhow!("no layout engine present"))?
.computed_layout(layout_id)
}
}

View file

@ -1,83 +0,0 @@
#![allow(dead_code, unused_variables)]
use crate::{color::black, style::StyleHelpers};
use element::Element;
use gpui::{
geometry::{rect::RectF, vector::vec2f},
platform::WindowOptions,
};
use log::LevelFilter;
use simplelog::SimpleLogger;
use themes::{rose_pine, ThemeColors};
use view::view;
mod adapter;
mod color;
mod components;
mod div;
mod element;
mod hoverable;
mod interactive;
mod layout_context;
mod paint_context;
mod style;
mod text;
mod themes;
mod view;
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
gpui::App::new(()).unwrap().run(|cx| {
cx.add_window(
WindowOptions {
bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
vec2f(0., 0.),
vec2f(400., 300.),
)),
center: true,
..Default::default()
},
|_| view(|_| playground(&rose_pine::moon())),
);
cx.platform().activate(true);
});
}
fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
use div::div;
div()
.text_color(black())
.h_full()
.w_1_2()
.fill(theme.success(0.5))
// .hover()
// .fill(theme.error(0.5))
// .child(button().label("Hello").click(|_, _, _| println!("click!")))
}
// todo!()
// // column()
// // .size(auto())
// // .fill(theme.base(0.5))
// // .text_color(theme.text(0.5))
// // .child(title_bar(theme))
// // .child(stage(theme))
// // .child(status_bar(theme))
// }
// fn title_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
// row()
// .fill(theme.base(0.2))
// .justify(0.)
// .width(auto())
// .child(text("Zed Playground"))
// }
// fn stage<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
// row().fill(theme.surface(0.9))
// }
// fn status_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
// row().fill(theme.surface(0.1))
// }

View file

@ -1,286 +0,0 @@
use crate::{
color::Hsla,
element::{Element, Layout},
paint_context::PaintContext,
};
use gpui::{
fonts::TextStyleRefinement,
geometry::{
AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement,
Size, SizeRefinement,
},
};
use playground_macros::styleable_helpers;
use refineable::Refineable;
pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
};
#[derive(Clone, Refineable)]
pub struct Style {
/// What layout strategy should be used?
pub display: Display,
// Overflow properties
/// How children overflowing their container should affect layout
#[refineable]
pub overflow: Point<Overflow>,
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
pub scrollbar_width: f32,
// Position properties
/// What should the `position` value of this struct use as a base offset?
pub position: Position,
/// How should the position of this element be tweaked relative to the layout defined?
#[refineable]
pub inset: Edges<Length>,
// Size properies
/// Sets the initial size of the item
#[refineable]
pub size: Size<Length>,
/// Controls the minimum size of the item
#[refineable]
pub min_size: Size<Length>,
/// Controls the maximum size of the item
#[refineable]
pub max_size: Size<Length>,
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
pub aspect_ratio: Option<f32>,
// Spacing Properties
/// How large should the margin be on each side?
#[refineable]
pub margin: Edges<Length>,
/// How large should the padding be on each side?
#[refineable]
pub padding: Edges<DefiniteLength>,
/// How large should the border be on each side?
#[refineable]
pub border: Edges<DefiniteLength>,
// Alignment properties
/// How this node's children aligned in the cross/block axis?
pub align_items: Option<AlignItems>,
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
pub align_self: Option<AlignSelf>,
/// How should content contained within this item be aligned in the cross/block axis
pub align_content: Option<AlignContent>,
/// How should contained within this item be aligned in the main/inline axis
pub justify_content: Option<JustifyContent>,
/// How large should the gaps between items in a flex container be?
#[refineable]
pub gap: Size<DefiniteLength>,
// Flexbox properies
/// Which direction does the main axis flow in?
pub flex_direction: FlexDirection,
/// Should elements wrap, or stay in a single line?
pub flex_wrap: FlexWrap,
/// Sets the initial main axis size of the item
pub flex_basis: Length,
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
pub flex_grow: f32,
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
pub flex_shrink: f32,
/// The fill color of this element
pub fill: Option<Fill>,
/// The radius of the corners of this element
#[refineable]
pub corner_radii: CornerRadii,
/// The color of text within this element. Cascades to children unless overridden.
pub text_color: Option<Hsla>,
}
impl Style {
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
taffy::style::Style {
display: self.display,
overflow: self.overflow.clone().into(),
scrollbar_width: self.scrollbar_width,
position: self.position,
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
min_size: self.min_size.to_taffy(rem_size),
max_size: self.max_size.to_taffy(rem_size),
aspect_ratio: self.aspect_ratio,
margin: self.margin.to_taffy(rem_size),
padding: self.padding.to_taffy(rem_size),
border: self.border.to_taffy(rem_size),
align_items: self.align_items,
align_self: self.align_self,
align_content: self.align_content,
justify_content: self.justify_content,
gap: self.gap.to_taffy(rem_size),
flex_direction: self.flex_direction,
flex_wrap: self.flex_wrap,
flex_basis: self.flex_basis.to_taffy(rem_size).into(),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
..Default::default() // Ignore grid properties for now
}
}
/// Paints the background of an element styled with this style.
/// Return the bounds in which to paint the content.
pub fn paint_background<V: 'static, E: Element<V>>(
&self,
layout: &mut Layout<V, E::Layout>,
cx: &mut PaintContext<V>,
) {
let bounds = layout.bounds(cx);
let rem_size = cx.rem_pixels();
if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
cx.scene.push_quad(gpui::Quad {
bounds,
background: Some(color.into()),
corner_radii: self.corner_radii.to_gpui(rem_size),
border: Default::default(),
});
}
}
}
impl Default for Style {
fn default() -> Self {
Style {
display: Display::DEFAULT,
overflow: Point {
x: Overflow::Visible,
y: Overflow::Visible,
},
scrollbar_width: 0.0,
position: Position::Relative,
inset: Edges::auto(),
margin: Edges::<Length>::zero(),
padding: Edges::<DefiniteLength>::zero(),
border: Edges::<DefiniteLength>::zero(),
size: Size::auto(),
min_size: Size::auto(),
max_size: Size::auto(),
aspect_ratio: None,
gap: Size::zero(),
// Aligment
align_items: None,
align_self: None,
align_content: None,
justify_content: None,
// Flexbox
flex_direction: FlexDirection::Row,
flex_wrap: FlexWrap::NoWrap,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Length::Auto,
fill: None,
text_color: None,
corner_radii: CornerRadii::default(),
}
}
}
impl StyleRefinement {
pub fn text_style(&self) -> Option<TextStyleRefinement> {
self.text_color.map(|color| TextStyleRefinement {
color: Some(color.into()),
..Default::default()
})
}
}
pub struct OptionalTextStyle {
color: Option<Hsla>,
}
impl OptionalTextStyle {
pub fn apply(&self, style: &mut gpui::fonts::TextStyle) {
if let Some(color) = self.color {
style.color = color.into();
}
}
}
#[derive(Clone)]
pub enum Fill {
Color(Hsla),
}
impl Fill {
pub fn color(&self) -> Option<Hsla> {
match self {
Fill::Color(color) => Some(*color),
}
}
}
impl Default for Fill {
fn default() -> Self {
Self::Color(Hsla::default())
}
}
impl From<Hsla> for Fill {
fn from(color: Hsla) -> Self {
Self::Color(color)
}
}
#[derive(Clone, Refineable, Default)]
pub struct CornerRadii {
top_left: AbsoluteLength,
top_right: AbsoluteLength,
bottom_left: AbsoluteLength,
bottom_right: AbsoluteLength,
}
impl CornerRadii {
pub fn to_gpui(&self, rem_size: f32) -> gpui::scene::CornerRadii {
gpui::scene::CornerRadii {
top_left: self.top_left.to_pixels(rem_size),
top_right: self.top_right.to_pixels(rem_size),
bottom_left: self.bottom_left.to_pixels(rem_size),
bottom_right: self.bottom_right.to_pixels(rem_size),
}
}
}
pub trait Styleable {
type Style: refineable::Refineable;
fn declared_style(&mut self) -> &mut playground::style::StyleRefinement;
fn style(&mut self) -> playground::style::Style {
let mut style = playground::style::Style::default();
style.refine(self.declared_style());
style
}
}
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
//
// Example:
// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
// fn p_2(mut self) -> Self where Self: Sized;
use crate as playground; // Macro invocation references this crate as playground.
pub trait StyleHelpers: Styleable<Style = Style> {
styleable_helpers!();
fn fill<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
Self: Sized,
{
self.declared_style().fill = Some(fill.into());
self
}
fn text_color<C>(mut self, color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.declared_style().text_color = Some(color.into());
self
}
}

View file

@ -1,151 +0,0 @@
use crate::{
element::{Element, IntoElement, Layout},
layout_context::LayoutContext,
paint_context::PaintContext,
};
use anyhow::Result;
use gpui::text_layout::LineLayout;
use parking_lot::Mutex;
use std::sync::Arc;
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
type Element = Text;
fn into_element(self) -> Self::Element {
Text { text: self.into() }
}
}
pub struct Text {
text: ArcCow<'static, str>,
}
impl<V: 'static> Element<V> for Text {
type Layout = Arc<Mutex<Option<TextLayout>>>;
fn layout(
&mut self,
view: &mut V,
cx: &mut LayoutContext<V>,
) -> Result<Layout<V, Self::Layout>> {
// let rem_size = cx.rem_pixels();
// let fonts = cx.platform().fonts();
// let text_style = cx.text_style();
// let line_height = cx.font_cache().line_height(text_style.font_size);
// let layout_engine = cx.layout_engine().expect("no layout engine present");
// let text = self.text.clone();
// let layout = Arc::new(Mutex::new(None));
// let style: Style = Style::default().refined(&self.metadata.style);
// let node_id = layout_engine.add_measured_node(style.to_taffy(rem_size), {
// let layout = layout.clone();
// move |params| {
// let line_layout = fonts.layout_line(
// text.as_ref(),
// text_style.font_size,
// &[(text.len(), text_style.to_run())],
// );
// let size = Size {
// width: line_layout.width,
// height: line_height,
// };
// layout.lock().replace(TextLayout {
// line_layout: Arc::new(line_layout),
// line_height,
// });
// size
// }
// })?;
// Ok((node_id, layout))
todo!()
}
fn paint<'a>(
&mut self,
view: &mut V,
layout: &mut Layout<V, Self::Layout>,
cx: &mut PaintContext<V>,
) {
// ) {
// let element_layout_lock = layout.from_element.lock();
// let element_layout = element_layout_lock
// .as_ref()
// .expect("layout has not been performed");
// let line_layout = element_layout.line_layout.clone();
// let line_height = element_layout.line_height;
// drop(element_layout_lock);
// let text_style = cx.text_style();
// let line =
// gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
// line.paint(
// cx.scene,
// layout.from_engine.bounds.origin(),
// layout.from_engine.bounds,
// line_height,
// cx.legacy_cx,
// );
todo!()
}
}
pub struct TextLayout {
line_layout: Arc<LineLayout>,
line_height: f32,
}
pub enum ArcCow<'a, T: ?Sized> {
Borrowed(&'a T),
Owned(Arc<T>),
}
impl<'a, T: ?Sized> Clone for ArcCow<'a, T> {
fn clone(&self) -> Self {
match self {
Self::Borrowed(borrowed) => Self::Borrowed(borrowed),
Self::Owned(owned) => Self::Owned(owned.clone()),
}
}
}
impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
fn from(s: &'a T) -> Self {
Self::Borrowed(s)
}
}
impl<T> From<Arc<T>> for ArcCow<'_, T> {
fn from(s: Arc<T>) -> Self {
Self::Owned(s)
}
}
impl From<String> for ArcCow<'_, str> {
fn from(value: String) -> Self {
Self::Owned(value.into())
}
}
impl<T: ?Sized> std::ops::Deref for ArcCow<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
ArcCow::Borrowed(s) => s,
ArcCow::Owned(s) => s.as_ref(),
}
}
}
impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
fn as_ref(&self) -> &T {
match self {
ArcCow::Borrowed(borrowed) => borrowed,
ArcCow::Owned(owned) => owned.as_ref(),
}
}
}

View file

@ -1,84 +0,0 @@
use crate::color::{Hsla, Lerp};
use std::ops::Range;
pub mod rose_pine;
pub struct ThemeColors {
pub base: Range<Hsla>,
pub surface: Range<Hsla>,
pub overlay: Range<Hsla>,
pub muted: Range<Hsla>,
pub subtle: Range<Hsla>,
pub text: Range<Hsla>,
pub highlight_low: Range<Hsla>,
pub highlight_med: Range<Hsla>,
pub highlight_high: Range<Hsla>,
pub success: Range<Hsla>,
pub warning: Range<Hsla>,
pub error: Range<Hsla>,
pub inserted: Range<Hsla>,
pub deleted: Range<Hsla>,
pub modified: Range<Hsla>,
}
impl ThemeColors {
pub fn base(&self, level: f32) -> Hsla {
self.base.lerp(level)
}
pub fn surface(&self, level: f32) -> Hsla {
self.surface.lerp(level)
}
pub fn overlay(&self, level: f32) -> Hsla {
self.overlay.lerp(level)
}
pub fn muted(&self, level: f32) -> Hsla {
self.muted.lerp(level)
}
pub fn subtle(&self, level: f32) -> Hsla {
self.subtle.lerp(level)
}
pub fn text(&self, level: f32) -> Hsla {
self.text.lerp(level)
}
pub fn highlight_low(&self, level: f32) -> Hsla {
self.highlight_low.lerp(level)
}
pub fn highlight_med(&self, level: f32) -> Hsla {
self.highlight_med.lerp(level)
}
pub fn highlight_high(&self, level: f32) -> Hsla {
self.highlight_high.lerp(level)
}
pub fn success(&self, level: f32) -> Hsla {
self.success.lerp(level)
}
pub fn warning(&self, level: f32) -> Hsla {
self.warning.lerp(level)
}
pub fn error(&self, level: f32) -> Hsla {
self.error.lerp(level)
}
pub fn inserted(&self, level: f32) -> Hsla {
self.inserted.lerp(level)
}
pub fn deleted(&self, level: f32) -> Hsla {
self.deleted.lerp(level)
}
pub fn modified(&self, level: f32) -> Hsla {
self.modified.lerp(level)
}
}

View file

@ -1,133 +0,0 @@
use std::ops::Range;
use crate::{
color::{hsla, rgb, Hsla},
ThemeColors,
};
pub struct RosePineThemes {
pub default: RosePinePalette,
pub dawn: RosePinePalette,
pub moon: RosePinePalette,
}
#[derive(Clone, Copy, Debug)]
pub struct RosePinePalette {
pub base: Hsla,
pub surface: Hsla,
pub overlay: Hsla,
pub muted: Hsla,
pub subtle: Hsla,
pub text: Hsla,
pub love: Hsla,
pub gold: Hsla,
pub rose: Hsla,
pub pine: Hsla,
pub foam: Hsla,
pub iris: Hsla,
pub highlight_low: Hsla,
pub highlight_med: Hsla,
pub highlight_high: Hsla,
}
impl RosePinePalette {
pub fn default() -> RosePinePalette {
RosePinePalette {
base: rgb(0x191724),
surface: rgb(0x1f1d2e),
overlay: rgb(0x26233a),
muted: rgb(0x6e6a86),
subtle: rgb(0x908caa),
text: rgb(0xe0def4),
love: rgb(0xeb6f92),
gold: rgb(0xf6c177),
rose: rgb(0xebbcba),
pine: rgb(0x31748f),
foam: rgb(0x9ccfd8),
iris: rgb(0xc4a7e7),
highlight_low: rgb(0x21202e),
highlight_med: rgb(0x403d52),
highlight_high: rgb(0x524f67),
}
}
pub fn moon() -> RosePinePalette {
RosePinePalette {
base: rgb(0x232136),
surface: rgb(0x2a273f),
overlay: rgb(0x393552),
muted: rgb(0x6e6a86),
subtle: rgb(0x908caa),
text: rgb(0xe0def4),
love: rgb(0xeb6f92),
gold: rgb(0xf6c177),
rose: rgb(0xea9a97),
pine: rgb(0x3e8fb0),
foam: rgb(0x9ccfd8),
iris: rgb(0xc4a7e7),
highlight_low: rgb(0x2a283e),
highlight_med: rgb(0x44415a),
highlight_high: rgb(0x56526e),
}
}
pub fn dawn() -> RosePinePalette {
RosePinePalette {
base: rgb(0xfaf4ed),
surface: rgb(0xfffaf3),
overlay: rgb(0xf2e9e1),
muted: rgb(0x9893a5),
subtle: rgb(0x797593),
text: rgb(0x575279),
love: rgb(0xb4637a),
gold: rgb(0xea9d34),
rose: rgb(0xd7827e),
pine: rgb(0x286983),
foam: rgb(0x56949f),
iris: rgb(0x907aa9),
highlight_low: rgb(0xf4ede8),
highlight_med: rgb(0xdfdad9),
highlight_high: rgb(0xcecacd),
}
}
}
pub fn default() -> ThemeColors {
theme_colors(&RosePinePalette::default())
}
pub fn moon() -> ThemeColors {
theme_colors(&RosePinePalette::moon())
}
pub fn dawn() -> ThemeColors {
theme_colors(&RosePinePalette::dawn())
}
fn theme_colors(p: &RosePinePalette) -> ThemeColors {
ThemeColors {
base: scale_sl(p.base, (0.8, 0.8), (1.2, 1.2)),
surface: scale_sl(p.surface, (0.8, 0.8), (1.2, 1.2)),
overlay: scale_sl(p.overlay, (0.8, 0.8), (1.2, 1.2)),
muted: scale_sl(p.muted, (0.8, 0.8), (1.2, 1.2)),
subtle: scale_sl(p.subtle, (0.8, 0.8), (1.2, 1.2)),
text: scale_sl(p.text, (0.8, 0.8), (1.2, 1.2)),
highlight_low: scale_sl(p.highlight_low, (0.8, 0.8), (1.2, 1.2)),
highlight_med: scale_sl(p.highlight_med, (0.8, 0.8), (1.2, 1.2)),
highlight_high: scale_sl(p.highlight_high, (0.8, 0.8), (1.2, 1.2)),
success: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
warning: scale_sl(p.gold, (0.8, 0.8), (1.2, 1.2)),
error: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
inserted: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
deleted: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
modified: scale_sl(p.rose, (0.8, 0.8), (1.2, 1.2)),
}
}
/// Produces a range by multiplying the saturation and lightness of the base color by the given
/// start and end factors.
fn scale_sl(base: Hsla, (start_s, start_l): (f32, f32), (end_s, end_l): (f32, f32)) -> Range<Hsla> {
let start = hsla(base.h, base.s * start_s, base.l * start_l, base.a);
let end = hsla(base.h, base.s * end_s, base.l * end_l, base.a);
Range { start, end }
}

View file

@ -1,147 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
struct StyleableMacroInput;
impl Parse for StyleableMacroInput {
fn parse(_input: ParseStream) -> Result<Self> {
Ok(StyleableMacroInput)
}
}
pub fn styleable_helpers(input: TokenStream) -> TokenStream {
let _ = parse_macro_input!(input as StyleableMacroInput);
let methods = generate_methods();
let output = quote! {
#(#methods)*
};
output.into()
}
fn generate_methods() -> Vec<TokenStream2> {
let mut methods = Vec::new();
for (prefix, auto_allowed, fields) in tailwind_prefixes() {
for (suffix, length_tokens) in tailwind_lengths() {
if !auto_allowed && suffix == "auto" {
// Conditional to skip "auto"
continue;
}
let method_name = format_ident!("{}_{}", prefix, suffix);
let field_assignments = fields
.iter()
.map(|field_tokens| {
quote! {
style.#field_tokens = Some(gpui::geometry::#length_tokens);
}
})
.collect::<Vec<_>>();
let method = quote! {
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
let mut style = self.declared_style();
#(#field_assignments)*
self
}
};
methods.push(method);
}
}
methods
}
fn tailwind_lengths() -> Vec<(&'static str, TokenStream2)> {
vec![
("0", quote! { pixels(0.) }),
("1", quote! { rems(0.25) }),
("2", quote! { rems(0.5) }),
("3", quote! { rems(0.75) }),
("4", quote! { rems(1.) }),
("5", quote! { rems(1.25) }),
("6", quote! { rems(1.5) }),
("8", quote! { rems(2.0) }),
("10", quote! { rems(2.5) }),
("12", quote! { rems(3.) }),
("16", quote! { rems(4.) }),
("20", quote! { rems(5.) }),
("24", quote! { rems(6.) }),
("32", quote! { rems(8.) }),
("40", quote! { rems(10.) }),
("48", quote! { rems(12.) }),
("56", quote! { rems(14.) }),
("64", quote! { rems(16.) }),
("72", quote! { rems(18.) }),
("80", quote! { rems(20.) }),
("96", quote! { rems(24.) }),
("auto", quote! { auto() }),
("px", quote! { pixels(1.) }),
("full", quote! { relative(1.) }),
("1_2", quote! { relative(0.5) }),
("1_3", quote! { relative(1./3.) }),
("2_3", quote! { relative(2./3.) }),
("1_4", quote! { relative(0.25) }),
("2_4", quote! { relative(0.5) }),
("3_4", quote! { relative(0.75) }),
("1_5", quote! { relative(0.2) }),
("2_5", quote! { relative(0.4) }),
("3_5", quote! { relative(0.6) }),
("4_5", quote! { relative(0.8) }),
("1_6", quote! { relative(1./6.) }),
("5_6", quote! { relative(5./6.) }),
("1_12", quote! { relative(1./12.) }),
// ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
// ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
// ("screen", quote! { DefiniteLength::Vh(100.0) }),
]
}
fn tailwind_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
vec![
("w", true, vec![quote! { size.width }]),
("h", true, vec![quote! { size.height }]),
("min_w", false, vec![quote! { min_size.width }]),
("min_h", false, vec![quote! { min_size.height }]),
("max_w", false, vec![quote! { max_size.width }]),
("max_h", false, vec![quote! { max_size.height }]),
(
"m",
true,
vec![quote! { margin.top }, quote! { margin.bottom }],
),
("mt", true, vec![quote! { margin.top }]),
("mb", true, vec![quote! { margin.bottom }]),
(
"mx",
true,
vec![quote! { margin.left }, quote! { margin.right }],
),
("ml", true, vec![quote! { margin.left }]),
("mr", true, vec![quote! { margin.right }]),
(
"p",
false,
vec![quote! { padding.top }, quote! { padding.bottom }],
),
("pt", false, vec![quote! { padding.top }]),
("pb", false, vec![quote! { padding.bottom }]),
(
"px",
false,
vec![quote! { padding.left }, quote! { padding.right }],
),
("pl", false, vec![quote! { padding.left }]),
("pr", false, vec![quote! { padding.right }]),
("top", true, vec![quote! { inset.top }]),
("bottom", true, vec![quote! { inset.bottom }]),
("left", true, vec![quote! { inset.left }]),
("right", true, vec![quote! { inset.right }]),
]
}

View file

@ -1,99 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, FnArg, ItemFn, PatType};
pub fn tailwind_lengths(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_function = parse_macro_input!(item as ItemFn);
let visibility = &input_function.vis;
let function_signature = input_function.sig.clone();
let function_body = input_function.block;
let where_clause = &function_signature.generics.where_clause;
let argument_name = match function_signature.inputs.iter().nth(1) {
Some(FnArg::Typed(PatType { pat, .. })) => pat,
_ => panic!("Couldn't find the second argument in the function signature"),
};
let mut output_functions = TokenStream2::new();
for (length, value) in fixed_lengths() {
let function_name = format_ident!("{}{}", function_signature.ident, length);
output_functions.extend(quote! {
#visibility fn #function_name(mut self) -> Self #where_clause {
let #argument_name = #value.into();
#function_body
}
});
}
output_functions.into()
}
fn fixed_lengths() -> Vec<(&'static str, TokenStream2)> {
vec![
("0", quote! { DefinedLength::Pixels(0.) }),
("px", quote! { DefinedLength::Pixels(1.) }),
("0_5", quote! { DefinedLength::Rems(0.125) }),
("1", quote! { DefinedLength::Rems(0.25) }),
("1_5", quote! { DefinedLength::Rems(0.375) }),
("2", quote! { DefinedLength::Rems(0.5) }),
("2_5", quote! { DefinedLength::Rems(0.625) }),
("3", quote! { DefinedLength::Rems(0.75) }),
("3_5", quote! { DefinedLength::Rems(0.875) }),
("4", quote! { DefinedLength::Rems(1.) }),
("5", quote! { DefinedLength::Rems(1.25) }),
("6", quote! { DefinedLength::Rems(1.5) }),
("7", quote! { DefinedLength::Rems(1.75) }),
("8", quote! { DefinedLength::Rems(2.) }),
("9", quote! { DefinedLength::Rems(2.25) }),
("10", quote! { DefinedLength::Rems(2.5) }),
("11", quote! { DefinedLength::Rems(2.75) }),
("12", quote! { DefinedLength::Rems(3.) }),
("14", quote! { DefinedLength::Rems(3.5) }),
("16", quote! { DefinedLength::Rems(4.) }),
("20", quote! { DefinedLength::Rems(5.) }),
("24", quote! { DefinedLength::Rems(6.) }),
("28", quote! { DefinedLength::Rems(7.) }),
("32", quote! { DefinedLength::Rems(8.) }),
("36", quote! { DefinedLength::Rems(9.) }),
("40", quote! { DefinedLength::Rems(10.) }),
("44", quote! { DefinedLength::Rems(11.) }),
("48", quote! { DefinedLength::Rems(12.) }),
("52", quote! { DefinedLength::Rems(13.) }),
("56", quote! { DefinedLength::Rems(14.) }),
("60", quote! { DefinedLength::Rems(15.) }),
("64", quote! { DefinedLength::Rems(16.) }),
("72", quote! { DefinedLength::Rems(18.) }),
("80", quote! { DefinedLength::Rems(20.) }),
("96", quote! { DefinedLength::Rems(24.) }),
("half", quote! { DefinedLength::Percent(50.) }),
("1_3rd", quote! { DefinedLength::Percent(33.333333) }),
("2_3rd", quote! { DefinedLength::Percent(66.666667) }),
("1_4th", quote! { DefinedLength::Percent(25.) }),
("2_4th", quote! { DefinedLength::Percent(50.) }),
("3_4th", quote! { DefinedLength::Percent(75.) }),
("1_5th", quote! { DefinedLength::Percent(20.) }),
("2_5th", quote! { DefinedLength::Percent(40.) }),
("3_5th", quote! { DefinedLength::Percent(60.) }),
("4_5th", quote! { DefinedLength::Percent(80.) }),
("1_6th", quote! { DefinedLength::Percent(16.666667) }),
("2_6th", quote! { DefinedLength::Percent(33.333333) }),
("3_6th", quote! { DefinedLength::Percent(50.) }),
("4_6th", quote! { DefinedLength::Percent(66.666667) }),
("5_6th", quote! { DefinedLength::Percent(83.333333) }),
("1_12th", quote! { DefinedLength::Percent(8.333333) }),
("2_12th", quote! { DefinedLength::Percent(16.666667) }),
("3_12th", quote! { DefinedLength::Percent(25.) }),
("4_12th", quote! { DefinedLength::Percent(33.333333) }),
("5_12th", quote! { DefinedLength::Percent(41.666667) }),
("6_12th", quote! { DefinedLength::Percent(50.) }),
("7_12th", quote! { DefinedLength::Percent(58.333333) }),
("8_12th", quote! { DefinedLength::Percent(66.666667) }),
("9_12th", quote! { DefinedLength::Percent(75.) }),
("10_12th", quote! { DefinedLength::Percent(83.333333) }),
("11_12th", quote! { DefinedLength::Percent(91.666667) }),
("full", quote! { DefinedLength::Percent(100.) }),
]
}

View file

@ -10,7 +10,7 @@ mod window_input_handler;
use crate::{
elements::{AnyElement, AnyRootElement, RootElement},
executor::{self, Task},
fonts::TextStyle,
image_cache::ImageCache,
json,
keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
platform::{
@ -28,6 +28,7 @@ use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
use derive_more::Deref;
pub use menu::*;
use parking_lot::Mutex;
use pathfinder_geometry::rect::RectF;
use platform::Event;
use postage::oneshot;
#[cfg(any(test, feature = "test-support"))]
@ -51,8 +52,12 @@ use std::{
};
#[cfg(any(test, feature = "test-support"))]
pub use test_app_context::{ContextHandle, TestAppContext};
use util::ResultExt;
use util::{
http::{self, HttpClient},
ResultExt,
};
use uuid::Uuid;
pub use window::MeasureParams;
use window_input_handler::WindowInputHandler;
pub trait Entity: 'static {
@ -154,12 +159,14 @@ impl App {
let platform = platform::current::platform();
let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
let foreground_platform = platform::current::foreground_platform(foreground.clone());
let http_client = http::client();
let app = Self(Rc::new(RefCell::new(AppContext::new(
foreground,
Arc::new(executor::Background::new()),
platform.clone(),
foreground_platform.clone(),
Arc::new(FontCache::new(platform.fonts())),
http_client,
Default::default(),
asset_source,
))));
@ -456,6 +463,7 @@ pub struct AppContext {
pub asset_cache: Arc<AssetCache>,
font_system: Arc<dyn FontSystem>,
pub font_cache: Arc<FontCache>,
pub image_cache: Arc<ImageCache>,
action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>,
capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
// Entity Types -> { Action Types -> Action Handlers }
@ -499,6 +507,7 @@ impl AppContext {
platform: Arc<dyn platform::Platform>,
foreground_platform: Rc<dyn platform::ForegroundPlatform>,
font_cache: Arc<FontCache>,
http_client: Arc<dyn HttpClient>,
ref_counts: RefCounts,
asset_source: impl AssetSource,
) -> Self {
@ -517,6 +526,7 @@ impl AppContext {
platform,
foreground_platform,
font_cache,
image_cache: Arc::new(ImageCache::new(http_client)),
asset_cache: Arc::new(AssetCache::new(asset_source)),
action_deserializers: Default::default(),
capture_actions: Default::default(),
@ -1898,7 +1908,6 @@ impl AppContext {
fn handle_repaint_window_effect(&mut self, window: AnyWindowHandle) {
self.update_window(window, |cx| {
cx.layout(false).log_err();
if let Some(scene) = cx.paint().log_err() {
cx.window.platform_window.present_scene(scene);
}
@ -3345,10 +3354,6 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
self.element_state::<Tag, T>(element_id, T::default())
}
pub fn rem_pixels(&self) -> f32 {
16.
}
pub fn default_element_state_dynamic<T: 'static + Default>(
&mut self,
tag: TypeTag,
@ -3356,6 +3361,59 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
) -> ElementStateHandle<T> {
self.element_state_dynamic::<T>(tag, element_id, T::default())
}
/// Return keystrokes that would dispatch the given action on the given view.
pub(crate) fn keystrokes_for_action(
&mut self,
view_id: usize,
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
self.notify_if_view_ancestors_change(view_id);
let window = self.window_handle;
let mut contexts = Vec::new();
let mut handler_depth = None;
for (i, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
if actions.contains_key(&action.id()) {
handler_depth = Some(i);
}
}
contexts.push(view_metadata.keymap_context.clone());
}
}
if self.global_actions.contains_key(&action.id()) {
handler_depth = Some(contexts.len())
}
let handler_depth = handler_depth.unwrap_or(0);
(0..=handler_depth).find_map(|depth| {
let contexts = &contexts[depth..];
self.keystroke_matcher
.keystrokes_for_action(action, contexts)
})
}
fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
let self_view_id = self.view_id;
self.window
.views_to_notify_if_ancestors_change
.entry(view_id)
.or_default()
.push(self_view_id);
}
pub fn paint_layer<F, R>(&mut self, clip_bounds: Option<RectF>, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.scene().push_layer(clip_bounds);
let result = f(self);
self.scene().pop_layer();
result
}
}
impl<V: View> ViewContext<'_, '_, V> {
@ -3447,265 +3505,6 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
}
}
/// Methods shared by both LayoutContext and PaintContext
///
/// It's that PaintContext should be implemented in terms of layout context and
/// deref to it, in which case we wouldn't need this.
pub trait RenderContext<'a, 'b, V> {
fn text_style(&self) -> TextStyle;
fn push_text_style(&mut self, style: TextStyle);
fn pop_text_style(&mut self);
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V>;
}
pub struct LayoutContext<'a, 'b, 'c, V> {
// Nathan: Making this is public while I work on playground.
pub view_context: &'c mut ViewContext<'a, 'b, V>,
new_parents: &'c mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
text_style_stack: Vec<TextStyle>,
pub refreshing: bool,
}
impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
pub fn new(
view_context: &'c mut ViewContext<'a, 'b, V>,
new_parents: &'c mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
refreshing: bool,
) -> Self {
Self {
view_context,
new_parents,
views_to_notify_if_ancestors_change,
text_style_stack: Vec::new(),
refreshing,
}
}
pub fn view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
self.view_context
}
/// Return keystrokes that would dispatch the given action on the given view.
pub(crate) fn keystrokes_for_action(
&mut self,
view_id: usize,
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
self.notify_if_view_ancestors_change(view_id);
let window = self.window_handle;
let mut contexts = Vec::new();
let mut handler_depth = None;
for (i, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
if actions.contains_key(&action.id()) {
handler_depth = Some(i);
}
}
contexts.push(view_metadata.keymap_context.clone());
}
}
if self.global_actions.contains_key(&action.id()) {
handler_depth = Some(contexts.len())
}
let handler_depth = handler_depth.unwrap_or(0);
(0..=handler_depth).find_map(|depth| {
let contexts = &contexts[depth..];
self.keystroke_matcher
.keystrokes_for_action(action, contexts)
})
}
fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
let self_view_id = self.view_id;
self.views_to_notify_if_ancestors_change
.entry(view_id)
.or_default()
.push(self_view_id);
}
pub fn with_text_style<F, T>(&mut self, style: TextStyle, f: F) -> T
where
F: FnOnce(&mut Self) -> T,
{
self.push_text_style(style);
let result = f(self);
self.pop_text_style();
result
}
}
impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, 'c, V> {
fn text_style(&self) -> TextStyle {
self.text_style_stack
.last()
.cloned()
.unwrap_or(TextStyle::default(&self.font_cache))
}
fn push_text_style(&mut self, style: TextStyle) {
self.text_style_stack.push(style);
}
fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, V> Deref for LayoutContext<'a, 'b, 'c, V> {
type Target = ViewContext<'a, 'b, V>;
fn deref(&self) -> &Self::Target {
&self.view_context
}
}
impl<V> DerefMut for LayoutContext<'_, '_, '_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view_context
}
}
impl<V> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
BorrowAppContext::read_with(&*self.view_context, f)
}
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
BorrowAppContext::update(&mut *self.view_context, f)
}
}
impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
BorrowWindowContext::read_window(&*self.view_context, window, f)
}
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window_optional(&*self.view_context, window, f)
}
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window: AnyWindowHandle,
f: F,
) -> T {
BorrowWindowContext::update_window(&mut *self.view_context, window, f)
}
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window_optional(&mut *self.view_context, window, f)
}
}
pub struct PaintContext<'a, 'b, 'c, V> {
pub view_context: &'c mut ViewContext<'a, 'b, V>,
text_style_stack: Vec<TextStyle>,
}
impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
Self {
view_context,
text_style_stack: Vec::new(),
}
}
}
impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for PaintContext<'a, 'b, 'c, V> {
fn text_style(&self) -> TextStyle {
self.text_style_stack
.last()
.cloned()
.unwrap_or(TextStyle::default(&self.font_cache))
}
fn push_text_style(&mut self, style: TextStyle) {
self.text_style_stack.push(style);
}
fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, V> Deref for PaintContext<'a, 'b, 'c, V> {
type Target = ViewContext<'a, 'b, V>;
fn deref(&self) -> &Self::Target {
&self.view_context
}
}
impl<V> DerefMut for PaintContext<'_, '_, '_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view_context
}
}
impl<V> BorrowAppContext for PaintContext<'_, '_, '_, V> {
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
BorrowAppContext::read_with(&*self.view_context, f)
}
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
BorrowAppContext::update(&mut *self.view_context, f)
}
}
impl<V> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
type Result<T> = T;
fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
where
F: FnOnce(&WindowContext) -> T,
{
BorrowWindowContext::read_window(self.view_context, window, f)
}
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window_optional(self.view_context, window, f)
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
where
F: FnOnce(&mut WindowContext) -> T,
{
BorrowWindowContext::update_window(self.view_context, window, f)
}
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window_optional(self.view_context, window, f)
}
}
pub struct EventContext<'a, 'b, 'c, V> {
view_context: &'c mut ViewContext<'a, 'b, V>,
pub(crate) handled: bool,
@ -6555,32 +6354,21 @@ mod tests {
view_1.update(cx, |_, cx| {
view_2.update(cx, |_, cx| {
// Sanity check
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
assert_eq!(
layout_cx
.keystrokes_for_action(view_1_id, &Action1)
cx.keystrokes_for_action(view_1_id, &Action1)
.unwrap()
.as_slice(),
&[Keystroke::parse("a").unwrap()]
);
assert_eq!(
layout_cx
.keystrokes_for_action(view_2.id(), &Action2)
cx.keystrokes_for_action(view_2.id(), &Action2)
.unwrap()
.as_slice(),
&[Keystroke::parse("b").unwrap()]
);
assert_eq!(layout_cx.keystrokes_for_action(view_1.id(), &Action3), None);
assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action3), None);
assert_eq!(
layout_cx
.keystrokes_for_action(view_2.id(), &Action3)
cx.keystrokes_for_action(view_2.id(), &Action3)
.unwrap()
.as_slice(),
&[Keystroke::parse("c").unwrap()]
@ -6589,21 +6377,17 @@ mod tests {
// The 'a' keystroke propagates up the view tree from view_2
// to view_1. The action, Action1, is handled by view_1.
assert_eq!(
layout_cx
.keystrokes_for_action(view_2.id(), &Action1)
cx.keystrokes_for_action(view_2.id(), &Action1)
.unwrap()
.as_slice(),
&[Keystroke::parse("a").unwrap()]
);
// Actions that are handled below the current view don't have bindings
assert_eq!(layout_cx.keystrokes_for_action(view_1_id, &Action2), None);
assert_eq!(cx.keystrokes_for_action(view_1_id, &Action2), None);
// Actions that are handled in other branches of the tree should not have a binding
assert_eq!(
layout_cx.keystrokes_for_action(view_2.id(), &GlobalAction),
None
);
assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None);
});
});

View file

@ -57,6 +57,7 @@ impl TestAppContext {
platform,
foreground_platform.clone(),
font_cache,
util::http::FakeHttpClient::with_404_response(),
RefCounts::new(leak_detector),
(),
);

View file

@ -1,5 +1,6 @@
use crate::{
elements::AnyRootElement,
fonts::{TextStyle, TextStyleRefinement},
geometry::{rect::RectF, Size},
json::ToJson,
keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
@ -15,9 +16,8 @@ use crate::{
text_layout::TextLayoutCache,
util::post_inc,
Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion,
MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle,
WindowInvalidation,
BorrowWindowContext, Effect, Element, Entity, Handle, MouseRegion, MouseRegionId, SceneBuilder,
Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet};
@ -30,7 +30,7 @@ use sqlez::{
statement::Statement,
};
use std::{
any::TypeId,
any::{type_name, Any, TypeId},
mem,
ops::{Deref, DerefMut, Range, Sub},
};
@ -50,20 +50,28 @@ pub struct Window {
pub(crate) parents: HashMap<usize, usize>,
pub(crate) is_active: bool,
pub(crate) is_fullscreen: bool,
inspector_enabled: bool,
pub(crate) invalidation: Option<WindowInvalidation>,
pub(crate) platform_window: Box<dyn platform::Window>,
pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
scene: SceneBuilder,
pub(crate) text_style_stack: Vec<TextStyle>,
pub(crate) theme_stack: Vec<Box<dyn Any>>,
pub(crate) new_parents: HashMap<usize, usize>,
pub(crate) views_to_notify_if_ancestors_change: HashMap<usize, SmallVec<[usize; 2]>>,
titlebar_height: f32,
appearance: Appearance,
cursor_regions: Vec<CursorRegion>,
mouse_regions: Vec<(MouseRegion, usize)>,
event_handlers: Vec<EventHandler>,
last_mouse_moved_event: Option<Event>,
last_mouse_position: Vector2F,
pressed_buttons: HashSet<MouseButton>,
pub(crate) hovered_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
mouse_position: Vector2F,
text_layout_cache: TextLayoutCache,
refreshing: bool,
}
impl Window {
@ -87,19 +95,27 @@ impl Window {
is_active: false,
invalidation: None,
is_fullscreen: false,
inspector_enabled: false,
platform_window,
rendered_views: Default::default(),
scene: SceneBuilder::new(),
text_style_stack: Vec::new(),
theme_stack: Vec::new(),
new_parents: HashMap::default(),
views_to_notify_if_ancestors_change: HashMap::default(),
cursor_regions: Default::default(),
mouse_regions: Default::default(),
event_handlers: Default::default(),
text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
last_mouse_moved_event: None,
last_mouse_position: Vector2F::zero(),
pressed_buttons: Default::default(),
hovered_region_ids: Default::default(),
clicked_region_ids: Default::default(),
clicked_region: None,
mouse_position: vec2f(0., 0.),
titlebar_height,
appearance,
refreshing: false,
};
let mut window_context = WindowContext::mutable(cx, &mut window, handle);
@ -226,6 +242,26 @@ impl<'a> WindowContext<'a> {
.push_back(Effect::RepaintWindow { window });
}
pub fn scene(&mut self) -> &mut SceneBuilder {
&mut self.window.scene
}
pub fn enable_inspector(&mut self) {
self.window.inspector_enabled = true;
}
pub fn is_inspector_enabled(&self) -> bool {
self.window.inspector_enabled
}
pub fn is_mouse_down(&self, button: MouseButton) -> bool {
self.window.pressed_buttons.contains(&button)
}
pub fn rem_size(&self) -> f32 {
16.
}
pub fn layout_engine(&mut self) -> Option<&mut LayoutEngine> {
self.window.layout_engines.last_mut()
}
@ -259,7 +295,11 @@ impl<'a> WindowContext<'a> {
}
pub fn mouse_position(&self) -> Vector2F {
self.window.mouse_position
self.window.platform_window.mouse_position()
}
pub fn refreshing(&self) -> bool {
self.window.refreshing
}
pub fn text_layout_cache(&self) -> &TextLayoutCache {
@ -507,7 +547,9 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
self.dispatch_to_new_event_handlers(&event);
if !event_reused {
self.dispatch_event_2(&event);
}
let mut mouse_events = SmallVec::<[_; 2]>::new();
let mut notified_views: HashSet<usize> = Default::default();
@ -576,7 +618,7 @@ impl<'a> WindowContext<'a> {
// Synthesize one last drag event to end the drag
mouse_events.push(MouseEvent::Drag(MouseDrag {
region: Default::default(),
prev_mouse_position: self.window.mouse_position,
prev_mouse_position: self.window.last_mouse_position,
platform_event: MouseMovedEvent {
position: e.position,
pressed_button: Some(e.button),
@ -630,14 +672,14 @@ impl<'a> WindowContext<'a> {
if pressed_button.is_some() {
mouse_events.push(MouseEvent::Drag(MouseDrag {
region: Default::default(),
prev_mouse_position: self.window.mouse_position,
prev_mouse_position: self.window.last_mouse_position,
platform_event: e.clone(),
end: false,
}));
} else if let Some((_, clicked_button)) = self.window.clicked_region {
mouse_events.push(MouseEvent::Drag(MouseDrag {
region: Default::default(),
prev_mouse_position: self.window.mouse_position,
prev_mouse_position: self.window.last_mouse_position,
platform_event: e.clone(),
end: true,
}));
@ -697,7 +739,7 @@ impl<'a> WindowContext<'a> {
}
if let Some(position) = event.position() {
self.window.mouse_position = position;
self.window.last_mouse_position = position;
}
// 2. Dispatch mouse events on regions
@ -711,7 +753,7 @@ impl<'a> WindowContext<'a> {
match &mouse_event {
MouseEvent::Hover(_) => {
let mut highest_z_index = None;
let mouse_position = self.window.mouse_position.clone();
let mouse_position = self.mouse_position();
let window = &mut *self.window;
let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
for (region, z_index) in window.mouse_regions.iter().rev() {
@ -756,7 +798,7 @@ impl<'a> WindowContext<'a> {
MouseEvent::Down(_) | MouseEvent::Up(_) => {
for (region, _) in self.window.mouse_regions.iter().rev() {
if region.bounds.contains_point(self.window.mouse_position) {
if region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(region.clone());
if region.notify_on_click {
notified_views.insert(region.id().view_id());
@ -783,10 +825,7 @@ impl<'a> WindowContext<'a> {
// Find regions which still overlap with the mouse since the last MouseDown happened
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
if clicked_region_ids.contains(&mouse_region.id()) {
if mouse_region
.bounds
.contains_point(self.window.mouse_position)
{
if mouse_region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(mouse_region.clone());
} else {
// Let the view know that it hasn't been clicked anymore
@ -813,10 +852,7 @@ impl<'a> WindowContext<'a> {
| MouseEvent::ClickOut(_) => {
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
// NOT contains
if !mouse_region
.bounds
.contains_point(self.window.mouse_position)
{
if !mouse_region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(mouse_region.clone());
}
}
@ -825,10 +861,7 @@ impl<'a> WindowContext<'a> {
_ => {
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
// Contains
if mouse_region
.bounds
.contains_point(self.window.mouse_position)
{
if mouse_region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(mouse_region.clone());
}
}
@ -892,12 +925,24 @@ impl<'a> WindowContext<'a> {
any_event_handled
}
fn dispatch_to_new_event_handlers(&mut self, event: &Event) {
fn dispatch_event_2(&mut self, event: &Event) {
match event {
Event::MouseDown(event) => {
self.window.pressed_buttons.insert(event.button);
}
Event::MouseUp(event) => {
self.window.pressed_buttons.remove(&event.button);
}
_ => {}
}
if let Some(mouse_event) = event.mouse_event() {
let event_handlers = self.window.take_event_handlers();
for event_handler in event_handlers.iter().rev() {
if event_handler.event_type == mouse_event.type_id() {
(event_handler.handler)(mouse_event, self);
if !(event_handler.handler)(mouse_event, self) {
break;
}
}
}
self.window.event_handlers = event_handlers;
@ -1000,21 +1045,17 @@ impl<'a> WindowContext<'a> {
let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
let mut new_parents = HashMap::default();
let mut views_to_notify_if_ancestors_change = HashMap::default();
rendered_root.layout(
SizeConstraint::new(window_size, window_size),
&mut new_parents,
&mut views_to_notify_if_ancestors_change,
refreshing,
self,
)?;
self.window.refreshing = refreshing;
rendered_root.layout(SizeConstraint::strict(window_size), self)?;
self.window.refreshing = false;
let views_to_notify_if_ancestors_change =
mem::take(&mut self.window.views_to_notify_if_ancestors_change);
for (view_id, view_ids_to_notify) in views_to_notify_if_ancestors_change {
let mut current_view_id = view_id;
loop {
let old_parent_id = self.window.parents.get(&current_view_id);
let new_parent_id = new_parents.get(&current_view_id);
let new_parent_id = self.window.new_parents.get(&current_view_id);
if old_parent_id.is_none() && new_parent_id.is_none() {
break;
} else if old_parent_id == new_parent_id {
@ -1029,6 +1070,7 @@ impl<'a> WindowContext<'a> {
}
}
let new_parents = mem::take(&mut self.window.new_parents);
let old_parents = mem::replace(&mut self.window.parents, new_parents);
self.window
.rendered_views
@ -1043,9 +1085,7 @@ impl<'a> WindowContext<'a> {
let root_view_id = self.window.root_view().id();
let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
let mut scene_builder = SceneBuilder::new(scale_factor);
rendered_root.paint(
&mut scene_builder,
Vector2F::zero(),
RectF::from_points(Vector2F::zero(), window_size),
self,
@ -1055,7 +1095,7 @@ impl<'a> WindowContext<'a> {
.insert(root_view_id, rendered_root);
self.window.text_layout_cache.finish_frame();
let mut scene = scene_builder.build();
let mut scene = self.window.scene.build(scale_factor);
self.window.cursor_regions = scene.cursor_regions();
self.window.mouse_regions = scene.mouse_regions();
self.window.event_handlers = scene.take_event_handlers();
@ -1203,6 +1243,10 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.bounds()
}
pub fn titlebar_height(&self) -> f32 {
self.window.titlebar_height
}
pub fn window_appearance(&self) -> Appearance {
self.window.appearance
}
@ -1274,6 +1318,43 @@ impl<'a> WindowContext<'a> {
};
handle
}
pub fn text_style(&self) -> TextStyle {
self.window
.text_style_stack
.last()
.cloned()
.unwrap_or(TextStyle::default(&self.font_cache))
}
pub fn push_text_style(&mut self, refinement: &TextStyleRefinement) -> Result<()> {
let mut style = self.text_style();
style.refine(refinement, self.font_cache())?;
self.window.text_style_stack.push(style);
Ok(())
}
pub fn pop_text_style(&mut self) {
self.window.text_style_stack.pop();
}
pub fn theme<T: 'static>(&self) -> &T {
self.window
.theme_stack
.iter()
.rev()
.find_map(|theme| theme.downcast_ref())
.ok_or_else(|| anyhow!("no theme provided of type {}", type_name::<T>()))
.unwrap()
}
pub fn push_theme<T: 'static>(&mut self, theme: T) {
self.window.theme_stack.push(Box::new(theme));
}
pub fn pop_theme(&mut self) {
self.window.theme_stack.pop();
}
}
#[derive(Default)]
@ -1289,9 +1370,12 @@ impl LayoutEngine {
where
C: IntoIterator<Item = LayoutId>,
{
Ok(self
.0
.new_with_children(style, &children.into_iter().collect::<Vec<_>>())?)
let children = children.into_iter().collect::<Vec<_>>();
if children.is_empty() {
Ok(self.0.new_leaf(style)?)
} else {
Ok(self.0.new_with_children(style, &children)?)
}
}
pub fn add_measured_node<F>(&mut self, style: LayoutStyle, measure: F) -> Result<LayoutId>
@ -1314,8 +1398,8 @@ impl LayoutEngine {
Ok(())
}
pub fn computed_layout(&mut self, node: LayoutId) -> Result<EngineLayout> {
Ok(self.0.layout(node)?.into())
pub fn computed_layout(&mut self, node: LayoutId) -> Result<Layout> {
Ok(Layout::from(self.0.layout(node)?))
}
}
@ -1339,7 +1423,7 @@ where
}
#[derive(Debug, Clone, Default)]
pub struct EngineLayout {
pub struct Layout {
pub bounds: RectF,
pub order: u32,
}
@ -1349,7 +1433,7 @@ pub struct MeasureParams {
pub available_space: Size<AvailableSpace>,
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
Pixels(f32),
@ -1375,7 +1459,7 @@ impl From<taffy::prelude::AvailableSpace> for AvailableSpace {
}
}
impl From<&taffy::tree::Layout> for EngineLayout {
impl From<&taffy::tree::Layout> for Layout {
fn from(value: &taffy::tree::Layout) -> Self {
Self {
bounds: RectF::new(
@ -1592,18 +1676,13 @@ impl<V: 'static> Element<V> for ChildView {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
cx.new_parents.insert(self.view_id, cx.view_id());
let parent_id = cx.view_id();
cx.window.new_parents.insert(self.view_id, parent_id);
let size = rendered_view
.layout(
constraint,
cx.new_parents,
cx.views_to_notify_if_ancestors_change,
cx.refreshing,
cx.view_context,
)
.layout(constraint, cx)
.log_err()
.unwrap_or(Vector2F::zero());
cx.window.rendered_views.insert(self.view_id, rendered_view);
@ -1620,16 +1699,15 @@ impl<V: 'static> Element<V> for ChildView {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
rendered_view
.paint(scene, bounds.origin(), visible_bounds, cx)
.paint(bounds.origin(), visible_bounds, cx)
.log_err();
cx.window.rendered_views.insert(self.view_id, rendered_view);
} else {

View file

@ -34,14 +34,12 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json, Action, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, TypeTag, View,
ViewContext, WeakViewHandle, WindowContext,
json, Action, Entity, SizeConstraint, TypeTag, View, ViewContext, WeakViewHandle,
WindowContext,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
use core::panic;
use json::ToJson;
use smallvec::SmallVec;
use std::{
any::{type_name, Any},
borrow::Cow,
@ -61,17 +59,16 @@ pub trait Element<V: 'static>: 'static {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState);
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState;
fn rect_for_text_range(
@ -262,16 +259,15 @@ trait AnyElementState<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Vector2F;
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
);
fn rect_for_text_range(
@ -314,7 +310,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Vector2F {
let result;
*self = match mem::take(self) {
@ -348,11 +344,10 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
*self = match mem::take(self) {
ElementState::PostLayout {
@ -362,14 +357,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
mut layout,
} => {
let bounds = RectF::new(origin, size);
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
ElementState::PostPaint {
element,
constraint,
@ -387,14 +375,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
..
} => {
let bounds = RectF::new(origin, bounds.size());
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
ElementState::PostPaint {
element,
constraint,
@ -517,20 +498,19 @@ impl<V> AnyElement<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Vector2F {
self.state.layout(constraint, view, cx)
}
pub fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.state.paint(scene, origin, visible_bounds, view, cx);
self.state.paint(origin, visible_bounds, view, cx);
}
pub fn rect_for_text_range(
@ -578,7 +558,7 @@ impl<V: 'static> Element<V> for AnyElement<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.layout(constraint, view, cx);
(size, ())
@ -586,14 +566,13 @@ impl<V: 'static> Element<V> for AnyElement<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@ -646,17 +625,9 @@ impl<V> RootElement<V> {
}
pub trait AnyRootElement {
fn layout(
&mut self,
constraint: SizeConstraint,
new_parents: &mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
refreshing: bool,
cx: &mut WindowContext,
) -> Result<Vector2F>;
fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
cx: &mut WindowContext,
@ -671,32 +642,16 @@ pub trait AnyRootElement {
}
impl<V: View> AnyRootElement for RootElement<V> {
fn layout(
&mut self,
constraint: SizeConstraint,
new_parents: &mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
refreshing: bool,
cx: &mut WindowContext,
) -> Result<Vector2F> {
fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
let view = self
.view
.upgrade(cx)
.ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
view.update(cx, |view, cx| {
let mut cx = LayoutContext::new(
cx,
new_parents,
views_to_notify_if_ancestors_change,
refreshing,
);
Ok(self.element.layout(constraint, view, &mut cx))
})
view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
cx: &mut WindowContext,
@ -707,9 +662,7 @@ impl<V: View> AnyRootElement for RootElement<V> {
.ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?;
view.update(cx, |view, cx| {
let mut cx = PaintContext::new(cx);
self.element
.paint(scene, origin, visible_bounds, view, &mut cx);
self.element.paint(origin, visible_bounds, view, cx);
Ok(())
})
}

View file

@ -1,7 +1,6 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
json, AnyElement, Element, SizeConstraint, ViewContext,
};
use json::ToJson;
@ -49,7 +48,7 @@ impl<V: 'static> Element<V> for Align<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max;
constraint.min = Vector2F::zero();
@ -65,12 +64,11 @@ impl<V: 'static> Element<V> for Align<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let my_center = bounds.size() / 2.;
let my_target = my_center + my_center * self.alignment;
@ -79,7 +77,6 @@ impl<V: 'static> Element<V> for Align<V> {
let child_target = child_center + child_center * self.alignment;
self.child.paint(
scene,
bounds.origin() - (child_target - my_target),
visible_bounds,
view,

View file

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Element;
use crate::{
json::{self, json},
PaintContext, SceneBuilder, ViewContext,
ViewContext,
};
use json::ToJson;
use pathfinder_geometry::{
@ -15,7 +15,7 @@ pub struct Canvas<V, F>(F, PhantomData<V>);
impl<V, F> Canvas<V, F>
where
F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
F: FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
{
pub fn new(f: F) -> Self {
Self(f, PhantomData)
@ -24,7 +24,7 @@ where
impl<V: 'static, F> Element<V> for Canvas<V, F>
where
F: 'static + FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
{
type LayoutState = ();
type PaintState = ();
@ -33,7 +33,7 @@ where
&mut self,
constraint: crate::SizeConstraint,
_: &mut V,
_: &mut crate::LayoutContext<V>,
_: &mut crate::ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() {
constraint.max.x()
@ -50,14 +50,13 @@ where
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.0(scene, bounds, visible_bounds, view, cx)
self.0(bounds, visible_bounds, view, cx)
}
fn rect_for_text_range(

View file

@ -3,10 +3,7 @@ use std::ops::Range;
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
use crate::{
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
};
use crate::{json, AnyElement, Element, SizeConstraint, ViewContext};
pub struct Clipped<V> {
child: AnyElement<V>,
@ -26,24 +23,23 @@ impl<V: 'static> Element<V> for Clipped<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(bounds), |scene| {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)
})
cx.scene().push_layer(Some(bounds));
let state = self.child.paint(bounds.origin(), visible_bounds, view, cx);
cx.scene().pop_layer();
state
}
fn rect_for_text_range(

View file

@ -2,9 +2,7 @@ use std::{any::Any, marker::PhantomData};
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use crate::{
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
};
use crate::{AnyElement, Element, SizeConstraint, ViewContext};
use super::Empty;
@ -284,14 +282,14 @@ impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if self.element.is_none() {
let element = self
.component
.take()
.expect("Component can only be rendered once")
.render(view, cx.view_context());
.render(view, cx);
self.element = Some(element);
}
let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
@ -300,17 +298,16 @@ impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.element
.as_mut()
.expect("Layout should always be called before paint")
.paint(scene, bounds.origin(), visible_bounds, view, cx)
.paint(bounds.origin(), visible_bounds, view, cx)
}
fn rect_for_text_range(

View file

@ -5,8 +5,7 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
json, AnyElement, Element, SizeConstraint, ViewContext,
};
pub struct ConstrainedBox<V> {
@ -16,7 +15,7 @@ pub struct ConstrainedBox<V> {
pub enum Constraint<V> {
Static(SizeConstraint),
Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
}
impl<V> ToJson for Constraint<V> {
@ -38,8 +37,7 @@ impl<V: 'static> ConstrainedBox<V> {
pub fn dynamically(
mut self,
constraint: impl 'static
+ FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint,
constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
) -> Self {
self.constraint = Constraint::Dynamic(Box::new(constraint));
self
@ -121,7 +119,7 @@ impl<V: 'static> ConstrainedBox<V> {
&mut self,
input_constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> SizeConstraint {
match &mut self.constraint {
Constraint::Static(constraint) => *constraint,
@ -140,7 +138,7 @@ impl<V: 'static> Element<V> for ConstrainedBox<V> {
&mut self,
mut parent_constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let constraint = self.constraint(parent_constraint, view, cx);
parent_constraint.min = parent_constraint.min.max(constraint.min);
@ -152,17 +150,15 @@ impl<V: 'static> Element<V> for ConstrainedBox<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(visible_bounds), |scene| {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
})
cx.scene().push_layer(Some(visible_bounds));
self.child.paint(bounds.origin(), visible_bounds, view, cx);
cx.scene().pop_layer();
}
fn rect_for_text_range(

View file

@ -9,8 +9,8 @@ use crate::{
},
json::ToJson,
platform::CursorStyle,
scene::{self, Border, CornerRadii, CursorRegion, Quad},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
scene::{self, CornerRadii, CursorRegion, Quad},
AnyElement, Element, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -206,6 +206,163 @@ impl<V> Container<V> {
}
}
#[derive(Copy, Clone, Debug, Default, JsonSchema)]
pub struct Border {
pub color: Color,
pub width: f32,
pub overlay: bool,
pub top: bool,
pub bottom: bool,
pub left: bool,
pub right: bool,
}
impl Into<scene::Border> for Border {
fn into(self) -> scene::Border {
scene::Border {
color: self.color,
left: if self.left { self.width } else { 0.0 },
right: if self.right { self.width } else { 0.0 },
top: if self.top { self.width } else { 0.0 },
bottom: if self.bottom { self.width } else { 0.0 },
}
}
}
impl Border {
pub fn new(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: false,
left: false,
bottom: false,
right: false,
}
}
pub fn all(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: true,
left: true,
bottom: true,
right: true,
}
}
pub fn top(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.top = true;
border
}
pub fn left(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.left = true;
border
}
pub fn bottom(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.bottom = true;
border
}
pub fn right(width: f32, color: Color) -> 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
}
pub fn top_width(&self) -> f32 {
if self.top {
self.width
} else {
0.0
}
}
pub fn left_width(&self) -> f32 {
if self.left {
self.width
} else {
0.0
}
}
}
impl<'de> Deserialize<'de> for Border {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct BorderData {
pub width: f32,
pub color: Color,
#[serde(default)]
pub overlay: bool,
#[serde(default)]
pub top: bool,
#[serde(default)]
pub right: bool,
#[serde(default)]
pub bottom: bool,
#[serde(default)]
pub left: bool,
}
let data = BorderData::deserialize(deserializer)?;
let mut border = Border {
width: data.width,
color: data.color,
overlay: data.overlay,
top: data.top,
bottom: data.bottom,
left: data.left,
right: data.right,
};
if !border.top && !border.bottom && !border.left && !border.right {
border.top = true;
border.bottom = true;
border.left = true;
border.right = true;
}
Ok(border)
}
}
impl ToJson for Border {
fn to_json(&self) -> serde_json::Value {
let mut value = json!({});
if self.top {
value["top"] = json!(self.width);
}
if self.right {
value["right"] = json!(self.width);
}
if self.bottom {
value["bottom"] = json!(self.width);
}
if self.left {
value["left"] = json!(self.width);
}
value
}
}
impl<V: 'static> Element<V> for Container<V> {
type LayoutState = ();
type PaintState = ();
@ -214,7 +371,7 @@ impl<V: 'static> Element<V> for Container<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size_buffer = self.margin_size() + self.padding_size();
if !self.style.border.overlay {
@ -230,12 +387,11 @@ impl<V: 'static> Element<V> for Container<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
@ -243,7 +399,7 @@ impl<V: 'static> Element<V> for Container<V> {
);
if let Some(shadow) = self.style.shadow.as_ref() {
scene.push_shadow(scene::Shadow {
cx.scene().push_shadow(scene::Shadow {
bounds: quad_bounds + shadow.offset,
corner_radii: self.style.corner_radii,
sigma: shadow.blur,
@ -253,7 +409,7 @@ impl<V: 'static> Element<V> for Container<V> {
if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
if let Some(style) = self.style.cursor {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: hit_bounds,
style,
});
@ -264,29 +420,28 @@ impl<V: 'static> Element<V> for Container<V> {
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
if self.style.border.overlay {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.background_color,
border: Default::default(),
corner_radii: self.style.corner_radii.into(),
});
self.child
.paint(scene, child_origin, visible_bounds, view, cx);
self.child.paint(child_origin, visible_bounds, view, cx);
scene.push_layer(None);
scene.push_quad(Quad {
cx.scene().push_layer(None);
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.overlay_color,
border: self.style.border,
border: self.style.border.into(),
corner_radii: self.style.corner_radii.into(),
});
scene.pop_layer();
cx.scene().pop_layer();
} else {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.background_color,
border: self.style.border,
border: self.style.border.into(),
corner_radii: self.style.corner_radii.into(),
});
@ -295,18 +450,17 @@ impl<V: 'static> Element<V> for Container<V> {
self.style.border.left_width(),
self.style.border.top_width(),
);
self.child
.paint(scene, child_origin, visible_bounds, view, cx);
self.child.paint(child_origin, visible_bounds, view, cx);
if self.style.overlay_color.is_some() {
scene.push_layer(None);
scene.push_quad(Quad {
cx.scene().push_layer(None);
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.overlay_color,
border: Default::default(),
corner_radii: self.style.corner_radii.into(),
});
scene.pop_layer();
cx.scene().pop_layer();
}
}
}

View file

@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
LayoutContext, PaintContext, SceneBuilder, ViewContext,
ViewContext,
};
use crate::{Element, SizeConstraint};
@ -34,7 +34,7 @@ impl<V: 'static> Element<V> for Empty {
&mut self,
constraint: SizeConstraint,
_: &mut V,
_: &mut LayoutContext<V>,
_: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() && !self.collapsed {
constraint.max.x()
@ -52,12 +52,11 @@ impl<V: 'static> Element<V> for Empty {
fn paint(
&mut self,
_: &mut SceneBuilder,
_: RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
_: &mut ViewContext<V>,
) -> Self::PaintState {
}

View file

@ -2,8 +2,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
json, AnyElement, Element, SizeConstraint, ViewContext,
};
use serde_json::json;
@ -43,7 +42,7 @@ impl<V: 'static> Element<V> for Expanded<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if self.full_width {
constraint.min.set_x(constraint.max.x());
@ -57,15 +56,13 @@ impl<V: 'static> Element<V> for Expanded<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View file

@ -2,8 +2,7 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{
json::{self, ToJson, Value},
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
SizeConstraint, Vector2FExt, ViewContext,
AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@ -85,7 +84,7 @@ impl<V: 'static> Flex<V> {
remaining_flex: &mut f32,
cross_axis_max: &mut f32,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) {
let cross_axis = self.axis.invert();
for child in self.children.iter_mut() {
@ -136,7 +135,7 @@ impl<V: 'static> Element<V> for Flex<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut total_flex = None;
let mut fixed_space = self.children.len().saturating_sub(1) as f32 * self.spacing;
@ -225,7 +224,7 @@ impl<V: 'static> Element<V> for Flex<V> {
}
if let Some(scroll_state) = self.scroll_state.as_ref() {
scroll_state.0.update(cx.view_context(), |scroll_state, _| {
scroll_state.0.update(cx, |scroll_state, _| {
if let Some(scroll_to) = scroll_state.scroll_to.take() {
let visible_start = scroll_state.scroll_position.get();
let visible_end = visible_start + size.along(self.axis);
@ -260,26 +259,25 @@ impl<V: 'static> Element<V> for Flex<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let mut remaining_space = *remaining_space;
let overflowing = remaining_space < 0.;
if overflowing {
scene.push_layer(Some(visible_bounds));
cx.scene().push_layer(Some(visible_bounds));
}
if let Some(scroll_state) = &self.scroll_state {
scene.push_mouse_region(
crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
if let Some((scroll_state, id)) = &self.scroll_state {
let scroll_state = scroll_state.read(cx).clone();
cx.scene().push_mouse_region(
crate::MouseRegion::new::<Self>(*id, 0, bounds)
.on_scroll({
let scroll_state = scroll_state.0.read(cx).clone();
let axis = self.axis;
move |e, _: &mut V, cx| {
if remaining_space < 0. {
@ -358,7 +356,7 @@ impl<V: 'static> Element<V> for Flex<V> {
aligned_child_origin
};
child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
child.paint(aligned_child_origin, visible_bounds, view, cx);
match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x() + self.spacing, 0.0),
@ -367,7 +365,7 @@ impl<V: 'static> Element<V> for Flex<V> {
}
if overflowing {
scene.pop_layer();
cx.scene().pop_layer();
}
}
@ -443,7 +441,7 @@ impl<V: 'static> Element<V> for FlexItem<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
(size, ())
@ -451,15 +449,13 @@ impl<V: 'static> Element<V> for FlexItem<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)
self.child.paint(bounds.origin(), visible_bounds, view, cx)
}
fn rect_for_text_range(

View file

@ -3,7 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
AnyElement, Element, SizeConstraint, ViewContext,
};
pub struct Hook<V> {
@ -36,7 +36,7 @@ impl<V: 'static> Element<V> for Hook<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
if let Some(handler) = self.after_layout.as_mut() {
@ -47,15 +47,13 @@ impl<V: 'static> Element<V> for Hook<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View file

@ -1,12 +1,11 @@
use super::constrain_size_preserving_aspect_ratio;
use super::{constrain_size_preserving_aspect_ratio, Border};
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
scene, Element, ImageData, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -65,7 +64,7 @@ impl<V: 'static> Element<V> for Image {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let data = match &self.source {
ImageSource::Path(path) => match cx.asset_cache.png(path) {
@ -92,17 +91,16 @@ impl<V: 'static> Element<V> for Image {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
if let Some(data) = layout {
scene.push_image(scene::Image {
cx.scene().push_image(scene::Image {
bounds,
border: self.style.border,
border: self.style.border.into(),
corner_radii: self.style.corner_radius.into(),
grayscale: self.style.grayscale,
data: data.clone(),

View file

@ -39,7 +39,7 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, AnyElement<V>) {
let mut element = if let Some(keystrokes) =
cx.keystrokes_for_action(self.view_id, self.action.as_ref())
@ -61,14 +61,13 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
element: &mut AnyElement<V>,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
element.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View file

@ -8,7 +8,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle},
Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
Element, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -136,7 +136,7 @@ impl<V: 'static> Element<V> for Label {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let runs = self.compute_runs();
let line = cx.text_layout_cache().layout_str(
@ -158,21 +158,14 @@ impl<V: 'static> Element<V> for Label {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
line: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
line.paint(
scene,
bounds.origin(),
visible_bounds,
bounds.size().y(),
cx,
)
line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
}
fn rect_for_text_range(

View file

@ -4,8 +4,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::json,
AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
AnyElement, Element, MouseRegion, SizeConstraint, ViewContext,
};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
@ -100,7 +99,7 @@ impl<V: 'static> Element<V> for List<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let state = &mut *self.state.0.borrow_mut();
let size = constraint.max;
@ -108,7 +107,7 @@ impl<V: 'static> Element<V> for List<V> {
item_constraint.min.set_y(0.);
item_constraint.max.set_y(f32::INFINITY);
if cx.refreshing || state.last_layout_width != Some(size.x()) {
if cx.refreshing() || state.last_layout_width != Some(size.x()) {
state.rendered_range = 0..0;
state.items = SumTree::from_iter(
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
@ -250,17 +249,17 @@ impl<V: 'static> Element<V> for List<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
scroll_top: &mut ListOffset,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
scene.push_mouse_region(
MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
cx.scene().push_layer(Some(visible_bounds));
let view_id = cx.view_id();
cx.scene()
.push_mouse_region(MouseRegion::new::<Self>(view_id, 0, bounds).on_scroll({
let state = self.state.clone();
let height = bounds.height();
let scroll_top = scroll_top.clone();
@ -274,17 +273,14 @@ impl<V: 'static> Element<V> for List<V> {
cx,
)
}
}),
);
}));
let state = &mut *self.state.0.borrow_mut();
for (element, origin) in state.visible_elements(bounds, scroll_top) {
element
.borrow_mut()
.paint(scene, origin, visible_bounds, view, cx);
element.borrow_mut().paint(origin, visible_bounds, view, cx);
}
scene.pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(
@ -453,7 +449,7 @@ impl<V: 'static> StateInner<V> {
existing_element: Option<&ListItem<V>>,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Option<Rc<RefCell<AnyElement<V>>>> {
if let Some(ListItem::Rendered(element)) = existing_element {
Some(element.clone())
@ -647,7 +643,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
#[cfg(test)]
mod tests {
use super::*;
use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
use crate::{elements::Empty, geometry::vector::vec2f, Entity};
use rand::prelude::*;
use std::env;
@ -666,15 +662,7 @@ mod tests {
});
let mut list = List::new(state.clone());
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, _) = list.layout(constraint, &mut view, &mut layout_cx);
let (size, _) = list.layout(constraint, &mut view, cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
@ -698,13 +686,7 @@ mod tests {
cx,
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
assert_eq!(
logical_scroll_top,
ListOffset {
@ -728,13 +710,7 @@ mod tests {
}
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
@ -852,18 +828,10 @@ mod tests {
let mut list = List::new(state.clone());
let window_size = vec2f(width, height);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(
SizeConstraint::new(vec2f(0., 0.), window_size),
&mut view,
&mut layout_cx,
cx,
);
assert_eq!(size, window_size);
last_logical_scroll_top = Some(logical_scroll_top);
@ -976,20 +944,12 @@ mod tests {
&mut self,
_: SizeConstraint,
_: &mut V,
_: &mut LayoutContext<V>,
_: &mut ViewContext<V>,
) -> (Vector2F, ()) {
(self.size, ())
}
fn paint(
&mut self,
_: &mut SceneBuilder,
_: RectF,
_: RectF,
_: &mut (),
_: &mut V,
_: &mut PaintContext<V>,
) {
fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext<V>) {
unimplemented!()
}

View file

@ -10,8 +10,8 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
SceneBuilder, SizeConstraint, TypeTag, ViewContext,
AnyElement, Element, EventContext, MouseRegion, MouseState, SizeConstraint, TypeTag,
ViewContext,
};
use serde_json::json;
use std::ops::Range;
@ -236,26 +236,21 @@ impl<V: 'static> MouseEventHandler<V> {
.round_out()
}
fn paint_regions(
&self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
cx: &mut ViewContext<V>,
) {
fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut ViewContext<V>) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
let hit_bounds = self.hit_bounds(visible_bounds);
if let Some(style) = self.cursor_style {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: hit_bounds,
style,
});
}
scene.push_mouse_region(
let view_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::from_handlers(
self.tag,
cx.view_id(),
view_id,
self.region_id,
hit_bounds,
self.handlers.clone(),
@ -275,31 +270,27 @@ impl<V: 'static> Element<V> for MouseEventHandler<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
if self.above {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
scene.paint_layer(None, |scene| {
self.paint_regions(scene, bounds, visible_bounds, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
cx.paint_layer(None, |cx| {
self.paint_regions(bounds, visible_bounds, cx);
});
} else {
self.paint_regions(scene, bounds, visible_bounds, cx);
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.paint_regions(bounds, visible_bounds, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
}

View file

@ -3,8 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, ViewContext,
AnyElement, Axis, Element, MouseRegion, SizeConstraint, ViewContext,
};
use serde_json::json;
@ -125,7 +124,7 @@ impl<V: 'static> Element<V> for Overlay<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let constraint = if self.anchor_position.is_some() {
SizeConstraint::new(Vector2F::zero(), cx.window_size())
@ -138,12 +137,11 @@ impl<V: 'static> Element<V> for Overlay<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
size: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
let (anchor_position, mut bounds) = match self.position_mode {
OverlayPositionMode::Window => {
@ -213,25 +211,23 @@ impl<V: 'static> Element<V> for Overlay<V> {
OverlayFitMode::None => {}
}
scene.paint_stacking_context(None, self.z_index, |scene| {
if self.hoverable {
enum OverlayHoverCapture {}
// Block hovers in lower stacking contexts
scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
cx.view_id(),
cx.view_id(),
bounds,
cx.scene().push_stacking_context(None, self.z_index);
if self.hoverable {
enum OverlayHoverCapture {}
// Block hovers in lower stacking contexts
let view_id = cx.view_id();
cx.scene()
.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
view_id, view_id, bounds,
));
}
self.child.paint(
scene,
bounds.origin(),
RectF::new(Vector2F::zero(), cx.window_size()),
view,
cx,
);
});
}
self.child.paint(
bounds.origin(),
RectF::new(Vector2F::zero(), cx.window_size()),
view,
cx,
);
cx.scene().pop_stacking_context();
}
fn rect_for_text_range(

View file

@ -7,8 +7,7 @@ use serde_json::json;
use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, TypeTag, View, ViewContext,
AnyElement, AppContext, Axis, Element, MouseRegion, SizeConstraint, TypeTag, View, ViewContext,
};
#[derive(Copy, Clone, Debug)]
@ -105,77 +104,77 @@ impl<V: 'static> Element<V> for Resizable<V> {
&mut self,
constraint: crate::SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), constraint)
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF,
constraint: &mut SizeConstraint,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.push_stacking_context(None, None);
cx.scene().push_stacking_context(None, None);
let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
enum ResizeHandle {}
scene.push_mouse_region(
MouseRegion::new::<ResizeHandle>(
cx.view_id(),
self.handle_side as usize,
handle_region,
)
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_click(MouseButton::Left, {
let on_resize = self.on_resize.clone();
move |click, v, cx| {
if click.click_count == 2 {
on_resize.borrow_mut()(v, None, cx);
let view_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::new::<ResizeHandle>(view_id, self.handle_side as usize, handle_region)
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_click(MouseButton::Left, {
let on_resize = self.on_resize.clone();
move |click, v, cx| {
if click.click_count == 2 {
on_resize.borrow_mut()(v, None, cx);
}
}
}
})
.on_drag(MouseButton::Left, {
let bounds = bounds.clone();
let side = self.handle_side;
let prev_size = side.relevant_component(bounds.size());
let min_size = side.relevant_component(constraint.min);
let max_size = side.relevant_component(constraint.max);
let on_resize = self.on_resize.clone();
let tag = self.tag;
move |event, view: &mut V, cx| {
if event.end {
return;
})
.on_drag(MouseButton::Left, {
let bounds = bounds.clone();
let side = self.handle_side;
let prev_size = side.relevant_component(bounds.size());
let min_size = side.relevant_component(constraint.min);
let max_size = side.relevant_component(constraint.max);
let on_resize = self.on_resize.clone();
let tag = self.tag;
move |event, view: &mut V, cx| {
if event.end {
return;
}
let Some((bounds, _)) = get_bounds(tag, cx) else {
return;
};
let new_size_raw = match side {
// Handle on top side of element => Element is on bottom
HandleSide::Top => {
bounds.height() + bounds.origin_y() - event.position.y()
}
// Handle on right side of element => Element is on left
HandleSide::Right => event.position.x() - bounds.lower_left().x(),
// Handle on left side of element => Element is on the right
HandleSide::Left => {
bounds.width() + bounds.origin_x() - event.position.x()
}
// Handle on bottom side of element => Element is on the top
HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
};
let new_size = min_size.max(new_size_raw).min(max_size).round();
if new_size != prev_size {
on_resize.borrow_mut()(view, Some(new_size), cx);
}
}
let Some((bounds, _)) = get_bounds(tag, cx) else {
return;
};
let new_size_raw = match side {
// Handle on top side of element => Element is on bottom
HandleSide::Top => bounds.height() + bounds.origin_y() - event.position.y(),
// Handle on right side of element => Element is on left
HandleSide::Right => event.position.x() - bounds.lower_left().x(),
// Handle on left side of element => Element is on the right
HandleSide::Left => bounds.width() + bounds.origin_x() - event.position.x(),
// Handle on bottom side of element => Element is on the top
HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
};
let new_size = min_size.max(new_size_raw).min(max_size).round();
if new_size != prev_size {
on_resize.borrow_mut()(view, Some(new_size), cx);
}
}
}),
}),
);
scene.push_cursor_region(crate::CursorRegion {
cx.scene().push_cursor_region(crate::CursorRegion {
bounds: handle_region,
style: match self.handle_side.axis() {
Axis::Horizontal => CursorStyle::ResizeLeftRight,
@ -183,10 +182,9 @@ impl<V: 'static> Element<V> for Resizable<V> {
},
});
scene.pop_stacking_context();
cx.scene().pop_stacking_context();
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@ -242,26 +240,24 @@ impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
&mut self,
constraint: crate::SizeConstraint,
view: &mut V,
cx: &mut crate::LayoutContext<V>,
cx: &mut crate::ViewContext<V>,
) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
scene: &mut crate::SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut crate::PaintContext<V>,
cx: &mut crate::ViewContext<V>,
) -> Self::PaintState {
cx.update_default_global::<ProviderMap, _, _>(|map, _| {
map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
});
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)
self.child.paint(bounds.origin(), visible_bounds, view, cx)
}
fn rect_for_text_range(

View file

@ -3,7 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
AnyElement, Element, SizeConstraint, ViewContext,
};
/// Element which renders it's children in a stack on top of each other.
@ -34,7 +34,7 @@ impl<V: 'static> Element<V> for Stack<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.min;
let mut children = self.children.iter_mut();
@ -52,17 +52,16 @@ impl<V: 'static> Element<V> for Stack<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
for child in &mut self.children {
scene.paint_layer(None, |scene| {
child.paint(scene, bounds.origin(), visible_bounds, view, cx);
});
cx.scene().push_layer(None);
child.paint(bounds.origin(), visible_bounds, view, cx);
cx.scene().pop_layer();
}
}

View file

@ -1,13 +1,12 @@
use super::constrain_size_preserving_aspect_ratio;
use crate::json::ToJson;
use crate::PaintContext;
use crate::{
color::Color,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
scene, Element, LayoutContext, SceneBuilder, SizeConstraint, ViewContext,
scene, Element, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde_derive::Deserialize;
@ -49,7 +48,7 @@ impl<V: 'static> Element<V> for Svg {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
match cx.asset_cache.svg(&self.path) {
Ok(tree) => {
@ -69,15 +68,14 @@ impl<V: 'static> Element<V> for Svg {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_visible_bounds: RectF,
svg: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
if let Some(svg) = svg.clone() {
scene.push_icon(scene::Icon {
cx.scene().push_icon(scene::Icon {
bounds,
svg,
path: self.path.clone(),

View file

@ -7,8 +7,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
TextLayoutCache, ViewContext,
Element, FontCache, SizeConstraint, TextLayoutCache, ViewContext, WindowContext,
};
use log::warn;
use serde_json::json;
@ -21,7 +20,7 @@ pub struct Text {
highlights: Option<Box<[(Range<usize>, HighlightStyle)]>>,
custom_runs: Option<(
Box<[Range<usize>]>,
Box<dyn FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext)>,
Box<dyn FnMut(usize, RectF, &mut WindowContext)>,
)>,
}
@ -58,7 +57,7 @@ impl Text {
pub fn with_custom_runs(
mut self,
runs: impl Into<Box<[Range<usize>]>>,
callback: impl 'static + FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext),
callback: impl 'static + FnMut(usize, RectF, &mut WindowContext),
) -> Self {
self.custom_runs = Some((runs.into(), Box::new(callback)));
self
@ -78,7 +77,7 @@ impl<V: 'static> Element<V> for Text {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
// Convert the string and highlight ranges into an iterator of highlighted chunks.
@ -166,16 +165,15 @@ impl<V: 'static> Element<V> for Text {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let mut origin = bounds.origin();
let empty = Vec::new();
let mut callback = |_, _, _: &mut SceneBuilder, _: &mut AppContext| {};
let mut callback = |_, _, _: &mut WindowContext| {};
let mouse_runs;
let custom_run_callback;
@ -202,7 +200,6 @@ impl<V: 'static> Element<V> for Text {
if boundaries.intersects(visible_bounds) {
if self.soft_wrap {
line.paint_wrapped(
scene,
origin,
visible_bounds,
layout.line_height,
@ -210,7 +207,7 @@ impl<V: 'static> Element<V> for Text {
cx,
);
} else {
line.paint(scene, origin, visible_bounds, layout.line_height, cx);
line.paint(origin, visible_bounds, layout.line_height, cx);
}
}
@ -248,7 +245,7 @@ impl<V: 'static> Element<V> for Text {
*run_origin,
glyph_origin + vec2f(0., layout.line_height),
);
custom_run_callback(*run_ix, bounds, scene, cx);
custom_run_callback(*run_ix, bounds, cx);
*run_origin =
vec2f(origin.x(), glyph_origin.y() + layout.line_height);
}
@ -264,7 +261,7 @@ impl<V: 'static> Element<V> for Text {
run_origin,
glyph_origin + vec2f(0., layout.line_height),
);
custom_run_callback(run_ix, bounds, scene, cx);
custom_run_callback(run_ix, bounds, cx);
custom_runs.next();
}
@ -294,7 +291,7 @@ impl<V: 'static> Element<V> for Text {
run_origin,
line_end + vec2f(0., layout.line_height),
);
custom_run_callback(run_ix, bounds, scene, cx);
custom_run_callback(run_ix, bounds, cx);
if end_offset == run_end_offset {
custom_runs.next();
}
@ -411,18 +408,10 @@ mod tests {
let mut view = TestView;
fonts::with_font_cache(cx.font_cache().clone(), || {
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, state) = text.layout(
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
&mut view,
&mut layout_cx,
cx,
);
assert_eq!(state.shaped_lines.len(), 2);
assert_eq!(state.wrap_boundaries.len(), 2);

View file

@ -6,8 +6,7 @@ use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
Task, TypeTag, ViewContext,
Action, Axis, ElementStateHandle, SizeConstraint, Task, TypeTag, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -189,7 +188,7 @@ impl<V: 'static> Element<V> for Tooltip<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
@ -204,17 +203,15 @@ impl<V: 'static> Element<V> for Tooltip<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
tooltip.paint(scene, bounds.origin(), visible_bounds, view, cx);
tooltip.paint(bounds.origin(), visible_bounds, view, cx);
}
}

View file

@ -6,7 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, ViewContext,
AnyElement, MouseRegion, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -158,7 +158,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if constraint.max.y().is_infinite() {
unimplemented!(
@ -272,18 +272,17 @@ impl<V: 'static> Element<V> for UniformList<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
cx.scene().push_layer(Some(visible_bounds));
scene.push_mouse_region(
cx.scene().push_mouse_region(
MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
let scroll_max = layout.scroll_max;
let state = self.state.clone();
@ -312,11 +311,11 @@ impl<V: 'static> Element<V> for UniformList<V> {
);
for item in &mut layout.items {
item.paint(scene, item_origin, visible_bounds, view, cx);
item.paint(item_origin, visible_bounds, view, cx);
item_origin += vec2f(0.0, layout.item_height);
}
scene.pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(

View file

@ -60,7 +60,7 @@ pub struct Features {
pub zero: Option<bool>,
}
#[derive(Clone, Debug, JsonSchema, Refineable)]
#[derive(Clone, Debug, JsonSchema)]
pub struct TextStyle {
pub color: Color,
pub font_family_name: Arc<str>,
@ -80,19 +80,78 @@ impl TextStyle {
..Default::default()
}
}
}
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
TextStyle {
color: refinement.color.unwrap_or(self.color),
font_family_name: refinement
.font_family_name
.unwrap_or_else(|| self.font_family_name.clone()),
font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
font_id: refinement.font_id.unwrap_or(self.font_id),
font_size: refinement.font_size.unwrap_or(self.font_size),
font_properties: refinement.font_properties.unwrap_or(self.font_properties),
underline: refinement.underline.unwrap_or(self.underline),
soft_wrap: refinement.soft_wrap.unwrap_or(self.soft_wrap),
impl TextStyle {
pub fn refine(
&mut self,
refinement: &TextStyleRefinement,
font_cache: &FontCache,
) -> Result<()> {
if let Some(font_size) = refinement.font_size {
self.font_size = font_size;
}
if let Some(color) = refinement.color {
self.color = color;
}
if let Some(underline) = refinement.underline {
self.underline = underline;
}
let mut update_font_id = false;
if let Some(font_family) = refinement.font_family.clone() {
self.font_family_id = font_cache.load_family(&[&font_family], &Default::default())?;
self.font_family_name = font_family;
update_font_id = true;
}
if let Some(font_weight) = refinement.font_weight {
self.font_properties.weight = font_weight;
update_font_id = true;
}
if let Some(font_style) = refinement.font_style {
self.font_properties.style = font_style;
update_font_id = true;
}
if update_font_id {
self.font_id = font_cache.select_font(self.font_family_id, &self.font_properties)?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub struct TextStyleRefinement {
pub color: Option<Color>,
pub font_family: Option<Arc<str>>,
pub font_size: Option<f32>,
pub font_weight: Option<Weight>,
pub font_style: Option<Style>,
pub underline: Option<Underline>,
}
impl Refineable for TextStyleRefinement {
type Refinement = Self;
fn refine(&mut self, refinement: &Self::Refinement) {
if refinement.color.is_some() {
self.color = refinement.color;
}
if refinement.font_family.is_some() {
self.font_family = refinement.font_family.clone();
}
if refinement.font_size.is_some() {
self.font_size = refinement.font_size;
}
if refinement.font_weight.is_some() {
self.font_weight = refinement.font_weight;
}
if refinement.font_style.is_some() {
self.font_style = refinement.font_style;
}
if refinement.underline.is_some() {
self.underline = refinement.underline;
}
}
}

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use super::scene::{Path, PathVertex};
use crate::{color::Color, json::ToJson};
pub use pathfinder_geometry::*;
@ -133,13 +135,14 @@ impl ToJson for RectF {
}
}
#[derive(Refineable)]
pub struct Point<T: Clone + Default> {
#[derive(Refineable, Debug)]
#[refineable(debug)]
pub struct Point<T: Clone + Default + Debug> {
pub x: T,
pub y: T,
}
impl<T: Clone + Default> Clone for Point<T> {
impl<T: Clone + Default + Debug> Clone for Point<T> {
fn clone(&self) -> Self {
Self {
x: self.x.clone(),
@ -148,7 +151,7 @@ impl<T: Clone + Default> Clone for Point<T> {
}
}
impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
fn into(self) -> taffy::geometry::Point<T> {
taffy::geometry::Point {
x: self.x,
@ -157,13 +160,14 @@ impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
}
}
#[derive(Clone, Refineable)]
pub struct Size<T: Clone + Default> {
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
pub struct Size<T: Clone + Default + Debug> {
pub width: T,
pub height: T,
}
impl<S, T: Clone + Default> From<taffy::geometry::Size<S>> for Size<T>
impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
where
S: Into<T>,
{
@ -175,7 +179,7 @@ where
}
}
impl<S, T: Clone + Default> Into<taffy::geometry::Size<S>> for Size<T>
impl<S, T: Clone + Default + Debug> Into<taffy::geometry::Size<S>> for Size<T>
where
T: Into<S>,
{
@ -222,34 +226,15 @@ impl Size<Length> {
}
}
#[derive(Clone, Default, Refineable)]
pub struct Edges<T: Clone + Default> {
#[derive(Clone, Default, Refineable, Debug)]
#[refineable(debug)]
pub struct Edges<T: Clone + Default + Debug> {
pub top: T,
pub right: T,
pub bottom: T,
pub left: T,
}
impl Edges<DefiniteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.),
right: pixels(0.),
bottom: pixels(0.),
left: pixels(0.),
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size),
right: self.right.to_taffy(rem_size),
bottom: self.bottom.to_taffy(rem_size),
left: self.left.to_taffy(rem_size),
}
}
}
impl Edges<Length> {
pub fn auto() -> Self {
Self {
@ -282,12 +267,76 @@ impl Edges<Length> {
}
}
impl Edges<DefiniteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.),
right: pixels(0.),
bottom: pixels(0.),
left: pixels(0.),
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size),
right: self.right.to_taffy(rem_size),
bottom: self.bottom.to_taffy(rem_size),
left: self.left.to_taffy(rem_size),
}
}
}
impl Edges<AbsoluteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.),
right: pixels(0.),
bottom: pixels(0.),
left: pixels(0.),
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size),
right: self.right.to_taffy(rem_size),
bottom: self.bottom.to_taffy(rem_size),
left: self.left.to_taffy(rem_size),
}
}
pub fn to_pixels(&self, rem_size: f32) -> Edges<f32> {
Edges {
top: self.top.to_pixels(rem_size),
right: self.right.to_pixels(rem_size),
bottom: self.bottom.to_pixels(rem_size),
left: self.left.to_pixels(rem_size),
}
}
}
impl Edges<f32> {
pub fn is_empty(&self) -> bool {
self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0
}
}
#[derive(Clone, Copy)]
pub enum AbsoluteLength {
Pixels(f32),
Rems(f32),
}
impl std::fmt::Debug for AbsoluteLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AbsoluteLength::Pixels(pixels) => write!(f, "{}px", pixels),
AbsoluteLength::Rems(rems) => write!(f, "{}rems", rems),
}
}
}
impl AbsoluteLength {
pub fn to_pixels(&self, rem_size: f32) -> f32 {
match self {
@ -295,6 +344,13 @@ impl AbsoluteLength {
AbsoluteLength::Rems(rems) => rems * rem_size,
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
match self {
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
AbsoluteLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
}
}
}
impl Default for AbsoluteLength {
@ -307,7 +363,7 @@ impl Default for AbsoluteLength {
#[derive(Clone, Copy)]
pub enum DefiniteLength {
Absolute(AbsoluteLength),
Relative(f32), // Percent, from 0 to 100.
Relative(f32), // 0. to 1.
}
impl DefiniteLength {
@ -326,6 +382,15 @@ impl DefiniteLength {
}
}
impl std::fmt::Debug for DefiniteLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DefiniteLength::Absolute(length) => std::fmt::Debug::fmt(length, f),
DefiniteLength::Relative(fract) => write!(f, "{}%", (fract * 100.0) as i32),
}
}
}
impl From<AbsoluteLength> for DefiniteLength {
fn from(length: AbsoluteLength) -> Self {
Self::Absolute(length)
@ -345,6 +410,15 @@ pub enum Length {
Auto,
}
impl std::fmt::Debug for Length {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
Length::Auto => write!(f, "auto"),
}
}
}
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
DefiniteLength::Relative(fraction).into()
}
@ -387,3 +461,9 @@ impl Default for Length {
Self::Definite(DefiniteLength::default())
}
}
impl From<()> for Length {
fn from(_: ()) -> Self {
Self::Definite(DefiniteLength::default())
}
}

View file

@ -1,4 +1,5 @@
mod app;
mod image_cache;
pub use app::*;
mod assets;
#[cfg(any(test, feature = "test-support"))]
@ -8,6 +9,7 @@ pub mod elements;
pub mod font_cache;
mod image_data;
pub use crate::image_data::ImageData;
pub use taffy;
pub mod views;
pub use font_cache::FontCache;
mod clipboard;
@ -27,9 +29,9 @@ pub mod json;
pub mod keymap_matcher;
pub mod platform;
pub use gpui_macros::{test, Element};
pub use usvg;
pub use window::{
Axis, EngineLayout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt,
WindowContext,
Axis, Layout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt, WindowContext,
};
pub use anyhow;

View file

@ -0,0 +1,99 @@
use std::sync::Arc;
use crate::ImageData;
use collections::HashMap;
use futures::{
future::{BoxFuture, Shared},
AsyncReadExt, FutureExt,
};
use image::ImageError;
use parking_lot::Mutex;
use thiserror::Error;
use util::{
arc_cow::ArcCow,
http::{self, HttpClient},
};
#[derive(Debug, Error, Clone)]
pub enum Error {
#[error("http error: {0}")]
Client(#[from] http::Error),
#[error("IO error: {0}")]
Io(Arc<std::io::Error>),
#[error("unexpected http status: {status}, body: {body}")]
BadStatus {
status: http::StatusCode,
body: String,
},
#[error("image error: {0}")]
Image(Arc<ImageError>),
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::Io(Arc::new(error))
}
}
impl From<ImageError> for Error {
fn from(error: ImageError) -> Self {
Error::Image(Arc::new(error))
}
}
pub struct ImageCache {
client: Arc<dyn HttpClient>,
images: Arc<Mutex<HashMap<ArcCow<'static, str>, FetchImageFuture>>>,
}
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
impl ImageCache {
pub fn new(client: Arc<dyn HttpClient>) -> Self {
ImageCache {
client,
images: Default::default(),
}
}
pub fn get(
&self,
uri: impl Into<ArcCow<'static, str>>,
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
let uri = uri.into();
let mut images = self.images.lock();
match images.get(uri.as_ref()) {
Some(future) => future.clone(),
None => {
let client = self.client.clone();
let future = {
let uri = uri.clone();
async move {
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
if !response.status().is_success() {
return Err(Error::BadStatus {
status: response.status(),
body: String::from_utf8_lossy(&body).into_owned(),
});
}
let format = image::guess_format(&body)?;
let image =
image::load_from_memory_with_format(&body, format)?.into_bgra8();
Ok(ImageData::new(image))
}
}
.boxed()
.shared();
images.insert(uri, future.clone());
future
}
}
}
}

View file

@ -146,6 +146,7 @@ pub trait Window {
fn titlebar_height(&self) -> f32;
fn appearance(&self) -> Appearance;
fn screen(&self) -> Rc<dyn Screen>;
fn mouse_position(&self) -> Vector2F;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);

View file

@ -37,6 +37,7 @@ use objc::{
runtime::{Class, Object, Sel},
sel, sel_impl,
};
use postage::oneshot;
use ptr::null_mut;
use std::{

View file

@ -577,7 +577,6 @@ impl Renderer {
};
for (ix, quad) in quads.iter().enumerate() {
let bounds = quad.bounds * scale_factor;
let border_width = quad.border.width * scale_factor;
let shader_quad = shaders::GPUIQuad {
origin: bounds.origin().round().to_float2(),
size: bounds.size().round().to_float2(),
@ -585,10 +584,10 @@ impl Renderer {
.background
.unwrap_or_else(Color::transparent_black)
.to_uchar4(),
border_top: border_width * (quad.border.top as usize as f32),
border_right: border_width * (quad.border.right as usize as f32),
border_bottom: border_width * (quad.border.bottom as usize as f32),
border_left: border_width * (quad.border.left as usize as f32),
border_top: quad.border.top * scale_factor,
border_right: quad.border.right * scale_factor,
border_bottom: quad.border.bottom * scale_factor,
border_left: quad.border.left * scale_factor,
border_color: quad.border.color.to_uchar4(),
corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
@ -746,7 +745,6 @@ impl Renderer {
let origin = image.bounds.origin() * scale_factor;
let target_size = image.bounds.size() * scale_factor;
let corner_radii = image.corner_radii * scale_factor;
let border_width = image.border.width * scale_factor;
let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
images_by_atlas
.entry(alloc_id.atlas_id)
@ -756,10 +754,10 @@ impl Renderer {
target_size: target_size.to_float2(),
source_size: atlas_bounds.size().to_float2(),
atlas_origin: atlas_bounds.origin().to_float2(),
border_top: border_width * (image.border.top as usize as f32),
border_right: border_width * (image.border.right as usize as f32),
border_bottom: border_width * (image.border.bottom as usize as f32),
border_left: border_width * (image.border.left as usize as f32),
border_top: image.border.top * scale_factor,
border_right: image.border.right * scale_factor,
border_bottom: image.border.bottom * scale_factor,
border_left: image.border.left * scale_factor,
border_color: image.border.color.to_uchar4(),
corner_radius_top_left: corner_radii.top_left,
corner_radius_top_right: corner_radii.top_right,

View file

@ -202,6 +202,10 @@ impl platform::Window for StatusItem {
}
}
fn mouse_position(&self) -> Vector2F {
unimplemented!()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}

View file

@ -221,6 +221,14 @@ unsafe fn build_classes() {
};
}
pub fn convert_mouse_position(position: NSPoint, window_height: f32) -> Vector2F {
vec2f(
position.x as f32,
// MacOS screen coordinates are relative to bottom left
window_height - position.y as f32,
)
}
unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
let mut decl = ClassDecl::new(name, superclass).unwrap();
decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
@ -661,6 +669,16 @@ impl platform::Window for MacWindow {
}
}
fn mouse_position(&self) -> Vector2F {
let position = unsafe {
self.0
.borrow()
.native_window
.mouseLocationOutsideOfEventStream()
};
convert_mouse_position(position, self.content_size().y())
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}

View file

@ -332,6 +332,10 @@ impl super::Window for Window {
Rc::new(Screen)
}
fn mouse_position(&self) -> Vector2F {
Vector2F::zero()
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}

View file

@ -8,7 +8,6 @@ use derive_more::Mul;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use serde_json::json;
use std::{
any::{Any, TypeId},
borrow::Cow,
@ -20,7 +19,6 @@ use crate::{
color::Color,
fonts::{FontId, GlyphId},
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
platform::{current::Surface, CursorStyle},
ImageData, WindowContext,
};
@ -28,10 +26,9 @@ pub use mouse_event::*;
pub use mouse_region::*;
pub struct SceneBuilder {
scale_factor: f32,
stacking_contexts: Vec<StackingContext>,
active_stacking_context_stack: Vec<usize>,
/// Used by the playground crate.
/// Used by the gpui2 crate.
pub event_handlers: Vec<EventHandler>,
#[cfg(debug_assertions)]
mouse_region_ids: HashSet<MouseRegionId>,
@ -171,15 +168,13 @@ pub struct Icon {
pub color: Color,
}
#[derive(Clone, Copy, Default, Debug, JsonSchema)]
#[derive(Clone, Copy, Default, Debug)]
pub struct Border {
pub width: f32,
pub color: Color,
pub overlay: bool,
pub top: bool,
pub right: bool,
pub bottom: bool,
pub left: bool,
pub top: f32,
pub right: f32,
pub bottom: f32,
pub left: f32,
}
#[derive(Clone, Copy, Default, Debug)]
@ -191,47 +186,6 @@ pub struct Underline {
pub squiggly: bool,
}
impl<'de> Deserialize<'de> for Border {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct BorderData {
pub width: f32,
pub color: Color,
#[serde(default)]
pub overlay: bool,
#[serde(default)]
pub top: bool,
#[serde(default)]
pub right: bool,
#[serde(default)]
pub bottom: bool,
#[serde(default)]
pub left: bool,
}
let data = BorderData::deserialize(deserializer)?;
let mut border = Border {
width: data.width,
color: data.color,
overlay: data.overlay,
top: data.top,
bottom: data.bottom,
left: data.left,
right: data.right,
};
if !border.top && !border.bottom && !border.left && !border.right {
border.top = true;
border.bottom = true;
border.left = true;
border.right = true;
}
Ok(border)
}
}
#[derive(Debug)]
pub struct Path {
pub bounds: RectF,
@ -290,45 +244,40 @@ impl Scene {
}
impl SceneBuilder {
pub fn new(scale_factor: f32) -> Self {
let stacking_context = StackingContext::new(None, 0);
SceneBuilder {
scale_factor,
stacking_contexts: vec![stacking_context],
active_stacking_context_stack: vec![0],
pub fn new() -> Self {
let mut this = SceneBuilder {
stacking_contexts: Vec::new(),
active_stacking_context_stack: Vec::new(),
#[cfg(debug_assertions)]
mouse_region_ids: Default::default(),
mouse_region_ids: HashSet::default(),
event_handlers: Vec::new(),
}
};
this.clear();
this
}
pub fn build(mut self) -> Scene {
self.stacking_contexts
.sort_by_key(|context| context.z_index);
pub fn clear(&mut self) {
self.stacking_contexts.clear();
self.stacking_contexts.push(StackingContext::new(None, 0));
self.active_stacking_context_stack.clear();
self.active_stacking_context_stack.push(0);
#[cfg(debug_assertions)]
self.mouse_region_ids.clear();
}
pub fn build(&mut self, scale_factor: f32) -> Scene {
let mut stacking_contexts = std::mem::take(&mut self.stacking_contexts);
stacking_contexts.sort_by_key(|context| context.z_index);
let event_handlers = std::mem::take(&mut self.event_handlers);
self.clear();
Scene {
scale_factor: self.scale_factor,
stacking_contexts: self.stacking_contexts,
event_handlers: self.event_handlers,
scale_factor,
stacking_contexts,
event_handlers,
}
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
pub fn paint_stacking_context<F>(
&mut self,
clip_bounds: Option<RectF>,
z_index: Option<usize>,
f: F,
) where
F: FnOnce(&mut Self),
{
self.push_stacking_context(clip_bounds, z_index);
f(self);
self.pop_stacking_context();
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
self.active_stacking_context_stack
@ -342,15 +291,6 @@ impl SceneBuilder {
assert!(!self.active_stacking_context_stack.is_empty());
}
pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
where
F: FnOnce(&mut Self),
{
self.push_layer(clip_bounds);
f(self);
self.pop_layer();
}
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context().push_layer(clip_bounds);
}
@ -606,99 +546,6 @@ impl Layer {
}
}
impl Border {
pub fn new(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: false,
left: false,
bottom: false,
right: false,
}
}
pub fn all(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: true,
left: true,
bottom: true,
right: true,
}
}
pub fn top(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.top = true;
border
}
pub fn left(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.left = true;
border
}
pub fn bottom(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.bottom = true;
border
}
pub fn right(width: f32, color: Color) -> 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
}
pub fn top_width(&self) -> f32 {
if self.top {
self.width
} else {
0.0
}
}
pub fn left_width(&self) -> f32 {
if self.left {
self.width
} else {
0.0
}
}
}
impl ToJson for Border {
fn to_json(&self) -> serde_json::Value {
let mut value = json!({});
if self.top {
value["top"] = json!(self.width);
}
if self.right {
value["right"] = json!(self.width);
}
if self.bottom {
value["bottom"] = json!(self.width);
}
if self.left {
value["left"] = json!(self.width);
}
value
}
}
impl MouseRegion {
pub fn id(&self) -> MouseRegionId {
self.id

View file

@ -9,7 +9,6 @@ use crate::{
platform::FontSystem,
scene,
window::WindowContext,
SceneBuilder,
};
use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@ -284,7 +283,6 @@ impl Line {
pub fn paint(
&self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
@ -347,7 +345,7 @@ impl Line {
}
if let Some((underline_origin, underline_style)) = finished_underline {
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
@ -357,14 +355,14 @@ impl Line {
}
if glyph.is_emoji {
scene.push_image_glyph(scene::ImageGlyph {
cx.scene().push_image_glyph(scene::ImageGlyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_origin,
});
} else {
scene.push_glyph(scene::Glyph {
cx.scene().push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
@ -377,7 +375,7 @@ impl Line {
if let Some((underline_start, underline_style)) = underline.take() {
let line_end_x = origin.x() + self.layout.width;
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_start,
width: line_end_x - underline_start.x(),
color: underline_style.color.unwrap(),
@ -389,7 +387,6 @@ impl Line {
pub fn paint_wrapped(
&self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
@ -417,7 +414,7 @@ impl Line {
{
boundaries.next();
if let Some((underline_origin, underline_style)) = underline {
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
@ -461,7 +458,7 @@ impl Line {
}
if let Some((underline_origin, underline_style)) = finished_underline {
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
@ -477,14 +474,14 @@ impl Line {
);
if glyph_bounds.intersects(visible_bounds) {
if glyph.is_emoji {
scene.push_image_glyph(scene::ImageGlyph {
cx.scene().push_image_glyph(scene::ImageGlyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_bounds.origin() + baseline_offset,
});
} else {
scene.push_glyph(scene::Glyph {
cx.scene().push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
@ -498,7 +495,7 @@ impl Line {
if let Some((underline_origin, underline_style)) = underline.take() {
let line_end_x = glyph_origin.x() + self.layout.width - prev_position;
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: line_end_x - underline_origin.x(),
thickness: underline_style.thickness.into(),

32
crates/gpui2/Cargo.toml Normal file
View file

@ -0,0 +1,32 @@
[package]
name = "gpui2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "gpui2"
path = "src/gpui2.rs"
[features]
test-support = ["gpui/test-support"]
[dependencies]
anyhow.workspace = true
derive_more.workspace = true
gpui = { path = "../gpui" }
log.workspace = true
futures.workspace = true
gpui2_macros = { path = "../gpui2_macros" }
parking_lot.workspace = true
refineable.workspace = true
rust-embed.workspace = true
serde.workspace = true
settings = { path = "../settings" }
simplelog = "0.9"
smallvec.workspace = true
theme = { path = "../theme" }
util = { path = "../util" }
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }

View file

@ -1,8 +1,8 @@
use crate::{layout_context::LayoutContext, paint_context::PaintContext};
use crate::ViewContext;
use gpui::{geometry::rect::RectF, LayoutEngine, LayoutId};
use util::ResultExt;
/// Makes a new, playground-style element into a legacy element.
/// Makes a new, gpui2-style element into a legacy element.
pub struct AdapterElement<V>(pub(crate) crate::element::AnyElement<V>);
impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
@ -13,12 +13,11 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
cx: &mut gpui::LayoutContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
cx.push_layout_engine(LayoutEngine::new());
let size = constraint.max;
let mut cx = LayoutContext::new(cx);
let mut cx = ViewContext::new(cx);
let layout_id = self.0.layout(view, &mut cx).log_err();
if let Some(layout_id) = layout_id {
cx.layout_engine()
@ -37,41 +36,40 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_visible_bounds: RectF,
layout_data: &mut Option<(LayoutEngine, LayoutId)>,
view: &mut V,
legacy_cx: &mut gpui::PaintContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> Self::PaintState {
let (layout_engine, layout_id) = layout_data.take().unwrap();
legacy_cx.push_layout_engine(layout_engine);
let mut cx = PaintContext::new(legacy_cx, scene);
self.0.paint(view, &mut cx);
*layout_data = legacy_cx.pop_layout_engine().zip(Some(layout_id));
cx.push_layout_engine(layout_engine);
self.0
.paint(view, bounds.origin(), &mut ViewContext::new(cx));
*layout_data = cx.pop_layout_engine().zip(Some(layout_id));
debug_assert!(layout_data.is_some());
}
fn rect_for_text_range(
&self,
range_utf16: std::ops::Range<usize>,
bounds: RectF,
visible_bounds: RectF,
layout: &Self::LayoutState,
paint: &Self::PaintState,
view: &V,
cx: &gpui::ViewContext<V>,
_range_utf16: std::ops::Range<usize>,
_bounds: RectF,
_visible_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
_view: &V,
_cx: &gpui::ViewContext<V>,
) -> Option<RectF> {
todo!("implement before merging to main")
}
fn debug(
&self,
bounds: RectF,
layout: &Self::LayoutState,
paint: &Self::PaintState,
view: &V,
cx: &gpui::ViewContext<V>,
_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
_view: &V,
_cx: &gpui::ViewContext<V>,
) -> gpui::serde_json::Value {
todo!("implement before merging to main")
}

View file

@ -1,8 +1,9 @@
#![allow(dead_code)]
use std::{num::ParseIntError, ops::Range};
use serde::de::{self, Deserialize, Deserializer, Visitor};
use smallvec::SmallVec;
use std::fmt;
use std::{num::ParseIntError, ops::Range};
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
@ -19,6 +20,40 @@ pub struct Rgba {
pub a: f32,
}
struct RgbaVisitor;
impl<'de> Visitor<'de> for RgbaVisitor {
type Value = Rgba;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
if value.len() == 7 || value.len() == 9 {
let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
let a = if value.len() == 9 {
u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
} else {
1.0
};
Ok(Rgba { r, g, b, a })
} else {
Err(E::custom(
"Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
))
}
}
}
impl<'de> Deserialize<'de> for Rgba {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(RgbaVisitor)
}
}
pub trait Lerp {
fn lerp(&self, level: f32) -> Hsla;
}
@ -219,6 +254,19 @@ impl Into<gpui::color::Color> for Hsla {
}
}
impl<'de> Deserialize<'de> for Hsla {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// First, deserialize it into Rgba
let rgba = Rgba::deserialize(deserializer)?;
// Then, use the From<Rgba> for Hsla implementation to convert it
Ok(Hsla::from(rgba))
}
}
pub struct ColorScale {
colors: SmallVec<[Hsla; 2]>,
positions: SmallVec<[f32; 2]>,

186
crates/gpui2/src/element.rs Normal file
View file

@ -0,0 +1,186 @@
pub use crate::ViewContext;
use anyhow::Result;
use gpui::geometry::vector::Vector2F;
pub use gpui::{Layout, LayoutId};
use smallvec::SmallVec;
pub trait Element<V: 'static>: 'static + IntoElement<V> {
type PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized;
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized;
fn into_any(self) -> AnyElement<V>
where
Self: 'static + Sized,
{
AnyElement(Box::new(StatefulElement {
element: self,
phase: ElementPhase::Init,
}))
}
}
/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
trait AnyStatefulElement<V> {
fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId>;
fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>);
}
/// A wrapper around an element that stores its layout state.
struct StatefulElement<V: 'static, E: Element<V>> {
element: E,
phase: ElementPhase<V, E>,
}
enum ElementPhase<V: 'static, E: Element<V>> {
Init,
PostLayout {
layout_id: LayoutId,
paint_state: E::PaintState,
},
#[allow(dead_code)]
PostPaint {
layout: Layout,
paint_state: E::PaintState,
},
Error(String),
}
impl<V: 'static, E: Element<V>> std::fmt::Debug for ElementPhase<V, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ElementPhase::Init => write!(f, "Init"),
ElementPhase::PostLayout { layout_id, .. } => {
write!(f, "PostLayout with layout id: {:?}", layout_id)
}
ElementPhase::PostPaint { layout, .. } => {
write!(f, "PostPaint with layout: {:?}", layout)
}
ElementPhase::Error(err) => write!(f, "Error: {}", err),
}
}
}
impl<V: 'static, E: Element<V>> Default for ElementPhase<V, E> {
fn default() -> Self {
Self::Init
}
}
/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
impl<V, E: Element<V>> AnyStatefulElement<V> for StatefulElement<V, E> {
fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId> {
let result;
self.phase = match self.element.layout(view, cx) {
Ok((layout_id, paint_state)) => {
result = Ok(layout_id);
ElementPhase::PostLayout {
layout_id,
paint_state,
}
}
Err(error) => {
let message = error.to_string();
result = Err(error);
ElementPhase::Error(message)
}
};
result
}
fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
self.phase = match std::mem::take(&mut self.phase) {
ElementPhase::PostLayout {
layout_id,
mut paint_state,
} => match cx.computed_layout(layout_id) {
Ok(layout) => {
self.element
.paint(view, parent_origin, &layout, &mut paint_state, cx);
ElementPhase::PostPaint {
layout,
paint_state,
}
}
Err(error) => ElementPhase::Error(error.to_string()),
},
ElementPhase::PostPaint {
layout,
mut paint_state,
} => {
self.element
.paint(view, parent_origin, &layout, &mut paint_state, cx);
ElementPhase::PostPaint {
layout,
paint_state,
}
}
phase @ ElementPhase::Error(_) => phase,
phase @ _ => {
panic!("invalid element phase to call paint: {:?}", phase);
}
};
}
}
/// A dynamic element.
pub struct AnyElement<V>(Box<dyn AnyStatefulElement<V>>);
impl<V> AnyElement<V> {
pub fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId> {
self.0.layout(view, cx)
}
pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
self.0.paint(view, parent_origin, cx)
}
}
pub trait ParentElement<V: 'static> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
fn child(mut self, child: impl IntoElement<V>) -> Self
where
Self: Sized,
{
self.children_mut().push(child.into_element().into_any());
self
}
fn children<I, E>(mut self, children: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoElement<V>,
Self: Sized,
{
self.children_mut().extend(
children
.into_iter()
.map(|child| child.into_element().into_any()),
);
self
}
}
pub trait IntoElement<V: 'static> {
type Element: Element<V>;
fn into_element(self) -> Self::Element;
}

View file

@ -0,0 +1,10 @@
pub mod div;
pub mod hoverable;
mod img;
pub mod pressable;
pub mod svg;
pub mod text;
pub use div::div;
pub use img::img;
pub use svg::svg;

View file

@ -0,0 +1,320 @@
use std::{cell::Cell, rc::Rc};
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
hsla,
style::{CornerRadii, Overflow, Style, StyleHelpers, Styleable},
InteractionHandlers, Interactive, ViewContext,
};
use anyhow::Result;
use gpui::{
geometry::{rect::RectF, vector::Vector2F, Point},
platform::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
scene::{self},
LayoutId,
};
use refineable::{Refineable, RefinementCascade};
use smallvec::SmallVec;
use util::ResultExt;
pub struct Div<V: 'static> {
styles: RefinementCascade<Style>,
handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<V>; 2]>,
scroll_state: Option<ScrollState>,
}
pub fn div<V>() -> Div<V> {
Div {
styles: Default::default(),
handlers: Default::default(),
children: Default::default(),
scroll_state: None,
}
}
impl<V: 'static> Element<V> for Div<V> {
type PaintState = Vec<LayoutId>;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
let style = self.computed_style();
let pop_text_style = style.text_style(cx).map_or(false, |style| {
cx.push_text_style(&style).log_err().is_some()
});
let children = self
.children
.iter_mut()
.map(|child| child.layout(view, cx))
.collect::<Result<Vec<LayoutId>>>()?;
if pop_text_style {
cx.pop_text_style();
}
Ok((cx.add_layout_node(style, children.clone())?, children))
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
child_layouts: &mut Vec<LayoutId>,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let order = layout.order;
let bounds = layout.bounds + parent_origin;
let style = self.computed_style();
let pop_text_style = style.text_style(cx).map_or(false, |style| {
cx.push_text_style(&style).log_err().is_some()
});
style.paint_background(bounds, cx);
self.interaction_handlers().paint(order, bounds, cx);
let scrolled_origin = bounds.origin() - self.scroll_offset(&style.overflow);
// TODO: Support only one dimension being hidden
let mut pop_layer = false;
if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
cx.scene().push_layer(Some(bounds));
pop_layer = true;
}
for child in &mut self.children {
child.paint(view, scrolled_origin, cx);
}
if pop_layer {
cx.scene().pop_layer();
}
style.paint_foreground(bounds, cx);
if pop_text_style {
cx.pop_text_style();
}
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
if cx.is_inspector_enabled() {
self.paint_inspector(parent_origin, layout, cx);
}
}
}
impl<V: 'static> Div<V> {
pub fn overflow_hidden(mut self) -> Self {
self.declared_style().overflow.x = Some(Overflow::Hidden);
self.declared_style().overflow.y = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_x(mut self) -> Self {
self.declared_style().overflow.x = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_y(mut self) -> Self {
self.declared_style().overflow.y = Some(Overflow::Hidden);
self
}
pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.x = Some(Overflow::Scroll);
self.declared_style().overflow.y = Some(Overflow::Scroll);
self
}
pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.x = Some(Overflow::Scroll);
self
}
pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.y = Some(Overflow::Scroll);
self
}
fn scroll_offset(&self, overflow: &Point<Overflow>) -> Vector2F {
let mut offset = Vector2F::zero();
if overflow.y == Overflow::Scroll {
offset.set_y(self.scroll_state.as_ref().unwrap().y());
}
if overflow.x == Overflow::Scroll {
offset.set_x(self.scroll_state.as_ref().unwrap().x());
}
offset
}
fn handle_scroll(
&mut self,
order: u32,
bounds: RectF,
overflow: Point<Overflow>,
child_layout_ids: &[LayoutId],
cx: &mut ViewContext<V>,
) {
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
let mut scroll_max = Vector2F::zero();
for child_layout_id in child_layout_ids {
if let Some(child_layout) = cx
.layout_engine()
.unwrap()
.computed_layout(*child_layout_id)
.log_err()
{
scroll_max = scroll_max.max(child_layout.bounds.lower_right());
}
}
scroll_max -= bounds.size();
let scroll_state = self.scroll_state.as_ref().unwrap().clone();
cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
if bounds.contains_point(event.position) {
let scroll_delta = match event.delta {
gpui::platform::ScrollDelta::Pixels(delta) => delta,
gpui::platform::ScrollDelta::Lines(delta) => {
delta * cx.text_style().font_size
}
};
if overflow.x == Overflow::Scroll {
scroll_state.set_x(
(scroll_state.x() - scroll_delta.x())
.max(0.)
.min(scroll_max.x()),
);
}
if overflow.y == Overflow::Scroll {
scroll_state.set_y(
(scroll_state.y() - scroll_delta.y())
.max(0.)
.min(scroll_max.y()),
);
}
cx.repaint();
} else {
cx.bubble_event();
}
})
}
}
fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut ViewContext<V>) {
let style = self.styles.merged();
let bounds = layout.bounds + parent_origin;
let hovered = bounds.contains_point(cx.mouse_position());
if hovered {
let rem_size = cx.rem_size();
cx.scene().push_quad(scene::Quad {
bounds,
background: Some(hsla(0., 0., 1., 0.05).into()),
border: gpui::Border {
color: hsla(0., 0., 1., 0.2).into(),
top: 1.,
right: 1.,
bottom: 1.,
left: 1.,
},
corner_radii: CornerRadii::default()
.refined(&style.corner_radii)
.to_gpui(bounds.size(), rem_size),
})
}
let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left));
cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| {
if bounds.contains_point(event.position) {
if event.is_down {
pressed.set(true);
} else if pressed.get() {
pressed.set(false);
eprintln!("clicked div {:?} {:#?}", bounds, style);
}
}
});
let hovered = Cell::new(hovered);
cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| {
cx.bubble_event();
let hovered_now = bounds.contains_point(event.position);
if hovered.get() != hovered_now {
hovered.set(hovered_now);
cx.repaint();
}
});
}
}
impl<V> Styleable for Div<V> {
type Style = Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
&mut self.styles
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
self.styles.base()
}
}
impl<V> StyleHelpers for Div<V> {}
impl<V> Interactive<V> for Div<V> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
&mut self.handlers
}
}
impl<V: 'static> ParentElement<V> for Div<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
impl<V: 'static> IntoElement<V> for Div<V> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
#[derive(Default, Clone)]
pub struct ScrollState(Rc<Cell<Vector2F>>);
impl ScrollState {
pub fn x(&self) -> f32 {
self.0.get().x()
}
pub fn set_x(&self, value: f32) {
let mut current_value = self.0.get();
current_value.set_x(value);
self.0.set(current_value);
}
pub fn y(&self) -> f32 {
self.0.get().y()
}
pub fn set_y(&self, value: f32) {
let mut current_value = self.0.get();
current_value.set_y(value);
self.0.set(current_value);
}
}

View file

@ -0,0 +1,105 @@
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
style::{Style, StyleHelpers, Styleable},
ViewContext,
};
use anyhow::Result;
use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
use refineable::{CascadeSlot, Refineable, RefinementCascade};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
pub struct Hoverable<E: Styleable> {
hovered: Rc<Cell<bool>>,
cascade_slot: CascadeSlot,
hovered_style: <E::Style as Refineable>::Refinement,
child: E,
}
pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
Hoverable {
hovered: Rc::new(Cell::new(false)),
cascade_slot: child.style_cascade().reserve(),
hovered_style: Default::default(),
child,
}
}
impl<E: Styleable> Styleable for Hoverable<E> {
type Style = E::Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
self.child.style_cascade()
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.hovered_style
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
type PaintState = E::PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
Ok(self.child.layout(view, cx)?)
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let bounds = layout.bounds + parent_origin;
self.hovered.set(bounds.contains_point(cx.mouse_position()));
let slot = self.cascade_slot;
let style = self.hovered.get().then_some(self.hovered_style.clone());
self.style_cascade().set(slot, style);
let hovered = self.hovered.clone();
cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
cx.bubble_event();
if bounds.contains_point(cx.mouse_position()) != hovered.get() {
cx.repaint();
}
});
self.child
.paint(view, parent_origin, layout, paint_state, cx);
}
}
impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
self.child.interaction_handlers()
}
}
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
self.child.children_mut()
}
}
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}

View file

@ -0,0 +1,110 @@
use crate as gpui2;
use crate::{
style::{Style, StyleHelpers, Styleable},
Element,
};
use futures::FutureExt;
use gpui::geometry::vector::Vector2F;
use gpui::scene;
use gpui2_macros::IntoElement;
use refineable::RefinementCascade;
use util::arc_cow::ArcCow;
use util::ResultExt;
#[derive(IntoElement)]
pub struct Img {
style: RefinementCascade<Style>,
uri: Option<ArcCow<'static, str>>,
}
pub fn img() -> Img {
Img {
style: RefinementCascade::default(),
uri: None,
}
}
impl Img {
pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
self.uri = Some(uri.into());
self
}
}
impl<V: 'static> Element<V> for Img {
type PaintState = ();
fn layout(
&mut self,
_: &mut V,
cx: &mut crate::ViewContext<V>,
) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
where
Self: Sized,
{
let style = self.computed_style();
let layout_id = cx.add_layout_node(style, [])?;
Ok((layout_id, ()))
}
fn paint(
&mut self,
_: &mut V,
parent_origin: Vector2F,
layout: &gpui::Layout,
_: &mut Self::PaintState,
cx: &mut crate::ViewContext<V>,
) where
Self: Sized,
{
let style = self.computed_style();
let bounds = layout.bounds + parent_origin;
style.paint_background(bounds, cx);
if let Some(uri) = &self.uri {
let image_future = cx.image_cache.get(uri.clone());
if let Some(data) = image_future
.clone()
.now_or_never()
.and_then(ResultExt::log_err)
{
let rem_size = cx.rem_size();
cx.scene().push_image(scene::Image {
bounds,
border: gpui::Border {
color: style.border_color.unwrap_or_default().into(),
top: style.border_widths.top.to_pixels(rem_size),
right: style.border_widths.right.to_pixels(rem_size),
bottom: style.border_widths.bottom.to_pixels(rem_size),
left: style.border_widths.left.to_pixels(rem_size),
},
corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
grayscale: false,
data,
})
} else {
cx.spawn(|this, mut cx| async move {
if image_future.await.log_err().is_some() {
this.update(&mut cx, |_, cx| cx.notify()).ok();
}
})
.detach();
}
}
}
}
impl Styleable for Img {
type Style = Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
&mut self.style
}
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
self.style.base()
}
}
impl StyleHelpers for Img {}

View file

@ -0,0 +1,108 @@
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
style::{Style, StyleHelpers, Styleable},
ViewContext,
};
use anyhow::Result;
use gpui::{geometry::vector::Vector2F, platform::MouseButtonEvent, LayoutId};
use refineable::{CascadeSlot, Refineable, RefinementCascade};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
pub struct Pressable<E: Styleable> {
pressed: Rc<Cell<bool>>,
pressed_style: <E::Style as Refineable>::Refinement,
cascade_slot: CascadeSlot,
child: E,
}
pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
Pressable {
pressed: Rc::new(Cell::new(false)),
pressed_style: Default::default(),
cascade_slot: child.style_cascade().reserve(),
child,
}
}
impl<E: Styleable> Styleable for Pressable<E> {
type Style = E::Style;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.pressed_style
}
fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
self.child.style_cascade()
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
type PaintState = E::PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
self.child.layout(view, cx)
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let slot = self.cascade_slot;
let style = self.pressed.get().then_some(self.pressed_style.clone());
self.style_cascade().set(slot, style);
let pressed = self.pressed.clone();
let bounds = layout.bounds + parent_origin;
cx.on_event(layout.order, move |_view, event: &MouseButtonEvent, cx| {
if event.is_down {
if bounds.contains_point(event.position) {
pressed.set(true);
cx.repaint();
}
} else if pressed.get() {
pressed.set(false);
cx.repaint();
}
});
self.child
.paint(view, parent_origin, layout, paint_state, cx);
}
}
impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
self.child.interaction_handlers()
}
}
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
self.child.children_mut()
}
}
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}

View file

@ -0,0 +1,84 @@
use crate::{
self as gpui2, scene,
style::{Style, StyleHelpers, Styleable},
Element, IntoElement, Layout, LayoutId, Rgba,
};
use gpui::geometry::vector::Vector2F;
use refineable::RefinementCascade;
use std::borrow::Cow;
use util::ResultExt;
#[derive(IntoElement)]
pub struct Svg {
path: Option<Cow<'static, str>>,
style: RefinementCascade<Style>,
}
pub fn svg() -> Svg {
Svg {
path: None,
style: RefinementCascade::<Style>::default(),
}
}
impl Svg {
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
self.path = Some(path.into());
self
}
}
impl<V: 'static> Element<V> for Svg {
type PaintState = ();
fn layout(
&mut self,
_: &mut V,
cx: &mut crate::ViewContext<V>,
) -> anyhow::Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
let style = self.computed_style();
Ok((cx.add_layout_node(style, [])?, ()))
}
fn paint(
&mut self,
_: &mut V,
parent_origin: Vector2F,
layout: &Layout,
_: &mut Self::PaintState,
cx: &mut crate::ViewContext<V>,
) where
Self: Sized,
{
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
let icon = scene::Icon {
bounds: layout.bounds + parent_origin,
svg: svg_tree,
path: path.clone(),
color: Rgba::from(fill_color).into(),
};
cx.scene().push_icon(icon);
}
}
}
}
impl Styleable for Svg {
type Style = Style;
fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
&mut self.style
}
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
self.style.base()
}
}
impl StyleHelpers for Svg {}

View file

@ -0,0 +1,109 @@
use crate::{
element::{Element, IntoElement, Layout},
ViewContext,
};
use anyhow::Result;
use gpui::{
geometry::{vector::Vector2F, Size},
text_layout::LineLayout,
LayoutId,
};
use parking_lot::Mutex;
use std::sync::Arc;
use util::arc_cow::ArcCow;
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
type Element = Text;
fn into_element(self) -> Self::Element {
Text { text: self.into() }
}
}
pub struct Text {
text: ArcCow<'static, str>,
}
impl<V: 'static> Element<V> for Text {
type PaintState = Arc<Mutex<Option<TextLayout>>>;
fn layout(
&mut self,
_view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)> {
let fonts = cx.platform().fonts();
let text_style = cx.text_style();
let line_height = cx.font_cache().line_height(text_style.font_size);
let text = self.text.clone();
let paint_state = Arc::new(Mutex::new(None));
let layout_id = cx.add_measured_layout_node(Default::default(), {
let paint_state = paint_state.clone();
move |_params| {
let line_layout = fonts.layout_line(
text.as_ref(),
text_style.font_size,
&[(text.len(), text_style.to_run())],
);
let size = Size {
width: line_layout.width,
height: line_height,
};
paint_state.lock().replace(TextLayout {
line_layout: Arc::new(line_layout),
line_height,
});
size
}
});
Ok((layout_id?, paint_state))
}
fn paint<'a>(
&mut self,
_view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) {
let bounds = layout.bounds + parent_origin;
let line_layout;
let line_height;
{
let paint_state = paint_state.lock();
let paint_state = paint_state
.as_ref()
.expect("measurement has not been performed");
line_layout = paint_state.line_layout.clone();
line_height = paint_state.line_height;
}
let text_style = cx.text_style();
let line =
gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
// TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder.
let visible_bounds = bounds;
line.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx);
}
}
impl<V: 'static> IntoElement<V> for Text {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
pub struct TextLayout {
line_layout: Arc<LineLayout>,
line_height: f32,
}

22
crates/gpui2/src/gpui2.rs Normal file
View file

@ -0,0 +1,22 @@
pub mod adapter;
pub mod color;
pub mod element;
pub mod elements;
pub mod interactive;
pub mod style;
pub mod view;
pub mod view_context;
pub use color::*;
pub use element::{AnyElement, Element, IntoElement, Layout, ParentElement};
pub use geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
};
pub use gpui::*;
pub use gpui2_macros::{Element, *};
pub use interactive::*;
pub use platform::{Platform, WindowBounds, WindowOptions};
pub use util::arc_cow::ArcCow;
pub use view::*;
pub use view_context::ViewContext;

View file

@ -0,0 +1,165 @@
use gpui::{
geometry::rect::RectF,
platform::{MouseButton, MouseButtonEvent},
EventContext,
};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
use crate::ViewContext;
pub trait Interactive<V: 'static> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
fn on_mouse_down(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_down
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_mouse_up(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_up
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_mouse_down_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_down_out
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_mouse_up_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_up_out
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_click(
self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
let pressed = Rc::new(Cell::new(false));
self.on_mouse_down(button, {
let pressed = pressed.clone();
move |_, _, _| {
pressed.set(true);
}
})
.on_mouse_up_out(button, {
let pressed = pressed.clone();
move |_, _, _| {
pressed.set(false);
}
})
.on_mouse_up(button, move |view, event, cx| {
if pressed.get() {
pressed.set(false);
handler(view, event, cx);
}
})
}
}
pub struct InteractionHandlers<V: 'static> {
mouse_down: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
mouse_down_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
mouse_up: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
mouse_up_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
}
impl<V: 'static> InteractionHandlers<V> {
pub fn paint(&self, order: u32, bounds: RectF, cx: &mut ViewContext<V>) {
for handler in self.mouse_down.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if event.is_down && bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
for handler in self.mouse_up.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if !event.is_down && bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
for handler in self.mouse_down_out.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if event.is_down && !bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
for handler in self.mouse_up_out.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if !event.is_down && !bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
}
}
impl<V> Default for InteractionHandlers<V> {
fn default() -> Self {
Self {
mouse_down: Default::default(),
mouse_up: Default::default(),
mouse_down_out: Default::default(),
mouse_up_out: Default::default(),
}
}
}

604
crates/gpui2/src/style.rs Normal file
View file

@ -0,0 +1,604 @@
use crate::{
color::Hsla,
elements::hoverable::{hoverable, Hoverable},
elements::pressable::{pressable, Pressable},
ViewContext,
};
pub use fonts::Style as FontStyle;
pub use fonts::Weight as FontWeight;
pub use gpui::taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
};
use gpui::{
fonts::{self, TextStyleRefinement},
geometry::{
rect::RectF, relative, vector::Vector2F, AbsoluteLength, DefiniteLength, Edges,
EdgesRefinement, Length, Point, PointRefinement, Size, SizeRefinement,
},
scene, taffy, WindowContext,
};
use gpui2_macros::styleable_helpers;
use refineable::{Refineable, RefinementCascade};
use std::sync::Arc;
#[derive(Clone, Refineable, Debug)]
#[refineable(debug)]
pub struct Style {
/// What layout strategy should be used?
pub display: Display,
// Overflow properties
/// How children overflowing their container should affect layout
#[refineable]
pub overflow: Point<Overflow>,
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
pub scrollbar_width: f32,
// Position properties
/// What should the `position` value of this struct use as a base offset?
pub position: Position,
/// How should the position of this element be tweaked relative to the layout defined?
#[refineable]
pub inset: Edges<Length>,
// Size properies
/// Sets the initial size of the item
#[refineable]
pub size: Size<Length>,
/// Controls the minimum size of the item
#[refineable]
pub min_size: Size<Length>,
/// Controls the maximum size of the item
#[refineable]
pub max_size: Size<Length>,
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
pub aspect_ratio: Option<f32>,
// Spacing Properties
/// How large should the margin be on each side?
#[refineable]
pub margin: Edges<Length>,
/// How large should the padding be on each side?
#[refineable]
pub padding: Edges<DefiniteLength>,
/// How large should the border be on each side?
#[refineable]
pub border_widths: Edges<AbsoluteLength>,
// Alignment properties
/// How this node's children aligned in the cross/block axis?
pub align_items: Option<AlignItems>,
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
pub align_self: Option<AlignSelf>,
/// How should content contained within this item be aligned in the cross/block axis
pub align_content: Option<AlignContent>,
/// How should contained within this item be aligned in the main/inline axis
pub justify_content: Option<JustifyContent>,
/// How large should the gaps between items in a flex container be?
#[refineable]
pub gap: Size<DefiniteLength>,
// Flexbox properies
/// Which direction does the main axis flow in?
pub flex_direction: FlexDirection,
/// Should elements wrap, or stay in a single line?
pub flex_wrap: FlexWrap,
/// Sets the initial main axis size of the item
pub flex_basis: Length,
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
pub flex_grow: f32,
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
pub flex_shrink: f32,
/// The fill color of this element
pub fill: Option<Fill>,
/// The border color of this element
pub border_color: Option<Hsla>,
/// The radius of the corners of this element
#[refineable]
pub corner_radii: CornerRadii,
/// The color of text within this element. Cascades to children unless overridden.
pub text_color: Option<Hsla>,
/// The font size in rems.
pub font_size: Option<f32>,
pub font_family: Option<Arc<str>>,
pub font_weight: Option<FontWeight>,
pub font_style: Option<FontStyle>,
}
impl Style {
pub fn text_style(&self, cx: &WindowContext) -> Option<TextStyleRefinement> {
if self.text_color.is_none()
&& self.font_size.is_none()
&& self.font_family.is_none()
&& self.font_weight.is_none()
&& self.font_style.is_none()
{
return None;
}
Some(TextStyleRefinement {
color: self.text_color.map(Into::into),
font_family: self.font_family.clone(),
font_size: self.font_size.map(|size| size * cx.rem_size()),
font_weight: self.font_weight,
font_style: self.font_style,
underline: None,
})
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
taffy::style::Style {
display: self.display,
overflow: self.overflow.clone().into(),
scrollbar_width: self.scrollbar_width,
position: self.position,
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
min_size: self.min_size.to_taffy(rem_size),
max_size: self.max_size.to_taffy(rem_size),
aspect_ratio: self.aspect_ratio,
margin: self.margin.to_taffy(rem_size),
padding: self.padding.to_taffy(rem_size),
border: self.border_widths.to_taffy(rem_size),
align_items: self.align_items,
align_self: self.align_self,
align_content: self.align_content,
justify_content: self.justify_content,
gap: self.gap.to_taffy(rem_size),
flex_direction: self.flex_direction,
flex_wrap: self.flex_wrap,
flex_basis: self.flex_basis.to_taffy(rem_size).into(),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
..Default::default() // Ignore grid properties for now
}
}
/// Paints the background of an element styled with this style.
pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size();
if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
cx.scene().push_quad(gpui::Quad {
bounds,
background: Some(color.into()),
corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
border: Default::default(),
});
}
}
/// Paints the foreground of an element styled with this style.
pub fn paint_foreground<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size();
if let Some(color) = self.border_color {
let border = self.border_widths.to_pixels(rem_size);
if !border.is_empty() {
cx.scene().push_quad(gpui::Quad {
bounds,
background: None,
corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
border: scene::Border {
color: color.into(),
top: border.top,
right: border.right,
bottom: border.bottom,
left: border.left,
},
});
}
}
}
}
impl Default for Style {
fn default() -> Self {
Style {
display: Display::Block,
overflow: Point {
x: Overflow::Visible,
y: Overflow::Visible,
},
scrollbar_width: 0.0,
position: Position::Relative,
inset: Edges::auto(),
margin: Edges::<Length>::zero(),
padding: Edges::<DefiniteLength>::zero(),
border_widths: Edges::<AbsoluteLength>::zero(),
size: Size::auto(),
min_size: Size::auto(),
max_size: Size::auto(),
aspect_ratio: None,
gap: Size::zero(),
// Aligment
align_items: None,
align_self: None,
align_content: None,
justify_content: None,
// Flexbox
flex_direction: FlexDirection::Row,
flex_wrap: FlexWrap::NoWrap,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Length::Auto,
fill: None,
border_color: None,
corner_radii: CornerRadii::default(),
text_color: None,
font_size: Some(1.),
font_family: None,
font_weight: None,
font_style: None,
}
}
}
#[derive(Clone, Debug)]
pub enum Fill {
Color(Hsla),
}
impl Fill {
pub fn color(&self) -> Option<Hsla> {
match self {
Fill::Color(color) => Some(*color),
}
}
}
impl Default for Fill {
fn default() -> Self {
Self::Color(Hsla::default())
}
}
impl From<Hsla> for Fill {
fn from(color: Hsla) -> Self {
Self::Color(color)
}
}
#[derive(Clone, Refineable, Default, Debug)]
#[refineable(debug)]
pub struct CornerRadii {
top_left: AbsoluteLength,
top_right: AbsoluteLength,
bottom_left: AbsoluteLength,
bottom_right: AbsoluteLength,
}
impl CornerRadii {
pub fn to_gpui(&self, box_size: Vector2F, rem_size: f32) -> gpui::scene::CornerRadii {
let max_radius = box_size.x().min(box_size.y()) / 2.;
gpui::scene::CornerRadii {
top_left: self.top_left.to_pixels(rem_size).min(max_radius),
top_right: self.top_right.to_pixels(rem_size).min(max_radius),
bottom_left: self.bottom_left.to_pixels(rem_size).min(max_radius),
bottom_right: self.bottom_right.to_pixels(rem_size).min(max_radius),
}
}
}
pub trait Styleable {
type Style: Refineable + Default;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
fn computed_style(&mut self) -> Self::Style {
Self::Style::from_refinement(&self.style_cascade().merged())
}
fn hover(self) -> Hoverable<Self>
where
Self: Sized,
{
hoverable(self)
}
fn active(self) -> Pressable<Self>
where
Self: Sized,
{
pressable(self)
}
}
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
//
// Example:
// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
// fn p_2(mut self) -> Self where Self: Sized;
pub trait StyleHelpers: Styleable<Style = Style> {
styleable_helpers!();
fn h(mut self, height: Length) -> Self
where
Self: Sized,
{
self.declared_style().size.height = Some(height);
self
}
/// size_{n}: Sets width & height to {n}
///
/// Example:
/// size_1: Sets width & height to 1
fn size(mut self, size: Length) -> Self
where
Self: Sized,
{
self.declared_style().size.height = Some(size);
self.declared_style().size.width = Some(size);
self
}
fn full(mut self) -> Self
where
Self: Sized,
{
self.declared_style().size.width = Some(relative(1.));
self.declared_style().size.height = Some(relative(1.));
self
}
fn relative(mut self) -> Self
where
Self: Sized,
{
self.declared_style().position = Some(Position::Relative);
self
}
fn absolute(mut self) -> Self
where
Self: Sized,
{
self.declared_style().position = Some(Position::Absolute);
self
}
fn block(mut self) -> Self
where
Self: Sized,
{
self.declared_style().display = Some(Display::Block);
self
}
fn flex(mut self) -> Self
where
Self: Sized,
{
self.declared_style().display = Some(Display::Flex);
self
}
fn flex_col(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_direction = Some(FlexDirection::Column);
self
}
fn flex_row(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_direction = Some(FlexDirection::Row);
self
}
fn flex_1(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(relative(0.));
self
}
fn flex_auto(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(Length::Auto);
self
}
fn flex_initial(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(0.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(Length::Auto);
self
}
fn flex_none(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(0.);
self.declared_style().flex_shrink = Some(0.);
self
}
fn grow(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self
}
fn items_start(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::FlexStart);
self
}
fn items_end(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::FlexEnd);
self
}
fn items_center(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::Center);
self
}
fn justify_between(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
self
}
fn justify_center(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::Center);
self
}
fn justify_start(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::Start);
self
}
fn justify_end(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::End);
self
}
fn justify_around(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
self
}
fn fill<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
Self: Sized,
{
self.declared_style().fill = Some(fill.into());
self
}
fn border_color<C>(mut self, border_color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.declared_style().border_color = Some(border_color.into());
self
}
fn text_color<C>(mut self, color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.declared_style().text_color = Some(color.into());
self
}
fn text_xs(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(0.75);
self
}
fn text_sm(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(0.875);
self
}
fn text_base(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.0);
self
}
fn text_lg(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.125);
self
}
fn text_xl(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.25);
self
}
fn text_2xl(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.5);
self
}
fn text_3xl(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.875);
self
}
fn font(mut self, family_name: impl Into<Arc<str>>) -> Self
where
Self: Sized,
{
self.declared_style().font_family = Some(family_name.into());
self
}
}

View file

@ -0,0 +1,79 @@
use std::{any::TypeId, rc::Rc};
use crate::{element::LayoutId, style::Style};
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams};
pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext};
#[derive(Deref, DerefMut)]
pub struct ViewContext<'a, 'b, 'c, V> {
#[deref]
#[deref_mut]
pub(crate) legacy_cx: &'c mut LegacyViewContext<'a, 'b, V>,
}
impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> {
pub fn new(legacy_cx: &'c mut LegacyViewContext<'a, 'b, V>) -> Self {
Self { legacy_cx }
}
pub fn add_layout_node(
&mut self,
style: Style,
children: impl IntoIterator<Item = NodeId>,
) -> Result<LayoutId> {
let rem_size = self.rem_size();
let style = style.to_taffy(rem_size);
let id = self
.legacy_cx
.layout_engine()
.ok_or_else(|| anyhow!("no layout engine"))?
.add_node(style, children)?;
Ok(id)
}
pub fn add_measured_layout_node<F>(&mut self, style: Style, measure: F) -> Result<LayoutId>
where
F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
{
let rem_size = self.rem_size();
let layout_id = self
.layout_engine()
.ok_or_else(|| anyhow!("no layout engine"))?
.add_measured_node(style.to_taffy(rem_size), measure)?;
Ok(layout_id)
}
pub fn on_event<E: 'static>(
&mut self,
order: u32,
handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
) {
let view = self.weak_handle();
self.scene().event_handlers.push(EventHandler {
order,
handler: Rc::new(move |event, window_cx| {
if let Some(view) = view.upgrade(window_cx) {
view.update(window_cx, |view, view_cx| {
let mut event_cx = EventContext::new(view_cx);
handler(view, event.downcast_ref().unwrap(), &mut event_cx);
event_cx.bubble
})
} else {
true
}
}),
event_type: TypeId::of::<E>(),
})
}
pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
self.layout_engine()
.ok_or_else(|| anyhow!("no layout engine present"))?
.computed_layout(layout_id)
}
}

View file

@ -1,11 +1,11 @@
[package]
name = "playground_macros"
name = "gpui2_macros"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/playground_macros.rs"
path = "src/gpui2_macros.rs"
proc-macro = true
[dependencies]

View file

@ -59,28 +59,30 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
);
let gen = quote! {
impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics
impl #impl_generics gpui2::element::Element<#view_type_name> for #type_name #type_generics
#where_clause
{
type Layout = Option<playground::element::AnyElement<#view_type_name #lifetimes>>;
type PaintState = gpui2::element::AnyElement<#view_type_name #lifetimes>;
fn layout(
&mut self,
view: &mut V,
cx: &mut playground::element::LayoutContext<V>,
) -> anyhow::Result<playground::element::Layout<V, Self::Layout>> {
let mut element = self.render(view, cx).into_any();
let layout_id = element.layout(view, cx)?;
Ok(playground::element::Layout::new(layout_id, Some(element)))
cx: &mut gpui2::ViewContext<V>,
) -> anyhow::Result<(gpui2::element::LayoutId, Self::PaintState)> {
let mut rendered_element = self.render(view, cx).into_element().into_any();
let layout_id = rendered_element.layout(view, cx)?;
Ok((layout_id, rendered_element))
}
fn paint(
&mut self,
view: &mut V,
layout: &mut playground::element::Layout<V, Self::Layout>,
cx: &mut playground::element::PaintContext<V>,
parent_origin: gpui2::Vector2F,
_: &gpui2::element::Layout,
rendered_element: &mut Self::PaintState,
cx: &mut gpui2::ViewContext<V>,
) {
layout.paint(view, cx);
rendered_element.paint(view, parent_origin, cx);
}
}

View file

@ -56,7 +56,7 @@ pub fn impl_into_element(
where_clause: &Option<&WhereClause>,
) -> proc_macro2::TokenStream {
quote! {
impl #impl_generics playground::element::IntoElement<#view_type_name> for #type_name #type_generics
impl #impl_generics gpui2::element::IntoElement<#view_type_name> for #type_name #type_generics
#where_clause
{
type Element = Self;

View file

@ -3,7 +3,6 @@ use proc_macro::TokenStream;
mod derive_element;
mod derive_into_element;
mod styleable_helpers;
mod tailwind_lengths;
#[proc_macro]
pub fn styleable_helpers(args: TokenStream) -> TokenStream {
@ -19,8 +18,3 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
pub fn derive_into_element(input: TokenStream) -> TokenStream {
derive_into_element::derive_into_element(input)
}
#[proc_macro_attribute]
pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
tailwind_lengths::tailwind_lengths(attr, item)
}

View file

@ -0,0 +1,327 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
struct StyleableMacroInput;
impl Parse for StyleableMacroInput {
fn parse(_input: ParseStream) -> Result<Self> {
Ok(StyleableMacroInput)
}
}
pub fn styleable_helpers(input: TokenStream) -> TokenStream {
let _ = parse_macro_input!(input as StyleableMacroInput);
let methods = generate_methods();
let output = quote! {
#(#methods)*
};
output.into()
}
fn generate_methods() -> Vec<TokenStream2> {
let mut methods = Vec::new();
for (prefix, auto_allowed, fields) in box_prefixes() {
for (suffix, length_tokens) in box_suffixes() {
if auto_allowed || suffix != "auto" {
let method = generate_method(prefix, suffix, &fields, length_tokens);
methods.push(method);
}
}
}
for (prefix, fields) in corner_prefixes() {
for (suffix, radius_tokens) in corner_suffixes() {
let method = generate_method(prefix, suffix, &fields, radius_tokens);
methods.push(method);
}
}
for (prefix, fields) in border_prefixes() {
for (suffix, width_tokens) in border_suffixes() {
let method = generate_method(prefix, suffix, &fields, width_tokens);
methods.push(method);
}
}
methods
}
fn generate_method(
prefix: &'static str,
suffix: &'static str,
fields: &Vec<TokenStream2>,
length_tokens: TokenStream2,
) -> TokenStream2 {
let method_name = if suffix.is_empty() {
format_ident!("{}", prefix)
} else {
format_ident!("{}_{}", prefix, suffix)
};
let field_assignments = fields
.iter()
.map(|field_tokens| {
quote! {
style.#field_tokens = Some(gpui::geometry::#length_tokens);
}
})
.collect::<Vec<_>>();
let method = quote! {
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
let mut style = self.declared_style();
#(#field_assignments)*
self
}
};
method
}
fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
vec![
("w", true, vec![quote! { size.width }]),
("h", true, vec![quote! { size.height }]),
(
"size",
true,
vec![quote! {size.width}, quote! {size.height}],
),
("min_w", false, vec![quote! { min_size.width }]),
("min_h", false, vec![quote! { min_size.height }]),
("max_w", false, vec![quote! { max_size.width }]),
("max_h", false, vec![quote! { max_size.height }]),
(
"m",
true,
vec![
quote! { margin.top },
quote! { margin.bottom },
quote! { margin.left },
quote! { margin.right },
],
),
("mt", true, vec![quote! { margin.top }]),
("mb", true, vec![quote! { margin.bottom }]),
(
"my",
true,
vec![quote! { margin.top }, quote! { margin.bottom }],
),
(
"mx",
true,
vec![quote! { margin.left }, quote! { margin.right }],
),
("ml", true, vec![quote! { margin.left }]),
("mr", true, vec![quote! { margin.right }]),
(
"p",
false,
vec![
quote! { padding.top },
quote! { padding.bottom },
quote! { padding.left },
quote! { padding.right },
],
),
("pt", false, vec![quote! { padding.top }]),
("pb", false, vec![quote! { padding.bottom }]),
(
"px",
false,
vec![quote! { padding.left }, quote! { padding.right }],
),
(
"py",
false,
vec![quote! { padding.top }, quote! { padding.bottom }],
),
("pl", false, vec![quote! { padding.left }]),
("pr", false, vec![quote! { padding.right }]),
("top", true, vec![quote! { inset.top }]),
("bottom", true, vec![quote! { inset.bottom }]),
("left", true, vec![quote! { inset.left }]),
("right", true, vec![quote! { inset.right }]),
(
"gap",
false,
vec![quote! { gap.width }, quote! { gap.height }],
),
("gap_x", false, vec![quote! { gap.width }]),
("gap_y", false, vec![quote! { gap.height }]),
]
}
fn box_suffixes() -> Vec<(&'static str, TokenStream2)> {
vec![
("0", quote! { pixels(0.) }),
("0p5", quote! { rems(0.125) }),
("1", quote! { rems(0.25) }),
("1p5", quote! { rems(0.375) }),
("2", quote! { rems(0.5) }),
("2p5", quote! { rems(0.625) }),
("3", quote! { rems(0.75) }),
("3p5", quote! { rems(0.875) }),
("4", quote! { rems(1.) }),
("5", quote! { rems(1.25) }),
("6", quote! { rems(1.5) }),
("7", quote! { rems(1.75) }),
("8", quote! { rems(2.0) }),
("9", quote! { rems(2.25) }),
("10", quote! { rems(2.5) }),
("11", quote! { rems(2.75) }),
("12", quote! { rems(3.) }),
("16", quote! { rems(4.) }),
("20", quote! { rems(5.) }),
("24", quote! { rems(6.) }),
("32", quote! { rems(8.) }),
("40", quote! { rems(10.) }),
("48", quote! { rems(12.) }),
("56", quote! { rems(14.) }),
("64", quote! { rems(16.) }),
("72", quote! { rems(18.) }),
("80", quote! { rems(20.) }),
("96", quote! { rems(24.) }),
("auto", quote! { auto() }),
("px", quote! { pixels(1.) }),
("full", quote! { relative(1.) }),
("1_2", quote! { relative(0.5) }),
("1_3", quote! { relative(1./3.) }),
("2_3", quote! { relative(2./3.) }),
("1_4", quote! { relative(0.25) }),
("2_4", quote! { relative(0.5) }),
("3_4", quote! { relative(0.75) }),
("1_5", quote! { relative(0.2) }),
("2_5", quote! { relative(0.4) }),
("3_5", quote! { relative(0.6) }),
("4_5", quote! { relative(0.8) }),
("1_6", quote! { relative(1./6.) }),
("5_6", quote! { relative(5./6.) }),
("1_12", quote! { relative(1./12.) }),
// ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
// ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
// ("screen", quote! { DefiniteLength::Vh(100.0) }),
]
}
fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
vec![
(
"rounded",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
quote! { corner_radii.bottom_left },
],
),
(
"rounded_t",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
],
),
(
"rounded_b",
vec![
quote! { corner_radii.bottom_left },
quote! { corner_radii.bottom_right },
],
),
(
"rounded_r",
vec![
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
],
),
(
"rounded_l",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.bottom_left },
],
),
("rounded_tl", vec![quote! { corner_radii.top_left }]),
("rounded_tr", vec![quote! { corner_radii.top_right }]),
("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
("rounded_br", vec![quote! { corner_radii.bottom_right }]),
]
}
fn corner_suffixes() -> Vec<(&'static str, TokenStream2)> {
vec![
("none", quote! { pixels(0.) }),
("sm", quote! { rems(0.125) }),
("md", quote! { rems(0.25) }),
("lg", quote! { rems(0.5) }),
("xl", quote! { rems(0.75) }),
("2xl", quote! { rems(1.) }),
("3xl", quote! { rems(1.5) }),
("full", quote! { pixels(9999.) }),
]
}
fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
vec![
(
"border",
vec![
quote! { border_widths.top },
quote! { border_widths.right },
quote! { border_widths.bottom },
quote! { border_widths.left },
],
),
("border_t", vec![quote! { border_widths.top }]),
("border_b", vec![quote! { border_widths.bottom }]),
("border_r", vec![quote! { border_widths.right }]),
("border_l", vec![quote! { border_widths.left }]),
(
"border_x",
vec![
quote! { border_widths.left },
quote! { border_widths.right },
],
),
(
"border_y",
vec![
quote! { border_widths.top },
quote! { border_widths.bottom },
],
),
]
}
fn border_suffixes() -> Vec<(&'static str, TokenStream2)> {
vec![
("", quote! { pixels(1.) }),
("0", quote! { pixels(0.) }),
("1", quote! { pixels(1.) }),
("2", quote! { pixels(2.) }),
("3", quote! { pixels(3.) }),
("4", quote! { pixels(4.) }),
("5", quote! { pixels(5.) }),
("6", quote! { pixels(6.) }),
("7", quote! { pixels(7.) }),
("8", quote! { pixels(8.) }),
("9", quote! { pixels(9.) }),
("10", quote! { pixels(10.) }),
("11", quote! { pixels(11.) }),
("12", quote! { pixels(12.) }),
("16", quote! { pixels(16.) }),
("20", quote! { pixels(20.) }),
("24", quote! { pixels(24.) }),
("32", quote! { pixels(32.) }),
]
}

View file

@ -329,7 +329,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
cx: &mut gpui::LayoutContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
let mut element = self.render(view, cx).into_any();
let size = element.layout(constraint, view, cx);
@ -338,14 +338,13 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: gpui::geometry::rect::RectF,
visible_bounds: gpui::geometry::rect::RectF,
element: &mut gpui::elements::AnyElement<V>,
view: &mut V,
cx: &mut gpui::PaintContext<V>,
cx: &mut gpui::ViewContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
element.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View file

@ -12,9 +12,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
ident,
data,
generics,
attrs,
..
} = parse_macro_input!(input);
let impl_debug_on_refinement = attrs
.iter()
.any(|attr| attr.path.is_ident("refineable") && attr.tokens.to_string().contains("debug"));
let refinement_ident = format_ident!("{}Refinement", ident);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
@ -120,6 +125,41 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
})
.collect();
let debug_impl = if impl_debug_on_refinement {
let refinement_field_debugs: Vec<TokenStream2> = fields
.iter()
.map(|field| {
let name = &field.ident;
quote! {
if self.#name.is_some() {
debug_struct.field(stringify!(#name), &self.#name);
} else {
all_some = false;
}
}
})
.collect();
quote! {
impl #impl_generics std::fmt::Debug for #refinement_ident #ty_generics
#where_clause
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug_struct = f.debug_struct(stringify!(#refinement_ident));
let mut all_some = true;
#( #refinement_field_debugs )*
if all_some {
debug_struct.finish()
} else {
debug_struct.finish_non_exhaustive()
}
}
}
}
} else {
quote! {}
};
let gen = quote! {
#[derive(Default, Clone)]
pub struct #refinement_ident #impl_generics {
@ -145,8 +185,22 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
#( #refinement_field_assignments )*
}
}
};
impl #impl_generics #refinement_ident #ty_generics
#where_clause
{
pub fn is_some(&self) -> bool {
#(
if self.#field_names.is_some() {
return true;
}
)*
false
}
}
#debug_impl
};
gen.into()
}

View file

@ -1,7 +1,7 @@
pub use derive_refineable::Refineable;
pub trait Refineable {
type Refinement: Default;
pub trait Refineable: Clone {
type Refinement: Refineable<Refinement = Self::Refinement> + Default;
fn refine(&mut self, refinement: &Self::Refinement);
fn refined(mut self, refinement: &Self::Refinement) -> Self
@ -11,4 +11,46 @@ pub trait Refineable {
self.refine(refinement);
self
}
fn from_refinement(refinement: &Self::Refinement) -> Self
where
Self: Default + Sized,
{
Self::default().refined(refinement)
}
}
pub struct RefinementCascade<S: Refineable>(Vec<Option<S::Refinement>>);
impl<S: Refineable + Default> Default for RefinementCascade<S> {
fn default() -> Self {
Self(vec![Some(Default::default())])
}
}
#[derive(Copy, Clone)]
pub struct CascadeSlot(usize);
impl<S: Refineable + Default> RefinementCascade<S> {
pub fn reserve(&mut self) -> CascadeSlot {
self.0.push(None);
return CascadeSlot(self.0.len() - 1);
}
pub fn base(&mut self) -> &mut S::Refinement {
self.0[0].as_mut().unwrap()
}
pub fn set(&mut self, slot: CascadeSlot, refinement: Option<S::Refinement>) {
self.0[slot.0] = refinement
}
pub fn merged(&self) -> S::Refinement {
let mut merged = self.0[0].clone().unwrap();
for refinement in self.0.iter().skip(1) {
if let Some(refinement) = refinement {
merged.refine(refinement);
}
}
merged
}
}

View file

@ -1686,7 +1686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "playground"
name = "storybook"
version = "0.1.0"
dependencies = [
"gpui",

View file

@ -0,0 +1,23 @@
[package]
name = "storybook"
version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "storybook"
path = "src/storybook.rs"
[dependencies]
gpui2 = { path = "../gpui2" }
anyhow.workspace = true
log.workspace = true
rust-embed.workspace = true
serde.workspace = true
settings = { path = "../settings" }
simplelog = "0.9"
theme = { path = "../theme" }
util = { path = "../util" }
[dev-dependencies]
gpui2 = { path = "../gpui2", features = ["test-support"] }

View file

@ -0,0 +1,177 @@
use crate::theme::{theme, Theme};
use gpui2::{
elements::{div, div::ScrollState, img, svg},
style::{StyleHelpers, Styleable},
ArcCow, Element, IntoElement, ParentElement, ViewContext,
};
use std::marker::PhantomData;
#[derive(Element)]
pub struct CollabPanelElement<V: 'static> {
view_type: PhantomData<V>,
scroll_state: ScrollState,
}
// When I improve child view rendering, I'd like to have V implement a trait that
// provides the scroll state, among other things.
pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
CollabPanelElement {
view_type: PhantomData,
scroll_state,
}
}
impl<V: 'static> CollabPanelElement<V> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
// Panel
div()
.w_64()
.h_full()
.flex()
.flex_col()
.font("Zed Sans Extended")
.text_color(theme.middle.base.default.foreground)
.border_color(theme.middle.base.default.border)
.border()
.fill(theme.middle.base.default.background)
.child(
div()
.w_full()
.flex()
.flex_col()
.overflow_y_scroll(self.scroll_state.clone())
// List Container
.child(
div()
.fill(theme.lowest.base.default.background)
.pb_1()
.border_color(theme.lowest.base.default.border)
.border_b()
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
// .group()
// List Section Header
.child(self.list_section_header("#CRDB", true, theme))
// List Item Large
.child(self.list_item(
"http://github.com/maxbrunsfeld.png?s=50",
"maxbrunsfeld",
theme,
)),
)
.child(
div()
.py_2()
.flex()
.flex_col()
.child(self.list_section_header("CHANNELS", true, theme)),
)
.child(
div()
.py_2()
.flex()
.flex_col()
.child(self.list_section_header("CONTACTS", true, theme))
.children(
std::iter::repeat_with(|| {
vec![
self.list_item(
"http://github.com/as-cii.png?s=50",
"as-cii",
theme,
),
self.list_item(
"http://github.com/nathansobo.png?s=50",
"nathansobo",
theme,
),
self.list_item(
"http://github.com/maxbrunsfeld.png?s=50",
"maxbrunsfeld",
theme,
),
]
})
.take(10)
.flatten(),
),
),
)
.child(
div()
.h_7()
.px_2()
.border_t()
.border_color(theme.middle.variant.default.border)
.flex()
.items_center()
.child(
div()
.text_sm()
.text_color(theme.middle.variant.default.foreground)
.child("Find..."),
),
)
}
fn list_section_header(
&self,
label: impl Into<ArcCow<'static, str>>,
expanded: bool,
theme: &Theme,
) -> impl Element<V> {
div()
.h_7()
.px_2()
.flex()
.justify_between()
.items_center()
.child(div().flex().gap_1().text_sm().child(label))
.child(
div().flex().h_full().gap_1().items_center().child(
svg()
.path(if expanded {
"icons/radix/caret-down.svg"
} else {
"icons/radix/caret-up.svg"
})
.w_3p5()
.h_3p5()
.fill(theme.middle.variant.default.foreground),
),
)
}
fn list_item(
&self,
avatar_uri: impl Into<ArcCow<'static, str>>,
label: impl Into<ArcCow<'static, str>>,
theme: &Theme,
) -> impl Element<V> {
div()
.h_7()
.px_2()
.flex()
.items_center()
.hover()
.fill(theme.lowest.variant.hovered.background)
.active()
.fill(theme.lowest.variant.pressed.background)
.child(
div()
.flex()
.items_center()
.gap_1()
.text_sm()
.child(
img()
.uri(avatar_uri)
.size_3p5()
.rounded_full()
.fill(theme.middle.positive.default.foreground),
)
.child(label),
)
}
}

View file

@ -1,16 +1,11 @@
use crate::{
div::div,
element::{Element, ParentElement},
style::StyleHelpers,
text::ArcCow,
themes::rose_pine,
use gpui2::{
elements::div, interactive::Interactive, platform::MouseButton, style::StyleHelpers, ArcCow,
Element, EventContext, IntoElement, ParentElement, ViewContext,
};
use gpui::ViewContext;
use playground_macros::Element;
use std::{marker::PhantomData, rc::Rc};
struct ButtonHandlers<V, D> {
click: Option<Rc<dyn Fn(&mut V, &D, &mut ViewContext<V>)>>,
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
}
impl<V, D> Default for ButtonHandlers<V, D> {
@ -19,7 +14,6 @@ impl<V, D> Default for ButtonHandlers<V, D> {
}
}
use crate as playground;
#[derive(Element)]
pub struct Button<V: 'static, D: 'static> {
handlers: ButtonHandlers<V, D>,
@ -53,7 +47,7 @@ impl<V: 'static> Button<V, ()> {
}
}
// Impl block for *any* button.
// Impl block for button regardless of its data type.
impl<V: 'static, D: 'static> Button<V, D> {
pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
self.label = Some(label.into());
@ -65,12 +59,13 @@ impl<V: 'static, D: 'static> Button<V, D> {
self
}
// pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
// let data = self.data.clone();
// Self::click(self, MouseButton::Left, move |view, _, cx| {
// handler(view, data.as_ref(), cx);
// })
// }
pub fn on_click(
mut self,
handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers.click = Some(Rc::new(handler));
self
}
}
pub fn button<V>() -> Button<V, ()> {
@ -78,23 +73,25 @@ pub fn button<V>() -> Button<V, ()> {
}
impl<V: 'static, D: 'static> Button<V, D> {
fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
// TODO: Drive theme from the context
fn render(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> impl IntoElement<V> + Interactive<V> {
// let colors = &cx.theme::<Theme>().colors;
let button = div()
.fill(rose_pine::dawn().error(0.5))
// .fill(colors.error(0.5))
.h_4()
.children(self.label.clone());
button
// TODO: Event handling
// if let Some(handler) = self.handlers.click.clone() {
// let data = self.data.clone();
// // button.mouse_down(MouseButton::Left, move |view, event, cx| {
// // handler(view, data.as_ref(), cx)
// // })
// } else {
// button
// }
if let Some(handler) = self.handlers.click.clone() {
let data = self.data.clone();
button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
handler(view, data.as_ref(), cx)
})
} else {
button
}
}
}

View file

@ -0,0 +1,22 @@
use crate::theme::{Theme, Themed};
use gpui2::Element;
use std::marker::PhantomData;
pub trait ElementExt<V: 'static>: Element<V> {
fn themed(self, theme: Theme) -> Themed<V, Self>
where
Self: Sized;
}
impl<V: 'static, E: Element<V>> ElementExt<V> for E {
fn themed(self, theme: Theme) -> Themed<V, Self>
where
Self: Sized,
{
Themed {
child: self,
theme,
view_type: PhantomData,
}
}
}

View file

@ -0,0 +1,109 @@
#![allow(dead_code, unused_variables)]
use crate::theme::Theme;
use ::theme as legacy_theme;
use element_ext::ElementExt;
use gpui2::{serde_json, vec2f, view, Element, RectF, ViewContext, WindowBounds};
use legacy_theme::ThemeSettings;
use log::LevelFilter;
use settings::{default_settings, SettingsStore};
use simplelog::SimpleLogger;
mod collab_panel;
mod components;
mod element_ext;
mod theme;
mod workspace;
gpui2::actions! {
storybook,
[ToggleInspector]
}
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
gpui2::App::new(Assets).unwrap().run(|cx| {
let mut store = SettingsStore::default();
store
.set_default_settings(default_settings().as_ref(), cx)
.unwrap();
cx.set_global(store);
legacy_theme::init(Assets, cx);
// load_embedded_fonts(cx.platform().as_ref());
cx.add_window(
gpui2::WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1400., 900.))),
center: true,
..Default::default()
},
|cx| {
view(|cx| {
cx.enable_inspector();
storybook(&mut ViewContext::new(cx))
})
},
);
cx.platform().activate(true);
});
}
fn storybook<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<V> {
workspace().themed(current_theme(cx))
}
// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct.
fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
settings::get::<ThemeSettings>(cx)
.theme
.deserialized_base_theme
.lock()
.get_or_insert_with(|| {
let theme: Theme =
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
.unwrap();
Box::new(theme)
})
.downcast_ref::<Theme>()
.unwrap()
.clone()
}
use anyhow::{anyhow, Result};
use gpui2::AssetSource;
use rust_embed::RustEmbed;
use workspace::workspace;
#[derive(RustEmbed)]
#[folder = "../../assets"]
#[include = "themes/**/*"]
#[include = "fonts/**/*"]
#[include = "icons/**/*"]
#[exclude = "*.DS_Store"]
pub struct Assets;
impl AssetSource for Assets {
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
Self::get(path)
.map(|f| f.data)
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
}
fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
Self::iter().filter(|p| p.starts_with(path)).collect()
}
}
// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
// let font_paths = Assets.list("fonts");
// let mut embedded_fonts = Vec::new();
// for font_path in &font_paths {
// if font_path.ends_with(".ttf") {
// let font_path = &*font_path;
// let font_bytes = Assets.load(font_path).unwrap().to_vec();
// embedded_fonts.push(Arc::from(font_bytes));
// }
// }
// platform.fonts().add_fonts(&embedded_fonts).unwrap();
// }

View file

@ -0,0 +1,192 @@
use gpui2::{
color::Hsla, element::Element, serde_json, AppContext, IntoElement, Vector2F, ViewContext,
WindowContext,
};
use serde::{de::Visitor, Deserialize, Deserializer};
use std::{collections::HashMap, fmt, marker::PhantomData};
use theme::ThemeSettings;
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Theme {
pub name: String,
pub is_light: bool,
pub lowest: Layer,
pub middle: Layer,
pub highest: Layer,
pub popover_shadow: Shadow,
pub modal_shadow: Shadow,
#[serde(deserialize_with = "deserialize_player_colors")]
pub players: Vec<PlayerColors>,
#[serde(deserialize_with = "deserialize_syntax_colors")]
pub syntax: HashMap<String, Hsla>,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Layer {
pub base: StyleSet,
pub variant: StyleSet,
pub on: StyleSet,
pub accent: StyleSet,
pub positive: StyleSet,
pub warning: StyleSet,
pub negative: StyleSet,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct StyleSet {
#[serde(rename = "default")]
pub default: ContainerColors,
pub hovered: ContainerColors,
pub pressed: ContainerColors,
pub active: ContainerColors,
pub disabled: ContainerColors,
pub inverted: ContainerColors,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct ContainerColors {
pub background: Hsla,
pub foreground: Hsla,
pub border: Hsla,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct PlayerColors {
pub selection: Hsla,
pub cursor: Hsla,
}
#[derive(Deserialize, Clone, Default, Debug)]
pub struct Shadow {
pub blur: u8,
pub color: Hsla,
pub offset: Vec<u8>,
}
fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
where
D: Deserializer<'de>,
{
struct PlayerArrayVisitor;
impl<'de> Visitor<'de> for PlayerArrayVisitor {
type Value = Vec<PlayerColors>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an object with integer keys")
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
let mut players = Vec::with_capacity(8);
while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
if key < 8 {
players.push(value);
} else {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(key as u64),
&"a key in range 0..7",
));
}
}
Ok(players)
}
}
deserializer.deserialize_map(PlayerArrayVisitor)
}
fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct ColorWrapper {
color: Hsla,
}
struct SyntaxVisitor;
impl<'de> Visitor<'de> for SyntaxVisitor {
type Value = HashMap<String, Hsla>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map with keys and objects with a single color field as values")
}
fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut result = HashMap::new();
while let Some(key) = map.next_key()? {
let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
result.insert(key, wrapper.color);
}
Ok(result)
}
}
deserializer.deserialize_map(SyntaxVisitor)
}
#[derive(IntoElement)]
pub struct Themed<V: 'static, E: Element<V>> {
pub(crate) theme: Theme,
pub(crate) child: E,
pub(crate) view_type: PhantomData<V>,
}
impl<V: 'static, E: Element<V>> Element<V> for Themed<V, E> {
type PaintState = E::PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> anyhow::Result<(gpui2::LayoutId, Self::PaintState)>
where
Self: Sized,
{
cx.push_theme(self.theme.clone());
let result = self.child.layout(view, cx);
cx.pop_theme();
result
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &gpui2::Layout,
state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
cx.push_theme(self.theme.clone());
self.child.paint(view, parent_origin, layout, state, cx);
cx.pop_theme();
}
}
fn preferred_theme<V: 'static>(cx: &AppContext) -> Theme {
settings::get::<ThemeSettings>(cx)
.theme
.deserialized_base_theme
.lock()
.get_or_insert_with(|| {
let theme: Theme =
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
.unwrap();
Box::new(theme)
})
.downcast_ref::<Theme>()
.unwrap()
.clone()
}
pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
cx.theme::<Theme>()
}

View file

@ -0,0 +1,435 @@
use crate::{collab_panel::collab_panel, theme::theme};
use gpui2::{
elements::{div, div::ScrollState, img, svg},
style::{StyleHelpers, Styleable},
Element, IntoElement, ParentElement, ViewContext,
};
#[derive(Element, Default)]
struct WorkspaceElement {
left_scroll_state: ScrollState,
right_scroll_state: ScrollState,
}
pub fn workspace<V: 'static>() -> impl Element<V> {
WorkspaceElement::default()
}
impl WorkspaceElement {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.size_full()
.flex()
.flex_col()
.font("Zed Sans Extended")
.gap_0()
.justify_start()
.items_start()
.text_color(theme.lowest.base.default.foreground)
.fill(theme.middle.base.default.background)
.child(titlebar())
.child(
div()
.flex_1()
.w_full()
.flex()
.flex_row()
.overflow_hidden()
.child(collab_panel(self.left_scroll_state.clone()))
.child(div().h_full().flex_1())
.child(collab_panel(self.right_scroll_state.clone())),
)
.child(statusbar())
}
}
#[derive(Element)]
struct TitleBar;
pub fn titlebar<V: 'static>() -> impl Element<V> {
TitleBar
}
impl TitleBar {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.justify_between()
.w_full()
.h_8()
.fill(theme.lowest.base.default.background)
.child(self.left_group(cx))
.child(self.right_group(cx))
}
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_4()
.px_2()
// === Traffic Lights === //
.child(
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.w_3()
.h_3()
.rounded_full()
.fill(theme.lowest.positive.default.foreground),
)
.child(
div()
.w_3()
.h_3()
.rounded_full()
.fill(theme.lowest.warning.default.foreground),
)
.child(
div()
.w_3()
.h_3()
.rounded_full()
.fill(theme.lowest.negative.default.foreground),
),
)
// === Project Info === //
.child(
div()
.flex()
.items_center()
.gap_1()
.child(
div()
.h_full()
.flex()
.items_center()
.justify_center()
.px_2()
.rounded_md()
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(div().text_sm().child("project")),
)
.child(
div()
.h_full()
.flex()
.items_center()
.justify_center()
.px_2()
.rounded_md()
.text_color(theme.lowest.variant.default.foreground)
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(div().text_sm().child("branch")),
),
)
}
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_3()
.px_2()
// === Actions === //
.child(
div().child(
div().flex().items_center().gap_1().child(
div().size_4().flex().items_center().justify_center().child(
svg()
.path("icons/exit.svg")
.size_4()
.fill(theme.lowest.base.default.foreground),
),
),
),
)
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
// === Comms === //
.child(
div().child(
div()
.flex()
.items_center()
.gap_px()
.child(
div()
.px_2()
.py_1()
.rounded_md()
.h_full()
.flex()
.items_center()
.justify_center()
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/microphone.svg")
.size_3p5()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.px_2()
.py_1()
.rounded_md()
.h_full()
.flex()
.items_center()
.justify_center()
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/radix/speaker-loud.svg")
.size_3p5()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.px_2()
.py_1()
.rounded_md()
.h_full()
.flex()
.items_center()
.justify_center()
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/radix/desktop.svg")
.size_3p5()
.fill(theme.lowest.base.default.foreground),
),
),
),
)
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
// User Group
.child(
div().child(
div()
.px_1()
.py_1()
.flex()
.items_center()
.justify_center()
.rounded_md()
.gap_0p5()
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(
img()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size_4()
.rounded_md()
.fill(theme.middle.on.default.foreground),
)
.child(
svg()
.path("icons/caret_down_8.svg")
.w_2()
.h_2()
.fill(theme.lowest.variant.default.foreground),
),
),
)
}
}
// ================================================================================ //
#[derive(Element)]
struct StatusBar;
pub fn statusbar<V: 'static>() -> impl Element<V> {
StatusBar
}
impl StatusBar {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.justify_between()
.w_full()
.h_8()
.fill(theme.lowest.base.default.background)
.child(self.left_group(cx))
.child(self.right_group(cx))
}
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_4()
.px_2()
// === Tools === //
.child(
div()
.flex()
.items_center()
.gap_1()
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/project.svg")
.w_4()
.h_4()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/conversations.svg")
.w_4()
.h_4()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/file_icons/notebook.svg")
.w_4()
.h_4()
.fill(theme.lowest.accent.default.foreground),
),
),
)
// === Diagnostics === //
.child(
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.h_full()
.flex()
.items_center()
.justify_center()
.gap_0p5()
.px_1()
.text_color(theme.lowest.variant.default.foreground)
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(
svg()
.path("icons/error.svg")
.w_4()
.h_4()
.fill(theme.lowest.negative.default.foreground),
)
.child(div().text_sm().child("2")),
)
.child(
div()
.text_sm()
.text_color(theme.lowest.variant.default.foreground)
.child("Something is wrong"),
),
)
}
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.flex()
.items_center()
.h_full()
.gap_4()
.px_2()
// === Tools === //
.child(
div()
.flex()
.items_center()
.gap_1()
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/check_circle.svg")
.w_4()
.h_4()
.fill(theme.lowest.base.default.foreground),
),
)
.child(
div()
.w_6()
.h_full()
.flex()
.items_center()
.justify_center()
.child(
svg()
.path("icons/copilot.svg")
.w_4()
.h_4()
.fill(theme.lowest.accent.default.foreground),
),
),
)
}
}

View file

@ -10,9 +10,8 @@ use gpui::{
platform::{CursorStyle, MouseButton},
serde_json::json,
text_layout::{Line, RunStyle},
AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion,
PaintContext, Quad, SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext,
WeakModelHandle,
AnyElement, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SizeConstraint,
TextLayoutCache, ViewContext, WeakModelHandle, WindowContext,
};
use itertools::Itertools;
use language::CursorShape;
@ -86,12 +85,11 @@ impl LayoutCell {
fn paint(
&self,
scene: &mut SceneBuilder,
origin: Vector2F,
layout: &LayoutState,
visible_bounds: RectF,
_view: &mut TerminalView,
cx: &mut ViewContext<TerminalView>,
cx: &mut WindowContext,
) {
let pos = {
let point = self.point;
@ -102,7 +100,7 @@ impl LayoutCell {
};
self.text
.paint(scene, pos, visible_bounds, layout.size.line_height, cx);
.paint(pos, visible_bounds, layout.size.line_height, cx);
}
}
@ -132,11 +130,10 @@ impl LayoutRect {
fn paint(
&self,
scene: &mut SceneBuilder,
origin: Vector2F,
layout: &LayoutState,
_view: &mut TerminalView,
_cx: &mut ViewContext<TerminalView>,
cx: &mut ViewContext<TerminalView>,
) {
let position = {
let point = self.point;
@ -150,7 +147,7 @@ impl LayoutRect {
layout.size.line_height,
);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(position, size),
background: Some(self.color),
border: Default::default(),
@ -387,7 +384,6 @@ impl TerminalElement {
fn attach_mouse_handlers(
&self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
mode: TermMode,
@ -518,7 +514,7 @@ impl TerminalElement {
)
}
scene.push_mouse_region(region);
cx.scene().push_mouse_region(region);
}
}
@ -530,7 +526,7 @@ impl Element<TerminalView> for TerminalElement {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut TerminalView,
cx: &mut LayoutContext<TerminalView>,
cx: &mut ViewContext<TerminalView>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
let settings = settings::get::<ThemeSettings>(cx);
let terminal_settings = settings::get::<TerminalSettings>(cx);
@ -733,25 +729,24 @@ impl Element<TerminalView> for TerminalElement {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut TerminalView,
cx: &mut PaintContext<TerminalView>,
cx: &mut ViewContext<TerminalView>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
//Setup element stuff
let clip_bounds = Some(visible_bounds);
scene.paint_layer(clip_bounds, |scene| {
cx.paint_layer(clip_bounds, |cx| {
let origin = bounds.origin() + vec2f(layout.gutter, 0.);
// Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
self.attach_mouse_handlers(scene, origin, visible_bounds, layout.mode, cx);
self.attach_mouse_handlers(origin, visible_bounds, layout.mode, cx);
scene.push_cursor_region(gpui::CursorRegion {
cx.scene().push_cursor_region(gpui::CursorRegion {
bounds,
style: if layout.hyperlink_tooltip.is_some() {
CursorStyle::PointingHand
@ -760,9 +755,9 @@ impl Element<TerminalView> for TerminalElement {
},
});
scene.paint_layer(clip_bounds, |scene| {
cx.paint_layer(clip_bounds, |cx| {
//Start with a background color
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(bounds.origin(), bounds.size()),
background: Some(layout.background_color),
border: Default::default(),
@ -770,12 +765,12 @@ impl Element<TerminalView> for TerminalElement {
});
for rect in &layout.rects {
rect.paint(scene, origin, layout, view, cx)
rect.paint(origin, layout, view, cx);
}
});
//Draw Highlighted Backgrounds
scene.paint_layer(clip_bounds, |scene| {
cx.paint_layer(clip_bounds, |cx| {
for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
{
if let Some((start_y, highlighted_range_lines)) =
@ -789,29 +784,29 @@ impl Element<TerminalView> for TerminalElement {
//Copied from editor. TODO: move to theme or something
corner_radius: 0.15 * layout.size.line_height,
};
hr.paint(bounds, scene);
hr.paint(bounds, cx);
}
}
});
//Draw the text cells
scene.paint_layer(clip_bounds, |scene| {
cx.paint_layer(clip_bounds, |cx| {
for cell in &layout.cells {
cell.paint(scene, origin, layout, visible_bounds, view, cx);
cell.paint(origin, layout, visible_bounds, view, cx);
}
});
//Draw cursor
if self.cursor_visible {
if let Some(cursor) = &layout.cursor {
scene.paint_layer(clip_bounds, |scene| {
cursor.paint(scene, origin, cx);
cx.paint_layer(clip_bounds, |cx| {
cursor.paint(origin, cx);
})
}
}
if let Some(element) = &mut layout.hyperlink_tooltip {
element.paint(scene, origin, visible_bounds, view, cx)
element.paint(origin, visible_bounds, view, cx)
}
});
}

View file

@ -6,15 +6,16 @@ pub mod ui;
use components::{action_button::ButtonStyle, disclosure::DisclosureStyle, ToggleIconButtonStyle};
use gpui::{
color::Color,
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
elements::{Border, ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
fonts::{HighlightStyle, TextStyle},
platform, AppContext, AssetSource, Border, MouseState,
platform, AppContext, AssetSource, MouseState,
};
use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use settings::SettingsStore;
use std::{collections::HashMap, ops::Deref, sync::Arc};
use std::{any::Any, collections::HashMap, ops::Deref, sync::Arc};
use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle};
pub use theme_registry::*;
@ -67,6 +68,14 @@ pub struct Theme {
pub welcome: WelcomeStyle,
pub titlebar: Titlebar,
pub component_test: ComponentTest,
// Nathan: New elements are styled in Rust, directly from the base theme.
// We store it on the legacy theme so we can mix both kinds of elements during the transition.
#[schemars(skip)]
pub base_theme: serde_json::Value,
// A place to cache deserialized base theme.
#[serde(skip_deserializing)]
#[schemars(skip)]
pub deserialized_base_theme: Mutex<Option<Box<dyn Any + Send + Sync>>>,
}
#[derive(Deserialize, Default, Clone, JsonSchema)]

Some files were not shown because too many files have changed in this diff Show more