Files
zellij/default-plugins/mobile/src/render.rs
T
Aram Drevekenin b6a5ad0e5a 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
2026-06-10 16:50:55 +02:00

126 lines
3.8 KiB
Rust

use crate::ansi::{move_to, RESET};
use crate::components::{modifier_bar, top_bar};
use crate::frame::chrome_offsets;
use crate::screens::ActiveScreen;
use crate::state::State;
pub fn render(state: &mut State, rows: usize, cols: usize) {
reset_frame(state, rows, cols);
if rows < 4 || cols < 8 {
render_too_small(state, rows, cols);
return;
}
let suppress_top_bar = top_bar_suppressed(state);
let (body_top, bar_height) =
chrome_offsets(rows, suppress_top_bar, state.frame.soft_keyboard_visible);
let body_bottom = rows.saturating_sub(bar_height);
let viewport_height = body_bottom.saturating_sub(body_top);
state
.frame
.emit_cursor(viewport_cursor(state, body_top, viewport_height, cols));
disable_autowrap();
if !suppress_top_bar && cols > 0 {
top_bar::render(&state.workspace, &mut state.frame, state.active, 0, cols);
}
if body_bottom > body_top {
render_active_screen(state, body_top, body_bottom, cols);
render_menu_overlay(state, body_top, body_bottom, cols);
}
if bar_height > 0 {
render_modifier_bar(state, body_bottom, cols);
}
enable_autowrap();
}
fn reset_frame(state: &mut State, rows: usize, cols: usize) {
state.frame.click_regions.clear();
state.viewport.viewport_region = None;
state.frame.last_render_rows = rows;
state.frame.last_render_cols = cols;
}
fn render_too_small(state: &mut State, rows: usize, cols: usize) {
state.frame.emit_cursor(None);
print!("{}\x1b[2J{}mobile {}x{}", RESET, move_to(0, 0), rows, cols);
}
fn top_bar_suppressed(state: &State) -> bool {
state.sessions.is_welcome_screen || state.active == ActiveScreen::Sessions
}
fn viewport_cursor(
state: &State,
body_top: usize,
viewport_height: usize,
cols: usize,
) -> Option<(usize, usize)> {
if state.active != ActiveScreen::Viewport {
return None;
}
let lines = state.workspace.current_pane_viewport_len();
let max_v_pan = lines.saturating_sub(viewport_height);
let skip = max_v_pan - state.viewport.viewport_v_pan.min(max_v_pan);
state.viewport.compute_cursor_position(
&state.workspace,
body_top,
viewport_height,
cols,
skip,
state.viewport.viewport_h_pan,
)
}
fn render_active_screen(state: &mut State, body_top: usize, body_bottom: usize, cols: usize) {
let frame = &mut state.frame;
match state.active {
ActiveScreen::Viewport => {
state
.viewport
.render(&state.workspace, frame, body_top, body_bottom, cols)
},
ActiveScreen::Sessions => {
state
.sessions
.render(&mut state.navigation, frame, body_top, body_bottom, cols)
},
ActiveScreen::Panes => state.panes.render(
&state.workspace,
&mut state.navigation,
frame,
body_top,
body_bottom,
cols,
),
ActiveScreen::NewSessionPrompt => {
state.new_session.render(frame, body_top, body_bottom, cols)
},
}
}
fn render_menu_overlay(state: &mut State, body_top: usize, body_bottom: usize, cols: usize) {
if state.menu.open && state.active == ActiveScreen::Viewport {
state
.menu
.render(&state.fit, &mut state.frame, body_top, body_bottom, cols);
}
}
fn render_modifier_bar(state: &mut State, body_bottom: usize, cols: usize) {
let armed = modifier_bar::KeyboardModifiers {
ctrl_armed: state.input.ctrl_held,
alt_armed: state.input.alt_held,
};
modifier_bar::render_modifier_bar(&armed, body_bottom, cols, &mut state.frame.click_regions);
}
fn disable_autowrap() {
print!("\x1b[?7l");
}
fn enable_autowrap() {
print!("\x1b[?7h");
}