Detailed changes
@@ -1,17 +1,17 @@
-use std::cell::Cell;
+use std::{cell::Cell, rc::Rc};
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
hsla,
layout_context::LayoutContext,
paint_context::PaintContext,
- style::{CornerRadii, Style, StyleHelpers, Styleable},
+ style::{CornerRadii, Overflow, Style, StyleHelpers, Styleable},
InteractionHandlers, Interactive,
};
use anyhow::Result;
use gpui::{
- geometry::vector::Vector2F,
- platform::{MouseButton, MouseButtonEvent, MouseMovedEvent},
+ geometry::{rect::RectF, vector::Vector2F, Point},
+ platform::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
scene::{self},
LayoutId,
};
@@ -23,6 +23,7 @@ pub struct Div<V: 'static> {
styles: RefinementCascade<Style>,
handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<V>; 2]>,
+ scroll_state: Option<ScrollState>,
}
pub fn div<V>() -> Div<V> {
@@ -30,11 +31,12 @@ pub fn div<V>() -> Div<V> {
styles: Default::default(),
handlers: Default::default(),
children: Default::default(),
+ scroll_state: None,
}
}
impl<V: 'static> Element<V> for Div<V> {
- type PaintState = ();
+ type PaintState = Vec<LayoutId>;
fn layout(
&mut self,
@@ -59,7 +61,7 @@ impl<V: 'static> Element<V> for Div<V> {
cx.pop_text_style();
}
- Ok((cx.add_layout_node(style, children)?, ()))
+ Ok((cx.add_layout_node(style, children.clone())?, children))
}
fn paint(
@@ -67,27 +69,45 @@ impl<V: 'static> Element<V> for Div<V> {
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
- _: &mut Self::PaintState,
+ child_layouts: &mut Vec<LayoutId>,
cx: &mut PaintContext<V>,
) where
Self: Sized,
{
let order = layout.order;
let bounds = layout.bounds + parent_origin;
+
let style = self.computed_style();
let pop_text_style = style.text_style(cx).map_or(false, |style| {
cx.push_text_style(&style).log_err().is_some()
});
style.paint_background(bounds, cx);
self.interaction_handlers().paint(order, bounds, cx);
+
+ let scrolled_origin = bounds.origin() - self.scroll_offset(&style.overflow);
+
+ // TODO: Support only one dimension being hidden
+ let mut pop_layer = false;
+ if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
+ cx.scene.push_layer(Some(bounds));
+ pop_layer = true;
+ }
+
for child in &mut self.children {
- child.paint(view, bounds.origin(), cx);
+ child.paint(view, scrolled_origin, cx);
+ }
+
+ if pop_layer {
+ cx.scene.pop_layer();
}
+
style.paint_foreground(bounds, cx);
if pop_text_style {
cx.pop_text_style();
}
+ self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
+
if cx.is_inspector_enabled() {
self.paint_inspector(parent_origin, layout, cx);
}
@@ -95,6 +115,106 @@ impl<V: 'static> Element<V> for Div<V> {
}
impl<V: 'static> Div<V> {
+ pub fn overflow_hidden(mut self) -> Self {
+ self.declared_style().overflow.x = Some(Overflow::Hidden);
+ self.declared_style().overflow.y = Some(Overflow::Hidden);
+ self
+ }
+
+ pub fn overflow_hidden_x(mut self) -> Self {
+ self.declared_style().overflow.x = Some(Overflow::Hidden);
+ self
+ }
+
+ pub fn overflow_hidden_y(mut self) -> Self {
+ self.declared_style().overflow.y = Some(Overflow::Hidden);
+ self
+ }
+
+ pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
+ self.scroll_state = Some(scroll_state);
+ self.declared_style().overflow.x = Some(Overflow::Scroll);
+ self.declared_style().overflow.y = Some(Overflow::Scroll);
+ self
+ }
+
+ pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
+ self.scroll_state = Some(scroll_state);
+ self.declared_style().overflow.x = Some(Overflow::Scroll);
+ self
+ }
+
+ pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
+ self.scroll_state = Some(scroll_state);
+ self.declared_style().overflow.y = Some(Overflow::Scroll);
+ self
+ }
+
+ fn scroll_offset(&self, overflow: &Point<Overflow>) -> Vector2F {
+ let mut offset = Vector2F::zero();
+ if overflow.y == Overflow::Scroll {
+ offset.set_y(self.scroll_state.as_ref().unwrap().y());
+ }
+ if overflow.x == Overflow::Scroll {
+ offset.set_x(self.scroll_state.as_ref().unwrap().x());
+ }
+
+ offset
+ }
+
+ fn handle_scroll(
+ &mut self,
+ order: u32,
+ bounds: RectF,
+ overflow: Point<Overflow>,
+ child_layout_ids: &[LayoutId],
+ cx: &mut PaintContext<V>,
+ ) {
+ if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
+ let mut scroll_max = Vector2F::zero();
+ for child_layout_id in child_layout_ids {
+ if let Some(child_layout) = cx
+ .layout_engine()
+ .unwrap()
+ .computed_layout(*child_layout_id)
+ .log_err()
+ {
+ scroll_max = scroll_max.max(child_layout.bounds.lower_right());
+ }
+ }
+ scroll_max -= bounds.size();
+
+ let scroll_state = self.scroll_state.as_ref().unwrap().clone();
+ cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
+ if bounds.contains_point(event.position) {
+ let scroll_delta = match event.delta {
+ gpui::platform::ScrollDelta::Pixels(delta) => delta,
+ gpui::platform::ScrollDelta::Lines(delta) => {
+ delta * cx.text_style().font_size
+ }
+ };
+ if overflow.x == Overflow::Scroll {
+ scroll_state.set_x(
+ (scroll_state.x() - scroll_delta.x())
+ .max(0.)
+ .min(scroll_max.x()),
+ );
+ }
+ if overflow.y == Overflow::Scroll {
+ scroll_state.set_y(
+ (scroll_state.y() - scroll_delta.y())
+ .max(0.)
+ .min(scroll_max.y()),
+ );
+ }
+ cx.repaint();
+ } else {
+ cx.bubble_event();
+ }
+ })
+ }
+ }
+
fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut PaintContext<V>) {
let style = self.styles.merged();
let bounds = layout.bounds + parent_origin;
@@ -175,3 +295,28 @@ impl<V: 'static> IntoElement<V> for Div<V> {
self
}
}
+
+#[derive(Default, Clone)]
+pub struct ScrollState(Rc<Cell<Vector2F>>);
+
+impl ScrollState {
+ pub fn x(&self) -> f32 {
+ self.0.get().x()
+ }
+
+ pub fn set_x(&self, value: f32) {
+ let mut current_value = self.0.get();
+ current_value.set_x(value);
+ self.0.set(current_value);
+ }
+
+ pub fn y(&self) -> f32 {
+ self.0.get().y()
+ }
+
+ pub fn set_y(&self, value: f32) {
+ let mut current_value = self.0.get();
+ current_value.set_y(value);
+ self.0.set(current_value);
+ }
+}
@@ -1,7 +1,7 @@
use crate::{element::LayoutId, style::Style};
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
-use gpui::{geometry::Size, MeasureParams};
+use gpui::{geometry::Size, taffy::style::Overflow, MeasureParams};
pub use gpui::{taffy::tree::NodeId, LayoutContext as LegacyLayoutContext};
#[derive(Deref, DerefMut)]
@@ -22,11 +22,12 @@ impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> {
children: impl IntoIterator<Item = NodeId>,
) -> Result<LayoutId> {
let rem_size = self.rem_size();
+ let style = style.to_taffy(rem_size);
let id = self
.legacy_cx
.layout_engine()
.ok_or_else(|| anyhow!("no layout engine"))?
- .add_node(style.to_taffy(rem_size), children)?;
+ .add_node(style, children)?;
Ok(id)
}
@@ -439,6 +439,14 @@ pub trait StyleHelpers: Styleable<Style = Style> {
self
}
+ fn grow(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.declared_style().flex_grow = Some(1.);
+ self
+ }
+
fn items_start(mut self) -> Self
where
Self: Sized,
@@ -1,6 +1,6 @@
use crate::theme::{theme, Theme};
use gpui2::{
- elements::{div, img, svg},
+ elements::{div, div::ScrollState, img, svg},
style::{StyleHelpers, Styleable},
ArcCow, Element, IntoElement, ParentElement, ViewContext,
};
@@ -9,11 +9,15 @@ use std::marker::PhantomData;
#[derive(Element)]
pub struct CollabPanelElement<V: 'static> {
view_type: PhantomData<V>,
+ scroll_state: ScrollState,
}
-pub fn collab_panel<V: 'static>() -> CollabPanelElement<V> {
+// When I improve child view rendering, I'd like to have V implement a trait that
+// provides the scroll state, among other things.
+pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
CollabPanelElement {
view_type: PhantomData,
+ scroll_state,
}
}
@@ -24,6 +28,7 @@ impl<V: 'static> CollabPanelElement<V> {
// Panel
div()
.w_64()
+ .h_full()
.flex()
.flex_col()
.font("Zed Sans Extended")
@@ -36,6 +41,7 @@ impl<V: 'static> CollabPanelElement<V> {
.w_full()
.flex()
.flex_col()
+ .overflow_y_scroll(self.scroll_state.clone())
// List Container
.child(
div()
@@ -67,21 +73,29 @@ impl<V: 'static> CollabPanelElement<V> {
.flex()
.flex_col()
.child(self.list_section_header("CONTACTS", true, theme))
- .child(self.list_item(
- "http://github.com/as-cii.png?s=50",
- "as-cii",
- theme,
- ))
- .child(self.list_item(
- "http://github.com/nathansobo.png?s=50",
- "nathansobo",
- theme,
- ))
- .child(self.list_item(
- "http://github.com/maxbrunsfeld.png?s=50",
- "maxbrunsfeld",
- theme,
- )),
+ .children(
+ std::iter::repeat_with(|| {
+ vec![
+ self.list_item(
+ "http://github.com/as-cii.png?s=50",
+ "as-cii",
+ theme,
+ ),
+ self.list_item(
+ "http://github.com/nathansobo.png?s=50",
+ "nathansobo",
+ theme,
+ ),
+ self.list_item(
+ "http://github.com/maxbrunsfeld.png?s=50",
+ "maxbrunsfeld",
+ theme,
+ ),
+ ]
+ })
+ .take(10)
+ .flatten(),
+ ),
),
)
.child(
@@ -1,10 +1,50 @@
use crate::{collab_panel::collab_panel, theme::theme};
use gpui2::{
- elements::{div, img, svg},
+ elements::{div, div::ScrollState, img, svg},
style::{StyleHelpers, Styleable},
Element, IntoElement, ParentElement, ViewContext,
};
+#[derive(Element, Default)]
+struct WorkspaceElement {
+ left_scroll_state: ScrollState,
+ right_scroll_state: ScrollState,
+}
+
+pub fn workspace<V: 'static>() -> impl Element<V> {
+ WorkspaceElement::default()
+}
+
+impl WorkspaceElement {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+
+ div()
+ .size_full()
+ .flex()
+ .flex_col()
+ .font("Zed Sans Extended")
+ .gap_0()
+ .justify_start()
+ .items_start()
+ .text_color(theme.lowest.base.default.foreground)
+ .fill(theme.middle.base.default.background)
+ .child(titlebar())
+ .child(
+ div()
+ .flex_1()
+ .w_full()
+ .flex()
+ .flex_row()
+ .overflow_hidden()
+ .child(collab_panel(self.left_scroll_state.clone()))
+ .child(div().h_full().flex_1())
+ .child(collab_panel(self.right_scroll_state.clone())),
+ )
+ .child(statusbar())
+ }
+}
+
#[derive(Element)]
struct TitleBar;
@@ -393,50 +433,3 @@ impl StatusBar {
)
}
}
-
-// ================================================================================ //
-
-#[derive(Element)]
-struct WorkspaceElement;
-
-pub fn workspace<V: 'static>() -> impl Element<V> {
- WorkspaceElement
-}
-
-impl WorkspaceElement {
- fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
- let theme = theme(cx);
-
- div()
- .size_full()
- .flex()
- .flex_col()
- .font("Zed Sans Extended")
- .gap_0()
- .justify_start()
- .items_start()
- .text_color(theme.lowest.base.default.foreground)
- // .fill(theme.middle.warning.default.background)
- .child(titlebar())
- .child(
- div()
- .flex_1()
- .flex()
- .flex_row()
- .w_full()
- .child(collab_panel())
- .child(div().h_full().flex_1())
- .child(collab_panel()),
- )
- .child(statusbar())
- }
-}
-
-// Hover over things
-// Paint its space... padding, margin, border, content
-
-/*
-* h_8, grow_0/flex_grow_0, shrink_0/flex_shrink_0
-* flex_grow
-* h_8, grow_0/flex_grow_0, shrink_0/flex_shrink_0
-*/