Rework element system, phase 1 (#2881)

This is a deep cut. There's still more work to do until we start
building UI with this. I've approached this as additively as possible,
but I've made a few changes to the rest of the code that I think would
be good to upstream before proceeding too much further.

Most of the interesting pieces are in gpui/playground, which is a
standalone binary that opens a single window and renders a new kind of
element. The layout of these new elements is provided by the taffy
layout engine crate, which conforms to web conventions. The idea is that
playground is relatively cheap to build and work on. As concepts
coalesce in playground, we can drop them into gpui and start
transitioning.
This commit is contained in:
Nathan Sobo 2023-08-23 08:26:38 -06:00 committed by GitHub
commit 1bc4f22373
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 12235 additions and 415 deletions

75
Cargo.lock generated
View file

@ -2085,6 +2085,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "derive_refineable"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "dhat"
version = "0.3.2"
@ -3067,6 +3076,7 @@ dependencies = [
"png",
"postage",
"rand 0.8.5",
"refineable",
"resvg",
"schemars",
"seahash",
@ -3078,6 +3088,7 @@ dependencies = [
"smol",
"sqlez",
"sum_tree",
"taffy",
"time 0.3.24",
"tiny-skia",
"usvg",
@ -3090,11 +3101,18 @@ dependencies = [
name = "gpui_macros"
version = "0.1.0"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "grid"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eec1c01eb1de97451ee0d60de7d81cf1e72aabefb021616027f3d1c3ec1c723c"
[[package]]
name = "h2"
version = "0.3.20"
@ -5087,6 +5105,33 @@ 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"
@ -5764,6 +5809,16 @@ dependencies = [
"thiserror",
]
[[package]]
name = "refineable"
version = "0.1.0"
dependencies = [
"derive_refineable",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "regalloc2"
version = "0.2.3"
@ -6929,6 +6984,15 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7"
[[package]]
name = "slotmap"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
"version_check",
]
[[package]]
name = "sluice"
version = "0.5.5"
@ -7368,6 +7432,17 @@ dependencies = [
"winx",
]
[[package]]
name = "taffy"
version = "0.3.11"
source = "git+https://github.com/DioxusLabs/taffy?rev=dab541d6104d58e2e10ce90c4a1dad0b703160cd#dab541d6104d58e2e10ce90c4a1dad0b703160cd"
dependencies = [
"arrayvec 0.7.4",
"grid",
"num-traits",
"slotmap",
]
[[package]]
name = "take-until"
version = "0.2.0"

View file

@ -17,6 +17,8 @@ members = [
"crates/copilot",
"crates/copilot_button",
"crates/db",
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/diagnostics",
"crates/drag_and_drop",
"crates/editor",
@ -28,6 +30,8 @@ members = [
"crates/git",
"crates/go_to_line",
"crates/gpui",
"crates/gpui/playground",
"crates/gpui/playground_macros",
"crates/gpui_macros",
"crates/install_cli",
"crates/journal",
@ -92,6 +96,7 @@ ordered-float = { version = "2.1.1" }
parking_lot = { version = "0.11.1" }
postage = { version = "0.5", features = ["futures-traits"] }
rand = { version = "0.8.5" }
refineable = { path = "./crates/refineable" }
regex = { version = "1.5" }
rust-embed = { version = "6.3", features = ["include-exclude"] }
schemars = { version = "0.8" }

View file

@ -1096,7 +1096,7 @@ impl CollabTitlebarItem {
style
}
fn render_face<V: View>(
fn render_face<V: 'static>(
avatar: Arc<ImageData>,
avatar_style: AvatarStyle,
background_color: Color,

View file

@ -2,14 +2,14 @@ use client::User;
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
AnyElement, Element, View, ViewContext,
AnyElement, Element, ViewContext,
};
use std::sync::Arc;
enum Dismiss {}
enum Button {}
pub fn render_user_notification<F, V>(
pub fn render_user_notification<F, V: 'static>(
user: Arc<User>,
title: &'static str,
body: Option<&'static str>,
@ -19,7 +19,6 @@ pub fn render_user_notification<F, V>(
) -> AnyElement<V>
where
F: 'static + Fn(&mut V, &mut ViewContext<V>),
V: View,
{
let theme = theme::current(cx).clone();
let theme = &theme.contact_notification;

View file

@ -538,7 +538,7 @@ impl ProjectDiagnosticsEditor {
}
impl Item for ProjectDiagnosticsEditor {
fn tab_content<T: View>(
fn tab_content<T: 'static>(
&self,
_detail: Option<usize>,
style: &theme::Tab,
@ -735,7 +735,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
})
}
pub(crate) fn render_summary<T: View>(
pub(crate) fn render_summary<T: 'static>(
summary: &DiagnosticSummary,
text_style: &TextStyle,
theme: &theme::ProjectDiagnostics,

View file

@ -11,7 +11,7 @@ use gpui::{
const DEAD_ZONE: f32 = 4.;
enum State<V: View> {
enum State<V> {
Down {
region_offset: Vector2F,
region: RectF,
@ -31,7 +31,7 @@ enum State<V: View> {
Canceled,
}
impl<V: View> Clone for State<V> {
impl<V> Clone for State<V> {
fn clone(&self) -> Self {
match self {
&State::Down {
@ -68,12 +68,12 @@ impl<V: View> Clone for State<V> {
}
}
pub struct DragAndDrop<V: View> {
pub struct DragAndDrop<V> {
containers: HashSet<WeakViewHandle<V>>,
currently_dragged: Option<State<V>>,
}
impl<V: View> Default for DragAndDrop<V> {
impl<V> Default for DragAndDrop<V> {
fn default() -> Self {
Self {
containers: Default::default(),
@ -82,7 +82,7 @@ impl<V: View> Default for DragAndDrop<V> {
}
}
impl<V: View> DragAndDrop<V> {
impl<V: 'static> DragAndDrop<V> {
pub fn register_container(&mut self, handle: WeakViewHandle<V>) {
self.containers.insert(handle);
}
@ -291,7 +291,7 @@ impl<V: View> DragAndDrop<V> {
}
}
pub trait Draggable<V: View> {
pub trait Draggable<V> {
fn as_draggable<D: View, P: Any>(
self,
payload: P,
@ -301,7 +301,7 @@ pub trait Draggable<V: View> {
Self: Sized;
}
impl<V: View> Draggable<V> for MouseEventHandler<V> {
impl<V: 'static> Draggable<V> for MouseEventHandler<V> {
fn as_draggable<D: View, P: Any>(
self,
payload: P,

View file

@ -8563,6 +8563,7 @@ fn build_style(
font_size,
font_properties,
underline: Default::default(),
soft_wrap: false,
},
placeholder_text: None,
line_height_scalar,

View file

@ -605,7 +605,7 @@ impl EditorElement {
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
cx: &mut PaintContext<Editor>,
) {
let line_height = layout.position_map.line_height;
@ -760,7 +760,7 @@ impl EditorElement {
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
cx: &mut PaintContext<Editor>,
) {
let style = &self.style;
let local_replica_id = editor.replica_id(cx);
@ -1337,7 +1337,7 @@ impl EditorElement {
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
cx: &mut PaintContext<Editor>,
) {
let scroll_position = layout.position_map.snapshot.scroll_position();
let scroll_left = scroll_position.x() * layout.position_map.em_width;

View file

@ -561,7 +561,7 @@ impl Item for Editor {
}
}
fn tab_content<T: View>(
fn tab_content<T: 'static>(
&self,
detail: Option<usize>,
style: &theme::Tab,

View file

@ -268,7 +268,7 @@ impl Item for FeedbackEditor {
Some("Send Feedback".into())
}
fn tab_content<T: View>(
fn tab_content<T: 'static>(
&self,
_: Option<usize>,
style: &theme::Tab,

View file

@ -39,6 +39,7 @@ pathfinder_color = "0.5"
pathfinder_geometry = "0.5"
postage.workspace = true
rand.workspace = true
refineable.workspace = true
resvg = "0.14"
schemars = "0.8"
seahash = "4.1"
@ -47,6 +48,7 @@ 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"] }
time.workspace = true
tiny-skia = "0.5"
usvg = { version = "0.14", features = [] }

View file

@ -58,6 +58,7 @@ impl gpui::View for TextView {
font_family_id: family,
underline: Default::default(),
font_properties: Default::default(),
soft_wrap: false,
},
)
.with_highlights(vec![(17..26, underline), (34..40, underline)])

2919
crates/gpui/playground/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,72 @@
Much of element styling is now handled by an external engine.
How do I make an element hover.
There's a hover style.
Hoverable needs to wrap another element. That element can be styled.
```rs
struct Hoverable<E: Element> {
}
impl<V> Element<V> for Hoverable {
}
```
```rs
#[derive(Styled, Interactive)]
pub struct Div {
declared_style: StyleRefinement,
interactions: Interactions
}
pub trait Styled {
fn declared_style(&mut self) -> &mut StyleRefinement;
fn compute_style(&mut self) -> Style {
Style::default().refine(self.declared_style())
}
// All the tailwind classes, modifying self.declared_style()
}
impl Style {
pub fn paint_background<V>(layout: Layout, cx: &mut PaintContext<V>);
pub fn paint_foreground<V>(layout: Layout, cx: &mut PaintContext<V>);
}
pub trait Interactive<V> {
fn interactions(&mut self) -> &mut Interactions<V>;
fn on_click(self, )
}
struct Interactions<V> {
click: SmallVec<[<Rc<dyn Fn(&mut V, &dyn Any, )>; 1]>,
}
```
```rs
trait Stylable {
type Style;
fn with_style(self, style: Self::Style) -> Self;
}
```

View file

@ -0,0 +1,78 @@
use crate::{layout_context::LayoutContext, paint_context::PaintContext};
use gpui::{geometry::rect::RectF, LayoutEngine, LayoutId};
use util::ResultExt;
/// Makes a new, playground-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> {
type LayoutState = Option<(LayoutEngine, LayoutId)>;
type PaintState = ();
fn layout(
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
cx: &mut gpui::LayoutContext<V>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
cx.push_layout_engine(LayoutEngine::new());
let size = constraint.max;
let mut cx = LayoutContext::new(cx);
let layout_id = self.0.layout(view, &mut cx).log_err();
if let Some(layout_id) = layout_id {
cx.layout_engine()
.unwrap()
.compute_layout(layout_id, constraint.max)
.log_err();
}
let layout_engine = cx.pop_layout_engine();
debug_assert!(layout_engine.is_some(),
"unexpected layout stack state. is there an unmatched pop_layout_engine in the called code?"
);
(constraint.max, layout_engine.zip(layout_id))
}
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout_data: &mut Option<(LayoutEngine, LayoutId)>,
view: &mut V,
legacy_cx: &mut gpui::PaintContext<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));
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>,
) -> 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>,
) -> gpui::serde_json::Value {
todo!("implement before merging to main")
}
}

View file

@ -0,0 +1,276 @@
#![allow(dead_code)]
use std::{num::ParseIntError, ops::Range};
use smallvec::SmallVec;
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
let b = (hex & 0xFF) as f32 / 255.0;
Rgba { r, g, b, a: 1.0 }.into()
}
#[derive(Clone, Copy, Default, Debug)]
pub struct Rgba {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
pub trait Lerp {
fn lerp(&self, level: f32) -> Hsla;
}
impl Lerp for Range<Hsla> {
fn lerp(&self, level: f32) -> Hsla {
let level = level.clamp(0., 1.);
Hsla {
h: self.start.h + (level * (self.end.h - self.start.h)),
s: self.start.s + (level * (self.end.s - self.start.s)),
l: self.start.l + (level * (self.end.l - self.start.l)),
a: self.start.a + (level * (self.end.a - self.start.a)),
}
}
}
impl From<gpui::color::Color> for Rgba {
fn from(value: gpui::color::Color) -> Self {
Self {
r: value.0.r as f32 / 255.0,
g: value.0.g as f32 / 255.0,
b: value.0.b as f32 / 255.0,
a: value.0.a as f32 / 255.0,
}
}
}
impl From<Hsla> for Rgba {
fn from(color: Hsla) -> Self {
let h = color.h;
let s = color.s;
let l = color.l;
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
let m = l - c / 2.0;
let cm = c + m;
let xm = x + m;
let (r, g, b) = match (h * 6.0).floor() as i32 {
0 | 6 => (cm, xm, m),
1 => (xm, cm, m),
2 => (m, cm, xm),
3 => (m, xm, cm),
4 => (xm, m, cm),
_ => (cm, m, xm),
};
Rgba {
r,
g,
b,
a: color.a,
}
}
}
impl TryFrom<&'_ str> for Rgba {
type Error = ParseIntError;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
let a = if value.len() > 7 {
u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
} else {
1.0
};
Ok(Rgba { r, g, b, a })
}
}
impl Into<gpui::color::Color> for Rgba {
fn into(self) -> gpui::color::Color {
gpui::color::rgba(self.r, self.g, self.b, self.a)
}
}
#[derive(Default, Copy, Clone, Debug, PartialEq)]
pub struct Hsla {
pub h: f32,
pub s: f32,
pub l: f32,
pub a: f32,
}
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla {
h: h.clamp(0., 1.),
s: s.clamp(0., 1.),
l: l.clamp(0., 1.),
a: a.clamp(0., 1.),
}
}
pub fn black() -> Hsla {
Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 1.,
}
}
impl From<Rgba> for Hsla {
fn from(color: Rgba) -> Self {
let r = color.r;
let g = color.g;
let b = color.b;
let max = r.max(g.max(b));
let min = r.min(g.min(b));
let delta = max - min;
let l = (max + min) / 2.0;
let s = if l == 0.0 || l == 1.0 {
0.0
} else if l < 0.5 {
delta / (2.0 * l)
} else {
delta / (2.0 - 2.0 * l)
};
let h = if delta == 0.0 {
0.0
} else if max == r {
((g - b) / delta).rem_euclid(6.0) / 6.0
} else if max == g {
((b - r) / delta + 2.0) / 6.0
} else {
((r - g) / delta + 4.0) / 6.0
};
Hsla {
h,
s,
l,
a: color.a,
}
}
}
impl Hsla {
/// Scales the saturation and lightness by the given values, clamping at 1.0.
pub fn scale_sl(mut self, s: f32, l: f32) -> Self {
self.s = (self.s * s).clamp(0., 1.);
self.l = (self.l * l).clamp(0., 1.);
self
}
/// Increases the saturation of the color by a certain amount, with a max
/// value of 1.0.
pub fn saturate(mut self, amount: f32) -> Self {
self.s += amount;
self.s = self.s.clamp(0.0, 1.0);
self
}
/// Decreases the saturation of the color by a certain amount, with a min
/// value of 0.0.
pub fn desaturate(mut self, amount: f32) -> Self {
self.s -= amount;
self.s = self.s.max(0.0);
if self.s < 0.0 {
self.s = 0.0;
}
self
}
/// Brightens the color by increasing the lightness by a certain amount,
/// with a max value of 1.0.
pub fn brighten(mut self, amount: f32) -> Self {
self.l += amount;
self.l = self.l.clamp(0.0, 1.0);
self
}
/// Darkens the color by decreasing the lightness by a certain amount,
/// with a max value of 0.0.
pub fn darken(mut self, amount: f32) -> Self {
self.l -= amount;
self.l = self.l.clamp(0.0, 1.0);
self
}
}
impl From<gpui::color::Color> for Hsla {
fn from(value: gpui::color::Color) -> Self {
Rgba::from(value).into()
}
}
impl Into<gpui::color::Color> for Hsla {
fn into(self) -> gpui::color::Color {
Rgba::from(self).into()
}
}
pub struct ColorScale {
colors: SmallVec<[Hsla; 2]>,
positions: SmallVec<[f32; 2]>,
}
pub fn scale<I, C>(colors: I) -> ColorScale
where
I: IntoIterator<Item = C>,
C: Into<Hsla>,
{
let mut scale = ColorScale {
colors: colors.into_iter().map(Into::into).collect(),
positions: SmallVec::new(),
};
let num_colors: f32 = scale.colors.len() as f32 - 1.0;
scale.positions = (0..scale.colors.len())
.map(|i| i as f32 / num_colors)
.collect();
scale
}
impl ColorScale {
fn at(&self, t: f32) -> Hsla {
// Ensure that the input is within [0.0, 1.0]
debug_assert!(
0.0 <= t && t <= 1.0,
"t value {} is out of range. Expected value in range 0.0 to 1.0",
t
);
let position = match self
.positions
.binary_search_by(|a| a.partial_cmp(&t).unwrap())
{
Ok(index) | Err(index) => index,
};
let lower_bound = position.saturating_sub(1);
let upper_bound = position.min(self.colors.len() - 1);
let lower_color = &self.colors[lower_bound];
let upper_color = &self.colors[upper_bound];
match upper_bound.checked_sub(lower_bound) {
Some(0) | None => *lower_color,
Some(_) => {
let interval_t = (t - self.positions[lower_bound])
/ (self.positions[upper_bound] - self.positions[lower_bound]);
let h = lower_color.h + interval_t * (upper_color.h - lower_color.h);
let s = lower_color.s + interval_t * (upper_color.s - lower_color.s);
let l = lower_color.l + interval_t * (upper_color.l - lower_color.l);
let a = lower_color.a + interval_t * (upper_color.a - lower_color.a);
Hsla { h, s, l, a }
}
}
}
}

View file

@ -0,0 +1,100 @@
use crate::{
div::div,
element::{Element, ParentElement},
style::StyleHelpers,
text::ArcCow,
themes::rose_pine,
};
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>)>>,
}
impl<V, D> Default for ButtonHandlers<V, D> {
fn default() -> Self {
Self { click: None }
}
}
use crate as playground;
#[derive(Element)]
pub struct Button<V: 'static, D: 'static> {
handlers: ButtonHandlers<V, D>,
label: Option<ArcCow<'static, str>>,
icon: Option<ArcCow<'static, str>>,
data: Rc<D>,
view_type: PhantomData<V>,
}
// Impl block for buttons without data.
// See below for an impl block for any button.
impl<V: 'static> Button<V, ()> {
fn new() -> Self {
Self {
handlers: ButtonHandlers::default(),
label: None,
icon: None,
data: Rc::new(()),
view_type: PhantomData,
}
}
pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
Button {
handlers: ButtonHandlers::default(),
label: self.label,
icon: self.icon,
data: Rc::new(data),
view_type: PhantomData,
}
}
}
// Impl block for *any* button.
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());
self
}
pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
self.icon = Some(icon.into());
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 button<V>() -> Button<V, ()> {
Button::new()
}
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
let button = div()
.fill(rose_pine::dawn().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
// }
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,26 @@
use crate::{
adapter::AdapterElement,
element::{AnyElement, Element},
};
use gpui::ViewContext;
pub fn view<F, E>(mut render: F) -> ViewFn
where
F: 'static + FnMut(&mut ViewContext<ViewFn>) -> E,
E: Element<ViewFn>,
{
ViewFn(Box::new(move |cx| (render)(cx).into_any()))
}
pub struct ViewFn(Box<dyn FnMut(&mut ViewContext<ViewFn>) -> AnyElement<ViewFn>>);
impl gpui::Entity for ViewFn {
type Event = ();
}
impl gpui::View for ViewFn {
fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
use gpui::Element as _;
AdapterElement((self.0)(cx)).into_any()
}
}

View file

@ -0,0 +1,14 @@
[package]
name = "playground_macros"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/playground_macros.rs"
proc-macro = true
[dependencies]
syn = "1.0.72"
quote = "1.0.9"
proc-macro2 = "1.0.66"

View file

@ -0,0 +1,91 @@
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{parse_macro_input, parse_quote, DeriveInput, GenericParam, Generics};
use crate::derive_into_element::impl_into_element;
pub fn derive_element(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let type_name = ast.ident;
let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
let (impl_generics, type_generics, where_clause, view_type_name, lifetimes) =
if let Some(first_type_param) = ast.generics.params.iter().find_map(|param| {
if let GenericParam::Type(type_param) = param {
Some(type_param.ident.clone())
} else {
None
}
}) {
let mut lifetimes = vec![];
for param in ast.generics.params.iter() {
if let GenericParam::Lifetime(lifetime_def) = param {
lifetimes.push(lifetime_def.lifetime.clone());
}
}
let generics = ast.generics.split_for_impl();
(
generics.0,
Some(generics.1),
generics.2,
first_type_param,
lifetimes,
)
} else {
let generics = placeholder_view_generics.split_for_impl();
let placeholder_view_type_name: Ident = parse_quote! { V };
(
generics.0,
None,
generics.2,
placeholder_view_type_name,
vec![],
)
};
let lifetimes = if !lifetimes.is_empty() {
quote! { <#(#lifetimes),*> }
} else {
quote! {}
};
let impl_into_element = impl_into_element(
&impl_generics,
&view_type_name,
&type_name,
&type_generics,
&where_clause,
);
let gen = quote! {
impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics
#where_clause
{
type Layout = Option<playground::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)))
}
fn paint(
&mut self,
view: &mut V,
layout: &mut playground::element::Layout<V, Self::Layout>,
cx: &mut playground::element::PaintContext<V>,
) {
layout.paint(view, cx);
}
}
#impl_into_element
};
gen.into()
}

View file

@ -0,0 +1,69 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, parse_quote, DeriveInput, GenericParam, Generics, Ident, WhereClause,
};
pub fn derive_into_element(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let type_name = ast.ident;
let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
let placeholder_view_type_name: Ident = parse_quote! { V };
let view_type_name: Ident;
let impl_generics: syn::ImplGenerics<'_>;
let type_generics: Option<syn::TypeGenerics<'_>>;
let where_clause: Option<&'_ WhereClause>;
match ast.generics.params.iter().find_map(|param| {
if let GenericParam::Type(type_param) = param {
Some(type_param.ident.clone())
} else {
None
}
}) {
Some(type_name) => {
view_type_name = type_name;
let generics = ast.generics.split_for_impl();
impl_generics = generics.0;
type_generics = Some(generics.1);
where_clause = generics.2;
}
_ => {
view_type_name = placeholder_view_type_name;
let generics = placeholder_view_generics.split_for_impl();
impl_generics = generics.0;
type_generics = None;
where_clause = generics.2;
}
}
impl_into_element(
&impl_generics,
&view_type_name,
&type_name,
&type_generics,
&where_clause,
)
.into()
}
pub fn impl_into_element(
impl_generics: &syn::ImplGenerics<'_>,
view_type_name: &Ident,
type_name: &Ident,
type_generics: &Option<syn::TypeGenerics<'_>>,
where_clause: &Option<&WhereClause>,
) -> proc_macro2::TokenStream {
quote! {
impl #impl_generics playground::element::IntoElement<#view_type_name> for #type_name #type_generics
#where_clause
{
type Element = Self;
fn into_element(self) -> Self {
self
}
}
}
}

View file

@ -0,0 +1,26 @@
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 {
styleable_helpers::styleable_helpers(args)
}
#[proc_macro_derive(Element, attributes(element_crate))]
pub fn derive_element(input: TokenStream) -> TokenStream {
derive_element::derive_element(input)
}
#[proc_macro_derive(IntoElement, attributes(element_crate))]
pub fn derive_into_element(input: TokenStream) -> TokenStream {
derive_into_element::derive_into_element(input)
}
#[proc_macro_attribute]
pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
tailwind_lengths::tailwind_lengths(attr, item)
}

View file

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

View file

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

View file

@ -7,42 +7,6 @@ pub mod test_app_context;
pub(crate) mod window;
mod window_input_handler;
use std::{
any::{type_name, Any, TypeId},
cell::RefCell,
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
mem,
ops::{Deref, DerefMut, Range},
path::{Path, PathBuf},
pin::Pin,
rc::{self, Rc},
sync::{Arc, Weak},
time::Duration,
};
use anyhow::{anyhow, Context, Result};
use derive_more::Deref;
use parking_lot::Mutex;
use postage::oneshot;
use smallvec::SmallVec;
use smol::prelude::*;
use util::ResultExt;
use uuid::Uuid;
pub use action::*;
use callback_collection::CallbackCollection;
use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
pub use menu::*;
use platform::Event;
#[cfg(any(test, feature = "test-support"))]
use ref_counts::LeakDetector;
#[cfg(any(test, feature = "test-support"))]
pub use test_app_context::{ContextHandle, TestAppContext};
use window_input_handler::WindowInputHandler;
use crate::{
elements::{AnyElement, AnyRootElement, RootElement},
executor::{self, Task},
@ -57,8 +21,39 @@ use crate::{
window::{Window, WindowContext},
AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId,
};
use self::ref_counts::RefCounts;
pub use action::*;
use anyhow::{anyhow, Context, Result};
use callback_collection::CallbackCollection;
use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
use derive_more::Deref;
pub use menu::*;
use parking_lot::Mutex;
use platform::Event;
use postage::oneshot;
#[cfg(any(test, feature = "test-support"))]
use ref_counts::LeakDetector;
use ref_counts::RefCounts;
use smallvec::SmallVec;
use smol::prelude::*;
use std::{
any::{type_name, Any, TypeId},
cell::RefCell,
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
mem,
ops::{Deref, DerefMut, Range},
path::{Path, PathBuf},
pin::Pin,
rc::{self, Rc},
sync::{Arc, Weak},
time::Duration,
};
#[cfg(any(test, feature = "test-support"))]
pub use test_app_context::{ContextHandle, TestAppContext};
use util::ResultExt;
use uuid::Uuid;
use window_input_handler::WindowInputHandler;
pub trait Entity: 'static {
type Event;
@ -73,10 +68,12 @@ pub trait Entity: 'static {
}
pub trait View: Entity + Sized {
fn ui_name() -> &'static str;
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self>;
fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
fn ui_name() -> &'static str {
type_name::<Self>()
}
fn key_down(&mut self, _: &KeyDownEvent, _: &mut ViewContext<Self>) -> bool {
false
}
@ -640,7 +637,7 @@ impl AppContext {
pub fn add_action<A, V, F, R>(&mut self, handler: F)
where
A: Action,
V: View,
V: 'static,
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
{
self.add_action_internal(handler, false)
@ -649,7 +646,7 @@ impl AppContext {
pub fn capture_action<A, V, F>(&mut self, handler: F)
where
A: Action,
V: View,
V: 'static,
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>),
{
self.add_action_internal(handler, true)
@ -658,7 +655,7 @@ impl AppContext {
fn add_action_internal<A, V, F, R>(&mut self, mut handler: F, capture: bool)
where
A: Action,
V: View,
V: 'static,
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
{
let handler = Box::new(
@ -699,7 +696,7 @@ impl AppContext {
pub fn add_async_action<A, V, F>(&mut self, mut handler: F)
where
A: Action,
V: View,
V: 'static,
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> Option<Task<Result<()>>>,
{
self.add_action(move |view, action, cx| {
@ -898,8 +895,8 @@ impl AppContext {
fn observe_focus<F, V>(&mut self, handle: &ViewHandle<V>, mut callback: F) -> Subscription
where
V: 'static,
F: 'static + FnMut(ViewHandle<V>, bool, &mut WindowContext) -> bool,
V: View,
{
let subscription_id = post_inc(&mut self.next_subscription_id);
let observed = handle.downgrade();
@ -1382,15 +1379,15 @@ impl AppContext {
self.windows.keys().copied()
}
pub fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
pub fn read_view<V: 'static>(&self, handle: &ViewHandle<V>) -> &V {
if let Some(view) = self.views.get(&(handle.window, handle.view_id)) {
view.as_any().downcast_ref().expect("downcast is type safe")
} else {
panic!("circular view reference for type {}", type_name::<T>());
panic!("circular view reference for type {}", type_name::<V>());
}
}
fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
fn upgrade_view_handle<V: 'static>(&self, handle: &WeakViewHandle<V>) -> Option<ViewHandle<V>> {
if self.ref_counts.lock().is_entity_alive(handle.view_id) {
Some(ViewHandle::new(
handle.window,
@ -1659,6 +1656,9 @@ impl AppContext {
subscription_id,
callback,
),
Effect::RepaintWindow { window } => {
self.handle_repaint_window_effect(window)
}
}
self.pending_notifications.clear();
} else {
@ -1896,6 +1896,15 @@ 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);
}
});
}
fn handle_window_activation_effect(&mut self, window: AnyWindowHandle, active: bool) -> bool {
self.update_window(window, |cx| {
if cx.window.is_active == active {
@ -2151,7 +2160,7 @@ struct ViewMetadata {
keymap_context: KeymapContext,
}
#[derive(Default, Clone)]
#[derive(Default, Clone, Debug)]
pub struct WindowInvalidation {
pub updated: HashSet<usize>,
pub removed: Vec<usize>,
@ -2255,6 +2264,9 @@ pub enum Effect {
window: AnyWindowHandle,
is_active: bool,
},
RepaintWindow {
window: AnyWindowHandle,
},
WindowActivationObservation {
window: AnyWindowHandle,
subscription_id: usize,
@ -2448,6 +2460,10 @@ impl Debug for Effect {
.debug_struct("Effect::ActiveLabeledTasksObservation")
.field("subscription_id", subscription_id)
.finish(),
Effect::RepaintWindow { window } => f
.debug_struct("Effect::RepaintWindow")
.field("window_id", &window.id())
.finish(),
}
}
}
@ -2543,10 +2559,7 @@ pub trait AnyView {
}
}
impl<V> AnyView for V
where
V: View,
{
impl<V: View> AnyView for V {
fn as_any(&self) -> &dyn Any {
self
}
@ -2878,7 +2891,7 @@ pub struct ViewContext<'a, 'b, T: ?Sized> {
view_type: PhantomData<T>,
}
impl<'a, 'b, T: View> Deref for ViewContext<'a, 'b, T> {
impl<'a, 'b, V> Deref for ViewContext<'a, 'b, V> {
type Target = WindowContext<'a>;
fn deref(&self) -> &Self::Target {
@ -2886,14 +2899,14 @@ impl<'a, 'b, T: View> Deref for ViewContext<'a, 'b, T> {
}
}
impl<T: View> DerefMut for ViewContext<'_, '_, T> {
impl<'a, 'b, V> DerefMut for ViewContext<'a, 'b, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.window_context
}
}
impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
pub(crate) fn mutable(window_context: &'b mut WindowContext<'a>, view_id: usize) -> Self {
impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
pub fn mutable(window_context: &'b mut WindowContext<'a>, view_id: usize) -> Self {
Self {
window_context: Reference::Mutable(window_context),
view_id,
@ -2901,7 +2914,7 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
}
}
pub(crate) fn immutable(window_context: &'b WindowContext<'a>, view_id: usize) -> Self {
pub fn immutable(window_context: &'b WindowContext<'a>, view_id: usize) -> Self {
Self {
window_context: Reference::Immutable(window_context),
view_id,
@ -2913,6 +2926,12 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
&mut self.window_context
}
pub fn notify(&mut self) {
let window = self.window_handle;
let view_id = self.view_id;
self.window_context.notify_view(window, view_id);
}
pub fn handle(&self) -> ViewHandle<V> {
ViewHandle::new(
self.window_handle,
@ -3226,21 +3245,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
})
}
pub fn emit(&mut self, payload: V::Event) {
self.window_context
.pending_effects
.push_back(Effect::Event {
entity_id: self.view_id,
payload: Box::new(payload),
});
}
pub fn notify(&mut self) {
let window = self.window_handle;
let view_id = self.view_id;
self.window_context.notify_view(window, view_id);
}
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut V, &mut ViewContext<V>)) {
let handle = self.handle();
self.window_context
@ -3341,6 +3345,10 @@ impl<'a, 'b, V: View> 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,
@ -3350,6 +3358,17 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
}
}
impl<V: View> ViewContext<'_, '_, V> {
pub fn emit(&mut self, event: V::Event) {
self.window_context
.pending_effects
.push_back(Effect::Event {
entity_id: self.view_id,
payload: Box::new(event),
});
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TypeTag {
tag: TypeId,
@ -3428,15 +3447,27 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
}
}
pub struct LayoutContext<'a, 'b, 'c, V: View> {
view_context: &'c mut ViewContext<'a, 'b, 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<Arc<TextStyle>>,
text_style_stack: Vec<TextStyle>,
pub refreshing: bool,
}
impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
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>,
@ -3500,26 +3531,39 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
.push(self_view_id);
}
pub fn text_style(&self) -> Arc<TextStyle> {
self.text_style_stack
.last()
.cloned()
.unwrap_or(Default::default())
}
pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
pub fn with_text_style<F, T>(&mut self, style: TextStyle, f: F) -> T
where
S: Into<Arc<TextStyle>>,
F: FnOnce(&mut Self) -> T,
{
self.text_style_stack.push(style.into());
self.push_text_style(style);
let result = f(self);
self.text_style_stack.pop();
self.pop_text_style();
result
}
}
impl<'a, 'b, 'c, V: View> Deref for LayoutContext<'a, 'b, 'c, V> {
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 {
@ -3527,13 +3571,13 @@ impl<'a, 'b, 'c, V: View> Deref for LayoutContext<'a, 'b, 'c, V> {
}
}
impl<V: View> DerefMut for LayoutContext<'_, '_, '_, V> {
impl<V> DerefMut for LayoutContext<'_, '_, '_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view_context
}
}
impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
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)
}
@ -3543,7 +3587,7 @@ impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
}
}
impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
@ -3573,39 +3617,42 @@ impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
}
}
pub struct PaintContext<'a, 'b, 'c, V: View> {
view_context: &'c mut ViewContext<'a, 'b, V>,
text_style_stack: Vec<Arc<TextStyle>>,
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: View> PaintContext<'a, 'b, 'c, V> {
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(),
}
}
}
pub fn text_style(&self) -> Arc<TextStyle> {
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(Default::default())
.unwrap_or(TextStyle::default(&self.font_cache))
}
pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
where
S: Into<Arc<TextStyle>>,
F: FnOnce(&mut Self) -> T,
{
self.text_style_stack.push(style.into());
let result = f(self);
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();
result
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, V: View> Deref for PaintContext<'a, 'b, 'c, V> {
impl<'a, 'b, 'c, V> Deref for PaintContext<'a, 'b, 'c, V> {
type Target = ViewContext<'a, 'b, V>;
fn deref(&self) -> &Self::Target {
@ -3613,13 +3660,13 @@ impl<'a, 'b, 'c, V: View> Deref for PaintContext<'a, 'b, 'c, V> {
}
}
impl<V: View> DerefMut for PaintContext<'_, '_, '_, V> {
impl<V> DerefMut for PaintContext<'_, '_, '_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view_context
}
}
impl<V: View> BorrowAppContext for PaintContext<'_, '_, '_, V> {
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)
}
@ -3629,7 +3676,7 @@ impl<V: View> BorrowAppContext for PaintContext<'_, '_, '_, V> {
}
}
impl<V: View> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
impl<V> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
type Result<T> = T;
fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
@ -3661,25 +3708,37 @@ impl<V: View> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
}
}
pub struct EventContext<'a, 'b, 'c, V: View> {
pub struct EventContext<'a, 'b, 'c, V> {
view_context: &'c mut ViewContext<'a, 'b, V>,
pub(crate) handled: bool,
// I would like to replace handled with this.
// Being additive for now.
pub bubble: bool,
}
impl<'a, 'b, 'c, V: View> EventContext<'a, 'b, 'c, V> {
pub(crate) fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
impl<'a, 'b, 'c, V: 'static> EventContext<'a, 'b, 'c, V> {
pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
EventContext {
view_context,
handled: true,
bubble: false,
}
}
pub fn propagate_event(&mut self) {
self.handled = false;
}
pub fn bubble_event(&mut self) {
self.bubble = true;
}
pub fn event_bubbled(&self) -> bool {
self.bubble
}
}
impl<'a, 'b, 'c, V: View> Deref for EventContext<'a, 'b, 'c, V> {
impl<'a, 'b, 'c, V> Deref for EventContext<'a, 'b, 'c, V> {
type Target = ViewContext<'a, 'b, V>;
fn deref(&self) -> &Self::Target {
@ -3687,13 +3746,13 @@ impl<'a, 'b, 'c, V: View> Deref for EventContext<'a, 'b, 'c, V> {
}
}
impl<V: View> DerefMut for EventContext<'_, '_, '_, V> {
impl<V> DerefMut for EventContext<'_, '_, '_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view_context
}
}
impl<V: View> BorrowAppContext for EventContext<'_, '_, '_, V> {
impl<V> BorrowAppContext for EventContext<'_, '_, '_, V> {
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
BorrowAppContext::read_with(&*self.view_context, f)
}
@ -3703,7 +3762,7 @@ impl<V: View> BorrowAppContext for EventContext<'_, '_, '_, V> {
}
}
impl<V: View> BorrowWindowContext for EventContext<'_, '_, '_, V> {
impl<V> BorrowWindowContext for EventContext<'_, '_, '_, V> {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
@ -4031,7 +4090,7 @@ impl<V> Clone for WindowHandle<V> {
impl<V> Copy for WindowHandle<V> {}
impl<V: View> WindowHandle<V> {
impl<V: 'static> WindowHandle<V> {
fn new(window_id: usize) -> Self {
WindowHandle {
any_handle: AnyWindowHandle::new(window_id, TypeId::of::<V>()),
@ -4069,7 +4128,9 @@ impl<V: View> WindowHandle<V> {
.update(cx, update)
})
}
}
impl<V: View> WindowHandle<V> {
pub fn replace_root<C, F>(&self, cx: &mut C, build_root: F) -> C::Result<ViewHandle<V>>
where
C: BorrowWindowContext,
@ -4149,7 +4210,7 @@ impl AnyWindowHandle {
self.update(cx, |cx| cx.add_view(build_view))
}
pub fn downcast<V: View>(self) -> Option<WindowHandle<V>> {
pub fn downcast<V: 'static>(self) -> Option<WindowHandle<V>> {
if self.root_view_type == TypeId::of::<V>() {
Some(WindowHandle {
any_handle: self,
@ -4160,7 +4221,7 @@ impl AnyWindowHandle {
}
}
pub fn root_is<V: View>(&self) -> bool {
pub fn root_is<V: 'static>(&self) -> bool {
self.root_view_type == TypeId::of::<V>()
}
@ -4238,9 +4299,9 @@ impl AnyWindowHandle {
}
#[repr(transparent)]
pub struct ViewHandle<T> {
pub struct ViewHandle<V> {
any_handle: AnyViewHandle,
view_type: PhantomData<T>,
view_type: PhantomData<V>,
}
impl<T> Deref for ViewHandle<T> {
@ -4251,15 +4312,15 @@ impl<T> Deref for ViewHandle<T> {
}
}
impl<T: View> ViewHandle<T> {
impl<V: 'static> ViewHandle<V> {
fn new(window: AnyWindowHandle, view_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
Self {
any_handle: AnyViewHandle::new(window, view_id, TypeId::of::<T>(), ref_counts.clone()),
any_handle: AnyViewHandle::new(window, view_id, TypeId::of::<V>(), ref_counts.clone()),
view_type: PhantomData,
}
}
pub fn downgrade(&self) -> WeakViewHandle<T> {
pub fn downgrade(&self) -> WeakViewHandle<V> {
WeakViewHandle::new(self.window, self.view_id)
}
@ -4275,14 +4336,14 @@ impl<T: View> ViewHandle<T> {
self.view_id
}
pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V {
cx.read_view(self)
}
pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> C::Result<S>
where
C: BorrowWindowContext,
F: FnOnce(&T, &ViewContext<T>) -> S,
F: FnOnce(&V, &ViewContext<V>) -> S,
{
cx.read_window(self.window, |cx| {
let cx = ViewContext::immutable(cx, self.view_id);
@ -4293,7 +4354,7 @@ impl<T: View> ViewHandle<T> {
pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> C::Result<S>
where
C: BorrowWindowContext,
F: FnOnce(&mut T, &mut ViewContext<T>) -> S,
F: FnOnce(&mut V, &mut ViewContext<V>) -> S,
{
let mut update = Some(update);
@ -4429,8 +4490,8 @@ impl AnyViewHandle {
TypeId::of::<T>() == self.view_type
}
pub fn downcast<T: View>(self) -> Option<ViewHandle<T>> {
if self.is::<T>() {
pub fn downcast<V: 'static>(self) -> Option<ViewHandle<V>> {
if self.is::<V>() {
Some(ViewHandle {
any_handle: self,
view_type: PhantomData,
@ -4440,8 +4501,8 @@ impl AnyViewHandle {
}
}
pub fn downcast_ref<T: View>(&self) -> Option<&ViewHandle<T>> {
if self.is::<T>() {
pub fn downcast_ref<V: 'static>(&self) -> Option<&ViewHandle<V>> {
if self.is::<V>() {
Some(unsafe { mem::transmute(self) })
} else {
None
@ -4640,7 +4701,7 @@ impl<T> WeakHandle for WeakViewHandle<T> {
}
}
impl<V: View> WeakViewHandle<V> {
impl<V: 'static> WeakViewHandle<V> {
fn new(window: AnyWindowHandle, view_id: usize) -> Self {
Self {
any_handle: AnyWeakViewHandle {
@ -4680,28 +4741,47 @@ impl<V: View> WeakViewHandle<V> {
cx.read(|cx| {
let handle = cx
.upgrade_view_handle(self)
.ok_or_else(|| anyhow!("view {} was dropped", V::ui_name()))?;
.ok_or_else(|| anyhow!("view was dropped"))?;
cx.read_window(self.window, |cx| handle.read_with(cx, read))
.ok_or_else(|| anyhow!("window was removed"))
})
}
pub fn update<T>(
pub fn update<T, B>(
&self,
cx: &mut AsyncAppContext,
cx: &mut B,
update: impl FnOnce(&mut V, &mut ViewContext<V>) -> T,
) -> Result<T> {
cx.update(|cx| {
let handle = cx
.upgrade_view_handle(self)
.ok_or_else(|| anyhow!("view {} was dropped", V::ui_name()))?;
cx.update_window(self.window, |cx| handle.update(cx, update))
.ok_or_else(|| anyhow!("window was removed"))
) -> Result<T>
where
B: BorrowWindowContext,
B::Result<Option<T>>: Flatten<T>,
{
cx.update_window(self.window(), |cx| {
cx.upgrade_view_handle(self)
.map(|handle| handle.update(cx, update))
})
.flatten()
.ok_or_else(|| anyhow!("window was removed"))
}
}
impl<T> Deref for WeakViewHandle<T> {
pub trait Flatten<T> {
fn flatten(self) -> Option<T>;
}
impl<T> Flatten<T> for Option<Option<T>> {
fn flatten(self) -> Option<T> {
self.flatten()
}
}
impl<T> Flatten<T> for Option<T> {
fn flatten(self) -> Option<T> {
self
}
}
impl<V> Deref for WeakViewHandle<V> {
type Target = AnyWeakViewHandle;
fn deref(&self) -> &Self::Target {
@ -4709,7 +4789,7 @@ impl<T> Deref for WeakViewHandle<T> {
}
}
impl<T> Clone for WeakViewHandle<T> {
impl<V> Clone for WeakViewHandle<V> {
fn clone(&self) -> Self {
Self {
any_handle: self.any_handle.clone(),
@ -5263,6 +5343,7 @@ mod tests {
button: MouseButton::Left,
modifiers: Default::default(),
click_count: 1,
is_down: true,
}),
false,
);

View file

@ -1,6 +1,6 @@
use crate::{
elements::AnyRootElement,
geometry::rect::RectF,
geometry::{rect::RectF, Size},
json::ToJson,
keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
platform::{
@ -8,8 +8,9 @@ use crate::{
MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
},
scene::{
CursorRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, MouseEvent,
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
CursorRegion, EventHandler, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
Scene,
},
text_layout::TextLayoutCache,
util::post_inc,
@ -31,7 +32,11 @@ use sqlez::{
use std::{
any::TypeId,
mem,
ops::{Deref, DerefMut, Range},
ops::{Deref, DerefMut, Range, Sub},
};
use taffy::{
tree::{Measurable, MeasureFunc},
Taffy,
};
use util::ResultExt;
use uuid::Uuid;
@ -39,6 +44,7 @@ use uuid::Uuid;
use super::{Reference, ViewMetadata};
pub struct Window {
layout_engines: Vec<LayoutEngine>,
pub(crate) root_view: Option<AnyViewHandle>,
pub(crate) focused_view_id: Option<usize>,
pub(crate) parents: HashMap<usize, usize>,
@ -51,6 +57,7 @@ pub struct Window {
appearance: Appearance,
cursor_regions: Vec<CursorRegion>,
mouse_regions: Vec<(MouseRegion, usize)>,
event_handlers: Vec<EventHandler>,
last_mouse_moved_event: Option<Event>,
pub(crate) hovered_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region_ids: Vec<MouseRegionId>,
@ -67,12 +74,13 @@ impl Window {
build_view: F,
) -> Self
where
F: FnOnce(&mut ViewContext<V>) -> V,
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
let titlebar_height = platform_window.titlebar_height();
let appearance = platform_window.appearance();
let mut window = Self {
layout_engines: Vec::new(),
root_view: None,
focused_view_id: None,
parents: Default::default(),
@ -83,6 +91,7 @@ impl Window {
rendered_views: Default::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,
hovered_region_ids: Default::default(),
@ -109,6 +118,10 @@ impl Window {
.as_ref()
.expect("root_view called during window construction")
}
pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
mem::take(&mut self.event_handlers)
}
}
pub struct WindowContext<'a> {
@ -207,6 +220,24 @@ impl<'a> WindowContext<'a> {
}
}
pub fn repaint(&mut self) {
let window = self.window();
self.pending_effects
.push_back(Effect::RepaintWindow { window });
}
pub fn layout_engine(&mut self) -> Option<&mut LayoutEngine> {
self.window.layout_engines.last_mut()
}
pub fn push_layout_engine(&mut self, engine: LayoutEngine) {
self.window.layout_engines.push(engine);
}
pub fn pop_layout_engine(&mut self) -> Option<LayoutEngine> {
self.window.layout_engines.pop()
}
pub fn remove_window(&mut self) {
self.removed = true;
}
@ -227,6 +258,10 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.content_size()
}
pub fn mouse_position(&self) -> Vector2F {
self.window.mouse_position
}
pub fn text_layout_cache(&self) -> &TextLayoutCache {
&self.window.text_layout_cache
}
@ -242,14 +277,11 @@ impl<'a> WindowContext<'a> {
Some(result)
}
pub(crate) fn update_view<T, S>(
pub(crate) fn update_view<V: 'static, S>(
&mut self,
handle: &ViewHandle<T>,
update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
) -> S
where
T: View,
{
handle: &ViewHandle<V>,
update: &mut dyn FnMut(&mut V, &mut ViewContext<V>) -> S,
) -> S {
self.update_any_view(handle.view_id, |view, cx| {
let mut cx = ViewContext::mutable(cx, handle.view_id);
update(
@ -475,6 +507,8 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
self.dispatch_to_new_event_handlers(&event);
let mut mouse_events = SmallVec::<[_; 2]>::new();
let mut notified_views: HashSet<usize> = Default::default();
let handle = self.window_handle;
@ -852,6 +886,18 @@ impl<'a> WindowContext<'a> {
any_event_handled
}
fn dispatch_to_new_event_handlers(&mut self, event: &Event) {
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);
}
}
self.window.event_handlers = event_handlers;
}
}
pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
let handle = self.window_handle;
if let Some(focused_view_id) = self.window.focused_view_id {
@ -942,14 +988,16 @@ impl<'a> WindowContext<'a> {
Ok(element)
}
pub(crate) fn layout(&mut self, refreshing: bool) -> Result<HashMap<usize, usize>> {
pub fn layout(&mut self, refreshing: bool) -> Result<HashMap<usize, usize>> {
let window_size = self.window.platform_window.content_size();
let root_view_id = self.window.root_view().id();
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::strict(window_size),
SizeConstraint::new(window_size, window_size),
&mut new_parents,
&mut views_to_notify_if_ancestors_change,
refreshing,
@ -982,7 +1030,7 @@ impl<'a> WindowContext<'a> {
Ok(old_parents)
}
pub(crate) fn paint(&mut self) -> Result<Scene> {
pub fn paint(&mut self) -> Result<Scene> {
let window_size = self.window.platform_window.content_size();
let scale_factor = self.window.platform_window.scale_factor();
@ -1001,9 +1049,10 @@ impl<'a> WindowContext<'a> {
.insert(root_view_id, rendered_root);
self.window.text_layout_cache.finish_frame();
let scene = scene_builder.build();
let mut scene = scene_builder.build();
self.window.cursor_regions = scene.cursor_regions();
self.window.mouse_regions = scene.mouse_regions();
self.window.event_handlers = scene.take_event_handlers();
if self.window_is_active() {
if let Some(event) = self.window.last_mouse_moved_event.clone() {
@ -1014,6 +1063,11 @@ impl<'a> WindowContext<'a> {
Ok(scene)
}
pub fn root_element(&self) -> &Box<dyn AnyRootElement> {
let view_id = self.window.root_view().id();
self.window.rendered_views.get(&view_id).unwrap()
}
pub fn rect_for_text_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
let focused_view_id = self.window.focused_view_id?;
self.window
@ -1216,6 +1270,119 @@ impl<'a> WindowContext<'a> {
}
}
#[derive(Default)]
pub struct LayoutEngine(Taffy);
pub use taffy::style::Style as LayoutStyle;
impl LayoutEngine {
pub fn new() -> Self {
Default::default()
}
pub fn add_node<C>(&mut self, style: LayoutStyle, children: C) -> Result<LayoutId>
where
C: IntoIterator<Item = LayoutId>,
{
Ok(self
.0
.new_with_children(style, &children.into_iter().collect::<Vec<_>>())?)
}
pub fn add_measured_node<F>(&mut self, style: LayoutStyle, measure: F) -> Result<LayoutId>
where
F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
{
Ok(self
.0
.new_leaf_with_measure(style, MeasureFunc::Boxed(Box::new(MeasureFn(measure))))?)
}
pub fn compute_layout(&mut self, root: LayoutId, available_space: Vector2F) -> Result<()> {
self.0.compute_layout(
root,
taffy::geometry::Size {
width: available_space.x().into(),
height: available_space.y().into(),
},
)?;
Ok(())
}
pub fn computed_layout(&mut self, node: LayoutId) -> Result<EngineLayout> {
Ok(self.0.layout(node)?.into())
}
}
pub struct MeasureFn<F>(F);
impl<F: Send + Sync> Measurable for MeasureFn<F>
where
F: Fn(MeasureParams) -> Size<f32>,
{
fn measure(
&self,
known_dimensions: taffy::prelude::Size<Option<f32>>,
available_space: taffy::prelude::Size<taffy::style::AvailableSpace>,
) -> taffy::prelude::Size<f32> {
(self.0)(MeasureParams {
known_dimensions: known_dimensions.into(),
available_space: available_space.into(),
})
.into()
}
}
#[derive(Debug, Clone, Default)]
pub struct EngineLayout {
pub bounds: RectF,
pub order: u32,
}
pub struct MeasureParams {
pub known_dimensions: Size<Option<f32>>,
pub available_space: Size<AvailableSpace>,
}
#[derive(Clone)]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
Pixels(f32),
/// The amount of space available is indefinite and the node should be laid out under a min-content constraint
MinContent,
/// The amount of space available is indefinite and the node should be laid out under a max-content constraint
MaxContent,
}
impl Default for AvailableSpace {
fn default() -> Self {
Self::Pixels(0.)
}
}
impl From<taffy::prelude::AvailableSpace> for AvailableSpace {
fn from(value: taffy::prelude::AvailableSpace) -> Self {
match value {
taffy::prelude::AvailableSpace::Definite(pixels) => Self::Pixels(pixels),
taffy::prelude::AvailableSpace::MinContent => Self::MinContent,
taffy::prelude::AvailableSpace::MaxContent => Self::MaxContent,
}
}
}
impl From<&taffy::tree::Layout> for EngineLayout {
fn from(value: &taffy::tree::Layout) -> Self {
Self {
bounds: RectF::new(
vec2f(value.location.x, value.location.y),
vec2f(value.size.width, value.size.height),
),
order: value.order,
}
}
}
pub type LayoutId = taffy::prelude::NodeId;
pub struct RenderParams {
pub view_id: usize,
pub titlebar_height: f32,
@ -1324,6 +1491,12 @@ impl SizeConstraint {
max: size,
}
}
pub fn loose(max: Vector2F) -> Self {
Self {
min: Vector2F::zero(),
max,
}
}
pub fn strict_along(axis: Axis, max: f32) -> Self {
match axis {
@ -1360,6 +1533,17 @@ impl SizeConstraint {
}
}
impl Sub<Vector2F> for SizeConstraint {
type Output = SizeConstraint;
fn sub(self, rhs: Vector2F) -> SizeConstraint {
SizeConstraint {
min: self.min - rhs,
max: self.max - rhs,
}
}
}
impl Default for SizeConstraint {
fn default() -> Self {
SizeConstraint {
@ -1378,6 +1562,7 @@ impl ToJson for SizeConstraint {
}
}
#[derive(Clone)]
pub struct ChildView {
view_id: usize,
view_name: &'static str,
@ -1393,7 +1578,7 @@ impl ChildView {
}
}
impl<V: View> Element<V> for ChildView {
impl<V: 'static> Element<V> for ChildView {
type LayoutState = ();
type PaintState = ();

View file

@ -15,35 +15,75 @@ use serde_json::json;
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
#[repr(transparent)]
pub struct Color(#[schemars(with = "String")] ColorU);
pub struct Color(#[schemars(with = "String")] pub ColorU);
pub fn color(rgba: u32) -> Color {
Color::from_u32(rgba)
}
pub fn rgb(r: f32, g: f32, b: f32) -> Color {
Color(ColorF::new(r, g, b, 1.).to_u8())
}
pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
Color(ColorF::new(r, g, b, a).to_u8())
}
pub fn transparent_black() -> Color {
Color(ColorU::transparent_black())
}
pub fn black() -> Color {
Color(ColorU::black())
}
pub fn white() -> Color {
Color(ColorU::white())
}
pub fn red() -> Color {
color(0xff0000ff)
}
pub fn green() -> Color {
color(0x00ff00ff)
}
pub fn blue() -> Color {
color(0x0000ffff)
}
pub fn yellow() -> Color {
color(0xffff00ff)
}
impl Color {
pub fn transparent_black() -> Self {
Self(ColorU::transparent_black())
transparent_black()
}
pub fn black() -> Self {
Self(ColorU::black())
black()
}
pub fn white() -> Self {
Self(ColorU::white())
white()
}
pub fn red() -> Self {
Self(ColorU::from_u32(0xff0000ff))
Color::from_u32(0xff0000ff)
}
pub fn green() -> Self {
Self(ColorU::from_u32(0x00ff00ff))
Color::from_u32(0x00ff00ff)
}
pub fn blue() -> Self {
Self(ColorU::from_u32(0x0000ffff))
Color::from_u32(0x0000ffff)
}
pub fn yellow() -> Self {
Self(ColorU::from_u32(0xffff00ff))
Color::from_u32(0xffff00ff)
}
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
@ -101,6 +141,12 @@ impl<'de> Deserialize<'de> for Color {
}
}
impl From<u32> for Color {
fn from(value: u32) -> Self {
Self(ColorU::from_u32(value))
}
}
impl ToJson for Color {
fn to_json(&self) -> serde_json::Value {
json!(format!(

View file

@ -34,7 +34,7 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, TypeTag, View,
json, Action, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, TypeTag, View,
ViewContext, WeakViewHandle, WindowContext,
};
use anyhow::{anyhow, Result};
@ -42,14 +42,19 @@ use collections::HashMap;
use core::panic;
use json::ToJson;
use smallvec::SmallVec;
use std::{any::Any, borrow::Cow, mem, ops::Range};
use std::{
any::{type_name, Any},
borrow::Cow,
mem,
ops::Range,
};
pub trait Element<V: View>: 'static {
pub trait Element<V: 'static>: 'static {
type LayoutState;
type PaintState;
fn view_name(&self) -> &'static str {
V::ui_name()
type_name::<V>()
}
fn layout(
@ -231,11 +236,7 @@ pub trait Element<V: View>: 'static {
}
}
pub trait RenderElement {
fn render<V: View>(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
}
trait AnyElementState<V: View> {
trait AnyElementState<V> {
fn layout(
&mut self,
constraint: SizeConstraint,
@ -249,7 +250,7 @@ trait AnyElementState<V: View> {
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
);
fn rect_for_text_range(
@ -266,7 +267,7 @@ trait AnyElementState<V: View> {
fn metadata(&self) -> Option<&dyn Any>;
}
enum ElementState<V: View, E: Element<V>> {
enum ElementState<V: 'static, E: Element<V>> {
Empty,
Init {
element: E,
@ -287,7 +288,7 @@ enum ElementState<V: View, E: Element<V>> {
},
}
impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
fn layout(
&mut self,
constraint: SizeConstraint,
@ -330,7 +331,7 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
*self = match mem::take(self) {
ElementState::PostLayout {
@ -469,18 +470,18 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
}
}
impl<V: View, E: Element<V>> Default for ElementState<V, E> {
impl<V, E: Element<V>> Default for ElementState<V, E> {
fn default() -> Self {
Self::Empty
}
}
pub struct AnyElement<V: View> {
pub struct AnyElement<V> {
state: Box<dyn AnyElementState<V>>,
name: Option<Cow<'static, str>>,
}
impl<V: View> AnyElement<V> {
impl<V> AnyElement<V> {
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
@ -506,7 +507,7 @@ impl<V: View> AnyElement<V> {
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut ViewContext<V>,
cx: &mut PaintContext<V>,
) {
self.state.paint(scene, origin, visible_bounds, view, cx);
}
@ -548,7 +549,7 @@ impl<V: View> AnyElement<V> {
}
}
impl<V: View> Element<V> for AnyElement<V> {
impl<V: 'static> Element<V> for AnyElement<V> {
type LayoutState = ();
type PaintState = ();
@ -606,12 +607,18 @@ impl<V: View> Element<V> for AnyElement<V> {
}
}
pub struct RootElement<V: View> {
impl Entity for AnyElement<()> {
type Event = ();
}
// impl View for AnyElement<()> {}
pub struct RootElement<V> {
element: AnyElement<V>,
view: WeakViewHandle<V>,
}
impl<V: View> RootElement<V> {
impl<V> RootElement<V> {
pub fn new(element: AnyElement<V>, view: WeakViewHandle<V>) -> Self {
Self { element, view }
}
@ -679,7 +686,9 @@ 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| {
self.element.paint(scene, origin, visible_bounds, view, cx);
let mut cx = PaintContext::new(cx);
self.element
.paint(scene, origin, visible_bounds, view, &mut cx);
Ok(())
})
}
@ -719,7 +728,7 @@ impl<V: View> AnyRootElement for RootElement<V> {
}
}
pub trait ParentElement<'a, V: View>: Extend<AnyElement<V>> + Sized {
pub trait ParentElement<'a, V: 'static>: Extend<AnyElement<V>> + Sized {
fn add_children<E: Element<V>>(&mut self, children: impl IntoIterator<Item = E>) {
self.extend(children.into_iter().map(|child| child.into_any()));
}
@ -739,7 +748,12 @@ pub trait ParentElement<'a, V: View>: Extend<AnyElement<V>> + Sized {
}
}
impl<'a, V: View, T> ParentElement<'a, V> for T where T: Extend<AnyElement<V>> {}
impl<'a, V, T> ParentElement<'a, V> for T
where
V: 'static,
T: Extend<AnyElement<V>>,
{
}
pub fn constrain_size_preserving_aspect_ratio(max_size: Vector2F, size: Vector2F) -> Vector2F {
if max_size.x().is_infinite() && max_size.y().is_infinite() {

View file

@ -1,18 +1,18 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
};
use json::ToJson;
use serde_json::json;
pub struct Align<V: View> {
pub struct Align<V> {
child: AnyElement<V>,
alignment: Vector2F,
}
impl<V: View> Align<V> {
impl<V> Align<V> {
pub fn new(child: AnyElement<V>) -> Self {
Self {
child,
@ -41,7 +41,7 @@ impl<V: View> Align<V> {
}
}
impl<V: View> Element<V> for Align<V> {
impl<V: 'static> Element<V> for Align<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Element;
use crate::{
json::{self, json},
PaintContext, SceneBuilder, View, ViewContext,
PaintContext, SceneBuilder, ViewContext,
};
use json::ToJson;
use pathfinder_geometry::{
@ -15,7 +15,6 @@ pub struct Canvas<V, F>(F, PhantomData<V>);
impl<V, F> Canvas<V, F>
where
V: View,
F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
{
pub fn new(f: F) -> Self {
@ -23,7 +22,7 @@ where
}
}
impl<V: View, F> Element<V> for Canvas<V, F>
impl<V: 'static, F> Element<V> for Canvas<V, F>
where
F: 'static + FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
{

View file

@ -4,21 +4,21 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
use crate::{
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
};
pub struct Clipped<V: View> {
pub struct Clipped<V> {
child: AnyElement<V>,
}
impl<V: View> Clipped<V> {
impl<V> Clipped<V> {
pub fn new(child: AnyElement<V>) -> Self {
Self { child }
}
}
impl<V: View> Element<V> for Clipped<V> {
impl<V: 'static> Element<V> for Clipped<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -5,21 +5,21 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
};
pub struct ConstrainedBox<V: View> {
pub struct ConstrainedBox<V> {
child: AnyElement<V>,
constraint: Constraint<V>,
}
pub enum Constraint<V: View> {
pub enum Constraint<V> {
Static(SizeConstraint),
Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
}
impl<V: View> ToJson for Constraint<V> {
impl<V> ToJson for Constraint<V> {
fn to_json(&self) -> serde_json::Value {
match self {
Constraint::Static(constraint) => constraint.to_json(),
@ -28,7 +28,7 @@ impl<V: View> ToJson for Constraint<V> {
}
}
impl<V: View> ConstrainedBox<V> {
impl<V: 'static> ConstrainedBox<V> {
pub fn new(child: impl Element<V>) -> Self {
Self {
child: child.into_any(),
@ -132,7 +132,7 @@ impl<V: View> ConstrainedBox<V> {
}
}
impl<V: View> Element<V> for ConstrainedBox<V> {
impl<V: 'static> Element<V> for ConstrainedBox<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -10,8 +10,7 @@ use crate::{
json::ToJson,
platform::CursorStyle,
scene::{self, Border, CornerRadii, CursorRegion, Quad},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -47,12 +46,12 @@ impl ContainerStyle {
}
}
pub struct Container<V: View> {
pub struct Container<V> {
child: AnyElement<V>,
style: ContainerStyle,
}
impl<V: View> Container<V> {
impl<V> Container<V> {
pub fn new(child: AnyElement<V>) -> Self {
Self {
child,
@ -199,7 +198,7 @@ impl<V: View> Container<V> {
}
}
impl<V: View> Element<V> for Container<V> {
impl<V: 'static> Element<V> for Container<V> {
type LayoutState = ();
type PaintState = ();
@ -350,8 +349,8 @@ impl ToJson for ContainerStyle {
#[derive(Clone, Copy, Debug, Default, JsonSchema)]
pub struct Margin {
pub top: f32,
pub left: f32,
pub bottom: f32,
pub left: f32,
pub right: f32,
}

View file

@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
LayoutContext, PaintContext, SceneBuilder, ViewContext,
};
use crate::{Element, SizeConstraint};
@ -26,7 +26,7 @@ impl Empty {
}
}
impl<V: View> Element<V> for Empty {
impl<V: 'static> Element<V> for Empty {
type LayoutState = ();
type PaintState = ();

View file

@ -2,18 +2,18 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
};
use serde_json::json;
pub struct Expanded<V: View> {
pub struct Expanded<V> {
child: AnyElement<V>,
full_width: bool,
full_height: bool,
}
impl<V: View> Expanded<V> {
impl<V: 'static> Expanded<V> {
pub fn new(child: impl Element<V>) -> Self {
Self {
child: child.into_any(),
@ -35,7 +35,7 @@ impl<V: View> Expanded<V> {
}
}
impl<V: View> Element<V> for Expanded<V> {
impl<V: 'static> Element<V> for Expanded<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -3,7 +3,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, View, ViewContext,
SizeConstraint, Vector2FExt, ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@ -17,14 +17,14 @@ struct ScrollState {
scroll_position: Cell<f32>,
}
pub struct Flex<V: View> {
pub struct Flex<V> {
axis: Axis,
children: Vec<AnyElement<V>>,
scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
child_alignment: f32,
}
impl<V: View> Flex<V> {
impl<V: 'static> Flex<V> {
pub fn new(axis: Axis) -> Self {
Self {
axis,
@ -115,13 +115,13 @@ impl<V: View> Flex<V> {
}
}
impl<V: View> Extend<AnyElement<V>> for Flex<V> {
impl<V> Extend<AnyElement<V>> for Flex<V> {
fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
self.children.extend(children);
}
}
impl<V: View> Element<V> for Flex<V> {
impl<V: 'static> Element<V> for Flex<V> {
type LayoutState = f32;
type PaintState = ();
@ -401,12 +401,12 @@ struct FlexParentData {
float: bool,
}
pub struct FlexItem<V: View> {
pub struct FlexItem<V> {
metadata: FlexParentData,
child: AnyElement<V>,
}
impl<V: View> FlexItem<V> {
impl<V: 'static> FlexItem<V> {
pub fn new(child: impl Element<V>) -> Self {
FlexItem {
metadata: FlexParentData {
@ -428,7 +428,7 @@ impl<V: View> FlexItem<V> {
}
}
impl<V: View> Element<V> for FlexItem<V> {
impl<V: 'static> Element<V> for FlexItem<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -3,16 +3,15 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
};
pub struct Hook<V: View> {
pub struct Hook<V> {
child: AnyElement<V>,
after_layout: Option<Box<dyn FnMut(Vector2F, &mut ViewContext<V>)>>,
}
impl<V: View> Hook<V> {
impl<V: 'static> Hook<V> {
pub fn new(child: impl Element<V>) -> Self {
Self {
child: child.into_any(),
@ -29,7 +28,7 @@ impl<V: View> Hook<V> {
}
}
impl<V: View> Element<V> for Hook<V> {
impl<V: 'static> Element<V> for Hook<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -6,7 +6,7 @@ use crate::{
},
json::{json, ToJson},
scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
View, ViewContext,
ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -57,7 +57,7 @@ impl Image {
}
}
impl<V: View> Element<V> for Image {
impl<V: 'static> Element<V> for Image {
type LayoutState = Option<Arc<ImageData>>;
type PaintState = ();

View file

@ -31,7 +31,7 @@ impl KeystrokeLabel {
}
}
impl<V: View> Element<V> for KeystrokeLabel {
impl<V: 'static> Element<V> for KeystrokeLabel {
type LayoutState = AnyElement<V>;
type PaintState = ();

View file

@ -8,7 +8,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle},
Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -128,7 +128,7 @@ impl Label {
}
}
impl<V: View> Element<V> for Label {
impl<V: 'static> Element<V> for Label {
type LayoutState = Line;
type PaintState = ();

View file

@ -5,16 +5,16 @@ use crate::{
},
json::json,
AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
View, ViewContext,
ViewContext,
};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
pub struct List<V: View> {
pub struct List<V> {
state: ListState<V>,
}
pub struct ListState<V: View>(Rc<RefCell<StateInner<V>>>);
pub struct ListState<V>(Rc<RefCell<StateInner<V>>>);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Orientation {
@ -22,7 +22,7 @@ pub enum Orientation {
Bottom,
}
struct StateInner<V: View> {
struct StateInner<V> {
last_layout_width: Option<f32>,
render_item: Box<dyn FnMut(&mut V, usize, &mut ViewContext<V>) -> AnyElement<V>>,
rendered_range: Range<usize>,
@ -40,13 +40,13 @@ pub struct ListOffset {
pub offset_in_item: f32,
}
enum ListItem<V: View> {
enum ListItem<V> {
Unrendered,
Rendered(Rc<RefCell<AnyElement<V>>>),
Removed(f32),
}
impl<V: View> Clone for ListItem<V> {
impl<V> Clone for ListItem<V> {
fn clone(&self) -> Self {
match self {
Self::Unrendered => Self::Unrendered,
@ -56,7 +56,7 @@ impl<V: View> Clone for ListItem<V> {
}
}
impl<V: View> Debug for ListItem<V> {
impl<V> Debug for ListItem<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unrendered => write!(f, "Unrendered"),
@ -86,13 +86,13 @@ struct UnrenderedCount(usize);
#[derive(Clone, Debug, Default)]
struct Height(f32);
impl<V: View> List<V> {
impl<V> List<V> {
pub fn new(state: ListState<V>) -> Self {
Self { state }
}
}
impl<V: View> Element<V> for List<V> {
impl<V: 'static> Element<V> for List<V> {
type LayoutState = ListOffset;
type PaintState = ();
@ -347,7 +347,7 @@ impl<V: View> Element<V> for List<V> {
}
}
impl<V: View> ListState<V> {
impl<V: 'static> ListState<V> {
pub fn new<D, F>(
element_count: usize,
orientation: Orientation,
@ -440,13 +440,13 @@ impl<V: View> ListState<V> {
}
}
impl<V: View> Clone for ListState<V> {
impl<V> Clone for ListState<V> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<V: View> StateInner<V> {
impl<V: 'static> StateInner<V> {
fn render_item(
&mut self,
ix: usize,
@ -560,7 +560,7 @@ impl<V: View> StateInner<V> {
}
}
impl<V: View> ListItem<V> {
impl<V> ListItem<V> {
fn remove(&self) -> Self {
match self {
ListItem::Unrendered => ListItem::Unrendered,
@ -570,7 +570,7 @@ impl<V: View> ListItem<V> {
}
}
impl<V: View> sum_tree::Item for ListItem<V> {
impl<V> sum_tree::Item for ListItem<V> {
type Summary = ListItemSummary;
fn summary(&self) -> Self::Summary {
@ -944,7 +944,7 @@ mod tests {
type Event = ();
}
impl View for TestView {
impl crate::View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
@ -968,7 +968,7 @@ mod tests {
}
}
impl<V: View> Element<V> for TestElement {
impl<V: 'static> Element<V> for TestElement {
type LayoutState = ();
type PaintState = ();

View file

@ -11,12 +11,12 @@ use crate::{
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
SceneBuilder, SizeConstraint, TypeTag, View, ViewContext,
SceneBuilder, SizeConstraint, TypeTag, ViewContext,
};
use serde_json::json;
use std::ops::Range;
pub struct MouseEventHandler<V: View> {
pub struct MouseEventHandler<V: 'static> {
child: AnyElement<V>,
region_id: usize,
cursor_style: Option<CursorStyle>,
@ -31,7 +31,7 @@ pub struct MouseEventHandler<V: View> {
/// Element which provides a render_child callback with a MouseState and paints a mouse
/// region under (or above) it for easy mouse event handling.
impl<V: View> MouseEventHandler<V> {
impl<V: 'static> MouseEventHandler<V> {
pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
Self {
child: child.into_any(),
@ -267,7 +267,7 @@ impl<V: View> MouseEventHandler<V> {
}
}
impl<V: View> Element<V> for MouseEventHandler<V> {
impl<V: 'static> Element<V> for MouseEventHandler<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -4,11 +4,11 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, View, ViewContext,
SizeConstraint, ViewContext,
};
use serde_json::json;
pub struct Overlay<V: View> {
pub struct Overlay<V> {
child: AnyElement<V>,
anchor_position: Option<Vector2F>,
anchor_corner: AnchorCorner,
@ -73,7 +73,7 @@ impl AnchorCorner {
}
}
impl<V: View> Overlay<V> {
impl<V: 'static> Overlay<V> {
pub fn new(child: impl Element<V>) -> Self {
Self {
child: child.into_any(),
@ -117,7 +117,7 @@ impl<V: View> Overlay<V> {
}
}
impl<V: View> Element<V> for Overlay<V> {
impl<V: 'static> Element<V> for Overlay<V> {
type LayoutState = Vector2F;
type PaintState = ();

View file

@ -59,7 +59,7 @@ where
.and_then(|map| map.0.get(&tag))
}
pub struct Resizable<V: View> {
pub struct Resizable<V: 'static> {
child: AnyElement<V>,
tag: TypeTag,
handle_side: HandleSide,
@ -69,7 +69,7 @@ pub struct Resizable<V: View> {
const DEFAULT_HANDLE_SIZE: f32 = 4.0;
impl<V: View> Resizable<V> {
impl<V: 'static> Resizable<V> {
pub fn new<Tag: 'static>(
child: AnyElement<V>,
handle_side: HandleSide,
@ -97,7 +97,7 @@ impl<V: View> Resizable<V> {
}
}
impl<V: View> Element<V> for Resizable<V> {
impl<V: 'static> Element<V> for Resizable<V> {
type LayoutState = SizeConstraint;
type PaintState = ();
@ -219,12 +219,12 @@ impl<V: View> Element<V> for Resizable<V> {
#[derive(Debug, Default)]
struct ProviderMap(HashMap<TypeTag, (RectF, RectF)>);
pub struct BoundsProvider<V: View, P> {
pub struct BoundsProvider<V: 'static, P> {
child: AnyElement<V>,
phantom: std::marker::PhantomData<P>,
}
impl<V: View, P: 'static> BoundsProvider<V, P> {
impl<V: 'static, P: 'static> BoundsProvider<V, P> {
pub fn new(child: AnyElement<V>) -> Self {
Self {
child,

View file

@ -3,17 +3,16 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
};
/// Element which renders it's children in a stack on top of each other.
/// The first child determines the size of the others.
pub struct Stack<V: View> {
pub struct Stack<V> {
children: Vec<AnyElement<V>>,
}
impl<V: View> Default for Stack<V> {
impl<V> Default for Stack<V> {
fn default() -> Self {
Self {
children: Vec::new(),
@ -21,13 +20,13 @@ impl<V: View> Default for Stack<V> {
}
}
impl<V: View> Stack<V> {
impl<V> Stack<V> {
pub fn new() -> Self {
Self::default()
}
}
impl<V: View> Element<V> for Stack<V> {
impl<V: 'static> Element<V> for Stack<V> {
type LayoutState = ();
type PaintState = ();
@ -99,7 +98,7 @@ impl<V: View> Element<V> for Stack<V> {
}
}
impl<V: View> Extend<AnyElement<V>> for Stack<V> {
impl<V> Extend<AnyElement<V>> for Stack<V> {
fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
self.children.extend(children)
}

View file

@ -7,7 +7,7 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
scene, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
scene, Element, LayoutContext, SceneBuilder, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde_derive::Deserialize;
@ -27,7 +27,7 @@ impl Svg {
}
}
pub fn for_style<V: View>(style: SvgStyle) -> impl Element<V> {
pub fn for_style<V: 'static>(style: SvgStyle) -> impl Element<V> {
Self::new(style.asset)
.with_color(style.color)
.constrained()
@ -41,7 +41,7 @@ impl Svg {
}
}
impl<V: View> Element<V> for Svg {
impl<V: 'static> Element<V> for Svg {
type LayoutState = Option<usvg::Tree>;
type PaintState = ();

View file

@ -8,7 +8,7 @@ use crate::{
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
TextLayoutCache, View, ViewContext,
TextLayoutCache, ViewContext,
};
use log::warn;
use serde_json::json;
@ -70,7 +70,7 @@ impl Text {
}
}
impl<V: View> Element<V> for Text {
impl<V: 'static> Element<V> for Text {
type LayoutState = LayoutState;
type PaintState = ();
@ -338,7 +338,7 @@ impl<V: View> Element<V> for Text {
}
/// Perform text layout on a series of highlighted chunks of text.
fn layout_highlighted_chunks<'a>(
pub fn layout_highlighted_chunks<'a>(
chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
text_style: &TextStyle,
text_layout_cache: &TextLayoutCache,

View file

@ -7,7 +7,7 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
Task, TypeTag, View, ViewContext,
Task, TypeTag, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -22,7 +22,7 @@ use util::ResultExt;
const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
pub struct Tooltip<V: View> {
pub struct Tooltip<V> {
child: AnyElement<V>,
tooltip: Option<AnyElement<V>>,
_state: ElementStateHandle<Rc<TooltipState>>,
@ -52,7 +52,7 @@ pub struct KeystrokeStyle {
text: TextStyle,
}
impl<V: View> Tooltip<V> {
impl<V: 'static> Tooltip<V> {
pub fn new<Tag: 'static>(
id: usize,
text: impl Into<Cow<'static, str>>,
@ -181,7 +181,7 @@ impl<V: View> Tooltip<V> {
}
}
impl<V: View> Element<V> for Tooltip<V> {
impl<V: 'static> Element<V> for Tooltip<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -6,7 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, View, ViewContext,
AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -36,13 +36,13 @@ struct StateInner {
scroll_to: Option<ScrollTarget>,
}
pub struct UniformListLayoutState<V: View> {
pub struct UniformListLayoutState<V> {
scroll_max: f32,
item_height: f32,
items: Vec<AnyElement<V>>,
}
pub struct UniformList<V: View> {
pub struct UniformList<V> {
state: UniformListState,
item_count: usize,
#[allow(clippy::type_complexity)]
@ -53,7 +53,7 @@ pub struct UniformList<V: View> {
view_id: usize,
}
impl<V: View> UniformList<V> {
impl<V: 'static> UniformList<V> {
pub fn new<F>(
state: UniformListState,
item_count: usize,
@ -61,7 +61,6 @@ impl<V: View> UniformList<V> {
append_items: F,
) -> Self
where
V: View,
F: 'static + Fn(&mut V, Range<usize>, &mut Vec<AnyElement<V>>, &mut ViewContext<V>),
{
Self {
@ -151,7 +150,7 @@ impl<V: View> UniformList<V> {
}
}
impl<V: View> Element<V> for UniformList<V> {
impl<V: 'static> Element<V> for UniformList<V> {
type LayoutState = UniformListLayoutState<V>;
type PaintState = ();

View file

@ -11,6 +11,7 @@ pub use font_kit::{
properties::{Properties, Stretch, Style, Weight},
};
use ordered_float::OrderedFloat;
use refineable::Refineable;
use schemars::JsonSchema;
use serde::{de, Deserialize, Serialize};
use serde_json::Value;
@ -59,7 +60,7 @@ pub struct Features {
pub zero: Option<bool>,
}
#[derive(Clone, Debug, JsonSchema)]
#[derive(Clone, Debug, JsonSchema, Refineable)]
pub struct TextStyle {
pub color: Color,
pub font_family_name: Arc<str>,
@ -69,6 +70,7 @@ pub struct TextStyle {
#[schemars(with = "PropertiesDef")]
pub font_properties: Properties,
pub underline: Underline,
pub soft_wrap: bool,
}
impl TextStyle {
@ -90,20 +92,11 @@ impl TextStyle {
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),
}
}
}
pub struct TextStyleRefinement {
pub color: Option<Color>,
pub font_family_name: Option<Arc<str>>,
pub font_family_id: Option<FamilyId>,
pub font_id: Option<FontId>,
pub font_size: Option<f32>,
pub font_properties: Option<Properties>,
pub underline: Option<Underline>,
}
#[derive(JsonSchema)]
#[serde(remote = "Properties")]
pub struct PropertiesDef {
@ -222,9 +215,31 @@ impl TextStyle {
font_size,
font_properties,
underline,
soft_wrap: false,
})
}
pub fn default(font_cache: &FontCache) -> Self {
let font_family_id = font_cache.known_existing_family();
let font_id = font_cache
.select_font(font_family_id, &Default::default())
.expect("did not have any font in system-provided family");
let font_family_name = font_cache
.family_name(font_family_id)
.expect("we loaded this family from the font cache, so this should work");
Self {
color: Color::default(),
font_family_name,
font_family_id,
font_id,
font_size: 14.,
font_properties: Default::default(),
underline: Default::default(),
soft_wrap: true,
}
}
pub fn with_font_size(mut self, font_size: f32) -> Self {
self.font_size = font_size;
self
@ -352,24 +367,7 @@ impl Default for TextStyle {
let font_cache = font_cache
.as_ref()
.expect("TextStyle::default can only be called within a call to with_font_cache");
let font_family_id = font_cache.known_existing_family();
let font_id = font_cache
.select_font(font_family_id, &Default::default())
.expect("did not have any font in system-provided family");
let font_family_name = font_cache
.family_name(font_family_id)
.expect("we loaded this family from the font cache, so this should work");
Self {
color: Default::default(),
font_family_name,
font_family_id,
font_id,
font_size: 14.,
font_properties: Default::default(),
underline: Default::default(),
}
Self::default(font_cache)
})
}
}

View file

@ -2,6 +2,7 @@ use super::scene::{Path, PathVertex};
use crate::{color::Color, json::ToJson};
pub use pathfinder_geometry::*;
use rect::RectF;
use refineable::Refineable;
use serde::{Deserialize, Deserializer};
use serde_json::json;
use vector::{vec2f, Vector2F};
@ -131,3 +132,258 @@ impl ToJson for RectF {
json!({"origin": self.origin().to_json(), "size": self.size().to_json()})
}
}
#[derive(Refineable)]
pub struct Point<T: Clone + Default> {
pub x: T,
pub y: T,
}
impl<T: Clone + Default> Clone for Point<T> {
fn clone(&self) -> Self {
Self {
x: self.x.clone(),
y: self.y.clone(),
}
}
}
impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
fn into(self) -> taffy::geometry::Point<T> {
taffy::geometry::Point {
x: self.x,
y: self.y,
}
}
}
#[derive(Clone, Refineable)]
pub struct Size<T: Clone + Default> {
pub width: T,
pub height: T,
}
impl<S, T: Clone + Default> From<taffy::geometry::Size<S>> for Size<T>
where
S: Into<T>,
{
fn from(value: taffy::geometry::Size<S>) -> Self {
Self {
width: value.width.into(),
height: value.height.into(),
}
}
}
impl<S, T: Clone + Default> Into<taffy::geometry::Size<S>> for Size<T>
where
T: Into<S>,
{
fn into(self) -> taffy::geometry::Size<S> {
taffy::geometry::Size {
width: self.width.into(),
height: self.height.into(),
}
}
}
impl Size<DefiniteLength> {
pub fn zero() -> Self {
Self {
width: pixels(0.),
height: pixels(0.),
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Size<taffy::style::LengthPercentage> {
taffy::geometry::Size {
width: self.width.to_taffy(rem_size),
height: self.height.to_taffy(rem_size),
}
}
}
impl Size<Length> {
pub fn auto() -> Self {
Self {
width: Length::Auto,
height: Length::Auto,
}
}
pub fn to_taffy<T: From<taffy::prelude::LengthPercentageAuto>>(
&self,
rem_size: f32,
) -> taffy::geometry::Size<T> {
taffy::geometry::Size {
width: self.width.to_taffy(rem_size).into(),
height: self.height.to_taffy(rem_size).into(),
}
}
}
#[derive(Clone, Default, Refineable)]
pub struct Edges<T: Clone + Default> {
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 {
top: Length::Auto,
right: Length::Auto,
bottom: Length::Auto,
left: Length::Auto,
}
}
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::LengthPercentageAuto> {
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),
}
}
}
#[derive(Clone, Copy)]
pub enum AbsoluteLength {
Pixels(f32),
Rems(f32),
}
impl AbsoluteLength {
pub fn to_pixels(&self, rem_size: f32) -> f32 {
match self {
AbsoluteLength::Pixels(pixels) => *pixels,
AbsoluteLength::Rems(rems) => rems * rem_size,
}
}
}
impl Default for AbsoluteLength {
fn default() -> Self {
Self::Pixels(0.0)
}
}
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
#[derive(Clone, Copy)]
pub enum DefiniteLength {
Absolute(AbsoluteLength),
Relative(f32), // Percent, from 0 to 100.
}
impl DefiniteLength {
fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
match self {
DefiniteLength::Absolute(length) => match length {
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
AbsoluteLength::Rems(rems) => {
taffy::style::LengthPercentage::Length(rems * rem_size)
}
},
DefiniteLength::Relative(fraction) => {
taffy::style::LengthPercentage::Percent(*fraction)
}
}
}
}
impl From<AbsoluteLength> for DefiniteLength {
fn from(length: AbsoluteLength) -> Self {
Self::Absolute(length)
}
}
impl Default for DefiniteLength {
fn default() -> Self {
Self::Absolute(AbsoluteLength::default())
}
}
/// A length that can be defined in pixels, rems, percent of parent, or auto.
#[derive(Clone, Copy)]
pub enum Length {
Definite(DefiniteLength),
Auto,
}
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
DefiniteLength::Relative(fraction).into()
}
pub fn rems<T: From<AbsoluteLength>>(rems: f32) -> T {
AbsoluteLength::Rems(rems).into()
}
pub fn pixels<T: From<AbsoluteLength>>(pixels: f32) -> T {
AbsoluteLength::Pixels(pixels).into()
}
pub fn auto() -> Length {
Length::Auto
}
impl Length {
pub fn to_taffy(&self, rem_size: f32) -> taffy::prelude::LengthPercentageAuto {
match self {
Length::Definite(length) => length.to_taffy(rem_size).into(),
Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
}
}
}
impl From<DefiniteLength> for Length {
fn from(length: DefiniteLength) -> Self {
Self::Definite(length)
}
}
impl From<AbsoluteLength> for Length {
fn from(length: AbsoluteLength) -> Self {
Self::Definite(length.into())
}
}
impl Default for Length {
fn default() -> Self {
Self::Definite(DefiniteLength::default())
}
}

View file

@ -27,7 +27,10 @@ pub mod json;
pub mod keymap_matcher;
pub mod platform;
pub use gpui_macros::{test, Element};
pub use window::{Axis, RectFExt, SizeConstraint, Vector2FExt, WindowContext};
pub use window::{
Axis, EngineLayout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt,
WindowContext,
};
pub use anyhow;
pub use serde_json;

View file

@ -192,7 +192,7 @@ impl<'a> WindowOptions<'a> {
}
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct TitlebarOptions<'a> {
pub title: Option<&'a str>,
pub appears_transparent: bool,

View file

@ -1,4 +1,4 @@
use std::ops::Deref;
use std::{any::Any, ops::Deref};
use pathfinder_geometry::vector::vec2f;
@ -142,6 +142,7 @@ pub struct MouseButtonEvent {
pub position: Vector2F,
pub modifiers: Modifiers,
pub click_count: usize,
pub is_down: bool,
}
impl Deref for MouseButtonEvent {
@ -174,6 +175,7 @@ impl MouseMovedEvent {
button: self.pressed_button.unwrap_or(button),
modifiers: self.modifiers,
click_count: 0,
is_down: self.pressed_button.is_some(),
}
}
}
@ -211,10 +213,24 @@ impl Event {
Event::KeyDown { .. } => None,
Event::KeyUp { .. } => None,
Event::ModifiersChanged { .. } => None,
Event::MouseDown(event) | Event::MouseUp(event) => Some(event.position),
Event::MouseDown(event) => Some(event.position),
Event::MouseUp(event) => Some(event.position),
Event::MouseMoved(event) => Some(event.position),
Event::MouseExited(event) => Some(event.position),
Event::ScrollWheel(event) => Some(event.position),
}
}
pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
match self {
Event::KeyDown { .. } => None,
Event::KeyUp { .. } => None,
Event::ModifiersChanged { .. } => None,
Event::MouseDown(event) => Some(event),
Event::MouseUp(event) => Some(event),
Event::MouseMoved(event) => Some(event),
Event::MouseExited(event) => Some(event),
Event::ScrollWheel(event) => Some(event),
}
}
}

View file

@ -132,6 +132,7 @@ impl Event {
),
modifiers: read_modifiers(native_event),
click_count: native_event.clickCount() as usize,
is_down: true,
})
})
}
@ -158,6 +159,7 @@ impl Event {
),
modifiers: read_modifiers(native_event),
click_count: native_event.clickCount() as usize,
is_down: false,
})
})
}

View file

@ -1,5 +1,6 @@
mod mouse_event;
mod mouse_region;
mod region;
#[cfg(debug_assertions)]
use collections::HashSet;
@ -8,7 +9,12 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use serde_json::json;
use std::{borrow::Cow, sync::Arc};
use std::{
any::{Any, TypeId},
borrow::Cow,
rc::Rc,
sync::Arc,
};
use crate::{
color::Color,
@ -16,7 +22,7 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
platform::{current::Surface, CursorStyle},
ImageData,
ImageData, WindowContext,
};
pub use mouse_event::*;
pub use mouse_region::*;
@ -25,6 +31,8 @@ pub struct SceneBuilder {
scale_factor: f32,
stacking_contexts: Vec<StackingContext>,
active_stacking_context_stack: Vec<usize>,
/// Used by the playground crate.
pub event_handlers: Vec<EventHandler>,
#[cfg(debug_assertions)]
mouse_region_ids: HashSet<MouseRegionId>,
}
@ -32,6 +40,7 @@ pub struct SceneBuilder {
pub struct Scene {
scale_factor: f32,
stacking_contexts: Vec<StackingContext>,
event_handlers: Vec<EventHandler>,
}
struct StackingContext {
@ -272,6 +281,12 @@ impl Scene {
})
.collect()
}
pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
self.event_handlers
.sort_by(|a, b| a.order.cmp(&b.order).reverse());
std::mem::take(&mut self.event_handlers)
}
}
impl SceneBuilder {
@ -283,6 +298,7 @@ impl SceneBuilder {
active_stacking_context_stack: vec![0],
#[cfg(debug_assertions)]
mouse_region_ids: Default::default(),
event_handlers: Vec::new(),
}
}
@ -292,6 +308,7 @@ impl SceneBuilder {
Scene {
scale_factor: self.scale_factor,
stacking_contexts: self.stacking_contexts,
event_handlers: self.event_handlers,
}
}
@ -688,6 +705,13 @@ impl MouseRegion {
}
}
pub struct EventHandler {
pub order: u32,
// The &dyn Any parameter below expects an event.
pub handler: Rc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>,
pub event_type: TypeId,
}
fn can_draw(bounds: RectF) -> bool {
let size = bounds.size();
size.x() > 0. && size.y() > 0.

View file

@ -1,6 +1,4 @@
use crate::{
platform::MouseButton, window::WindowContext, EventContext, TypeTag, View, ViewContext,
};
use crate::{platform::MouseButton, window::WindowContext, EventContext, TypeTag, ViewContext};
use collections::HashMap;
use pathfinder_geometry::rect::RectF;
use smallvec::SmallVec;
@ -72,7 +70,7 @@ impl MouseRegion {
pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_down(button, handler);
@ -81,7 +79,7 @@ impl MouseRegion {
pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_up(button, handler);
@ -90,7 +88,7 @@ impl MouseRegion {
pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_click(button, handler);
@ -99,7 +97,7 @@ impl MouseRegion {
pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_click_out(button, handler);
@ -108,7 +106,7 @@ impl MouseRegion {
pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_down_out(button, handler);
@ -117,7 +115,7 @@ impl MouseRegion {
pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_up_out(button, handler);
@ -126,7 +124,7 @@ impl MouseRegion {
pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_drag(button, handler);
@ -135,7 +133,7 @@ impl MouseRegion {
pub fn on_hover<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_hover(handler);
@ -144,7 +142,7 @@ impl MouseRegion {
pub fn on_move<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_move(handler);
@ -153,7 +151,7 @@ impl MouseRegion {
pub fn on_move_out<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_move_out(handler);
@ -162,7 +160,7 @@ impl MouseRegion {
pub fn on_scroll<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
{
self.handlers = self.handlers.on_scroll(handler);
@ -314,7 +312,7 @@ impl HandlerSet {
pub fn on_move<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::move_disc(), None,
@ -336,7 +334,7 @@ impl HandlerSet {
pub fn on_move_out<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::move_out_disc(), None,
@ -358,7 +356,7 @@ impl HandlerSet {
pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::down_disc(), Some(button),
@ -380,7 +378,7 @@ impl HandlerSet {
pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::up_disc(), Some(button),
@ -402,7 +400,7 @@ impl HandlerSet {
pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::click_disc(), Some(button),
@ -424,7 +422,7 @@ impl HandlerSet {
pub fn on_click_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::click_out_disc(), Some(button),
@ -446,7 +444,7 @@ impl HandlerSet {
pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::down_out_disc(), Some(button),
@ -468,7 +466,7 @@ impl HandlerSet {
pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::up_out_disc(), Some(button),
@ -490,7 +488,7 @@ impl HandlerSet {
pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::drag_disc(), Some(button),
@ -512,7 +510,7 @@ impl HandlerSet {
pub fn on_hover<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::hover_disc(), None,
@ -534,7 +532,7 @@ impl HandlerSet {
pub fn on_scroll<V, F>(mut self, handler: F) -> Self
where
V: View,
V: 'static,
F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
{
self.insert(MouseEvent::scroll_wheel_disc(), None,

View file

@ -0,0 +1,7 @@
// use crate::geometry::rect::RectF;
// use crate::WindowContext;
// struct Region {
// pub bounds: RectF,
// pub click_handler: Option<Rc<dyn Fn(&dyn Any, MouseEvent, &mut WindowContext)>>,
// }

View file

@ -212,7 +212,7 @@ pub struct Glyph {
}
impl Line {
fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
let mut style_runs = SmallVec::new();
for (len, style) in runs {
style_runs.push(StyleRun {

View file

@ -1,14 +1,14 @@
use gpui::{elements::RenderElement, View, ViewContext};
use gpui_macros::Element;
use gpui::{elements::Empty, Element, ViewContext};
// use gpui_macros::Element;
#[test]
fn test_derive_render_element() {
#[derive(Element)]
struct TestElement {}
impl RenderElement for TestElement {
fn render<V: View>(&mut self, _: &mut V, _: &mut ViewContext<V>) -> gpui::AnyElement<V> {
unimplemented!()
impl TestElement {
fn render<V: 'static>(&mut self, _: &mut V, _: &mut ViewContext<V>) -> impl Element<V> {
Empty::new()
}
}
}

View file

@ -10,6 +10,7 @@ proc-macro = true
doctest = false
[dependencies]
lazy_static.workspace = true
proc-macro2 = "1.0"
syn = "1.0"
quote = "1.0"
proc-macro2 = "1.0"

View file

@ -4,7 +4,7 @@ use quote::{format_ident, quote};
use std::mem;
use syn::{
parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg,
ItemFn, Lit, Meta, NestedMeta, Type,
GenericParam, Generics, ItemFn, Lit, Meta, NestedMeta, Type, WhereClause,
};
#[proc_macro_attribute]
@ -278,18 +278,44 @@ fn parse_bool(literal: &Lit) -> Result<bool, TokenStream> {
#[proc_macro_derive(Element)]
pub fn element_derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
let ast = parse_macro_input!(input as DeriveInput);
let type_name = ast.ident;
// The name of the struct/enum
let name = input.ident;
let must_implement = format_ident!("{}MustImplementRenderElement", name);
let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
let placeholder_view_type_name: Ident = parse_quote! { V };
let view_type_name: Ident;
let impl_generics: syn::ImplGenerics<'_>;
let type_generics: Option<syn::TypeGenerics<'_>>;
let where_clause: Option<&'_ WhereClause>;
let expanded = quote! {
trait #must_implement : gpui::elements::RenderElement {}
impl #must_implement for #name {}
match ast.generics.params.iter().find_map(|param| {
if let GenericParam::Type(type_param) = param {
Some(type_param.ident.clone())
} else {
None
}
}) {
Some(type_name) => {
view_type_name = type_name;
let generics = ast.generics.split_for_impl();
impl_generics = generics.0;
type_generics = Some(generics.1);
where_clause = generics.2;
}
_ => {
view_type_name = placeholder_view_type_name;
let generics = placeholder_view_generics.split_for_impl();
impl_generics = generics.0;
type_generics = None;
where_clause = generics.2;
}
}
let gen = quote! {
impl #impl_generics Element<#view_type_name> for #type_name #type_generics
#where_clause
{
impl<V: gpui::View> gpui::elements::Element<V> for #name {
type LayoutState = gpui::elements::AnyElement<V>;
type PaintState = ();
@ -299,7 +325,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
view: &mut V,
cx: &mut gpui::LayoutContext<V>,
) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
let mut element = self.render(view, cx);
let mut element = self.render(view, cx).into_any();
let size = element.layout(constraint, view, cx);
(size, element)
}
@ -336,11 +362,11 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
_: &(),
view: &V,
cx: &gpui::ViewContext<V>,
) -> gpui::serde_json::Value {
) -> gpui::json::Value {
element.debug(view, cx)
}
}
};
// Return generated code
TokenStream::from(expanded)
gen.into()
}

View file

@ -450,7 +450,7 @@ impl View for LspLogView {
}
impl Item for LspLogView {
fn tab_content<V: View>(
fn tab_content<V: 'static>(
&self,
_: Option<usize>,
style: &theme::Tab,

View file

@ -373,6 +373,7 @@ impl View for SyntaxTreeView {
font_size,
font_properties: Default::default(),
underline: Default::default(),
soft_wrap: false,
};
let line_height = cx.font_cache().line_height(font_size);
@ -451,7 +452,7 @@ impl View for SyntaxTreeView {
}
impl Item for SyntaxTreeView {
fn tab_content<V: View>(
fn tab_content<V: 'static>(
&self,
_: Option<usize>,
style: &theme::Tab,

View file

@ -1320,7 +1320,7 @@ impl ProjectPanel {
}
}
fn render_entry_visual_element<V: View>(
fn render_entry_visual_element<V: 'static>(
details: &EntryDetails,
editor: Option<&ViewHandle<Editor>>,
padding: f32,

View file

@ -3,7 +3,7 @@ use std::path::Path;
use fuzzy::StringMatch;
use gpui::{
elements::{Label, LabelStyle},
AnyElement, Element, View,
AnyElement, Element,
};
use util::paths::PathExt;
use workspace::WorkspaceLocation;
@ -43,7 +43,7 @@ impl HighlightedText {
}
}
pub fn render<V: View>(self, style: impl Into<LabelStyle>) -> AnyElement<V> {
pub fn render<V: 'static>(self, style: impl Into<LabelStyle>) -> AnyElement<V> {
Label::new(self.text, style)
.with_highlights(self.highlight_positions)
.into_any()

View file

@ -0,0 +1,15 @@
[package]
name = "refineable"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/refineable.rs"
doctest = false
[dependencies]
syn = "1.0.72"
quote = "1.0.9"
proc-macro2 = "1.0.66"
derive_refineable = { path = "./derive_refineable" }

View file

@ -0,0 +1,15 @@
[package]
name = "derive_refineable"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/derive_refineable.rs"
proc-macro = true
doctest = false
[dependencies]
syn = "1.0.72"
quote = "1.0.9"
proc-macro2 = "1.0.66"

View file

@ -0,0 +1,188 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, parse_quote, DeriveInput, Field, FieldsNamed, PredicateType, TraitBound,
Type, TypeParamBound, WhereClause, WherePredicate,
};
#[proc_macro_derive(Refineable, attributes(refineable))]
pub fn derive_refineable(input: TokenStream) -> TokenStream {
let DeriveInput {
ident,
data,
generics,
..
} = parse_macro_input!(input);
let refinement_ident = format_ident!("{}Refinement", ident);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let fields = match data {
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(FieldsNamed { named, .. }),
..
}) => named.into_iter().collect::<Vec<Field>>(),
_ => panic!("This derive macro only supports structs with named fields"),
};
let field_names: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
let field_visibilities: Vec<_> = fields.iter().map(|f| &f.vis).collect();
let wrapped_types: Vec<_> = fields.iter().map(|f| get_wrapper_type(f, &f.ty)).collect();
// Create trait bound that each wrapped type must implement Clone & Default
let type_param_bounds: Vec<_> = wrapped_types
.iter()
.map(|ty| {
WherePredicate::Type(PredicateType {
lifetimes: None,
bounded_ty: ty.clone(),
colon_token: Default::default(),
bounds: {
let mut punctuated = syn::punctuated::Punctuated::new();
punctuated.push_value(TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path: parse_quote!(Clone),
}));
punctuated.push_punct(syn::token::Add::default());
punctuated.push_value(TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path: parse_quote!(Default),
}));
punctuated
},
})
})
.collect();
// Append to where_clause or create a new one if it doesn't exist
let where_clause = match where_clause.cloned() {
Some(mut where_clause) => {
where_clause
.predicates
.extend(type_param_bounds.into_iter());
where_clause.clone()
}
None => WhereClause {
where_token: Default::default(),
predicates: type_param_bounds.into_iter().collect(),
},
};
let field_assignments: Vec<TokenStream2> = fields
.iter()
.map(|field| {
let name = &field.ident;
let is_refineable = is_refineable_field(field);
let is_optional = is_optional_field(field);
if is_refineable {
quote! {
self.#name.refine(&refinement.#name);
}
} else if is_optional {
quote! {
if let Some(ref value) = &refinement.#name {
self.#name = Some(value.clone());
}
}
} else {
quote! {
if let Some(ref value) = &refinement.#name {
self.#name = value.clone();
}
}
}
})
.collect();
let refinement_field_assignments: Vec<TokenStream2> = fields
.iter()
.map(|field| {
let name = &field.ident;
let is_refineable = is_refineable_field(field);
if is_refineable {
quote! {
self.#name.refine(&refinement.#name);
}
} else {
quote! {
if let Some(ref value) = &refinement.#name {
self.#name = Some(value.clone());
}
}
}
})
.collect();
let gen = quote! {
#[derive(Default, Clone)]
pub struct #refinement_ident #impl_generics {
#( #field_visibilities #field_names: #wrapped_types ),*
}
impl #impl_generics Refineable for #ident #ty_generics
#where_clause
{
type Refinement = #refinement_ident #ty_generics;
fn refine(&mut self, refinement: &Self::Refinement) {
#( #field_assignments )*
}
}
impl #impl_generics Refineable for #refinement_ident #ty_generics
#where_clause
{
type Refinement = #refinement_ident #ty_generics;
fn refine(&mut self, refinement: &Self::Refinement) {
#( #refinement_field_assignments )*
}
}
};
gen.into()
}
fn is_refineable_field(f: &Field) -> bool {
f.attrs.iter().any(|attr| attr.path.is_ident("refineable"))
}
fn is_optional_field(f: &Field) -> bool {
if let Type::Path(typepath) = &f.ty {
if typepath.qself.is_none() {
let segments = &typepath.path.segments;
if segments.len() == 1 && segments.iter().any(|s| s.ident == "Option") {
return true;
}
}
}
false
}
fn get_wrapper_type(field: &Field, ty: &Type) -> syn::Type {
if is_refineable_field(field) {
let struct_name = if let Type::Path(tp) = ty {
tp.path.segments.last().unwrap().ident.clone()
} else {
panic!("Expected struct type for a refineable field");
};
let refinement_struct_name = format_ident!("{}Refinement", struct_name);
let generics = if let Type::Path(tp) = ty {
&tp.path.segments.last().unwrap().arguments
} else {
&syn::PathArguments::None
};
parse_quote!(#refinement_struct_name #generics)
} else if is_optional_field(field) {
ty.clone()
} else {
parse_quote!(Option<#ty>)
}
}

View file

@ -0,0 +1,14 @@
pub use derive_refineable::Refineable;
pub trait Refineable {
type Refinement: Default;
fn refine(&mut self, refinement: &Self::Refinement);
fn refined(mut self, refinement: &Self::Refinement) -> Self
where
Self: Sized,
{
self.refine(refinement);
self
}
}

View file

@ -482,7 +482,7 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.deactivated(cx));
}
fn tab_content<T: View>(
fn tab_content<T: 'static>(
&self,
_detail: Option<usize>,
tab_theme: &theme::Tab,

View file

@ -567,6 +567,7 @@ impl Element<TerminalView> for TerminalElement {
font_size,
font_properties: Default::default(),
underline: Default::default(),
soft_wrap: false,
};
let selection_color = settings.theme.editor.selection.selection;
let match_color = settings.theme.search.match_background;

View file

@ -661,7 +661,7 @@ impl Item for TerminalView {
Some(self.terminal().read(cx).title().into())
}
fn tab_content<T: View>(
fn tab_content<T: 'static>(
&self,
_detail: Option<usize>,
tab_theme: &theme::Tab,

View file

@ -10,7 +10,7 @@ use gpui::{
platform,
platform::MouseButton,
scene::MouseClick,
Action, Element, EventContext, MouseState, View, ViewContext,
Action, Element, EventContext, MouseState, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -37,7 +37,7 @@ pub fn checkbox<Tag, V, F>(
) -> MouseEventHandler<V>
where
Tag: 'static,
V: View,
V: 'static,
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
{
let label = Label::new(label, style.label.text.clone())
@ -57,7 +57,7 @@ pub fn checkbox_with_label<Tag, D, V, F>(
where
Tag: 'static,
D: Element<V>,
V: View,
V: 'static,
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
{
MouseEventHandler::new::<Tag, _>(id, cx, |state, _| {
@ -93,7 +93,7 @@ where
.with_cursor_style(platform::CursorStyle::PointingHand)
}
pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
pub fn svg<V: 'static>(style: &SvgStyle) -> ConstrainedBox<V> {
Svg::new(style.asset.clone())
.with_color(style.color)
.constrained()
@ -117,11 +117,11 @@ impl IconStyle {
}
}
pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
pub fn icon<V: 'static>(style: &IconStyle) -> Container<V> {
svg(&style.icon).contained().with_style(style.container)
}
pub fn keystroke_label<V: View>(
pub fn keystroke_label<V: 'static>(
label_text: &'static str,
label_style: &ContainedText,
keystroke_style: &ContainedText,
@ -157,7 +157,7 @@ pub fn cta_button<Tag, L, V, F>(
where
Tag: 'static,
L: Into<Cow<'static, str>>,
V: View,
V: 'static,
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
@ -196,9 +196,9 @@ pub fn modal<Tag, V, I, D, F>(
) -> impl Element<V>
where
Tag: 'static,
V: View,
I: Into<Cow<'static, str>>,
D: Element<V>,
V: 'static,
F: FnOnce(&mut gpui::ViewContext<V>) -> D,
{
const TITLEBAR_HEIGHT: f32 = 28.;

View file

@ -232,7 +232,7 @@ impl Item for WelcomePage {
Some("Welcome to Zed!".into())
}
fn tab_content<T: View>(
fn tab_content<T: 'static>(
&self,
_detail: Option<usize>,
style: &theme::Tab,

View file

@ -101,7 +101,7 @@ pub trait Item: View {
fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
None
}
fn tab_content<V: View>(
fn tab_content<V: 'static>(
&self,
detail: Option<usize>,
style: &theme::Tab,
@ -943,7 +943,7 @@ pub mod test {
})
}
fn tab_content<V: View>(
fn tab_content<V: 'static>(
&self,
detail: Option<usize>,
_: &theme::Tab,

View file

@ -1976,12 +1976,12 @@ impl NavHistoryState {
}
}
pub struct PaneBackdrop<V: View> {
pub struct PaneBackdrop<V> {
child_view: usize,
child: AnyElement<V>,
}
impl<V: View> PaneBackdrop<V> {
impl<V> PaneBackdrop<V> {
pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
PaneBackdrop {
child,
@ -1990,7 +1990,7 @@ impl<V: View> PaneBackdrop<V> {
}
}
impl<V: View> Element<V> for PaneBackdrop<V> {
impl<V: 'static> Element<V> for PaneBackdrop<V> {
type LayoutState = ();
type PaintState = ();

View file

@ -7,7 +7,7 @@ use gpui::{
geometry::{rect::RectF, vector::Vector2F},
platform::MouseButton,
scene::MouseUp,
AppContext, Element, EventContext, MouseState, Quad, View, ViewContext, WeakViewHandle,
AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
};
use project::ProjectEntryId;
@ -107,7 +107,7 @@ where
handler
}
pub fn handle_dropped_item<V: View>(
pub fn handle_dropped_item<V: 'static>(
event: MouseUp,
workspace: WeakViewHandle<Workspace>,
pane: &WeakViewHandle<Pane>,

View file

@ -104,7 +104,7 @@ impl Item for SharedScreen {
}
}
fn tab_content<V: View>(
fn tab_content<V: 'static>(
&self,
_: Option<usize>,
style: &theme::Tab,

View file

@ -93,7 +93,7 @@ postage.workspace = true
rand.workspace = true
regex.workspace = true
rsa = "0.4"
rust-embed = { version = "6.3", features = ["include-exclude"] }
rust-embed = { version = "6.8.1" }
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true

5670
test.rs Normal file

File diff suppressed because it is too large Load diff