scissor me baby

This commit is contained in:
airstruck
2015-12-08 17:14:49 -05:00
parent d11ea06503
commit 5e6cd8c29e
12 changed files with 138 additions and 71 deletions

View File

@@ -52,27 +52,27 @@ return { id = 'mainWindow', type = 'panel',
{ type = 'panel', id = 'mainCanvas' }, { type = 'panel', id = 'mainCanvas' },
{ type = 'sash' }, { type = 'sash' },
{ type = 'panel', id = 'rightSideBox', width = 200, minwidth = 64, scroll = true, { type = 'panel', id = 'rightSideBox', width = 200, minwidth = 64, scroll = true,
{ id = 'flowTest', { id = 'flowTest', height = 'auto', minheight = 128,
{ minheight = 64, {
{ type = 'label', text = 'Slider' }, { type = 'label', text = 'Slider' },
{ type = 'slider', id = 'slidey', value = 0 }, { type = 'slider', id = 'slidey', width = false },
}, },
{ minheight = 64, {
{ type = 'label', text = 'Stepper' }, { type = 'label', text = 'Stepper' },
{ type = 'stepper', { type = 'stepper', id = 'stepper', width = false,
{ value = 1, text = 'Thing One' }, { value = 1, text = 'Thing One' },
{ value = 2, text = 'Thing Two' }, { value = 2, text = 'Thing Two' },
{ value = 3, text = 'Thing Three' }, { value = 3, text = 'Thing Three' },
}, },
}, },
{ minheight = 64, {
{ type = 'label', text = 'Progress' }, { type = 'label', text = 'Progress' },
{ type = 'progress', id = 'progressBar', }, { type = 'progress', id = 'progressBar', width = false },
}, },
}, },
{ { height = 'auto',
{ type = 'label', text = 'Above layout' }, { type = 'label', text = 'Flow test' },
{ type = 'check', text = 'Flow horizontal', id = 'flowToggle', }, { type = 'check', text = 'Vertical controls', id = 'flowToggle', },
{ type = 'label', text = 'Some radio widgets' }, { type = 'label', text = 'Some radio widgets' },
{ type = 'radio', text = 'One fish' }, { type = 'radio', text = 'One fish' },
{ type = 'radio', text = 'Two fish' }, { type = 'radio', text = 'Two fish' },

View File

@@ -11,9 +11,10 @@ layout.slidey:onChange(function (event)
end) end)
layout.flowToggle:onChange(function (event) layout.flowToggle:onChange(function (event)
layout.flowTest.flow = event.value and 'x' or 'y'
layout.slidey.flow = event.value and 'y' or 'x' layout.slidey.flow = event.value and 'y' or 'x'
layout.progressBar.flow = event.value and 'y' or 'x' layout.progressBar.flow = event.value and 'y' or 'x'
layout.stepper.flow = event.value and 'y' or 'x'
layout.flowTest.flow = event.value and 'x' or 'y'
end) end)
layout.newButton:onPress(function (event) layout.newButton:onPress(function (event)

View File

@@ -9,6 +9,7 @@ return {
type = 'button', type = 'button',
align = 'center middle', align = 'center middle',
width = 48, width = 48,
height = 48,
slices = function (self) slices = function (self)
if self.focused or self.hovered or self.pressed then if self.focused or self.hovered or self.pressed then
return nil -- fall back to theme default return nil -- fall back to theme default

View File

@@ -283,7 +283,9 @@ To get the calculated height, use `Widget:getHeight`.
Attribute.height = {} Attribute.height = {}
function Attribute.height.set (widget, value) function Attribute.height.set (widget, value)
value = value and math.max(value, widget.minheight or 0) if value ~= 'auto' then
value = value and math.max(value, widget.minheight or 0)
end
widget.attributes.height = value widget.attributes.height = value
widget.reshape(widget.parent or widget) widget.reshape(widget.parent or widget)
end end
@@ -415,6 +417,8 @@ and horizontal alignment is defined by either 'left', 'center', or 'right'.
For example, `align = 'top left'` For example, `align = 'top left'`
- This attribute cascades.
@attrib align @attrib align
--]]-- --]]--
Attribute.align = {} Attribute.align = {}
@@ -424,6 +428,10 @@ function Attribute.align.set (widget, value)
widget.textData = nil widget.textData = nil
end end
function Attribute.align.get (widget)
return widget.attributes.align or widget.parent and widget.parent.align
end
--[[-- --[[--
Wrap text onto multiple lines. Wrap text onto multiple lines.

View File

@@ -12,6 +12,8 @@ local Text = require((...) .. '.text')
local IntOut = ffi.typeof 'int[1]' local IntOut = ffi.typeof 'int[1]'
local stack = {}
-- create window and renderer -- create window and renderer
sdl.setHint(sdl.HINT_VIDEO_ALLOW_SCREENSAVER, '1') sdl.setHint(sdl.HINT_VIDEO_ALLOW_SCREENSAVER, '1')
@@ -257,6 +259,12 @@ Backend.setScissor = function (x, y, w, h)
sdl.renderSetClipRect(renderer, lastScissor) sdl.renderSetClipRect(renderer, lastScissor)
end end
Backend.getScissor = function ()
if lastScissor ~= nil then
return lastScissor.x, lastScissor.y, lastScissor.w, lastScissor.h
end
end
function Backend.hide (layout) function Backend.hide (layout)
for _, item in ipairs(layout.hooks) do for _, item in ipairs(layout.hooks) do
Hooker.unhook(item) Hooker.unhook(item)
@@ -269,15 +277,14 @@ local function hook (layout, key, method, hookLast)
callback, key, method, hookLast) callback, key, method, hookLast)
end end
local stack = {}
Backend.pop = function () Backend.pop = function ()
local history = stack[#stack] local history = stack[#stack]
local color = history.color or { 0, 0, 0, 255 } lastColor = history.color or { 0, 0, 0, 255 }
lastScissor = history.scissor
sdl.setRenderDrawColor(renderer, sdl.setRenderDrawColor(renderer,
color[1], color[2], color[3], color[4] or 255) lastColor[1], lastColor[2], lastColor[3], lastColor[4] or 255)
sdl.renderSetClipRect(renderer, history.scissor) -- Backend.setScissor(history.scissor) sdl.renderSetClipRect(renderer, lastScissor) -- Backend.setScissor(history.scissor)
stack[#stack] = nil stack[#stack] = nil
end end

View File

@@ -73,6 +73,8 @@ end
Backend.setScissor = love.graphics.setScissor Backend.setScissor = love.graphics.setScissor
Backend.getScissor = love.graphics.getScissor
function Backend.hide (layout) function Backend.hide (layout)
for _, item in ipairs(layout.hooks) do for _, item in ipairs(layout.hooks) do
Hooker.unhook(item) Hooker.unhook(item)

View File

@@ -11,6 +11,25 @@ local Renderer = Base:extend()
local imageCache = {} local imageCache = {}
local sliceCache = {} local sliceCache = {}
local function intersectScissor (x, y, w, h)
local sx, sy, sw, sh = Backend.getScissor()
if not sx then
return Backend.setScissor(x, y, w, h)
end
local x1 = math.max(sx, x)
local y1 = math.max(sy, y)
local x2 = math.min(sx + sw, x + w)
local y2 = math.min(sy + sh, y + h)
if x2 > x1 and y2 > y1 then
Backend.setScissor(x1, y1, x2 - x1, y2 - y1)
else
-- HACK
Backend.setScissor(-100, -100, 1, 1)
end
end
function Renderer:loadImage (path) function Renderer:loadImage (path)
if not imageCache[path] then if not imageCache[path] then
imageCache[path] = Backend.Image(path) imageCache[path] = Backend.Image(path)
@@ -194,8 +213,6 @@ function Renderer:renderIconAndText (widget)
return return
end end
local parentY = widget.parent and widget.parent:getY() or 0
-- calculate position for icon and text based on alignment and padding -- calculate position for icon and text based on alignment and padding
local iconX, iconY, x1, y1, x2, y2 = self:positionIcon( local iconX, iconY, x1, y1, x2, y2 = self:positionIcon(
widget, x, y, x + w, y + h) widget, x, y, x + w, y + h)
@@ -239,14 +256,12 @@ function Renderer:renderIconAndText (widget)
end end
Backend.push() Backend.push()
Backend.setScissor(x, math.max(y, parentY), w, h)
intersectScissor(x, y, w, h)
-- draw the icon -- draw the icon
if icon then if icon then
iconX, iconY = math.floor(iconX), math.floor(iconY) iconX, iconY = math.floor(iconX), math.floor(iconY)
if widget.tint then
Backend.setColor(widget.tint)
end
Backend.draw(icon, iconX, iconY) Backend.draw(icon, iconX, iconY)
end end
@@ -277,9 +292,9 @@ function Renderer:render (widget)
Backend.push() Backend.push()
if widget.parent then local parent = widget.parent
local parentY = widget.parent:getY() if parent then
Backend.setScissor(x, math.max(y, parentY), w, h) intersectScissor(x, y, w, h)
else else
Backend.setScissor() Backend.setScissor()
end end
@@ -288,11 +303,10 @@ function Renderer:render (widget)
self:renderOutline(widget) self:renderOutline(widget)
self:renderSlices(widget) self:renderSlices(widget)
self:renderIconAndText(widget) self:renderIconAndText(widget)
self:renderChildren(widget)
Backend.pop() Backend.pop()
return self:renderChildren(widget)
end) end)
Event.Display:emit(widget, { target = widget }) Event.Display:emit(widget, { target = widget })
end end

View File

@@ -55,11 +55,11 @@ return function (config)
end end
local function getSashHeight (self) local function getSashHeight (self)
return self.parent.flow ~= 'x' and 4 return self.parent and self.parent.flow ~= 'x' and 4
end end
local function getSashWidth (self) local function getSashWidth (self)
return self.parent.flow == 'x' and 4 return self.parent and self.parent.flow == 'x' and 4
end end
local function getTextSlices (self) local function getTextSlices (self)
@@ -75,10 +75,10 @@ return function (config)
color = textColor, color = textColor,
minheight = 28, minheight = 28,
minwidth = 28, minwidth = 28,
align = 'center middle',
}, },
button = { button = {
type = 'control', type = 'control',
align = 'center middle',
padding = 6, padding = 6,
slices = getButtonSlices, slices = getButtonSlices,
minwidth = 24, minwidth = 24,
@@ -92,11 +92,13 @@ return function (config)
color = textColor, color = textColor,
icon = getCheckOrRadioIcon, icon = getCheckOrRadioIcon,
padding = 4, padding = 4,
align = 'left middle',
}, },
label = { label = {
type = 'control', type = 'control',
background = backColor, background = backColor,
padding = 4, padding = 4,
align = 'left middle',
}, },
menu = { menu = {
height = 24, height = 24,
@@ -131,6 +133,7 @@ return function (config)
color = textColor, color = textColor,
icon = getCheckOrRadioIcon, icon = getCheckOrRadioIcon,
padding = 4, padding = 4,
align = 'left middle',
}, },
sash = { sash = {
background = getSashBackground, background = getSashBackground,
@@ -153,7 +156,6 @@ return function (config)
slices = resources .. 'button_pressed.png', slices = resources .. 'button_pressed.png',
}, },
['stepper.item'] = { ['stepper.item'] = {
type = 'control',
align = 'center middle', align = 'center middle',
color = textColor, color = textColor,
}, },

View File

@@ -291,15 +291,11 @@ function Widget:addChild (data)
return child return child
end end
local function clamp (value, min, max)
return value < min and min or value > max and max or value
end
local function checkReshape (widget) local function checkReshape (widget)
if widget.needsReshape then if widget.needsReshape then
widget.position = {} widget.position = {}
widget.dimensions = {} widget.dimensions = {}
widget.needsReshape = false widget.needsReshape = nil
end end
end end
@@ -313,19 +309,21 @@ function Widget:calculateDimension (name)
local min = (name == 'width') and (self.minwidth or 0) local min = (name == 'width') and (self.minwidth or 0)
or (self.minheight or 0) or (self.minheight or 0)
local windowWidth, windowHeight = Backend.getWindowSize()
local max = name == 'width' and windowWidth or windowHeight
if self[name] then if self[name] then
self.dimensions[name] = clamp(self[name], min, max) if self[name] == 'auto' then
self.dimensions[name] = self:calculateDimensionMinimum(name)
return self.dimensions[name]
end
self.dimensions[name] = math.max(self[name], min)
return self.dimensions[name] return self.dimensions[name]
end end
local parent = self.parent local parent = self.parent
if not parent then if not parent then
self.dimensions[name] = max local windowWidth, windowHeight = Backend.getWindowSize()
local size = name == 'width' and windowWidth or windowHeight
self.dimensions[name] = size
return self.dimensions[name] return self.dimensions[name]
end end
@@ -333,21 +331,23 @@ function Widget:calculateDimension (name)
parentDimension = parentDimension - (parent.margin or 0) * 2 parentDimension = parentDimension - (parent.margin or 0) * 2
parentDimension = parentDimension - (parent.padding or 0) * 2 parentDimension = parentDimension - (parent.padding or 0) * 2
local parentFlow = parent.flow or 'y' local parentFlow = parent.flow or 'y'
if (parentFlow == 'y' and name == 'width') or if (parentFlow ~= 'x' and name == 'width')
(parentFlow == 'x' and name == 'height') or (parentFlow == 'x' and name == 'height') then
then self.dimensions[name] = math.max(parentDimension, min)
self.dimensions[name] = clamp(parentDimension, min, max)
return self.dimensions[name] return self.dimensions[name]
end end
local claimed = 0 local claimed = 0
local unsized = 1 local unsized = 1
for i, widget in ipairs(self.parent) do for i, widget in ipairs(self.parent) do
if widget ~= self then if widget ~= self then
if widget[name] then local value = widget[name]
claimed = claimed + widget:calculateDimension(name) if value == 'auto' then
if claimed > parentDimension then if not widget.dimensions[name] then
claimed = parentDimension widget.dimensions[name] = widget:calculateDimensionMinimum(name)
end end
claimed = claimed + widget.dimensions[name]
elseif value then
claimed = claimed + value
else else
unsized = unsized + 1 unsized = unsized + 1
end end
@@ -355,13 +355,13 @@ function Widget:calculateDimension (name)
end end
local size = (parentDimension - claimed) / unsized local size = (parentDimension - claimed) / unsized
size = clamp(size, min, max) size = math.max(size, min)
self.dimensions[name] = size self.dimensions[name] = size
return size return size
end end
local function calculateRootPosition (self, axis) local function calculateRootPosition (self, axis)
local value = (axis == 'x' and self.left) or (axis == 'y' and self.top) local value = (axis == 'x' and self.left) or (axis ~= 'x' and self.top)
if value then if value then
self.position[axis] = value self.position[axis] = value
@@ -370,9 +370,9 @@ local function calculateRootPosition (self, axis)
local ww, wh = Backend.getWindowSize() local ww, wh = Backend.getWindowSize()
if axis == 'x' and self.width then if axis == 'x' and type(self.width) == 'number' then
value = (ww - self.width) / 2 value = (ww - self.width) / 2
elseif axis == 'y' and self.height then elseif axis ~= 'x' and type(self.height) == 'number' then
value = (wh - self.height) / 2 value = (wh - self.height) / 2
else else
value = 0 value = 0
@@ -394,7 +394,7 @@ function Widget:calculatePosition (axis)
return calculateRootPosition(self, axis) return calculateRootPosition(self, axis)
else else
scroll = axis == 'x' and (parent.scrollX or 0) scroll = axis == 'x' and (parent.scrollX or 0)
or axis == 'y' and (parent.scrollY or 0) or axis ~= 'x' and (parent.scrollY or 0)
end end
local parentPos = parent:calculatePosition(axis) local parentPos = parent:calculatePosition(axis)
local p = parentPos - scroll local p = parentPos - scroll
@@ -415,6 +415,24 @@ function Widget:calculatePosition (axis)
return 0 return 0
end end
function Widget:calculateDimensionMinimum (name)
local space = (self.margin or 0) * 2 + (self.padding or 0) * 2
local min = name == 'width' and 'minwidth' or 'minheight'
local value = space
for _, child in ipairs(self) do
if (name == 'width' and self.flow == 'x')
or (name == 'height' and self.flow ~= 'x') then
value = value + child:calculateDimensionMinimum(name)
else
value = math.max(value, child:calculateDimensionMinimum(name))
end
end
local dim = self[name]
dim = type(dim) == 'number' and dim
return math.max(value, dim or 0, self[min] or 0)
end
--[[-- --[[--
Get the widget's X coordinate. Get the widget's X coordinate.

View File

@@ -8,7 +8,7 @@ between 0 and 1 (inclusive) to change the width of the bar.
--]]-- --]]--
return function (self) return function (self)
self.value = 0 self.value = self.value or 0
local pad = self:addChild { local pad = self:addChild {
width = 0, width = 0,
@@ -35,7 +35,7 @@ return function (self)
y1 = y1 + min y1 = y1 + min
bar.width = false bar.width = false
bar.height = false bar.height = false
pad.height = h - (self.value * (y2 - y1) + min) pad.height = math.ceil(h - (self.value * (y2 - y1) + min))
end end
end) end)
end end

View File

@@ -44,8 +44,6 @@ local function setDimension (widget, name, size)
or (widget.minheight or 0) or (widget.minheight or 0)
widget[name] = math.max(size, min) widget[name] = math.max(size, min)
return widget[name]
end end
@@ -79,10 +77,9 @@ return function (self)
end end
if nextSize then if nextSize then
setDimension(nextSibling, dimension, setDimension(nextSibling, dimension,
nextSibling:calculatePosition(axis) + nextSibling:calculatePosition(axis) + nextSize - event[axis])
nextSibling:calculateDimension(dimension) - event[axis])
end end
self.parent:reshape()
end) end)
end end

View File

@@ -21,13 +21,22 @@ return function (self)
self[index] = nil self[index] = nil
end end
local decrement = self:addChild { type = 'stepper.left' } local before = self:addChild { type = 'stepper.left' }
local view = self:addChild() local view = self:addChild()
local increment = self:addChild { type = 'stepper.right' } local after = self:addChild { type = 'stepper.right' }
self:onReshape(function (event) self:onReshape(function (event)
decrement.width = decrement:getHeight() if self.flow == 'x' then
increment.width = increment:getHeight() before.height = false
after.height = false
before.width = 0
after.width = 0
else
before.width = false
after.width = false
before.height = 0
after.height = 0
end
end) end)
local function updateValue () local function updateValue ()
@@ -35,25 +44,33 @@ return function (self)
self.value = item.value self.value = item.value
view[1] = nil view[1] = nil
view:addChild(item) view:addChild(item)
item:reshape() view:reshape()
end end
decrement:onPress(function (event) local function decrement ()
if not self.items then return end if not self.items then return end
self.index = self.index - 1 self.index = self.index - 1
if self.index < 1 then if self.index < 1 then
self.index = #self.items self.index = #self.items
end end
updateValue() updateValue()
end) end
increment:onPress(function (event) local function increment ()
if not self.items then return end if not self.items then return end
self.index = self.index + 1 self.index = self.index + 1
if self.index > #self.items then if self.index > #self.items then
self.index = 1 self.index = 1
end end
updateValue() updateValue()
end
before:onPress(function (event)
if self.flow == 'x' then decrement() else increment() end
end)
after:onPress(function (event)
if self.flow == 'x' then increment() else decrement() end
end) end)
updateValue() updateValue()