mirror of
https://codeberg.org/1414codeforge/yui.git
synced 2025-06-05 22:19:11 +02:00
[*] Initial commit.
This commit is contained in:
251
layout.lua
Normal file
251
layout.lua
Normal file
@ -0,0 +1,251 @@
|
||||
local BASE = (...):gsub('layout', '')
|
||||
|
||||
local Widget = require BASE..'widget'
|
||||
local core = require BASE..'core'
|
||||
|
||||
local utils = require 'lib.gear'
|
||||
|
||||
local isinstance = gear.meta.isinstance
|
||||
local rectunion = gear.rect.union
|
||||
local pointinrect = gear.rect.pointinside
|
||||
|
||||
local Layout = setmetatable({}, Widget)
|
||||
Layout.__index = Layout
|
||||
|
||||
|
||||
-- Calculate initial widget size.
|
||||
local function calcsize(sizes, widget)
|
||||
local w, h = widget.w, widget.h
|
||||
if w == nil then
|
||||
assert(#sizes > 0, "Default width is undefined!")
|
||||
w = sizes[#sizes].w
|
||||
end
|
||||
if h == nil then
|
||||
assert(#sizes > 0, "Default height is undefined!")
|
||||
h = sizes[#sizes].h
|
||||
end
|
||||
|
||||
if w == 'max' then
|
||||
w = 0
|
||||
for _,v in ipairs(sizes) do
|
||||
if v.w > w then
|
||||
w = v.w
|
||||
end
|
||||
end
|
||||
elseif w == 'median' then
|
||||
w = 0
|
||||
for _,v in ipairs(sizes) do
|
||||
w = w + v.w
|
||||
end
|
||||
w = math.ceil(w / #sizes)
|
||||
elseif w == 'min' then
|
||||
w = math.huge
|
||||
for _,v in ipairs(sizes) do
|
||||
if v.w < w then
|
||||
w = v.w
|
||||
end
|
||||
end
|
||||
else
|
||||
assert(type(w) == 'number')
|
||||
end
|
||||
|
||||
if h == 'max' then
|
||||
h = 0
|
||||
for _,v in ipairs(sizes) do
|
||||
if v.h > h then
|
||||
h = v.h
|
||||
end
|
||||
end
|
||||
elseif h == 'median' then
|
||||
h = 0
|
||||
for _,v in ipairs(sizes) do
|
||||
h = h + v.h
|
||||
end
|
||||
h = math.ceil(h / #sizes)
|
||||
elseif h == 'min' then
|
||||
h = math.huge
|
||||
for _,v in ipairs(sizes) do
|
||||
if v.h < h then
|
||||
h = v.h
|
||||
end
|
||||
end
|
||||
else
|
||||
assert(type(h) == 'number')
|
||||
end
|
||||
|
||||
sizes[#sizes+1] = { w = w, h = h }
|
||||
|
||||
widget.w, widget.h = w, h
|
||||
return w, h
|
||||
end
|
||||
|
||||
-- Lay down container widgets according to Layout type.
|
||||
function Layout:layoutWidgets()
|
||||
local nx,ny = self.x,self.y
|
||||
local sizes = {}
|
||||
local stack = self.stack
|
||||
local pad = self.padding
|
||||
|
||||
-- Container bounds, empty
|
||||
local rx,ry,rw,rh = nx,ny,-1,-1
|
||||
|
||||
-- Layout container children
|
||||
for _,widget in ipairs(self) do
|
||||
widget.x, widget.y = nx, ny
|
||||
widget.ui = self.ui
|
||||
widget.parent = self
|
||||
|
||||
if isinstance(widget, Layout) then
|
||||
widget:layoutWidgets()
|
||||
end
|
||||
|
||||
local w,h = calcsize(sizes, widget)
|
||||
rx,ry,rw,rh = rectunion(rx,ry,rw,rh, nx,ny,w,h)
|
||||
|
||||
nx,ny = self.advance(nx,ny, w,h, pad)
|
||||
|
||||
stack[#stack+1] = widget
|
||||
end
|
||||
|
||||
self.x = rx
|
||||
self.y = ry
|
||||
self.w = math.max(rw, 0)
|
||||
self.h = math.max(rh, 0)
|
||||
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:handlePointer(px,py, clicked, down)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Find layout's child containing the provided widget.
|
||||
local function childof(layout, widget)
|
||||
local parent = widget.parent
|
||||
while parent ~= layout do
|
||||
widget = parent
|
||||
parent = widget.parent
|
||||
end
|
||||
return widget
|
||||
end
|
||||
local function findfirst(widget)
|
||||
while isinstance(widget, Layout) do
|
||||
-- Find first element accepting focus
|
||||
for i = 1,#widget do
|
||||
if not widget[i].nofocus then
|
||||
widget = widget[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return widget
|
||||
end
|
||||
local function findnext(layout, widget)
|
||||
local child = childof(layout, widget)
|
||||
|
||||
for i,w in ipairs(layout) do
|
||||
if w == child then
|
||||
-- Search to the right, wraparound to the left
|
||||
for j = i+1,#layout do
|
||||
if not layout[j].nofocus then
|
||||
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
|
||||
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
|
||||
|
||||
function Layout.new(args)
|
||||
local self = setmetatable(args, Layout)
|
||||
|
||||
self.padding = self.padding or 0
|
||||
self.stack = {}
|
||||
|
||||
-- A Layout ignores focus if empty or containing only nofocus widgets
|
||||
self.nofocus = true
|
||||
for _,w in ipairs(self) do
|
||||
if not w.nofocus then
|
||||
self.nofocus = false
|
||||
break
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
return Layout
|
Reference in New Issue
Block a user