pub mod floating_pane_grid; use zellij_utils::{ data::{Direction, FloatingPaneCoordinates, PaneInfo, ResizeStrategy}, position::Position, }; use crate::resize_pty; use crate::tab::{pane_info_for_pane, Pane}; use floating_pane_grid::FloatingPaneGrid; use crate::{ os_input_output::ServerOsApi, output::{FloatingPanesStack, Output}, panes::{ActivePanes, PaneId}, plugins::PluginInstruction, thread_bus::ThreadSenders, ui::pane_contents_and_ui::PaneContentsAndUi, ClientId, }; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; use std::rc::Rc; use std::time::Instant; use zellij_utils::{ data::{ModeInfo, Style, Styling}, errors::prelude::*, input::command::RunCommand, input::layout::{FloatingPaneLayout, Run, RunPluginOrAlias}, pane_size::{Dimension, Offset, PaneGeom, Size, SizeInPixels, Viewport}, }; const RESIZE_INCREMENT_WIDTH: usize = 5; const RESIZE_INCREMENT_HEIGHT: usize = 2; pub struct FloatingPanes { panes: BTreeMap>, display_area: Rc>, viewport: Rc>, connected_clients: Rc>>, connected_clients_in_app: Rc>>, mode_info: Rc>>, character_cell_size: Rc>>, default_mode_info: ModeInfo, style: Style, session_is_mirrored: bool, desired_pane_positions: HashMap, // this represents the positions of panes the user moved with intention, rather than by resizing the terminal window z_indices: Vec, active_panes: ActivePanes, show_panes: bool, pane_being_moved_with_mouse: Option<(PaneId, Position)>, senders: ThreadSenders, } #[allow(clippy::borrowed_box)] #[allow(clippy::too_many_arguments)] impl FloatingPanes { pub fn new( display_area: Rc>, viewport: Rc>, connected_clients: Rc>>, connected_clients_in_app: Rc>>, mode_info: Rc>>, character_cell_size: Rc>>, session_is_mirrored: bool, default_mode_info: ModeInfo, style: Style, os_input: Box, senders: ThreadSenders, ) -> Self { FloatingPanes { panes: BTreeMap::new(), display_area, viewport, connected_clients, connected_clients_in_app, mode_info, character_cell_size, session_is_mirrored, default_mode_info, style, desired_pane_positions: HashMap::new(), z_indices: vec![], show_panes: false, active_panes: ActivePanes::new(&os_input), pane_being_moved_with_mouse: None, senders, } } pub fn stack(&self) -> Option { if self.panes_are_visible() { let layers: Vec = self .z_indices .iter() .filter_map(|pane_id| self.panes.get(pane_id).map(|p| p.position_and_size())) .collect(); if layers.is_empty() { None } else { Some(FloatingPanesStack { layers }) } } else if self.has_pinned_panes() { let layers = self .z_indices .iter() .filter_map(|pane_id| { self.panes .get(pane_id) .map(|p| p.position_and_size()) .and_then(|p| if p.is_pinned { Some(p) } else { None }) }) .collect(); Some(FloatingPanesStack { layers }) } else { None } } pub fn has_pinned_panes(&self) -> bool { self.panes .iter() .any(|(_, p)| p.position_and_size().is_pinned) } pub fn pane_ids(&self) -> impl Iterator { self.panes.keys() } pub fn add_pane(&mut self, pane_id: PaneId, pane: Box) { self.desired_pane_positions .insert(pane_id, pane.position_and_size()); self.panes.insert(pane_id, pane); self.z_indices.push(pane_id); } pub fn replace_active_pane( &mut self, pane: Box, client_id: ClientId, ) -> Result> { self.active_panes .get(&client_id) .with_context(|| format!("failed to determine active pane for client {client_id}")) .copied() .and_then(|active_pane_id| self.replace_pane(active_pane_id, pane)) .with_context(|| format!("failed to replace active pane for client {client_id}")) } pub fn replace_pane( &mut self, pane_id: PaneId, mut with_pane: Box, ) -> Result> { let err_context = || format!("failed to replace pane {pane_id:?} with pane"); let with_pane_id = with_pane.pid(); with_pane.set_content_offset(Offset::frame(1)); let removed_pane = self .panes .remove(&pane_id) .with_context(|| format!("failed to remove unknown pane with ID {pane_id:?}")) .and_then(|removed_pane| { let removed_pane_id = removed_pane.pid(); let with_pane_id = with_pane.pid(); let removed_pane_geom = removed_pane.current_geom(); with_pane.set_geom(removed_pane_geom); self.panes.insert(with_pane_id, with_pane); let z_index = self .z_indices .iter() .position(|pane_id| pane_id == &removed_pane_id) .context("no z-index found for pane to be removed with ID {removed_pane_id:?}") .with_context(err_context)?; self.z_indices.remove(z_index); self.z_indices.insert(z_index, with_pane_id); Ok(removed_pane) }); // update the desired_pane_positions to relate to the new pane if let Some(desired_pane_position) = self.desired_pane_positions.remove(&pane_id) { self.desired_pane_positions .insert(with_pane_id, desired_pane_position); } // move clients from the previously active pane to the new pane we just inserted self.move_clients_between_panes(pane_id, with_pane_id); let _ = self.set_pane_frames(); removed_pane } pub fn remove_pane(&mut self, pane_id: PaneId) -> Option> { self.z_indices.retain(|p_id| *p_id != pane_id); self.desired_pane_positions.remove(&pane_id); self.panes.remove(&pane_id) } pub fn hold_pane( &mut self, pane_id: PaneId, exit_status: Option, is_first_run: bool, run_command: RunCommand, ) { self.panes .get_mut(&pane_id) .map(|p| p.hold(exit_status, is_first_run, run_command)); } pub fn get(&self, pane_id: &PaneId) -> Option<&Box> { self.panes.get(pane_id) } pub fn get_mut(&mut self, pane_id: &PaneId) -> Option<&mut Box> { self.panes.get_mut(pane_id) } pub fn get_active_pane(&self, client_id: ClientId) -> Option<&Box> { self.active_panes .get(&client_id) .and_then(|active_pane_id| self.panes.get(active_pane_id)) } pub fn get_active_pane_mut(&mut self, client_id: ClientId) -> Option<&mut Box> { self.active_panes .get(&client_id) .and_then(|active_pane_id| self.panes.get_mut(active_pane_id)) } pub fn panes_are_visible(&self) -> bool { self.show_panes } pub fn has_active_panes(&self) -> bool { !self.active_panes.is_empty() } pub fn has_panes(&self) -> bool { !self.panes.is_empty() } pub fn active_pane_id(&self, client_id: ClientId) -> Option { self.active_panes.get(&client_id).copied() } pub fn active_pane_id_or_focused_pane_id(&self, client_id: Option) -> Option { // returns the focused pane of any client_id - should be safe because the way things are // set up at the time of writing, all clients are focused on the same floating pane due to // z_index issues client_id .and_then(|client_id| self.active_panes.get(&client_id).copied()) .or_else(|| self.panes.keys().next().copied()) } pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) { self.show_panes = should_show_floating_panes; if should_show_floating_panes { self.active_panes.focus_all_panes(&mut self.panes); } else { self.active_panes.unfocus_all_panes(&mut self.panes); } } pub fn active_panes_contain(&self, client_id: &ClientId) -> bool { self.active_panes.contains_key(client_id) } pub fn panes_contain(&self, pane_id: &PaneId) -> bool { self.panes.contains_key(pane_id) } pub fn find_room_for_new_pane(&mut self) -> Option { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid.find_room_for_new_pane() } pub fn position_floating_pane_layout( &mut self, floating_pane_layout: &FloatingPaneLayout, ) -> Result { let err_context = || format!("failed to find position for floating pane"); let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); let mut position = floating_pane_grid .find_room_for_new_pane() .with_context(err_context)?; if let Some(x) = &floating_pane_layout.x { position.x = x.to_position(viewport.cols); } if let Some(y) = &floating_pane_layout.y { position.y = y.to_position(viewport.rows); } if let Some(width) = &floating_pane_layout.width { position.cols = Dimension::fixed(width.to_position(viewport.cols)); } if let Some(height) = &floating_pane_layout.height { position.rows = Dimension::fixed(height.to_position(viewport.rows)); } if let Some(is_pinned) = &floating_pane_layout.pinned { position.is_pinned = *is_pinned; } if let Some(logical_position) = &floating_pane_layout.logical_position { position.logical_position = Some(*logical_position); } if position.cols.as_usize() > viewport.cols { position.cols = Dimension::fixed(viewport.cols); } if position.rows.as_usize() > viewport.rows { position.rows = Dimension::fixed(viewport.rows); } if position.x + position.cols.as_usize() > viewport.cols { position.x = position .x .saturating_sub((position.x + position.cols.as_usize()) - viewport.cols); } if position.y + position.rows.as_usize() > viewport.rows { position.y = position .y .saturating_sub((position.y + position.rows.as_usize()) - viewport.rows); } Ok(position) } pub fn first_floating_pane_id(&self) -> Option { self.panes.keys().next().copied() } pub fn last_floating_pane_id(&self) -> Option { self.panes.keys().last().copied() } pub fn first_active_floating_pane_id(&self) -> Option { self.active_panes.values().next().copied() } pub fn set_force_render(&mut self) { for pane in self.panes.values_mut() { pane.set_should_render(true); pane.set_should_render_boundaries(true); pane.render_full_viewport(); } } pub fn set_pane_frames(&mut self) -> Result<()> { let err_context = |pane_id: &PaneId| format!("failed to activate frame on pane {pane_id:?}"); for pane in self.panes.values_mut() { // floating panes should always have a frame unless explicitly set otherwise if !pane.borderless() { pane.set_frame(true); pane.set_content_offset(Offset::frame(1)); } else { pane.set_content_offset(Offset::default()); } resize_pty!(pane, os_api, self.senders, self.character_cell_size) .with_context(|| err_context(&pane.pid()))?; } Ok(()) } pub fn render(&mut self, output: &mut Output) -> Result<()> { let err_context = || "failed to render output"; let connected_clients: Vec = { self.connected_clients.borrow().iter().copied().collect() }; let active_panes = if self.panes_are_visible() { self.active_panes.clone_active_panes() } else { Default::default() }; let mut floating_panes: Vec<_> = if self.panes_are_visible() { self.panes.iter_mut().collect() } else if self.has_pinned_panes() { self.panes .iter_mut() .filter(|(_, p)| p.position_and_size().is_pinned) .collect() } else { vec![] }; floating_panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| { self.z_indices .iter() .position(|id| id == *a_id) .with_context(err_context) .fatal() .cmp( &self .z_indices .iter() .position(|id| id == *b_id) .with_context(err_context) .fatal(), ) }); for (z_index, (kind, pane)) in floating_panes.iter_mut().enumerate() { let mut active_panes = active_panes.clone(); let multiple_users_exist_in_session = { self.connected_clients_in_app.borrow().len() > 1 }; active_panes.retain(|c_id, _| self.connected_clients.borrow().contains(c_id)); let mut pane_contents_and_ui = PaneContentsAndUi::new( pane, output, self.style, &active_panes, multiple_users_exist_in_session, Some(z_index + 1), // +1 because 0 is reserved for non-floating panes false, false, true, ); for client_id in &connected_clients { let client_mode = self .mode_info .borrow() .get(client_id) .unwrap_or(&self.default_mode_info) .mode; let is_floating = true; pane_contents_and_ui .render_pane_frame( *client_id, client_mode, self.session_is_mirrored, is_floating, ) .with_context(err_context)?; if let PaneId::Plugin(..) = kind { pane_contents_and_ui .render_pane_contents_for_client(*client_id) .with_context(err_context)?; } // this is done for panes that don't have their own cursor (eg. panes of // another user) pane_contents_and_ui .render_fake_cursor_if_needed(*client_id) .with_context(err_context)?; } if let PaneId::Terminal(..) = kind { pane_contents_and_ui .render_pane_contents_to_multiple_clients(connected_clients.iter().copied()) .with_context(err_context)?; } } Ok(()) } pub fn resize(&mut self, new_screen_size: Size) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let mut floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid.resize(new_screen_size).non_fatal(); self.set_force_render(); } pub fn resize_pty_all_panes(&mut self, _os_api: &mut Box) -> Result<()> { for pane in self.panes.values_mut() { resize_pty!(pane, os_api, self.senders, self.character_cell_size) .with_context(|| format!("failed to resize PTY in pane {:?}", pane.pid()))?; } Ok(()) } pub fn resize_active_pane( &mut self, client_id: ClientId, _os_api: &mut Box, strategy: &ResizeStrategy, ) -> Result { // true => successfully resized if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) { return self.resize_pane_with_id(*strategy, *active_floating_pane_id); } Ok(false) } pub fn resize_pane_with_id( &mut self, strategy: ResizeStrategy, pane_id: PaneId, ) -> Result { // true => successfully resized let err_context = || format!("Failed to resize pane with id: {:?}", pane_id); let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let mut floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid .change_pane_size( &pane_id, &strategy, (RESIZE_INCREMENT_WIDTH, RESIZE_INCREMENT_HEIGHT), ) .with_context(err_context)?; for pane in self.panes.values_mut() { resize_pty!(pane, os_api, self.senders, self.character_cell_size) .with_context(err_context)?; } self.set_force_render(); Ok(true) } fn set_pane_active_at(&mut self, pane_id: PaneId) { if let Some(pane) = self.panes.get_mut(&pane_id) { pane.set_active_at(Instant::now()); } } pub fn move_focus( &mut self, client_id: ClientId, connected_clients: &HashSet, direction: &Direction, ) -> Result { // true => successfully moved let _err_context = || { format!("failed to move focus of floating pane {direction:?} for client {client_id}") }; let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let active_pane_id = self.active_panes.get(&client_id).copied(); let updated_active_pane = if let Some(active_pane_id) = active_pane_id { let floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); let next_index = match direction { Direction::Left => { floating_pane_grid.next_selectable_pane_id_to_the_left(&active_pane_id) }, Direction::Down => { floating_pane_grid.next_selectable_pane_id_below(&active_pane_id) }, Direction::Up => floating_pane_grid.next_selectable_pane_id_above(&active_pane_id), Direction::Right => { floating_pane_grid.next_selectable_pane_id_to_the_right(&active_pane_id) }, }; match next_index { Some(p) => { // render previously active pane so that its frame does not remain actively // colored let Some(previously_active_pane) = self.get_active_pane_mut(client_id) else { log::error!("Failed to get active pane"); return Ok(false); }; previously_active_pane.set_should_render(true); // we render the full viewport to remove any ui elements that might have been // there before (eg. another user's cursor) previously_active_pane.render_full_viewport(); let Some(next_active_pane) = self.get_pane_mut(p) else { log::error!("Failed to get next active pane"); return Ok(false); }; next_active_pane.set_should_render(true); // we render the full viewport to remove any ui elements that might have been // there before (eg. another user's cursor) next_active_pane.render_full_viewport(); // move all clients let connected_clients: Vec = connected_clients.iter().copied().collect(); for client_id in connected_clients { self.focus_pane(p, client_id); } self.set_pane_active_at(p); self.set_force_render(); return Ok(true); }, None => Some(active_pane_id), } } else { active_pane_id }; match updated_active_pane { Some(updated_active_pane) => { let connected_clients: Vec = connected_clients.iter().copied().collect(); for client_id in connected_clients { self.focus_pane(updated_active_pane, client_id); } self.set_pane_active_at(updated_active_pane); self.set_force_render(); }, None => { // TODO: can this happen? self.active_panes.clear(&mut self.panes); self.z_indices.clear(); }, } Ok(false) } pub fn focus_pane_on_edge(&mut self, direction: Direction, client_id: ClientId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); if let Some(pane_id) = floating_pane_grid.pane_id_on_edge(direction) { self.focus_pane(pane_id, client_id); self.set_force_render(); } } pub fn move_active_pane_down(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.active_panes.get(&client_id) { self.move_pane_down(*active_pane_id); } } pub fn move_pane_down(&mut self, pane_id: PaneId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let mut floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid.move_pane_down(&pane_id).non_fatal(); self.set_force_render(); } pub fn move_active_pane_up(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.active_panes.get(&client_id) { self.move_pane_up(*active_pane_id); } } pub fn move_pane_up(&mut self, pane_id: PaneId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let mut floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid.move_pane_up(&pane_id).non_fatal(); self.set_force_render(); } pub fn move_active_pane_left(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.active_panes.get(&client_id) { self.move_pane_left(*active_pane_id); } } pub fn move_pane_left(&mut self, pane_id: PaneId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let mut floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid.move_pane_left(&pane_id).non_fatal(); self.set_force_render(); } pub fn move_active_pane_right(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.active_panes.get(&client_id) { self.move_pane_right(*active_pane_id); } } pub fn move_pane_right(&mut self, pane_id: PaneId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let mut floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid.move_pane_right(&pane_id).non_fatal(); self.set_force_render(); } pub fn move_active_pane( &mut self, search_backwards: bool, _os_api: &mut Box, client_id: ClientId, ) { if let Some(active_pane_id) = self.get_active_pane_id(client_id) { self.move_pane(search_backwards, active_pane_id) } } pub fn move_pane(&mut self, search_backwards: bool, pane_id: PaneId) { let new_position_id = { let pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, *self.display_area.borrow(), *self.viewport.borrow(), ); if search_backwards { pane_grid.previous_selectable_pane_id(&pane_id) } else { pane_grid.next_selectable_pane_id(&pane_id) } }; if let Some(new_position_id) = new_position_id { let Some(current_position) = self.panes.get(&pane_id) else { log::error!("Failed to find current position"); return; }; let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); let Some(new_position) = self.panes.get_mut(&new_position_id) else { log::error!("Failed to find new position"); return; }; let next_geom = new_position.position_and_size(); let next_geom_override = new_position.geom_override(); new_position.set_geom(prev_geom); if let Some(geom) = prev_geom_override { new_position.set_geom_override(geom); } new_position.set_should_render(true); let Some(current_position) = self.panes.get_mut(&pane_id) else { log::error!("Failed to find current position"); return; }; current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.set_geom_override(geom); } current_position.set_should_render(true); let _ = self.set_pane_frames(); } } pub fn change_pane_coordinates( &mut self, pane_id: PaneId, new_coordinates: FloatingPaneCoordinates, ) -> Result<()> { let err_context = || format!("Failed to change_pane_coordinates"); { let viewport = { self.viewport.borrow().clone() }; let pane = self.get_pane_mut(pane_id).with_context(err_context)?; let mut pane_geom = pane.position_and_size(); if let Some(pinned) = new_coordinates.pinned.as_ref() { pane.set_pinned(*pinned); } pane_geom.adjust_coordinates(new_coordinates, viewport); pane.set_geom(pane_geom); pane.set_should_render(true); self.desired_pane_positions.insert(pane_id, pane_geom); } let _ = self.set_pane_frames(); Ok(()) } pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) { let active_panes: Vec<(ClientId, PaneId)> = self .active_panes .iter() .map(|(cid, pid)| (*cid, *pid)) .collect(); // find the most recently active pane let mut next_active_pane_candidates: Vec<(&PaneId, &Box)> = self .panes .iter() .filter(|(_p_id, p)| p.selectable()) .collect(); next_active_pane_candidates.sort_by(|(_pane_id_a, pane_a), (_pane_id_b, pane_b)| { pane_a.active_at().cmp(&pane_b.active_at()) }); let next_active_pane_id = next_active_pane_candidates .last() .map(|(pane_id, _pane)| **pane_id); for (client_id, active_pane_id) in active_panes { if active_pane_id == pane_id { match next_active_pane_id { Some(next_active_pane_id) => { self.active_panes .insert(client_id, next_active_pane_id, &mut self.panes); self.focus_pane(next_active_pane_id, client_id); }, None => { self.defocus_pane(pane_id, client_id); }, } } } } pub fn focus_pane_for_all_clients(&mut self, pane_id: PaneId) { let connected_clients: Vec = self.connected_clients.borrow().iter().copied().collect(); for client_id in connected_clients { self.active_panes .insert(client_id, pane_id, &mut self.panes); } self.z_indices.retain(|p_id| *p_id != pane_id); self.z_indices.push(pane_id); self.set_pane_active_at(pane_id); self.set_force_render(); } pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) { let pane_is_selectable = self .panes .get(&pane_id) .map(|p| p.selectable()) .unwrap_or(false); if !pane_is_selectable { log::error!("Cannot focus pane {:?} as it is not selectable!", pane_id); return; } self.active_panes .insert(client_id, pane_id, &mut self.panes); self.focus_pane_for_all_clients(pane_id); } pub fn focus_pane_if_client_not_focused(&mut self, pane_id: PaneId, client_id: ClientId) { match self.active_panes.get(&client_id) { Some(already_focused_pane_id) => self.focus_pane(*already_focused_pane_id, client_id), None => self.focus_pane(pane_id, client_id), } } pub fn focus_first_pane_if_client_not_focused(&mut self, client_id: ClientId) { match self.active_panes.get(&client_id) { Some(already_focused_pane_id) => self.focus_pane(*already_focused_pane_id, client_id), None => { if let Some(first_pane_id) = self.panes.iter().next().map(|(p_id, _)| *p_id) { self.focus_pane(first_pane_id, client_id); } }, } } pub fn defocus_pane(&mut self, pane_id: PaneId, client_id: ClientId) { self.z_indices.retain(|p_id| *p_id != pane_id); self.active_panes.remove(&client_id, &mut self.panes); self.set_force_render(); } pub fn get_pane(&self, pane_id: PaneId) -> Option<&Box> { self.panes.get(&pane_id) } pub fn get_pane_mut(&mut self, pane_id: PaneId) -> Option<&mut Box> { self.panes.get_mut(&pane_id) } pub fn get_pane_id_at( &self, point: &Position, search_selectable: bool, ) -> Result> { let _err_context = || format!("failed to determine floating pane at point {point:?}"); // TODO: better - loop through z-indices and check each one if it contains the point let mut panes: Vec<_> = if search_selectable { self.panes.iter().filter(|(_, p)| p.selectable()).collect() } else { self.panes.iter().collect() }; panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| { // TODO: continue Ord::cmp( &self .z_indices .iter() .position(|id| id == *b_id) .unwrap_or(0), &self .z_indices .iter() .position(|id| id == *a_id) .unwrap_or(0), ) }); Ok(panes .iter() .find(|(_, p)| p.contains(point)) .map(|(&id, _)| id)) } pub fn get_pinned_pane_id_at( &self, point: &Position, search_selectable: bool, ) -> Result> { let _err_context = || format!("failed to determine floating pane at point {point:?}"); let mut panes: Vec<_> = if search_selectable { self.panes .iter() .filter(|(_, p)| p.selectable()) .filter(|(_, p)| p.current_geom().is_pinned) .collect() } else { self.panes .iter() .filter(|(_, p)| p.current_geom().is_pinned) .collect() }; panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| { // TODO: continue Ord::cmp( &self .z_indices .iter() .position(|id| id == *b_id) .unwrap_or(0), &self .z_indices .iter() .position(|id| id == *a_id) .unwrap_or(0), ) }); Ok(panes .iter() .find(|(_, p)| p.contains(point)) .map(|(&id, _)| id)) } pub fn has_pinned_pane_at(&self, point: &Position) -> bool { let mut panes: Vec<_> = self .panes .iter() .filter(|(_, p)| p.current_geom().is_pinned) .collect(); panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| { Ord::cmp( &self .z_indices .iter() .position(|id| id == *b_id) .unwrap_or(0), &self .z_indices .iter() .position(|id| id == *a_id) .unwrap_or(0), ) }); panes.iter().find(|(_, p)| p.contains(point)).is_some() } pub fn get_pane_at_mut( &mut self, position: &Position, search_selectable: bool, ) -> Option<&mut Box> { self.get_pane_id_at(position, search_selectable) .ok()? .and_then(|pane_id| self.panes.get_mut(&pane_id)) } pub fn set_pane_being_moved_with_mouse(&mut self, pane_id: PaneId, position: Position) { self.pane_being_moved_with_mouse = Some((pane_id, position)); } pub fn pane_is_being_moved_with_mouse(&self) -> bool { self.pane_being_moved_with_mouse.is_some() } pub fn move_pane_to_position(&mut self, click_position: &Position) -> bool { // true => changed position let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); let Some((pane_id, previous_position)) = self.pane_being_moved_with_mouse else { log::error!("Pane is not being moved with mousd"); return false; }; if click_position == &previous_position { return false; } let move_x_by = click_position.column() as isize - previous_position.column() as isize; let move_y_by = click_position.line() as isize - previous_position.line() as isize; let mut floating_pane_grid = FloatingPaneGrid::new( &mut self.panes, &mut self.desired_pane_positions, display_area, viewport, ); floating_pane_grid .move_pane_by(pane_id, move_x_by, move_y_by) .non_fatal(); self.set_pane_being_moved_with_mouse(pane_id, *click_position); self.set_force_render(); true } pub fn move_pane_with_mouse(&mut self, position: Position, search_selectable: bool) -> bool { // true => handled, false => not handled (eg. no pane at this position) let show_panes = self.show_panes; if self.pane_being_moved_with_mouse.is_some() { if self.move_pane_to_position(&position) { // pane was moved to a new position self.set_force_render(); return true; } } else if let Some(pane) = self.get_pane_at_mut(&position, search_selectable) { let clicked_on_frame = pane.position_is_on_frame(&position); if show_panes && clicked_on_frame { let pid = pane.pid(); if self.pane_being_moved_with_mouse.is_none() { self.set_pane_being_moved_with_mouse(pid, position); } self.move_pane_to_position(&position); self.set_force_render(); return true; } }; false } pub fn stop_moving_pane_with_mouse(&mut self, position: Position) { if self.pane_being_moved_with_mouse.is_some() { self.move_pane_to_position(&position); self.set_force_render(); }; self.pane_being_moved_with_mouse = None; } pub fn get_active_pane_id(&self, client_id: ClientId) -> Option { self.active_panes.get(&client_id).copied() } pub fn get_panes(&self) -> impl Iterator)> { self.panes.iter() } pub fn visible_panes_count(&self) -> usize { self.panes.len() } pub fn drain(&mut self) -> BTreeMap> { self.z_indices.clear(); self.desired_pane_positions.clear(); match self.panes.iter().next().map(|(pid, _p)| *pid) { Some(first_pid) => self.panes.split_off(&first_pid), None => BTreeMap::new(), } } fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) { let clients_in_pane: Vec = self .active_panes .iter() .filter(|(_cid, pid)| **pid == from_pane_id) .map(|(cid, _pid)| *cid) .collect(); for client_id in clients_in_pane { self.active_panes.remove(&client_id, &mut self.panes); self.active_panes .insert(client_id, to_pane_id, &mut self.panes); } } pub fn reapply_pane_focus(&mut self) { if let Some(focused_pane) = self.first_active_floating_pane_id() { // floating pane focus is the same for all clients self.focus_pane_for_all_clients(focused_pane); } } pub fn switch_active_pane_with(&mut self, _os_api: &mut Box, pane_id: PaneId) { if let Some(active_pane_id) = self.first_active_floating_pane_id() { let Some(current_position) = self.panes.get(&active_pane_id) else { log::error!("Can't find current position"); return; }; let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); let Some(new_position) = self.panes.get_mut(&pane_id) else { log::error!("Can't find position"); return; }; let next_geom = new_position.position_and_size(); let next_geom_override = new_position.geom_override(); new_position.set_geom(prev_geom); if let Some(geom) = prev_geom_override { new_position.set_geom_override(geom); } resize_pty!(new_position, os_api, self.senders, self.character_cell_size).non_fatal(); new_position.set_should_render(true); let Some(current_position) = self.panes.get_mut(&active_pane_id) else { log::error!("Can't find current position"); return; }; current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.set_geom_override(geom); } resize_pty!( current_position, os_api, self.senders, self.character_cell_size ) .non_fatal(); current_position.set_should_render(true); self.focus_pane_for_all_clients(active_pane_id); } } pub fn get_plugin_pane_id(&self, run_plugin_or_alias: &RunPluginOrAlias) -> Option { self.panes .iter() .find(|(_id, pane)| run_plugin_or_alias.is_equivalent_to_run(pane.invoked_with())) .map(|(id, _)| *id) } pub fn focus_pane_if_exists(&mut self, pane_id: PaneId, client_id: ClientId) -> Result<()> { if self.panes.get(&pane_id).is_some() { self.focus_pane(pane_id, client_id); Ok(()) } else { Err(anyhow!("Pane not found")) } } pub fn pane_info(&self) -> Vec { let mut pane_infos = vec![]; for (pane_id, pane) in self.panes.iter() { let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane); let is_focused = self.active_panes.pane_id_is_focused(pane_id); pane_info_for_pane.is_floating = true; pane_info_for_pane.is_suppressed = false; pane_info_for_pane.is_focused = is_focused; pane_info_for_pane.is_fullscreen = false; pane_infos.push(pane_info_for_pane); } pane_infos } pub fn set_geom_for_pane_with_run(&mut self, run: Option, geom: PaneGeom) { match self .panes .iter_mut() .find(|(_, p)| p.invoked_with() == &run) { Some((_, pane)) => { pane.set_geom(geom); }, None => { log::error!("Failed to find pane with run: {:?}", run); }, } } pub fn update_pane_themes(&mut self, theme: Styling) { self.style.colors = theme; for pane in self.panes.values_mut() { pane.update_theme(theme); } } pub fn update_pane_arrow_fonts(&mut self, should_support_arrow_fonts: bool) { for pane in self.panes.values_mut() { pane.update_arrow_fonts(should_support_arrow_fonts); } } pub fn update_pane_rounded_corners(&mut self, rounded_corners: bool) { self.style.rounded_corners = rounded_corners; for pane in self.panes.values_mut() { pane.update_rounded_corners(rounded_corners); } } }