aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/mobile/src/workspace.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-06-10 16:50:55 +0200
committerGitHub <noreply@github.com>2026-06-10 16:50:55 +0200
commitb6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0 (patch)
tree38c77a381cfb937b428752a588c09a45c81434c9 /default-plugins/mobile/src/workspace.rs
parentfeat: PWA support for the web client (#5184) (diff)
downloadsl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.tar.gz
sl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.tar.xz
sl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.zip
Mobile layout (#5241)
* phase 2+3 * initial mobile plugin (phases 4 + 5 + 6) * some bugfixes * implement stage 7 * mobile interface improvements * improve mobile pane/tab picker * more ui improvements and fixes * various mobile fixes * some mobile fixes * mobile keyboard triggering fix * some cleanups * initial plugin keyboard implementation * improve keyboard hit zones * improve keyboard * pinch resize * pinching fixes * implement scrolling + panning * initial keyboard resizing * fix: mobile first render issues * keyboard responsiveness * fix some issues with the keyboard * implement fit * fix mobile scrolling * add tilde cell to compact * improve mobile keyboard * fix starting mobile terminal font to natural keyboard size * fix mobile resize blank screen flash * shadow focus for mobile * more keyboard fixes * improve mobile status bar * fix: render plugins to mobile clients * allow opening new panes/tabs from the mobile plugin * allow configuring * fix dot slash * improve mobile native soft keyboard interaction * switch to native keyboard - initial implementation * dirty hacks, but phone keyboard works now * adjust keyboard help strip * fix cursor in mobile * fix keyboard and help line * rename keyboard to modifier bar * allow starting new session * make clicking session name bring us to new session menu * allow switching to desktop from the mobile ui * mobile welcome screen initial implementation * mobile welcome screen fixes * various welcome screen fixes * more mobile fixes * moar mobile fixes * mobile ui redesign * fix: swallowed backspace bug * switch session screen improvement * touch up some interfaces * refactor: move mobile state to own struct * refactor: move fit resizing to the server * remove dead flash code * some cleanups * refactor: clear fit on close * refactor: improve fit api * refactor: pane sync functions * refactor: mouse * refactor: break into modules * some cleanups for the sessions screen * fix scrolling and other stuff * refactor: sessions screen * refactor: panes screen * refactor: new session screen * refactor: menu screen * refactor: render * cleanups * refactor: plugin structure * remove unused welcome screen path * Rename api * refactor input.js * cleanup useless comments * fit/mobile refactoring * take out the garbage * remove more comments * remove mobile flash on startup/attach * fix: various mobile startup issues * fix space bugs * cleanup * rustfmt * fix windows build * adjust modifier bar ui * add pr
Diffstat (limited to 'default-plugins/mobile/src/workspace.rs')
-rw-r--r--default-plugins/mobile/src/workspace.rs122
1 files changed, 122 insertions, 0 deletions
diff --git a/default-plugins/mobile/src/workspace.rs b/default-plugins/mobile/src/workspace.rs
new file mode 100644
index 00000000..3b59bf32
--- /dev/null
+++ b/default-plugins/mobile/src/workspace.rs
@@ -0,0 +1,122 @@
+use std::collections::HashMap;
+use zellij_tile::prelude::*;
+
+const WELCOME_SCREEN_PLUGIN_ALIAS: &str = "welcome-screen";
+
+#[derive(Default)]
+pub struct Workspace {
+ pub own_plugin_pane_id: Option<PaneId>,
+ pub tabs: Vec<TabInfo>,
+ pub panes_by_tab_position: HashMap<usize, Vec<PaneInfo>>,
+ pub selected_tab_position: Option<usize>,
+ pub selected_pane_id: Option<PaneId>,
+ pub latest_pane_contents: HashMap<PaneId, PaneContents>,
+ pub mode_info: Option<ModeInfo>,
+ pub session_name: Option<String>,
+ pub pane_last_activity: HashMap<PaneId, u64>,
+ pub pending_new_tab_position: Option<usize>,
+}
+
+impl Workspace {
+ pub fn tabs_in_order(&self) -> Vec<&TabInfo> {
+ let own = self.own_plugin_pane_id;
+ let mut tabs: Vec<&TabInfo> = self
+ .tabs
+ .iter()
+ .filter(|t| !self.tab_is_self_only(t.position, own))
+ .collect();
+ tabs.sort_by_key(|t| t.position);
+ tabs
+ }
+
+ fn tab_is_self_only(&self, tab_position: usize, own: Option<PaneId>) -> bool {
+ let Some(panes) = self.panes_by_tab_position.get(&tab_position) else {
+ return false;
+ };
+ let Some(own) = own else {
+ return false;
+ };
+ let visible: Vec<&PaneInfo> = panes.iter().filter(|p| !p.is_suppressed).collect();
+ if visible.is_empty() {
+ return false;
+ }
+ visible.iter().all(|p| pane_info_matches(p, own))
+ }
+
+ pub fn current_tab(&self) -> Option<&TabInfo> {
+ let visible = self.tabs_in_order();
+ if let Some(pos) = self.selected_tab_position {
+ if let Some(t) = visible.iter().find(|t| t.position == pos) {
+ return Some(*t);
+ }
+ }
+ visible.first().copied()
+ }
+
+ pub fn current_tab_panes(&self) -> Vec<&PaneInfo> {
+ let Some(tab) = self.current_tab() else {
+ return vec![];
+ };
+ self.panes_for_tab(tab.position)
+ }
+
+ pub fn panes_for_tab(&self, tab_position: usize) -> Vec<&PaneInfo> {
+ let own = self.own_plugin_pane_id;
+ let mut panes: Vec<&PaneInfo> = self
+ .panes_by_tab_position
+ .get(&tab_position)
+ .map(|v| v.iter().collect())
+ .unwrap_or_default();
+ panes.retain(|p| {
+ !p.is_suppressed
+ && p.is_selectable
+ && !own.map(|id| pane_info_matches(p, id)).unwrap_or(false)
+ });
+ panes.sort_by_key(|p| (p.is_floating, p.pane_y, p.pane_x, p.id));
+ panes
+ }
+
+ pub fn current_pane_viewport_len(&self) -> usize {
+ self.current_pane()
+ .as_ref()
+ .map(pane_id_of)
+ .and_then(|id| self.latest_pane_contents.get(&id))
+ .map(|c| c.viewport.len())
+ .unwrap_or(0)
+ }
+
+ pub fn current_pane_is_welcome(&self) -> bool {
+ self.current_pane()
+ .map(|p| p.is_plugin && p.plugin_url.as_deref() == Some(WELCOME_SCREEN_PLUGIN_ALIAS))
+ .unwrap_or(false)
+ }
+
+ // `is_focused` is server-global (true if any client focuses the pane), so
+ // selecting by it would make this client's viewport follow another
+ // connected client's focus; fall back to the first pane instead.
+ pub fn current_pane(&self) -> Option<PaneInfo> {
+ if let Some(selected) = self.selected_pane_id {
+ for pane in self.current_tab_panes() {
+ if pane_info_matches(pane, selected) {
+ return Some(pane.clone());
+ }
+ }
+ }
+ self.current_tab_panes().into_iter().next().cloned()
+ }
+}
+
+pub fn pane_info_matches(info: &PaneInfo, id: PaneId) -> bool {
+ match id {
+ PaneId::Terminal(tid) => !info.is_plugin && info.id == tid,
+ PaneId::Plugin(pid) => info.is_plugin && info.id == pid,
+ }
+}
+
+pub fn pane_id_of(info: &PaneInfo) -> PaneId {
+ if info.is_plugin {
+ PaneId::Plugin(info.id)
+ } else {
+ PaneId::Terminal(info.id)
+ }
+}