Support overflow scroll
This commit is contained in:
parent
713d72942d
commit
21d390fa4a
5 changed files with 237 additions and 76 deletions
|
@ -1,17 +1,17 @@
|
||||||
use std::cell::Cell;
|
use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
|
||||||
hsla,
|
hsla,
|
||||||
layout_context::LayoutContext,
|
layout_context::LayoutContext,
|
||||||
paint_context::PaintContext,
|
paint_context::PaintContext,
|
||||||
style::{CornerRadii, Style, StyleHelpers, Styleable},
|
style::{CornerRadii, Overflow, Style, StyleHelpers, Styleable},
|
||||||
InteractionHandlers, Interactive,
|
InteractionHandlers, Interactive,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
geometry::vector::Vector2F,
|
geometry::{rect::RectF, vector::Vector2F, Point},
|
||||||
platform::{MouseButton, MouseButtonEvent, MouseMovedEvent},
|
platform::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
|
||||||
scene::{self},
|
scene::{self},
|
||||||
LayoutId,
|
LayoutId,
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,7 @@ pub struct Div<V: 'static> {
|
||||||
styles: RefinementCascade<Style>,
|
styles: RefinementCascade<Style>,
|
||||||
handlers: InteractionHandlers<V>,
|
handlers: InteractionHandlers<V>,
|
||||||
children: SmallVec<[AnyElement<V>; 2]>,
|
children: SmallVec<[AnyElement<V>; 2]>,
|
||||||
|
scroll_state: Option<ScrollState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn div<V>() -> Div<V> {
|
pub fn div<V>() -> Div<V> {
|
||||||
|
@ -30,11 +31,12 @@ pub fn div<V>() -> Div<V> {
|
||||||
styles: Default::default(),
|
styles: Default::default(),
|
||||||
handlers: Default::default(),
|
handlers: Default::default(),
|
||||||
children: Default::default(),
|
children: Default::default(),
|
||||||
|
scroll_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> Element<V> for Div<V> {
|
impl<V: 'static> Element<V> for Div<V> {
|
||||||
type PaintState = ();
|
type PaintState = Vec<LayoutId>;
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -59,7 +61,7 @@ impl<V: 'static> Element<V> for Div<V> {
|
||||||
cx.pop_text_style();
|
cx.pop_text_style();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((cx.add_layout_node(style, children)?, ()))
|
Ok((cx.add_layout_node(style, children.clone())?, children))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
|
@ -67,27 +69,45 @@ impl<V: 'static> Element<V> for Div<V> {
|
||||||
view: &mut V,
|
view: &mut V,
|
||||||
parent_origin: Vector2F,
|
parent_origin: Vector2F,
|
||||||
layout: &Layout,
|
layout: &Layout,
|
||||||
_: &mut Self::PaintState,
|
child_layouts: &mut Vec<LayoutId>,
|
||||||
cx: &mut PaintContext<V>,
|
cx: &mut PaintContext<V>,
|
||||||
) where
|
) where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let order = layout.order;
|
let order = layout.order;
|
||||||
let bounds = layout.bounds + parent_origin;
|
let bounds = layout.bounds + parent_origin;
|
||||||
|
|
||||||
let style = self.computed_style();
|
let style = self.computed_style();
|
||||||
let pop_text_style = style.text_style(cx).map_or(false, |style| {
|
let pop_text_style = style.text_style(cx).map_or(false, |style| {
|
||||||
cx.push_text_style(&style).log_err().is_some()
|
cx.push_text_style(&style).log_err().is_some()
|
||||||
});
|
});
|
||||||
style.paint_background(bounds, cx);
|
style.paint_background(bounds, cx);
|
||||||
self.interaction_handlers().paint(order, bounds, cx);
|
self.interaction_handlers().paint(order, bounds, cx);
|
||||||
for child in &mut self.children {
|
|
||||||
child.paint(view, bounds.origin(), cx);
|
let scrolled_origin = bounds.origin() - self.scroll_offset(&style.overflow);
|
||||||
|
|
||||||
|
// TODO: Support only one dimension being hidden
|
||||||
|
let mut pop_layer = false;
|
||||||
|
if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
|
||||||
|
cx.scene.push_layer(Some(bounds));
|
||||||
|
pop_layer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for child in &mut self.children {
|
||||||
|
child.paint(view, scrolled_origin, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if pop_layer {
|
||||||
|
cx.scene.pop_layer();
|
||||||
|
}
|
||||||
|
|
||||||
style.paint_foreground(bounds, cx);
|
style.paint_foreground(bounds, cx);
|
||||||
if pop_text_style {
|
if pop_text_style {
|
||||||
cx.pop_text_style();
|
cx.pop_text_style();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
|
||||||
|
|
||||||
if cx.is_inspector_enabled() {
|
if cx.is_inspector_enabled() {
|
||||||
self.paint_inspector(parent_origin, layout, cx);
|
self.paint_inspector(parent_origin, layout, cx);
|
||||||
}
|
}
|
||||||
|
@ -95,6 +115,106 @@ impl<V: 'static> Element<V> for Div<V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> 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>) {
|
fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut PaintContext<V>) {
|
||||||
let style = self.styles.merged();
|
let style = self.styles.merged();
|
||||||
let bounds = layout.bounds + parent_origin;
|
let bounds = layout.bounds + parent_origin;
|
||||||
|
@ -175,3 +295,28 @@ impl<V: 'static> IntoElement<V> for Div<V> {
|
||||||
self
|
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 crate::{element::LayoutId, style::Style};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use derive_more::{Deref, DerefMut};
|
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};
|
pub use gpui::{taffy::tree::NodeId, LayoutContext as LegacyLayoutContext};
|
||||||
|
|
||||||
#[derive(Deref, DerefMut)]
|
#[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>,
|
children: impl IntoIterator<Item = NodeId>,
|
||||||
) -> Result<LayoutId> {
|
) -> Result<LayoutId> {
|
||||||
let rem_size = self.rem_size();
|
let rem_size = self.rem_size();
|
||||||
|
let style = style.to_taffy(rem_size);
|
||||||
let id = self
|
let id = self
|
||||||
.legacy_cx
|
.legacy_cx
|
||||||
.layout_engine()
|
.layout_engine()
|
||||||
.ok_or_else(|| anyhow!("no layout engine"))?
|
.ok_or_else(|| anyhow!("no layout engine"))?
|
||||||
.add_node(style.to_taffy(rem_size), children)?;
|
.add_node(style, children)?;
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -439,6 +439,14 @@ pub trait StyleHelpers: Styleable<Style = Style> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn grow(mut self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.declared_style().flex_grow = Some(1.);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn items_start(mut self) -> Self
|
fn items_start(mut self) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::theme::{theme, Theme};
|
use crate::theme::{theme, Theme};
|
||||||
use gpui2::{
|
use gpui2::{
|
||||||
elements::{div, img, svg},
|
elements::{div, div::ScrollState, img, svg},
|
||||||
style::{StyleHelpers, Styleable},
|
style::{StyleHelpers, Styleable},
|
||||||
ArcCow, Element, IntoElement, ParentElement, ViewContext,
|
ArcCow, Element, IntoElement, ParentElement, ViewContext,
|
||||||
};
|
};
|
||||||
|
@ -9,11 +9,15 @@ use std::marker::PhantomData;
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct CollabPanelElement<V: 'static> {
|
pub struct CollabPanelElement<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
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 {
|
CollabPanelElement {
|
||||||
view_type: PhantomData,
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +28,7 @@ impl<V: 'static> CollabPanelElement<V> {
|
||||||
// Panel
|
// Panel
|
||||||
div()
|
div()
|
||||||
.w_64()
|
.w_64()
|
||||||
|
.h_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.font("Zed Sans Extended")
|
.font("Zed Sans Extended")
|
||||||
|
@ -36,6 +41,7 @@ impl<V: 'static> CollabPanelElement<V> {
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
// List Container
|
// List Container
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
@ -67,21 +73,29 @@ impl<V: 'static> CollabPanelElement<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.child(self.list_section_header("CONTACTS", true, theme))
|
.child(self.list_section_header("CONTACTS", true, theme))
|
||||||
.child(self.list_item(
|
.children(
|
||||||
|
std::iter::repeat_with(|| {
|
||||||
|
vec![
|
||||||
|
self.list_item(
|
||||||
"http://github.com/as-cii.png?s=50",
|
"http://github.com/as-cii.png?s=50",
|
||||||
"as-cii",
|
"as-cii",
|
||||||
theme,
|
theme,
|
||||||
))
|
),
|
||||||
.child(self.list_item(
|
self.list_item(
|
||||||
"http://github.com/nathansobo.png?s=50",
|
"http://github.com/nathansobo.png?s=50",
|
||||||
"nathansobo",
|
"nathansobo",
|
||||||
theme,
|
theme,
|
||||||
))
|
),
|
||||||
.child(self.list_item(
|
self.list_item(
|
||||||
"http://github.com/maxbrunsfeld.png?s=50",
|
"http://github.com/maxbrunsfeld.png?s=50",
|
||||||
"maxbrunsfeld",
|
"maxbrunsfeld",
|
||||||
theme,
|
theme,
|
||||||
)),
|
),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.take(10)
|
||||||
|
.flatten(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
|
|
@ -1,10 +1,50 @@
|
||||||
use crate::{collab_panel::collab_panel, theme::theme};
|
use crate::{collab_panel::collab_panel, theme::theme};
|
||||||
use gpui2::{
|
use gpui2::{
|
||||||
elements::{div, img, svg},
|
elements::{div, div::ScrollState, img, svg},
|
||||||
style::{StyleHelpers, Styleable},
|
style::{StyleHelpers, Styleable},
|
||||||
Element, IntoElement, ParentElement, ViewContext,
|
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)]
|
#[derive(Element)]
|
||||||
struct TitleBar;
|
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
|
|
||||||
*/
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue