Merge pull request #745 from zed-industries/scroll-tabs
Allow pane tabs to be scrolled when they overflow
This commit is contained in:
commit
52251c3463
5 changed files with 146 additions and 35 deletions
|
@ -1165,10 +1165,7 @@ pub mod tests {
|
||||||
*markers[0].column_mut() += 1;
|
*markers[0].column_mut() += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
|
||||||
unmarked_snapshot.clip_point(dbg!(markers[0]), bias),
|
|
||||||
markers[1]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ use std::{any::Any, f32::INFINITY};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
json::{self, ToJson, Value},
|
json::{self, ToJson, Value},
|
||||||
Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
Axis, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
|
||||||
SizeConstraint, Vector2FExt,
|
EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
|
||||||
};
|
};
|
||||||
use pathfinder_geometry::{
|
use pathfinder_geometry::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
|
@ -11,9 +11,16 @@ use pathfinder_geometry::{
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ScrollState {
|
||||||
|
scroll_to: Option<usize>,
|
||||||
|
scroll_position: f32,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Flex {
|
pub struct Flex {
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
children: Vec<ElementBox>,
|
children: Vec<ElementBox>,
|
||||||
|
scroll_state: Option<ElementStateHandle<ScrollState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flex {
|
impl Flex {
|
||||||
|
@ -21,6 +28,7 @@ impl Flex {
|
||||||
Self {
|
Self {
|
||||||
axis,
|
axis,
|
||||||
children: Default::default(),
|
children: Default::default(),
|
||||||
|
scroll_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +40,22 @@ impl Flex {
|
||||||
Self::new(Axis::Vertical)
|
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(
|
fn layout_flex_children(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout_expanded: bool,
|
layout_expanded: bool,
|
||||||
|
@ -167,6 +191,30 @@ impl Element for Flex {
|
||||||
size.set_y(constraint.max.y());
|
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)
|
(size, remaining_space)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +229,16 @@ impl Element for Flex {
|
||||||
if overflowing {
|
if overflowing {
|
||||||
cx.scene.push_layer(Some(bounds));
|
cx.scene.push_layer(Some(bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut child_origin = bounds.origin();
|
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 {
|
for child in &mut self.children {
|
||||||
if *remaining_space > 0. {
|
if *remaining_space > 0. {
|
||||||
if let Some(metadata) = child.metadata::<FlexParentData>() {
|
if let Some(metadata) = child.metadata::<FlexParentData>() {
|
||||||
|
@ -208,15 +265,54 @@ impl Element for Flex {
|
||||||
fn dispatch_event(
|
fn dispatch_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
_: RectF,
|
bounds: RectF,
|
||||||
_: &mut Self::LayoutState,
|
remaining_space: &mut Self::LayoutState,
|
||||||
_: &mut Self::PaintState,
|
_: &mut Self::PaintState,
|
||||||
cx: &mut EventContext,
|
cx: &mut EventContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
if let Some(position) = event.position() {
|
||||||
|
if !bounds.contains_point(position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut handled = false;
|
let mut handled = false;
|
||||||
for child in &mut self.children {
|
for child in &mut self.children {
|
||||||
handled = child.dispatch_event(event, cx) || handled;
|
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
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,10 @@ use crate::{
|
||||||
ElementBox,
|
ElementBox,
|
||||||
};
|
};
|
||||||
use json::ToJson;
|
use json::ToJson;
|
||||||
use parking_lot::Mutex;
|
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||||
use std::{cmp, ops::Range, sync::Arc};
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct UniformListState(Arc<Mutex<StateInner>>);
|
pub struct UniformListState(Rc<RefCell<StateInner>>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ScrollTarget {
|
pub enum ScrollTarget {
|
||||||
|
@ -22,11 +21,11 @@ pub enum ScrollTarget {
|
||||||
|
|
||||||
impl UniformListState {
|
impl UniformListState {
|
||||||
pub fn scroll_to(&self, scroll_to: ScrollTarget) {
|
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 {
|
pub fn scroll_top(&self) -> f32 {
|
||||||
self.0.lock().scroll_top
|
self.0.borrow().scroll_top
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ where
|
||||||
delta *= 20.;
|
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);
|
state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
|
@ -104,7 +103,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
|
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() {
|
if let Some(scroll_to) = state.scroll_to.take() {
|
||||||
let item_ix;
|
let item_ix;
|
||||||
|
@ -141,7 +140,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_top(&self) -> f32 {
|
fn scroll_top(&self) -> f32 {
|
||||||
self.state.0.lock().scroll_top
|
self.state.0.borrow().scroll_top
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,3 +61,20 @@ pub enum Event {
|
||||||
left_mouse_down: bool,
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ pub enum Event {
|
||||||
pub struct Pane {
|
pub struct Pane {
|
||||||
items: Vec<Box<dyn ItemHandle>>,
|
items: Vec<Box<dyn ItemHandle>>,
|
||||||
active_item_index: usize,
|
active_item_index: usize,
|
||||||
|
autoscroll: bool,
|
||||||
nav_history: Rc<RefCell<NavHistory>>,
|
nav_history: Rc<RefCell<NavHistory>>,
|
||||||
toolbar: ViewHandle<Toolbar>,
|
toolbar: ViewHandle<Toolbar>,
|
||||||
}
|
}
|
||||||
|
@ -142,6 +143,7 @@ impl Pane {
|
||||||
Self {
|
Self {
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
active_item_index: 0,
|
active_item_index: 0,
|
||||||
|
autoscroll: false,
|
||||||
nav_history: Default::default(),
|
nav_history: Default::default(),
|
||||||
toolbar: cx.add_view(|_| Toolbar::new()),
|
toolbar: cx.add_view(|_| Toolbar::new()),
|
||||||
}
|
}
|
||||||
|
@ -200,27 +202,19 @@ impl Pane {
|
||||||
.upgrade(cx)
|
.upgrade(cx)
|
||||||
.and_then(|v| pane.index_for_item(v.as_ref()))
|
.and_then(|v| pane.index_for_item(v.as_ref()))
|
||||||
{
|
{
|
||||||
if let Some(item) = pane.active_item() {
|
let prev_active_item_index = pane.active_item_index;
|
||||||
pane.nav_history.borrow_mut().set_mode(mode);
|
pane.nav_history.borrow_mut().set_mode(mode);
|
||||||
item.deactivated(cx);
|
pane.activate_item(index, true, cx);
|
||||||
pane.nav_history
|
pane.nav_history
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.set_mode(NavigationMode::Normal);
|
.set_mode(NavigationMode::Normal);
|
||||||
}
|
|
||||||
|
|
||||||
let prev_active_index = mem::replace(&mut pane.active_item_index, index);
|
let mut navigated = prev_active_item_index != pane.active_item_index;
|
||||||
|
|
||||||
let mut navigated = prev_active_index != pane.active_item_index;
|
|
||||||
if let Some(data) = entry.data {
|
if let Some(data) = entry.data {
|
||||||
navigated |= pane.active_item()?.navigate(data, cx);
|
navigated |= pane.active_item()?.navigate(data, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if navigated {
|
if navigated {
|
||||||
pane.focus_active_item(cx);
|
|
||||||
pane.update_toolbar(cx);
|
|
||||||
pane.activate(cx);
|
|
||||||
cx.emit(Event::ActivateItem { local: true });
|
|
||||||
cx.notify();
|
|
||||||
break None;
|
break None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,10 +370,12 @@ impl Pane {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_item(&mut self, index: usize, local: bool, cx: &mut ViewContext<Self>) {
|
pub fn activate_item(&mut self, index: usize, local: bool, cx: &mut ViewContext<Self>) {
|
||||||
|
use NavigationMode::{GoingBack, GoingForward};
|
||||||
if index < self.items.len() {
|
if index < self.items.len() {
|
||||||
let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
|
let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
|
||||||
if prev_active_item_ix != self.active_item_index
|
if matches!(self.nav_history.borrow().mode, GoingBack | GoingForward)
|
||||||
&& prev_active_item_ix < self.items.len()
|
|| (prev_active_item_ix != self.active_item_index
|
||||||
|
&& prev_active_item_ix < self.items.len())
|
||||||
{
|
{
|
||||||
self.items[prev_active_item_ix].deactivated(cx);
|
self.items[prev_active_item_ix].deactivated(cx);
|
||||||
cx.emit(Event::ActivateItem { local });
|
cx.emit(Event::ActivateItem { local });
|
||||||
|
@ -389,6 +385,7 @@ impl Pane {
|
||||||
self.focus_active_item(cx);
|
self.focus_active_item(cx);
|
||||||
self.activate(cx);
|
self.activate(cx);
|
||||||
}
|
}
|
||||||
|
self.autoscroll = true;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -628,13 +625,18 @@ impl Pane {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tabs(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
|
|
||||||
enum Tabs {}
|
enum Tabs {}
|
||||||
let pane = cx.handle();
|
let pane = cx.handle();
|
||||||
let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
|
let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
|
||||||
let mut row = Flex::row();
|
let autoscroll = if mem::take(&mut self.autoscroll) {
|
||||||
|
Some(self.active_item_index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
|
||||||
for (ix, item) in self.items.iter().enumerate() {
|
for (ix, item) in self.items.iter().enumerate() {
|
||||||
let is_active = ix == self.active_item_index;
|
let is_active = ix == self.active_item_index;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue