Add support for activating a pane by direction

Contributes: zed-industries/community#476
Contributes: zed-industries/community#478
This commit is contained in:
Conrad Irwin 2023-07-19 17:58:21 -06:00
parent b13e86aba6
commit e1379f0ef0
7 changed files with 157 additions and 5 deletions

View file

@ -5,6 +5,7 @@ use crate::{
use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
use anyhow::Result;
use client::{proto, Client};
use gpui::geometry::vector::Vector2F;
use gpui::{
fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
@ -203,6 +204,9 @@ pub trait Item: View {
fn show_toolbar(&self) -> bool {
true
}
fn pixel_position_of_cursor(&self) -> Option<Vector2F> {
None
}
}
pub trait ItemHandle: 'static + fmt::Debug {
@ -271,6 +275,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
fn serialized_item_kind(&self) -> Option<&'static str>;
fn show_toolbar(&self, cx: &AppContext) -> bool;
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F>;
}
pub trait WeakItemHandle {
@ -615,6 +620,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
fn show_toolbar(&self, cx: &AppContext) -> bool {
self.read(cx).show_toolbar()
}
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
self.read(cx).pixel_position_of_cursor()
}
}
impl From<Box<dyn ItemHandle>> for AnyViewHandle {

View file

@ -542,6 +542,12 @@ impl Pane {
self.items.get(self.active_item_index).cloned()
}
pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
self.items
.get(self.active_item_index)?
.pixel_position_of_cursor(cx)
}
pub fn item_for_entry(
&self,
entry_id: ProjectEntryId,

View file

@ -54,6 +54,20 @@ impl PaneGroup {
}
}
pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
match &self.root {
Member::Pane(_) => None,
Member::Axis(axis) => axis.bounding_box_for_pane(pane),
}
}
pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
match &self.root {
Member::Pane(pane) => Some(pane),
Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
}
}
/// Returns:
/// - Ok(true) if it found and removed a pane
/// - Ok(false) if it found but did not remove the pane
@ -309,15 +323,18 @@ pub(crate) struct PaneAxis {
pub axis: Axis,
pub members: Vec<Member>,
pub flexes: Rc<RefCell<Vec<f32>>>,
pub bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
}
impl PaneAxis {
pub fn new(axis: Axis, members: Vec<Member>) -> Self {
let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
Self {
axis,
members,
flexes,
bounding_boxes,
}
}
@ -326,10 +343,12 @@ impl PaneAxis {
debug_assert!(members.len() == flexes.len());
let flexes = Rc::new(RefCell::new(flexes));
let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
Self {
axis,
members,
flexes,
bounding_boxes,
}
}
@ -409,6 +428,40 @@ impl PaneAxis {
}
}
fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
for (idx, member) in self.members.iter().enumerate() {
match member {
Member::Pane(found) => {
if pane == found {
return self.bounding_boxes.borrow()[idx];
}
}
Member::Axis(axis) => {
if let Some(rect) = axis.bounding_box_for_pane(pane) {
return Some(rect);
}
}
}
}
None
}
fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
let bounding_boxes = self.bounding_boxes.borrow();
for (idx, member) in self.members.iter().enumerate() {
if let Some(coordinates) = bounding_boxes[idx] {
if coordinates.contains_point(coordinate) {
return match member {
Member::Pane(found) => Some(found),
Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
};
}
}
}
None
}
fn render(
&self,
project: &ModelHandle<Project>,
@ -423,7 +476,12 @@ impl PaneAxis {
) -> AnyElement<Workspace> {
debug_assert!(self.members.len() == self.flexes.borrow().len());
let mut pane_axis = PaneAxisElement::new(self.axis, basis, self.flexes.clone());
let mut pane_axis = PaneAxisElement::new(
self.axis,
basis,
self.flexes.clone(),
self.bounding_boxes.clone(),
);
let mut active_pane_ix = None;
let mut members = self.members.iter().enumerate().peekable();
@ -546,14 +604,21 @@ mod element {
active_pane_ix: Option<usize>,
flexes: Rc<RefCell<Vec<f32>>>,
children: Vec<AnyElement<Workspace>>,
bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
}
impl PaneAxisElement {
pub fn new(axis: Axis, basis: usize, flexes: Rc<RefCell<Vec<f32>>>) -> Self {
pub fn new(
axis: Axis,
basis: usize,
flexes: Rc<RefCell<Vec<f32>>>,
bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
) -> Self {
Self {
axis,
basis,
flexes,
bounding_boxes,
active_pane_ix: None,
children: Default::default(),
}
@ -708,11 +773,16 @@ mod element {
let mut child_origin = bounds.origin();
let mut bounding_boxes = self.bounding_boxes.borrow_mut();
bounding_boxes.clear();
let mut children_iter = self.children.iter_mut().enumerate().peekable();
while let Some((ix, child)) = children_iter.next() {
let child_start = child_origin.clone();
child.paint(scene, child_origin, visible_bounds, view, cx);
bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),

View file

@ -152,6 +152,9 @@ pub struct OpenPaths {
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePane(pub usize);
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePaneInDirection(pub SplitDirection);
#[derive(Deserialize)]
pub struct Toast {
id: usize,
@ -197,7 +200,7 @@ impl Clone for Toast {
}
}
impl_actions!(workspace, [ActivatePane, Toast]);
impl_actions!(workspace, [ActivatePane, ActivatePaneInDirection, Toast]);
pub type WorkspaceId = i64;
@ -262,6 +265,13 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
workspace.activate_next_pane(cx)
});
cx.add_action(
|workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
workspace.activate_pane_in_direction(action.0, cx)
},
);
cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
workspace.toggle_dock(DockPosition::Left, cx);
});
@ -2054,6 +2064,40 @@ impl Workspace {
}
}
pub fn activate_pane_in_direction(
&mut self,
direction: SplitDirection,
cx: &mut ViewContext<Self>,
) {
let bounding_box = match self.center.bounding_box_for_pane(&self.active_pane) {
Some(coordinates) => coordinates,
None => {
return;
}
};
let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
let center = match cursor {
Some(cursor) => cursor,
None => bounding_box.center(),
};
// currently there's a small gap between panes, so we can't just look "1px to the left"
// instead of trying to calcuate this exactly, we assume it'll always be smaller than
// "pane_gap" pixels (and that no-one uses panes smaller in any dimension than pane_gap).
let pane_gap = 20.;
let target = match direction {
SplitDirection::Left => vec2f(bounding_box.origin_x() - pane_gap, center.y()),
SplitDirection::Right => vec2f(bounding_box.max_x() + pane_gap, center.y()),
SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - pane_gap),
SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + pane_gap),
};
if let Some(pane) = self.center.pane_at_pixel_position(target) {
cx.focus(pane);
}
}
fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
if self.active_pane != pane {
self.active_pane = pane.clone();
@ -3030,6 +3074,7 @@ impl Workspace {
axis,
members,
flexes,
bounding_boxes: _,
}) => SerializedPaneGroup::Group {
axis: *axis,
children: members