[*] General code improvement.
* Rework navigation allowing direct management and triggering for grabkeyboard widgets. * Navigation code now behaves better with deeply nested layouts. * Allow widget hierarchies deeper than 2 (__call() is implemented for every Widget metatable). * Make BASE locals more secure (match '<filename>$' in regexp)
This commit is contained in:
parent
3b2b460012
commit
1d42498ee7
|
@ -1,4 +1,4 @@
|
||||||
local BASE = (...):gsub('button', '')
|
local BASE = (...):gsub('button$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local core = require(BASE..'core')
|
local core = require(BASE..'core')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
local BASE = (...):gsub('checkbox', '')
|
local BASE = (...):gsub('checkbox$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local core = require(BASE..'core')
|
local core = require(BASE..'core')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
local BASE = (...):gsub('choice', '')
|
local BASE = (...):gsub('choice$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local core = require(BASE..'core')
|
local core = require(BASE..'core')
|
||||||
|
@ -6,7 +6,9 @@ local core = require(BASE..'core')
|
||||||
local shadowtext = require 'lib.gear.shadowtext'
|
local shadowtext = require 'lib.gear.shadowtext'
|
||||||
local T = require('lib.moonspeak').translate
|
local T = require('lib.moonspeak').translate
|
||||||
|
|
||||||
local Choice = setmetatable({}, Widget)
|
local Choice = setmetatable({
|
||||||
|
__call = function(cls, args) return cls.new(args) end
|
||||||
|
}, Widget)
|
||||||
Choice.__index = Choice
|
Choice.__index = Choice
|
||||||
|
|
||||||
|
|
||||||
|
|
17
columns.lua
17
columns.lua
|
@ -1,21 +1,20 @@
|
||||||
local BASE = (...):gsub('columns', '')
|
local BASE = (...):gsub('columns$', '')
|
||||||
|
|
||||||
local Layout = require(BASE..'layout')
|
local Layout = require(BASE..'layout')
|
||||||
|
|
||||||
|
|
||||||
-- Advance position to next column,
|
-- Advance position to next column,
|
||||||
-- given current position, widget dimensions and padding.
|
-- given current position, widget dimensions and padding.
|
||||||
local function columnadvance(x,y, ww,wh, padding)
|
local function columnadvance(x,y, ww,wh, padding)
|
||||||
return x + ww + padding, y
|
return x + ww + padding, y
|
||||||
end
|
end
|
||||||
|
|
||||||
function Columns(args)
|
local Columns = setmetatable({
|
||||||
local self = Layout.new(args)
|
advance = columnadvance,
|
||||||
|
__call = function(cls, args) return cls.new(args) end
|
||||||
|
}, Layout)
|
||||||
|
Columns.__index = Columns
|
||||||
|
|
||||||
self.advance = columnadvance
|
|
||||||
self.prev = 'left'
|
function Columns.new(args) return setmetatable(Layout.new(args), Columns) end
|
||||||
self.next = 'right'
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
return Columns
|
return Columns
|
||||||
|
|
2
core.lua
2
core.lua
|
@ -1,4 +1,4 @@
|
||||||
local BASE = (...):gsub('core', '')
|
local BASE = (...):gsub('core$', '')
|
||||||
|
|
||||||
local core = { theme = require(BASE..'theme') }
|
local core = { theme = require(BASE..'theme') }
|
||||||
core.__index = core
|
core.__index = core
|
||||||
|
|
30
input.lua
30
input.lua
|
@ -1,12 +1,15 @@
|
||||||
local BASE = (...):gsub('input', '')
|
local BASE = (...):gsub('input$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local core = require(BASE..'core')
|
local core = require(BASE..'core')
|
||||||
|
|
||||||
local utf8 = require 'utf8'
|
local utf8 = require 'utf8'
|
||||||
|
|
||||||
-- Grabs keyboard on focus
|
-- NOTE: Input manages keyboard directly.
|
||||||
local Input = setmetatable({ grabkeyboard = true }, Widget)
|
local Input = setmetatable({
|
||||||
|
grabkeyboard = true,
|
||||||
|
__call = function(cls, args) return cls.new(args) end
|
||||||
|
}, Widget)
|
||||||
Input.__index = Input
|
Input.__index = Input
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +31,18 @@ function Input.new(args)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- NOTE: Input steals keyboard input on focus.
|
||||||
|
function Input:gainFocus()
|
||||||
|
love.keyboard.setTextInput(true, self.x,self.y,self.w,self.h)
|
||||||
|
love.keyboard.setKeyRepeat(true)
|
||||||
|
end
|
||||||
|
function Input:loseFocus()
|
||||||
|
if love.system.getOS() == 'Android' or love.system.getOS() == 'iOS' then
|
||||||
|
love.keyboard.setTextInput(false)
|
||||||
|
end
|
||||||
|
love.keyboard.setKeyRepeat(false)
|
||||||
|
end
|
||||||
|
|
||||||
function Input:onPointerInput(px,py, clicked)
|
function Input:onPointerInput(px,py, clicked)
|
||||||
if clicked then
|
if clicked then
|
||||||
self:grabFocus()
|
self:grabFocus()
|
||||||
|
@ -92,9 +107,12 @@ function Input:keyreleased(key)
|
||||||
self.cursor = 1
|
self.cursor = 1
|
||||||
elseif key == 'end' then
|
elseif key == 'end' then
|
||||||
self.cursor = utf8.len(self.text)+1
|
self.cursor = utf8.len(self.text)+1
|
||||||
elseif key == 'return' then
|
elseif key == 'up' or key == 'down' then
|
||||||
self:onChange(self.text)
|
self.ui:navigate(key)
|
||||||
self.cursor = 1
|
elseif key == 'tab' or key == 'return' then
|
||||||
|
self.ui:navigate('right')
|
||||||
|
elseif key == 'escape' then
|
||||||
|
self.ui:navigate('cancel')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
local BASE = (...):gsub('label', '')
|
local BASE = (...):gsub('label$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local core = require(BASE..'core')
|
local core = require(BASE..'core')
|
||||||
|
@ -7,7 +7,10 @@ local shadowtext = require 'lib.gear.shadowtext'
|
||||||
local T = require('lib.moonspeak').translate
|
local T = require('lib.moonspeak').translate
|
||||||
|
|
||||||
-- Labels don't accept focus
|
-- Labels don't accept focus
|
||||||
local Label = setmetatable({ nofocus = true }, Widget)
|
local Label = setmetatable({
|
||||||
|
nofocus = true,
|
||||||
|
__call = function(cls, args) return cls.new(args) end
|
||||||
|
}, Widget)
|
||||||
Label.__index = Label
|
Label.__index = Label
|
||||||
|
|
||||||
|
|
||||||
|
|
185
layout.lua
185
layout.lua
|
@ -1,4 +1,4 @@
|
||||||
local BASE = (...):gsub('layout', '')
|
local BASE = (...):gsub('layout$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local core = require(BASE..'core')
|
local core = require(BASE..'core')
|
||||||
|
@ -9,7 +9,9 @@ local isinstance = gear.meta.isinstance
|
||||||
local rectunion = gear.rect.union
|
local rectunion = gear.rect.union
|
||||||
local pointinrect = gear.rect.pointinside
|
local pointinrect = gear.rect.pointinside
|
||||||
|
|
||||||
local Layout = setmetatable({}, Widget)
|
local Layout = setmetatable({
|
||||||
|
__call = function(cls, args) return cls.new(args) end
|
||||||
|
}, Widget)
|
||||||
Layout.__index = Layout
|
Layout.__index = Layout
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,21 +115,6 @@ function Layout:layoutWidgets()
|
||||||
self.h = math.max(rh, 0)
|
self.h = math.max(rh, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Layout:onPointerInput(px,py, clicked, down)
|
|
||||||
local stack = self.stack
|
|
||||||
|
|
||||||
-- Propagate pointer event from topmost widget to bottom
|
|
||||||
for i = #stack,1,-1 do
|
|
||||||
local widget = stack[i]
|
|
||||||
local x,y,w,h = widget.x,widget.y,widget.w,widget.h
|
|
||||||
|
|
||||||
if pointinrect(px,py, x,y,w,h) then
|
|
||||||
widget:onPointerInput(px,py, clicked, down)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Find layout's child containing the provided widget.
|
-- Find layout's child containing the provided widget.
|
||||||
local function childof(layout, widget)
|
local function childof(layout, widget)
|
||||||
local parent = widget.parent
|
local parent = widget.parent
|
||||||
|
@ -137,98 +124,27 @@ local function childof(layout, widget)
|
||||||
end
|
end
|
||||||
return widget
|
return widget
|
||||||
end
|
end
|
||||||
local function findfirst(widget)
|
local function scanforward(layout, from)
|
||||||
while isinstance(widget, Layout) do
|
from = from or 1
|
||||||
-- Find first element accepting focus
|
|
||||||
for i = 1,#widget do
|
for i = from,#layout do
|
||||||
if not widget[i].nofocus then
|
local w = layout[i]
|
||||||
widget = widget[i]
|
|
||||||
break
|
if not w.nofocus then
|
||||||
end
|
return isinstance(w, Layout) and scanforward(w) or w
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return widget
|
|
||||||
end
|
end
|
||||||
local function findnext(layout, widget)
|
local function scanbackwards(layout, from)
|
||||||
local child = childof(layout, widget)
|
from = from or #layout
|
||||||
|
|
||||||
for i,w in ipairs(layout) do
|
for i = from,1,-1 do
|
||||||
if w == child then
|
local w = layout[i]
|
||||||
-- Search to the right, wraparound to the left
|
|
||||||
for j = i+1,#layout do
|
if not w.nofocus then
|
||||||
if not layout[j].nofocus then
|
return isinstance(w, Layout) and scanforward(w) or w
|
||||||
return findfirst(layout[j])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for j = 1,i-1 do
|
|
||||||
if not layout[j].nofocus then
|
|
||||||
return findfirst(layout[j])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return widget
|
|
||||||
end
|
|
||||||
local function findprev(layout, widget)
|
|
||||||
local child = childof(layout, widget)
|
|
||||||
|
|
||||||
for i,w in ipairs(layout) do
|
|
||||||
if w == child then
|
|
||||||
-- Search to the left, wraparound to the right
|
|
||||||
for j = i-1,1,-1 do
|
|
||||||
if not layout[j].nofocus then
|
|
||||||
return findfirst(layout[j])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for j = #layout,i+1,-1 do
|
|
||||||
if not layout[j].nofocus then
|
|
||||||
return findfirst(layout[j])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return widget
|
|
||||||
end
|
|
||||||
|
|
||||||
function Layout:firstFocusableWidget()
|
|
||||||
return findfirst(self)
|
|
||||||
end
|
|
||||||
function Layout:nextFocusableWidget()
|
|
||||||
return findnext(self, self.ui.focused)
|
|
||||||
end
|
|
||||||
function Layout:previousFocusableWidget()
|
|
||||||
return findprev(self, self.ui.focused)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Layout:onActionInput(action)
|
|
||||||
local handled = false
|
|
||||||
|
|
||||||
if action[self.next] then
|
|
||||||
local n = self:nextFocusableWidget()
|
|
||||||
|
|
||||||
n:grabFocus()
|
|
||||||
handled = true
|
|
||||||
end
|
|
||||||
if action[self.prev] then
|
|
||||||
local p = self:previousFocusableWidget()
|
|
||||||
|
|
||||||
p:grabFocus()
|
|
||||||
handled = true
|
|
||||||
end
|
|
||||||
return handled
|
|
||||||
end
|
|
||||||
|
|
||||||
function Layout:update(dt)
|
|
||||||
for _,widget in ipairs(self.stack) do
|
|
||||||
widget:update(dt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Layout:draw()
|
|
||||||
-- Draw all children according to their order (topmost last)
|
|
||||||
for _,widget in ipairs(self.stack) do
|
|
||||||
widget:draw()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Layout.new(args)
|
function Layout.new(args)
|
||||||
|
@ -245,7 +161,70 @@ function Layout.new(args)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Find first widget in layout accepting focus.
|
||||||
|
function Layout:first()
|
||||||
|
return scanforward(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Find last widget in layout accepting focus.
|
||||||
|
function Layout:last()
|
||||||
|
return scanbackwards(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Find next focusable widget after the provided one.
|
||||||
|
function Layout:after(widget)
|
||||||
|
widget = childof(self, widget)
|
||||||
|
|
||||||
|
for i = 1,#self do
|
||||||
|
if self[i] == widget then
|
||||||
|
-- Search to the right/down
|
||||||
|
return scanforward(self, i+1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Find previous focusable widget before the provided one.
|
||||||
|
function Layout:before(widget)
|
||||||
|
widget = childof(self, widget)
|
||||||
|
|
||||||
|
for i = 1,#self do
|
||||||
|
if self[i] == widget then
|
||||||
|
-- Search to the left/up
|
||||||
|
return scanbackwards(self, i-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Layout:onPointerInput(px,py, clicked, down)
|
||||||
|
local stack = self.stack
|
||||||
|
|
||||||
|
-- Propagate pointer event from topmost widget to bottom
|
||||||
|
for i = #stack,1,-1 do
|
||||||
|
local widget = stack[i]
|
||||||
|
local x,y,w,h = widget.x,widget.y,widget.w,widget.h
|
||||||
|
|
||||||
|
if pointinrect(px,py, x,y,w,h) then
|
||||||
|
widget:onPointerInput(px,py, clicked, down)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Layout:update(dt)
|
||||||
|
for _,widget in ipairs(self.stack) do
|
||||||
|
widget:update(dt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Layout:draw()
|
||||||
|
-- Draw all children according to their order (topmost last)
|
||||||
|
for _,widget in ipairs(self.stack) do
|
||||||
|
widget:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return Layout
|
return Layout
|
||||||
|
|
17
rows.lua
17
rows.lua
|
@ -1,21 +1,20 @@
|
||||||
local BASE = (...):gsub('rows', '')
|
local BASE = (...):gsub('rows$', '')
|
||||||
|
|
||||||
local Layout = require(BASE..'layout')
|
local Layout = require(BASE..'layout')
|
||||||
|
|
||||||
|
|
||||||
-- Advance position to next row,
|
-- Advance position to next row,
|
||||||
-- given current position, widget dimensions and padding.
|
-- given current position, widget dimensions and padding.
|
||||||
local function rowadvance(x,y, ww,wh, padding)
|
local function rowadvance(x,y, ww,wh, padding)
|
||||||
return x, y + wh + padding
|
return x, y + wh + padding
|
||||||
end
|
end
|
||||||
|
|
||||||
function Rows(args)
|
local Rows = setmetatable({
|
||||||
local self = Layout.new(args)
|
advance = rowadvance,
|
||||||
|
__call = function(cls, args) return cls.new(args) end
|
||||||
|
}, Layout)
|
||||||
|
Rows.__index = Rows
|
||||||
|
|
||||||
self.advance = rowadvance
|
|
||||||
self.prev = 'up'
|
function Rows.new(args) return setmetatable(Layout.new(args), Rows) end
|
||||||
self.next = 'down'
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
return Rows
|
return Rows
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
local BASE = (...):gsub('slider', '')
|
local BASE = (...):gsub('slider$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local core = require(BASE..'core')
|
local core = require(BASE..'core')
|
||||||
|
|
11
spacer.lua
11
spacer.lua
|
@ -1,14 +1,15 @@
|
||||||
local BASE = (...):gsub('spacer', '')
|
local BASE = (...):gsub('spacer$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
|
|
||||||
-- Spacers don't accept focus
|
-- Spacers don't accept focus
|
||||||
local Spacer = setmetatable({ nofocus = true }, Widget)
|
local Spacer = setmetatable({
|
||||||
|
nofocus = true,
|
||||||
|
__call = function(cls, args) return cls.new(args) end
|
||||||
|
}, Widget)
|
||||||
Spacer.__index = Spacer
|
Spacer.__index = Spacer
|
||||||
|
|
||||||
|
|
||||||
function Spacer.new(args)
|
function Spacer.new(args) return setmetatable(args, Spacer) end
|
||||||
return setmetatable(args, Spacer)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Spacer
|
return Spacer
|
||||||
|
|
156
ui.lua
156
ui.lua
|
@ -2,6 +2,8 @@ local BASE = (...):gsub('ui$', '')
|
||||||
|
|
||||||
local Widget = require(BASE..'widget')
|
local Widget = require(BASE..'widget')
|
||||||
local Layout = require(BASE..'layout')
|
local Layout = require(BASE..'layout')
|
||||||
|
local Columns = require(BASE..'columns')
|
||||||
|
local Rows = require(BASE..'rows')
|
||||||
|
|
||||||
local gear = require 'lib.gear'
|
local gear = require 'lib.gear'
|
||||||
|
|
||||||
|
@ -50,24 +52,6 @@ local function resolveautofocus(widget)
|
||||||
return firstfocus, cancelfocus
|
return firstfocus, cancelfocus
|
||||||
end
|
end
|
||||||
|
|
||||||
local function propagateaction(ui, action)
|
|
||||||
local focused = ui.focused
|
|
||||||
if focused.grabkeyboard then
|
|
||||||
-- A widget stealing input
|
|
||||||
-- explicitly consumes any action.
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
while focused ~= nil do
|
|
||||||
if focused:onActionInput(action) then
|
|
||||||
return true -- action consumed
|
|
||||||
end
|
|
||||||
|
|
||||||
focused = focused.parent
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function Ui.new(args)
|
function Ui.new(args)
|
||||||
local self = setmetatable(args, Ui)
|
local self = setmetatable(args, Ui)
|
||||||
assert(#self == 1, "Ui.new() must have exactly one root widget.")
|
assert(#self == 1, "Ui.new() must have exactly one root widget.")
|
||||||
|
@ -98,7 +82,7 @@ function Ui.new(args)
|
||||||
local firstfocus, cancelfocus = resolveautofocus(root)
|
local firstfocus, cancelfocus = resolveautofocus(root)
|
||||||
if firstfocus == nil then
|
if firstfocus == nil then
|
||||||
firstfocus = isinstance(root, Layout) and
|
firstfocus = isinstance(root, Layout) and
|
||||||
root:firstFocusableWidget() or
|
root:first() or
|
||||||
root
|
root
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -138,28 +122,136 @@ function Ui:textedited(text, start, length)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Ui:update(dt)
|
local function actionpropagate(widget, action)
|
||||||
local root = self[1]
|
while widget ~= nil do
|
||||||
local x,y,w,h = root.x,root.y,root.w,root.h
|
if widget:onActionInput(action) then
|
||||||
local snap = self.device:snapshot()
|
return true -- action consumed
|
||||||
|
end
|
||||||
|
|
||||||
self.timer:update(dt)
|
widget = widget.parent
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local navs = { 'cancel', 'up', 'down', 'left', 'right' }
|
||||||
|
|
||||||
|
local function globalactions(ui, snap)
|
||||||
|
for _,nav in ipairs(navs) do
|
||||||
|
if snap[nav] then
|
||||||
|
ui:navigate(nav)
|
||||||
|
break -- discard other directions, if any
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function eventpropagate(ui, snap)
|
||||||
|
-- 1. Pointer events
|
||||||
|
if snap.pointer and not ui.grabpointer then
|
||||||
|
local root = ui[1]
|
||||||
|
local x,y,w,h = root.x,root.y,root.w,root.h
|
||||||
|
|
||||||
-- Propagate pointer events in focus order
|
|
||||||
if self.pointerActive then
|
|
||||||
if snap.pointer and pointinrect(snap.px,snap.py, x,y,w,h) then
|
if snap.pointer and pointinrect(snap.px,snap.py, x,y,w,h) then
|
||||||
root:onPointerInput(snap.px,snap.py, snap.clicked, snap.pointing)
|
root:onPointerInput(snap.px,snap.py, snap.clicked, snap.pointing)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Propagate actions from focused widget up
|
-- 2. Actions (keyboard/buttons)
|
||||||
if snap.action and not propagateaction(self, snap) then
|
if snap.action and not ui.grabkeyboard then
|
||||||
-- Take global actions if nobody consumed the event
|
local consumed = actionpropagate(ui.focused, snap)
|
||||||
if snap.cancel and self.cancelfocus then
|
|
||||||
-- Focus on the last widget with 'cancelfocus'
|
if not consumed then
|
||||||
self.cancelfocus:grabFocus()
|
-- 3. If no widget consumed action,
|
||||||
|
-- take global actions (e.g. navigation).
|
||||||
|
globalactions(ui, snap)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- cls may be Rows, Columns or Layout in general
|
||||||
|
local function containerof(widget, cls)
|
||||||
|
repeat
|
||||||
|
widget = widget.parent
|
||||||
|
if isinstance(widget, cls) then
|
||||||
|
return widget
|
||||||
|
end
|
||||||
|
until widget == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function findprev(ui, cls, widget)
|
||||||
|
local child = widget
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local container = containerof(child, cls)
|
||||||
|
if container == nil then
|
||||||
|
-- All the way up, either return the original
|
||||||
|
-- widget or wraparound to the bottom
|
||||||
|
return isinstance(child, cls) and child:last() or widget
|
||||||
|
end
|
||||||
|
|
||||||
|
local w = container:before(child)
|
||||||
|
if w ~= nil then
|
||||||
|
return w
|
||||||
|
end
|
||||||
|
|
||||||
|
child = container -- move up into the hierarchy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Specular to findprev()
|
||||||
|
local function findnext(ui, cls, widget)
|
||||||
|
local child = widget
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local container = containerof(child, cls)
|
||||||
|
if container == nil then
|
||||||
|
return isinstance(child, cls) and child:first() or widget
|
||||||
|
end
|
||||||
|
|
||||||
|
local w = container:after(child)
|
||||||
|
if w ~= nil then
|
||||||
|
return w
|
||||||
|
end
|
||||||
|
|
||||||
|
child = container
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move focus to the given direction.
|
||||||
|
--
|
||||||
|
-- @param where (string) Direction to move to,
|
||||||
|
-- one of: 'up', 'down', 'left', 'right', 'cancel'.
|
||||||
|
function Ui:navigate(where)
|
||||||
|
local nextfocus = nil
|
||||||
|
|
||||||
|
if where == 'cancel' then
|
||||||
|
nextfocus = self.cancelfocus
|
||||||
|
elseif where == 'up' then
|
||||||
|
nextfocus = findprev(self, Rows, self.focused)
|
||||||
|
elseif where == 'down' then
|
||||||
|
nextfocus = findnext(self, Rows, self.focused)
|
||||||
|
elseif where == 'left' then
|
||||||
|
nextfocus = findprev(self, Columns, self.focused)
|
||||||
|
elseif where == 'right' then
|
||||||
|
nextfocus = findnext(self, Columns, self.focused)
|
||||||
|
else
|
||||||
|
error("Bad direction: "..tostring(where))
|
||||||
|
end
|
||||||
|
|
||||||
|
if nextfocus ~= nil then
|
||||||
|
nextfocus:grabFocus()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Ui:update(dt)
|
||||||
|
local root = self[1]
|
||||||
|
local snap = self.device:snapshot()
|
||||||
|
|
||||||
|
-- Update timer related effects
|
||||||
|
self.timer:update(dt)
|
||||||
|
|
||||||
|
-- Regular event propagation
|
||||||
|
eventpropagate(self, snap)
|
||||||
|
|
||||||
-- Perform regular lifetime updates
|
-- Perform regular lifetime updates
|
||||||
root:update(dt)
|
root:update(dt)
|
||||||
|
|
33
widget.lua
33
widget.lua
|
@ -1,9 +1,7 @@
|
||||||
local rectunion = require('lib.gear.rect').union
|
local rectunion = require('lib.gear.rect').union
|
||||||
|
|
||||||
local Widget = {
|
local Widget = {
|
||||||
__call = function(cls, args)
|
__call = function(cls, args) return cls.new(args) end
|
||||||
return cls.new(args)
|
|
||||||
end
|
|
||||||
}
|
}
|
||||||
Widget.__index = Widget
|
Widget.__index = Widget
|
||||||
|
|
||||||
|
@ -40,25 +38,26 @@ function Widget:grabFocus()
|
||||||
if focused ~= nil then
|
if focused ~= nil then
|
||||||
-- Notify leave
|
-- Notify leave
|
||||||
focused.hovered = false
|
focused.hovered = false
|
||||||
if focused.grabkeyboard then
|
-- Widget specific focus loss
|
||||||
if love.system.getOS() == 'Android' or love.system.getOS() == 'iOS' then
|
focused:loseFocus()
|
||||||
love.keyboard.setTextInput(false)
|
-- Event handler
|
||||||
end
|
|
||||||
love.keyboard.setKeyRepeat(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
focused:onLeave()
|
focused:onLeave()
|
||||||
|
|
||||||
|
if focused.grabkeyboard then
|
||||||
|
-- If focused widget stole input,
|
||||||
|
-- then drop current input snapshot, since
|
||||||
|
-- those events should have been already
|
||||||
|
-- managed directly
|
||||||
|
ui.device:snapshot()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local wasHovered = self.hovered
|
local wasHovered = self.hovered
|
||||||
|
|
||||||
self.hovered = true
|
self.hovered = true
|
||||||
if self.grabkeyboard then
|
|
||||||
love.keyboard.setTextInput(true, self.x,self.y,self.w,self.h)
|
|
||||||
love.keyboard.setKeyRepeat(true)
|
|
||||||
end
|
|
||||||
if not wasHovered then
|
if not wasHovered then
|
||||||
-- First time hovered, notify enter
|
-- First time hovered, notify enter
|
||||||
|
self:gainFocus()
|
||||||
self:onEnter()
|
self:onEnter()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -100,7 +99,11 @@ function Widget:colorForState()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Common NOP event handlers
|
-- NOP hooks for UI internal use
|
||||||
|
function Widget:loseFocus() end
|
||||||
|
function Widget:gainFocus() end
|
||||||
|
|
||||||
|
-- NOP event handlers, publicly overridable
|
||||||
function Widget:onHit() end
|
function Widget:onHit() end
|
||||||
function Widget:onEnter() end
|
function Widget:onEnter() end
|
||||||
function Widget:onLeave() end
|
function Widget:onLeave() end
|
||||||
|
|
Loading…
Reference in New Issue