Compare commits
14 Commits
ui-enhance
...
master-1.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3a0222ef0 | ||
|
|
136adff7b1 | ||
|
|
60ab8377b5 | ||
|
|
6a58db5e1c | ||
|
|
d692116631 | ||
|
|
af1ef38147 | ||
|
|
58c3a1529f | ||
|
|
6f87c5a8a7 | ||
|
|
7891f9863a | ||
|
|
0fd349a487 | ||
|
|
cef5b21921 | ||
|
|
ee6af86da8 | ||
|
|
16843bdb78 | ||
|
|
f21ff42e44 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1 @@
|
||||
/ignore
|
||||
.project
|
||||
|
||||
@@ -82,60 +82,16 @@ local Browser = UI.Page {
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2, ey = -2,
|
||||
sortCompare = function(self, a, b)
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end,
|
||||
getRowTextColor = function(_, file)
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'copy' then -- let copy be handled by parent
|
||||
return false
|
||||
end
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'totalSize', width = 6 },
|
||||
},
|
||||
draw = function(self)
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
end,
|
||||
},
|
||||
question = UI.Question {
|
||||
y = -2, x = -19,
|
||||
label = 'Delete',
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
associations = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Save', event = 'save' },
|
||||
@@ -143,7 +99,7 @@ local Browser = UI.Page {
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -6, y = 3, ey = -8,
|
||||
x = 2, ex = -6, y = 3, ey = -5,
|
||||
columns = {
|
||||
{ heading = 'Extension', key = 'name' },
|
||||
{ heading = 'Program', key = 'value' },
|
||||
@@ -158,11 +114,8 @@ local Browser = UI.Page {
|
||||
x = -4, y = 6,
|
||||
text = '-', event = 'remove_entry', help = 'Remove',
|
||||
},
|
||||
[1] = UI.Window {
|
||||
x = 2, y = -6, ex = -6, ey = -3,
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 3, y = -5, ex = -7, ey = -3,
|
||||
x = 3, y = -3, ey = -2,
|
||||
margin = 1,
|
||||
manualControls = true,
|
||||
[1] = UI.TextEntry {
|
||||
@@ -184,7 +137,9 @@ local Browser = UI.Page {
|
||||
text = 'Add', event = 'add_association',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'quit',
|
||||
@@ -220,6 +175,51 @@ function Browser.menuBar:getActive(menuItem)
|
||||
return true
|
||||
end
|
||||
|
||||
function Browser.grid:sortCompare(a, b)
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end
|
||||
|
||||
function Browser.grid:getRowTextColor(file)
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end
|
||||
|
||||
function Browser.grid:eventHandler(event)
|
||||
if event.type == 'copy' then -- let copy be handled by parent
|
||||
return false
|
||||
end
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function Browser.statusBar:draw()
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Browser:setStatus(status, ...)
|
||||
self.notification:info(string.format(status, ...))
|
||||
end
|
||||
@@ -255,6 +255,7 @@ function Browser:getDirectory(directory)
|
||||
end
|
||||
|
||||
function Browser:updateDirectory(dir)
|
||||
|
||||
dir.size = 0
|
||||
dir.totalSize = 0
|
||||
Util.clear(dir.files)
|
||||
@@ -343,7 +344,7 @@ function Browser:eventHandler(event)
|
||||
local file = self.grid:getSelected()
|
||||
|
||||
if event.type == 'quit' then
|
||||
UI:quit()
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'edit' and file then
|
||||
self:run('edit', file.name)
|
||||
@@ -431,25 +432,28 @@ function Browser:eventHandler(event)
|
||||
|
||||
elseif event.type == 'delete' then
|
||||
if self:hasMarked() then
|
||||
self.question:show()
|
||||
local width = self.statusBar:getColumnWidth('status')
|
||||
self.statusBar:setColumnWidth('status', UI.term.width)
|
||||
self.statusBar:setValue('status', 'Delete marked? (y/n)')
|
||||
self.statusBar:draw()
|
||||
self.statusBar:sync()
|
||||
local _, ch = os.pullEvent('char')
|
||||
if ch == 'y' or ch == 'Y' then
|
||||
for _,m in pairs(marked) do
|
||||
pcall(function()
|
||||
fs.delete(m.fullName)
|
||||
end)
|
||||
end
|
||||
end
|
||||
marked = { }
|
||||
self.statusBar:setColumnWidth('status', width)
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self:updateDirectory(self.dir)
|
||||
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
return true
|
||||
|
||||
elseif event.type == 'question_yes' then
|
||||
for _,m in pairs(marked) do
|
||||
pcall(fs.delete, m.fullName)
|
||||
end
|
||||
marked = { }
|
||||
self:updateDirectory(self.dir)
|
||||
|
||||
self.question:hide()
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
|
||||
elseif event.type == 'question_no' then
|
||||
self.question:hide()
|
||||
self:setFocus(self.grid)
|
||||
|
||||
elseif event.type == 'copy' or event.type == 'cut' then
|
||||
if self:hasMarked() then
|
||||
@@ -545,4 +549,6 @@ local args = Util.parse(...)
|
||||
Browser:setDir(args[1] or shell.dir())
|
||||
|
||||
UI:setPage(Browser)
|
||||
UI:start()
|
||||
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local help = _G.help
|
||||
|
||||
UI:configure('Help', ...)
|
||||
|
||||
local topics = { }
|
||||
for _,topic in pairs(help.topics()) do
|
||||
table.insert(topics, { name = topic, lname = topic:lower() })
|
||||
if help.lookup(topic) then
|
||||
table.insert(topics, { name = topic })
|
||||
end
|
||||
end
|
||||
|
||||
UI:addPage('main', UI.Page {
|
||||
UI.Text {
|
||||
local page = UI.Page {
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Search',
|
||||
},
|
||||
UI.TextEntry {
|
||||
filter = UI.TextEntry {
|
||||
x = 10, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
transform = 'lowercase',
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 4,
|
||||
@@ -25,71 +29,76 @@ UI:addPage('main', UI.Page {
|
||||
columns = {
|
||||
{ heading = 'Topic', key = 'name' },
|
||||
},
|
||||
sortColumn = 'lname',
|
||||
sortColumn = 'name',
|
||||
},
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'quit',
|
||||
enter = 'grid_select',
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'quit' then
|
||||
UI:quit()
|
||||
}
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
if self.grid:getSelected() then
|
||||
UI:setPage('topic', self.grid:getSelected().name)
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
if not event.text then
|
||||
self.grid.values = topics
|
||||
else
|
||||
self.grid.values = { }
|
||||
for _,f in pairs(topics) do
|
||||
if string.find(f.lname, event.text:lower()) then
|
||||
table.insert(self.grid.values, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
UI:addPage('topic', UI.Page {
|
||||
backgroundColor = 'black',
|
||||
local topicPage = UI.Page {
|
||||
backgroundColor = colors.black,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'text',
|
||||
event = 'back',
|
||||
},
|
||||
helpText = UI.TextArea {
|
||||
backgroundColor = colors.black,
|
||||
x = 2, ex = -1, y = 3, ey = -2,
|
||||
},
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
enable = function(self, name)
|
||||
local f = help.lookup(name)
|
||||
}
|
||||
|
||||
self.titleBar.title = name
|
||||
self.helpText:setText(f and Util.readFile(f) or 'No help available for ' .. name)
|
||||
function topicPage:enable(name)
|
||||
local f = help.lookup(name)
|
||||
|
||||
return UI.Page.enable(self)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'back' then
|
||||
UI:setPage('main')
|
||||
self.titleBar.title = name
|
||||
self.helpText:setText(f and Util.readFile(f) or 'No help available for ' .. name)
|
||||
|
||||
return UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function topicPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPage(page)
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
if self.grid:getSelected() then
|
||||
local name = self.grid:getSelected().name
|
||||
|
||||
UI:setPage(topicPage, name)
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
if #event.text == 0 then
|
||||
self.grid.values = topics
|
||||
else
|
||||
self.grid.values = { }
|
||||
for _,f in pairs(topics) do
|
||||
if string.find(f.name, event.text) then
|
||||
table.insert(self.grid.values, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local args = Util.parse(...)
|
||||
UI:setPage(args[1] and 'topic' or 'main', args[1])
|
||||
UI:start()
|
||||
UI:setPage(args[1] and topicPage or page, args[1])
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -58,16 +58,11 @@ local page = UI.Page {
|
||||
},
|
||||
[2] = UI.Tab {
|
||||
tabTitle = 'Output',
|
||||
backgroundColor = 'black',
|
||||
output = UI.Embedded {
|
||||
y = 2,
|
||||
visible = true,
|
||||
maxScroll = 1000,
|
||||
backgroundColor = 'black',
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
draw = function(self)
|
||||
self:write(1, 1, string.rep('\131', self.width), 'black', 'primary')
|
||||
self:drawChildren()
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -158,11 +153,10 @@ function page:eventHandler(event)
|
||||
self.tabs:selectTab(self.tabs[2])
|
||||
|
||||
elseif event.type == 'autocomplete' then
|
||||
local value = self.prompt.value or ''
|
||||
local sz = #value
|
||||
local sz = #self.prompt.value
|
||||
local pos = self.prompt.entry.pos
|
||||
self:setPrompt(autocomplete(sandboxEnv, value, self.prompt.entry.pos))
|
||||
self.prompt:setPosition(pos + #(self.prompt.value or '') - sz)
|
||||
self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.entry.pos))
|
||||
self.prompt:setPosition(pos + #self.prompt.value - sz)
|
||||
self.prompt:updateCursor()
|
||||
|
||||
elseif event.type == 'device' then
|
||||
@@ -183,7 +177,7 @@ function page:eventHandler(event)
|
||||
history:reset()
|
||||
|
||||
elseif event.type == 'command_enter' then
|
||||
local s = tostring(self.prompt.value or '')
|
||||
local s = tostring(self.prompt.value)
|
||||
|
||||
if #s > 0 then
|
||||
self:executeStatement(s)
|
||||
@@ -201,6 +195,7 @@ function page:eventHandler(event)
|
||||
command = nil
|
||||
self.grid:setValues(t)
|
||||
self.grid:setIndex(1)
|
||||
self.grid:adjustWidth()
|
||||
self:draw()
|
||||
end
|
||||
return true
|
||||
@@ -247,6 +242,7 @@ function page:setResult(result)
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
self.grid:setIndex(1)
|
||||
self.grid:adjustWidth()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
@@ -376,7 +372,7 @@ function page:executeStatement(statement)
|
||||
end
|
||||
|
||||
if _exit then
|
||||
UI:quit()
|
||||
UI:exitPullEvents()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -385,8 +381,7 @@ if args[1] then
|
||||
command = 'args[1]'
|
||||
sandboxEnv.args = args
|
||||
page:setResult(args[1])
|
||||
page:setPrompt(command)
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -4,6 +4,7 @@ local Socket = require('opus.socket')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
@@ -55,31 +56,6 @@ local page = UI.Page {
|
||||
columns = gridColumns,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
getRowTextColor = function(self, row, selected)
|
||||
if not row.active then
|
||||
return 'lightGray'
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row, selected)
|
||||
end,
|
||||
getDisplayValues = function(_, row)
|
||||
row = Util.shallowCopy(row)
|
||||
if row.uptime then
|
||||
if row.uptime < 60 then
|
||||
row.uptime = string.format("%ds", math.floor(row.uptime))
|
||||
elseif row.uptime < 3600 then
|
||||
row.uptime = string.format("%sm", math.floor(row.uptime / 60))
|
||||
else
|
||||
row.uptime = string.format("%sh", math.floor(row.uptime / 3600))
|
||||
end
|
||||
end
|
||||
if row.fuel then
|
||||
row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||
end
|
||||
if row.distance then
|
||||
row.distance = Util.toBytes(Util.round(row.distance, 1))
|
||||
end
|
||||
return row
|
||||
end,
|
||||
},
|
||||
ports = UI.SlideOut {
|
||||
titleBar = UI.TitleBar {
|
||||
@@ -96,22 +72,17 @@ local page = UI.Page {
|
||||
sortColumn = 'port',
|
||||
autospace = true,
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'grid_select' then
|
||||
shell.openForegroundTab('Sniff ' .. event.selected.port)
|
||||
end
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end,
|
||||
},
|
||||
help = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
x = 5, ex = -5, height = 8, y = -8,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Network Help',
|
||||
event = 'slide_hide',
|
||||
},
|
||||
text = UI.TextArea {
|
||||
x = 1, y = 2,
|
||||
marginLeft = 1,
|
||||
x = 2, y = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
value = [[
|
||||
|
||||
In order to connect to another computer:
|
||||
@@ -156,6 +127,13 @@ local function sendCommand(host, command)
|
||||
end
|
||||
end
|
||||
|
||||
function page.ports:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
shell.openForegroundTab('Sniff ' .. event.selected.port)
|
||||
end
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page.ports.grid:update()
|
||||
local transport = network:getTransport()
|
||||
|
||||
@@ -252,7 +230,7 @@ function page:eventHandler(event)
|
||||
Config.update('network', config)
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:quit()
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
@@ -265,6 +243,33 @@ function page.menuBar:getActive(menuItem)
|
||||
return menuItem.noCheck or not not t
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if not row.active then
|
||||
return colors.lightGray
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
if row.uptime then
|
||||
if row.uptime < 60 then
|
||||
row.uptime = string.format("%ds", math.floor(row.uptime))
|
||||
elseif row.uptime < 3600 then
|
||||
row.uptime = string.format("%sm", math.floor(row.uptime / 60))
|
||||
else
|
||||
row.uptime = string.format("%sh", math.floor(row.uptime / 3600))
|
||||
end
|
||||
end
|
||||
if row.fuel then
|
||||
row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||
end
|
||||
if row.distance then
|
||||
row.distance = Util.toBytes(Util.round(row.distance, 1))
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
@@ -290,4 +295,4 @@ if not device.wireless_modem then
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
local Alt = require('opus.alternate')
|
||||
local Array = require('opus.array')
|
||||
local class = require('opus.class')
|
||||
local Config = require('opus.config')
|
||||
local Event = require('opus.event')
|
||||
@@ -10,6 +9,7 @@ local Tween = require('opus.ui.tween')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
@@ -18,12 +18,6 @@ local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
local turtle = _G.turtle
|
||||
|
||||
--[[
|
||||
turtle: 39x13
|
||||
computer: 51x19
|
||||
pocket: 26x20
|
||||
]]
|
||||
|
||||
if not _ENV.multishell then
|
||||
error('multishell is required')
|
||||
end
|
||||
@@ -32,15 +26,6 @@ local REGISTRY_DIR = 'usr/.registry'
|
||||
local DEFAULT_ICON = NFT.parse("\0308\0317\153\153\153\153\153\
|
||||
\0307\0318\153\153\153\153\153\
|
||||
\0308\0317\153\153\153\153\153")
|
||||
local TRANS_ICON = NFT.parse("\0302\0312\32\32\32\32\32\
|
||||
\0302\0312\32\32\32\32\32\
|
||||
\0302\0312\32\32\32\32\32")
|
||||
|
||||
-- overview
|
||||
local uid = _ENV.multishell.getCurrent()
|
||||
device.keyboard.addHotkey('control-o', function()
|
||||
_ENV.multishell.setFocus(uid)
|
||||
end)
|
||||
|
||||
UI:configure('Overview', ...)
|
||||
|
||||
@@ -74,7 +59,6 @@ local function parseIcon(iconText)
|
||||
if icon.height > 3 or icon.width > 8 then
|
||||
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||
end
|
||||
NFT.transparency(icon)
|
||||
end
|
||||
return icon
|
||||
end)
|
||||
@@ -86,38 +70,45 @@ local function parseIcon(iconText)
|
||||
return s, m
|
||||
end
|
||||
|
||||
UI.VerticalTabBar = class(UI.TabBar)
|
||||
function UI.VerticalTabBar:setParent()
|
||||
self.x = 1
|
||||
self.width = 8
|
||||
self.height = nil
|
||||
self.ey = -2
|
||||
UI.TabBar.setParent(self)
|
||||
for k,c in pairs(self.children) do
|
||||
c.x = 1
|
||||
c.y = k + 1
|
||||
c.ox, c.oy = c.x, c.y
|
||||
c.ow = 8
|
||||
c.width = 8
|
||||
end
|
||||
end
|
||||
|
||||
local cx = 9
|
||||
local cy = 1
|
||||
|
||||
local page = UI.Page {
|
||||
container = UI.Viewport {
|
||||
x = 9, y = 1,
|
||||
},
|
||||
tabBar = UI.TabBar {
|
||||
ey = -2,
|
||||
width = 8,
|
||||
selectedBackgroundColor = 'primary',
|
||||
backgroundColor = 'tertiary',
|
||||
layout = function(self)
|
||||
self.height = nil
|
||||
UI.TabBar.layout(self)
|
||||
end,
|
||||
x = cx,
|
||||
y = cy,
|
||||
},
|
||||
tray = UI.Window {
|
||||
y = -1, width = 8,
|
||||
backgroundColor = 'tertiary',
|
||||
newApp = UI.FlatButton {
|
||||
x = 2,
|
||||
backgroundColor = colors.lightGray,
|
||||
newApp = UI.Button {
|
||||
text = '+', event = 'new',
|
||||
},
|
||||
mode = UI.FlatButton {
|
||||
x = 4,
|
||||
text = '=', event = 'display_mode',
|
||||
},
|
||||
help = UI.FlatButton {
|
||||
x = 6,
|
||||
text = '?', event = 'help',
|
||||
},
|
||||
--[[
|
||||
volume = UI.Button {
|
||||
x = 3,
|
||||
text = '\15', event = 'volume',
|
||||
},]]
|
||||
},
|
||||
editor = UI.SlideOut {
|
||||
y = -12, height = 12,
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Edit Application',
|
||||
event = 'slide_hide',
|
||||
@@ -125,7 +116,7 @@ local page = UI.Page {
|
||||
form = UI.Form {
|
||||
y = 2, ey = -2,
|
||||
[1] = UI.TextEntry {
|
||||
formLabel = 'Title', formKey = 'title', limit = 11, width = 13, help = 'Application title',
|
||||
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
|
||||
required = true,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
@@ -133,50 +124,23 @@ local page = UI.Page {
|
||||
required = true,
|
||||
},
|
||||
[3] = UI.TextEntry {
|
||||
formLabel = 'Category', formKey = 'category', limit = 6, width = 8, help = 'Category of application',
|
||||
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
|
||||
required = true,
|
||||
},
|
||||
editIcon = UI.Button {
|
||||
x = 11, y = 6,
|
||||
text = 'Edit', event = 'editIcon', help = 'Edit icon file',
|
||||
iconFile = UI.TextEntry {
|
||||
x = 11, ex = -12, y = 7,
|
||||
limit = 128, help = 'Path to icon file',
|
||||
shadowText = 'Path to icon file',
|
||||
},
|
||||
loadIcon = UI.Button {
|
||||
x = 11, y = 8,
|
||||
text = 'Load', event = 'loadIcon', help = 'Load icon file',
|
||||
},
|
||||
helpIcon = UI.Button {
|
||||
x = 11, y = 8,
|
||||
x = 11, y = 9,
|
||||
text = 'Load', event = 'loadIcon', help = 'Load icon file',
|
||||
},
|
||||
image = UI.NftImage {
|
||||
backgroundColor = 'black',
|
||||
y = 6, x = 2, height = 3, width = 8,
|
||||
backgroundColor = colors.black,
|
||||
y = 7, x = 2, height = 3, width = 8,
|
||||
},
|
||||
},
|
||||
file_open = UI.FileSelect {
|
||||
modal = true,
|
||||
enable = function() end,
|
||||
transitionHint = 'expandUp',
|
||||
show = function(self)
|
||||
UI.FileSelect.enable(self)
|
||||
self:focusFirst()
|
||||
self:draw()
|
||||
end,
|
||||
disable = function(self)
|
||||
UI.FileSelect.disable(self)
|
||||
self.parent:focusFirst()
|
||||
-- need to recapture as we are opening a modal within another modal
|
||||
self.parent:capture(self.parent)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'select_cancel' then
|
||||
self:disable()
|
||||
elseif event.type == 'select_file' then
|
||||
self:disable()
|
||||
end
|
||||
return UI.FileSelect.eventHandler(self, event)
|
||||
end,
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
statusBar = UI.StatusBar(),
|
||||
},
|
||||
@@ -187,7 +151,6 @@ local page = UI.Page {
|
||||
f = 'files',
|
||||
s = 'shell',
|
||||
l = 'lua',
|
||||
n = 'network',
|
||||
[ 'control-n' ] = 'new',
|
||||
delete = 'delete',
|
||||
},
|
||||
@@ -235,7 +198,7 @@ local function loadApplications()
|
||||
return requirements[a.requires]
|
||||
end
|
||||
|
||||
return true
|
||||
return true -- Util.startsWith(a.run, 'http') or shell.resolveProgram(a.run)
|
||||
end)
|
||||
|
||||
local categories = { }
|
||||
@@ -245,7 +208,6 @@ local function loadApplications()
|
||||
categories[f.category] = true
|
||||
table.insert(buttons, {
|
||||
text = f.category,
|
||||
width = 8,
|
||||
selected = config.currentCategory == f.category
|
||||
})
|
||||
end
|
||||
@@ -253,13 +215,13 @@ local function loadApplications()
|
||||
table.sort(buttons, function(a, b) return a.text < b.text end)
|
||||
table.insert(buttons, 1, { text = 'Recent' })
|
||||
|
||||
for k,v in pairs(buttons) do
|
||||
v.x = 1
|
||||
v.y = k + 1
|
||||
end
|
||||
Util.removeByValue(page.children, page.tabBar)
|
||||
|
||||
page.tabBar.children = { }
|
||||
page.tabBar:addButtons(buttons)
|
||||
page:add {
|
||||
tabBar = UI.VerticalTabBar {
|
||||
buttons = buttons,
|
||||
},
|
||||
}
|
||||
|
||||
--page.tabBar:selectTab(config.currentCategory or 'Apps')
|
||||
page.container:setCategory(config.currentCategory or 'Apps')
|
||||
@@ -274,6 +236,7 @@ UI.Icon.defaults = {
|
||||
function UI.Icon:eventHandler(event)
|
||||
if event.type == 'mouse_click' then
|
||||
self:setFocus(self.button)
|
||||
--self:emit({ type = self.button.event, button = self.button })
|
||||
return true
|
||||
elseif event.type == 'mouse_doubleclick' then
|
||||
self:emit({ type = self.button.event, button = self.button })
|
||||
@@ -289,23 +252,37 @@ function page.container:setCategory(categoryName, animate)
|
||||
self.children = { }
|
||||
self:reset()
|
||||
|
||||
local filtered = { }
|
||||
local function filter(it, f)
|
||||
local ot = { }
|
||||
for _,v in pairs(it) do
|
||||
if f(v) then
|
||||
table.insert(ot, v)
|
||||
end
|
||||
end
|
||||
return ot
|
||||
end
|
||||
|
||||
local filtered
|
||||
|
||||
if categoryName == 'Recent' then
|
||||
filtered = { }
|
||||
|
||||
for _,v in ipairs(config.Recent) do
|
||||
local app = Util.find(applications, 'key', v)
|
||||
if app then
|
||||
if app then -- and fs.exists(app.run) then
|
||||
table.insert(filtered, app)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
filtered = Array.filter(applications, function(a)
|
||||
return a.category == categoryName
|
||||
filtered = filter(applications, function(a)
|
||||
return a.category == categoryName -- and fs.exists(a.run)
|
||||
end)
|
||||
table.sort(filtered, function(a, b) return a.title < b.title end)
|
||||
end
|
||||
|
||||
for _,program in ipairs(filtered) do
|
||||
|
||||
local icon
|
||||
if extSupport and program.iconExt then
|
||||
icon = parseIcon(program.iconExt)
|
||||
@@ -320,43 +297,27 @@ function page.container:setCategory(categoryName, animate)
|
||||
local title = ellipsis(program.title, 8)
|
||||
|
||||
local width = math.max(icon.width + 2, #title + 2)
|
||||
if config.listMode then
|
||||
table.insert(self.children, UI.Icon {
|
||||
width = self.width - 2,
|
||||
height = 1,
|
||||
UI.Button {
|
||||
x = 1, ex = -1,
|
||||
text = program.title,
|
||||
centered = false,
|
||||
backgroundColor = self:getProperty('backgroundColor'),
|
||||
backgroundFocusColor = 'gray',
|
||||
textColor = 'white',
|
||||
textFocusColor = 'white',
|
||||
event = 'button',
|
||||
app = program,
|
||||
}
|
||||
})
|
||||
else
|
||||
table.insert(self.children, UI.Icon({
|
||||
width = width,
|
||||
image = UI.NftImage({
|
||||
x = math.floor((width - icon.width) / 2) + 1,
|
||||
image = icon,
|
||||
}),
|
||||
button = UI.Button({
|
||||
x = math.floor((width - #title - 2) / 2) + 1,
|
||||
y = 4,
|
||||
text = title,
|
||||
backgroundColor = self:getProperty('backgroundColor'),
|
||||
backgroundFocusColor = 'gray',
|
||||
textColor = 'white',
|
||||
textFocusColor = 'white',
|
||||
width = #title + 2,
|
||||
event = 'button',
|
||||
app = program,
|
||||
}),
|
||||
}))
|
||||
end
|
||||
table.insert(self.children, UI.Icon({
|
||||
width = width,
|
||||
image = UI.NftImage({
|
||||
x = math.floor((width - icon.width) / 2) + 1,
|
||||
image = icon,
|
||||
width = 5,
|
||||
height = 3,
|
||||
}),
|
||||
button = UI.Button({
|
||||
x = math.floor((width - #title - 2) / 2) + 1,
|
||||
y = 4,
|
||||
text = title,
|
||||
backgroundColor = self.backgroundColor,
|
||||
backgroundFocusColor = colors.gray,
|
||||
textColor = colors.white,
|
||||
textFocusColor = colors.white,
|
||||
width = #title + 2,
|
||||
event = 'button',
|
||||
app = program,
|
||||
}),
|
||||
}))
|
||||
end
|
||||
|
||||
local gutter = 2
|
||||
@@ -366,8 +327,7 @@ function page.container:setCategory(categoryName, animate)
|
||||
local col, row = gutter, 2
|
||||
local count = #self.children
|
||||
|
||||
local r = math.random(1, 7)
|
||||
local frames = 5
|
||||
local r = math.random(1, 5)
|
||||
-- reposition all children
|
||||
for k,child in ipairs(self.children) do
|
||||
if r == 1 then
|
||||
@@ -389,27 +349,19 @@ function page.container:setCategory(categoryName, animate)
|
||||
child.x = self.width
|
||||
child.y = self.height - 3
|
||||
end
|
||||
elseif r == 6 then
|
||||
child.x = col
|
||||
child.y = 1
|
||||
elseif r == 7 then
|
||||
child.x = 1
|
||||
child.y = self.height - 3
|
||||
end
|
||||
child.tween = Tween.new(frames, child, { x = col, y = row }, 'inQuad')
|
||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
||||
|
||||
if not animate then
|
||||
child.x = col
|
||||
child.y = row
|
||||
end
|
||||
|
||||
self:setViewHeight(row + (config.listMode and 1 or 4))
|
||||
|
||||
if k < count then
|
||||
col = col + child.width
|
||||
if col + self.children[k + 1].width + gutter - 2 > self.width then
|
||||
col = gutter
|
||||
row = row + (config.listMode and 1 or 5)
|
||||
row = row + 5
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -419,12 +371,15 @@ function page.container:setCategory(categoryName, animate)
|
||||
local function transition()
|
||||
local i = 1
|
||||
return function()
|
||||
self:clear()
|
||||
for _,child in pairs(self.children) do
|
||||
child.tween:update(1)
|
||||
child:move(math.floor(child.x), math.floor(child.y))
|
||||
child.x = math.floor(child.x)
|
||||
child.y = math.floor(child.y)
|
||||
child:draw()
|
||||
end
|
||||
i = i + 1
|
||||
return i <= frames
|
||||
return i < 7
|
||||
end
|
||||
end
|
||||
self:addTransition(transition)
|
||||
@@ -474,12 +429,6 @@ function page:eventHandler(event)
|
||||
elseif event.type == 'files' then
|
||||
shell.switchTab(shell.openTab(Alt.get('files')))
|
||||
|
||||
elseif event.type == 'network' then
|
||||
shell.switchTab(shell.openTab('network'))
|
||||
|
||||
elseif event.type == 'help' then
|
||||
shell.switchTab(shell.openTab('Help Overview'))
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused.parent.UIElement == 'Icon' then
|
||||
event.focused.parent:scrollIntoView()
|
||||
@@ -514,13 +463,6 @@ function page:eventHandler(event)
|
||||
end
|
||||
self.editor:show({ category = category })
|
||||
|
||||
elseif event.type == 'display_mode' then
|
||||
config.listMode = not config.listMode
|
||||
Config.update('Overview', config)
|
||||
loadApplications()
|
||||
self:refresh()
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'edit' then
|
||||
local focused = page:getFocused()
|
||||
if focused.app then
|
||||
@@ -528,7 +470,7 @@ function page:eventHandler(event)
|
||||
end
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
@@ -550,6 +492,11 @@ function page.editor:show(app)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function page.editor.form.image:draw()
|
||||
self:clear()
|
||||
UI.NftImage.draw(self)
|
||||
end
|
||||
|
||||
function page.editor:updateApplications(app)
|
||||
if not app.key then
|
||||
app.key = SHA.compute(app.title)
|
||||
@@ -559,51 +506,36 @@ function page.editor:updateApplications(app)
|
||||
loadApplications()
|
||||
end
|
||||
|
||||
function page.editor:loadImage(filename)
|
||||
local s, m = pcall(function()
|
||||
local iconLines = Util.readFile(filename)
|
||||
if not iconLines then
|
||||
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||
end
|
||||
local icon, m = parseIcon(iconLines)
|
||||
if not icon then
|
||||
error(m)
|
||||
end
|
||||
if extSupport then
|
||||
self.form.values.iconExt = iconLines
|
||||
else
|
||||
self.form.values.icon = iconLines
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
self.form.image:draw()
|
||||
end)
|
||||
if not s and m then
|
||||
local msg = m:gsub('.*: (.*)', '%1')
|
||||
self.notification:error(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function page.editor:eventHandler(event)
|
||||
if event.type == 'form_cancel' or event.type == 'cancel' then
|
||||
self:hide()
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help or '')
|
||||
|
||||
elseif event.type == 'editIcon' then
|
||||
local filename = '/tmp/editing.nft'
|
||||
NFT.save(self.form.image.image or TRANS_ICON, filename)
|
||||
local success = shell.run('pain.lua ' .. filename)
|
||||
self.parent:dirty(true)
|
||||
if success then
|
||||
self:loadImage(filename)
|
||||
end
|
||||
|
||||
elseif event.type == 'select_file' then
|
||||
self:loadImage(event.file)
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'loadIcon' then
|
||||
self.file_open:show()
|
||||
local s, m = pcall(function()
|
||||
local iconLines = Util.readFile(self.form.iconFile.value)
|
||||
if not iconLines then
|
||||
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||
end
|
||||
local icon, m = parseIcon(iconLines)
|
||||
if not icon then
|
||||
error(m)
|
||||
end
|
||||
if extSupport then
|
||||
self.form.values.iconExt = iconLines
|
||||
else
|
||||
self.form.values.icon = iconLines
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
self.form.image:draw()
|
||||
end)
|
||||
if not s and m then
|
||||
local msg = m:gsub('.*: (.*)', '%1')
|
||||
self.notification:error(msg)
|
||||
end
|
||||
|
||||
elseif event.type == 'form_invalid' then
|
||||
self.notification:error(event.message)
|
||||
@@ -612,6 +544,8 @@ function page.editor:eventHandler(event)
|
||||
local values = self.form.values
|
||||
self:hide()
|
||||
self:updateApplications(values)
|
||||
--page:refresh()
|
||||
--page:draw()
|
||||
config.currentCategory = values.category
|
||||
Config.update('Overview', config)
|
||||
os.queueEvent('overview_refresh')
|
||||
@@ -621,6 +555,10 @@ function page.editor:eventHandler(event)
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPages({
|
||||
main = page,
|
||||
})
|
||||
|
||||
local function reload()
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
@@ -646,4 +584,5 @@ end)
|
||||
loadApplications()
|
||||
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -44,6 +44,7 @@ local page = UI.Page {
|
||||
marginRight = 0, marginLeft = 0,
|
||||
},
|
||||
action = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
event = 'hide-action',
|
||||
},
|
||||
@@ -183,7 +184,7 @@ function page:eventHandler(event)
|
||||
self.action.button:draw()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:quit()
|
||||
UI:exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
@@ -195,4 +196,4 @@ Packages:downloadList()
|
||||
page:loadPackages()
|
||||
page:sync()
|
||||
|
||||
UI:start()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -27,4 +27,4 @@ kernel.hook('kernel_focus', function(_, eventData)
|
||||
end
|
||||
end)
|
||||
|
||||
os.pullEventRaw('kernel_halt')
|
||||
os.pullEventRaw('kernel_halt')
|
||||
@@ -5,13 +5,14 @@ local Util = require('opus.util')
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
local textutils = _G.textutils
|
||||
local peripheral = _G.peripheral
|
||||
local multishell = _ENV.multishell
|
||||
|
||||
local gridColumns = {}
|
||||
table.insert(gridColumns, { heading = '#', key = 'id', width = 5, align = 'right' })
|
||||
table.insert(gridColumns, { heading = 'Port', key = 'portid', width = 5, align = 'right' })
|
||||
table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' })
|
||||
if UI.term.width > 50 then
|
||||
if UI.defaultDevice.width > 50 then
|
||||
table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' })
|
||||
end
|
||||
table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' })
|
||||
@@ -41,13 +42,12 @@ local page = UI.Page {
|
||||
|
||||
configSlide = UI.SlideOut {
|
||||
y = -11,
|
||||
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close', backgroundColor = colors.black },
|
||||
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close' },
|
||||
accelerators = { ['backspace'] = 'config_close' },
|
||||
configTabs = UI.Tabs {
|
||||
y = 2,
|
||||
filterTab = UI.Tab {
|
||||
tabTitle = 'Filter',
|
||||
noFill = true,
|
||||
filterGridText = UI.Text {
|
||||
x = 2, y = 2,
|
||||
value = 'ID filter',
|
||||
@@ -130,6 +130,7 @@ local page = UI.Page {
|
||||
title = 'Packet Information',
|
||||
event = 'packet_close',
|
||||
},
|
||||
backgroundColor = colors.cyan,
|
||||
accelerators = {
|
||||
['backspace'] = 'packet_close',
|
||||
['left'] = 'prev_packet',
|
||||
@@ -279,7 +280,7 @@ function page.packetSlide:eventHandler(event)
|
||||
end
|
||||
|
||||
function page.packetGrid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
local row = Util.shallowCopy(row)
|
||||
row.distance = Util.toBytes(Util.round(row.distance), 2)
|
||||
return row
|
||||
end
|
||||
@@ -306,16 +307,14 @@ end
|
||||
|
||||
function page:enable()
|
||||
modemConfig.modems = {}
|
||||
Util.each(_G.device, function(dev)
|
||||
if dev.type == "modem" then
|
||||
modemConfig.modems[dev.side] = {
|
||||
type = dev.isWireless() and 'Wireless' or 'Wired',
|
||||
side = dev.side,
|
||||
openChannels = { },
|
||||
device = dev,
|
||||
loaded = false
|
||||
}
|
||||
end
|
||||
peripheral.find('modem', function(side, dev)
|
||||
modemConfig.modems[side] = {
|
||||
type = dev.isWireless() and 'Wireless' or 'Wired',
|
||||
side = side,
|
||||
openChannels = { },
|
||||
device = dev,
|
||||
loaded = false
|
||||
}
|
||||
end)
|
||||
modemConfig.currentModem = device.wireless_modem and
|
||||
modemConfig.modems[device.wireless_modem.side] or
|
||||
@@ -355,7 +354,7 @@ function page:eventHandler(event)
|
||||
self.packetSlide:show(event.selected)
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:quit()
|
||||
Event.exitPullEvents()
|
||||
|
||||
else return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
@@ -385,4 +384,4 @@ if args[1] then
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -11,7 +11,7 @@ local systemPage = UI.Page {
|
||||
settings = UI.Tab {
|
||||
tabTitle = 'Category',
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, y = 2, ex = -2, ey = -2,
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Description', key = 'description' },
|
||||
@@ -35,14 +35,14 @@ function systemPage.tabs.settings:eventHandler(event)
|
||||
tab:disable()
|
||||
end
|
||||
systemPage.tabs:selectTab(tab)
|
||||
--self.parent:draw()
|
||||
self.parent:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function systemPage:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:quit()
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'success_message' then
|
||||
self.notification:success(event.message)
|
||||
@@ -82,4 +82,4 @@ local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
|
||||
systemPage.tabs.settings.grid:setValues(plugins)
|
||||
|
||||
UI:setPage(systemPage)
|
||||
UI:start()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -3,7 +3,6 @@ local UI = require('opus.ui')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local multishell = _ENV.multishell
|
||||
local tasks = multishell and multishell.getTabs and multishell.getTabs() or kernel.routines
|
||||
|
||||
UI:configure('Tasks', ...)
|
||||
|
||||
@@ -22,42 +21,44 @@ local page = UI.Page {
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Time', key = 'timestamp' },
|
||||
},
|
||||
values = tasks,
|
||||
values = kernel.routines,
|
||||
sortColumn = 'uid',
|
||||
autospace = true,
|
||||
getDisplayValues = function (_, row)
|
||||
local elapsed = os.clock()-row.timestamp
|
||||
return {
|
||||
uid = row.uid,
|
||||
title = row.title,
|
||||
status = row.isDead and 'error' or coroutine.status(row.co),
|
||||
timestamp = elapsed < 60 and
|
||||
string.format("%ds", math.floor(elapsed)) or
|
||||
string.format("%sm", math.floor(elapsed/6)/10),
|
||||
}
|
||||
end
|
||||
},
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'quit',
|
||||
space = 'activate',
|
||||
t = 'terminate',
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
local t = self.grid:getSelected()
|
||||
if t then
|
||||
if event.type == 'activate' or event.type == 'grid_select' then
|
||||
multishell.setFocus(t.uid)
|
||||
elseif event.type == 'terminate' then
|
||||
multishell.terminate(t.uid)
|
||||
end
|
||||
end
|
||||
if event.type == 'quit' then
|
||||
UI:quit()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
}
|
||||
|
||||
function page:eventHandler(event)
|
||||
local t = self.grid:getSelected()
|
||||
if t then
|
||||
if event.type == 'activate' or event.type == 'grid_select' then
|
||||
multishell.setFocus(t.uid)
|
||||
elseif event.type == 'terminate' then
|
||||
multishell.terminate(t.uid)
|
||||
end
|
||||
end
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
local elapsed = os.clock()-row.timestamp
|
||||
return {
|
||||
uid = row.uid,
|
||||
title = row.title,
|
||||
status = row.isDead and 'error' or coroutine.status(row.co),
|
||||
timestamp = elapsed < 60 and
|
||||
string.format("%ds", math.floor(elapsed)) or
|
||||
string.format("%sm", math.floor(elapsed/6)/10),
|
||||
}
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
@@ -65,4 +66,4 @@ Event.onInterval(1, function()
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -60,12 +60,6 @@ local page = UI.Page {
|
||||
x = 3, ex = -3, y = 4, ey = -3,
|
||||
value = string.format(labelIntro, Ansi.white),
|
||||
},
|
||||
validate = function (self)
|
||||
if self.label.value then
|
||||
os.setComputerLabel(self.label.value)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
},
|
||||
password = UI.WizardPage {
|
||||
index = 3,
|
||||
@@ -79,18 +73,23 @@ local page = UI.Page {
|
||||
mask = true,
|
||||
shadowText = 'password',
|
||||
},
|
||||
--[[
|
||||
groupLabel = UI.Text {
|
||||
x = 3, y = 3,
|
||||
value = 'Group'
|
||||
},
|
||||
group = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 3,
|
||||
limit = 32,
|
||||
shadowText = 'network group',
|
||||
},
|
||||
]]
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 5, ey = -3,
|
||||
value = string.format(passwordIntro, Ansi.white),
|
||||
},
|
||||
validate = function (self)
|
||||
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
|
||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||
end
|
||||
return true
|
||||
end,
|
||||
},
|
||||
packages = UI.WizardPage {
|
||||
index = 4,
|
||||
@@ -120,6 +119,25 @@ local page = UI.Page {
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
function page.wizard.pages.label:validate()
|
||||
os.setComputerLabel(self.label.value)
|
||||
return true
|
||||
end
|
||||
|
||||
function page.wizard.pages.password:validate()
|
||||
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
|
||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||
end
|
||||
--[[
|
||||
if #self.group.value > 0 then
|
||||
local config = Config.load('os')
|
||||
config.group = self.group.value
|
||||
Config.update('os', config)
|
||||
end
|
||||
]]
|
||||
return true
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'skip' then
|
||||
self.wizard:emit({ type = 'nextView' })
|
||||
@@ -131,7 +149,7 @@ function page:eventHandler(event)
|
||||
shell.openForegroundTab('PackageManager')
|
||||
|
||||
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
|
||||
UI:quit()
|
||||
UI.exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
@@ -140,4 +158,4 @@ function page:eventHandler(event)
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local shell = _ENV.shell
|
||||
local multishell = _ENV.multishell
|
||||
|
||||
-- fileui [--path=path] [--exec=filename] [--title=title]
|
||||
|
||||
local page = UI.Page {
|
||||
fileselect = UI.FileSelect { },
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'select_file' then
|
||||
self.selected = event.file
|
||||
UI:quit()
|
||||
|
||||
elseif event.type == 'select_cancel' then
|
||||
UI:quit()
|
||||
end
|
||||
|
||||
return UI.FileSelect.eventHandler(self, event)
|
||||
end,
|
||||
}
|
||||
|
||||
local _, args = Util.parse(...)
|
||||
|
||||
if args.title and multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), args.title)
|
||||
end
|
||||
|
||||
UI:setPage(page, args.path)
|
||||
UI:start()
|
||||
UI.term:setCursorBlink(false)
|
||||
|
||||
if args.exec and page.selected then
|
||||
shell.openForegroundTab(string.format('%s %s', args.exec, page.selected))
|
||||
return
|
||||
end
|
||||
|
||||
return page.selected
|
||||
@@ -1,195 +0,0 @@
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local multishell = _ENV.multishell
|
||||
|
||||
local name = ({ ... })[1] or error('Syntax: inspect COMPONENT')
|
||||
local events = { }
|
||||
local page, lastEvent, focused
|
||||
|
||||
local function isRelevant(el)
|
||||
return page.testContainer == el or el.parent and isRelevant(el.parent)
|
||||
end
|
||||
|
||||
local emitter = UI.Window.emit
|
||||
function UI.Window:emit(event)
|
||||
if event ~= lastEvent and isRelevant(self) then
|
||||
lastEvent = event
|
||||
local t = { }
|
||||
for k,v in pairs(event) do
|
||||
if k ~= 'type' and k ~= 'recorded' then
|
||||
table.insert(t, k .. ':' .. (type(v) == 'table' and (v.UIElement and v.uid or 'tbl') or tostring(v)))
|
||||
end
|
||||
end
|
||||
table.insert(events, 1, { type = event.type, value = table.concat(t, ' '), raw = event })
|
||||
while #events > 20 do
|
||||
table.remove(events)
|
||||
end
|
||||
page.tabs.events.grid:update()
|
||||
if page.tabs.events.enabled then
|
||||
page.tabs.events.grid:draw()
|
||||
end
|
||||
end
|
||||
return emitter(self, event)
|
||||
end
|
||||
|
||||
-- do not load component until emit hook is in place
|
||||
local component = UI[name] and UI[name]() or error('Invalid component')
|
||||
if not component.example then
|
||||
error('No example present')
|
||||
end
|
||||
|
||||
page = UI.Page {
|
||||
testContainer = UI.Window {
|
||||
ey = '50%',
|
||||
testing = component.example(),
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
backgroundColor = colors.red,
|
||||
y = '50%',
|
||||
properties = UI.Tab {
|
||||
tabTitle = 'Properties',
|
||||
grid = UI.ScrollingGrid {
|
||||
headerBackgroundColor = colors.red,
|
||||
sortColumn = 'key',
|
||||
columns = {
|
||||
{ heading = 'key', key = 'key' },
|
||||
{ heading = 'value', key = 'value', }
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'edit_property',
|
||||
},
|
||||
},
|
||||
},
|
||||
methodsTab = UI.Tab {
|
||||
index = 2,
|
||||
tabTitle = 'Methods',
|
||||
grid = UI.ScrollingGrid {
|
||||
ex = '50%',
|
||||
headerBackgroundColor = colors.red,
|
||||
sortColumn = 'key',
|
||||
columns = {
|
||||
{ heading = 'key', key = 'key' },
|
||||
},
|
||||
},
|
||||
docs = UI.TextArea {
|
||||
x = '50%',
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
if event.type == 'grid_focus_row' and focused then
|
||||
self.docs:setText(focused:getDoc(event.selected.key) or '')
|
||||
end
|
||||
end,
|
||||
},
|
||||
events = UI.Tab {
|
||||
index = 1,
|
||||
tabTitle = 'Events',
|
||||
UI.MenuBar {
|
||||
y = -1,
|
||||
backgroundColor = colors.red,
|
||||
buttons = {
|
||||
{ text = 'Clear' },
|
||||
}
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -2,
|
||||
headerBackgroundColor = colors.red,
|
||||
values = events,
|
||||
autospace = true,
|
||||
columns = {
|
||||
{ heading = 'type', key = 'type' },
|
||||
{ heading = 'value', key = 'value', }
|
||||
},
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
if event.type == 'button_press' then
|
||||
Util.clear(self.grid.values)
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.selected.raw },
|
||||
focused = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
}
|
||||
},
|
||||
editor = UI.SlideOut {
|
||||
y = -4, height = 4,
|
||||
backgroundColor = colors.green,
|
||||
titleBar = UI.TitleBar {
|
||||
event = 'editor_cancel',
|
||||
title = 'Enter value',
|
||||
},
|
||||
entry = UI.TextEntry {
|
||||
y = 3, x = 2, ex = 10,
|
||||
accelerators = {
|
||||
enter = 'editor_apply',
|
||||
},
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
['shift-right'] = 'size',
|
||||
['shift-left' ] = 'size',
|
||||
['shift-up' ] = 'size',
|
||||
['shift-down' ] = 'size',
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
if event.type == 'focus_change' and isRelevant(event.focused) then
|
||||
focused = event.focused
|
||||
local t = { }
|
||||
for k,v in pairs(event.focused) do
|
||||
table.insert(t, {
|
||||
key = k,
|
||||
value = tostring(v),
|
||||
})
|
||||
end
|
||||
self.tabs.properties.grid:setValues(t)
|
||||
self.tabs.properties.grid:draw()
|
||||
|
||||
t = { }
|
||||
for k,v in pairs(getmetatable(event.focused)) do
|
||||
if type(v) == 'function' then
|
||||
table.insert(t, {
|
||||
key = k,
|
||||
})
|
||||
end
|
||||
end
|
||||
self.tabs.methodsTab.grid:setValues(t)
|
||||
self.tabs.methodsTab.grid:draw()
|
||||
|
||||
elseif event.type == 'edit_property' then
|
||||
self.editor.entry.value = event.selected.value
|
||||
self.editor:show()
|
||||
|
||||
elseif event.type == 'editor_cancel' then
|
||||
self.editor:hide()
|
||||
|
||||
elseif event.type == 'editor_apply' then
|
||||
self.editor:hide()
|
||||
|
||||
elseif event.type == 'size' then
|
||||
local sizing = {
|
||||
['shift-right'] = { 1, 0 },
|
||||
['shift-left' ] = { -1, 0 },
|
||||
['shift-up' ] = { 0, -1 },
|
||||
['shift-down' ] = { 0, 1 },
|
||||
}
|
||||
self.ox = math.max(self.ox + sizing[event.ie.code][1], 1)
|
||||
self.oy = math.max(self.oy + sizing[event.ie.code][2], 1)
|
||||
UI.term:clear()
|
||||
self:resize()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
}
|
||||
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
@@ -10,30 +10,30 @@ local keyPairs = { }
|
||||
local function generateKeyPair()
|
||||
local key = { }
|
||||
for _ = 1, 32 do
|
||||
table.insert(key, math.random(0, 0xFF))
|
||||
table.insert(key, ("%02x"):format(math.random(0, 0xFF)))
|
||||
end
|
||||
local privateKey = setmetatable(key, Util.byteArrayMT)
|
||||
local privateKey = Util.hexToByteArray(table.concat(key))
|
||||
return privateKey, ECC.publicKey(privateKey)
|
||||
end
|
||||
|
||||
getmetatable(network).__index.getKeyPair = function()
|
||||
local keys = table.remove(keyPairs)
|
||||
os.queueEvent('generate_keypair')
|
||||
if not keys then
|
||||
return generateKeyPair()
|
||||
end
|
||||
return table.unpack(keys)
|
||||
local keys = table.remove(keyPairs)
|
||||
os.queueEvent('generate_keypair')
|
||||
if not keys then
|
||||
return generateKeyPair()
|
||||
end
|
||||
return table.unpack(keys)
|
||||
end
|
||||
|
||||
-- Generate key pairs in the background as this is a time-consuming process
|
||||
Event.on('generate_keypair', function()
|
||||
while true do
|
||||
os.sleep(5)
|
||||
local timer = Util.timer()
|
||||
table.insert(keyPairs, { generateKeyPair() })
|
||||
_G._syslog('Generated keypair in ' .. timer())
|
||||
if #keyPairs >= 3 then
|
||||
break
|
||||
end
|
||||
end
|
||||
while true do
|
||||
os.sleep(5)
|
||||
local timer = Util.timer()
|
||||
table.insert(keyPairs, { generateKeyPair() })
|
||||
_G._syslog('Generated keypair in ' .. timer())
|
||||
if #keyPairs >= 3 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -66,11 +66,11 @@ local function run(env, ...)
|
||||
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)'))
|
||||
end
|
||||
|
||||
tProgramStack[#tProgramStack + 1] = {
|
||||
path = path, -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
|
||||
env = env,
|
||||
args = args,
|
||||
}
|
||||
if isUrl then
|
||||
tProgramStack[#tProgramStack + 1] = path -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
|
||||
else
|
||||
tProgramStack[#tProgramStack + 1] = path
|
||||
end
|
||||
|
||||
env[ "arg" ] = { [0] = path, table.unpack(args) }
|
||||
local r = { fn(table.unpack(args)) }
|
||||
@@ -278,10 +278,6 @@ function shell.getCompletionInfo()
|
||||
end
|
||||
|
||||
function shell.getRunningProgram()
|
||||
return tProgramStack[#tProgramStack] and tProgramStack[#tProgramStack].path
|
||||
end
|
||||
|
||||
function shell.getRunningInfo()
|
||||
return tProgramStack[#tProgramStack]
|
||||
end
|
||||
|
||||
@@ -590,7 +586,6 @@ local function shellRead(history)
|
||||
end
|
||||
local _,cy = term.getCursorPos()
|
||||
term.setCursorPos(3, cy)
|
||||
entry.value = entry.value or ''
|
||||
local filler = #entry.value < lastLen
|
||||
and string.rep(' ', lastLen - #entry.value)
|
||||
or ''
|
||||
@@ -640,7 +635,6 @@ local function shellRead(history)
|
||||
redraw()
|
||||
|
||||
elseif ie.code == 'tab' then
|
||||
entry.value = entry.value or ''
|
||||
if entry.pos == #entry.value then
|
||||
local cline = autocomplete(entry.value)
|
||||
if cline then
|
||||
@@ -656,7 +650,6 @@ local function shellRead(history)
|
||||
|
||||
else
|
||||
entry:process(ie)
|
||||
entry.value = entry.value or ''
|
||||
if entry.textChanged then
|
||||
redraw()
|
||||
elseif entry.posChanged then
|
||||
@@ -673,7 +666,7 @@ local function shellRead(history)
|
||||
|
||||
print()
|
||||
term.setCursorBlink( false )
|
||||
return entry.value or ''
|
||||
return entry.value
|
||||
end
|
||||
|
||||
local history = History.load('usr/.shell_history', 25)
|
||||
|
||||
@@ -20,7 +20,7 @@ local aliasTab = UI.Tab {
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 2, y = 5, ex = -2, ey = -2,
|
||||
y = 5,
|
||||
sortColumn = 'alias',
|
||||
columns = {
|
||||
{ heading = 'Alias', key = 'alias' },
|
||||
|
||||
@@ -2,6 +2,8 @@ local Array = require('opus.array')
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Preferred',
|
||||
description = 'Select preferred applications',
|
||||
@@ -20,19 +22,20 @@ local tab = UI.Tab {
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ key = 'file' },
|
||||
},
|
||||
getRowTextColor = function(self, row)
|
||||
if row == self.values[1] then
|
||||
return 'yellow'
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end,
|
||||
}
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = 'Double-click to set as preferred'
|
||||
},
|
||||
}
|
||||
|
||||
function tab.choices:getRowTextColor(row)
|
||||
if row == self.values[1] then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end
|
||||
|
||||
function tab:updateChoices()
|
||||
local app = self.apps:getSelected().name
|
||||
local choices = { }
|
||||
|
||||
@@ -2,17 +2,18 @@ local Ansi = require('opus.ansi')
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
-- -t80x30
|
||||
|
||||
if _G.http.websocket then
|
||||
local config = Config.load('cloud')
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Cloud',
|
||||
description = 'Cloud Catcher options',
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 4,
|
||||
},
|
||||
key = UI.TextEntry {
|
||||
x = 3, ex = -3, y = 3,
|
||||
x = 3, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
value = config.key,
|
||||
shadowText = 'Cloud key',
|
||||
@@ -21,15 +22,14 @@ if _G.http.websocket then
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -8, ex = -2, y = -2,
|
||||
text = 'Apply',
|
||||
x = 3, y = 4,
|
||||
text = 'Update',
|
||||
event = 'update_key',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 2, ex = -2, y = 5, ey = -4,
|
||||
textColor = 'yellow',
|
||||
backgroundColor = 'black',
|
||||
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||
x = 3, ex = -3, y = 6,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 0, marginRight = 0,
|
||||
value = string.format(
|
||||
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
|
||||
To obtain a key, visit:
|
||||
@@ -42,7 +42,7 @@ To obtain a key, visit:
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_key' then
|
||||
if self.key.value then
|
||||
if #self.key.value > 0 then
|
||||
config.key = self.key.value
|
||||
else
|
||||
config.key = nil
|
||||
|
||||
@@ -20,8 +20,8 @@ local tab = UI.Tab {
|
||||
description = 'Visualise HDD and disks usage',
|
||||
|
||||
drives = UI.ScrollingGrid {
|
||||
x = 2, y = 2,
|
||||
ex = '47%', ey = -8,
|
||||
x = 2, y = 1,
|
||||
ex = '47%', ey = -7,
|
||||
columns = {
|
||||
{ heading = 'Drive', key = 'name' },
|
||||
{ heading = 'Side' ,key = 'side', textColor = colors.yellow }
|
||||
@@ -30,7 +30,7 @@ local tab = UI.Tab {
|
||||
},
|
||||
infos = UI.Grid {
|
||||
x = '52%', y = 2,
|
||||
ex = -2, ey = -8,
|
||||
ex = -2, ey = -4,
|
||||
disableHeader = true,
|
||||
unfocusedBackgroundSelectedColor = colors.black,
|
||||
inactive = true,
|
||||
@@ -40,23 +40,18 @@ local tab = UI.Tab {
|
||||
{ key = 'value', align = 'right', textColor = colors.yellow },
|
||||
}
|
||||
},
|
||||
[1] = UI.Window {
|
||||
x = 2, y = -6, ex = -2, ey = -2,
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
|
||||
progress = UI.ProgressBar {
|
||||
x = 11, y = -3,
|
||||
ex = -3,
|
||||
x = 11, y = -2,
|
||||
ex = -2,
|
||||
},
|
||||
percentage = UI.Text {
|
||||
y = -4, width = 5,
|
||||
x = 12,
|
||||
--align = 'center',
|
||||
backgroundColor = colors.black,
|
||||
x = 11, y = -3,
|
||||
ex = '47%',
|
||||
align = 'center',
|
||||
},
|
||||
icon = UI.NftImage {
|
||||
x = 2, y = -6, ey = -2,
|
||||
backgroundColor = colors.black,
|
||||
x = 2, y = -5,
|
||||
image = NFT.parse(NftImages.blank)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ local colors = _G.colors
|
||||
local peripheral = _G.peripheral
|
||||
local settings = _G.settings
|
||||
|
||||
return peripheral.find('monitor') and UI.Tab {
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Kiosk',
|
||||
description = 'Kiosk options',
|
||||
form = UI.Form {
|
||||
x = 2, y = 2, ex = -2, ey = 5,
|
||||
x = 2, ex = -2,
|
||||
manualControls = true,
|
||||
monitor = UI.Chooser {
|
||||
formLabel = 'Monitor', formKey = 'monitor',
|
||||
@@ -22,36 +22,41 @@ return peripheral.find('monitor') and UI.Tab {
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 2, ex = -2, y = 5,
|
||||
textColor = colors.yellow,
|
||||
value = 'Settings apply to kiosk mode selected during startup'
|
||||
},
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 2, ex = -2, y = 7, ey = -2,
|
||||
textColor = colors.yellow,
|
||||
backgroundColor = colors.black,
|
||||
value = 'Settings apply to kiosk mode selected during startup'
|
||||
},
|
||||
enable = function(self)
|
||||
local choices = { }
|
||||
|
||||
peripheral.find('monitor', function(side)
|
||||
table.insert(choices, { name = side, value = side })
|
||||
end)
|
||||
|
||||
self.form.monitor.choices = choices
|
||||
self.form.monitor.value = settings.get('kiosk.monitor')
|
||||
|
||||
self.form.textScale.value = settings.get('kiosk.textscale')
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'choice_change' then
|
||||
if self.form.monitor.value then
|
||||
settings.set('kiosk.monitor', self.form.monitor.value)
|
||||
end
|
||||
if self.form.textScale.value then
|
||||
settings.set('kiosk.textscale', self.form.textScale.value)
|
||||
end
|
||||
settings.save('.settings')
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
function tab:enable()
|
||||
local choices = { }
|
||||
|
||||
peripheral.find('monitor', function(side)
|
||||
table.insert(choices, { name = side, value = side })
|
||||
end)
|
||||
|
||||
self.form.monitor.choices = choices
|
||||
self.form.monitor.value = settings.get('kiosk.monitor')
|
||||
|
||||
self.form.textScale.value = settings.get('kiosk.textscale')
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'choice_change' then
|
||||
if self.form.monitor.value then
|
||||
settings.set('kiosk.monitor', self.form.monitor.value)
|
||||
end
|
||||
if self.form.textScale.value then
|
||||
settings.set('kiosk.textscale', self.form.textScale.value)
|
||||
end
|
||||
settings.save('.settings')
|
||||
end
|
||||
end
|
||||
|
||||
if peripheral.find('monitor') then
|
||||
return tab
|
||||
end
|
||||
|
||||
@@ -4,26 +4,23 @@ local Util = require('opus.util')
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
|
||||
return UI.Tab {
|
||||
local labelTab = UI.Tab {
|
||||
tabTitle = 'Label',
|
||||
description = 'Set the computer label',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 3,
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 3, ex = -4,
|
||||
x = 9, y = 2, ex = -4,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
accelerators = {
|
||||
enter = 'update_label',
|
||||
},
|
||||
},
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 4,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, y = 5, ex = -2, ey = -2,
|
||||
y = 3,
|
||||
values = {
|
||||
{ name = '', value = '' },
|
||||
{ name = 'CC version', value = Util.getVersion() },
|
||||
@@ -33,18 +30,20 @@ return UI.Tab {
|
||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||
{ name = 'Day', value = tostring(os.day()) },
|
||||
},
|
||||
disableHeader = true,
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ key = 'name', width = 12 },
|
||||
{ key = 'value', textColor = colors.yellow },
|
||||
{ key = 'value' },
|
||||
},
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'update_label' and self.label.value then
|
||||
os.setComputerLabel(self.label.value)
|
||||
self:emit({ type = 'success_message', message = 'Label updated' })
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
function labelTab:eventHandler(event)
|
||||
if event.type == 'update_label' then
|
||||
os.setComputerLabel(self.label.value)
|
||||
self:emit({ type = 'success_message', message = 'Label updated' })
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return labelTab
|
||||
|
||||
@@ -9,15 +9,12 @@ local config = Config.load('multishell')
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Launcher',
|
||||
description = 'Set the application launcher',
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 5,
|
||||
},
|
||||
launcherLabel = UI.Text {
|
||||
x = 3, y = 3,
|
||||
x = 3, y = 2,
|
||||
value = 'Launcher',
|
||||
},
|
||||
launcher = UI.Chooser {
|
||||
x = 13, y = 3, width = 12,
|
||||
x = 13, y = 2, width = 12,
|
||||
choices = {
|
||||
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
|
||||
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
|
||||
@@ -25,20 +22,18 @@ local tab = UI.Tab {
|
||||
},
|
||||
},
|
||||
custom = UI.TextEntry {
|
||||
x = 13, ex = -3, y = 4,
|
||||
x = 13, ex = -3, y = 3,
|
||||
limit = 128,
|
||||
shadowText = 'File name',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -8, ex = -2, y = -2,
|
||||
text = 'Apply',
|
||||
x = 3, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 2, ex = -2, y = 6, ey = -4,
|
||||
backgroundColor = colors.black,
|
||||
x = 3, ex = -3, y = 7,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||
value = 'Choose an application launcher',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,61 +2,59 @@ local Ansi = require('opus.ansi')
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
|
||||
return UI.Tab {
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Network',
|
||||
description = 'Networking options',
|
||||
info = UI.TextArea {
|
||||
x = 2, y = 5, ex = -2, ey = -2,
|
||||
backgroundColor = colors.black,
|
||||
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||
x = 3, y = 4,
|
||||
value = string.format(
|
||||
[[%sSet the primary modem used for wireless communications.%s
|
||||
|
||||
Reboot to take effect.]], Ansi.yellow, Ansi.reset)
|
||||
},
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 4,
|
||||
},
|
||||
label = UI.Text {
|
||||
x = 3, y = 3,
|
||||
x = 3, y = 2,
|
||||
value = 'Modem',
|
||||
},
|
||||
modem = UI.Chooser {
|
||||
x = 10, ex = -3, y = 3,
|
||||
x = 10, ex = -3, y = 2,
|
||||
nochoice = 'auto',
|
||||
},
|
||||
enable = function(self)
|
||||
local width = 7
|
||||
local choices = {
|
||||
{ name = 'auto', value = 'auto' },
|
||||
{ name = 'disable', value = 'none' },
|
||||
}
|
||||
}
|
||||
|
||||
for k,v in pairs(device) do
|
||||
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
|
||||
table.insert(choices, { name = k, value = v.name })
|
||||
width = math.max(width, #k)
|
||||
end
|
||||
end
|
||||
function tab:enable()
|
||||
local width = 7
|
||||
local choices = {
|
||||
{ name = 'auto', value = 'auto' },
|
||||
{ name = 'disable', value = 'none' },
|
||||
}
|
||||
|
||||
self.modem.choices = choices
|
||||
--self.modem.width = width + 4
|
||||
|
||||
local config = Config.load('os')
|
||||
self.modem.value = config.wirelessModem or 'auto'
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'choice_change' then
|
||||
local config = Config.load('os')
|
||||
config.wirelessModem = self.modem.value
|
||||
Config.update('os', config)
|
||||
self:emit({ type = 'success_message', message = 'reboot to take effect' })
|
||||
return true
|
||||
for k,v in pairs(device) do
|
||||
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
|
||||
table.insert(choices, { name = k, value = v.name })
|
||||
width = math.max(width, #k)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
self.modem.choices = choices
|
||||
--self.modem.width = width + 4
|
||||
|
||||
local config = Config.load('os')
|
||||
self.modem.value = config.wirelessModem or 'auto'
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'choice_change' then
|
||||
local config = Config.load('os')
|
||||
config.wirelessModem = self.modem.value
|
||||
Config.update('os', config)
|
||||
self:emit({ type = 'success_message', message = 'reboot to take effect' })
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
||||
|
||||
@@ -2,12 +2,11 @@ local Security = require('opus.security')
|
||||
local SHA = require('opus.crypto.sha2')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
return UI.Tab {
|
||||
local colors = _G.colors
|
||||
|
||||
local passwordTab = UI.Tab {
|
||||
tabTitle = 'Password',
|
||||
description = 'Wireless network password',
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 4,
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
x = 3, ex = -3, y = 3,
|
||||
limit = 32,
|
||||
@@ -18,28 +17,28 @@ return UI.Tab {
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -8, ex = -2, y = -2,
|
||||
text = 'Apply',
|
||||
x = 3, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update_password',
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 2, ex = -2, y = 5, ey = -4,
|
||||
backgroundColor = 'black',
|
||||
textColor = 'yellow',
|
||||
x = 3, ex = -3, y = 7,
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||
value = 'Add a password to enable other computers to connect to this one.',
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'update_password' then
|
||||
if not self.newPass.value or #self.newPass.value == 0 then
|
||||
self:emit({ type = 'error_message', message = 'Invalid password' })
|
||||
|
||||
else
|
||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||
self:emit({ type = 'success_message', message = 'Password updated' })
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
function passwordTab:eventHandler(event)
|
||||
if event.type == 'update_password' then
|
||||
if not self.newPass.value or #self.newPass.value == 0 then
|
||||
self:emit({ type = 'error_message', message = 'Invalid password' })
|
||||
|
||||
else
|
||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||
self:emit({ type = 'success_message', message = 'Password updated' })
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return passwordTab
|
||||
|
||||
@@ -6,11 +6,8 @@ local tab = UI.Tab {
|
||||
tabTitle = 'Path',
|
||||
description = 'Set the shell path',
|
||||
tabClose = true,
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 4,
|
||||
},
|
||||
entry = UI.TextEntry {
|
||||
x = 3, y = 3, ex = -3,
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'enter new path',
|
||||
accelerators = {
|
||||
@@ -19,7 +16,7 @@ local tab = UI.Tab {
|
||||
help = 'add a new path',
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 2, y = 6, ex = -2, ey = -3,
|
||||
y = 4, ey = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
@@ -62,7 +59,7 @@ function tab:save()
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_path' and self.entry.value then
|
||||
if event.type == 'update_path' then
|
||||
table.insert(self.grid.values, {
|
||||
value = self.entry.value,
|
||||
})
|
||||
|
||||
@@ -2,94 +2,48 @@ local UI = require('opus.ui')
|
||||
|
||||
local settings = _G.settings
|
||||
|
||||
local transform = {
|
||||
string = tostring,
|
||||
number = tonumber,
|
||||
}
|
||||
|
||||
return settings and UI.Tab {
|
||||
tabTitle = 'Settings',
|
||||
description = 'Computercraft settings',
|
||||
grid = UI.Grid {
|
||||
x = 2, y = 2, ex = -2, ey = -2,
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Setting', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
},
|
||||
},
|
||||
editor = UI.SlideOut {
|
||||
y = -6, height = 6,
|
||||
titleBar = UI.TitleBar {
|
||||
event = 'slide_hide',
|
||||
title = 'Enter value',
|
||||
},
|
||||
form = UI.Form {
|
||||
if settings then
|
||||
local settingsTab = UI.Tab {
|
||||
tabTitle = 'Settings',
|
||||
description = 'Computercraft configurable settings',
|
||||
grid = UI.Grid {
|
||||
y = 2,
|
||||
value = UI.TextEntry {
|
||||
limit = 256,
|
||||
formIndex = 1,
|
||||
formLabel = 'Value',
|
||||
formKey = 'value',
|
||||
autospace = true,
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Setting', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
},
|
||||
validateField = function(self, entry)
|
||||
if entry.value then
|
||||
return transform[self.type](entry.value)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
},
|
||||
accelerators = {
|
||||
form_cancel = 'slide_hide',
|
||||
},
|
||||
show = function(self, entry)
|
||||
self.form.type = type(entry.value) or 'string'
|
||||
self.form:setValues(entry)
|
||||
self.titleBar.title = entry.name
|
||||
UI.SlideOut.show(self)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'form_complete' then
|
||||
if not event.values.value then
|
||||
settings.unset(event.values.name)
|
||||
self.parent:reload()
|
||||
else
|
||||
event.values.value = transform[self.form.type](event.values.value)
|
||||
settings.set(event.values.name, event.values.value)
|
||||
end
|
||||
self.parent.grid:draw()
|
||||
self:hide()
|
||||
settings.save('.settings')
|
||||
end
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end,
|
||||
},
|
||||
reload = function(self)
|
||||
}
|
||||
|
||||
function settingsTab:enable()
|
||||
local values = { }
|
||||
for _,v in pairs(settings.getNames()) do
|
||||
local value = settings.get(v)
|
||||
if not value then
|
||||
value = false
|
||||
end
|
||||
table.insert(values, {
|
||||
name = v,
|
||||
value = settings.get(v) or false,
|
||||
value = value,
|
||||
})
|
||||
end
|
||||
self.grid:setValues(values)
|
||||
self.grid:setIndex(1)
|
||||
end,
|
||||
enable = function(self)
|
||||
self:reload()
|
||||
UI.Tab.enable(self)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
end
|
||||
|
||||
function settingsTab:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
if type(event.selected.value) == 'boolean' then
|
||||
if not event.selected.value or type(event.selected.value) == 'boolean' then
|
||||
event.selected.value = not event.selected.value
|
||||
settings.set(event.selected.name, event.selected.value)
|
||||
settings.save('.settings')
|
||||
self.grid:draw()
|
||||
else
|
||||
self.editor:show(event.selected)
|
||||
end
|
||||
settings.set(event.selected.name, event.selected.value)
|
||||
settings.save('.settings')
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return settingsTab
|
||||
end
|
||||
|
||||
@@ -28,7 +28,7 @@ local defaults = {
|
||||
local _colors = config.color or Util.shallowCopy(defaults)
|
||||
|
||||
local allSettings = { }
|
||||
for k in pairs(defaults) do
|
||||
for k, v in pairs(defaults) do
|
||||
table.insert(allSettings, { name = k })
|
||||
end
|
||||
|
||||
@@ -38,34 +38,29 @@ if not _colors.backgroundColor then
|
||||
_colors.fileColor = colors.white
|
||||
end
|
||||
|
||||
return UI.Tab {
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Shell',
|
||||
description = 'Shell options',
|
||||
grid1 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = 2, ex = -17,
|
||||
y = 2, ey = -10, x = 3, ex = -16,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allSettings,
|
||||
sortColumn = 'name',
|
||||
},
|
||||
grid2 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = -14, ex = -2,
|
||||
y = 2, ey = -10, x = -14, ex = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allColors,
|
||||
sortColumn = 'name',
|
||||
getRowTextColor = function(self, row)
|
||||
local selected = self.parent.grid1:getSelected()
|
||||
if _colors[selected.name] == row.value then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end
|
||||
},
|
||||
directoryLabel = UI.Text {
|
||||
x = 2, y = -2,
|
||||
value = 'Display directory',
|
||||
},
|
||||
directory = UI.Checkbox {
|
||||
x = 2, y = -2,
|
||||
labelBackgroundColor = colors.black,
|
||||
label = 'Directory',
|
||||
x = 20, y = -2,
|
||||
value = config.displayDirectory
|
||||
},
|
||||
reset = UI.Button {
|
||||
@@ -79,57 +74,69 @@ return UI.Tab {
|
||||
event = 'update',
|
||||
},
|
||||
display = UI.Window {
|
||||
x = 2, ex = -2, y = -8, height = 5,
|
||||
draw = function(self)
|
||||
self:clear(_colors.backgroundColor)
|
||||
local offset = 0
|
||||
if config.displayDirectory then
|
||||
self:write(1, 1,
|
||||
'==' .. os.getComputerLabel() .. ':/dir/etc',
|
||||
_colors.directoryBackgroundColor, _colors.directoryTextColor)
|
||||
offset = 1
|
||||
end
|
||||
|
||||
self:write(1, 1 + offset, '$ ',
|
||||
_colors.promptBackgroundColor, _colors.promptTextColor)
|
||||
|
||||
self:write(3, 1 + offset, 'ls /',
|
||||
_colors.backgroundColor, _colors.commandTextColor)
|
||||
|
||||
self:write(1, 2 + offset, 'sys usr',
|
||||
_colors.backgroundColor, _colors.directoryColor)
|
||||
|
||||
self:write(1, 3 + offset, 'startup',
|
||||
_colors.backgroundColor, _colors.fileColor)
|
||||
end,
|
||||
x = 3, ex = -3, y = -8, height = 5,
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type =='checkbox_change' then
|
||||
config.displayDirectory = not not event.checked
|
||||
self.display:draw()
|
||||
|
||||
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == self.grid2 then
|
||||
_colors[self.grid1:getSelected().name] = event.selected.value
|
||||
self.display:draw()
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'reset' then
|
||||
config.color = defaults
|
||||
config.displayDirectory = true
|
||||
self.directory.value = true
|
||||
_colors = Util.shallowCopy(defaults)
|
||||
|
||||
Config.update('shellprompt', config)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
config.color = _colors
|
||||
Config.update('shellprompt', config)
|
||||
|
||||
end
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
}
|
||||
|
||||
function tab.grid2:getRowTextColor(row)
|
||||
local selected = tab.grid1:getSelected()
|
||||
if _colors[selected.name] == row.value then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end
|
||||
|
||||
function tab.display:draw()
|
||||
self:clear(_colors.backgroundColor)
|
||||
local offset = 0
|
||||
if config.displayDirectory then
|
||||
self:write(1, 1,
|
||||
'==' .. os.getComputerLabel() .. ':/dir/etc',
|
||||
_colors.directoryBackgroundColor, _colors.directoryTextColor)
|
||||
offset = 1
|
||||
end
|
||||
|
||||
self:write(1, 1 + offset, '$ ',
|
||||
_colors.promptBackgroundColor, _colors.promptTextColor)
|
||||
|
||||
self:write(3, 1 + offset, 'ls /',
|
||||
_colors.backgroundColor, _colors.commandTextColor)
|
||||
|
||||
self:write(1, 2 + offset, 'sys usr',
|
||||
_colors.backgroundColor, _colors.directoryColor)
|
||||
|
||||
self:write(1, 3 + offset, 'startup',
|
||||
_colors.backgroundColor, _colors.fileColor)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type =='checkbox_change' then
|
||||
config.displayDirectory = not not event.checked
|
||||
self.display:draw()
|
||||
|
||||
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == self.grid2 then
|
||||
_colors[tab.grid1:getSelected().name] = event.selected.value
|
||||
self.display:draw()
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'reset' then
|
||||
config.color = defaults
|
||||
config.displayDirectory = true
|
||||
self.directory.value = true
|
||||
_colors = Util.shallowCopy(defaults)
|
||||
|
||||
Config.update('shellprompt', config)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
config.color = _colors
|
||||
Config.update('shellprompt', config)
|
||||
|
||||
end
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
|
||||
return tab
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local allColors = { }
|
||||
for k,v in pairs(colors) do
|
||||
if type(v) == 'number' then
|
||||
table.insert(allColors, { name = k, value = v })
|
||||
end
|
||||
end
|
||||
|
||||
local allSettings = { }
|
||||
for k,v in pairs(UI.colors) do
|
||||
allSettings[k] = { name = k, value = v }
|
||||
end
|
||||
|
||||
return UI.Tab {
|
||||
tabTitle = 'Theme',
|
||||
description = 'Theme colors',
|
||||
grid1 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = 2, ex = -17,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allSettings,
|
||||
sortColumn = 'name',
|
||||
},
|
||||
grid2 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = -14, ex = -2,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allColors,
|
||||
sortColumn = 'name',
|
||||
getRowTextColor = function(self, row)
|
||||
local selected = self.parent.grid1:getSelected()
|
||||
if selected.value == row.value then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -9, y = -2,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
display = UI.Window {
|
||||
x = 2, ex = -2, y = -8, height = 5,
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.black,
|
||||
draw = function(self)
|
||||
self:clear()
|
||||
|
||||
self:write(1, 1, Util.widthify(' Local Global Device', self.width),
|
||||
allSettings.secondary.value)
|
||||
|
||||
self:write(2, 2, 'enter command ',
|
||||
colors.black, colors.gray)
|
||||
|
||||
self:write(1, 3, ' Formatted ',
|
||||
allSettings.primary.value)
|
||||
|
||||
self:write(12, 3, Util.widthify(' Output ', self.width - 11),
|
||||
allSettings.tertiary.value)
|
||||
|
||||
self:write(1, 4, Util.widthify(' Key', self.width),
|
||||
allSettings.primary.value)
|
||||
end,
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'grid_focus_row' and event.element == self.grid1 then
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == self.grid2 then
|
||||
self.grid1:getSelected().value = event.selected.value
|
||||
self.display:draw()
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
local config = Config.load('ui.theme', { colors = { } })
|
||||
for k,v in pairs(allSettings) do
|
||||
config.colors[k] = v.value
|
||||
end
|
||||
Config.update('ui.theme', config)
|
||||
end
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
}
|
||||
@@ -9,7 +9,7 @@ kernel.hook('clipboard_copy', function(_, args)
|
||||
keyboard.clipboard = args[1]
|
||||
end)
|
||||
|
||||
local function queuePaste()
|
||||
keyboard.addHotkey('shift-paste', function()
|
||||
local data = keyboard.clipboard
|
||||
|
||||
if type(data) == 'table' then
|
||||
@@ -20,7 +20,4 @@ local function queuePaste()
|
||||
if data then
|
||||
os.queueEvent('paste', data)
|
||||
end
|
||||
end
|
||||
|
||||
kernel.hook('clipboard_paste', queuePaste)
|
||||
keyboard.addHotkey('shift-paste', queuePaste)
|
||||
end)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
local fs = _G.fs
|
||||
|
||||
local function completeMultipleChoice(sText, tOptions, bAddSpaces)
|
||||
local tResults = { }
|
||||
for n = 1,#tOptions do
|
||||
@@ -22,14 +20,3 @@ _ENV.shell.setCompletionFunction("sys/apps/package.lua",
|
||||
return completeMultipleChoice(text, { "install ", "update ", "uninstall ", "updateall ", "refresh" })
|
||||
end
|
||||
end)
|
||||
|
||||
_ENV.shell.setCompletionFunction("sys/apps/inspect.lua",
|
||||
function(_, index, text)
|
||||
if index == 1 then
|
||||
local components = { }
|
||||
for _, f in pairs(fs.list('sys/modules/opus/ui/components')) do
|
||||
table.insert(components, (f:gsub("%.lua$", "")))
|
||||
end
|
||||
return completeMultipleChoice(text, components)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -4,45 +4,58 @@ local kernel = _G.kernel
|
||||
local keyboard = _G.device.keyboard
|
||||
local multishell = _ENV.multishell
|
||||
|
||||
if multishell and multishell.getTabs then
|
||||
-- restart tab
|
||||
keyboard.addHotkey('control-backspace', function()
|
||||
local tab = kernel.getFocused()
|
||||
if tab and not tab.noTerminate then
|
||||
multishell.terminate(tab.uid)
|
||||
multishell.openTab({
|
||||
path = tab.path,
|
||||
env = tab.env,
|
||||
args = tab.args,
|
||||
focused = true,
|
||||
})
|
||||
end
|
||||
end)
|
||||
if not multishell or not multishell.getTabs then
|
||||
return
|
||||
end
|
||||
|
||||
-- overview
|
||||
keyboard.addHotkey('control-o', function()
|
||||
for _,tab in pairs(multishell.getTabs()) do
|
||||
if tab.isOverview then
|
||||
multishell.setFocus(tab.uid)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- restart tab
|
||||
keyboard.addHotkey('control-backspace', function()
|
||||
local uid = multishell.getFocus()
|
||||
local tab = kernel.find(uid)
|
||||
if not tab.isOverview then
|
||||
multishell.terminate(uid)
|
||||
multishell.openTab({
|
||||
path = tab.path,
|
||||
env = tab.env,
|
||||
args = tab.args,
|
||||
focused = true,
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
-- next tab
|
||||
keyboard.addHotkey('control-tab', function()
|
||||
local tabs = multishell.getTabs()
|
||||
local visibleTabs = { }
|
||||
local currentTab = kernel.getFocused()
|
||||
local currentTabId = multishell.getFocus()
|
||||
|
||||
local function compareTab(a, b)
|
||||
return a.uid < b.uid
|
||||
end
|
||||
for _,tab in Util.spairs(kernel.routines, compareTab) do
|
||||
for _,tab in Util.spairs(tabs, compareTab) do
|
||||
if not tab.hidden and not tab.noFocus then
|
||||
table.insert(visibleTabs, tab)
|
||||
end
|
||||
end
|
||||
|
||||
for k,tab in ipairs(visibleTabs) do
|
||||
if tab.uid == currentTab.uid then
|
||||
if tab.uid == currentTabId then
|
||||
if k < #visibleTabs then
|
||||
kernel.raise(visibleTabs[k + 1].uid)
|
||||
multishell.setFocus(visibleTabs[k + 1].uid)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
if #visibleTabs > 0 then
|
||||
kernel.raise(visibleTabs[1].uid)
|
||||
multishell.setFocus(visibleTabs[1].uid)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -50,9 +50,16 @@ local function systemLog()
|
||||
keyboard.removeHotkey('control-d')
|
||||
end
|
||||
|
||||
kernel.run({
|
||||
title = 'System Log',
|
||||
fn = systemLog,
|
||||
noTerminate = true,
|
||||
hidden = true,
|
||||
})
|
||||
if multishell and multishell.openTab then
|
||||
multishell.openTab({
|
||||
title = 'System Log',
|
||||
fn = systemLog,
|
||||
noTerminate = true,
|
||||
hidden = true,
|
||||
})
|
||||
else
|
||||
kernel.run({
|
||||
title = 'Syslog',
|
||||
fn = systemLog,
|
||||
})
|
||||
end
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
local fs = _G.fs
|
||||
|
||||
local function deleteIfExists(path)
|
||||
if fs.exists(path) then
|
||||
fs.delete(path)
|
||||
print("Deleted outdated file at: "..path)
|
||||
end
|
||||
if fs.exists(path) then
|
||||
fs.delete(path)
|
||||
print("Deleted outdated file at: "..path)
|
||||
end
|
||||
end
|
||||
-- cleanup outdated files
|
||||
deleteIfExists('sys/apps/shell')
|
||||
@@ -18,7 +18,3 @@ deleteIfExists('sys/autorun/gpshost.lua')
|
||||
deleteIfExists('sys/apps/network/redserver.lua')
|
||||
deleteIfExists('sys/apis')
|
||||
deleteIfExists('sys/autorun/apps.lua')
|
||||
deleteIfExists('sys/init/6.tl3.lua')
|
||||
|
||||
-- remove this file
|
||||
-- deleteIfExists('sys/autorun/upgraded.lua')
|
||||
@@ -19,7 +19,7 @@ local function run(file, ...)
|
||||
end
|
||||
|
||||
_G._syslog = function() end
|
||||
_G.OPUS_BRANCH = 'develop-1.8'
|
||||
_G.OPUS_BRANCH = 'master-1.8'
|
||||
|
||||
-- Install require shim
|
||||
_G.requireInjector = run('sys/modules/opus/injector.lua')
|
||||
|
||||
@@ -50,10 +50,7 @@
|
||||
title = "System",
|
||||
category = "System",
|
||||
icon = "\030 \0307\031f| \010\0307\031f---o\030 \031 \010\030 \009 \0307\031f| ",
|
||||
iconExt = "22070b02424\
|
||||
0277044\
|
||||
7071724",
|
||||
--iconExt = "\030 \0318\138\0308\031 \130\0318\128\031 \129\030 \0318\133\010\030 \0318\143\0308\128\0317\143\0318\128\030 \143\010\030 \0318\138\135\143\139\133",
|
||||
iconExt = "\030 \0318\138\0308\031 \130\0318\128\031 \129\030 \0318\133\010\030 \0318\143\0308\128\0317\143\0318\128\030 \143\010\030 \0318\138\135\143\139\133",
|
||||
run = "System.lua",
|
||||
},
|
||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||
|
||||
@@ -6,11 +6,10 @@ Shortcut keys
|
||||
* l: Lua application
|
||||
* f: Files
|
||||
* e: Edit an application (or right-click)
|
||||
* n: Network
|
||||
* control-n: Add a new application
|
||||
* delete: Delete an application
|
||||
|
||||
Adding a new application
|
||||
========================
|
||||
The run entry can be either a disk file or a URL.
|
||||
Icons must be in NFT format with a height of 3 and a width of 3 to 8 characters. Magenta is used for transparency.
|
||||
Icons must be in NFT format with a height of 3 and a width of 3 to 8 characters.
|
||||
@@ -1,3 +1,5 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Peripheral = require('opus.peripheral')
|
||||
|
||||
_G.device = Peripheral.getList()
|
||||
|
||||
@@ -4,8 +4,11 @@ if fs.native then
|
||||
return
|
||||
end
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
local Util = require('opus.util')
|
||||
|
||||
-- TODO: support getDrive for virtual nodes
|
||||
|
||||
fs.native = Util.shallowCopy(fs)
|
||||
|
||||
local fstypes = { }
|
||||
@@ -20,6 +23,7 @@ for k,fn in pairs(fs) do
|
||||
end
|
||||
|
||||
function nativefs.list(node, dir)
|
||||
|
||||
local files
|
||||
if fs.native.isDir(dir) then
|
||||
files = fs.native.list(dir)
|
||||
@@ -261,6 +265,7 @@ local function getfstype(fstype)
|
||||
end
|
||||
|
||||
function fs.mount(path, fstype, ...)
|
||||
|
||||
local vfs = getfstype(fstype)
|
||||
if not vfs then
|
||||
error('Invalid file system type')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('opus.config')
|
||||
|
||||
local device = _G.device
|
||||
|
||||
@@ -32,23 +32,3 @@ end
|
||||
|
||||
help.setPath(table.concat(helpPaths, ':'))
|
||||
shell.setPath(table.concat(appPaths, ':'))
|
||||
|
||||
local function runDir(directory)
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
|
||||
for _,file in ipairs(files) do
|
||||
os.sleep(0)
|
||||
local result, err = shell.run(directory .. '/' .. file)
|
||||
if not result and err then
|
||||
_G.printError('\n' .. err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, package in pairs(Packages:installedSorted()) do
|
||||
local packageDir = 'packages/' .. package.name .. '/init'
|
||||
if fs.exists(packageDir) and fs.isDir(packageDir) then
|
||||
runDir(packageDir)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local Blit = require('opus.ui.blit')
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('opus.config')
|
||||
local trace = require('opus.trace')
|
||||
local Util = require('opus.util')
|
||||
@@ -46,7 +47,6 @@ local config = {
|
||||
Config.load('multishell', config)
|
||||
|
||||
local _colors = parentTerm.isColor() and config.color or config.standard
|
||||
local palette = parentTerm.isColor() and Blit.colorPalette or Blit.grayscalePalette
|
||||
|
||||
local function redrawMenu()
|
||||
if not tabsDirty then
|
||||
@@ -207,11 +207,17 @@ end)
|
||||
kernel.hook('multishell_redraw', function()
|
||||
tabsDirty = false
|
||||
|
||||
local blit = Blit(w, {
|
||||
bg = _colors.tabBarBackgroundColor,
|
||||
fg = _colors.textColor,
|
||||
palette = palette,
|
||||
})
|
||||
local function write(x, text, bg, fg)
|
||||
parentTerm.setBackgroundColor(bg)
|
||||
parentTerm.setTextColor(fg)
|
||||
parentTerm.setCursorPos(x, 1)
|
||||
parentTerm.write(text)
|
||||
end
|
||||
|
||||
local bg = _colors.tabBarBackgroundColor
|
||||
parentTerm.setBackgroundColor(bg)
|
||||
parentTerm.setCursorPos(1, 1)
|
||||
parentTerm.clearLine()
|
||||
|
||||
local currentTab = kernel.getFocused()
|
||||
|
||||
@@ -248,26 +254,21 @@ kernel.hook('multishell_redraw', function()
|
||||
tabX = tabX + tab.width
|
||||
if tab ~= currentTab then
|
||||
local textColor = tab.isDead and _colors.errorColor or _colors.textColor
|
||||
blit:write(tab.sx, tab.title:sub(1, tab.width - 1),
|
||||
write(tab.sx, tab.title:sub(1, tab.width - 1),
|
||||
_colors.backgroundColor, textColor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if currentTab then
|
||||
if currentTab.sx then
|
||||
blit:write(currentTab.sx - 1,
|
||||
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
|
||||
_colors.focusBackgroundColor, _colors.focusTextColor)
|
||||
end
|
||||
write(currentTab.sx - 1,
|
||||
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
|
||||
_colors.focusBackgroundColor, _colors.focusTextColor)
|
||||
if not currentTab.noTerminate then
|
||||
blit:write(w, closeInd, nil, _colors.focusTextColor)
|
||||
write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor)
|
||||
end
|
||||
end
|
||||
|
||||
parentTerm.setCursorPos(1, 1)
|
||||
parentTerm.blit(blit.text, blit.fg, blit.bg)
|
||||
|
||||
if currentTab and currentTab.window then
|
||||
currentTab.window.restoreCursor()
|
||||
end
|
||||
@@ -333,12 +334,16 @@ kernel.hook('mouse_scroll', function(_, eventData)
|
||||
end)
|
||||
|
||||
kernel.hook('kernel_ready', function()
|
||||
local env = Util.shallowCopy(shell.getEnv())
|
||||
_G.requireInjector(env)
|
||||
|
||||
overviewId = multishell.openTab({
|
||||
path = config.launcher or 'sys/apps/Overview.lua',
|
||||
isOverview = true,
|
||||
noTerminate = true,
|
||||
focused = true,
|
||||
title = '+',
|
||||
env = env,
|
||||
})
|
||||
|
||||
multishell.openTab({
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Array = require('opus.array')
|
||||
local Terminal = require('opus.terminal')
|
||||
local Util = require('opus.util')
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
local Util = require('opus.util')
|
||||
|
||||
local Array = { }
|
||||
|
||||
function Array.filter(it, f)
|
||||
@@ -16,11 +14,9 @@ function Array.removeByValue(t, e)
|
||||
for k,v in pairs(t) do
|
||||
if v == e then
|
||||
table.remove(t, k)
|
||||
return e
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Array.find = Util.find
|
||||
|
||||
return Array
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local shell = _ENV.shell
|
||||
|
||||
local Config = { }
|
||||
|
||||
@@ -24,6 +25,23 @@ function Config.load(fname, data)
|
||||
return data
|
||||
end
|
||||
|
||||
function Config.loadWithCheck(fname, data)
|
||||
local filename = 'usr/config/' .. fname
|
||||
|
||||
if not fs.exists(filename) then
|
||||
Config.load(fname, data)
|
||||
print()
|
||||
print('The configuration file has been created.')
|
||||
print('The file name is: ' .. filename)
|
||||
print()
|
||||
_G.printError('Press enter to configure')
|
||||
_G.read()
|
||||
shell.run('edit ' .. filename)
|
||||
end
|
||||
|
||||
return Config.load(fname, data)
|
||||
end
|
||||
|
||||
function Config.update(fname, data)
|
||||
local filename = 'usr/config/' .. fname
|
||||
Util.writeTable(filename, data)
|
||||
|
||||
@@ -12,11 +12,12 @@ local band = bit32.band
|
||||
local blshift = bit32.lshift
|
||||
local brshift = bit32.arshift
|
||||
local textutils = _G.textutils
|
||||
local mt = Util.byteArrayMT
|
||||
|
||||
local mod = 2^32
|
||||
local tau = {("expand 16-byte k"):byte(1,-1)}
|
||||
local sigma = {("expand 32-byte k"):byte(1,-1)}
|
||||
local null32 = {("A"):rep(32):byte(1,-1)}
|
||||
local null12 = {("A"):rep(12):byte(1,-1)}
|
||||
|
||||
local function rotl(n, b)
|
||||
local s = n/(2^(32-b))
|
||||
@@ -90,6 +91,22 @@ local function serialize(state)
|
||||
return r
|
||||
end
|
||||
|
||||
local mt = {
|
||||
__tostring = function(a) return string.char(table.unpack(a)) end,
|
||||
__index = {
|
||||
toHex = function(self) return ("%02x"):rep(#self):format(table.unpack(self)) end,
|
||||
isEqual = function(self, t)
|
||||
if type(t) ~= "table" then return false end
|
||||
if #self ~= #t then return false end
|
||||
local ret = 0
|
||||
for i = 1, #self do
|
||||
ret = bit32.bor(ret, bxor(self[i], t[i]))
|
||||
end
|
||||
return ret == 0
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
local function crypt(data, key, nonce, cntr, round)
|
||||
assert(type(key) == "table", "ChaCha20: Invalid key format ("..type(key).."), must be table")
|
||||
assert(type(nonce) == "table", "ChaCha20: Invalid nonce format ("..type(nonce).."), must be table")
|
||||
@@ -116,12 +133,15 @@ local function crypt(data, key, nonce, cntr, round)
|
||||
out[#out+1] = bxor(block[j], ks[j])
|
||||
end
|
||||
|
||||
throttle()
|
||||
--if i % 1000 == 0 then
|
||||
throttle()
|
||||
--os.queueEvent("")
|
||||
--os.pullEvent("")
|
||||
--end
|
||||
end
|
||||
return setmetatable(out, mt)
|
||||
end
|
||||
|
||||
-- Helper functions
|
||||
local function genNonce(len)
|
||||
local nonce = {}
|
||||
for i = 1, len do
|
||||
@@ -150,9 +170,6 @@ end
|
||||
local obj = {}
|
||||
local rng_mt = {['__index'] = obj}
|
||||
|
||||
-- PRNG object
|
||||
local null32 = {("A"):rep(32):byte(1,-1)}
|
||||
local null12 = {("A"):rep(12):byte(1,-1)}
|
||||
function obj:nextInt(byte)
|
||||
if not byte or byte < 1 or byte > 6 then error("Can only return 1-6 bytes", 2) end
|
||||
local output = 0
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
local fq = require('opus.crypto.ecc.fq')
|
||||
local elliptic = require('opus.crypto.ecc.elliptic')
|
||||
local sha256 = require('opus.crypto.sha2')
|
||||
local Util = require('opus.util')
|
||||
|
||||
|
||||
local os = _G.os
|
||||
local unpack = table.unpack
|
||||
local mt = Util.byteArrayMT
|
||||
|
||||
local q = {1372, 62520, 47765, 8105, 45059, 9616, 65535, 65535, 65535, 65535, 65535, 65532}
|
||||
|
||||
@@ -30,7 +27,7 @@ local function publicKey(sk)
|
||||
local Y = elliptic.scalarMulG(x)
|
||||
local pk = elliptic.pointEncode(Y)
|
||||
|
||||
return setmetatable(pk, mt)
|
||||
return pk
|
||||
end
|
||||
|
||||
local function exchange(sk, pk)
|
||||
@@ -65,7 +62,7 @@ local function sign(sk, message)
|
||||
sig[#sig + 1] = s[i]
|
||||
end
|
||||
|
||||
return setmetatable(sig, mt)
|
||||
return sig
|
||||
end
|
||||
|
||||
local function verify(pk, message, sig)
|
||||
|
||||
@@ -9,7 +9,6 @@ local bnot = bit32 and bit32.bnot or bit.bnot
|
||||
local bxor = bit32 and bit32.bxor or bit.bxor
|
||||
local blshift = bit32 and bit32.lshift or bit.blshift
|
||||
local upack = unpack or table.unpack
|
||||
local mt = Util.byteArrayMT
|
||||
|
||||
local function rrotate(n, b)
|
||||
local s = n/(2^b)
|
||||
@@ -69,16 +68,17 @@ end
|
||||
|
||||
local function digestblock(w, C)
|
||||
for j = 17, 64 do
|
||||
local s0 = bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18), brshift(w[j-15], 3))
|
||||
local s1 = bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19), brshift(w[j-2], 10))
|
||||
-- local v = w[j-15]
|
||||
local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
|
||||
local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
|
||||
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
|
||||
end
|
||||
local a, b, c, d, e, f, g, h = upack(C)
|
||||
for j = 1, 64 do
|
||||
local S1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
|
||||
local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
|
||||
local ch = bxor(band(e, f), band(bnot(e), g))
|
||||
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
|
||||
local S0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
|
||||
local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
|
||||
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
|
||||
local temp2 = (S0 + maj)%mod32
|
||||
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
|
||||
@@ -94,6 +94,22 @@ local function digestblock(w, C)
|
||||
return C
|
||||
end
|
||||
|
||||
local mt = {
|
||||
__tostring = function(a) return string.char(upack(a)) end,
|
||||
__index = {
|
||||
toHex = function(self) return ("%02x"):rep(#self):format(upack(self)) end,
|
||||
isEqual = function(self, t)
|
||||
if type(t) ~= "table" then return false end
|
||||
if #self ~= #t then return false end
|
||||
local ret = 0
|
||||
for i = 1, #self do
|
||||
ret = bit32.bor(ret, bxor(self[i], t[i]))
|
||||
end
|
||||
return ret == 0
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
local function toBytes(t, n)
|
||||
local b = {}
|
||||
for i = 1, n do
|
||||
|
||||
@@ -2,48 +2,39 @@ local class = require('opus.class')
|
||||
|
||||
local os = _G.os
|
||||
|
||||
-- convert value to a string (supporting nils or numbers in value)
|
||||
local function _val(a)
|
||||
return a and tostring(a) or ''
|
||||
end
|
||||
|
||||
local Entry = class()
|
||||
|
||||
function Entry:init(args)
|
||||
self.pos = 0
|
||||
self.scroll = 0
|
||||
self.value = args.value
|
||||
self.value = ''
|
||||
self.width = args.width or 256
|
||||
self.limit = args.limit or 1024
|
||||
self.mark = { }
|
||||
self.offset = args.offset or 1
|
||||
self.transform = args.transform or function(a) return a end
|
||||
end
|
||||
|
||||
function Entry:reset()
|
||||
self.pos = 0
|
||||
self.scroll = 0
|
||||
self.value = nil
|
||||
self.value = ''
|
||||
self.mark = { }
|
||||
end
|
||||
|
||||
function Entry:nextWord()
|
||||
local value = _val(self.value)
|
||||
return select(2, value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #value
|
||||
return select(2, self.value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #self.value
|
||||
end
|
||||
|
||||
function Entry:prevWord()
|
||||
local value = _val(self.value)
|
||||
local x = #value - (self.pos - 1)
|
||||
local _, n = value:reverse():find("[%s%p]?%w[%s%p]", x)
|
||||
return n and #value - n + 1 or 0
|
||||
local x = #self.value - (self.pos - 1)
|
||||
local _, n = self.value:reverse():find("[%s%p]?%w[%s%p]", x)
|
||||
return n and #self.value - n + 1 or 0
|
||||
end
|
||||
|
||||
function Entry:updateScroll()
|
||||
local ps = self.scroll
|
||||
local len = #_val(self.value)
|
||||
if self.pos > len then
|
||||
self.pos = len
|
||||
if self.pos > #self.value then
|
||||
self.pos = #self.value
|
||||
self.scroll = 0 -- ??
|
||||
end
|
||||
if self.pos - self.scroll > self.width then
|
||||
@@ -51,38 +42,27 @@ function Entry:updateScroll()
|
||||
elseif self.pos < self.scroll then
|
||||
self.scroll = self.pos
|
||||
end
|
||||
if self.scroll > 0 then
|
||||
if self.scroll + self.width > len then
|
||||
self.scroll = len - self.width
|
||||
end
|
||||
end
|
||||
if ps ~= self.scroll then
|
||||
self.textChanged = true
|
||||
end
|
||||
end
|
||||
|
||||
function Entry:copyText(cx, ex)
|
||||
-- this should be transformed (ie. if number)
|
||||
return _val(self.value):sub(cx + 1, ex)
|
||||
return self.value:sub(cx + 1, ex)
|
||||
end
|
||||
|
||||
function Entry:insertText(x, text)
|
||||
text = tostring(self.transform(text) or '')
|
||||
if #text > 0 then
|
||||
local value = _val(self.value)
|
||||
if #value + #text > self.limit then
|
||||
text = text:sub(1, self.limit-#value)
|
||||
end
|
||||
self.value = self.transform(value:sub(1, x) .. text .. value:sub(x + 1))
|
||||
self.pos = self.pos + #text
|
||||
if #self.value + #text > self.limit then
|
||||
text = text:sub(1, self.limit-#self.value)
|
||||
end
|
||||
self.value = self.value:sub(1, x) .. text .. self.value:sub(x + 1)
|
||||
self.pos = self.pos + #text
|
||||
end
|
||||
|
||||
function Entry:deleteText(sx, ex)
|
||||
local value = _val(self.value)
|
||||
local front = value:sub(1, sx)
|
||||
local back = value:sub(ex + 1, #value)
|
||||
self.value = self.transform(front .. back)
|
||||
local front = self.value:sub(1, sx)
|
||||
local back = self.value:sub(ex + 1, #self.value)
|
||||
self.value = front .. back
|
||||
self.pos = sx
|
||||
end
|
||||
|
||||
@@ -94,7 +74,7 @@ function Entry:moveLeft()
|
||||
end
|
||||
|
||||
function Entry:moveRight()
|
||||
if self.pos < #_val(self.value) then
|
||||
if self.pos < #self.value then
|
||||
self.pos = self.pos + 1
|
||||
return true
|
||||
end
|
||||
@@ -108,14 +88,14 @@ function Entry:moveHome()
|
||||
end
|
||||
|
||||
function Entry:moveEnd()
|
||||
if self.pos ~= #_val(self.value) then
|
||||
self.pos = #_val(self.value)
|
||||
if self.pos ~= #self.value then
|
||||
self.pos = #self.value
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Entry:moveTo(ie)
|
||||
self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #_val(self.value)))
|
||||
self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #self.value))
|
||||
end
|
||||
|
||||
function Entry:backspace()
|
||||
@@ -127,7 +107,7 @@ function Entry:backspace()
|
||||
end
|
||||
|
||||
function Entry:moveWordRight()
|
||||
if self.pos < #_val(self.value) then
|
||||
if self.pos < #self.value then
|
||||
self.pos = self:nextWord(self.value, self.pos + 1)
|
||||
return true
|
||||
end
|
||||
@@ -143,7 +123,7 @@ end
|
||||
function Entry:delete()
|
||||
if self.mark.active then
|
||||
self:deleteText(self.mark.x, self.mark.ex)
|
||||
elseif self.pos < #_val(self.value) then
|
||||
elseif self.pos < #self.value then
|
||||
self:deleteText(self.pos, self.pos + 1)
|
||||
end
|
||||
end
|
||||
@@ -157,16 +137,15 @@ function Entry:cutFromStart()
|
||||
end
|
||||
|
||||
function Entry:cutToEnd()
|
||||
local value = _val(self.value)
|
||||
if self.pos < #value then
|
||||
local text = self:copyText(self.pos, #value)
|
||||
self:deleteText(self.pos, #value)
|
||||
if self.pos < #self.value then
|
||||
local text = self:copyText(self.pos, #self.value)
|
||||
self:deleteText(self.pos, #self.value)
|
||||
os.queueEvent('clipboard_copy', text)
|
||||
end
|
||||
end
|
||||
|
||||
function Entry:cutNextWord()
|
||||
if self.pos < #_val(self.value) then
|
||||
if self.pos < #self.value then
|
||||
local ex = self:nextWord(self.value, self.pos)
|
||||
local text = self:copyText(self.pos, ex)
|
||||
self:deleteText(self.pos, ex)
|
||||
@@ -191,7 +170,7 @@ function Entry:insertChar(ie)
|
||||
end
|
||||
|
||||
function Entry:copy()
|
||||
if #_val(self.value) > 0 then
|
||||
if #self.value > 0 then
|
||||
self.mark.continue = true
|
||||
if self.mark.active then
|
||||
self:copyMarked()
|
||||
@@ -222,12 +201,8 @@ function Entry:paste(ie)
|
||||
end
|
||||
end
|
||||
|
||||
function Entry:forcePaste()
|
||||
os.queueEvent('clipboard_paste')
|
||||
end
|
||||
|
||||
function Entry:clearLine()
|
||||
if #_val(self.value) > 0 then
|
||||
if #self.value > 0 then
|
||||
self:reset()
|
||||
end
|
||||
end
|
||||
@@ -258,13 +233,10 @@ function Entry:unmark()
|
||||
end
|
||||
|
||||
function Entry:markAnchor(ie)
|
||||
local wasMarking = self.mark.active
|
||||
self:unmark()
|
||||
self:moveTo(ie)
|
||||
self:markBegin()
|
||||
self:markFinish()
|
||||
|
||||
self.textChanged = wasMarking
|
||||
end
|
||||
|
||||
function Entry:markLeft()
|
||||
@@ -285,7 +257,7 @@ function Entry:markWord(ie)
|
||||
local index = 1
|
||||
self:moveTo(ie)
|
||||
while true do
|
||||
local s, e = _val(self.value):find('%w+', index)
|
||||
local s, e = self.value:find('%w+', index)
|
||||
if not s or s - 1 > self.pos then
|
||||
break
|
||||
end
|
||||
@@ -316,12 +288,12 @@ function Entry:markPrevWord()
|
||||
end
|
||||
|
||||
function Entry:markAll()
|
||||
if #_val(self.value) > 0 then
|
||||
if #self.value > 0 then
|
||||
self.mark.anchor = { x = 1 }
|
||||
self.mark.active = true
|
||||
self.mark.continue = true
|
||||
self.mark.x = 0
|
||||
self.mark.ex = #_val(self.value)
|
||||
self.mark.ex = #self.value
|
||||
self.textChanged = true
|
||||
end
|
||||
end
|
||||
@@ -372,10 +344,9 @@ local mappings = {
|
||||
--[ 'control-d' ] = Entry.cutNextWord,
|
||||
[ 'control-x' ] = Entry.cut,
|
||||
[ 'paste' ] = Entry.paste,
|
||||
[ 'control-y' ] = Entry.forcePaste, -- well this won't work...
|
||||
-- [ 'control-y' ] = Entry.paste, -- well this won't work...
|
||||
|
||||
[ 'mouse_doubleclick' ] = Entry.markWord,
|
||||
[ 'mouse_tripleclick' ] = Entry.markAll,
|
||||
[ 'shift-left' ] = Entry.markLeft,
|
||||
[ 'shift-right' ] = Entry.markRight,
|
||||
[ 'mouse_down' ] = Entry.markAnchor,
|
||||
@@ -402,10 +373,6 @@ function Entry:process(ie)
|
||||
|
||||
action(self, ie)
|
||||
|
||||
if not self.value or #_val(self.value) == 0 then
|
||||
self.value = nil
|
||||
end
|
||||
|
||||
self.textChanged = self.textChanged or self.value ~= line
|
||||
self.posChanged = pos ~= self.pos
|
||||
self:updateScroll()
|
||||
|
||||
@@ -77,10 +77,6 @@ function ramfs.open(node, fn, fl)
|
||||
local ctr = 0
|
||||
local lines
|
||||
return {
|
||||
read = function()
|
||||
ctr = ctr + 1
|
||||
return node.contents:sub(ctr, ctr)
|
||||
end,
|
||||
readLine = function()
|
||||
if not lines then
|
||||
lines = Util.split(node.contents)
|
||||
|
||||
@@ -74,10 +74,6 @@ function urlfs.open(node, fn, fl)
|
||||
|
||||
if fl == 'r' then
|
||||
return {
|
||||
read = function()
|
||||
ctr = ctr + 1
|
||||
return c:sub(ctr, ctr)
|
||||
end,
|
||||
readLine = function()
|
||||
if not lines then
|
||||
lines = Util.split(c)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
-- Based on Squid's fuzzy search
|
||||
-- https://github.com/SquidDev-CC/artist/blob/vnext/artist/lib/match.lua
|
||||
--
|
||||
-- not very fuzzy anymore
|
||||
|
||||
local SCORE_WEIGHT = 1000
|
||||
local LEADING_LETTER_PENALTY = -30
|
||||
local LEADING_LETTER_PENALTY_MAX = -90
|
||||
|
||||
local _find = string.find
|
||||
local _max = math.max
|
||||
|
||||
return function(str, pattern)
|
||||
local start = _find(str, pattern, 1, true)
|
||||
if start then
|
||||
-- All letters before the current one are considered leading, so add them to our penalty
|
||||
return SCORE_WEIGHT
|
||||
+ _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
|
||||
- (#str - #pattern)
|
||||
end
|
||||
end
|
||||
@@ -3,13 +3,15 @@ local Util = require('opus.util')
|
||||
|
||||
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'
|
||||
local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
|
||||
local TREE_HEADERS = {}
|
||||
local git = { }
|
||||
|
||||
if _G._GIT_API_KEY then
|
||||
TREE_HEADERS.Authorization = 'token ' .. _G._GIT_API_KEY
|
||||
TREE_URL = TREE_URL .. '&access_token=' .. _G._GIT_API_KEY
|
||||
end
|
||||
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
|
||||
function git.list(repository)
|
||||
local t = Util.split(repository, '(.-)/')
|
||||
|
||||
@@ -24,10 +26,8 @@ function git.list(repository)
|
||||
|
||||
local function getContents()
|
||||
local dataUrl = string.format(TREE_URL, user, repo, branch)
|
||||
local contents, msg = Util.httpGet(dataUrl, TREE_HEADERS)
|
||||
if not contents then
|
||||
error(string.format('Failed to download %s\n%s', dataUrl, msg), 2)
|
||||
else
|
||||
local contents = Util.download(dataUrl)
|
||||
if contents then
|
||||
return json.decode(contents)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,71 +1,17 @@
|
||||
local Util = require('opus.util')
|
||||
|
||||
local GPS = { }
|
||||
GPS.CHANNEL_GPS = 65534
|
||||
|
||||
local device = _G.device
|
||||
local vector = _G.vector
|
||||
local gps = _G.gps
|
||||
|
||||
function GPS.locate(timeout, debug)
|
||||
if not device.wireless_modem then
|
||||
if debug then
|
||||
print('No wireless modem attached')
|
||||
end
|
||||
return nil
|
||||
local pt = { }
|
||||
timeout = timeout or 10
|
||||
pt.x, pt.y, pt.z = gps.locate(timeout, debug)
|
||||
if pt.x then
|
||||
return pt
|
||||
end
|
||||
|
||||
if debug then
|
||||
print('Finding position...')
|
||||
end
|
||||
|
||||
local modem = device.wireless_modem
|
||||
local closeChannel = false
|
||||
local selfID = os.getComputerID()
|
||||
if not modem.isOpen(selfID) then
|
||||
modem.open(selfID)
|
||||
closeChannel = true
|
||||
end
|
||||
|
||||
modem.transmit(GPS.CHANNEL_GPS, selfID, "PING")
|
||||
|
||||
local fixes = {}
|
||||
local pos = nil
|
||||
local timer = os.startTimer(timeout or 1)
|
||||
while true do
|
||||
local e, side, chan, reply, msg, dist = os.pullEvent()
|
||||
if e == "modem_message" then
|
||||
if side == modem.side and chan == selfID and reply == GPS.CHANNEL_GPS and dist then
|
||||
if type(msg) == "table" and #msg == 3 and tonumber(msg[1]) and tonumber(msg[2]) and tonumber(msg[3]) then
|
||||
local fix = {
|
||||
position = vector.new(unpack(msg)),
|
||||
distance = dist,
|
||||
}
|
||||
if debug then
|
||||
print(fix.distance..' meters from '..fix.position:tostring())
|
||||
end
|
||||
if fix.distance == 0 then
|
||||
pos = fix.position
|
||||
else
|
||||
fixes[#fixes+1] = fix
|
||||
if #fixes > 3 then
|
||||
pos = GPS.trilaterate(fixes)
|
||||
if pos then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif e == "timer" and side == timer then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if closeChannel then
|
||||
modem.close(selfID)
|
||||
end
|
||||
if debug then
|
||||
print("Position is "..pos.x..","..pos.y..","..pos.z)
|
||||
end
|
||||
return pos and vector.new(pos.x, pos.y, pos.z)
|
||||
end
|
||||
|
||||
function GPS.isAvailable()
|
||||
@@ -120,26 +66,26 @@ local function trilaterate(A, B, C)
|
||||
local result1 = result + (ez * z)
|
||||
local result2 = result - (ez * z)
|
||||
|
||||
local rounded1, rounded2 = result1:round(0.01), result2:round(0.01)
|
||||
local rounded1, rounded2 = result1:round(), result2:round()
|
||||
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
|
||||
return rounded1, rounded2
|
||||
else
|
||||
return rounded1
|
||||
end
|
||||
end
|
||||
return result:round(0.01)
|
||||
return result:round()
|
||||
end
|
||||
|
||||
local function narrow( p1, p2, fix )
|
||||
local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
|
||||
local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
|
||||
|
||||
if math.abs(dist1 - dist2) < 0.01 then
|
||||
if math.abs(dist1 - dist2) < 0.05 then
|
||||
return p1, p2
|
||||
elseif dist1 < dist2 then
|
||||
return p1:round(0.01)
|
||||
return p1:round()
|
||||
else
|
||||
return p2:round(0.01)
|
||||
return p2:round()
|
||||
end
|
||||
end
|
||||
-- end stock gps api
|
||||
@@ -152,7 +98,7 @@ function GPS.trilaterate(tFixes)
|
||||
if pos2 then
|
||||
pos1, pos2 = narrow(pos1, pos2, tFixes[1])
|
||||
end
|
||||
if not pos2 and pos1 and not (pos1.x ~= pos1.x) then
|
||||
if not pos2 then
|
||||
return pos1, attemps
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,20 +50,18 @@ function input:toCode(ch, code)
|
||||
table.insert(result, 'alt')
|
||||
end
|
||||
|
||||
if ch then -- some weird things happen with control/command on mac
|
||||
if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or
|
||||
code == keys.leftShift or code == keys.rightShift then
|
||||
if code and modifiers[code] then
|
||||
table.insert(result, 'shift')
|
||||
elseif #ch == 1 then
|
||||
table.insert(result, ch:upper())
|
||||
else
|
||||
table.insert(result, 'shift')
|
||||
table.insert(result, ch)
|
||||
end
|
||||
elseif not code or not modifiers[code] then
|
||||
if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or
|
||||
code == keys.leftShift or code == keys.rightShift then
|
||||
if code and modifiers[code] then
|
||||
table.insert(result, 'shift')
|
||||
elseif #ch == 1 then
|
||||
table.insert(result, ch:upper())
|
||||
else
|
||||
table.insert(result, 'shift')
|
||||
table.insert(result, ch)
|
||||
end
|
||||
elseif not code or not modifiers[code] then
|
||||
table.insert(result, ch)
|
||||
end
|
||||
|
||||
return table.concat(result, '-')
|
||||
@@ -120,7 +118,6 @@ function input:translate(event, code, p1, p2)
|
||||
local buttons = { 'mouse_click', 'mouse_rightclick' }
|
||||
self.mch = buttons[code]
|
||||
self.mfired = nil
|
||||
self.anchor = { x = p1, y = p2 }
|
||||
return {
|
||||
code = input:toCode('mouse_down', 255),
|
||||
button = code,
|
||||
@@ -135,8 +132,6 @@ function input:translate(event, code, p1, p2)
|
||||
button = code,
|
||||
x = p1,
|
||||
y = p2,
|
||||
dx = p1 - self.anchor.x,
|
||||
dy = p2 - self.anchor.y,
|
||||
}
|
||||
|
||||
elseif event == 'mouse_up' then
|
||||
@@ -146,26 +141,18 @@ function input:translate(event, code, p1, p2)
|
||||
p1 == self.x and p2 == self.y and
|
||||
(clock - self.timer < .5) then
|
||||
|
||||
self.clickCount = self.clickCount + 1
|
||||
if self.clickCount == 3 then
|
||||
self.mch = 'mouse_tripleclick'
|
||||
self.timer = nil
|
||||
self.clickCount = 1
|
||||
else
|
||||
self.mch = 'mouse_doubleclick'
|
||||
end
|
||||
self.mch = 'mouse_doubleclick'
|
||||
self.timer = nil
|
||||
else
|
||||
self.timer = os.clock()
|
||||
self.x = p1
|
||||
self.y = p2
|
||||
self.clickCount = 1
|
||||
end
|
||||
self.mfired = input:toCode(self.mch, 255)
|
||||
else
|
||||
self.mch = 'mouse_up'
|
||||
self.mfired = input:toCode(self.mch, 255)
|
||||
end
|
||||
|
||||
return {
|
||||
code = self.mfired,
|
||||
button = code,
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local NFT = { }
|
||||
|
||||
-- largely copied from http://www.computercraft.info/forums2/index.php?/topic/5029-145-npaintpro/
|
||||
|
||||
local hexToColor = { }
|
||||
local tColourLookup = { }
|
||||
for n = 1, 16 do
|
||||
hexToColor[string.sub("0123456789abcdef", n, n)] = 2 ^ (n - 1)
|
||||
tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
|
||||
end
|
||||
local colorToHex = Util.transpose(hexToColor)
|
||||
|
||||
local function getColourOf(hex)
|
||||
return hexToColor[hex]
|
||||
return tColourLookup[hex:byte()]
|
||||
end
|
||||
|
||||
function NFT.parse(imageText)
|
||||
@@ -65,22 +62,8 @@ function NFT.parse(imageText)
|
||||
return image
|
||||
end
|
||||
|
||||
function NFT.transparency(image)
|
||||
for y = 1, image.height do
|
||||
for _,key in pairs(Util.keys(image.fg[y])) do
|
||||
if image.fg[y][key] == colors.magenta then
|
||||
image.fg[y][key] = nil
|
||||
end
|
||||
end
|
||||
for _,key in pairs(Util.keys(image.bg[y])) do
|
||||
if image.bg[y][key] == colors.magenta then
|
||||
image.bg[y][key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NFT.load(path)
|
||||
|
||||
local imageText = Util.readFile(path)
|
||||
if not imageText then
|
||||
error('Unable to read image file')
|
||||
@@ -88,35 +71,4 @@ function NFT.load(path)
|
||||
return NFT.parse(imageText)
|
||||
end
|
||||
|
||||
function NFT.save(image, filename)
|
||||
local bgcode, txcode = '\30', '\31'
|
||||
local output = { }
|
||||
|
||||
for y = 1, image.height do
|
||||
local lastBG, lastFG
|
||||
if image.text[y] then
|
||||
for x = 1, #image.text[y] do
|
||||
local bg = image.bg[y][x] or colors.magenta
|
||||
if bg ~= lastBG then
|
||||
lastBG = bg
|
||||
table.insert(output, bgcode .. colorToHex[bg])
|
||||
end
|
||||
|
||||
local fg = image.fg[y][x] or colors.magenta
|
||||
if fg ~= lastFG then
|
||||
lastFG = fg
|
||||
table.insert(output, txcode .. colorToHex[fg])
|
||||
end
|
||||
|
||||
table.insert(output, image.text[y][x])
|
||||
end
|
||||
end
|
||||
|
||||
if y < image.height then
|
||||
table.insert(output, '\n')
|
||||
end
|
||||
end
|
||||
Util.writeFile(filename, table.concat(output))
|
||||
end
|
||||
|
||||
return NFT
|
||||
|
||||
@@ -57,7 +57,7 @@ end
|
||||
function Packages:downloadList()
|
||||
local packages = {
|
||||
[ 'develop-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/packages.list',
|
||||
[ 'master-1.8' ] = 'https://pastebin.com/raw/pexZpAxt',
|
||||
[ 'master-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/packages.list',
|
||||
}
|
||||
|
||||
if packages[_G.OPUS_BRANCH] then
|
||||
|
||||
@@ -126,11 +126,7 @@ function Socket.connect(host, port, options)
|
||||
|
||||
local socket = newSocket(host == os.getComputerID())
|
||||
socket.dhost = tonumber(host)
|
||||
if options and options.keypair then
|
||||
socket.privKey, socket.pubKey = unpack(options.keypair)
|
||||
else
|
||||
socket.privKey, socket.pubKey = network.getKeyPair()
|
||||
end
|
||||
socket.privKey, socket.pubKey = network.getKeyPair()
|
||||
local identifier = options and options.identifier or Security.getIdentifier()
|
||||
|
||||
socket.transmit(port, socket.sport, {
|
||||
@@ -139,7 +135,7 @@ function Socket.connect(host, port, options)
|
||||
dhost = socket.dhost,
|
||||
t = Crypto.encrypt({ -- this is not that much data...
|
||||
ts = os.epoch('utc'),
|
||||
pk = socket.pubKey:toHex(),
|
||||
pk = Util.byteArrayToHex(socket.pubKey),
|
||||
}, Util.hexToByteArray(identifier)),
|
||||
})
|
||||
|
||||
@@ -237,7 +233,7 @@ function Socket.server(port, options)
|
||||
type = 'CONN',
|
||||
dhost = socket.dhost,
|
||||
shost = socket.shost,
|
||||
pk = socket.pubKey:toHex(),
|
||||
pk = Util.byteArrayToHex(socket.pubKey),
|
||||
options = socket.options.ENCRYPT and { ENCRYPT = true },
|
||||
})
|
||||
|
||||
|
||||
@@ -36,66 +36,61 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
local maxScroll = 100
|
||||
local cx, cy = 1, 1
|
||||
local blink = false
|
||||
local _bg, _fg = parent.getBackgroundColor(), parent.getTextColor()
|
||||
local bg, fg = parent.getBackgroundColor(), parent.getTextColor()
|
||||
|
||||
win.canvas = Canvas({
|
||||
local canvas = Canvas({
|
||||
x = sx,
|
||||
y = sy,
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = parent.isColor(),
|
||||
offy = 0,
|
||||
bg = _bg,
|
||||
fg = _fg,
|
||||
})
|
||||
|
||||
win.canvas = canvas
|
||||
|
||||
local function update()
|
||||
if isVisible then
|
||||
win.canvas:render(parent)
|
||||
canvas:render(parent)
|
||||
win.setCursorPos(cx, cy)
|
||||
end
|
||||
end
|
||||
|
||||
local function scrollTo(y)
|
||||
y = math.max(0, y)
|
||||
y = math.min(#win.canvas.lines - win.canvas.height, y)
|
||||
y = math.min(#canvas.lines - canvas.height, y)
|
||||
|
||||
if y ~= win.canvas.offy then
|
||||
win.canvas.offy = y
|
||||
win.canvas:dirty()
|
||||
if y ~= canvas.offy then
|
||||
canvas.offy = y
|
||||
canvas:dirty()
|
||||
update()
|
||||
end
|
||||
end
|
||||
|
||||
function win.write(str)
|
||||
str = tostring(str) or ''
|
||||
win.canvas:write(cx, cy + win.canvas.offy, str, win.canvas.bg, win.canvas.fg)
|
||||
canvas:write(cx, cy + canvas.offy, str, bg, fg)
|
||||
win.setCursorPos(cx + #str, cy)
|
||||
update()
|
||||
end
|
||||
|
||||
function win.blit(str, fg, bg)
|
||||
win.canvas:blit(cx, cy + win.canvas.offy, str, bg, fg)
|
||||
canvas:blit(cx, cy + canvas.offy, str, bg, fg)
|
||||
win.setCursorPos(cx + #str, cy)
|
||||
update()
|
||||
end
|
||||
|
||||
function win.clear()
|
||||
win.canvas.offy = 0
|
||||
for i = #win.canvas.lines, win.canvas.height + 1, -1 do
|
||||
win.canvas.lines[i] = nil
|
||||
canvas.offy = 0
|
||||
for i = #canvas.lines, canvas.height + 1, -1 do
|
||||
canvas.lines[i] = nil
|
||||
end
|
||||
win.canvas:clear()
|
||||
canvas:clear(bg, fg)
|
||||
update()
|
||||
end
|
||||
|
||||
function win.getLine(n)
|
||||
local line = win.canvas.lines[n]
|
||||
return line.text, line.fg, line.bg
|
||||
end
|
||||
|
||||
function win.clearLine()
|
||||
win.canvas:clearLine(cy + win.canvas.offy)
|
||||
canvas:clearLine(cy + canvas.offy, bg, fg)
|
||||
win.setCursorPos(cx, cy)
|
||||
update()
|
||||
end
|
||||
@@ -107,14 +102,10 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
function win.setCursorPos(x, y)
|
||||
cx, cy = math.floor(x), math.floor(y)
|
||||
if isVisible then
|
||||
parent.setCursorPos(cx + win.canvas.x - 1, cy + win.canvas.y - 1)
|
||||
parent.setCursorPos(cx + canvas.x - 1, cy + canvas.y - 1)
|
||||
end
|
||||
end
|
||||
|
||||
function win.getCursorBlink()
|
||||
return blink
|
||||
end
|
||||
|
||||
function win.setCursorBlink(b)
|
||||
blink = b
|
||||
if isVisible then
|
||||
@@ -123,12 +114,12 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
end
|
||||
|
||||
function win.isColor()
|
||||
return win.canvas.isColor
|
||||
return canvas.isColor
|
||||
end
|
||||
win.isColour = win.isColor
|
||||
|
||||
function win.setTextColor(c)
|
||||
win.canvas.fg = c
|
||||
fg = c
|
||||
end
|
||||
win.setTextColour = win.setTextColor
|
||||
|
||||
@@ -148,38 +139,38 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
win.setPaletteColour = win.setPaletteColor
|
||||
|
||||
function win.setBackgroundColor(c)
|
||||
win.canvas.bg = c
|
||||
bg = c
|
||||
end
|
||||
win.setBackgroundColour = win.setBackgroundColor
|
||||
|
||||
function win.getSize()
|
||||
return win.canvas.width, win.canvas.height
|
||||
return canvas.width, canvas.height
|
||||
end
|
||||
|
||||
function win.scroll(n)
|
||||
n = n or 1
|
||||
if n > 0 then
|
||||
local lines = #win.canvas.lines
|
||||
local lines = #canvas.lines
|
||||
for i = 1, n do
|
||||
win.canvas.lines[lines + i] = { }
|
||||
win.canvas:clearLine(lines + i)
|
||||
canvas.lines[lines + i] = { }
|
||||
canvas:clearLine(lines + i, bg, fg)
|
||||
end
|
||||
while #win.canvas.lines > maxScroll do
|
||||
table.remove(win.canvas.lines, 1)
|
||||
while #canvas.lines > maxScroll do
|
||||
table.remove(canvas.lines, 1)
|
||||
end
|
||||
scrollTo(#win.canvas.lines)
|
||||
win.canvas:dirty()
|
||||
scrollTo(#canvas.lines)
|
||||
canvas:dirty()
|
||||
update()
|
||||
end
|
||||
end
|
||||
|
||||
function win.getTextColor()
|
||||
return win.canvas.fg
|
||||
return fg
|
||||
end
|
||||
win.getTextColour = win.getTextColor
|
||||
|
||||
function win.getBackgroundColor()
|
||||
return win.canvas.bg
|
||||
return bg
|
||||
end
|
||||
win.getBackgroundColour = win.getBackgroundColor
|
||||
|
||||
@@ -187,7 +178,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
if visible ~= isVisible then
|
||||
isVisible = visible
|
||||
if isVisible then
|
||||
win.canvas:dirty()
|
||||
canvas:dirty()
|
||||
update()
|
||||
end
|
||||
end
|
||||
@@ -195,7 +186,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
|
||||
function win.redraw()
|
||||
if isVisible then
|
||||
win.canvas:dirty()
|
||||
canvas:dirty()
|
||||
update()
|
||||
end
|
||||
end
|
||||
@@ -203,27 +194,27 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
function win.restoreCursor()
|
||||
if isVisible then
|
||||
win.setCursorPos(cx, cy)
|
||||
win.setTextColor(win.canvas.fg)
|
||||
win.setTextColor(fg)
|
||||
win.setCursorBlink(blink)
|
||||
end
|
||||
end
|
||||
|
||||
function win.getPosition()
|
||||
return win.canvas.x, win.canvas.y
|
||||
return canvas.x, canvas.y
|
||||
end
|
||||
|
||||
function win.reposition(x, y, width, height)
|
||||
win.canvas.x, win.canvas.y = x, y
|
||||
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
|
||||
canvas.x, canvas.y = x, y
|
||||
canvas:resize(width or canvas.width, height or canvas.height)
|
||||
end
|
||||
|
||||
--[[ Additional methods ]]--
|
||||
function win.scrollDown()
|
||||
scrollTo(win.canvas.offy + 1)
|
||||
scrollTo(canvas.offy + 1)
|
||||
end
|
||||
|
||||
function win.scrollUp()
|
||||
scrollTo(win.canvas.offy - 1)
|
||||
scrollTo(canvas.offy - 1)
|
||||
end
|
||||
|
||||
function win.scrollTop()
|
||||
@@ -231,7 +222,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
end
|
||||
|
||||
function win.scrollBottom()
|
||||
scrollTo(#win.canvas.lines)
|
||||
scrollTo(#canvas.lines)
|
||||
end
|
||||
|
||||
function win.setMaxScroll(ms)
|
||||
@@ -239,35 +230,37 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
end
|
||||
|
||||
function win.getCanvas()
|
||||
return win.canvas
|
||||
return canvas
|
||||
end
|
||||
|
||||
function win.getParent()
|
||||
return parent
|
||||
end
|
||||
|
||||
win.canvas:clear()
|
||||
canvas:clear()
|
||||
|
||||
return win
|
||||
end
|
||||
|
||||
-- get windows contents
|
||||
function Terminal.getContents(win)
|
||||
if not win.getLine then
|
||||
error('window is required')
|
||||
end
|
||||
|
||||
function Terminal.getContents(win, parent)
|
||||
local oblit, oscp = parent.blit, parent.setCursorPos
|
||||
local lines = { }
|
||||
local _, h = win.getSize()
|
||||
|
||||
for i = 1, h do
|
||||
local text, fg, bg = win.getLine(i)
|
||||
lines[i] = {
|
||||
parent.blit = function(text, fg, bg)
|
||||
lines[#lines + 1] = {
|
||||
text = text,
|
||||
fg = fg,
|
||||
bg = bg,
|
||||
}
|
||||
end
|
||||
parent.setCursorPos = function() end
|
||||
|
||||
win.setVisible(true)
|
||||
win.redraw()
|
||||
|
||||
parent.blit = oblit
|
||||
parent.setCursorPos = oscp
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,174 +0,0 @@
|
||||
local colors = _G.colors
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
local Blit = { }
|
||||
|
||||
Blit.colorPalette = { }
|
||||
Blit.grayscalePalette = { }
|
||||
|
||||
for n = 1, 16 do
|
||||
Blit.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
Blit.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
|
||||
end
|
||||
|
||||
-- default palette
|
||||
Blit.palette = Blit.colorPalette
|
||||
|
||||
function Blit:init(t, args)
|
||||
if args then
|
||||
for k,v in pairs(args) do
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
if type(t) == 'string' then
|
||||
-- create a blit from a string
|
||||
self.text, self.bg, self.fg = Blit.toblit(t, args or { })
|
||||
|
||||
elseif type(t) == 'number' then
|
||||
-- create a fixed width blit
|
||||
self.width = t
|
||||
self.text = _rep(' ', self.width)
|
||||
self.bg = _rep(self.palette[args.bg], self.width)
|
||||
self.fg = _rep(self.palette[args.fg], self.width)
|
||||
|
||||
else
|
||||
self.text = t.text
|
||||
self.bg = t.bg
|
||||
self.fg = t.fg
|
||||
end
|
||||
end
|
||||
|
||||
function Blit:write(x, text, bg, fg)
|
||||
self:insert(x, text,
|
||||
bg and _rep(self.palette[bg], #text),
|
||||
fg and _rep(self.palette[fg], #text))
|
||||
end
|
||||
|
||||
function Blit:insert(x, text, bg, fg)
|
||||
if x <= self.width then
|
||||
local width = #text
|
||||
local tx, tex
|
||||
|
||||
if x < 1 then
|
||||
tx = 2 - x
|
||||
width = width + x - 1
|
||||
x = 1
|
||||
end
|
||||
|
||||
if x + width - 1 > self.width then
|
||||
tex = self.width - x + (tx or 1)
|
||||
width = tex - (tx or 1) + 1
|
||||
end
|
||||
|
||||
if width > 0 then
|
||||
local function replace(sstr, rstr)
|
||||
if tx or tex then
|
||||
rstr = _sub(rstr, tx or 1, tex)
|
||||
end
|
||||
if x == 1 and width == self.width then
|
||||
return rstr
|
||||
elseif x == 1 then
|
||||
return rstr .. _sub(sstr, x + width)
|
||||
elseif x + width > self.width then
|
||||
return _sub(sstr, 1, x - 1) .. rstr
|
||||
end
|
||||
return _sub(sstr, 1, x - 1) .. rstr .. _sub(sstr, x + width)
|
||||
end
|
||||
|
||||
self.text = replace(self.text, text)
|
||||
if fg then
|
||||
self.fg = replace(self.fg, fg)
|
||||
end
|
||||
if bg then
|
||||
self.bg = replace(self.bg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Blit:sub(s, e)
|
||||
return Blit({
|
||||
text = self.text:sub(s, e),
|
||||
bg = self.bg:sub(s, e),
|
||||
fg = self.fg:sub(s, e),
|
||||
})
|
||||
end
|
||||
|
||||
function Blit:wrap(max)
|
||||
local lines = { }
|
||||
local data = self
|
||||
|
||||
repeat
|
||||
if #data.text <= max then
|
||||
table.insert(lines, data)
|
||||
break
|
||||
elseif data.text:sub(max+1, max+1) == ' ' then
|
||||
table.insert(lines, data:sub(1, max))
|
||||
data = data:sub(max + 2)
|
||||
else
|
||||
local x = data.text:sub(1, max)
|
||||
local s = x:match('(.*) ') or x
|
||||
table.insert(lines, data:sub(1, #s))
|
||||
data = data:sub(#s + 1)
|
||||
end
|
||||
local t = data.text:match('^%s*(.*)')
|
||||
local spaces = #data.text - #t
|
||||
if spaces > 0 then
|
||||
data = data:sub(spaces + 1)
|
||||
end
|
||||
until not data.text or #data.text == 0
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- convert a string of text to blit format doing color conversion
|
||||
-- and processing ansi color sequences
|
||||
function Blit.toblit(str, cs)
|
||||
local text, fg, bg = '', '', ''
|
||||
|
||||
if not cs.cbg then
|
||||
-- reset colors
|
||||
cs.rbg = cs.bg or colors.black
|
||||
cs.rfg = cs.fg or colors.white
|
||||
-- current colors
|
||||
cs.cbg = cs.rbg
|
||||
cs.cfg = cs.rfg
|
||||
|
||||
cs.palette = cs.palette or Blit.palette
|
||||
end
|
||||
|
||||
str = str:gsub('(.-)\027%[([%d;]+)m',
|
||||
function(k, seq)
|
||||
text = text .. k
|
||||
bg = bg .. string.rep(cs.palette[cs.cbg], #k)
|
||||
fg = fg .. string.rep(cs.palette[cs.cfg], #k)
|
||||
for color in string.gmatch(seq, "%d+") do
|
||||
color = tonumber(color)
|
||||
if color == 0 then
|
||||
-- reset to default
|
||||
cs.cfg = cs.rfg
|
||||
cs.cbg = cs.rbg
|
||||
elseif color > 20 then
|
||||
cs.cbg = 2 ^ (color - 21)
|
||||
else
|
||||
cs.cfg = 2 ^ (color - 1)
|
||||
end
|
||||
end
|
||||
return k
|
||||
end)
|
||||
|
||||
local k = str:sub(#text + 1)
|
||||
return text .. k,
|
||||
bg .. string.rep(cs.palette[cs.cbg], #k),
|
||||
fg .. string.rep(cs.palette[cs.cfg], #k)
|
||||
end
|
||||
|
||||
return setmetatable(Blit, {
|
||||
__call = function(_, ...)
|
||||
local obj = setmetatable({ }, { __index = Blit })
|
||||
obj:init(...)
|
||||
return obj
|
||||
end
|
||||
})
|
||||
@@ -9,34 +9,27 @@ local colors = _G.colors
|
||||
|
||||
local Canvas = class()
|
||||
|
||||
local function genPalette(map)
|
||||
local t = { }
|
||||
local rcolors = Util.transpose(colors)
|
||||
for n = 1, 16 do
|
||||
local pow = 2 ^ (n - 1)
|
||||
local ch = _sub(map, n, n)
|
||||
t[pow] = ch
|
||||
t[rcolors[pow]] = ch
|
||||
end
|
||||
return t
|
||||
end
|
||||
Canvas.colorPalette = { }
|
||||
Canvas.darkPalette = { }
|
||||
Canvas.grayscalePalette = { }
|
||||
|
||||
Canvas.colorPalette = genPalette('0123456789abcdef')
|
||||
Canvas.grayscalePalette = genPalette('088888878877787f')
|
||||
for n = 1, 16 do
|
||||
Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
|
||||
Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n)
|
||||
end
|
||||
|
||||
--[[
|
||||
A canvas can have more lines than canvas.height in order to scroll
|
||||
|
||||
TODO: finish vertical scrolling
|
||||
]]
|
||||
|
||||
function Canvas:init(args)
|
||||
self.bg = colors.black
|
||||
self.fg = colors.white
|
||||
self.x = 1
|
||||
self.y = 1
|
||||
self.layers = { }
|
||||
|
||||
Util.merge(self, args)
|
||||
|
||||
self.x = self.x or 1
|
||||
self.y = self.y or 1
|
||||
self.ex = self.x + self.width - 1
|
||||
self.ey = self.y + self.height - 1
|
||||
|
||||
@@ -52,31 +45,16 @@ function Canvas:init(args)
|
||||
for i = 1, self.height do
|
||||
self.lines[i] = { }
|
||||
end
|
||||
|
||||
self:clear()
|
||||
end
|
||||
|
||||
function Canvas:move(x, y)
|
||||
self.x, self.y = x, y
|
||||
self.ex = self.x + self.width - 1
|
||||
self.ey = self.y + self.height - 1
|
||||
if self.parent then
|
||||
self.parent:dirty(true)
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:resize(w, h)
|
||||
self:resizeBuffer(w, h)
|
||||
|
||||
self.ex = self.x + w - 1
|
||||
self.ey = self.y + h - 1
|
||||
self.width = w
|
||||
self.height = h
|
||||
end
|
||||
|
||||
-- resize the canvas buffer - not the canvas itself
|
||||
function Canvas:resizeBuffer(w, h)
|
||||
for i = #self.lines + 1, h do
|
||||
for i = #self.lines, h do
|
||||
self.lines[i] = { }
|
||||
self:clearLine(i)
|
||||
end
|
||||
@@ -87,24 +65,26 @@ function Canvas:resizeBuffer(w, h)
|
||||
|
||||
if w < self.width then
|
||||
for i = 1, h do
|
||||
local ln = self.lines[i]
|
||||
ln.text = _sub(ln.text, 1, w)
|
||||
ln.fg = _sub(ln.fg, 1, w)
|
||||
ln.bg = _sub(ln.bg, 1, w)
|
||||
self.lines[i].text = _sub(self.lines[i].text, 1, w)
|
||||
self.lines[i].fg = _sub(self.lines[i].fg, 1, w)
|
||||
self.lines[i].bg = _sub(self.lines[i].bg, 1, w)
|
||||
end
|
||||
elseif w > self.width then
|
||||
local d = w - self.width
|
||||
local text = _rep(' ', d)
|
||||
local fg = _rep(self.palette[self.fg], d)
|
||||
local bg = _rep(self.palette[self.bg], d)
|
||||
local fg = _rep(self.palette[self.fg or colors.white], d)
|
||||
local bg = _rep(self.palette[self.bg or colors.black], d)
|
||||
for i = 1, h do
|
||||
local ln = self.lines[i]
|
||||
ln.text = ln.text .. text
|
||||
ln.fg = ln.fg .. fg
|
||||
ln.bg = ln.bg .. bg
|
||||
ln.dirty = true
|
||||
self.lines[i].text = self.lines[i].text .. text
|
||||
self.lines[i].fg = self.lines[i].fg .. fg
|
||||
self.lines[i].bg = self.lines[i].bg .. bg
|
||||
end
|
||||
end
|
||||
|
||||
self.ex = self.x + w - 1
|
||||
self.ey = self.y + h - 1
|
||||
self.width = w
|
||||
self.height = h
|
||||
end
|
||||
|
||||
function Canvas:copy()
|
||||
@@ -124,26 +104,30 @@ function Canvas:copy()
|
||||
end
|
||||
|
||||
function Canvas:addLayer(layer)
|
||||
layer.parent = self
|
||||
if not self.children then
|
||||
self.children = { }
|
||||
end
|
||||
table.insert(self.children, 1, layer)
|
||||
return layer
|
||||
local canvas = Canvas({
|
||||
x = layer.x,
|
||||
y = layer.y,
|
||||
width = layer.width,
|
||||
height = layer.height,
|
||||
isColor = self.isColor,
|
||||
})
|
||||
canvas.parent = self
|
||||
table.insert(self.layers, canvas)
|
||||
return canvas
|
||||
end
|
||||
|
||||
function Canvas:removeLayer()
|
||||
for k, layer in pairs(self.parent.children) do
|
||||
for k, layer in pairs(self.parent.layers) do
|
||||
if layer == self then
|
||||
self:setVisible(false)
|
||||
table.remove(self.parent.children, k)
|
||||
table.remove(self.parent.layers, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:setVisible(visible)
|
||||
self.visible = visible -- TODO: use self.active = visible
|
||||
self.visible = visible
|
||||
if not visible and self.parent then
|
||||
self.parent:dirty()
|
||||
-- TODO: set parent's lines to dirty for each line in self
|
||||
@@ -152,10 +136,11 @@ end
|
||||
|
||||
-- Push a layer to the top
|
||||
function Canvas:raise()
|
||||
if self.parent and self.parent.children then
|
||||
for k, v in pairs(self.parent.children) do
|
||||
if self.parent then
|
||||
local layers = self.parent.layers or { }
|
||||
for k, v in pairs(layers) do
|
||||
if v == self then
|
||||
table.insert(self.parent.children, table.remove(self.parent.children, k))
|
||||
table.insert(layers, table.remove(layers, k))
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -175,42 +160,54 @@ end
|
||||
function Canvas:blit(x, y, text, bg, fg)
|
||||
if y > 0 and y <= #self.lines and x <= self.width then
|
||||
local width = #text
|
||||
local tx, tex
|
||||
|
||||
-- fix ffs
|
||||
if x < 1 then
|
||||
tx = 2 - x
|
||||
text = _sub(text, 2 - x)
|
||||
if bg then
|
||||
bg = _sub(bg, 2 - x)
|
||||
end
|
||||
if fg then
|
||||
fg = _sub(fg, 2 - x)
|
||||
end
|
||||
width = width + x - 1
|
||||
x = 1
|
||||
end
|
||||
|
||||
if x + width - 1 > self.width then
|
||||
tex = self.width - x + (tx or 1)
|
||||
width = tex - (tx or 1) + 1
|
||||
text = _sub(text, 1, self.width - x + 1)
|
||||
if bg then
|
||||
bg = _sub(bg, 1, self.width - x + 1)
|
||||
end
|
||||
if fg then
|
||||
fg = _sub(fg, 1, self.width - x + 1)
|
||||
end
|
||||
width = #text
|
||||
end
|
||||
|
||||
if width > 0 then
|
||||
local function replace(sstr, rstr)
|
||||
if tx or tex then
|
||||
rstr = _sub(rstr, tx or 1, tex)
|
||||
end
|
||||
if x == 1 and width == self.width then
|
||||
|
||||
local function replace(sstr, pos, rstr)
|
||||
if pos == 1 and width == self.width then
|
||||
return rstr
|
||||
elseif x == 1 then
|
||||
return rstr .. _sub(sstr, x + width)
|
||||
elseif x + width > self.width then
|
||||
return _sub(sstr, 1, x - 1) .. rstr
|
||||
elseif pos == 1 then
|
||||
return rstr .. _sub(sstr, pos+width)
|
||||
elseif pos + width > self.width then
|
||||
return _sub(sstr, 1, pos-1) .. rstr
|
||||
end
|
||||
return _sub(sstr, 1, x - 1) .. rstr .. _sub(sstr, x + width)
|
||||
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
|
||||
end
|
||||
|
||||
local line = self.lines[y]
|
||||
line.dirty = true
|
||||
line.text = replace(line.text, text)
|
||||
if fg then
|
||||
line.fg = replace(line.fg, fg)
|
||||
end
|
||||
if bg then
|
||||
line.bg = replace(line.bg, bg)
|
||||
if line then
|
||||
line.dirty = true
|
||||
line.text = replace(line.text, x, text, width)
|
||||
if fg then
|
||||
line.fg = replace(line.fg, x, fg, width)
|
||||
end
|
||||
if bg then
|
||||
line.bg = replace(line.bg, x, bg, width)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -226,15 +223,15 @@ function Canvas:writeLine(y, text, fg, bg)
|
||||
end
|
||||
|
||||
function Canvas:clearLine(y, bg, fg)
|
||||
fg = _rep(self.palette[fg or self.fg], self.width)
|
||||
bg = _rep(self.palette[bg or self.bg], self.width)
|
||||
fg = _rep(self.palette[fg or colors.white], self.width)
|
||||
bg = _rep(self.palette[bg or colors.black], self.width)
|
||||
self:writeLine(y, _rep(' ', self.width), fg, bg)
|
||||
end
|
||||
|
||||
function Canvas:clear(bg, fg)
|
||||
local text = _rep(' ', self.width)
|
||||
fg = _rep(self.palette[fg or self.fg], self.width)
|
||||
bg = _rep(self.palette[bg or self.bg], self.width)
|
||||
fg = _rep(self.palette[fg or colors.white], self.width)
|
||||
bg = _rep(self.palette[bg or colors.black], self.width)
|
||||
for i = 1, #self.lines do
|
||||
self:writeLine(i, text, fg, bg)
|
||||
end
|
||||
@@ -248,16 +245,13 @@ function Canvas:isDirty()
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:dirty(includingChildren)
|
||||
if self.lines then
|
||||
for i = 1, #self.lines do
|
||||
self.lines[i].dirty = true
|
||||
end
|
||||
|
||||
if includingChildren and self.children then
|
||||
for _, child in pairs(self.children) do
|
||||
child:dirty(true)
|
||||
end
|
||||
function Canvas:dirty()
|
||||
for i = 1, #self.lines do
|
||||
self.lines[i].dirty = true
|
||||
end
|
||||
if self.layers then
|
||||
for _, canvas in pairs(self.layers) do
|
||||
canvas:dirty()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -283,110 +277,123 @@ function Canvas:applyPalette(palette)
|
||||
self.palette = palette
|
||||
end
|
||||
|
||||
-- either render directly to the device
|
||||
-- or use another canvas as a backing buffer
|
||||
function Canvas:render(device, doubleBuffer)
|
||||
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
||||
self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }, doubleBuffer)
|
||||
|
||||
-- doubleBuffering to reduce the amount of
|
||||
-- setCursorPos, blits
|
||||
if doubleBuffer then
|
||||
--[[
|
||||
local drew = false
|
||||
local bg = _rep(2, device.width)
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, bg)
|
||||
drew = true
|
||||
end
|
||||
end
|
||||
if drew then
|
||||
local c = os.clock()
|
||||
repeat until os.clock()-c > .1
|
||||
end
|
||||
]]
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, v.bg)
|
||||
v.dirty = false
|
||||
end
|
||||
end
|
||||
function Canvas:render(device)
|
||||
local offset = { x = 0, y = 0 }
|
||||
local parent = self.parent
|
||||
while parent do
|
||||
offset.x = offset.x + parent.x - 1
|
||||
offset.y = offset.y + parent.y - 1
|
||||
parent = parent.parent
|
||||
end
|
||||
if #self.layers > 0 then
|
||||
self:__renderLayers(device, offset)
|
||||
else
|
||||
self:__blitRect(device, nil, {
|
||||
x = self.x + offset.x,
|
||||
y = self.y + offset.y
|
||||
})
|
||||
self:clean()
|
||||
end
|
||||
end
|
||||
|
||||
-- regions are comprised of absolute values that correspond to the output device.
|
||||
-- regions are comprised of absolute values that coorespond to the output device.
|
||||
-- canvases have coordinates relative to their parent.
|
||||
-- canvas layer's stacking order is determined by the position within the array.
|
||||
-- layers in the beginning of the array are overlayed by layers further down in
|
||||
-- the array.
|
||||
function Canvas:__renderLayers(device, offset, doubleBuffer)
|
||||
if self.children then
|
||||
for i = #self.children, 1, -1 do
|
||||
local canvas = self.children[i]
|
||||
if canvas.visible or canvas.enabled then
|
||||
-- get the area to render for this layer
|
||||
canvas.regions = Region.new(
|
||||
canvas.x + offset.x - (self.offx or 0),
|
||||
canvas.y + offset.y - (self.offy or 0),
|
||||
canvas.ex + offset.x - (self.offx or 0),
|
||||
canvas.ey + offset.y - (self.offy or 0))
|
||||
function Canvas:__renderLayers(device, offset)
|
||||
if #self.layers > 0 then
|
||||
self.regions = self.regions or Region.new(self.x, self.y, self.ex, self.ey)
|
||||
|
||||
-- contain within parent
|
||||
canvas.regions:andRegion(self.regions)
|
||||
for i = 1, #self.layers do
|
||||
local canvas = self.layers[i]
|
||||
if canvas.visible then
|
||||
|
||||
-- punch out this area from the parent's canvas
|
||||
self.regions:subRect(
|
||||
canvas.x + offset.x - (self.offx or 0),
|
||||
canvas.y + offset.y - (self.offy or 0),
|
||||
canvas.ex + offset.x - (self.offx or 0),
|
||||
canvas.ey + offset.y - (self.offy or 0))
|
||||
self:__punch(canvas, offset)
|
||||
|
||||
-- get the area to render for this layer
|
||||
canvas.regions = Region.new(
|
||||
canvas.x + offset.x,
|
||||
canvas.y + offset.y,
|
||||
canvas.ex + offset.x,
|
||||
canvas.ey + offset.y)
|
||||
|
||||
-- punch out any layers that overlap this one
|
||||
for j = i + 1, #self.layers do
|
||||
if self.layers[j].visible then
|
||||
canvas:__punch(self.layers[j], offset)
|
||||
end
|
||||
end
|
||||
if #canvas.regions.region > 0 then
|
||||
canvas:__renderLayers(device, {
|
||||
x = canvas.x + offset.x - 1 - (self.offx or 0),
|
||||
y = canvas.y + offset.y - 1 - (self.offy or 0),
|
||||
}, doubleBuffer)
|
||||
x = canvas.x + offset.x - 1,
|
||||
y = canvas.y + offset.y - 1,
|
||||
})
|
||||
end
|
||||
canvas.regions = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _,region in ipairs(self.regions.region) do
|
||||
self:__blitRect(device,
|
||||
{ x = region[1] - offset.x,
|
||||
y = region[2] - offset.y,
|
||||
ex = region[3] - offset.x,
|
||||
ey = region[4] - offset.y },
|
||||
{ x = region[1], y = region[2] }, doubleBuffer)
|
||||
end
|
||||
self.regions = nil
|
||||
self:__blitClipped(device, offset)
|
||||
self.regions = nil
|
||||
|
||||
elseif self.regions and #self.regions.region > 0 then
|
||||
self:__blitClipped(device, offset)
|
||||
self.regions = nil
|
||||
|
||||
else
|
||||
self:__blitRect(device, nil, {
|
||||
x = self.x + offset.x,
|
||||
y = self.y + offset.y
|
||||
})
|
||||
self.regions = nil
|
||||
end
|
||||
self:clean()
|
||||
end
|
||||
|
||||
function Canvas:__blitRect(device, src, tgt, doubleBuffer)
|
||||
-- for visualizing updates on the screen
|
||||
function Canvas:__blitClipped(device, offset)
|
||||
for _,region in ipairs(self.regions.region) do
|
||||
self:__blitRect(device,
|
||||
{ x = region[1] - offset.x,
|
||||
y = region[2] - offset.y,
|
||||
ex = region[3] - offset.x,
|
||||
ey = region[4] - offset.y},
|
||||
{ x = region[1], y = region[2] })
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:__punch(rect, offset)
|
||||
self.regions:subRect(
|
||||
rect.x + offset.x,
|
||||
rect.y + offset.y,
|
||||
rect.ex + offset.x,
|
||||
rect.ey + offset.y)
|
||||
end
|
||||
|
||||
function Canvas:__blitRect(device, src, tgt)
|
||||
src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
|
||||
tgt = tgt or self
|
||||
|
||||
--[[
|
||||
if Canvas.__visualize or self.visualize then
|
||||
local drew
|
||||
local t = _rep(' ', src.ex-src.x + 1)
|
||||
local bg = _rep(2, src.ex-src.x + 1)
|
||||
for i = 0, src.ey - src.y do
|
||||
local line = self.lines[src.y + i + (self.offy or 0)]
|
||||
if line and line.dirty then
|
||||
drew = true
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, bg, bg)
|
||||
-- for visualizing updates on the screen
|
||||
local drew
|
||||
for i = 0, src.ey - src.y do
|
||||
local line = self.lines[src.y + i + (self.offy or 0)]
|
||||
if line and line.dirty then
|
||||
drew = true
|
||||
local t, fg, bg = line.text, line.fg, line.bg
|
||||
if src.x > 1 or src.ex < self.ex then
|
||||
t = _sub(t, src.x, src.ex)
|
||||
fg = _rep(1, src.ex-src.x + 1)
|
||||
bg = _rep(2, src.ex-src.x + 1)
|
||||
end
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
end
|
||||
if drew then
|
||||
local c = os.clock()
|
||||
repeat until os.clock()-c > .03
|
||||
end
|
||||
end
|
||||
if drew then
|
||||
os.sleep(.3)
|
||||
end
|
||||
]]
|
||||
for i = 0, src.ey - src.y do
|
||||
@@ -398,13 +405,8 @@ function Canvas:__blitRect(device, src, tgt, doubleBuffer)
|
||||
fg = _sub(fg, src.x, src.ex)
|
||||
bg = _sub(bg, src.x, src.ex)
|
||||
end
|
||||
if doubleBuffer then
|
||||
Canvas.blit(device, tgt.x, tgt.y + i,
|
||||
t, bg, fg)
|
||||
else
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
end
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
32
sys/modules/opus/ui/components/ActiveLayer.lua
Normal file
32
sys/modules/opus/ui/components/ActiveLayer.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.ActiveLayer = class(UI.Window)
|
||||
UI.ActiveLayer.defaults = {
|
||||
UIElement = 'ActiveLayer',
|
||||
}
|
||||
function UI.ActiveLayer:layout()
|
||||
UI.Window.layout(self)
|
||||
if not self.canvas then
|
||||
self.canvas = self:addLayer()
|
||||
else
|
||||
self.canvas:resize(self.width, self.height)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.ActiveLayer:enable(...)
|
||||
self.canvas:raise()
|
||||
self.canvas:setVisible(true)
|
||||
UI.Window.enable(self, ...)
|
||||
if self.parent.transitionHint then
|
||||
self:addTransition(self.parent.transitionHint)
|
||||
end
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function UI.ActiveLayer:disable()
|
||||
if self.canvas then
|
||||
self.canvas:setVisible(false)
|
||||
end
|
||||
UI.Window.disable(self)
|
||||
end
|
||||
@@ -2,30 +2,32 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Button = class(UI.Window)
|
||||
UI.Button.defaults = {
|
||||
UIElement = 'Button',
|
||||
text = 'button',
|
||||
backgroundColor = 'lightGray',
|
||||
backgroundFocusColor = 'gray',
|
||||
textFocusColor = 'white',
|
||||
textInactiveColor = 'gray',
|
||||
textColor = 'black',
|
||||
backgroundColor = colors.lightGray,
|
||||
backgroundFocusColor = colors.gray,
|
||||
textFocusColor = colors.white,
|
||||
textInactiveColor = colors.gray,
|
||||
textColor = colors.black,
|
||||
centered = true,
|
||||
height = 1,
|
||||
focusIndicator = ' ',
|
||||
event = 'button_press',
|
||||
accelerators = {
|
||||
[ ' ' ] = 'button_activate',
|
||||
space = 'button_activate',
|
||||
enter = 'button_activate',
|
||||
mouse_click = 'button_activate',
|
||||
}
|
||||
}
|
||||
function UI.Button:layout()
|
||||
function UI.Button:setParent()
|
||||
if not self.width and not self.ex then
|
||||
self.width = self.noPadding and #self.text or #self.text + 2
|
||||
self.width = #self.text + 2
|
||||
end
|
||||
UI.Window.layout(self)
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Button:draw()
|
||||
@@ -33,13 +35,13 @@ function UI.Button:draw()
|
||||
local bg = self.backgroundColor
|
||||
local ind = ' '
|
||||
if self.focused then
|
||||
bg = self:getProperty('backgroundFocusColor')
|
||||
fg = self:getProperty('textFocusColor')
|
||||
bg = self.backgroundFocusColor
|
||||
fg = self.textFocusColor
|
||||
ind = self.focusIndicator
|
||||
elseif self.inactive then
|
||||
fg = self:getProperty('textInactiveColor')
|
||||
fg = self.textInactiveColor
|
||||
end
|
||||
local text = self.noPadding and self.text or ind .. self.text .. ' '
|
||||
local text = ind .. self.text .. ' '
|
||||
if self.centered then
|
||||
self:clear(bg)
|
||||
self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg)
|
||||
@@ -57,28 +59,8 @@ end
|
||||
|
||||
function UI.Button:eventHandler(event)
|
||||
if event.type == 'button_activate' then
|
||||
self:emit({ type = self.event, button = self, element = self })
|
||||
self:emit({ type = self.event, button = self })
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function UI.Button.example()
|
||||
return UI.Window {
|
||||
button1 = UI.Button {
|
||||
x = 2, y = 2,
|
||||
text = 'Press',
|
||||
},
|
||||
button2 = UI.Button {
|
||||
x = 2, y = 4,
|
||||
backgroundColor = 'green',
|
||||
event = 'custom_event',
|
||||
},
|
||||
button3 = UI.Button {
|
||||
x = 12, y = 2,
|
||||
height = 5,
|
||||
event = 'big_event',
|
||||
text = 'large button'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Checkbox = class(UI.Window)
|
||||
UI.Checkbox.defaults = {
|
||||
UIElement = 'Checkbox',
|
||||
@@ -9,9 +11,9 @@ UI.Checkbox.defaults = {
|
||||
leftMarker = UI.extChars and '\124' or '[',
|
||||
rightMarker = UI.extChars and '\124' or ']',
|
||||
value = false,
|
||||
textColor = 'white',
|
||||
backgroundColor = 'black',
|
||||
backgroundFocusColor = 'lightGray',
|
||||
textColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
height = 1,
|
||||
width = 3,
|
||||
accelerators = {
|
||||
@@ -19,18 +21,21 @@ UI.Checkbox.defaults = {
|
||||
mouse_click = 'checkbox_toggle',
|
||||
}
|
||||
}
|
||||
function UI.Checkbox:layout()
|
||||
self.width = self.label and #self.label + 4 or 3
|
||||
UI.Window.layout(self)
|
||||
end
|
||||
|
||||
function UI.Checkbox:draw()
|
||||
local bg = self.focused and self.backgroundFocusColor or self.backgroundColor
|
||||
local bg = self.backgroundColor
|
||||
if self.focused then
|
||||
bg = self.backgroundFocusColor
|
||||
end
|
||||
if type(self.value) == 'string' then
|
||||
self.value = nil -- TODO: fix form
|
||||
end
|
||||
local text = string.format('[%s]', not self.value and ' ' or self.checkedIndicator)
|
||||
local x = 1
|
||||
if self.label then
|
||||
self:write(1, 1, self.label, self.labelBackgroundColor)
|
||||
self:write(1, 1, self.label)
|
||||
x = #self.label + 2
|
||||
end
|
||||
self:write(x, 1, text, bg)
|
||||
self:write(x, 1, self.leftMarker, self.backgroundColor, self.textColor)
|
||||
self:write(x + 1, 1, not self.value and ' ' or self.checkedIndicator, bg)
|
||||
self:write(x + 2, 1, self.rightMarker, self.backgroundColor, self.textColor)
|
||||
@@ -41,12 +46,11 @@ function UI.Checkbox:focus()
|
||||
end
|
||||
|
||||
function UI.Checkbox:setValue(v)
|
||||
self.value = not not v
|
||||
self.value = v
|
||||
end
|
||||
|
||||
function UI.Checkbox:reset()
|
||||
self.value = false
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function UI.Checkbox:eventHandler(event)
|
||||
@@ -57,15 +61,3 @@ function UI.Checkbox:eventHandler(event)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Checkbox.example()
|
||||
return UI.Window {
|
||||
ex1 = UI.Checkbox {
|
||||
label = 'test',
|
||||
x = 2, y = 2,
|
||||
},
|
||||
ex2 = UI.Checkbox {
|
||||
x = 2, y = 4,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local function safeValue(v)
|
||||
local t = type(v)
|
||||
if t == 'string' or t == 'number' then
|
||||
return v
|
||||
end
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
UI.CheckboxGrid = class(UI.Grid)
|
||||
UI.CheckboxGrid.defaults = {
|
||||
UIElement = 'CheckboxGrid',
|
||||
checkedKey = 'checked',
|
||||
accelerators = {
|
||||
space = 'grid_toggle',
|
||||
},
|
||||
}
|
||||
function UI.CheckboxGrid:drawRow(sb, row, focused, bg, fg)
|
||||
local ind = focused and self.focusIndicator or ' '
|
||||
|
||||
for _,col in pairs(self.columns) do
|
||||
sb:write(ind .. safeValue(row[col.key] or ''),
|
||||
col.cw + 1,
|
||||
col.align,
|
||||
col.backgroundColor or bg,
|
||||
col.textColor or fg)
|
||||
ind = ' '
|
||||
end
|
||||
end
|
||||
|
||||
function UI.CheckboxGrid:eventHandler(event)
|
||||
if event.type == 'key_enter' and self.selected then
|
||||
self.selected.checked = not self.selected.checked
|
||||
self:draw()
|
||||
self:emit({ type = 'grid_check', checked = self.selected, element = self })
|
||||
else
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.CheckboxGrid.example()
|
||||
return UI.CheckboxGrid {
|
||||
values = {
|
||||
{ checked = false, name = 'unchecked' },
|
||||
{ checked = true, name = 'checked' },
|
||||
},
|
||||
columns = {
|
||||
{ heading = 'Checked', key = 'checked' },
|
||||
{ heading = 'Data', key = 'name', }
|
||||
},
|
||||
}
|
||||
end
|
||||
@@ -11,16 +11,11 @@ UI.Chooser.defaults = {
|
||||
nochoice = 'Select',
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
textInactiveColor = colors.gray,
|
||||
leftIndicator = UI.extChars and '\171' or '<',
|
||||
rightIndicator = UI.extChars and '\187' or '>',
|
||||
leftIndicator = UI.extChars and '\17' or '<',
|
||||
rightIndicator = UI.extChars and '\16' or '>',
|
||||
height = 1,
|
||||
accelerators = {
|
||||
space = 'choice_next',
|
||||
right = 'choice_next',
|
||||
left = 'choice_prev',
|
||||
}
|
||||
}
|
||||
function UI.Chooser:layout()
|
||||
function UI.Chooser:setParent()
|
||||
if not self.width and not self.ex then
|
||||
self.width = 1
|
||||
for _,v in pairs(self.choices) do
|
||||
@@ -30,15 +25,20 @@ function UI.Chooser:layout()
|
||||
end
|
||||
self.width = self.width + 4
|
||||
end
|
||||
UI.Window.layout(self)
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Chooser:draw()
|
||||
local bg = self.focused and self.backgroundFocusColor or self.backgroundColor
|
||||
local bg = self.backgroundColor
|
||||
if self.focused then
|
||||
bg = self.backgroundFocusColor
|
||||
end
|
||||
local fg = self.inactive and self.textInactiveColor or self.textColor
|
||||
local choice = Util.find(self.choices, 'value', self.value)
|
||||
local value = choice and choice.name or self.nochoice
|
||||
|
||||
local value = self.nochoice
|
||||
if choice then
|
||||
value = choice.name
|
||||
end
|
||||
self:write(1, 1, self.leftIndicator, self.backgroundColor, colors.black)
|
||||
self:write(2, 1, ' ' .. Util.widthify(tostring(value), self.width-4) .. ' ', bg, fg)
|
||||
self:write(self.width, 1, self.rightIndicator, self.backgroundColor, colors.black)
|
||||
@@ -49,60 +49,40 @@ function UI.Chooser:focus()
|
||||
end
|
||||
|
||||
function UI.Chooser:eventHandler(event)
|
||||
if event.type == 'choice_next' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if not k then k = 0 end
|
||||
if k and k < #self.choices then
|
||||
choice = self.choices[k+1]
|
||||
else
|
||||
choice = self.choices[1]
|
||||
if event.type == 'key' then
|
||||
if event.key == 'right' or event.key == 'space' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if not k then k = 1 end
|
||||
if k and k < #self.choices then
|
||||
choice = self.choices[k+1]
|
||||
else
|
||||
choice = self.choices[1]
|
||||
end
|
||||
self.value = choice.value
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
elseif event.key == 'left' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if k and k > 1 then
|
||||
choice = self.choices[k-1]
|
||||
else
|
||||
choice = self.choices[#self.choices]
|
||||
end
|
||||
self.value = choice.value
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
end
|
||||
self.value = choice.value
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
elseif event.type == 'choice_prev' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if k and k > 1 then
|
||||
choice = self.choices[k-1]
|
||||
else
|
||||
choice = self.choices[#self.choices]
|
||||
end
|
||||
self.value = choice.value
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
elseif event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then
|
||||
if event.x == 1 then
|
||||
self:emit({ type = 'choice_prev' })
|
||||
self:emit({ type = 'key', key = 'left' })
|
||||
return true
|
||||
elseif event.x == self.width then
|
||||
self:emit({ type = 'choice_next' })
|
||||
self:emit({ type = 'key', key = 'right' })
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Chooser.example()
|
||||
return UI.Window {
|
||||
a = UI.Chooser {
|
||||
x = 2, y = 2,
|
||||
choices = {
|
||||
{ name = 'choice1', value = 'value1' },
|
||||
{ name = 'choice2', value = 'value2' },
|
||||
{ name = 'choice3', value = 'value3' },
|
||||
},
|
||||
value = 'value2',
|
||||
},
|
||||
b = UI.Chooser {
|
||||
x = 2, y = 4,
|
||||
choices = {
|
||||
{ name = 'choice1', value = 'value1' },
|
||||
{ name = 'choice2', value = 'value2' },
|
||||
{ name = 'choice3', value = 'value3' },
|
||||
},
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
local Canvas = require('opus.ui.canvas')
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Dialog = class(UI.SlideOut)
|
||||
UI.Dialog.defaults = {
|
||||
UIElement = 'Dialog',
|
||||
height = 7,
|
||||
noFill = true,
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.white,
|
||||
okEvent ='dialog_ok',
|
||||
cancelEvent = 'dialog_cancel',
|
||||
}
|
||||
@@ -14,36 +18,22 @@ function UI.Dialog:postInit()
|
||||
self.titleBar = UI.TitleBar({ event = self.cancelEvent, title = self.title })
|
||||
end
|
||||
|
||||
function UI.Dialog:show(...)
|
||||
local canvas = self.parent:getCanvas()
|
||||
self.oldPalette = canvas.palette
|
||||
canvas:applyPalette(Canvas.darkPalette)
|
||||
UI.SlideOut.show(self, ...)
|
||||
end
|
||||
|
||||
function UI.Dialog:hide(...)
|
||||
self.parent:getCanvas().palette = self.oldPalette
|
||||
UI.SlideOut.hide(self, ...)
|
||||
self.parent:draw()
|
||||
end
|
||||
|
||||
function UI.Dialog:eventHandler(event)
|
||||
if event.type == 'dialog_cancel' then
|
||||
self:hide()
|
||||
end
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function UI.Dialog.example()
|
||||
return UI.Dialog {
|
||||
title = 'Enter Starting Level',
|
||||
height = 7,
|
||||
form = UI.Form {
|
||||
y = 3, x = 2, height = 4,
|
||||
event = 'setStartLevel',
|
||||
cancelEvent = 'slide_hide',
|
||||
text = UI.Text {
|
||||
x = 5, y = 1, width = 20,
|
||||
textColor = 'gray',
|
||||
},
|
||||
textEntry = UI.TextEntry {
|
||||
formKey = 'level',
|
||||
x = 15, y = 1, width = 7,
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar(),
|
||||
enable = function(self)
|
||||
require('opus.event').onTimeout(0, function()
|
||||
self:show()
|
||||
self:sync()
|
||||
end)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -2,10 +2,12 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.DropMenu = class(UI.MenuBar)
|
||||
UI.DropMenu.defaults = {
|
||||
UIElement = 'DropMenu',
|
||||
backgroundColor = 'white',
|
||||
backgroundColor = colors.white,
|
||||
buttonClass = 'DropMenuItem',
|
||||
}
|
||||
function UI.DropMenu:layout()
|
||||
@@ -30,77 +32,44 @@ function UI.DropMenu:layout()
|
||||
self.height = #self.children + 1
|
||||
self.width = maxWidth + 2
|
||||
|
||||
if self.x + self.width > self.parent.width then
|
||||
self.x = self.parent.width - self.width + 1
|
||||
if not self.canvas then
|
||||
self.canvas = self:addLayer()
|
||||
else
|
||||
self.canvas:resize(self.width, self.height)
|
||||
end
|
||||
|
||||
self:reposition(self.x, self.y, self.width, self.height)
|
||||
end
|
||||
|
||||
function UI.DropMenu:enable()
|
||||
local menuBar = self.parent:find(self.menuUid)
|
||||
local hasActive
|
||||
|
||||
for _,c in pairs(self.children) do
|
||||
if not c.spacer and menuBar then
|
||||
c.inactive = not menuBar:getActive(c)
|
||||
end
|
||||
if not c.inactive then
|
||||
hasActive = true
|
||||
end
|
||||
end
|
||||
|
||||
-- jump through a lot of hoops if all selections are inactive
|
||||
-- there's gotta be a better way
|
||||
-- lots of exception code just to handle drop menus
|
||||
self.focus = not hasActive and function() end
|
||||
|
||||
UI.Window.enable(self)
|
||||
if self.focus then
|
||||
self:setFocus(self)
|
||||
else
|
||||
self:focusFirst()
|
||||
end
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function UI.DropMenu:disable()
|
||||
UI.Window.disable(self)
|
||||
self:remove()
|
||||
function UI.DropMenu:show(x, y)
|
||||
self.x, self.y = x, y
|
||||
self.canvas:move(x, y)
|
||||
self.canvas:setVisible(true)
|
||||
|
||||
UI.Window.enable(self)
|
||||
|
||||
self:draw()
|
||||
self:capture(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function UI.DropMenu:hide()
|
||||
self:disable()
|
||||
self.canvas:setVisible(false)
|
||||
self:release(self)
|
||||
end
|
||||
|
||||
function UI.DropMenu:eventHandler(event)
|
||||
if event.type == 'focus_lost' and self.enabled then
|
||||
if not (Util.contains(self.children, event.focused) or event.focused == self) then
|
||||
self:disable()
|
||||
if not Util.contains(self.children, event.focused) then
|
||||
self:hide()
|
||||
end
|
||||
elseif event.type == 'mouse_out' and self.enabled then
|
||||
self:disable()
|
||||
self:setFocus(self.parent:find(self.lastFocus))
|
||||
self:hide()
|
||||
self:refocus()
|
||||
else
|
||||
return UI.MenuBar.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UI.DropMenu.example()
|
||||
return UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'File', dropdown = {
|
||||
{ text = 'Run', event = 'run' },
|
||||
{ text = 'Shell s', event = 'shell' },
|
||||
{ spacer = true },
|
||||
{ text = 'Quit ^q', event = 'quit' },
|
||||
} },
|
||||
{ text = 'Edit', dropdown = {
|
||||
{ text = 'Copy', event = 'run' },
|
||||
{ text = 'Paste s', event = 'shell' },
|
||||
} },
|
||||
{ text = '\187',
|
||||
x = -3,
|
||||
dropdown = {
|
||||
{ text = 'Associations', event = 'associate' },
|
||||
} },
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
--[[-- DropMenuItem --]]--
|
||||
UI.DropMenuItem = class(UI.Button)
|
||||
UI.DropMenuItem.defaults = {
|
||||
UIElement = 'DropMenuItem',
|
||||
textColor = 'black',
|
||||
backgroundColor = 'white',
|
||||
textFocusColor = 'white',
|
||||
textInactiveColor = 'lightGray',
|
||||
backgroundFocusColor = 'lightGray',
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.white,
|
||||
textFocusColor = colors.white,
|
||||
textInactiveColor = colors.lightGray,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
}
|
||||
function UI.DropMenuItem:eventHandler(event)
|
||||
if event.type == 'button_activate' then
|
||||
self.parent:disable()
|
||||
self.parent:hide()
|
||||
end
|
||||
return UI.Button.eventHandler(self, event)
|
||||
end
|
||||
|
||||
@@ -1,63 +1,62 @@
|
||||
local class = require('opus.class')
|
||||
local Event = require('opus.event')
|
||||
local Terminal = require('opus.terminal')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Embedded = class(UI.Window)
|
||||
UI.Embedded.defaults = {
|
||||
UIElement = 'Embedded',
|
||||
backgroundColor = 'black',
|
||||
textColor = 'white',
|
||||
backgroundColor = colors.black,
|
||||
textColor = colors.white,
|
||||
maxScroll = 100,
|
||||
accelerators = {
|
||||
up = 'scroll_up',
|
||||
down = 'scroll_down',
|
||||
}
|
||||
}
|
||||
function UI.Embedded:setParent()
|
||||
UI.Window.setParent(self)
|
||||
|
||||
self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false)
|
||||
self.win.setMaxScroll(self.maxScroll)
|
||||
|
||||
local canvas = self:getCanvas()
|
||||
self.win.getCanvas().parent = canvas
|
||||
table.insert(canvas.layers, self.win.getCanvas())
|
||||
self.canvas = self.win.getCanvas()
|
||||
|
||||
self.win.setCursorPos(1, 1)
|
||||
self.win.setBackgroundColor(self.backgroundColor)
|
||||
self.win.setTextColor(self.textColor)
|
||||
self.win.clear()
|
||||
end
|
||||
|
||||
function UI.Embedded:layout()
|
||||
UI.Window.layout(self)
|
||||
|
||||
if not self.win then
|
||||
local t
|
||||
function self.render()
|
||||
if not t then
|
||||
t = Event.onTimeout(0, function()
|
||||
t = nil
|
||||
if self.focused then
|
||||
self:setCursorPos(self.win.getCursorPos())
|
||||
end
|
||||
self:sync()
|
||||
end)
|
||||
end
|
||||
end
|
||||
self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false)
|
||||
self.win.canvas = self
|
||||
self.win.setMaxScroll(self.maxScroll)
|
||||
self.win.setCursorPos(1, 1)
|
||||
self.win.setBackgroundColor(self.backgroundColor)
|
||||
self.win.setTextColor(self.textColor)
|
||||
self.win.clear()
|
||||
if self.win then
|
||||
self.win.reposition(self.x, self.y, self.width, self.height)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Embedded:draw()
|
||||
self:dirty()
|
||||
end
|
||||
|
||||
function UI.Embedded:focus()
|
||||
-- allow scrolling
|
||||
if self.focused then
|
||||
self:setCursorBlink(self.win.getCursorBlink())
|
||||
end
|
||||
self.canvas:dirty()
|
||||
end
|
||||
|
||||
function UI.Embedded:enable()
|
||||
self.canvas:setVisible(true)
|
||||
self.canvas:raise()
|
||||
if self.visible then
|
||||
-- the window will automatically update on changes
|
||||
-- the canvas does not need to be rendereed
|
||||
self.win.setVisible(true)
|
||||
end
|
||||
UI.Window.enable(self)
|
||||
self.win.setVisible(true)
|
||||
self:dirty()
|
||||
self.canvas:dirty()
|
||||
end
|
||||
|
||||
function UI.Embedded:disable()
|
||||
self.canvas:setVisible(false)
|
||||
self.win.setVisible(false)
|
||||
UI.Window.disable(self)
|
||||
end
|
||||
@@ -72,25 +71,6 @@ function UI.Embedded:eventHandler(event)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Embedded.example()
|
||||
local Util = require('opus.util')
|
||||
local term = _G.term
|
||||
|
||||
return UI.Embedded {
|
||||
y = 2, x = 2, ex = -2, ey = -2,
|
||||
enable = function (self)
|
||||
UI.Embedded.enable(self)
|
||||
Event.addRoutine(function()
|
||||
local oterm = term.redirect(self.win)
|
||||
Util.run(_ENV, '/sys/apps/shell.lua')
|
||||
term.redirect(oterm)
|
||||
end)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'key' then
|
||||
return true
|
||||
end
|
||||
return UI.Embedded.eventHandler(self, event)
|
||||
end
|
||||
}
|
||||
function UI.Embedded:focus()
|
||||
-- allow scrolling
|
||||
end
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
|
||||
UI.FileSelect = class(UI.Window)
|
||||
UI.FileSelect.defaults = {
|
||||
UIElement = 'FileSelect',
|
||||
}
|
||||
function UI.FileSelect:postInit()
|
||||
self.grid = UI.ScrollingGrid {
|
||||
x = 2, y = 2, ex = -2, ey = -4,
|
||||
dir = '/',
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Size', key = 'size', width = 5 }
|
||||
},
|
||||
getDisplayValues = function(_, row)
|
||||
if row.size then
|
||||
row = Util.shallowCopy(row)
|
||||
row.size = Util.toBytes(row.size)
|
||||
end
|
||||
return row
|
||||
end,
|
||||
getRowTextColor = function(_, file)
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end,
|
||||
sortCompare = function(self, a, b)
|
||||
if self.sortColumn == 'size' then
|
||||
return a.size < b.size
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end,
|
||||
draw = function(self)
|
||||
local files = fs.listEx(self.dir)
|
||||
if #self.dir > 0 then
|
||||
table.insert(files, {
|
||||
name = '..',
|
||||
isDir = true,
|
||||
})
|
||||
end
|
||||
self:setValues(files)
|
||||
self:setIndex(1)
|
||||
UI.Grid.draw(self)
|
||||
end,
|
||||
}
|
||||
self.path = UI.TextEntry {
|
||||
x = 2,
|
||||
y = -2,
|
||||
ex = -11,
|
||||
limit = 256,
|
||||
accelerators = {
|
||||
enter = 'path_enter',
|
||||
}
|
||||
}
|
||||
self.cancel = UI.Button {
|
||||
text = 'Cancel',
|
||||
x = -9,
|
||||
y = -2,
|
||||
event = 'select_cancel',
|
||||
}
|
||||
end
|
||||
|
||||
function UI.FileSelect:draw()
|
||||
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray)
|
||||
self:drawChildren()
|
||||
end
|
||||
|
||||
function UI.FileSelect:enable(path)
|
||||
self:setPath(path or '')
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
|
||||
function UI.FileSelect:setPath(path)
|
||||
self.grid.dir = path
|
||||
while not fs.isDir(self.grid.dir) do
|
||||
self.grid.dir = fs.getDir(self.grid.dir)
|
||||
end
|
||||
self.path.value = self.grid.dir
|
||||
end
|
||||
|
||||
function UI.FileSelect:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
self.grid.dir = fs.combine(self.grid.dir, event.selected.name)
|
||||
self.path.value = self.grid.dir
|
||||
if event.selected.isDir then
|
||||
self.grid:draw()
|
||||
self.path:draw()
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
return true
|
||||
|
||||
elseif event.type == 'path_enter' then
|
||||
if self.path.value then
|
||||
if fs.isDir(self.path.value) then
|
||||
self:setPath(self.path.value)
|
||||
self.grid:draw()
|
||||
self.path:draw()
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -1,16 +0,0 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.FlatButton = class(UI.Button)
|
||||
UI.FlatButton.defaults = {
|
||||
UIElement = 'FlatButton',
|
||||
textColor = 'black',
|
||||
textFocusColor = 'white',
|
||||
noPadding = true,
|
||||
}
|
||||
function UI.FlatButton:setParent()
|
||||
self.backgroundColor = self.parent:getProperty('backgroundColor')
|
||||
self.backgroundFocusColor = self.backgroundColor
|
||||
|
||||
UI.Button.setParent(self)
|
||||
end
|
||||
@@ -2,6 +2,8 @@ local class = require('opus.class')
|
||||
local Sound = require('opus.sound')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Form = class(UI.Window)
|
||||
UI.Form.defaults = {
|
||||
UIElement = 'Form',
|
||||
@@ -30,7 +32,7 @@ function UI.Form:setValues(values)
|
||||
if child.setValue then
|
||||
child:setValue(self.values[child.formKey])
|
||||
else
|
||||
child.value = self.values[child.formKey]
|
||||
child.value = self.values[child.formKey] or ''
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -54,7 +56,7 @@ function UI.Form:createForm()
|
||||
for _, child in pairs(self) do
|
||||
if type(child) == 'table' and child.UIElement then
|
||||
if child.formKey then
|
||||
child.value = self.values[child.formKey]
|
||||
child.value = self.values[child.formKey] or ''
|
||||
end
|
||||
if child.formLabel then
|
||||
child.x = self.labelWidth + self.margin - 1
|
||||
@@ -66,7 +68,7 @@ function UI.Form:createForm()
|
||||
table.insert(self.children, UI.Text {
|
||||
x = self.margin,
|
||||
y = child.y,
|
||||
textColor = 'black',
|
||||
textColor = colors.black,
|
||||
width = #child.formLabel,
|
||||
value = child.formLabel,
|
||||
})
|
||||
@@ -97,6 +99,14 @@ function UI.Form:validateField(field)
|
||||
return false, 'Field is required'
|
||||
end
|
||||
end
|
||||
if field.validate == 'numeric' then
|
||||
field.value = field.value or ''
|
||||
if #tostring(field.value) > 0 then
|
||||
if not tonumber(field.value) then
|
||||
return false, 'Invalid number'
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -114,7 +124,11 @@ function UI.Form:save()
|
||||
end
|
||||
for _,child in pairs(self.children) do
|
||||
if child.formKey then
|
||||
self.values[child.formKey] = child.value
|
||||
if child.validate == 'numeric' then
|
||||
self.values[child.formKey] = tonumber(child.value)
|
||||
else
|
||||
self.values[child.formKey] = child.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,23 +146,3 @@ function UI.Form:eventHandler(event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UI.Form.example()
|
||||
return UI.Form {
|
||||
x = 2, ex = -2, y = 2,
|
||||
ptype = UI.Chooser {
|
||||
formLabel = 'Type', formKey = 'type', formIndex = 1,
|
||||
width = 10,
|
||||
choices = {
|
||||
{ name = 'Modem', value = 'wireless_modem' },
|
||||
{ name = 'Drive', value = 'disk_drive' },
|
||||
},
|
||||
},
|
||||
drive_id = UI.TextEntry {
|
||||
formLabel = 'Drive', formKey = 'drive_id', formIndex = 2,
|
||||
required = true,
|
||||
width = 5,
|
||||
transform = 'number',
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -2,8 +2,10 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
local function safeValue(v)
|
||||
local t = type(v)
|
||||
@@ -21,7 +23,18 @@ function Writer:init(element, y)
|
||||
end
|
||||
|
||||
function Writer:write(s, width, align, bg, fg)
|
||||
s = Util.widthify(s, width, align)
|
||||
local len = #tostring(s or '')
|
||||
if len > width then
|
||||
s = _sub(s, 1, width)
|
||||
end
|
||||
local padding = len < width and _rep(' ', width - len)
|
||||
if padding then
|
||||
if align == 'right' then
|
||||
s = padding .. s
|
||||
else
|
||||
s = s .. padding
|
||||
end
|
||||
end
|
||||
self.element:write(self.x, self.y, s, bg, fg)
|
||||
self.x = self.x + width
|
||||
end
|
||||
@@ -43,16 +56,16 @@ UI.Grid.defaults = {
|
||||
disableHeader = false,
|
||||
headerHeight = 1,
|
||||
marginRight = 0,
|
||||
textColor = 'white',
|
||||
textSelectedColor = 'white',
|
||||
backgroundColor = 'black',
|
||||
backgroundSelectedColor = 'gray',
|
||||
headerBackgroundColor = 'primary',
|
||||
headerTextColor = 'white',
|
||||
headerSortColor = 'yellow',
|
||||
unfocusedTextSelectedColor = 'white',
|
||||
unfocusedBackgroundSelectedColor = 'gray',
|
||||
focusIndicator = UI.extChars and '\26' or '>',
|
||||
textColor = colors.white,
|
||||
textSelectedColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
backgroundSelectedColor = colors.gray,
|
||||
headerBackgroundColor = colors.cyan,
|
||||
headerTextColor = colors.white,
|
||||
headerSortColor = colors.yellow,
|
||||
unfocusedTextSelectedColor = colors.white,
|
||||
unfocusedBackgroundSelectedColor = colors.gray,
|
||||
focusIndicator = UI.extChars and '\183' or '>',
|
||||
sortIndicator = ' ',
|
||||
inverseSortIndicator = UI.extChars and '\24' or '^',
|
||||
values = { },
|
||||
@@ -70,8 +83,8 @@ UI.Grid.defaults = {
|
||||
[ 'control-f' ] = 'scroll_pageDown',
|
||||
},
|
||||
}
|
||||
function UI.Grid:layout()
|
||||
UI.Window.layout(self)
|
||||
function UI.Grid:setParent()
|
||||
UI.Window.setParent(self)
|
||||
|
||||
for _,c in pairs(self.columns) do
|
||||
c.cw = c.width
|
||||
@@ -480,46 +493,3 @@ function UI.Grid:eventHandler(event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UI.Grid.example()
|
||||
local values = {
|
||||
{ key = 'key1', value = 'value1' },
|
||||
{ key = 'key2', value = 'value2' },
|
||||
{ key = 'key3', value = 'value3-longer value text' },
|
||||
{ key = 'key4', value = 'value4' },
|
||||
{ key = 'key5', value = 'value5' },
|
||||
}
|
||||
return UI.Window {
|
||||
regular = UI.Grid {
|
||||
ex = '48%', ey = 4,
|
||||
values = values,
|
||||
sortColumn = 'key',
|
||||
inverseSort = true,
|
||||
columns = {
|
||||
{ heading = 'key', key = 'key' },
|
||||
{ heading = 'value', key = 'value' },
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'custom_select',
|
||||
}
|
||||
},
|
||||
noheader = UI.Grid {
|
||||
ex = '48%', y = 6, ey = -2,
|
||||
disableHeader = true,
|
||||
values = values,
|
||||
columns = {
|
||||
{ heading = 'key', key = 'key', width = 6, },
|
||||
{ heading = 'value', key = 'value', textColor = 'yellow' },
|
||||
},
|
||||
},
|
||||
autospace = UI.Grid {
|
||||
x = '52%', ey = 4,
|
||||
autospace = true,
|
||||
values = values,
|
||||
columns = {
|
||||
{ heading = 'key', key = 'key' },
|
||||
{ heading = 'value', key = 'value' },
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local lookup = '0123456789abcdef'
|
||||
|
||||
-- handle files produced by Paint
|
||||
UI.Image = class(UI.Window)
|
||||
UI.Image.defaults = {
|
||||
UIElement = 'Image',
|
||||
event = 'button_press',
|
||||
}
|
||||
function UI.Image:postInit()
|
||||
if self.filename then
|
||||
self.image = Util.readLines(self.filename)
|
||||
end
|
||||
|
||||
if self.image and not (self.height or self.ey) then
|
||||
function UI.Image:setParent()
|
||||
if self.image then
|
||||
self.height = #self.image
|
||||
end
|
||||
if self.image and not (self.width or self.ex) then
|
||||
for i = 1, self.height do
|
||||
self.width = math.max(self.width or 0, #self.image[i])
|
||||
end
|
||||
if self.image and not self.width then
|
||||
self.width = #self.image[1]
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Image:draw()
|
||||
@@ -31,22 +22,19 @@ function UI.Image:draw()
|
||||
for y = 1, #self.image do
|
||||
local line = self.image[y]
|
||||
for x = 1, #line do
|
||||
local ch = lookup:find(line:sub(x, x))
|
||||
if ch then
|
||||
self:write(x, y, ' ', 2 ^ (ch -1))
|
||||
local ch = line[x]
|
||||
if type(ch) == 'number' then
|
||||
if ch > 0 then
|
||||
self:write(x, y, ' ', ch)
|
||||
end
|
||||
else
|
||||
self:write(x, y, ch)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:drawChildren()
|
||||
end
|
||||
|
||||
function UI.Image:setImage(image)
|
||||
self.image = image
|
||||
end
|
||||
|
||||
function UI.Image.example()
|
||||
return UI.Image {
|
||||
filename = 'test.paint',
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
--[[-- Menu --]]--
|
||||
UI.Menu = class(UI.Grid)
|
||||
UI.Menu.defaults = {
|
||||
UIElement = 'Menu',
|
||||
@@ -13,7 +14,8 @@ function UI.Menu:postInit()
|
||||
self.pageSize = #self.menuItems
|
||||
end
|
||||
|
||||
function UI.Menu:layout()
|
||||
function UI.Menu:setParent()
|
||||
UI.Grid.setParent(self)
|
||||
self.itemWidth = 1
|
||||
for _,v in pairs(self.values) do
|
||||
if #v.prompt > self.itemWidth then
|
||||
@@ -27,7 +29,6 @@ function UI.Menu:layout()
|
||||
else
|
||||
self.width = self.itemWidth + 2
|
||||
end
|
||||
UI.Grid.layout(self)
|
||||
end
|
||||
|
||||
function UI.Menu:center()
|
||||
@@ -58,14 +59,3 @@ function UI.Menu:eventHandler(event)
|
||||
end
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function UI.Menu.example()
|
||||
return UI.Menu {
|
||||
x = 2, y = 2, height = 3,
|
||||
menuItems = {
|
||||
{ prompt = 'Start', event = 'start' },
|
||||
{ prompt = 'Continue', event = 'continue' },
|
||||
{ prompt = 'Quit', event = 'quit' }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local function getPosition(element)
|
||||
local x, y = 1, 1
|
||||
repeat
|
||||
x = element.x + x - 1
|
||||
y = element.y + y - 1
|
||||
element = element.parent
|
||||
until not element
|
||||
return x, y
|
||||
end
|
||||
|
||||
UI.MenuBar = class(UI.Window)
|
||||
UI.MenuBar.defaults = {
|
||||
UIElement = 'MenuBar',
|
||||
buttons = { },
|
||||
height = 1,
|
||||
backgroundColor = 'secondary',
|
||||
textColor = 'black',
|
||||
backgroundColor = colors.lightGray,
|
||||
textColor = colors.black,
|
||||
spacing = 2,
|
||||
lastx = 1,
|
||||
showBackButton = false,
|
||||
buttonClass = 'MenuItem',
|
||||
}
|
||||
function UI.MenuBar:postInit()
|
||||
@@ -21,15 +34,6 @@ function UI.MenuBar:addButtons(buttons)
|
||||
self.children = { }
|
||||
end
|
||||
|
||||
for _,button in pairs(buttons) do
|
||||
if button.index then -- don't sort unless needed
|
||||
table.sort(buttons, function(a, b)
|
||||
return (a.index or 999) < (b.index or 999)
|
||||
end)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for _,button in pairs(buttons) do
|
||||
if button.UIElement then
|
||||
table.insert(self.children, button)
|
||||
@@ -38,7 +42,6 @@ function UI.MenuBar:addButtons(buttons)
|
||||
x = self.lastx,
|
||||
width = #(button.text or 'button') + self.spacing,
|
||||
centered = false,
|
||||
backgroundColor = self.backgroundColor,
|
||||
}
|
||||
self.lastx = self.lastx + buttonProperties.width
|
||||
UI:mergeProperties(buttonProperties, button)
|
||||
@@ -49,6 +52,10 @@ function UI.MenuBar:addButtons(buttons)
|
||||
else
|
||||
table.insert(self.children, button)
|
||||
end
|
||||
|
||||
if button.dropdown then
|
||||
button.dropmenu = UI.DropMenu { buttons = button.dropdown }
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.parent then
|
||||
@@ -61,38 +68,23 @@ function UI.MenuBar:getActive(menuItem)
|
||||
end
|
||||
|
||||
function UI.MenuBar:eventHandler(event)
|
||||
if event.type == 'button_press' and event.button.dropdown then
|
||||
local function getPosition(element)
|
||||
local x, y = 1, 1
|
||||
repeat
|
||||
x = element.x + x - 1
|
||||
y = element.y + y - 1
|
||||
element = element.parent
|
||||
until not element
|
||||
return x, y
|
||||
if event.type == 'button_press' and event.button.dropmenu then
|
||||
if event.button.dropmenu.enabled then
|
||||
event.button.dropmenu:hide()
|
||||
self:refocus()
|
||||
return true
|
||||
else
|
||||
local x, y = getPosition(event.button)
|
||||
if x + event.button.dropmenu.width > self.width then
|
||||
x = self.width - event.button.dropmenu.width + 1
|
||||
end
|
||||
for _,c in pairs(event.button.dropmenu.children) do
|
||||
if not c.spacer then
|
||||
c.inactive = not self:getActive(c)
|
||||
end
|
||||
end
|
||||
event.button.dropmenu:show(x, y + 1)
|
||||
end
|
||||
|
||||
local x, y = getPosition(event.button)
|
||||
|
||||
local menu = UI.DropMenu {
|
||||
buttons = event.button.dropdown,
|
||||
x = x,
|
||||
y = y + 1,
|
||||
lastFocus = event.button.uid,
|
||||
menuUid = self.uid,
|
||||
}
|
||||
self.parent:add({ dropmenu = menu })
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UI.MenuBar.example()
|
||||
return UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Choice1', event = 'event1' },
|
||||
{ text = 'Choice2', event = 'event2', inactive = true },
|
||||
{ text = 'Choice3', event = 'event3' },
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.MenuItem = class(UI.FlatButton)
|
||||
local colors = _G.colors
|
||||
|
||||
--[[-- MenuItem --]]--
|
||||
UI.MenuItem = class(UI.Button)
|
||||
UI.MenuItem.defaults = {
|
||||
UIElement = 'MenuItem',
|
||||
noPadding = false,
|
||||
textInactiveColor = 'gray',
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.lightGray,
|
||||
textFocusColor = colors.white,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.MiniSlideOut = class(UI.SlideOut)
|
||||
UI.MiniSlideOut.defaults = {
|
||||
UIElement = 'MiniSlideOut',
|
||||
noFill = true,
|
||||
backgroundColor = 'primary',
|
||||
height = 1,
|
||||
}
|
||||
function UI.MiniSlideOut:postInit()
|
||||
self.close_button = UI.Button {
|
||||
x = -1,
|
||||
backgroundColor = self.backgroundColor,
|
||||
backgroundFocusColor = self.backgroundColor,
|
||||
text = 'x',
|
||||
event = 'slide_hide',
|
||||
noPadding = true,
|
||||
}
|
||||
if self.label then
|
||||
self.label_text = UI.Text {
|
||||
x = 2,
|
||||
value = self.label,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function UI.MiniSlideOut:show(...)
|
||||
UI.SlideOut.show(self, ...)
|
||||
self:addTransition('slideLeft', { easing = 'outBounce' })
|
||||
end
|
||||
@@ -5,18 +5,17 @@ UI.NftImage = class(UI.Window)
|
||||
UI.NftImage.defaults = {
|
||||
UIElement = 'NftImage',
|
||||
}
|
||||
function UI.NftImage:postInit()
|
||||
if self.image and not (self.ey or self.height) then
|
||||
function UI.NftImage:setParent()
|
||||
if self.image then
|
||||
self.height = self.image.height
|
||||
end
|
||||
if self.image and not (self.ex or self.width) then
|
||||
if self.image and not self.width then
|
||||
self.width = self.image.width
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.NftImage:draw()
|
||||
self:clear()
|
||||
|
||||
if self.image then
|
||||
-- due to blittle, the background and foreground transparent
|
||||
-- color is the same as the background color
|
||||
@@ -26,6 +25,8 @@ function UI.NftImage:draw()
|
||||
self:write(x, y, self.image.text[y][x], self.image.bg[y][x], self.image.fg[y][x] or bg)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:clear()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,34 +4,36 @@ local Sound = require('opus.sound')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Notification = class(UI.Window)
|
||||
UI.Notification.defaults = {
|
||||
UIElement = 'Notification',
|
||||
backgroundColor = 'gray',
|
||||
backgroundColor = colors.gray,
|
||||
closeInd = UI.extChars and '\215' or '*',
|
||||
height = 3,
|
||||
timeout = 3,
|
||||
anchor = 'bottom',
|
||||
}
|
||||
function UI.Notification.draw()
|
||||
function UI.Notification:draw()
|
||||
end
|
||||
|
||||
function UI.Notification.enable()
|
||||
function UI.Notification:enable()
|
||||
end
|
||||
|
||||
function UI.Notification:error(value, timeout)
|
||||
self.backgroundColor = 'red'
|
||||
self.backgroundColor = colors.red
|
||||
Sound.play('entity.villager.no', .5)
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
function UI.Notification:info(value, timeout)
|
||||
self.backgroundColor = 'lightGray'
|
||||
self.backgroundColor = colors.lightGray
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
function UI.Notification:success(value, timeout)
|
||||
self.backgroundColor = 'green'
|
||||
self.backgroundColor = colors.green
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
@@ -41,34 +43,32 @@ function UI.Notification:cancel()
|
||||
self.timer = nil
|
||||
end
|
||||
|
||||
self:disable()
|
||||
if self.canvas then
|
||||
self.enabled = false
|
||||
self.canvas:removeLayer()
|
||||
self.canvas = nil
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Notification:display(value, timeout)
|
||||
local lines = Util.wordWrap(value, self.width - 3)
|
||||
|
||||
self:cancel()
|
||||
self.enabled = true
|
||||
local lines = Util.wordWrap(value, self.width - 3)
|
||||
self.height = #lines
|
||||
|
||||
if self.anchor == 'bottom' then
|
||||
self.y = self.parent.height - self.height + 1
|
||||
self.canvas = self:addLayer(self.backgroundColor, self.textColor)
|
||||
self:addTransition('expandUp', { ticks = self.height })
|
||||
else
|
||||
self.canvas = self:addLayer(self.backgroundColor, self.textColor)
|
||||
self.y = 1
|
||||
end
|
||||
|
||||
self:reposition(self.x, self.y, self.width, self.height)
|
||||
self:raise()
|
||||
self.canvas:setVisible(true)
|
||||
self:clear()
|
||||
for k,v in pairs(lines) do
|
||||
self:write(2, k, v)
|
||||
end
|
||||
self:write(self.width, 1, self.closeInd)
|
||||
|
||||
if self.timer then
|
||||
Event.off(self.timer)
|
||||
self.timer = nil
|
||||
end
|
||||
|
||||
timeout = timeout or self.timeout
|
||||
if timeout > 0 then
|
||||
@@ -77,6 +77,7 @@ function UI.Notification:display(value, timeout)
|
||||
self:sync()
|
||||
end)
|
||||
else
|
||||
self:write(self.width, 1, self.closeInd)
|
||||
self:sync()
|
||||
end
|
||||
end
|
||||
@@ -89,31 +90,3 @@ function UI.Notification:eventHandler(event)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Notification.example()
|
||||
return UI.Window {
|
||||
notify1 = UI.Notification {
|
||||
anchor = 'top',
|
||||
},
|
||||
notify2 = UI.Notification { },
|
||||
button1 = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'example 1',
|
||||
event = 'test_success',
|
||||
},
|
||||
button2 = UI.Button {
|
||||
x = 2, y = 5,
|
||||
text = 'example 2',
|
||||
event = 'test_error',
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
if event.type == 'test_success' then
|
||||
self.notify1:success('Example text')
|
||||
elseif event.type == 'test_error' then
|
||||
self.notify2:error([[Example text test test
|
||||
test test test test test
|
||||
test test test]], 0)
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
UI.Page = class(UI.Window)
|
||||
UI.Page.defaults = {
|
||||
UIElement = 'Page',
|
||||
accelerators = {
|
||||
down = 'focus_next',
|
||||
scroll_down = 'focus_next',
|
||||
enter = 'focus_next',
|
||||
tab = 'focus_next',
|
||||
['shift-tab' ] = 'focus_prev',
|
||||
up = 'focus_prev',
|
||||
scroll_up = 'focus_prev',
|
||||
},
|
||||
backgroundColor = 'primary',
|
||||
textColor = 'white',
|
||||
}
|
||||
function UI.Page:postInit()
|
||||
self.parent = self.parent or UI.term
|
||||
self.__target = self
|
||||
end
|
||||
|
||||
function UI.Page:sync()
|
||||
if self.enabled then
|
||||
self:checkFocus()
|
||||
self.parent:setCursorBlink(self.focused and self.focused.cursorBlink)
|
||||
self.parent:sync()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Page:capture(child)
|
||||
self.__target = child
|
||||
end
|
||||
|
||||
function UI.Page:release(child)
|
||||
if self.__target == child then
|
||||
self.__target = self
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Page:pointToChild(x, y)
|
||||
if self.__target == self then
|
||||
return UI.Window.pointToChild(self, x, y)
|
||||
end
|
||||
|
||||
local function getPosition(element)
|
||||
local x, y = 1, 1
|
||||
repeat
|
||||
x = element.x + x - 1
|
||||
y = element.y + y - 1
|
||||
element = element.parent
|
||||
until not element
|
||||
return x, y
|
||||
end
|
||||
|
||||
local absX, absY = getPosition(self.__target)
|
||||
return self.__target:pointToChild(x - absX + self.__target.x, y - absY + self.__target.y)
|
||||
end
|
||||
|
||||
function UI.Page:getFocusables()
|
||||
if self.__target == self or not self.__target.modal then
|
||||
return UI.Window.getFocusables(self)
|
||||
end
|
||||
return self.__target:getFocusables()
|
||||
end
|
||||
|
||||
function UI.Page:getFocused()
|
||||
return self.focused
|
||||
end
|
||||
|
||||
function UI.Page:focusPrevious()
|
||||
local function getPreviousFocus(focused)
|
||||
local focusables = self:getFocusables()
|
||||
local k = Util.contains(focusables, focused)
|
||||
if k then
|
||||
if k > 1 then
|
||||
return focusables[k - 1]
|
||||
end
|
||||
return focusables[#focusables]
|
||||
end
|
||||
end
|
||||
|
||||
local focused = getPreviousFocus(self.focused)
|
||||
if focused then
|
||||
self:setFocus(focused)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Page:focusNext()
|
||||
local function getNextFocus(focused)
|
||||
local focusables = self:getFocusables()
|
||||
local k = Util.contains(focusables, focused)
|
||||
if k then
|
||||
if k < #focusables then
|
||||
return focusables[k + 1]
|
||||
end
|
||||
return focusables[1]
|
||||
end
|
||||
end
|
||||
|
||||
local focused = getNextFocus(self.focused)
|
||||
if focused then
|
||||
self:setFocus(focused)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Page:setFocus(child)
|
||||
if not child or not child.focus then
|
||||
return
|
||||
end
|
||||
|
||||
if self.focused and self.focused ~= child then
|
||||
self.focused.focused = false
|
||||
self.focused:focus()
|
||||
self.focused:emit({ type = 'focus_lost', focused = child, unfocused = self.focused })
|
||||
end
|
||||
|
||||
self.focused = child
|
||||
if not child.focused then
|
||||
child.focused = true
|
||||
child:emit({ type = 'focus_change', focused = child })
|
||||
end
|
||||
|
||||
child:focus()
|
||||
end
|
||||
|
||||
function UI.Page:checkFocus()
|
||||
if not self.focused or not self.focused.enabled then
|
||||
self.__target:focusFirst()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Page:eventHandler(event)
|
||||
if self.focused then
|
||||
if event.type == 'focus_next' then
|
||||
self:focusNext()
|
||||
return true
|
||||
elseif event.type == 'focus_prev' then
|
||||
self:focusPrevious()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,36 +1,28 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.ProgressBar = class(UI.Window)
|
||||
UI.ProgressBar.defaults = {
|
||||
UIElement = 'ProgressBar',
|
||||
backgroundColor = 'gray',
|
||||
backgroundColor = colors.gray,
|
||||
height = 1,
|
||||
progressColor = 'lime',
|
||||
progressColor = colors.lime,
|
||||
progressChar = UI.extChars and '\153' or ' ',
|
||||
fillChar = ' ',
|
||||
fillColor = 'gray',
|
||||
textColor = 'green',
|
||||
fillColor = colors.gray,
|
||||
textColor = colors.green,
|
||||
value = 0,
|
||||
}
|
||||
function UI.ProgressBar:draw()
|
||||
local width = math.ceil(self.value / 100 * self.width)
|
||||
|
||||
self:fillArea(width + 1, 1, self.width - width, self.height, self.fillChar, nil, self.fillColor)
|
||||
self:fillArea(1, 1, width, self.height, self.progressChar, self.progressColor)
|
||||
end
|
||||
local filler = string.rep(self.fillChar, self.width)
|
||||
local progress = string.rep(self.progressChar, width)
|
||||
|
||||
function UI.ProgressBar.example()
|
||||
return UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 2, height = 2,
|
||||
focus = function() end,
|
||||
enable = function(self)
|
||||
require('opus.event').onInterval(.25, function()
|
||||
self.value = self.value == 100 and 0 or self.value + 5
|
||||
self:draw()
|
||||
self:sync()
|
||||
end)
|
||||
return UI.ProgressBar.enable(self)
|
||||
end
|
||||
}
|
||||
for i = 1, self.height do
|
||||
self:write(1, i, filler, nil, self.fillColor)
|
||||
self:write(1, i, progress, self.progressColor)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.Question = class(UI.MiniSlideOut)
|
||||
UI.Question.defaults = {
|
||||
UIElement = 'Question',
|
||||
accelerators = {
|
||||
y = 'question_yes',
|
||||
n = 'question_no',
|
||||
}
|
||||
}
|
||||
function UI.Question:postInit()
|
||||
local x = self.label and #self.label + 3 or 1
|
||||
|
||||
self.yes_button = UI.Button {
|
||||
x = x,
|
||||
text = 'Yes',
|
||||
backgroundColor = 'primary',
|
||||
event = 'question_yes',
|
||||
}
|
||||
self.no_button = UI.Button {
|
||||
x = x + 5,
|
||||
text = 'No',
|
||||
backgroundColor = 'primary',
|
||||
event = 'question_no',
|
||||
}
|
||||
end
|
||||
@@ -17,13 +17,7 @@ UI.ScrollBar.defaults = {
|
||||
ey = -1,
|
||||
}
|
||||
function UI.ScrollBar:draw()
|
||||
local parent = self.target or self.parent --self:find(self.target)
|
||||
local view = parent:getViewArea()
|
||||
|
||||
self:clear()
|
||||
|
||||
-- ...
|
||||
self:write(1, 1, ' ', view.fill)
|
||||
local view = self.parent:getViewArea()
|
||||
|
||||
if view.totalHeight > view.height then
|
||||
local maxScroll = view.totalHeight - view.height
|
||||
@@ -33,7 +27,7 @@ function UI.ScrollBar:draw()
|
||||
|
||||
local row = view.y
|
||||
if not view.static then -- does the container scroll ?
|
||||
self:reposition(self.x, self.y, self.width, view.totalHeight)
|
||||
self.height = view.totalHeight
|
||||
end
|
||||
|
||||
for i = 1, view.height - 2 do
|
||||
@@ -62,17 +56,16 @@ end
|
||||
function UI.ScrollBar:eventHandler(event)
|
||||
if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then
|
||||
if event.x == 1 then
|
||||
local parent = self.target or self.parent --self:find(self.target)
|
||||
local view = parent:getViewArea()
|
||||
local view = self.parent:getViewArea()
|
||||
if view.totalHeight > view.height then
|
||||
if event.y == view.y then
|
||||
parent:emit({ type = 'scroll_up'})
|
||||
self:emit({ type = 'scroll_up'})
|
||||
elseif event.y == view.y + view.height - 1 then
|
||||
parent:emit({ type = 'scroll_down'})
|
||||
self:emit({ type = 'scroll_down'})
|
||||
else
|
||||
local percent = (event.y - view.y) / (view.height - 2)
|
||||
local y = math.floor((view.totalHeight - view.height) * percent)
|
||||
parent :emit({ type = 'scroll_to', offset = y })
|
||||
self:emit({ type = 'scroll_to', offset = y })
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
@@ -2,6 +2,7 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
--[[-- ScrollingGrid --]]--
|
||||
UI.ScrollingGrid = class(UI.Grid)
|
||||
UI.ScrollingGrid.defaults = {
|
||||
UIElement = 'ScrollingGrid',
|
||||
@@ -29,7 +30,6 @@ function UI.ScrollingGrid:getViewArea()
|
||||
height = self.pageSize, -- viewable height
|
||||
totalHeight = Util.size(self.values), -- total height
|
||||
offsetY = self.scrollOffset, -- scroll offset
|
||||
fill = not self.disableHeader and self.headerBackgroundColor,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -58,21 +58,3 @@ function UI.ScrollingGrid:setIndex(index)
|
||||
end
|
||||
UI.Grid.setIndex(self, index)
|
||||
end
|
||||
|
||||
function UI.ScrollingGrid.example()
|
||||
local values = { }
|
||||
for i = 1, 20 do
|
||||
table.insert(values, { key = 'key' .. i, value = 'value' .. i })
|
||||
end
|
||||
return UI.ScrollingGrid {
|
||||
values = values,
|
||||
sortColumn = 'key',
|
||||
columns = {
|
||||
{ heading = 'key', key = 'key' },
|
||||
{ heading = 'value', key = 'value' },
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'custom_select',
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -1,38 +1,43 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
--[[-- SlideOut --]]--
|
||||
UI.SlideOut = class(UI.Window)
|
||||
UI.SlideOut.defaults = {
|
||||
UIElement = 'SlideOut',
|
||||
transitionHint = 'expandUp',
|
||||
modal = true,
|
||||
pageType = 'modal',
|
||||
}
|
||||
function UI.SlideOut:enable()
|
||||
end
|
||||
|
||||
function UI.SlideOut:toggle()
|
||||
if self.enabled then
|
||||
self:hide()
|
||||
function UI.SlideOut:layout()
|
||||
UI.Window.layout(self)
|
||||
if not self.canvas then
|
||||
self.canvas = self:addLayer()
|
||||
else
|
||||
self:show()
|
||||
self.canvas:resize(self.width, self.height)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.SlideOut:enable()
|
||||
end
|
||||
|
||||
function UI.SlideOut:show(...)
|
||||
self:addTransition('expandUp')
|
||||
self.canvas:raise()
|
||||
self.canvas:setVisible(true)
|
||||
UI.Window.enable(self, ...)
|
||||
self:draw()
|
||||
self:capture(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function UI.SlideOut:disable()
|
||||
self.canvas:setVisible(false)
|
||||
UI.Window.disable(self)
|
||||
end
|
||||
|
||||
function UI.SlideOut:hide()
|
||||
self:disable()
|
||||
end
|
||||
|
||||
function UI.SlideOut:draw()
|
||||
if not self.noFill then
|
||||
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), 'black', 'gray')
|
||||
end
|
||||
self:drawChildren()
|
||||
self:release(self)
|
||||
self:refocus()
|
||||
end
|
||||
|
||||
function UI.SlideOut:eventHandler(event)
|
||||
@@ -45,31 +50,3 @@ function UI.SlideOut:eventHandler(event)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UI.SlideOut.example()
|
||||
return UI.Window {
|
||||
y = 3,
|
||||
backgroundColor = 2048,
|
||||
button = UI.Button {
|
||||
x = 2, y = 5,
|
||||
text = 'show',
|
||||
},
|
||||
slideOut = UI.SlideOut {
|
||||
backgroundColor = 16,
|
||||
y = -7, height = 4, x = 3, ex = -3,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'test',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 2, y = 2,
|
||||
text = 'hide',
|
||||
--visualize = true,
|
||||
},
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
if event.type == 'button_press' then
|
||||
self.slideOut:toggle()
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -2,15 +2,17 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Slider = class(UI.Window)
|
||||
UI.Slider.defaults = {
|
||||
UIElement = 'Slider',
|
||||
height = 1,
|
||||
barChar = UI.extChars and '\140' or '-',
|
||||
barColor = 'gray',
|
||||
barColor = colors.gray,
|
||||
sliderChar = UI.extChars and '\143' or '\124',
|
||||
sliderColor = 'blue',
|
||||
sliderFocusColor = 'lightBlue',
|
||||
sliderColor = colors.blue,
|
||||
sliderFocusColor = colors.lightBlue,
|
||||
leftBorder = UI.extChars and '\141' or '\124',
|
||||
rightBorder = UI.extChars and '\142' or '\124',
|
||||
value = 0,
|
||||
@@ -47,7 +49,7 @@ function UI.Slider:draw()
|
||||
i == self.width and self.rightBorder or
|
||||
self.barChar
|
||||
|
||||
table.insert(bar, filler)
|
||||
table.insert(bar, filler)
|
||||
end
|
||||
self:write(1, 1, table.concat(bar), nil, self.barColor)
|
||||
self:write(progress, 1, self.sliderChar, nil, self.focused and self.sliderFocusColor or self.sliderColor)
|
||||
@@ -55,16 +57,8 @@ end
|
||||
|
||||
function UI.Slider:eventHandler(event)
|
||||
if event.type == "mouse_down" or event.type == "mouse_drag" then
|
||||
|
||||
local pos = event.x - 1
|
||||
if event.type == 'mouse_down' then
|
||||
self.anchor = event.x - 1
|
||||
else
|
||||
pos = self.anchor + event.dx
|
||||
end
|
||||
|
||||
local range = self.max - self.min
|
||||
local i = pos / (self.width - 1)
|
||||
local i = (event.x - 1) / (self.width - 1)
|
||||
self.value = self.min + (i * range)
|
||||
self:emit({ type = self.event, value = self.value, element = self })
|
||||
self:draw()
|
||||
@@ -81,10 +75,3 @@ function UI.Slider:eventHandler(event)
|
||||
self:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Slider.example()
|
||||
return UI.Slider {
|
||||
y = 2, x = 2, ex = -2,
|
||||
min = 0, max = 1,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -3,16 +3,17 @@ local Event = require('opus.event')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.StatusBar = class(UI.Window)
|
||||
UI.StatusBar.defaults = {
|
||||
UIElement = 'StatusBar',
|
||||
backgroundColor = 'lightGray',
|
||||
textColor = 'gray',
|
||||
backgroundColor = colors.lightGray,
|
||||
textColor = colors.gray,
|
||||
height = 1,
|
||||
ey = -1,
|
||||
}
|
||||
function UI.StatusBar:layout()
|
||||
UI.Window.layout(self)
|
||||
function UI.StatusBar:adjustWidth()
|
||||
-- Can only have 1 adjustable width
|
||||
if self.columns then
|
||||
local w = self.width - #self.columns - 1
|
||||
@@ -30,6 +31,16 @@ function UI.StatusBar:layout()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.StatusBar:resize()
|
||||
UI.Window.resize(self)
|
||||
self:adjustWidth()
|
||||
end
|
||||
|
||||
function UI.StatusBar:setParent()
|
||||
UI.Window.setParent(self)
|
||||
self:adjustWidth()
|
||||
end
|
||||
|
||||
function UI.StatusBar:setStatus(status)
|
||||
if self.values ~= status then
|
||||
self.values = status
|
||||
@@ -52,7 +63,7 @@ end
|
||||
|
||||
function UI.StatusBar:timedStatus(status, timeout)
|
||||
self:write(2, 1, Util.widthify(status, self.width-2), self.backgroundColor)
|
||||
Event.onTimeout(timeout or 3, function()
|
||||
Event.on(timeout or 3, function()
|
||||
if self.enabled then
|
||||
self:draw()
|
||||
self:sync()
|
||||
@@ -78,29 +89,10 @@ function UI.StatusBar:draw()
|
||||
elseif type(self.values) == 'string' then
|
||||
self:write(1, 1, Util.widthify(' ' .. self.values, self.width))
|
||||
else
|
||||
local x = 2
|
||||
self:clear()
|
||||
local s = ''
|
||||
for _,c in ipairs(self.columns) do
|
||||
local s = Util.widthify(tostring(self.values[c.key] or ''), c.cw)
|
||||
self:write(x, 1, s, c.bg, c.fg)
|
||||
x = x + c.cw + 1
|
||||
s = s .. ' ' .. Util.widthify(tostring(self.values[c.key] or ''), c.cw)
|
||||
end
|
||||
self:write(1, 1, Util.widthify(s, self.width))
|
||||
end
|
||||
end
|
||||
|
||||
function UI.StatusBar.example()
|
||||
return UI.Window {
|
||||
status1 = UI.StatusBar { values = 'standard' },
|
||||
status2 = UI.StatusBar {
|
||||
ey = -3,
|
||||
columns = {
|
||||
{ key = 'field1' },
|
||||
{ key = 'field2', width = 6 },
|
||||
},
|
||||
values = {
|
||||
field1 = 'test',
|
||||
field2 = '42',
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.Tab = class(UI.Window)
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Tab = class(UI.ActiveLayer)
|
||||
UI.Tab.defaults = {
|
||||
UIElement = 'Tab',
|
||||
tabTitle = 'tab',
|
||||
backgroundColor = colors.cyan,
|
||||
y = 2,
|
||||
}
|
||||
|
||||
function UI.Tab:draw()
|
||||
if not self.noFill then
|
||||
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray)
|
||||
end
|
||||
self:drawChildren()
|
||||
end
|
||||
|
||||
@@ -2,13 +2,13 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.TabBar = class(UI.MenuBar)
|
||||
UI.TabBar.defaults = {
|
||||
UIElement = 'TabBar',
|
||||
buttonClass = 'TabBarMenuItem',
|
||||
backgroundColor = 'black',
|
||||
selectedBackgroundColor = 'primary',
|
||||
unselectedBackgroundColor = 'tertiary',
|
||||
selectedBackgroundColor = colors.cyan,
|
||||
}
|
||||
function UI.TabBar:enable()
|
||||
UI.MenuBar.enable(self)
|
||||
@@ -32,7 +32,7 @@ function UI.TabBar:eventHandler(event)
|
||||
self:emit({ type = 'tab_change', current = si, last = pi, tab = selected })
|
||||
end
|
||||
end
|
||||
self:draw(self)
|
||||
UI.MenuBar.draw(self)
|
||||
end
|
||||
return UI.MenuBar.eventHandler(self, event)
|
||||
end
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
--[[-- TabBarMenuItem --]]--
|
||||
UI.TabBarMenuItem = class(UI.Button)
|
||||
UI.TabBarMenuItem.defaults = {
|
||||
UIElement = 'TabBarMenuItem',
|
||||
event = 'tab_select',
|
||||
textInactiveColor = 'lightGray',
|
||||
textColor = colors.black,
|
||||
selectedBackgroundColor = colors.cyan,
|
||||
unselectedBackgroundColor = colors.lightGray,
|
||||
backgroundColor = colors.lightGray,
|
||||
}
|
||||
function UI.TabBarMenuItem:draw()
|
||||
if self.selected then
|
||||
self.backgroundColor = self:getProperty('selectedBackgroundColor')
|
||||
self.backgroundFocusColor = self.backgroundColor
|
||||
self.backgroundColor = self.selectedBackgroundColor
|
||||
self.backgroundFocusColor = self.selectedBackgroundColor
|
||||
else
|
||||
self.backgroundColor = self:getProperty('unselectedBackgroundColor')
|
||||
self.backgroundFocusColor = self.backgroundColor
|
||||
self.backgroundColor = self.unselectedBackgroundColor
|
||||
self.backgroundFocusColor = self.unselectedBackgroundColor
|
||||
end
|
||||
UI.Button.draw(self)
|
||||
end
|
||||
|
||||
@@ -3,7 +3,6 @@ local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
UI.Tabs = class(UI.Window)
|
||||
UI.Tabs.docs = { }
|
||||
UI.Tabs.defaults = {
|
||||
UIElement = 'Tabs',
|
||||
}
|
||||
@@ -17,7 +16,6 @@ function UI.Tabs:add(children)
|
||||
if type(child) == 'table' and child.UIElement and child.tabTitle then
|
||||
child.y = 2
|
||||
table.insert(buttons, {
|
||||
index = child.index,
|
||||
text = child.tabTitle,
|
||||
event = 'tab_select',
|
||||
tabUid = child.uid,
|
||||
@@ -34,12 +32,10 @@ function UI.Tabs:add(children)
|
||||
end
|
||||
|
||||
if self.parent then
|
||||
UI.Window.add(self, children)
|
||||
return UI.Window.add(self, children)
|
||||
end
|
||||
end
|
||||
|
||||
UI.Tabs.docs.selectTab = [[selectTab(TAB)
|
||||
Make to the passed tab active.]]
|
||||
function UI.Tabs:selectTab(tab)
|
||||
local menuItem = Util.find(self.tabBar.children, 'tabUid', tab.uid)
|
||||
if menuItem then
|
||||
@@ -56,12 +52,12 @@ end
|
||||
|
||||
function UI.Tabs:enable()
|
||||
self.enabled = true
|
||||
self.transitionHint = nil
|
||||
self.tabBar:enable()
|
||||
|
||||
local menuItem = Util.find(self.tabBar.children, 'selected', true)
|
||||
|
||||
for child in self:eachChild() do
|
||||
child.transitionHint = nil
|
||||
for _,child in pairs(self.children) do
|
||||
if child.uid == menuItem.tabUid then
|
||||
child:enable()
|
||||
self:emit({ type = 'tab_activate', activated = child })
|
||||
@@ -74,11 +70,14 @@ end
|
||||
function UI.Tabs:eventHandler(event)
|
||||
if event.type == 'tab_change' then
|
||||
local tab = self:find(event.tab.tabUid)
|
||||
local hint = event.current > event.last and 'slideLeft' or 'slideRight'
|
||||
if event.current > event.last then
|
||||
self.transitionHint = 'slideLeft'
|
||||
else
|
||||
self.transitionHint = 'slideRight'
|
||||
end
|
||||
|
||||
for child in self:eachChild() do
|
||||
for _,child in pairs(self.children) do
|
||||
if child.uid == event.tab.tabUid then
|
||||
child.transitionHint = hint
|
||||
child:enable()
|
||||
elseif child.tabTitle then
|
||||
child:disable()
|
||||
@@ -86,40 +85,5 @@ function UI.Tabs:eventHandler(event)
|
||||
end
|
||||
self:emit({ type = 'tab_activate', activated = tab })
|
||||
tab:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Tabs.example()
|
||||
return UI.Tabs {
|
||||
tab1 = UI.Tab {
|
||||
index = 1,
|
||||
tabTitle = 'tab1',
|
||||
entry = UI.TextEntry { y = 3, shadowText = 'text' },
|
||||
},
|
||||
tab2 = UI.Tab {
|
||||
index = 2,
|
||||
tabTitle = 'tab2',
|
||||
subtabs = UI.Tabs {
|
||||
x = 3, y = 2, ex = -3, ey = -2,
|
||||
tab1 = UI.Tab {
|
||||
index = 1,
|
||||
tabTitle = 'tab4',
|
||||
entry = UI.TextEntry { y = 3, shadowText = 'text' },
|
||||
},
|
||||
tab3 = UI.Tab {
|
||||
index = 2,
|
||||
tabTitle = 'tab5',
|
||||
},
|
||||
},
|
||||
},
|
||||
tab3 = UI.Tab {
|
||||
index = 3,
|
||||
tabTitle = 'tab3',
|
||||
},
|
||||
enable = function(self)
|
||||
UI.Tabs.enable(self)
|
||||
self:setActive(self.tab3, false)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -8,11 +8,11 @@ UI.Text.defaults = {
|
||||
value = '',
|
||||
height = 1,
|
||||
}
|
||||
function UI.Text:layout()
|
||||
function UI.Text:setParent()
|
||||
if not self.width and not self.ex then
|
||||
self.width = #tostring(self.value)
|
||||
end
|
||||
UI.Window.layout(self)
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Text:draw()
|
||||
|
||||
@@ -1,49 +1,36 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
--[[-- TextArea --]]--
|
||||
UI.TextArea = class(UI.Viewport)
|
||||
UI.TextArea.defaults = {
|
||||
UIElement = 'TextArea',
|
||||
marginRight = 2,
|
||||
value = '',
|
||||
showScrollBar = true,
|
||||
}
|
||||
function UI.TextArea:postInit()
|
||||
self.scrollBar = UI.ScrollBar()
|
||||
end
|
||||
|
||||
function UI.TextArea:setText(text)
|
||||
self:reset()
|
||||
self.value = text
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function UI.TextArea.focus()
|
||||
function UI.TextArea:focus()
|
||||
-- allow keyboard scrolling
|
||||
end
|
||||
|
||||
function UI.TextArea:draw()
|
||||
self:clear()
|
||||
-- self:setCursorPos(1, 1)
|
||||
self.cursorX, self.cursorY = 1, 1
|
||||
self:print(self.value)
|
||||
self:drawChildren()
|
||||
end
|
||||
|
||||
function UI.TextArea.example()
|
||||
local Ansi = require('opus.ansi')
|
||||
return UI.Window {
|
||||
backgroundColor = 2048,
|
||||
t1 = UI.TextArea {
|
||||
ey = 3,
|
||||
value = 'sample text\nabc'
|
||||
},
|
||||
t2 = UI.TextArea {
|
||||
y = 5,
|
||||
backgroundColor = 'green',
|
||||
value = string.format([[now %%is the %stime %sfor%s all good men to come to the aid of their country.
|
||||
1
|
||||
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8]], Ansi.yellow, Ansi.onred, Ansi.reset),
|
||||
}
|
||||
}
|
||||
end
|
||||
for _,child in pairs(self.children) do
|
||||
if child.enabled then
|
||||
child:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,37 +3,29 @@ local entry = require('opus.entry')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local _rep = string.rep
|
||||
|
||||
local function transform(directive)
|
||||
local transforms = {
|
||||
lowercase = string.lower,
|
||||
uppercase = string.upper,
|
||||
number = tonumber,
|
||||
}
|
||||
return transforms[directive]
|
||||
end
|
||||
local _lower = string.lower
|
||||
local _upper = string.upper
|
||||
|
||||
UI.TextEntry = class(UI.Window)
|
||||
UI.TextEntry.docs = { }
|
||||
UI.TextEntry.defaults = {
|
||||
UIElement = 'TextEntry',
|
||||
--value = '',
|
||||
shadowText = '',
|
||||
focused = false,
|
||||
textColor = 'white',
|
||||
shadowTextColor = 'gray',
|
||||
markBackgroundColor = 'gray',
|
||||
backgroundColor = 'black',
|
||||
backgroundFocusColor = 'black',
|
||||
textColor = colors.white,
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.black, -- colors.lightGray,
|
||||
backgroundFocusColor = colors.black, --lightGray,
|
||||
height = 1,
|
||||
limit = 6,
|
||||
cursorBlink = true,
|
||||
accelerators = {
|
||||
[ 'control-c' ] = 'copy',
|
||||
}
|
||||
}
|
||||
function UI.TextEntry:postInit()
|
||||
self.entry = entry({ limit = self.limit, offset = 2, transform = transform(self.transform) })
|
||||
self.entry = entry({ limit = self.limit, offset = 2 })
|
||||
end
|
||||
|
||||
function UI.TextEntry:layout()
|
||||
@@ -42,15 +34,15 @@ function UI.TextEntry:layout()
|
||||
end
|
||||
|
||||
function UI.TextEntry:setValue(value)
|
||||
self.value = value
|
||||
self.value = value --or ''
|
||||
self.entry:unmark()
|
||||
self.entry.value = value
|
||||
self.entry.value = tostring(value)
|
||||
self.entry:updateScroll()
|
||||
end
|
||||
|
||||
function UI.TextEntry:setPosition(pos)
|
||||
self.entry.pos = pos
|
||||
self.entry.value = self.value -- WHY HERE ?
|
||||
self.entry.value = tostring(self.value or '')
|
||||
self.entry:updateScroll()
|
||||
end
|
||||
|
||||
@@ -74,9 +66,7 @@ function UI.TextEntry:draw()
|
||||
text = self.shadowText
|
||||
end
|
||||
|
||||
local ss = self.entry.scroll > 0 and '\183' or ' '
|
||||
self:write(2, 1, Util.widthify(text, self.width - 2) .. ' ', bg, tc)
|
||||
self:write(1, 1, ss, bg, self.shadowTextColor)
|
||||
self:write(1, 1, ' ' .. Util.widthify(text, self.width - 2) .. ' ', bg, tc)
|
||||
|
||||
if self.entry.mark.active then
|
||||
local tx = math.max(self.entry.mark.x - self.entry.scroll, 0)
|
||||
@@ -87,7 +77,7 @@ function UI.TextEntry:draw()
|
||||
end
|
||||
|
||||
if tx ~= tex then
|
||||
self:write(tx + 2, 1, text:sub(tx + 1, tex), self.markBackgroundColor, tc)
|
||||
self:write(tx + 2, 1, text:sub(tx + 1, tex), colors.gray, tc)
|
||||
end
|
||||
end
|
||||
if self.focused then
|
||||
@@ -95,8 +85,6 @@ function UI.TextEntry:draw()
|
||||
end
|
||||
end
|
||||
|
||||
UI.TextEntry.docs.reset = [[reset()
|
||||
Clears the value and resets the cursor.]]
|
||||
function UI.TextEntry:reset()
|
||||
self.entry:reset()
|
||||
self.value = nil--''
|
||||
@@ -108,25 +96,34 @@ function UI.TextEntry:updateCursor()
|
||||
self:setCursorPos(self.entry.pos - self.entry.scroll + 2, 1)
|
||||
end
|
||||
|
||||
function UI.TextEntry:markAll()
|
||||
self.entry:markAll()
|
||||
end
|
||||
|
||||
function UI.TextEntry:focus()
|
||||
self:draw()
|
||||
if self.focused then
|
||||
self:setCursorBlink(true)
|
||||
else
|
||||
self:setCursorBlink(false)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.TextEntry:_transform(text)
|
||||
if self.transform == 'lowercase' then
|
||||
return _lower(text)
|
||||
elseif self.transform == 'uppercase' then
|
||||
return _upper(text)
|
||||
elseif self.transform == 'number' then
|
||||
return tonumber(text) --or 0
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
function UI.TextEntry:eventHandler(event)
|
||||
local text = self.value
|
||||
self.entry.value = text
|
||||
local text = self.value --or ''
|
||||
self.entry.value = tostring(text or '')
|
||||
if event.ie and self.entry:process(event.ie) then
|
||||
if self.entry.textChanged then
|
||||
local changed = self.value ~= self.entry.value
|
||||
self.value = self.entry.value
|
||||
self.value = self:_transform(self.entry.value)
|
||||
self:draw()
|
||||
if changed then
|
||||
-- we get entry.textChanged when marking is updated
|
||||
-- no need to emit in that case
|
||||
if text ~= self.value then
|
||||
self:emit({ type = 'text_change', text = self.value, element = self })
|
||||
end
|
||||
elseif self.entry.posChanged then
|
||||
@@ -137,35 +134,3 @@ function UI.TextEntry:eventHandler(event)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function UI.TextEntry.example()
|
||||
return UI.Window {
|
||||
text = UI.TextEntry {
|
||||
x = 2, y = 2,
|
||||
width = 12,
|
||||
limit = 36,
|
||||
shadowText = 'normal',
|
||||
},
|
||||
upper = UI.TextEntry {
|
||||
x = 2, y = 3,
|
||||
width = 12,
|
||||
limit = 36,
|
||||
shadowText = 'upper',
|
||||
transform = 'uppercase',
|
||||
},
|
||||
lower = UI.TextEntry {
|
||||
x = 2, y = 4,
|
||||
width = 12,
|
||||
limit = 36,
|
||||
shadowText = 'lower',
|
||||
transform = 'lowercase',
|
||||
},
|
||||
number = UI.TextEntry {
|
||||
x = 2, y = 5,
|
||||
width = 12,
|
||||
limit = 36,
|
||||
transform = 'number',
|
||||
shadowText = 'number',
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -20,15 +20,24 @@ UI.Throttle.defaults = {
|
||||
' //) (O ). @ \\-d ) (@ '
|
||||
}
|
||||
}
|
||||
function UI.Throttle:layout()
|
||||
function UI.Throttle:setParent()
|
||||
self.x = math.ceil((self.parent.width - self.width) / 2)
|
||||
self.y = math.ceil((self.parent.height - self.height) / 2)
|
||||
self:reposition(self.x, self.y, self.width, self.height)
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Throttle:enable()
|
||||
self.c = os.clock()
|
||||
self.ctr = 0
|
||||
self.enabled = false
|
||||
end
|
||||
|
||||
function UI.Throttle:disable()
|
||||
if self.canvas then
|
||||
self.enabled = false
|
||||
self.canvas:removeLayer()
|
||||
self.canvas = nil
|
||||
self.ctr = 0
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Throttle:update()
|
||||
@@ -37,7 +46,11 @@ function UI.Throttle:update()
|
||||
os.sleep(0)
|
||||
self.c = os.clock()
|
||||
self.enabled = true
|
||||
self:clear(self.borderColor)
|
||||
if not self.canvas then
|
||||
self.canvas = self:addLayer(self.backgroundColor, self.borderColor)
|
||||
self.canvas:setVisible(true)
|
||||
self:clear(self.borderColor)
|
||||
end
|
||||
local image = self.image[self.ctr + 1]
|
||||
local width = self.width - 2
|
||||
for i = 0, #self.image do
|
||||
@@ -50,25 +63,3 @@ function UI.Throttle:update()
|
||||
self:sync()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Throttle.example()
|
||||
return UI.Window {
|
||||
button1 = UI.Button {
|
||||
x = 2, y = 2,
|
||||
text = 'Test',
|
||||
},
|
||||
throttle = UI.Throttle {
|
||||
textColor = colors.yellow,
|
||||
borderColor = colors.green,
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
if event.type == 'button_press' then
|
||||
for _ = 1, 40 do
|
||||
self.throttle:update()
|
||||
os.sleep(.05)
|
||||
end
|
||||
self.throttle:disable()
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,20 +1,59 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
-- For manipulating text in a fixed width string
|
||||
local SB = class()
|
||||
function SB:init(width)
|
||||
self.width = width
|
||||
self.buf = _rep(' ', width)
|
||||
end
|
||||
function SB:insert(x, str, width)
|
||||
if x < 1 then
|
||||
x = self.width + x + 1
|
||||
end
|
||||
width = width or #str
|
||||
if x + width - 1 > self.width then
|
||||
width = self.width - x
|
||||
end
|
||||
if width > 0 then
|
||||
self.buf = _sub(self.buf, 1, x - 1) .. _sub(str, 1, width) .. _sub(self.buf, x + width)
|
||||
end
|
||||
end
|
||||
function SB:fill(x, ch, width)
|
||||
width = width or self.width - x + 1
|
||||
self:insert(x, _rep(ch, width))
|
||||
end
|
||||
function SB:center(str)
|
||||
self:insert(math.max(1, math.ceil((self.width - #str + 1) / 2)), str)
|
||||
end
|
||||
function SB:get()
|
||||
return self.buf
|
||||
end
|
||||
|
||||
UI.TitleBar = class(UI.Window)
|
||||
UI.TitleBar.defaults = {
|
||||
UIElement = 'TitleBar',
|
||||
height = 1,
|
||||
textColor = colors.white,
|
||||
backgroundColor = colors.cyan,
|
||||
title = '',
|
||||
frameChar = UI.extChars and '\140' or '-',
|
||||
closeInd = UI.extChars and '\215' or '*',
|
||||
}
|
||||
function UI.TitleBar:draw()
|
||||
self:fillArea(2, 1, self.width - 2, 1, self.frameChar)
|
||||
self:centeredWrite(1, string.format(' %s ', self.title))
|
||||
local sb = SB(self.width)
|
||||
sb:fill(2, self.frameChar, sb.width - 3)
|
||||
sb:center(string.format(' %s ', self.title))
|
||||
if self.previousPage or self.event then
|
||||
self:write(self.width - 1, 1, ' ' .. self.closeInd)
|
||||
sb:insert(-1, self.closeInd)
|
||||
else
|
||||
sb:insert(-2, self.frameChar)
|
||||
end
|
||||
self:write(1, 1, sb:get())
|
||||
end
|
||||
|
||||
function UI.TitleBar:eventHandler(event)
|
||||
@@ -30,74 +69,5 @@ function UI.TitleBar:eventHandler(event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
elseif event.type == 'mouse_down' then
|
||||
self.anchor = { x = event.x, y = event.y, ox = self.parent.x, oy = self.parent.y, h = self.parent.height }
|
||||
|
||||
elseif event.type == 'mouse_drag' then
|
||||
if self.expand == 'height' then
|
||||
local d = event.dy
|
||||
if self.anchor.h - d > 0 and self.anchor.oy + d > 0 then
|
||||
self.parent:reposition(self.parent.x, self.anchor.oy + event.dy, self.width, self.anchor.h - d)
|
||||
end
|
||||
|
||||
elseif self.moveable then
|
||||
local d = event.dy
|
||||
if self.anchor.oy + d > 0 and self.anchor.oy + d <= self.parent.parent.height then
|
||||
self.parent:move(self.anchor.ox + event.dx, self.anchor.oy + event.dy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.TitleBar.example()
|
||||
return UI.Window {
|
||||
win1 = UI.Window {
|
||||
x = 9, y = 2, ex = -7, ey = -3,
|
||||
backgroundColor = 'green',
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'A really, really, really long title', moveable = true,
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'Press',
|
||||
},
|
||||
focus = function (self)
|
||||
self:raise()
|
||||
end,
|
||||
},
|
||||
win2 = UI.Window {
|
||||
x = 7, y = 3, ex = -9, ey = -2,
|
||||
backgroundColor = 'orange',
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'test', moveable = true,
|
||||
event = 'none',
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'Press',
|
||||
},
|
||||
focus = function (self)
|
||||
self:raise()
|
||||
end,
|
||||
},
|
||||
draw = function(self, isBG)
|
||||
for i = 1, self.height do
|
||||
self:write(1, i, self.filler or '')
|
||||
end
|
||||
if not isBG then
|
||||
for _,v in pairs(self.children) do
|
||||
v:draw()
|
||||
end
|
||||
end
|
||||
end,
|
||||
enable = function (self)
|
||||
require('opus.event').onInterval(.5, function()
|
||||
self.filler = string.rep(string.char(math.random(33, 126)), self.width)
|
||||
self:draw(true)
|
||||
self:sync()
|
||||
end)
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user