UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

278 lines 8.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Screen = void 0; const sys_1 = require("./sys"); const geometry_1 = require("./geometry"); const View_1 = require("./View"); const Viewport_1 = require("./Viewport"); const log_1 = require("./log"); const Buffer_1 = require("./Buffer"); const FocusManager_1 = require("./managers/FocusManager"); const ModalManager_1 = require("./managers/ModalManager"); const MouseManager_1 = require("./managers/MouseManager"); const TickManager_1 = require("./managers/TickManager"); const Window_1 = require("./components/Window"); const System_1 = require("./System"); class Screen { #program; #onExit; rootView; #buffer; #focusManager = new FocusManager_1.FocusManager(); #modalManager = new ModalManager_1.ModalManager(); #mouseManager = new MouseManager_1.MouseManager(); #tickManager = new TickManager_1.TickManager(() => this.render()); static reset() { const program = (0, sys_1.program)({ useBuffer: true, tput: true, }); program.alternateBuffer(); program.enableMouse(); program.setMouse({ sendFocus: true }, true); program.clear(); program.disableMouse(); program.showCursor(); program.normalBuffer(); (0, log_1.flushLogs)(); process.exit(0); } static async start(viewConstructor = new Window_1.Window(), opts = { quitChar: 'c' }) { const program = (0, sys_1.program)({ useBuffer: true, tput: true, }); program.alternateBuffer(); program.enableMouse(); program.hideCursor(); program.clear(); program.setMouse({ sendFocus: true }, true); // weird quirk of blessed - bind anything to 'keypress' before // attaching the screen or else I-don't-remember-what will happen. const fn = function () { }; program.on('keypress', fn); program.off('keypress', fn); const rootView = viewConstructor instanceof View_1.View ? viewConstructor : await viewConstructor(program); const screen = new Screen(program, rootView); screen.onExit(() => { program.clear(); program.disableMouse(); program.showCursor(); program.normalBuffer(); }); program.on('focus', function () { screen.trigger({ type: 'focus' }); }); program.on('blur', function () { screen.trigger({ type: 'blur' }); }); program.on('resize', function () { screen.trigger({ type: 'resize' }); }); if (opts?.quitChar) { program.key(`C-${opts.quitChar}`, () => { screen.exit(); }); } program.on('keypress', (char, key) => { screen.trigger({ type: 'key', ...key }); }); program.on('mouse', function (data) { let action = data.action; if (action === 'focus' || action === 'blur') { return; } if (data.button === 'unknown') { return; } screen.trigger({ ...data, name: translateMouseAction(action), type: 'mouse', }); }); screen.start(); return [screen, program, rootView]; } constructor(program, rootView) { this.#program = program; this.#buffer = new Buffer_1.Buffer(); this.rootView = rootView; Object.defineProperty(this, 'program', { enumerable: false, }); } onExit(callback) { if (this.#onExit) { const prev = this.#onExit; this.#onExit = () => { prev(); callback(); }; } else { this.#onExit = callback; } } start() { this.rootView.moveToScreen(this); this.render(); } exit() { this.#tickManager.stop(); this.rootView.moveToScreen(undefined); this.#onExit?.(); (0, log_1.flushLogs)(); process.exit(0); } trigger(event) { switch (event.type) { case 'resize': case 'focus': case 'blur': break; case 'key': this.triggerKeyboard(event); break; case 'mouse': { this.triggerMouse(event); break; } } this.render(); } /** * Requests a modal. A modal will be created if: * (a) no modal is already displayed * or * (b) a modal is requesting a nested modal */ requestModal(parent, modal, onClose, rect) { return this.#modalManager.requestModal(parent, modal, onClose, rect); } /** * @return boolean Whether the current view has focus */ registerFocus(view) { return this.#focusManager.registerFocus(view); } registerHotKey(view, key) { return this.#focusManager.registerHotKey(view, key); } requestFocus(view) { return this.#focusManager.requestFocus(view); } nextFocus() { this.#focusManager.nextFocus(); } prevFocus() { this.#focusManager.prevFocus(); } triggerKeyboard(event) { event = translateKeyEvent(event); this.#focusManager.trigger(event); } /** * @see MouseManager.registerMouse */ registerMouse(view, offset, point, eventNames) { this.#mouseManager.registerMouse(view, offset, point, eventNames); } checkMouse(view, x, y) { this.#mouseManager.checkMouse(view, x, y); } triggerMouse(systemEvent) { const system = new System_1.UnboundSystem(this.#focusManager); this.#mouseManager.trigger(systemEvent, system); } registerTick(view) { this.#tickManager.registerTick(view); } triggerTick(dt) { } preRender(view) { this.#modalManager.reset(); this.#tickManager.reset(); this.#mouseManager.reset(); this.#focusManager.reset(view === this.rootView); } /** * @return boolean Whether or not to rerender the view due to focus or mouse change */ commit() { const system = new System_1.UnboundSystem(this.#focusManager); const focusNeedsRender = this.#focusManager.commit(); const mouseNeedsRender = this.#mouseManager.commit(system); return focusNeedsRender || mouseNeedsRender; } needsRender() { this.#tickManager.needsRender(); } render() { const screenSize = new geometry_1.Size(this.#program.cols, this.#program.rows); this.#buffer.resize(screenSize); // this may be called again by renderModals, before the last modal renders this.preRender(this.rootView); const size = this.rootView.naturalSize(screenSize).max(screenSize); const viewport = new Viewport_1.Viewport(this, this.#buffer, size); this.rootView.render(viewport); const rerenderView = this.#modalManager.renderModals(this, viewport); const needsRerender = this.commit(); // one -and only one- re-render if a change is detected to focus or mouse-hover if (needsRerender) { rerenderView.render(viewport); } this.#tickManager.endRender(); this.#buffer.flush(this.#program); } } exports.Screen = Screen; function translateMouseAction(action) { switch (action) { case 'mousemove': return 'mouse.move.in'; case 'mousedown': return `mouse.button.down`; case 'mouseup': return `mouse.button.up`; case 'wheeldown': return 'mouse.wheel.down'; case 'wheelup': return 'mouse.wheel.up'; case 'wheelleft': return 'mouse.wheel.left'; case 'wheelright': return 'mouse.wheel.right'; } } /** * These are mostly due to my own terminal keybindings; would be better to have * these configured in some .rc file. */ function translateKeyEvent(event) { if (event.full === 'M-b') { return { type: 'key', full: 'M-left', name: 'left', ctrl: false, meta: true, shift: false, char: '1;9D', }; } if (event.full === 'M-f') { return { type: 'key', full: 'M-right', name: 'right', ctrl: false, meta: true, shift: false, char: '1;9C', }; } return event; } //# sourceMappingURL=Screen.js.map