From 1eea0d7cd849d8b7da2e6deb3fbb1366c8dac8e3 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Sun, 21 Jan 2018 22:08:30 -0500 Subject: [PATCH] embedded windows --- sys/apis/ui.lua | 59 +++++ sys/apis/ui/canvas.lua | 8 +- sys/apis/util.lua | 8 +- sys/apps/Lua.lua | 529 ++++++++++++++++++++++------------------- 4 files changed, 357 insertions(+), 247 deletions(-) diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index e2815a7..497ad9a 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -3,6 +3,7 @@ local class = require('class') local Event = require('event') local Input = require('input') local Peripheral = require('peripheral') +local Terminal = require('terminal') local Transition = require('ui.transition') local Util = require('util') @@ -13,6 +14,7 @@ local device = _G.device local fs = _G.fs local os = _G.os local term = _G.term +local window = _G.window --[[ Using the shorthand window definition, elements are created from @@ -2405,6 +2407,63 @@ function UI.SlideOut:eventHandler(event) end end +--[[-- Embedded --]]-- +UI.Embedded = class(UI.Window) +UI.Embedded.defaults = { + UIElement = 'Embedded', + backgroundColor = colors.black, + textColor = colors.white, + accelerators = { + up = 'scroll_up', + down = 'scroll_down', + } +} + +function UI.Embedded:setParent() + UI.Window.setParent(self) + self.win = window.create(UI.term.device, 1, 1, self.width, self.height, false) + Canvas.convertWindow(self.win, UI.term.device, self.x, self.y) + Terminal.scrollable(self.win, 100) + + local canvas = self:getCanvas() + self.win.canvas.parent = canvas + table.insert(canvas.layers, self.win.canvas) + self.canvas = self.win.canvas + + self.win.setCursorPos(1, 1) + self.win.setBackgroundColor(self.backgroundColor) + self.win.setTextColor(self.textColor) + self.win.clear() +end + +function UI.Embedded:draw() + self.canvas:dirty() +end + +function UI.Embedded:enable() + self.canvas:setVisible(true) + UI.Window.enable(self) +end + +function UI.Embedded:disable() + self.canvas:setVisible(false) + UI.Window.disable(self) +end + +function UI.Embedded:eventHandler(event) + if event.type == 'scroll_up' then + self.win.scrollUp() + return true + elseif event.type == 'scroll_down' then + self.win.scrollDown() + return true + end +end + +function UI.Embedded:focus() + -- allow scrolling +end + --[[-- Notification --]]-- UI.Notification = class(UI.Window) UI.Notification.defaults = { diff --git a/sys/apis/ui/canvas.lua b/sys/apis/ui/canvas.lua index d99a0d1..38e9898 100644 --- a/sys/apis/ui/canvas.lua +++ b/sys/apis/ui/canvas.lua @@ -129,7 +129,6 @@ end function Canvas:writeBlit(x, y, text, bg, fg) if y > 0 and y <= self.height and x <= self.width then - local width = #text -- fix ffs @@ -342,6 +341,7 @@ function Canvas.convertWindow(win, parent, wx, wy) str, win.getBackgroundColor(), win.getTextColor()) + win.setCursorPos(x + #str, y) end function win.blit(text, fg, bg) @@ -353,8 +353,10 @@ function Canvas.convertWindow(win, parent, wx, wy) win.canvas:redraw(parent) end - function win.scroll() - error('scroll: not implemented') + function win.scroll(n) + table.insert(win.canvas.lines, table.remove(win.canvas.lines, 1)) + win.canvas.lines[#win.canvas.lines].text = _rep(' ', win.canvas.width) + win.canvas:dirty() end function win.reposition(x, y, width, height) diff --git a/sys/apis/util.lua b/sys/apis/util.lua index 6dc6bce..267b8db 100644 --- a/sys/apis/util.lua +++ b/sys/apis/util.lua @@ -55,11 +55,11 @@ function Util.tostring(pattern, ...) end str = str .. string.format(' %s: %s\n', k, value) end - if #str < width then - str = str:gsub('\n', '') .. ' }' - else + --if #str < width then + --str = str:gsub('\n', '') .. ' }' + --else str = str .. '}' - end + --end return str end diff --git a/sys/apps/Lua.lua b/sys/apps/Lua.lua index 41b6867..94c0fe2 100644 --- a/sys/apps/Lua.lua +++ b/sys/apps/Lua.lua @@ -8,6 +8,7 @@ local Util = require('util') local colors = _G.colors local os = _G.os local textutils = _G.textutils +local term = _G.term local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) sandboxEnv.exit = function() Event.exitPullEvents() end @@ -21,304 +22,352 @@ local history = History.load('usr/.lua_history', 25) local extChars = Util.getVersion() > 1.76 local page = UI.Page { - menuBar = UI.MenuBar { - buttons = { - { text = 'Local', event = 'local' }, - { text = 'Global', event = 'global' }, - { text = 'Device', event = 'device', name = 'Device' }, - }, - }, - prompt = UI.TextEntry { - y = 2, - shadowText = 'enter command', - limit = 256, - accelerators = { - enter = 'command_enter', - up = 'history_back', - down = 'history_forward', - mouse_rightclick = 'clear_prompt', - [ 'control-space' ] = 'autocomplete', - }, - }, - indicator = UI.Text { - backgroundColor = colors.black, - y = 2, x = -1, width = 1, - }, - grid = UI.ScrollingGrid { - y = 3, - columns = { - { heading = 'Key', key = 'name' }, - { heading = 'Value', key = 'value' }, - }, - sortColumn = 'name', - autospace = true, - }, - notification = UI.Notification(), + menuBar = UI.MenuBar { + buttons = { + { text = 'Local', event = 'local' }, + { text = 'Global', event = 'global' }, + { text = 'Device', event = 'device', name = 'Device' }, + }, + }, + prompt = UI.TextEntry { + y = 2, + shadowText = 'enter command', + limit = 256, + accelerators = { + enter = 'command_enter', + up = 'history_back', + down = 'history_forward', + mouse_rightclick = 'clear_prompt', + [ 'control-space' ] = 'autocomplete', + }, + }, + indicator = UI.Text { + backgroundColor = colors.black, + y = 2, x = -1, width = 1, + }, + grid = UI.ScrollingGrid { + y = 3, ey = -2, + columns = { + { heading = 'Key', key = 'name' }, + { heading = 'Value', key = 'value' }, + }, + sortColumn = 'name', + autospace = true, + }, + titleBar = UI.TitleBar { + title = 'Output', + y = -1, + event = 'show_output', + closeInd = '^' + }, + output = UI.Embedded { + y = -6, + }, + --notification = UI.Notification(), } function page.indicator:showResult(s) - local values = { - [ true ] = { c = colors.green, i = (extChars and '\003') or '+' }, - [ false ] = { c = colors.red, i = 'x' } - } + local values = { + [ true ] = { c = colors.green, i = (extChars and '\003') or '+' }, + [ false ] = { c = colors.red, i = 'x' } + } - self.textColor = values[s].c - self.value = values[s].i - self:draw() + self.textColor = values[s].c + self.value = values[s].i + self:draw() end function page:setPrompt(value, focus) - self.prompt:setValue(value) - self.prompt.scroll = 0 - self.prompt:setPosition(#value) - self.prompt:updateScroll() + self.prompt:setValue(value) + self.prompt.scroll = 0 + self.prompt:setPosition(#value) + self.prompt:updateScroll() - if value:sub(-1) == ')' then - self.prompt:setPosition(#value - 1) - end + if value:sub(-1) == ')' then + self.prompt:setPosition(#value - 1) + end - self.prompt:draw() - if focus then - page:setFocus(self.prompt) - end + self.prompt:draw() + if focus then + page:setFocus(self.prompt) + end end function page:enable() - self:setFocus(self.prompt) - UI.Page.enable(self) + self:setFocus(self.prompt) + UI.Page.enable(self) + self.output:disable() end local function autocomplete(env, oLine, x) - local sLine = oLine:sub(1, x) - local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$") - if nStartPos then - sLine = sLine:sub(nStartPos) - end + local sLine = oLine:sub(1, x) + local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$") + if nStartPos then + sLine = sLine:sub(nStartPos) + end - if #sLine > 0 then - local results = textutils.complete(sLine, env) + if #sLine > 0 then + local results = textutils.complete(sLine, env) - if #results == 1 then - return Util.insertString(oLine, results[1], x + 1) + if #results == 1 then + return Util.insertString(oLine, results[1], x + 1) - elseif #results > 1 then - local prefix = results[1] - for n = 1, #results do - local result = results[n] - while #prefix > 0 do - if result:find(prefix, 1, true) == 1 then - break - end - prefix = prefix:sub(1, #prefix - 1) - end - end - if #prefix > 0 then - return Util.insertString(oLine, prefix, x + 1) - end - end - end - return oLine + elseif #results > 1 then + local prefix = results[1] + for n = 1, #results do + local result = results[n] + while #prefix > 0 do + if result:find(prefix, 1, true) == 1 then + break + end + prefix = prefix:sub(1, #prefix - 1) + end + end + if #prefix > 0 then + return Util.insertString(oLine, prefix, x + 1) + end + end + end + return oLine end function page:eventHandler(event) - if event.type == 'global' then - self:setPrompt('', true) - self:executeStatement('_G') - command = nil + if event.type == 'global' then + self:setPrompt('', true) + self:executeStatement('_G') + command = nil - elseif event.type == 'local' then - self:setPrompt('', true) - self:executeStatement('_ENV') - command = nil + elseif event.type == 'local' then + self:setPrompt('', true) + self:executeStatement('_ENV') + command = nil - elseif event.type == 'autocomplete' then - local sz = #self.prompt.value - local pos = self.prompt.pos - self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos)) - self.prompt:setPosition(pos + #self.prompt.value - sz) - self.prompt:updateCursor() + elseif event.type == 'hide_output' then + self.output:disable() - elseif event.type == 'device' then - self:setPrompt('device', true) - self:executeStatement('device') + self.titleBar.oy = -1 + self.titleBar.event = 'show_output' + self.titleBar.closeInd = '^' + self.titleBar:resize() - elseif event.type == 'history_back' then - local value = history:back() - if value then - self:setPrompt(value) - end + self.grid.ey = -2 + self.grid:resize() - elseif event.type == 'history_forward' then - self:setPrompt(history:forward() or '') + self:draw() - elseif event.type == 'clear_prompt' then - self:setPrompt('') - history:reset() + elseif event.type == 'show_output' then + self.output:enable() - elseif event.type == 'command_enter' then - local s = tostring(self.prompt.value) + self.titleBar.oy = -7 + self.titleBar.event = 'hide_output' + self.titleBar.closeInd = 'v' + self.titleBar:resize() - if #s > 0 then - history:add(s) - history:back() - self:executeStatement(s) - else - local t = { } - for k = #history.entries, 1, -1 do - table.insert(t, { - name = #t + 1, - value = history.entries[k], - isHistory = true, - pos = k, - }) - end - history:reset() - command = nil - self.grid:setValues(t) - self.grid:setIndex(1) - self.grid:adjustWidth() - self:draw() - end - return true + self.grid.ey = -8 + self.grid:resize() - else - return UI.Page.eventHandler(self, event) - end - return true + self:draw() + + elseif event.type == 'autocomplete' then + local sz = #self.prompt.value + local pos = self.prompt.pos + self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos)) + self.prompt:setPosition(pos + #self.prompt.value - sz) + self.prompt:updateCursor() + + elseif event.type == 'device' then + self:setPrompt('device', true) + self:executeStatement('device') + + elseif event.type == 'history_back' then + local value = history:back() + if value then + self:setPrompt(value) + end + + elseif event.type == 'history_forward' then + self:setPrompt(history:forward() or '') + + elseif event.type == 'clear_prompt' then + self:setPrompt('') + history:reset() + + elseif event.type == 'command_enter' then + local s = tostring(self.prompt.value) + + if #s > 0 then + history:add(s) + history:back() + self:executeStatement(s) + else + local t = { } + for k = #history.entries, 1, -1 do + table.insert(t, { + name = #t + 1, + value = history.entries[k], + isHistory = true, + pos = k, + }) + end + history:reset() + command = nil + self.grid:setValues(t) + self.grid:setIndex(1) + self.grid:adjustWidth() + self:draw() + end + return true + + else + return UI.Page.eventHandler(self, event) + end + return true end function page:setResult(result) - local t = { } + local t = { } - local function safeValue(v) - if type(v) == 'string' or type(v) == 'number' then - return v - end - return tostring(v) - end + local oterm = term.redirect(self.output.win) + Util.print(result) + term.redirect(oterm) - if type(result) == 'table' then - for k,v in pairs(result) do - local entry = { - name = safeValue(k), - rawName = k, - value = safeValue(v), - rawValue = v, - } - if type(v) == 'table' then - if Util.size(v) == 0 then - entry.value = 'table: (empty)' - else - entry.value = tostring(v) - end - end - table.insert(t, entry) - end - else - table.insert(t, { - name = type(result), - value = tostring(result), - rawValue = result, - }) - end - self.grid:setValues(t) - self.grid:setIndex(1) - self.grid:adjustWidth() - self:draw() + local function safeValue(v) + if type(v) == 'string' or type(v) == 'number' then + return v + end + return tostring(v) + end + + if type(result) == 'table' then + for k,v in pairs(result) do + local entry = { + name = safeValue(k), + rawName = k, + value = safeValue(v), + rawValue = v, + } + if type(v) == 'table' then + if Util.size(v) == 0 then + entry.value = 'table: (empty)' + else + entry.value = tostring(v) + end + end + table.insert(t, entry) + end + else + table.insert(t, { + name = type(result), + value = tostring(result), + rawValue = result, + }) + end + self.grid:setValues(t) + self.grid:setIndex(1) + self.grid:adjustWidth() + self:draw() end function page.grid:eventHandler(event) - local entry = self:getSelected() + local entry = self:getSelected() - local function commandAppend() - if entry.isHistory then - --history.setPosition(entry.pos) - return entry.value - end - if type(entry.rawValue) == 'function' then - if command then - return command .. '.' .. entry.name .. '()' - end - return entry.name .. '()' - end - if command then - if type(entry.rawName) == 'number' then - return command .. '[' .. entry.name .. ']' - end - if entry.name:match("%W") or - entry.name:sub(1, 1):match("%d") then - return command .. "['" .. tostring(entry.name) .. "']" - end - return command .. '.' .. entry.name - end - return entry.name - end + local function commandAppend() + if entry.isHistory then + --history.setPosition(entry.pos) + return entry.value + end + if type(entry.rawValue) == 'function' then + if command then + return command .. '.' .. entry.name .. '()' + end + return entry.name .. '()' + end + if command then + if type(entry.rawName) == 'number' then + return command .. '[' .. entry.name .. ']' + end + if entry.name:match("%W") or + entry.name:sub(1, 1):match("%d") then + return command .. "['" .. tostring(entry.name) .. "']" + end + return command .. '.' .. entry.name + end + return entry.name + end - if event.type == 'grid_focus_row' then - if self.focused then - page:setPrompt(commandAppend()) - end - elseif event.type == 'grid_select' then - page:setPrompt(commandAppend(), true) - page:executeStatement(commandAppend()) + if event.type == 'grid_focus_row' then + if self.focused then + page:setPrompt(commandAppend()) + end + elseif event.type == 'grid_select' then + page:setPrompt(commandAppend(), true) + page:executeStatement(commandAppend()) - elseif event.type == 'copy' then - if entry then - os.queueEvent('clipboard_copy', entry.rawValue) - end - else - return UI.ScrollingGrid.eventHandler(self, event) - end - return true + elseif event.type == 'copy' then + if entry then + os.queueEvent('clipboard_copy', entry.rawValue) + end + else + return UI.ScrollingGrid.eventHandler(self, event) + end + return true end function page:rawExecute(s) - local fn, m + local fn, m - fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv) + fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv) - if fn then - fn = load('return {' ..s.. '}', 'lua', nil, sandboxEnv) - end + if fn then + fn = load('return {' ..s.. '}', 'lua', nil, sandboxEnv) + end - if fn then - fn, m = pcall(fn) - if #m == 1 then - m = m[1] - end - return fn, m - end + if fn then + fn, m = pcall(fn) + if #m == 1 then + m = m[1] + end + return fn, m + end - fn, m = load(s, 'lua', nil, sandboxEnv) - if fn then - fn, m = pcall(fn) - end + fn, m = load(s, 'lua', nil, sandboxEnv) + if fn then + fn, m = pcall(fn) + end - return fn, m + return fn, m end function page:executeStatement(statement) - command = statement + command = statement - local s, m = self:rawExecute(command) + local oterm = term.redirect(self.output.win) + local s, m = self:rawExecute(command) + if not s then + _G.printError(m) + end + term.redirect(oterm) - if s and m then - self:setResult(m) - else - self.grid:setValues({ }) - self.grid:draw() - if m then - self.notification:error(m, 5) - end - end - self.indicator:showResult(not not s) + if s and m then + self:setResult(m) + else + self.grid:setValues({ }) + self.grid:draw() + if m then + if not self.output.enabled then + self:emit({ type = 'show_output' }) + end + --self.notification:error(m, 5) + end + end + self.indicator:showResult(not not s) end local args = { ... } if args[1] then - command = 'args[1]' - sandboxEnv.args = args - page:setResult(args[1]) + command = 'args[1]' + sandboxEnv.args = args + page:setResult(args[1]) end UI:setPage(page)