partition manager + tab/wizard rework

This commit is contained in:
kepler155c@gmail.com 2020-04-26 19:39:58 -06:00
parent b0d2ce0199
commit ef9f0e09b6
35 changed files with 588 additions and 334 deletions

View File

@ -45,8 +45,9 @@ local page = UI.Page {
},
tabs = UI.Tabs {
y = 3,
[1] = UI.Tab {
tabTitle = 'Formatted',
formatted = UI.Tab {
title = 'Formatted',
index = 1,
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Key', key = 'name' },
@ -56,8 +57,9 @@ local page = UI.Page {
autospace = true,
},
},
[2] = UI.Tab {
tabTitle = 'Output',
output = UI.Tab {
title = 'Output',
index = 2,
backgroundColor = 'black',
output = UI.Embedded {
y = 2,
@ -72,8 +74,8 @@ local page = UI.Page {
},
}
page.grid = page.tabs[1].grid
page.output = page.tabs[2].output
page.grid = page.tabs.formatted.grid
page.output = page.tabs.output.output
function page:setPrompt(value, focus)
self.prompt:setValue(value)
@ -142,7 +144,7 @@ function page:eventHandler(event)
self:setFocus(self.prompt)
elseif event.type == 'show_output' then
self.tabs:selectTab(self.tabs[2])
self.tabs:selectTab(self.tabs.output)
elseif event.type == 'autocomplete' then
local value = self.prompt.value or ''

View File

@ -29,12 +29,14 @@ if not _ENV.multishell then
end
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\
-- icon:gsub('.', function(b) return '\\' .. b:byte() end)
local DEFAULT_ICON = NFT.parse('\30\55\31\48\136\140\140\140\132\
\30\48\31\55\149\31\48\128\128\128\30\55\149\
\30\55\31\48\138\143\143\143\133')
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")
\0302\0312\32\32\32\32\32')
-- overview
local uid = _ENV.multishell.getCurrent()

238
sys/apps/Partition.lua Normal file
View File

@ -0,0 +1,238 @@
local Ansi = require('opus.ansi')
local Event = require('opus.event')
local UI = require('opus.ui')
local Util = require('opus.util')
local fs = _G.fs
local peripheral = _G.peripheral
local source, target
local function getDriveInfo(tgt)
local total = 0
local throttle = Util.throttle()
tgt = fs.combine(tgt, '')
local src = fs.getNode(tgt).source or tgt
local function recurse(path)
throttle()
if fs.isDir(path) then
if path ~= src then
total = total + 500
end
for _, v in pairs(fs.native.list(path)) do
recurse(fs.combine(path, v))
end
else
local sz = fs.getSize(path)
total = total + math.max(500, sz)
end
end
recurse(src)
local drive = fs.getDrive(src)
return {
path = tgt,
drive = drive,
type = peripheral.getType(drive) or drive,
used = total,
free = fs.getFreeSpace(src),
mountPoint = src,
}
end
local function getDrives(exclude)
local drives = { }
for _, path in pairs(fs.native.list('/')) do
local side = fs.getDrive(path)
if side and not drives[side] and not fs.isReadOnly(path) and side ~= exclude then
if side == 'hdd' then
path = ''
end
drives[side] = getDriveInfo(path)
end
end
return drives
end
local page = UI.Page {
wizard = UI.Wizard {
ey = -2,
partitions = UI.WizardPage {
index = 1,
info = UI.TextArea {
x = 3, y = 2, ex = -3, ey = 5,
value = [[Move the contents of a directory to another disk. A link will be created to point to that location.]]
},
grid = UI.Grid {
x = 2, y = 7, ex = -2, ey = -2,
columns = {
{ heading = 'Path', key = 'path', textColor = 'yellow', width = 10 },
{ heading = 'Mount Point', key = 'mountPoint' },
{ heading = 'Used', key = 'used', width = 6 },
},
sortColumn = 'path',
getDisplayValues = function (_, row)
row = Util.shallowCopy(row)
row.used = Util.toBytes(row.used)
return row
end,
enable = function(self)
Event.onTimeout(0, function()
local mounts = {
usr = getDriveInfo('usr/config'),
packages = getDriveInfo('packages'),
}
self:setValues(mounts)
self:draw()
self:sync()
end)
self:setValues({ })
UI.Grid.enable(self)
end,
},
validate = function(self)
target = self.grid:getSelected()
return not not target
end,
},
mounts = UI.WizardPage {
index = 2,
info = UI.TextArea {
x = 3, y = 2, ex = -3, ey = 5,
value = [[Select the target disk. Labeled computers can be inserted into disk drives for larger volumes.]]
},
grid = UI.Grid {
x = 2, y = 7, ex = -2, ey = -2,
columns = {
{ heading = 'Path', key = 'path', textColor = 'yellow', width = 10 },
{ heading = 'Type', key = 'type' },
{ heading = 'Side', key = 'drive' },
{ heading = 'Free', key = 'free', width = 6 },
},
sortColumn = 'path',
getDisplayValues = function (_, row)
row = Util.shallowCopy(row)
row.free = Util.toBytes(row.free)
return row
end,
getRowTextColor = function(self, row)
if row.free < target.used then
return 'lightGray'
end
return UI.Grid.getRowTextColor(self, row)
end,
enable = function(self)
Event.on({ 'disk', 'disk_eject', 'partition_update' }, function()
self:setValues(getDrives(target.drive))
self:draw()
self:sync()
end)
os.queueEvent('partition_update')
self:setValues({ })
UI.Grid.enable(self)
end,
},
validate = function(self)
source = self.grid:getSelected()
if not source then
self:emit({ type = 'notify', message = 'No drive selected' })
elseif source.free < target.used then
self:emit({ type = 'notify', message = 'Insufficient disk space' })
else
return true
end
end,
},
confirm = UI.WizardPage {
index = 3,
info = UI.TextArea {
x = 2, y = 2, ex = -2, ey = -2,
marginTop = 1, marginLeft = 1,
backgroundColor = 'black',
},
enable = function(self)
local fstab = Util.readFile('usr/etc/fstab')
local lines = { }
table.insert(lines, string.format('%sReview changes%s\n', Ansi.yellow, Ansi.reset))
if fstab then
for _,l in ipairs(Util.split(fstab)) do
l = Util.trim(l)
if #l > 0 and l:sub(1, 1) ~= '#' then
local m = Util.matches(l)
if m and m[1] and m[1] == target.path then
table.insert(lines, string.format('Removed from usr/etc/fstab:\n%s%s%s\n', Ansi.red, l, Ansi.reset))
end
end
end
end
local t = target.path
local s = fs.combine(source.path .. '/' .. target.path, '')
if t ~= s then
table.insert(lines, string.format('Added to usr/etc/fstab:\n%s%s linkfs %s%s\n', Ansi.green, t, s, Ansi.reset))
end
table.insert(lines, string.format('Move directory:\n%s/%s -> /%s', Ansi.green, target.mountPoint, s))
self.info:setText(table.concat(lines, '\n'))
UI.WizardPage.enable(self)
end,
validate = function(self)
if self.changesApplied then
return true
end
local fstab = Util.readFile('usr/etc/fstab')
local lines = { }
if fstab then
for _,l in ipairs(Util.split(fstab)) do
table.insert(lines, l)
l = Util.trim(l)
if #l > 0 and l:sub(1, 1) ~= '#' then
local m = Util.matches(l)
if m and m[1] and m[1] == target.path then
fs.unmount(m[1])
table.remove(lines)
end
end
end
end
local t = target.path
local s = fs.combine(source.path .. '/' .. target.path, '')
fs.move('/' .. target.mountPoint, '/' .. s)
if t ~= s then
table.insert(lines, string.format('%s linkfs %s', t, s))
fs.mount(t, 'linkfs', s)
end
Util.writeFile('usr/etc/fstab', table.concat(lines, '\n'))
self.parent.nextButton.text = 'Exit'
self.parent.cancelButton:disable()
self.parent.previousButton:disable()
self.changesApplied = true
self.info:setValue('Changes have been applied')
self.parent:draw()
end,
},
},
notification = UI.Notification { },
eventHandler = function(self, event)
if event.type == 'notify' then
self.notification:error(event.message)
elseif event.type == 'accept' or event.type == 'cancel' then
UI:quit()
end
return UI.Page.eventHandler(self, event)
end,
}
UI:disableEffects()
UI:setPage(page)
UI:start()

View File

@ -46,7 +46,7 @@ local page = UI.Page {
configTabs = UI.Tabs {
y = 2,
filterTab = UI.Tab {
tabTitle = 'Filter',
title = 'Filter',
noFill = true,
filterGridText = UI.Text {
x = 2, y = 2,
@ -93,7 +93,7 @@ local page = UI.Page {
},
},
modemTab = UI.Tab {
tabTitle = 'Modem',
title = 'Modem',
channelGrid = UI.ScrollingGrid {
x = 2, y = 2,
width = 12, height = 5,

View File

@ -6,62 +6,6 @@ local shell = _ENV.shell
UI:configure('System', ...)
local systemPage = UI.Page {
tabs = UI.Tabs {
settings = UI.Tab {
tabTitle = 'Category',
grid = UI.ScrollingGrid {
x = 2, y = 2, ex = -2, ey = -2,
columns = {
{ heading = 'Name', key = 'name' },
{ heading = 'Description', key = 'description' },
},
sortColumn = 'name',
autospace = true,
},
},
},
notification = UI.Notification(),
accelerators = {
[ 'control-q' ] = 'quit',
},
}
function systemPage.tabs.settings:eventHandler(event)
if event.type == 'grid_select' then
local tab = event.selected.tab
if not systemPage.tabs[tab.tabTitle] then
systemPage.tabs:add({ [ tab.tabTitle ] = tab })
tab:disable()
end
systemPage.tabs:selectTab(tab)
--self.parent:draw()
return true
end
end
function systemPage:eventHandler(event)
if event.type == 'quit' then
UI:quit()
elseif event.type == 'success_message' then
self.notification:success(event.message)
elseif event.type == 'info_message' then
self.notification:info(event.message)
elseif event.type == 'error_message' then
self.notification:error(event.message)
elseif event.type == 'tab_activate' then
event.activated:focusFirst()
else
return UI.Page.eventHandler(self, event)
end
return true
end
local function loadDirectory(dir)
local plugins = { }
for _, file in pairs(fs.list(dir)) do
@ -70,7 +14,7 @@ local function loadDirectory(dir)
_G.printError('Error loading: ' .. file)
error(m or 'Unknown error')
elseif s and m then
table.insert(plugins, { tab = m, name = m.tabTitle, description = m.description })
table.insert(plugins, { tab = m, name = m.title, description = m.description })
end
end
return plugins
@ -79,7 +23,60 @@ end
local programDir = fs.getDir(shell.getRunningProgram())
local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
systemPage.tabs.settings.grid:setValues(plugins)
local page = UI.Page {
tabs = UI.Tabs {
settings = UI.Tab {
title = 'Category',
grid = UI.ScrollingGrid {
x = 2, y = 2, ex = -2, ey = -2,
columns = {
{ heading = 'Name', key = 'name' },
{ heading = 'Description', key = 'description' },
},
sortColumn = 'name',
autospace = true,
values = plugins,
},
accelerators = {
grid_select = 'category_select',
}
},
},
notification = UI.Notification(),
accelerators = {
[ 'control-q' ] = 'quit',
},
eventHandler = function(self, event)
if event.type == 'quit' then
UI:quit()
UI:setPage(systemPage)
elseif event.type == 'category_select' then
local tab = event.selected.tab
if not self.tabs[tab.title] then
self.tabs:add({ [ tab.title ] = tab })
end
self.tabs:selectTab(tab)
return true
elseif event.type == 'success_message' then
self.notification:success(event.message)
elseif event.type == 'info_message' then
self.notification:info(event.message)
elseif event.type == 'error_message' then
self.notification:error(event.message)
elseif event.type == 'tab_activate' then
event.activated:focusFirst()
else
return UI.Page.eventHandler(self, event)
end
return true
end,
}
UI:setPage(page)
UI:start()

View File

@ -33,87 +33,85 @@ https://github.com/kepler155c/opus]]
local page = UI.Page {
wizard = UI.Wizard {
ey = -2,
pages = {
splash = UI.WizardPage {
index = 1,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white),
},
splash = UI.WizardPage {
index = 1,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white),
},
label = UI.WizardPage {
index = 2,
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, ex = -3,
limit = 32,
value = os.getComputerLabel(),
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
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,
},
label = UI.WizardPage {
index = 2,
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
password = UI.WizardPage {
index = 3,
passwordLabel = UI.Text {
x = 3, y = 2,
value = 'Password'
},
newPass = UI.TextEntry {
x = 12, ex = -3, y = 2,
limit = 32,
mask = true,
shadowText = 'password',
},
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,
label = UI.TextEntry {
x = 9, y = 2, ex = -3,
limit = 32,
value = os.getComputerLabel(),
},
packages = UI.WizardPage {
index = 4,
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white),
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 4, ey = -3,
value = string.format(labelIntro, Ansi.white),
},
contributors = UI.WizardPage {
index = 5,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
},
validate = function (self)
if self.label.value then
os.setComputerLabel(self.label.value)
end
return true
end,
},
password = UI.WizardPage {
index = 3,
passwordLabel = UI.Text {
x = 3, y = 2,
value = 'Password'
},
newPass = UI.TextEntry {
x = 12, ex = -3, y = 2,
limit = 32,
mask = true,
shadowText = 'password',
},
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,
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white),
},
},
contributors = UI.WizardPage {
index = 5,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
},
},
},

View File

@ -49,7 +49,7 @@ page = UI.Page {
backgroundColor = colors.red,
y = '50%',
properties = UI.Tab {
tabTitle = 'Properties',
title = 'Properties',
grid = UI.ScrollingGrid {
headerBackgroundColor = colors.red,
sortColumn = 'key',
@ -64,7 +64,7 @@ page = UI.Page {
},
methodsTab = UI.Tab {
index = 2,
tabTitle = 'Methods',
title = 'Methods',
grid = UI.ScrollingGrid {
ex = '50%',
headerBackgroundColor = colors.red,
@ -85,7 +85,7 @@ page = UI.Page {
},
events = UI.Tab {
index = 1,
tabTitle = 'Events',
title = 'Events',
UI.MenuBar {
y = -1,
backgroundColor = colors.red,

View File

@ -4,7 +4,7 @@ local UI = require('opus.ui')
local kernel = _G.kernel
local aliasTab = UI.Tab {
tabTitle = 'Aliases',
title = 'Aliases',
description = 'Shell aliases',
alias = UI.TextEntry {
x = 2, y = 2, ex = -2,

View File

@ -3,7 +3,7 @@ local Config = require('opus.config')
local UI = require('opus.ui')
local tab = UI.Tab {
tabTitle = 'Preferred',
title = 'Preferred',
description = 'Select preferred applications',
apps = UI.ScrollingGrid {
x = 2, y = 2,

View File

@ -6,7 +6,7 @@ if _G.http.websocket then
local config = Config.load('cloud')
local tab = UI.Tab {
tabTitle = 'Cloud',
title = 'Cloud',
description = 'Cloud Catcher options',
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,

View File

@ -16,7 +16,7 @@ local NftImages = {
}
local tab = UI.Tab {
tabTitle = 'Disks Usage',
title = 'Disks Usage',
description = 'Visualise HDD and disks usage',
drives = UI.ScrollingGrid {
@ -138,11 +138,9 @@ function tab:enable()
UI.Tab.enable(self)
self.handler = Event.on({ 'disk', 'disk_eject' }, function()
os.sleep(1)
if tab.enabled then
tab:updateDrives()
tab:updateInfo()
tab:sync()
end
tab:updateDrives()
tab:updateInfo()
tab:sync()
end)
end

View File

@ -5,7 +5,7 @@ local peripheral = _G.peripheral
local settings = _G.settings
return peripheral.find('monitor') and UI.Tab {
tabTitle = 'Kiosk',
title = 'Kiosk',
description = 'Kiosk options',
form = UI.Form {
x = 2, y = 2, ex = -2, ey = 5,

View File

@ -5,7 +5,7 @@ local fs = _G.fs
local os = _G.os
return UI.Tab {
tabTitle = 'Label',
title = 'Label',
description = 'Set the computer label',
labelText = UI.Text {
x = 3, y = 3,

View File

@ -7,7 +7,7 @@ local fs = _G.fs
local config = Config.load('multishell')
local tab = UI.Tab {
tabTitle = 'Launcher',
title = 'Launcher',
description = 'Set the application launcher',
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 5,

View File

@ -6,7 +6,7 @@ local colors = _G.colors
local device = _G.device
return UI.Tab {
tabTitle = 'Network',
title = 'Network',
description = 'Networking options',
info = UI.TextArea {
x = 2, y = 5, ex = -2, ey = -2,

View File

@ -3,7 +3,7 @@ local SHA = require('opus.crypto.sha2')
local UI = require('opus.ui')
return UI.Tab {
tabTitle = 'Password',
title = 'Password',
description = 'Wireless network password',
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,

View File

@ -3,7 +3,7 @@ local UI = require('opus.ui')
local Util = require('opus.util')
local tab = UI.Tab {
tabTitle = 'Path',
title = 'Path',
description = 'Set the shell path',
tabClose = true,
[1] = UI.Window {

View File

@ -3,7 +3,7 @@ local UI = require('opus.ui')
local Util = require('opus.util')
local tab = UI.Tab {
tabTitle = 'Requires',
title = 'Requires',
description = 'Require path',
tabClose = true,
entry = UI.TextEntry {

View File

@ -8,7 +8,7 @@ local transform = {
}
return settings and UI.Tab {
tabTitle = 'Settings',
title = 'Settings',
description = 'Computercraft settings',
grid = UI.Grid {
x = 2, y = 2, ex = -2, ey = -2,

View File

@ -39,7 +39,7 @@ if not _colors.backgroundColor then
end
return UI.Tab {
tabTitle = 'Shell',
title = 'Shell',
description = 'Shell options',
grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 2, ex = -17,

View File

@ -17,7 +17,7 @@ for k,v in pairs(UI.colors) do
end
return UI.Tab {
tabTitle = 'Theme',
title = 'Theme',
description = 'Theme colors',
grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 2, ex = -17,

View File

@ -136,4 +136,10 @@
iconExt = "\030 \031 \128\0307\143\131\131\131\131\143\030 \128\010\0307\031 \129\0317\128\0319\136\0309\031b\136\132\0307\0319\132\0317\128\031 \130\010\030 \0317\130\143\0307\128\128\128\128\030 \143\129",
run = "/rom/programs/fun/dj",
},
[ "4dbdd221e957eff27cc47796f3ed8447290f71c7ad8b95e5bd828b31c1858f15" ] = {
title = "Partition",
category = "System",
iconExt = "\30\55\31\55\128\30\48\135\131\139\30\55\128\128\128\10\30\48\31\55\149\31\48\128\30\55\145\30\48\31\56\140\30\55\157\144\144\10\30\55\31\55\128\31\48\139\143\135\31\55\128\31\56\142\133",
run = "Partition",
}
}

View File

@ -134,6 +134,8 @@ local function getNode(dir)
return node
end
fs.getNode = getNode
local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' }

View File

@ -10,6 +10,13 @@ if not fs.exists('usr/autorun') then
fs.makeDir('usr/autorun')
end
-- move the fstab out of config so that the config directory
-- can be remapped to another disk (and for consistency)
if fs.exists('usr/config/fstab') and not fs.exists('usr/etc/fstab') then
fs.move('usr/config/fstab', 'usr/etc/fstab')
end
fs.loadTab('usr/etc/fstab')
-- TODO: Temporary
local upgrade = Util.readTable('usr/config/shell')
if upgrade and (not upgrade.upgraded or upgrade.upgraded ~= 1) then
@ -45,5 +52,3 @@ shell.setPath(table.concat(path, ':'))
--_G.LUA_PATH = config.lua_path
--_G.settings.set('mbs.shell.require_path', config.lua_path)
fs.loadTab('usr/config/fstab')

View File

@ -14,7 +14,7 @@ for name in pairs(Packages:installed()) do
local packageDir = fs.combine('packages', name)
table.insert(appPaths, 1, '/' .. packageDir)
local apiPath = fs.combine(packageDir, 'apis')
local apiPath = fs.combine(packageDir, 'apis') -- TODO: rename dir to 'modules' (someday)
if fs.exists(apiPath) then
fs.mount(fs.combine('rom/modules/main', name), 'linkfs', apiPath)
end

View File

@ -675,7 +675,7 @@ function UI.Window:drawChildren()
end
UI.Window.docs.getDoc = [[getDoc(STRING method)
Gets the documentation for a method.]]
Get the documentation for a method.]]
function UI.Window:getDoc(method)
local m = getmetatable(self) -- get the class for this instance
repeat
@ -743,6 +743,8 @@ function UI.Window:clear(bg, fg)
Canvas.clear(self, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor'))
end
UI.Window.docs.clearLine = [[clearLine(NUMBER y, opt COLOR bg)
Clears the specified line.]]
function UI.Window:clearLine(y, bg)
self:write(1, y, _rep(' ', self.width), bg)
end
@ -760,6 +762,10 @@ function UI.Window:fillArea(x, y, width, height, fillChar, bg, fg)
end
end
UI.Window.docs.write = [[write(NUMBER x, NUMBER y, String text, opt COLOR bg, opt COLOR fg)
Write text to the canvas.
If colors are not specified, the colors from the base class will be used.
If the base class does not have colors defined, colors will be inherited from the parent container.]]
function UI.Window:write(x, y, text, bg, fg)
Canvas.write(self, x, y, text, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor'))
end

View File

@ -1,86 +1,75 @@
local class = require('opus.class')
local UI = require('opus.ui')
local Util = require('opus.util')
local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors
local fs = _G.fs
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',
}
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)
return {
name = row.name,
size = row.size and Util.toBytes(row.size),
}
end,
getRowTextColor = function(_, file)
return file.isDir and 'cyan' or file.isReadOnly and 'pink' or '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 {
x = -9, y = -2,
text = 'Cancel',
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()
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), 'black', 'gray')
self:drawChildren()
end
function UI.FileSelect:enable(path)
self:setPath(path or '')
UI.Window.enable(self)
self:setPath(path or '')
UI.Window.enable(self)
end
function UI.FileSelect:setPath(path)
@ -98,21 +87,21 @@ function UI.FileSelect:eventHandler(event)
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
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
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

@ -33,7 +33,7 @@ function UI.Image:draw()
for x = 1, #line do
local ch = lookup:find(line:sub(x, x))
if ch then
self:write(x, y, ' ', 2 ^ (ch -1))
self:write(x, y, ' ', 2 ^ (ch - 1))
end
end
end
@ -47,6 +47,7 @@ end
function UI.Image.example()
return UI.Image {
backgroundColor = 'primary',
filename = 'test.paint',
}
end

View File

@ -7,7 +7,7 @@ local colors = _G.colors
UI.ScrollBar = class(UI.Window)
UI.ScrollBar.defaults = {
UIElement = 'ScrollBar',
lineChar = '|',
lineChar = '\166',
sliderChar = UI.extChars and '\127' or '#',
upArrowChar = UI.extChars and '\30' or '^',
downArrowChar = UI.extChars and '\31' or 'v',

View File

@ -4,7 +4,7 @@ local UI = require('opus.ui')
UI.Tab = class(UI.Window)
UI.Tab.defaults = {
UIElement = 'Tab',
tabTitle = 'tab',
title = 'tab',
y = 2,
}
@ -14,3 +14,8 @@ function UI.Tab:draw()
end
self:drawChildren()
end
function UI.Tab:enable()
UI.Window.enable(self)
self:emit({ type = 'tab_activate', activated = self })
end

View File

@ -37,9 +37,3 @@ function UI.TabBar:eventHandler(event)
return UI.MenuBar.eventHandler(self, event)
end
function UI.TabBar:selectTab(text)
local menuItem = Util.find(self.children, 'text', text)
if menuItem then
menuItem.selected = true
end
end

View File

@ -14,11 +14,11 @@ end
function UI.Tabs:add(children)
local buttons = { }
for _,child in pairs(children) do
if type(child) == 'table' and child.UIElement and child.tabTitle then
if type(child) == 'table' and child.UIElement and child.UIElement == 'Tab' then
child.y = 2
table.insert(buttons, {
index = child.index,
text = child.tabTitle,
text = child.title,
event = 'tab_select',
tabUid = child.uid,
})
@ -34,7 +34,12 @@ function UI.Tabs:add(children)
end
if self.parent then
local enabled = self.enabled
-- don't enable children upon add
self.enabled = nil
UI.Window.add(self, children)
self.enabled = enabled
end
end
@ -43,7 +48,15 @@ Make to the passed tab active.]]
function UI.Tabs:selectTab(tab)
local menuItem = Util.find(self.tabBar.children, 'tabUid', tab.uid)
if menuItem then
self.tabBar:emit({ type = 'tab_select', button = { uid = menuItem.uid } })
if self.enabled then
self.tabBar:emit({ type = 'tab_select', button = { uid = menuItem.uid } })
else
local previous = Util.find(self.tabBar.children, 'selected', true)
if previous then
previous.selected = false
end
menuItem.selected = true
end
end
end
@ -59,14 +72,20 @@ function UI.Tabs:enable()
self.tabBar:enable()
local menuItem = Util.find(self.tabBar.children, 'selected', true)
self:enableTab(menuItem.tabUid)
end
function UI.Tabs:enableTab(tabUid, hint)
for child in self:eachChild() do
child.transitionHint = nil
if child.uid == menuItem.tabUid then
child:enable()
self:emit({ type = 'tab_activate', activated = child })
elseif child.tabTitle then
child:disable()
child.transitionHint = hint
if child.uid == tabUid then
if not child.enabled then
child:enable()
end
elseif child.UIElement == 'Tab' then
if child.enabled then
child:disable()
end
end
end
end
@ -76,15 +95,7 @@ function UI.Tabs:eventHandler(event)
local tab = self:find(event.tab.tabUid)
local hint = event.current > event.last and 'slideLeft' or 'slideRight'
for child in self:eachChild() do
if child.uid == event.tab.tabUid then
child.transitionHint = hint
child:enable()
elseif child.tabTitle then
child:disable()
end
end
self:emit({ type = 'tab_activate', activated = tab })
self:enableTab(event.tab.tabUid, hint)
tab:draw()
return true
end
@ -94,28 +105,28 @@ function UI.Tabs.example()
return UI.Tabs {
tab1 = UI.Tab {
index = 1,
tabTitle = 'tab1',
title = 'tab1',
entry = UI.TextEntry { y = 3, shadowText = 'text' },
},
tab2 = UI.Tab {
index = 2,
tabTitle = 'tab2',
title = 'tab2',
subtabs = UI.Tabs {
x = 3, y = 2, ex = -3, ey = -2,
tab1 = UI.Tab {
index = 1,
tabTitle = 'tab4',
title = 'tab4',
entry = UI.TextEntry { y = 3, shadowText = 'text' },
},
tab3 = UI.Tab {
index = 2,
tabTitle = 'tab5',
title = 'tab5',
},
},
},
tab3 = UI.Tab {
index = 3,
tabTitle = 'tab3',
title = 'tab3',
},
enable = function(self)
UI.Tabs.enable(self)

View File

@ -8,11 +8,12 @@ UI.TextArea.defaults = {
value = '',
showScrollBar = true,
}
function UI.TextArea:setText(text)
function UI.TextArea:setValue(text)
self:reset()
self.value = text
self:draw()
end
UI.TextArea.setText = UI.TextArea.setValue -- deprecate
function UI.TextArea.focus()
-- allow keyboard scrolling

View File

@ -15,52 +15,50 @@ function UI.Wizard:postInit()
}
self.previousButton = UI.Button {
x = -18, y = -1,
text = '< Back',
text = '\17 Back',
event = 'previousView',
}
self.nextButton = UI.Button {
x = -9, y = -1,
text = 'Next >',
text = 'Next \16',
event = 'nextView',
}
Util.merge(self, self.pages)
end
function UI.Wizard:add(pages)
Util.merge(self.pages, pages)
Util.merge(self, pages)
for _, child in pairs(self.pages) do
child.ey = child.ey or -2
end
if self.parent then
self:initChildren()
function UI.Wizard:getPages()
local t = { }
for child in self:eachChild() do
if type(child) == 'table' and child.UIElement == 'WizardPage' then
table.insert(t, child)
end
end
return t
end
function UI.Wizard:getPage(index)
return Util.find(self.pages, 'index', index)
local pages = self:getPages()
return Util.find(pages, 'index', index)
end
function UI.Wizard:enable(...)
function UI.Wizard:enable()
self.enabled = true
self.index = 1
local initial = self:getPage(1)
for child in self:eachChild() do
if child == initial or not child.index then
child:enable(...)
else
if child.UIElement ~= 'WizardPage' then
child:enable()
elseif child.enabled then
child:disable()
end
end
local initial = self:getPage(1)
self:emit({ type = 'enable_view', next = initial })
end
function UI.Wizard:isViewValid()
local currentView = self:getPage(self.index)
return not currentView.validate and true or currentView:validate()
return currentView:validate()
end
function UI.Wizard:eventHandler(event)
@ -107,7 +105,7 @@ function UI.Wizard:eventHandler(event)
end
if self:getPage(self.index + 1) then
self.nextButton.text = 'Next >'
self.nextButton.text = 'Next \16'
self.nextButton.event = 'nextView'
else
self.nextButton.text = 'Accept'
@ -124,29 +122,27 @@ end
function UI.Wizard.example()
return UI.Wizard {
ey = -2,
pages = {
splash = UI.WizardPage {
index = 1,
intro = UI.TextArea {
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = 'sample text',
},
splash = UI.WizardPage {
index = 1,
intro = UI.TextArea {
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = 'sample text',
},
label = UI.WizardPage {
index = 2,
intro = UI.TextArea {
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = 'sample more text',
},
},
label = UI.WizardPage {
index = 2,
intro = UI.TextArea {
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = 'sample more text',
},
password = UI.WizardPage {
index = 3,
text = UI.TextEntry {
x = 12, ex = -3, y = 2,
shadowText = 'tet',
},
},
password = UI.WizardPage {
index = 3,
text = UI.TextEntry {
x = 12, ex = -3, y = 2,
shadowText = 'tet',
},
},
}

View File

@ -6,3 +6,6 @@ UI.WizardPage.defaults = {
UIElement = 'WizardPage',
ey = -2,
}
function UI.WizardPage.validate()
return true
end