From 8f13a0932e0e2e480d087b8ae63919cb833fcaa9 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Mon, 4 May 2020 16:45:31 -0600 Subject: [PATCH] neural window manager --- ccemux/system/ccemux.lua | 192 +++++++++++++++++++-------------------- neural/apis/glasses.lua | 121 ++++++++++++++++++++++++ neural/nwm.lua | 160 ++++++++++++++++++++++++++++++++ neural/overlay.lua | 116 ----------------------- 4 files changed, 377 insertions(+), 212 deletions(-) create mode 100644 neural/apis/glasses.lua create mode 100644 neural/nwm.lua delete mode 100644 neural/overlay.lua diff --git a/ccemux/system/ccemux.lua b/ccemux/system/ccemux.lua index 2c60daf..e4360ea 100644 --- a/ccemux/system/ccemux.lua +++ b/ccemux/system/ccemux.lua @@ -6,120 +6,120 @@ local ccemux = _G.ccemux local sides = { 'bottom', 'top', 'back', 'front', 'right', 'left' } local tab = UI.Tab { - title = 'CCEmuX', - description = 'CCEmuX peripherals', - form = UI.Form { - x = 2, ex = -2, y = 2, ey = 5, - values = { - side = 'bottom', - type = 'wireless_modem', - }, - manualControls = true, - side = UI.Chooser { - formLabel = 'Side', formKey = 'side', - width = 10, - }, - ptype = UI.Chooser { - formLabel = 'Type', formKey = 'type', - width = 10, - choices = { - { name = 'Modem', value = 'wireless_modem' }, - { name = 'Drive', value = 'disk_drive' }, - }, - }, - drive_id = UI.TextEntry { - x = 19, y = 3, - formKey = 'drive_id', - shadowText = 'id', - width = 5, - limit = 3, - transform = 'number', - }, - add = UI.Button { - x = -6, y = 3, width = 5, - text = 'Add', event = 'form_ok', - }, - }, - grid = UI.Grid { - x = 2, ex = -2, y = 7, ey = -2, - columns = { - { heading = 'Side', key = 'side', width = 8 }, - { heading = 'Type', key = 'type' }, - { heading = 'ID', key = 'args', width = 4 }, - }, - }, + title = 'CCEmuX', + description = 'CCEmuX peripherals', + form = UI.Form { + x = 2, ex = -2, y = 2, ey = 5, + values = { + side = 'bottom', + type = 'wireless_modem', + }, + manualControls = true, + side = UI.Chooser { + formLabel = 'Side', formKey = 'side', + width = 10, + }, + ptype = UI.Chooser { + formLabel = 'Type', formKey = 'type', + width = 10, + choices = { + { name = 'Modem', value = 'wireless_modem' }, + { name = 'Drive', value = 'disk_drive' }, + }, + }, + drive_id = UI.TextEntry { + x = 19, y = 3, + formKey = 'drive_id', + shadowText = 'id', + width = 5, + limit = 3, + transform = 'number', + }, + add = UI.Button { + x = -6, y = 3, width = 5, + text = 'Add', event = 'form_ok', + }, + }, + grid = UI.Grid { + x = 2, ex = -2, y = 7, ey = -2, + columns = { + { heading = 'Side', key = 'side', width = 8 }, + { heading = 'Type', key = 'type' }, + { heading = 'ID', key = 'args', width = 4 }, + }, + }, } function tab:updatePeripherals(config) - self.grid.values = { } - for k,v in pairs(config) do - table.insert(self.grid.values, { - side = k, - type = v.type, - args = v.args and v.args.id, - }) - end - self.grid:update() + self.grid.values = { } + for k,v in pairs(config) do + table.insert(self.grid.values, { + side = k, + type = v.type, + args = v.args and v.args.id, + }) + end + self.grid:update() end function tab:enable() - local config = Config.load('ccemux') + local config = Config.load('ccemux') - local choices = { } - for _,k in pairs(sides) do - table.insert(choices, { name = k, value = k }) - end - self.form.side.choices = choices + local choices = { } + for _,k in pairs(sides) do + table.insert(choices, { name = k, value = k }) + end + self.form.side.choices = choices - self:updatePeripherals(config) - UI.Tab.enable(self) + self:updatePeripherals(config) + UI.Tab.enable(self) - self.form.drive_id.enabled = false + self.form.drive_id.enabled = false end function tab:eventHandler(event) - if event.type == 'form_complete' then - if event.values.type == 'disk_drive' and not event.values.drive_id then - self:emit({ type = 'error_message', message = 'Invalid drive ID' }) - else - ccemux.detach(event.values.side) + if event.type == 'form_complete' then + if event.values.type == 'disk_drive' and not event.values.drive_id then + self:emit({ type = 'error_message', message = 'Invalid drive ID' }) + else + ccemux.detach(event.values.side) - local config = Config.load('ccemux') - config[event.values.side] = { - type = event.values.type - } - if event.values.type == 'disk_drive' then - config[event.values.side].args = { - id = event.values.drive_id - } - ccemux.attach(event.values.side, event.values.type, { id = event.values.drive_id }) - else - ccemux.attach(event.values.side, event.values.type) - end - Config.update('ccemux', config) - self:updatePeripherals(config) - self.grid:draw() + local config = Config.load('ccemux') + config[event.values.side] = { + type = event.values.type + } + if event.values.type == 'disk_drive' then + config[event.values.side].args = { + id = event.values.drive_id + } + ccemux.attach(event.values.side, event.values.type, { id = event.values.drive_id }) + else + ccemux.attach(event.values.side, event.values.type) + end + Config.update('ccemux', config) + self:updatePeripherals(config) + self.grid:draw() - self:emit({ type = 'success_message', message = 'Attached' }) - end + self:emit({ type = 'success_message', message = 'Attached' }) + end - elseif event.type == 'choice_change' then - if event.element == self.form.ptype then - self.form.drive_id.enabled = event.value == 'disk_drive' - self.form:draw() - end + elseif event.type == 'choice_change' then + if event.element == self.form.ptype then + self.form.drive_id.enabled = event.value == 'disk_drive' + self.form:draw() + end - elseif event.type == 'grid_select' then - local config = Config.load('ccemux') - config[event.selected.side] = nil - Config.update('ccemux', config) - self:updatePeripherals(config) - self.grid:draw() + elseif event.type == 'grid_select' then + local config = Config.load('ccemux') + config[event.selected.side] = nil + Config.update('ccemux', config) + self:updatePeripherals(config) + self.grid:draw() - self:emit({ type = 'success_message', message = 'Detached' }) + self:emit({ type = 'success_message', message = 'Detached' }) - return true - end + return true + end end return tab diff --git a/neural/apis/glasses.lua b/neural/apis/glasses.lua new file mode 100644 index 0000000..f6b62a1 --- /dev/null +++ b/neural/apis/glasses.lua @@ -0,0 +1,121 @@ +--[[ + Create a terminal compatible window for glasses canvas. +]] + +local Terminal = require('opus.terminal') + +local colors = _G.colors +local device = _G.device + +local scale = .5 +local xs, ys = 6 * scale, 9 * scale + +local Glasses = { } + +function Glasses.create(name, sx, sy, w, h) + w, h = w or 46, h or 19 + sx, sy = sx or 1, sy or 20 + + local glasses = device['plethora:glasses'] + local canvas = glasses.canvas() + local _, cy = 1, 1 + local lines = { } + local map = { + ['0'] = 0xF0F0F0FF, + ['1'] = 0xF2B233FF, + ['2'] = 0xE57FD8FF, + ['3'] = 0x99B2F2FF, + ['4'] = 0xDEDE6CFF, + ['5'] = 0x7FCC19FF, + ['6'] = 0xF2B2CCFF, + ['7'] = 0x4C4C4CFF, + ['8'] = 0x999999FF, + ['9'] = 0x4C99B2FF, + ['a'] = 0xB266E5FF, + ['b'] = 0x3366CCFF, + ['c'] = 0x7F664CFF, + ['d'] = 0x57A64EFF, + ['e'] = 0xCC4C4CFF, + ['f'] = 0x191919FF, + } + + -- Position bottom left + local pos = { x = sx * xs, y = sy * ys } + + local function init(group) + for y = 1, h do + lines[y] = { + text = { }, + bg = { } + } + for x = 1, w do + lines[y].bg[x] = group.addRectangle(x * xs, y * ys, xs, ys, 0xF0F0F04F) + lines[y].text[x] = group.addText({ x * xs, y * ys }, '', 0x7FCC19FF) + lines[y].text[x].setScale(scale) + end + end + end + + local group = canvas.addGroup(pos) + init(group) + + local gterm = Terminal.window({ + getSize = function() + return w, h + end, + isColor = function() + return true + end, + clear = function() + for y = 1, h do + for x = 1, w do + local ln = lines[y] + ln.bg[x].setColor(0xF0F0F04F) + ln.text[x].setText('') + end + end + end, + blit = function(text, fg, bg) + for x = 1, #text do + local ln = lines[cy] + ln.bg[x].setColor(map[bg:sub(x, x)]) + ln.text[x].setColor(map[fg:sub(x, x)]) + ln.text[x].setText(text:sub(x, x)) + end + end, + setCursorPos = function(_, y) + cy = y -- full lines are always blit + end, + getTextColor = function() + return colors.white + end, + setTextColor = function() end, + getBackgroundColor = function() + return colors.black + end, + setBackgroundColor = function() end, + setCursorBlink = function() end, + }, 1, 1, w, h, true) + + function gterm.setTextScale() end + function gterm.getPosition() return sx, sy end + function gterm.setVisible() end + function gterm.raise() + local g = canvas.addGroup(pos) + init(g) + gterm.redraw() + group.remove() + group = g + end + function gterm.destroy() + group.remove() + end + + gterm.name = name + gterm.side = name + gterm.type = 'glasses' + + return gterm +end + +return Glasses diff --git a/neural/nwm.lua b/neural/nwm.lua new file mode 100644 index 0000000..b956e62 --- /dev/null +++ b/neural/nwm.lua @@ -0,0 +1,160 @@ +--[[ + A simplistic window manager for glasses. + TODO: support moving windows via mouse drag. +]] + +local Config = require('opus.config') +local Glasses = require('neural.glasses') +local UI = require('opus.ui') +local Util = require('opus.util') + +local kernel = _G.kernel +local multishell = _ENV.multishell +local shell = _ENV.shell + +local sandbox = Util.shallowCopy(_ENV) + +-- TODO: figure out how to better define scaling +local scale = .5 +local xs, ys = 6 * scale, 9 * scale + +local events = { + glasses_click = 'mouse_click', + glasses_up = 'mouse_up', + glasses_drag = 'mouse_drag', + glasses_scroll = 'mouse_scroll', +} + +local hookEvents = { 'glasses_click', 'glasses_up', 'glasses_drag', 'glasses_scroll' } + +local function hook(e, eventData) + local currentTab = kernel.getFocused() + local x = math.floor(eventData[2] / xs) + local y = math.floor(eventData[3] / ys) + local clickedTab + + for _,tab in ipairs(kernel.routines) do + if tab.window.type == 'glasses' then + local wx, wy = tab.window.getPosition() + local ww, wh = tab.window.getSize() + + if x >= wx and x <= wx + ww and y >= wy and y <= wy + wh then + clickedTab = tab + x = x - wx + y = y - wy + break + end + end + end + + if clickedTab then + if clickedTab ~= currentTab then + clickedTab.window.raise() + multishell.setFocus(clickedTab.uid) + end + + kernel.event(events[e], { + eventData[1], x, y, clickedTab.window.side, + }) + + end + return true +end + +local config = Config.load('nwm', { session = { } }) + +local function run(args) + local window = Glasses.create('glasses', args.x, args.y, args.w, args.h) + + local env = Util.shallowCopy(sandbox) + _G.requireInjector(env) + + multishell.openTab({ + path = args.path, + args = args.args, + env = env, + focused = false, + hidden = true, + onDestroy = function() + Util.removeByValue(config.session, args) + Config.update('nwm', config) + window.destroy() + end, + window = window, + }) +end + +kernel.hook(hookEvents, hook) + +UI:setPage(UI.Page { + form = UI.Form { + values = { + x = 1, y = 25, w = 51, h = 19, + }, + path = UI.TextEntry { + y = 5, + formKey = 'path', formLabel = 'Run', required = true, + }, + args = UI.TextEntry { + y = 7, + formKey = 'args', formLabel = 'Args', + }, + UI.Text { + x = 7, y = 5, + textColor = 'yellow', + value = ' x y' + }, + wx = UI.TextEntry { + x = 7, y = 6, width = 7, limit = 3, + transform = 'number', + formKey = 'x', required = true, + }, + wy = UI.TextEntry { + x = 15, y = 6, width = 7, limit = 4, + transform = 'number', + formKey = 'y', required = true, + }, + UI.Text { + x = 7, y = 8, + textColor = 'yellow', + value = ' width height' + }, + ww = UI.TextEntry { + x = 7, y = 9, width = 7, limit = 4, + transform = 'number', + formKey = 'w', required = true, + }, + wh = UI.TextEntry { + x = 15, y = 9, width = 7, limit = 4, + transform = 'number', + formKey = 'h', required = true, + }, + }, + notification = UI.Notification { }, + eventHandler = function(self, event) + if event.type == 'form_complete' then + local args = Util.shallowCopy(event.values) + args.path = shell.resolveProgram(args.path) + if not args.path then + self.notification:error('Invalid program') + else + if args.args then + args.args = Util.split(args.args, '(.-) ') + end + table.insert(config.session, args) + Config.update('nwm', config) + run(args) + self.notification:success('Started program') + end + end + return UI.Page.eventHandler(self, event) + end, +}) + +for _,v in pairs(config.session) do + run(v) +end + +UI:start() + +kernel.unhook(hookEvents, hook) diff --git a/neural/overlay.lua b/neural/overlay.lua deleted file mode 100644 index 83984c5..0000000 --- a/neural/overlay.lua +++ /dev/null @@ -1,116 +0,0 @@ -local Terminal = require('opus.terminal') - -local colors = _G.colors -local device = _G.device -local kernel = _G.kernel - ---[[ - Create a device for glasses - Usable as a redirect or UI target - - Example usage: - Files --display=glasses - debugMonitor glasses - - In a program: - local prev = term.redirect(device.glasses) - shell.run('shell') - term.redirect(prev) - - Glasses do not use the CC font - so extended chars - do not display correctly. -]] - --- configurable -local w, h = 46, 19 -local scale = .5 - -local glasses = device['plethora:glasses'] -local canvas = glasses.canvas() -local _, cy = 1, 1 -local _, gh = canvas:getSize() -local lines = { } -local map = { - ['0'] = 0xF0F0F0FF, - ['1'] = 0xF2B233FF, - ['2'] = 0xE57FD8FF, - ['3'] = 0x99B2F2FF, - ['4'] = 0xDEDE6CFF, - ['5'] = 0x7FCC19FF, - ['6'] = 0xF2B2CCFF, - ['7'] = 0x4C4C4CFF, - ['8'] = 0x999999FF, - ['9'] = 0x4C99B2FF, - ['a'] = 0xB266E5FF, - ['b'] = 0x3366CCFF, - ['c'] = 0x7F664CFF, - ['d'] = 0x57A64EFF, - ['e'] = 0xCC4C4CFF, - ['f'] = 0x191919FF, -- transparent -} - -local xs, ys = 6 * scale, 9 * scale - --- Position bottom left -local pos = { x = 1, y = gh - (h * ys) - 10 } -local group = canvas.addGroup(pos) - -for y = 1, h do - lines[y] = { - text = { }, - bg = { } - } - for x = 1, w do - lines[y].bg[x] = group.addRectangle(x * xs, y * ys, xs, ys, 0xF0F0F04F) - lines[y].text[x] = group.addText({ x * xs, y * ys }, '', 0x7FCC19FF) - lines[y].text[x].setScale(scale) - end -end - -device.glasses = Terminal.window({ - getSize = function() - return w, h - end, - isColor = function() - return true - end, - clear = function() - --canvas.clear() - end, - blit = function(text, fg, bg) - for x = 1, #text do - local ln = lines[cy] - ln.bg[x].setColor(map[bg:sub(x, x)]) - ln.text[x].setColor(map[fg:sub(x, x)]) - ln.text[x].setText(text:sub(x, x)) - end - end, - setCursorPos = function(_, y) - -- full lines are always blit - cy = y - end, - setBackgroundColor = function() - end, - setTextColor = function() - end, - setCursorBlink = function() - end, - getBackgroundColor = function() - return colors.black - end, - getTextColor = function() - return colors.white - end, -}, 1, 1, w, h, true) - -function device.glasses.setTextScale() end - -device.glasses.side = 'glasses' -device.glasses.type = 'glasses' -device.glasses.name = 'glasses' - -kernel.hook('glasses_click', function(_, eventData) - os.queueEvent('monitor_touch', 'glasses', - math.floor((eventData[2] - pos.x) / xs), - math.floor((eventData[3] - pos.y) / ys)) -end)