1
0
mirror of https://github.com/kepler155c/opus synced 2025-07-01 17:42:59 +00:00

embedded windows

This commit is contained in:
kepler155c@gmail.com 2018-01-21 22:08:30 -05:00
parent 8451c94abe
commit 1eea0d7cd8
4 changed files with 357 additions and 247 deletions

View File

@ -3,6 +3,7 @@ local class = require('class')
local Event = require('event') local Event = require('event')
local Input = require('input') local Input = require('input')
local Peripheral = require('peripheral') local Peripheral = require('peripheral')
local Terminal = require('terminal')
local Transition = require('ui.transition') local Transition = require('ui.transition')
local Util = require('util') local Util = require('util')
@ -13,6 +14,7 @@ local device = _G.device
local fs = _G.fs local fs = _G.fs
local os = _G.os local os = _G.os
local term = _G.term local term = _G.term
local window = _G.window
--[[ --[[
Using the shorthand window definition, elements are created from Using the shorthand window definition, elements are created from
@ -2405,6 +2407,63 @@ function UI.SlideOut:eventHandler(event)
end end
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 --]]-- --[[-- Notification --]]--
UI.Notification = class(UI.Window) UI.Notification = class(UI.Window)
UI.Notification.defaults = { UI.Notification.defaults = {

View File

@ -129,7 +129,6 @@ end
function Canvas:writeBlit(x, y, text, bg, fg) function Canvas:writeBlit(x, y, text, bg, fg)
if y > 0 and y <= self.height and x <= self.width then if y > 0 and y <= self.height and x <= self.width then
local width = #text local width = #text
-- fix ffs -- fix ffs
@ -342,6 +341,7 @@ function Canvas.convertWindow(win, parent, wx, wy)
str, str,
win.getBackgroundColor(), win.getBackgroundColor(),
win.getTextColor()) win.getTextColor())
win.setCursorPos(x + #str, y)
end end
function win.blit(text, fg, bg) function win.blit(text, fg, bg)
@ -353,8 +353,10 @@ function Canvas.convertWindow(win, parent, wx, wy)
win.canvas:redraw(parent) win.canvas:redraw(parent)
end end
function win.scroll() function win.scroll(n)
error('scroll: not implemented') 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 end
function win.reposition(x, y, width, height) function win.reposition(x, y, width, height)

View File

@ -55,11 +55,11 @@ function Util.tostring(pattern, ...)
end end
str = str .. string.format(' %s: %s\n', k, value) str = str .. string.format(' %s: %s\n', k, value)
end end
if #str < width then --if #str < width then
str = str:gsub('\n', '') .. ' }' --str = str:gsub('\n', '') .. ' }'
else --else
str = str .. '}' str = str .. '}'
end --end
return str return str
end end

View File

@ -8,6 +8,7 @@ local Util = require('util')
local colors = _G.colors local colors = _G.colors
local os = _G.os local os = _G.os
local textutils = _G.textutils local textutils = _G.textutils
local term = _G.term
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
sandboxEnv.exit = function() Event.exitPullEvents() end 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 extChars = Util.getVersion() > 1.76
local page = UI.Page { local page = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Local', event = 'local' }, { text = 'Local', event = 'local' },
{ text = 'Global', event = 'global' }, { text = 'Global', event = 'global' },
{ text = 'Device', event = 'device', name = 'Device' }, { text = 'Device', event = 'device', name = 'Device' },
}, },
}, },
prompt = UI.TextEntry { prompt = UI.TextEntry {
y = 2, y = 2,
shadowText = 'enter command', shadowText = 'enter command',
limit = 256, limit = 256,
accelerators = { accelerators = {
enter = 'command_enter', enter = 'command_enter',
up = 'history_back', up = 'history_back',
down = 'history_forward', down = 'history_forward',
mouse_rightclick = 'clear_prompt', mouse_rightclick = 'clear_prompt',
[ 'control-space' ] = 'autocomplete', [ 'control-space' ] = 'autocomplete',
}, },
}, },
indicator = UI.Text { indicator = UI.Text {
backgroundColor = colors.black, backgroundColor = colors.black,
y = 2, x = -1, width = 1, y = 2, x = -1, width = 1,
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 3, y = 3, ey = -2,
columns = { columns = {
{ heading = 'Key', key = 'name' }, { heading = 'Key', key = 'name' },
{ heading = 'Value', key = 'value' }, { heading = 'Value', key = 'value' },
}, },
sortColumn = 'name', sortColumn = 'name',
autospace = true, autospace = true,
}, },
notification = UI.Notification(), titleBar = UI.TitleBar {
title = 'Output',
y = -1,
event = 'show_output',
closeInd = '^'
},
output = UI.Embedded {
y = -6,
},
--notification = UI.Notification(),
} }
function page.indicator:showResult(s) function page.indicator:showResult(s)
local values = { local values = {
[ true ] = { c = colors.green, i = (extChars and '\003') or '+' }, [ true ] = { c = colors.green, i = (extChars and '\003') or '+' },
[ false ] = { c = colors.red, i = 'x' } [ false ] = { c = colors.red, i = 'x' }
} }
self.textColor = values[s].c self.textColor = values[s].c
self.value = values[s].i self.value = values[s].i
self:draw() self:draw()
end end
function page:setPrompt(value, focus) function page:setPrompt(value, focus)
self.prompt:setValue(value) self.prompt:setValue(value)
self.prompt.scroll = 0 self.prompt.scroll = 0
self.prompt:setPosition(#value) self.prompt:setPosition(#value)
self.prompt:updateScroll() self.prompt:updateScroll()
if value:sub(-1) == ')' then if value:sub(-1) == ')' then
self.prompt:setPosition(#value - 1) self.prompt:setPosition(#value - 1)
end end
self.prompt:draw() self.prompt:draw()
if focus then if focus then
page:setFocus(self.prompt) page:setFocus(self.prompt)
end end
end end
function page:enable() function page:enable()
self:setFocus(self.prompt) self:setFocus(self.prompt)
UI.Page.enable(self) UI.Page.enable(self)
self.output:disable()
end end
local function autocomplete(env, oLine, x) local function autocomplete(env, oLine, x)
local sLine = oLine:sub(1, x) local sLine = oLine:sub(1, x)
local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$") local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$")
if nStartPos then if nStartPos then
sLine = sLine:sub(nStartPos) sLine = sLine:sub(nStartPos)
end end
if #sLine > 0 then if #sLine > 0 then
local results = textutils.complete(sLine, env) local results = textutils.complete(sLine, env)
if #results == 1 then if #results == 1 then
return Util.insertString(oLine, results[1], x + 1) return Util.insertString(oLine, results[1], x + 1)
elseif #results > 1 then elseif #results > 1 then
local prefix = results[1] local prefix = results[1]
for n = 1, #results do for n = 1, #results do
local result = results[n] local result = results[n]
while #prefix > 0 do while #prefix > 0 do
if result:find(prefix, 1, true) == 1 then if result:find(prefix, 1, true) == 1 then
break break
end end
prefix = prefix:sub(1, #prefix - 1) prefix = prefix:sub(1, #prefix - 1)
end end
end end
if #prefix > 0 then if #prefix > 0 then
return Util.insertString(oLine, prefix, x + 1) return Util.insertString(oLine, prefix, x + 1)
end end
end end
end end
return oLine return oLine
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'global' then if event.type == 'global' then
self:setPrompt('', true) self:setPrompt('', true)
self:executeStatement('_G') self:executeStatement('_G')
command = nil command = nil
elseif event.type == 'local' then elseif event.type == 'local' then
self:setPrompt('', true) self:setPrompt('', true)
self:executeStatement('_ENV') self:executeStatement('_ENV')
command = nil command = nil
elseif event.type == 'autocomplete' then elseif event.type == 'hide_output' then
local sz = #self.prompt.value self.output:disable()
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.titleBar.oy = -1
self:setPrompt('device', true) self.titleBar.event = 'show_output'
self:executeStatement('device') self.titleBar.closeInd = '^'
self.titleBar:resize()
elseif event.type == 'history_back' then self.grid.ey = -2
local value = history:back() self.grid:resize()
if value then
self:setPrompt(value)
end
elseif event.type == 'history_forward' then self:draw()
self:setPrompt(history:forward() or '')
elseif event.type == 'clear_prompt' then elseif event.type == 'show_output' then
self:setPrompt('') self.output:enable()
history:reset()
elseif event.type == 'command_enter' then self.titleBar.oy = -7
local s = tostring(self.prompt.value) self.titleBar.event = 'hide_output'
self.titleBar.closeInd = 'v'
self.titleBar:resize()
if #s > 0 then self.grid.ey = -8
history:add(s) self.grid:resize()
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 self:draw()
return UI.Page.eventHandler(self, event)
end elseif event.type == 'autocomplete' then
return true 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 end
function page:setResult(result) function page:setResult(result)
local t = { } local t = { }
local function safeValue(v) local oterm = term.redirect(self.output.win)
if type(v) == 'string' or type(v) == 'number' then Util.print(result)
return v term.redirect(oterm)
end
return tostring(v)
end
if type(result) == 'table' then local function safeValue(v)
for k,v in pairs(result) do if type(v) == 'string' or type(v) == 'number' then
local entry = { return v
name = safeValue(k), end
rawName = k, return tostring(v)
value = safeValue(v), end
rawValue = v,
} if type(result) == 'table' then
if type(v) == 'table' then for k,v in pairs(result) do
if Util.size(v) == 0 then local entry = {
entry.value = 'table: (empty)' name = safeValue(k),
else rawName = k,
entry.value = tostring(v) value = safeValue(v),
end rawValue = v,
end }
table.insert(t, entry) if type(v) == 'table' then
end if Util.size(v) == 0 then
else entry.value = 'table: (empty)'
table.insert(t, { else
name = type(result), entry.value = tostring(v)
value = tostring(result), end
rawValue = result, end
}) table.insert(t, entry)
end end
self.grid:setValues(t) else
self.grid:setIndex(1) table.insert(t, {
self.grid:adjustWidth() name = type(result),
self:draw() value = tostring(result),
rawValue = result,
})
end
self.grid:setValues(t)
self.grid:setIndex(1)
self.grid:adjustWidth()
self:draw()
end end
function page.grid:eventHandler(event) function page.grid:eventHandler(event)
local entry = self:getSelected() local entry = self:getSelected()
local function commandAppend() local function commandAppend()
if entry.isHistory then if entry.isHistory then
--history.setPosition(entry.pos) --history.setPosition(entry.pos)
return entry.value return entry.value
end end
if type(entry.rawValue) == 'function' then if type(entry.rawValue) == 'function' then
if command then if command then
return command .. '.' .. entry.name .. '()' return command .. '.' .. entry.name .. '()'
end end
return entry.name .. '()' return entry.name .. '()'
end end
if command then if command then
if type(entry.rawName) == 'number' then if type(entry.rawName) == 'number' then
return command .. '[' .. entry.name .. ']' return command .. '[' .. entry.name .. ']'
end end
if entry.name:match("%W") or if entry.name:match("%W") or
entry.name:sub(1, 1):match("%d") then entry.name:sub(1, 1):match("%d") then
return command .. "['" .. tostring(entry.name) .. "']" return command .. "['" .. tostring(entry.name) .. "']"
end end
return command .. '.' .. entry.name return command .. '.' .. entry.name
end end
return entry.name return entry.name
end end
if event.type == 'grid_focus_row' then if event.type == 'grid_focus_row' then
if self.focused then if self.focused then
page:setPrompt(commandAppend()) page:setPrompt(commandAppend())
end end
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
page:setPrompt(commandAppend(), true) page:setPrompt(commandAppend(), true)
page:executeStatement(commandAppend()) page:executeStatement(commandAppend())
elseif event.type == 'copy' then elseif event.type == 'copy' then
if entry then if entry then
os.queueEvent('clipboard_copy', entry.rawValue) os.queueEvent('clipboard_copy', entry.rawValue)
end end
else else
return UI.ScrollingGrid.eventHandler(self, event) return UI.ScrollingGrid.eventHandler(self, event)
end end
return true return true
end end
function page:rawExecute(s) 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 if fn then
fn = load('return {' ..s.. '}', 'lua', nil, sandboxEnv) fn = load('return {' ..s.. '}', 'lua', nil, sandboxEnv)
end end
if fn then if fn then
fn, m = pcall(fn) fn, m = pcall(fn)
if #m == 1 then if #m == 1 then
m = m[1] m = m[1]
end end
return fn, m return fn, m
end end
fn, m = load(s, 'lua', nil, sandboxEnv) fn, m = load(s, 'lua', nil, sandboxEnv)
if fn then if fn then
fn, m = pcall(fn) fn, m = pcall(fn)
end end
return fn, m return fn, m
end end
function page:executeStatement(statement) 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 if s and m then
self:setResult(m) self:setResult(m)
else else
self.grid:setValues({ }) self.grid:setValues({ })
self.grid:draw() self.grid:draw()
if m then if m then
self.notification:error(m, 5) if not self.output.enabled then
end self:emit({ type = 'show_output' })
end end
self.indicator:showResult(not not s) --self.notification:error(m, 5)
end
end
self.indicator:showResult(not not s)
end end
local args = { ... } local args = { ... }
if args[1] then if args[1] then
command = 'args[1]' command = 'args[1]'
sandboxEnv.args = args sandboxEnv.args = args
page:setResult(args[1]) page:setResult(args[1])
end end
UI:setPage(page) UI:setPage(page)