summaryrefslogtreecommitdiffstats
path: root/zellij-server/src/ui
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2021-11-25 16:21:59 +0100
committerGitHub <noreply@github.com>2021-11-25 16:21:59 +0100
commit6c6a4393f403359838ea9b32b09520de4f50c019 (patch)
treed1b553cab1a22c271be99bbc35a5e608bc96676d /zellij-server/src/ui
parent9fb2c7ca168679905d79dcdaf686d40f4a6dc958 (diff)
This adds a UI for multiple users in panes (behind a feature flag) (#897)
* feat(ui): multiple users in panes * style(fmt): make rustfmt happy * style(fmt): make clippy happy
Diffstat (limited to 'zellij-server/src/ui')
-rw-r--r--zellij-server/src/ui/boundaries.rs11
-rw-r--r--zellij-server/src/ui/mod.rs1
-rw-r--r--zellij-server/src/ui/pane_boundaries_frame.rs491
-rw-r--r--zellij-server/src/ui/pane_contents_and_ui.rs165
4 files changed, 610 insertions, 58 deletions
diff --git a/zellij-server/src/ui/boundaries.rs b/zellij-server/src/ui/boundaries.rs
index a912664ca..317c2e5dc 100644
--- a/zellij-server/src/ui/boundaries.rs
+++ b/zellij-server/src/ui/boundaries.rs
@@ -3,7 +3,7 @@ use zellij_utils::{pane_size::Viewport, zellij_tile};
use crate::tab::Pane;
use ansi_term::Colour::{Fixed, RGB};
use std::collections::HashMap;
-use zellij_tile::data::{InputMode, Palette, PaletteColor};
+use zellij_tile::data::PaletteColor;
use zellij_utils::shared::colors;
use std::fmt::{Display, Error, Formatter};
@@ -413,17 +413,10 @@ impl Boundaries {
boundary_characters: HashMap::new(),
}
}
- pub fn add_rect(&mut self, rect: &dyn Pane, input_mode: InputMode, palette: Option<Palette>) {
+ pub fn add_rect(&mut self, rect: &dyn Pane, color: Option<PaletteColor>) {
if !self.is_fully_inside_screen(rect) {
return;
}
- let color = match palette.is_some() {
- true => match input_mode {
- InputMode::Normal | InputMode::Locked => Some(palette.unwrap().green),
- _ => Some(palette.unwrap().orange),
- },
- false => None,
- };
if rect.x() > self.viewport.x {
// left boundary
let boundary_x_coords = rect.x() - 1;
diff --git a/zellij-server/src/ui/mod.rs b/zellij-server/src/ui/mod.rs
index 08c381836..1bf630712 100644
--- a/zellij-server/src/ui/mod.rs
+++ b/zellij-server/src/ui/mod.rs
@@ -1,4 +1,5 @@
pub mod boundaries;
pub mod overlay;
pub mod pane_boundaries_frame;
+pub mod pane_contents_and_ui;
pub mod pane_resizer;
diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs
index a283a6b3b..5ab381582 100644
--- a/zellij-server/src/ui/pane_boundaries_frame.rs
+++ b/zellij-server/src/ui/pane_boundaries_frame.rs
@@ -1,8 +1,9 @@
use crate::ui::boundaries::boundary_type;
+use crate::ClientId;
use ansi_term::Colour::{Fixed, RGB};
use ansi_term::Style;
use zellij_utils::pane_size::Viewport;
-use zellij_utils::zellij_tile::prelude::PaletteColor;
+use zellij_utils::zellij_tile::prelude::{Palette, PaletteColor};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
@@ -20,27 +21,104 @@ fn color_string(character: &str, color: Option<PaletteColor>) -> String {
}
}
+fn background_color(character: &str, color: Option<PaletteColor>) -> String {
+ match color {
+ Some(PaletteColor::Rgb((r, g, b))) => {
+ format!("{}", Style::new().on(RGB(r, g, b)).paint(character))
+ }
+ Some(PaletteColor::EightBit(color)) => {
+ format!("{}", Style::new().on(Fixed(color)).paint(character))
+ }
+ None => String::from(character),
+ }
+}
+
+// TODO: move elsewhere
+pub(crate) fn client_id_to_colors(
+ client_id: ClientId,
+ colors: Palette,
+) -> Option<(PaletteColor, PaletteColor)> {
+ // (primary color, secondary color)
+ match client_id {
+ 1 => Some((colors.green, colors.black)),
+ 2 => Some((colors.blue, colors.black)),
+ 3 => Some((colors.cyan, colors.black)),
+ 4 => Some((colors.magenta, colors.black)),
+ 5 => Some((colors.yellow, colors.black)),
+ _ => None,
+ }
+}
+
+pub struct FrameParams {
+ pub focused_client: Option<ClientId>,
+ pub is_main_client: bool,
+ pub other_focused_clients: Vec<ClientId>,
+ pub colors: Palette,
+ pub color: Option<PaletteColor>,
+ pub other_cursors_exist_in_session: bool,
+}
+
#[derive(Default, PartialEq)]
pub struct PaneFrame {
pub geom: Viewport,
pub title: String,
pub scroll_position: (usize, usize), // (position, length)
+ pub colors: Palette,
pub color: Option<PaletteColor>,
+ pub focused_client: Option<ClientId>,
+ pub is_main_client: bool,
+ pub other_cursors_exist_in_session: bool,
+ pub other_focused_clients: Vec<ClientId>,
}
impl PaneFrame {
- fn render_title_right_side(&self, max_length: usize) -> Option<String> {
+ pub fn new(
+ geom: Viewport,
+ scroll_position: (usize, usize),
+ main_title: String,
+ frame_params: FrameParams,
+ ) -> Self {
+ PaneFrame {
+ geom,
+ title: main_title,
+ scroll_position,
+ colors: frame_params.colors,
+ color: frame_params.color,
+ focused_client: frame_params.focused_client,
+ is_main_client: frame_params.is_main_client,
+ other_focused_clients: frame_params.other_focused_clients,
+ other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session,
+ }
+ }
+ fn client_cursor(&self, client_id: ClientId) -> String {
+ let color = client_id_to_colors(client_id, self.colors);
+ background_color(" ", color.map(|c| c.0))
+ }
+ fn render_title_right_side(&self, max_length: usize) -> Option<(String, usize)> {
+ // string and length because of color
if self.scroll_position.0 > 0 || self.scroll_position.1 > 0 {
let prefix = " SCROLL: ";
let full_indication =
format!(" {}/{} ", self.scroll_position.0, self.scroll_position.1);
let short_indication = format!(" {} ", self.scroll_position.0);
- if prefix.width() + full_indication.width() <= max_length {
- Some(format!("{}{}", prefix, full_indication))
- } else if full_indication.width() <= max_length {
- Some(full_indication)
- } else if short_indication.width() <= max_length {
- Some(short_indication)
+ let full_indication_len = full_indication.chars().count();
+ let short_indication_len = short_indication.chars().count();
+ let prefix_len = prefix.chars().count();
+ if prefix_len + full_indication_len <= max_length {
+ Some((
+ color_string(&format!("{}{}", prefix, full_indication), self.color),
+ prefix_len + full_indication_len,
+ ))
+ } else if full_indication_len <= max_length {
+ Some((
+ color_string(&full_indication, self.color),
+ full_indication_len,
+ ))
+ } else if short_indication_len <= max_length {
+ Some((
+ color_string(&short_indication, self.color),
+ short_indication_len,
+ ))
} else {
None
}
@@ -48,14 +126,148 @@ impl PaneFrame {
None
}
}
- fn render_title_left_side(&self, max_length: usize) -> Option<String> {
+ fn render_my_focus(&self, max_length: usize) -> Option<(String, usize)> {
+ let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
+ let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
+ let full_indication_text = "MY FOCUS";
+ let full_indication = format!(
+ "{} {} {}",
+ left_separator,
+ color_string(full_indication_text, self.color),
+ right_separator
+ );
+ let full_indication_len = full_indication_text.width() + 4; // 2 for separators 2 for padding
+ let short_indication_text = "ME";
+ let short_indication = format!(
+ "{} {} {}",
+ left_separator,
+ color_string(short_indication_text, self.color),
+ right_separator
+ );
+ let short_indication_len = short_indication_text.width() + 4; // 2 for separators 2 for padding
+ if full_indication_len <= max_length {
+ Some((full_indication, full_indication_len))
+ } else if short_indication_len <= max_length {
+ Some((short_indication, short_indication_len))
+ } else {
+ None
+ }
+ }
+ fn render_my_and_others_focus(&self, max_length: usize) -> Option<(String, usize)> {
+ let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
+ let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
+ let full_indication_text = "MY FOCUS AND:";
+ let short_indication_text = "+";
+ let mut full_indication = color_string(full_indication_text, self.color);
+ let mut full_indication_len = full_indication_text.width();
+ let mut short_indication = color_string(short_indication_text, self.color);
+ let mut short_indication_len = short_indication_text.width();
+ for client_id in &self.other_focused_clients {
+ let text = format!(" {}", self.client_cursor(*client_id));
+ full_indication_len += 2;
+ full_indication.push_str(&text);
+ short_indication_len += 2;
+ short_indication.push_str(&text);
+ }
+ if full_indication_len + 4 <= max_length {
+ // 2 for separators, 2 for padding
+ Some((
+ format!("{} {} {}", left_separator, full_indication, right_separator),
+ full_indication_len + 4,
+ ))
+ } else if short_indication_len + 4 <= max_length {
+ // 2 for separators, 2 for padding
+ Some((
+ format!(
+ "{} {} {}",
+ left_separator, short_indication, right_separator
+ ),
+ short_indication_len + 4,
+ ))
+ } else {
+ None
+ }
+ }
+ fn render_other_focused_users(&self, max_length: usize) -> Option<(String, usize)> {
+ let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
+ let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
+ let full_indication_text = if self.other_focused_clients.len() == 1 {
+ "FOCUSED USER:"
+ } else {
+ "FOCUSED USERS:"
+ };
+ let middle_indication_text = "U:";
+ let mut full_indication = color_string(full_indication_text, self.color);
+ let mut full_indication_len = full_indication_text.width();
+ let mut middle_indication = color_string(middle_indication_text, self.color);
+ let mut middle_indication_len = middle_indication_text.width();
+ let mut short_indication = String::from("");
+ let mut short_indication_len = 0;
+ for client_id in &self.other_focused_clients {
+ let text = format!(" {}", self.client_cursor(*client_id));
+ full_indication_len += 2;
+ full_indication.push_str(&text);
+ middle_indication_len += 2;
+ middle_indication.push_str(&text);
+ short_indication_len += 2;
+ short_indication.push_str(&text);
+ }
+ if full_indication_len + 4 <= max_length {
+ // 2 for separators, 2 for padding
+ Some((
+ format!("{} {} {}", left_separator, full_indication, right_separator),
+ full_indication_len + 4,
+ ))
+ } else if middle_indication_len + 4 <= max_length {
+ // 2 for separators, 2 for padding
+ Some((
+ format!(
+ "{} {} {}",
+ left_separator, middle_indication, right_separator
+ ),
+ middle_indication_len + 4,
+ ))
+ } else if short_indication_len + 3 <= max_length {
+ // 2 for separators, 1 for padding
+ Some((
+ format!("{}{} {}", left_separator, short_indication, right_separator),
+ short_indication_len + 3,
+ ))
+ } else {
+ None
+ }
+ }
+ fn render_title_middle(&self, max_length: usize) -> Option<(String, usize)> {
+ // string and length because of color
+ if self.is_main_client
+ && self.other_focused_clients.is_empty()
+ && !self.other_cursors_exist_in_session
+ {
+ None
+ } else if self.is_main_client
+ && self.other_focused_clients.is_empty()
+ && self.other_cursors_exist_in_session
+ {
+ self.render_my_focus(max_length)
+ } else if self.is_main_client && !self.other_focused_clients.is_empty() {
+ self.render_my_and_others_focus(max_length)
+ } else if !self.other_focused_clients.is_empty() {
+ self.render_other_focused_users(max_length)
+ } else {
+ None
+ }
+ }
+ fn render_title_left_side(&self, max_length: usize) -> Option<(String, usize)> {
let middle_truncated_sign = "[..]";
let middle_truncated_sign_long = "[...]";
let full_text = format!(" {} ", &self.title);
if max_length <= 6 || self.title.is_empty() {
None
} else if full_text.width() <= max_length {
- Some(full_text)
+ Some((
+ color_string(&full_text, self.color),
+ full_text.chars().count(),
+ ))
} else {
let length_of_each_half = (max_length - middle_truncated_sign.width()) / 2;
@@ -89,53 +301,234 @@ impl PaneFrame {
} else {
format!("{}{}{}", first_part, middle_truncated_sign, second_part)
};
- Some(title_left_side)
+ Some((
+ color_string(&title_left_side, self.color),
+ title_left_side.chars().count(),
+ ))
}
}
- fn render_title(&self, vte_output: &mut String) {
+ fn three_part_title_line(
+ &self,
+ left_side: &str,
+ left_side_len: &usize,
+ middle: &str,
+ middle_len: &usize,
+ right_side: &str,
+ right_side_len: &usize,
+ ) -> String {
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+ let mut title_line = String::new();
+ let left_side_start_position = self.geom.x + 1;
+ let middle_start_position = self.geom.x + (total_title_length / 2) - (middle_len / 2) + 1;
+ let right_side_start_position =
+ (self.geom.x + self.geom.cols - 1).saturating_sub(*right_side_len);
+
+ let mut col = self.geom.x;
+ loop {
+ if col == self.geom.x {
+ title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
+ } else if col == self.geom.x + self.geom.cols - 1 {
+ title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
+ } else if col == left_side_start_position {
+ title_line.push_str(left_side);
+ col += left_side_len;
+ continue;
+ } else if col == middle_start_position {
+ title_line.push_str(middle);
+ col += middle_len;
+ continue;
+ } else if col == right_side_start_position {
+ title_line.push_str(right_side);
+ col += right_side_len;
+ continue;
+ } else {
+ title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
+ // TODO: BETTER
+ }
+ if col == self.geom.x + self.geom.cols - 1 {
+ break;
+ }
+ col += 1;
+ }
+ title_line
+ }
+ fn left_and_middle_title_line(
+ &self,
+ left_side: &str,
+ left_side_len: &usize,
+ middle: &str,
+ middle_len: &usize,
+ ) -> String {
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+ let mut title_line = String::new();
+ let left_side_start_position = self.geom.x + 1;
+ let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1;
+
+ let mut col = self.geom.x;
+ loop {
+ if col == self.geom.x {
+ title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
+ } else if col == self.geom.x + self.geom.cols - 1 {
+ title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
+ } else if col == left_side_start_position {
+ title_line.push_str(left_side);
+ col += *left_side_len;
+ continue;
+ } else if col == middle_start_position {
+ title_line.push_str(middle);
+ col += *middle_len;
+ continue;
+ } else {
+ title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
+ // TODO: BETTER
+ }
+ if col == self.geom.x + self.geom.cols - 1 {
+ break;
+ }
+ col += 1;
+ }
+ title_line
+ }
+ fn middle_only_title_line(&self, middle: &str, middle_len: &usize) -> String {
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+ let mut title_line = String::new();
+ let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1;
+
+ let mut col = self.geom.x;
+ loop {
+ if col == self.geom.x {
+ title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
+ } else if col == self.geom.x + self.geom.cols - 1 {
+ title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
+ } else if col == middle_start_position {
+ title_line.push_str(middle);
+ col += *middle_len;
+ continue;
+ } else {
+ title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
+ // TODO: BETTER
+ }
+ if col == self.geom.x + self.geom.cols - 1 {
+ break;
+ }
+ col += 1;
+ }
+ title_line
+ }
+ fn two_part_title_line(
+ &self,
+ left_side: &str,
+ left_side_len: &usize,
+ right_side: &str,
+ right_side_len: &usize,
+ ) -> String {
+ let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
+ let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+ let mut middle = String::new();
+ for _ in (left_side_len + right_side_len)..total_title_length {
+ middle.push_str(boundary_type::HORIZONTAL);
+ }
+ format!(
+ "{}{}{}{}{}",
+ left_boundary,
+ left_side,
+ color_string(&middle, self.color),
+ color_string(right_side, self.color),
+ &right_boundary
+ )
+ }
+ fn left_only_title_line(&self, left_side: &str, left_side_len: &usize) -> String {
+ let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
+ let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+ let mut middle_padding = String::new();
+ for _ in *left_side_len..total_title_length {
+ middle_padding.push_str(boundary_type::HORIZONTAL);
+ }
+ format!(
+ "{}{}{}{}",
+ left_boundary,
+ left_side,
+ color_string(&middle_padding, self.color),
+ &right_boundary
+ )
+ }
+ fn empty_title_line(&self) -> String {
+ let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
+ let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+ let mut middle_padding = String::new();
+ for _ in 0..total_title_length {
+ middle_padding.push_str(boundary_type::HORIZONTAL);
+ }
+ format!(
+ "{}{}{}",
+ left_boundary,
+ color_string(&middle_padding, self.color),
+ right_boundary
+ )
+ }
+ fn title_line_with_middle(&self, middle: &str, middle_len: &usize) -> String {
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+ let length_of_each_side = total_title_length.saturating_sub(*middle_len + 2) / 2;
+ let mut left_side = self.render_title_left_side(length_of_each_side);
+ let mut right_side = self.render_title_right_side(length_of_each_side);
+
+ match (left_side.as_mut(), right_side.as_mut()) {
+ (Some((left_side, left_side_len)), Some((right_side, right_side_len))) => self
+ .three_part_title_line(
+ left_side,
+ left_side_len,
+ middle,
+ middle_len,
+ right_side,
+ right_side_len,
+ ),
+ (Some((left_side, left_side_len)), None) => {
+ self.left_and_middle_title_line(left_side, left_side_len, middle, middle_len)
+ }
+ _ => self.middle_only_title_line(middle, middle_len),
+ }
+ }
+ fn title_line_without_middle(&self) -> String {
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
- let left_boundary = boundary_type::TOP_LEFT;
- let right_boundary = boundary_type::TOP_RIGHT;
let left_side = self.render_title_left_side(total_title_length);
- let right_side = left_side.as_ref().and_then(|left_side| {
- let space_left = total_title_length.saturating_sub(left_side.width() + 1); // 1 for a middle separator
+ let right_side = left_side.as_ref().and_then(|(_left_side, left_side_len)| {
+ let space_left = total_title_length.saturating_sub(*left_side_len + 1); // 1 for a middle separator
self.render_title_right_side(space_left)
});
- let title_text = match (left_side, right_side) {
- (Some(left_side), Some(right_side)) => {
- let mut middle = String::new();
- for _ in (left_side.width() + right_side.width())..total_title_length {
- middle.push_str(boundary_type::HORIZONTAL);
- }
- format!(
- "{}{}{}{}{}",
- left_boundary, left_side, middle, right_side, right_boundary
- )
- }
- (Some(left_side), None) => {
- let mut middle_padding = String::new();
- for _ in left_side.width()..total_title_length {
- middle_padding.push_str(boundary_type::HORIZONTAL);
- }
- format!(
- "{}{}{}{}",
- left_boundary, left_side, middle_padding, right_boundary
- )
+ match (left_side, right_side) {
+ (Some((left_side, left_side_len)), Some((right_side, right_side_len))) => {
+ self.two_part_title_line(&left_side, &left_side_len, &right_side, &right_side_len)
}
- _ => {
- let mut middle_padding = String::new();
- for _ in 0..total_title_length {
- middle_padding.push_str(boundary_type::HORIZONTAL);
- }
- format!("{}{}{}", left_boundary, middle_padding, right_boundary)
+ (Some((left_side, left_side_len)), None) => {
+ self.left_only_title_line(&left_side, &left_side_len)
}
- };
- vte_output.push_str(&format!(
- "\u{1b}[{};{}H\u{1b}[m{}",
- self.geom.y + 1, // +1 because goto is 1 indexed
- self.geom.x + 1, // +1 because goto is 1 indexed
- color_string(&title_text, self.color),
- )); // goto row/col + boundary character
+ _ => self.empty_title_line(),
+ }
+ }
+ fn render_title(&self, vte_output: &mut String) {
+ let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
+
+ if let Some((middle, middle_length)) = self.render_title_middle(total_title_length).as_mut()
+ {
+ let title_text = self.title_line_with_middle(middle, middle_length);
+ vte_output.push_str(&format!(
+ "\u{1b}[{};{}H\u{1b}[m{}",
+ self.geom.y + 1, // +1 because goto is 1 indexed
+ self.geom.x + 1, // +1 because goto is 1 indexed
+ color_string(&title_text, self.color),
+ )); // goto row/col + boundary character
+ } else {
+ let title_text = self.title_line_without_middle();
+ vte_output.push_str(&format!(
+ "\u{1b}[{};{}H\u{1b}[m{}",
+ self.geom.y + 1, // +1 because goto is 1 indexed
+ self.geom.x + 1, // +1 because goto is 1 indexed
+ color_string(&title_text, self.color),
+ )); // goto row/col + boundary character
+ }
}
pub fn render(&self) -> String {
let mut vte_output = String::new();
diff --git a/zellij-server/src/ui/pane_contents_and_ui.rs b/zellij-server/src/ui/pane_contents_and_ui.rs
new file mode 100644
index 000000000..4f700f5dc
--- /dev/null
+++ b/zellij-server/src/ui/pane_contents_and_ui.rs
@@ -0,0 +1,165 @@
+use crate::panes::PaneId;
+use crate::tab::{Output, Pane};
+use crate::ui::boundaries::Boundaries;
+use crate::ui::pane_boundaries_frame::client_id_to_colors;
+use crate::ui::pane_boundaries_frame::FrameParams;
+use crate::ClientId;
+use std::collections::HashMap;
+use zellij_tile::data::{InputMode, Palette, PaletteColor};
+
+pub struct PaneContentsAndUi<'a> {
+ pane: &'a mut Box<dyn Pane>,
+ output: &'a mut Output,
+ colors: Palette,
+ focused_clients: Vec<ClientId>,
+ multiple_users_exist_in_session: bool,
+ mode: InputMode, // TODO: per client
+}
+
+impl<'a> PaneContentsAndUi<'a> {
+ pub fn new(
+ pane: &'a mut Box<dyn Pane>,
+ output: &'a mut Output,
+ colors: Palette,
+ active_panes: &HashMap<ClientId, PaneId>,
+ mode: InputMode,
+ ) -> Self {
+ let focused_clients: Vec<ClientId> = active_panes
+ .iter()
+ .filter(|(_c_id, p_id)| **p_id == pane.pid())
+ .map(|(c_id, _p_id)| *c_id)
+ .collect();
+ let multiple_users_exist_in_session = active_panes.len() > 1;
+ PaneContentsAndUi {
+ pane,
+ output,
+ colors,
+ focused_clients,
+ multiple_users_exist_in_session,
+