Detailed changes
@@ -3166,6 +3166,22 @@ dependencies = [
[[package]]
name = "gpui2"
version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "derive_more",
+ "gpui",
+ "gpui2_macros",
+ "log",
+ "parking_lot 0.11.2",
+ "refineable",
+ "rust-embed",
+ "serde",
+ "settings",
+ "simplelog",
+ "smallvec",
+ "theme",
+ "util",
+]
[[package]]
name = "gpui2_macros"
@@ -20,7 +20,7 @@ impl<V, D> Default for ButtonHandlers<V, D> {
}
}
-use crate as playground;
+use crate as gpui2;
#[derive(Element)]
pub struct Button<V: 'static, D: 'static> {
handlers: ButtonHandlers<V, D>,
@@ -4,6 +4,25 @@ version = "0.1.0"
edition = "2021"
publish = false
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+name = "gpui2"
+path = "src/gpui2.rs"
[dependencies]
+anyhow.workspace = true
+derive_more.workspace = true
+gpui = { path = "../gpui" }
+log.workspace = true
+gpui2_macros = { path = "../gpui2_macros" }
+parking_lot.workspace = true
+refineable.workspace = true
+rust-embed.workspace = true
+serde.workspace = true
+settings = { path = "../settings" }
+simplelog = "0.9"
+smallvec.workspace = true
+theme = { path = "../theme" }
+util = { path = "../util" }
+
+[dev-dependencies]
+gpui = { path = "../gpui", features = ["test-support"] }
@@ -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, bounds.origin(), &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")
+ }
+}
@@ -0,0 +1,101 @@
+use crate::{
+ div::div,
+ element::{IntoElement, ParentElement},
+ interactive::Interactive,
+ style::StyleHelpers,
+ text::ArcCow,
+ // themes::Theme,
+};
+use gpui::{platform::MouseButton, ViewContext};
+use gpui2_macros::Element;
+use std::{marker::PhantomData, rc::Rc};
+
+struct ButtonHandlers<V, D> {
+ click: Option<Rc<dyn Fn(&mut V, &D, &mut ViewContext<V>)>>,
+}
+
+impl<V, D> Default for ButtonHandlers<V, D> {
+ fn default() -> Self {
+ Self { click: None }
+ }
+}
+
+use crate as gpui2;
+#[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 button regardless of its data type.
+impl<V: 'static, D: 'static> Button<V, D> {
+ pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
+ self.label = Some(label.into());
+ self
+ }
+
+ pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
+ self.icon = Some(icon.into());
+ self
+ }
+
+ pub fn on_click(mut self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
+ self.handlers.click = Some(Rc::new(handler));
+ self
+ }
+}
+
+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 IntoElement<V> + Interactive<V> {
+ // let colors = &cx.theme::<Theme>().colors;
+
+ let button = div()
+ // .fill(colors.error(0.5))
+ .h_4()
+ .children(self.label.clone());
+
+ if let Some(handler) = self.handlers.click.clone() {
+ let data = self.data.clone();
+ button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
+ handler(view, data.as_ref(), cx)
+ })
+ } else {
+ button
+ }
+ }
+}
@@ -0,0 +1,116 @@
+use crate::{
+ element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+ interactive::{InteractionHandlers, Interactive},
+ layout_context::LayoutContext,
+ paint_context::PaintContext,
+ style::{Style, StyleHelpers, Styleable},
+};
+use anyhow::Result;
+use gpui::{LayoutId, RenderContext};
+use refineable::{Refineable, RefinementCascade};
+use smallvec::SmallVec;
+
+pub struct Div<V: 'static> {
+ styles: RefinementCascade<Style>,
+ handlers: InteractionHandlers<V>,
+ children: SmallVec<[AnyElement<V>; 2]>,
+}
+
+pub fn div<V>() -> Div<V> {
+ Div {
+ styles: Default::default(),
+ handlers: Default::default(),
+ children: Default::default(),
+ }
+}
+
+impl<V: 'static> Element<V> for Div<V> {
+ type PaintState = ();
+
+ fn layout(
+ &mut self,
+ view: &mut V,
+ cx: &mut LayoutContext<V>,
+ ) -> Result<(LayoutId, Self::PaintState)>
+ where
+ Self: Sized,
+ {
+ let style = self.computed_style();
+ let pop_text_style = style.text_style().map_or(false, |style| {
+ cx.push_text_style(cx.text_style().clone().refined(&style));
+ true
+ });
+
+ let children = self
+ .children
+ .iter_mut()
+ .map(|child| child.layout(view, cx))
+ .collect::<Result<Vec<LayoutId>>>()?;
+
+ if pop_text_style {
+ cx.pop_text_style();
+ }
+
+ Ok((cx.add_layout_node(style, children)?, ()))
+ }
+
+ fn paint(
+ &mut self,
+ view: &mut V,
+ layout: &Layout,
+ paint_state: &mut Self::PaintState,
+ cx: &mut PaintContext<V>,
+ ) where
+ Self: Sized,
+ {
+ let style = &self.computed_style();
+ let pop_text_style = style.text_style().map_or(false, |style| {
+ let style = cx.text_style().clone().refined(&style);
+ cx.push_text_style(style);
+ true
+ });
+ style.paint_background(layout.bounds, cx);
+ self.interaction_handlers()
+ .paint(layout.order, layout.bounds, cx);
+ for child in &mut self.children {
+ child.paint(view, layout.bounds.origin(), cx);
+ }
+ if pop_text_style {
+ cx.pop_text_style();
+ }
+ }
+}
+
+impl<V> Styleable for Div<V> {
+ type Style = Style;
+
+ fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+ &mut self.styles
+ }
+
+ fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+ self.styles.base()
+ }
+}
+
+impl<V> StyleHelpers for Div<V> {}
+
+impl<V> Interactive<V> for Div<V> {
+ fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+ &mut self.handlers
+ }
+}
+
+impl<V: 'static> ParentElement<V> for Div<V> {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+ &mut self.children
+ }
+}
+
+impl<V: 'static> IntoElement<V> for Div<V> {
+ type Element = Self;
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
@@ -0,0 +1,170 @@
+use std::marker::PhantomData;
+
+pub use crate::layout_context::LayoutContext;
+pub use crate::paint_context::PaintContext;
+use crate::themes::{Theme, Themed};
+use anyhow::Result;
+use gpui::geometry::vector::Vector2F;
+pub use gpui::{Layout, LayoutId};
+use smallvec::SmallVec;
+
+pub trait Element<V: 'static>: 'static {
+ type PaintState;
+
+ fn layout(
+ &mut self,
+ view: &mut V,
+ cx: &mut LayoutContext<V>,
+ ) -> Result<(LayoutId, Self::PaintState)>
+ where
+ Self: Sized;
+
+ fn paint(
+ &mut self,
+ view: &mut V,
+ layout: &Layout,
+ state: &mut Self::PaintState,
+ cx: &mut PaintContext<V>,
+ ) where
+ Self: Sized;
+
+ fn into_any(self) -> AnyElement<V>
+ where
+ Self: 'static + Sized,
+ {
+ AnyElement(Box::new(StatefulElement {
+ element: self,
+ phase: ElementPhase::Init,
+ }))
+ }
+
+ fn themed(self, theme: Theme) -> Themed<V, Self>
+ where
+ Self: Sized,
+ {
+ crate::themes::Themed {
+ child: self,
+ theme,
+ view_type: PhantomData,
+ }
+ }
+}
+
+/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
+trait AnyStatefulElement<V> {
+ fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId>;
+ fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>);
+}
+
+/// A wrapper around an element that stores its layout state.
+struct StatefulElement<V: 'static, E: Element<V>> {
+ element: E,
+ phase: ElementPhase<V, E>,
+}
+
+enum ElementPhase<V: 'static, E: Element<V>> {
+ Init,
+ PostLayout {
+ layout_id: LayoutId,
+ paint_state: E::PaintState,
+ },
+ PostPaint {
+ layout: Layout,
+ paint_state: E::PaintState,
+ },
+ Error(String),
+}
+
+impl<V: 'static, E: Element<V>> Default for ElementPhase<V, E> {
+ fn default() -> Self {
+ Self::Init
+ }
+}
+
+/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
+impl<V, E: Element<V>> AnyStatefulElement<V> for StatefulElement<V, E> {
+ fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
+ let result;
+ self.phase = match self.element.layout(view, cx) {
+ Ok((layout_id, paint_state)) => {
+ result = Ok(layout_id);
+ ElementPhase::PostLayout {
+ layout_id,
+ paint_state,
+ }
+ }
+ Err(error) => {
+ let message = error.to_string();
+ result = Err(error);
+ ElementPhase::Error(message)
+ }
+ };
+ result
+ }
+
+ fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>) {
+ self.phase = match std::mem::take(&mut self.phase) {
+ ElementPhase::PostLayout {
+ layout_id,
+ mut paint_state,
+ } => match cx.computed_layout(layout_id) {
+ Ok(mut layout) => {
+ layout.bounds = layout.bounds + parent_origin;
+ self.element.paint(view, &layout, &mut paint_state, cx);
+ ElementPhase::PostPaint {
+ layout,
+ paint_state,
+ }
+ }
+ Err(error) => ElementPhase::Error(error.to_string()),
+ },
+ phase @ ElementPhase::Error(_) => phase,
+ _ => panic!("invalid element phase to call paint"),
+ };
+ }
+}
+
+/// A dynamic element.
+pub struct AnyElement<V>(Box<dyn AnyStatefulElement<V>>);
+
+impl<V> AnyElement<V> {
+ pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
+ self.0.layout(view, cx)
+ }
+
+ pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>) {
+ self.0.paint(view, parent_origin, cx)
+ }
+}
+
+pub trait ParentElement<V: 'static> {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
+
+ fn child(mut self, child: impl IntoElement<V>) -> Self
+ where
+ Self: Sized,
+ {
+ self.children_mut().push(child.into_element().into_any());
+ self
+ }
+
+ fn children<I, E>(mut self, children: I) -> Self
+ where
+ I: IntoIterator<Item = E>,
+ E: IntoElement<V>,
+ Self: Sized,
+ {
+ self.children_mut().extend(
+ children
+ .into_iter()
+ .map(|child| child.into_element().into_any()),
+ );
+ self
+ }
+}
+
+pub trait IntoElement<V: 'static> {
+ type Element: Element<V>;
+
+ fn into_element(self) -> Self::Element;
+}
@@ -0,0 +1,36 @@
+#![allow(dead_code, unused_variables)]
+use gpui::{serde_json, ViewContext};
+use theme::ThemeSettings;
+use themes::Theme;
+
+mod adapter;
+mod color;
+mod components;
+mod div;
+mod element;
+mod hoverable;
+mod interactive;
+mod layout_context;
+mod paint_context;
+mod pressable;
+mod style;
+mod text;
+mod themes;
+mod view;
+
+// Nathan: During the transition, we will include the base theme on the legacy Theme struct.
+fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
+ settings::get::<ThemeSettings>(cx)
+ .theme
+ .deserialized_base_theme
+ .lock()
+ .get_or_insert_with(|| {
+ let theme: Theme =
+ serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
+ .unwrap();
+ Box::new(theme)
+ })
+ .downcast_ref::<Theme>()
+ .unwrap()
+ .clone()
+}
@@ -0,0 +1,104 @@
+use crate::{
+ element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+ interactive::{InteractionHandlers, Interactive},
+ layout_context::LayoutContext,
+ paint_context::PaintContext,
+ style::{Style, StyleHelpers, Styleable},
+};
+use anyhow::Result;
+use gpui::{platform::MouseMovedEvent, LayoutId};
+use refineable::{CascadeSlot, Refineable, RefinementCascade};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+pub struct Hoverable<E: Styleable> {
+ hovered: Rc<Cell<bool>>,
+ cascade_slot: CascadeSlot,
+ hovered_style: <E::Style as Refineable>::Refinement,
+ child: E,
+}
+
+pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
+ Hoverable {
+ hovered: Rc::new(Cell::new(false)),
+ cascade_slot: child.style_cascade().reserve(),
+ hovered_style: Default::default(),
+ child,
+ }
+}
+
+impl<E: Styleable> Styleable for Hoverable<E> {
+ type Style = E::Style;
+
+ fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+ self.child.style_cascade()
+ }
+
+ fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+ &mut self.hovered_style
+ }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
+ type PaintState = E::PaintState;
+
+ fn layout(
+ &mut self,
+ view: &mut V,
+ cx: &mut LayoutContext<V>,
+ ) -> Result<(LayoutId, Self::PaintState)>
+ where
+ Self: Sized,
+ {
+ Ok(self.child.layout(view, cx)?)
+ }
+
+ fn paint(
+ &mut self,
+ view: &mut V,
+ layout: &Layout,
+ paint_state: &mut Self::PaintState,
+ cx: &mut PaintContext<V>,
+ ) where
+ Self: Sized,
+ {
+ self.hovered
+ .set(layout.bounds.contains_point(cx.mouse_position()));
+
+ let slot = self.cascade_slot;
+ let style = self.hovered.get().then_some(self.hovered_style.clone());
+ self.style_cascade().set(slot, style);
+
+ let hovered = self.hovered.clone();
+ let bounds = layout.bounds;
+ cx.on_event(layout.order, move |view, event: &MouseMovedEvent, cx| {
+ if bounds.contains_point(cx.mouse_position()) != hovered.get() {
+ cx.repaint();
+ }
+ });
+
+ self.child.paint(view, layout, paint_state, cx);
+ }
+}
+
+impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
+
+impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
+ fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+ self.child.interaction_handlers()
+ }
+}
+
+impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+ self.child.children_mut()
+ }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
+ type Element = Self;
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
@@ -0,0 +1,147 @@
+use gpui::{
+ geometry::rect::RectF,
+ platform::{MouseButton, MouseButtonEvent},
+ EventContext,
+};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+use crate::element::PaintContext;
+
+pub trait Interactive<V: 'static> {
+ fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
+
+ fn on_mouse_down(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interaction_handlers()
+ .mouse_down
+ .push(Rc::new(handler));
+ self
+ }
+
+ fn on_mouse_up(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interaction_handlers().mouse_up.push(Rc::new(handler));
+ self
+ }
+
+ fn on_mouse_down_out(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interaction_handlers()
+ .mouse_down_out
+ .push(Rc::new(handler));
+ self
+ }
+
+ fn on_mouse_up_out(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interaction_handlers()
+ .mouse_up_out
+ .push(Rc::new(handler));
+ self
+ }
+
+ fn on_click(
+ self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ let pressed = Rc::new(Cell::new(false));
+ self.on_mouse_down(button, {
+ let pressed = pressed.clone();
+ move |_, _, _| {
+ pressed.set(true);
+ }
+ })
+ .on_mouse_up_out(button, {
+ let pressed = pressed.clone();
+ move |_, _, _| {
+ pressed.set(false);
+ }
+ })
+ .on_mouse_up(button, move |view, event, cx| {
+ if pressed.get() {
+ pressed.set(false);
+ handler(view, event, cx);
+ }
+ })
+ }
+}
+
+pub struct InteractionHandlers<V: 'static> {
+ mouse_down: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+ mouse_down_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+ mouse_up: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+ mouse_up_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+}
+
+impl<V: 'static> InteractionHandlers<V> {
+ pub fn paint(&self, order: u32, bounds: RectF, cx: &mut PaintContext<V>) {
+ for handler in self.mouse_down.iter().cloned() {
+ cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+ if event.is_down && bounds.contains_point(event.position) {
+ handler(view, event, cx);
+ }
+ })
+ }
+ for handler in self.mouse_up.iter().cloned() {
+ cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+ if !event.is_down && bounds.contains_point(event.position) {
+ handler(view, event, cx);
+ }
+ })
+ }
+ for handler in self.mouse_down_out.iter().cloned() {
+ cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+ if event.is_down && !bounds.contains_point(event.position) {
+ handler(view, event, cx);
+ }
+ })
+ }
+ for handler in self.mouse_up_out.iter().cloned() {
+ cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+ if !event.is_down && !bounds.contains_point(event.position) {
+ handler(view, event, cx);
+ }
+ })
+ }
+ }
+}
+
+impl<V> Default for InteractionHandlers<V> {
+ fn default() -> Self {
+ Self {
+ mouse_down: Default::default(),
+ mouse_up: Default::default(),
+ mouse_down_out: Default::default(),
+ mouse_up_out: Default::default(),
+ }
+ }
+}
@@ -0,0 +1,64 @@
+use crate::{element::LayoutId, style::Style};
+use anyhow::{anyhow, Result};
+use derive_more::{Deref, DerefMut};
+use gpui::{geometry::Size, MeasureParams, RenderContext, ViewContext};
+pub use gpui::{taffy::tree::NodeId, LayoutContext as LegacyLayoutContext};
+
+#[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(
+ &mut self,
+ style: Style,
+ children: impl IntoIterator<Item = NodeId>,
+ ) -> Result<LayoutId> {
+ 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(id)
+ }
+
+ pub fn add_measured_layout_node<F>(&mut self, style: Style, measure: F) -> Result<LayoutId>
+ where
+ F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
+ {
+ let rem_size = self.rem_pixels();
+ let layout_id = self
+ .layout_engine()
+ .ok_or_else(|| anyhow!("no layout engine"))?
+ .add_measured_node(style.to_taffy(rem_size), measure)?;
+
+ Ok(layout_id)
+ }
+}
@@ -1,14 +0,0 @@
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn it_works() {
- let result = add(2, 2);
- assert_eq!(result, 4);
- }
-}
@@ -0,0 +1,54 @@
+use anyhow::{anyhow, Result};
+use derive_more::{Deref, DerefMut};
+pub use gpui::taffy::tree::NodeId;
+use gpui::{
+ scene::EventHandler, EventContext, Layout, LayoutId, PaintContext as LegacyPaintContext,
+};
+use std::{any::TypeId, rc::Rc};
+
+#[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, '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 EventContext<V>) + 'static,
+ ) {
+ let view = self.weak_handle();
+
+ self.scene.event_handlers.push(EventHandler {
+ order,
+ handler: Rc::new(move |event, window_cx| {
+ if let Some(view) = view.upgrade(window_cx) {
+ view.update(window_cx, |view, view_cx| {
+ let mut event_cx = EventContext::new(view_cx);
+ handler(view, event.downcast_ref().unwrap(), &mut event_cx);
+ event_cx.bubble
+ })
+ } else {
+ true
+ }
+ }),
+ event_type: TypeId::of::<E>(),
+ })
+ }
+
+ pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
+ self.layout_engine()
+ .ok_or_else(|| anyhow!("no layout engine present"))?
+ .computed_layout(layout_id)
+ }
+}
@@ -0,0 +1,99 @@
+#![allow(dead_code, unused_variables)]
+use crate::element::Element;
+use gpui::{
+ geometry::{rect::RectF, vector::vec2f},
+ platform::WindowOptions,
+ serde_json, ViewContext,
+};
+use log::LevelFilter;
+use settings::{default_settings, SettingsStore};
+use simplelog::SimpleLogger;
+use theme::ThemeSettings;
+use themes::Theme;
+use view::view;
+use workspace::workspace;
+
+mod adapter;
+mod color;
+mod components;
+mod div;
+mod element;
+mod hoverable;
+mod interactive;
+mod layout_context;
+mod paint_context;
+mod pressable;
+mod style;
+mod text;
+mod themes;
+mod view;
+mod workspace;
+
+fn main() {
+ SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+ gpui::App::new(()).unwrap().run(|cx| {
+ let mut store = SettingsStore::default();
+ store
+ .set_default_settings(default_settings().as_ref(), cx)
+ .unwrap();
+ cx.set_global(store);
+ theme::init(Assets, cx);
+
+ cx.add_window(
+ WindowOptions {
+ bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
+ vec2f(0., 0.),
+ vec2f(400., 300.),
+ )),
+ center: true,
+ ..Default::default()
+ },
+ |_| view(|cx| playground(cx)),
+ );
+ cx.platform().activate(true);
+ });
+}
+
+fn playground<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<V> {
+ workspace().themed(current_theme(cx))
+}
+
+// Nathan: During the transition, we will include the base theme on the legacy Theme struct.
+fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
+ settings::get::<ThemeSettings>(cx)
+ .theme
+ .deserialized_base_theme
+ .lock()
+ .get_or_insert_with(|| {
+ let theme: Theme =
+ serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
+ .unwrap();
+ Box::new(theme)
+ })
+ .downcast_ref::<Theme>()
+ .unwrap()
+ .clone()
+}
+
+use anyhow::{anyhow, Result};
+use gpui::AssetSource;
+use rust_embed::RustEmbed;
+
+#[derive(RustEmbed)]
+#[folder = "../../../assets"]
+#[include = "themes/**/*"]
+#[exclude = "*.DS_Store"]
+pub struct Assets;
+
+impl AssetSource for Assets {
+ fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
+ Self::get(path)
+ .map(|f| f.data)
+ .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+ }
+
+ fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
+ Self::iter().filter(|p| p.starts_with(path)).collect()
+ }
+}
@@ -0,0 +1,107 @@
+use crate::{
+ element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+ interactive::{InteractionHandlers, Interactive},
+ layout_context::LayoutContext,
+ paint_context::PaintContext,
+ style::{Style, StyleHelpers, Styleable},
+};
+use anyhow::Result;
+use gpui::{platform::MouseButtonEvent, LayoutId};
+use refineable::{CascadeSlot, Refineable, RefinementCascade};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+pub struct Pressable<E: Styleable> {
+ pressed: Rc<Cell<bool>>,
+ pressed_style: <E::Style as Refineable>::Refinement,
+ cascade_slot: CascadeSlot,
+ child: E,
+}
+
+pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
+ Pressable {
+ pressed: Rc::new(Cell::new(false)),
+ pressed_style: Default::default(),
+ cascade_slot: child.style_cascade().reserve(),
+ child,
+ }
+}
+
+impl<E: Styleable> Styleable for Pressable<E> {
+ type Style = E::Style;
+
+ fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+ &mut self.pressed_style
+ }
+
+ fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
+ self.child.style_cascade()
+ }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
+ type PaintState = E::PaintState;
+
+ fn layout(
+ &mut self,
+ view: &mut V,
+ cx: &mut LayoutContext<V>,
+ ) -> Result<(LayoutId, Self::PaintState)>
+ where
+ Self: Sized,
+ {
+ self.child.layout(view, cx)
+ }
+
+ fn paint(
+ &mut self,
+ view: &mut V,
+ layout: &Layout,
+ paint_state: &mut Self::PaintState,
+ cx: &mut PaintContext<V>,
+ ) where
+ Self: Sized,
+ {
+ let slot = self.cascade_slot;
+ let style = self.pressed.get().then_some(self.pressed_style.clone());
+ self.style_cascade().set(slot, style);
+
+ let pressed = self.pressed.clone();
+ let bounds = layout.bounds;
+ cx.on_event(layout.order, move |view, event: &MouseButtonEvent, cx| {
+ if event.is_down {
+ if bounds.contains_point(event.position) {
+ pressed.set(true);
+ cx.repaint();
+ }
+ } else if pressed.get() {
+ pressed.set(false);
+ cx.repaint();
+ }
+ });
+
+ self.child.paint(view, layout, paint_state, cx);
+ }
+}
+
+impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
+
+impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
+ fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+ self.child.interaction_handlers()
+ }
+}
+
+impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+ self.child.children_mut()
+ }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
+ type Element = Self;
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
@@ -0,0 +1,379 @@
+use crate::{
+ color::Hsla,
+ hoverable::{hoverable, Hoverable},
+ paint_context::PaintContext,
+ pressable::{pressable, Pressable},
+};
+pub use gpui::taffy::style::{
+ AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
+ Overflow, Position,
+};
+use gpui::{
+ fonts::TextStyleRefinement,
+ geometry::{
+ rect::RectF, relative, AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length,
+ Point, PointRefinement, Size, SizeRefinement,
+ },
+ taffy,
+};
+use gpui2_macros::styleable_helpers;
+use refineable::{Refineable, RefinementCascade};
+
+#[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>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
+ 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(),
+ });
+ }
+ }
+
+ pub fn text_style(&self) -> Option<TextStyleRefinement> {
+ if let Some(color) = self.text_color {
+ Some(TextStyleRefinement {
+ color: Some(color.into()),
+ ..Default::default()
+ })
+ } else {
+ None
+ }
+ }
+}
+
+impl Default for Style {
+ fn default() -> Self {
+ Style {
+ display: Display::Block,
+ overflow: Point {
+ x: Overflow::Visible,
+ y: Overflow::Visible,
+ },
+ scrollbar_width: 0.0,
+ position: Position::Relative,
+ inset: Edges::auto(),
+ margin: Edges::<Length>::zero(),
+ padding: Edges::<DefiniteLength>::zero(),
+ border: 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, Debug)]
+pub enum Fill {
+ Color(Hsla),
+}
+
+impl Fill {
+ pub fn color(&self) -> Option<Hsla> {
+ match self {
+ Fill::Color(color) => Some(*color),
+ }
+ }
+}
+
+impl Default for Fill {
+ fn default() -> Self {
+ Self::Color(Hsla::default())
+ }
+}
+
+impl From<Hsla> for Fill {
+ fn from(color: Hsla) -> Self {
+ Self::Color(color)
+ }
+}
+
+#[derive(Clone, Refineable, Default)]
+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 + Default;
+
+ fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
+ fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
+
+ fn computed_style(&mut self) -> Self::Style {
+ Self::Style::from_refinement(&self.style_cascade().merged())
+ }
+
+ fn hovered(self) -> Hoverable<Self>
+ where
+ Self: Sized,
+ {
+ hoverable(self)
+ }
+
+ fn pressed(self) -> Pressable<Self>
+ where
+ Self: Sized,
+ {
+ pressable(self)
+ }
+}
+
+// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
+//
+// Example:
+// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
+// fn p_2(mut self) -> Self where Self: Sized;
+pub trait StyleHelpers: Styleable<Style = Style> {
+ styleable_helpers!();
+
+ fn h(mut self, height: Length) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().size.height = Some(height);
+ self
+ }
+
+ fn full(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().size.width = Some(relative(1.));
+ self.declared_style().size.height = Some(relative(1.));
+ self
+ }
+
+ fn relative(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().position = Some(Position::Relative);
+ self
+ }
+
+ fn absolute(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().position = Some(Position::Absolute);
+ self
+ }
+
+ fn block(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().display = Some(Display::Block);
+ self
+ }
+
+ fn flex(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().display = Some(Display::Flex);
+ self
+ }
+
+ fn flex_col(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().flex_direction = Some(FlexDirection::Column);
+ self
+ }
+
+ fn flex_row(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().flex_direction = Some(FlexDirection::Row);
+ self
+ }
+
+ fn flex_grow(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().flex_grow = Some(1.);
+ self
+ }
+
+ 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
+ }
+}
@@ -0,0 +1,148 @@
+use crate::{
+ element::{Element, IntoElement, Layout},
+ layout_context::LayoutContext,
+ paint_context::PaintContext,
+};
+use anyhow::Result;
+use gpui::{geometry::Size, text_layout::LineLayout, LayoutId, RenderContext};
+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 PaintState = Arc<Mutex<Option<TextLayout>>>;
+
+ fn layout(
+ &mut self,
+ view: &mut V,
+ cx: &mut LayoutContext<V>,
+ ) -> Result<(LayoutId, Self::PaintState)> {
+ 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 text = self.text.clone();
+ let paint_state = Arc::new(Mutex::new(None));
+
+ let layout_id = cx.add_measured_layout_node(Default::default(), {
+ let paint_state = paint_state.clone();
+ move |params| {
+ let line_layout = fonts.layout_line(
+ text.as_ref(),
+ text_style.font_size,
+ &[(text.len(), text_style.to_run())],
+ );
+
+ let size = Size {
+ width: line_layout.width,
+ height: line_height,
+ };
+
+ paint_state.lock().replace(TextLayout {
+ line_layout: Arc::new(line_layout),
+ line_height,
+ });
+
+ size
+ }
+ });
+
+ Ok((layout_id?, paint_state))
+ }
+
+ fn paint<'a>(
+ &mut self,
+ view: &mut V,
+ layout: &Layout,
+ paint_state: &mut Self::PaintState,
+ cx: &mut PaintContext<V>,
+ ) {
+ let line_layout;
+ let line_height;
+ {
+ let paint_state = paint_state.lock();
+ let paint_state = paint_state
+ .as_ref()
+ .expect("measurement has not been performed");
+ line_layout = paint_state.line_layout.clone();
+ line_height = paint_state.line_height;
+ }
+
+ let text_style = cx.text_style();
+ let line =
+ gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
+
+ let origin = layout.bounds.origin();
+ // TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder.
+ let visible_bounds = layout.bounds;
+ line.paint(cx.scene, origin, visible_bounds, line_height, cx.legacy_cx);
+ }
+}
+
+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(),
+ }
+ }
+}
@@ -0,0 +1,175 @@
+use crate::{
+ color::Hsla,
+ element::{Element, PaintContext},
+ layout_context::LayoutContext,
+};
+use gpui::WindowContext;
+use serde::{de::Visitor, Deserialize, Deserializer};
+use std::{collections::HashMap, fmt, marker::PhantomData};
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Theme {
+ pub name: String,
+ pub is_light: bool,
+ pub lowest: Layer,
+ pub middle: Layer,
+ pub highest: Layer,
+ pub popover_shadow: Shadow,
+ pub modal_shadow: Shadow,
+ #[serde(deserialize_with = "deserialize_player_colors")]
+ pub players: Vec<PlayerColors>,
+ #[serde(deserialize_with = "deserialize_syntax_colors")]
+ pub syntax: HashMap<String, Hsla>,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Layer {
+ pub base: StyleSet,
+ pub variant: StyleSet,
+ pub on: StyleSet,
+ pub accent: StyleSet,
+ pub positive: StyleSet,
+ pub warning: StyleSet,
+ pub negative: StyleSet,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct StyleSet {
+ #[serde(rename = "default")]
+ pub default: ContainerColors,
+ pub hovered: ContainerColors,
+ pub pressed: ContainerColors,
+ pub active: ContainerColors,
+ pub disabled: ContainerColors,
+ pub inverted: ContainerColors,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct ContainerColors {
+ pub background: Hsla,
+ pub foreground: Hsla,
+ pub border: Hsla,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct PlayerColors {
+ pub selection: Hsla,
+ pub cursor: Hsla,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Shadow {
+ pub blur: u8,
+ pub color: Hsla,
+ pub offset: Vec<u8>,
+}
+
+pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
+ cx.theme::<Theme>()
+}
+
+fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ struct PlayerArrayVisitor;
+
+ impl<'de> Visitor<'de> for PlayerArrayVisitor {
+ type Value = Vec<PlayerColors>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("an object with integer keys")
+ }
+
+ fn visit_map<A: serde::de::MapAccess<'de>>(
+ self,
+ mut map: A,
+ ) -> Result<Self::Value, A::Error> {
+ let mut players = Vec::with_capacity(8);
+ while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
+ if key < 8 {
+ players.push(value);
+ } else {
+ return Err(serde::de::Error::invalid_value(
+ serde::de::Unexpected::Unsigned(key as u64),
+ &"a key in range 0..7",
+ ));
+ }
+ }
+ Ok(players)
+ }
+ }
+
+ deserializer.deserialize_map(PlayerArrayVisitor)
+}
+
+fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
+where
+ D: serde::Deserializer<'de>,
+{
+ #[derive(Deserialize)]
+ struct ColorWrapper {
+ color: Hsla,
+ }
+
+ struct SyntaxVisitor;
+
+ impl<'de> Visitor<'de> for SyntaxVisitor {
+ type Value = HashMap<String, Hsla>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a map with keys and objects with a single color field as values")
+ }
+
+ fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
+ where
+ M: serde::de::MapAccess<'de>,
+ {
+ let mut result = HashMap::new();
+ while let Some(key) = map.next_key()? {
+ let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
+ result.insert(key, wrapper.color);
+ }
+ Ok(result)
+ }
+ }
+ deserializer.deserialize_map(SyntaxVisitor)
+}
+
+pub struct Themed<V: 'static, E> {
+ pub(crate) theme: Theme,
+ pub(crate) child: E,
+ pub(crate) view_type: PhantomData<V>,
+}
+
+impl<V: 'static, E: Element<V>> Element<V> for Themed<V, E> {
+ type PaintState = E::PaintState;
+
+ fn layout(
+ &mut self,
+ view: &mut V,
+ cx: &mut LayoutContext<V>,
+ ) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
+ where
+ Self: Sized,
+ {
+ cx.push_theme(self.theme.clone());
+ let result = self.child.layout(view, cx);
+ cx.pop_theme();
+ result
+ }
+
+ fn paint(
+ &mut self,
+ view: &mut V,
+ layout: &gpui::Layout,
+ state: &mut Self::PaintState,
+ cx: &mut PaintContext<V>,
+ ) where
+ Self: Sized,
+ {
+ cx.push_theme(self.theme.clone());
+ self.child.paint(view, layout, state, cx);
+ cx.pop_theme();
+ }
+}
@@ -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 }
+}
@@ -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()
+ }
+}
@@ -59,16 +59,16 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
);
let gen = quote! {
- impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics
+ impl #impl_generics gpui2::element::Element<#view_type_name> for #type_name #type_generics
#where_clause
{
- type PaintState = playground::element::AnyElement<#view_type_name #lifetimes>;
+ type PaintState = gpui2::element::AnyElement<#view_type_name #lifetimes>;
fn layout(
&mut self,
view: &mut V,
- cx: &mut playground::element::LayoutContext<V>,
- ) -> anyhow::Result<(playground::element::LayoutId, Self::PaintState)> {
+ cx: &mut gpui2::element::LayoutContext<V>,
+ ) -> anyhow::Result<(gpui2::element::LayoutId, Self::PaintState)> {
let mut rendered_element = self.render(view, cx).into_element().into_any();
let layout_id = rendered_element.layout(view, cx)?;
Ok((layout_id, rendered_element))
@@ -77,9 +77,9 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
fn paint(
&mut self,
view: &mut V,
- layout: &playground::element::Layout,
+ layout: &gpui2::element::Layout,
rendered_element: &mut Self::PaintState,
- cx: &mut playground::element::PaintContext<V>,
+ cx: &mut gpui2::element::PaintContext<V>,
) {
rendered_element.paint(view, layout.bounds.origin(), cx);
}
@@ -56,7 +56,7 @@ pub fn impl_into_element(
where_clause: &Option<&WhereClause>,
) -> proc_macro2::TokenStream {
quote! {
- impl #impl_generics playground::element::IntoElement<#view_type_name> for #type_name #type_generics
+ impl #impl_generics gpui2::element::IntoElement<#view_type_name> for #type_name #type_generics
#where_clause
{
type Element = Self;