#![allow(non_snake_case)]
#![allow(dead_code)]
use std::io::{self, Write};
use crate::ported::action::State;
use crate::ported::crt::{
ColorElements, ColorScheme, ERR, KEY_ALT, KEY_CTRL, KEY_DOWN, KEY_F, KEY_FOCUS_IN,
KEY_FOCUS_OUT, KEY_LEFT, KEY_RESIZE, KEY_RIGHT, KEY_UP,
};
use crate::ported::functionbar::Ncurses;
use crate::ported::header::{Header, Header_draw, Header_updateData};
use crate::ported::machine::{Machine, Machine_scanTables};
use crate::ported::panel::{
HandlerResult, Panel, PanelClass, Panel_draw, Panel_getCh, Panel_move, Panel_onKey,
Panel_resize, Panel_size, EVENT_PANEL_LOST_FOCUS,
};
use crate::ported::table::Table_rebuildPanel;
const SCREEN_TAB_MARGIN_LEFT: i32 = 2;
const SCREEN_TAB_COLUMN_GAP: i32 = 1;
const ALT_H: i32 = KEY_ALT(b'H' as i32);
const ALT_J: i32 = KEY_ALT(b'J' as i32);
const ALT_K: i32 = KEY_ALT(b'K' as i32);
const ALT_L: i32 = KEY_ALT(b'L' as i32);
const CTRL_B: i32 = KEY_CTRL(b'B' as i32);
const CTRL_F: i32 = KEY_CTRL(b'F' as i32);
const F10: i32 = KEY_F(10);
const HASH: i32 = b'#' as i32;
const ESC: i32 = 27;
const KEY_Q: i32 = b'q' as i32;
pub struct ScreenManager {
pub x1: i32,
pub y1: i32,
pub x2: i32,
pub y2: i32,
pub allowFocusChange: bool,
pub panelCount: u32,
pub panels: Vec<Box<dyn PanelClass>>,
pub name: Option<String>,
pub header: *mut Header,
pub host: *mut Machine,
pub state: *mut State,
}
impl ScreenManager {
fn empty() -> ScreenManager {
ScreenManager {
x1: 0,
y1: 0,
x2: 0,
y2: -1,
allowFocusChange: true,
panelCount: 0,
panels: Vec::new(),
name: None,
header: core::ptr::null_mut(),
host: core::ptr::null_mut(),
state: core::ptr::null_mut(),
}
}
}
pub fn ScreenManager_new(
header: *mut Header,
host: *mut Machine,
state: *mut State,
) -> ScreenManager {
ScreenManager {
x1: 0,
y1: 0,
x2: 0,
y2: -1,
allowFocusChange: true,
panelCount: 0,
panels: Vec::new(),
name: None,
header,
host,
state,
}
}
pub fn ScreenManager_delete(this: ScreenManager) {
let _ = this;
}
pub fn ScreenManager_size(this: &ScreenManager) -> i32 {
this.panelCount as i32
}
pub fn ScreenManager_add(this: &mut ScreenManager, item: Box<dyn PanelClass>, size: i32) {
let idx = this.panels.len() as i32;
ScreenManager_insert(this, item, size, idx);
}
pub fn header_height(this: &ScreenManager) -> i32 {
if unsafe { &*this.state }.hideMeters {
return 0;
}
if !this.header.is_null() {
return unsafe { &*this.header }.height;
}
0
}
pub fn ScreenManager_insert(
this: &mut ScreenManager,
mut item: Box<dyn PanelClass>,
mut size: i32,
idx: i32,
) {
let mut lastX = 0;
if idx > 0 {
let last = this.panels[(idx - 1) as usize].as_panel();
lastX = last.x + last.w + 1;
}
let hh = header_height(this);
let height = Ncurses::lines() - this.y1 - hh + this.y2;
if size <= 0 {
size = Ncurses::cols() - this.x1 + this.x2 - lastX;
}
Panel_resize(item.as_panel_mut(), size, height);
Panel_move(item.as_panel_mut(), lastX, this.y1 + hh);
if (idx as u32) < this.panelCount {
for i in (idx + 1)..=(this.panelCount as i32) {
let (px, py) = {
let p = this.panels[i as usize].as_panel();
(p.x, p.y)
};
Panel_move(this.panels[i as usize].as_panel_mut(), px + size, py);
}
}
item.as_panel_mut().needsRedraw = true;
this.panels.insert(idx as usize, item);
this.panelCount += 1;
}
pub fn ScreenManager_remove(this: &mut ScreenManager, idx: i32) -> Box<dyn PanelClass> {
debug_assert!((idx as u32) < this.panelCount);
let w = this.panels[idx as usize].as_panel().w;
let panel = this.panels.remove(idx as usize);
this.panelCount -= 1;
if (idx as u32) < this.panelCount {
for i in (idx as usize)..(this.panelCount as usize) {
let (px, py) = {
let p = this.panels[i].as_panel();
(p.x, p.y)
};
Panel_move(this.panels[i].as_panel_mut(), px - w, py);
}
}
panel
}
pub fn ScreenManager_resize(this: &mut ScreenManager) {
let y1_header = this.y1 + header_height(this);
let panels = this.panelCount as i32;
let lines = Ncurses::lines();
let cols = Ncurses::cols();
let mut lastX = 0;
for i in 0..(panels - 1) {
let w = this.panels[i as usize].as_panel().w;
Panel_resize(
this.panels[i as usize].as_panel_mut(),
w,
lines - y1_header + this.y2,
);
Panel_move(this.panels[i as usize].as_panel_mut(), lastX, y1_header);
let p = this.panels[i as usize].as_panel();
lastX = p.x + p.w + 1;
}
let last = (panels - 1) as usize;
Panel_resize(
this.panels[last].as_panel_mut(),
cols - this.x1 + this.x2 - lastX,
lines - y1_header + this.y2,
);
Panel_move(this.panels[last].as_panel_mut(), lastX, y1_header);
}
pub fn checkRecalculation(
this: &mut ScreenManager,
oldTime: &mut f64,
sortTimeout: &mut i32,
redraw: &mut bool,
rescan: &mut bool,
timedOut: &mut bool,
force_redraw: &mut bool,
) {
let _ = &force_redraw;
#[cfg(target_os = "macos")]
{
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
let ms = unsafe { &mut (*this.host).realtimeMs };
crate::ported::darwin::platform::Platform_gettime_realtime(&mut tv, ms);
}
let newTime = unsafe { (*this.host).realtimeMs } as f64 / 100.0;
let delay = unsafe {
(*this.host)
.settings
.as_ref()
.map(|s| s.delay)
.unwrap_or(15)
} as f64;
*timedOut = (newTime - *oldTime) > delay;
*rescan |= *timedOut;
if newTime < *oldTime {
*rescan = true; }
if *rescan {
*oldTime = newTime;
let pauseUpdate = unsafe { (*this.state).pauseUpdate };
let treeView = unsafe {
(*this.host)
.settings
.as_ref()
.and_then(|s| s.screens.get(s.ssIndex as usize))
.map(|ss| ss.treeView)
.unwrap_or(false)
};
if !pauseUpdate && (*sortTimeout == 0 || treeView) {
if let Some(table) = unsafe { (*this.host).activeTable } {
unsafe {
(*table).needsSort = true;
}
}
*sortTimeout = 1;
}
#[cfg(target_os = "macos")]
{
let dhost = this.host as *mut crate::ported::darwin::darwinmachine::DarwinMachine;
crate::ported::darwin::darwinmachine::Machine_scan(unsafe { &mut *dhost });
}
#[cfg(all(not(target_os = "macos"), target_os = "linux"))]
{
let lhost = this.host as *mut crate::ported::linux::linuxmachine::LinuxMachine;
crate::ported::linux::linuxmachine::Machine_scan(unsafe { &mut *lhost });
}
if !pauseUpdate {
Machine_scanTables(unsafe { &mut *this.host });
}
if !this.header.is_null() {
Header_updateData(unsafe { &mut *this.header });
}
*redraw = true;
}
if *redraw {
if let Some(table) = unsafe { (*this.host).activeTable } {
unsafe {
Table_rebuildPanel(&mut *table);
}
}
if !unsafe { (*this.state).hideMeters } && !this.header.is_null() {
let header = unsafe { &mut *this.header };
let mut out = io::stdout().lock();
Header_draw(header, &mut out);
let _ = out.flush();
}
}
*rescan = false;
}
pub fn drawTab(y: i32, x: &mut i32, l: i32, name: &str, cur: bool) -> bool {
debug_assert!(*x >= 0);
debug_assert!(*x < l);
let scheme = ColorScheme::active();
let border = if cur {
ColorElements::SCREENS_CUR_BORDER
} else {
ColorElements::SCREENS_OTH_BORDER
}
.packed(scheme);
let text = if cur {
ColorElements::SCREENS_CUR_TEXT
} else {
ColorElements::SCREENS_OTH_TEXT
}
.packed(scheme);
let mut out = io::stdout().lock();
Ncurses::attrset(&mut out, border);
Ncurses::mvaddch(&mut out, y, *x, '[');
*x += 1;
if *x >= l {
let _ = out.flush();
return false;
}
let name_width = name.len().min((l - *x) as usize) as i32;
Ncurses::attrset(&mut out, text);
Ncurses::mvaddnstr(&mut out, y, *x, name, name_width);
*x += name_width;
if *x >= l {
let _ = out.flush();
return false;
}
Ncurses::attrset(&mut out, border);
Ncurses::mvaddch(&mut out, y, *x, ']');
let _ = out.flush();
if *x >= l - (1 + SCREEN_TAB_COLUMN_GAP) {
*x = l;
return false;
}
*x += 1 + SCREEN_TAB_COLUMN_GAP;
true
}
pub fn ScreenManager_drawScreenTabs(this: &ScreenManager) {
let host = unsafe { &*this.host };
let settings = host.settings.as_ref().unwrap();
let screens = &settings.screens;
let cur = settings.ssIndex as i32;
let l = Ncurses::cols();
let panel = this.panels[0].as_panel();
let y = panel.y - 1;
let mut x = SCREEN_TAB_MARGIN_LEFT;
if x < l {
if let Some(name) = &this.name {
drawTab(y, &mut x, l, name, true);
} else {
for (s, screen) in screens.iter().enumerate() {
let heading = screen.heading.as_deref().unwrap_or("");
let ok = drawTab(y, &mut x, l, heading, s as i32 == cur);
if !ok {
break;
}
}
}
}
let scheme = ColorScheme::active();
let mut out = io::stdout().lock();
Ncurses::attrset(&mut out, ColorElements::RESET_COLOR.packed(scheme));
let _ = out.flush();
}
pub fn ScreenManager_drawPanels(this: &mut ScreenManager, focus: usize, force_redraw: bool) {
let (screen_tabs, hide_function_bar) = {
let settings = unsafe { &*this.host }.settings.as_ref().unwrap();
let hfb = settings.hideFunctionBar;
let hide_selection = unsafe { &*this.state }.hideSelection;
(
settings.screenTabs,
hfb == 2 || (hfb == 1 && hide_selection),
)
};
if screen_tabs {
ScreenManager_drawScreenTabs(this);
}
let hide_selection = unsafe { &*this.state }.hideSelection;
let main_panel_base: *const Panel = {
let mp = unsafe { &*this.state }.mainPanel;
if mp.is_null() {
core::ptr::null()
} else {
unsafe { (*mp).as_panel() as *const Panel }
}
};
let n_panels = this.panelCount as usize;
let mut out = io::stdout().lock();
for i in 0..n_panels {
this.panels[i].print_header();
let highlight_selected = {
let panel_base = this.panels[i].as_panel() as *const Panel;
panel_base != main_panel_base || !hide_selection
};
Panel_draw(
this.panels[i].as_panel_mut(),
force_redraw,
i == focus,
highlight_selected,
hide_function_bar,
);
let (py, px, pw, ph) = {
let p = this.panels[i].as_panel();
(p.y, p.x, p.w, p.h)
};
Ncurses::mvvline(
&mut out,
py,
px + pw,
' ',
ph + if hide_function_bar { 1 } else { 0 },
);
}
let _ = out.flush();
}
pub fn ScreenManager_run(
this: &mut ScreenManager,
lastFocus: Option<&mut usize>,
lastKey: Option<&mut i32>,
name: Option<&str>,
) {
let mut quit = false;
let mut focus: usize = 0;
let mut oldTime = 0.0f64;
let mut ch = ERR;
let mut closeTimeout = 0;
let mut timedOut = true;
let mut redraw = true;
let mut force_redraw = true;
let mut rescan = false;
let mut sortTimeout = 0;
let resetSortTimeout = 5;
this.name = name.map(|s| s.to_string());
'main: while !quit {
if !this.header.is_null() {
checkRecalculation(
this,
&mut oldTime,
&mut sortTimeout,
&mut redraw,
&mut rescan,
&mut timedOut,
&mut force_redraw,
);
}
if redraw || force_redraw {
ScreenManager_drawPanels(this, focus, force_redraw);
{
let mut out = io::stdout().lock();
crate::extensions::overlay::draw_chrome(&mut out);
crate::extensions::overlay::draw_active(&mut out);
}
force_redraw = false;
if unsafe { &*this.host }.iterationsRemaining != -1 {
let host = unsafe { &mut *this.host };
host.iterationsRemaining -= 1;
if host.iterationsRemaining == 0 {
quit = true;
continue;
}
}
}
let prevCh = ch;
ch = Panel_getCh(this.panels[focus].as_panel());
if ch == ERR {
if sortTimeout > 0 {
sortTimeout -= 1;
}
if prevCh == ch && !timedOut {
closeTimeout += 1;
if closeTimeout == 100 {
break;
}
} else {
closeTimeout = 0;
}
redraw = false;
continue;
}
if crate::extensions::overlay::dispatch_key(ch) {
if crate::extensions::overlay::take_layout_dirty() {
ScreenManager_resize(this);
}
redraw = true;
force_redraw = true;
continue;
}
match ch {
ALT_H => ch = KEY_LEFT,
ALT_J => ch = KEY_DOWN,
ALT_K => ch = KEY_UP,
ALT_L => ch = KEY_RIGHT,
_ => {}
}
redraw = true;
let result = this.panels[focus].event_handler(ch);
if result.contains(HandlerResult::SYNTH_KEY) {
ch = (result.0 >> 16) as i32;
}
if result.contains(HandlerResult::REFRESH) {
sortTimeout = 0;
}
if result.contains(HandlerResult::REDRAW) {
force_redraw = true;
}
if result.contains(HandlerResult::RESIZE) {
ScreenManager_resize(this);
force_redraw = true;
}
if result.contains(HandlerResult::RESCAN) {
rescan = true;
sortTimeout = 0;
}
if result.contains(HandlerResult::HANDLED) {
continue;
} else if result.contains(HandlerResult::BREAK_LOOP) {
quit = true;
continue;
}
'sw: {
match ch {
KEY_RESIZE => {
ScreenManager_resize(this);
continue 'main;
}
KEY_FOCUS_IN | KEY_FOCUS_OUT => break 'sw,
KEY_LEFT | CTRL_B => {
if this.panelCount >= 2 {
if !this.allowFocusChange {
break 'sw;
}
if focus > 0 {
this.panels[focus].event_handler(EVENT_PANEL_LOST_FOCUS);
}
loop {
if focus > 0 {
focus -= 1;
}
if Panel_size(this.panels[focus].as_panel()) == 0 && focus > 0 {
continue;
}
break;
}
break 'sw;
}
}
KEY_RIGHT | CTRL_F | 9 => {
if this.panelCount >= 2 {
if !this.allowFocusChange {
break 'sw;
}
if (focus as u32) < this.panelCount - 1 {
this.panels[focus].event_handler(EVENT_PANEL_LOST_FOCUS);
}
loop {
if (focus as u32) < this.panelCount - 1 {
focus += 1;
}
if Panel_size(this.panels[focus].as_panel()) == 0
&& (focus as u32) < this.panelCount - 1
{
continue;
}
break;
}
break 'sw;
}
}
HASH => {
{
let st = unsafe { &mut *this.state };
st.hideMeters = !st.hideMeters;
}
ScreenManager_resize(this);
force_redraw = true;
break 'sw;
}
ESC | KEY_Q | F10 => {
quit = true;
continue 'main;
}
_ => {
}
}
sortTimeout = resetSortTimeout;
Panel_onKey(this.panels[focus].as_panel_mut(), ch);
}
}
if let Some(lf) = lastFocus {
*lf = focus;
}
if let Some(lk) = lastKey {
*lk = ch;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::panel::Panel_new;
use crate::ported::settings::HeaderLayout;
fn sm_with_panels(widths: &[i32]) -> ScreenManager {
let mut sm = ScreenManager::empty();
let mut x = 0;
for &w in widths {
let mut p = Panel_new(x, 0, w, 10, None);
p.x = x;
sm.panels.push(Box::new(p));
sm.panelCount += 1;
x += w + 1;
}
sm
}
fn state(hideMeters: bool) -> State {
State {
pauseUpdate: false,
hideSelection: false,
hideMeters,
host: core::ptr::null_mut(),
mainPanel: core::ptr::null_mut(),
header: core::ptr::null_mut(),
failedUpdate: None,
}
}
fn header(height: i32) -> Header {
Header {
host: core::ptr::null(),
columns: Vec::new(),
headerLayout: HeaderLayout::HF_ONE_100,
pad: 0,
height,
headerMargin: false,
screenTabs: false,
}
}
#[test]
fn size_returns_panel_count() {
let mut sm = ScreenManager::empty();
assert_eq!(ScreenManager_size(&sm), 0);
sm.panelCount = 3;
assert_eq!(ScreenManager_size(&sm), 3);
}
#[test]
fn new_sets_layout_defaults_and_stores_pointers() {
let mut hdr = header(4);
let mut host = Machine::default();
let mut st = state(false);
let sm = ScreenManager_new(&mut hdr, &mut host, &mut st);
assert_eq!((sm.x1, sm.y1, sm.x2, sm.y2), (0, 0, 0, -1));
assert!(sm.allowFocusChange);
assert_eq!(sm.panelCount, 0);
assert!(sm.panels.is_empty());
assert!(sm.name.is_none());
assert!(!sm.host.is_null());
assert!(!sm.state.is_null());
assert_eq!(unsafe { &*sm.header }.height, 4);
}
#[test]
fn header_height_zero_when_meters_hidden() {
let mut st = state(true); let mut hdr = header(7);
let mut sm = ScreenManager::empty();
sm.state = &mut st;
sm.header = &mut hdr;
assert_eq!(header_height(&sm), 0);
}
#[test]
fn header_height_returns_header_height_when_present() {
let mut st = state(false);
let mut hdr = header(7);
let mut sm = ScreenManager::empty();
sm.state = &mut st;
sm.header = &mut hdr;
assert_eq!(header_height(&sm), 7);
}
#[test]
fn header_height_zero_when_no_header() {
let mut st = state(false);
let mut sm = ScreenManager::empty();
sm.state = &mut st;
assert_eq!(header_height(&sm), 0);
}
#[test]
fn insert_first_panel_sizes_to_available_height() {
let mut st = state(false); let mut sm = ScreenManager::empty();
sm.state = &mut st;
let p = Panel_new(0, 0, 10, 5, None);
ScreenManager_insert(&mut sm, Box::new(p), 10, 0);
assert_eq!(sm.panelCount, 1);
assert_eq!(sm.panels[0].as_panel().w, 10); assert_eq!(sm.panels[0].as_panel().h, Ncurses::lines() - 1);
assert_eq!(
(sm.panels[0].as_panel().x, sm.panels[0].as_panel().y),
(0, 0)
);
assert!(sm.panels[0].as_panel().needsRedraw);
}
#[test]
fn insert_negative_size_fills_remaining_width() {
let mut st = state(false);
let mut sm = ScreenManager::empty();
sm.state = &mut st;
let p = Panel_new(0, 0, 3, 5, None);
ScreenManager_insert(&mut sm, Box::new(p), 0, 0); assert_eq!(sm.panels[0].as_panel().w, Ncurses::cols());
}
#[test]
fn add_appends_and_places_right_of_predecessor() {
let mut st = state(false);
let mut sm = ScreenManager::empty();
sm.state = &mut st;
ScreenManager_add(&mut sm, Box::new(Panel_new(0, 0, 5, 5, None)), 5);
ScreenManager_add(&mut sm, Box::new(Panel_new(0, 0, 8, 5, None)), 8);
assert_eq!(sm.panelCount, 2);
assert_eq!(sm.panels[1].as_panel().x, 6);
assert_eq!(sm.panels[1].as_panel().w, 8);
assert_eq!(sm.panels[1].as_panel().y, 0);
}
#[test]
fn resize_relays_panels_across_the_width() {
let mut st = state(false); let mut sm = sm_with_panels(&[10, 20]);
sm.state = &mut st;
ScreenManager_resize(&mut sm);
let lines = Ncurses::lines();
let cols = Ncurses::cols();
assert_eq!(sm.panels[0].as_panel().w, 10);
assert_eq!(sm.panels[0].as_panel().h, lines - 1); assert_eq!(
(sm.panels[0].as_panel().x, sm.panels[0].as_panel().y),
(0, 0)
);
assert_eq!(sm.panels[1].as_panel().x, 11);
assert_eq!(sm.panels[1].as_panel().w, cols - 11); assert_eq!(sm.panels[1].as_panel().h, lines - 1);
}
#[test]
fn resize_single_panel_takes_full_width() {
let mut st = state(false);
let mut sm = sm_with_panels(&[10]);
sm.state = &mut st;
ScreenManager_resize(&mut sm);
assert_eq!(sm.panels[0].as_panel().w, Ncurses::cols());
assert_eq!(sm.panels[0].as_panel().x, 0);
}
#[test]
fn remove_returns_panel_and_updates_count() {
let mut sm = sm_with_panels(&[10, 20, 5]);
assert_eq!(sm.panelCount, 3);
let removed = ScreenManager_remove(&mut sm, 1);
assert_eq!(removed.as_panel().w, 20);
assert_eq!(sm.panelCount, 2);
assert_eq!(sm.panels.len(), 2);
}
#[test]
fn remove_shifts_right_panels_left_by_width() {
let mut sm = sm_with_panels(&[10, 20, 5]);
let x_third_before = sm.panels[2].as_panel().x; ScreenManager_remove(&mut sm, 0); assert_eq!(sm.panels[0].as_panel().x, 11 - 10); assert_eq!(sm.panels[1].as_panel().x, x_third_before - 10);
}
#[test]
fn remove_last_panel_no_shift() {
let mut sm = sm_with_panels(&[10, 20]);
let first_x = sm.panels[0].as_panel().x;
ScreenManager_remove(&mut sm, 1);
assert_eq!(sm.panelCount, 1);
assert_eq!(sm.panels[0].as_panel().x, first_x); }
#[test]
fn draw_tab_advances_x_when_it_fits() {
let mut x = SCREEN_TAB_MARGIN_LEFT;
let ok = drawTab(0, &mut x, 80, "main", true);
assert!(ok);
assert_eq!(
x,
SCREEN_TAB_MARGIN_LEFT + 1 + 4 + 1 + SCREEN_TAB_COLUMN_GAP
);
}
#[test]
fn draw_tab_truncates_name_to_line_width() {
let mut x = 2;
let ok = drawTab(0, &mut x, 5, "abcdef", false);
assert!(!ok);
assert_eq!(x, 5); }
#[test]
fn draw_tab_returns_false_when_bracket_overflows() {
let mut x = 4;
let ok = drawTab(0, &mut x, 5, "name", true);
assert!(!ok);
assert_eq!(x, 5);
}
}