1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 22:01:47 +03:00

wezterm: allow dragging pane edges to resize them

refs: https://github.com/wez/wezterm/issues/157
This commit is contained in:
Wez Furlong 2020-09-23 18:01:48 -07:00
parent f9c9510691
commit 86cc214344
3 changed files with 247 additions and 55 deletions

View File

@ -260,6 +260,7 @@ pub struct TermWindow {
last_mouse_coords: (usize, i64),
last_mouse_terminal_coords: (usize, StableRowIndex),
scroll_drag_start: Option<isize>,
split_drag_start: Option<PositionedSplit>,
config_generation: usize,
prev_cursor: PrevCursorPos,
last_scroll_info: RenderableDimensions,
@ -374,7 +375,11 @@ impl WindowCallbacks for TermWindow {
WMEK::Release(ref press) => {
self.current_mouse_button = None;
if press == &MousePress::Left && self.scroll_drag_start.take().is_some() {
// Completed a drag
// Completed a scrollbar drag
return;
}
if press == &MousePress::Left && self.split_drag_start.take().is_some() {
// Completed a split drag
return;
}
}
@ -439,6 +444,33 @@ impl WindowCallbacks for TermWindow {
context.invalidate();
return;
}
if let Some(split) = self.split_drag_start.take() {
let mux = Mux::get().unwrap();
let tab = match mux.get_active_tab_for_window(self.mux_window_id) {
Some(tab) => tab,
None => return,
};
let delta = match split.direction {
SplitDirection::Horizontal => {
(x as isize).saturating_sub(split.left as isize)
}
SplitDirection::Vertical => {
(term_y as isize).saturating_sub(split.top as isize)
}
};
if delta != 0 {
tab.resize_split_by(split.index, delta);
self.split_drag_start = tab.iter_splits().into_iter().nth(split.index);
context.invalidate();
} else {
self.split_drag_start.replace(split);
}
return;
}
}
_ => {}
}
@ -619,6 +651,7 @@ impl WindowCallbacks for TermWindow {
last_mouse_coords: self.last_mouse_coords.clone(),
last_mouse_terminal_coords: self.last_mouse_terminal_coords.clone(),
scroll_drag_start: self.scroll_drag_start.clone(),
split_drag_start: self.split_drag_start.clone(),
config_generation: self.config_generation,
prev_cursor: self.prev_cursor.clone(),
last_scroll_info: self.last_scroll_info.clone(),
@ -827,6 +860,7 @@ impl TermWindow {
last_mouse_coords: (0, -1),
last_mouse_terminal_coords: (0, 0),
scroll_drag_start: None,
split_drag_start: None,
config_generation: config.generation(),
prev_cursor: PrevCursorPos::new(),
last_scroll_info: RenderableDimensions::default(),
@ -3424,6 +3458,36 @@ impl TermWindow {
event: &MouseEvent,
context: &dyn WindowOps,
) {
let mut on_split = false;
if y >= 0 {
let y = y as usize;
for split in self.get_splits() {
on_split = match split.direction {
SplitDirection::Horizontal => {
if x == split.left && y >= split.top && y <= split.top + split.size {
true
} else {
false
}
}
SplitDirection::Vertical => {
if y == split.top && x >= split.left && x <= split.left + split.size {
true
} else {
false
}
}
};
if on_split && event.kind == WMEK::Press(MousePress::Left) {
context.set_cursor(Some(MouseCursor::Hand));
self.split_drag_start.replace(split);
return;
}
}
}
for pos in self.get_panes_to_render() {
if y >= pos.top as i64
&& y <= (pos.top + pos.height) as i64
@ -3487,7 +3551,7 @@ impl TermWindow {
}
};
context.set_cursor(Some(if self.current_highlight.is_some() {
context.set_cursor(Some(if on_split || self.current_highlight.is_some() {
// When hovering over a hyperlink, show an appropriate
// mouse cursor to give the cue that it is clickable
MouseCursor::Hand

View File

@ -2,7 +2,7 @@ use crate::mux::domain::DomainId;
use crate::mux::renderable::Renderable;
use crate::mux::Mux;
use async_trait::async_trait;
use bintree::{PathBranch, Tree};
use bintree::PathBranch;
use downcast_rs::{impl_downcast, Downcast};
use portable_pty::PtySize;
use serde::{Deserialize, Serialize};
@ -13,6 +13,9 @@ use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Clipboard, KeyCode, KeyModifiers, MouseEvent, StableRowIndex};
pub type Tree = bintree::Tree<Rc<dyn Pane>, SplitDirectionAndSize>;
pub type Cursor = bintree::Cursor<Rc<dyn Pane>, SplitDirectionAndSize>;
static TAB_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(0);
pub type TabId = usize;
@ -93,7 +96,7 @@ pub struct SearchResult {
/// At this time only a single pane is supported
pub struct Tab {
id: TabId,
pane: RefCell<Option<Tree<Rc<dyn Pane>, SplitDirectionAndSize>>>,
pane: RefCell<Option<Tree>>,
size: RefCell<PtySize>,
active: RefCell<usize>,
}
@ -325,9 +328,9 @@ impl Tab {
*self.size.borrow()
}
/// Apply the new size of the tab to the panes contained within.
/// This works by adjusting the size of the second half of each split.
pub fn resize(&self, size: PtySize) {
let cell_width = size.pixel_width / size.cols;
let cell_height = size.pixel_height / size.rows;
let mut root = self.pane.borrow_mut();
let mut cursor = root.take().unwrap().cursor();
@ -350,28 +353,7 @@ impl Tab {
// Apply our size to the tty
cursor.leaf_mut().map(|pane| pane.resize(pane_size));
} else {
if let Ok(Some(node)) = cursor.node_mut() {
// Adjust the size of the node; we preserve the size of the first
// child and adjust the second, so if we are split down the middle
// and the window is made wider, the right column will grow in
// size, leaving the left at its current width.
if node.direction == SplitDirection::Horizontal {
node.first.rows = pane_size.rows;
node.second.rows = pane_size.rows;
node.second.cols = pane_size.cols.saturating_sub(1 + node.first.cols);
} else {
node.first.cols = pane_size.cols;
node.second.cols = pane_size.cols;
node.second.rows = pane_size.rows.saturating_sub(1 + node.first.rows);
}
node.first.pixel_width = node.first.cols * cell_width;
node.first.pixel_height = node.first.rows * cell_height;
node.second.pixel_width = node.second.cols * cell_width;
node.second.pixel_height = node.second.rows * cell_height;
}
self.apply_pane_size(pane_size, &mut cursor);
}
match cursor.preorder_next() {
Ok(c) => cursor = c,
@ -383,10 +365,137 @@ impl Tab {
}
}
pub fn prune_dead_panes(&self) {
fn apply_pane_size(&self, pane_size: PtySize, cursor: &mut Cursor) {
let cell_width = pane_size.pixel_width / pane_size.cols;
let cell_height = pane_size.pixel_height / pane_size.rows;
if let Ok(Some(node)) = cursor.node_mut() {
// Adjust the size of the node; we preserve the size of the first
// child and adjust the second, so if we are split down the middle
// and the window is made wider, the right column will grow in
// size, leaving the left at its current width.
if node.direction == SplitDirection::Horizontal {
node.first.rows = pane_size.rows;
node.second.rows = pane_size.rows;
node.second.cols = pane_size.cols.saturating_sub(1 + node.first.cols);
} else {
node.first.cols = pane_size.cols;
node.second.cols = pane_size.cols;
node.second.rows = pane_size.rows.saturating_sub(1 + node.first.rows);
}
node.first.pixel_width = node.first.cols * cell_width;
node.first.pixel_height = node.first.rows * cell_height;
node.second.pixel_width = node.second.cols * cell_width;
node.second.pixel_height = node.second.rows * cell_height;
}
}
/// Given split_index, the topological index of a split returned by
/// iter_splits() as PositionedSplit::index, revised the split position
/// by the provided delta; positive values move the split to the right/bottom,
/// and negative values to the left/top.
/// The adjusted size is propogated downwards to contained children and
/// their panes are resized accordingly.
pub fn resize_split_by(&self, split_index: usize, delta: isize) {
let mut root = self.pane.borrow_mut();
let mut cursor = root.take().unwrap().cursor();
let mut index = 0;
// Position cursor on the specified split
loop {
if !cursor.is_leaf() {
if index == split_index {
// Found it
break;
}
index += 1;
}
match cursor.preorder_next() {
Ok(c) => cursor = c,
Err(c) => {
// Didn't find it
root.replace(c.tree());
return;
}
}
}
// Now cursor is looking at the split
if let Ok(Some(node)) = cursor.node_mut() {
match node.direction {
SplitDirection::Horizontal => {
let width = node.width();
let mut cols = node.first.cols as isize;
cols = cols
.saturating_add(delta)
.max(1)
.min((width as isize).saturating_sub(2));
node.first.cols = cols as u16;
node.second.cols = width.saturating_sub(node.first.cols.saturating_add(1));
}
SplitDirection::Vertical => {
let height = node.height();
let mut rows = node.first.rows as isize;
rows = rows
.saturating_add(delta)
.max(1)
.min((height as isize).saturating_sub(2));
node.first.rows = rows as u16;
node.second.rows = height.saturating_sub(node.first.rows.saturating_add(1));
}
}
}
// Now we need to cascade this down to children
match cursor.preorder_next() {
Ok(c) => cursor = c,
Err(c) => {
root.replace(c.tree());
return;
}
}
let root_size = *self.size.borrow();
loop {
// Figure out the available size by looking at our immediate parent node.
// If we are the root, look at the provided new size
let pane_size = if let Some((branch, Some(parent))) = cursor.path_to_root().next() {
if branch == PathBranch::IsRight {
parent.second
} else {
parent.first
}
} else {
root_size
};
if cursor.is_leaf() {
// Apply our size to the tty
cursor.leaf_mut().map(|pane| pane.resize(pane_size));
} else {
self.apply_pane_size(pane_size, &mut cursor);
}
match cursor.preorder_next() {
Ok(c) => cursor = c,
Err(c) => {
root.replace(c.tree());
break;
}
}
}
}
pub fn prune_dead_panes(&self) -> bool {
let mut dead_panes = vec![];
{
let root_size = *self.size.borrow();
let mut active_idx = *self.active.borrow();
let mut root = self.pane.borrow_mut();
let mut cursor = root.take().unwrap().cursor();
@ -394,6 +503,18 @@ impl Tab {
let cell_dims = self.cell_dimensions();
loop {
// Figure out the available size by looking at our immediate parent node.
// If we are the root, look at the provided new size
let pane_size = if let Some((branch, Some(parent))) = cursor.path_to_root().next() {
if branch == PathBranch::IsRight {
parent.second
} else {
parent.first
}
} else {
root_size
};
if cursor.is_leaf() {
let pane = Rc::clone(cursor.leaf_mut().unwrap());
if pane.is_dead() {
@ -416,32 +537,26 @@ impl Tab {
// Now we need to increase the size of the current node
// and propagate the revised size to its children.
// FIXME: propagate?
let size = PtySize {
rows: parent.height(),
cols: parent.width(),
pixel_width: cell_dims.pixel_width * parent.width(),
pixel_height: cell_dims.pixel_height * parent.height(),
};
if let Some(unsplit) = cursor.leaf_mut() {
let (rows, cols) = match parent.direction {
SplitDirection::Horizontal => (
parent.first.rows,
parent.first.cols + parent.second.cols + 1,
),
SplitDirection::Vertical => (
parent.first.rows + parent.second.rows + 1,
parent.first.cols,
),
};
let size = PtySize {
rows,
cols,
pixel_width: cell_dims.pixel_width * cols,
pixel_height: cell_dims.pixel_height * rows,
};
log::error!("prune_dead_panes resize a pane to {:?}", size);
unsplit.resize(size).ok();
} else {
self.apply_pane_size(size, &mut cursor);
}
} else if !dead_panes.is_empty() {
// Apply our revised size to the tty
pane.resize(pane_size).ok();
}
pane_index += 1;
} else if !dead_panes.is_empty() {
self.apply_pane_size(pane_size, &mut cursor);
}
match cursor.preorder_next() {
Ok(c) => cursor = c,
@ -454,12 +569,17 @@ impl Tab {
*self.active.borrow_mut() = active_idx;
}
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
for pane_id in dead_panes.into_iter() {
mux.remove_pane(pane_id);
}
});
if !dead_panes.is_empty() {
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::get().unwrap();
for pane_id in dead_panes.into_iter() {
mux.remove_pane(pane_id);
}
});
true
} else {
false
}
}
pub fn is_dead(&self) -> bool {

View File

@ -126,11 +126,14 @@ impl Window {
}
pub fn prune_dead_tabs(&mut self, live_tab_ids: &[TabId]) {
let mut invalidated = false;
let dead: Vec<TabId> = self
.tabs
.iter()
.filter_map(|tab| {
tab.prune_dead_panes();
if tab.prune_dead_panes() {
invalidated = true;
}
if tab.is_dead() {
return Some(tab.tab_id());
} else {
@ -140,6 +143,7 @@ impl Window {
.collect();
for tab_id in dead {
self.remove_by_id(tab_id);
invalidated = true;
}
let dead: Vec<TabId> = self
@ -160,5 +164,9 @@ impl Window {
for tab_id in dead {
self.remove_by_id(tab_id);
}
if invalidated {
self.invalidated = true;
}
}
}