diff --git a/sys/apps/Files.lua b/sys/apps/Files.lua index b39ddd9..8f3144e 100644 --- a/sys/apps/Files.lua +++ b/sys/apps/Files.lua @@ -91,7 +91,6 @@ local Browser = UI.Page { }, notification = UI.Notification { }, associations = UI.SlideOut { - backgroundColor = colors.cyan, menuBar = UI.MenuBar { buttons = { { text = 'Save', event = 'save' }, @@ -99,7 +98,7 @@ local Browser = UI.Page { }, }, grid = UI.ScrollingGrid { - x = 2, ex = -6, y = 3, ey = -5, + x = 2, ex = -6, y = 3, ey = -8, columns = { { heading = 'Extension', key = 'name' }, { heading = 'Program', key = 'value' }, @@ -114,8 +113,11 @@ 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 = -3, ey = -2, + x = 3, y = -5, ex = -7, ey = -3, margin = 1, manualControls = true, [1] = UI.TextEntry { diff --git a/sys/apps/Lua.lua b/sys/apps/Lua.lua index 2de6195..3ddcc17 100644 --- a/sys/apps/Lua.lua +++ b/sys/apps/Lua.lua @@ -59,7 +59,6 @@ local page = UI.Page { [2] = UI.Tab { tabTitle = 'Output', output = UI.Embedded { - visible = true, maxScroll = 1000, backgroundColor = colors.black, }, diff --git a/sys/apps/Network.lua b/sys/apps/Network.lua index 6ae002a..d8db622 100644 --- a/sys/apps/Network.lua +++ b/sys/apps/Network.lua @@ -74,7 +74,6 @@ local page = UI.Page { }, }, help = UI.SlideOut { - backgroundColor = colors.cyan, x = 5, ex = -5, height = 8, y = -8, titleBar = UI.TitleBar { title = 'Network Help', @@ -82,7 +81,6 @@ local page = UI.Page { }, text = UI.TextArea { x = 2, y = 2, - backgroundColor = colors.cyan, value = [[ In order to connect to another computer: diff --git a/sys/apps/Overview.lua b/sys/apps/Overview.lua index b4b524f..41c3ac2 100644 --- a/sys/apps/Overview.lua +++ b/sys/apps/Overview.lua @@ -26,6 +26,9 @@ 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() @@ -65,6 +68,7 @@ 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) @@ -89,6 +93,7 @@ function UI.VerticalTabBar:setParent() c.ox, c.oy = c.x, c.y c.ow = 8 c.width = 8 + c:reposition(c.x, c.y, c.width, c.height) end end @@ -114,7 +119,6 @@ local page = UI.Page { }, editor = UI.SlideOut { y = -12, height = 12, - backgroundColor = colors.cyan, titleBar = UI.TitleBar { title = 'Edit Application', event = 'slide_hide', @@ -122,7 +126,7 @@ local page = UI.Page { form = UI.Form { y = 2, ey = -2, [1] = UI.TextEntry { - formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title', + formLabel = 'Title', formKey = 'title', limit = 11, width = 13, help = 'Application title', required = true, }, [2] = UI.TextEntry { @@ -130,21 +134,24 @@ local page = UI.Page { required = true, }, [3] = UI.TextEntry { - formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application', + formLabel = 'Category', formKey = 'category', limit = 6, width = 8, help = 'Category of application', required = true, }, - iconFile = UI.TextEntry { - x = 11, ex = -12, y = 7, - limit = 128, help = 'Path to icon file', - shadowText = 'Path to icon file', + editIcon = UI.Button { + x = 11, y = 9, + text = 'Edit', event = 'editIcon', help = 'Edit icon file', }, loadIcon = UI.Button { - x = 11, y = 9, + x = 18, y = 9, text = 'Load', event = 'loadIcon', help = 'Load icon file', }, + info = UI.TextArea { + x = 11, y = 6, height = 2, + value = 'magenta is transparent\n3 high - max width is 8' + }, image = UI.NftImage { backgroundColor = colors.black, - y = 7, x = 2, height = 3, width = 8, + y = 6, x = 2, height = 3, width = 8, }, }, notification = UI.Notification(), @@ -226,6 +233,7 @@ local function loadApplications() page:add { tabBar = UI.VerticalTabBar { buttons = buttons, + selectedBackgroundColor = UI.colors.primary, }, } @@ -308,14 +316,12 @@ function page.container:setCategory(categoryName, animate) 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, + backgroundColor = self:getProperty('backgroundColor'), backgroundFocusColor = colors.gray, textColor = colors.white, textFocusColor = colors.white, @@ -333,7 +339,8 @@ function page.container:setCategory(categoryName, animate) local col, row = gutter, 2 local count = #self.children - local r = math.random(1, 5) + local r = math.random(1, 7) + local frames = 5 -- reposition all children for k,child in ipairs(self.children) do if r == 1 then @@ -355,14 +362,22 @@ 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(6, child, { x = col, y = row }, 'linear') + child.tween = Tween.new(frames, child, { x = col, y = row }, 'inQuad') if not animate then child.x = col child.y = row end + self:setViewHeight(row + 3) + if k < count then col = col + child.width if col + self.children[k + 1].width + gutter - 2 > self.width then @@ -377,15 +392,13 @@ 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.x = math.floor(child.x) - child.y = math.floor(child.y) - child:draw() + child:move(math.floor(child.x), math.floor(child.y)) end + --os.sleep(.5) i = i + 1 - return i < 7 + return i <= frames end end self:addTransition(transition) @@ -512,6 +525,30 @@ 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() @@ -520,27 +557,20 @@ function page.editor:eventHandler(event) self.statusBar:setStatus(event.focused.help or '') self.statusBar:draw() + 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 == 'loadIcon' then - 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) + local success, filename = shell.run('fileui.lua') + self.parent:dirty(true) + if success and filename then + self:loadImage(filename) end elseif event.type == 'form_invalid' then @@ -590,5 +620,4 @@ end) loadApplications() UI:setPage(page) - -UI:pullEvents() +UI:start() diff --git a/sys/apps/PackageManager.lua b/sys/apps/PackageManager.lua index 3a3dd8b..6960066 100644 --- a/sys/apps/PackageManager.lua +++ b/sys/apps/PackageManager.lua @@ -44,7 +44,6 @@ local page = UI.Page { marginRight = 0, marginLeft = 0, }, action = UI.SlideOut { - backgroundColor = colors.cyan, titleBar = UI.TitleBar { event = 'hide-action', }, diff --git a/sys/apps/Sniff.lua b/sys/apps/Sniff.lua index 3b4ca9c..62d1a1e 100644 --- a/sys/apps/Sniff.lua +++ b/sys/apps/Sniff.lua @@ -48,6 +48,7 @@ local page = UI.Page { y = 2, filterTab = UI.Tab { tabTitle = 'Filter', + noFill = true, filterGridText = UI.Text { x = 2, y = 2, value = 'ID filter', @@ -130,7 +131,6 @@ local page = UI.Page { title = 'Packet Information', event = 'packet_close', }, - backgroundColor = colors.cyan, accelerators = { ['backspace'] = 'packet_close', ['left'] = 'prev_packet', diff --git a/sys/apps/System.lua b/sys/apps/System.lua index b18200d..5ee1efb 100644 --- a/sys/apps/System.lua +++ b/sys/apps/System.lua @@ -11,7 +11,7 @@ local systemPage = UI.Page { settings = UI.Tab { tabTitle = 'Category', grid = UI.ScrollingGrid { - y = 2, + x = 2, y = 2, ex = -2, ey = -2, columns = { { heading = 'Name', key = 'name' }, { heading = 'Description', key = 'description' }, @@ -35,7 +35,7 @@ function systemPage.tabs.settings:eventHandler(event) tab:disable() end systemPage.tabs:selectTab(tab) - self.parent:draw() + --self.parent:draw() return true end end diff --git a/sys/apps/Tasks.lua b/sys/apps/Tasks.lua index cde082a..62b8401 100644 --- a/sys/apps/Tasks.lua +++ b/sys/apps/Tasks.lua @@ -3,6 +3,7 @@ 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', ...) @@ -21,7 +22,7 @@ local page = UI.Page { { heading = 'Status', key = 'status' }, { heading = 'Time', key = 'timestamp' }, }, - values = kernel.routines, + values = tasks, sortColumn = 'uid', autospace = true, getDisplayValues = function (_, row) diff --git a/sys/apps/fileui.lua b/sys/apps/fileui.lua new file mode 100644 index 0000000..0269b9f --- /dev/null +++ b/sys/apps/fileui.lua @@ -0,0 +1,138 @@ +local UI = require('opus.ui') +local Util = require('opus.util') + +local colors = _G.colors +local fs = _G.fs +local shell = _ENV.shell + +local selected + +-- fileui [--path=path] [--exec=filename] + +local page = UI.Page { + title = 'Select File', + -- x = 3, ex = -3, y = 2, ey = -2, + grid = UI.ScrollingGrid { + x = 2, y = 2, ex = -2, ey = -4, + path = '', + 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, + }, + path = UI.TextEntry { + x = 2, + y = -2, + ex = -11, + limit = 256, + accelerators = { + enter = 'path_enter', + } + }, + cancel = UI.Button { + text = 'Cancel', + x = -9, + y = -2, + event = 'cancel', + }, + draw = function(self) + self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray) + self:drawChildren() + end, +} + +function page:enable(path) + self:setPath(path or shell.dir()) + UI.Page.enable(self) +end + +function page: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 page: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 + selected = self.path.value + UI:quit() + end + + elseif event.type == 'path_enter' then + if fs.isDir(self.path.value) then + self:setPath(self.path.value) + self.grid:draw() + self.path:draw() + else + selected = self.path.value + UI:quit() + end + + elseif event.type == 'cancel' then + UI:quit() + else + return UI.Page.eventHandler(self, event) + end + return true +end + +local _, args = Util.parse(...) + +UI:setPage(page, args.path) +UI:start() +UI.term:setCursorBlink(false) + +if args.exec and selected then + shell.openForegroundTab(string.format('%s %s', args.exec, selected)) + return +end + +--print('selected: ' .. tostring(selected)) +return selected diff --git a/sys/apps/netdaemon.lua b/sys/apps/netdaemon.lua index 1d3fd66..8ffca25 100644 --- a/sys/apps/netdaemon.lua +++ b/sys/apps/netdaemon.lua @@ -1,5 +1,3 @@ -_G.requireInjector(_ENV) - local Event = require('opus.event') local Util = require('opus.util') diff --git a/sys/apps/shell.lua b/sys/apps/shell.lua index 6124a3d..7036afa 100644 --- a/sys/apps/shell.lua +++ b/sys/apps/shell.lua @@ -66,11 +66,11 @@ local function run(env, ...) _ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)')) end - if isUrl then - tProgramStack[#tProgramStack + 1] = path -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$") - else - tProgramStack[#tProgramStack + 1] = path - end + tProgramStack[#tProgramStack + 1] = { + path = path, -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$") + env = env, + args = args, + } env[ "arg" ] = { [0] = path, table.unpack(args) } local r = { fn(table.unpack(args)) } @@ -278,6 +278,10 @@ function shell.getCompletionInfo() end function shell.getRunningProgram() + return tProgramStack[#tProgramStack] and tProgramStack[#tProgramStack].path +end + +function shell.getRunningInfo() return tProgramStack[#tProgramStack] end diff --git a/sys/apps/system/aliases.lua b/sys/apps/system/aliases.lua index 6b04c5c..2d122bb 100644 --- a/sys/apps/system/aliases.lua +++ b/sys/apps/system/aliases.lua @@ -20,7 +20,7 @@ local aliasTab = UI.Tab { }, }, grid = UI.Grid { - y = 5, + x = 2, y = 5, ex = -2, ey = -2, sortColumn = 'alias', columns = { { heading = 'Alias', key = 'alias' }, diff --git a/sys/apps/system/alternate.lua b/sys/apps/system/alternate.lua index 6894428..67c455d 100644 --- a/sys/apps/system/alternate.lua +++ b/sys/apps/system/alternate.lua @@ -22,20 +22,19 @@ local tab = UI.Tab { disableHeader = true, columns = { { key = 'file' }, - } + }, + getRowTextColor = function(self, row) + if row == self.values[1] then + return colors.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 = { } diff --git a/sys/apps/system/cloud.lua b/sys/apps/system/cloud.lua index 66078f6..3bd9ad3 100644 --- a/sys/apps/system/cloud.lua +++ b/sys/apps/system/cloud.lua @@ -4,16 +4,17 @@ 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 = 2, + x = 3, ex = -3, y = 3, limit = 32, value = config.key, shadowText = 'Cloud key', @@ -22,13 +23,14 @@ if _G.http.websocket then }, }, button = UI.Button { - x = 3, y = 4, - text = 'Update', + x = -8, ex = -2, y = -2, + text = 'Apply', event = 'update_key', }, labelText = UI.TextArea { - x = 3, ex = -3, y = 6, + x = 2, ex = -2, y = 6, ey = -4, textColor = colors.yellow, + backgroundColor = colors.black, 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. diff --git a/sys/apps/system/diskusage.lua b/sys/apps/system/diskusage.lua index 98b8516..c0db595 100644 --- a/sys/apps/system/diskusage.lua +++ b/sys/apps/system/diskusage.lua @@ -20,8 +20,8 @@ local tab = UI.Tab { description = 'Visualise HDD and disks usage', drives = UI.ScrollingGrid { - x = 2, y = 1, - ex = '47%', ey = -7, + x = 2, y = 2, + ex = '47%', ey = -8, 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 = -4, + ex = -2, ey = -8, disableHeader = true, unfocusedBackgroundSelectedColor = colors.black, inactive = true, @@ -40,18 +40,23 @@ 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 = -2, - ex = -2, + x = 11, y = -3, + ex = -3, }, percentage = UI.Text { - x = 11, y = -3, - ex = '47%', - align = 'center', + y = -4, width = 5, + x = 12, + --align = 'center', + backgroundColor = colors.black, }, icon = UI.NftImage { - x = 2, y = -5, + x = 2, y = -6, ey = -2, + backgroundColor = colors.black, image = NFT.parse(NftImages.blank) }, } diff --git a/sys/apps/system/kiosk.lua b/sys/apps/system/kiosk.lua index becee72..29cb351 100644 --- a/sys/apps/system/kiosk.lua +++ b/sys/apps/system/kiosk.lua @@ -8,7 +8,7 @@ local tab = UI.Tab { tabTitle = 'Kiosk', description = 'Kiosk options', form = UI.Form { - x = 2, ex = -2, + x = 2, y = 2, ex = -2, ey = 5, manualControls = true, monitor = UI.Chooser { formLabel = 'Monitor', formKey = 'monitor', @@ -22,11 +22,12 @@ local tab = 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' }, } diff --git a/sys/apps/system/label.lua b/sys/apps/system/label.lua index 0dc77a7..11ee539 100644 --- a/sys/apps/system/label.lua +++ b/sys/apps/system/label.lua @@ -8,19 +8,22 @@ local labelTab = UI.Tab { tabTitle = 'Label', description = 'Set the computer label', labelText = UI.Text { - x = 3, y = 2, + x = 3, y = 3, value = 'Label' }, label = UI.TextEntry { - x = 9, y = 2, ex = -4, + x = 9, y = 3, 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 { - y = 3, + x = 2, y = 6, ex = -2, ey = -2, values = { { name = '', value = '' }, { name = 'CC version', value = Util.getVersion() }, @@ -30,10 +33,11 @@ local labelTab = 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' }, + { key = 'value', textColor = colors.yellow }, }, }, } diff --git a/sys/apps/system/launcher.lua b/sys/apps/system/launcher.lua index 6bd290c..8f5ac13 100644 --- a/sys/apps/system/launcher.lua +++ b/sys/apps/system/launcher.lua @@ -9,12 +9,15 @@ 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 = 2, + x = 3, y = 3, value = 'Launcher', }, launcher = UI.Chooser { - x = 13, y = 2, width = 12, + x = 13, y = 3, width = 12, choices = { { name = 'Overview', value = 'sys/apps/Overview.lua' }, { name = 'Shell', value = 'sys/apps/ShellLauncher.lua' }, @@ -22,17 +25,18 @@ local tab = UI.Tab { }, }, custom = UI.TextEntry { - x = 13, ex = -3, y = 3, + x = 13, ex = -3, y = 4, limit = 128, shadowText = 'File name', }, button = UI.Button { - x = 3, y = 5, - text = 'Update', + x = -8, ex = -2, y = -2, + text = 'Apply', event = 'update', }, labelText = UI.TextArea { - x = 3, ex = -3, y = 7, + x = 2, ex = -2, y = 7, ey = -4, + backgroundColor = colors.black, textColor = colors.yellow, value = 'Choose an application launcher', }, diff --git a/sys/apps/system/network.lua b/sys/apps/system/network.lua index 414c080..adaf239 100644 --- a/sys/apps/system/network.lua +++ b/sys/apps/system/network.lua @@ -2,24 +2,29 @@ local Ansi = require('opus.ansi') local Config = require('opus.config') local UI = require('opus.ui') +local colors = _G.colors local device = _G.device local tab = UI.Tab { tabTitle = 'Network', description = 'Networking options', info = UI.TextArea { - x = 3, y = 4, + x = 2, y = 6, ex = -2, ey = -2, + backgroundColor = colors.black, 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 = 2, + x = 3, y = 3, value = 'Modem', }, modem = UI.Chooser { - x = 10, ex = -3, y = 2, + x = 10, ex = -3, y = 3, nochoice = 'auto', }, } diff --git a/sys/apps/system/password.lua b/sys/apps/system/password.lua index d822996..90e1367 100644 --- a/sys/apps/system/password.lua +++ b/sys/apps/system/password.lua @@ -7,6 +7,9 @@ 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, @@ -17,12 +20,13 @@ local passwordTab = UI.Tab { }, }, button = UI.Button { - x = 3, y = 5, - text = 'Update', + x = -8, ex = -2, y = -2, + text = 'Apply', event = 'update_password', }, info = UI.TextArea { - x = 3, ex = -3, y = 7, + x = 2, ex = -2, y = 6, ey = -4, + backgroundColor = colors.black, textColor = colors.yellow, inactive = true, value = 'Add a password to enable other computers to connect to this one.', diff --git a/sys/apps/system/path.lua b/sys/apps/system/path.lua index 33249dd..eef9f16 100644 --- a/sys/apps/system/path.lua +++ b/sys/apps/system/path.lua @@ -6,8 +6,11 @@ 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 = 2, y = 2, ex = -2, + x = 3, y = 3, ex = -3, limit = 256, shadowText = 'enter new path', accelerators = { @@ -16,7 +19,7 @@ local tab = UI.Tab { help = 'add a new path', }, grid = UI.Grid { - y = 4, ey = -3, + x = 2, y = 6, ex = -2, ey = -3, disableHeader = true, columns = { { key = 'value' } }, autospace = true, diff --git a/sys/apps/system/settings.lua b/sys/apps/system/settings.lua index 8e071ac..16cdbb0 100644 --- a/sys/apps/system/settings.lua +++ b/sys/apps/system/settings.lua @@ -7,7 +7,7 @@ if settings then tabTitle = 'Settings', description = 'Computercraft configurable settings', grid = UI.Grid { - y = 2, + x = 2, y = 2, ex = -2, ey = -2, autospace = true, sortColumn = 'name', columns = { diff --git a/sys/apps/system/shell.lua b/sys/apps/system/shell.lua index 13dfda6..7f0751d 100644 --- a/sys/apps/system/shell.lua +++ b/sys/apps/system/shell.lua @@ -42,25 +42,23 @@ local tab = UI.Tab { tabTitle = 'Shell', description = 'Shell options', grid1 = UI.ScrollingGrid { - y = 2, ey = -10, x = 3, ex = -16, + 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 = -3, + y = 2, ey = -10, x = -14, ex = -2, disableHeader = true, columns = { { key = 'name' } }, values = allColors, sortColumn = 'name', }, - directoryLabel = UI.Text { - x = 2, y = -2, - value = 'Display directory', - }, directory = UI.Checkbox { - x = 20, y = -2, + x = 2, y = -2, + labelBackgroundColor = colors.black, + label = 'Directory', value = config.displayDirectory }, reset = UI.Button { @@ -74,7 +72,7 @@ local tab = UI.Tab { event = 'update', }, display = UI.Window { - x = 3, ex = -3, y = -8, height = 5, + x = 2, ex = -2, y = -8, height = 5, }, } diff --git a/sys/autorun/upgraded.lua b/sys/autorun/upgraded.lua index 141bf1d..9392cb3 100644 --- a/sys/autorun/upgraded.lua +++ b/sys/autorun/upgraded.lua @@ -21,4 +21,4 @@ deleteIfExists('sys/autorun/apps.lua') deleteIfExists('sys/init/6.tl3.lua') -- remove this file -deleteIfExists('sys/autorun/upgraded.lua') \ No newline at end of file +--deleteIfExists('sys/autorun/upgraded.lua') \ No newline at end of file diff --git a/sys/init/1.device.lua b/sys/init/1.device.lua index b61a767..024a08f 100644 --- a/sys/init/1.device.lua +++ b/sys/init/1.device.lua @@ -1,5 +1,3 @@ -_G.requireInjector(_ENV) - local Peripheral = require('opus.peripheral') _G.device = Peripheral.getList() diff --git a/sys/init/2.vfs.lua b/sys/init/2.vfs.lua index 5b3c5f9..69eb387 100644 --- a/sys/init/2.vfs.lua +++ b/sys/init/2.vfs.lua @@ -4,11 +4,8 @@ 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 = { } @@ -23,7 +20,6 @@ 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) @@ -265,7 +261,6 @@ local function getfstype(fstype) end function fs.mount(path, fstype, ...) - local vfs = getfstype(fstype) if not vfs then error('Invalid file system type') diff --git a/sys/init/5.network.lua b/sys/init/5.network.lua index b02937a..d526b62 100644 --- a/sys/init/5.network.lua +++ b/sys/init/5.network.lua @@ -1,5 +1,3 @@ -_G.requireInjector(_ENV) - local Config = require('opus.config') local device = _G.device diff --git a/sys/init/7.multishell.lua b/sys/init/7.multishell.lua index d8b722a..61a1c83 100644 --- a/sys/init/7.multishell.lua +++ b/sys/init/7.multishell.lua @@ -1,5 +1,3 @@ -_G.requireInjector(_ENV) - local Config = require('opus.config') local trace = require('opus.trace') local Util = require('opus.util') @@ -334,16 +332,12 @@ 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({ diff --git a/sys/kernel.lua b/sys/kernel.lua index b2ef8a8..546300d 100644 --- a/sys/kernel.lua +++ b/sys/kernel.lua @@ -1,5 +1,3 @@ -_G.requireInjector(_ENV) - local Array = require('opus.array') local Terminal = require('opus.terminal') local Util = require('opus.util') diff --git a/sys/modules/opus/array.lua b/sys/modules/opus/array.lua index f0aa73f..dd5e8d9 100644 --- a/sys/modules/opus/array.lua +++ b/sys/modules/opus/array.lua @@ -14,7 +14,7 @@ function Array.removeByValue(t, e) for k,v in pairs(t) do if v == e then table.remove(t, k) - break + return e end end end diff --git a/sys/modules/opus/input.lua b/sys/modules/opus/input.lua index 46b58fe..b6a9b8f 100644 --- a/sys/modules/opus/input.lua +++ b/sys/modules/opus/input.lua @@ -50,18 +50,20 @@ function input:toCode(ch, code) table.insert(result, 'alt') end - 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') + 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 table.insert(result, ch) end - elseif not code or not modifiers[code] then - table.insert(result, ch) end return table.concat(result, '-') @@ -118,6 +120,7 @@ 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, @@ -132,6 +135,8 @@ 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 diff --git a/sys/modules/opus/nft.lua b/sys/modules/opus/nft.lua index 4fd8d02..a12834d 100644 --- a/sys/modules/opus/nft.lua +++ b/sys/modules/opus/nft.lua @@ -1,16 +1,19 @@ 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 tColourLookup = { } +local hexToColor = { } for n = 1, 16 do - tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1) + hexToColor[string.sub("0123456789abcdef", n, n)] = 2 ^ (n - 1) end +local colorToHex = Util.transpose(hexToColor) local function getColourOf(hex) - return tColourLookup[hex:byte()] + return hexToColor[hex] end function NFT.parse(imageText) @@ -62,8 +65,22 @@ function NFT.parse(imageText) return image end -function NFT.load(path) +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') @@ -71,4 +88,35 @@ 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 diff --git a/sys/modules/opus/terminal.lua b/sys/modules/opus/terminal.lua index cb7df22..a64530a 100644 --- a/sys/modules/opus/terminal.lua +++ b/sys/modules/opus/terminal.lua @@ -38,7 +38,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) local blink = false local bg, fg = parent.getBackgroundColor(), parent.getTextColor() - local canvas = Canvas({ + win.canvas = Canvas({ x = sx, y = sy, width = w, @@ -47,50 +47,53 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) offy = 0, }) - win.canvas = canvas - local function update() if isVisible then - canvas:render(parent) + win.canvas:render(parent) win.setCursorPos(cx, cy) end end local function scrollTo(y) y = math.max(0, y) - y = math.min(#canvas.lines - canvas.height, y) + y = math.min(#win.canvas.lines - win.canvas.height, y) - if y ~= canvas.offy then - canvas.offy = y - canvas:dirty() + if y ~= win.canvas.offy then + win.canvas.offy = y + win.canvas:dirty() update() end end function win.write(str) str = tostring(str) or '' - canvas:write(cx, cy + canvas.offy, str, bg, fg) + win.canvas:write(cx, cy + win.canvas.offy, str, bg, fg) win.setCursorPos(cx + #str, cy) update() end function win.blit(str, fg, bg) - canvas:blit(cx, cy + canvas.offy, str, bg, fg) + win.canvas:blit(cx, cy + win.canvas.offy, str, bg, fg) win.setCursorPos(cx + #str, cy) update() end function win.clear() - canvas.offy = 0 - for i = #canvas.lines, canvas.height + 1, -1 do - canvas.lines[i] = nil + win.canvas.offy = 0 + for i = #win.canvas.lines, win.canvas.height + 1, -1 do + win.canvas.lines[i] = nil end - canvas:clear(bg, fg) + win.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() - canvas:clearLine(cy + canvas.offy, bg, fg) + win.canvas:clearLine(cy + win.canvas.offy, bg, fg) win.setCursorPos(cx, cy) update() end @@ -102,10 +105,14 @@ 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 + canvas.x - 1, cy + canvas.y - 1) + parent.setCursorPos(cx + win.canvas.x - 1, cy + win.canvas.y - 1) end end + function win.getCursorBlink() + return blink + end + function win.setCursorBlink(b) blink = b if isVisible then @@ -114,7 +121,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) end function win.isColor() - return canvas.isColor + return win.canvas.isColor end win.isColour = win.isColor @@ -144,22 +151,22 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) win.setBackgroundColour = win.setBackgroundColor function win.getSize() - return canvas.width, canvas.height + return win.canvas.width, win.canvas.height end function win.scroll(n) n = n or 1 if n > 0 then - local lines = #canvas.lines + local lines = #win.canvas.lines for i = 1, n do - canvas.lines[lines + i] = { } - canvas:clearLine(lines + i, bg, fg) + win.canvas.lines[lines + i] = { } + win.canvas:clearLine(lines + i, bg, fg) end - while #canvas.lines > maxScroll do - table.remove(canvas.lines, 1) + while #win.canvas.lines > maxScroll do + table.remove(win.canvas.lines, 1) end - scrollTo(#canvas.lines) - canvas:dirty() + scrollTo(#win.canvas.lines) + win.canvas:dirty() update() end end @@ -178,7 +185,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) if visible ~= isVisible then isVisible = visible if isVisible then - canvas:dirty() + win.canvas:dirty() update() end end @@ -186,7 +193,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) function win.redraw() if isVisible then - canvas:dirty() + win.canvas:dirty() update() end end @@ -200,21 +207,21 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) end function win.getPosition() - return canvas.x, canvas.y + return win.canvas.x, win.canvas.y end function win.reposition(x, y, width, height) - canvas.x, canvas.y = x, y - canvas:resize(width or canvas.width, height or canvas.height) + win.canvas.x, win.canvas.y = x, y + win.canvas:resize(width or win.canvas.width, height or win.canvas.height) end --[[ Additional methods ]]-- function win.scrollDown() - scrollTo(canvas.offy + 1) + scrollTo(win.canvas.offy + 1) end function win.scrollUp() - scrollTo(canvas.offy - 1) + scrollTo(win.canvas.offy - 1) end function win.scrollTop() @@ -222,7 +229,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) end function win.scrollBottom() - scrollTo(#canvas.lines) + scrollTo(#win.canvas.lines) end function win.setMaxScroll(ms) @@ -230,37 +237,35 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) end function win.getCanvas() - return canvas + return win.canvas end function win.getParent() return parent end - canvas:clear() + win.canvas:clear() return win end -- get windows contents -function Terminal.getContents(win, parent) - local oblit, oscp = parent.blit, parent.setCursorPos - local lines = { } +function Terminal.getContents(win) + if not win.getLine then + error('window is required') + end - parent.blit = function(text, fg, bg) - lines[#lines + 1] = { + local lines = { } + local _, h = win.getSize() + + for i = 1, h do + local text, fg, bg = win.getLine(i) + lines[i] = { 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 diff --git a/sys/modules/opus/ui.lua b/sys/modules/opus/ui.lua index c627005..a2851aa 100644 --- a/sys/modules/opus/ui.lua +++ b/sys/modules/opus/ui.lua @@ -1,8 +1,10 @@ +local Array = require('opus.array') local class = require('opus.class') local Event = require('opus.event') local Input = require('opus.input') local Transition = require('opus.ui.transition') local Util = require('opus.util') +local Canvas = require('opus.ui.canvas') local _rep = string.rep local _sub = string.sub @@ -44,8 +46,7 @@ function Manager:init() local currentPage = self:getActivePage() if ie and currentPage then local target = currentPage.focused or currentPage - self:inputEvent(target, - { type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target, ie = ie }) + target:emit({ type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target, ie = ie }) currentPage:sync() end end @@ -81,8 +82,7 @@ function Manager:init() } -- revisit - should send out scroll_up and scroll_down events -- let the element convert them to up / down - self:inputEvent(event.element, - { type = 'key', key = directions[direction] }) + event.element:emit({ type = 'key', key = directions[direction] }) currentPage:sync() end end, @@ -92,7 +92,7 @@ function Manager:init() if dev and dev.currentPage then Input:translate('mouse_click', 1, x, y) local ie = Input:translate('mouse_up', 1, x, y) - self:click(dev.currentPage, ie.code, 1, x, y) + self:click(dev.currentPage, ie) end end, @@ -107,7 +107,7 @@ function Manager:init() currentPage:setFocus(event.element) currentPage:sync() end - self:click(currentPage, ie.code, button, x, y) + self:click(currentPage, ie) end end end, @@ -125,7 +125,7 @@ function Manager:init() elseif ie and currentPage then if not currentPage.parent.device.side then - self:click(currentPage, ie.code, button, x, y) + self:click(currentPage, ie) end end end, @@ -135,7 +135,7 @@ function Manager:init() local currentPage = self:getActivePage() if ie and currentPage then - self:click(currentPage, ie.code, button, x, y) + self:click(currentPage, ie) end end, @@ -247,30 +247,44 @@ function Manager:emitEvent(event) end end -function Manager:inputEvent(parent, event) -- deprecate ? - return parent and parent:emit(event) -end +function Manager:click(target, ie) + local clickEvent -function Manager:click(target, code, button, x, y) - local clickEvent = target:pointToChild(x, y) + if ie.code == 'mouse_drag' then + clickEvent = { + element = self.lastClicked, + x = ie.x, + y = ie.y, -- this is not correct (should be relative to element) + dx = ie.dx, + dy = ie.dy, + } + else + clickEvent = target:pointToChild(ie.x, ie.y) + end - if code == 'mouse_doubleclick' then - if self.doubleClickElement ~= clickEvent.element then + -- hack for dropdown menus + if ie.code == 'mouse_click' and not clickEvent.element.focus then + self:emitEvent({ type = 'mouse_out' }) + end + + if ie.code == 'mouse_doubleclick' then + if self.lastClicked ~= clickEvent.element then return end else - self.doubleClickElement = clickEvent.element + self.lastClicked = clickEvent.element end - clickEvent.button = button - clickEvent.type = code - clickEvent.key = code - clickEvent.ie = { code = code, x = clickEvent.x, y = clickEvent.y } + clickEvent.button = ie.button + clickEvent.type = ie.code + clickEvent.key = ie.code + clickEvent.ie = { code = ie.code, x = clickEvent.x, y = clickEvent.y } + clickEvent.raw = ie if clickEvent.element.focus then target:setFocus(clickEvent.element) end - self:inputEvent(clickEvent.element, clickEvent) + clickEvent.element:emit(clickEvent) target:sync() end @@ -310,7 +324,6 @@ end function Manager:setActivePage(page) page.parent.currentPage = page - page.parent.canvas = page.canvas end function Manager:setPage(pageOrName, ...) @@ -388,6 +401,12 @@ function Manager:pullEvents(...) end end +Manager.colors = { + primary = colors.cyan, + secondary = colors.blue, + tertiary = colors.blue, +} + Manager.exitPullEvents = Event.exitPullEvents Manager.quit = Event.exitPullEvents Manager.start = Manager.pullEvents @@ -395,7 +414,7 @@ Manager.start = Manager.pullEvents local UI = Manager() --[[-- Basic drawable area --]]-- -UI.Window = class() +UI.Window = class(Canvas) UI.Window.uid = 1 UI.Window.docs = { } UI.Window.defaults = { @@ -413,7 +432,9 @@ function UI.Window:init(args) local defaults = args local m = getmetatable(self) -- get the class for this instance repeat - defaults = UI:getDefaults(m, defaults) + if m.disable then + defaults = UI:getDefaults(m, defaults) + end m = m._base until not m UI:mergeProperties(self, defaults) @@ -531,6 +552,8 @@ function UI.Window:layout() if not self.height then self.height = self.parent.height - self.y + 1 end + + self:reposition(self.x, self.y, self.width, self.height) end -- Called when the window's parent has be assigned @@ -539,23 +562,6 @@ function UI.Window:setParent() self.ox, self.oy = self.x, self.y self:layout() - - -- Experimental - -- Inherit properties from the parent container - -- does this need to be in reverse order ? - local m = getmetatable(self) -- get the class for this instance - repeat - if m.inherits then - for k, v in pairs(m.inherits) do - local value = self.parent:getProperty(v) - if value then - self[k] = value - end - end - end - m = m._base - until not m - self:initChildren() end @@ -566,12 +572,33 @@ function UI.Window:resize() self:layout() if self.children then - for _,child in ipairs(self.children) do + for child in self:eachChild() do child:resize() end end end +function UI.Window:reposition(x, y, w, h) + if not self.lines then + Canvas.init(self, { + x = x, + y = y, + width = w, + height = h, + isColor = self.parent.isColor, + }) + else + self:move(x, y) + Canvas.resize(self, w, h) + end +end + +function UI.Window:raise() + Array.removeByValue(self.parent.children, self) + table.insert(self.parent.children, self) + self:dirty(true) +end + UI.Window.docs.add = [[add(TABLE) Add element(s) to a window. Example: page:add({ @@ -584,6 +611,20 @@ function UI.Window:add(children) self:initChildren() end +function UI.Window:eachChild() + local c = self.children and Util.shallowCopy(self.children) + local i = 0 + return function() + i = i + 1 + return c and c[i] + end +end + +function UI.Window:remove() + Array.removeByValue(self.parent.children, self) + self.parent:dirty(true) +end + function UI.Window:getCursorPos() return self.cursorX, self.cursorY end @@ -601,12 +642,14 @@ end UI.Window.docs.draw = [[draw(VOID) Redraws the window in the internal buffer.]] function UI.Window:draw() - self:clear(self.backgroundColor) - if self.children then - for _,child in pairs(self.children) do - if child.enabled then - child:draw() - end + self:clear() + self:drawChildren() +end + +function UI.Window:drawChildren() + for child in self:eachChild() do + if child.enabled then + child:draw() end end end @@ -634,19 +677,30 @@ end function UI.Window:enable(...) self.enabled = true - if self.children then - for _,child in pairs(self.children) do - child:enable(...) - end + if self.transitionHint then + self:addTransition(self.transitionHint) + end + + if self.modal then + self:raise() + self:capture(self) + end + + for child in self:eachChild() do + child:enable(...) end end function UI.Window:disable() self.enabled = false - if self.children then - for _,child in pairs(self.children) do - child:disable() - end + self.parent:dirty(true) + + if self.modal then + self:release(self) + end + + for child in self:eachChild() do + child:disable() end end @@ -656,13 +710,9 @@ function UI.Window:setTextScale(textScale) end UI.Window.docs.clear = [[clear(opt COLOR bg, opt COLOR fg) -Clears the window using the either the passed values or the defaults for that window.]] +Clears the window using either the passed values or the defaults for that window.]] function UI.Window:clear(bg, fg) - if self.canvas then - self.canvas:clear(bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) - else - self:clearArea(1 + self.offx, 1 + self.offy, self.width, self.height, bg) - end + Canvas.clear(self, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) end function UI.Window:clearLine(y, bg) @@ -670,28 +720,20 @@ function UI.Window:clearLine(y, bg) end function UI.Window:clearArea(x, y, width, height, bg) + self:fillArea(x, y, width, height, ' ', bg) +end + +function UI.Window:fillArea(x, y, width, height, fillChar, bg, fg) if width > 0 then - local filler = _rep(' ', width) + local filler = _rep(fillChar, width) for i = 0, height - 1 do - self:write(x, y + i, filler, bg) + self:write(x, y + i, filler, bg, fg) end end end function UI.Window:write(x, y, text, bg, fg) - bg = bg or self.backgroundColor - fg = fg or self.textColor - - if self.canvas then - self.canvas:write(x, y, text, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) - else - x = x - self.offx - y = y - self.offy - if y <= self.height and y > 0 then - self.parent:write( - self.x + x - 1, self.y + y - 1, tostring(text), bg, fg) - end - end + Canvas.write(self, x, y, text, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) end function UI.Window:centeredWrite(y, text, bg, fg) @@ -826,7 +868,8 @@ function UI.Window:pointToChild(x, y) x = x + self.offx - self.x + 1 y = y + self.offy - self.y + 1 if self.children then - for _,child in pairs(self.children) do + for i = #self.children, 1, -1 do + local child = self.children[i] if child.enabled and not child.inactive and x >= child.x and x < child.x + child.width and y >= child.y and y < child.y + child.height then @@ -882,36 +925,28 @@ function UI.Window:focusFirst() end end -function UI.Window:refocus() - local el = self - while el do - local focusables = el:getFocusables() - if focusables[1] then - self:setFocus(focusables[1]) - break - end - el = el.parent - end -end - function UI.Window:scrollIntoView() local parent = self.parent + local offx, offy = parent.offx, parent.offy if self.x <= parent.offx then parent.offx = math.max(0, self.x - 1) - parent:draw() + if offx ~= parent.offx then + parent:draw() + end elseif self.x + self.width > parent.width + parent.offx then parent.offx = self.x + self.width - parent.width - 1 - parent:draw() + if offx ~= parent.offx then + parent:draw() + end end -- TODO: fix local function setOffset(y) parent.offy = y - if parent.canvas then - parent.canvas.offy = parent.offy + if offy ~= parent.offy then + parent:draw() end - parent:draw() end if self.y <= parent.offy then @@ -921,43 +956,16 @@ function UI.Window:scrollIntoView() end end -function UI.Window:getCanvas() - local el = self - repeat - if el.canvas then - return el.canvas - end - el = el.parent - until not el -end - -function UI.Window:addLayer(bg, fg) - local canvas = self:getCanvas() - local x, y = self.x, self.y - local parent = self.parent - while parent and not parent.canvas do - x = x + parent.x - 1 - y = y + parent.y - 1 - parent = parent.parent - end - canvas = canvas:addLayer({ - x = x, y = y, height = self.height, width = self.width - }, bg, fg) - - canvas:clear(bg or self.backgroundColor, fg or self.textColor) - return canvas -end - function UI.Window:addTransition(effect, args) if self.parent then args = args or { } if not args.x then -- not good - args.x, args.y = self.x, self.y -- getPosition(self) + args.x, args.y = self.x, self.y args.width = self.width args.height = self.height + args.canvas = self end - args.canvas = args.canvas or self.canvas self.parent:addTransition(effect, args) end end @@ -991,7 +999,16 @@ function UI.Window:getProperty(property) end function UI.Window:find(uid) - return self.children and Util.find(self.children, 'uid', uid) + local el = self.children and Util.find(self.children, 'uid', uid) + if not el then + for child in self:eachChild() do + el = child:find(uid) + if el then + break + end + end + end + return el end function UI.Window:eventHandler() @@ -1010,18 +1027,14 @@ UI.Device.defaults = { function UI.Device:postInit() self.device = self.device or term.current() - --if self.deviceType then - -- self.device = device[self.deviceType] - --end - if not self.device.setTextScale then self.device.setTextScale = function() end end self.device.setTextScale(self.textScale) self.width, self.height = self.device.getSize() - self.isColor = self.device.isColor() + Canvas.init(self, { isColor = self.isColor }) UI.devices[self.device.side or 'terminal'] = self end @@ -1031,8 +1044,8 @@ function UI.Device:resize() self.width, self.height = self.device.getSize() self.lines = { } -- TODO: resize all pages added to this device - self.canvas:resize(self.width, self.height) - self.canvas:clear(self.backgroundColor, self.textColor) + Canvas.resize(self, self.width, self.height) + Canvas.clear(self, self.backgroundColor, self.textColor) end function UI.Device:setCursorPos(x, y) @@ -1069,7 +1082,7 @@ function UI.Device:addTransition(effect, args) args = args or { } args.ex = args.x + args.width - 1 args.ey = args.y + args.height - 1 - args.canvas = args.canvas or self.canvas + args.canvas = args.canvas or self if type(effect) == 'string' then effect = Transition[effect] @@ -1078,10 +1091,13 @@ function UI.Device:addTransition(effect, args) end end - table.insert(self.transitions, { update = effect(args), args = args }) + table.insert(self.transitions, { effect = effect, args = args }) end -function UI.Device:runTransitions(transitions, canvas) +function UI.Device:runTransitions(transitions) + for _,k in pairs(transitions) do + k.update = k.effect(k.args) + end while true do for _,k in ipairs(Util.keys(transitions)) do local transition = transitions[k] @@ -1089,7 +1105,7 @@ function UI.Device:runTransitions(transitions, canvas) transitions[k] = nil end end - canvas:render(self.device) + self.currentPage:render(self.device) if Util.empty(transitions) then break end @@ -1105,9 +1121,9 @@ function UI.Device:sync() self.device.setCursorBlink(false) end - self.canvas:render(self.device) + self.currentPage:render(self.device) if transitions then - self:runTransitions(transitions, self.canvas) + self:runTransitions(transitions) end if self:getCursorBlink() then diff --git a/sys/modules/opus/ui/canvas.lua b/sys/modules/opus/ui/canvas.lua index 6fe12b1..980f281 100644 --- a/sys/modules/opus/ui/canvas.lua +++ b/sys/modules/opus/ui/canvas.lua @@ -9,7 +9,6 @@ local colors = _G.colors local Canvas = class() -Canvas.__visualize = false Canvas.colorPalette = { } Canvas.darkPalette = { } Canvas.grayscalePalette = { } @@ -22,15 +21,17 @@ end --[[ A canvas can have more lines than canvas.height in order to scroll -]] + TODO: finish vertical scrolling +]] function Canvas:init(args) - self.x = 1 - self.y = 1 - self.layers = { } + self.bg = colors.black + self.fg = colors.white 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 @@ -46,15 +47,30 @@ 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, h do self.lines[i] = { } self:clearLine(i) @@ -66,26 +82,24 @@ function Canvas:resize(w, h) if w < self.width then for i = 1, h do - 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) + 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) end elseif w > self.width then local d = w - self.width local text = _rep(' ', d) - local fg = _rep(self.palette[self.fg or colors.white], d) - local bg = _rep(self.palette[self.bg or colors.black], d) + local fg = _rep(self.palette[self.fg], d) + local bg = _rep(self.palette[self.bg], d) for i = 1, h do - 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 + local ln = self.lines[i] + ln.text = ln.text .. text + ln.fg = ln.fg .. fg + ln.bg = ln.bg .. bg + ln.dirty = true end end - - self.ex = self.x + w - 1 - self.ey = self.y + h - 1 - self.width = w - self.height = h end function Canvas:copy() @@ -113,22 +127,25 @@ function Canvas:addLayer(layer) isColor = self.isColor, }) canvas.parent = self - table.insert(self.layers, canvas) + if not self.children then + self.children = { } + end + table.insert(self.children, canvas) return canvas end function Canvas:removeLayer() - for k, layer in pairs(self.parent.layers) do + for k, layer in pairs(self.parent.children) do if layer == self then self:setVisible(false) - table.remove(self.parent.layers, k) + table.remove(self.parent.children, k) break end end end function Canvas:setVisible(visible) - self.visible = visible + self.visible = visible -- TODO: use self.active = visible if not visible and self.parent then self.parent:dirty() -- TODO: set parent's lines to dirty for each line in self @@ -137,11 +154,10 @@ end -- Push a layer to the top function Canvas:raise() - if self.parent then - local layers = self.parent.layers or { } - for k, v in pairs(layers) do + if self.parent and self.parent.children then + for k, v in pairs(self.parent.children) do if v == self then - table.insert(layers, table.remove(layers, k)) + table.insert(self.parent.children, table.remove(self.parent.children, k)) break end end @@ -224,15 +240,15 @@ function Canvas:writeLine(y, text, fg, bg) end function Canvas:clearLine(y, bg, fg) - fg = _rep(self.palette[fg or colors.white], self.width) - bg = _rep(self.palette[bg or colors.black], self.width) + fg = _rep(self.palette[fg or self.fg], self.width) + bg = _rep(self.palette[bg or self.bg], 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 colors.white], self.width) - bg = _rep(self.palette[bg or colors.black], self.width) + fg = _rep(self.palette[fg or self.fg], self.width) + bg = _rep(self.palette[bg or self.bg], self.width) for i = 1, #self.lines do self:writeLine(i, text, fg, bg) end @@ -246,13 +262,16 @@ function Canvas:isDirty() end 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() +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 end end end @@ -280,104 +299,89 @@ end function Canvas:render(device) local offset = { x = 0, y = 0 } + + -- WIP + local function getRegion(canvas) + local region + if canvas.parent then + region = getRegion(canvas.parent) + else + region = Region.new(self.x, self.y, self.ex, self.ey) + end + offset.x = offset.x + canvas.x - 1 + offset.y = offset.y + canvas.y - 1 + -- clip against parent + return region + end + + -- this code works - but is all kinds of wrong + -- adding a margin to UI.Page will cause issues + -- and could be clipping issues + offset = { x = self.x - 1, y = self.y - 1 } 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 + + -- TODO: need to clip if there is a parent + --self.regions = Region.new(self.x + offset.x, self.y + offset.y, self.ex + offset.x, self.ey + offset.y) + --self:__renderLayers(device, offset) + + self.regions = Region.new(self.x, self.y, self.ex, self.ey) + self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }) end --- regions are comprised of absolute values that coorespond to the output device. +-- regions are comprised of absolute values that correspond 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) - if #self.layers > 0 then - self.regions = self.regions or Region.new(self.x + offset.x, self.y + offset.y, self.ex + offset.x, self.ey + offset.y) - - 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:__punch(canvas, offset) - + 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, - canvas.y + offset.y, - canvas.ex + offset.x, - canvas.ey + offset.y) + 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)) + + -- contain within parent + canvas.regions:andRegion(self.regions) + + -- 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)) - -- 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, - y = canvas.y + offset.y - 1, + x = canvas.x + offset.x - 1 - (self.offx or 0), + y = canvas.y + offset.y - 1 - (self.offy or 0), }) end canvas.regions = nil end end - - 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:__blitClipped(device, offset) - if self.parent then - -- contain the rendered region in the parent's region - local p = Region.new(1, 1, - self.parent.width + offset.x - self.x + 1, - self.parent.height + offset.y - self.y + 1) - self.regions:andRegion(p) 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}, + y = region[2] - offset.y, + ex = region[3] - offset.x, + ey = region[4] - offset.y }, { x = region[1], y = region[2] }) end -end + self.regions = nil -function Canvas:__punch(rect, offset) - self.regions:subRect( - rect.x + offset.x, - rect.y + offset.y, - rect.ex + offset.x, - rect.ey + offset.y) + self:clean() end -- performance can probably be improved by using one more buffer tied to the device @@ -386,7 +390,7 @@ function Canvas:__blitRect(device, src, tgt) tgt = tgt or self -- for visualizing updates on the screen - if Canvas.__visualize then + 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) @@ -399,8 +403,8 @@ function Canvas:__blitRect(device, src, tgt) end end if drew then - local t = os.clock() - repeat until os.clock()-t > .2 + local c = os.clock() + repeat until os.clock()-c > .03 end end for i = 0, src.ey - src.y do @@ -418,4 +422,16 @@ function Canvas:__blitRect(device, src, tgt) end end +if not ({ ... })[1] then + local UI = require('opus.ui') + + UI:setPage(UI.Page { + button = UI.Button { + x = 5, y = 5, + text = 'abc' + } + }) + UI:start() +end + return Canvas diff --git a/sys/modules/opus/ui/components/ActiveLayer.lua b/sys/modules/opus/ui/components/ActiveLayer.lua deleted file mode 100644 index dcbb499..0000000 --- a/sys/modules/opus/ui/components/ActiveLayer.lua +++ /dev/null @@ -1,32 +0,0 @@ -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 diff --git a/sys/modules/opus/ui/components/Button.lua b/sys/modules/opus/ui/components/Button.lua index 2442614..b07dda9 100644 --- a/sys/modules/opus/ui/components/Button.lua +++ b/sys/modules/opus/ui/components/Button.lua @@ -35,11 +35,11 @@ function UI.Button:draw() local bg = self.backgroundColor local ind = ' ' if self.focused then - bg = self.backgroundFocusColor - fg = self.textFocusColor + bg = self:getProperty('backgroundFocusColor') + fg = self:getProperty('textFocusColor') ind = self.focusIndicator elseif self.inactive then - fg = self.textInactiveColor + fg = self:getProperty('textInactiveColor') end local text = ind .. self.text .. ' ' if self.centered then diff --git a/sys/modules/opus/ui/components/Checkbox.lua b/sys/modules/opus/ui/components/Checkbox.lua index 567833e..e757797 100644 --- a/sys/modules/opus/ui/components/Checkbox.lua +++ b/sys/modules/opus/ui/components/Checkbox.lua @@ -21,9 +21,6 @@ UI.Checkbox.defaults = { mouse_click = 'checkbox_toggle', } } -UI.Checkbox.inherits = { - labelBackgroundColor = 'backgroundColor', -} function UI.Checkbox:postInit() self.width = self.label and #self.label + 4 or 3 end diff --git a/sys/modules/opus/ui/components/Chooser.lua b/sys/modules/opus/ui/components/Chooser.lua index 32fd3e2..fd1e327 100644 --- a/sys/modules/opus/ui/components/Chooser.lua +++ b/sys/modules/opus/ui/components/Chooser.lua @@ -11,8 +11,8 @@ UI.Chooser.defaults = { nochoice = 'Select', backgroundFocusColor = colors.lightGray, textInactiveColor = colors.gray, - leftIndicator = UI.extChars and '\17' or '<', - rightIndicator = UI.extChars and '\16' or '>', + leftIndicator = UI.extChars and '\171' or '<', + rightIndicator = UI.extChars and '\187' or '>', height = 1, accelerators = { space = 'choice_next', diff --git a/sys/modules/opus/ui/components/Dialog.lua b/sys/modules/opus/ui/components/Dialog.lua index dd9de1a..6e9bb03 100644 --- a/sys/modules/opus/ui/components/Dialog.lua +++ b/sys/modules/opus/ui/components/Dialog.lua @@ -1,4 +1,3 @@ -local Canvas = require('opus.ui.canvas') local class = require('opus.class') local UI = require('opus.ui') @@ -19,14 +18,14 @@ function UI.Dialog:postInit() end function UI.Dialog:show(...) - local canvas = self.parent:getCanvas() + local canvas = self.parent self.oldPalette = canvas.palette - canvas:applyPalette(Canvas.darkPalette) + canvas:applyPalette(self.darkPalette) UI.SlideOut.show(self, ...) end function UI.Dialog:hide(...) - self.parent:getCanvas().palette = self.oldPalette + self.parent.palette = self.oldPalette UI.SlideOut.hide(self, ...) self.parent:draw() end @@ -37,3 +36,30 @@ function UI.Dialog:eventHandler(event) 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 = colors.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 diff --git a/sys/modules/opus/ui/components/DropMenu.lua b/sys/modules/opus/ui/components/DropMenu.lua index 41bdaf9..e2a75cc 100644 --- a/sys/modules/opus/ui/components/DropMenu.lua +++ b/sys/modules/opus/ui/components/DropMenu.lua @@ -32,42 +32,38 @@ function UI.DropMenu:layout() self.height = #self.children + 1 self.width = maxWidth + 2 - if not self.canvas then - self.canvas = self:addLayer() - else - self.canvas:resize(self.width, self.height) + if self.x + self.width > self.parent.width then + self.x = self.parent.width - self.width + 1 end + + self:reposition(self.x, self.y, self.width, self.height) end function UI.DropMenu:enable() -end - -function UI.DropMenu:show(x, y) - self.x, self.y = x, y - self.canvas:move(x, y) - self.canvas:setVisible(true) + for _,c in pairs(self.children) do + if not c.spacer then + c.inactive = not self:getActive(c) + end + end UI.Window.enable(self) - - self:draw() - self:capture(self) self:focusFirst() + self:draw() end -function UI.DropMenu:hide() - self:disable() - self.canvas:setVisible(false) - self:release(self) +function UI.DropMenu:disable() + UI.Window.disable(self) + self:remove() end function UI.DropMenu:eventHandler(event) if event.type == 'focus_lost' and self.enabled then if not Util.contains(self.children, event.focused) then - self:hide() + self:disable() end elseif event.type == 'mouse_out' and self.enabled then - self:hide() - self:refocus() + self:disable() + self:setFocus(self.parent:find(self.lastFocus)) else return UI.MenuBar.eventHandler(self, event) end @@ -83,6 +79,15 @@ function UI.DropMenu.example() { 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 diff --git a/sys/modules/opus/ui/components/DropMenuItem.lua b/sys/modules/opus/ui/components/DropMenuItem.lua index ff28047..47ced08 100644 --- a/sys/modules/opus/ui/components/DropMenuItem.lua +++ b/sys/modules/opus/ui/components/DropMenuItem.lua @@ -14,7 +14,7 @@ UI.DropMenuItem.defaults = { } function UI.DropMenuItem:eventHandler(event) if event.type == 'button_activate' then - self.parent:hide() + self.parent:disable() end return UI.Button.eventHandler(self, event) end diff --git a/sys/modules/opus/ui/components/Embedded.lua b/sys/modules/opus/ui/components/Embedded.lua index e15df33..1747a10 100644 --- a/sys/modules/opus/ui/components/Embedded.lua +++ b/sys/modules/opus/ui/components/Embedded.lua @@ -18,45 +18,33 @@ UI.Embedded.defaults = { function UI.Embedded:setParent() UI.Window.setParent(self) + function self.render() + self:sync() + 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) - - 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 self.win then - self.win.reposition(self.x, self.y, self.width, self.height) - end +function UI.Embedded:draw() + self:dirty() end -function UI.Embedded:draw() - self.canvas:dirty() +function UI.Embedded:focus() + -- allow scrolling 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.canvas:dirty() + self.win.setVisible(true) + self:dirty() end function UI.Embedded:disable() - self.canvas:setVisible(false) self.win.setVisible(false) UI.Window.disable(self) end @@ -71,17 +59,12 @@ function UI.Embedded:eventHandler(event) end end -function UI.Embedded:focus() - -- allow scrolling -end - function UI.Embedded.example() local Event = require('opus.event') local Util = require('opus.util') local term = _G.term return UI.Embedded { - visible = true, enable = function (self) UI.Embedded.enable(self) Event.addRoutine(function() diff --git a/sys/modules/opus/ui/components/Grid.lua b/sys/modules/opus/ui/components/Grid.lua index 1d6e480..b728229 100644 --- a/sys/modules/opus/ui/components/Grid.lua +++ b/sys/modules/opus/ui/components/Grid.lua @@ -60,7 +60,7 @@ UI.Grid.defaults = { textSelectedColor = colors.white, backgroundColor = colors.black, backgroundSelectedColor = colors.gray, - headerBackgroundColor = colors.cyan, + headerBackgroundColor = UI.colors.tertiary, headerTextColor = colors.white, headerSortColor = colors.yellow, unfocusedTextSelectedColor = colors.white, diff --git a/sys/modules/opus/ui/components/Image.lua b/sys/modules/opus/ui/components/Image.lua index 787d813..7d67c35 100644 --- a/sys/modules/opus/ui/components/Image.lua +++ b/sys/modules/opus/ui/components/Image.lua @@ -1,19 +1,28 @@ 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:setParent() - if self.image then +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 self.height = #self.image end - if self.image and not self.width then - self.width = #self.image[1] + 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 end - UI.Window.setParent(self) end function UI.Image:draw() @@ -22,19 +31,22 @@ function UI.Image:draw() for y = 1, #self.image do local line = self.image[y] for x = 1, #line do - 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) + local ch = lookup:find(line:sub(x, x)) + if ch then + self:write(x, y, ' ', 2 ^ (ch -1)) 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 diff --git a/sys/modules/opus/ui/components/MenuBar.lua b/sys/modules/opus/ui/components/MenuBar.lua index 1b544cc..d7e793f 100644 --- a/sys/modules/opus/ui/components/MenuBar.lua +++ b/sys/modules/opus/ui/components/MenuBar.lua @@ -3,16 +3,6 @@ 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', @@ -22,7 +12,6 @@ UI.MenuBar.defaults = { textColor = colors.black, spacing = 2, lastx = 1, - showBackButton = false, buttonClass = 'MenuItem', } function UI.MenuBar:postInit() @@ -62,10 +51,6 @@ 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 @@ -78,23 +63,27 @@ function UI.MenuBar:getActive(menuItem) end function UI.MenuBar:eventHandler(event) - 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) + 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 end + + local x, y = getPosition(event.button) + + local menu = UI.DropMenu { + buttons = event.button.dropdown, + x = x, + y = y + 1, + lastFocus = event.button.uid, + } + self.parent:add({ dropmenu = menu }) + return true end end diff --git a/sys/modules/opus/ui/components/MenuItem.lua b/sys/modules/opus/ui/components/MenuItem.lua index 61bc0b1..57f3b31 100644 --- a/sys/modules/opus/ui/components/MenuItem.lua +++ b/sys/modules/opus/ui/components/MenuItem.lua @@ -6,8 +6,6 @@ local colors = _G.colors UI.MenuItem = class(UI.Button) UI.MenuItem.defaults = { UIElement = 'MenuItem', - textColor = colors.black, - backgroundColor = colors.lightGray, textFocusColor = colors.white, backgroundFocusColor = colors.lightGray, } diff --git a/sys/modules/opus/ui/components/NftImage.lua b/sys/modules/opus/ui/components/NftImage.lua index 4d295d7..6c5158f 100644 --- a/sys/modules/opus/ui/components/NftImage.lua +++ b/sys/modules/opus/ui/components/NftImage.lua @@ -5,17 +5,18 @@ UI.NftImage = class(UI.Window) UI.NftImage.defaults = { UIElement = 'NftImage', } -function UI.NftImage:setParent() - if self.image then +function UI.NftImage:postInit() + if self.image and not (self.ey or self.height) then self.height = self.image.height end - if self.image and not self.width then + if self.image and not (self.ex or 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 @@ -25,8 +26,6 @@ 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 diff --git a/sys/modules/opus/ui/components/Notification.lua b/sys/modules/opus/ui/components/Notification.lua index 701733e..f3b0d85 100644 --- a/sys/modules/opus/ui/components/Notification.lua +++ b/sys/modules/opus/ui/components/Notification.lua @@ -43,32 +43,34 @@ function UI.Notification:cancel() self.timer = nil end - if self.canvas then - self.enabled = false - self.canvas:removeLayer() - self.canvas = nil - end + self:disable() end function UI.Notification:display(value, timeout) - self:cancel() - self.enabled = true local lines = Util.wordWrap(value, self.width - 3) + + self.enabled = true 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.canvas:setVisible(true) + + self:reposition(self.x, self.y, self.width, self.height) + self:raise() 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,7 +79,6 @@ function UI.Notification:display(value, timeout) self:sync() end) else - self:write(self.width, 1, self.closeInd) self:sync() end end @@ -92,7 +93,7 @@ function UI.Notification:eventHandler(event) end function UI.Notification.example() - return UI.ActiveLayer { + return UI.Window { notify1 = UI.Notification { anchor = 'top', }, @@ -111,7 +112,9 @@ function UI.Notification.example() if event.type == 'test_success' then self.notify1:success('Example text') elseif event.type == 'test_error' then - self.notify2:error('Example text', 0) + self.notify2:error([[Example text test test +test test test test test +test test test]], 0) end end, } diff --git a/sys/modules/opus/ui/components/Page.lua b/sys/modules/opus/ui/components/Page.lua index 7bc8e70..0e20f5e 100644 --- a/sys/modules/opus/ui/components/Page.lua +++ b/sys/modules/opus/ui/components/Page.lua @@ -1,21 +1,9 @@ -local Canvas = require('opus.ui.canvas') local class = require('opus.class') local UI = require('opus.ui') local Util = require('opus.util') local colors = _G.colors --- need to add offsets to this test -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.Page = class(UI.Window) UI.Page.defaults = { UIElement = 'Page', @@ -26,21 +14,15 @@ UI.Page.defaults = { ['shift-tab' ] = 'focus_prev', up = 'focus_prev', }, - backgroundColor = colors.cyan, + backgroundColor = UI.colors.primary, textColor = colors.white, } function UI.Page:postInit() self.parent = self.parent or UI.defaultDevice self.__target = self - self.canvas = Canvas({ - x = 1, y = 1, width = self.parent.width, height = self.parent.height, - isColor = self.parent.isColor, - }) - self.canvas:clear(self.backgroundColor, self.textColor) end function UI.Page:enable() - self.canvas.visible = true UI.Window.enable(self) if not self.focused or not self.focused.enabled then @@ -49,12 +31,12 @@ function UI.Page:enable() end function UI.Page:disable() - self.canvas.visible = false UI.Window.disable(self) end function UI.Page:sync() if self.enabled then + self:checkFocus() self.parent:sync() end end @@ -73,22 +55,24 @@ function UI.Page:pointToChild(x, y) if self.__target == self then return UI.Window.pointToChild(self, x, y) end - x = x + self.offx - self.x + 1 - y = y + self.offy - self.y + 1 ---[[ - -- this is supposed to fix when there are multiple sub canvases - local absX, absY = getPosition(self.__target) - if self.__target.canvas then - x = x - (self.__target.canvas.x - self.__target.x) - y = y - (self.__target.canvas.y - self.__target.y) - _syslog({'raw', self.__target.canvas.y, self.__target.y}) + + -- need to add offsets to this test + 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 - ]] - return self.__target:pointToChild(x, y) + + 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 self.__target.pageType ~= 'modal' then + if self.__target == self or not self.__target.modal then return UI.Window.getFocusables(self) end return self.__target:getFocusables() @@ -149,12 +133,25 @@ function UI.Page:setFocus(child) if not child.focused then child.focused = true child:emit({ type = 'focus_change', focused = child }) - --self:emit({ type = 'focus_change', focused = child }) end child:focus() end +function UI.Page:checkFocus() + if not self.focused or not self.focused.enabled then + local el = self.__target + while el do + local focusables = el:getFocusables() + if focusables[1] then + self:setFocus(focusables[1]) + break + end + el = el.parent + end + end +end + function UI.Page:eventHandler(event) if self.focused then if event.type == 'focus_next' then diff --git a/sys/modules/opus/ui/components/ProgressBar.lua b/sys/modules/opus/ui/components/ProgressBar.lua index a066952..969e115 100644 --- a/sys/modules/opus/ui/components/ProgressBar.lua +++ b/sys/modules/opus/ui/components/ProgressBar.lua @@ -28,12 +28,11 @@ function UI.ProgressBar:draw() end function UI.ProgressBar.example() - local Event = require('opus.event') return UI.ProgressBar { x = 2, ex = -2, y = 2, focus = function() end, enable = function(self) - Event.onInterval(.25, function() + require('opus.event').onInterval(.25, function() self.value = self.value == 100 and 0 or self.value + 5 self:draw() self:sync() diff --git a/sys/modules/opus/ui/components/ScrollBar.lua b/sys/modules/opus/ui/components/ScrollBar.lua index d4bef52..d9412db 100644 --- a/sys/modules/opus/ui/components/ScrollBar.lua +++ b/sys/modules/opus/ui/components/ScrollBar.lua @@ -17,7 +17,10 @@ UI.ScrollBar.defaults = { ey = -1, } function UI.ScrollBar:draw() - local view = self.parent:getViewArea() + local parent = self.target or self.parent --self:find(self.target) + local view = parent:getViewArea() + + self:clear() if view.totalHeight > view.height then local maxScroll = view.totalHeight - view.height @@ -27,7 +30,7 @@ function UI.ScrollBar:draw() local row = view.y if not view.static then -- does the container scroll ? - self.height = view.totalHeight + self:reposition(self.x, self.y, self.width, view.totalHeight) end for i = 1, view.height - 2 do @@ -56,16 +59,17 @@ end function UI.ScrollBar:eventHandler(event) if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then if event.x == 1 then - local view = self.parent:getViewArea() + local parent = self.target or self.parent --self:find(self.target) + local view = parent:getViewArea() if view.totalHeight > view.height then if event.y == view.y then - self:emit({ type = 'scroll_up'}) + parent:emit({ type = 'scroll_up'}) elseif event.y == view.y + view.height - 1 then - self:emit({ type = 'scroll_down'}) + parent:emit({ type = 'scroll_down'}) else local percent = (event.y - view.y) / (view.height - 2) local y = math.floor((view.totalHeight - view.height) * percent) - self:emit({ type = 'scroll_to', offset = y }) + parent :emit({ type = 'scroll_to', offset = y }) end end return true diff --git a/sys/modules/opus/ui/components/ScrollingGrid.lua b/sys/modules/opus/ui/components/ScrollingGrid.lua index 06dd5b8..76726d5 100644 --- a/sys/modules/opus/ui/components/ScrollingGrid.lua +++ b/sys/modules/opus/ui/components/ScrollingGrid.lua @@ -57,3 +57,21 @@ 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 \ No newline at end of file diff --git a/sys/modules/opus/ui/components/SlideOut.lua b/sys/modules/opus/ui/components/SlideOut.lua index 479be4b..a087d3c 100644 --- a/sys/modules/opus/ui/components/SlideOut.lua +++ b/sys/modules/opus/ui/components/SlideOut.lua @@ -4,17 +4,9 @@ local UI = require('opus.ui') UI.SlideOut = class(UI.Window) UI.SlideOut.defaults = { UIElement = 'SlideOut', - pageType = 'modal', + transitionHint = 'expandUp', + modal = true, } -function UI.SlideOut: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.SlideOut:enable() end @@ -27,24 +19,20 @@ function UI.SlideOut:toggle() 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() - self:release(self) - self:refocus() +end + +function UI.SlideOut: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 function UI.SlideOut:eventHandler(event) @@ -59,24 +47,27 @@ function UI.SlideOut:eventHandler(event) end function UI.SlideOut.example() - -- for the transistion to work properly, the parent must have a canvas - return UI.ActiveLayer { - y = 2, + return UI.Window { + y = 3, + backgroundColor = 2048, button = UI.Button { x = 2, y = 5, text = 'show', }, slideOut = UI.SlideOut { - backgroundColor = _G.colors.yellow, - y = -4, height = 4, x = 3, ex = -3, + 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.canvas.xxx = true self.slideOut:toggle() end end, diff --git a/sys/modules/opus/ui/components/Slider.lua b/sys/modules/opus/ui/components/Slider.lua index 9a58cdb..e040cc8 100644 --- a/sys/modules/opus/ui/components/Slider.lua +++ b/sys/modules/opus/ui/components/Slider.lua @@ -57,8 +57,16 @@ 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 = (event.x - 1) / (self.width - 1) + local i = pos / (self.width - 1) self.value = self.min + (i * range) self:emit({ type = self.event, value = self.value, element = self }) self:draw() diff --git a/sys/modules/opus/ui/components/StatusBar.lua b/sys/modules/opus/ui/components/StatusBar.lua index 96a1943..6f9b80a 100644 --- a/sys/modules/opus/ui/components/StatusBar.lua +++ b/sys/modules/opus/ui/components/StatusBar.lua @@ -63,7 +63,7 @@ end function UI.StatusBar:timedStatus(status, timeout) self:write(2, 1, Util.widthify(status, self.width-2), self.backgroundColor) - Event.on(timeout or 3, function() + Event.onTimeout(timeout or 3, function() if self.enabled then self:draw() self:sync() diff --git a/sys/modules/opus/ui/components/Tab.lua b/sys/modules/opus/ui/components/Tab.lua index 1e31de6..d2e591b 100644 --- a/sys/modules/opus/ui/components/Tab.lua +++ b/sys/modules/opus/ui/components/Tab.lua @@ -1,9 +1,16 @@ local class = require('opus.class') local UI = require('opus.ui') -UI.Tab = class(UI.ActiveLayer) +UI.Tab = class(UI.Window) UI.Tab.defaults = { UIElement = 'Tab', tabTitle = 'tab', 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 diff --git a/sys/modules/opus/ui/components/TabBar.lua b/sys/modules/opus/ui/components/TabBar.lua index e684cae..27f7e59 100644 --- a/sys/modules/opus/ui/components/TabBar.lua +++ b/sys/modules/opus/ui/components/TabBar.lua @@ -2,13 +2,14 @@ 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', -} -UI.TabBar.inherits = { - selectedBackgroundColor = 'backgroundColor', + selectedBackgroundColor = UI.colors.secondary, + unselectedBackgroundColor = colors.lightGray, } function UI.TabBar:enable() UI.MenuBar.enable(self) diff --git a/sys/modules/opus/ui/components/TabBarMenuItem.lua b/sys/modules/opus/ui/components/TabBarMenuItem.lua index f9f549b..a17a59f 100644 --- a/sys/modules/opus/ui/components/TabBarMenuItem.lua +++ b/sys/modules/opus/ui/components/TabBarMenuItem.lua @@ -1,27 +1,18 @@ local class = require('opus.class') local UI = require('opus.ui') -local colors = _G.colors - UI.TabBarMenuItem = class(UI.Button) UI.TabBarMenuItem.defaults = { UIElement = 'TabBarMenuItem', event = 'tab_select', - textColor = colors.black, - selectedBackgroundColor = colors.cyan, - unselectedBackgroundColor = colors.lightGray, - backgroundColor = colors.lightGray, -} -UI.TabBarMenuItem.inherits = { - selectedBackgroundColor = 'selectedBackgroundColor', } function UI.TabBarMenuItem:draw() if self.selected then - self.backgroundColor = self.selectedBackgroundColor - self.backgroundFocusColor = self.selectedBackgroundColor + self.backgroundColor = self:getProperty('selectedBackgroundColor') + self.backgroundFocusColor = self.backgroundColor else - self.backgroundColor = self.unselectedBackgroundColor - self.backgroundFocusColor = self.unselectedBackgroundColor + self.backgroundColor = self:getProperty('unselectedBackgroundColor') + self.backgroundFocusColor = self.backgroundColor end UI.Button.draw(self) end diff --git a/sys/modules/opus/ui/components/Tabs.lua b/sys/modules/opus/ui/components/Tabs.lua index 2b0c824..8eb45c7 100644 --- a/sys/modules/opus/ui/components/Tabs.lua +++ b/sys/modules/opus/ui/components/Tabs.lua @@ -56,12 +56,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 pairs(self.children or { }) do + for child in self:eachChild() do + child.transitionHint = nil if child.uid == menuItem.tabUid then child:enable() self:emit({ type = 'tab_activate', activated = child }) @@ -74,14 +74,11 @@ end function UI.Tabs:eventHandler(event) if event.type == 'tab_change' then local tab = self:find(event.tab.tabUid) - if event.current > event.last then - self.transitionHint = 'slideLeft' - else - self.transitionHint = 'slideRight' - end + local hint = event.current > event.last and 'slideLeft' or 'slideRight' - for _,child in pairs(self.children) do + for child in self:eachChild() do if child.uid == event.tab.tabUid then + child.transitionHint = hint child:enable() elseif child.tabTitle then child:disable() @@ -89,6 +86,7 @@ function UI.Tabs:eventHandler(event) end self:emit({ type = 'tab_activate', activated = tab }) tab:draw() + return true end end @@ -102,7 +100,18 @@ function UI.Tabs.example() tab2 = UI.Tab { index = 2, tabTitle = 'tab2', - button = UI.Button { y = 3 }, + 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, diff --git a/sys/modules/opus/ui/components/TextArea.lua b/sys/modules/opus/ui/components/TextArea.lua index 2b8c342..69fb998 100644 --- a/sys/modules/opus/ui/components/TextArea.lua +++ b/sys/modules/opus/ui/components/TextArea.lua @@ -6,11 +6,8 @@ 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 @@ -23,19 +20,28 @@ end function UI.TextArea:draw() self:clear() --- self:setCursorPos(1, 1) self.cursorX, self.cursorY = 1, 1 self:print(self.value) - - for _,child in pairs(self.children) do - if child.enabled then - child:draw() - end - end + self:drawChildren() end function UI.TextArea.example() - return UI.TextArea { - value = 'sample text\nabc' + return UI.Window { + backgroundColor = 2048, + t1 = UI.TextArea { + ey = 3, + value = 'sample text\nabc' + }, + t2 = UI.TextArea { + y = 5, + value = [[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]] + } } end \ No newline at end of file diff --git a/sys/modules/opus/ui/components/TextEntry.lua b/sys/modules/opus/ui/components/TextEntry.lua index 929cb3c..06fa712 100644 --- a/sys/modules/opus/ui/components/TextEntry.lua +++ b/sys/modules/opus/ui/components/TextEntry.lua @@ -19,7 +19,6 @@ UI.TextEntry = class(UI.Window) UI.TextEntry.docs = { } UI.TextEntry.defaults = { UIElement = 'TextEntry', - --value = '', shadowText = '', focused = false, textColor = colors.white, diff --git a/sys/modules/opus/ui/components/Throttle.lua b/sys/modules/opus/ui/components/Throttle.lua index 0466486..d83662e 100644 --- a/sys/modules/opus/ui/components/Throttle.lua +++ b/sys/modules/opus/ui/components/Throttle.lua @@ -20,24 +20,15 @@ UI.Throttle.defaults = { ' //) (O ). @ \\-d ) (@ ' } } -function UI.Throttle:setParent() +function UI.Throttle:layout() self.x = math.ceil((self.parent.width - self.width) / 2) self.y = math.ceil((self.parent.height - self.height) / 2) - UI.Window.setParent(self) + self:reposition(self.x, self.y, self.width, self.height) end function UI.Throttle:enable() self.c = os.clock() - 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 + self.ctr = 0 end function UI.Throttle:update() @@ -46,11 +37,7 @@ function UI.Throttle:update() os.sleep(0) self.c = os.clock() self.enabled = true - if not self.canvas then - self.canvas = self:addLayer(self.backgroundColor, self.borderColor) - self.canvas:setVisible(true) - self:clear(self.borderColor) - end + self:clear(self.borderColor) local image = self.image[self.ctr + 1] local width = self.width - 2 for i = 0, #self.image do @@ -63,3 +50,25 @@ 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 diff --git a/sys/modules/opus/ui/components/TitleBar.lua b/sys/modules/opus/ui/components/TitleBar.lua index ce7fdd4..fa3d1e4 100644 --- a/sys/modules/opus/ui/components/TitleBar.lua +++ b/sys/modules/opus/ui/components/TitleBar.lua @@ -38,8 +38,6 @@ 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 '*', @@ -69,5 +67,73 @@ 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 + + else --if 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 = colors.green, + titleBar = UI.TitleBar { + title = 'test', 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 = colors.orange, + titleBar = UI.TitleBar { + title = 'test', moveable = true, + }, + 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 diff --git a/sys/modules/opus/ui/components/VerticalMeter.lua b/sys/modules/opus/ui/components/VerticalMeter.lua index 051d740..3c11cea 100644 --- a/sys/modules/opus/ui/components/VerticalMeter.lua +++ b/sys/modules/opus/ui/components/VerticalMeter.lua @@ -18,12 +18,11 @@ function UI.VerticalMeter:draw() end function UI.VerticalMeter.example() - local Event = require('opus.event') return UI.VerticalMeter { x = 2, width = 3, y = 2, ey = -2, focus = function() end, enable = function(self) - Event.onInterval(.25, function() + require('opus.event').onInterval(.25, function() self.value = self.value == 100 and 0 or self.value + 5 self:draw() self:sync() diff --git a/sys/modules/opus/ui/components/Viewport.lua b/sys/modules/opus/ui/components/Viewport.lua index 35cd0eb..f4707fd 100644 --- a/sys/modules/opus/ui/components/Viewport.lua +++ b/sys/modules/opus/ui/components/Viewport.lua @@ -1,16 +1,15 @@ local class = require('opus.class') local UI = require('opus.ui') -local colors = _G.colors - UI.Viewport = class(UI.Window) UI.Viewport.defaults = { UIElement = 'Viewport', - backgroundColor = colors.cyan, accelerators = { down = 'scroll_down', up = 'scroll_up', home = 'scroll_top', + left = 'scroll_left', + right = 'scroll_right', [ 'end' ] = 'scroll_bottom', pageUp = 'scroll_pageUp', [ 'control-b' ] = 'scroll_pageUp', @@ -18,53 +17,53 @@ UI.Viewport.defaults = { [ 'control-f' ] = 'scroll_pageDown', }, } -function UI.Viewport:layout() - UI.Window.layout(self) - if not self.canvas then - self.canvas = self:addLayer() - else - self.canvas:resize(self.width, self.height) +function UI.Viewport:postInit() + if self.showScrollBar then + self.scrollBar = UI.ScrollBar() end end -function UI.Viewport:enable() - UI.Window.enable(self) - self.canvas:setVisible(true) -end - -function UI.Viewport:disable() - UI.Window.disable(self) - self.canvas:setVisible(false) -end - -function UI.Viewport:setScrollPosition(offset) - local oldOffset = self.offy - self.offy = math.max(offset, 0) - self.offy = math.min(self.offy, math.max(#self.canvas.lines, self.height) - self.height) - if self.offy ~= oldOffset then +function UI.Viewport:setScrollPosition(offy, offx) -- argh - reverse + local oldOffy = self.offy + self.offy = math.max(offy, 0) + self.offy = math.min(self.offy, math.max(#self.lines, self.height) - self.height) + if self.offy ~= oldOffy then if self.scrollBar then self.scrollBar:draw() end - self.canvas.offy = offset - self.canvas:dirty() + self.offy = offy + self:dirty(true) + end + + local oldOffx = self.offx + self.offx = math.max(offx or 0, 0) + self.offx = math.min(self.offx, math.max(#self.lines[1], self.width) - self.width) + if self.offx ~= oldOffx then + if self.scrollBar then + --self.scrollBar:draw() + end + self.offx = offx or 0 + self:dirty(true) end end function UI.Viewport:write(x, y, text, bg, tc) - if y > #self.canvas.lines then - for i = #self.canvas.lines, y do - self.canvas.lines[i + 1] = { } - self.canvas:clearLine(i + 1, self.backgroundColor, self.textColor) - end + if y > #self.lines then + self:resizeBuffer(self.width, y) end return UI.Window.write(self, x, y, text, bg, tc) end +function UI.Viewport:setViewHeight(h) + if h > #self.lines then + self:resizeBuffer(self.width, h) + end +end + function UI.Viewport:reset() self.offy = 0 - self.canvas.offy = 0 - for i = self.height + 1, #self.canvas.lines do - self.canvas.lines[i] = nil + for i = self.height + 1, #self.lines do + self.lines[i] = nil end end @@ -72,26 +71,30 @@ function UI.Viewport:getViewArea() return { y = (self.offy or 0) + 1, height = self.height, - totalHeight = #self.canvas.lines, + totalHeight = #self.lines, offsetY = self.offy or 0, } end function UI.Viewport:eventHandler(event) if event.type == 'scroll_down' then - self:setScrollPosition(self.offy + 1) + self:setScrollPosition(self.offy + 1, self.offx) elseif event.type == 'scroll_up' then - self:setScrollPosition(self.offy - 1) + self:setScrollPosition(self.offy - 1, self.offx) + elseif event.type == 'scroll_left' then + self:setScrollPosition(self.offy, self.offx - 1) + elseif event.type == 'scroll_right' then + self:setScrollPosition(self.offy, self.offx + 1) elseif event.type == 'scroll_top' then - self:setScrollPosition(0) + self:setScrollPosition(0, 0) elseif event.type == 'scroll_bottom' then - self:setScrollPosition(10000000) + self:setScrollPosition(10000000, 0) elseif event.type == 'scroll_pageUp' then - self:setScrollPosition(self.offy - self.height) + self:setScrollPosition(self.offy - self.height, self.offx) elseif event.type == 'scroll_pageDown' then - self:setScrollPosition(self.offy + self.height) + self:setScrollPosition(self.offy + self.height, self.offx) elseif event.type == 'scroll_to' then - self:setScrollPosition(event.offset) + self:setScrollPosition(event.offset, 0) else return false end diff --git a/sys/modules/opus/ui/components/Wizard.lua b/sys/modules/opus/ui/components/Wizard.lua index 549669e..0fcab17 100644 --- a/sys/modules/opus/ui/components/Wizard.lua +++ b/sys/modules/opus/ui/components/Wizard.lua @@ -25,9 +25,6 @@ function UI.Wizard:postInit() } Util.merge(self, self.pages) - --for _, child in pairs(self.pages) do - -- child.ey = -2 - --end end function UI.Wizard:add(pages) @@ -50,9 +47,8 @@ end function UI.Wizard:enable(...) self.enabled = true self.index = 1 - self.transitionHint = nil local initial = self:getPage(1) - for _,child in pairs(self.children) do + for child in self:eachChild() do if child == initial or not child.index then child:enable(...) else @@ -93,12 +89,13 @@ function UI.Wizard:eventHandler(event) elseif event.type == 'enable_view' then local current = event.next or event.prev if not current then error('property "index" is required on wizard pages') end + local hint if event.current then if event.next then - self.transitionHint = 'slideLeft' + hint = 'slideLeft' elseif event.prev then - self.transitionHint = 'slideRight' + hint = 'slideRight' end event.current:disable() end @@ -117,6 +114,7 @@ function UI.Wizard:eventHandler(event) self.nextButton.event = 'wizard_complete' end -- a new current view + current.transitionHint = hint current:enable() current:emit({ type = 'view_enabled', view = current }) self:draw() diff --git a/sys/modules/opus/ui/components/WizardPage.lua b/sys/modules/opus/ui/components/WizardPage.lua index cb2c2de..da895ef 100644 --- a/sys/modules/opus/ui/components/WizardPage.lua +++ b/sys/modules/opus/ui/components/WizardPage.lua @@ -1,11 +1,8 @@ local class = require('opus.class') local UI = require('opus.ui') -local colors = _G.colors - -UI.WizardPage = class(UI.ActiveLayer) +UI.WizardPage = class(UI.Window) UI.WizardPage.defaults = { UIElement = 'WizardPage', - backgroundColor = colors.cyan, ey = -2, } diff --git a/sys/modules/opus/ui/transition.lua b/sys/modules/opus/ui/transition.lua index 4448760..20189ee 100644 --- a/sys/modules/opus/ui/transition.lua +++ b/sys/modules/opus/ui/transition.lua @@ -13,7 +13,7 @@ function Transition.slideLeft(args) return function() local finished = tween:update(1) args.canvas:move(math.floor(pos.x), args.canvas.y) - args.canvas:dirty() + args.canvas:dirty(true) return not finished end end @@ -29,7 +29,7 @@ function Transition.slideRight(args) return function() local finished = tween:update(1) args.canvas:move(math.floor(pos.x), args.canvas.y) - args.canvas:dirty() + args.canvas:dirty(true) return not finished end end @@ -45,7 +45,7 @@ function Transition.expandUp(args) return function() local finished = tween:update(1) args.canvas:move(args.x, math.floor(pos.y)) - args.canvas:dirty() + args.canvas.parent:dirty(true) return not finished end end