mirror of
https://github.com/kepler155c/opus
synced 2025-01-16 18:32:52 +00:00
ui overhaul
This commit is contained in:
parent
89400ac1bd
commit
915085ac5f
@ -7,6 +7,9 @@ return function(base)
|
||||
local c = { } -- a new class instance
|
||||
if type(base) == 'table' then
|
||||
-- our new class is a shallow copy of the base class!
|
||||
if base._preload then
|
||||
base = base._preload(base)
|
||||
end
|
||||
for i,v in pairs(base) do
|
||||
c[i] = v
|
||||
end
|
||||
|
@ -1,205 +1,202 @@
|
||||
local Canvas = require('ui.canvas')
|
||||
|
||||
local colors = _G.colors
|
||||
local term = _G.term
|
||||
local _gsub = string.gsub
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
local Terminal = { }
|
||||
|
||||
-- add scrolling functions to a window
|
||||
function Terminal.scrollable(win, maxScroll)
|
||||
local lines = { }
|
||||
local scrollPos = 0
|
||||
local oblit, oreposition = win.blit, win.reposition
|
||||
|
||||
local palette = { }
|
||||
for n = 1, 16 do
|
||||
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
-- Replacement for window api with scrolling and buffering
|
||||
function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
isVisible = isVisible ~= false
|
||||
if not w or not h then
|
||||
w, h = parent.getSize()
|
||||
end
|
||||
|
||||
maxScroll = maxScroll or 100
|
||||
local win = { }
|
||||
local maxScroll = 100
|
||||
local cx, cy = 1, 1
|
||||
local blink = false
|
||||
local bg, fg = parent.getBackgroundColor(), parent.getTextColor()
|
||||
|
||||
-- should only do if window is visible...
|
||||
local function redraw()
|
||||
local _, h = win.getSize()
|
||||
local x, y = win.getCursorPos()
|
||||
for i = 1, h do
|
||||
local line = lines[i + scrollPos]
|
||||
if line and line.dirty then
|
||||
win.setCursorPos(1, i)
|
||||
oblit(line.text, line.fg, line.bg)
|
||||
line.dirty = false
|
||||
end
|
||||
end
|
||||
win.setCursorPos(x, y)
|
||||
end
|
||||
local canvas = Canvas({
|
||||
x = sx,
|
||||
y = sy,
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = parent.isColor(),
|
||||
})
|
||||
canvas.offy = 0
|
||||
|
||||
local function scrollTo(p, forceRedraw)
|
||||
local _, h = win.getSize()
|
||||
local ms = #lines - h -- max scroll
|
||||
p = math.min(math.max(p, 0), ms) -- normalize
|
||||
|
||||
if p ~= scrollPos or forceRedraw then
|
||||
scrollPos = p
|
||||
for _, line in pairs(lines) do
|
||||
line.dirty = true
|
||||
end
|
||||
local function update()
|
||||
if isVisible then
|
||||
canvas:render(parent)
|
||||
win.setCursorPos(cx, cy)
|
||||
end
|
||||
end
|
||||
|
||||
function win.write(text)
|
||||
local _, h = win.getSize()
|
||||
local function scrollTo(y)
|
||||
y = math.max(0, y)
|
||||
y = math.min(#canvas.lines - canvas.height, y)
|
||||
|
||||
text = tostring(text) or ''
|
||||
scrollTo(#lines - h)
|
||||
win.blit(text,
|
||||
_rep(palette[win.getTextColor()], #text),
|
||||
_rep(palette[win.getBackgroundColor()], #text))
|
||||
local x, y = win.getCursorPos()
|
||||
win.setCursorPos(x + #text, y)
|
||||
end
|
||||
|
||||
function win.clearLine()
|
||||
local w, h = win.getSize()
|
||||
local _, y = win.getCursorPos()
|
||||
|
||||
scrollTo(#lines - h)
|
||||
lines[y + scrollPos] = {
|
||||
text = _rep(' ', w),
|
||||
fg = _rep(palette[win.getTextColor()], w),
|
||||
bg = _rep(palette[win.getBackgroundColor()], w),
|
||||
dirty = true,
|
||||
}
|
||||
redraw()
|
||||
end
|
||||
|
||||
function win.blit(text, fg, bg)
|
||||
local x, y = win.getCursorPos()
|
||||
local w, h = win.getSize()
|
||||
|
||||
if y > 0 and y <= h and x <= w then
|
||||
local width = #text
|
||||
|
||||
-- fix ffs
|
||||
if x < 1 then
|
||||
text = _sub(text, 2 - x)
|
||||
if bg then
|
||||
bg = _sub(bg, 2 - x)
|
||||
end
|
||||
if bg then
|
||||
fg = _sub(fg, 2 - x)
|
||||
end
|
||||
width = width + x - 1
|
||||
x = 1
|
||||
end
|
||||
|
||||
if x + width - 1 > w then
|
||||
text = _sub(text, 1, w - x + 1)
|
||||
if bg then
|
||||
bg = _sub(bg, 1, w - x + 1)
|
||||
end
|
||||
if bg then
|
||||
fg = _sub(fg, 1, w - x + 1)
|
||||
end
|
||||
width = #text
|
||||
end
|
||||
|
||||
if width > 0 then
|
||||
local function replace(sstr, pos, rstr)
|
||||
if pos == 1 and width == w then
|
||||
return rstr
|
||||
elseif pos == 1 then
|
||||
return rstr .. _sub(sstr, pos+width)
|
||||
elseif pos + width > w then
|
||||
return _sub(sstr, 1, pos-1) .. rstr
|
||||
end
|
||||
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
|
||||
end
|
||||
|
||||
local line = lines[y + scrollPos]
|
||||
line.dirty = true
|
||||
line.text = replace(line.text, x, text, width)
|
||||
if fg then
|
||||
line.fg = replace(line.fg, x, fg, width)
|
||||
end
|
||||
if bg then
|
||||
line.bg = replace(line.bg, x, bg, width)
|
||||
end
|
||||
end
|
||||
if y ~= canvas.offy then
|
||||
canvas.offy = y
|
||||
canvas:dirty()
|
||||
update()
|
||||
end
|
||||
redraw()
|
||||
end
|
||||
|
||||
function win.write(str)
|
||||
str = tostring(str) or ''
|
||||
canvas:write(cx, cy + canvas.offy, str, bg, fg)
|
||||
win.setCursorPos(cx + #str, cy)
|
||||
update()
|
||||
end
|
||||
|
||||
function win.blit(str, fg, bg)
|
||||
canvas:blit(cx, cy + canvas.offy, str, bg, fg)
|
||||
win.setCursorPos(cx + #str, cy)
|
||||
update()
|
||||
end
|
||||
|
||||
function win.clear()
|
||||
local w, h = win.getSize()
|
||||
|
||||
local text = _rep(' ', w)
|
||||
local fg = _rep(palette[win.getTextColor()], w)
|
||||
local bg = _rep(palette[win.getBackgroundColor()], w)
|
||||
lines = { }
|
||||
for y = 1, h do
|
||||
lines[y] = {
|
||||
dirty = true,
|
||||
text = text,
|
||||
fg = fg,
|
||||
bg = bg,
|
||||
}
|
||||
canvas.offy = 0
|
||||
canvas:clear(bg, fg)
|
||||
for i = #canvas.lines, canvas.height + 1, -1 do
|
||||
canvas.lines[i] = nil
|
||||
end
|
||||
scrollPos = 0
|
||||
redraw()
|
||||
update()
|
||||
end
|
||||
|
||||
function win.clearLine()
|
||||
canvas:clearLine(cy, bg, fg)
|
||||
win.setCursorPos(cx, cy)
|
||||
update()
|
||||
end
|
||||
|
||||
function win.getCursorPos()
|
||||
return cx, cy
|
||||
end
|
||||
|
||||
function win.setCursorPos(x, y)
|
||||
cx, cy = x, y
|
||||
parent.setCursorPos(x + canvas.x - 1, y + canvas.y - 1)
|
||||
end
|
||||
|
||||
function win.setCursorBlink(b)
|
||||
blink = b
|
||||
parent.setCursorBlink(b)
|
||||
end
|
||||
|
||||
function win.isColor()
|
||||
return canvas.isColor
|
||||
end
|
||||
win.isColour = win.isColor
|
||||
|
||||
function win.setTextColor(c)
|
||||
fg = c
|
||||
end
|
||||
win.setTextColour = win.setTextColor
|
||||
|
||||
function win.setBackgroundColor(c)
|
||||
bg = c
|
||||
end
|
||||
win.setBackgroundColour = win.setBackgroundColor
|
||||
|
||||
function win.getSize()
|
||||
return canvas.width, canvas.height
|
||||
end
|
||||
|
||||
-- doesn't support negative scrolling...
|
||||
function win.scroll(n)
|
||||
local w = win.getSize()
|
||||
|
||||
for _ = 1, n do
|
||||
lines[#lines + 1] = {
|
||||
text = _rep(' ', w),
|
||||
fg = _rep(palette[win.getTextColor()], w),
|
||||
bg = _rep(palette[win.getBackgroundColor()], w),
|
||||
}
|
||||
n = n or 1
|
||||
if n > 0 then
|
||||
for _ = 1, n do
|
||||
canvas.lines[#canvas.lines + 1] = { }
|
||||
canvas:clearLine(#canvas.lines, bg, fg)
|
||||
end
|
||||
while #canvas.lines > maxScroll do
|
||||
table.remove(canvas.lines, 1)
|
||||
end
|
||||
scrollTo(#canvas.lines - canvas.height)
|
||||
canvas.offy = #canvas.lines - canvas.height
|
||||
canvas:dirty()
|
||||
update()
|
||||
end
|
||||
end
|
||||
|
||||
while #lines > maxScroll do
|
||||
table.remove(lines, 1)
|
||||
function win.getTextColor()
|
||||
return fg
|
||||
end
|
||||
win.getTextColour = win.getTextColor
|
||||
|
||||
function win.getBackgroundColor()
|
||||
return bg
|
||||
end
|
||||
win.getBackgroundColour = win.getBackgroundColor
|
||||
|
||||
function win.setVisible(visible)
|
||||
if visible ~= isVisible then
|
||||
isVisible = visible
|
||||
if isVisible then
|
||||
canvas:dirty()
|
||||
update()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scrollTo(maxScroll, true)
|
||||
redraw()
|
||||
function win.redraw()
|
||||
if isVisible then
|
||||
canvas:dirty()
|
||||
canvas:render(parent)
|
||||
end
|
||||
end
|
||||
|
||||
function win.restoreCursor()
|
||||
win.setCursorPos(cx, cy)
|
||||
win.setCursorBlink(blink)
|
||||
end
|
||||
|
||||
function win.getPosition()
|
||||
return canvas.x, 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)
|
||||
end
|
||||
|
||||
--[[ Additional methods ]]--
|
||||
function win.scrollDown()
|
||||
scrollTo(canvas.offy + 1)
|
||||
end
|
||||
|
||||
function win.scrollUp()
|
||||
scrollTo(scrollPos - 1)
|
||||
redraw()
|
||||
scrollTo(canvas.offy - 1)
|
||||
end
|
||||
|
||||
function win.scrollDown()
|
||||
scrollTo(scrollPos + 1)
|
||||
redraw()
|
||||
function win.scrollTop()
|
||||
scrollTo(0)
|
||||
end
|
||||
|
||||
function win.reposition(x, y, nw, nh)
|
||||
local w, h = win.getSize()
|
||||
local D = (nh or h) - h
|
||||
|
||||
if D > 0 then
|
||||
for _ = 1, D do
|
||||
lines[#lines + 1] = {
|
||||
text = _rep(' ', w),
|
||||
fg = _rep(palette[win.getTextColor()], w),
|
||||
bg = _rep(palette[win.getBackgroundColor()], w),
|
||||
}
|
||||
end
|
||||
elseif D < 0 then
|
||||
for _ = D, -1 do
|
||||
lines[#lines] = nil
|
||||
end
|
||||
end
|
||||
return oreposition(x, y, nw, nh)
|
||||
function win.scrollBottom()
|
||||
scrollTo(#canvas.lines)
|
||||
end
|
||||
|
||||
win.clear()
|
||||
function win.setMaxScroll(ms)
|
||||
maxScroll = ms
|
||||
end
|
||||
|
||||
function win.getCanvas()
|
||||
return canvas
|
||||
end
|
||||
|
||||
function win.getParent()
|
||||
return parent
|
||||
end
|
||||
|
||||
canvas:clear()
|
||||
|
||||
return win
|
||||
end
|
||||
|
||||
-- get windows contents
|
||||
|
2400
sys/apis/ui.lua
2400
sys/apis/ui.lua
File diff suppressed because it is too large
Load Diff
@ -2,9 +2,9 @@ local class = require('class')
|
||||
local Region = require('ui.region')
|
||||
local Util = require('util')
|
||||
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
local _gsub = string.gsub
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
local _gsub = string.gsub
|
||||
local colors = _G.colors
|
||||
|
||||
local Canvas = class()
|
||||
@ -19,6 +19,10 @@ for n = 1, 16 do
|
||||
Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n)
|
||||
end
|
||||
|
||||
--[[
|
||||
A canvas can have more lines than canvas.height in order to scroll
|
||||
]]
|
||||
|
||||
function Canvas:init(args)
|
||||
self.x = 1
|
||||
self.y = 1
|
||||
@ -50,7 +54,7 @@ function Canvas:move(x, y)
|
||||
end
|
||||
|
||||
function Canvas:resize(w, h)
|
||||
for i = self.height, h do
|
||||
for i = #self.lines, h do
|
||||
self.lines[i] = { }
|
||||
end
|
||||
|
||||
@ -78,7 +82,7 @@ function Canvas:copy()
|
||||
height = self.height,
|
||||
isColor = self.isColor,
|
||||
})
|
||||
for i = 1, self.height do
|
||||
for i = 1, #self.lines do
|
||||
b.lines[i].text = self.lines[i].text
|
||||
b.lines[i].fg = self.lines[i].fg
|
||||
b.lines[i].bg = self.lines[i].bg
|
||||
@ -117,6 +121,19 @@ function Canvas:setVisible(visible)
|
||||
end
|
||||
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 v == self then
|
||||
table.insert(layers, table.remove(layers, k))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:write(x, y, text, bg, fg)
|
||||
if bg then
|
||||
bg = _rep(self.palette[bg], #text)
|
||||
@ -124,10 +141,10 @@ function Canvas:write(x, y, text, bg, fg)
|
||||
if fg then
|
||||
fg = _rep(self.palette[fg], #text)
|
||||
end
|
||||
self:writeBlit(x, y, text, bg, fg)
|
||||
self:blit(x, y, text, bg, fg)
|
||||
end
|
||||
|
||||
function Canvas:writeBlit(x, y, text, bg, fg)
|
||||
function Canvas:blit(x, y, text, bg, fg)
|
||||
if y > 0 and y <= #self.lines and x <= self.width then
|
||||
local width = #text
|
||||
|
||||
@ -157,7 +174,7 @@ function Canvas:writeBlit(x, y, text, bg, fg)
|
||||
|
||||
if width > 0 then
|
||||
|
||||
local function replace(sstr, pos, rstr, width)
|
||||
local function replace(sstr, pos, rstr)
|
||||
if pos == 1 and width == self.width then
|
||||
return rstr
|
||||
elseif pos == 1 then
|
||||
@ -188,39 +205,48 @@ function Canvas:writeLine(y, text, fg, bg)
|
||||
self.lines[y].bg = bg
|
||||
end
|
||||
|
||||
function Canvas:reset()
|
||||
self.regions = nil
|
||||
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)
|
||||
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)
|
||||
for i = 1, self.height do
|
||||
for i = 1, #self.lines do
|
||||
self:writeLine(i, text, fg, bg)
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:punch(rect)
|
||||
if not self.regions then
|
||||
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
||||
end
|
||||
self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey)
|
||||
local offset = { x = 0, y = 0 }
|
||||
|
||||
|
||||
self.regions:subRect(rect.x + offset.x, rect.y + offset.y, rect.ex + offset.x, rect.ey + offset.y)
|
||||
end
|
||||
|
||||
function Canvas:blitClipped(device)
|
||||
function Canvas:blitClipped(device, offset)
|
||||
offset = { x = self.x, y = self.y }
|
||||
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
|
||||
for _,region in ipairs(self.regions.region) do
|
||||
self:blit(device,
|
||||
{ x = region[1] - self.x + 1,
|
||||
y = region[2] - self.y + 1,
|
||||
ex = region[3]- self.x + 1,
|
||||
ey = region[4] - self.y + 1 },
|
||||
{ x = region[1], y = region[2] })
|
||||
self:blitRect(device,
|
||||
{ x = region[1],
|
||||
y = region[2],
|
||||
ex = region[3],
|
||||
ey = region[4] },
|
||||
{ x = region[1] + offset.x - 1, y = region[2] + offset.y - 1 })
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:redraw(device)
|
||||
self:reset()
|
||||
--[[
|
||||
if #self.layers > 0 then
|
||||
for _,layer in pairs(self.layers) do
|
||||
self:punch(layer)
|
||||
@ -230,19 +256,20 @@ function Canvas:redraw(device)
|
||||
self:blit(device)
|
||||
end
|
||||
self:clean()
|
||||
]]
|
||||
end
|
||||
|
||||
function Canvas:isDirty()
|
||||
for _, line in pairs(self.lines) do
|
||||
if line.dirty then
|
||||
for i = 1, #self.lines do
|
||||
if self.lines[i].dirty then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:dirty()
|
||||
for _, line in pairs(self.lines) do
|
||||
line.dirty = true
|
||||
for i = 1, #self.lines do
|
||||
self.lines[i].dirty = true
|
||||
end
|
||||
if self.layers then
|
||||
for _, canvas in pairs(self.layers) do
|
||||
@ -252,37 +279,81 @@ function Canvas:dirty()
|
||||
end
|
||||
|
||||
function Canvas:clean()
|
||||
for _, line in pairs(self.lines) do
|
||||
line.dirty = false
|
||||
for i = 1, #self.lines do
|
||||
self.lines[i].dirty = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:render(device, layers) --- redrawAll ?
|
||||
layers = layers or self.layers
|
||||
if #layers > 0 then
|
||||
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
||||
local l = Util.shallowCopy(layers)
|
||||
for _, canvas in ipairs(layers) do
|
||||
table.remove(l, 1)
|
||||
function Canvas:renderLayers(device, offset)
|
||||
if not offset then
|
||||
offset = { x = self.x, y = self.y }
|
||||
end
|
||||
if #self.layers > 0 then
|
||||
self.regions = Region.new(1, 1, self.ex, self.ey)
|
||||
|
||||
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)
|
||||
canvas:render(device, l)
|
||||
|
||||
-- get the area to render for this layer
|
||||
canvas.regions = Region.new(canvas.x, canvas.y, canvas.ex, canvas.ey)
|
||||
|
||||
-- determine if we should render this layer by punching
|
||||
-- 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])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:blitClipped(device)
|
||||
self:reset()
|
||||
|
||||
for _, canvas in ipairs(self.layers) do
|
||||
if canvas.visible and #canvas.regions.region > 0 then
|
||||
canvas:renderLayers(device, {
|
||||
x = canvas.x, --offset.x + self.x - 1,
|
||||
y = canvas.y,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self:blitClipped(device, offset)
|
||||
|
||||
--elseif #self.regions.region > 0 then
|
||||
-- self:blitClipped(device, offset)
|
||||
|
||||
else
|
||||
self:blit(device)
|
||||
offset = { x = self.x, y = self.y }
|
||||
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
|
||||
self:blitRect(device, nil, offset)
|
||||
end
|
||||
self:clean()
|
||||
end
|
||||
|
||||
function Canvas:blit(device, src, tgt)
|
||||
function Canvas:render(device)
|
||||
--_G._p = self
|
||||
if #self.layers > 0 then
|
||||
self:renderLayers(device)
|
||||
else
|
||||
self:blitRect(device)
|
||||
self:clean()
|
||||
end
|
||||
end
|
||||
|
||||
function Canvas:blitRect(device, src, tgt)
|
||||
src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
|
||||
tgt = tgt or self
|
||||
|
||||
for i = 0, src.ey - src.y do
|
||||
local line = self.lines[src.y + i]
|
||||
local line = self.lines[src.y + i + (self.offy or 0)]
|
||||
if line and line.dirty then
|
||||
local t, fg, bg = line.text, line.fg, line.bg
|
||||
if src.x > 1 or src.ex < self.ex then
|
||||
@ -290,13 +361,11 @@ function Canvas:blit(device, src, tgt)
|
||||
fg = _sub(fg, src.x, src.ex)
|
||||
bg = _sub(bg, src.x, src.ex)
|
||||
end
|
||||
--if tgt.y + i > self.ey then -- wrong place to do clipping ??
|
||||
-- break
|
||||
--end
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
end
|
||||
end
|
||||
--os.sleep(.1)
|
||||
end
|
||||
|
||||
function Canvas:applyPalette(palette)
|
||||
@ -351,7 +420,7 @@ function Canvas.convertWindow(win, parent, wx, wy)
|
||||
|
||||
function win.blit(text, fg, bg)
|
||||
local x, y = win.getCursorPos()
|
||||
win.canvas:writeBlit(x, y, text, bg, fg)
|
||||
win.canvas:blit(x, y, text, bg, fg)
|
||||
end
|
||||
|
||||
function win.redraw()
|
||||
@ -372,132 +441,4 @@ function Canvas.convertWindow(win, parent, wx, wy)
|
||||
win.clear()
|
||||
end
|
||||
|
||||
function Canvas.scrollingWindow(win, wx, wy)
|
||||
local w, h = win.getSize()
|
||||
local scrollPos = 0
|
||||
local maxScroll = h
|
||||
|
||||
-- canvas lines are are a sliding window within the local lines table
|
||||
local lines = { }
|
||||
|
||||
local parent
|
||||
local canvas = Canvas({
|
||||
x = wx,
|
||||
y = wy,
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = win.isColor(),
|
||||
})
|
||||
win.canvas = canvas
|
||||
|
||||
local function scrollTo(p, forceRedraw)
|
||||
local ms = #lines - canvas.height -- max scroll
|
||||
p = math.min(math.max(p, 0), ms) -- normalize
|
||||
|
||||
if p ~= scrollPos or forceRedraw then
|
||||
scrollPos = p
|
||||
for i = 1, canvas.height do
|
||||
canvas.lines[i] = lines[i + scrollPos]
|
||||
end
|
||||
canvas:dirty()
|
||||
end
|
||||
end
|
||||
|
||||
function win.blit(text, fg, bg)
|
||||
local x, y = win.getCursorPos()
|
||||
win.canvas:writeBlit(x, y, text, bg, fg)
|
||||
win.redraw()
|
||||
end
|
||||
|
||||
function win.clear()
|
||||
lines = { }
|
||||
for i = 1, canvas.height do
|
||||
lines[i] = canvas.lines[i]
|
||||
end
|
||||
scrollPos = 0
|
||||
canvas:clear(win.getBackgroundColor(), win.getTextColor())
|
||||
win.redraw()
|
||||
end
|
||||
|
||||
function win.clearLine()
|
||||
local _, y = win.getCursorPos()
|
||||
|
||||
scrollTo(#lines - canvas.height)
|
||||
win.canvas:write(1,
|
||||
y,
|
||||
_rep(' ', win.canvas.width),
|
||||
win.getBackgroundColor(),
|
||||
win.getTextColor())
|
||||
win.redraw()
|
||||
end
|
||||
|
||||
function win.redraw()
|
||||
if parent and canvas.visible then
|
||||
local x, y = win.getCursorPos()
|
||||
for i = 1, canvas.height do
|
||||
local line = canvas.lines[i]
|
||||
if line and line.dirty then
|
||||
parent.setCursorPos(canvas.x, canvas.y + i - 1)
|
||||
parent.blit(line.text, line.fg, line.bg)
|
||||
line.dirty = false
|
||||
end
|
||||
end
|
||||
win.setCursorPos(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
-- doesn't support negative scrolling...
|
||||
function win.scroll(n)
|
||||
for _ = 1, n do
|
||||
lines[#lines + 1] = {
|
||||
text = _rep(' ', canvas.width),
|
||||
fg = _rep(canvas.palette[win.getTextColor()], canvas.width),
|
||||
bg = _rep(canvas.palette[win.getBackgroundColor()], canvas.width),
|
||||
}
|
||||
end
|
||||
|
||||
while #lines > maxScroll do
|
||||
table.remove(lines, 1)
|
||||
end
|
||||
|
||||
scrollTo(maxScroll, true)
|
||||
win.redraw()
|
||||
end
|
||||
|
||||
function win.scrollDown()
|
||||
scrollTo(scrollPos + 1)
|
||||
win.redraw()
|
||||
end
|
||||
|
||||
function win.scrollUp()
|
||||
scrollTo(scrollPos - 1)
|
||||
win.redraw()
|
||||
end
|
||||
|
||||
function win.setMaxScroll(ms)
|
||||
maxScroll = ms
|
||||
end
|
||||
|
||||
function win.setParent(p)
|
||||
parent = p
|
||||
end
|
||||
|
||||
function win.write(str)
|
||||
str = tostring(str) or ''
|
||||
|
||||
local x, y = win.getCursorPos()
|
||||
scrollTo(#lines - canvas.height)
|
||||
win.blit(str,
|
||||
_rep(canvas.palette[win.getTextColor()], #str),
|
||||
_rep(canvas.palette[win.getBackgroundColor()], #str))
|
||||
win.setCursorPos(x + #str, y)
|
||||
end
|
||||
|
||||
function win.reposition(x, y, width, height)
|
||||
win.canvas.x, win.canvas.y = x, y
|
||||
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
|
||||
end
|
||||
|
||||
win.clear()
|
||||
end
|
||||
return Canvas
|
||||
|
30
sys/apis/ui/components/ActiveLayer.lua
Normal file
30
sys/apis/ui/components/ActiveLayer.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
UI.ActiveLayer = class(UI.Window)
|
||||
UI.ActiveLayer.defaults = {
|
||||
UIElement = 'ActiveLayer',
|
||||
}
|
||||
function UI.ActiveLayer:setParent()
|
||||
self:layout(self)
|
||||
self.canvas = self:addLayer()
|
||||
|
||||
UI.Window.setParent(self)
|
||||
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
|
66
sys/apis/ui/components/Button.lua
Normal file
66
sys/apis/ui/components/Button.lua
Normal file
@ -0,0 +1,66 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('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,
|
||||
centered = true,
|
||||
height = 1,
|
||||
focusIndicator = ' ',
|
||||
event = 'button_press',
|
||||
accelerators = {
|
||||
space = 'button_activate',
|
||||
enter = 'button_activate',
|
||||
mouse_click = 'button_activate',
|
||||
}
|
||||
}
|
||||
function UI.Button:setParent()
|
||||
if not self.width and not self.ex then
|
||||
self.width = #self.text + 2
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Button:draw()
|
||||
local fg = self.textColor
|
||||
local bg = self.backgroundColor
|
||||
local ind = ' '
|
||||
if self.focused then
|
||||
bg = self.backgroundFocusColor
|
||||
fg = self.textFocusColor
|
||||
ind = self.focusIndicator
|
||||
elseif self.inactive then
|
||||
fg = self.textInactiveColor
|
||||
end
|
||||
local text = ind .. self.text .. ' '
|
||||
if self.centered then
|
||||
self:clear(bg)
|
||||
self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg)
|
||||
else
|
||||
self:write(1, 1, Util.widthify(text, self.width), bg, fg)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Button:focus()
|
||||
if self.focused then
|
||||
self:scrollIntoView()
|
||||
end
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function UI.Button:eventHandler(event)
|
||||
if event.type == 'button_activate' then
|
||||
self:emit({ type = self.event, button = self })
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
67
sys/apis/ui/components/Checkbox.lua
Normal file
67
sys/apis/ui/components/Checkbox.lua
Normal file
@ -0,0 +1,67 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Checkbox = class(UI.Window)
|
||||
UI.Checkbox.defaults = {
|
||||
UIElement = 'Checkbox',
|
||||
nochoice = 'Select',
|
||||
checkedIndicator = 'X',
|
||||
leftMarker = '[',
|
||||
rightMarker = ']',
|
||||
value = false,
|
||||
textColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
height = 1,
|
||||
accelerators = {
|
||||
space = 'checkbox_toggle',
|
||||
mouse_click = 'checkbox_toggle',
|
||||
}
|
||||
}
|
||||
function UI.Checkbox:setParent()
|
||||
if not self.width and not self.ex then
|
||||
self.width = (self.label and #self.label or 0) + 3
|
||||
else
|
||||
self.widthh = 3
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Checkbox:draw()
|
||||
local bg = self.backgroundColor
|
||||
if self.focused then
|
||||
bg = self.backgroundFocusColor
|
||||
end
|
||||
if type(self.value) == 'string' then
|
||||
self.value = nil -- TODO: fix form
|
||||
end
|
||||
local text = string.format('[%s]', not self.value and ' ' or self.checkedIndicator)
|
||||
local x = 1
|
||||
if self.label then
|
||||
self:write(1, 1, self.label)
|
||||
x = #self.label + 2
|
||||
end
|
||||
self:write(x, 1, text, bg)
|
||||
self:write(x, 1, self.leftMarker, self.backgroundColor, self.textColor)
|
||||
self:write(x + 1, 1, not self.value and ' ' or self.checkedIndicator, bg)
|
||||
self:write(x + 2, 1, self.rightMarker, self.backgroundColor, self.textColor)
|
||||
end
|
||||
|
||||
function UI.Checkbox:focus()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function UI.Checkbox:reset()
|
||||
self.value = false
|
||||
end
|
||||
|
||||
function UI.Checkbox:eventHandler(event)
|
||||
if event.type == 'checkbox_toggle' then
|
||||
self.value = not self.value
|
||||
self:emit({ type = 'checkbox_change', checked = self.value, element = self })
|
||||
self:draw()
|
||||
return true
|
||||
end
|
||||
end
|
88
sys/apis/ui/components/Chooser.lua
Normal file
88
sys/apis/ui/components/Chooser.lua
Normal file
@ -0,0 +1,88 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Chooser = class(UI.Window)
|
||||
UI.Chooser.defaults = {
|
||||
UIElement = 'Chooser',
|
||||
choices = { },
|
||||
nochoice = 'Select',
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
textInactiveColor = colors.gray,
|
||||
leftIndicator = '<',
|
||||
rightIndicator = '>',
|
||||
height = 1,
|
||||
}
|
||||
function UI.Chooser:setParent()
|
||||
if not self.width and not self.ex then
|
||||
self.width = 1
|
||||
for _,v in pairs(self.choices) do
|
||||
if #v.name > self.width then
|
||||
self.width = #v.name
|
||||
end
|
||||
end
|
||||
self.width = self.width + 4
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Chooser:draw()
|
||||
local bg = self.backgroundColor
|
||||
if self.focused then
|
||||
bg = self.backgroundFocusColor
|
||||
end
|
||||
local fg = self.inactive and self.textInactiveColor or self.textColor
|
||||
local choice = Util.find(self.choices, 'value', self.value)
|
||||
local value = self.nochoice
|
||||
if choice then
|
||||
value = choice.name
|
||||
end
|
||||
self:write(1, 1, self.leftIndicator, self.backgroundColor, colors.black)
|
||||
self:write(2, 1, ' ' .. Util.widthify(tostring(value), self.width-4) .. ' ', bg, fg)
|
||||
self:write(self.width, 1, self.rightIndicator, self.backgroundColor, colors.black)
|
||||
end
|
||||
|
||||
function UI.Chooser:focus()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function UI.Chooser:eventHandler(event)
|
||||
if event.type == 'key' then
|
||||
if event.key == 'right' or event.key == 'space' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if not k then k = 1 end
|
||||
if k and k < #self.choices then
|
||||
choice = self.choices[k+1]
|
||||
else
|
||||
choice = self.choices[1]
|
||||
end
|
||||
self.value = choice.value
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
elseif event.key == 'left' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if k and k > 1 then
|
||||
choice = self.choices[k-1]
|
||||
else
|
||||
choice = self.choices[#self.choices]
|
||||
end
|
||||
self.value = choice.value
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
end
|
||||
elseif event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then
|
||||
if event.x == 1 then
|
||||
self:emit({ type = 'key', key = 'left' })
|
||||
return true
|
||||
elseif event.x == self.width then
|
||||
self:emit({ type = 'key', key = 'right' })
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
39
sys/apis/ui/components/Dialog.lua
Normal file
39
sys/apis/ui/components/Dialog.lua
Normal file
@ -0,0 +1,39 @@
|
||||
local Canvas = require('ui.canvas')
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Dialog = class(UI.SlideOut)
|
||||
UI.Dialog.defaults = {
|
||||
UIElement = 'Dialog',
|
||||
height = 7,
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.white,
|
||||
okEvent ='dialog_ok',
|
||||
cancelEvent = 'dialog_cancel',
|
||||
}
|
||||
function UI.Dialog:postInit()
|
||||
self.y = -self.height
|
||||
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
|
71
sys/apis/ui/components/DropMenu.lua
Normal file
71
sys/apis/ui/components/DropMenu.lua
Normal file
@ -0,0 +1,71 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.DropMenu = class(UI.MenuBar)
|
||||
UI.DropMenu.defaults = {
|
||||
UIElement = 'DropMenu',
|
||||
backgroundColor = colors.white,
|
||||
buttonClass = 'DropMenuItem',
|
||||
}
|
||||
function UI.DropMenu:setParent()
|
||||
UI.MenuBar.setParent(self)
|
||||
|
||||
local maxWidth = 1
|
||||
for y,child in ipairs(self.children) do
|
||||
child.x = 1
|
||||
child.y = y
|
||||
if #(child.text or '') > maxWidth then
|
||||
maxWidth = #child.text
|
||||
end
|
||||
end
|
||||
for _,child in ipairs(self.children) do
|
||||
child.width = maxWidth + 2
|
||||
if child.spacer then
|
||||
child.text = string.rep('-', child.width - 2)
|
||||
end
|
||||
end
|
||||
|
||||
self.height = #self.children + 1
|
||||
self.width = maxWidth + 2
|
||||
self.ow = self.width
|
||||
|
||||
self.canvas = self:addLayer()
|
||||
end
|
||||
|
||||
function UI.DropMenu:enable()
|
||||
end
|
||||
|
||||
function UI.DropMenu:show(x, y)
|
||||
self.x, self.y = x, y
|
||||
self.canvas:move(x, y)
|
||||
self.canvas:setVisible(true)
|
||||
|
||||
UI.Window.enable(self)
|
||||
|
||||
self:draw()
|
||||
self:capture(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function UI.DropMenu:hide()
|
||||
self:disable()
|
||||
self.canvas:setVisible(false)
|
||||
self:release(self)
|
||||
end
|
||||
|
||||
function UI.DropMenu:eventHandler(event)
|
||||
if event.type == 'focus_lost' and self.enabled then
|
||||
if not Util.contains(self.children, event.focused) then
|
||||
self:hide()
|
||||
end
|
||||
elseif event.type == 'mouse_out' and self.enabled then
|
||||
self:hide()
|
||||
self:refocus()
|
||||
else
|
||||
return UI.MenuBar.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
21
sys/apis/ui/components/DropMenuItem.lua
Normal file
21
sys/apis/ui/components/DropMenuItem.lua
Normal file
@ -0,0 +1,21 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
--[[-- DropMenuItem --]]--
|
||||
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,
|
||||
}
|
||||
function UI.DropMenuItem:eventHandler(event)
|
||||
if event.type == 'button_activate' then
|
||||
self.parent:hide()
|
||||
end
|
||||
return UI.Button.eventHandler(self, event)
|
||||
end
|
69
sys/apis/ui/components/Embedded.lua
Normal file
69
sys/apis/ui/components/Embedded.lua
Normal file
@ -0,0 +1,69 @@
|
||||
local class = require('class')
|
||||
local Terminal = require('terminal')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Embedded = class(UI.Window)
|
||||
UI.Embedded.defaults = {
|
||||
UIElement = 'Embedded',
|
||||
backgroundColor = colors.black,
|
||||
textColor = colors.white,
|
||||
maxScroll = 100,
|
||||
accelerators = {
|
||||
up = 'scroll_up',
|
||||
down = 'scroll_down',
|
||||
}
|
||||
}
|
||||
function UI.Embedded:setParent()
|
||||
UI.Window.setParent(self)
|
||||
|
||||
self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false)
|
||||
self.win.setMaxScroll(self.maxScroll)
|
||||
|
||||
local canvas = self:getCanvas()
|
||||
self.win.getCanvas().parent = canvas
|
||||
table.insert(canvas.layers, self.win.getCanvas())
|
||||
self.canvas = self.win.getCanvas()
|
||||
|
||||
self.win.setCursorPos(1, 1)
|
||||
self.win.setBackgroundColor(self.backgroundColor)
|
||||
self.win.setTextColor(self.textColor)
|
||||
self.win.clear()
|
||||
end
|
||||
|
||||
function UI.Embedded:draw()
|
||||
self.canvas:dirty()
|
||||
end
|
||||
|
||||
function UI.Embedded:enable()
|
||||
self.canvas:setVisible(true)
|
||||
self.canvas:raise()
|
||||
if self.visible then
|
||||
-- the window will automatically update on changes
|
||||
-- the canvas does not need to be rendereed
|
||||
self.win.setVisible(true)
|
||||
end
|
||||
UI.Window.enable(self)
|
||||
self.canvas:dirty()
|
||||
end
|
||||
|
||||
function UI.Embedded:disable()
|
||||
self.canvas:setVisible(false)
|
||||
self.win.setVisible(false)
|
||||
UI.Window.disable(self)
|
||||
end
|
||||
|
||||
function UI.Embedded:eventHandler(event)
|
||||
if event.type == 'scroll_up' then
|
||||
self.win.scrollUp()
|
||||
return true
|
||||
elseif event.type == 'scroll_down' then
|
||||
self.win.scrollDown()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Embedded:focus()
|
||||
-- allow scrolling
|
||||
end
|
149
sys/apis/ui/components/Form.lua
Normal file
149
sys/apis/ui/components/Form.lua
Normal file
@ -0,0 +1,149 @@
|
||||
local class = require('class')
|
||||
local Sound = require('sound')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Form = class(UI.Window)
|
||||
UI.Form.defaults = {
|
||||
UIElement = 'Form',
|
||||
values = { },
|
||||
margin = 2,
|
||||
event = 'form_complete',
|
||||
cancelEvent = 'form_cancel',
|
||||
}
|
||||
function UI.Form:postInit()
|
||||
self:createForm()
|
||||
end
|
||||
|
||||
function UI.Form:reset()
|
||||
for _,child in pairs(self.children) do
|
||||
if child.reset then
|
||||
child:reset()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Form:setValues(values)
|
||||
self:reset()
|
||||
self.values = values
|
||||
for _,child in pairs(self.children) do
|
||||
if child.formKey then
|
||||
-- this should be child:setValue(self.values[child.formKey])
|
||||
-- so chooser can set default choice if null
|
||||
-- null should be valid as well
|
||||
child.value = self.values[child.formKey] or ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Form:createForm()
|
||||
self.children = self.children or { }
|
||||
|
||||
if not self.labelWidth then
|
||||
self.labelWidth = 1
|
||||
for _, child in pairs(self) do
|
||||
if type(child) == 'table' and child.UIElement then
|
||||
if child.formLabel then
|
||||
self.labelWidth = math.max(self.labelWidth, #child.formLabel + 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local y = self.margin
|
||||
for _, child in pairs(self) do
|
||||
if type(child) == 'table' and child.UIElement then
|
||||
if child.formKey then
|
||||
child.value = self.values[child.formKey] or ''
|
||||
end
|
||||
if child.formLabel then
|
||||
child.x = self.labelWidth + self.margin - 1
|
||||
child.y = y
|
||||
if not child.width and not child.ex then
|
||||
child.ex = -self.margin
|
||||
end
|
||||
|
||||
table.insert(self.children, UI.Text {
|
||||
x = self.margin,
|
||||
y = y,
|
||||
textColor = colors.black,
|
||||
width = #child.formLabel,
|
||||
value = child.formLabel,
|
||||
})
|
||||
end
|
||||
if child.formKey or child.formLabel then
|
||||
y = y + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not self.manualControls then
|
||||
table.insert(self.children, UI.Button {
|
||||
y = -self.margin, x = -12 - self.margin,
|
||||
text = 'Ok',
|
||||
event = 'form_ok',
|
||||
})
|
||||
table.insert(self.children, UI.Button {
|
||||
y = -self.margin, x = -7 - self.margin,
|
||||
text = 'Cancel',
|
||||
event = self.cancelEvent,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Form:validateField(field)
|
||||
if field.required then
|
||||
if not field.value or #tostring(field.value) == 0 then
|
||||
return false, 'Field is required'
|
||||
end
|
||||
end
|
||||
if field.validate == 'numeric' then
|
||||
if #tostring(field.value) > 0 then
|
||||
if not tonumber(field.value) then
|
||||
return false, 'Invalid number'
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function UI.Form:save()
|
||||
for _,child in pairs(self.children) do
|
||||
if child.formKey then
|
||||
local s, m = self:validateField(child)
|
||||
if not s then
|
||||
self:setFocus(child)
|
||||
Sound.play('entity.villager.no', .5)
|
||||
self:emit({ type = 'form_invalid', message = m, field = child })
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,child in pairs(self.children) do
|
||||
if child.formKey then
|
||||
if (child.pruneEmpty and type(child.value) == 'string' and #child.value == 0) or
|
||||
(child.pruneEmpty and type(child.value) == 'boolean' and not child.value) then
|
||||
self.values[child.formKey] = nil
|
||||
elseif child.validate == 'numeric' then
|
||||
self.values[child.formKey] = tonumber(child.value)
|
||||
else
|
||||
self.values[child.formKey] = child.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function UI.Form:eventHandler(event)
|
||||
if event.type == 'form_ok' then
|
||||
if not self:save() then
|
||||
return false
|
||||
end
|
||||
self:emit({ type = self.event, UIElement = self, values = self.values })
|
||||
else
|
||||
return UI.Window.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
492
sys/apis/ui/components/Grid.lua
Normal file
492
sys/apis/ui/components/Grid.lua
Normal file
@ -0,0 +1,492 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('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)
|
||||
if t == 'string' or t == 'number' then
|
||||
return v
|
||||
end
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
local Writer = class()
|
||||
function Writer:init(element, y)
|
||||
self.element = element
|
||||
self.y = y
|
||||
self.x = 1
|
||||
end
|
||||
|
||||
function Writer:write(s, width, justify, 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 justify == 'right' then
|
||||
s = padding .. s
|
||||
else
|
||||
s = s .. padding
|
||||
end
|
||||
end
|
||||
self.element:write(self.x, self.y, s, bg, fg)
|
||||
self.x = self.x + width
|
||||
end
|
||||
|
||||
function Writer:finish(bg)
|
||||
if self.x <= self.element.width then
|
||||
self.element:write(self.x, self.y, _rep(' ', self.element.width - self.x + 1), bg)
|
||||
end
|
||||
self.x = 1
|
||||
self.y = self.y + 1
|
||||
end
|
||||
|
||||
--[[-- Grid --]]--
|
||||
UI.Grid = class(UI.Window)
|
||||
UI.Grid.defaults = {
|
||||
UIElement = 'Grid',
|
||||
index = 1,
|
||||
inverseSort = false,
|
||||
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 = '>',
|
||||
sortIndicator = ' ',
|
||||
inverseSortIndicator = '^',
|
||||
values = { },
|
||||
columns = { },
|
||||
accelerators = {
|
||||
enter = 'key_enter',
|
||||
[ 'control-c' ] = 'copy',
|
||||
down = 'scroll_down',
|
||||
up = 'scroll_up',
|
||||
home = 'scroll_top',
|
||||
[ 'end' ] = 'scroll_bottom',
|
||||
pageUp = 'scroll_pageUp',
|
||||
[ 'control-b' ] = 'scroll_pageUp',
|
||||
pageDown = 'scroll_pageDown',
|
||||
[ 'control-f' ] = 'scroll_pageDown',
|
||||
},
|
||||
}
|
||||
function UI.Grid:setParent()
|
||||
UI.Window.setParent(self)
|
||||
|
||||
for _,c in pairs(self.columns) do
|
||||
c.cw = c.width
|
||||
if not c.heading then
|
||||
c.heading = ''
|
||||
end
|
||||
end
|
||||
|
||||
self:update()
|
||||
|
||||
if not self.pageSize then
|
||||
if self.disableHeader then
|
||||
self.pageSize = self.height
|
||||
else
|
||||
self.pageSize = self.height - self.headerHeight
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Grid:resize()
|
||||
UI.Window.resize(self)
|
||||
|
||||
if self.disableHeader then
|
||||
self.pageSize = self.height
|
||||
else
|
||||
self.pageSize = self.height - self.headerHeight
|
||||
end
|
||||
self:adjustWidth()
|
||||
end
|
||||
|
||||
function UI.Grid:adjustWidth()
|
||||
local t = { } -- cols without width
|
||||
local w = self.width - #self.columns - 1 - self.marginRight -- width remaining
|
||||
|
||||
for _,c in pairs(self.columns) do
|
||||
if c.width then
|
||||
c.cw = c.width
|
||||
w = w - c.cw
|
||||
else
|
||||
table.insert(t, c)
|
||||
end
|
||||
end
|
||||
|
||||
if #t == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if #t == 1 then
|
||||
t[1].cw = #(t[1].heading or '')
|
||||
t[1].cw = math.max(t[1].cw, w)
|
||||
return
|
||||
end
|
||||
|
||||
if not self.autospace then
|
||||
for k,c in ipairs(t) do
|
||||
c.cw = math.floor(w / (#t - k + 1))
|
||||
w = w - c.cw
|
||||
end
|
||||
|
||||
else
|
||||
for _,c in ipairs(t) do
|
||||
c.cw = #(c.heading or '')
|
||||
w = w - c.cw
|
||||
end
|
||||
-- adjust the size to the length of the value
|
||||
for key,row in pairs(self.values) do
|
||||
if w <= 0 then
|
||||
break
|
||||
end
|
||||
row = self:getDisplayValues(row, key)
|
||||
for _,col in pairs(t) do
|
||||
local value = row[col.key]
|
||||
if value then
|
||||
value = tostring(value)
|
||||
if #value > col.cw then
|
||||
w = w + col.cw
|
||||
col.cw = math.min(#value, w)
|
||||
w = w - col.cw
|
||||
if w <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- last column does not get padding (right alignment)
|
||||
if not self.columns[#self.columns].width then
|
||||
Util.removeByValue(t, self.columns[#self.columns])
|
||||
end
|
||||
|
||||
-- got some extra room - add some padding
|
||||
if w > 0 then
|
||||
for k,c in ipairs(t) do
|
||||
local padding = math.floor(w / (#t - k + 1))
|
||||
c.cw = c.cw + padding
|
||||
w = w - padding
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Grid:setPageSize(pageSize)
|
||||
self.pageSize = pageSize
|
||||
end
|
||||
|
||||
function UI.Grid:getValues()
|
||||
return self.values
|
||||
end
|
||||
|
||||
function UI.Grid:setValues(t)
|
||||
self.values = t
|
||||
self:update()
|
||||
end
|
||||
|
||||
function UI.Grid:setInverseSort(inverseSort)
|
||||
self.inverseSort = inverseSort
|
||||
self:update()
|
||||
self:setIndex(self.index)
|
||||
end
|
||||
|
||||
function UI.Grid:setSortColumn(column)
|
||||
self.sortColumn = column
|
||||
end
|
||||
|
||||
function UI.Grid:getDisplayValues(row, key)
|
||||
return row
|
||||
end
|
||||
|
||||
function UI.Grid:getSelected()
|
||||
if self.sorted then
|
||||
return self.values[self.sorted[self.index]], self.sorted[self.index]
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Grid:setSelected(name, value)
|
||||
if self.sorted then
|
||||
for k,v in pairs(self.sorted) do
|
||||
if self.values[v][name] == value then
|
||||
self:setIndex(k)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
self:setIndex(1)
|
||||
end
|
||||
|
||||
function UI.Grid:focus()
|
||||
self:drawRows()
|
||||
end
|
||||
|
||||
function UI.Grid:draw()
|
||||
if not self.disableHeader then
|
||||
self:drawHeadings()
|
||||
end
|
||||
|
||||
if self.index <= 0 then
|
||||
self:setIndex(1)
|
||||
elseif self.index > #self.sorted then
|
||||
self:setIndex(#self.sorted)
|
||||
end
|
||||
self:drawRows()
|
||||
end
|
||||
|
||||
-- Something about the displayed table has changed
|
||||
-- resort the table
|
||||
function UI.Grid:update()
|
||||
local function sort(a, b)
|
||||
if not a[self.sortColumn] then
|
||||
return false
|
||||
elseif not b[self.sortColumn] then
|
||||
return true
|
||||
end
|
||||
return self:sortCompare(a, b)
|
||||
end
|
||||
|
||||
local function inverseSort(a, b)
|
||||
return not sort(a, b)
|
||||
end
|
||||
|
||||
local order
|
||||
if self.sortColumn then
|
||||
order = sort
|
||||
if self.inverseSort then
|
||||
order = inverseSort
|
||||
end
|
||||
end
|
||||
|
||||
self.sorted = Util.keys(self.values)
|
||||
if order then
|
||||
table.sort(self.sorted, function(a,b)
|
||||
return order(self.values[a], self.values[b])
|
||||
end)
|
||||
end
|
||||
|
||||
self:adjustWidth()
|
||||
end
|
||||
|
||||
function UI.Grid:drawHeadings()
|
||||
if self.headerHeight > 1 then
|
||||
self:clear(self.headerBackgroundColor)
|
||||
end
|
||||
local sb = Writer(self, math.ceil(self.headerHeight / 2))
|
||||
for _,col in ipairs(self.columns) do
|
||||
local ind = ' '
|
||||
local color = self.headerTextColor
|
||||
if col.key == self.sortColumn then
|
||||
if self.inverseSort then
|
||||
ind = self.inverseSortIndicator
|
||||
else
|
||||
ind = self.sortIndicator
|
||||
end
|
||||
color = self.headerSortColor
|
||||
end
|
||||
sb:write(ind .. col.heading,
|
||||
col.cw + 1,
|
||||
col.justify,
|
||||
self.headerBackgroundColor,
|
||||
color)
|
||||
end
|
||||
sb:finish(self.headerBackgroundColor)
|
||||
end
|
||||
|
||||
function UI.Grid:sortCompare(a, b)
|
||||
a = safeValue(a[self.sortColumn])
|
||||
b = safeValue(b[self.sortColumn])
|
||||
if type(a) == type(b) then
|
||||
return a < b
|
||||
end
|
||||
return tostring(a) < tostring(b)
|
||||
end
|
||||
|
||||
function UI.Grid:drawRows()
|
||||
local startRow = math.max(1, self:getStartRow())
|
||||
|
||||
local sb = Writer(self, self.disableHeader and 1 or self.headerHeight + 1)
|
||||
|
||||
local lastRow = math.min(startRow + self.pageSize - 1, #self.sorted)
|
||||
for index = startRow, lastRow do
|
||||
|
||||
local key = self.sorted[index]
|
||||
local rawRow = self.values[key]
|
||||
local row = self:getDisplayValues(rawRow, key)
|
||||
|
||||
local ind = ' '
|
||||
if self.focused and index == self.index and not self.inactive then
|
||||
ind = self.focusIndicator
|
||||
end
|
||||
|
||||
local selected = index == self.index and not self.inactive
|
||||
local bg = self:getRowBackgroundColor(rawRow, selected)
|
||||
local fg = self:getRowTextColor(rawRow, selected)
|
||||
|
||||
for _,col in pairs(self.columns) do
|
||||
sb:write(ind .. safeValue(row[col.key] or ''),
|
||||
col.cw + 1,
|
||||
col.justify,
|
||||
bg,
|
||||
fg)
|
||||
ind = ' '
|
||||
end
|
||||
sb:finish(bg)
|
||||
end
|
||||
|
||||
if sb.y <= self.height then
|
||||
self:clearArea(1, sb.y, self.width, self.height - sb.y + 1)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Grid:getRowTextColor(row, selected)
|
||||
if selected then
|
||||
if self.focused then
|
||||
return self.textSelectedColor
|
||||
end
|
||||
return self.unfocusedTextSelectedColor
|
||||
end
|
||||
return self.textColor
|
||||
end
|
||||
|
||||
function UI.Grid:getRowBackgroundColor(row, selected)
|
||||
if selected then
|
||||
if self.focused then
|
||||
return self.backgroundSelectedColor
|
||||
end
|
||||
return self.unfocusedBackgroundSelectedColor
|
||||
end
|
||||
return self.backgroundColor
|
||||
end
|
||||
|
||||
function UI.Grid:getIndex()
|
||||
return self.index
|
||||
end
|
||||
|
||||
function UI.Grid:setIndex(index)
|
||||
index = math.max(1, index)
|
||||
self.index = math.min(index, #self.sorted)
|
||||
|
||||
local selected = self:getSelected()
|
||||
if selected ~= self.selected then
|
||||
self:drawRows()
|
||||
self.selected = selected
|
||||
if selected then
|
||||
self:emit({ type = 'grid_focus_row', selected = selected, element = self })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Grid:getStartRow()
|
||||
return math.floor((self.index - 1) / self.pageSize) * self.pageSize + 1
|
||||
end
|
||||
|
||||
function UI.Grid:getPage()
|
||||
return math.floor(self.index / self.pageSize) + 1
|
||||
end
|
||||
|
||||
function UI.Grid:getPageCount()
|
||||
local tableSize = Util.size(self.values)
|
||||
local pc = math.floor(tableSize / self.pageSize)
|
||||
if tableSize % self.pageSize > 0 then
|
||||
pc = pc + 1
|
||||
end
|
||||
return pc
|
||||
end
|
||||
|
||||
function UI.Grid:nextPage()
|
||||
self:setPage(self:getPage() + 1)
|
||||
end
|
||||
|
||||
function UI.Grid:previousPage()
|
||||
self:setPage(self:getPage() - 1)
|
||||
end
|
||||
|
||||
function UI.Grid:setPage(pageNo)
|
||||
-- 1 based paging
|
||||
self:setIndex((pageNo-1) * self.pageSize + 1)
|
||||
end
|
||||
|
||||
function UI.Grid:eventHandler(event)
|
||||
if event.type == 'mouse_click' or
|
||||
event.type == 'mouse_rightclick' or
|
||||
event.type == 'mouse_doubleclick' then
|
||||
if not self.disableHeader then
|
||||
if event.y <= self.headerHeight then
|
||||
local col = 2
|
||||
for _,c in ipairs(self.columns) do
|
||||
if event.x < col + c.cw then
|
||||
self:emit({
|
||||
type = 'grid_sort',
|
||||
sortColumn = c.key,
|
||||
inverseSort = self.sortColumn == c.key and not self.inverseSort,
|
||||
element = self,
|
||||
})
|
||||
break
|
||||
end
|
||||
col = col + c.cw + 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
local row = self:getStartRow() + event.y - 1
|
||||
if not self.disableHeader then
|
||||
row = row - self.headerHeight
|
||||
end
|
||||
if row > 0 and row <= Util.size(self.values) then
|
||||
self:setIndex(row)
|
||||
if event.type == 'mouse_doubleclick' then
|
||||
self:emit({ type = 'key_enter' })
|
||||
elseif event.type == 'mouse_rightclick' then
|
||||
self:emit({ type = 'grid_select_right', selected = self.selected, element = self })
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
||||
elseif event.type == 'grid_sort' then
|
||||
self.sortColumn = event.sortColumn
|
||||
self:setInverseSort(event.inverseSort)
|
||||
self:draw()
|
||||
elseif event.type == 'scroll_down' then
|
||||
self:setIndex(self.index + 1)
|
||||
elseif event.type == 'scroll_up' then
|
||||
self:setIndex(self.index - 1)
|
||||
elseif event.type == 'scroll_top' then
|
||||
self:setIndex(1)
|
||||
elseif event.type == 'scroll_bottom' then
|
||||
self:setIndex(Util.size(self.values))
|
||||
elseif event.type == 'scroll_pageUp' then
|
||||
self:setIndex(self.index - self.pageSize)
|
||||
elseif event.type == 'scroll_pageDown' then
|
||||
self:setIndex(self.index + self.pageSize)
|
||||
elseif event.type == 'scroll_to' then
|
||||
self:setIndex(event.offset)
|
||||
elseif event.type == 'key_enter' then
|
||||
if self.selected then
|
||||
self:emit({ type = 'grid_select', selected = self.selected, element = self })
|
||||
end
|
||||
elseif event.type == 'copy' then
|
||||
if self.selected then
|
||||
os.queueEvent('clipboard_copy', self.selected)
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
40
sys/apis/ui/components/Image.lua
Normal file
40
sys/apis/ui/components/Image.lua
Normal file
@ -0,0 +1,40 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
UI.Image = class(UI.Window)
|
||||
UI.Image.defaults = {
|
||||
UIElement = 'Image',
|
||||
event = 'button_press',
|
||||
}
|
||||
function UI.Image:setParent()
|
||||
if self.image then
|
||||
self.height = #self.image
|
||||
end
|
||||
if self.image and not self.width then
|
||||
self.width = #self.image[1]
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Image:draw()
|
||||
self:clear()
|
||||
if self.image then
|
||||
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)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Image:setImage(image)
|
||||
self.image = image
|
||||
end
|
60
sys/apis/ui/components/Menu.lua
Normal file
60
sys/apis/ui/components/Menu.lua
Normal file
@ -0,0 +1,60 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
--[[-- Menu --]]--
|
||||
UI.Menu = class(UI.Grid)
|
||||
UI.Menu.defaults = {
|
||||
UIElement = 'Menu',
|
||||
disableHeader = true,
|
||||
columns = { { heading = 'Prompt', key = 'prompt', width = 20 } },
|
||||
}
|
||||
function UI.Menu:postInit()
|
||||
self.values = self.menuItems
|
||||
self.pageSize = #self.menuItems
|
||||
end
|
||||
|
||||
function UI.Menu:setParent()
|
||||
UI.Grid.setParent(self)
|
||||
self.itemWidth = 1
|
||||
for _,v in pairs(self.values) do
|
||||
if #v.prompt > self.itemWidth then
|
||||
self.itemWidth = #v.prompt
|
||||
end
|
||||
end
|
||||
self.columns[1].width = self.itemWidth
|
||||
|
||||
if self.centered then
|
||||
self:center()
|
||||
else
|
||||
self.width = self.itemWidth + 2
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Menu:center()
|
||||
self.x = (self.width - self.itemWidth + 2) / 2
|
||||
self.width = self.itemWidth + 2
|
||||
end
|
||||
|
||||
function UI.Menu:eventHandler(event)
|
||||
if event.type == 'key' then
|
||||
if event.key == 'enter' then
|
||||
local selected = self.menuItems[self.index]
|
||||
self:emit({
|
||||
type = selected.event or 'menu_select',
|
||||
selected = selected
|
||||
})
|
||||
return true
|
||||
end
|
||||
elseif event.type == 'mouse_click' then
|
||||
if event.y <= #self.menuItems then
|
||||
UI.Grid.setIndex(self, event.y)
|
||||
local selected = self.menuItems[self.index]
|
||||
self:emit({
|
||||
type = selected.event or 'menu_select',
|
||||
selected = selected
|
||||
})
|
||||
return true
|
||||
end
|
||||
end
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
end
|
92
sys/apis/ui/components/MenuBar.lua
Normal file
92
sys/apis/ui/components/MenuBar.lua
Normal file
@ -0,0 +1,92 @@
|
||||
local class = require('class')
|
||||
local UI = require('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,
|
||||
spacing = 2,
|
||||
lastx = 1,
|
||||
showBackButton = false,
|
||||
buttonClass = 'MenuItem',
|
||||
}
|
||||
UI.MenuBar.spacer = { spacer = true, text = 'spacer', inactive = true }
|
||||
|
||||
function UI.MenuBar:postInit()
|
||||
self:addButtons(self.buttons)
|
||||
end
|
||||
|
||||
function UI.MenuBar:addButtons(buttons)
|
||||
if not self.children then
|
||||
self.children = { }
|
||||
end
|
||||
|
||||
for _,button in pairs(buttons) do
|
||||
if button.UIElement then
|
||||
table.insert(self.children, button)
|
||||
else
|
||||
local buttonProperties = {
|
||||
x = self.lastx,
|
||||
width = #button.text + self.spacing,
|
||||
centered = false,
|
||||
}
|
||||
self.lastx = self.lastx + buttonProperties.width
|
||||
UI:mergeProperties(buttonProperties, button)
|
||||
|
||||
button = UI[self.buttonClass](buttonProperties)
|
||||
if button.name then
|
||||
self[button.name] = button
|
||||
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
|
||||
self:initChildren()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.MenuBar:getActive(menuItem)
|
||||
return not menuItem.inactive
|
||||
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)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
14
sys/apis/ui/components/MenuItem.lua
Normal file
14
sys/apis/ui/components/MenuItem.lua
Normal file
@ -0,0 +1,14 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
--[[-- MenuItem --]]--
|
||||
UI.MenuItem = class(UI.Button)
|
||||
UI.MenuItem.defaults = {
|
||||
UIElement = 'MenuItem',
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.lightGray,
|
||||
textFocusColor = colors.white,
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
}
|
33
sys/apis/ui/components/NftImage.lua
Normal file
33
sys/apis/ui/components/NftImage.lua
Normal file
@ -0,0 +1,33 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
UI.NftImage = class(UI.Window)
|
||||
UI.NftImage.defaults = {
|
||||
UIElement = 'NftImage',
|
||||
event = 'button_press',
|
||||
}
|
||||
function UI.NftImage:setParent()
|
||||
if self.image then
|
||||
self.height = self.image.height
|
||||
end
|
||||
if self.image and not self.width then
|
||||
self.width = self.image.width
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.NftImage:draw()
|
||||
if self.image then
|
||||
for y = 1, self.image.height do
|
||||
for x = 1, #self.image.text[y] do
|
||||
self:write(x, y, self.image.text[y][x], self.image.bg[y][x], self.image.fg[y][x])
|
||||
end
|
||||
end
|
||||
else
|
||||
self:clear()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.NftImage:setImage(image)
|
||||
self.image = image
|
||||
end
|
67
sys/apis/ui/components/Notification.lua
Normal file
67
sys/apis/ui/components/Notification.lua
Normal file
@ -0,0 +1,67 @@
|
||||
local class = require('class')
|
||||
local Event = require('event')
|
||||
local Sound = require('sound')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Notification = class(UI.Window)
|
||||
UI.Notification.defaults = {
|
||||
UIElement = 'Notification',
|
||||
backgroundColor = colors.gray,
|
||||
height = 3,
|
||||
}
|
||||
function UI.Notification:draw()
|
||||
end
|
||||
|
||||
function UI.Notification:enable()
|
||||
end
|
||||
|
||||
function UI.Notification:error(value, timeout)
|
||||
self.backgroundColor = colors.red
|
||||
Sound.play('entity.villager.no', .5)
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
function UI.Notification:info(value, timeout)
|
||||
self.backgroundColor = colors.gray
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
function UI.Notification:success(value, timeout)
|
||||
self.backgroundColor = colors.green
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
function UI.Notification:cancel()
|
||||
if self.canvas then
|
||||
Event.cancelNamedTimer('notificationTimer')
|
||||
self.enabled = false
|
||||
self.canvas:removeLayer()
|
||||
self.canvas = nil
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Notification:display(value, timeout)
|
||||
self.enabled = true
|
||||
local lines = Util.wordWrap(value, self.width - 2)
|
||||
self.height = #lines + 1
|
||||
self.y = self.parent.height - self.height + 1
|
||||
if self.canvas then
|
||||
self.canvas:removeLayer()
|
||||
end
|
||||
|
||||
self.canvas = self:addLayer(self.backgroundColor, self.textColor)
|
||||
self:addTransition('expandUp', { ticks = self.height })
|
||||
self.canvas:setVisible(true)
|
||||
self:clear()
|
||||
for k,v in pairs(lines) do
|
||||
self:write(2, k, v)
|
||||
end
|
||||
|
||||
Event.addNamedTimer('notificationTimer', timeout or 3, false, function()
|
||||
self:cancel()
|
||||
self:sync()
|
||||
end)
|
||||
end
|
18
sys/apis/ui/components/ProgressBar.lua
Normal file
18
sys/apis/ui/components/ProgressBar.lua
Normal file
@ -0,0 +1,18 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.ProgressBar = class(UI.Window)
|
||||
UI.ProgressBar.defaults = {
|
||||
UIElement = 'ProgressBar',
|
||||
progressColor = colors.lime,
|
||||
backgroundColor = colors.gray,
|
||||
height = 1,
|
||||
value = 0,
|
||||
}
|
||||
function UI.ProgressBar:draw()
|
||||
self:clear()
|
||||
local width = math.ceil(self.value / 100 * self.width)
|
||||
self:clearArea(1, 1, width, self.height, self.progressColor)
|
||||
end
|
74
sys/apis/ui/components/ScrollBar.lua
Normal file
74
sys/apis/ui/components/ScrollBar.lua
Normal file
@ -0,0 +1,74 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.ScrollBar = class(UI.Window)
|
||||
UI.ScrollBar.defaults = {
|
||||
UIElement = 'ScrollBar',
|
||||
lineChar = '|',
|
||||
sliderChar = '#',
|
||||
upArrowChar = '^',
|
||||
downArrowChar = 'v',
|
||||
scrollbarColor = colors.lightGray,
|
||||
width = 1,
|
||||
x = -1,
|
||||
ey = -1,
|
||||
}
|
||||
function UI.ScrollBar:draw()
|
||||
local view = self.parent:getViewArea()
|
||||
|
||||
if view.totalHeight > view.height then
|
||||
local maxScroll = view.totalHeight - view.height
|
||||
local percent = view.offsetY / maxScroll
|
||||
local sliderSize = math.max(1, Util.round(view.height / view.totalHeight * (view.height - 2)))
|
||||
local x = self.width
|
||||
|
||||
local row = view.y
|
||||
if not view.static then -- does the container scroll ?
|
||||
self.height = view.totalHeight
|
||||
end
|
||||
|
||||
for i = 1, view.height - 2 do
|
||||
self:write(x, row + i, self.lineChar, nil, self.scrollbarColor)
|
||||
end
|
||||
|
||||
local y = Util.round((view.height - 2 - sliderSize) * percent)
|
||||
for i = 1, sliderSize do
|
||||
self:write(x, row + y + i, self.sliderChar, nil, self.scrollbarColor)
|
||||
end
|
||||
|
||||
local color = self.scrollbarColor
|
||||
if view.offsetY > 0 then
|
||||
color = colors.white
|
||||
end
|
||||
self:write(x, row, self.upArrowChar, nil, color)
|
||||
|
||||
color = self.scrollbarColor
|
||||
if view.offsetY + view.height < view.totalHeight then
|
||||
color = colors.white
|
||||
end
|
||||
self:write(x, row + view.height - 1, self.downArrowChar, nil, color)
|
||||
end
|
||||
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()
|
||||
if view.totalHeight > view.height then
|
||||
if event.y == view.y then
|
||||
self:emit({ type = 'scroll_up'})
|
||||
elseif event.y == view.y + view.height - 1 then
|
||||
self: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 })
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
60
sys/apis/ui/components/ScrollingGrid.lua
Normal file
60
sys/apis/ui/components/ScrollingGrid.lua
Normal file
@ -0,0 +1,60 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
--[[-- ScrollingGrid --]]--
|
||||
UI.ScrollingGrid = class(UI.Grid)
|
||||
UI.ScrollingGrid.defaults = {
|
||||
UIElement = 'ScrollingGrid',
|
||||
scrollOffset = 0,
|
||||
marginRight = 1,
|
||||
}
|
||||
function UI.ScrollingGrid:postInit()
|
||||
self.scrollBar = UI.ScrollBar()
|
||||
end
|
||||
|
||||
function UI.ScrollingGrid:drawRows()
|
||||
UI.Grid.drawRows(self)
|
||||
self.scrollBar:draw()
|
||||
end
|
||||
|
||||
function UI.ScrollingGrid:getViewArea()
|
||||
local y = 1
|
||||
if not self.disableHeader then
|
||||
y = y + self.headerHeight
|
||||
end
|
||||
|
||||
return {
|
||||
static = true, -- the container doesn't scroll
|
||||
y = y, -- scrollbar Y
|
||||
height = self.pageSize, -- viewable height
|
||||
totalHeight = Util.size(self.values), -- total height
|
||||
offsetY = self.scrollOffset, -- scroll offset
|
||||
}
|
||||
end
|
||||
|
||||
function UI.ScrollingGrid:getStartRow()
|
||||
local ts = Util.size(self.values)
|
||||
if ts < self.pageSize then
|
||||
self.scrollOffset = 0
|
||||
end
|
||||
return self.scrollOffset + 1
|
||||
end
|
||||
|
||||
function UI.ScrollingGrid:setIndex(index)
|
||||
if index < self.scrollOffset + 1 then
|
||||
self.scrollOffset = index - 1
|
||||
elseif index - self.scrollOffset > self.pageSize then
|
||||
self.scrollOffset = index - self.pageSize
|
||||
end
|
||||
|
||||
if self.scrollOffset < 0 then
|
||||
self.scrollOffset = 0
|
||||
else
|
||||
local ts = Util.size(self.values)
|
||||
if self.pageSize + self.scrollOffset + 1 > ts then
|
||||
self.scrollOffset = math.max(0, ts - self.pageSize)
|
||||
end
|
||||
end
|
||||
UI.Grid.setIndex(self, index)
|
||||
end
|
51
sys/apis/ui/components/SlideOut.lua
Normal file
51
sys/apis/ui/components/SlideOut.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
--[[-- SlideOut --]]--
|
||||
UI.SlideOut = class(UI.Window)
|
||||
UI.SlideOut.defaults = {
|
||||
UIElement = 'SlideOut',
|
||||
pageType = 'modal',
|
||||
}
|
||||
function UI.SlideOut:setParent()
|
||||
-- TODO: size should be set at this point
|
||||
self:layout()
|
||||
self.canvas = self:addLayer()
|
||||
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.SlideOut:enable()
|
||||
end
|
||||
|
||||
function UI.SlideOut:show(...)
|
||||
self:addTransition('expandUp')
|
||||
self.canvas:raise()
|
||||
self.canvas:setVisible(true)
|
||||
UI.Window.enable(self, ...)
|
||||
self:draw()
|
||||
self:capture(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function UI.SlideOut:disable()
|
||||
self.canvas:setVisible(false)
|
||||
UI.Window.disable(self)
|
||||
end
|
||||
|
||||
function UI.SlideOut:hide()
|
||||
self:disable()
|
||||
self:release(self)
|
||||
self:refocus()
|
||||
end
|
||||
|
||||
function UI.SlideOut:eventHandler(event)
|
||||
if event.type == 'slide_show' then
|
||||
self:show()
|
||||
return true
|
||||
|
||||
elseif event.type == 'slide_hide' then
|
||||
self:hide()
|
||||
return true
|
||||
end
|
||||
end
|
99
sys/apis/ui/components/StatusBar.lua
Normal file
99
sys/apis/ui/components/StatusBar.lua
Normal file
@ -0,0 +1,99 @@
|
||||
local class = require('class')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.StatusBar = class(UI.Window)
|
||||
UI.StatusBar.defaults = {
|
||||
UIElement = 'StatusBar',
|
||||
backgroundColor = colors.lightGray,
|
||||
textColor = colors.gray,
|
||||
height = 1,
|
||||
ey = -1,
|
||||
}
|
||||
function UI.StatusBar:adjustWidth()
|
||||
-- Can only have 1 adjustable width
|
||||
if self.columns then
|
||||
local w = self.width - #self.columns - 1
|
||||
for _,c in pairs(self.columns) do
|
||||
if c.width then
|
||||
c.cw = c.width -- computed width
|
||||
w = w - c.width
|
||||
end
|
||||
end
|
||||
for _,c in pairs(self.columns) do
|
||||
if not c.width then
|
||||
c.cw = w
|
||||
end
|
||||
end
|
||||
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
|
||||
self:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.StatusBar:setValue(name, value)
|
||||
if not self.values then
|
||||
self.values = { }
|
||||
end
|
||||
self.values[name] = value
|
||||
end
|
||||
|
||||
function UI.StatusBar:getValue(name)
|
||||
if self.values then
|
||||
return self.values[name]
|
||||
end
|
||||
end
|
||||
|
||||
function UI.StatusBar:timedStatus(status, timeout)
|
||||
timeout = timeout or 3
|
||||
self:write(2, 1, Util.widthify(status, self.width-2), self.backgroundColor)
|
||||
Event.addNamedTimer('statusTimer', timeout, false, function()
|
||||
if self.parent.enabled then
|
||||
self:draw()
|
||||
self:sync()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function UI.StatusBar:getColumnWidth(name)
|
||||
local c = Util.find(self.columns, 'key', name)
|
||||
return c and c.cw
|
||||
end
|
||||
|
||||
function UI.StatusBar:setColumnWidth(name, width)
|
||||
local c = Util.find(self.columns, 'key', name)
|
||||
if c then
|
||||
c.cw = width
|
||||
end
|
||||
end
|
||||
|
||||
function UI.StatusBar:draw()
|
||||
if not self.values then
|
||||
self:clear()
|
||||
elseif type(self.values) == 'string' then
|
||||
self:write(1, 1, Util.widthify(' ' .. self.values, self.width))
|
||||
else
|
||||
local s = ''
|
||||
for _,c in ipairs(self.columns) do
|
||||
s = s .. ' ' .. Util.widthify(tostring(self.values[c.key] or ''), c.cw)
|
||||
end
|
||||
self:write(1, 1, Util.widthify(s, self.width))
|
||||
end
|
||||
end
|
12
sys/apis/ui/components/Tab.lua
Normal file
12
sys/apis/ui/components/Tab.lua
Normal file
@ -0,0 +1,12 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.Tab = class(UI.ActiveLayer)
|
||||
UI.Tab.defaults = {
|
||||
UIElement = 'Tab',
|
||||
tabTitle = 'tab',
|
||||
backgroundColor = colors.cyan,
|
||||
y = 2,
|
||||
}
|
45
sys/apis/ui/components/TabBar.lua
Normal file
45
sys/apis/ui/components/TabBar.lua
Normal file
@ -0,0 +1,45 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.TabBar = class(UI.MenuBar)
|
||||
UI.TabBar.defaults = {
|
||||
UIElement = 'TabBar',
|
||||
buttonClass = 'TabBarMenuItem',
|
||||
selectedBackgroundColor = colors.cyan,
|
||||
}
|
||||
function UI.TabBar:enable()
|
||||
UI.MenuBar.enable(self)
|
||||
if not Util.find(self.children, 'selected', true) then
|
||||
local menuItem = self:getFocusables()[1]
|
||||
if menuItem then
|
||||
menuItem.selected = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UI.TabBar:eventHandler(event)
|
||||
if event.type == 'tab_select' then
|
||||
local selected, si = Util.find(self.children, 'uid', event.button.uid)
|
||||
local previous, pi = Util.find(self.children, 'selected', true)
|
||||
|
||||
if si ~= pi then
|
||||
selected.selected = true
|
||||
if previous then
|
||||
previous.selected = false
|
||||
self:emit({ type = 'tab_change', current = si, last = pi, tab = selected })
|
||||
end
|
||||
end
|
||||
UI.MenuBar.draw(self)
|
||||
end
|
||||
return UI.MenuBar.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function UI.TabBar:selectTab(text)
|
||||
local menuItem = Util.find(self.children, 'text', text)
|
||||
if menuItem then
|
||||
menuItem.selected = true
|
||||
end
|
||||
end
|
25
sys/apis/ui/components/TabBarMenuItem.lua
Normal file
25
sys/apis/ui/components/TabBarMenuItem.lua
Normal file
@ -0,0 +1,25 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
--[[-- TabBarMenuItem --]]--
|
||||
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,
|
||||
}
|
||||
function UI.TabBarMenuItem:draw()
|
||||
if self.selected then
|
||||
self.backgroundColor = self.selectedBackgroundColor
|
||||
self.backgroundFocusColor = self.selectedBackgroundColor
|
||||
else
|
||||
self.backgroundColor = self.unselectedBackgroundColor
|
||||
self.backgroundFocusColor = self.unselectedBackgroundColor
|
||||
end
|
||||
UI.Button.draw(self)
|
||||
end
|
89
sys/apis/ui/components/Tabs.lua
Normal file
89
sys/apis/ui/components/Tabs.lua
Normal file
@ -0,0 +1,89 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
UI.Tabs = class(UI.Window)
|
||||
UI.Tabs.defaults = {
|
||||
UIElement = 'Tabs',
|
||||
}
|
||||
function UI.Tabs:postInit()
|
||||
self:add(self)
|
||||
end
|
||||
|
||||
function UI.Tabs:add(children)
|
||||
local buttons = { }
|
||||
for _,child in pairs(children) do
|
||||
if type(child) == 'table' and child.UIElement and child.tabTitle then
|
||||
child.y = 2
|
||||
table.insert(buttons, {
|
||||
text = child.tabTitle,
|
||||
event = 'tab_select',
|
||||
tabUid = child.uid,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if not self.tabBar then
|
||||
self.tabBar = UI.TabBar({
|
||||
buttons = buttons,
|
||||
})
|
||||
else
|
||||
self.tabBar:addButtons(buttons)
|
||||
end
|
||||
|
||||
if self.parent then
|
||||
return UI.Window.add(self, children)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Tabs:selectTab(tab)
|
||||
local menuItem = Util.find(self.tabBar.children, 'tabUid', tab.uid)
|
||||
if menuItem then
|
||||
self.tabBar:emit({ type = 'tab_select', button = { uid = menuItem.uid } })
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Tabs:setActive(tab, active)
|
||||
local menuItem = Util.find(self.tabBar.children, 'tabUid', tab.uid)
|
||||
if menuItem then
|
||||
menuItem.inactive = not active
|
||||
end
|
||||
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) do
|
||||
if child.uid == menuItem.tabUid then
|
||||
child:enable()
|
||||
self:emit({ type = 'tab_activate', activated = child })
|
||||
elseif child.tabTitle then
|
||||
child:disable()
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
for _,child in pairs(self.children) do
|
||||
if child.uid == event.tab.tabUid then
|
||||
child:enable()
|
||||
elseif child.tabTitle then
|
||||
child:disable()
|
||||
end
|
||||
end
|
||||
self:emit({ type = 'tab_activate', activated = tab })
|
||||
tab:draw()
|
||||
end
|
||||
end
|
20
sys/apis/ui/components/Text.lua
Normal file
20
sys/apis/ui/components/Text.lua
Normal file
@ -0,0 +1,20 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
UI.Text = class(UI.Window)
|
||||
UI.Text.defaults = {
|
||||
UIElement = 'Text',
|
||||
value = '',
|
||||
height = 1,
|
||||
}
|
||||
function UI.Text:setParent()
|
||||
if not self.width and not self.ex then
|
||||
self.width = #tostring(self.value)
|
||||
end
|
||||
UI.Window.setParent(self)
|
||||
end
|
||||
|
||||
function UI.Text:draw()
|
||||
self:write(1, 1, Util.widthify(self.value or '', self.width), self.backgroundColor)
|
||||
end
|
36
sys/apis/ui/components/TextArea.lua
Normal file
36
sys/apis/ui/components/TextArea.lua
Normal file
@ -0,0 +1,36 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
--[[-- TextArea --]]--
|
||||
UI.TextArea = class(UI.Viewport)
|
||||
UI.TextArea.defaults = {
|
||||
UIElement = 'TextArea',
|
||||
marginRight = 2,
|
||||
value = '',
|
||||
}
|
||||
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()
|
||||
-- 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
|
||||
end
|
189
sys/apis/ui/components/TextEntry.lua
Normal file
189
sys/apis/ui/components/TextEntry.lua
Normal file
@ -0,0 +1,189 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
local _rep = string.rep
|
||||
|
||||
UI.TextEntry = class(UI.Window)
|
||||
UI.TextEntry.defaults = {
|
||||
UIElement = 'TextEntry',
|
||||
value = '',
|
||||
shadowText = '',
|
||||
focused = false,
|
||||
textColor = colors.white,
|
||||
shadowTextColor = colors.gray,
|
||||
backgroundColor = colors.black, -- colors.lightGray,
|
||||
backgroundFocusColor = colors.black, --lightGray,
|
||||
height = 1,
|
||||
limit = 6,
|
||||
pos = 0,
|
||||
accelerators = {
|
||||
[ 'control-c' ] = 'copy',
|
||||
}
|
||||
}
|
||||
function UI.TextEntry:postInit()
|
||||
self.value = tostring(self.value)
|
||||
end
|
||||
|
||||
function UI.TextEntry:setValue(value)
|
||||
self.value = value
|
||||
end
|
||||
|
||||
function UI.TextEntry:setPosition(pos)
|
||||
self.pos = pos
|
||||
end
|
||||
|
||||
function UI.TextEntry:updateScroll()
|
||||
if not self.scroll then
|
||||
self.scroll = 0
|
||||
end
|
||||
|
||||
if not self.pos then
|
||||
self.pos = #tostring(self.value)
|
||||
self.scroll = 0
|
||||
elseif self.pos > #tostring(self.value) then
|
||||
self.pos = #tostring(self.value)
|
||||
self.scroll = 0
|
||||
end
|
||||
|
||||
if self.pos - self.scroll > self.width - 2 then
|
||||
self.scroll = self.pos - (self.width - 2)
|
||||
elseif self.pos < self.scroll then
|
||||
self.scroll = self.pos
|
||||
end
|
||||
end
|
||||
|
||||
function UI.TextEntry:draw()
|
||||
local bg = self.backgroundColor
|
||||
local tc = self.textColor
|
||||
if self.focused then
|
||||
bg = self.backgroundFocusColor
|
||||
end
|
||||
|
||||
self:updateScroll()
|
||||
local text = tostring(self.value)
|
||||
if #text > 0 then
|
||||
if self.scroll and self.scroll > 0 then
|
||||
text = text:sub(1 + self.scroll)
|
||||
end
|
||||
if self.mask then
|
||||
text = _rep('*', #text)
|
||||
end
|
||||
else
|
||||
tc = self.shadowTextColor
|
||||
text = self.shadowText
|
||||
end
|
||||
|
||||
self:write(1, 1, ' ' .. Util.widthify(text, self.width - 2) .. ' ', bg, tc)
|
||||
if self.focused then
|
||||
self:setCursorPos(self.pos-self.scroll+2, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function UI.TextEntry:reset()
|
||||
self.pos = 0
|
||||
self.value = ''
|
||||
self:draw()
|
||||
self:updateCursor()
|
||||
end
|
||||
|
||||
function UI.TextEntry:updateCursor()
|
||||
self:updateScroll()
|
||||
self:setCursorPos(self.pos-self.scroll+2, 1)
|
||||
end
|
||||
|
||||
function UI.TextEntry:focus()
|
||||
self:draw()
|
||||
if self.focused then
|
||||
self:setCursorBlink(true)
|
||||
else
|
||||
self:setCursorBlink(false)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
A few lines below from theoriginalbit
|
||||
http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/
|
||||
--]]
|
||||
function UI.TextEntry:eventHandler(event)
|
||||
if event.type == 'key' then
|
||||
local ch = event.key
|
||||
if ch == 'left' then
|
||||
if self.pos > 0 then
|
||||
self.pos = math.max(self.pos-1, 0)
|
||||
self:draw()
|
||||
end
|
||||
elseif ch == 'right' then
|
||||
local input = tostring(self.value)
|
||||
if self.pos < #input then
|
||||
self.pos = math.min(self.pos+1, #input)
|
||||
self:draw()
|
||||
end
|
||||
elseif ch == 'home' then
|
||||
self.pos = 0
|
||||
self:draw()
|
||||
elseif ch == 'end' then
|
||||
self.pos = #tostring(self.value)
|
||||
self:draw()
|
||||
elseif ch == 'backspace' then
|
||||
if self.pos > 0 then
|
||||
local input = tostring(self.value)
|
||||
self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1)
|
||||
self.pos = self.pos - 1
|
||||
self:draw()
|
||||
self:emit({ type = 'text_change', text = self.value, element = self })
|
||||
end
|
||||
elseif ch == 'delete' then
|
||||
local input = tostring(self.value)
|
||||
if self.pos < #input then
|
||||
self.value = input:sub(1, self.pos) .. input:sub(self.pos+2)
|
||||
self:draw()
|
||||
self:emit({ type = 'text_change', text = self.value, element = self })
|
||||
end
|
||||
elseif #ch == 1 then
|
||||
local input = tostring(self.value)
|
||||
if #input < self.limit then
|
||||
self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1)
|
||||
self.pos = self.pos + 1
|
||||
self:draw()
|
||||
self:emit({ type = 'text_change', text = self.value, element = self })
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
return true
|
||||
|
||||
elseif event.type == 'copy' then
|
||||
os.queueEvent('clipboard_copy', self.value)
|
||||
|
||||
elseif event.type == 'paste' then
|
||||
local input = tostring(self.value)
|
||||
local text = event.text
|
||||
if #input + #text > self.limit then
|
||||
text = text:sub(1, self.limit-#input)
|
||||
end
|
||||
self.value = input:sub(1, self.pos) .. text .. input:sub(self.pos+1)
|
||||
self.pos = self.pos + #text
|
||||
self:draw()
|
||||
self:updateCursor()
|
||||
self:emit({ type = 'text_change', text = self.value, element = self })
|
||||
return true
|
||||
|
||||
elseif event.type == 'mouse_click' then
|
||||
if self.focused and event.x > 1 then
|
||||
self.pos = event.x + self.scroll - 2
|
||||
self:updateCursor()
|
||||
return true
|
||||
end
|
||||
elseif event.type == 'mouse_rightclick' then
|
||||
local input = tostring(self.value)
|
||||
if #input > 0 then
|
||||
self:reset()
|
||||
self:emit({ type = 'text_change', text = self.value, element = self })
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
65
sys/apis/ui/components/Throttle.lua
Normal file
65
sys/apis/ui/components/Throttle.lua
Normal file
@ -0,0 +1,65 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
|
||||
UI.Throttle = class(UI.Window)
|
||||
UI.Throttle.defaults = {
|
||||
UIElement = 'Throttle',
|
||||
backgroundColor = colors.gray,
|
||||
bordercolor = colors.cyan,
|
||||
height = 4,
|
||||
width = 10,
|
||||
timeout = .075,
|
||||
ctr = 0,
|
||||
image = {
|
||||
' //) (O )~@ &~&-( ?Q ',
|
||||
' //) (O )- @ \\-( ?) && ',
|
||||
' //) (O ), @ \\-(?) && ',
|
||||
' //) (O ). @ \\-d ) (@ '
|
||||
}
|
||||
}
|
||||
function UI.Throttle:setParent()
|
||||
self.x = math.ceil((self.parent.width - self.width) / 2)
|
||||
self.y = math.ceil((self.parent.height - self.height) / 2)
|
||||
UI.Window.setParent(self)
|
||||
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
|
||||
end
|
||||
|
||||
function UI.Throttle:update()
|
||||
local cc = os.clock()
|
||||
if cc > self.c + self.timeout then
|
||||
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
|
||||
local image = self.image[self.ctr + 1]
|
||||
local width = self.width - 2
|
||||
for i = 0, #self.image do
|
||||
self:write(2, i + 1, image:sub(width * i + 1, width * i + width),
|
||||
self.backgroundColor, self.textColor)
|
||||
end
|
||||
|
||||
self.ctr = (self.ctr + 1) % #self.image
|
||||
|
||||
self:sync()
|
||||
end
|
||||
end
|
73
sys/apis/ui/components/TitleBar.lua
Normal file
73
sys/apis/ui/components/TitleBar.lua
Normal file
@ -0,0 +1,73 @@
|
||||
local class = require('class')
|
||||
local UI = require('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 = '-',
|
||||
closeInd = '*',
|
||||
}
|
||||
function UI.TitleBar:draw()
|
||||
local sb = SB(self.width)
|
||||
sb:fill(2, self.frameChar, sb.width - 3)
|
||||
sb:center(string.format(' %s ', self.title))
|
||||
if self.previousPage or self.event then
|
||||
sb:insert(-1, self.closeInd)
|
||||
else
|
||||
sb:insert(-2, self.frameChar)
|
||||
end
|
||||
self:write(1, 1, sb:get())
|
||||
end
|
||||
|
||||
function UI.TitleBar:eventHandler(event)
|
||||
if event.type == 'mouse_click' then
|
||||
if (self.previousPage or self.event) and event.x == self.width then
|
||||
if self.event then
|
||||
self:emit({ type = self.event, element = self })
|
||||
elseif type(self.previousPage) == 'string' or
|
||||
type(self.previousPage) == 'table' then
|
||||
UI:setPage(self.previousPage)
|
||||
else
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
18
sys/apis/ui/components/VerticalMeter.lua
Normal file
18
sys/apis/ui/components/VerticalMeter.lua
Normal file
@ -0,0 +1,18 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.VerticalMeter = class(UI.Window)
|
||||
UI.VerticalMeter.defaults = {
|
||||
UIElement = 'VerticalMeter',
|
||||
backgroundColor = colors.gray,
|
||||
meterColor = colors.lime,
|
||||
width = 1,
|
||||
value = 0,
|
||||
}
|
||||
function UI.VerticalMeter:draw()
|
||||
local height = self.height - math.ceil(self.value / 100 * self.height)
|
||||
self:clear()
|
||||
self:clearArea(1, height + 1, self.width, self.height, self.meterColor)
|
||||
end
|
96
sys/apis/ui/components/Viewport.lua
Normal file
96
sys/apis/ui/components/Viewport.lua
Normal file
@ -0,0 +1,96 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
--[[-- Viewport --]]--
|
||||
UI.Viewport = class(UI.Window)
|
||||
UI.Viewport.defaults = {
|
||||
UIElement = 'Viewport',
|
||||
backgroundColor = colors.cyan,
|
||||
accelerators = {
|
||||
down = 'scroll_down',
|
||||
up = 'scroll_up',
|
||||
home = 'scroll_top',
|
||||
[ 'end' ] = 'scroll_bottom',
|
||||
pageUp = 'scroll_pageUp',
|
||||
[ 'control-b' ] = 'scroll_pageUp',
|
||||
pageDown = 'scroll_pageDown',
|
||||
[ 'control-f' ] = 'scroll_pageDown',
|
||||
},
|
||||
}
|
||||
function UI.Viewport:setParent()
|
||||
UI.Window.setParent(self)
|
||||
self.canvas = self:addLayer()
|
||||
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
|
||||
if self.scrollBar then
|
||||
self.scrollBar:draw()
|
||||
end
|
||||
self.canvas.offy = offset
|
||||
self.canvas:dirty()
|
||||
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
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Viewport:getViewArea()
|
||||
return {
|
||||
y = (self.offy or 0) + 1,
|
||||
height = self.height,
|
||||
totalHeight = #self.canvas.lines,
|
||||
offsetY = self.offy or 0,
|
||||
}
|
||||
end
|
||||
|
||||
function UI.Viewport:eventHandler(event)
|
||||
if event.type == 'scroll_down' then
|
||||
self:setScrollPosition(self.offy + 1)
|
||||
elseif event.type == 'scroll_up' then
|
||||
self:setScrollPosition(self.offy - 1)
|
||||
elseif event.type == 'scroll_top' then
|
||||
self:setScrollPosition(0)
|
||||
elseif event.type == 'scroll_bottom' then
|
||||
self:setScrollPosition(10000000)
|
||||
elseif event.type == 'scroll_pageUp' then
|
||||
self:setScrollPosition(self.offy - self.height)
|
||||
elseif event.type == 'scroll_pageDown' then
|
||||
self:setScrollPosition(self.offy + self.height)
|
||||
elseif event.type == 'scroll_to' then
|
||||
self:setScrollPosition(event.offset)
|
||||
else
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
124
sys/apis/ui/components/Wizard.lua
Normal file
124
sys/apis/ui/components/Wizard.lua
Normal file
@ -0,0 +1,124 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
UI.Wizard = class(UI.Window)
|
||||
UI.Wizard.defaults = {
|
||||
UIElement = 'Wizard',
|
||||
pages = { },
|
||||
}
|
||||
function UI.Wizard:postInit()
|
||||
self.cancelButton = UI.Button {
|
||||
x = 2, y = -1,
|
||||
text = 'Cancel',
|
||||
event = 'cancel',
|
||||
}
|
||||
self.previousButton = UI.Button {
|
||||
x = -18, y = -1,
|
||||
text = '< Back',
|
||||
event = 'previousView',
|
||||
}
|
||||
self.nextButton = UI.Button {
|
||||
x = -9, y = -1,
|
||||
text = 'Next >',
|
||||
event = 'nextView',
|
||||
}
|
||||
|
||||
Util.merge(self, self.pages)
|
||||
--for _, child in pairs(self.pages) do
|
||||
-- child.ey = -2
|
||||
--end
|
||||
end
|
||||
|
||||
function UI.Wizard:add(pages)
|
||||
Util.merge(self.pages, pages)
|
||||
Util.merge(self, pages)
|
||||
|
||||
for _, child in pairs(self.pages) do
|
||||
child.ey = child.ey or -2
|
||||
end
|
||||
|
||||
if self.parent then
|
||||
self:initChildren()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Wizard:getPage(index)
|
||||
return Util.find(self.pages, 'index', index)
|
||||
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
|
||||
if child == initial or not child.index then
|
||||
child:enable(...)
|
||||
else
|
||||
child:disable()
|
||||
end
|
||||
end
|
||||
self:emit({ type = 'enable_view', next = initial })
|
||||
end
|
||||
|
||||
function UI.Wizard:isViewValid()
|
||||
local currentView = self:getPage(self.index)
|
||||
return not currentView.validate and true or currentView:validate()
|
||||
end
|
||||
|
||||
function UI.Wizard:eventHandler(event)
|
||||
if event.type == 'nextView' then
|
||||
local currentView = self:getPage(self.index)
|
||||
if self:isViewValid() then
|
||||
self.index = self.index + 1
|
||||
local nextView = self:getPage(self.index)
|
||||
currentView:emit({ type = 'enable_view', next = nextView, current = currentView })
|
||||
end
|
||||
|
||||
elseif event.type == 'previousView' then
|
||||
local currentView = self:getPage(self.index)
|
||||
local nextView = self:getPage(self.index - 1)
|
||||
if nextView then
|
||||
self.index = self.index - 1
|
||||
currentView:emit({ type = 'enable_view', prev = nextView, current = currentView })
|
||||
end
|
||||
return true
|
||||
|
||||
elseif event.type == 'wizard_complete' then
|
||||
if self:isViewValid() then
|
||||
self:emit({ type = 'accept' })
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
if event.current then
|
||||
if event.next then
|
||||
self.transitionHint = 'slideLeft'
|
||||
elseif event.prev then
|
||||
self.transitionHint = 'slideRight'
|
||||
end
|
||||
event.current:disable()
|
||||
end
|
||||
|
||||
if self:getPage(self.index - 1) then
|
||||
self.previousButton:enable()
|
||||
else
|
||||
self.previousButton:disable()
|
||||
end
|
||||
|
||||
if self:getPage(self.index + 1) then
|
||||
self.nextButton.text = 'Next >'
|
||||
self.nextButton.event = 'nextView'
|
||||
else
|
||||
self.nextButton.text = 'Accept'
|
||||
self.nextButton.event = 'wizard_complete'
|
||||
end
|
||||
-- a new current view
|
||||
current:enable()
|
||||
current:emit({ type = 'view_enabled', view = current })
|
||||
self:draw()
|
||||
end
|
||||
end
|
11
sys/apis/ui/components/WizardPage.lua
Normal file
11
sys/apis/ui/components/WizardPage.lua
Normal file
@ -0,0 +1,11 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
UI.WizardPage = class(UI.ActiveLayer)
|
||||
UI.WizardPage.defaults = {
|
||||
UIElement = 'WizardPage',
|
||||
backgroundColor = colors.cyan,
|
||||
ey = -2,
|
||||
}
|
@ -3,35 +3,33 @@ local Tween = require('ui.tween')
|
||||
local Transition = { }
|
||||
|
||||
function Transition.slideLeft(args)
|
||||
local ticks = args.ticks or 6
|
||||
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)
|
||||
|
||||
args.canvas:move(pos.x, args.canvas.y)
|
||||
|
||||
return function(device)
|
||||
return function()
|
||||
local finished = tween:update(1)
|
||||
args.canvas:move(math.floor(pos.x), args.canvas.y)
|
||||
args.canvas:dirty()
|
||||
args.canvas:render(device)
|
||||
return not finished
|
||||
end
|
||||
end
|
||||
|
||||
function Transition.slideRight(args)
|
||||
local ticks = args.ticks or 6
|
||||
local ticks = args.ticks or 10
|
||||
local easing = args.easing or'outQuint'
|
||||
local pos = { x = -args.canvas.width }
|
||||
local tween = Tween.new(ticks, pos, { x = 1 }, easing)
|
||||
|
||||
args.canvas:move(pos.x, args.canvas.y)
|
||||
|
||||
return function(device)
|
||||
return function()
|
||||
local finished = tween:update(1)
|
||||
args.canvas:move(math.floor(pos.x), args.canvas.y)
|
||||
args.canvas:dirty()
|
||||
args.canvas:render(device)
|
||||
return not finished
|
||||
end
|
||||
end
|
||||
@ -44,11 +42,10 @@ function Transition.expandUp(args)
|
||||
|
||||
args.canvas:move(args.x, pos.y)
|
||||
|
||||
return function(device)
|
||||
return function()
|
||||
local finished = tween:update(1)
|
||||
args.canvas:move(args.x, math.floor(pos.y))
|
||||
args.canvas:dirty()
|
||||
args.canvas:render(device)
|
||||
return not finished
|
||||
end
|
||||
end
|
||||
|
@ -1,457 +0,0 @@
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local http = _G.http
|
||||
local install = _ENV.install
|
||||
local os = _G.os
|
||||
|
||||
local injector
|
||||
if not install.testing then
|
||||
_G.OPUS_BRANCH = 'master-1.8'
|
||||
local url ='https://raw.githubusercontent.com/kepler155c/opus/master-1.8/sys/apis/injector.lua'
|
||||
injector = load(http.get(url).readAll(), 'injector.lua', nil, _ENV)()
|
||||
else
|
||||
injector = _G.requireInjector
|
||||
end
|
||||
|
||||
injector(_ENV)
|
||||
|
||||
if not install.testing then
|
||||
if package then
|
||||
for _ = 1, 4 do
|
||||
table.remove(package.loaders, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local Git = require('git')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local currentFile = ''
|
||||
local currentProgress = 0
|
||||
local cancelEvent
|
||||
|
||||
local args = { ... }
|
||||
local steps = install.steps[args[1] or 'install']
|
||||
|
||||
if not steps then
|
||||
error('Invalid install type')
|
||||
end
|
||||
|
||||
local mode = steps[#steps]
|
||||
|
||||
if UI.term.width < 32 then
|
||||
cancelEvent = 'quit'
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
event = cancelEvent,
|
||||
},
|
||||
wizard = UI.Wizard {
|
||||
y = 2, ey = -2,
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
local pages = {
|
||||
splash = UI.Viewport { },
|
||||
review = UI.Viewport { },
|
||||
license = UI.Viewport {
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
branch = UI.Window {
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -3,
|
||||
columns = {
|
||||
{ heading = 'Branch', key = 'branch' },
|
||||
{ heading = 'Description', key = 'description' },
|
||||
},
|
||||
values = install.branches,
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
files = UI.Window {
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -3,
|
||||
columns = {
|
||||
{ heading = 'Files', key = 'file' },
|
||||
},
|
||||
sortColumn = 'file',
|
||||
},
|
||||
},
|
||||
install = UI.Window {
|
||||
progressBar = UI.ProgressBar {
|
||||
y = -1,
|
||||
},
|
||||
},
|
||||
uninstall = UI.Window {
|
||||
progressBar = UI.ProgressBar {
|
||||
y = -1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local function getFileList()
|
||||
if install.gitRepo then
|
||||
local gitFiles = Git.list(string.format('%s/%s', install.gitRepo, install.gitBranch or 'master'))
|
||||
install.files = { }
|
||||
install.diskspace = 0
|
||||
for path, entry in pairs(gitFiles) do
|
||||
install.files[path] = entry.url
|
||||
install.diskspace = install.diskspace + entry.size
|
||||
end
|
||||
end
|
||||
|
||||
if not install.files or Util.empty(install.files) then
|
||||
error('File list is missing or empty')
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Splash ]]--
|
||||
function pages.splash:enable()
|
||||
page.titleBar.title = 'Installer v1.0'
|
||||
UI.Viewport.enable(self)
|
||||
end
|
||||
|
||||
function pages.splash:draw()
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(
|
||||
string.format('%s v%s\n', install.title, install.version), nil, colors.yellow)
|
||||
self:print(
|
||||
string.format('By: %s\n\n%s\n', install.author, install.description))
|
||||
|
||||
self.ymax = self.cursorY
|
||||
end
|
||||
|
||||
--[[ License ]]--
|
||||
function pages.license:enable()
|
||||
page.titleBar.title = 'License Review'
|
||||
page.wizard.nextButton.text = 'Accept'
|
||||
UI.Viewport.enable(self)
|
||||
end
|
||||
|
||||
function pages.license:draw()
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(
|
||||
string.format('Copyright (c) %s %s\n\n', install.copyrightYear,
|
||||
install.copyrightHolders),
|
||||
nil, colors.yellow)
|
||||
self:print(install.license)
|
||||
|
||||
self.ymax = self.cursorY + 1
|
||||
end
|
||||
|
||||
--[[ Review ]]--
|
||||
function pages.review:enable()
|
||||
if mode == 'uninstall' then
|
||||
page.nextButton.text = 'Remove'
|
||||
page.titleBar.title = 'Remove Installed Files'
|
||||
else
|
||||
page.wizard.nextButton.text = 'Begin'
|
||||
page.titleBar.title = 'Download and Install'
|
||||
end
|
||||
UI.Viewport.enable(self)
|
||||
end
|
||||
|
||||
function pages.review:draw()
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
|
||||
local text = 'Ready to begin installation.\n\nProceeding will download and install the files to the hard drive.'
|
||||
if mode == 'uninstall' then
|
||||
text = 'Ready to begin.\n\nProceeding will remove the files previously installed.'
|
||||
end
|
||||
self:print(text)
|
||||
|
||||
self.ymax = self.cursorY + 1
|
||||
end
|
||||
|
||||
--[[ Files ]]--
|
||||
function pages.files:enable()
|
||||
page.titleBar.title = 'Review Files'
|
||||
self.grid.values = { }
|
||||
for k,v in pairs(install.files) do
|
||||
table.insert(self.grid.values, { file = k, code = v })
|
||||
end
|
||||
self.grid:update()
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
|
||||
function pages.files:draw()
|
||||
self:clear()
|
||||
|
||||
local function formatSize(size)
|
||||
if size >= 1000000 then
|
||||
return string.format('%dM', math.floor(size/1000000, 2))
|
||||
elseif size >= 1000 then
|
||||
return string.format('%dK', math.floor(size/1000, 2))
|
||||
end
|
||||
return size
|
||||
end
|
||||
|
||||
if install.diskspace then
|
||||
|
||||
local bg = self.backgroundColor
|
||||
|
||||
local diskFree = fs.getFreeSpace('/')
|
||||
if install.diskspace > diskFree then
|
||||
bg = colors.red
|
||||
end
|
||||
|
||||
local text = string.format('Space Required: %s, Free: %s',
|
||||
formatSize(install.diskspace), formatSize(diskFree))
|
||||
|
||||
if #text > self.width then
|
||||
text = string.format('Space: %s Free: %s',
|
||||
formatSize(install.diskspace), formatSize(diskFree))
|
||||
end
|
||||
|
||||
self:write(1, self.height, Util.widthify(text, self.width), bg)
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
--[[
|
||||
function pages.files:view(url)
|
||||
local s, m = pcall(function()
|
||||
page.notification:info('Downloading')
|
||||
page:sync()
|
||||
Util.download(url, '/.source')
|
||||
end)
|
||||
page.notification:disable()
|
||||
if s then
|
||||
shell.run('edit /.source')
|
||||
fs.delete('/.source')
|
||||
page:draw()
|
||||
page.notification:cancel()
|
||||
else
|
||||
page.notification:error(m:gsub('.*: (.*)', '%1'))
|
||||
end
|
||||
end
|
||||
|
||||
function pages.files:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
self:view(event.selected.code)
|
||||
return true
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
local function drawCommon(self)
|
||||
if currentFile then
|
||||
self:write(1, 3, 'File:')
|
||||
self:write(1, 4, Util.widthify(currentFile, self.width))
|
||||
else
|
||||
self:write(1, 3, 'Finished')
|
||||
end
|
||||
if self.failed then
|
||||
self:write(1, 5, Util.widthify(self.failed, self.width), colors.red)
|
||||
end
|
||||
self:write(1, self.height - 1, 'Progress')
|
||||
|
||||
self.progressBar.value = currentProgress
|
||||
self.progressBar:draw()
|
||||
self:sync()
|
||||
end
|
||||
|
||||
--[[ Branch ]]--
|
||||
function pages.branch:enable()
|
||||
page.titleBar.title = 'Select Branch'
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
|
||||
function pages.branch:eventHandler(event)
|
||||
-- user is navigating to next view (not previous)
|
||||
if event.type == 'enable_view' and event.next then
|
||||
install.gitBranch = self.grid:getSelected().branch
|
||||
getFileList()
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Install ]]--
|
||||
function pages.install:enable()
|
||||
page.wizard.cancelButton:disable()
|
||||
page.wizard.previousButton:disable()
|
||||
page.wizard.nextButton:disable()
|
||||
|
||||
page.titleBar.title = 'Installing...'
|
||||
page.titleBar.event = nil
|
||||
|
||||
UI.Window.enable(self)
|
||||
|
||||
page:draw()
|
||||
page:sync()
|
||||
|
||||
local i = 0
|
||||
local numFiles = Util.size(install.files)
|
||||
for filename,url in pairs(install.files) do
|
||||
currentFile = filename
|
||||
currentProgress = i / numFiles * 100
|
||||
self:draw(self)
|
||||
self:sync()
|
||||
local s, m = pcall(function()
|
||||
Util.download(url, fs.combine(install.directory or '', filename))
|
||||
end)
|
||||
if not s then
|
||||
self.failed = m:gsub('.*: (.*)', '%1')
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if not self.failed then
|
||||
currentProgress = 100
|
||||
currentFile = nil
|
||||
|
||||
if install.postInstall then
|
||||
local s, m = pcall(function() install.postInstall(page, UI) end)
|
||||
if not s then
|
||||
self.failed = m:gsub('.*: (.*)', '%1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
page.wizard.nextButton.text = 'Exit'
|
||||
page.wizard.nextButton.event = 'quit'
|
||||
if not self.failed and install.rebootAfter then
|
||||
page.wizard.nextButton.text = 'Reboot'
|
||||
page.wizard.nextButton.event = 'reboot'
|
||||
end
|
||||
|
||||
page.wizard.nextButton:enable()
|
||||
page:draw()
|
||||
page:sync()
|
||||
|
||||
if not self.failed and Util.key(args, 'automatic') then
|
||||
if install.rebootAfter then
|
||||
os.reboot()
|
||||
else
|
||||
UI:exitPullEvents()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function pages.install:draw()
|
||||
self:clear()
|
||||
local text = 'The files are being installed'
|
||||
if #text > self.width then
|
||||
text = 'Installing files'
|
||||
end
|
||||
self:write(1, 1, text, nil, colors.yellow)
|
||||
|
||||
drawCommon(self)
|
||||
end
|
||||
|
||||
--[[ Uninstall ]]--
|
||||
function pages.uninstall:enable()
|
||||
page.wizard.cancelButton:disable()
|
||||
page.wizard.previousButton:disable()
|
||||
page.wizard.nextButton:disable()
|
||||
|
||||
page.titleBar.title = 'Uninstalling...'
|
||||
page.titleBar.event = nil
|
||||
|
||||
page:draw()
|
||||
page:sync()
|
||||
|
||||
UI.Window.enable(self)
|
||||
|
||||
local function pruneDir(dir)
|
||||
if #dir > 0 then
|
||||
if fs.exists(dir) then
|
||||
local files = fs.list(dir)
|
||||
if #files == 0 then
|
||||
fs.delete(dir)
|
||||
pruneDir(fs.getDir(dir))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local i = 0
|
||||
local numFiles = Util.size(install.files)
|
||||
for k in pairs(install.files) do
|
||||
currentFile = k
|
||||
currentProgress = i / numFiles * 100
|
||||
self:draw()
|
||||
self:sync()
|
||||
fs.delete(k)
|
||||
pruneDir(fs.getDir(k))
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
currentProgress = 100
|
||||
currentFile = nil
|
||||
|
||||
page.wizard.nextButton.text = 'Exit'
|
||||
page.wizard.nextButton.event = 'quit'
|
||||
page.wizard.nextButton:enable()
|
||||
|
||||
page:draw()
|
||||
page:sync()
|
||||
end
|
||||
|
||||
function pages.uninstall:draw()
|
||||
self:clear()
|
||||
self:write(1, 1, 'Uninstalling files', nil, colors.yellow)
|
||||
drawCommon(self)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'cancel' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'reboot' then
|
||||
os.reboot()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(page.wizard.nextButton)
|
||||
if UI.term.width < 32 then
|
||||
page.wizard.cancelButton:disable()
|
||||
page.wizard.previousButton.x = 2
|
||||
end
|
||||
end
|
||||
|
||||
getFileList()
|
||||
|
||||
local wizardPages = { }
|
||||
for k,v in ipairs(steps) do
|
||||
if not pages[v] then
|
||||
error('Invalid step: ' .. v)
|
||||
end
|
||||
wizardPages[k] = pages[v]
|
||||
wizardPages[k].index = k
|
||||
wizardPages[k].x = 2
|
||||
wizardPages[k].y = 2
|
||||
wizardPages[k].ey = -3
|
||||
wizardPages[k].ex = -2
|
||||
end
|
||||
page.wizard:add(wizardPages)
|
||||
|
||||
if Util.key(steps, 'install') and install.preInstall then
|
||||
install.preInstall(page, UI)
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
local s, m = pcall(function() UI:pullEvents() end)
|
||||
if not s then
|
||||
UI.term:reset()
|
||||
_G.printError(m)
|
||||
end
|
@ -58,6 +58,7 @@ local page = UI.Page {
|
||||
},
|
||||
output = UI.Embedded {
|
||||
y = -6,
|
||||
visible = true,
|
||||
backgroundColor = colors.gray,
|
||||
},
|
||||
}
|
||||
@ -328,6 +329,7 @@ function page:executeStatement(statement)
|
||||
|
||||
local s, m
|
||||
local oterm = term.redirect(self.output.win)
|
||||
self.output.win.scrollBottom()
|
||||
pcall(function()
|
||||
s, m = self:rawExecute(command)
|
||||
end)
|
||||
|
@ -252,7 +252,7 @@ end
|
||||
function page.container:setCategory(categoryName, animate)
|
||||
-- reset the viewport window
|
||||
self.children = { }
|
||||
self.offy = 0
|
||||
self:reset()
|
||||
|
||||
local function filter(it, f)
|
||||
local ot = { }
|
||||
@ -334,10 +334,10 @@ function page.container:setCategory(categoryName, animate)
|
||||
for k,child in ipairs(self.children) do
|
||||
if r == 1 then
|
||||
child.x = math.random(1, self.width)
|
||||
child.y = math.random(1, self.height)
|
||||
child.y = math.random(1, self.height - 3)
|
||||
elseif r == 2 then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
child.y = self.height - 3
|
||||
elseif r == 3 then
|
||||
child.x = math.floor(self.width / 2)
|
||||
child.y = math.floor(self.height / 2)
|
||||
@ -349,7 +349,7 @@ function page.container:setCategory(categoryName, animate)
|
||||
child.y = row
|
||||
if k == #self.children then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
child.y = self.height - 3
|
||||
end
|
||||
end
|
||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
||||
@ -369,10 +369,10 @@ function page.container:setCategory(categoryName, animate)
|
||||
end
|
||||
|
||||
self:initChildren()
|
||||
if animate then -- need to fix transitions under layers
|
||||
local function transition(args)
|
||||
if animate then
|
||||
local function transition()
|
||||
local i = 1
|
||||
return function(device)
|
||||
return function()
|
||||
self:clear()
|
||||
for _,child in pairs(self.children) do
|
||||
child.tween:update(1)
|
||||
@ -380,7 +380,6 @@ function page.container:setCategory(categoryName, animate)
|
||||
child.y = math.floor(child.y)
|
||||
child:draw()
|
||||
end
|
||||
args.canvas:blit(device, args, args)
|
||||
i = i + 1
|
||||
return i < 7
|
||||
end
|
||||
|
@ -45,8 +45,10 @@ local page = UI.Page {
|
||||
help = 'Download the latest package list',
|
||||
},
|
||||
action = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
backgroundColor = colors.brown,
|
||||
y = 3,
|
||||
titleBar = UI.TitleBar {
|
||||
backgroundColor = colors.brown,
|
||||
event = 'hide-action',
|
||||
},
|
||||
button = UI.Button {
|
||||
@ -56,9 +58,6 @@ local page = UI.Page {
|
||||
output = UI.Embedded {
|
||||
y = 5, ey = -2, x = 2, ex = -2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
}
|
||||
@ -101,9 +100,10 @@ function page.grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function page.action:show()
|
||||
self.output.win:clear()
|
||||
UI.SlideOut.show(self)
|
||||
self.output:draw()
|
||||
self.output.win.redraw()
|
||||
--self.output:draw()
|
||||
--self.output.win.redraw()
|
||||
end
|
||||
|
||||
function page:run(operation, name)
|
||||
@ -116,6 +116,7 @@ function page:run(operation, name)
|
||||
print(cmd .. '\n')
|
||||
term.setTextColor(colors.white)
|
||||
local s, m = Util.run(_ENV, '/sys/apps/package.lua', operation, name)
|
||||
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
|
@ -24,7 +24,7 @@ local page = UI.Page {
|
||||
wizard = UI.Wizard {
|
||||
ey = -2,
|
||||
pages = {
|
||||
splash = UI.Window {
|
||||
splash = UI.WizardPage {
|
||||
index = 1,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
@ -33,7 +33,7 @@ local page = UI.Page {
|
||||
value = string.format(splashIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
label = UI.Window {
|
||||
label = UI.WizardPage {
|
||||
index = 2,
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
@ -51,7 +51,7 @@ local page = UI.Page {
|
||||
value = string.format(labelIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
password = UI.Window {
|
||||
password = UI.WizardPage {
|
||||
index = 3,
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
@ -73,7 +73,7 @@ local page = UI.Page {
|
||||
value = string.format(passwordIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
packages = UI.Window {
|
||||
packages = UI.WizardPage {
|
||||
index = 4,
|
||||
button = UI.Button {
|
||||
x = 3, y = -3,
|
||||
|
@ -361,16 +361,21 @@ local Config = require('config')
|
||||
local Entry = require('entry')
|
||||
local History = require('history')
|
||||
local Input = require('input')
|
||||
local Terminal = require('terminal')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
local term = _G.term
|
||||
local textutils = _G.textutils
|
||||
|
||||
local oldTerm
|
||||
local terminal = term.current()
|
||||
--Terminal.scrollable(terminal, 100)
|
||||
terminal.noAutoScroll = true
|
||||
|
||||
if not terminal.scrollUp then
|
||||
local Terminal = require('terminal')
|
||||
terminal = Terminal.window(term.current())
|
||||
terminal.setMaxScroll(200)
|
||||
oldTerm = term.redirect(terminal)
|
||||
end
|
||||
|
||||
local config = {
|
||||
standard = {
|
||||
@ -555,6 +560,9 @@ local function shellRead(history)
|
||||
term.setCursorBlink(true)
|
||||
|
||||
local function redraw()
|
||||
if terminal.scrollBottom then
|
||||
terminal.scrollBottom()
|
||||
end
|
||||
local _,cy = term.getCursorPos()
|
||||
term.setCursorPos(3, cy)
|
||||
local filler = #entry.value < lastLen
|
||||
@ -571,11 +579,11 @@ local function shellRead(history)
|
||||
|
||||
local ie = Input:translate(event, p1, p2, p3)
|
||||
if ie then
|
||||
if ie.code == 'scroll_up' then
|
||||
--terminal.scrollUp()
|
||||
if ie.code == 'scroll_up' and terminal.scrollUp then
|
||||
terminal.scrollUp()
|
||||
|
||||
elseif ie.code == 'scroll_down' then
|
||||
--terminal.scrollDown()
|
||||
elseif ie.code == 'scroll_down' and terminal.scrollDown then
|
||||
terminal.scrollDown()
|
||||
|
||||
elseif ie.code == 'terminate' then
|
||||
bExit = true
|
||||
@ -652,3 +660,7 @@ while not bExit do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if oldTerm then
|
||||
term.redirect(oldTerm)
|
||||
end
|
||||
|
@ -25,11 +25,13 @@ local function systemLog()
|
||||
|
||||
if y > 1 then
|
||||
local currentTab = kernel.getFocused()
|
||||
if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then
|
||||
if dir == -1 then
|
||||
currentTab.terminal.scrollUp()
|
||||
else
|
||||
currentTab.terminal.scrollDown()
|
||||
if currentTab == routine then
|
||||
if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then
|
||||
if dir == -1 then
|
||||
currentTab.terminal.scrollUp()
|
||||
else
|
||||
currentTab.terminal.scrollDown()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -14,13 +14,12 @@ local kernel = _G.kernel
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
local window = _G.window
|
||||
|
||||
local w, h = term.getSize()
|
||||
kernel.terminal = term.current()
|
||||
kernel.window = window.create(kernel.terminal, 1, 1, w, h, false)
|
||||
|
||||
Terminal.scrollable(kernel.window)
|
||||
kernel.window = Terminal.window(kernel.terminal, 1, 1, w, h, false)
|
||||
kernel.window.setMaxScroll(100)
|
||||
|
||||
local focusedRoutineEvents = Util.transpose {
|
||||
'char', 'key', 'key_up',
|
||||
@ -30,6 +29,7 @@ local focusedRoutineEvents = Util.transpose {
|
||||
|
||||
_G._debug = function(pattern, ...)
|
||||
local oldTerm = term.redirect(kernel.window)
|
||||
kernel.window.scrollBottom()
|
||||
Util.print(pattern, ...)
|
||||
term.redirect(oldTerm)
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user