.gitignore 🔗
@@ -1,4 +1,5 @@
**/target
+**/cargo-target
/zed.xcworkspace
.DS_Store
/plugins/bin
Nathan Sobo created
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.
.gitignore | 1
Cargo.lock | 75
Cargo.toml | 5
assets/icons/Icons/exit.svg | 3
assets/icons/stop_sharing.svg | 2
crates/collab_ui/src/collab_panel.rs | 8
crates/collab_ui/src/collab_titlebar_item.rs | 11
crates/collab_ui/src/face_pile.rs | 14
crates/editor/src/element.rs | 453 +--
crates/editor/src/hover_popover.rs | 8
crates/gpui/Cargo.toml | 3
crates/gpui/examples/corner_radii.rs | 27
crates/gpui/examples/text.rs | 6
crates/gpui/playground/Cargo.toml | 26
crates/gpui/playground/src/div.rs | 108 -
crates/gpui/playground/src/element.rs | 158 -
crates/gpui/playground/src/hoverable.rs | 76
crates/gpui/playground/src/interactive.rs | 34
crates/gpui/playground/src/layout_context.rs | 54
crates/gpui/playground/src/paint_context.rs | 71
crates/gpui/playground/src/playground.rs | 83
crates/gpui/playground/src/style.rs | 286 --
crates/gpui/playground/src/text.rs | 151 -
crates/gpui/playground/src/themes.rs | 84
crates/gpui/playground/src/themes/rose_pine.rs | 133 -
crates/gpui/playground_macros/src/styleable_helpers.rs | 147 -
crates/gpui/playground_macros/src/tailwind_lengths.rs | 99
crates/gpui/src/app.rs | 356 --
crates/gpui/src/app/test_app_context.rs | 1
crates/gpui/src/app/window.rs | 200 +
crates/gpui/src/elements.rs | 87
crates/gpui/src/elements/align.rs | 9
crates/gpui/src/elements/canvas.rs | 13
crates/gpui/src/elements/clipped.rs | 18
crates/gpui/src/elements/component.rs | 13
crates/gpui/src/elements/constrained_box.rs | 22
crates/gpui/src/elements/container.rs | 196 +
crates/gpui/src/elements/empty.rs | 7
crates/gpui/src/elements/expanded.rs | 11
crates/gpui/src/elements/flex.rs | 34
crates/gpui/src/elements/hook.rs | 10
crates/gpui/src/elements/image.rs | 14
crates/gpui/src/elements/keystroke_label.rs | 7
crates/gpui/src/elements/label.rs | 15
crates/gpui/src/elements/list.rs | 78
crates/gpui/src/elements/mouse_event_handler.rs | 37
crates/gpui/src/elements/overlay.rs | 42
crates/gpui/src/elements/resizable.rs | 120
crates/gpui/src/elements/stack.rs | 13
crates/gpui/src/elements/svg.rs | 10
crates/gpui/src/elements/text.rs | 33
crates/gpui/src/elements/tooltip.rs | 13
crates/gpui/src/elements/uniform_list.rs | 15
crates/gpui/src/fonts.rs | 85
crates/gpui/src/geometry.rs | 126 +
crates/gpui/src/gpui.rs | 6
crates/gpui/src/image_cache.rs | 99
crates/gpui/src/platform.rs | 1
crates/gpui/src/platform/mac/platform.rs | 1
crates/gpui/src/platform/mac/renderer.rs | 18
crates/gpui/src/platform/mac/status_item.rs | 4
crates/gpui/src/platform/mac/window.rs | 18
crates/gpui/src/platform/test.rs | 4
crates/gpui/src/scene.rs | 215 -
crates/gpui/src/text_layout.rs | 21
crates/gpui2/Cargo.toml | 32
crates/gpui2/src/adapter.rs | 46
crates/gpui2/src/color.rs | 52
crates/gpui2/src/element.rs | 186 +
crates/gpui2/src/elements.rs | 10
crates/gpui2/src/elements/div.rs | 320 +++
crates/gpui2/src/elements/hoverable.rs | 105 +
crates/gpui2/src/elements/img.rs | 110 +
crates/gpui2/src/elements/pressable.rs | 108 +
crates/gpui2/src/elements/svg.rs | 84
crates/gpui2/src/elements/text.rs | 109 +
crates/gpui2/src/gpui2.rs | 22
crates/gpui2/src/interactive.rs | 165 +
crates/gpui2/src/style.rs | 604 ++++++
crates/gpui2/src/view.rs | 0
crates/gpui2/src/view_context.rs | 79
crates/gpui2_macros/Cargo.toml | 4
crates/gpui2_macros/src/derive_element.rs | 22
crates/gpui2_macros/src/derive_into_element.rs | 2
crates/gpui2_macros/src/gpui2_macros.rs | 6
crates/gpui2_macros/src/styleable_helpers.rs | 327 +++
crates/gpui_macros/src/gpui_macros.rs | 7
crates/refineable/derive_refineable/src/derive_refineable.rs | 56
crates/refineable/src/refineable.rs | 46
crates/storybook/Cargo.lock | 2
crates/storybook/Cargo.toml | 23
crates/storybook/docs/thoughts.md | 0
crates/storybook/src/collab_panel.rs | 177 +
crates/storybook/src/components.rs | 59
crates/storybook/src/element_ext.rs | 22
crates/storybook/src/storybook.rs | 109 +
crates/storybook/src/theme.rs | 192 +
crates/storybook/src/workspace.rs | 435 ++++
crates/terminal_view/src/terminal_element.rs | 49
crates/theme/src/theme.rs | 15
crates/util/src/arc_cow.rs | 74
crates/util/src/http.rs | 2
crates/util/src/util.rs | 17
crates/workspace/src/pane.rs | 24
crates/workspace/src/pane/dragged_item_receiver.rs | 17
crates/workspace/src/pane_group.rs | 27
crates/workspace/src/shared_screen.rs | 4
crates/workspace/src/status_bar.rs | 15
crates/workspace/src/workspace.rs | 4
styles/src/build_themes.ts | 3
test.rs | 416 +--
111 files changed, 5,045 insertions(+), 3,241 deletions(-)
@@ -1,4 +1,5 @@
**/target
+**/cargo-target
/zed.xcworkspace
.DS_Store
/plugins/bin
@@ -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",
@@ -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",
@@ -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>
@@ -0,0 +1,5 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M7.70312 4L7.26046 2.97338C7.10239 2.60678 6.74141 2.36933 6.34219 2.36933H2.5C2.22386 2.36933 2 2.59319 2 2.86933V4.375V8" stroke="#11181C" stroke-width="1.25" stroke-linecap="round"/>
@@ -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),
@@ -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(
@@ -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;
}
@@ -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();
- }
- }
- })
- .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();
+ 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_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_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_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,
)
});
@@ -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(),
@@ -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 = [] }
@@ -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(
@@ -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 |_, _, _| {
@@ -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"] }
@@ -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 }
-// }
-// }
@@ -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;
-}
@@ -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();
- }
- });
- }
-}
@@ -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(),
- }
- }
-}
@@ -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))
- }
-}
@@ -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)
- }
-}
@@ -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))
-// }
@@ -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
- }
-}
@@ -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(),
- }
- }
-}
@@ -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)
- }
-}
@@ -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 }
-}
@@ -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 }]),
- ]
-}
@@ -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.) }),
- ]
-}
@@ -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,136 +3361,6 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
) -> ElementStateHandle<T> {
self.element_state_dynamic::<T>(tag, element_id, T::default())
}
-}
-
-impl<V: View> ViewContext<'_, '_, V> {
- pub fn emit(&mut self, event: V::Event) {
- self.window_context
- .pending_effects
- .push_back(Effect::Event {
- entity_id: self.view_id,
- payload: Box::new(event),
- });
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub struct TypeTag {
- tag: TypeId,
- composed: Option<TypeId>,
- #[cfg(debug_assertions)]
- tag_type_name: &'static str,
-}
-
-impl TypeTag {
- pub fn new<Tag: 'static>() -> Self {
- Self {
- tag: TypeId::of::<Tag>(),
- composed: None,
- #[cfg(debug_assertions)]
- tag_type_name: std::any::type_name::<Tag>(),
- }
- }
-
- pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
- Self {
- tag,
- composed: None,
- #[cfg(debug_assertions)]
- tag_type_name: type_name,
- }
- }
-
- pub fn compose(mut self, other: TypeTag) -> Self {
- self.composed = Some(other.tag);
- self
- }
-
- #[cfg(debug_assertions)]
- pub(crate) fn type_name(&self) -> &'static str {
- self.tag_type_name
- }
-}
-
-impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
- fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- BorrowAppContext::read_with(&*self.window_context, f)
- }
-
- fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- BorrowAppContext::update(&mut *self.window_context, f)
- }
-}
-
-impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
- type Result<T> = T;
-
- fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
- BorrowWindowContext::read_window(&*self.window_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.window_context, window, f)
- }
-
- fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
- &mut self,
- window: AnyWindowHandle,
- f: F,
- ) -> T {
- BorrowWindowContext::update_window(&mut *self.window_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.window_context, window, f)
- }
-}
-
-/// 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(
@@ -3523,80 +3398,95 @@ impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
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
+ self.window
+ .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
+ pub fn paint_layer<F, R>(&mut self, clip_bounds: Option<RectF>, f: F) -> R
where
- F: FnOnce(&mut Self) -> T,
+ F: FnOnce(&mut Self) -> R,
{
- self.push_text_style(style);
+ self.scene().push_layer(clip_bounds);
let result = f(self);
- self.pop_text_style();
+ self.scene().pop_layer();
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))
+impl<V: View> ViewContext<'_, '_, V> {
+ pub fn emit(&mut self, event: V::Event) {
+ self.window_context
+ .pending_effects
+ .push_back(Effect::Event {
+ entity_id: self.view_id,
+ payload: Box::new(event),
+ });
}
+}
- fn push_text_style(&mut self, style: TextStyle) {
- self.text_style_stack.push(style);
- }
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct TypeTag {
+ tag: TypeId,
+ composed: Option<TypeId>,
+ #[cfg(debug_assertions)]
+ tag_type_name: &'static str,
+}
- fn pop_text_style(&mut self) {
- self.text_style_stack.pop();
+impl TypeTag {
+ pub fn new<Tag: 'static>() -> Self {
+ Self {
+ tag: TypeId::of::<Tag>(),
+ composed: None,
+ #[cfg(debug_assertions)]
+ tag_type_name: std::any::type_name::<Tag>(),
+ }
}
- fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
- &mut self.view_context
+ pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
+ Self {
+ tag,
+ composed: None,
+ #[cfg(debug_assertions)]
+ tag_type_name: type_name,
+ }
}
-}
-
-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
+ pub fn compose(mut self, other: TypeTag) -> Self {
+ self.composed = Some(other.tag);
+ self
}
-}
-impl<V> DerefMut for LayoutContext<'_, '_, '_, V> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.view_context
+ #[cfg(debug_assertions)]
+ pub(crate) fn type_name(&self) -> &'static str {
+ self.tag_type_name
}
}
-impl<V> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
+impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
- BorrowAppContext::read_with(&*self.view_context, f)
+ BorrowAppContext::read_with(&*self.window_context, f)
}
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
- BorrowAppContext::update(&mut *self.view_context, f)
+ BorrowAppContext::update(&mut *self.window_context, f)
}
}
-impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
+impl<V> BorrowWindowContext for ViewContext<'_, '_, 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)
+ BorrowWindowContext::read_window(&*self.window_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)
+ BorrowWindowContext::read_window_optional(&*self.window_context, window, f)
}
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
@@ -3604,105 +3494,14 @@ impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
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)
+ BorrowWindowContext::update_window(&mut *self.window_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)
+ BorrowWindowContext::update_window_optional(&mut *self.window_context, window, f)
}
}
@@ -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);
});
});
@@ -57,6 +57,7 @@ impl TestAppContext {
platform,
foreground_platform.clone(),
font_cache,
+ util::http::FakeHttpClient::with_404_response(),
RefCounts::new(leak_detector),
(),
);
@@ -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(¤t_view_id);
- let new_parent_id = new_parents.get(¤t_view_id);
+ let new_parent_id = self.window.new_parents.get(¤t_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 {
@@ -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(())
})
}
@@ -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,
@@ -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(
@@ -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(
@@ -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(
@@ -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(
@@ -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();
}
}
}
@@ -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 {
}
@@ -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(
@@ -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(
@@ -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(
@@ -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(),
@@ -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(
@@ -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(
@@ -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!()
}
@@ -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);
}
}
@@ -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(
@@ -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(
@@ -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();
}
}
@@ -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(),
@@ -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);
@@ -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);
}
}
@@ -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(
@@ -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()
}
}
+}
+
+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)?;
+ }
- 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),
+ 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;
}
}
}
@@ -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,15 +226,25 @@ 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> {
+impl Edges<Length> {
+ pub fn auto() -> Self {
+ Self {
+ top: Length::Auto,
+ right: Length::Auto,
+ bottom: Length::Auto,
+ left: Length::Auto,
+ }
+ }
+
pub fn zero() -> Self {
Self {
top: pixels(0.),
@@ -240,7 +254,10 @@ impl Edges<DefiniteLength> {
}
}
- pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
+ pub fn to_taffy(
+ &self,
+ rem_size: f32,
+ ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size),
right: self.right.to_taffy(rem_size),
@@ -250,16 +267,27 @@ impl Edges<DefiniteLength> {
}
}
-impl Edges<Length> {
- pub fn auto() -> Self {
+impl Edges<DefiniteLength> {
+ pub fn zero() -> Self {
Self {
- top: Length::Auto,
- right: Length::Auto,
- bottom: Length::Auto,
- left: Length::Auto,
+ 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.),
@@ -269,10 +297,7 @@ impl Edges<Length> {
}
}
- pub fn to_taffy(
- &self,
- rem_size: f32,
- ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
+ 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),
@@ -280,6 +305,21 @@ impl Edges<Length> {
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)]
@@ -288,6 +328,15 @@ pub enum AbsoluteLength {
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())
+ }
+}
@@ -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;
@@ -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
+ }
+ }
+ }
+}
@@ -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>);
@@ -37,6 +37,7 @@ use objc::{
runtime::{Class, Object, Sel},
sel, sel_impl,
};
+
use postage::oneshot;
use ptr::null_mut;
use std::{
@@ -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,
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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,43 +244,38 @@ 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);
- Scene {
- scale_factor: self.scale_factor,
- stacking_contexts: self.stacking_contexts,
- event_handlers: self.event_handlers,
- }
+ 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 scale_factor(&self) -> f32 {
- self.scale_factor
- }
+ 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();
- 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();
+ Scene {
+ scale_factor,
+ stacking_contexts,
+ event_handlers,
+ }
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
@@ -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
@@ -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(),
@@ -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"] }
@@ -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")
}
@@ -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]>,
@@ -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;
+}
@@ -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;
@@ -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);
+ }
+}
@@ -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
+ }
+}
@@ -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 {}
@@ -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
+ }
+}
@@ -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 {}
@@ -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,
+}
@@ -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;
@@ -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(),
+ }
+ }
+}
@@ -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
+ }
+}
@@ -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)
+ }
+}
@@ -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]
@@ -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);
}
}
@@ -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;
@@ -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)
-}
@@ -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.) }),
+ ]
+}
@@ -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(
@@ -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()
}
@@ -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
+ }
}
@@ -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",
@@ -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"] }
@@ -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),
+ )
+ }
+}
@@ -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
+ }
}
}
@@ -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,
+ }
+ }
+}
@@ -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();
+// }
@@ -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>()
+}
@@ -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),
+ ),
+ ),
+ )
+ }
+}
@@ -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)
}
});
}
@@ -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)]
@@ -0,0 +1,74 @@
+use std::sync::Arc;
+
+#[derive(PartialEq, Eq)]
+pub enum ArcCow<'a, T: ?Sized> {
+ Borrowed(&'a T),
+ Owned(Arc<T>),
+}
+
+use std::hash::{Hash, Hasher};
+
+impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ match self {
+ Self::Borrowed(borrowed) => Hash::hash(borrowed, state),
+ Self::Owned(owned) => Hash::hash(&**owned, state),
+ }
+ }
+}
+
+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<'a, T: ?Sized + ToOwned> std::borrow::Borrow<T> for ArcCow<'a, T> {
+ fn borrow(&self) -> &T {
+ match self {
+ ArcCow::Borrowed(borrowed) => borrowed,
+ ArcCow::Owned(owned) => owned.as_ref(),
+ }
+ }
+}
+
+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(),
+ }
+ }
+}
@@ -2,7 +2,7 @@ pub use anyhow::{anyhow, Result};
use futures::future::BoxFuture;
use isahc::config::{Configurable, RedirectPolicy};
pub use isahc::{
- http::{Method, Uri},
+ http::{Method, StatusCode, Uri},
Error,
};
pub use isahc::{AsyncBody, Request, Response};
@@ -1,3 +1,4 @@
+pub mod arc_cow;
pub mod channel;
pub mod fs;
pub mod github;
@@ -246,9 +247,16 @@ where
}
}
-struct Defer<F: FnOnce()>(Option<F>);
+pub struct Deferred<F: FnOnce()>(Option<F>);
-impl<F: FnOnce()> Drop for Defer<F> {
+impl<F: FnOnce()> Deferred<F> {
+ /// Drop without running the deferred function.
+ pub fn cancel(mut self) {
+ self.0.take();
+ }
+}
+
+impl<F: FnOnce()> Drop for Deferred<F> {
fn drop(&mut self) {
if let Some(f) = self.0.take() {
f()
@@ -256,8 +264,9 @@ impl<F: FnOnce()> Drop for Defer<F> {
}
}
-pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
- Defer(Some(f))
+/// Run the given function when the returned value is dropped (unless it's cancelled).
+pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
+ Deferred(Some(f))
}
pub struct RandomCharIter<T: Rng> {
@@ -25,8 +25,8 @@ use gpui::{
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
- LayoutContext, ModelHandle, MouseRegion, PaintContext, Quad, Task, View, ViewContext,
- ViewHandle, WeakViewHandle, WindowContext,
+ ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ WindowContext,
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
@@ -1506,10 +1506,10 @@ impl Pane {
None
};
- Canvas::new(move |scene, bounds, _, _, _| {
+ Canvas::new(move |bounds, _, _, cx| {
if let Some(color) = icon_color {
let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
- scene.push_quad(Quad {
+ cx.scene().push_quad(Quad {
bounds: square,
background: Some(color),
border: Default::default(),
@@ -2066,7 +2066,7 @@ impl<V: 'static> Element<V> for PaneBackdrop<V> {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
- cx: &mut LayoutContext<V>,
+ cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
(size, ())
@@ -2074,25 +2074,24 @@ impl<V: 'static> Element<V> for PaneBackdrop<V> {
fn paint(
&mut self,
- scene: &mut gpui::SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
- cx: &mut PaintContext<V>,
+ cx: &mut ViewContext<V>,
) -> Self::PaintState {
let background = theme::current(cx).editor.background;
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
- scene.push_quad(gpui::Quad {
+ cx.scene().push_quad(gpui::Quad {
bounds: RectF::new(bounds.origin(), bounds.size()),
background: Some(background),
..Default::default()
});
let child_view_id = self.child_view;
- scene.push_mouse_region(
+ cx.scene().push_mouse_region(
MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
gpui::platform::MouseButton::Left,
move |_, _: &mut V, cx| {
@@ -2102,10 +2101,9 @@ impl<V: 'static> Element<V> for PaneBackdrop<V> {
),
);
- scene.paint_layer(Some(bounds), |scene| {
- self.child
- .paint(scene, bounds.origin(), visible_bounds, view, cx)
- })
+ cx.scene().push_layer(Some(bounds));
+ self.child.paint(bounds.origin(), visible_bounds, view, cx);
+ cx.scene().pop_layer();
}
fn rect_for_text_range(
@@ -50,7 +50,7 @@ where
Stack::new()
.with_child(render_child(state, cx))
.with_children(drag_position.map(|drag_position| {
- Canvas::new(move |scene, bounds, _, _, cx| {
+ Canvas::new(move |bounds, _, _, cx| {
if bounds.contains_point(drag_position) {
let overlay_region = split_margin
.and_then(|split_margin| {
@@ -60,14 +60,15 @@ where
.map(|(dir, margin)| dir.along_edge(bounds, margin))
.unwrap_or(bounds);
- scene.paint_stacking_context(None, None, |scene| {
- scene.push_quad(Quad {
- bounds: overlay_region,
- background: Some(overlay_color(cx)),
- border: Default::default(),
- corner_radii: Default::default(),
- });
+ cx.scene().push_stacking_context(None, None);
+ let background = overlay_color(cx);
+ cx.scene().push_quad(Quad {
+ bounds: overlay_region,
+ background: Some(background),
+ border: Default::default(),
+ corner_radii: Default::default(),
});
+ cx.scene().pop_stacking_context();
}
})
}))
@@ -9,7 +9,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton},
- AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle,
+ AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
};
use project::Project;
use serde::Deserialize;
@@ -594,8 +594,8 @@ mod element {
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
- AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
- PaintContext, RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
+ AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt,
+ SizeConstraint, Vector2FExt, ViewContext,
};
use crate::{
@@ -641,7 +641,7 @@ mod element {
remaining_flex: &mut f32,
cross_axis_max: &mut f32,
view: &mut Workspace,
- cx: &mut LayoutContext<Workspace>,
+ cx: &mut ViewContext<Workspace>,
) {
let flexes = self.flexes.borrow();
let cross_axis = self.axis.invert();
@@ -789,7 +789,7 @@ mod element {
&mut self,
constraint: SizeConstraint,
view: &mut Workspace,
- cx: &mut LayoutContext<Workspace>,
+ cx: &mut ViewContext<Workspace>,
) -> (Vector2F, Self::LayoutState) {
debug_assert!(self.children.len() == self.flexes.borrow().len());
@@ -851,19 +851,18 @@ mod element {
fn paint(
&mut self,
- scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut Workspace,
- cx: &mut PaintContext<Workspace>,
+ cx: &mut ViewContext<Workspace>,
) -> Self::PaintState {
let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let overflowing = *remaining_space < 0.;
if overflowing {
- scene.push_layer(Some(visible_bounds));
+ cx.scene().push_layer(Some(visible_bounds));
}
let mut child_origin = bounds.origin();
@@ -874,7 +873,7 @@ mod element {
let mut children_iter = self.children.iter_mut().enumerate().peekable();
while let Some((ix, child)) = children_iter.next() {
let child_start = child_origin.clone();
- child.paint(scene, child_origin, visible_bounds, view, cx);
+ child.paint(child_origin, visible_bounds, view, cx);
bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
@@ -884,7 +883,7 @@ mod element {
}
if can_resize && children_iter.peek().is_some() {
- scene.push_stacking_context(None, None);
+ cx.scene().push_stacking_context(None, None);
let handle_origin = match self.axis {
Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
@@ -907,7 +906,7 @@ mod element {
Axis::Vertical => CursorStyle::ResizeUpDown,
};
- scene.push_cursor_region(CursorRegion {
+ cx.scene().push_cursor_region(CursorRegion {
bounds: handle_bounds,
style,
});
@@ -940,14 +939,14 @@ mod element {
}
}
});
- scene.push_mouse_region(mouse_region);
+ cx.scene().push_mouse_region(mouse_region);
- scene.pop_stacking_context();
+ cx.scene().pop_stacking_context();
}
}
if overflowing {
- scene.pop_layer();
+ cx.scene().pop_layer();
}
}
@@ -73,14 +73,14 @@ impl View for SharedScreen {
let frame = self.frame.clone();
MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
- Canvas::new(move |scene, bounds, _, _, _| {
+ Canvas::new(move |bounds, _, _, cx| {
if let Some(frame) = frame.clone() {
let size = constrain_size_preserving_aspect_ratio(
bounds.size(),
vec2f(frame.width() as f32, frame.height() as f32),
);
let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
- scene.push_surface(gpui::platform::mac::Surface {
+ cx.scene().push_surface(gpui::platform::mac::Surface {
bounds: RectF::new(origin, size),
image_buffer: frame.image(),
});
@@ -8,8 +8,8 @@ use gpui::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
- AnyElement, AnyViewHandle, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
- Subscription, View, ViewContext, ViewHandle, WindowContext,
+ AnyElement, AnyViewHandle, Entity, SizeConstraint, Subscription, View, ViewContext, ViewHandle,
+ WindowContext,
};
pub trait StatusItemView: View {
@@ -208,7 +208,7 @@ impl Element<StatusBar> for StatusBarElement {
&mut self,
mut constraint: SizeConstraint,
view: &mut StatusBar,
- cx: &mut LayoutContext<StatusBar>,
+ cx: &mut ViewContext<StatusBar>,
) -> (Vector2F, Self::LayoutState) {
let max_width = constraint.max.x();
constraint.min = vec2f(0., constraint.min.y());
@@ -226,23 +226,20 @@ impl Element<StatusBar> for StatusBarElement {
fn paint(
&mut self,
- scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut StatusBar,
- cx: &mut PaintContext<StatusBar>,
+ cx: &mut ViewContext<StatusBar>,
) -> Self::PaintState {
let origin_y = bounds.upper_right().y();
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let left_origin = vec2f(bounds.lower_left().x(), origin_y);
- self.left
- .paint(scene, left_origin, visible_bounds, view, cx);
+ self.left.paint(left_origin, visible_bounds, view, cx);
let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
- self.right
- .paint(scene, right_origin, visible_bounds, view, cx);
+ self.right.paint(right_origin, visible_bounds, view, cx);
}
fn rect_for_text_range(
@@ -3628,13 +3628,13 @@ fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppCo
"Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
text,
)
- .with_custom_runs(vec![26..32, 34..46], |_, bounds, scene, cx| {
+ .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
let code_span_background_color = settings::get::<ThemeSettings>(cx)
.theme
.editor
.document_highlight_read_background;
- scene.push_quad(gpui::Quad {
+ cx.scene().push_quad(gpui::Quad {
bounds,
background: Some(code_span_background_color),
border: Default::default(),
@@ -30,6 +30,9 @@ function write_themes(themes: Theme[], output_directory: string) {
setTheme(theme)
const style_tree = app()
+ // Nathan: New elements will read directly from the theme colors.
+ // Adding this during the transition. Afterwards, we can port all themes to Rust.
+ style_tree.base_theme = theme
const style_tree_json = JSON.stringify(style_tree, null, 2)
const temp_path = path.join(temp_directory, `${theme.name}.json`)
const out_path = path.join(output_directory, `${theme.name}.json`)
@@ -17,10 +17,10 @@ use simplelog::SimpleLogger;
use themes::{rose_pine, ThemeColors};
use view::view;
mod adapter {
+ use crate::element::AnyElement;
use crate::element::{LayoutContext, PaintContext};
use gpui::{geometry::rect::RectF, LayoutEngine};
use util::ResultExt;
- use crate::element::AnyElement;
pub struct Adapter<V>(pub(crate) AnyElement<V>);
impl<V: 'static> gpui::Element<V> for Adapter<V> {
type LayoutState = Option<LayoutEngine>;
@@ -90,8 +90,8 @@ mod adapter {
}
mod color {
#![allow(dead_code)]
- use std::{num::ParseIntError, ops::Range};
use smallvec::SmallVec;
+ use std::{num::ParseIntError, ops::Range};
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
@@ -130,16 +130,7 @@ mod color {
impl ::core::fmt::Debug for Rgba {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(
- f,
- "Rgba",
- "r",
- &self.r,
- "g",
- &self.g,
- "b",
- &self.b,
- "a",
- &&self.a,
+ f, "Rgba", "r", &self.r, "g", &self.g, "b", &self.b, "a", &&self.a,
)
}
}
@@ -185,7 +176,12 @@ mod color {
4 => (xm, m, cm),
_ => (cm, m, xm),
};
- Rgba { r, g, b, a: color.a }
+ Rgba {
+ r,
+ g,
+ b,
+ a: color.a,
+ }
}
}
impl TryFrom<&'_ str> for Rgba {
@@ -239,16 +235,7 @@ mod color {
impl ::core::fmt::Debug for Hsla {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(
- f,
- "Hsla",
- "h",
- &self.h,
- "s",
- &self.s,
- "l",
- &self.l,
- "a",
- &&self.a,
+ f, "Hsla", "h", &self.h, "s", &self.s, "l", &self.l, "a", &&self.a,
)
}
}
@@ -258,8 +245,7 @@ mod color {
impl ::core::cmp::PartialEq for Hsla {
#[inline]
fn eq(&self, other: &Hsla) -> bool {
- self.h == other.h && self.s == other.s && self.l == other.l
- && self.a == other.a
+ self.h == other.h && self.s == other.s && self.l == other.l && self.a == other.a
}
}
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
@@ -271,7 +257,12 @@ mod color {
}
}
pub fn black() -> Hsla {
- Hsla { h: 0., s: 0., l: 0., a: 1. }
+ Hsla {
+ h: 0.,
+ s: 0.,
+ l: 0.,
+ a: 1.,
+ }
}
impl From<Rgba> for Hsla {
fn from(color: Rgba) -> Self {
@@ -298,7 +289,12 @@ mod color {
} else {
((r - g) / delta + 4.0) / 6.0
};
- Hsla { h, s, l, a: color.a }
+ Hsla {
+ h,
+ s,
+ l,
+ a: color.a,
+ }
}
}
impl Hsla {
@@ -364,8 +360,7 @@ mod color {
positions: SmallVec::new(),
};
let num_colors: f32 = scale.colors.len() as f32 - 1.0;
- scale
- .positions = (0..scale.colors.len())
+ scale.positions = (0..scale.colors.len())
.map(|i| i as f32 / num_colors)
.collect();
scale
@@ -375,12 +370,10 @@ mod color {
if true {
if !(0.0 <= t && t <= 1.0) {
{
- ::core::panicking::panic_fmt(
- format_args!(
- "t value {0} is out of range. Expected value in range 0.0 to 1.0",
- t,
- ),
- );
+ ::core::panicking::panic_fmt(format_args!(
+ "t value {0} is out of range. Expected value in range 0.0 to 1.0",
+ t,
+ ));
}
}
}
@@ -412,10 +405,12 @@ mod color {
mod components {
use crate::{
element::{Element, ElementMetadata},
- frame, text::ArcCow, themes::rose_pine,
+ frame,
+ text::ArcCow,
+ themes::rose_pine,
};
use gpui::{platform::MouseButton, ViewContext};
- use playground_macros::Element;
+ use gpui2_macros::Element;
use std::{marker::PhantomData, rc::Rc};
struct ButtonHandlers<V, D> {
click: Option<Rc<dyn Fn(&mut V, &D, &mut ViewContext<V>)>>,
@@ -498,18 +493,11 @@ mod components {
self.icon = Some(icon.into());
self
}
- pub fn click(
- self,
- handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static,
- ) -> Self {
+ pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
let data = self.data.clone();
- Element::click(
- self,
- MouseButton::Left,
- move |view, _, cx| {
- handler(view, data.as_ref(), cx);
- },
- )
+ Element::click(self, MouseButton::Left, move |view, _, cx| {
+ handler(view, data.as_ref(), cx);
+ })
}
}
pub fn button<V>() -> Button<V, ()> {
@@ -523,11 +511,9 @@ mod components {
.children(self.label.clone());
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) },
- )
+ button.mouse_down(MouseButton::Left, move |view, event, cx| {
+ handler(view, data.as_ref(), cx)
+ })
} else {
button
}
@@ -535,8 +521,11 @@ mod components {
}
}
mod element {
+ pub use crate::paint_context::PaintContext;
use crate::{
- adapter::Adapter, color::Hsla, hoverable::Hoverable,
+ adapter::Adapter,
+ color::Hsla,
+ hoverable::Hoverable,
style::{Display, Fill, OptionalStyle, Overflow, Position},
};
use anyhow::Result;
@@ -546,12 +535,12 @@ mod element {
platform::{MouseButton, MouseButtonEvent},
EngineLayout, EventContext, RenderContext, ViewContext,
};
- use playground_macros::tailwind_lengths;
+ use gpui2_macros::tailwind_lengths;
use std::{
any::{Any, TypeId},
- cell::Cell, rc::Rc,
+ cell::Cell,
+ rc::Rc,
};
- pub use crate::paint_context::PaintContext;
pub use taffy::tree::NodeId;
pub struct Layout<'a, E: ?Sized> {
pub from_engine: EngineLayout,
@@ -627,33 +616,24 @@ mod element {
Self: Sized,
{
let pressed: Rc<Cell<bool>> = Default::default();
- self.mouse_down(
- button,
- {
- let pressed = pressed.clone();
- move |_, _, _| {
- pressed.set(true);
- }
- },
- )
- .mouse_up_outside(
- button,
- {
- let pressed = pressed.clone();
- move |_, _, _| {
- pressed.set(false);
- }
- },
- )
- .mouse_up(
- button,
- move |view, event, event_cx| {
- if pressed.get() {
- pressed.set(false);
- handler(view, event, event_cx);
- }
- },
- )
+ self.mouse_down(button, {
+ let pressed = pressed.clone();
+ move |_, _, _| {
+ pressed.set(true);
+ }
+ })
+ .mouse_up_outside(button, {
+ let pressed = pressed.clone();
+ move |_, _, _| {
+ pressed.set(false);
+ }
+ })
+ .mouse_up(button, move |view, event, event_cx| {
+ if pressed.get() {
+ pressed.set(false);
+ handler(view, event, event_cx);
+ }
+ })
}
fn mouse_down(
mut self,
@@ -663,17 +643,16 @@ mod element {
where
Self: Sized,
{
- self.handlers_mut()
- .push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: false,
- });
+ self.handlers_mut().push(EventHandler {
+ handler: Rc::new(move |view, event, event_cx| {
+ let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+ if event.button == button && event.is_down {
+ handler(view, event, event_cx);
+ }
+ }),
+ event_type: TypeId::of::<MouseButtonEvent>(),
+ outside_bounds: false,
+ });
self
}
fn mouse_down_outside(
@@ -684,17 +663,16 @@ mod element {
where
Self: Sized,
{
- self.handlers_mut()
- .push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: true,
- });
+ self.handlers_mut().push(EventHandler {
+ handler: Rc::new(move |view, event, event_cx| {
+ let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+ if event.button == button && event.is_down {
+ handler(view, event, event_cx);
+ }
+ }),
+ event_type: TypeId::of::<MouseButtonEvent>(),
+ outside_bounds: true,
+ });
self
}
fn mouse_up(
@@ -705,17 +683,16 @@ mod element {
where
Self: Sized,
{
- self.handlers_mut()
- .push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && !event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: false,
- });
+ self.handlers_mut().push(EventHandler {
+ handler: Rc::new(move |view, event, event_cx| {
+ let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+ if event.button == button && !event.is_down {
+ handler(view, event, event_cx);
+ }
+ }),
+ event_type: TypeId::of::<MouseButtonEvent>(),
+ outside_bounds: false,
+ });
self
}
fn mouse_up_outside(
@@ -726,17 +703,16 @@ mod element {
where
Self: Sized,
{
- self.handlers_mut()
- .push(EventHandler {
- handler: Rc::new(move |view, event, event_cx| {
- let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
- if event.button == button && !event.is_down {
- handler(view, event, event_cx);
- }
- }),
- event_type: TypeId::of::<MouseButtonEvent>(),
- outside_bounds: true,
- });
+ self.handlers_mut().push(EventHandler {
+ handler: Rc::new(move |view, event, event_cx| {
+ let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+ if event.button == button && !event.is_down {
+ handler(view, event, event_cx);
+ }
+ }),
+ event_type: TypeId::of::<MouseButtonEvent>(),
+ outside_bounds: true,
+ });
self
}
fn block(mut self) -> Self
@@ -764,9 +740,7 @@ mod element {
where
Self: Sized,
{
- self
- .declared_style()
- .overflow = OptionalPoint {
+ self.declared_style().overflow = OptionalPoint {
x: Some(Overflow::Visible),
y: Some(Overflow::Visible),
};
@@ -776,9 +750,7 @@ mod element {
where
Self: Sized,
{
- self
- .declared_style()
- .overflow = OptionalPoint {
+ self.declared_style().overflow = OptionalPoint {
x: Some(Overflow::Hidden),
y: Some(Overflow::Hidden),
};
@@ -788,9 +760,7 @@ mod element {
where
Self: Sized,
{
- self
- .declared_style()
- .overflow = OptionalPoint {
+ self.declared_style().overflow = OptionalPoint {
x: Some(Overflow::Scroll),
y: Some(Overflow::Scroll),
};
@@ -4485,11 +4455,7 @@ mod element {
layout: Option<(NodeId, Box<dyn Any>)>,
}
impl<V: 'static> AnyElement<V> {
- pub fn layout(
- &mut self,
- view: &mut V,
- cx: &mut LayoutContext<V>,
- ) -> Result<NodeId> {
+ pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
let pushed_text_style = self.push_text_style(cx);
let (node_id, layout) = self.element.layout(view, cx)?;
self.layout = Some((node_id, layout));
@@ -4511,30 +4477,25 @@ mod element {
}
pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) -> Result<()> {
let pushed_text_style = self.push_text_style(cx);
- let (layout_node_id, element_layout) = self
- .layout
- .as_mut()
- .expect("paint called before layout");
+ let (layout_node_id, element_layout) =
+ self.layout.as_mut().expect("paint called before layout");
let layout = Layout {
from_engine: cx
.layout_engine()
.unwrap()
.computed_layout(*layout_node_id)
- .expect(
- "you can currently only use playground elements within an adapter",
- ),
+ .expect("make sure you're using this within a gpui2 adapter element"),
from_element: element_layout.as_mut(),
};
let style = self.element.style();
let fill_color = style.fill.flatten().and_then(|fill| fill.color());
if let Some(fill_color) = fill_color {
- cx.scene
- .push_quad(gpui::scene::Quad {
- bounds: layout.from_engine.bounds,
- background: Some(fill_color.into()),
- border: Default::default(),
- corner_radii: Default::default(),
- });
+ cx.scene.push_quad(gpui::scene::Quad {
+ bounds: layout.from_engine.bounds,
+ background: Some(fill_color.into()),
+ border: Default::default(),
+ corner_radii: Default::default(),
+ });
}
for event_handler in self.element.handlers_mut().iter().cloned() {
let EngineLayout { order, bounds } = layout.from_engine;
@@ -4547,10 +4508,7 @@ mod element {
bounds,
outside_bounds: event_handler.outside_bounds,
event_handler: Rc::new(move |view, event, window_cx, view_id| {
- let mut view_context = ViewContext::mutable(
- window_cx,
- view_id,
- );
+ let mut view_context = ViewContext::mutable(window_cx, view_id);
let mut event_context = EventContext::new(&mut view_context);
view_event_handler(
view.downcast_mut().unwrap(),
@@ -4607,14 +4565,14 @@ mod element {
mod frame {
use crate::{
element::{
- AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext,
- NodeId, PaintContext,
+ AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext, NodeId,
+ PaintContext,
},
style::{OptionalStyle, Style},
};
use anyhow::{anyhow, Result};
use gpui::LayoutNodeId;
- use playground_macros::IntoElement;
+ use gpui2_macros::IntoElement;
#[element_crate = "crate"]
pub struct Frame<V: 'static> {
style: OptionalStyle,
@@ -4656,12 +4614,13 @@ mod frame {
let style: Style = self.style.into();
let node_id = cx
.layout_engine()
- .ok_or_else(|| ::anyhow::__private::must_use({
- let error = ::anyhow::__private::format_err(
- format_args!("no layout engine"),
- );
- error
- }))?
+ .ok_or_else(|| {
+ ::anyhow::__private::must_use({
+ let error =
+ ::anyhow::__private::format_err(format_args!("no layout engine"));
+ error
+ })
+ })?
.add_node(style.to_taffy(rem_size), child_layout_node_ids)?;
Ok((node_id, ()))
}
@@ -4687,18 +4646,23 @@ mod frame {
I: IntoIterator<Item = E>,
E: IntoElement<V>,
{
- self.children.extend(children.into_iter().map(|e| e.into_any_element()));
+ self.children
+ .extend(children.into_iter().map(|e| e.into_any_element()));
self
}
}
}
mod hoverable {
- use std::{cell::Cell, marker::PhantomData, rc::Rc};
+ use crate::{
+ element::Element,
+ style::{OptionalStyle, Style},
+ };
use gpui::{
geometry::{rect::RectF, vector::Vector2F},
- scene::MouseMove, EngineLayout,
+ scene::MouseMove,
+ EngineLayout,
};
- use crate::{element::Element, style::{OptionalStyle, Style}};
+ use std::{cell::Cell, marker::PhantomData, rc::Rc};
pub struct Hoverable<V, E> {
hover_style: OptionalStyle,
computed_style: Option<Style>,
@@ -4760,10 +4724,10 @@ mod hoverable {
}
}
mod paint_context {
- use std::{any::TypeId, rc::Rc};
use derive_more::{Deref, DerefMut};
use gpui::{geometry::rect::RectF, EventContext, RenderContext, ViewContext};
pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
+ use std::{any::TypeId, rc::Rc};
pub use taffy::tree::NodeId;
pub struct PaintContext<'a, 'b, 'c, 'd, V> {
#[deref]
@@ -4833,13 +4797,12 @@ mod paint_context {
mod style {
use crate::color::Hsla;
use gpui::geometry::{
- DefinedLength, Edges, Length, OptionalEdges, OptionalPoint, OptionalSize, Point,
- Size,
+ DefinedLength, Edges, Length, OptionalEdges, OptionalPoint, OptionalSize, Point, Size,
};
use optional::Optional;
pub use taffy::style::{
- AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap,
- JustifyContent, Overflow, Position,
+ AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
+ Overflow, Position,
};
pub struct Style {
/// What layout strategy should be used?
@@ -5194,9 +5157,7 @@ mod style {
#[inline]
fn clone(&self) -> Fill {
match self {
- Fill::Color(__self_0) => {
- Fill::Color(::core::clone::Clone::clone(__self_0))
- }
+ Fill::Color(__self_0) => Fill::Color(::core::clone::Clone::clone(__self_0)),
}
}
}
@@ -5257,32 +5218,25 @@ mod text {
let text = self.text.clone();
let layout = Arc::new(Mutex::new(None));
let style: Style = self.metadata.style.into();
- 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
- }
- },
- )?;
+ 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))
}
fn paint<'a>(
@@ -5369,11 +5323,11 @@ mod themes {
use crate::color::{Hsla, Lerp};
use std::ops::Range;
pub mod rose_pine {
- use std::ops::Range;
use crate::{
color::{hsla, rgb, Hsla},
ThemeColors,
};
+ use std::ops::Range;
pub struct RosePineThemes {
pub default: RosePinePalette,
pub dawn: RosePinePalette,
@@ -5426,7 +5380,7 @@ mod themes {
"highlight_med",
"highlight_high",
];
- let values: &[&dyn ::core::fmt::Debug] = &[
+ let values: &[&dyn::core::fmt::Debug] = &[
&self.base,
&self.surface,
&self.overlay,
@@ -5636,35 +5590,29 @@ 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);
- });
+ 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(|_| storybook(&rose_pine::moon())),
+ );
+ cx.platform().activate(true);
+ });
}
-fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
+fn storybook<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
frame()
.text_color(black())
.h_full()
.w_half()
.fill(theme.success(0.5))
- .child(
- button()
- .label("Hello")
- .click(|_, _, _| {
- ::std::io::_print(format_args!("click!\n"));
- }),
- )
+ .child(button().label("Hello").click(|_, _, _| {
+ ::std::io::_print(format_args!("click!\n"));
+ }))
}