From 5a25d394b9d6e5f878e2486a46c5531fe5898e79 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 26 May 2026 09:29:37 +0900 Subject: [PATCH] wintex default theme changes --- assets/disk0/home/fsh.js | 12 +- assets/disk0/tvdos/bin/zfm.js | 231 +++++++++++++++++++++++++- assets/disk0/tvdos/include/wintex.mjs | 61 ++++--- 3 files changed, 263 insertions(+), 41 deletions(-) diff --git a/assets/disk0/home/fsh.js b/assets/disk0/home/fsh.js index 9d5da53..27457b5 100644 --- a/assets/disk0/home/fsh.js +++ b/assets/disk0/home/fsh.js @@ -442,7 +442,7 @@ _fsh.redrawAll = function() { _fsh.openAddTodoDialog = function() { let res = win.showDialog({ title: "New Todo", - fields: [{label: "Text", initial: "", width: _fsh.TODO_TEXT_WIDTH}], + fields: [{label: "Text:", initial: "", width: _fsh.TODO_TEXT_WIDTH}], allowDelete: false }) _fsh.redrawAll() @@ -459,7 +459,7 @@ _fsh.openEditTodoDialog = function(index) { if (!entry) return let res = win.showDialog({ title: "Edit Todo", - fields: [{label: "Text", initial: entry[0], width: _fsh.TODO_TEXT_WIDTH}], + fields: [{label: "Text:", initial: entry[0], width: _fsh.TODO_TEXT_WIDTH}], allowDelete: true }) _fsh.redrawAll() @@ -479,8 +479,8 @@ _fsh.openAddQaDialog = function() { let res = win.showDialog({ title: "New Quick Access", fields: [ - {label: "Label", initial: "", width: _fsh.QA_LABEL_WIDTH}, - {label: "Command", initial: "", width: _fsh.QA_CMD_WIDTH} + {label: "Label:", initial: "", width: _fsh.QA_LABEL_WIDTH}, + {label: "Command:", initial: "", width: _fsh.QA_CMD_WIDTH} ], allowDelete: false }) @@ -500,8 +500,8 @@ _fsh.openEditQaDialog = function(index) { let res = win.showDialog({ title: "Edit Quick Access", fields: [ - {label: "Label", initial: entry[0], width: _fsh.QA_LABEL_WIDTH}, - {label: "Command", initial: entry[1], width: _fsh.QA_CMD_WIDTH} + {label: "Label:", initial: entry[0], width: _fsh.QA_LABEL_WIDTH}, + {label: "Command:", initial: entry[1], width: _fsh.QA_CMD_WIDTH} ], allowDelete: true }) diff --git a/assets/disk0/tvdos/bin/zfm.js b/assets/disk0/tvdos/bin/zfm.js index f7feb37..d0560ad 100644 --- a/assets/disk0/tvdos/bin/zfm.js +++ b/assets/disk0/tvdos/bin/zfm.js @@ -597,6 +597,181 @@ function showMessagePopup(title, message) { }) } +// Vertical-list popup: items are stacked rows, navigable with arrow keys / +// mouse, selection (Enter / left-click on row) returns that item's action. +// A single Close button sits below the list; Esc and Close both yield 'close'. +function showActionListPopup(opts) { + const title = opts.title || '' + const items = opts.items || [] + const closeLabel = opts.closeLabel || 'Close' + const message = opts.message + const messageLines = !message ? [] + : Array.isArray(message) ? message + : ('' + message).split('\n') + + const fg = 254 + const bg = 243 + const dimFg = 249 + const hlFg = 230 + const itemSelBg = 81 + + const longestItem = items.reduce((m, it) => Math.max(m, it.label.length), 0) + const longestMsg = messageLines.reduce((m, l) => Math.max(m, l.length), 0) + const titleW = title.length + 4 + const closeBtnW = closeLabel.length + 4 + const w = Math.max(longestItem + 8, titleW + 4, longestMsg + 6, closeBtnW + 4, 24) + + const msgRows = messageLines.length + (messageLines.length > 0 ? 1 : 0) + const itemsRowOff = 1 + msgRows + const buttonsRowOff = itemsRowOff + items.length + 1 + const h = buttonsRowOff + 2 + const screen = con.getmaxyx() + const row = Math.max(2, Math.floor((screen[0] - h) / 2)) + const col = Math.max(2, Math.floor((screen[1] - w) / 2)) + + let oldFG = con.get_color_fore() + let oldBG = con.get_color_back() + + // Initial focus: first 'default: true' item, otherwise first item, otherwise close button. + let focusIdx = -1 + for (let i = 0; i < items.length; i++) { + if (items[i].default) { focusIdx = i; break } + } + if (focusIdx < 0) focusIdx = (items.length > 0) ? 0 : items.length + const totalFocus = items.length + 1 + let done = null + + function itemRow(i) { return row + itemsRowOff + i } + function itemCol() { return col + 1 } + function itemWidth() { return w - 2 } + function closeBtnRow(){ return row + buttonsRowOff } + function closeBtnCol(){ return col + Math.floor((w - closeBtnW) / 2) } + + function drawFrameBox() { + con.color_pair(fg, bg) + for (let r = row; r < row + h; r++) { + con.move(r, col) + print(' '.repeat(w)) + } + const wo = new win.WindowObject(col, row, w, h, ()=>{}, ()=>{}, title) + wo.isHighlighted = true + wo.titleBack = bg + wo.drawFrame() + con.color_pair(fg, bg) + } + + function drawMessage() { + if (messageLines.length === 0) return + con.color_pair(dimFg, bg) + for (let i = 0; i < messageLines.length; i++) { + con.move(row + 1 + i, col + 2) + print(messageLines[i].padEnd(w - 4, ' ')) + } + con.color_pair(fg, bg) + } + + function drawItem(i) { + const focused = (focusIdx === i) + const useFg = focused ? hlFg : fg + const useBg = focused ? itemSelBg : bg + con.color_pair(useFg, useBg) + con.move(itemRow(i), itemCol()) + print(' ' + items[i].label.padEnd(itemWidth() - 2, ' ')) + con.color_pair(fg, bg) + } + + function drawCloseBtn() { + const focused = (focusIdx === items.length) + const useFg = focused ? hlFg : fg + con.color_pair(useFg, bg) + con.move(closeBtnRow(), closeBtnCol()) + print('[ ' + closeLabel + ' ]') + con.color_pair(fg, bg) + } + + function render() { + drawFrameBox() + drawMessage() + for (let i = 0; i < items.length; i++) drawItem(i) + drawCloseBtn() + con.curs_set(0) + } + + function hitTest(ev) { + const [cy, cx] = pixelToCell(ev[1], ev[2]) + for (let i = 0; i < items.length; i++) { + if (cy === itemRow(i) && cx >= itemCol() && cx < itemCol() + itemWidth()) { + return { kind: 'item', idx: i } + } + } + const cbx = closeBtnCol() + if (cy === closeBtnRow() && cx >= cbx && cx < cbx + closeBtnW) { + return { kind: 'close' } + } + return null + } + + function moveFocus(dir) { + focusIdx = (focusIdx + dir + totalFocus) % totalFocus + render() + } + + render() + + let eventJustReceived = true + while (done === null) { + input.withEvent(ev => { + if (eventJustReceived && (ev[0] === 'key_down' || ev[0] === 'mouse_down')) { + eventJustReceived = false; return + } + + if (ev[0] === 'mouse_move') { + const hit = hitTest(ev) + let newFocus = null + if (hit && hit.kind === 'item') newFocus = hit.idx + else if (hit && hit.kind === 'close') newFocus = items.length + if (newFocus !== null && newFocus !== focusIdx) { + focusIdx = newFocus + for (let i = 0; i < items.length; i++) drawItem(i) + drawCloseBtn() + } + return + } + if (ev[0] === 'mouse_down') { + if (ev[3] !== 1) return + const hit = hitTest(ev) + if (!hit) return + if (hit.kind === 'item') { + focusIdx = hit.idx; render() + done = { action: items[hit.idx].action } + return + } + if (hit.kind === 'close') { + focusIdx = items.length; render() + done = { action: 'close' } + return + } + return + } + if (ev[0] !== 'key_down') return + if (1 !== ev[2]) return + const ks = ev[1] + + if (ks === '') { done = { action: 'close' }; return } + if (ks === '\t' || ks === '' || ks === '') { moveFocus(+1); return } + if (ks === '') { moveFocus(-1); return } + if (ks === '\n' || ks === ' ') { + if (focusIdx < items.length) done = { action: items[focusIdx].action } + else done = { action: 'close' } + return + } + }) + } + + con.color_pair(oldFG, oldBG) + return done +} + /////////////////////////////////////////////////////////////////////////////////////////////////// const windows = [ @@ -891,17 +1066,22 @@ function actRename() { function actMore() { if (path[windowMode].length === 0) return const cache = filePanelCache[windowMode][cursor[windowMode]] - if (!cache || !cache.file || cache.isDirectory) return + if (!cache || !cache.file) return - const res = win.showDialog({ + const items = cache.isDirectory + ? [ + { label: 'Open terminal here', action: 'terminal', default: true }, + ] + : [ + { label: 'Execute', action: 'execute', default: true }, + { label: 'Edit', action: 'edit' }, + { label: 'Open terminal here', action: 'terminal' }, + ] + + const res = showActionListPopup({ title: 'More', message: cache.file.name, - fields: [], - buttons: [ - { label: 'Execute', action: 'execute', default: true }, - { label: 'Edit', action: 'edit' }, - { label: 'Close', action: 'close' }, - ], + items: items, }) _redraw() @@ -930,7 +1110,42 @@ function actMore() { refreshFilePanelCache(windowMode) pendingPostExecDrain = true redraw() + return } + if (res.action === 'terminal') { + actTerminal(cache) + } +} + +function actTerminal(cache) { + const targetDir = (cache && cache.isDirectory && cache.file) + ? cache.file.fullPath + : getCurrentDirStr(windowMode) + if (!targetDir || targetDir.length === 0) return + + // TVDOS shell.parse has no working escape inside quotes (the `^` ESCAPE + // state is a TODO), so we can't pass a quoted path through `command -k + // "cd \"X\""`. The outer quotes carry the whole `cd ` as one token; + // shell.execute then re-parses it. This works for paths without spaces; + // paths with spaces will only cd to the first component. + let errorlevel = 0 + con.curs_set(1); clearScr(); con.move(1, 1) + try { + errorlevel = _G.shell.execute(`command -k "cd ${targetDir}"`) + } + catch (e) { + println(e) + errorlevel = 1 + } + if (errorlevel) { + println("Hit Return/Enter key to continue . . . .") + sys.read() + } + firstRunLatch = true + con.curs_set(0); clearScr() + refreshFilePanelCache(windowMode) + pendingPostExecDrain = true + redraw() } function actQuit() { exit = true } diff --git a/assets/disk0/tvdos/include/wintex.mjs b/assets/disk0/tvdos/include/wintex.mjs index f7103ec..df1b6f8 100644 --- a/assets/disk0/tvdos/include/wintex.mjs +++ b/assets/disk0/tvdos/include/wintex.mjs @@ -65,12 +65,12 @@ class WindowObject { } if (this.titleRight !== undefined) { let tt = ''+this.titleRight - con.move(this.y, this.x + this.width - tt.length - 2) + con.move(this.y + this.height - 1, this.x + this.width - tt.length - 2) print(`\x84${charset[4]}u`) if (this.titleBackRight !== undefined) print(`\x1B[48;5;${this.titleBackRight}m`) print(`\x1B[38;5;${colourText}m${tt}`) if (this.titleBackRight !== undefined) print(`\x1B[48;5;${oldBack}m`) - print(`\x1B[38;5;${colour}m\x84${charset[1]}u`) + print(`\x1B[38;5;${colour}m\x84${charset[3]}u`) } @@ -186,7 +186,7 @@ function scrollHorz(dx, stringSize, stringViewSize, currentCursorPos, currentScr // opts = { // title: string, // message: string | string[]? -- optional body text drawn above fields -// fields: [{label, initial?, width}, ...] -- omit / [] for no input field +// fields: [{label, initial?, width}, ...] -- omit / [] for no input field. Label does NOT get auto-colon // buttons: [{label, action, default?}, ...] -- defaults to [OK, Cancel] (+ Delete // if `allowDelete:true`) // allowDelete: bool, -- inserts a Delete button (fsh compat) @@ -208,7 +208,7 @@ function scrollHorz(dx, stringSize, stringViewSize, currentCursorPos, currentScr // hidden when a button has focus. // - Mouse: left-click on a button activates it; click on a field puts focus // on that field and positions the caret under the click. Mouse hover on a -// button highlights it. +// button moves focus to it (the same focus the keyboard uses). const _dialogScreen = con.getmaxyx() const _dialogPixDim = graphics.getPixelDimension() const _CELL_PW = (_dialogPixDim[0] / _dialogScreen[1]) | 0 @@ -243,14 +243,15 @@ function showDialog(opts) { const bg = (c.bg != null) ? c.bg : 243 const fieldBg = (c.fieldBg != null) ? c.fieldBg : 240 const dimFg = (c.dimFg != null) ? c.dimFg : 249 - const hlFg = (c.hlFg != null) ? c.hlFg : 230 - const focusBg = (c.focusBg != null) ? c.focusBg : bg + const hlFg = (c.hlFg != null) ? c.hlFg : 240 + const focusBg = (c.focusBg != null) ? c.focusBg : 253 // Layout + const buttonGap = 3 const maxFieldW = fields.reduce((m, f) => Math.max(m, f.width), 16) const longestMsg = messageLines.reduce((m, l) => Math.max(m, l.length), 0) const titleW = title.length + 4 - const btnRowW = buttons.reduce((s, b) => s + b.label.length + 5, 0) - 1 + const btnRowW = buttons.reduce((s, b) => s + b.label.length + 4, 0) + buttonGap * Math.max(0, buttons.length - 1) const w = Math.max(maxFieldW + 6, titleW + 4, longestMsg + 6, btnRowW + 4, 24) const msgTopOff = (messageLines.length > 0) ? 1 : 0 const msgRows = messageLines.length + (messageLines.length > 0 ? 1 : 0) @@ -268,7 +269,6 @@ function showDialog(opts) { } if (focusIdx < 0) focusIdx = fields.length > 0 ? 0 : fields.length const totalFocus = fields.length + buttons.length - let hoverBtn = -1 let done = null function fieldScroll(cur, fw) { return cur < fw ? 0 : cur - fw + 1 } @@ -282,7 +282,7 @@ function showDialog(opts) { let bx = col + Math.floor((w - btnRowW) / 2) return buttons.map(b => { const r = { x: bx, y: row + buttonsRowOff, w: b.label.length + 4 } - bx += b.label.length + 5 + bx += b.label.length + 4 + buttonGap return r }) } @@ -320,27 +320,29 @@ function showDialog(opts) { // Label con.color_pair(fg, bg) con.move(fieldLabelRow(i), fbCol) - print(f.label + ':') + print(f.label) - // Top border + // Top border (3px padding w/ TSVM chr rom) con.color_pair(fieldBg, bg) con.move(fbRow, fbCol) print('\u00EC' + '\u00A9'.repeat(fw) + '\u00ED') - // Side borders + content + // Left border (3px padding w/ TSVM chr rom) con.move(fbRow + 1, fbCol) print('\u00AB') + // the content con.color_pair(fg, fieldBg) const s = fieldScroll(cursors[i], fw) const vis = values[i].substring(s, s + fw) print(vis.padEnd(fw, ' ')) + // Right border (3px padding w/ TSVM chr rom) con.color_pair(fieldBg, bg) con.move(fbRow + 1, fbCol + fw + 1) print('\u00AA') - // Bottom border + // Bottom border (3px padding w/ TSVM chr rom) con.move(fbRow + 2, fbCol) print('\u00F4' + '\u00AC'.repeat(fw) + '\u00F5') con.color_pair(fg, bg) @@ -350,16 +352,21 @@ function showDialog(opts) { const b = buttons[i] const bIdx = fields.length + i const focused = (focusIdx === bIdx) - const hovered = (hoverBtn === i) const r = regions[i] - let useFg, useBg - if (focused && hovered) { useFg = hlFg; useBg = focusBg } - else if (focused) { useFg = hlFg; useBg = focusBg } - else if (hovered) { useFg = hlFg; useBg = bg } - else { useFg = fg; useBg = bg } + const useFg = focused ? hlFg : fg + const useBg = focused ? focusBg : bg con.color_pair(useFg, useBg) - con.move(r.y, r.x) - print('[ ' + b.label + ' ]') + con.move(r.y, r.x-1) + if (focused) { + con.color_pair(useBg, bg) + print('\u00DE') + con.color_pair(useFg, useBg) + print('[ ' + b.label + ' ]') + con.color_pair(useBg, bg) + print('\u00DD') + } + else + print(' [ ' + b.label + ' ] ') con.color_pair(fg, bg) } @@ -418,12 +425,12 @@ function showDialog(opts) { if (ev[0] === 'mouse_move') { const hit = hitTestMouse(ev) - const newHover = (hit && hit.kind === 'button') ? hit.idx : -1 - if (newHover !== hoverBtn) { - hoverBtn = newHover - const regs = buttonRegions() - for (let i = 0; i < buttons.length; i++) drawButton(i, regs) - positionCaret() + if (hit && hit.kind === 'button') { + const newFocus = fields.length + hit.idx + if (newFocus !== focusIdx) { + focusIdx = newFocus + render() + } } return }