color rework + cleanup

This commit is contained in:
kepler155c@gmail.com 2020-04-16 23:13:19 -06:00
parent 3e41996b9b
commit 9d2a76f4ea
28 changed files with 364 additions and 238 deletions

View File

@ -184,9 +184,7 @@ local Browser = UI.Page {
text = 'Add', event = 'add_association', text = 'Add', event = 'add_association',
}, },
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar { },
backgroundColor = colors.cyan,
},
}, },
accelerators = { accelerators = {
[ 'control-q' ] = 'quit', [ 'control-q' ] = 'quit',
@ -547,6 +545,4 @@ local args = Util.parse(...)
Browser:setDir(args[1] or shell.dir()) Browser:setDir(args[1] or shell.dir())
UI:setPage(Browser) UI:setPage(Browser)
UI:start()
Event.pullEvents()
UI.term:reset()

View File

@ -382,11 +382,12 @@ function page:executeStatement(statement)
end end
end end
local args = Util.parse(...) local args = { ... }
if args[1] then if args[1] then
command = 'args[1]' command = 'args[1]'
sandboxEnv.args = args sandboxEnv.args = args
page:setResult(args[1]) page:setResult(args[1])
page:setPrompt(command)
end end
UI:setPage(page) UI:setPage(page)

View File

@ -95,6 +95,7 @@ local page = UI.Page {
ey = -2, ey = -2,
width = 8, width = 8,
selectedBackgroundColor = UI.colors.primary, selectedBackgroundColor = UI.colors.primary,
backgroundColor = UI.colors.tertiary,
layout = function(self) layout = function(self)
self.height = nil self.height = nil
UI.TabBar.layout(self) UI.TabBar.layout(self)

View File

@ -12,7 +12,7 @@ local gridColumns = {}
table.insert(gridColumns, { heading = '#', key = 'id', width = 5, align = 'right' }) table.insert(gridColumns, { heading = '#', key = 'id', width = 5, align = 'right' })
table.insert(gridColumns, { heading = 'Port', key = 'portid', width = 5, align = 'right' }) table.insert(gridColumns, { heading = 'Port', key = 'portid', width = 5, align = 'right' })
table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' }) table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' })
if UI.defaultDevice.width > 50 then if UI.term.width > 50 then
table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' }) table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' })
end end
table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' }) table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' })
@ -42,7 +42,7 @@ local page = UI.Page {
configSlide = UI.SlideOut { configSlide = UI.SlideOut {
y = -11, y = -11,
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close' }, titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close', backgroundColor = colors.black },
accelerators = { ['backspace'] = 'config_close' }, accelerators = { ['backspace'] = 'config_close' },
configTabs = UI.Tabs { configTabs = UI.Tabs {
y = 2, y = 2,

View File

@ -28,10 +28,10 @@ if _G.http.websocket then
event = 'update_key', event = 'update_key',
}, },
labelText = UI.TextArea { labelText = UI.TextArea {
x = 2, ex = -2, y = 6, ey = -4, x = 2, ex = -2, y = 5, ey = -4,
textColor = colors.yellow, textColor = colors.yellow,
backgroundColor = colors.black, backgroundColor = colors.black,
marginLeft = 0, marginRight = 0, marginLeft = 1, marginRight = 1, marginTop = 1,
value = string.format( value = string.format(
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time. [[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
To obtain a key, visit: To obtain a key, visit:

View File

@ -23,7 +23,7 @@ return UI.Tab {
x = 2, y = 2, ex = -2, ey = 4, x = 2, y = 2, ex = -2, ey = 4,
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
x = 2, y = 6, ex = -2, ey = -2, x = 2, y = 5, ex = -2, ey = -2,
values = { values = {
{ name = '', value = '' }, { name = '', value = '' },
{ name = 'CC version', value = Util.getVersion() }, { name = 'CC version', value = Util.getVersion() },

View File

@ -35,9 +35,10 @@ local tab = UI.Tab {
event = 'update', event = 'update',
}, },
labelText = UI.TextArea { labelText = UI.TextArea {
x = 2, ex = -2, y = 7, ey = -4, x = 2, ex = -2, y = 6, ey = -4,
backgroundColor = colors.black, backgroundColor = colors.black,
textColor = colors.yellow, textColor = colors.yellow,
marginLeft = 1, marginRight = 1, marginTop = 1,
value = 'Choose an application launcher', value = 'Choose an application launcher',
}, },
} }

View File

@ -9,8 +9,9 @@ return UI.Tab {
tabTitle = 'Network', tabTitle = 'Network',
description = 'Networking options', description = 'Networking options',
info = UI.TextArea { info = UI.TextArea {
x = 2, y = 6, ex = -2, ey = -2, x = 2, y = 5, ex = -2, ey = -2,
backgroundColor = colors.black, backgroundColor = colors.black,
marginLeft = 1, marginRight = 1, marginTop = 1,
value = string.format( value = string.format(
[[%sSet the primary modem used for wireless communications.%s [[%sSet the primary modem used for wireless communications.%s

View File

@ -25,10 +25,11 @@ return UI.Tab {
event = 'update_password', event = 'update_password',
}, },
info = UI.TextArea { info = UI.TextArea {
x = 2, ex = -2, y = 6, ey = -4, x = 2, ex = -2, y = 5, ey = -4,
backgroundColor = colors.black, backgroundColor = colors.black,
textColor = colors.yellow, textColor = colors.yellow,
inactive = true, inactive = true,
marginLeft = 1, marginRight = 1, marginTop = 1,
value = 'Add a password to enable other computers to connect to this one.', value = 'Add a password to enable other computers to connect to this one.',
}, },
eventHandler = function(self, event) eventHandler = function(self, event)

View File

@ -2,49 +2,94 @@ local UI = require('opus.ui')
local settings = _G.settings local settings = _G.settings
if settings then local transform = {
local settingsTab = UI.Tab { string = tostring,
tabTitle = 'Settings', number = tonumber,
description = 'Computercraft configurable settings', }
grid = UI.Grid {
x = 2, y = 2, ex = -2, ey = -2,
autospace = true,
sortColumn = 'name',
columns = {
{ heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' },
},
},
}
function settingsTab:enable() return settings and UI.Tab {
tabTitle = 'Settings',
description = 'Computercraft settings',
grid = UI.Grid {
x = 2, y = 2, ex = -2, ey = -2,
sortColumn = 'name',
columns = {
{ heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' },
},
},
editor = UI.SlideOut {
y = -6, height = 6,
titleBar = UI.TitleBar {
event = 'slide_hide',
title = 'Enter value',
},
form = UI.Form {
y = 2,
value = UI.TextEntry {
limit = 256,
formIndex = 1,
formLabel = 'Value',
formKey = 'value',
},
validateField = function(self, entry)
if entry.value then
return transform[self.type](entry.value)
end
return true
end,
},
accelerators = {
form_cancel = 'slide_hide',
},
show = function(self, entry)
self.form.type = type(entry.value) or 'string'
self.form:setValues(entry)
self.titleBar.title = entry.name
UI.SlideOut.show(self)
end,
eventHandler = function(self, event)
if event.type == 'form_complete' then
if not event.values.value then
settings.unset(event.values.name)
self.parent:reload()
else
event.values.value = transform[self.form.type](event.values.value)
settings.set(event.values.name, event.values.value)
end
self.parent.grid:draw()
self:hide()
settings.save('.settings')
end
return UI.SlideOut.eventHandler(self, event)
end,
},
reload = function(self)
local values = { } local values = { }
for _,v in pairs(settings.getNames()) do for _,v in pairs(settings.getNames()) do
local value = settings.get(v)
if not value then
value = false
end
table.insert(values, { table.insert(values, {
name = v, name = v,
value = value, value = settings.get(v) or false,
}) })
end end
self.grid:setValues(values) self.grid:setValues(values)
self.grid:setIndex(1)
end,
enable = function(self)
self:reload()
UI.Tab.enable(self) UI.Tab.enable(self)
end end,
eventHandler = function(self, event)
function settingsTab:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
if not event.selected.value or type(event.selected.value) == 'boolean' then if type(event.selected.value) == 'boolean' then
event.selected.value = not event.selected.value event.selected.value = not event.selected.value
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
else
self.editor:show(event.selected)
end end
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
return true return true
end end
end end,
}
-- this needs lots of work - currently only works with booleans
--return settingsTab
end

View File

@ -32,3 +32,23 @@ end
help.setPath(table.concat(helpPaths, ':')) help.setPath(table.concat(helpPaths, ':'))
shell.setPath(table.concat(appPaths, ':')) shell.setPath(table.concat(appPaths, ':'))
local function runDir(directory)
local files = fs.list(directory)
table.sort(files)
for _,file in ipairs(files) do
os.sleep(0)
local result, err = shell.run(directory .. '/' .. file)
if not result and err then
_G.printError('\n' .. err)
end
end
end
for _, package in pairs(Packages:installedSorted()) do
local packageDir = 'packages/' .. package.name .. '/init'
if fs.exists(packageDir) and fs.isDir(packageDir) then
runDir(packageDir)
end
end

View File

@ -65,7 +65,7 @@ function GPS.locate(timeout, debug)
if debug then if debug then
print("Position is "..pos.x..","..pos.y..","..pos.z) print("Position is "..pos.x..","..pos.y..","..pos.z)
end end
return vector.new(pos.x, pos.y, pos.z) return pos and vector.new(pos.x, pos.y, pos.z)
end end
function GPS.isAvailable() function GPS.isAvailable()

View File

@ -1,4 +1,5 @@
local Array = require('opus.array') local Array = require('opus.array')
local Blit = require('opus.ui.blit')
local class = require('opus.class') local class = require('opus.class')
local Event = require('opus.event') local Event = require('opus.event')
local Input = require('opus.input') local Input = require('opus.input')
@ -34,11 +35,16 @@ local textutils = _G.textutils
]] ]]
--[[-- Top Level Manager --]]-- --[[-- Top Level Manager --]]--
local Manager = class() local UI = { }
function Manager:init() function UI:init()
self.devices = { } self.devices = { }
self.theme = { } self.theme = { }
self.extChars = Util.getVersion() >= 1.76 self.extChars = Util.getVersion() >= 1.76
self.colors = {
primary = colors.green,
secondary = colors.lightGray,
tertiary = colors.gray,
}
local function keyFunction(event, code, held) local function keyFunction(event, code, held)
local ie = Input:translate(event, code, held) local ie = Input:translate(event, code, held)
@ -151,7 +157,7 @@ function Manager:init()
end) end)
end end
function Manager:configure(appName, ...) function UI:configure(appName, ...)
local defaults = Util.loadTable('usr/config/' .. appName) or { } local defaults = Util.loadTable('usr/config/' .. appName) or { }
if not defaults.device then if not defaults.device then
defaults.device = { } defaults.device = { }
@ -193,11 +199,11 @@ function Manager:configure(appName, ...)
end end
end end
function Manager:disableEffects() function UI:disableEffects()
self.defaultDevice.effectsEnabled = false self.term.effectsEnabled = false
end end
function Manager:loadTheme(filename) function UI:loadTheme(filename)
if fs.exists(filename) then if fs.exists(filename) then
local theme, err = Util.loadTable(filename) local theme, err = Util.loadTable(filename)
if not theme then if not theme then
@ -207,7 +213,7 @@ function Manager:loadTheme(filename)
end end
end end
function Manager:generateTheme(filename) function UI:generateTheme(filename)
local t = { } local t = { }
local function getName(d) local function getName(d)
@ -244,14 +250,14 @@ function Manager:generateTheme(filename)
Util.writeFile(filename, textutils.serialize(t):gsub('(")', '')) Util.writeFile(filename, textutils.serialize(t):gsub('(")', ''))
end end
function Manager:emitEvent(event) function UI:emitEvent(event)
local currentPage = self:getActivePage() local currentPage = self:getActivePage()
if currentPage and currentPage.focused then if currentPage and currentPage.focused then
return currentPage.focused:emit(event) return currentPage.focused:emit(event)
end end
end end
function Manager:click(target, ie) function UI:click(target, ie)
local clickEvent local clickEvent
if ie.code == 'mouse_drag' then if ie.code == 'mouse_drag' then
@ -304,23 +310,22 @@ function Manager:click(target, ie)
target:sync() target:sync()
end end
function Manager:setDefaultDevice(dev) function UI:setDefaultDevice(dev)
self.defaultDevice = dev
self.term = dev self.term = dev
end end
function Manager:addPage(name, page) function UI:addPage(name, page)
if not self.pages then if not self.pages then
self.pages = { } self.pages = { }
end end
self.pages[name] = page self.pages[name] = page
end end
function Manager:setPages(pages) function UI:setPages(pages)
self.pages = pages self.pages = pages
end end
function Manager:getPage(pageName) function UI:getPage(pageName)
local page = self.pages[pageName] local page = self.pages[pageName]
if not page then if not page then
@ -330,18 +335,18 @@ function Manager:getPage(pageName)
return page return page
end end
function Manager:getActivePage(page) function UI:getActivePage(page)
if page then if page then
return page.parent.currentPage return page.parent.currentPage
end end
return self.defaultDevice.currentPage return self.term.currentPage
end end
function Manager:setActivePage(page) function UI:setActivePage(page)
page.parent.currentPage = page page.parent.currentPage = page
end end
function Manager:setPage(pageOrName, ...) function UI:setPage(pageOrName, ...)
local page = pageOrName local page = pageOrName
if type(pageOrName) == 'string' then if type(pageOrName) == 'string' then
@ -361,7 +366,6 @@ function Manager:setPage(pageOrName, ...)
page.previousPage = currentPage page.previousPage = currentPage
end end
self:setActivePage(page) self:setActivePage(page)
--page:clear(page.backgroundColor)
page:enable(...) page:enable(...)
page:draw() page:draw()
if page.focused then if page.focused then
@ -372,27 +376,27 @@ function Manager:setPage(pageOrName, ...)
end end
end end
function Manager:getCurrentPage() function UI:getCurrentPage()
return self.defaultDevice.currentPage return self.term.currentPage
end end
function Manager:setPreviousPage() function UI:setPreviousPage()
if self.defaultDevice.currentPage.previousPage then if self.term.currentPage.previousPage then
local previousPage = self.defaultDevice.currentPage.previousPage.previousPage local previousPage = self.term.currentPage.previousPage.previousPage
self:setPage(self.defaultDevice.currentPage.previousPage) self:setPage(self.term.currentPage.previousPage)
self.defaultDevice.currentPage.previousPage = previousPage self.term.currentPage.previousPage = previousPage
end end
end end
function Manager:getDefaults(element, args) function UI:getDefaults(element, args)
local defaults = Util.deepCopy(element.defaults) local defaults = Util.deepCopy(element.defaults)
if args then if args then
Manager:mergeProperties(defaults, args) UI:mergeProperties(defaults, args)
end end
return defaults return defaults
end end
function Manager:mergeProperties(obj, args) function UI:mergeProperties(obj, args)
if args then if args then
for k,v in pairs(args) do for k,v in pairs(args) do
if k == 'accelerators' then if k == 'accelerators' then
@ -408,7 +412,7 @@ function Manager:mergeProperties(obj, args)
end end
end end
function Manager:pullEvents(...) function UI:pullEvents(...)
local s, m = pcall(Event.pullEvents, ...) local s, m = pcall(Event.pullEvents, ...)
self.term:reset() self.term:reset()
if not s and m then if not s and m then
@ -416,17 +420,11 @@ function Manager:pullEvents(...)
end end
end end
Manager.colors = { UI.exitPullEvents = Event.exitPullEvents
primary = colors.cyan, UI.quit = Event.exitPullEvents
secondary = colors.lightGray, UI.start = UI.pullEvents
tertiary = colors.gray,
}
Manager.exitPullEvents = Event.exitPullEvents UI:init()
Manager.quit = Event.exitPullEvents
Manager.start = Manager.pullEvents
local UI = Manager()
--[[-- Basic drawable area --]]-- --[[-- Basic drawable area --]]--
UI.Window = class(Canvas) UI.Window = class(Canvas)
@ -785,89 +783,19 @@ function UI.Window:print(text, bg, fg)
local marginLeft = self.marginLeft or 0 local marginLeft = self.marginLeft or 0
local marginRight = self.marginRight or 0 local marginRight = self.marginRight or 0
local width = self.width - marginLeft - marginRight local width = self.width - marginLeft - marginRight
local cs = {
bg = bg or self:getProperty('backgroundColor'),
fg = fg or self:getProperty('textColor'),
palette = self.palette,
}
local function nextWord(line, cx) local y = (self.marginTop or 0) + 1
local result = { line:find("(%w+)", cx) } for _,line in pairs(Util.split(text)) do
if #result > 1 and result[2] > cx then for _, ln in ipairs(Blit(line, cs):wrap(width)) do
return _sub(line, cx, result[2] + 1) self:blit(marginLeft + 1, y, ln.text, ln.bg, ln.fg)
elseif #result > 0 and result[1] == cx then y = y + 1
result = { line:find("(%w+)", result[2]) }
if #result > 0 then
return _sub(line, cx, result[1] + 1)
end
end
if cx <= #line then
return _sub(line, cx, #line)
end end
end end
local function pieces(f, bg, fg)
local pos = 1
local t = { }
while true do
local s = string.find(f, '\027', pos, true)
if not s then
break
end
if pos < s then
table.insert(t, _sub(f, pos, s - 1))
end
local seq = _sub(f, s)
seq = seq:match("\027%[([%d;]+)m")
local e = { }
for color in string.gmatch(seq, "%d+") do
color = tonumber(color)
if color == 0 then
e.fg = fg
e.bg = bg
elseif color > 20 then
e.bg = 2 ^ (color - 21)
else
e.fg = 2 ^ (color - 1)
end
end
table.insert(t, e)
pos = s + #seq + 3
end
if pos <= #f then
table.insert(t, _sub(f, pos))
end
return t
end
local lines = Util.split(text)
for k,line in pairs(lines) do
local fragments = pieces(line, bg, fg)
for _, fragment in ipairs(fragments) do
local lx = 1
if type(fragment) == 'table' then -- ansi sequence
fg = fragment.fg
bg = fragment.bg
else
while true do
local word = nextWord(fragment, lx)
if not word then
break
end
local w = word
if self.cursorX + #word > width then
self.cursorX = marginLeft + 1
self.cursorY = self.cursorY + 1
w = word:gsub('^ ', '')
end
self:write(self.cursorX, self.cursorY, w, bg, fg)
self.cursorX = self.cursorX + #w
lx = lx + #word
end
end
end
if lines[k + 1] then
self.cursorX = marginLeft + 1
self.cursorY = self.cursorY + 1
end
end
return self.cursorX, self.cursorY
end end
UI.Window.docs.focus = [[focus(VOID) UI.Window.docs.focus = [[focus(VOID)
@ -1126,7 +1054,7 @@ function UI.Device:runTransitions(transitions)
transitions[k] = nil transitions[k] = nil
end end
end end
self.currentPage:render(self.device) self.currentPage:render(self, true)
if Util.empty(transitions) then if Util.empty(transitions) then
break break
end end
@ -1143,7 +1071,7 @@ function UI.Device:sync()
if transitions then if transitions then
self:runTransitions(transitions) self:runTransitions(transitions)
else else
self.currentPage:render(self.device) self.currentPage:render(self, true)
end end
if self:getCursorBlink() then if self:getCursorBlink() then
@ -1179,7 +1107,7 @@ local function loadComponents()
return self(...) return self(...)
end end
}) })
UI[name]._preload = function(self) UI[name]._preload = function()
return load(name) return load(name)
end end
end end

View File

@ -0,0 +1,94 @@
local class = require('opus.class')
local colors = _G.colors
local Blit = class()
function Blit:init(t, cs)
if type(t) == 'string' then
t = Blit.toblit(t, cs or { })
end
self.text = t.text
self.bg = t.bg
self.fg = t.fg
end
function Blit:sub(s, e)
return Blit({
text = self.text:sub(s, e),
bg = self.bg:sub(s, e),
fg = self.fg:sub(s, e),
})
end
function Blit:wrap(max)
local index = 1
local lines = { }
local data = self
repeat
if #data.text <= max then
table.insert(lines, data)
break
elseif data.text:sub(max+1, max+1) == ' ' then
table.insert(lines, data:sub(index, max))
data = data:sub(max + 2)
else
local x = data.text:sub(1, max)
local s = x:match('(.*) ') or x
table.insert(lines, data:sub(1, #s))
data = data:sub(#s + 1)
end
local t = data.text:match('^%s*(.*)')
local spaces = #data.text - #t
if spaces > 0 then
data = data:sub(spaces + 1)
end
until not data.text or #data.text == 0
return lines
end
-- convert a string of text to blit format doing color conversion
-- and processing ansi color sequences
function Blit.toblit(str, cs)
local text, fg, bg = '', '', ''
if not cs.cbg then
-- reset colors
cs.rbg = cs.palette[cs.bg or colors.black]
cs.rfg = cs.palette[cs.fg or colors.white]
-- current colors
cs.cbg = cs.rbg
cs.cfg = cs.rfg
end
str = str:gsub('(.-)\027%[([%d;]+)m',
function(k, seq)
text = text .. k
bg = bg .. string.rep(cs.cbg, #k)
fg = fg .. string.rep(cs.cfg, #k)
for color in string.gmatch(seq, "%d+") do
color = tonumber(color)
if color == 0 then
-- reset to default
cs.cfg = cs.rfg
cs.cbg = cs.rbg
elseif color > 20 then
cs.cbg = string.sub("0123456789abcdef", color - 21, color - 21)
else
cs.cfg = string.sub("0123456789abcdef", color, color)
end
end
return k
end)
local k = str:sub(#text + 1)
return {
text = text .. k,
bg = bg .. string.rep(cs.cbg, #k),
fg = fg .. string.rep(cs.cfg, #k),
}
end
return Blit

View File

@ -71,7 +71,7 @@ end
-- resize the canvas buffer - not the canvas itself -- resize the canvas buffer - not the canvas itself
function Canvas:resizeBuffer(w, h) function Canvas:resizeBuffer(w, h)
for i = #self.lines, h do for i = #self.lines + 1, h do
self.lines[i] = { } self.lines[i] = { }
self:clearLine(i) self:clearLine(i)
end end
@ -297,40 +297,38 @@ function Canvas:applyPalette(palette)
self.palette = palette self.palette = palette
end end
function Canvas:render(device) -- either render directly to the device
local offset = { x = 0, y = 0 } -- or use another canvas as a backing buffer
function Canvas:render(device, doubleBuffer)
-- 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
-- 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.regions = Region.new(self.x, self.y, self.ex, self.ey)
self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }) self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }, doubleBuffer)
-- doubleBuffering to reduce the amount of
-- setCursorPos, blits
if doubleBuffer then
--[[
local drew = false
local bg = _rep(2, device.width)
for k,v in pairs(device.lines) do
if v.dirty then
device.device.setCursorPos(device.x, device.y + k - 1)
device.device.blit(v.text, v.fg, bg)
drew = true
end
end
if drew then
local c = os.clock()
repeat until os.clock()-c > .1
end
]]
for k,v in pairs(device.lines) do
if v.dirty then
device.device.setCursorPos(device.x, device.y + k - 1)
device.device.blit(v.text, v.fg, v.bg)
v.dirty = false
end
end
end
end end
-- regions are comprised of absolute values that correspond to the output device. -- regions are comprised of absolute values that correspond to the output device.
@ -338,7 +336,7 @@ end
-- canvas layer's stacking order is determined by the position within the array. -- 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 -- layers in the beginning of the array are overlayed by layers further down in
-- the array. -- the array.
function Canvas:__renderLayers(device, offset) function Canvas:__renderLayers(device, offset, doubleBuffer)
if self.children then if self.children then
for i = #self.children, 1, -1 do for i = #self.children, 1, -1 do
local canvas = self.children[i] local canvas = self.children[i]
@ -364,7 +362,7 @@ function Canvas:__renderLayers(device, offset)
canvas:__renderLayers(device, { canvas:__renderLayers(device, {
x = canvas.x + offset.x - 1 - (self.offx or 0), x = canvas.x + offset.x - 1 - (self.offx or 0),
y = canvas.y + offset.y - 1 - (self.offy or 0), y = canvas.y + offset.y - 1 - (self.offy or 0),
}) }, doubleBuffer)
end end
canvas.regions = nil canvas.regions = nil
end end
@ -377,19 +375,19 @@ function Canvas:__renderLayers(device, offset)
y = region[2] - offset.y, y = region[2] - offset.y,
ex = region[3] - offset.x, ex = region[3] - offset.x,
ey = region[4] - offset.y }, ey = region[4] - offset.y },
{ x = region[1], y = region[2] }) { x = region[1], y = region[2] }, doubleBuffer)
end end
self.regions = nil self.regions = nil
self:clean() self:clean()
end end
-- performance can probably be improved by using one more buffer tied to the device function Canvas:__blitRect(device, src, tgt, doubleBuffer)
function Canvas:__blitRect(device, src, tgt)
src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 } src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
tgt = tgt or self tgt = tgt or self
-- for visualizing updates on the screen -- for visualizing updates on the screen
--[[
if Canvas.__visualize or self.visualize then if Canvas.__visualize or self.visualize then
local drew local drew
local t = _rep(' ', src.ex-src.x + 1) local t = _rep(' ', src.ex-src.x + 1)
@ -407,6 +405,7 @@ function Canvas:__blitRect(device, src, tgt)
repeat until os.clock()-c > .03 repeat until os.clock()-c > .03
end end
end end
]]
for i = 0, src.ey - src.y do for i = 0, src.ey - src.y do
local line = self.lines[src.y + i + (self.offy or 0)] local line = self.lines[src.y + i + (self.offy or 0)]
if line and line.dirty then if line and line.dirty then
@ -416,8 +415,13 @@ function Canvas:__blitRect(device, src, tgt)
fg = _sub(fg, src.x, src.ex) fg = _sub(fg, src.x, src.ex)
bg = _sub(bg, src.x, src.ex) bg = _sub(bg, src.x, src.ex)
end end
device.setCursorPos(tgt.x, tgt.y + i) if doubleBuffer then
device.blit(t, fg, bg) Canvas.blit(device, tgt.x, tgt.y + i,
t, bg, fg)
else
device.setCursorPos(tgt.x, tgt.y + i)
device.blit(t, fg, bg)
end
end end
end end
end end

View File

@ -7,8 +7,7 @@ UI.Dialog = class(UI.SlideOut)
UI.Dialog.defaults = { UI.Dialog.defaults = {
UIElement = 'Dialog', UIElement = 'Dialog',
height = 7, height = 7,
textColor = colors.black, noFill = true,
backgroundColor = colors.white,
okEvent ='dialog_ok', okEvent ='dialog_ok',
cancelEvent = 'dialog_cancel', cancelEvent = 'dialog_cancel',
} }
@ -17,19 +16,6 @@ function UI.Dialog:postInit()
self.titleBar = UI.TitleBar({ event = self.cancelEvent, title = self.title }) self.titleBar = UI.TitleBar({ event = self.cancelEvent, title = self.title })
end end
function UI.Dialog:show(...)
local canvas = self.parent
self.oldPalette = canvas.palette
canvas:applyPalette(self.darkPalette)
UI.SlideOut.show(self, ...)
end
function UI.Dialog:hide(...)
self.parent.palette = self.oldPalette
UI.SlideOut.hide(self, ...)
self.parent:draw()
end
function UI.Dialog:eventHandler(event) function UI.Dialog:eventHandler(event)
if event.type == 'dialog_cancel' then if event.type == 'dialog_cancel' then
self:hide() self:hide()

View File

@ -1,4 +1,5 @@
local class = require('opus.class') local class = require('opus.class')
local Event = require('opus.event')
local Terminal = require('opus.terminal') local Terminal = require('opus.terminal')
local UI = require('opus.ui') local UI = require('opus.ui')
@ -19,10 +20,16 @@ function UI.Embedded:layout()
UI.Window.layout(self) UI.Window.layout(self)
if not self.win then if not self.win then
local t
function self.render() function self.render()
self:sync() if not t then
if self.focused then t = Event.onTimeout(0, function()
self:setCursorPos(self.win.getCursorPos()) t = nil
if self.focused then
self:setCursorPos(self.win.getCursorPos())
end
self:sync()
end)
end end
end end
self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false) self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false)
@ -68,7 +75,6 @@ function UI.Embedded:eventHandler(event)
end end
function UI.Embedded.example() function UI.Embedded.example()
local Event = require('opus.event')
local Util = require('opus.util') local Util = require('opus.util')
local term = _G.term local term = _G.term

View File

@ -93,7 +93,8 @@ function UI.MenuBar.example()
return UI.MenuBar { return UI.MenuBar {
buttons = { buttons = {
{ text = 'Choice1', event = 'event1' }, { text = 'Choice1', event = 'event1' },
{ text = 'Choice2', event = 'event2' }, { text = 'Choice2', event = 'event2', inactive = true },
{ text = 'Choice3', event = 'event3' },
} }
} }
end end

View File

@ -5,4 +5,5 @@ UI.MenuItem = class(UI.FlatButton)
UI.MenuItem.defaults = { UI.MenuItem.defaults = {
UIElement = 'MenuItem', UIElement = 'MenuItem',
noPadding = false, noPadding = false,
textInactiveColor = colors.gray,
} }

View File

@ -20,7 +20,7 @@ UI.Page.defaults = {
textColor = colors.white, textColor = colors.white,
} }
function UI.Page:postInit() function UI.Page:postInit()
self.parent = self.parent or UI.defaultDevice self.parent = self.parent or UI.term
self.__target = self self.__target = self
end end

View File

@ -22,6 +22,9 @@ function UI.ScrollBar:draw()
self:clear() self:clear()
-- ...
self:write(1, 1, ' ', view.fill)
if view.totalHeight > view.height then if view.totalHeight > view.height then
local maxScroll = view.totalHeight - view.height local maxScroll = view.totalHeight - view.height
local percent = view.offsetY / maxScroll local percent = view.offsetY / maxScroll

View File

@ -29,6 +29,7 @@ function UI.ScrollingGrid:getViewArea()
height = self.pageSize, -- viewable height height = self.pageSize, -- viewable height
totalHeight = Util.size(self.values), -- total height totalHeight = Util.size(self.values), -- total height
offsetY = self.scrollOffset, -- scroll offset offsetY = self.scrollOffset, -- scroll offset
fill = not self.disableHeader and self.headerBackgroundColor,
} }
end end

View File

@ -6,7 +6,7 @@ UI.TabBar = class(UI.MenuBar)
UI.TabBar.defaults = { UI.TabBar.defaults = {
UIElement = 'TabBar', UIElement = 'TabBar',
buttonClass = 'TabBarMenuItem', buttonClass = 'TabBarMenuItem',
backgroundColor = UI.colors.tertiary, backgroundColor = colors.black,
selectedBackgroundColor = UI.colors.primary, selectedBackgroundColor = UI.colors.primary,
unselectedBackgroundColor = UI.colors.tertiary, unselectedBackgroundColor = UI.colors.tertiary,
} }
@ -32,7 +32,7 @@ function UI.TabBar:eventHandler(event)
self:emit({ type = 'tab_change', current = si, last = pi, tab = selected }) self:emit({ type = 'tab_change', current = si, last = pi, tab = selected })
end end
end end
UI.MenuBar.draw(self) self:draw(self)
end end
return UI.MenuBar.eventHandler(self, event) return UI.MenuBar.eventHandler(self, event)
end end

View File

@ -5,6 +5,7 @@ UI.TabBarMenuItem = class(UI.Button)
UI.TabBarMenuItem.defaults = { UI.TabBarMenuItem.defaults = {
UIElement = 'TabBarMenuItem', UIElement = 'TabBarMenuItem',
event = 'tab_select', event = 'tab_select',
textInactiveColor = colors.lightGray,
} }
function UI.TabBarMenuItem:draw() function UI.TabBarMenuItem:draw()
if self.selected then if self.selected then

View File

@ -117,5 +117,9 @@ function UI.Tabs.example()
index = 3, index = 3,
tabTitle = 'tab3', tabTitle = 'tab3',
}, },
enable = function(self)
UI.Tabs.enable(self)
self:setActive(self.tab3, false)
end,
} }
end end

View File

@ -20,7 +20,6 @@ end
function UI.TextArea:draw() function UI.TextArea:draw()
self:clear() self:clear()
self.cursorX, self.cursorY = 1, 1
self:print(self.value) self:print(self.value)
self:drawChildren() self:drawChildren()
end end

View File

@ -47,11 +47,18 @@ function UI.Viewport:setScrollPosition(offy, offx) -- argh - reverse
end end
end end
function UI.Viewport:write(x, y, text, bg, tc) function UI.Viewport:blit(x, y, text, bg, fg)
if y > #self.lines then if y > #self.lines then
self:resizeBuffer(self.width, y) self:resizeBuffer(self.width, y)
end end
return UI.Window.write(self, x, y, text, bg, tc) return UI.Window.blit(self, x, y, text, bg, fg)
end
function UI.Viewport:write(x, y, text, bg, fg)
if y > #self.lines then
self:resizeBuffer(self.width, y)
end
return UI.Window.write(self, x, y, text, bg, fg)
end end
function UI.Viewport:setViewHeight(h) function UI.Viewport:setViewHeight(h)

View File

@ -699,6 +699,31 @@ local function paragraphwrap(text, linewidth, res)
end end
-- end word wrapping -- end word wrapping
--[[
-- better wrapping - needs further testing before replacing the current wrapping
functions
local function wrap(text, max)
local index = 1
local lines = { }
repeat
if #text <= max then
table.insert(lines, text)
text = ''
elseif text:sub(max+1, max+1) == ' ' then
table.insert(lines, text:sub(index, max))
text = text:sub(max + 2)
else
local x = text:sub(1, max)
local s = x:match('(.*) ') or x
text = text:sub(#s + 1)
table.insert(lines, s)
end
text = text:match('^%s*(.*)')
until not text or #text == 0
return lines
end
]]
function Util.wordWrap(str, limit) function Util.wordWrap(str, limit)
local longLines = Util.split(str) local longLines = Util.split(str)
local lines = { } local lines = { }