use crate::output::Output; use crate::panes::PaneId; use crate::tab::Pane; use crate::ui::boundaries::Boundaries; use crate::ui::pane_boundaries_frame::FrameParams; use crate::ClientId; use std::collections::HashMap; use zellij_utils::data::{ client_id_to_colors, single_client_color, InputMode, PaletteColor, Style, }; use zellij_utils::errors::prelude::*; pub struct PaneContentsAndUi<'a> { pane: &'a mut Box, output: &'a mut Output, style: Style, focused_clients: Vec, multiple_users_exist_in_session: bool, z_index: Option, pane_is_stacked_under: bool, pane_is_stacked_over: bool, should_draw_pane_frames: bool, } impl<'a> PaneContentsAndUi<'a> { pub fn new( pane: &'a mut Box, output: &'a mut Output, style: Style, active_panes: &HashMap, multiple_users_exist_in_session: bool, z_index: Option, pane_is_stacked_under: bool, pane_is_stacked_over: bool, should_draw_pane_frames: bool, ) -> Self { let mut focused_clients: Vec = active_panes .iter() .filter(|(_c_id, p_id)| **p_id == pane.pid()) .map(|(c_id, _p_id)| *c_id) .collect(); focused_clients.sort_unstable(); PaneContentsAndUi { pane, output, style, focused_clients, multiple_users_exist_in_session, z_index, pane_is_stacked_under, pane_is_stacked_over, should_draw_pane_frames, } } pub fn render_pane_contents_to_multiple_clients( &mut self, clients: impl Iterator, ) -> Result<()> { let err_context = "failed to render pane contents to multiple clients"; // here we drop the fake cursors so that their lines will be updated // and we can clear them from the UI below drop(self.pane.drain_fake_cursors()); if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = self.pane.render(None).context(err_context)? { let clients: Vec = clients.collect(); self.output .add_character_chunks_to_multiple_clients( character_chunks, clients.iter().copied(), self.z_index, ) .context(err_context)?; self.output.add_sixel_image_chunks_to_multiple_clients( sixel_image_chunks, clients.iter().copied(), self.z_index, ); if let Some(raw_vte_output) = raw_vte_output { if !raw_vte_output.is_empty() { self.output.add_post_vte_instruction_to_multiple_clients( clients.iter().copied(), &format!( "\u{1b}[{};{}H\u{1b}[m{}", self.pane.y() + 1, self.pane.x() + 1, raw_vte_output ), ); } } } Ok(()) } pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to render pane contents for client {client_id}"); if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = self .pane .render(Some(client_id)) .with_context(err_context)? { self.output .add_character_chunks_to_client(client_id, character_chunks, self.z_index) .with_context(err_context)?; self.output.add_sixel_image_chunks_to_client( client_id, sixel_image_chunks, self.z_index, ); if let Some(raw_vte_output) = raw_vte_output { self.output.add_post_vte_instruction_to_client( client_id, &format!( "\u{1b}[{};{}H\u{1b}[m{}", self.pane.y() + 1, self.pane.x() + 1, raw_vte_output ), ); } } Ok(()) } pub fn render_fake_cursor_if_needed(&mut self, client_id: ClientId) -> Result<()> { let pane_focused_for_client_id = self.focused_clients.contains(&client_id); let pane_focused_for_different_client = self .focused_clients .iter() .filter(|&&c_id| c_id != client_id) .count() > 0; if pane_focused_for_different_client && !pane_focused_for_client_id { let fake_cursor_client_id = self .focused_clients .iter() .find(|&&c_id| c_id != client_id) .with_context(|| { format!("failed to render fake cursor if needed for client {client_id}") })?; if let Some(colors) = client_id_to_colors(*fake_cursor_client_id, self.style.colors) { let cursor_is_visible = self .pane .cursor_coordinates() .map(|(x, y)| { self.output .cursor_is_visible(self.pane.x() + x, self.pane.y() + y) }) .unwrap_or(false); if cursor_is_visible { if let Some(vte_output) = self.pane.render_fake_cursor(colors.0, colors.1) { self.output.add_post_vte_instruction_to_client( client_id, &format!( "\u{1b}[{};{}H\u{1b}[m{}", self.pane.y() + 1, self.pane.x() + 1, vte_output ), ); } } } } Ok(()) } pub fn render_terminal_title_if_needed( &mut self, client_id: ClientId, client_mode: InputMode, previous_title: &mut Option, ) { if !self.focused_clients.contains(&client_id) { return; } let vte_output = self.pane.render_terminal_title(client_mode); if let Some(previous_title) = previous_title { if *previous_title == vte_output { return; } } *previous_title = Some(vte_output.clone()); self.output .add_post_vte_instruction_to_client(client_id, &vte_output); } pub fn render_pane_frame( &mut self, client_id: ClientId, client_mode: InputMode, session_is_mirrored: bool, pane_is_floating: bool, ) -> Result<()> { let err_context = || format!("failed to render pane frame for client {client_id}"); let pane_focused_for_client_id = self.focused_clients.contains(&client_id); let other_focused_clients: Vec = self .focused_clients .iter() .filter(|&&c_id| c_id != client_id) .copied() .collect(); let pane_focused_for_differet_client = !other_focused_clients.is_empty(); let frame_color = self.frame_color(client_id, client_mode, session_is_mirrored); let focused_client = if pane_focused_for_client_id { Some(client_id) } else if pane_focused_for_differet_client { Some(*other_focused_clients.first().with_context(err_context)?) } else { None }; let frame_params = if session_is_mirrored { FrameParams { focused_client, is_main_client: pane_focused_for_client_id, other_focused_clients: vec![], style: self.style, color: frame_color, other_cursors_exist_in_session: false, pane_is_stacked_over: self.pane_is_stacked_over, pane_is_stacked_under: self.pane_is_stacked_under, should_draw_pane_frames: self.should_draw_pane_frames, pane_is_floating, } } else { FrameParams { focused_client, is_main_client: pane_focused_for_client_id, other_focused_clients, style: self.style, color: frame_color, other_cursors_exist_in_session: self.multiple_users_exist_in_session, pane_is_stacked_over: self.pane_is_stacked_over, pane_is_stacked_under: self.pane_is_stacked_under, should_draw_pane_frames: self.should_draw_pane_frames, pane_is_floating, } }; if let Some((frame_terminal_characters, vte_output)) = self .pane .render_frame(client_id, frame_params, client_mode) .with_context(err_context)? { self.output .add_character_chunks_to_client(client_id, frame_terminal_characters, self.z_index) .with_context(err_context)?; if let Some(vte_output) = vte_output { self.output .add_post_vte_instruction_to_client(client_id, &vte_output); } } Ok(()) } pub fn render_pane_boundaries( &self, client_id: ClientId, client_mode: InputMode, boundaries: &mut Boundaries, session_is_mirrored: bool, ) { let color = self.frame_color(client_id, client_mode, session_is_mirrored); boundaries.add_rect(self.pane.as_ref(), color); } fn frame_color( &self, client_id: ClientId, mode: InputMode, session_is_mirrored: bool, ) -> Option { let pane_focused_for_client_id = self.focused_clients.contains(&client_id); if let Some(override_color) = self.pane.frame_color_override() { Some(override_color) } else if pane_focused_for_client_id { match mode { InputMode::Normal | InputMode::Locked => { if session_is_mirrored || !self.multiple_users_exist_in_session { let colors = single_client_color(self.style.colors); // mirrored sessions only have one focused color Some(colors.0) } else { let colors = client_id_to_colors(client_id, self.style.colors); colors.map(|colors| colors.0) } }, _ => Some(self.style.colors.orange), } } else { None } } }