mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-27 10:59:53 +00:00
Introduce gpui2 and storybook crates (#2919)
This PR splits `crates/gpui/playground` with two new crates: `gpui2` and `storybook`. GPUI 2 re-exports most of `gpui`, but makes some adjustments. I want to keep the scope focused. This isn't literally version 2.0 of GPUI or anything. It's just a convenient way to make a create with fewer dependencies where we can iterate quickly on aspects of GPUI's design. Most of the focus is on improving our approach to element layout and styling. The `storybook` crate is pretty empty for now, but it's where I intend to start rebuilding interfaces. I welcome anyone else to join me in this, though until I get one interface fully built, buyer beware. You may need to pair on it with me.
This commit is contained in:
commit
c3a3543ebb
111 changed files with 5059 additions and 3252 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
**/target
|
||||
**/cargo-target
|
||||
/zed.xcworkspace
|
||||
.DS_Store
|
||||
/plugins/bin
|
||||
|
|
75
Cargo.lock
generated
75
Cargo.lock
generated
|
@ -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",
|
||||
|
|
3
assets/icons/Icons/exit.svg
Normal file
3
assets/icons/Icons/exit.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 740 B |
5
assets/icons/stop_sharing.svg
Normal file
5
assets/icons/stop_sharing.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.6 KiB |
|
@ -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();
|
||||
let view_id = cx.view_id();
|
||||
cx.scene().push_mouse_region(
|
||||
MouseRegion::new::<EditorElementMouseHandlers>(view_id, view_id, visible_bounds)
|
||||
.on_down(MouseButton::Left, {
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::mouse_down(
|
||||
editor,
|
||||
event.platform_event,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
gutter_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_down(MouseButton::Right, {
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::mouse_right_down(
|
||||
editor,
|
||||
event.position,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event();
|
||||
})
|
||||
.on_down(MouseButton::Right, {
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::mouse_right_down(
|
||||
editor,
|
||||
event.position,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_up(MouseButton::Left, {
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::mouse_up(
|
||||
editor,
|
||||
event.position,
|
||||
event.cmd,
|
||||
event.shift,
|
||||
event.alt,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_drag(MouseButton::Left, {
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if event.end {
|
||||
return;
|
||||
})
|
||||
.on_up(MouseButton::Left, {
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::mouse_up(
|
||||
editor,
|
||||
event.position,
|
||||
event.cmd,
|
||||
event.shift,
|
||||
event.alt,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_drag(MouseButton::Left, {
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if event.end {
|
||||
return;
|
||||
}
|
||||
|
||||
if !Self::mouse_dragged(
|
||||
editor,
|
||||
event.platform_event,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
if !Self::mouse_dragged(
|
||||
editor,
|
||||
event.platform_event,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_move({
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::mouse_moved(
|
||||
editor,
|
||||
event.platform_event,
|
||||
&position_map,
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
})
|
||||
.on_move({
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::mouse_moved(
|
||||
editor,
|
||||
event.platform_event,
|
||||
&position_map,
|
||||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_move_out(move |_, editor: &mut Editor, cx| {
|
||||
if has_popovers {
|
||||
hide_hover(editor, cx);
|
||||
}
|
||||
})
|
||||
.on_scroll({
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::scroll(
|
||||
editor,
|
||||
event.position,
|
||||
*event.delta.raw(),
|
||||
event.delta.precise(),
|
||||
&position_map,
|
||||
bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
})
|
||||
.on_move_out(move |_, editor: &mut Editor, cx| {
|
||||
if has_popovers {
|
||||
hide_hover(editor, cx);
|
||||
}
|
||||
}
|
||||
}),
|
||||
})
|
||||
.on_scroll({
|
||||
let position_map = position_map.clone();
|
||||
move |event, editor, cx| {
|
||||
if !Self::scroll(
|
||||
editor,
|
||||
event.position,
|
||||
*event.delta.raw(),
|
||||
event.delta.precise(),
|
||||
&position_map,
|
||||
bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
enum GutterHandlers {}
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<GutterHandlers>(cx.view_id(), cx.view_id() + 1, gutter_bounds)
|
||||
.on_hover(|hover, editor: &mut Editor, cx| {
|
||||
let view_id = cx.view_id();
|
||||
let region_id = cx.view_id() + 1;
|
||||
cx.scene().push_mouse_region(
|
||||
MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
|
||||
|hover, editor: &mut Editor, cx| {
|
||||
editor.gutter_hover(
|
||||
&GutterHover {
|
||||
hovered: hover.started,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -528,24 +527,24 @@ impl EditorElement {
|
|||
|
||||
fn paint_background(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
gutter_bounds: RectF,
|
||||
text_bounds: RectF,
|
||||
layout: &LayoutState,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let bounds = gutter_bounds.union_rect(text_bounds);
|
||||
let scroll_top =
|
||||
layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: gutter_bounds,
|
||||
background: Some(self.style.gutter_background),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
border: Border::new(0., Color::transparent_black()).into(),
|
||||
corner_radii: Default::default(),
|
||||
});
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: text_bounds,
|
||||
background: Some(self.style.background),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
border: Border::new(0., Color::transparent_black()).into(),
|
||||
corner_radii: Default::default(),
|
||||
});
|
||||
|
||||
|
@ -570,10 +569,10 @@ impl EditorElement {
|
|||
bounds.width(),
|
||||
layout.position_map.line_height * (end_row - start_row + 1) as f32,
|
||||
);
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: RectF::new(origin, size),
|
||||
background: Some(self.style.active_line_background),
|
||||
border: Border::default(),
|
||||
border: Border::default().into(),
|
||||
corner_radii: Default::default(),
|
||||
});
|
||||
}
|
||||
|
@ -590,10 +589,10 @@ impl EditorElement {
|
|||
bounds.width(),
|
||||
layout.position_map.line_height * highlighted_rows.len() as f32,
|
||||
);
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: RectF::new(origin, size),
|
||||
background: Some(self.style.highlighted_line_background),
|
||||
border: Border::default(),
|
||||
border: Border::default().into(),
|
||||
corner_radii: Default::default(),
|
||||
});
|
||||
}
|
||||
|
@ -617,13 +616,13 @@ impl EditorElement {
|
|||
} else {
|
||||
self.style.wrap_guide
|
||||
};
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: RectF::new(
|
||||
vec2f(x, text_bounds.origin_y()),
|
||||
vec2f(1., text_bounds.height()),
|
||||
),
|
||||
background: Some(color),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
border: Border::new(0., Color::transparent_black()).into(),
|
||||
corner_radii: Default::default(),
|
||||
});
|
||||
}
|
||||
|
@ -632,12 +631,11 @@ impl EditorElement {
|
|||
|
||||
fn paint_gutter(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut LayoutState,
|
||||
editor: &mut Editor,
|
||||
cx: &mut PaintContext<Editor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
|
||||
|
@ -650,7 +648,7 @@ impl EditorElement {
|
|||
);
|
||||
|
||||
if show_gutter {
|
||||
Self::paint_diff_hunks(scene, bounds, layout, cx);
|
||||
Self::paint_diff_hunks(bounds, layout, cx);
|
||||
}
|
||||
|
||||
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
|
||||
|
@ -661,7 +659,7 @@ impl EditorElement {
|
|||
ix as f32 * line_height - (scroll_top % line_height),
|
||||
);
|
||||
|
||||
line.paint(scene, line_origin, visible_bounds, line_height, cx);
|
||||
line.paint(line_origin, visible_bounds, line_height, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,7 +676,7 @@ impl EditorElement {
|
|||
|
||||
let indicator_origin = bounds.origin() + position + centering_offset;
|
||||
|
||||
indicator.paint(scene, indicator_origin, visible_bounds, editor, cx);
|
||||
indicator.paint(indicator_origin, visible_bounds, editor, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -687,22 +685,11 @@ impl EditorElement {
|
|||
let mut y = *row as f32 * line_height - scroll_top;
|
||||
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
|
||||
y += (line_height - indicator.size().y()) / 2.;
|
||||
indicator.paint(
|
||||
scene,
|
||||
bounds.origin() + vec2f(x, y),
|
||||
visible_bounds,
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_diff_hunks(
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut ViewContext<Editor>) {
|
||||
let diff_style = &theme::current(cx).editor.diff.clone();
|
||||
let line_height = layout.position_map.line_height;
|
||||
|
||||
|
@ -721,10 +708,10 @@ impl EditorElement {
|
|||
let highlight_size = vec2f(width * 2., end_y - start_y);
|
||||
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
|
||||
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: highlight_bounds,
|
||||
background: Some(diff_style.modified),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
border: Border::new(0., Color::transparent_black()).into(),
|
||||
corner_radii: (1. * line_height).into(),
|
||||
});
|
||||
|
||||
|
@ -754,10 +741,10 @@ impl EditorElement {
|
|||
let highlight_size = vec2f(width * 2., end_y - start_y);
|
||||
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
|
||||
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: highlight_bounds,
|
||||
background: Some(diff_style.deleted),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
border: Border::new(0., Color::transparent_black()).into(),
|
||||
corner_radii: (1. * line_height).into(),
|
||||
});
|
||||
|
||||
|
@ -776,10 +763,10 @@ impl EditorElement {
|
|||
let highlight_size = vec2f(width * 2., end_y - start_y);
|
||||
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
|
||||
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: highlight_bounds,
|
||||
background: Some(color),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
border: Border::new(0., Color::transparent_black()).into(),
|
||||
corner_radii: (diff_style.corner_radius * line_height).into(),
|
||||
});
|
||||
}
|
||||
|
@ -787,12 +774,11 @@ impl EditorElement {
|
|||
|
||||
fn paint_text(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut LayoutState,
|
||||
editor: &mut Editor,
|
||||
cx: &mut PaintContext<Editor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let style = &self.style;
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
|
@ -804,9 +790,9 @@ impl EditorElement {
|
|||
let line_end_overshoot = 0.15 * layout.position_map.line_height;
|
||||
let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
|
||||
|
||||
scene.push_layer(Some(bounds));
|
||||
cx.scene().push_layer(Some(bounds));
|
||||
|
||||
scene.push_cursor_region(CursorRegion {
|
||||
cx.scene().push_cursor_region(CursorRegion {
|
||||
bounds,
|
||||
style: if !editor.link_go_to_definition_state.definitions.is_empty() {
|
||||
CursorStyle::PointingHand
|
||||
|
@ -819,7 +805,6 @@ impl EditorElement {
|
|||
self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
|
||||
for (id, range, color) in layout.fold_ranges.iter() {
|
||||
self.paint_highlighted_range(
|
||||
scene,
|
||||
range.clone(),
|
||||
*color,
|
||||
fold_corner_radius,
|
||||
|
@ -829,6 +814,7 @@ impl EditorElement {
|
|||
scroll_top,
|
||||
scroll_left,
|
||||
bounds,
|
||||
cx,
|
||||
);
|
||||
|
||||
for bound in range_to_bounds(
|
||||
|
@ -840,7 +826,7 @@ impl EditorElement {
|
|||
line_end_overshoot,
|
||||
&layout.position_map,
|
||||
) {
|
||||
scene.push_cursor_region(CursorRegion {
|
||||
cx.scene().push_cursor_region(CursorRegion {
|
||||
bounds: bound,
|
||||
style: CursorStyle::PointingHand,
|
||||
});
|
||||
|
@ -851,8 +837,9 @@ impl EditorElement {
|
|||
.to_point(&layout.position_map.snapshot.display_snapshot)
|
||||
.row;
|
||||
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<FoldMarkers>(cx.view_id(), *id as usize, bound)
|
||||
let view_id = cx.view_id();
|
||||
cx.scene().push_mouse_region(
|
||||
MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
|
||||
.on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
|
||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx)
|
||||
})
|
||||
|
@ -864,7 +851,6 @@ impl EditorElement {
|
|||
|
||||
for (range, color) in &layout.highlighted_ranges {
|
||||
self.paint_highlighted_range(
|
||||
scene,
|
||||
range.clone(),
|
||||
*color,
|
||||
0.,
|
||||
|
@ -874,6 +860,7 @@ impl EditorElement {
|
|||
scroll_top,
|
||||
scroll_left,
|
||||
bounds,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -891,7 +878,6 @@ impl EditorElement {
|
|||
|
||||
for selection in selections {
|
||||
self.paint_highlighted_range(
|
||||
scene,
|
||||
selection.range.clone(),
|
||||
selection_style.selection,
|
||||
corner_radius,
|
||||
|
@ -901,6 +887,7 @@ impl EditorElement {
|
|||
scroll_top,
|
||||
scroll_left,
|
||||
bounds,
|
||||
cx,
|
||||
);
|
||||
|
||||
if selection.is_local && !selection.range.is_empty() {
|
||||
|
@ -980,7 +967,6 @@ impl EditorElement {
|
|||
layout,
|
||||
row,
|
||||
scroll_top,
|
||||
scene,
|
||||
content_origin,
|
||||
scroll_left,
|
||||
visible_text_bounds,
|
||||
|
@ -992,14 +978,14 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
scene.paint_layer(Some(bounds), |scene| {
|
||||
for cursor in cursors {
|
||||
cursor.paint(scene, content_origin, cx);
|
||||
}
|
||||
});
|
||||
cx.scene().push_layer(Some(bounds));
|
||||
for cursor in cursors {
|
||||
cursor.paint(content_origin, cx);
|
||||
}
|
||||
cx.scene().pop_layer();
|
||||
|
||||
if let Some((position, context_menu)) = layout.context_menu.as_mut() {
|
||||
scene.push_stacking_context(None, None);
|
||||
cx.scene().push_stacking_context(None, None);
|
||||
let cursor_row_layout =
|
||||
&layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
|
||||
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
|
||||
|
@ -1019,18 +1005,17 @@ impl EditorElement {
|
|||
}
|
||||
|
||||
context_menu.paint(
|
||||
scene,
|
||||
list_origin,
|
||||
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
|
||||
editor,
|
||||
cx,
|
||||
);
|
||||
|
||||
scene.pop_stacking_context();
|
||||
cx.scene().pop_stacking_context();
|
||||
}
|
||||
|
||||
if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
|
||||
scene.push_stacking_context(None, None);
|
||||
cx.scene().push_stacking_context(None, None);
|
||||
|
||||
// This is safe because we check on layout whether the required row is available
|
||||
let hovered_row_layout =
|
||||
|
@ -1061,7 +1046,6 @@ impl EditorElement {
|
|||
}
|
||||
|
||||
hover_popover.paint(
|
||||
scene,
|
||||
popover_origin,
|
||||
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
|
||||
editor,
|
||||
|
@ -1083,7 +1067,6 @@ impl EditorElement {
|
|||
}
|
||||
|
||||
hover_popover.paint(
|
||||
scene,
|
||||
popover_origin,
|
||||
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
|
||||
editor,
|
||||
|
@ -1094,10 +1077,10 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
scene.pop_stacking_context();
|
||||
cx.scene().pop_stacking_context();
|
||||
}
|
||||
|
||||
scene.pop_layer();
|
||||
cx.scene().pop_layer();
|
||||
}
|
||||
|
||||
fn scrollbar_left(&self, bounds: &RectF) -> f32 {
|
||||
|
@ -1106,11 +1089,10 @@ impl EditorElement {
|
|||
|
||||
fn paint_scrollbar(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
editor: &Editor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
enum ScrollbarMouseHandlers {}
|
||||
if layout.mode != EditorMode::Full {
|
||||
|
@ -1147,9 +1129,9 @@ impl EditorElement {
|
|||
let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
|
||||
|
||||
if layout.show_scrollbars {
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: track_bounds,
|
||||
border: style.track.border,
|
||||
border: style.track.border.into(),
|
||||
background: style.track.background_color,
|
||||
..Default::default()
|
||||
});
|
||||
|
@ -1177,10 +1159,10 @@ impl EditorElement {
|
|||
}
|
||||
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
|
||||
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(color),
|
||||
border,
|
||||
border: border.into(),
|
||||
corner_radii: style.thumb.corner_radii.into(),
|
||||
})
|
||||
};
|
||||
|
@ -1237,29 +1219,30 @@ impl EditorElement {
|
|||
left: true,
|
||||
};
|
||||
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(color),
|
||||
border,
|
||||
border: border.into(),
|
||||
corner_radii: style.thumb.corner_radii.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds: thumb_bounds,
|
||||
border: style.thumb.border,
|
||||
border: style.thumb.border.into(),
|
||||
background: style.thumb.background_color,
|
||||
corner_radii: style.thumb.corner_radii.into(),
|
||||
});
|
||||
}
|
||||
|
||||
scene.push_cursor_region(CursorRegion {
|
||||
cx.scene().push_cursor_region(CursorRegion {
|
||||
bounds: track_bounds,
|
||||
style: CursorStyle::Arrow,
|
||||
});
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<ScrollbarMouseHandlers>(cx.view_id(), cx.view_id(), track_bounds)
|
||||
let region_id = cx.view_id();
|
||||
cx.scene().push_mouse_region(
|
||||
MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
|
||||
.on_move(move |event, editor: &mut Editor, cx| {
|
||||
if event.pressed_button.is_none() {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
|
@ -1305,7 +1288,6 @@ impl EditorElement {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn paint_highlighted_range(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
range: Range<DisplayPoint>,
|
||||
color: Color,
|
||||
corner_radius: f32,
|
||||
|
@ -1315,6 +1297,7 @@ impl EditorElement {
|
|||
scroll_top: f32,
|
||||
scroll_left: f32,
|
||||
bounds: RectF,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let start_row = layout.visible_display_row_range.start;
|
||||
let end_row = layout.visible_display_row_range.end;
|
||||
|
@ -1358,18 +1341,17 @@ impl EditorElement {
|
|||
.collect(),
|
||||
};
|
||||
|
||||
highlighted_range.paint(bounds, scene);
|
||||
highlighted_range.paint(bounds, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_blocks(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut LayoutState,
|
||||
editor: &mut Editor,
|
||||
cx: &mut PaintContext<Editor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
let scroll_left = scroll_position.x() * layout.position_map.em_width;
|
||||
|
@ -1384,9 +1366,7 @@ impl EditorElement {
|
|||
if !matches!(block.style, BlockStyle::Sticky) {
|
||||
origin += vec2f(-scroll_left, 0.);
|
||||
}
|
||||
block
|
||||
.element
|
||||
.paint(scene, origin, visible_bounds, editor, cx);
|
||||
block.element.paint(origin, visible_bounds, editor, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1690,7 +1670,7 @@ impl EditorElement {
|
|||
style: &EditorStyle,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
editor: &mut Editor,
|
||||
cx: &mut LayoutContext<Editor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (f32, Vec<BlockLayout>) {
|
||||
let mut block_id = 0;
|
||||
let scroll_x = snapshot.scroll_anchor.offset.x();
|
||||
|
@ -2022,7 +2002,6 @@ impl LineWithInvisibles {
|
|||
layout: &LayoutState,
|
||||
row: u32,
|
||||
scroll_top: f32,
|
||||
scene: &mut SceneBuilder,
|
||||
content_origin: Vector2F,
|
||||
scroll_left: f32,
|
||||
visible_text_bounds: RectF,
|
||||
|
@ -2035,7 +2014,6 @@ impl LineWithInvisibles {
|
|||
let line_y = row as f32 * line_height - scroll_top;
|
||||
|
||||
self.line.paint(
|
||||
scene,
|
||||
content_origin + vec2f(-scroll_left, line_y),
|
||||
visible_text_bounds,
|
||||
line_height,
|
||||
|
@ -2049,7 +2027,6 @@ impl LineWithInvisibles {
|
|||
scroll_left,
|
||||
line_y,
|
||||
row,
|
||||
scene,
|
||||
visible_bounds,
|
||||
line_height,
|
||||
whitespace_setting,
|
||||
|
@ -2065,7 +2042,6 @@ impl LineWithInvisibles {
|
|||
scroll_left: f32,
|
||||
line_y: f32,
|
||||
row: u32,
|
||||
scene: &mut SceneBuilder,
|
||||
visible_bounds: RectF,
|
||||
line_height: f32,
|
||||
whitespace_setting: ShowWhitespaceSetting,
|
||||
|
@ -2097,7 +2073,7 @@ impl LineWithInvisibles {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
invisible_symbol.paint(scene, origin, visible_bounds, line_height, cx);
|
||||
invisible_symbol.paint(origin, visible_bounds, line_height, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2116,7 +2092,7 @@ impl Element<Editor> for EditorElement {
|
|||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
editor: &mut Editor,
|
||||
cx: &mut LayoutContext<Editor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let mut size = constraint.max;
|
||||
if size.x().is_infinite() {
|
||||
|
@ -2590,15 +2566,14 @@ impl Element<Editor> for EditorElement {
|
|||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
editor: &mut Editor,
|
||||
cx: &mut PaintContext<Editor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
scene.push_layer(Some(visible_bounds));
|
||||
cx.scene().push_layer(Some(visible_bounds));
|
||||
|
||||
let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
|
||||
let text_bounds = RectF::new(
|
||||
|
@ -2607,7 +2582,6 @@ impl Element<Editor> for EditorElement {
|
|||
);
|
||||
|
||||
Self::attach_mouse_handlers(
|
||||
scene,
|
||||
&layout.position_map,
|
||||
layout.hover_popovers.is_some(),
|
||||
visible_bounds,
|
||||
|
@ -2617,20 +2591,19 @@ impl Element<Editor> for EditorElement {
|
|||
cx,
|
||||
);
|
||||
|
||||
self.paint_background(scene, gutter_bounds, text_bounds, layout);
|
||||
self.paint_background(gutter_bounds, text_bounds, layout, cx);
|
||||
if layout.gutter_size.x() > 0. {
|
||||
self.paint_gutter(scene, gutter_bounds, visible_bounds, layout, editor, cx);
|
||||
self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
|
||||
}
|
||||
self.paint_text(scene, text_bounds, visible_bounds, layout, editor, cx);
|
||||
self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
|
||||
|
||||
scene.push_layer(Some(bounds));
|
||||
cx.scene().push_layer(Some(bounds));
|
||||
if !layout.blocks.is_empty() {
|
||||
self.paint_blocks(scene, bounds, visible_bounds, layout, editor, cx);
|
||||
self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
|
||||
}
|
||||
self.paint_scrollbar(scene, bounds, layout, cx, &editor);
|
||||
scene.pop_layer();
|
||||
|
||||
scene.pop_layer();
|
||||
self.paint_scrollbar(bounds, layout, &editor, cx);
|
||||
cx.scene().pop_layer();
|
||||
cx.scene().pop_layer();
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
@ -2873,7 +2846,7 @@ impl Cursor {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn paint(&self, scene: &mut SceneBuilder, origin: Vector2F, cx: &mut WindowContext) {
|
||||
pub fn paint(&self, origin: Vector2F, cx: &mut WindowContext) {
|
||||
let bounds = match self.shape {
|
||||
CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)),
|
||||
CursorShape::Block | CursorShape::Hollow => RectF::new(
|
||||
|
@ -2888,14 +2861,14 @@ impl Cursor {
|
|||
|
||||
//Draw background or border quad
|
||||
if matches!(self.shape, CursorShape::Hollow) {
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds,
|
||||
background: None,
|
||||
border: Border::all(1., self.color),
|
||||
border: Border::all(1., self.color).into(),
|
||||
corner_radii: Default::default(),
|
||||
});
|
||||
} else {
|
||||
scene.push_quad(Quad {
|
||||
cx.scene().push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(self.color),
|
||||
border: Default::default(),
|
||||
|
@ -2904,7 +2877,7 @@ impl Cursor {
|
|||
}
|
||||
|
||||
if let Some(block_text) = &self.block_text {
|
||||
block_text.paint(scene, self.origin + origin, bounds, self.line_height, cx);
|
||||
block_text.paint(self.origin + origin, bounds, self.line_height, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2929,17 +2902,17 @@ pub struct HighlightedRangeLine {
|
|||
}
|
||||
|
||||
impl HighlightedRange {
|
||||
pub fn paint(&self, bounds: RectF, scene: &mut SceneBuilder) {
|
||||
pub fn paint(&self, bounds: RectF, cx: &mut WindowContext) {
|
||||
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
|
||||
self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
|
||||
self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
|
||||
self.paint_lines(
|
||||
self.start_y + self.line_height,
|
||||
&self.lines[1..],
|
||||
bounds,
|
||||
scene,
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
self.paint_lines(self.start_y, &self.lines, bounds, scene);
|
||||
self.paint_lines(self.start_y, &self.lines, bounds, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2948,7 +2921,7 @@ impl HighlightedRange {
|
|||
start_y: f32,
|
||||
lines: &[HighlightedRangeLine],
|
||||
bounds: RectF,
|
||||
scene: &mut SceneBuilder,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if lines.is_empty() {
|
||||
return;
|
||||
|
@ -3046,7 +3019,7 @@ impl HighlightedRange {
|
|||
}
|
||||
path.line_to(first_top_right - top_curve_width);
|
||||
|
||||
scene.push_path(path.build(self.color, Some(bounds)));
|
||||
cx.scene().push_path(path.build(self.color, Some(bounds)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3204,18 +3177,10 @@ mod tests {
|
|||
Point::new(5, 6)..Point::new(6, 0),
|
||||
]);
|
||||
});
|
||||
let mut new_parents = Default::default();
|
||||
let mut notify_views_if_parents_change = Default::default();
|
||||
let mut layout_cx = LayoutContext::new(
|
||||
cx,
|
||||
&mut new_parents,
|
||||
&mut notify_views_if_parents_change,
|
||||
false,
|
||||
);
|
||||
element.layout(
|
||||
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
|
||||
editor,
|
||||
&mut layout_cx,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
assert_eq!(state.selections.len(), 1);
|
||||
|
@ -3296,18 +3261,10 @@ mod tests {
|
|||
DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
|
||||
]);
|
||||
});
|
||||
let mut new_parents = Default::default();
|
||||
let mut notify_views_if_parents_change = Default::default();
|
||||
let mut layout_cx = LayoutContext::new(
|
||||
cx,
|
||||
&mut new_parents,
|
||||
&mut notify_views_if_parents_change,
|
||||
false,
|
||||
);
|
||||
element.layout(
|
||||
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
|
||||
editor,
|
||||
&mut layout_cx,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -3363,18 +3320,10 @@ mod tests {
|
|||
|
||||
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
let (size, mut state) = editor.update(cx, |editor, cx| {
|
||||
let mut new_parents = Default::default();
|
||||
let mut notify_views_if_parents_change = Default::default();
|
||||
let mut layout_cx = LayoutContext::new(
|
||||
cx,
|
||||
&mut new_parents,
|
||||
&mut notify_views_if_parents_change,
|
||||
false,
|
||||
);
|
||||
element.layout(
|
||||
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
|
||||
editor,
|
||||
&mut layout_cx,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -3389,17 +3338,9 @@ mod tests {
|
|||
);
|
||||
|
||||
// Don't panic.
|
||||
let mut scene = SceneBuilder::new(1.0);
|
||||
let bounds = RectF::new(Default::default(), size);
|
||||
editor.update(cx, |editor, cx| {
|
||||
element.paint(
|
||||
&mut scene,
|
||||
bounds,
|
||||
bounds,
|
||||
&mut state,
|
||||
editor,
|
||||
&mut PaintContext::new(cx),
|
||||
);
|
||||
element.paint(bounds, bounds, &mut state, editor, cx);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3567,18 +3508,10 @@ mod tests {
|
|||
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_wrap_width(Some(editor_width), cx);
|
||||
|
||||
let mut new_parents = Default::default();
|
||||
let mut notify_views_if_parents_change = Default::default();
|
||||
let mut layout_cx = LayoutContext::new(
|
||||
cx,
|
||||
&mut new_parents,
|
||||
&mut notify_views_if_parents_change,
|
||||
false,
|
||||
);
|
||||
element.layout(
|
||||
SizeConstraint::new(vec2f(editor_width, 500.), vec2f(editor_width, 500.)),
|
||||
editor,
|
||||
&mut layout_cx,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
|
|
|
@ -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,6 +3361,59 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
|
|||
) -> ElementStateHandle<T> {
|
||||
self.element_state_dynamic::<T>(tag, element_id, T::default())
|
||||
}
|
||||
|
||||
/// Return keystrokes that would dispatch the given action on the given view.
|
||||
pub(crate) fn keystrokes_for_action(
|
||||
&mut self,
|
||||
view_id: usize,
|
||||
action: &dyn Action,
|
||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||
self.notify_if_view_ancestors_change(view_id);
|
||||
|
||||
let window = self.window_handle;
|
||||
let mut contexts = Vec::new();
|
||||
let mut handler_depth = None;
|
||||
for (i, view_id) in self.ancestors(view_id).enumerate() {
|
||||
if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
|
||||
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||
if actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(i);
|
||||
}
|
||||
}
|
||||
contexts.push(view_metadata.keymap_context.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if self.global_actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(contexts.len())
|
||||
}
|
||||
|
||||
let handler_depth = handler_depth.unwrap_or(0);
|
||||
(0..=handler_depth).find_map(|depth| {
|
||||
let contexts = &contexts[depth..];
|
||||
self.keystroke_matcher
|
||||
.keystrokes_for_action(action, contexts)
|
||||
})
|
||||
}
|
||||
|
||||
fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
|
||||
let self_view_id = self.view_id;
|
||||
self.window
|
||||
.views_to_notify_if_ancestors_change
|
||||
.entry(view_id)
|
||||
.or_default()
|
||||
.push(self_view_id);
|
||||
}
|
||||
|
||||
pub fn paint_layer<F, R>(&mut self, clip_bounds: Option<RectF>, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.scene().push_layer(clip_bounds);
|
||||
let result = f(self);
|
||||
self.scene().pop_layer();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> ViewContext<'_, '_, V> {
|
||||
|
@ -3447,265 +3505,6 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Methods shared by both LayoutContext and PaintContext
|
||||
///
|
||||
/// It's that PaintContext should be implemented in terms of layout context and
|
||||
/// deref to it, in which case we wouldn't need this.
|
||||
pub trait RenderContext<'a, 'b, V> {
|
||||
fn text_style(&self) -> TextStyle;
|
||||
fn push_text_style(&mut self, style: TextStyle);
|
||||
fn pop_text_style(&mut self);
|
||||
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V>;
|
||||
}
|
||||
|
||||
pub struct LayoutContext<'a, 'b, 'c, V> {
|
||||
// Nathan: Making this is public while I work on playground.
|
||||
pub view_context: &'c mut ViewContext<'a, 'b, V>,
|
||||
new_parents: &'c mut HashMap<usize, usize>,
|
||||
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
|
||||
text_style_stack: Vec<TextStyle>,
|
||||
pub refreshing: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
|
||||
pub fn new(
|
||||
view_context: &'c mut ViewContext<'a, 'b, V>,
|
||||
new_parents: &'c mut HashMap<usize, usize>,
|
||||
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
|
||||
refreshing: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
view_context,
|
||||
new_parents,
|
||||
views_to_notify_if_ancestors_change,
|
||||
text_style_stack: Vec::new(),
|
||||
refreshing,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
|
||||
self.view_context
|
||||
}
|
||||
|
||||
/// Return keystrokes that would dispatch the given action on the given view.
|
||||
pub(crate) fn keystrokes_for_action(
|
||||
&mut self,
|
||||
view_id: usize,
|
||||
action: &dyn Action,
|
||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||
self.notify_if_view_ancestors_change(view_id);
|
||||
|
||||
let window = self.window_handle;
|
||||
let mut contexts = Vec::new();
|
||||
let mut handler_depth = None;
|
||||
for (i, view_id) in self.ancestors(view_id).enumerate() {
|
||||
if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
|
||||
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||
if actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(i);
|
||||
}
|
||||
}
|
||||
contexts.push(view_metadata.keymap_context.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if self.global_actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(contexts.len())
|
||||
}
|
||||
|
||||
let handler_depth = handler_depth.unwrap_or(0);
|
||||
(0..=handler_depth).find_map(|depth| {
|
||||
let contexts = &contexts[depth..];
|
||||
self.keystroke_matcher
|
||||
.keystrokes_for_action(action, contexts)
|
||||
})
|
||||
}
|
||||
|
||||
fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
|
||||
let self_view_id = self.view_id;
|
||||
self.views_to_notify_if_ancestors_change
|
||||
.entry(view_id)
|
||||
.or_default()
|
||||
.push(self_view_id);
|
||||
}
|
||||
|
||||
pub fn with_text_style<F, T>(&mut self, style: TextStyle, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Self) -> T,
|
||||
{
|
||||
self.push_text_style(style);
|
||||
let result = f(self);
|
||||
self.pop_text_style();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, 'c, V> {
|
||||
fn text_style(&self) -> TextStyle {
|
||||
self.text_style_stack
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or(TextStyle::default(&self.font_cache))
|
||||
}
|
||||
|
||||
fn push_text_style(&mut self, style: TextStyle) {
|
||||
self.text_style_stack.push(style);
|
||||
}
|
||||
|
||||
fn pop_text_style(&mut self) {
|
||||
self.text_style_stack.pop();
|
||||
}
|
||||
|
||||
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
|
||||
&mut self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V> Deref for LayoutContext<'a, 'b, 'c, V> {
|
||||
type Target = ViewContext<'a, 'b, V>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> DerefMut for LayoutContext<'_, '_, '_, V> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
|
||||
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
|
||||
BorrowAppContext::read_with(&*self.view_context, f)
|
||||
}
|
||||
|
||||
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
|
||||
BorrowAppContext::update(&mut *self.view_context, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
|
||||
type Result<T> = T;
|
||||
|
||||
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
|
||||
BorrowWindowContext::read_window(&*self.view_context, window, f)
|
||||
}
|
||||
|
||||
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&WindowContext) -> Option<T>,
|
||||
{
|
||||
BorrowWindowContext::read_window_optional(&*self.view_context, window, f)
|
||||
}
|
||||
|
||||
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
f: F,
|
||||
) -> T {
|
||||
BorrowWindowContext::update_window(&mut *self.view_context, window, f)
|
||||
}
|
||||
|
||||
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut WindowContext) -> Option<T>,
|
||||
{
|
||||
BorrowWindowContext::update_window_optional(&mut *self.view_context, window, f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PaintContext<'a, 'b, 'c, V> {
|
||||
pub view_context: &'c mut ViewContext<'a, 'b, V>,
|
||||
text_style_stack: Vec<TextStyle>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
|
||||
pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
|
||||
Self {
|
||||
view_context,
|
||||
text_style_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for PaintContext<'a, 'b, 'c, V> {
|
||||
fn text_style(&self) -> TextStyle {
|
||||
self.text_style_stack
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or(TextStyle::default(&self.font_cache))
|
||||
}
|
||||
|
||||
fn push_text_style(&mut self, style: TextStyle) {
|
||||
self.text_style_stack.push(style);
|
||||
}
|
||||
|
||||
fn pop_text_style(&mut self) {
|
||||
self.text_style_stack.pop();
|
||||
}
|
||||
|
||||
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
|
||||
&mut self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V> Deref for PaintContext<'a, 'b, 'c, V> {
|
||||
type Target = ViewContext<'a, 'b, V>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> DerefMut for PaintContext<'_, '_, '_, V> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> BorrowAppContext for PaintContext<'_, '_, '_, V> {
|
||||
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
|
||||
BorrowAppContext::read_with(&*self.view_context, f)
|
||||
}
|
||||
|
||||
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
|
||||
BorrowAppContext::update(&mut *self.view_context, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
|
||||
type Result<T> = T;
|
||||
|
||||
fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
|
||||
where
|
||||
F: FnOnce(&WindowContext) -> T,
|
||||
{
|
||||
BorrowWindowContext::read_window(self.view_context, window, f)
|
||||
}
|
||||
|
||||
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&WindowContext) -> Option<T>,
|
||||
{
|
||||
BorrowWindowContext::read_window_optional(self.view_context, window, f)
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
|
||||
where
|
||||
F: FnOnce(&mut WindowContext) -> T,
|
||||
{
|
||||
BorrowWindowContext::update_window(self.view_context, window, f)
|
||||
}
|
||||
|
||||
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut WindowContext) -> Option<T>,
|
||||
{
|
||||
BorrowWindowContext::update_window_optional(self.view_context, window, f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventContext<'a, 'b, 'c, V> {
|
||||
view_context: &'c mut ViewContext<'a, 'b, V>,
|
||||
pub(crate) handled: bool,
|
||||
|
@ -6555,32 +6354,21 @@ mod tests {
|
|||
view_1.update(cx, |_, cx| {
|
||||
view_2.update(cx, |_, cx| {
|
||||
// Sanity check
|
||||
let mut new_parents = Default::default();
|
||||
let mut notify_views_if_parents_change = Default::default();
|
||||
let mut layout_cx = LayoutContext::new(
|
||||
cx,
|
||||
&mut new_parents,
|
||||
&mut notify_views_if_parents_change,
|
||||
false,
|
||||
);
|
||||
assert_eq!(
|
||||
layout_cx
|
||||
.keystrokes_for_action(view_1_id, &Action1)
|
||||
cx.keystrokes_for_action(view_1_id, &Action1)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
&[Keystroke::parse("a").unwrap()]
|
||||
);
|
||||
assert_eq!(
|
||||
layout_cx
|
||||
.keystrokes_for_action(view_2.id(), &Action2)
|
||||
cx.keystrokes_for_action(view_2.id(), &Action2)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
&[Keystroke::parse("b").unwrap()]
|
||||
);
|
||||
assert_eq!(layout_cx.keystrokes_for_action(view_1.id(), &Action3), None);
|
||||
assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action3), None);
|
||||
assert_eq!(
|
||||
layout_cx
|
||||
.keystrokes_for_action(view_2.id(), &Action3)
|
||||
cx.keystrokes_for_action(view_2.id(), &Action3)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
&[Keystroke::parse("c").unwrap()]
|
||||
|
@ -6589,21 +6377,17 @@ mod tests {
|
|||
// The 'a' keystroke propagates up the view tree from view_2
|
||||
// to view_1. The action, Action1, is handled by view_1.
|
||||
assert_eq!(
|
||||
layout_cx
|
||||
.keystrokes_for_action(view_2.id(), &Action1)
|
||||
cx.keystrokes_for_action(view_2.id(), &Action1)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
&[Keystroke::parse("a").unwrap()]
|
||||
);
|
||||
|
||||
// Actions that are handled below the current view don't have bindings
|
||||
assert_eq!(layout_cx.keystrokes_for_action(view_1_id, &Action2), None);
|
||||
assert_eq!(cx.keystrokes_for_action(view_1_id, &Action2), None);
|
||||
|
||||
// Actions that are handled in other branches of the tree should not have a binding
|
||||
assert_eq!(
|
||||
layout_cx.keystrokes_for_action(view_2.id(), &GlobalAction),
|
||||
None
|
||||
);
|
||||
assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
|
||||
TextStyle {
|
||||
color: refinement.color.unwrap_or(self.color),
|
||||
font_family_name: refinement
|
||||
.font_family_name
|
||||
.unwrap_or_else(|| self.font_family_name.clone()),
|
||||
font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
|
||||
font_id: refinement.font_id.unwrap_or(self.font_id),
|
||||
font_size: refinement.font_size.unwrap_or(self.font_size),
|
||||
font_properties: refinement.font_properties.unwrap_or(self.font_properties),
|
||||
underline: refinement.underline.unwrap_or(self.underline),
|
||||
soft_wrap: refinement.soft_wrap.unwrap_or(self.soft_wrap),
|
||||
impl TextStyle {
|
||||
pub fn refine(
|
||||
&mut self,
|
||||
refinement: &TextStyleRefinement,
|
||||
font_cache: &FontCache,
|
||||
) -> Result<()> {
|
||||
if let Some(font_size) = refinement.font_size {
|
||||
self.font_size = font_size;
|
||||
}
|
||||
if let Some(color) = refinement.color {
|
||||
self.color = color;
|
||||
}
|
||||
if let Some(underline) = refinement.underline {
|
||||
self.underline = underline;
|
||||
}
|
||||
|
||||
let mut update_font_id = false;
|
||||
if let Some(font_family) = refinement.font_family.clone() {
|
||||
self.font_family_id = font_cache.load_family(&[&font_family], &Default::default())?;
|
||||
self.font_family_name = font_family;
|
||||
update_font_id = true;
|
||||
}
|
||||
if let Some(font_weight) = refinement.font_weight {
|
||||
self.font_properties.weight = font_weight;
|
||||
update_font_id = true;
|
||||
}
|
||||
if let Some(font_style) = refinement.font_style {
|
||||
self.font_properties.style = font_style;
|
||||
update_font_id = true;
|
||||
}
|
||||
|
||||
if update_font_id {
|
||||
self.font_id = font_cache.select_font(self.font_family_id, &self.font_properties)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TextStyleRefinement {
|
||||
pub color: Option<Color>,
|
||||
pub font_family: Option<Arc<str>>,
|
||||
pub font_size: Option<f32>,
|
||||
pub font_weight: Option<Weight>,
|
||||
pub font_style: Option<Style>,
|
||||
pub underline: Option<Underline>,
|
||||
}
|
||||
|
||||
impl Refineable for TextStyleRefinement {
|
||||
type Refinement = Self;
|
||||
|
||||
fn refine(&mut self, refinement: &Self::Refinement) {
|
||||
if refinement.color.is_some() {
|
||||
self.color = refinement.color;
|
||||
}
|
||||
if refinement.font_family.is_some() {
|
||||
self.font_family = refinement.font_family.clone();
|
||||
}
|
||||
if refinement.font_size.is_some() {
|
||||
self.font_size = refinement.font_size;
|
||||
}
|
||||
if refinement.font_weight.is_some() {
|
||||
self.font_weight = refinement.font_weight;
|
||||
}
|
||||
if refinement.font_style.is_some() {
|
||||
self.font_style = refinement.font_style;
|
||||
}
|
||||
if refinement.underline.is_some() {
|
||||
self.underline = refinement.underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use super::scene::{Path, PathVertex};
|
||||
use crate::{color::Color, json::ToJson};
|
||||
pub use pathfinder_geometry::*;
|
||||
|
@ -133,13 +135,14 @@ impl ToJson for RectF {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Refineable)]
|
||||
pub struct Point<T: Clone + Default> {
|
||||
#[derive(Refineable, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Point<T: Clone + Default + Debug> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T: Clone + Default> Clone for Point<T> {
|
||||
impl<T: Clone + Default + Debug> Clone for Point<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
x: self.x.clone(),
|
||||
|
@ -148,7 +151,7 @@ impl<T: Clone + Default> Clone for Point<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
|
||||
impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
|
||||
fn into(self) -> taffy::geometry::Point<T> {
|
||||
taffy::geometry::Point {
|
||||
x: self.x,
|
||||
|
@ -157,13 +160,14 @@ impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Refineable)]
|
||||
pub struct Size<T: Clone + Default> {
|
||||
#[derive(Refineable, Clone, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Size<T: Clone + Default + Debug> {
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
}
|
||||
|
||||
impl<S, T: Clone + Default> From<taffy::geometry::Size<S>> for Size<T>
|
||||
impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
|
||||
where
|
||||
S: Into<T>,
|
||||
{
|
||||
|
@ -175,7 +179,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, T: Clone + Default> Into<taffy::geometry::Size<S>> for Size<T>
|
||||
impl<S, T: Clone + Default + Debug> Into<taffy::geometry::Size<S>> for Size<T>
|
||||
where
|
||||
T: Into<S>,
|
||||
{
|
||||
|
@ -222,34 +226,15 @@ impl Size<Length> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Refineable)]
|
||||
pub struct Edges<T: Clone + Default> {
|
||||
#[derive(Clone, Default, Refineable, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Edges<T: Clone + Default + Debug> {
|
||||
pub top: T,
|
||||
pub right: T,
|
||||
pub bottom: T,
|
||||
pub left: T,
|
||||
}
|
||||
|
||||
impl Edges<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: pixels(0.),
|
||||
right: pixels(0.),
|
||||
bottom: pixels(0.),
|
||||
left: pixels(0.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
|
||||
taffy::geometry::Rect {
|
||||
top: self.top.to_taffy(rem_size),
|
||||
right: self.right.to_taffy(rem_size),
|
||||
bottom: self.bottom.to_taffy(rem_size),
|
||||
left: self.left.to_taffy(rem_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<Length> {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
|
@ -282,12 +267,76 @@ impl Edges<Length> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Edges<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: pixels(0.),
|
||||
right: pixels(0.),
|
||||
bottom: pixels(0.),
|
||||
left: pixels(0.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
|
||||
taffy::geometry::Rect {
|
||||
top: self.top.to_taffy(rem_size),
|
||||
right: self.right.to_taffy(rem_size),
|
||||
bottom: self.bottom.to_taffy(rem_size),
|
||||
left: self.left.to_taffy(rem_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<AbsoluteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: pixels(0.),
|
||||
right: pixels(0.),
|
||||
bottom: pixels(0.),
|
||||
left: pixels(0.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
|
||||
taffy::geometry::Rect {
|
||||
top: self.top.to_taffy(rem_size),
|
||||
right: self.right.to_taffy(rem_size),
|
||||
bottom: self.bottom.to_taffy(rem_size),
|
||||
left: self.left.to_taffy(rem_size),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pixels(&self, rem_size: f32) -> Edges<f32> {
|
||||
Edges {
|
||||
top: self.top.to_pixels(rem_size),
|
||||
right: self.right.to_pixels(rem_size),
|
||||
bottom: self.bottom.to_pixels(rem_size),
|
||||
left: self.left.to_pixels(rem_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<f32> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum AbsoluteLength {
|
||||
Pixels(f32),
|
||||
Rems(f32),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AbsoluteLength {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(pixels) => write!(f, "{}px", pixels),
|
||||
AbsoluteLength::Rems(rems) => write!(f, "{}rems", rems),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AbsoluteLength {
|
||||
pub fn to_pixels(&self, rem_size: f32) -> f32 {
|
||||
match self {
|
||||
|
@ -295,6 +344,13 @@ impl AbsoluteLength {
|
|||
AbsoluteLength::Rems(rems) => rems * rem_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
|
||||
AbsoluteLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AbsoluteLength {
|
||||
|
@ -307,7 +363,7 @@ impl Default for AbsoluteLength {
|
|||
#[derive(Clone, Copy)]
|
||||
pub enum DefiniteLength {
|
||||
Absolute(AbsoluteLength),
|
||||
Relative(f32), // Percent, from 0 to 100.
|
||||
Relative(f32), // 0. to 1.
|
||||
}
|
||||
|
||||
impl DefiniteLength {
|
||||
|
@ -326,6 +382,15 @@ impl DefiniteLength {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DefiniteLength {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => std::fmt::Debug::fmt(length, f),
|
||||
DefiniteLength::Relative(fract) => write!(f, "{}%", (fract * 100.0) as i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsoluteLength> for DefiniteLength {
|
||||
fn from(length: AbsoluteLength) -> Self {
|
||||
Self::Absolute(length)
|
||||
|
@ -345,6 +410,15 @@ pub enum Length {
|
|||
Auto,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Length {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
|
||||
Length::Auto => write!(f, "auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
|
||||
DefiniteLength::Relative(fraction).into()
|
||||
}
|
||||
|
@ -387,3 +461,9 @@ impl Default for Length {
|
|||
Self::Definite(DefiniteLength::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for Length {
|
||||
fn from(_: ()) -> Self {
|
||||
Self::Definite(DefiniteLength::default())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
99
crates/gpui/src/image_cache.rs
Normal file
99
crates/gpui/src/image_cache.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::ImageData;
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
future::{BoxFuture, Shared},
|
||||
AsyncReadExt, FutureExt,
|
||||
};
|
||||
use image::ImageError;
|
||||
use parking_lot::Mutex;
|
||||
use thiserror::Error;
|
||||
use util::{
|
||||
arc_cow::ArcCow,
|
||||
http::{self, HttpClient},
|
||||
};
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum Error {
|
||||
#[error("http error: {0}")]
|
||||
Client(#[from] http::Error),
|
||||
#[error("IO error: {0}")]
|
||||
Io(Arc<std::io::Error>),
|
||||
#[error("unexpected http status: {status}, body: {body}")]
|
||||
BadStatus {
|
||||
status: http::StatusCode,
|
||||
body: String,
|
||||
},
|
||||
#[error("image error: {0}")]
|
||||
Image(Arc<ImageError>),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
Error::Io(Arc::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageError> for Error {
|
||||
fn from(error: ImageError) -> Self {
|
||||
Error::Image(Arc::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageCache {
|
||||
client: Arc<dyn HttpClient>,
|
||||
images: Arc<Mutex<HashMap<ArcCow<'static, str>, FetchImageFuture>>>,
|
||||
}
|
||||
|
||||
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
|
||||
|
||||
impl ImageCache {
|
||||
pub fn new(client: Arc<dyn HttpClient>) -> Self {
|
||||
ImageCache {
|
||||
client,
|
||||
images: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
&self,
|
||||
uri: impl Into<ArcCow<'static, str>>,
|
||||
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
|
||||
let uri = uri.into();
|
||||
let mut images = self.images.lock();
|
||||
|
||||
match images.get(uri.as_ref()) {
|
||||
Some(future) => future.clone(),
|
||||
None => {
|
||||
let client = self.client.clone();
|
||||
let future = {
|
||||
let uri = uri.clone();
|
||||
async move {
|
||||
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(Error::BadStatus {
|
||||
status: response.status(),
|
||||
body: String::from_utf8_lossy(&body).into_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let format = image::guess_format(&body)?;
|
||||
let image =
|
||||
image::load_from_memory_with_format(&body, format)?.into_bgra8();
|
||||
|
||||
Ok(ImageData::new(image))
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
.shared();
|
||||
|
||||
images.insert(uri, future.clone());
|
||||
future
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,45 +244,40 @@ impl Scene {
|
|||
}
|
||||
|
||||
impl SceneBuilder {
|
||||
pub fn new(scale_factor: f32) -> Self {
|
||||
let stacking_context = StackingContext::new(None, 0);
|
||||
SceneBuilder {
|
||||
scale_factor,
|
||||
stacking_contexts: vec![stacking_context],
|
||||
active_stacking_context_stack: vec![0],
|
||||
pub fn new() -> Self {
|
||||
let mut this = SceneBuilder {
|
||||
stacking_contexts: Vec::new(),
|
||||
active_stacking_context_stack: Vec::new(),
|
||||
#[cfg(debug_assertions)]
|
||||
mouse_region_ids: Default::default(),
|
||||
mouse_region_ids: HashSet::default(),
|
||||
event_handlers: Vec::new(),
|
||||
}
|
||||
};
|
||||
this.clear();
|
||||
this
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> Scene {
|
||||
self.stacking_contexts
|
||||
.sort_by_key(|context| context.z_index);
|
||||
pub fn clear(&mut self) {
|
||||
self.stacking_contexts.clear();
|
||||
self.stacking_contexts.push(StackingContext::new(None, 0));
|
||||
self.active_stacking_context_stack.clear();
|
||||
self.active_stacking_context_stack.push(0);
|
||||
#[cfg(debug_assertions)]
|
||||
self.mouse_region_ids.clear();
|
||||
}
|
||||
|
||||
pub fn build(&mut self, scale_factor: f32) -> Scene {
|
||||
let mut stacking_contexts = std::mem::take(&mut self.stacking_contexts);
|
||||
stacking_contexts.sort_by_key(|context| context.z_index);
|
||||
let event_handlers = std::mem::take(&mut self.event_handlers);
|
||||
self.clear();
|
||||
|
||||
Scene {
|
||||
scale_factor: self.scale_factor,
|
||||
stacking_contexts: self.stacking_contexts,
|
||||
event_handlers: self.event_handlers,
|
||||
scale_factor,
|
||||
stacking_contexts,
|
||||
event_handlers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f32 {
|
||||
self.scale_factor
|
||||
}
|
||||
|
||||
pub fn paint_stacking_context<F>(
|
||||
&mut self,
|
||||
clip_bounds: Option<RectF>,
|
||||
z_index: Option<usize>,
|
||||
f: F,
|
||||
) where
|
||||
F: FnOnce(&mut Self),
|
||||
{
|
||||
self.push_stacking_context(clip_bounds, z_index);
|
||||
f(self);
|
||||
self.pop_stacking_context();
|
||||
}
|
||||
|
||||
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
|
||||
let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
|
||||
self.active_stacking_context_stack
|
||||
|
@ -342,15 +291,6 @@ impl SceneBuilder {
|
|||
assert!(!self.active_stacking_context_stack.is_empty());
|
||||
}
|
||||
|
||||
pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Self),
|
||||
{
|
||||
self.push_layer(clip_bounds);
|
||||
f(self);
|
||||
self.pop_layer();
|
||||
}
|
||||
|
||||
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
|
||||
self.active_stacking_context().push_layer(clip_bounds);
|
||||
}
|
||||
|
@ -606,99 +546,6 @@ impl Layer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Border {
|
||||
pub fn new(width: f32, color: Color) -> Self {
|
||||
Self {
|
||||
width,
|
||||
color,
|
||||
overlay: false,
|
||||
top: false,
|
||||
left: false,
|
||||
bottom: false,
|
||||
right: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all(width: f32, color: Color) -> Self {
|
||||
Self {
|
||||
width,
|
||||
color,
|
||||
overlay: false,
|
||||
top: true,
|
||||
left: true,
|
||||
bottom: true,
|
||||
right: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn top(width: f32, color: Color) -> Self {
|
||||
let mut border = Self::new(width, color);
|
||||
border.top = true;
|
||||
border
|
||||
}
|
||||
|
||||
pub fn left(width: f32, color: Color) -> Self {
|
||||
let mut border = Self::new(width, color);
|
||||
border.left = true;
|
||||
border
|
||||
}
|
||||
|
||||
pub fn bottom(width: f32, color: Color) -> Self {
|
||||
let mut border = Self::new(width, color);
|
||||
border.bottom = true;
|
||||
border
|
||||
}
|
||||
|
||||
pub fn right(width: f32, color: Color) -> Self {
|
||||
let mut border = Self::new(width, color);
|
||||
border.right = true;
|
||||
border
|
||||
}
|
||||
|
||||
pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
|
||||
self.top = top;
|
||||
self.left = left;
|
||||
self.bottom = bottom;
|
||||
self.right = right;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn top_width(&self) -> f32 {
|
||||
if self.top {
|
||||
self.width
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left_width(&self) -> f32 {
|
||||
if self.left {
|
||||
self.width
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for Border {
|
||||
fn to_json(&self) -> serde_json::Value {
|
||||
let mut value = json!({});
|
||||
if self.top {
|
||||
value["top"] = json!(self.width);
|
||||
}
|
||||
if self.right {
|
||||
value["right"] = json!(self.width);
|
||||
}
|
||||
if self.bottom {
|
||||
value["bottom"] = json!(self.width);
|
||||
}
|
||||
if self.left {
|
||||
value["left"] = json!(self.width);
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseRegion {
|
||||
pub fn id(&self) -> MouseRegionId {
|
||||
self.id
|
||||
|
|
|
@ -9,7 +9,6 @@ use crate::{
|
|||
platform::FontSystem,
|
||||
scene,
|
||||
window::WindowContext,
|
||||
SceneBuilder,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
|
@ -284,7 +283,6 @@ impl Line {
|
|||
|
||||
pub fn paint(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
origin: Vector2F,
|
||||
visible_bounds: RectF,
|
||||
line_height: f32,
|
||||
|
@ -347,7 +345,7 @@ impl Line {
|
|||
}
|
||||
|
||||
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||
scene.push_underline(scene::Underline {
|
||||
cx.scene().push_underline(scene::Underline {
|
||||
origin: underline_origin,
|
||||
width: glyph_origin.x() - underline_origin.x(),
|
||||
thickness: underline_style.thickness.into(),
|
||||
|
@ -357,14 +355,14 @@ impl Line {
|
|||
}
|
||||
|
||||
if glyph.is_emoji {
|
||||
scene.push_image_glyph(scene::ImageGlyph {
|
||||
cx.scene().push_image_glyph(scene::ImageGlyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
origin: glyph_origin,
|
||||
});
|
||||
} else {
|
||||
scene.push_glyph(scene::Glyph {
|
||||
cx.scene().push_glyph(scene::Glyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
|
@ -377,7 +375,7 @@ impl Line {
|
|||
|
||||
if let Some((underline_start, underline_style)) = underline.take() {
|
||||
let line_end_x = origin.x() + self.layout.width;
|
||||
scene.push_underline(scene::Underline {
|
||||
cx.scene().push_underline(scene::Underline {
|
||||
origin: underline_start,
|
||||
width: line_end_x - underline_start.x(),
|
||||
color: underline_style.color.unwrap(),
|
||||
|
@ -389,7 +387,6 @@ impl Line {
|
|||
|
||||
pub fn paint_wrapped(
|
||||
&self,
|
||||
scene: &mut SceneBuilder,
|
||||
origin: Vector2F,
|
||||
visible_bounds: RectF,
|
||||
line_height: f32,
|
||||
|
@ -417,7 +414,7 @@ impl Line {
|
|||
{
|
||||
boundaries.next();
|
||||
if let Some((underline_origin, underline_style)) = underline {
|
||||
scene.push_underline(scene::Underline {
|
||||
cx.scene().push_underline(scene::Underline {
|
||||
origin: underline_origin,
|
||||
width: glyph_origin.x() - underline_origin.x(),
|
||||
thickness: underline_style.thickness.into(),
|
||||
|
@ -461,7 +458,7 @@ impl Line {
|
|||
}
|
||||
|
||||
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||
scene.push_underline(scene::Underline {
|
||||
cx.scene().push_underline(scene::Underline {
|
||||
origin: underline_origin,
|
||||
width: glyph_origin.x() - underline_origin.x(),
|
||||
thickness: underline_style.thickness.into(),
|
||||
|
@ -477,14 +474,14 @@ impl Line {
|
|||
);
|
||||
if glyph_bounds.intersects(visible_bounds) {
|
||||
if glyph.is_emoji {
|
||||
scene.push_image_glyph(scene::ImageGlyph {
|
||||
cx.scene().push_image_glyph(scene::ImageGlyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
origin: glyph_bounds.origin() + baseline_offset,
|
||||
});
|
||||
} else {
|
||||
scene.push_glyph(scene::Glyph {
|
||||
cx.scene().push_glyph(scene::Glyph {
|
||||
font_id: run.font_id,
|
||||
font_size: self.layout.font_size,
|
||||
id: glyph.id,
|
||||
|
@ -498,7 +495,7 @@ impl Line {
|
|||
|
||||
if let Some((underline_origin, underline_style)) = underline.take() {
|
||||
let line_end_x = glyph_origin.x() + self.layout.width - prev_position;
|
||||
scene.push_underline(scene::Underline {
|
||||
cx.scene().push_underline(scene::Underline {
|
||||
origin: underline_origin,
|
||||
width: line_end_x - underline_origin.x(),
|
||||
thickness: underline_style.thickness.into(),
|
||||
|
|
32
crates/gpui2/Cargo.toml
Normal file
32
crates/gpui2/Cargo.toml
Normal file
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "gpui2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "gpui2"
|
||||
path = "src/gpui2.rs"
|
||||
|
||||
[features]
|
||||
test-support = ["gpui/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
derive_more.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
log.workspace = true
|
||||
futures.workspace = true
|
||||
gpui2_macros = { path = "../gpui2_macros" }
|
||||
parking_lot.workspace = true
|
||||
refineable.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
simplelog = "0.9"
|
||||
smallvec.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
|
@ -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]>,
|
186
crates/gpui2/src/element.rs
Normal file
186
crates/gpui2/src/element.rs
Normal file
|
@ -0,0 +1,186 @@
|
|||
pub use crate::ViewContext;
|
||||
use anyhow::Result;
|
||||
use gpui::geometry::vector::Vector2F;
|
||||
pub use gpui::{Layout, LayoutId};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub trait Element<V: 'static>: 'static + IntoElement<V> {
|
||||
type PaintState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized;
|
||||
|
||||
fn into_any(self) -> AnyElement<V>
|
||||
where
|
||||
Self: 'static + Sized,
|
||||
{
|
||||
AnyElement(Box::new(StatefulElement {
|
||||
element: self,
|
||||
phase: ElementPhase::Init,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
|
||||
trait AnyStatefulElement<V> {
|
||||
fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId>;
|
||||
fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>);
|
||||
}
|
||||
|
||||
/// A wrapper around an element that stores its layout state.
|
||||
struct StatefulElement<V: 'static, E: Element<V>> {
|
||||
element: E,
|
||||
phase: ElementPhase<V, E>,
|
||||
}
|
||||
|
||||
enum ElementPhase<V: 'static, E: Element<V>> {
|
||||
Init,
|
||||
PostLayout {
|
||||
layout_id: LayoutId,
|
||||
paint_state: E::PaintState,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
PostPaint {
|
||||
layout: Layout,
|
||||
paint_state: E::PaintState,
|
||||
},
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V>> std::fmt::Debug for ElementPhase<V, E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ElementPhase::Init => write!(f, "Init"),
|
||||
ElementPhase::PostLayout { layout_id, .. } => {
|
||||
write!(f, "PostLayout with layout id: {:?}", layout_id)
|
||||
}
|
||||
ElementPhase::PostPaint { layout, .. } => {
|
||||
write!(f, "PostPaint with layout: {:?}", layout)
|
||||
}
|
||||
ElementPhase::Error(err) => write!(f, "Error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V>> Default for ElementPhase<V, E> {
|
||||
fn default() -> Self {
|
||||
Self::Init
|
||||
}
|
||||
}
|
||||
|
||||
/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
|
||||
impl<V, E: Element<V>> AnyStatefulElement<V> for StatefulElement<V, E> {
|
||||
fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId> {
|
||||
let result;
|
||||
self.phase = match self.element.layout(view, cx) {
|
||||
Ok((layout_id, paint_state)) => {
|
||||
result = Ok(layout_id);
|
||||
ElementPhase::PostLayout {
|
||||
layout_id,
|
||||
paint_state,
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let message = error.to_string();
|
||||
result = Err(error);
|
||||
ElementPhase::Error(message)
|
||||
}
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
|
||||
self.phase = match std::mem::take(&mut self.phase) {
|
||||
ElementPhase::PostLayout {
|
||||
layout_id,
|
||||
mut paint_state,
|
||||
} => match cx.computed_layout(layout_id) {
|
||||
Ok(layout) => {
|
||||
self.element
|
||||
.paint(view, parent_origin, &layout, &mut paint_state, cx);
|
||||
ElementPhase::PostPaint {
|
||||
layout,
|
||||
paint_state,
|
||||
}
|
||||
}
|
||||
Err(error) => ElementPhase::Error(error.to_string()),
|
||||
},
|
||||
ElementPhase::PostPaint {
|
||||
layout,
|
||||
mut paint_state,
|
||||
} => {
|
||||
self.element
|
||||
.paint(view, parent_origin, &layout, &mut paint_state, cx);
|
||||
ElementPhase::PostPaint {
|
||||
layout,
|
||||
paint_state,
|
||||
}
|
||||
}
|
||||
phase @ ElementPhase::Error(_) => phase,
|
||||
|
||||
phase @ _ => {
|
||||
panic!("invalid element phase to call paint: {:?}", phase);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic element.
|
||||
pub struct AnyElement<V>(Box<dyn AnyStatefulElement<V>>);
|
||||
|
||||
impl<V> AnyElement<V> {
|
||||
pub fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId> {
|
||||
self.0.layout(view, cx)
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
|
||||
self.0.paint(view, parent_origin, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ParentElement<V: 'static> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
|
||||
|
||||
fn child(mut self, child: impl IntoElement<V>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.children_mut().push(child.into_element().into_any());
|
||||
self
|
||||
}
|
||||
|
||||
fn children<I, E>(mut self, children: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = E>,
|
||||
E: IntoElement<V>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.children_mut().extend(
|
||||
children
|
||||
.into_iter()
|
||||
.map(|child| child.into_element().into_any()),
|
||||
);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoElement<V: 'static> {
|
||||
type Element: Element<V>;
|
||||
|
||||
fn into_element(self) -> Self::Element;
|
||||
}
|
10
crates/gpui2/src/elements.rs
Normal file
10
crates/gpui2/src/elements.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
pub mod div;
|
||||
pub mod hoverable;
|
||||
mod img;
|
||||
pub mod pressable;
|
||||
pub mod svg;
|
||||
pub mod text;
|
||||
|
||||
pub use div::div;
|
||||
pub use img::img;
|
||||
pub use svg::svg;
|
320
crates/gpui2/src/elements/div.rs
Normal file
320
crates/gpui2/src/elements/div.rs
Normal file
|
@ -0,0 +1,320 @@
|
|||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
||||
hsla,
|
||||
style::{CornerRadii, Overflow, Style, StyleHelpers, Styleable},
|
||||
InteractionHandlers, Interactive, ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
geometry::{rect::RectF, vector::Vector2F, Point},
|
||||
platform::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
|
||||
scene::{self},
|
||||
LayoutId,
|
||||
};
|
||||
use refineable::{Refineable, RefinementCascade};
|
||||
use smallvec::SmallVec;
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct Div<V: 'static> {
|
||||
styles: RefinementCascade<Style>,
|
||||
handlers: InteractionHandlers<V>,
|
||||
children: SmallVec<[AnyElement<V>; 2]>,
|
||||
scroll_state: Option<ScrollState>,
|
||||
}
|
||||
|
||||
pub fn div<V>() -> Div<V> {
|
||||
Div {
|
||||
styles: Default::default(),
|
||||
handlers: Default::default(),
|
||||
children: Default::default(),
|
||||
scroll_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for Div<V> {
|
||||
type PaintState = Vec<LayoutId>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
let pop_text_style = style.text_style(cx).map_or(false, |style| {
|
||||
cx.push_text_style(&style).log_err().is_some()
|
||||
});
|
||||
|
||||
let children = self
|
||||
.children
|
||||
.iter_mut()
|
||||
.map(|child| child.layout(view, cx))
|
||||
.collect::<Result<Vec<LayoutId>>>()?;
|
||||
|
||||
if pop_text_style {
|
||||
cx.pop_text_style();
|
||||
}
|
||||
|
||||
Ok((cx.add_layout_node(style, children.clone())?, children))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
child_layouts: &mut Vec<LayoutId>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let order = layout.order;
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
|
||||
let style = self.computed_style();
|
||||
let pop_text_style = style.text_style(cx).map_or(false, |style| {
|
||||
cx.push_text_style(&style).log_err().is_some()
|
||||
});
|
||||
style.paint_background(bounds, cx);
|
||||
self.interaction_handlers().paint(order, bounds, cx);
|
||||
|
||||
let scrolled_origin = bounds.origin() - self.scroll_offset(&style.overflow);
|
||||
|
||||
// TODO: Support only one dimension being hidden
|
||||
let mut pop_layer = false;
|
||||
if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
|
||||
cx.scene().push_layer(Some(bounds));
|
||||
pop_layer = true;
|
||||
}
|
||||
|
||||
for child in &mut self.children {
|
||||
child.paint(view, scrolled_origin, cx);
|
||||
}
|
||||
|
||||
if pop_layer {
|
||||
cx.scene().pop_layer();
|
||||
}
|
||||
|
||||
style.paint_foreground(bounds, cx);
|
||||
if pop_text_style {
|
||||
cx.pop_text_style();
|
||||
}
|
||||
|
||||
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
|
||||
|
||||
if cx.is_inspector_enabled() {
|
||||
self.paint_inspector(parent_origin, layout, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Div<V> {
|
||||
pub fn overflow_hidden(mut self) -> Self {
|
||||
self.declared_style().overflow.x = Some(Overflow::Hidden);
|
||||
self.declared_style().overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_x(mut self) -> Self {
|
||||
self.declared_style().overflow.x = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_y(mut self) -> Self {
|
||||
self.declared_style().overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.x = Some(Overflow::Scroll);
|
||||
self.declared_style().overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.x = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
fn scroll_offset(&self, overflow: &Point<Overflow>) -> Vector2F {
|
||||
let mut offset = Vector2F::zero();
|
||||
if overflow.y == Overflow::Scroll {
|
||||
offset.set_y(self.scroll_state.as_ref().unwrap().y());
|
||||
}
|
||||
if overflow.x == Overflow::Scroll {
|
||||
offset.set_x(self.scroll_state.as_ref().unwrap().x());
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
fn handle_scroll(
|
||||
&mut self,
|
||||
order: u32,
|
||||
bounds: RectF,
|
||||
overflow: Point<Overflow>,
|
||||
child_layout_ids: &[LayoutId],
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
|
||||
let mut scroll_max = Vector2F::zero();
|
||||
for child_layout_id in child_layout_ids {
|
||||
if let Some(child_layout) = cx
|
||||
.layout_engine()
|
||||
.unwrap()
|
||||
.computed_layout(*child_layout_id)
|
||||
.log_err()
|
||||
{
|
||||
scroll_max = scroll_max.max(child_layout.bounds.lower_right());
|
||||
}
|
||||
}
|
||||
scroll_max -= bounds.size();
|
||||
|
||||
let scroll_state = self.scroll_state.as_ref().unwrap().clone();
|
||||
cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
|
||||
if bounds.contains_point(event.position) {
|
||||
let scroll_delta = match event.delta {
|
||||
gpui::platform::ScrollDelta::Pixels(delta) => delta,
|
||||
gpui::platform::ScrollDelta::Lines(delta) => {
|
||||
delta * cx.text_style().font_size
|
||||
}
|
||||
};
|
||||
if overflow.x == Overflow::Scroll {
|
||||
scroll_state.set_x(
|
||||
(scroll_state.x() - scroll_delta.x())
|
||||
.max(0.)
|
||||
.min(scroll_max.x()),
|
||||
);
|
||||
}
|
||||
if overflow.y == Overflow::Scroll {
|
||||
scroll_state.set_y(
|
||||
(scroll_state.y() - scroll_delta.y())
|
||||
.max(0.)
|
||||
.min(scroll_max.y()),
|
||||
);
|
||||
}
|
||||
cx.repaint();
|
||||
} else {
|
||||
cx.bubble_event();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut ViewContext<V>) {
|
||||
let style = self.styles.merged();
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
|
||||
let hovered = bounds.contains_point(cx.mouse_position());
|
||||
if hovered {
|
||||
let rem_size = cx.rem_size();
|
||||
cx.scene().push_quad(scene::Quad {
|
||||
bounds,
|
||||
background: Some(hsla(0., 0., 1., 0.05).into()),
|
||||
border: gpui::Border {
|
||||
color: hsla(0., 0., 1., 0.2).into(),
|
||||
top: 1.,
|
||||
right: 1.,
|
||||
bottom: 1.,
|
||||
left: 1.,
|
||||
},
|
||||
corner_radii: CornerRadii::default()
|
||||
.refined(&style.corner_radii)
|
||||
.to_gpui(bounds.size(), rem_size),
|
||||
})
|
||||
}
|
||||
|
||||
let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left));
|
||||
cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| {
|
||||
if bounds.contains_point(event.position) {
|
||||
if event.is_down {
|
||||
pressed.set(true);
|
||||
} else if pressed.get() {
|
||||
pressed.set(false);
|
||||
eprintln!("clicked div {:?} {:#?}", bounds, style);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let hovered = Cell::new(hovered);
|
||||
cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| {
|
||||
cx.bubble_event();
|
||||
let hovered_now = bounds.contains_point(event.position);
|
||||
if hovered.get() != hovered_now {
|
||||
hovered.set(hovered_now);
|
||||
cx.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Styleable for Div<V> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
&mut self.styles
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
self.styles.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> StyleHelpers for Div<V> {}
|
||||
|
||||
impl<V> Interactive<V> for Div<V> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
&mut self.handlers
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> ParentElement<V> for Div<V> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> IntoElement<V> for Div<V> {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ScrollState(Rc<Cell<Vector2F>>);
|
||||
|
||||
impl ScrollState {
|
||||
pub fn x(&self) -> f32 {
|
||||
self.0.get().x()
|
||||
}
|
||||
|
||||
pub fn set_x(&self, value: f32) {
|
||||
let mut current_value = self.0.get();
|
||||
current_value.set_x(value);
|
||||
self.0.set(current_value);
|
||||
}
|
||||
|
||||
pub fn y(&self) -> f32 {
|
||||
self.0.get().y()
|
||||
}
|
||||
|
||||
pub fn set_y(&self, value: f32) {
|
||||
let mut current_value = self.0.get();
|
||||
current_value.set_y(value);
|
||||
self.0.set(current_value);
|
||||
}
|
||||
}
|
105
crates/gpui2/src/elements/hoverable.rs
Normal file
105
crates/gpui2/src/elements/hoverable.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use crate::{
|
||||
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
||||
interactive::{InteractionHandlers, Interactive},
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
|
||||
use refineable::{CascadeSlot, Refineable, RefinementCascade};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
pub struct Hoverable<E: Styleable> {
|
||||
hovered: Rc<Cell<bool>>,
|
||||
cascade_slot: CascadeSlot,
|
||||
hovered_style: <E::Style as Refineable>::Refinement,
|
||||
child: E,
|
||||
}
|
||||
|
||||
pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
|
||||
Hoverable {
|
||||
hovered: Rc::new(Cell::new(false)),
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
hovered_style: Default::default(),
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable> Styleable for Hoverable<E> {
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
&mut self.hovered_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
|
||||
type PaintState = E::PaintState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(self.child.layout(view, cx)?)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
paint_state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
self.hovered.set(bounds.contains_point(cx.mouse_position()));
|
||||
|
||||
let slot = self.cascade_slot;
|
||||
let style = self.hovered.get().then_some(self.hovered_style.clone());
|
||||
self.style_cascade().set(slot, style);
|
||||
|
||||
let hovered = self.hovered.clone();
|
||||
cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
|
||||
cx.bubble_event();
|
||||
if bounds.contains_point(cx.mouse_position()) != hovered.get() {
|
||||
cx.repaint();
|
||||
}
|
||||
});
|
||||
|
||||
self.child
|
||||
.paint(view, parent_origin, layout, paint_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
|
||||
|
||||
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
self.child.interaction_handlers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
110
crates/gpui2/src/elements/img.rs
Normal file
110
crates/gpui2/src/elements/img.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use crate as gpui2;
|
||||
use crate::{
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
Element,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use gpui::geometry::vector::Vector2F;
|
||||
use gpui::scene;
|
||||
use gpui2_macros::IntoElement;
|
||||
use refineable::RefinementCascade;
|
||||
use util::arc_cow::ArcCow;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Img {
|
||||
style: RefinementCascade<Style>,
|
||||
uri: Option<ArcCow<'static, str>>,
|
||||
}
|
||||
|
||||
pub fn img() -> Img {
|
||||
Img {
|
||||
style: RefinementCascade::default(),
|
||||
uri: None,
|
||||
}
|
||||
}
|
||||
|
||||
impl Img {
|
||||
pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
|
||||
self.uri = Some(uri.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for Img {
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
let layout_id = cx.add_layout_node(style, [])?;
|
||||
Ok((layout_id, ()))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &gpui::Layout,
|
||||
_: &mut Self::PaintState,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
|
||||
style.paint_background(bounds, cx);
|
||||
|
||||
if let Some(uri) = &self.uri {
|
||||
let image_future = cx.image_cache.get(uri.clone());
|
||||
if let Some(data) = image_future
|
||||
.clone()
|
||||
.now_or_never()
|
||||
.and_then(ResultExt::log_err)
|
||||
{
|
||||
let rem_size = cx.rem_size();
|
||||
cx.scene().push_image(scene::Image {
|
||||
bounds,
|
||||
border: gpui::Border {
|
||||
color: style.border_color.unwrap_or_default().into(),
|
||||
top: style.border_widths.top.to_pixels(rem_size),
|
||||
right: style.border_widths.right.to_pixels(rem_size),
|
||||
bottom: style.border_widths.bottom.to_pixels(rem_size),
|
||||
left: style.border_widths.left.to_pixels(rem_size),
|
||||
},
|
||||
corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
|
||||
grayscale: false,
|
||||
data,
|
||||
})
|
||||
} else {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if image_future.await.log_err().is_some() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Styleable for Img {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
|
||||
&mut self.style
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleHelpers for Img {}
|
108
crates/gpui2/src/elements/pressable.rs
Normal file
108
crates/gpui2/src/elements/pressable.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use crate::{
|
||||
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
||||
interactive::{InteractionHandlers, Interactive},
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{geometry::vector::Vector2F, platform::MouseButtonEvent, LayoutId};
|
||||
use refineable::{CascadeSlot, Refineable, RefinementCascade};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
pub struct Pressable<E: Styleable> {
|
||||
pressed: Rc<Cell<bool>>,
|
||||
pressed_style: <E::Style as Refineable>::Refinement,
|
||||
cascade_slot: CascadeSlot,
|
||||
child: E,
|
||||
}
|
||||
|
||||
pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
|
||||
Pressable {
|
||||
pressed: Rc::new(Cell::new(false)),
|
||||
pressed_style: Default::default(),
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable> Styleable for Pressable<E> {
|
||||
type Style = E::Style;
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
&mut self.pressed_style
|
||||
}
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
|
||||
type PaintState = E::PaintState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.child.layout(view, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
paint_state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let slot = self.cascade_slot;
|
||||
let style = self.pressed.get().then_some(self.pressed_style.clone());
|
||||
self.style_cascade().set(slot, style);
|
||||
|
||||
let pressed = self.pressed.clone();
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
cx.on_event(layout.order, move |_view, event: &MouseButtonEvent, cx| {
|
||||
if event.is_down {
|
||||
if bounds.contains_point(event.position) {
|
||||
pressed.set(true);
|
||||
cx.repaint();
|
||||
}
|
||||
} else if pressed.get() {
|
||||
pressed.set(false);
|
||||
cx.repaint();
|
||||
}
|
||||
});
|
||||
|
||||
self.child
|
||||
.paint(view, parent_origin, layout, paint_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
|
||||
|
||||
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
|
||||
self.child.interaction_handlers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
84
crates/gpui2/src/elements/svg.rs
Normal file
84
crates/gpui2/src/elements/svg.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use crate::{
|
||||
self as gpui2, scene,
|
||||
style::{Style, StyleHelpers, Styleable},
|
||||
Element, IntoElement, Layout, LayoutId, Rgba,
|
||||
};
|
||||
use gpui::geometry::vector::Vector2F;
|
||||
use refineable::RefinementCascade;
|
||||
use std::borrow::Cow;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Svg {
|
||||
path: Option<Cow<'static, str>>,
|
||||
style: RefinementCascade<Style>,
|
||||
}
|
||||
|
||||
pub fn svg() -> Svg {
|
||||
Svg {
|
||||
path: None,
|
||||
style: RefinementCascade::<Style>::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Svg {
|
||||
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.path = Some(path.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for Svg {
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) -> anyhow::Result<(LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
Ok((cx.add_layout_node(style, [])?, ()))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
_: &mut Self::PaintState,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
||||
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
||||
if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
|
||||
let icon = scene::Icon {
|
||||
bounds: layout.bounds + parent_origin,
|
||||
svg: svg_tree,
|
||||
path: path.clone(),
|
||||
color: Rgba::from(fill_color).into(),
|
||||
};
|
||||
|
||||
cx.scene().push_icon(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Styleable for Svg {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
|
||||
&mut self.style
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.style.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleHelpers for Svg {}
|
109
crates/gpui2/src/elements/text.rs
Normal file
109
crates/gpui2/src/elements/text.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use crate::{
|
||||
element::{Element, IntoElement, Layout},
|
||||
ViewContext,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
geometry::{vector::Vector2F, Size},
|
||||
text_layout::LineLayout,
|
||||
LayoutId,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use util::arc_cow::ArcCow;
|
||||
|
||||
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
|
||||
type Element = Text;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
Text { text: self.into() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
text: ArcCow<'static, str>,
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for Text {
|
||||
type PaintState = Arc<Mutex<Option<TextLayout>>>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Result<(LayoutId, Self::PaintState)> {
|
||||
let fonts = cx.platform().fonts();
|
||||
let text_style = cx.text_style();
|
||||
let line_height = cx.font_cache().line_height(text_style.font_size);
|
||||
let text = self.text.clone();
|
||||
let paint_state = Arc::new(Mutex::new(None));
|
||||
|
||||
let layout_id = cx.add_measured_layout_node(Default::default(), {
|
||||
let paint_state = paint_state.clone();
|
||||
move |_params| {
|
||||
let line_layout = fonts.layout_line(
|
||||
text.as_ref(),
|
||||
text_style.font_size,
|
||||
&[(text.len(), text_style.to_run())],
|
||||
);
|
||||
|
||||
let size = Size {
|
||||
width: line_layout.width,
|
||||
height: line_height,
|
||||
};
|
||||
|
||||
paint_state.lock().replace(TextLayout {
|
||||
line_layout: Arc::new(line_layout),
|
||||
line_height,
|
||||
});
|
||||
|
||||
size
|
||||
}
|
||||
});
|
||||
|
||||
Ok((layout_id?, paint_state))
|
||||
}
|
||||
|
||||
fn paint<'a>(
|
||||
&mut self,
|
||||
_view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &Layout,
|
||||
paint_state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let bounds = layout.bounds + parent_origin;
|
||||
|
||||
let line_layout;
|
||||
let line_height;
|
||||
{
|
||||
let paint_state = paint_state.lock();
|
||||
let paint_state = paint_state
|
||||
.as_ref()
|
||||
.expect("measurement has not been performed");
|
||||
line_layout = paint_state.line_layout.clone();
|
||||
line_height = paint_state.line_height;
|
||||
}
|
||||
|
||||
let text_style = cx.text_style();
|
||||
let line =
|
||||
gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
|
||||
|
||||
// TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder.
|
||||
let visible_bounds = bounds;
|
||||
line.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> IntoElement<V> for Text {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextLayout {
|
||||
line_layout: Arc<LineLayout>,
|
||||
line_height: f32,
|
||||
}
|
22
crates/gpui2/src/gpui2.rs
Normal file
22
crates/gpui2/src/gpui2.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
pub mod adapter;
|
||||
pub mod color;
|
||||
pub mod element;
|
||||
pub mod elements;
|
||||
pub mod interactive;
|
||||
pub mod style;
|
||||
pub mod view;
|
||||
pub mod view_context;
|
||||
|
||||
pub use color::*;
|
||||
pub use element::{AnyElement, Element, IntoElement, Layout, ParentElement};
|
||||
pub use geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
};
|
||||
pub use gpui::*;
|
||||
pub use gpui2_macros::{Element, *};
|
||||
pub use interactive::*;
|
||||
pub use platform::{Platform, WindowBounds, WindowOptions};
|
||||
pub use util::arc_cow::ArcCow;
|
||||
pub use view::*;
|
||||
pub use view_context::ViewContext;
|
165
crates/gpui2/src/interactive.rs
Normal file
165
crates/gpui2/src/interactive.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use gpui::{
|
||||
geometry::rect::RectF,
|
||||
platform::{MouseButton, MouseButtonEvent},
|
||||
EventContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use crate::ViewContext;
|
||||
|
||||
pub trait Interactive<V: 'static> {
|
||||
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
|
||||
|
||||
fn on_mouse_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.interaction_handlers()
|
||||
.mouse_down
|
||||
.push(Rc::new(move |view, event, cx| {
|
||||
if event.button == button {
|
||||
handler(view, event, cx)
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_mouse_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.interaction_handlers()
|
||||
.mouse_up
|
||||
.push(Rc::new(move |view, event, cx| {
|
||||
if event.button == button {
|
||||
handler(view, event, cx)
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_mouse_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.interaction_handlers()
|
||||
.mouse_down_out
|
||||
.push(Rc::new(move |view, event, cx| {
|
||||
if event.button == button {
|
||||
handler(view, event, cx)
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_mouse_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.interaction_handlers()
|
||||
.mouse_up_out
|
||||
.push(Rc::new(move |view, event, cx| {
|
||||
if event.button == button {
|
||||
handler(view, event, cx)
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_click(
|
||||
self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let pressed = Rc::new(Cell::new(false));
|
||||
self.on_mouse_down(button, {
|
||||
let pressed = pressed.clone();
|
||||
move |_, _, _| {
|
||||
pressed.set(true);
|
||||
}
|
||||
})
|
||||
.on_mouse_up_out(button, {
|
||||
let pressed = pressed.clone();
|
||||
move |_, _, _| {
|
||||
pressed.set(false);
|
||||
}
|
||||
})
|
||||
.on_mouse_up(button, move |view, event, cx| {
|
||||
if pressed.get() {
|
||||
pressed.set(false);
|
||||
handler(view, event, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InteractionHandlers<V: 'static> {
|
||||
mouse_down: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
|
||||
mouse_down_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
|
||||
mouse_up: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
|
||||
mouse_up_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
|
||||
}
|
||||
|
||||
impl<V: 'static> InteractionHandlers<V> {
|
||||
pub fn paint(&self, order: u32, bounds: RectF, cx: &mut ViewContext<V>) {
|
||||
for handler in self.mouse_down.iter().cloned() {
|
||||
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
|
||||
if event.is_down && bounds.contains_point(event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
for handler in self.mouse_up.iter().cloned() {
|
||||
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
|
||||
if !event.is_down && bounds.contains_point(event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
for handler in self.mouse_down_out.iter().cloned() {
|
||||
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
|
||||
if event.is_down && !bounds.contains_point(event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
for handler in self.mouse_up_out.iter().cloned() {
|
||||
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
|
||||
if !event.is_down && !bounds.contains_point(event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for InteractionHandlers<V> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mouse_down: Default::default(),
|
||||
mouse_up: Default::default(),
|
||||
mouse_down_out: Default::default(),
|
||||
mouse_up_out: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
604
crates/gpui2/src/style.rs
Normal file
604
crates/gpui2/src/style.rs
Normal file
|
@ -0,0 +1,604 @@
|
|||
use crate::{
|
||||
color::Hsla,
|
||||
elements::hoverable::{hoverable, Hoverable},
|
||||
elements::pressable::{pressable, Pressable},
|
||||
ViewContext,
|
||||
};
|
||||
pub use fonts::Style as FontStyle;
|
||||
pub use fonts::Weight as FontWeight;
|
||||
pub use gpui::taffy::style::{
|
||||
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
|
||||
Overflow, Position,
|
||||
};
|
||||
use gpui::{
|
||||
fonts::{self, TextStyleRefinement},
|
||||
geometry::{
|
||||
rect::RectF, relative, vector::Vector2F, AbsoluteLength, DefiniteLength, Edges,
|
||||
EdgesRefinement, Length, Point, PointRefinement, Size, SizeRefinement,
|
||||
},
|
||||
scene, taffy, WindowContext,
|
||||
};
|
||||
use gpui2_macros::styleable_helpers;
|
||||
use refineable::{Refineable, RefinementCascade};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Refineable, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct Style {
|
||||
/// What layout strategy should be used?
|
||||
pub display: Display,
|
||||
|
||||
// Overflow properties
|
||||
/// How children overflowing their container should affect layout
|
||||
#[refineable]
|
||||
pub overflow: Point<Overflow>,
|
||||
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
|
||||
pub scrollbar_width: f32,
|
||||
|
||||
// Position properties
|
||||
/// What should the `position` value of this struct use as a base offset?
|
||||
pub position: Position,
|
||||
/// How should the position of this element be tweaked relative to the layout defined?
|
||||
#[refineable]
|
||||
pub inset: Edges<Length>,
|
||||
|
||||
// Size properies
|
||||
/// Sets the initial size of the item
|
||||
#[refineable]
|
||||
pub size: Size<Length>,
|
||||
/// Controls the minimum size of the item
|
||||
#[refineable]
|
||||
pub min_size: Size<Length>,
|
||||
/// Controls the maximum size of the item
|
||||
#[refineable]
|
||||
pub max_size: Size<Length>,
|
||||
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
|
||||
pub aspect_ratio: Option<f32>,
|
||||
|
||||
// Spacing Properties
|
||||
/// How large should the margin be on each side?
|
||||
#[refineable]
|
||||
pub margin: Edges<Length>,
|
||||
/// How large should the padding be on each side?
|
||||
#[refineable]
|
||||
pub padding: Edges<DefiniteLength>,
|
||||
/// How large should the border be on each side?
|
||||
#[refineable]
|
||||
pub border_widths: Edges<AbsoluteLength>,
|
||||
|
||||
// Alignment properties
|
||||
/// How this node's children aligned in the cross/block axis?
|
||||
pub align_items: Option<AlignItems>,
|
||||
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
|
||||
pub align_self: Option<AlignSelf>,
|
||||
/// How should content contained within this item be aligned in the cross/block axis
|
||||
pub align_content: Option<AlignContent>,
|
||||
/// How should contained within this item be aligned in the main/inline axis
|
||||
pub justify_content: Option<JustifyContent>,
|
||||
/// How large should the gaps between items in a flex container be?
|
||||
#[refineable]
|
||||
pub gap: Size<DefiniteLength>,
|
||||
|
||||
// Flexbox properies
|
||||
/// Which direction does the main axis flow in?
|
||||
pub flex_direction: FlexDirection,
|
||||
/// Should elements wrap, or stay in a single line?
|
||||
pub flex_wrap: FlexWrap,
|
||||
/// Sets the initial main axis size of the item
|
||||
pub flex_basis: Length,
|
||||
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
|
||||
pub flex_grow: f32,
|
||||
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
|
||||
pub flex_shrink: f32,
|
||||
|
||||
/// The fill color of this element
|
||||
pub fill: Option<Fill>,
|
||||
|
||||
/// The border color of this element
|
||||
pub border_color: Option<Hsla>,
|
||||
|
||||
/// The radius of the corners of this element
|
||||
#[refineable]
|
||||
pub corner_radii: CornerRadii,
|
||||
|
||||
/// The color of text within this element. Cascades to children unless overridden.
|
||||
pub text_color: Option<Hsla>,
|
||||
|
||||
/// The font size in rems.
|
||||
pub font_size: Option<f32>,
|
||||
|
||||
pub font_family: Option<Arc<str>>,
|
||||
|
||||
pub font_weight: Option<FontWeight>,
|
||||
|
||||
pub font_style: Option<FontStyle>,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn text_style(&self, cx: &WindowContext) -> Option<TextStyleRefinement> {
|
||||
if self.text_color.is_none()
|
||||
&& self.font_size.is_none()
|
||||
&& self.font_family.is_none()
|
||||
&& self.font_weight.is_none()
|
||||
&& self.font_style.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(TextStyleRefinement {
|
||||
color: self.text_color.map(Into::into),
|
||||
font_family: self.font_family.clone(),
|
||||
font_size: self.font_size.map(|size| size * cx.rem_size()),
|
||||
font_weight: self.font_weight,
|
||||
font_style: self.font_style,
|
||||
underline: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
|
||||
taffy::style::Style {
|
||||
display: self.display,
|
||||
overflow: self.overflow.clone().into(),
|
||||
scrollbar_width: self.scrollbar_width,
|
||||
position: self.position,
|
||||
inset: self.inset.to_taffy(rem_size),
|
||||
size: self.size.to_taffy(rem_size),
|
||||
min_size: self.min_size.to_taffy(rem_size),
|
||||
max_size: self.max_size.to_taffy(rem_size),
|
||||
aspect_ratio: self.aspect_ratio,
|
||||
margin: self.margin.to_taffy(rem_size),
|
||||
padding: self.padding.to_taffy(rem_size),
|
||||
border: self.border_widths.to_taffy(rem_size),
|
||||
align_items: self.align_items,
|
||||
align_self: self.align_self,
|
||||
align_content: self.align_content,
|
||||
justify_content: self.justify_content,
|
||||
gap: self.gap.to_taffy(rem_size),
|
||||
flex_direction: self.flex_direction,
|
||||
flex_wrap: self.flex_wrap,
|
||||
flex_basis: self.flex_basis.to_taffy(rem_size).into(),
|
||||
flex_grow: self.flex_grow,
|
||||
flex_shrink: self.flex_shrink,
|
||||
..Default::default() // Ignore grid properties for now
|
||||
}
|
||||
}
|
||||
|
||||
/// Paints the background of an element styled with this style.
|
||||
pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
|
||||
let rem_size = cx.rem_size();
|
||||
if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
|
||||
cx.scene().push_quad(gpui::Quad {
|
||||
bounds,
|
||||
background: Some(color.into()),
|
||||
corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
|
||||
border: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Paints the foreground of an element styled with this style.
|
||||
pub fn paint_foreground<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
|
||||
let rem_size = cx.rem_size();
|
||||
|
||||
if let Some(color) = self.border_color {
|
||||
let border = self.border_widths.to_pixels(rem_size);
|
||||
if !border.is_empty() {
|
||||
cx.scene().push_quad(gpui::Quad {
|
||||
bounds,
|
||||
background: None,
|
||||
corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
|
||||
border: scene::Border {
|
||||
color: color.into(),
|
||||
top: border.top,
|
||||
right: border.right,
|
||||
bottom: border.bottom,
|
||||
left: border.left,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style {
|
||||
display: Display::Block,
|
||||
overflow: Point {
|
||||
x: Overflow::Visible,
|
||||
y: Overflow::Visible,
|
||||
},
|
||||
scrollbar_width: 0.0,
|
||||
position: Position::Relative,
|
||||
inset: Edges::auto(),
|
||||
margin: Edges::<Length>::zero(),
|
||||
padding: Edges::<DefiniteLength>::zero(),
|
||||
border_widths: Edges::<AbsoluteLength>::zero(),
|
||||
size: Size::auto(),
|
||||
min_size: Size::auto(),
|
||||
max_size: Size::auto(),
|
||||
aspect_ratio: None,
|
||||
gap: Size::zero(),
|
||||
// Aligment
|
||||
align_items: None,
|
||||
align_self: None,
|
||||
align_content: None,
|
||||
justify_content: None,
|
||||
// Flexbox
|
||||
flex_direction: FlexDirection::Row,
|
||||
flex_wrap: FlexWrap::NoWrap,
|
||||
flex_grow: 0.0,
|
||||
flex_shrink: 1.0,
|
||||
flex_basis: Length::Auto,
|
||||
fill: None,
|
||||
border_color: None,
|
||||
corner_radii: CornerRadii::default(),
|
||||
text_color: None,
|
||||
font_size: Some(1.),
|
||||
font_family: None,
|
||||
font_weight: None,
|
||||
font_style: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Fill {
|
||||
Color(Hsla),
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
pub fn color(&self) -> Option<Hsla> {
|
||||
match self {
|
||||
Fill::Color(color) => Some(*color),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Fill {
|
||||
fn default() -> Self {
|
||||
Self::Color(Hsla::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsla> for Fill {
|
||||
fn from(color: Hsla) -> Self {
|
||||
Self::Color(color)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Refineable, Default, Debug)]
|
||||
#[refineable(debug)]
|
||||
pub struct CornerRadii {
|
||||
top_left: AbsoluteLength,
|
||||
top_right: AbsoluteLength,
|
||||
bottom_left: AbsoluteLength,
|
||||
bottom_right: AbsoluteLength,
|
||||
}
|
||||
|
||||
impl CornerRadii {
|
||||
pub fn to_gpui(&self, box_size: Vector2F, rem_size: f32) -> gpui::scene::CornerRadii {
|
||||
let max_radius = box_size.x().min(box_size.y()) / 2.;
|
||||
|
||||
gpui::scene::CornerRadii {
|
||||
top_left: self.top_left.to_pixels(rem_size).min(max_radius),
|
||||
top_right: self.top_right.to_pixels(rem_size).min(max_radius),
|
||||
bottom_left: self.bottom_left.to_pixels(rem_size).min(max_radius),
|
||||
bottom_right: self.bottom_right.to_pixels(rem_size).min(max_radius),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Styleable {
|
||||
type Style: Refineable + Default;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
|
||||
|
||||
fn computed_style(&mut self) -> Self::Style {
|
||||
Self::Style::from_refinement(&self.style_cascade().merged())
|
||||
}
|
||||
|
||||
fn hover(self) -> Hoverable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
hoverable(self)
|
||||
}
|
||||
|
||||
fn active(self) -> Pressable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
pressable(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
|
||||
//
|
||||
// Example:
|
||||
// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
|
||||
// fn p_2(mut self) -> Self where Self: Sized;
|
||||
pub trait StyleHelpers: Styleable<Style = Style> {
|
||||
styleable_helpers!();
|
||||
|
||||
fn h(mut self, height: Length) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().size.height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
/// size_{n}: Sets width & height to {n}
|
||||
///
|
||||
/// Example:
|
||||
/// size_1: Sets width & height to 1
|
||||
fn size(mut self, size: Length) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().size.height = Some(size);
|
||||
self.declared_style().size.width = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
fn full(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().size.width = Some(relative(1.));
|
||||
self.declared_style().size.height = Some(relative(1.));
|
||||
self
|
||||
}
|
||||
|
||||
fn relative(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().position = Some(Position::Relative);
|
||||
self
|
||||
}
|
||||
|
||||
fn absolute(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().position = Some(Position::Absolute);
|
||||
self
|
||||
}
|
||||
|
||||
fn block(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().display = Some(Display::Block);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().display = Some(Display::Flex);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_col(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_direction = Some(FlexDirection::Column);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_row(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_direction = Some(FlexDirection::Row);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_1(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(relative(0.));
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_auto(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_initial(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(0.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
fn flex_none(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(0.);
|
||||
self.declared_style().flex_shrink = Some(0.);
|
||||
self
|
||||
}
|
||||
|
||||
fn grow(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_start(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().align_items = Some(AlignItems::FlexStart);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_end(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().align_items = Some(AlignItems::FlexEnd);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_center(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().align_items = Some(AlignItems::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_between(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_center(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_start(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::Start);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_end(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::End);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_around(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
|
||||
self
|
||||
}
|
||||
|
||||
fn fill<F>(mut self, fill: F) -> Self
|
||||
where
|
||||
F: Into<Fill>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().fill = Some(fill.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn border_color<C>(mut self, border_color: C) -> Self
|
||||
where
|
||||
C: Into<Hsla>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().border_color = Some(border_color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_color<C>(mut self, color: C) -> Self
|
||||
where
|
||||
C: Into<Hsla>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().text_color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xs(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_size = Some(0.75);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_sm(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_size = Some(0.875);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_base(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_size = Some(1.0);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_lg(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_size = Some(1.125);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_size = Some(1.25);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_2xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_size = Some(1.5);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_3xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_size = Some(1.875);
|
||||
self
|
||||
}
|
||||
|
||||
fn font(mut self, family_name: impl Into<Arc<str>>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().font_family = Some(family_name.into());
|
||||
self
|
||||
}
|
||||
}
|
79
crates/gpui2/src/view_context.rs
Normal file
79
crates/gpui2/src/view_context.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use std::{any::TypeId, rc::Rc};
|
||||
|
||||
use crate::{element::LayoutId, style::Style};
|
||||
use anyhow::{anyhow, Result};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams};
|
||||
pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct ViewContext<'a, 'b, 'c, V> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub(crate) legacy_cx: &'c mut LegacyViewContext<'a, 'b, V>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> {
|
||||
pub fn new(legacy_cx: &'c mut LegacyViewContext<'a, 'b, V>) -> Self {
|
||||
Self { legacy_cx }
|
||||
}
|
||||
|
||||
pub fn add_layout_node(
|
||||
&mut self,
|
||||
style: Style,
|
||||
children: impl IntoIterator<Item = NodeId>,
|
||||
) -> Result<LayoutId> {
|
||||
let rem_size = self.rem_size();
|
||||
let style = style.to_taffy(rem_size);
|
||||
let id = self
|
||||
.legacy_cx
|
||||
.layout_engine()
|
||||
.ok_or_else(|| anyhow!("no layout engine"))?
|
||||
.add_node(style, children)?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn add_measured_layout_node<F>(&mut self, style: Style, measure: F) -> Result<LayoutId>
|
||||
where
|
||||
F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
|
||||
{
|
||||
let rem_size = self.rem_size();
|
||||
let layout_id = self
|
||||
.layout_engine()
|
||||
.ok_or_else(|| anyhow!("no layout engine"))?
|
||||
.add_measured_node(style.to_taffy(rem_size), measure)?;
|
||||
|
||||
Ok(layout_id)
|
||||
}
|
||||
|
||||
pub fn on_event<E: 'static>(
|
||||
&mut self,
|
||||
order: u32,
|
||||
handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
|
||||
) {
|
||||
let view = self.weak_handle();
|
||||
|
||||
self.scene().event_handlers.push(EventHandler {
|
||||
order,
|
||||
handler: Rc::new(move |event, window_cx| {
|
||||
if let Some(view) = view.upgrade(window_cx) {
|
||||
view.update(window_cx, |view, view_cx| {
|
||||
let mut event_cx = EventContext::new(view_cx);
|
||||
handler(view, event.downcast_ref().unwrap(), &mut event_cx);
|
||||
event_cx.bubble
|
||||
})
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}),
|
||||
event_type: TypeId::of::<E>(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
|
||||
self.layout_engine()
|
||||
.ok_or_else(|| anyhow!("no layout engine present"))?
|
||||
.computed_layout(layout_id)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
327
crates/gpui2_macros/src/styleable_helpers.rs
Normal file
327
crates/gpui2_macros/src/styleable_helpers.rs
Normal file
|
@ -0,0 +1,327 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input,
|
||||
};
|
||||
|
||||
struct StyleableMacroInput;
|
||||
|
||||
impl Parse for StyleableMacroInput {
|
||||
fn parse(_input: ParseStream) -> Result<Self> {
|
||||
Ok(StyleableMacroInput)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn styleable_helpers(input: TokenStream) -> TokenStream {
|
||||
let _ = parse_macro_input!(input as StyleableMacroInput);
|
||||
let methods = generate_methods();
|
||||
let output = quote! {
|
||||
#(#methods)*
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
||||
|
||||
fn generate_methods() -> Vec<TokenStream2> {
|
||||
let mut methods = Vec::new();
|
||||
|
||||
for (prefix, auto_allowed, fields) in box_prefixes() {
|
||||
for (suffix, length_tokens) in box_suffixes() {
|
||||
if auto_allowed || suffix != "auto" {
|
||||
let method = generate_method(prefix, suffix, &fields, length_tokens);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (prefix, fields) in corner_prefixes() {
|
||||
for (suffix, radius_tokens) in corner_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, radius_tokens);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
|
||||
for (prefix, fields) in border_prefixes() {
|
||||
for (suffix, width_tokens) in border_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, width_tokens);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
|
||||
methods
|
||||
}
|
||||
|
||||
fn generate_method(
|
||||
prefix: &'static str,
|
||||
suffix: &'static str,
|
||||
fields: &Vec<TokenStream2>,
|
||||
length_tokens: TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let method_name = if suffix.is_empty() {
|
||||
format_ident!("{}", prefix)
|
||||
} else {
|
||||
format_ident!("{}_{}", prefix, suffix)
|
||||
};
|
||||
|
||||
let field_assignments = fields
|
||||
.iter()
|
||||
.map(|field_tokens| {
|
||||
quote! {
|
||||
style.#field_tokens = Some(gpui::geometry::#length_tokens);
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let method = quote! {
|
||||
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
|
||||
let mut style = self.declared_style();
|
||||
#(#field_assignments)*
|
||||
self
|
||||
}
|
||||
};
|
||||
|
||||
method
|
||||
}
|
||||
|
||||
fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
|
||||
vec![
|
||||
("w", true, vec![quote! { size.width }]),
|
||||
("h", true, vec![quote! { size.height }]),
|
||||
(
|
||||
"size",
|
||||
true,
|
||||
vec![quote! {size.width}, quote! {size.height}],
|
||||
),
|
||||
("min_w", false, vec![quote! { min_size.width }]),
|
||||
("min_h", false, vec![quote! { min_size.height }]),
|
||||
("max_w", false, vec![quote! { max_size.width }]),
|
||||
("max_h", false, vec![quote! { max_size.height }]),
|
||||
(
|
||||
"m",
|
||||
true,
|
||||
vec![
|
||||
quote! { margin.top },
|
||||
quote! { margin.bottom },
|
||||
quote! { margin.left },
|
||||
quote! { margin.right },
|
||||
],
|
||||
),
|
||||
("mt", true, vec![quote! { margin.top }]),
|
||||
("mb", true, vec![quote! { margin.bottom }]),
|
||||
(
|
||||
"my",
|
||||
true,
|
||||
vec![quote! { margin.top }, quote! { margin.bottom }],
|
||||
),
|
||||
(
|
||||
"mx",
|
||||
true,
|
||||
vec![quote! { margin.left }, quote! { margin.right }],
|
||||
),
|
||||
("ml", true, vec![quote! { margin.left }]),
|
||||
("mr", true, vec![quote! { margin.right }]),
|
||||
(
|
||||
"p",
|
||||
false,
|
||||
vec![
|
||||
quote! { padding.top },
|
||||
quote! { padding.bottom },
|
||||
quote! { padding.left },
|
||||
quote! { padding.right },
|
||||
],
|
||||
),
|
||||
("pt", false, vec![quote! { padding.top }]),
|
||||
("pb", false, vec![quote! { padding.bottom }]),
|
||||
(
|
||||
"px",
|
||||
false,
|
||||
vec![quote! { padding.left }, quote! { padding.right }],
|
||||
),
|
||||
(
|
||||
"py",
|
||||
false,
|
||||
vec![quote! { padding.top }, quote! { padding.bottom }],
|
||||
),
|
||||
("pl", false, vec![quote! { padding.left }]),
|
||||
("pr", false, vec![quote! { padding.right }]),
|
||||
("top", true, vec![quote! { inset.top }]),
|
||||
("bottom", true, vec![quote! { inset.bottom }]),
|
||||
("left", true, vec![quote! { inset.left }]),
|
||||
("right", true, vec![quote! { inset.right }]),
|
||||
(
|
||||
"gap",
|
||||
false,
|
||||
vec![quote! { gap.width }, quote! { gap.height }],
|
||||
),
|
||||
("gap_x", false, vec![quote! { gap.width }]),
|
||||
("gap_y", false, vec![quote! { gap.height }]),
|
||||
]
|
||||
}
|
||||
|
||||
fn box_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
vec![
|
||||
("0", quote! { pixels(0.) }),
|
||||
("0p5", quote! { rems(0.125) }),
|
||||
("1", quote! { rems(0.25) }),
|
||||
("1p5", quote! { rems(0.375) }),
|
||||
("2", quote! { rems(0.5) }),
|
||||
("2p5", quote! { rems(0.625) }),
|
||||
("3", quote! { rems(0.75) }),
|
||||
("3p5", quote! { rems(0.875) }),
|
||||
("4", quote! { rems(1.) }),
|
||||
("5", quote! { rems(1.25) }),
|
||||
("6", quote! { rems(1.5) }),
|
||||
("7", quote! { rems(1.75) }),
|
||||
("8", quote! { rems(2.0) }),
|
||||
("9", quote! { rems(2.25) }),
|
||||
("10", quote! { rems(2.5) }),
|
||||
("11", quote! { rems(2.75) }),
|
||||
("12", quote! { rems(3.) }),
|
||||
("16", quote! { rems(4.) }),
|
||||
("20", quote! { rems(5.) }),
|
||||
("24", quote! { rems(6.) }),
|
||||
("32", quote! { rems(8.) }),
|
||||
("40", quote! { rems(10.) }),
|
||||
("48", quote! { rems(12.) }),
|
||||
("56", quote! { rems(14.) }),
|
||||
("64", quote! { rems(16.) }),
|
||||
("72", quote! { rems(18.) }),
|
||||
("80", quote! { rems(20.) }),
|
||||
("96", quote! { rems(24.) }),
|
||||
("auto", quote! { auto() }),
|
||||
("px", quote! { pixels(1.) }),
|
||||
("full", quote! { relative(1.) }),
|
||||
("1_2", quote! { relative(0.5) }),
|
||||
("1_3", quote! { relative(1./3.) }),
|
||||
("2_3", quote! { relative(2./3.) }),
|
||||
("1_4", quote! { relative(0.25) }),
|
||||
("2_4", quote! { relative(0.5) }),
|
||||
("3_4", quote! { relative(0.75) }),
|
||||
("1_5", quote! { relative(0.2) }),
|
||||
("2_5", quote! { relative(0.4) }),
|
||||
("3_5", quote! { relative(0.6) }),
|
||||
("4_5", quote! { relative(0.8) }),
|
||||
("1_6", quote! { relative(1./6.) }),
|
||||
("5_6", quote! { relative(5./6.) }),
|
||||
("1_12", quote! { relative(1./12.) }),
|
||||
// ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
|
||||
// ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
|
||||
// ("screen", quote! { DefiniteLength::Vh(100.0) }),
|
||||
]
|
||||
}
|
||||
|
||||
fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
|
||||
vec![
|
||||
(
|
||||
"rounded",
|
||||
vec![
|
||||
quote! { corner_radii.top_left },
|
||||
quote! { corner_radii.top_right },
|
||||
quote! { corner_radii.bottom_right },
|
||||
quote! { corner_radii.bottom_left },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_t",
|
||||
vec![
|
||||
quote! { corner_radii.top_left },
|
||||
quote! { corner_radii.top_right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_b",
|
||||
vec![
|
||||
quote! { corner_radii.bottom_left },
|
||||
quote! { corner_radii.bottom_right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_r",
|
||||
vec![
|
||||
quote! { corner_radii.top_right },
|
||||
quote! { corner_radii.bottom_right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"rounded_l",
|
||||
vec![
|
||||
quote! { corner_radii.top_left },
|
||||
quote! { corner_radii.bottom_left },
|
||||
],
|
||||
),
|
||||
("rounded_tl", vec![quote! { corner_radii.top_left }]),
|
||||
("rounded_tr", vec![quote! { corner_radii.top_right }]),
|
||||
("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
|
||||
("rounded_br", vec![quote! { corner_radii.bottom_right }]),
|
||||
]
|
||||
}
|
||||
|
||||
fn corner_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
vec![
|
||||
("none", quote! { pixels(0.) }),
|
||||
("sm", quote! { rems(0.125) }),
|
||||
("md", quote! { rems(0.25) }),
|
||||
("lg", quote! { rems(0.5) }),
|
||||
("xl", quote! { rems(0.75) }),
|
||||
("2xl", quote! { rems(1.) }),
|
||||
("3xl", quote! { rems(1.5) }),
|
||||
("full", quote! { pixels(9999.) }),
|
||||
]
|
||||
}
|
||||
|
||||
fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
|
||||
vec![
|
||||
(
|
||||
"border",
|
||||
vec![
|
||||
quote! { border_widths.top },
|
||||
quote! { border_widths.right },
|
||||
quote! { border_widths.bottom },
|
||||
quote! { border_widths.left },
|
||||
],
|
||||
),
|
||||
("border_t", vec![quote! { border_widths.top }]),
|
||||
("border_b", vec![quote! { border_widths.bottom }]),
|
||||
("border_r", vec![quote! { border_widths.right }]),
|
||||
("border_l", vec![quote! { border_widths.left }]),
|
||||
(
|
||||
"border_x",
|
||||
vec![
|
||||
quote! { border_widths.left },
|
||||
quote! { border_widths.right },
|
||||
],
|
||||
),
|
||||
(
|
||||
"border_y",
|
||||
vec![
|
||||
quote! { border_widths.top },
|
||||
quote! { border_widths.bottom },
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn border_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
vec![
|
||||
("", quote! { pixels(1.) }),
|
||||
("0", quote! { pixels(0.) }),
|
||||
("1", quote! { pixels(1.) }),
|
||||
("2", quote! { pixels(2.) }),
|
||||
("3", quote! { pixels(3.) }),
|
||||
("4", quote! { pixels(4.) }),
|
||||
("5", quote! { pixels(5.) }),
|
||||
("6", quote! { pixels(6.) }),
|
||||
("7", quote! { pixels(7.) }),
|
||||
("8", quote! { pixels(8.) }),
|
||||
("9", quote! { pixels(9.) }),
|
||||
("10", quote! { pixels(10.) }),
|
||||
("11", quote! { pixels(11.) }),
|
||||
("12", quote! { pixels(12.) }),
|
||||
("16", quote! { pixels(16.) }),
|
||||
("20", quote! { pixels(20.) }),
|
||||
("24", quote! { pixels(24.) }),
|
||||
("32", quote! { pixels(32.) }),
|
||||
]
|
||||
}
|
|
@ -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",
|
23
crates/storybook/Cargo.toml
Normal file
23
crates/storybook/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "storybook"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "storybook"
|
||||
path = "src/storybook.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui2 = { path = "../gpui2" }
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
simplelog = "0.9"
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui2 = { path = "../gpui2", features = ["test-support"] }
|
177
crates/storybook/src/collab_panel.rs
Normal file
177
crates/storybook/src/collab_panel.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use crate::theme::{theme, Theme};
|
||||
use gpui2::{
|
||||
elements::{div, div::ScrollState, img, svg},
|
||||
style::{StyleHelpers, Styleable},
|
||||
ArcCow, Element, IntoElement, ParentElement, ViewContext,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct CollabPanelElement<V: 'static> {
|
||||
view_type: PhantomData<V>,
|
||||
scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
// When I improve child view rendering, I'd like to have V implement a trait that
|
||||
// provides the scroll state, among other things.
|
||||
pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
|
||||
CollabPanelElement {
|
||||
view_type: PhantomData,
|
||||
scroll_state,
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> CollabPanelElement<V> {
|
||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
// Panel
|
||||
div()
|
||||
.w_64()
|
||||
.h_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans Extended")
|
||||
.text_color(theme.middle.base.default.foreground)
|
||||
.border_color(theme.middle.base.default.border)
|
||||
.border()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.overflow_y_scroll(self.scroll_state.clone())
|
||||
// List Container
|
||||
.child(
|
||||
div()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.pb_1()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
.border_b()
|
||||
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
||||
// .group()
|
||||
// List Section Header
|
||||
.child(self.list_section_header("#CRDB", true, theme))
|
||||
// List Item Large
|
||||
.child(self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
theme,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CHANNELS", true, theme)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CONTACTS", true, theme))
|
||||
.children(
|
||||
std::iter::repeat_with(|| {
|
||||
vec![
|
||||
self.list_item(
|
||||
"http://github.com/as-cii.png?s=50",
|
||||
"as-cii",
|
||||
theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/nathansobo.png?s=50",
|
||||
"nathansobo",
|
||||
theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
theme,
|
||||
),
|
||||
]
|
||||
})
|
||||
.take(10)
|
||||
.flatten(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.border_t()
|
||||
.border_color(theme.middle.variant.default.border)
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.middle.variant.default.foreground)
|
||||
.child("Find..."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_section_header(
|
||||
&self,
|
||||
label: impl Into<ArcCow<'static, str>>,
|
||||
expanded: bool,
|
||||
theme: &Theme,
|
||||
) -> impl Element<V> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(div().flex().gap_1().text_sm().child(label))
|
||||
.child(
|
||||
div().flex().h_full().gap_1().items_center().child(
|
||||
svg()
|
||||
.path(if expanded {
|
||||
"icons/radix/caret-down.svg"
|
||||
} else {
|
||||
"icons/radix/caret-up.svg"
|
||||
})
|
||||
.w_3p5()
|
||||
.h_3p5()
|
||||
.fill(theme.middle.variant.default.foreground),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_item(
|
||||
&self,
|
||||
avatar_uri: impl Into<ArcCow<'static, str>>,
|
||||
label: impl Into<ArcCow<'static, str>>,
|
||||
theme: &Theme,
|
||||
) -> impl Element<V> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.variant.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.variant.pressed.background)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
img()
|
||||
.uri(avatar_uri)
|
||||
.size_3p5()
|
||||
.rounded_full()
|
||||
.fill(theme.middle.positive.default.foreground),
|
||||
)
|
||||
.child(label),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
22
crates/storybook/src/element_ext.rs
Normal file
22
crates/storybook/src/element_ext.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use crate::theme::{Theme, Themed};
|
||||
use gpui2::Element;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub trait ElementExt<V: 'static>: Element<V> {
|
||||
fn themed(self, theme: Theme) -> Themed<V, Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V>> ElementExt<V> for E {
|
||||
fn themed(self, theme: Theme) -> Themed<V, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Themed {
|
||||
child: self,
|
||||
theme,
|
||||
view_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
109
crates/storybook/src/storybook.rs
Normal file
109
crates/storybook/src/storybook.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
use crate::theme::Theme;
|
||||
use ::theme as legacy_theme;
|
||||
use element_ext::ElementExt;
|
||||
use gpui2::{serde_json, vec2f, view, Element, RectF, ViewContext, WindowBounds};
|
||||
use legacy_theme::ThemeSettings;
|
||||
use log::LevelFilter;
|
||||
use settings::{default_settings, SettingsStore};
|
||||
use simplelog::SimpleLogger;
|
||||
|
||||
mod collab_panel;
|
||||
mod components;
|
||||
mod element_ext;
|
||||
mod theme;
|
||||
mod workspace;
|
||||
|
||||
gpui2::actions! {
|
||||
storybook,
|
||||
[ToggleInspector]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
||||
gpui2::App::new(Assets).unwrap().run(|cx| {
|
||||
let mut store = SettingsStore::default();
|
||||
store
|
||||
.set_default_settings(default_settings().as_ref(), cx)
|
||||
.unwrap();
|
||||
cx.set_global(store);
|
||||
legacy_theme::init(Assets, cx);
|
||||
// load_embedded_fonts(cx.platform().as_ref());
|
||||
|
||||
cx.add_window(
|
||||
gpui2::WindowOptions {
|
||||
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1400., 900.))),
|
||||
center: true,
|
||||
..Default::default()
|
||||
},
|
||||
|cx| {
|
||||
view(|cx| {
|
||||
cx.enable_inspector();
|
||||
storybook(&mut ViewContext::new(cx))
|
||||
})
|
||||
},
|
||||
);
|
||||
cx.platform().activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
fn storybook<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<V> {
|
||||
workspace().themed(current_theme(cx))
|
||||
}
|
||||
|
||||
// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct.
|
||||
fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
|
||||
settings::get::<ThemeSettings>(cx)
|
||||
.theme
|
||||
.deserialized_base_theme
|
||||
.lock()
|
||||
.get_or_insert_with(|| {
|
||||
let theme: Theme =
|
||||
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
|
||||
.unwrap();
|
||||
Box::new(theme)
|
||||
})
|
||||
.downcast_ref::<Theme>()
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use gpui2::AssetSource;
|
||||
use rust_embed::RustEmbed;
|
||||
use workspace::workspace;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
#[include = "themes/**/*"]
|
||||
#[include = "fonts/**/*"]
|
||||
#[include = "icons/**/*"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct Assets;
|
||||
|
||||
impl AssetSource for Assets {
|
||||
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
|
||||
Self::get(path)
|
||||
.map(|f| f.data)
|
||||
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||
}
|
||||
|
||||
fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
|
||||
Self::iter().filter(|p| p.starts_with(path)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
|
||||
// let font_paths = Assets.list("fonts");
|
||||
// let mut embedded_fonts = Vec::new();
|
||||
// for font_path in &font_paths {
|
||||
// if font_path.ends_with(".ttf") {
|
||||
// let font_path = &*font_path;
|
||||
// let font_bytes = Assets.load(font_path).unwrap().to_vec();
|
||||
// embedded_fonts.push(Arc::from(font_bytes));
|
||||
// }
|
||||
// }
|
||||
// platform.fonts().add_fonts(&embedded_fonts).unwrap();
|
||||
// }
|
192
crates/storybook/src/theme.rs
Normal file
192
crates/storybook/src/theme.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
use gpui2::{
|
||||
color::Hsla, element::Element, serde_json, AppContext, IntoElement, Vector2F, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||
use std::{collections::HashMap, fmt, marker::PhantomData};
|
||||
use theme::ThemeSettings;
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Theme {
|
||||
pub name: String,
|
||||
pub is_light: bool,
|
||||
pub lowest: Layer,
|
||||
pub middle: Layer,
|
||||
pub highest: Layer,
|
||||
pub popover_shadow: Shadow,
|
||||
pub modal_shadow: Shadow,
|
||||
#[serde(deserialize_with = "deserialize_player_colors")]
|
||||
pub players: Vec<PlayerColors>,
|
||||
#[serde(deserialize_with = "deserialize_syntax_colors")]
|
||||
pub syntax: HashMap<String, Hsla>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Layer {
|
||||
pub base: StyleSet,
|
||||
pub variant: StyleSet,
|
||||
pub on: StyleSet,
|
||||
pub accent: StyleSet,
|
||||
pub positive: StyleSet,
|
||||
pub warning: StyleSet,
|
||||
pub negative: StyleSet,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct StyleSet {
|
||||
#[serde(rename = "default")]
|
||||
pub default: ContainerColors,
|
||||
pub hovered: ContainerColors,
|
||||
pub pressed: ContainerColors,
|
||||
pub active: ContainerColors,
|
||||
pub disabled: ContainerColors,
|
||||
pub inverted: ContainerColors,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct ContainerColors {
|
||||
pub background: Hsla,
|
||||
pub foreground: Hsla,
|
||||
pub border: Hsla,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct PlayerColors {
|
||||
pub selection: Hsla,
|
||||
pub cursor: Hsla,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Default, Debug)]
|
||||
pub struct Shadow {
|
||||
pub blur: u8,
|
||||
pub color: Hsla,
|
||||
pub offset: Vec<u8>,
|
||||
}
|
||||
|
||||
fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct PlayerArrayVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for PlayerArrayVisitor {
|
||||
type Value = Vec<PlayerColors>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("an object with integer keys")
|
||||
}
|
||||
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(
|
||||
self,
|
||||
mut map: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
let mut players = Vec::with_capacity(8);
|
||||
while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
|
||||
if key < 8 {
|
||||
players.push(value);
|
||||
} else {
|
||||
return Err(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Unsigned(key as u64),
|
||||
&"a key in range 0..7",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(players)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(PlayerArrayVisitor)
|
||||
}
|
||||
|
||||
fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct ColorWrapper {
|
||||
color: Hsla,
|
||||
}
|
||||
|
||||
struct SyntaxVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for SyntaxVisitor {
|
||||
type Value = HashMap<String, Hsla>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a map with keys and objects with a single color field as values")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
|
||||
where
|
||||
M: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let mut result = HashMap::new();
|
||||
while let Some(key) = map.next_key()? {
|
||||
let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
|
||||
result.insert(key, wrapper.color);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_map(SyntaxVisitor)
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Themed<V: 'static, E: Element<V>> {
|
||||
pub(crate) theme: Theme,
|
||||
pub(crate) child: E,
|
||||
pub(crate) view_type: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V: 'static, E: Element<V>> Element<V> for Themed<V, E> {
|
||||
type PaintState = E::PaintState;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> anyhow::Result<(gpui2::LayoutId, Self::PaintState)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
cx.push_theme(self.theme.clone());
|
||||
let result = self.child.layout(view, cx);
|
||||
cx.pop_theme();
|
||||
result
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
view: &mut V,
|
||||
parent_origin: Vector2F,
|
||||
layout: &gpui2::Layout,
|
||||
state: &mut Self::PaintState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
cx.push_theme(self.theme.clone());
|
||||
self.child.paint(view, parent_origin, layout, state, cx);
|
||||
cx.pop_theme();
|
||||
}
|
||||
}
|
||||
|
||||
fn preferred_theme<V: 'static>(cx: &AppContext) -> Theme {
|
||||
settings::get::<ThemeSettings>(cx)
|
||||
.theme
|
||||
.deserialized_base_theme
|
||||
.lock()
|
||||
.get_or_insert_with(|| {
|
||||
let theme: Theme =
|
||||
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
|
||||
.unwrap();
|
||||
Box::new(theme)
|
||||
})
|
||||
.downcast_ref::<Theme>()
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
|
||||
cx.theme::<Theme>()
|
||||
}
|
435
crates/storybook/src/workspace.rs
Normal file
435
crates/storybook/src/workspace.rs
Normal file
|
@ -0,0 +1,435 @@
|
|||
use crate::{collab_panel::collab_panel, theme::theme};
|
||||
use gpui2::{
|
||||
elements::{div, div::ScrollState, img, svg},
|
||||
style::{StyleHelpers, Styleable},
|
||||
Element, IntoElement, ParentElement, ViewContext,
|
||||
};
|
||||
|
||||
#[derive(Element, Default)]
|
||||
struct WorkspaceElement {
|
||||
left_scroll_state: ScrollState,
|
||||
right_scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
pub fn workspace<V: 'static>() -> impl Element<V> {
|
||||
WorkspaceElement::default()
|
||||
}
|
||||
|
||||
impl WorkspaceElement {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans Extended")
|
||||
.gap_0()
|
||||
.justify_start()
|
||||
.items_start()
|
||||
.text_color(theme.lowest.base.default.foreground)
|
||||
.fill(theme.middle.base.default.background)
|
||||
.child(titlebar())
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.overflow_hidden()
|
||||
.child(collab_panel(self.left_scroll_state.clone()))
|
||||
.child(div().h_full().flex_1())
|
||||
.child(collab_panel(self.right_scroll_state.clone())),
|
||||
)
|
||||
.child(statusbar())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Element)]
|
||||
struct TitleBar;
|
||||
|
||||
pub fn titlebar<V: 'static>() -> impl Element<V> {
|
||||
TitleBar
|
||||
}
|
||||
|
||||
impl TitleBar {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.child(self.left_group(cx))
|
||||
.child(self.right_group(cx))
|
||||
}
|
||||
|
||||
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Traffic Lights === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.positive.default.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.warning.default.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
),
|
||||
)
|
||||
// === Project Info === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.px_2()
|
||||
.rounded_md()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(div().text_sm().child("project")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.px_2()
|
||||
.rounded_md()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(div().text_sm().child("branch")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_3()
|
||||
.px_2()
|
||||
// === Actions === //
|
||||
.child(
|
||||
div().child(
|
||||
div().flex().items_center().gap_1().child(
|
||||
div().size_4().flex().items_center().justify_center().child(
|
||||
svg()
|
||||
.path("icons/exit.svg")
|
||||
.size_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
// === Comms === //
|
||||
.child(
|
||||
div().child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/microphone.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/radix/speaker-loud.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.rounded_md()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/radix/desktop.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
// User Group
|
||||
.child(
|
||||
div().child(
|
||||
div()
|
||||
.px_1()
|
||||
.py_1()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_md()
|
||||
.gap_0p5()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
img()
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size_4()
|
||||
.rounded_md()
|
||||
.fill(theme.middle.on.default.foreground),
|
||||
)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/caret_down_8.svg")
|
||||
.w_2()
|
||||
.h_2()
|
||||
.fill(theme.lowest.variant.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================ //
|
||||
|
||||
#[derive(Element)]
|
||||
struct StatusBar;
|
||||
|
||||
pub fn statusbar<V: 'static>() -> impl Element<V> {
|
||||
StatusBar
|
||||
}
|
||||
|
||||
impl StatusBar {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.child(self.left_group(cx))
|
||||
.child(self.right_group(cx))
|
||||
}
|
||||
|
||||
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Tools === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/project.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/conversations.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/file_icons/notebook.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
// === Diagnostics === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.gap_0p5()
|
||||
.px_1()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/error.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
)
|
||||
.child(div().text_sm().child("2")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.lowest.variant.default.foreground)
|
||||
.child("Something is wrong"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_full()
|
||||
.gap_4()
|
||||
.px_2()
|
||||
// === Tools === //
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/check_circle.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_6()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/copilot.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue