color rework + cleanup
This commit is contained in:
94
sys/modules/opus/ui/blit.lua
Normal file
94
sys/modules/opus/ui/blit.lua
Normal file
@@ -0,0 +1,94 @@
|
||||
local class = require('opus.class')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local Blit = class()
|
||||
|
||||
function Blit:init(t, cs)
|
||||
if type(t) == 'string' then
|
||||
t = Blit.toblit(t, cs or { })
|
||||
end
|
||||
self.text = t.text
|
||||
self.bg = t.bg
|
||||
self.fg = t.fg
|
||||
end
|
||||
|
||||
function Blit:sub(s, e)
|
||||
return Blit({
|
||||
text = self.text:sub(s, e),
|
||||
bg = self.bg:sub(s, e),
|
||||
fg = self.fg:sub(s, e),
|
||||
})
|
||||
end
|
||||
|
||||
function Blit:wrap(max)
|
||||
local index = 1
|
||||
local lines = { }
|
||||
local data = self
|
||||
|
||||
repeat
|
||||
if #data.text <= max then
|
||||
table.insert(lines, data)
|
||||
break
|
||||
elseif data.text:sub(max+1, max+1) == ' ' then
|
||||
table.insert(lines, data:sub(index, max))
|
||||
data = data:sub(max + 2)
|
||||
else
|
||||
local x = data.text:sub(1, max)
|
||||
local s = x:match('(.*) ') or x
|
||||
table.insert(lines, data:sub(1, #s))
|
||||
data = data:sub(#s + 1)
|
||||
end
|
||||
local t = data.text:match('^%s*(.*)')
|
||||
local spaces = #data.text - #t
|
||||
if spaces > 0 then
|
||||
data = data:sub(spaces + 1)
|
||||
end
|
||||
until not data.text or #data.text == 0
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- convert a string of text to blit format doing color conversion
|
||||
-- and processing ansi color sequences
|
||||
function Blit.toblit(str, cs)
|
||||
local text, fg, bg = '', '', ''
|
||||
|
||||
if not cs.cbg then
|
||||
-- reset colors
|
||||
cs.rbg = cs.palette[cs.bg or colors.black]
|
||||
cs.rfg = cs.palette[cs.fg or colors.white]
|
||||
-- current colors
|
||||
cs.cbg = cs.rbg
|
||||
cs.cfg = cs.rfg
|
||||
end
|
||||
|
||||
str = str:gsub('(.-)\027%[([%d;]+)m',
|
||||
function(k, seq)
|
||||
text = text .. k
|
||||
bg = bg .. string.rep(cs.cbg, #k)
|
||||
fg = fg .. string.rep(cs.cfg, #k)
|
||||
for color in string.gmatch(seq, "%d+") do
|
||||
color = tonumber(color)
|
||||
if color == 0 then
|
||||
-- reset to default
|
||||
cs.cfg = cs.rfg
|
||||
cs.cbg = cs.rbg
|
||||
elseif color > 20 then
|
||||
cs.cbg = string.sub("0123456789abcdef", color - 21, color - 21)
|
||||
else
|
||||
cs.cfg = string.sub("0123456789abcdef", color, color)
|
||||
end
|
||||
end
|
||||
return k
|
||||
end)
|
||||
|
||||
local k = str:sub(#text + 1)
|
||||
return {
|
||||
text = text .. k,
|
||||
bg = bg .. string.rep(cs.cbg, #k),
|
||||
fg = fg .. string.rep(cs.cfg, #k),
|
||||
}
|
||||
end
|
||||
|
||||
return Blit
|
||||
@@ -71,7 +71,7 @@ end
|
||||
|
||||
-- resize the canvas buffer - not the canvas itself
|
||||
function Canvas:resizeBuffer(w, h)
|
||||
for i = #self.lines, h do
|
||||
for i = #self.lines + 1, h do
|
||||
self.lines[i] = { }
|
||||
self:clearLine(i)
|
||||
end
|
||||
@@ -297,40 +297,38 @@ function Canvas:applyPalette(palette)
|
||||
self.palette = palette
|
||||
end
|
||||
|
||||
function Canvas:render(device)
|
||||
local offset = { x = 0, y = 0 }
|
||||
|
||||
-- WIP
|
||||
local function getRegion(canvas)
|
||||
local region
|
||||
if canvas.parent then
|
||||
region = getRegion(canvas.parent)
|
||||
else
|
||||
region = Region.new(self.x, self.y, self.ex, self.ey)
|
||||
end
|
||||
offset.x = offset.x + canvas.x - 1
|
||||
offset.y = offset.y + canvas.y - 1
|
||||
-- clip against parent
|
||||
return region
|
||||
end
|
||||
|
||||
-- this code works - but is all kinds of wrong
|
||||
-- adding a margin to UI.Page will cause issues
|
||||
-- and could be clipping issues
|
||||
offset = { x = self.x - 1, y = self.y - 1 }
|
||||
local parent = self.parent
|
||||
while parent do
|
||||
offset.x = offset.x + parent.x - 1
|
||||
offset.y = offset.y + parent.y - 1
|
||||
parent = parent.parent
|
||||
end
|
||||
|
||||
-- TODO: need to clip if there is a parent
|
||||
--self.regions = Region.new(self.x + offset.x, self.y + offset.y, self.ex + offset.x, self.ey + offset.y)
|
||||
--self:__renderLayers(device, offset)
|
||||
|
||||
-- either render directly to the device
|
||||
-- or use another canvas as a backing buffer
|
||||
function Canvas:render(device, doubleBuffer)
|
||||
self.regions = Region.new(self.x, self.y, self.ex, self.ey)
|
||||
self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 })
|
||||
self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }, doubleBuffer)
|
||||
|
||||
-- doubleBuffering to reduce the amount of
|
||||
-- setCursorPos, blits
|
||||
if doubleBuffer then
|
||||
--[[
|
||||
local drew = false
|
||||
local bg = _rep(2, device.width)
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, bg)
|
||||
drew = true
|
||||
end
|
||||
end
|
||||
if drew then
|
||||
local c = os.clock()
|
||||
repeat until os.clock()-c > .1
|
||||
end
|
||||
]]
|
||||
for k,v in pairs(device.lines) do
|
||||
if v.dirty then
|
||||
device.device.setCursorPos(device.x, device.y + k - 1)
|
||||
device.device.blit(v.text, v.fg, v.bg)
|
||||
v.dirty = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- regions are comprised of absolute values that correspond to the output device.
|
||||
@@ -338,7 +336,7 @@ end
|
||||
-- canvas layer's stacking order is determined by the position within the array.
|
||||
-- layers in the beginning of the array are overlayed by layers further down in
|
||||
-- the array.
|
||||
function Canvas:__renderLayers(device, offset)
|
||||
function Canvas:__renderLayers(device, offset, doubleBuffer)
|
||||
if self.children then
|
||||
for i = #self.children, 1, -1 do
|
||||
local canvas = self.children[i]
|
||||
@@ -364,7 +362,7 @@ function Canvas:__renderLayers(device, offset)
|
||||
canvas:__renderLayers(device, {
|
||||
x = canvas.x + offset.x - 1 - (self.offx or 0),
|
||||
y = canvas.y + offset.y - 1 - (self.offy or 0),
|
||||
})
|
||||
}, doubleBuffer)
|
||||
end
|
||||
canvas.regions = nil
|
||||
end
|
||||
@@ -377,19 +375,19 @@ function Canvas:__renderLayers(device, offset)
|
||||
y = region[2] - offset.y,
|
||||
ex = region[3] - offset.x,
|
||||
ey = region[4] - offset.y },
|
||||
{ x = region[1], y = region[2] })
|
||||
{ x = region[1], y = region[2] }, doubleBuffer)
|
||||
end
|
||||
self.regions = nil
|
||||
|
||||
self:clean()
|
||||
end
|
||||
|
||||
-- performance can probably be improved by using one more buffer tied to the device
|
||||
function Canvas:__blitRect(device, src, tgt)
|
||||
function Canvas:__blitRect(device, src, tgt, doubleBuffer)
|
||||
src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
|
||||
tgt = tgt or self
|
||||
|
||||
-- for visualizing updates on the screen
|
||||
--[[
|
||||
if Canvas.__visualize or self.visualize then
|
||||
local drew
|
||||
local t = _rep(' ', src.ex-src.x + 1)
|
||||
@@ -407,6 +405,7 @@ function Canvas:__blitRect(device, src, tgt)
|
||||
repeat until os.clock()-c > .03
|
||||
end
|
||||
end
|
||||
]]
|
||||
for i = 0, src.ey - src.y do
|
||||
local line = self.lines[src.y + i + (self.offy or 0)]
|
||||
if line and line.dirty then
|
||||
@@ -416,8 +415,13 @@ function Canvas:__blitRect(device, src, tgt)
|
||||
fg = _sub(fg, src.x, src.ex)
|
||||
bg = _sub(bg, src.x, src.ex)
|
||||
end
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
if doubleBuffer then
|
||||
Canvas.blit(device, tgt.x, tgt.y + i,
|
||||
t, bg, fg)
|
||||
else
|
||||
device.setCursorPos(tgt.x, tgt.y + i)
|
||||
device.blit(t, fg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,8 +7,7 @@ UI.Dialog = class(UI.SlideOut)
|
||||
UI.Dialog.defaults = {
|
||||
UIElement = 'Dialog',
|
||||
height = 7,
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.white,
|
||||
noFill = true,
|
||||
okEvent ='dialog_ok',
|
||||
cancelEvent = 'dialog_cancel',
|
||||
}
|
||||
@@ -17,19 +16,6 @@ function UI.Dialog:postInit()
|
||||
self.titleBar = UI.TitleBar({ event = self.cancelEvent, title = self.title })
|
||||
end
|
||||
|
||||
function UI.Dialog:show(...)
|
||||
local canvas = self.parent
|
||||
self.oldPalette = canvas.palette
|
||||
canvas:applyPalette(self.darkPalette)
|
||||
UI.SlideOut.show(self, ...)
|
||||
end
|
||||
|
||||
function UI.Dialog:hide(...)
|
||||
self.parent.palette = self.oldPalette
|
||||
UI.SlideOut.hide(self, ...)
|
||||
self.parent:draw()
|
||||
end
|
||||
|
||||
function UI.Dialog:eventHandler(event)
|
||||
if event.type == 'dialog_cancel' then
|
||||
self:hide()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local class = require('opus.class')
|
||||
local Event = require('opus.event')
|
||||
local Terminal = require('opus.terminal')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
@@ -19,10 +20,16 @@ function UI.Embedded:layout()
|
||||
UI.Window.layout(self)
|
||||
|
||||
if not self.win then
|
||||
local t
|
||||
function self.render()
|
||||
self:sync()
|
||||
if self.focused then
|
||||
self:setCursorPos(self.win.getCursorPos())
|
||||
if not t then
|
||||
t = Event.onTimeout(0, function()
|
||||
t = nil
|
||||
if self.focused then
|
||||
self:setCursorPos(self.win.getCursorPos())
|
||||
end
|
||||
self:sync()
|
||||
end)
|
||||
end
|
||||
end
|
||||
self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false)
|
||||
@@ -68,7 +75,6 @@ function UI.Embedded:eventHandler(event)
|
||||
end
|
||||
|
||||
function UI.Embedded.example()
|
||||
local Event = require('opus.event')
|
||||
local Util = require('opus.util')
|
||||
local term = _G.term
|
||||
|
||||
|
||||
@@ -93,7 +93,8 @@ function UI.MenuBar.example()
|
||||
return UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Choice1', event = 'event1' },
|
||||
{ text = 'Choice2', event = 'event2' },
|
||||
{ text = 'Choice2', event = 'event2', inactive = true },
|
||||
{ text = 'Choice3', event = 'event3' },
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -5,4 +5,5 @@ UI.MenuItem = class(UI.FlatButton)
|
||||
UI.MenuItem.defaults = {
|
||||
UIElement = 'MenuItem',
|
||||
noPadding = false,
|
||||
textInactiveColor = colors.gray,
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ UI.Page.defaults = {
|
||||
textColor = colors.white,
|
||||
}
|
||||
function UI.Page:postInit()
|
||||
self.parent = self.parent or UI.defaultDevice
|
||||
self.parent = self.parent or UI.term
|
||||
self.__target = self
|
||||
end
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ function UI.ScrollBar:draw()
|
||||
|
||||
self:clear()
|
||||
|
||||
-- ...
|
||||
self:write(1, 1, ' ', view.fill)
|
||||
|
||||
if view.totalHeight > view.height then
|
||||
local maxScroll = view.totalHeight - view.height
|
||||
local percent = view.offsetY / maxScroll
|
||||
|
||||
@@ -29,6 +29,7 @@ function UI.ScrollingGrid:getViewArea()
|
||||
height = self.pageSize, -- viewable height
|
||||
totalHeight = Util.size(self.values), -- total height
|
||||
offsetY = self.scrollOffset, -- scroll offset
|
||||
fill = not self.disableHeader and self.headerBackgroundColor,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ UI.TabBar = class(UI.MenuBar)
|
||||
UI.TabBar.defaults = {
|
||||
UIElement = 'TabBar',
|
||||
buttonClass = 'TabBarMenuItem',
|
||||
backgroundColor = UI.colors.tertiary,
|
||||
backgroundColor = colors.black,
|
||||
selectedBackgroundColor = UI.colors.primary,
|
||||
unselectedBackgroundColor = UI.colors.tertiary,
|
||||
}
|
||||
@@ -32,7 +32,7 @@ function UI.TabBar:eventHandler(event)
|
||||
self:emit({ type = 'tab_change', current = si, last = pi, tab = selected })
|
||||
end
|
||||
end
|
||||
UI.MenuBar.draw(self)
|
||||
self:draw(self)
|
||||
end
|
||||
return UI.MenuBar.eventHandler(self, event)
|
||||
end
|
||||
|
||||
@@ -5,6 +5,7 @@ UI.TabBarMenuItem = class(UI.Button)
|
||||
UI.TabBarMenuItem.defaults = {
|
||||
UIElement = 'TabBarMenuItem',
|
||||
event = 'tab_select',
|
||||
textInactiveColor = colors.lightGray,
|
||||
}
|
||||
function UI.TabBarMenuItem:draw()
|
||||
if self.selected then
|
||||
|
||||
@@ -117,5 +117,9 @@ function UI.Tabs.example()
|
||||
index = 3,
|
||||
tabTitle = 'tab3',
|
||||
},
|
||||
enable = function(self)
|
||||
UI.Tabs.enable(self)
|
||||
self:setActive(self.tab3, false)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -20,7 +20,6 @@ end
|
||||
|
||||
function UI.TextArea:draw()
|
||||
self:clear()
|
||||
self.cursorX, self.cursorY = 1, 1
|
||||
self:print(self.value)
|
||||
self:drawChildren()
|
||||
end
|
||||
|
||||
@@ -47,11 +47,18 @@ function UI.Viewport:setScrollPosition(offy, offx) -- argh - reverse
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Viewport:write(x, y, text, bg, tc)
|
||||
function UI.Viewport:blit(x, y, text, bg, fg)
|
||||
if y > #self.lines then
|
||||
self:resizeBuffer(self.width, y)
|
||||
end
|
||||
return UI.Window.write(self, x, y, text, bg, tc)
|
||||
return UI.Window.blit(self, x, y, text, bg, fg)
|
||||
end
|
||||
|
||||
function UI.Viewport:write(x, y, text, bg, fg)
|
||||
if y > #self.lines then
|
||||
self:resizeBuffer(self.width, y)
|
||||
end
|
||||
return UI.Window.write(self, x, y, text, bg, fg)
|
||||
end
|
||||
|
||||
function UI.Viewport:setViewHeight(h)
|
||||
|
||||
Reference in New Issue
Block a user