Merge pull request #745 from zed-industries/scroll-tabs

Allow pane tabs to be scrolled when they overflow
This commit is contained in:
Antonio Scandurra 2022-04-07 09:48:54 +02:00 committed by GitHub
commit 52251c3463
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 35 deletions

View file

@ -2,8 +2,8 @@ use std::{any::Any, f32::INFINITY};
use crate::{
json::{self, ToJson, Value},
Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint, Vector2FExt,
Axis, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
};
use pathfinder_geometry::{
rect::RectF,
@ -11,9 +11,16 @@ use pathfinder_geometry::{
};
use serde_json::json;
#[derive(Default)]
struct ScrollState {
scroll_to: Option<usize>,
scroll_position: f32,
}
pub struct Flex {
axis: Axis,
children: Vec<ElementBox>,
scroll_state: Option<ElementStateHandle<ScrollState>>,
}
impl Flex {
@ -21,6 +28,7 @@ impl Flex {
Self {
axis,
children: Default::default(),
scroll_state: None,
}
}
@ -32,6 +40,22 @@ impl Flex {
Self::new(Axis::Vertical)
}
pub fn scrollable<Tag, C>(
mut self,
element_id: usize,
scroll_to: Option<usize>,
cx: &mut C,
) -> Self
where
Tag: 'static,
C: ElementStateContext,
{
let scroll_state = cx.element_state::<Tag, ScrollState>(element_id);
scroll_state.update(cx, |scroll_state, _| scroll_state.scroll_to = scroll_to);
self.scroll_state = Some(scroll_state);
self
}
fn layout_flex_children(
&mut self,
layout_expanded: bool,
@ -167,6 +191,30 @@ impl Element for Flex {
size.set_y(constraint.max.y());
}
if let Some(scroll_state) = self.scroll_state.as_ref() {
scroll_state.update(cx, |scroll_state, _| {
if let Some(scroll_to) = scroll_state.scroll_to.take() {
let visible_start = scroll_state.scroll_position;
let visible_end = visible_start + size.along(self.axis);
if let Some(child) = self.children.get(scroll_to) {
let child_start: f32 = self.children[..scroll_to]
.iter()
.map(|c| c.size().along(self.axis))
.sum();
let child_end = child_start + child.size().along(self.axis);
if child_start < visible_start {
scroll_state.scroll_position = child_start;
} else if child_end > visible_end {
scroll_state.scroll_position = child_end - size.along(self.axis);
}
}
}
scroll_state.scroll_position =
scroll_state.scroll_position.min(-remaining_space).max(0.);
});
}
(size, remaining_space)
}
@ -181,7 +229,16 @@ impl Element for Flex {
if overflowing {
cx.scene.push_layer(Some(bounds));
}
let mut child_origin = bounds.origin();
if let Some(scroll_state) = self.scroll_state.as_ref() {
let scroll_position = scroll_state.read(cx).scroll_position;
match self.axis {
Axis::Horizontal => child_origin.set_x(child_origin.x() - scroll_position),
Axis::Vertical => child_origin.set_y(child_origin.y() - scroll_position),
}
}
for child in &mut self.children {
if *remaining_space > 0. {
if let Some(metadata) = child.metadata::<FlexParentData>() {
@ -208,15 +265,54 @@ impl Element for Flex {
fn dispatch_event(
&mut self,
event: &Event,
_: RectF,
_: &mut Self::LayoutState,
bounds: RectF,
remaining_space: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
if let Some(position) = event.position() {
if !bounds.contains_point(position) {
return false;
}
}
let mut handled = false;
for child in &mut self.children {
handled = child.dispatch_event(event, cx) || handled;
}
if !handled {
if let &Event::ScrollWheel {
position,
delta,
precise,
} = event
{
if *remaining_space < 0. && bounds.contains_point(position) {
if let Some(scroll_state) = self.scroll_state.as_ref() {
scroll_state.update(cx, |scroll_state, cx| {
let mut delta = match self.axis {
Axis::Horizontal => {
if delta.x() != 0. {
delta.x()
} else {
delta.y()
}
}
Axis::Vertical => delta.y(),
};
if !precise {
delta *= 20.;
}
scroll_state.scroll_position -= delta;
handled = true;
cx.notify();
});
}
}
}
}
handled
}

View file

@ -8,11 +8,10 @@ use crate::{
ElementBox,
};
use json::ToJson;
use parking_lot::Mutex;
use std::{cmp, ops::Range, sync::Arc};
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
#[derive(Clone, Default)]
pub struct UniformListState(Arc<Mutex<StateInner>>);
pub struct UniformListState(Rc<RefCell<StateInner>>);
#[derive(Debug)]
pub enum ScrollTarget {
@ -22,11 +21,11 @@ pub enum ScrollTarget {
impl UniformListState {
pub fn scroll_to(&self, scroll_to: ScrollTarget) {
self.0.lock().scroll_to = Some(scroll_to);
self.0.borrow_mut().scroll_to = Some(scroll_to);
}
pub fn scroll_top(&self) -> f32 {
self.0.lock().scroll_top
self.0.borrow().scroll_top
}
}
@ -96,7 +95,7 @@ where
delta *= 20.;
}
let mut state = self.state.0.lock();
let mut state = self.state.0.borrow_mut();
state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
cx.notify();
@ -104,7 +103,7 @@ where
}
fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
let mut state = self.state.0.lock();
let mut state = self.state.0.borrow_mut();
if let Some(scroll_to) = state.scroll_to.take() {
let item_ix;
@ -141,7 +140,7 @@ where
}
fn scroll_top(&self) -> f32 {
self.state.0.lock().scroll_top
self.state.0.borrow().scroll_top
}
}

View file

@ -61,3 +61,20 @@ pub enum Event {
left_mouse_down: bool,
},
}
impl Event {
pub fn position(&self) -> Option<Vector2F> {
match self {
Event::KeyDown { .. } => None,
Event::ScrollWheel { position, .. }
| Event::LeftMouseDown { position, .. }
| Event::LeftMouseUp { position }
| Event::LeftMouseDragged { position }
| Event::RightMouseDown { position, .. }
| Event::RightMouseUp { position }
| Event::NavigateMouseDown { position, .. }
| Event::NavigateMouseUp { position, .. }
| Event::MouseMoved { position, .. } => Some(*position),
}
}
}