1
0
mirror of https://github.com/kepler155c/opus synced 2024-12-25 16:10:26 +00:00

UI improvements

This commit is contained in:
kepler155c@gmail.com 2017-10-07 23:03:18 -04:00
parent af981dd1f8
commit 98ec840db1
8 changed files with 240 additions and 166 deletions

View File

@ -36,7 +36,7 @@ return function(base)
c.is_a =
function(self, klass)
local m = getmetatable(self)
while m do
while m do
if m == klass then return true end
m = m._base
end

View File

@ -4,8 +4,10 @@ local Event = require('event')
local Transition = require('ui.transition')
local Util = require('util')
local _rep = string.rep
local _sub = string.sub
local _rep = string.rep
local _sub = string.sub
local colors = _G.colors
local keys = _G.keys
local function safeValue(v)
local t = type(v)
@ -147,7 +149,8 @@ function Manager:init()
singleThread('char', function(ch)
control = false
if self.currentPage then
self:inputEvent(self.currentPage.focused, { type = 'key', key = ch })
local target = self.currentPage.focused or self.currentPage
self:inputEvent(target, { type = 'key', key = ch })
self.currentPage:sync()
end
end)
@ -180,8 +183,9 @@ function Manager:init()
-- as char events
if ch and #ch > 1 and (code < 2 or code > 11) then
if self.currentPage then
self:inputEvent(self.currentPage.focused,
{ type = 'key', key = ch, element = self.currentPage.focused })
local target = self.currentPage.focused or self.currentPage
self:inputEvent(target,
{ type = 'key', key = ch, element = target })
self.currentPage:sync()
end
end
@ -264,10 +268,12 @@ end
function Manager:inputEvent(parent, event)
while parent do
local acc = parent.accelerators[event.key]
if acc then
if parent:emit({ type = acc, element = parent }) then
return true
if parent.accelerators then
local acc = parent.accelerators[event.key]
if acc then
if parent:emit({ type = acc, element = parent }) then
return true
end
end
end
if parent.eventHandler then
@ -515,7 +521,7 @@ UI.Window.defaults = {
offy = 0,
cursorX = 1,
cursorY = 1,
accelerators = { },
-- accelerators = { },
}
function UI.Window:init(args)
local defaults = UI:getDefaults(UI.Window, args)
@ -727,8 +733,10 @@ function UI.Window:centeredWrite(y, text, bg, fg)
end
end
function UI.Window:print(text, bg, fg, indent)
indent = indent or 1
function UI.Window:print(text, bg, fg)
local marginLeft = self.marginLeft or 0
local marginRight = self.marginRight or 0
local width = self.width - marginLeft - marginRight
local function nextWord(line, cx)
local result = { line:find("(%w+)", cx) }
@ -749,7 +757,7 @@ function UI.Window:print(text, bg, fg, indent)
local pos = 1
local t = { }
while true do
local s = f:find('\027', pos)
local s = string.find(f, '\027', pos, true)
if not s then
break
end
@ -773,7 +781,7 @@ function UI.Window:print(text, bg, fg, indent)
table.insert(t, e)
pos = s + #seq + 3
end
if pos < #f then
if pos <= #f then
table.insert(t, _sub(f, pos))
end
return t
@ -794,8 +802,8 @@ function UI.Window:print(text, bg, fg, indent)
break
end
local w = word
if self.cursorX + #word > self.width then
self.cursorX = indent
if self.cursorX + #word > width then
self.cursorX = marginLeft + 1
self.cursorY = self.cursorY + 1
w = word:gsub(' ', '')
end
@ -806,7 +814,7 @@ function UI.Window:print(text, bg, fg, indent)
end
end
if lines[k + 1] then
self.cursorX = indent
self.cursorX = marginLeft + 1
self.cursorY = self.cursorY + 1
end
end
@ -1246,6 +1254,7 @@ UI.Grid.defaults = {
index = 1,
inverseSort = false,
disableHeader = false,
marginRight = 0,
textColor = colors.white,
textSelectedColor = colors.white,
backgroundColor = colors.black,
@ -1311,7 +1320,7 @@ end
function UI.Grid:adjustWidth()
local t = { } -- cols without width
local w = self.width - #self.columns - 1 -- width remaing
local w = self.width - #self.columns - 1 - self.marginRight -- width remaing
for _,c in pairs(self.columns) do
if c.width then
@ -1668,109 +1677,59 @@ end
UI.ScrollingGrid = class(UI.Grid)
UI.ScrollingGrid.defaults = {
UIElement = 'ScrollingGrid',
scrollOffset = 1,
lineChar = '|',
sliderChar = '#',
upArrowChar = '^',
downArrowChar = 'v',
scrollbarColor = colors.lightGray,
scrollOffset = 0,
marginRight = 1,
}
function UI.ScrollingGrid:init(args)
UI.Grid.init(self, UI:getDefaults(UI.ScrollingGrid, args))
self.scrollBar = UI.ScrollBar()
end
function UI.ScrollingGrid:drawRows()
UI.Grid.drawRows(self)
self:drawScrollbar()
self.scrollBar:draw()
end
function UI.ScrollingGrid:drawScrollbar()
local ts = Util.size(self.values)
if ts > self.pageSize then
local maxScroll = ts - self.pageSize
local percent = (self.scrollOffset - 1) / maxScroll
local sliderSize = self.pageSize / ts * (self.pageSize - 2)
local row = 2
if self.disableHeader then
row = 1
end
local x = self.width
for i = 1, self.pageSize - 2 do
self:write(x, row + i, self.lineChar, nil, self.scrollbarColor)
end
local y = Util.round((self.pageSize - 2 - sliderSize) * percent)
for i = 1, Util.round(sliderSize) do
self:write(x, row + y + i, self.sliderChar, nil, self.scrollbarColor)
end
local color = self.scrollbarColor
if self.scrollOffset > 1 then
color = colors.white
end
self:write(x, 2, self.upArrowChar, nil, color)
color = self.scrollbarColor
if self.scrollOffset + self.pageSize - 1 < Util.size(self.values) then
color = colors.white
end
self:write(x, self.pageSize + 1, self.downArrowChar, nil, color)
function UI.ScrollingGrid:getViewArea()
local y = 1
if not self.disableHeader then
y = 2
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 = 1
self.scrollOffset = 0
end
return self.scrollOffset
return self.scrollOffset + 1
end
function UI.ScrollingGrid:setIndex(index)
if index < self.scrollOffset then
self.scrollOffset = index
elseif index - (self.scrollOffset - 1) > self.pageSize then
self.scrollOffset = index - self.pageSize + 1
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 < 1 then
self.scrollOffset = 1
if self.scrollOffset < 0 then
self.scrollOffset = 0
else
local ts = Util.size(self.values)
if self.pageSize + self.scrollOffset > ts then
self.scrollOffset = math.max(1, ts - self.pageSize + 1)
if self.pageSize + self.scrollOffset + 1 > ts then
self.scrollOffset = math.max(0, ts - self.pageSize)
end
end
UI.Grid.setIndex(self, index)
end
function UI.ScrollingGrid:eventHandler(event)
if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then
if event.x == self.width then
local ts = Util.size(self.values)
if ts > self.pageSize then
local row = 2
if self.disableHeader then
row = 1
end
if event.y == row then
self:setIndex(self.scrollOffset - 1)
elseif event.y == self.height then
self:setIndex(self.scrollOffset + self.pageSize)
-- else
-- ... percentage ...
end
return true
end
end
end
return UI.Grid.eventHandler(self, event)
end
--[[-- Menu --]]--
UI.Menu = class(UI.Grid)
UI.Menu.defaults = {
@ -1832,10 +1791,10 @@ function UI.Menu:eventHandler(event)
return UI.Grid.eventHandler(self, event)
end
--[[-- ViewportWindow --]]--
UI.ViewportWindow = class(UI.Window)
UI.ViewportWindow.defaults = {
UIElement = 'ViewportWindow',
--[[-- Viewport --]]--
UI.Viewport = class(UI.Window)
UI.Viewport.defaults = {
UIElement = 'Viewport',
backgroundColor = colors.cyan,
accelerators = {
down = 'scroll_down',
@ -1848,18 +1807,20 @@ UI.ViewportWindow.defaults = {
[ 'control-f' ] = 'scroll_pageDown',
},
}
function UI.ViewportWindow:init(args)
local defaults = UI:getDefaults(UI.ViewportWindow, args)
function UI.Viewport:init(args)
local defaults = UI:getDefaults(UI.Viewport, args)
UI.Window.init(self, defaults)
end
function UI.ViewportWindow:setScrollPosition(offset)
function UI.Viewport:setScrollPosition(offset)
local oldOffset = self.offy
self.offy = math.max(offset, 0)
local max = self.ymax or self.height
if self.children then
for _, child in ipairs(self.children) do
max = math.max(child.y + child.height - 1, max)
if child ~= self.scrollBar then -- hack !
max = math.max(child.y + child.height - 1, max)
end
end
end
self.offy = math.min(self.offy, math.max(max, self.height) - self.height)
@ -1868,11 +1829,20 @@ function UI.ViewportWindow:setScrollPosition(offset)
end
end
function UI.ViewportWindow:reset()
function UI.Viewport:reset()
self.offy = 0
end
function UI.ViewportWindow:eventHandler(event)
function UI.Viewport:getViewArea()
return {
y = (self.offy or 0) + 1,
height = self.height,
totalHeight = self.ymax,
offsetY = self.offy or 0,
}
end
function UI.Viewport:eventHandler(event)
if event.type == 'scroll_down' then
self:setScrollPosition(self.offy + 1)
@ -2042,8 +2012,7 @@ UI.MenuItem.defaults = {
}
function UI.MenuItem:init(args)
local defaults = UI:getDefaults(UI.MenuItem, args)
UI.Button.init(self, defaults)
UI.Button.init(self, UI:getDefaults(UI.MenuItem, args))
end
--[[-- MenuBar --]]--
@ -2144,8 +2113,7 @@ UI.DropMenuItem.defaults = {
}
function UI.DropMenuItem:init(args)
local defaults = UI:getDefaults(UI.DropMenuItem, args)
UI.Button.init(self, defaults)
UI.Button.init(self, UI:getDefaults(UI.DropMenuItem, args))
end
--[[-- DropMenu --]]--
@ -2466,9 +2434,9 @@ UI.Throttle.defaults = {
ctr = 0,
image = {
' //) (O )~@ &~&-( ?Q ',
' //) (O )- @ \-( ?) && ',
' //) (O ), @ \-(?) && ',
' //) (O ). @ \-d ) (@ '
' //) (O )- @ \\-( ?) && ',
' //) (O ), @ \\-(?) && ',
' //) (O ). @ \\-d ) (@ '
}
}
@ -2949,22 +2917,100 @@ function UI.Text:setParent()
end
function UI.Text:draw()
local value = self.value or ''
self:write(1, 1, Util.widthify(value, self.width), self.backgroundColor)
self:write(1, 1, Util.widthify(self.value or '', self.width), self.backgroundColor)
end
--[[-- ScrollBar --]]--
UI.ScrollBar = class(UI.Window)
UI.ScrollBar.defaults = {
UIElement = 'ScrollBar',
lineChar = '|',
sliderChar = '#',
upArrowChar = '^',
downArrowChar = 'v',
scrollbarColor = colors.lightGray,
value = '',
width = 1,
x = -1,
ey = -1,
}
function UI.ScrollBar:init(args)
UI.Window.init(self, UI:getDefaults(UI.ScrollBar, args))
end
function UI.ScrollBar:draw()
local parent = self.parent
local view = 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.y = row -- if so, move the scrollbar onscreen
row = 1
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 == self.height then
self:emit({ type = 'scroll_down'})
-- else
-- ... percentage ...
end
end
return true
end
end
end
--[[-- TextArea --]]--
UI.TextArea = class(UI.Window)
UI.TextArea = class(UI.Viewport)
UI.TextArea.defaults = {
UIElement = 'TextArea',
marginRight = 2,
value = '',
}
function UI.TextArea:init(args)
local defaults = UI:getDefaults(UI.TextArea, args)
UI.Window.init(self, defaults)
UI.Viewport.init(self, UI:getDefaults(UI.TextArea, args))
self.scrollBar = UI.ScrollBar()
end
function UI.TextArea:setText(text)
self.offy = 0
self.ymax = nil
self.value = text
self:draw()
end
@ -2973,6 +3019,13 @@ function UI.TextArea:draw()
self:clear()
self:setCursorPos(1, 1)
self:print(self.value)
self.ymax = self.cursorY + 1
for _,child in pairs(self.children) do
if child.enabled then
child:draw()
end
end
end
--[[-- Form --]]--

View File

@ -5,6 +5,7 @@ local Util = require('util')
local _rep = string.rep
local _sub = string.sub
local _gsub = string.gsub
local colors = _G.colors
local Canvas = class()
@ -171,7 +172,7 @@ function Canvas:writeBlit(x, y, text, bg, fg)
end
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
end
local line = self.lines[y]
line.dirty = true
line.text = replace(line.text, x, text, width)
@ -217,7 +218,7 @@ function Canvas:blitClipped(device)
self:blit(device,
{ x = region[1] - self.x + 1,
y = region[2] - self.y + 1,
ex = region[3]- self.x + 1,
ex = region[3]- self.x + 1,
ey = region[4] - self.y + 1 },
{ x = region[1], y = region[2] })
end
@ -251,7 +252,7 @@ function Canvas:dirty()
end
function Canvas:clean()
for y, line in pairs(self.lines) do
for _, line in pairs(self.lines) do
line.dirty = false
end
end
@ -314,13 +315,13 @@ function Canvas:applyPalette(palette)
self.palette = palette
end
function Canvas.convertWindow(win, parent, x, y)
function Canvas.convertWindow(win, parent, wx, wy)
local w, h = win.getSize()
win.canvas = Canvas({
x = x,
y = y,
x = wx,
y = wy,
width = w,
height = h,
isColor = win.isColor(),
@ -331,7 +332,7 @@ function Canvas.convertWindow(win, parent, x, y)
end
function win.clearLine()
local x, y = win.getCursorPos()
local _, y = win.getCursorPos()
win.canvas:write(1,
y,
_rep(' ', win.canvas.width),

View File

@ -426,7 +426,7 @@ function Util.split(str, pattern)
pattern = pattern or "(.-)\n"
local t = {}
local function helper(line) table.insert(t, line) return "" end
helper((str:gsub(pattern, helper)))
helper((str:gsub(pattern, helper)))
return t
end

View File

@ -5,6 +5,8 @@ local Event = require('event')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
multishell.setTitle(multishell.getCurrent(), 'Files')
UI:configure('Files', ...)
@ -20,7 +22,7 @@ local marked = { }
local directories = { }
local cutMode = false
function formatSize(size)
local function formatSize(size)
if size >= 1000000 then
return string.format('%dM', math.floor(size/1000000, 2))
elseif size >= 1000 then
@ -58,16 +60,16 @@ local Browser = UI.Page {
},
},
grid = UI.ScrollingGrid {
columns = {
columns = {
{ heading = 'Name', key = 'name' },
{ key = 'flags', width = 2 },
{ heading = 'Size', key = 'fsize', width = 6 },
{ heading = 'Size', key = 'fsize', width = 5 },
},
sortColumn = 'name',
y = 2, ey = -2,
},
statusBar = UI.StatusBar {
columns = {
columns = {
{ key = 'status' },
{ key = 'totalSize', width = 6 },
},
@ -84,6 +86,7 @@ local Browser = UI.Page {
d = 'delete',
delete = 'delete',
[ 'control-h' ] = 'toggle_hidden',
[ 'control-s' ] = 'toggle_dirSize',
[ 'control-x' ] = 'cut',
[ 'control-c' ] = 'copy',
paste = 'paste',
@ -117,7 +120,7 @@ function Browser.grid:sortCompare(a, b)
return a.isDir
end
function Browser.grid:getRowTextColor(file, selected)
function Browser.grid:getRowTextColor(file)
if file.marked then
return colors.green
end
@ -130,13 +133,6 @@ function Browser.grid:getRowTextColor(file, selected)
return colors.white
end
function Browser.grid:getRowBackgroundColorX(file, selected)
if selected then
return colors.gray
end
return self.backgroundColor
end
function Browser.grid:eventHandler(event)
if event.type == 'copy' then -- let copy be handled by parent
return false
@ -169,7 +165,6 @@ function Browser:unmarkAll()
end
function Browser:getDirectory(directory)
local s, dir = pcall(function()
local dir = directories[directory]
@ -350,7 +345,7 @@ function Browser:eventHandler(event)
self.statusBar:sync()
local _, ch = os.pullEvent('char')
if ch == 'y' or ch == 'Y' then
for k,m in pairs(marked) do
for _,m in pairs(marked) do
pcall(function()
fs.delete(m.fullName)
end)
@ -377,7 +372,7 @@ function Browser:eventHandler(event)
end
elseif event.type == 'paste' then
for k,m in pairs(copied) do
for _,m in pairs(copied) do
local s, m = pcall(function()
if cutMode then
fs.move(m.fullName, fs.combine(self.dir.name, m.name))

View File

@ -1,14 +1,19 @@
requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local help = _G.help
multishell.setTitle(multishell.getCurrent(), 'Help')
UI:configure('Help', ...)
local files = { }
for _,f in pairs(help.topics()) do
table.insert(files, { name = f })
local topics = { }
for _,topic in pairs(help.topics()) do
if help.lookup(topic) then
table.insert(topics, { name = topic })
end
end
local page = UI.Page {
@ -22,9 +27,9 @@ local page = UI.Page {
},
grid = UI.ScrollingGrid {
y = 4,
values = files,
values = topics,
columns = {
{ heading = 'Name', key = 'name' },
{ heading = 'Topic', key = 'name' },
},
sortColumn = 'name',
},
@ -34,36 +39,56 @@ local page = UI.Page {
},
}
local function showHelp(name)
UI.term:reset()
shell.run('help ' .. name)
print('Press enter to return')
repeat
os.pullEvent('key')
local _, k = os.pullEvent('key_up')
until k == keys.enter
local topicPage = UI.Page {
backgroundColor = colors.black,
titleBar = UI.TitleBar {
title = 'text',
previousPage = true,
},
helpText = UI.TextArea {
backgroundColor = colors.black,
x = 2, ex = -1, y = 3, ey = -2,
},
accelerators = {
q = 'back',
backspace = 'back',
},
}
function topicPage.helpText:focus()
-- let the help text get focused so we consume key strokes
end
function topicPage:eventHandler(event)
if event.type == 'back' then
UI:setPreviousPage()
end
return UI.Page.eventHandler(self, event)
end
function page:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
UI:exitPullEvents()
elseif event.type == 'grid_select' then
if self.grid:getSelected() then
showHelp(self.grid:getSelected().name)
self:setFocus(self.filter)
self:draw()
local name = self.grid:getSelected().name
local f = help.lookup(name)
topicPage.titleBar.title = name
topicPage.helpText:setText(Util.readFile(f))
UI:setPage(topicPage)
end
elseif event.type == 'text_change' then
local text = event.text
if #text == 0 then
self.grid.values = files
if #event.text == 0 then
self.grid.values = topics
else
self.grid.values = { }
for _,f in pairs(files) do
if string.find(f.name, text) then
for _,f in pairs(topics) do
if string.find(f.name, event.text) then
table.insert(self.grid.values, f)
end
end
@ -72,7 +97,7 @@ function page:eventHandler(event)
self.grid:setIndex(1)
self.grid:draw()
else
UI.Page.eventHandler(self, event)
return UI.Page.eventHandler(self, event)
end
end

View File

@ -148,7 +148,7 @@ local page = UI.Page {
tabBar = UI.VerticalTabBar {
buttons = buttons,
},
container = UI.ViewportWindow {
container = UI.Viewport {
x = cx,
y = cy,
},
@ -324,7 +324,7 @@ function page.container:setCategory(categoryName)
end
function page.container:draw()
UI.ViewportWindow.draw(self)
UI.Viewport.draw(self)
end
function page:refresh()

View File

@ -1,5 +1,5 @@
{
ScrollingGrid = {
ScrollBar = {
lineChar = '|',
sliderChar = '\127',
upArrowChar = '\30',