14 Commits

Author SHA1 Message Date
Anavrins
e3a0222ef0 fix: up-to-date package on master branch 2021-02-15 21:31:43 -05:00
kepler155c
136adff7b1 Delete .opus_version 2020-04-30 21:56:26 -06:00
github-actions[bot]
60ab8377b5 Update version date 2020-05-01 03:39:14 +00:00
kepler155c
6a58db5e1c Delete .opus_version 2020-04-30 21:32:52 -06:00
github-actions[bot]
d692116631 Update version date 2020-05-01 03:27:43 +00:00
kepler155c
af1ef38147 Delete .opus_version 2020-04-30 20:24:23 -06:00
github-actions[bot]
58c3a1529f Update version date 2020-05-01 02:23:31 +00:00
kepler155c
6f87c5a8a7 Delete .opus_version 2020-04-30 20:08:20 -06:00
github-actions[bot]
7891f9863a Update version date 2020-05-01 01:09:13 +00:00
Anavrins
0fd349a487 Fix wrong branch name
this also fixes packages loading on master-1.8
2020-01-15 00:12:29 -05:00
kepler155c@gmail.com
cef5b21921 update to master 2019-11-10 18:52:28 -07:00
kepler155c@gmail.com
ee6af86da8 Merge branch 'develop-1.8' into master-1.8 2019-11-10 18:51:51 -07:00
kepler155c
16843bdb78 Update git.lua 2019-01-18 14:25:44 -05:00
kepler155c
f21ff42e44 Update README.md 2018-12-30 13:53:20 -05:00
106 changed files with 2229 additions and 3689 deletions

1
.gitignore vendored
View File

@@ -1,2 +1 @@
/ignore
.project

View File

@@ -15,4 +15,5 @@
## Install
```
pastebin run uzghlbnc
reboot
```

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -27,4 +27,4 @@ kernel.hook('kernel_focus', function(_, eventData)
end
end)
os.pullEventRaw('kernel_halt')
os.pullEventRaw('kernel_halt')

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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' },

View File

@@ -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 = { }

View File

@@ -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

View File

@@ -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)
},
}

View File

@@ -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

View File

@@ -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

View File

@@ -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',
},
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,
})

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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')

View File

@@ -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')

View File

@@ -50,10 +50,7 @@
title = "System",
category = "System",
icon = "\030 \0307\031f| \010\0307\031f---o\030 \031 \010\030 \009 \0307\031f| ",
iconExt = "22€070†b02‹4Ÿ24\
02—7Ž704ˆ4€€€€\
7ƒ07„1ƒ7‹24ƒƒ",
--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" ] = {

View File

@@ -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.

View File

@@ -1,3 +1,5 @@
_G.requireInjector(_ENV)
local Peripheral = require('opus.peripheral')
_G.device = Peripheral.getList()

View File

@@ -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')

View File

@@ -1,3 +1,5 @@
_G.requireInjector(_ENV)
local Config = require('opus.config')
local device = _G.device

View File

@@ -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

View File

@@ -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({

View File

@@ -1,3 +1,5 @@
_G.requireInjector(_ENV)
local Array = require('opus.array')
local Terminal = require('opus.terminal')
local Util = require('opus.util')

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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 },
})

View File

@@ -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

View File

@@ -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
})

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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