Ui enhancements 2.0 (#31)
* canvas overhaul * minor tweaks * list mode for overview * bugfixes + tweaks for editor 2.0 * minor tweaks * more editor work * refactor + new transitions * use layout() where appropriate and cleanup * mouse triple click + textEntry scroll ind * cleanup * cleanup + theme editor * color rework + cleanup * changes for deprecated ui methods * can now use named colors
This commit was merged in pull request #31.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
local Util = require('opus.util')
|
||||
|
||||
local Array = { }
|
||||
|
||||
function Array.filter(it, f)
|
||||
@@ -14,9 +16,11 @@ 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
|
||||
|
||||
Array.find = Util.find
|
||||
|
||||
return Array
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local shell = _ENV.shell
|
||||
|
||||
local Config = { }
|
||||
|
||||
@@ -25,23 +24,6 @@ function Config.load(fname, data)
|
||||
return data
|
||||
end
|
||||
|
||||
function Config.loadWithCheck(fname, data)
|
||||
local filename = 'usr/config/' .. fname
|
||||
|
||||
if not fs.exists(filename) then
|
||||
Config.load(fname, data)
|
||||
print()
|
||||
print('The configuration file has been created.')
|
||||
print('The file name is: ' .. filename)
|
||||
print()
|
||||
_G.printError('Press enter to configure')
|
||||
_G.read()
|
||||
shell.run('edit ' .. filename)
|
||||
end
|
||||
|
||||
return Config.load(fname, data)
|
||||
end
|
||||
|
||||
function Config.update(fname, data)
|
||||
local filename = 'usr/config/' .. fname
|
||||
Util.writeTable(filename, data)
|
||||
|
||||
@@ -41,9 +41,9 @@ end
|
||||
|
||||
function Entry:updateScroll()
|
||||
local ps = self.scroll
|
||||
local value = _val(self.value)
|
||||
if self.pos > #value then
|
||||
self.pos = #value
|
||||
local len = #_val(self.value)
|
||||
if self.pos > len then
|
||||
self.pos = len
|
||||
self.scroll = 0 -- ??
|
||||
end
|
||||
if self.pos - self.scroll > self.width then
|
||||
@@ -51,6 +51,11 @@ function Entry:updateScroll()
|
||||
elseif self.pos < self.scroll then
|
||||
self.scroll = self.pos
|
||||
end
|
||||
if self.scroll > 0 then
|
||||
if self.scroll + self.width > len then
|
||||
self.scroll = len - self.width
|
||||
end
|
||||
end
|
||||
if ps ~= self.scroll then
|
||||
self.textChanged = true
|
||||
end
|
||||
@@ -217,6 +222,10 @@ function Entry:paste(ie)
|
||||
end
|
||||
end
|
||||
|
||||
function Entry:forcePaste()
|
||||
os.queueEvent('clipboard_paste')
|
||||
end
|
||||
|
||||
function Entry:clearLine()
|
||||
if #_val(self.value) > 0 then
|
||||
self:reset()
|
||||
@@ -363,9 +372,10 @@ local mappings = {
|
||||
--[ 'control-d' ] = Entry.cutNextWord,
|
||||
[ 'control-x' ] = Entry.cut,
|
||||
[ 'paste' ] = Entry.paste,
|
||||
-- [ 'control-y' ] = Entry.paste, -- well this won't work...
|
||||
[ 'control-y' ] = Entry.forcePaste, -- well this won't work...
|
||||
|
||||
[ 'mouse_doubleclick' ] = Entry.markWord,
|
||||
[ 'mouse_tripleclick' ] = Entry.markAll,
|
||||
[ 'shift-left' ] = Entry.markLeft,
|
||||
[ 'shift-right' ] = Entry.markRight,
|
||||
[ 'mouse_down' ] = Entry.markAnchor,
|
||||
|
||||
21
sys/modules/opus/fuzzy.lua
Normal file
21
sys/modules/opus/fuzzy.lua
Normal file
@@ -0,0 +1,21 @@
|
||||
-- Based on Squid's fuzzy search
|
||||
-- https://github.com/SquidDev-CC/artist/blob/vnext/artist/lib/match.lua
|
||||
--
|
||||
-- not very fuzzy anymore
|
||||
|
||||
local SCORE_WEIGHT = 1000
|
||||
local LEADING_LETTER_PENALTY = -30
|
||||
local LEADING_LETTER_PENALTY_MAX = -90
|
||||
|
||||
local _find = string.find
|
||||
local _max = math.max
|
||||
|
||||
return function(str, pattern)
|
||||
local start = _find(str, pattern, 1, true)
|
||||
if start then
|
||||
-- All letters before the current one are considered leading, so add them to our penalty
|
||||
return SCORE_WEIGHT
|
||||
+ _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
|
||||
- (#str - #pattern)
|
||||
end
|
||||
end
|
||||
@@ -24,9 +24,9 @@ function git.list(repository)
|
||||
|
||||
local function getContents()
|
||||
local dataUrl = string.format(TREE_URL, user, repo, branch)
|
||||
local contents, msg = Util.httpGet(dataUrl,TREE_HEADERS)
|
||||
local contents, msg = Util.httpGet(dataUrl, TREE_HEADERS)
|
||||
if not contents then
|
||||
error(_sformat('Failed to download %s\n%s', dataUrl, msg), 2)
|
||||
error(string.format('Failed to download %s\n%s', dataUrl, msg), 2)
|
||||
else
|
||||
return json.decode(contents)
|
||||
end
|
||||
|
||||
@@ -65,7 +65,7 @@ function GPS.locate(timeout, debug)
|
||||
if debug then
|
||||
print("Position is "..pos.x..","..pos.y..","..pos.z)
|
||||
end
|
||||
return vector.new(pos.x, pos.y, pos.z)
|
||||
return pos and vector.new(pos.x, pos.y, pos.z)
|
||||
end
|
||||
|
||||
function GPS.isAvailable()
|
||||
|
||||
@@ -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
|
||||
@@ -141,18 +146,26 @@ function input:translate(event, code, p1, p2)
|
||||
p1 == self.x and p2 == self.y and
|
||||
(clock - self.timer < .5) then
|
||||
|
||||
self.mch = 'mouse_doubleclick'
|
||||
self.timer = nil
|
||||
self.clickCount = self.clickCount + 1
|
||||
if self.clickCount == 3 then
|
||||
self.mch = 'mouse_tripleclick'
|
||||
self.timer = nil
|
||||
self.clickCount = 1
|
||||
else
|
||||
self.mch = 'mouse_doubleclick'
|
||||
end
|
||||
else
|
||||
self.timer = os.clock()
|
||||
self.x = p1
|
||||
self.y = p2
|
||||
self.clickCount = 1
|
||||
end
|
||||
self.mfired = input:toCode(self.mch, 255)
|
||||
else
|
||||
self.mch = 'mouse_up'
|
||||
self.mfired = input:toCode(self.mch, 255)
|
||||
end
|
||||
|
||||
return {
|
||||
code = self.mfired,
|
||||
button = code,
|
||||
|
||||
@@ -1,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
|
||||
|
||||
@@ -36,61 +36,66 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
local maxScroll = 100
|
||||
local cx, cy = 1, 1
|
||||
local blink = false
|
||||
local bg, fg = parent.getBackgroundColor(), parent.getTextColor()
|
||||
local _bg, _fg = parent.getBackgroundColor(), parent.getTextColor()
|
||||
|
||||
local canvas = Canvas({
|
||||
win.canvas = Canvas({
|
||||
x = sx,
|
||||
y = sy,
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = parent.isColor(),
|
||||
offy = 0,
|
||||
bg = _bg,
|
||||
fg = _fg,
|
||||
})
|
||||
|
||||
win.canvas = canvas
|
||||
|
||||
local function update()
|
||||
if isVisible then
|
||||
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, win.canvas.bg, win.canvas.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()
|
||||
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)
|
||||
win.setCursorPos(cx, cy)
|
||||
update()
|
||||
end
|
||||
@@ -102,10 +107,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,12 +123,12 @@ 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
|
||||
|
||||
function win.setTextColor(c)
|
||||
fg = c
|
||||
win.canvas.fg = c
|
||||
end
|
||||
win.setTextColour = win.setTextColor
|
||||
|
||||
@@ -139,38 +148,38 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
win.setPaletteColour = win.setPaletteColor
|
||||
|
||||
function win.setBackgroundColor(c)
|
||||
bg = c
|
||||
win.canvas.bg = c
|
||||
end
|
||||
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)
|
||||
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
|
||||
|
||||
function win.getTextColor()
|
||||
return fg
|
||||
return win.canvas.fg
|
||||
end
|
||||
win.getTextColour = win.getTextColor
|
||||
|
||||
function win.getBackgroundColor()
|
||||
return bg
|
||||
return win.canvas.bg
|
||||
end
|
||||
win.getBackgroundColour = win.getBackgroundColor
|
||||
|
||||
@@ -178,7 +187,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 +195,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
|
||||
function win.redraw()
|
||||
if isVisible then
|
||||
canvas:dirty()
|
||||
win.canvas:dirty()
|
||||
update()
|
||||
end
|
||||
end
|
||||
@@ -194,27 +203,27 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
function win.restoreCursor()
|
||||
if isVisible then
|
||||
win.setCursorPos(cx, cy)
|
||||
win.setTextColor(fg)
|
||||
win.setTextColor(win.canvas.fg)
|
||||
win.setCursorBlink(blink)
|
||||
end
|
||||
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 +231,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 +239,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
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
local Array = require('opus.array')
|
||||
local Blit = require('opus.ui.blit')
|
||||
local Canvas = require('opus.ui.canvas')
|
||||
local class = require('opus.class')
|
||||
local Event = require('opus.event')
|
||||
local Input = require('opus.input')
|
||||
@@ -5,7 +8,6 @@ local Transition = require('opus.ui.transition')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
local fs = _G.fs
|
||||
@@ -32,11 +34,16 @@ local textutils = _G.textutils
|
||||
]]
|
||||
|
||||
--[[-- Top Level Manager --]]--
|
||||
local Manager = class()
|
||||
function Manager:init()
|
||||
local UI = { }
|
||||
function UI:init()
|
||||
self.devices = { }
|
||||
self.theme = { }
|
||||
self.extChars = Util.getVersion() >= 1.76
|
||||
self.colors = {
|
||||
primary = colors.green,
|
||||
secondary = colors.lightGray,
|
||||
tertiary = colors.gray,
|
||||
}
|
||||
|
||||
local function keyFunction(event, code, held)
|
||||
local ie = Input:translate(event, code, held)
|
||||
@@ -44,8 +51,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
|
||||
@@ -53,10 +59,7 @@ function Manager:init()
|
||||
local function resize(_, side)
|
||||
local dev = self.devices[side or 'terminal']
|
||||
if dev and dev.currentPage then
|
||||
-- the parent doesn't have any children set...
|
||||
-- that's why we have to resize both the parent and the current page
|
||||
-- kinda makes sense
|
||||
dev.currentPage.parent:resize()
|
||||
dev:resize()
|
||||
|
||||
dev.currentPage:resize()
|
||||
dev.currentPage:draw()
|
||||
@@ -72,17 +75,14 @@ function Manager:init()
|
||||
monitor_resize = resize,
|
||||
|
||||
mouse_scroll = function(_, direction, x, y)
|
||||
local ie = Input:translate('mouse_scroll', direction, x, y)
|
||||
|
||||
local currentPage = self:getActivePage()
|
||||
if currentPage then
|
||||
local event = currentPage:pointToChild(x, y)
|
||||
local directions = {
|
||||
[ -1 ] = 'up',
|
||||
[ 1 ] = 'down'
|
||||
}
|
||||
-- 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.type = ie.code
|
||||
event.ie = { code = ie.code, x = event.x, y = event.y }
|
||||
event.element:emit(event)
|
||||
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,
|
||||
|
||||
@@ -156,7 +156,7 @@ function Manager:init()
|
||||
end)
|
||||
end
|
||||
|
||||
function Manager:configure(appName, ...)
|
||||
function UI:configure(appName, ...)
|
||||
local defaults = Util.loadTable('usr/config/' .. appName) or { }
|
||||
if not defaults.device then
|
||||
defaults.device = { }
|
||||
@@ -190,19 +190,15 @@ function Manager:configure(appName, ...)
|
||||
end
|
||||
|
||||
if defaults.theme then
|
||||
for k,v in pairs(defaults.theme) do
|
||||
if self[k] and self[k].defaults then
|
||||
Util.merge(self[k].defaults, v)
|
||||
end
|
||||
end
|
||||
Util.deepMerge(self.theme, defaults.theme)
|
||||
end
|
||||
end
|
||||
|
||||
function Manager:disableEffects()
|
||||
self.defaultDevice.effectsEnabled = false
|
||||
function UI:disableEffects()
|
||||
self.term.effectsEnabled = false
|
||||
end
|
||||
|
||||
function Manager:loadTheme(filename)
|
||||
function UI:loadTheme(filename)
|
||||
if fs.exists(filename) then
|
||||
local theme, err = Util.loadTable(filename)
|
||||
if not theme then
|
||||
@@ -212,8 +208,20 @@ function Manager:loadTheme(filename)
|
||||
end
|
||||
end
|
||||
|
||||
function Manager:generateTheme(filename)
|
||||
function UI:generateTheme(filename)
|
||||
local t = { }
|
||||
|
||||
local function getName(d)
|
||||
if type(d) == 'string' then
|
||||
return string.format("'%s'", d)
|
||||
end
|
||||
for c, n in pairs(colors) do
|
||||
if n == d then
|
||||
return 'colors.' .. c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k,v in pairs(self) do
|
||||
if type(v) == 'table' then
|
||||
if v._preload then
|
||||
@@ -226,72 +234,96 @@ function Manager:generateTheme(filename)
|
||||
if not t[k] then
|
||||
t[k] = { }
|
||||
end
|
||||
for c, n in pairs(colors) do
|
||||
if n == d then
|
||||
t[k][p] = 'colors.' .. c
|
||||
break
|
||||
end
|
||||
end
|
||||
t[k][p] = getName(d)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
t.colors = {
|
||||
primary = getName(self.colors.primary),
|
||||
secondary = getName(self.colors.secondary),
|
||||
tertiary = getName(self.colors.tertiary),
|
||||
}
|
||||
Util.writeFile(filename, textutils.serialize(t):gsub('(")', ''))
|
||||
end
|
||||
|
||||
function Manager:emitEvent(event)
|
||||
function UI:emitEvent(event)
|
||||
local currentPage = self:getActivePage()
|
||||
if currentPage and currentPage.focused then
|
||||
return currentPage.focused:emit(event)
|
||||
end
|
||||
end
|
||||
|
||||
function Manager:inputEvent(parent, event) -- deprecate ?
|
||||
return parent and parent:emit(event)
|
||||
end
|
||||
function UI: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
|
||||
local function getPosition(element, x, y)
|
||||
repeat
|
||||
x = x - element.x + 1
|
||||
y = y - element.y + 1
|
||||
element = element.parent
|
||||
until not element
|
||||
return x, y
|
||||
end
|
||||
|
||||
if code == 'mouse_doubleclick' then
|
||||
if self.doubleClickElement ~= clickEvent.element then
|
||||
local x, y = getPosition(self.lastClicked, ie.x, ie.y)
|
||||
|
||||
clickEvent = {
|
||||
element = self.lastClicked,
|
||||
x = x,
|
||||
y = y,
|
||||
dx = ie.dx,
|
||||
dy = ie.dy,
|
||||
}
|
||||
else
|
||||
clickEvent = target:pointToChild(ie.x, ie.y)
|
||||
end
|
||||
|
||||
-- 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
|
||||
|
||||
function Manager:setDefaultDevice(dev)
|
||||
self.defaultDevice = dev
|
||||
function UI:setDefaultDevice(dev)
|
||||
self.term = dev
|
||||
end
|
||||
|
||||
function Manager:addPage(name, page)
|
||||
function UI:addPage(name, page)
|
||||
if not self.pages then
|
||||
self.pages = { }
|
||||
end
|
||||
self.pages[name] = page
|
||||
end
|
||||
|
||||
function Manager:setPages(pages)
|
||||
function UI:setPages(pages)
|
||||
self.pages = pages
|
||||
end
|
||||
|
||||
function Manager:getPage(pageName)
|
||||
function UI:getPage(pageName)
|
||||
local page = self.pages[pageName]
|
||||
|
||||
if not page then
|
||||
@@ -301,19 +333,18 @@ function Manager:getPage(pageName)
|
||||
return page
|
||||
end
|
||||
|
||||
function Manager:getActivePage(page)
|
||||
function UI:getActivePage(page)
|
||||
if page then
|
||||
return page.parent.currentPage
|
||||
end
|
||||
return self.defaultDevice.currentPage
|
||||
return self.term.currentPage
|
||||
end
|
||||
|
||||
function Manager:setActivePage(page)
|
||||
function UI:setActivePage(page)
|
||||
page.parent.currentPage = page
|
||||
page.parent.canvas = page.canvas
|
||||
end
|
||||
|
||||
function Manager:setPage(pageOrName, ...)
|
||||
function UI:setPage(pageOrName, ...)
|
||||
local page = pageOrName
|
||||
|
||||
if type(pageOrName) == 'string' then
|
||||
@@ -333,7 +364,6 @@ function Manager:setPage(pageOrName, ...)
|
||||
page.previousPage = currentPage
|
||||
end
|
||||
self:setActivePage(page)
|
||||
--page:clear(page.backgroundColor)
|
||||
page:enable(...)
|
||||
page:draw()
|
||||
if page.focused then
|
||||
@@ -344,27 +374,27 @@ function Manager:setPage(pageOrName, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Manager:getCurrentPage()
|
||||
return self.defaultDevice.currentPage
|
||||
function UI:getCurrentPage()
|
||||
return self.term.currentPage
|
||||
end
|
||||
|
||||
function Manager:setPreviousPage()
|
||||
if self.defaultDevice.currentPage.previousPage then
|
||||
local previousPage = self.defaultDevice.currentPage.previousPage.previousPage
|
||||
self:setPage(self.defaultDevice.currentPage.previousPage)
|
||||
self.defaultDevice.currentPage.previousPage = previousPage
|
||||
function UI:setPreviousPage()
|
||||
if self.term.currentPage.previousPage then
|
||||
local previousPage = self.term.currentPage.previousPage.previousPage
|
||||
self:setPage(self.term.currentPage.previousPage)
|
||||
self.term.currentPage.previousPage = previousPage
|
||||
end
|
||||
end
|
||||
|
||||
function Manager:getDefaults(element, args)
|
||||
function UI:getDefaults(element, args)
|
||||
local defaults = Util.deepCopy(element.defaults)
|
||||
if args then
|
||||
Manager:mergeProperties(defaults, args)
|
||||
UI:mergeProperties(defaults, args)
|
||||
end
|
||||
return defaults
|
||||
end
|
||||
|
||||
function Manager:mergeProperties(obj, args)
|
||||
function UI:mergeProperties(obj, args)
|
||||
if args then
|
||||
for k,v in pairs(args) do
|
||||
if k == 'accelerators' then
|
||||
@@ -380,7 +410,7 @@ function Manager:mergeProperties(obj, args)
|
||||
end
|
||||
end
|
||||
|
||||
function Manager:pullEvents(...)
|
||||
function UI:pullEvents(...)
|
||||
local s, m = pcall(Event.pullEvents, ...)
|
||||
self.term:reset()
|
||||
if not s and m then
|
||||
@@ -388,21 +418,20 @@ function Manager:pullEvents(...)
|
||||
end
|
||||
end
|
||||
|
||||
Manager.exitPullEvents = Event.exitPullEvents
|
||||
Manager.quit = Event.exitPullEvents
|
||||
Manager.start = Manager.pullEvents
|
||||
UI.exitPullEvents = Event.exitPullEvents
|
||||
UI.quit = Event.exitPullEvents
|
||||
UI.start = UI.pullEvents
|
||||
|
||||
local UI = Manager()
|
||||
UI:init()
|
||||
|
||||
--[[-- Basic drawable area --]]--
|
||||
UI.Window = class()
|
||||
UI.Window = class(Canvas)
|
||||
UI.Window.uid = 1
|
||||
UI.Window.docs = { }
|
||||
UI.Window.defaults = {
|
||||
UIElement = 'Window',
|
||||
x = 1,
|
||||
y = 1,
|
||||
-- z = 0, -- eventually...
|
||||
offx = 0,
|
||||
offy = 0,
|
||||
cursorX = 1,
|
||||
@@ -413,7 +442,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 ~= Canvas then
|
||||
defaults = UI:getDefaults(m, defaults)
|
||||
end
|
||||
m = m._base
|
||||
until not m
|
||||
UI:mergeProperties(self, defaults)
|
||||
@@ -438,6 +469,9 @@ function UI.Window:init(args)
|
||||
until not m
|
||||
end
|
||||
|
||||
UI.Window.docs.postInit = [[postInit(VOID)
|
||||
Called once the window has all the properties set.
|
||||
Override to calculate properties or to dynamically add children]]
|
||||
function UI.Window:postInit()
|
||||
if self.parent then
|
||||
-- this will cascade down the whole tree of elements starting at the
|
||||
@@ -531,47 +565,60 @@ function UI.Window:layout()
|
||||
if not self.height then
|
||||
self.height = self.parent.height - self.y + 1
|
||||
end
|
||||
|
||||
self.width = math.max(self.width, 1)
|
||||
self.height = math.max(self.height, 1)
|
||||
|
||||
self:reposition(self.x, self.y, self.width, self.height)
|
||||
end
|
||||
|
||||
-- Called when the window's parent has be assigned
|
||||
function UI.Window:setParent()
|
||||
self.oh, self.ow = self.height, self.width
|
||||
self.ox, self.oy = self.x, self.y
|
||||
self.oex, self.oey = self.ex, self.ey
|
||||
|
||||
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
|
||||
|
||||
function UI.Window:resize()
|
||||
self.height, self.width = self.oh, self.ow
|
||||
self.x, self.y = self.ox, self.oy
|
||||
self.ex, self.ey = self.oex, self.oey
|
||||
|
||||
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
|
||||
|
||||
UI.Window.docs.raise = [[raise(VOID)
|
||||
Raise this window to the top]]
|
||||
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 +631,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
|
||||
@@ -595,18 +656,20 @@ function UI.Window:setCursorPos(x, y)
|
||||
end
|
||||
|
||||
function UI.Window:setCursorBlink(blink)
|
||||
self.parent:setCursorBlink(blink)
|
||||
self.cursorBlink = blink
|
||||
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
|
||||
@@ -633,19 +696,38 @@ function UI.Window:sync()
|
||||
end
|
||||
|
||||
function UI.Window:enable(...)
|
||||
self.enabled = true
|
||||
if self.children then
|
||||
for _,child in pairs(self.children) do
|
||||
child:enable(...)
|
||||
if not self.enabled then
|
||||
self.enabled = true
|
||||
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
|
||||
if not child.enabled then
|
||||
child:enable(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Window:disable()
|
||||
self.enabled = false
|
||||
if self.children then
|
||||
for _,child in pairs(self.children) do
|
||||
child:disable()
|
||||
if self.enabled then
|
||||
self.enabled = false
|
||||
self.parent:dirty(true)
|
||||
|
||||
if self.modal then
|
||||
self:release(self)
|
||||
end
|
||||
|
||||
for child in self:eachChild() do
|
||||
if child.enabled then
|
||||
child:disable()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -656,13 +738,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,39 +748,28 @@ 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)
|
||||
if #text >= self.width then
|
||||
self:write(1, y, text, bg, fg)
|
||||
else
|
||||
local space = math.floor((self.width-#text) / 2)
|
||||
local filler = _rep(' ', space + 1)
|
||||
local str = _sub(filler, 1, space) .. text
|
||||
str = str .. _sub(filler, self.width - #str + 1)
|
||||
self:write(1, y, str, bg, fg)
|
||||
local x = math.floor((self.width-#text) / 2) + 1
|
||||
self:write(x, y, text, bg, fg)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -710,89 +777,19 @@ function UI.Window:print(text, bg, fg)
|
||||
local marginLeft = self.marginLeft or 0
|
||||
local marginRight = self.marginRight or 0
|
||||
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 result = { line:find("(%w+)", cx) }
|
||||
if #result > 1 and result[2] > cx then
|
||||
return _sub(line, cx, result[2] + 1)
|
||||
elseif #result > 0 and result[1] == cx then
|
||||
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)
|
||||
local y = (self.marginTop or 0) + 1
|
||||
for _,line in pairs(Util.split(text)) do
|
||||
for _, ln in ipairs(Blit(line, cs):wrap(width)) do
|
||||
self:blit(marginLeft + 1, y, ln.text, ln.bg, ln.fg)
|
||||
y = y + 1
|
||||
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
|
||||
|
||||
UI.Window.docs.focus = [[focus(VOID)
|
||||
@@ -822,11 +819,11 @@ function UI.Window:release(child)
|
||||
end
|
||||
|
||||
function UI.Window:pointToChild(x, y)
|
||||
-- TODO: get rid of this offx/y mess and scroll canvas instead
|
||||
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
|
||||
@@ -868,7 +865,7 @@ function UI.Window:getFocusables()
|
||||
end
|
||||
|
||||
if self.children then
|
||||
getFocusable(self, self.x, self.y)
|
||||
getFocusable(self)
|
||||
end
|
||||
|
||||
return focusable
|
||||
@@ -882,36 +879,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,45 +910,8 @@ 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.width = self.width
|
||||
args.height = self.height
|
||||
end
|
||||
|
||||
args.canvas = args.canvas or self.canvas
|
||||
self.parent:addTransition(effect, args)
|
||||
end
|
||||
function UI.Window:addTransition(effect, args, canvas)
|
||||
self.parent:addTransition(effect, args, canvas or self)
|
||||
end
|
||||
|
||||
function UI.Window:emit(event)
|
||||
@@ -991,7 +943,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 +971,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 +988,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)
|
||||
@@ -1046,7 +1003,6 @@ end
|
||||
|
||||
function UI.Device:setCursorBlink(blink)
|
||||
self.cursorBlink = blink
|
||||
self.device.setCursorBlink(blink)
|
||||
end
|
||||
|
||||
function UI.Device:setTextScale(textScale)
|
||||
@@ -1061,27 +1017,30 @@ function UI.Device:reset()
|
||||
self.device.setCursorPos(1, 1)
|
||||
end
|
||||
|
||||
function UI.Device:addTransition(effect, args)
|
||||
function UI.Device:addTransition(effect, args, canvas)
|
||||
if not self.transitions then
|
||||
self.transitions = { }
|
||||
end
|
||||
|
||||
args = args or { }
|
||||
args.ex = args.x + args.width - 1
|
||||
args.ey = args.y + args.height - 1
|
||||
args.canvas = args.canvas or self.canvas
|
||||
|
||||
if type(effect) == 'string' then
|
||||
effect = Transition[effect]
|
||||
if not effect then
|
||||
error('Invalid transition')
|
||||
effect = Transition[effect] or error('Invalid transition')
|
||||
end
|
||||
|
||||
-- there can be only one
|
||||
for k,v in pairs(self.transitions) do
|
||||
if v.canvas == canvas then
|
||||
table.remove(self.transitions, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(self.transitions, { update = effect(args), args = args })
|
||||
table.insert(self.transitions, { effect = effect, args = args or { }, canvas = canvas })
|
||||
end
|
||||
|
||||
function UI.Device:runTransitions(transitions, canvas)
|
||||
function UI.Device:runTransitions(transitions)
|
||||
for _,k in pairs(transitions) do
|
||||
k.update = k.effect(k.canvas, k.args)
|
||||
end
|
||||
while true do
|
||||
for _,k in ipairs(Util.keys(transitions)) do
|
||||
local transition = transitions[k]
|
||||
@@ -1089,7 +1048,7 @@ function UI.Device:runTransitions(transitions, canvas)
|
||||
transitions[k] = nil
|
||||
end
|
||||
end
|
||||
canvas:render(self.device)
|
||||
self.currentPage:render(self, true)
|
||||
if Util.empty(transitions) then
|
||||
break
|
||||
end
|
||||
@@ -1101,17 +1060,19 @@ function UI.Device:sync()
|
||||
local transitions = self.effectsEnabled and self.transitions
|
||||
self.transitions = nil
|
||||
|
||||
if self:getCursorBlink() then
|
||||
self.device.setCursorBlink(false)
|
||||
end
|
||||
self.device.setCursorBlink(false)
|
||||
|
||||
self.canvas:render(self.device)
|
||||
if transitions then
|
||||
self:runTransitions(transitions, self.canvas)
|
||||
self:runTransitions(transitions)
|
||||
else
|
||||
self.currentPage:render(self, true)
|
||||
end
|
||||
|
||||
if self:getCursorBlink() then
|
||||
self.device.setCursorPos(self.cursorX, self.cursorY)
|
||||
if self.isColor then
|
||||
self.device.setTextColor(colors.orange)
|
||||
end
|
||||
self.device.setCursorBlink(true)
|
||||
end
|
||||
end
|
||||
@@ -1143,7 +1104,7 @@ local function loadComponents()
|
||||
return self(...)
|
||||
end
|
||||
})
|
||||
UI[name]._preload = function(self)
|
||||
UI[name]._preload = function()
|
||||
return load(name)
|
||||
end
|
||||
end
|
||||
@@ -1152,6 +1113,12 @@ end
|
||||
loadComponents()
|
||||
UI:loadTheme('usr/config/ui.theme')
|
||||
Util.merge(UI.Window.defaults, UI.theme.Window)
|
||||
UI:setDefaultDevice(UI.Device({ device = term.current() }))
|
||||
Util.merge(UI.colors, UI.theme.colors)
|
||||
UI:setDefaultDevice(UI.Device())
|
||||
|
||||
for k,v in pairs(UI.colors) do
|
||||
Canvas.colorPalette[k] = Canvas.colorPalette[v]
|
||||
Canvas.grayscalePalette[k] = Canvas.grayscalePalette[v]
|
||||
end
|
||||
|
||||
return UI
|
||||
|
||||
174
sys/modules/opus/ui/blit.lua
Normal file
174
sys/modules/opus/ui/blit.lua
Normal file
@@ -0,0 +1,174 @@
|
||||
local colors = _G.colors
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
local Blit = { }
|
||||
|
||||
Blit.colorPalette = { }
|
||||
Blit.grayscalePalette = { }
|
||||
|
||||
for n = 1, 16 do
|
||||
Blit.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
Blit.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
|
||||
end
|
||||
|
||||
-- default palette
|
||||
Blit.palette = Blit.colorPalette
|
||||
|
||||
function Blit:init(t, args)
|
||||
if args then
|
||||
for k,v in pairs(args) do
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
if type(t) == 'string' then
|
||||
-- create a blit from a string
|
||||
self.text, self.bg, self.fg = Blit.toblit(t, args or { })
|
||||
|
||||
elseif type(t) == 'number' then
|
||||
-- create a fixed width blit
|
||||
self.width = t
|
||||
self.text = _rep(' ', self.width)
|
||||
self.bg = _rep(self.palette[args.bg], self.width)
|
||||
self.fg = _rep(self.palette[args.fg], self.width)
|
||||
|
||||
else
|
||||
self.text = t.text
|
||||
self.bg = t.bg
|
||||
self.fg = t.fg
|
||||
end
|
||||
end
|
||||
|
||||
function Blit:write(x, text, bg, fg)
|
||||
self:insert(x, text,
|
||||
bg and _rep(self.palette[bg], #text),
|
||||
fg and _rep(self.palette[fg], #text))
|
||||
end
|
||||
|
||||
function Blit:insert(x, text, bg, fg)
|
||||
if x <= self.width then
|
||||
local width = #text
|
||||
local tx, tex
|
||||
|
||||
if x < 1 then
|
||||
tx = 2 - x
|
||||
width = width + x - 1
|
||||
x = 1
|
||||
end
|
||||
|
||||
if x + width - 1 > self.width then
|
||||
tex = self.width - x + (tx or 1)
|
||||
width = tex - (tx or 1) + 1
|
||||
end
|
||||
|
||||
if width > 0 then
|
||||
local function replace(sstr, rstr)
|
||||
if tx or tex then
|
||||
rstr = _sub(rstr, tx or 1, tex)
|
||||
end
|
||||
if x == 1 and width == self.width then
|
||||
return rstr
|
||||
elseif x == 1 then
|
||||
return rstr .. _sub(sstr, x + width)
|
||||
elseif x + width > self.width then
|
||||
return _sub(sstr, 1, x - 1) .. rstr
|
||||
end
|
||||
return _sub(sstr, 1, x - 1) .. rstr .. _sub(sstr, x + width)
|
||||
end
|
||||
|
||||
self.text = replace(self.text, text)
|
||||
if fg then
|
||||
self.fg = replace(self.fg, fg)
|
||||
end
|
||||
if bg then
|
||||
self.bg = replace(self.bg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Blit:sub(s, e)
|
||||
return Blit({
|
||||
text = self.text:sub(s, e),
|
||||
bg = self.bg:sub(s, e),
|
||||
fg = self.fg:sub(s, e),
|
||||
})
|
||||
end
|
||||
|
||||
function Blit:wrap(max)
|
||||
local lines = { }
|
||||
local data = self
|
||||
|
||||
repeat
|
||||
if #data.text <= max then
|
||||
table.insert(lines, data)
|
||||
break
|
||||
elseif data.text:sub(max+1, max+1) == ' ' then
|
||||
table.insert(lines, data:sub(1, max))
|
||||
data = data:sub(max + 2)
|
||||
else
|
||||
local x = data.text:sub(1, max)
|
||||
local s = x:match('(.*) ') or x
|
||||
table.insert(lines, data:sub(1, #s))
|
||||
data = data:sub(#s + 1)
|
||||
end
|
||||
local t = data.text:match('^%s*(.*)')
|
||||
local spaces = #data.text - #t
|
||||
if spaces > 0 then
|
||||
data = data:sub(spaces + 1)
|
||||
end
|
||||
until not data.text or #data.text == 0
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- convert a string of text to blit format doing color conversion
|
||||
-- and processing ansi color sequences
|
||||
function Blit.toblit(str, cs)
|
||||
local text, fg, bg = '', '', ''
|
||||
|
||||
if not cs.cbg then
|
||||
-- reset colors
|
||||
cs.rbg = cs.bg or colors.black
|
||||
cs.rfg = cs.fg or colors.white
|
||||
-- current colors
|
||||
cs.cbg = cs.rbg
|
||||
cs.cfg = cs.rfg
|
||||
|
||||
cs.palette = cs.palette or Blit.palette
|
||||
end
|
||||
|
||||
str = str:gsub('(.-)\027%[([%d;]+)m',
|
||||
function(k, seq)
|
||||
text = text .. k
|
||||
bg = bg .. string.rep(cs.palette[cs.cbg], #k)
|
||||
fg = fg .. string.rep(cs.palette[cs.cfg], #k)
|
||||
for color in string.gmatch(seq, "%d+") do
|
||||
color = tonumber(color)
|
||||
if color == 0 then
|
||||
-- reset to default
|
||||
cs.cfg = cs.rfg
|
||||
cs.cbg = cs.rbg
|
||||
elseif color > 20 then
|
||||
cs.cbg = 2 ^ (color - 21)
|
||||
else
|
||||
cs.cfg = 2 ^ (color - 1)
|
||||
end
|
||||
end
|
||||
return k
|
||||
end)
|
||||
|
||||
local k = str:sub(#text + 1)
|
||||
return text .. k,
|
||||
bg .. string.rep(cs.palette[cs.cbg], #k),
|
||||
fg .. string.rep(cs.palette[cs.cfg], #k)
|
||||
end
|
||||
|
||||
return setmetatable(Blit, {
|
||||
__call = function(_, ...)
|
||||
local obj = setmetatable({ }, { __index = Blit })
|
||||
obj:init(...)
|
||||
return obj
|
||||
end
|
||||
})
|
||||
@@ -9,28 +9,34 @@ local colors = _G.colors
|
||||
|
||||
local Canvas = class()
|
||||
|
||||
Canvas.__visualize = false
|
||||
Canvas.colorPalette = { }
|
||||
Canvas.darkPalette = { }
|
||||
Canvas.grayscalePalette = { }
|
||||
|
||||
for n = 1, 16 do
|
||||
Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
|
||||
Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n)
|
||||
local function genPalette(map)
|
||||
local t = { }
|
||||
local rcolors = Util.transpose(colors)
|
||||
for n = 1, 16 do
|
||||
local pow = 2 ^ (n - 1)
|
||||
local ch = _sub(map, n, n)
|
||||
t[pow] = ch
|
||||
t[rcolors[pow]] = ch
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
Canvas.colorPalette = genPalette('0123456789abcdef')
|
||||
Canvas.grayscalePalette = genPalette('088888878877787f')
|
||||
|
||||
--[[
|
||||
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,16 +52,31 @@ 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)
|
||||
for i = #self.lines, h do
|
||||
self:resizeBuffer(w, h)
|
||||
|
||||
self.ex = self.x + w - 1
|
||||
self.ey = self.y + h - 1
|
||||
self.width = w
|
||||
self.height = h
|
||||
end
|
||||
|
||||
-- resize the canvas buffer - not the canvas itself
|
||||
function Canvas:resizeBuffer(w, h)
|
||||
for i = #self.lines + 1, h do
|
||||
self.lines[i] = { }
|
||||
self:clearLine(i)
|
||||
end
|
||||
@@ -66,26 +87,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()
|
||||
@@ -105,30 +124,26 @@ function Canvas:copy()
|
||||
end
|
||||
|
||||
function Canvas:addLayer(layer)
|
||||
local canvas = Canvas({
|
||||
x = layer.x,
|
||||
y = layer.y,
|
||||
width = layer.width,
|
||||
height = layer.height,
|
||||
isColor = self.isColor,
|
||||
})
|
||||
canvas.parent = self
|
||||
table.insert(self.layers, canvas)
|
||||
return canvas
|
||||
layer.parent = self
|
||||
if not self.children then
|
||||
self.children = { }
|
||||
end
|
||||
table.insert(self.children, 1, layer)
|
||||
return layer
|
||||
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 +152,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
|
||||
@@ -161,54 +175,42 @@ end
|
||||
function Canvas:blit(x, y, text, bg, fg)
|
||||
if y > 0 and y <= #self.lines and x <= self.width then
|
||||
local width = #text
|
||||
local tx, tex
|
||||
|
||||
-- fix ffs
|
||||
if x < 1 then
|
||||
text = _sub(text, 2 - x)
|
||||
if bg then
|
||||
bg = _sub(bg, 2 - x)
|
||||
end
|
||||
if fg then
|
||||
fg = _sub(fg, 2 - x)
|
||||
end
|
||||
tx = 2 - x
|
||||
width = width + x - 1
|
||||
x = 1
|
||||
end
|
||||
|
||||
if x + width - 1 > self.width then
|
||||
text = _sub(text, 1, self.width - x + 1)
|
||||
if bg then
|
||||
bg = _sub(bg, 1, self.width - x + 1)
|
||||
end
|
||||
if fg then
|
||||
fg = _sub(fg, 1, self.width - x + 1)
|
||||
end
|
||||
width = #text
|
||||
tex = self.width - x + (tx or 1)
|
||||
width = tex - (tx or 1) + 1
|
||||
end
|
||||
|
||||
if width > 0 then
|
||||
|
||||
local function replace(sstr, pos, rstr)
|
||||
if pos == 1 and width == self.width then
|
||||
return rstr
|
||||
elseif pos == 1 then
|
||||
return rstr .. _sub(sstr, pos+width)
|
||||
elseif pos + width > self.width then
|
||||
return _sub(sstr, 1, pos-1) .. rstr
|
||||
local function replace(sstr, rstr)
|
||||
if tx or tex then
|
||||
rstr = _sub(rstr, tx or 1, tex)
|
||||
end
|
||||
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
|
||||
if x == 1 and width == self.width then
|
||||
return rstr
|
||||
elseif x == 1 then
|
||||
return rstr .. _sub(sstr, x + width)
|
||||
elseif x + width > self.width then
|
||||
return _sub(sstr, 1, x - 1) .. rstr
|
||||
end
|
||||
return _sub(sstr, 1, x - 1) .. rstr .. _sub(sstr, x + width)
|
||||
end
|
||||
|
||||
local line = self.lines[y]
|
||||
if line then
|
||||
line.dirty = true
|
||||
line.text = replace(line.text, x, text, width)
|
||||
if fg then
|
||||
line.fg = replace(line.fg, x, fg, width)
|
||||
end
|
||||
if bg then
|
||||
line.bg = replace(line.bg, x, bg, width)
|
||||
end
|
||||
line.dirty = true
|
||||
line.text = replace(line.text, text)
|
||||
if fg then
|
||||
line.fg = replace(line.fg, fg)
|
||||
end
|
||||
if bg then
|
||||
line.bg = replace(line.bg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -224,15 +226,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 +248,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
|
||||
@@ -278,115 +283,95 @@ function Canvas:applyPalette(palette)
|
||||
self.palette = palette
|
||||
end
|
||||
|
||||
function Canvas:render(device)
|
||||
local offset = { x = 0, y = 0 }
|
||||
local parent = self.parent
|
||||
while parent do
|
||||
offset.x = offset.x + parent.x - 1
|
||||
offset.y = offset.y + parent.y - 1
|
||||
parent = parent.parent
|
||||
end
|
||||
if #self.layers > 0 then
|
||||
self:__renderLayers(device, offset)
|
||||
else
|
||||
self:__blitRect(device, nil, {
|
||||
x = self.x + offset.x,
|
||||
y = self.y + offset.y
|
||||
})
|
||||
self:clean()
|
||||
-- either render directly to the device
|
||||
-- or use another canvas as a backing buffer
|
||||
function Canvas:render(device, doubleBuffer)
|
||||
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
||||
self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }, doubleBuffer)
|
||||
|
||||
-- doubleBuffering to reduce the amount of
|
||||
-- setCursorPos, blits
|
||||
if doubleBuffer then
|
||||
--[[
|
||||
local drew = false
|
||||
local bg = _rep(2, device.width)
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, bg)
|
||||
drew = true
|
||||
end
|
||||
end
|
||||
if drew then
|
||||
local c = os.clock()
|
||||
repeat until os.clock()-c > .1
|
||||
end
|
||||
]]
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, v.bg)
|
||||
v.dirty = false
|
||||
end
|
||||
end
|
||||
end
|
||||
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)
|
||||
|
||||
function Canvas:__renderLayers(device, offset, doubleBuffer)
|
||||
if self.children then
|
||||
for i = #self.children, 1, -1 do
|
||||
local canvas = self.children[i]
|
||||
if canvas.visible or canvas.enabled then
|
||||
-- get the area to render for this layer
|
||||
canvas.regions = Region.new(
|
||||
canvas.x + offset.x,
|
||||
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),
|
||||
}, doubleBuffer)
|
||||
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},
|
||||
{ x = region[1], y = region[2] })
|
||||
y = region[2] - offset.y,
|
||||
ex = region[3] - offset.x,
|
||||
ey = region[4] - offset.y },
|
||||
{ x = region[1], y = region[2] }, doubleBuffer)
|
||||
end
|
||||
self.regions = nil
|
||||
|
||||
self:clean()
|
||||
end
|
||||
|
||||
function Canvas:__punch(rect, offset)
|
||||
self.regions:subRect(
|
||||
rect.x + offset.x,
|
||||
rect.y + offset.y,
|
||||
rect.ex + offset.x,
|
||||
rect.ey + offset.y)
|
||||
end
|
||||
|
||||
-- performance can probably be improved by using one more buffer tied to the device
|
||||
function Canvas:__blitRect(device, src, tgt)
|
||||
src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
|
||||
tgt = tgt or self
|
||||
|
||||
function Canvas:__blitRect(device, src, tgt, doubleBuffer)
|
||||
-- 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,10 +384,11 @@ 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
|
||||
local line = self.lines[src.y + i + (self.offy or 0)]
|
||||
if line and line.dirty then
|
||||
@@ -412,8 +398,13 @@ function Canvas:__blitRect(device, src, tgt)
|
||||
fg = _sub(fg, src.x, src.ex)
|
||||
bg = _sub(bg, src.x, src.ex)
|
||||
end
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
if doubleBuffer then
|
||||
Canvas.blit(device, tgt.x, tgt.y + i,
|
||||
t, bg, fg)
|
||||
else
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -2,32 +2,30 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Button = class(UI.Window)
|
||||
UI.Button.defaults = {
|
||||
UIElement = 'Button',
|
||||
text = 'button',
|
||||
backgroundColor = colors.lightGray,
|
||||
backgroundFocusColor = colors.gray,
|
||||
textFocusColor = colors.white,
|
||||
textInactiveColor = colors.gray,
|
||||
textColor = colors.black,
|
||||
backgroundColor = 'lightGray',
|
||||
backgroundFocusColor = 'gray',
|
||||
textFocusColor = 'white',
|
||||
textInactiveColor = 'gray',
|
||||
textColor = 'black',
|
||||
centered = true,
|
||||
height = 1,
|
||||
focusIndicator = ' ',
|
||||
event = 'button_press',
|
||||
accelerators = {
|
||||
space = 'button_activate',
|
||||
[ ' ' ] = 'button_activate',
|
||||
enter = 'button_activate',
|
||||
mouse_click = 'button_activate',
|
||||
}
|
||||
}
|
||||
function UI.Button:setParent()
|
||||
function UI.Button:layout()
|
||||
if not self.width and not self.ex then
|
||||
self.width = #self.text + 2
|
||||
self.width = self.noPadding and #self.text or #self.text + 2
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
UI.Window.layout(self)
|
||||
end
|
||||
|
||||
function UI.Button:draw()
|
||||
@@ -35,13 +33,13 @@ 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 .. ' '
|
||||
local text = self.noPadding and self.text or ind .. self.text .. ' '
|
||||
if self.centered then
|
||||
self:clear(bg)
|
||||
self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg)
|
||||
@@ -59,7 +57,7 @@ end
|
||||
|
||||
function UI.Button:eventHandler(event)
|
||||
if event.type == 'button_activate' then
|
||||
self:emit({ type = self.event, button = self })
|
||||
self:emit({ type = self.event, button = self, element = self })
|
||||
return true
|
||||
end
|
||||
return false
|
||||
@@ -73,7 +71,7 @@ function UI.Button.example()
|
||||
},
|
||||
button2 = UI.Button {
|
||||
x = 2, y = 4,
|
||||
backgroundColor = colors.green,
|
||||
backgroundColor = 'green',
|
||||
event = 'custom_event',
|
||||
},
|
||||
button3 = UI.Button {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Checkbox = class(UI.Window)
|
||||
UI.Checkbox.defaults = {
|
||||
UIElement = 'Checkbox',
|
||||
@@ -11,9 +9,9 @@ UI.Checkbox.defaults = {
|
||||
leftMarker = UI.extChars and '\124' or '[',
|
||||
rightMarker = UI.extChars and '\124' or ']',
|
||||
value = false,
|
||||
textColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
textColor = 'white',
|
||||
backgroundColor = 'black',
|
||||
backgroundFocusColor = 'lightGray',
|
||||
height = 1,
|
||||
width = 3,
|
||||
accelerators = {
|
||||
@@ -21,11 +19,9 @@ UI.Checkbox.defaults = {
|
||||
mouse_click = 'checkbox_toggle',
|
||||
}
|
||||
}
|
||||
UI.Checkbox.inherits = {
|
||||
labelBackgroundColor = 'backgroundColor',
|
||||
}
|
||||
function UI.Checkbox:postInit()
|
||||
function UI.Checkbox:layout()
|
||||
self.width = self.label and #self.label + 4 or 3
|
||||
UI.Window.layout(self)
|
||||
end
|
||||
|
||||
function UI.Checkbox:draw()
|
||||
|
||||
@@ -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',
|
||||
@@ -20,7 +20,7 @@ UI.Chooser.defaults = {
|
||||
left = 'choice_prev',
|
||||
}
|
||||
}
|
||||
function UI.Chooser:setParent()
|
||||
function UI.Chooser:layout()
|
||||
if not self.width and not self.ex then
|
||||
self.width = 1
|
||||
for _,v in pairs(self.choices) do
|
||||
@@ -30,7 +30,7 @@ function UI.Chooser:setParent()
|
||||
end
|
||||
self.width = self.width + 4
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
UI.Window.layout(self)
|
||||
end
|
||||
|
||||
function UI.Chooser:draw()
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
local Canvas = require('opus.ui.canvas')
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Dialog = class(UI.SlideOut)
|
||||
UI.Dialog.defaults = {
|
||||
UIElement = 'Dialog',
|
||||
height = 7,
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.white,
|
||||
noFill = true,
|
||||
okEvent ='dialog_ok',
|
||||
cancelEvent = 'dialog_cancel',
|
||||
}
|
||||
@@ -18,22 +14,36 @@ function UI.Dialog:postInit()
|
||||
self.titleBar = UI.TitleBar({ event = self.cancelEvent, title = self.title })
|
||||
end
|
||||
|
||||
function UI.Dialog:show(...)
|
||||
local canvas = self.parent:getCanvas()
|
||||
self.oldPalette = canvas.palette
|
||||
canvas:applyPalette(Canvas.darkPalette)
|
||||
UI.SlideOut.show(self, ...)
|
||||
end
|
||||
|
||||
function UI.Dialog:hide(...)
|
||||
self.parent:getCanvas().palette = self.oldPalette
|
||||
UI.SlideOut.hide(self, ...)
|
||||
self.parent:draw()
|
||||
end
|
||||
|
||||
function UI.Dialog:eventHandler(event)
|
||||
if event.type == 'dialog_cancel' then
|
||||
self:hide()
|
||||
end
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function UI.Dialog.example()
|
||||
return UI.Dialog {
|
||||
title = 'Enter Starting Level',
|
||||
height = 7,
|
||||
form = UI.Form {
|
||||
y = 3, x = 2, height = 4,
|
||||
event = 'setStartLevel',
|
||||
cancelEvent = 'slide_hide',
|
||||
text = UI.Text {
|
||||
x = 5, y = 1, width = 20,
|
||||
textColor = 'gray',
|
||||
},
|
||||
textEntry = UI.TextEntry {
|
||||
formKey = 'level',
|
||||
x = 15, y = 1, width = 7,
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar(),
|
||||
enable = function(self)
|
||||
require('opus.event').onTimeout(0, function()
|
||||
self:show()
|
||||
self:sync()
|
||||
end)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -2,12 +2,10 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.DropMenu = class(UI.MenuBar)
|
||||
UI.DropMenu.defaults = {
|
||||
UIElement = 'DropMenu',
|
||||
backgroundColor = colors.white,
|
||||
backgroundColor = 'white',
|
||||
buttonClass = 'DropMenuItem',
|
||||
}
|
||||
function UI.DropMenu:layout()
|
||||
@@ -32,42 +30,53 @@ 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
|
||||
local menuBar = self.parent:find(self.menuUid)
|
||||
local hasActive
|
||||
|
||||
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 and menuBar then
|
||||
c.inactive = not menuBar:getActive(c)
|
||||
end
|
||||
if not c.inactive then
|
||||
hasActive = true
|
||||
end
|
||||
end
|
||||
|
||||
-- jump through a lot of hoops if all selections are inactive
|
||||
-- there's gotta be a better way
|
||||
-- lots of exception code just to handle drop menus
|
||||
self.focus = not hasActive and function() end
|
||||
|
||||
UI.Window.enable(self)
|
||||
|
||||
if self.focus then
|
||||
self:setFocus(self)
|
||||
else
|
||||
self:focusFirst()
|
||||
end
|
||||
self:draw()
|
||||
self:capture(self)
|
||||
self:focusFirst()
|
||||
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()
|
||||
if not (Util.contains(self.children, event.focused) or event.focused == self) then
|
||||
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 +92,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
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.DropMenuItem = class(UI.Button)
|
||||
UI.DropMenuItem.defaults = {
|
||||
UIElement = 'DropMenuItem',
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.white,
|
||||
textFocusColor = colors.white,
|
||||
textInactiveColor = colors.lightGray,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
textColor = 'black',
|
||||
backgroundColor = 'white',
|
||||
textFocusColor = 'white',
|
||||
textInactiveColor = 'lightGray',
|
||||
backgroundFocusColor = 'lightGray',
|
||||
}
|
||||
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
|
||||
|
||||
@@ -1,62 +1,63 @@
|
||||
local class = require('opus.class')
|
||||
local Event = require('opus.event')
|
||||
local Terminal = require('opus.terminal')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Embedded = class(UI.Window)
|
||||
UI.Embedded.defaults = {
|
||||
UIElement = 'Embedded',
|
||||
backgroundColor = colors.black,
|
||||
textColor = colors.white,
|
||||
backgroundColor = 'black',
|
||||
textColor = 'white',
|
||||
maxScroll = 100,
|
||||
accelerators = {
|
||||
up = 'scroll_up',
|
||||
down = 'scroll_down',
|
||||
}
|
||||
}
|
||||
function UI.Embedded:setParent()
|
||||
UI.Window.setParent(self)
|
||||
|
||||
self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false)
|
||||
self.win.setMaxScroll(self.maxScroll)
|
||||
|
||||
local canvas = self:getCanvas()
|
||||
self.win.getCanvas().parent = canvas
|
||||
table.insert(canvas.layers, self.win.getCanvas())
|
||||
self.canvas = self.win.getCanvas()
|
||||
|
||||
self.win.setCursorPos(1, 1)
|
||||
self.win.setBackgroundColor(self.backgroundColor)
|
||||
self.win.setTextColor(self.textColor)
|
||||
self.win.clear()
|
||||
end
|
||||
|
||||
function UI.Embedded:layout()
|
||||
UI.Window.layout(self)
|
||||
if self.win then
|
||||
self.win.reposition(self.x, self.y, self.width, self.height)
|
||||
|
||||
if not self.win then
|
||||
local t
|
||||
function self.render()
|
||||
if not t then
|
||||
t = Event.onTimeout(0, function()
|
||||
t = nil
|
||||
if self.focused then
|
||||
self:setCursorPos(self.win.getCursorPos())
|
||||
end
|
||||
self:sync()
|
||||
end)
|
||||
end
|
||||
end
|
||||
self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false)
|
||||
self.win.canvas = self
|
||||
self.win.setMaxScroll(self.maxScroll)
|
||||
self.win.setCursorPos(1, 1)
|
||||
self.win.setBackgroundColor(self.backgroundColor)
|
||||
self.win.setTextColor(self.textColor)
|
||||
self.win.clear()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Embedded:draw()
|
||||
self.canvas:dirty()
|
||||
self:dirty()
|
||||
end
|
||||
|
||||
function UI.Embedded:focus()
|
||||
-- allow scrolling
|
||||
if self.focused then
|
||||
self:setCursorBlink(self.win.getCursorBlink())
|
||||
end
|
||||
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 +72,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,
|
||||
y = 2, x = 2, ex = -2, ey = -2,
|
||||
enable = function (self)
|
||||
UI.Embedded.enable(self)
|
||||
Event.addRoutine(function()
|
||||
@@ -90,10 +86,11 @@ function UI.Embedded.example()
|
||||
term.redirect(oterm)
|
||||
end)
|
||||
end,
|
||||
eventHandler = function(_, event)
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'key' then
|
||||
return true
|
||||
end
|
||||
return UI.Embedded.eventHandler(self, event)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
118
sys/modules/opus/ui/components/FileSelect.lua
Normal file
118
sys/modules/opus/ui/components/FileSelect.lua
Normal file
@@ -0,0 +1,118 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
|
||||
UI.FileSelect = class(UI.Window)
|
||||
UI.FileSelect.defaults = {
|
||||
UIElement = 'FileSelect',
|
||||
}
|
||||
function UI.FileSelect:postInit()
|
||||
self.grid = UI.ScrollingGrid {
|
||||
x = 2, y = 2, ex = -2, ey = -4,
|
||||
dir = '/',
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Size', key = 'size', width = 5 }
|
||||
},
|
||||
getDisplayValues = function(_, row)
|
||||
if row.size then
|
||||
row = Util.shallowCopy(row)
|
||||
row.size = Util.toBytes(row.size)
|
||||
end
|
||||
return row
|
||||
end,
|
||||
getRowTextColor = function(_, file)
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end,
|
||||
sortCompare = function(self, a, b)
|
||||
if self.sortColumn == 'size' then
|
||||
return a.size < b.size
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end,
|
||||
draw = function(self)
|
||||
local files = fs.listEx(self.dir)
|
||||
if #self.dir > 0 then
|
||||
table.insert(files, {
|
||||
name = '..',
|
||||
isDir = true,
|
||||
})
|
||||
end
|
||||
self:setValues(files)
|
||||
self:setIndex(1)
|
||||
UI.Grid.draw(self)
|
||||
end,
|
||||
}
|
||||
self.path = UI.TextEntry {
|
||||
x = 2,
|
||||
y = -2,
|
||||
ex = -11,
|
||||
limit = 256,
|
||||
accelerators = {
|
||||
enter = 'path_enter',
|
||||
}
|
||||
}
|
||||
self.cancel = UI.Button {
|
||||
text = 'Cancel',
|
||||
x = -9,
|
||||
y = -2,
|
||||
event = 'select_cancel',
|
||||
}
|
||||
end
|
||||
|
||||
function UI.FileSelect:draw()
|
||||
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray)
|
||||
self:drawChildren()
|
||||
end
|
||||
|
||||
function UI.FileSelect:enable(path)
|
||||
self:setPath(path or '')
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
|
||||
function UI.FileSelect:setPath(path)
|
||||
self.grid.dir = path
|
||||
while not fs.isDir(self.grid.dir) do
|
||||
self.grid.dir = fs.getDir(self.grid.dir)
|
||||
end
|
||||
self.path.value = self.grid.dir
|
||||
end
|
||||
|
||||
function UI.FileSelect:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
self.grid.dir = fs.combine(self.grid.dir, event.selected.name)
|
||||
self.path.value = self.grid.dir
|
||||
if event.selected.isDir then
|
||||
self.grid:draw()
|
||||
self.path:draw()
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
return true
|
||||
|
||||
elseif event.type == 'path_enter' then
|
||||
if self.path.value then
|
||||
if fs.isDir(self.path.value) then
|
||||
self:setPath(self.path.value)
|
||||
self.grid:draw()
|
||||
self.path:draw()
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
16
sys/modules/opus/ui/components/FlatButton.lua
Normal file
16
sys/modules/opus/ui/components/FlatButton.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.FlatButton = class(UI.Button)
|
||||
UI.FlatButton.defaults = {
|
||||
UIElement = 'FlatButton',
|
||||
textColor = 'black',
|
||||
textFocusColor = 'white',
|
||||
noPadding = true,
|
||||
}
|
||||
function UI.FlatButton:setParent()
|
||||
self.backgroundColor = self.parent:getProperty('backgroundColor')
|
||||
self.backgroundFocusColor = self.backgroundColor
|
||||
|
||||
UI.Button.setParent(self)
|
||||
end
|
||||
@@ -2,8 +2,6 @@ local class = require('opus.class')
|
||||
local Sound = require('opus.sound')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Form = class(UI.Window)
|
||||
UI.Form.defaults = {
|
||||
UIElement = 'Form',
|
||||
@@ -68,7 +66,7 @@ function UI.Form:createForm()
|
||||
table.insert(self.children, UI.Text {
|
||||
x = self.margin,
|
||||
y = child.y,
|
||||
textColor = colors.black,
|
||||
textColor = 'black',
|
||||
width = #child.formLabel,
|
||||
value = child.formLabel,
|
||||
})
|
||||
|
||||
@@ -2,10 +2,8 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
local function safeValue(v)
|
||||
local t = type(v)
|
||||
@@ -23,18 +21,7 @@ function Writer:init(element, y)
|
||||
end
|
||||
|
||||
function Writer:write(s, width, align, bg, fg)
|
||||
local len = #tostring(s or '')
|
||||
if len > width then
|
||||
s = _sub(s, 1, width)
|
||||
end
|
||||
local padding = len < width and _rep(' ', width - len)
|
||||
if padding then
|
||||
if align == 'right' then
|
||||
s = padding .. s
|
||||
else
|
||||
s = s .. padding
|
||||
end
|
||||
end
|
||||
s = Util.widthify(s, width, align)
|
||||
self.element:write(self.x, self.y, s, bg, fg)
|
||||
self.x = self.x + width
|
||||
end
|
||||
@@ -56,16 +43,16 @@ UI.Grid.defaults = {
|
||||
disableHeader = false,
|
||||
headerHeight = 1,
|
||||
marginRight = 0,
|
||||
textColor = colors.white,
|
||||
textSelectedColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
backgroundSelectedColor = colors.gray,
|
||||
headerBackgroundColor = colors.cyan,
|
||||
headerTextColor = colors.white,
|
||||
headerSortColor = colors.yellow,
|
||||
unfocusedTextSelectedColor = colors.white,
|
||||
unfocusedBackgroundSelectedColor = colors.gray,
|
||||
focusIndicator = UI.extChars and '\183' or '>',
|
||||
textColor = 'white',
|
||||
textSelectedColor = 'white',
|
||||
backgroundColor = 'black',
|
||||
backgroundSelectedColor = 'gray',
|
||||
headerBackgroundColor = 'primary',
|
||||
headerTextColor = 'white',
|
||||
headerSortColor = 'yellow',
|
||||
unfocusedTextSelectedColor = 'white',
|
||||
unfocusedBackgroundSelectedColor = 'gray',
|
||||
focusIndicator = UI.extChars and '\26' or '>',
|
||||
sortIndicator = ' ',
|
||||
inverseSortIndicator = UI.extChars and '\24' or '^',
|
||||
values = { },
|
||||
@@ -83,8 +70,8 @@ UI.Grid.defaults = {
|
||||
[ 'control-f' ] = 'scroll_pageDown',
|
||||
},
|
||||
}
|
||||
function UI.Grid:setParent()
|
||||
UI.Window.setParent(self)
|
||||
function UI.Grid:layout()
|
||||
UI.Window.layout(self)
|
||||
|
||||
for _,c in pairs(self.columns) do
|
||||
c.cw = c.width
|
||||
@@ -522,7 +509,7 @@ function UI.Grid.example()
|
||||
values = values,
|
||||
columns = {
|
||||
{ heading = 'key', key = 'key', width = 6, },
|
||||
{ heading = 'value', key = 'value', textColor = colors.yellow },
|
||||
{ heading = 'value', key = 'value', textColor = 'yellow' },
|
||||
},
|
||||
},
|
||||
autospace = UI.Grid {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,8 +13,7 @@ function UI.Menu:postInit()
|
||||
self.pageSize = #self.menuItems
|
||||
end
|
||||
|
||||
function UI.Menu:setParent()
|
||||
UI.Grid.setParent(self)
|
||||
function UI.Menu:layout()
|
||||
self.itemWidth = 1
|
||||
for _,v in pairs(self.values) do
|
||||
if #v.prompt > self.itemWidth then
|
||||
@@ -28,6 +27,7 @@ function UI.Menu:setParent()
|
||||
else
|
||||
self.width = self.itemWidth + 2
|
||||
end
|
||||
UI.Grid.layout(self)
|
||||
end
|
||||
|
||||
function UI.Menu:center()
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local function getPosition(element)
|
||||
local x, y = 1, 1
|
||||
repeat
|
||||
x = element.x + x - 1
|
||||
y = element.y + y - 1
|
||||
element = element.parent
|
||||
until not element
|
||||
return x, y
|
||||
end
|
||||
|
||||
UI.MenuBar = class(UI.Window)
|
||||
UI.MenuBar.defaults = {
|
||||
UIElement = 'MenuBar',
|
||||
buttons = { },
|
||||
height = 1,
|
||||
backgroundColor = colors.lightGray,
|
||||
textColor = colors.black,
|
||||
backgroundColor = 'secondary',
|
||||
textColor = 'black',
|
||||
spacing = 2,
|
||||
lastx = 1,
|
||||
showBackButton = false,
|
||||
buttonClass = 'MenuItem',
|
||||
}
|
||||
function UI.MenuBar:postInit()
|
||||
@@ -62,10 +49,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 +61,28 @@ 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,
|
||||
menuUid = self.uid,
|
||||
}
|
||||
self.parent:add({ dropmenu = menu })
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -103,7 +91,8 @@ function UI.MenuBar.example()
|
||||
return UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Choice1', event = 'event1' },
|
||||
{ text = 'Choice2', event = 'event2' },
|
||||
{ text = 'Choice2', event = 'event2', inactive = true },
|
||||
{ text = 'Choice3', event = 'event3' },
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.MenuItem = class(UI.Button)
|
||||
UI.MenuItem = class(UI.FlatButton)
|
||||
UI.MenuItem.defaults = {
|
||||
UIElement = 'MenuItem',
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.lightGray,
|
||||
textFocusColor = colors.white,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
noPadding = false,
|
||||
textInactiveColor = 'gray',
|
||||
}
|
||||
|
||||
31
sys/modules/opus/ui/components/MiniSlideOut.lua
Normal file
31
sys/modules/opus/ui/components/MiniSlideOut.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.MiniSlideOut = class(UI.SlideOut)
|
||||
UI.MiniSlideOut.defaults = {
|
||||
UIElement = 'MiniSlideOut',
|
||||
noFill = true,
|
||||
backgroundColor = 'primary',
|
||||
height = 1,
|
||||
}
|
||||
function UI.MiniSlideOut:postInit()
|
||||
self.close_button = UI.Button {
|
||||
x = -1,
|
||||
backgroundColor = self.backgroundColor,
|
||||
backgroundFocusColor = self.backgroundColor,
|
||||
text = 'x',
|
||||
event = 'slide_hide',
|
||||
noPadding = true,
|
||||
}
|
||||
if self.label then
|
||||
self.label_text = UI.Text {
|
||||
x = 2,
|
||||
value = self.label,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function UI.MiniSlideOut:show(...)
|
||||
UI.SlideOut.show(self, ...)
|
||||
self:addTransition('slideLeft', { easing = 'outBounce' })
|
||||
end
|
||||
@@ -5,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
|
||||
|
||||
|
||||
@@ -4,36 +4,34 @@ local Sound = require('opus.sound')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Notification = class(UI.Window)
|
||||
UI.Notification.defaults = {
|
||||
UIElement = 'Notification',
|
||||
backgroundColor = colors.gray,
|
||||
backgroundColor = 'gray',
|
||||
closeInd = UI.extChars and '\215' or '*',
|
||||
height = 3,
|
||||
timeout = 3,
|
||||
anchor = 'bottom',
|
||||
}
|
||||
function UI.Notification:draw()
|
||||
function UI.Notification.draw()
|
||||
end
|
||||
|
||||
function UI.Notification:enable()
|
||||
function UI.Notification.enable()
|
||||
end
|
||||
|
||||
function UI.Notification:error(value, timeout)
|
||||
self.backgroundColor = colors.red
|
||||
self.backgroundColor = 'red'
|
||||
Sound.play('entity.villager.no', .5)
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
function UI.Notification:info(value, timeout)
|
||||
self.backgroundColor = colors.lightGray
|
||||
self.backgroundColor = 'lightGray'
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
function UI.Notification:success(value, timeout)
|
||||
self.backgroundColor = colors.green
|
||||
self.backgroundColor = 'green'
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
@@ -43,32 +41,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 +77,6 @@ function UI.Notification:display(value, timeout)
|
||||
self:sync()
|
||||
end)
|
||||
else
|
||||
self:write(self.width, 1, self.closeInd)
|
||||
self:sync()
|
||||
end
|
||||
end
|
||||
@@ -92,7 +91,7 @@ function UI.Notification:eventHandler(event)
|
||||
end
|
||||
|
||||
function UI.Notification.example()
|
||||
return UI.ActiveLayer {
|
||||
return UI.Window {
|
||||
notify1 = UI.Notification {
|
||||
anchor = 'top',
|
||||
},
|
||||
@@ -111,7 +110,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,
|
||||
}
|
||||
|
||||
@@ -1,60 +1,31 @@
|
||||
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',
|
||||
accelerators = {
|
||||
down = 'focus_next',
|
||||
scroll_down = 'focus_next',
|
||||
enter = 'focus_next',
|
||||
tab = 'focus_next',
|
||||
['shift-tab' ] = 'focus_prev',
|
||||
up = 'focus_prev',
|
||||
scroll_up = 'focus_prev',
|
||||
},
|
||||
backgroundColor = colors.cyan,
|
||||
textColor = colors.white,
|
||||
backgroundColor = 'primary',
|
||||
textColor = 'white',
|
||||
}
|
||||
function UI.Page:postInit()
|
||||
self.parent = self.parent or UI.defaultDevice
|
||||
self.parent = self.parent or UI.term
|
||||
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
|
||||
self:focusFirst()
|
||||
end
|
||||
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:setCursorBlink(self.focused and self.focused.cursorBlink)
|
||||
self.parent:sync()
|
||||
end
|
||||
end
|
||||
@@ -73,22 +44,23 @@ 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})
|
||||
|
||||
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 +121,17 @@ 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
|
||||
self.__target:focusFirst()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Page:eventHandler(event)
|
||||
if self.focused then
|
||||
if event.type == 'focus_next' then
|
||||
|
||||
@@ -1,39 +1,31 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.ProgressBar = class(UI.Window)
|
||||
UI.ProgressBar.defaults = {
|
||||
UIElement = 'ProgressBar',
|
||||
backgroundColor = colors.gray,
|
||||
backgroundColor = 'gray',
|
||||
height = 1,
|
||||
progressColor = colors.lime,
|
||||
progressColor = 'lime',
|
||||
progressChar = UI.extChars and '\153' or ' ',
|
||||
fillChar = ' ',
|
||||
fillColor = colors.gray,
|
||||
textColor = colors.green,
|
||||
fillColor = 'gray',
|
||||
textColor = 'green',
|
||||
value = 0,
|
||||
}
|
||||
function UI.ProgressBar:draw()
|
||||
local width = math.ceil(self.value / 100 * self.width)
|
||||
|
||||
local filler = string.rep(self.fillChar, self.width)
|
||||
local progress = string.rep(self.progressChar, width)
|
||||
|
||||
for i = 1, self.height do
|
||||
self:write(1, i, filler, nil, self.fillColor)
|
||||
self:write(1, i, progress, self.progressColor)
|
||||
end
|
||||
self:fillArea(width + 1, 1, self.width - width, self.height, self.fillChar, nil, self.fillColor)
|
||||
self:fillArea(1, 1, width, self.height, self.progressChar, self.progressColor)
|
||||
end
|
||||
|
||||
function UI.ProgressBar.example()
|
||||
local Event = require('opus.event')
|
||||
return UI.ProgressBar {
|
||||
x = 2, ex = -2, y = 2,
|
||||
x = 2, ex = -2, y = 2, height = 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()
|
||||
|
||||
27
sys/modules/opus/ui/components/Question.lua
Normal file
27
sys/modules/opus/ui/components/Question.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
UI.Question = class(UI.MiniSlideOut)
|
||||
UI.Question.defaults = {
|
||||
UIElement = 'Question',
|
||||
accelerators = {
|
||||
y = 'question_yes',
|
||||
n = 'question_no',
|
||||
}
|
||||
}
|
||||
function UI.Question:postInit()
|
||||
local x = self.label and #self.label + 3 or 1
|
||||
|
||||
self.yes_button = UI.Button {
|
||||
x = x,
|
||||
text = 'Yes',
|
||||
backgroundColor = 'primary',
|
||||
event = 'question_yes',
|
||||
}
|
||||
self.no_button = UI.Button {
|
||||
x = x + 5,
|
||||
text = 'No',
|
||||
backgroundColor = 'primary',
|
||||
event = 'question_no',
|
||||
}
|
||||
end
|
||||
@@ -17,7 +17,13 @@ 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()
|
||||
|
||||
-- ...
|
||||
self:write(1, 1, ' ', view.fill)
|
||||
|
||||
if view.totalHeight > view.height then
|
||||
local maxScroll = view.totalHeight - view.height
|
||||
@@ -27,7 +33,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 +62,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
|
||||
|
||||
@@ -29,6 +29,7 @@ function UI.ScrollingGrid:getViewArea()
|
||||
height = self.pageSize, -- viewable height
|
||||
totalHeight = Util.size(self.values), -- total height
|
||||
offsetY = self.scrollOffset, -- scroll offset
|
||||
fill = not self.disableHeader and self.headerBackgroundColor,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -57,3 +58,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
|
||||
@@ -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), 'black', '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,
|
||||
|
||||
@@ -2,17 +2,15 @@ local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Slider = class(UI.Window)
|
||||
UI.Slider.defaults = {
|
||||
UIElement = 'Slider',
|
||||
height = 1,
|
||||
barChar = UI.extChars and '\140' or '-',
|
||||
barColor = colors.gray,
|
||||
barColor = 'gray',
|
||||
sliderChar = UI.extChars and '\143' or '\124',
|
||||
sliderColor = colors.blue,
|
||||
sliderFocusColor = colors.lightBlue,
|
||||
sliderColor = 'blue',
|
||||
sliderFocusColor = 'lightBlue',
|
||||
leftBorder = UI.extChars and '\141' or '\124',
|
||||
rightBorder = UI.extChars and '\142' or '\124',
|
||||
value = 0,
|
||||
@@ -57,8 +55,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()
|
||||
|
||||
@@ -3,17 +3,16 @@ local Event = require('opus.event')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.StatusBar = class(UI.Window)
|
||||
UI.StatusBar.defaults = {
|
||||
UIElement = 'StatusBar',
|
||||
backgroundColor = colors.lightGray,
|
||||
textColor = colors.gray,
|
||||
backgroundColor = 'lightGray',
|
||||
textColor = 'gray',
|
||||
height = 1,
|
||||
ey = -1,
|
||||
}
|
||||
function UI.StatusBar:adjustWidth()
|
||||
function UI.StatusBar:layout()
|
||||
UI.Window.layout(self)
|
||||
-- Can only have 1 adjustable width
|
||||
if self.columns then
|
||||
local w = self.width - #self.columns - 1
|
||||
@@ -31,16 +30,6 @@ function UI.StatusBar:adjustWidth()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.StatusBar:resize()
|
||||
UI.Window.resize(self)
|
||||
self:adjustWidth()
|
||||
end
|
||||
|
||||
function UI.StatusBar:setParent()
|
||||
UI.Window.setParent(self)
|
||||
self:adjustWidth()
|
||||
end
|
||||
|
||||
function UI.StatusBar:setStatus(status)
|
||||
if self.values ~= status then
|
||||
self.values = status
|
||||
@@ -63,7 +52,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()
|
||||
@@ -89,11 +78,13 @@ function UI.StatusBar:draw()
|
||||
elseif type(self.values) == 'string' then
|
||||
self:write(1, 1, Util.widthify(' ' .. self.values, self.width))
|
||||
else
|
||||
local s = ''
|
||||
local x = 2
|
||||
self:clear()
|
||||
for _,c in ipairs(self.columns) do
|
||||
s = s .. ' ' .. Util.widthify(tostring(self.values[c.key] or ''), c.cw)
|
||||
local s = Util.widthify(tostring(self.values[c.key] or ''), c.cw)
|
||||
self:write(x, 1, s, c.bg, c.fg)
|
||||
x = x + c.cw + 1
|
||||
end
|
||||
self:write(1, 1, Util.widthify(s, self.width))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,9 +6,9 @@ UI.TabBar = class(UI.MenuBar)
|
||||
UI.TabBar.defaults = {
|
||||
UIElement = 'TabBar',
|
||||
buttonClass = 'TabBarMenuItem',
|
||||
}
|
||||
UI.TabBar.inherits = {
|
||||
selectedBackgroundColor = 'backgroundColor',
|
||||
backgroundColor = 'black',
|
||||
selectedBackgroundColor = 'primary',
|
||||
unselectedBackgroundColor = 'tertiary',
|
||||
}
|
||||
function UI.TabBar:enable()
|
||||
UI.MenuBar.enable(self)
|
||||
@@ -32,7 +32,7 @@ function UI.TabBar:eventHandler(event)
|
||||
self:emit({ type = 'tab_change', current = si, last = pi, tab = selected })
|
||||
end
|
||||
end
|
||||
UI.MenuBar.draw(self)
|
||||
self:draw(self)
|
||||
end
|
||||
return UI.MenuBar.eventHandler(self, event)
|
||||
end
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
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',
|
||||
textInactiveColor = 'lightGray',
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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,11 +100,26 @@ 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,
|
||||
tabTitle = 'tab3',
|
||||
},
|
||||
enable = function(self)
|
||||
UI.Tabs.enable(self)
|
||||
self:setActive(self.tab3, false)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -8,11 +8,11 @@ UI.Text.defaults = {
|
||||
value = '',
|
||||
height = 1,
|
||||
}
|
||||
function UI.Text:setParent()
|
||||
function UI.Text:layout()
|
||||
if not self.width and not self.ex then
|
||||
self.width = #tostring(self.value)
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
UI.Window.layout(self)
|
||||
end
|
||||
|
||||
function UI.Text:draw()
|
||||
|
||||
@@ -6,36 +6,44 @@ UI.TextArea.defaults = {
|
||||
UIElement = 'TextArea',
|
||||
marginRight = 2,
|
||||
value = '',
|
||||
showScrollBar = true,
|
||||
}
|
||||
function UI.TextArea:postInit()
|
||||
self.scrollBar = UI.ScrollBar()
|
||||
end
|
||||
|
||||
function UI.TextArea:setText(text)
|
||||
self:reset()
|
||||
self.value = text
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function UI.TextArea:focus()
|
||||
function UI.TextArea.focus()
|
||||
-- allow keyboard scrolling
|
||||
end
|
||||
|
||||
function UI.TextArea:draw()
|
||||
self:clear()
|
||||
-- self:setCursorPos(1, 1)
|
||||
self.cursorX, self.cursorY = 1, 1
|
||||
self:print(self.value)
|
||||
|
||||
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'
|
||||
local Ansi = require('opus.ansi')
|
||||
return UI.Window {
|
||||
backgroundColor = 2048,
|
||||
t1 = UI.TextArea {
|
||||
ey = 3,
|
||||
value = 'sample text\nabc'
|
||||
},
|
||||
t2 = UI.TextArea {
|
||||
y = 5,
|
||||
backgroundColor = 'green',
|
||||
value = string.format([[now %%is the %stime %sfor%s all good men to come to the aid of their country.
|
||||
1
|
||||
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8]], Ansi.yellow, Ansi.onred, Ansi.reset),
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -3,7 +3,6 @@ local entry = require('opus.entry')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local _rep = string.rep
|
||||
|
||||
local function transform(directive)
|
||||
@@ -19,15 +18,16 @@ UI.TextEntry = class(UI.Window)
|
||||
UI.TextEntry.docs = { }
|
||||
UI.TextEntry.defaults = {
|
||||
UIElement = 'TextEntry',
|
||||
--value = '',
|
||||
shadowText = '',
|
||||
focused = false,
|
||||
textColor = colors.white,
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.black, -- colors.lightGray,
|
||||
backgroundFocusColor = colors.black, --lightGray,
|
||||
textColor = 'white',
|
||||
shadowTextColor = 'gray',
|
||||
markBackgroundColor = 'gray',
|
||||
backgroundColor = 'black',
|
||||
backgroundFocusColor = 'black',
|
||||
height = 1,
|
||||
limit = 6,
|
||||
cursorBlink = true,
|
||||
accelerators = {
|
||||
[ 'control-c' ] = 'copy',
|
||||
}
|
||||
@@ -74,7 +74,9 @@ function UI.TextEntry:draw()
|
||||
text = self.shadowText
|
||||
end
|
||||
|
||||
self:write(1, 1, ' ' .. Util.widthify(text, self.width - 2) .. ' ', bg, tc)
|
||||
local ss = self.entry.scroll > 0 and '\183' or ' '
|
||||
self:write(2, 1, Util.widthify(text, self.width - 2) .. ' ', bg, tc)
|
||||
self:write(1, 1, ss, bg, self.shadowTextColor)
|
||||
|
||||
if self.entry.mark.active then
|
||||
local tx = math.max(self.entry.mark.x - self.entry.scroll, 0)
|
||||
@@ -85,7 +87,7 @@ function UI.TextEntry:draw()
|
||||
end
|
||||
|
||||
if tx ~= tex then
|
||||
self:write(tx + 2, 1, text:sub(tx + 1, tex), colors.gray, tc)
|
||||
self:write(tx + 2, 1, text:sub(tx + 1, tex), self.markBackgroundColor, tc)
|
||||
end
|
||||
end
|
||||
if self.focused then
|
||||
@@ -106,13 +108,12 @@ function UI.TextEntry:updateCursor()
|
||||
self:setCursorPos(self.entry.pos - self.entry.scroll + 2, 1)
|
||||
end
|
||||
|
||||
function UI.TextEntry:markAll()
|
||||
self.entry:markAll()
|
||||
end
|
||||
|
||||
function UI.TextEntry:focus()
|
||||
self:draw()
|
||||
if self.focused then
|
||||
self:setCursorBlink(true)
|
||||
else
|
||||
self:setCursorBlink(false)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.TextEntry:eventHandler(event)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,59 +1,20 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
-- For manipulating text in a fixed width string
|
||||
local SB = class()
|
||||
function SB:init(width)
|
||||
self.width = width
|
||||
self.buf = _rep(' ', width)
|
||||
end
|
||||
function SB:insert(x, str, width)
|
||||
if x < 1 then
|
||||
x = self.width + x + 1
|
||||
end
|
||||
width = width or #str
|
||||
if x + width - 1 > self.width then
|
||||
width = self.width - x
|
||||
end
|
||||
if width > 0 then
|
||||
self.buf = _sub(self.buf, 1, x - 1) .. _sub(str, 1, width) .. _sub(self.buf, x + width)
|
||||
end
|
||||
end
|
||||
function SB:fill(x, ch, width)
|
||||
width = width or self.width - x + 1
|
||||
self:insert(x, _rep(ch, width))
|
||||
end
|
||||
function SB:center(str)
|
||||
self:insert(math.max(1, math.ceil((self.width - #str + 1) / 2)), str)
|
||||
end
|
||||
function SB:get()
|
||||
return self.buf
|
||||
end
|
||||
|
||||
UI.TitleBar = class(UI.Window)
|
||||
UI.TitleBar.defaults = {
|
||||
UIElement = 'TitleBar',
|
||||
height = 1,
|
||||
textColor = colors.white,
|
||||
backgroundColor = colors.cyan,
|
||||
title = '',
|
||||
frameChar = UI.extChars and '\140' or '-',
|
||||
closeInd = UI.extChars and '\215' or '*',
|
||||
}
|
||||
function UI.TitleBar:draw()
|
||||
local sb = SB(self.width)
|
||||
sb:fill(2, self.frameChar, sb.width - 3)
|
||||
sb:center(string.format(' %s ', self.title))
|
||||
self:fillArea(2, 1, self.width - 2, 1, self.frameChar)
|
||||
self:centeredWrite(1, string.format(' %s ', self.title))
|
||||
if self.previousPage or self.event then
|
||||
sb:insert(-1, self.closeInd)
|
||||
else
|
||||
sb:insert(-2, self.frameChar)
|
||||
self:write(self.width - 1, 1, ' ' .. self.closeInd)
|
||||
end
|
||||
self:write(1, 1, sb:get())
|
||||
end
|
||||
|
||||
function UI.TitleBar:eventHandler(event)
|
||||
@@ -69,5 +30,74 @@ function UI.TitleBar:eventHandler(event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
elseif event.type == 'mouse_down' then
|
||||
self.anchor = { x = event.x, y = event.y, ox = self.parent.x, oy = self.parent.y, h = self.parent.height }
|
||||
|
||||
elseif event.type == 'mouse_drag' then
|
||||
if self.expand == 'height' then
|
||||
local d = event.dy
|
||||
if self.anchor.h - d > 0 and self.anchor.oy + d > 0 then
|
||||
self.parent:reposition(self.parent.x, self.anchor.oy + event.dy, self.width, self.anchor.h - d)
|
||||
end
|
||||
|
||||
elseif self.moveable then
|
||||
local d = event.dy
|
||||
if self.anchor.oy + d > 0 and self.anchor.oy + d <= self.parent.parent.height then
|
||||
self.parent:move(self.anchor.ox + event.dx, self.anchor.oy + event.dy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.TitleBar.example()
|
||||
return UI.Window {
|
||||
win1 = UI.Window {
|
||||
x = 9, y = 2, ex = -7, ey = -3,
|
||||
backgroundColor = 'green',
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'A really, really, really long title', moveable = true,
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'Press',
|
||||
},
|
||||
focus = function (self)
|
||||
self:raise()
|
||||
end,
|
||||
},
|
||||
win2 = UI.Window {
|
||||
x = 7, y = 3, ex = -9, ey = -2,
|
||||
backgroundColor = 'orange',
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'test', moveable = true,
|
||||
event = 'none',
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'Press',
|
||||
},
|
||||
focus = function (self)
|
||||
self:raise()
|
||||
end,
|
||||
},
|
||||
draw = function(self, isBG)
|
||||
for i = 1, self.height do
|
||||
self:write(1, i, self.filler or '')
|
||||
end
|
||||
if not isBG then
|
||||
for _,v in pairs(self.children) do
|
||||
v:draw()
|
||||
end
|
||||
end
|
||||
end,
|
||||
enable = function (self)
|
||||
require('opus.event').onInterval(.5, function()
|
||||
self.filler = string.rep(string.char(math.random(33, 126)), self.width)
|
||||
self:draw(true)
|
||||
self:sync()
|
||||
end)
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.VerticalMeter = class(UI.Window)
|
||||
UI.VerticalMeter.defaults = {
|
||||
UIElement = 'VerticalMeter',
|
||||
backgroundColor = colors.gray,
|
||||
meterColor = colors.lime,
|
||||
backgroundColor = 'gray',
|
||||
meterColor = 'lime',
|
||||
width = 1,
|
||||
value = 0,
|
||||
}
|
||||
@@ -18,12 +16,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()
|
||||
@@ -31,4 +28,4 @@ function UI.VerticalMeter.example()
|
||||
return UI.VerticalMeter.enable(self)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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,60 @@ 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
|
||||
function UI.Viewport:blit(x, y, text, bg, fg)
|
||||
if y > #self.lines then
|
||||
self:resizeBuffer(self.width, y)
|
||||
end
|
||||
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
|
||||
|
||||
function UI.Viewport:setViewHeight(h)
|
||||
if h > #self.lines then
|
||||
self:resizeBuffer(self.width, h)
|
||||
end
|
||||
return UI.Window.write(self, x, y, text, bg, tc)
|
||||
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 +78,33 @@ 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 #self.lines <= self.height then
|
||||
return
|
||||
end
|
||||
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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -2,50 +2,85 @@ local Tween = require('opus.ui.tween')
|
||||
|
||||
local Transition = { }
|
||||
|
||||
function Transition.slideLeft(args)
|
||||
local ticks = args.ticks or 10
|
||||
local easing = args.easing or 'outQuint'
|
||||
local pos = { x = args.ex }
|
||||
local tween = Tween.new(ticks, pos, { x = args.x }, easing)
|
||||
function Transition.slideLeft(canvas, args)
|
||||
local ticks = args.ticks or 6
|
||||
local easing = args.easing or 'inCirc'
|
||||
local pos = { x = canvas.ex }
|
||||
local tween = Tween.new(ticks, pos, { x = canvas.x }, easing)
|
||||
|
||||
args.canvas:move(pos.x, args.canvas.y)
|
||||
canvas:move(pos.x, canvas.y)
|
||||
|
||||
return function()
|
||||
local finished = tween:update(1)
|
||||
args.canvas:move(math.floor(pos.x), args.canvas.y)
|
||||
args.canvas:dirty()
|
||||
canvas:move(math.floor(pos.x), canvas.y)
|
||||
canvas:dirty(true)
|
||||
return not finished
|
||||
end
|
||||
end
|
||||
|
||||
function Transition.slideRight(args)
|
||||
local ticks = args.ticks or 10
|
||||
local easing = args.easing or'outQuint'
|
||||
local pos = { x = -args.canvas.width }
|
||||
function Transition.slideRight(canvas, args)
|
||||
local ticks = args.ticks or 6
|
||||
local easing = args.easing or 'inCirc'
|
||||
local pos = { x = -canvas.width }
|
||||
local tween = Tween.new(ticks, pos, { x = 1 }, easing)
|
||||
|
||||
args.canvas:move(pos.x, args.canvas.y)
|
||||
canvas:move(pos.x, canvas.y)
|
||||
|
||||
return function()
|
||||
local finished = tween:update(1)
|
||||
args.canvas:move(math.floor(pos.x), args.canvas.y)
|
||||
args.canvas:dirty()
|
||||
canvas:move(math.floor(pos.x), canvas.y)
|
||||
canvas:dirty(true)
|
||||
return not finished
|
||||
end
|
||||
end
|
||||
|
||||
function Transition.expandUp(args)
|
||||
function Transition.expandUp(canvas, args)
|
||||
local ticks = args.ticks or 3
|
||||
local easing = args.easing or 'linear'
|
||||
local pos = { y = args.ey + 1 }
|
||||
local tween = Tween.new(ticks, pos, { y = args.y }, easing)
|
||||
local pos = { y = canvas.ey + 1 }
|
||||
local tween = Tween.new(ticks, pos, { y = canvas.y }, easing)
|
||||
|
||||
args.canvas:move(args.x, pos.y)
|
||||
canvas:move(canvas.x, pos.y)
|
||||
|
||||
return function()
|
||||
local finished = tween:update(1)
|
||||
args.canvas:move(args.x, math.floor(pos.y))
|
||||
args.canvas:dirty()
|
||||
canvas:move(canvas.x, math.floor(pos.y))
|
||||
canvas.parent:dirty(true)
|
||||
return not finished
|
||||
end
|
||||
end
|
||||
|
||||
function Transition.shake(canvas, args)
|
||||
local ticks = args.ticks or 8
|
||||
local i = ticks
|
||||
|
||||
return function()
|
||||
i = -i
|
||||
canvas:move(canvas.x + i, canvas.y)
|
||||
if i > 0 then
|
||||
i = i - 2
|
||||
end
|
||||
return i ~= 0
|
||||
end
|
||||
end
|
||||
|
||||
function Transition.shuffle(canvas, args)
|
||||
local ticks = args.ticks or 4
|
||||
local easing = args.easing or 'linear'
|
||||
local t = { }
|
||||
|
||||
for _,child in pairs(canvas.children) do
|
||||
t[child] = Tween.new(ticks, child, { x = child.x, y = child.y }, easing)
|
||||
child.x = math.random(1, canvas.parent.width)
|
||||
child.y = math.random(1, canvas.parent.height)
|
||||
end
|
||||
|
||||
return function()
|
||||
local finished
|
||||
for child, tween in pairs(t) do
|
||||
finished = tween:update(1)
|
||||
child:move(math.floor(child.x), math.floor(child.y))
|
||||
end
|
||||
return not finished
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,6 +13,7 @@ local _unpack = table.unpack
|
||||
local _bor = bit32.bor
|
||||
local _bxor = bit32.bxor
|
||||
|
||||
local byteArrayMT
|
||||
byteArrayMT = {
|
||||
__tostring = function(a) return string.char(_unpack(a)) end,
|
||||
__index = {
|
||||
@@ -668,42 +669,31 @@ function Util.trimr(s)
|
||||
end
|
||||
-- end http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76
|
||||
|
||||
-- word wrapping based on:
|
||||
-- https://www.rosettacode.org/wiki/Word_wrap#Lua and
|
||||
-- http://lua-users.org/wiki/StringRecipes
|
||||
local function paragraphwrap(text, linewidth, res)
|
||||
linewidth = linewidth or 75
|
||||
local spaceleft = linewidth
|
||||
local line = { }
|
||||
|
||||
for word in text:gmatch("%S+") do
|
||||
local len = #word + 1
|
||||
|
||||
--if colorMode then
|
||||
-- word:gsub('()@([@%d])', function(pos, c) len = len - 2 end)
|
||||
--end
|
||||
|
||||
if len > spaceleft then
|
||||
table.insert(res, table.concat(line, ' '))
|
||||
line = { word }
|
||||
spaceleft = linewidth - len - 1
|
||||
local function wrap(text, max, lines)
|
||||
local index = 1
|
||||
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
|
||||
table.insert(line, word)
|
||||
spaceleft = spaceleft - len
|
||||
local x = text:sub(1, max)
|
||||
local s = x:match('(.*) ') or x
|
||||
text = text:sub(#s + 1)
|
||||
table.insert(lines, s)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(res, table.concat(line, ' '))
|
||||
return table.concat(res, '\n')
|
||||
text = text:match('^%s*(.*)')
|
||||
until not text or #text == 0
|
||||
return lines
|
||||
end
|
||||
-- end word wrapping
|
||||
|
||||
function Util.wordWrap(str, limit)
|
||||
local longLines = Util.split(str)
|
||||
local lines = { }
|
||||
|
||||
for _,line in ipairs(longLines) do
|
||||
paragraphwrap(line, limit, lines)
|
||||
for _,line in ipairs(Util.split(str)) do
|
||||
wrap(line, limit, lines)
|
||||
end
|
||||
|
||||
return lines
|
||||
|
||||
Reference in New Issue
Block a user