layout/fixed: Prevent overloading widgets with negative spacing

For each widget, the layout function checks whether placing it would
make the function exceed the allowed geometry.
If not, the function places both the widget and a spacing widget.
This check ignores the size of the spacing widget itself, this can cause
overloading of widgets on top of each other.

For example, the following scenario with these widgets:
    widgets: widget1 { width = 10, height = 10  }
	     widget2 { width = 10, height = 10  }
	     widget3 { width = 10, height = 10  }
and a call to horizontal layout with the
{ width = 10, height = 10, spacing = -5 } parameters.

The function would layout the widgets the following way:
{
    widget1: { x = 0, y = 0, width = 10, height = 10 }
    spacing: { x = 5, y = 0, width = 5, height = 10  }
    widget2: { x = 5, y = 0, width = 5, height = 10  }
    spacing: { x = 5, y = 0, width = 5, height = 10 }
    widget3: { x = 5, y = 0, width = 5, height = 10  }
}

This behaviour would be the same for any number of widgets for negative
layout.

This patch changes the layout function to check whether the current
widget uses up the whole space.
It also removes 'pos' variable. Its purpose isn't intuitive in the
presence of x and y. This helps to understand where each widget is
placed now that x, y don't hold the end location of the widget in the
previous loop iteration.

The result of the previous example becomes:
{
    widget1: { x = 0, y = 0, width = 10, height = 10 }
}

While this might not be the wanted behaviour exactly, distinguishing
between the scenario where 2 widgets are drawn and a scenario where 3
are drawn might complicate the layout function too much.

This patch also adds unit testing that catches the described behaviour.

Signed-off-by: Shay Agroskin <agrosshay@gmail.com>
This commit is contained in:
Shay Agroskin 2021-01-01 21:13:19 +02:00 committed by Shay Agroskin
parent a18e3508f6
commit 62850476d2
2 changed files with 44 additions and 21 deletions

View File

@ -20,45 +20,61 @@ local fixed = {}
-- @param height The available height.
function fixed:layout(context, width, height)
local result = {}
local pos,spacing = 0, self._private.spacing
local spacing_widget = self._private.spacing_widget
local spacing = self._private.spacing or 0
local is_y = self._private.dir == "y"
local is_x = not is_y
local abspace = math.abs(spacing)
local spoffset = spacing < 0 and 0 or spacing
local widgets_nr = #self._private.widgets
local spacing_widget
local x, y = 0, 0
spacing_widget = spacing ~= 0 and self._private.spacing_widget or nil
for k, v in pairs(self._private.widgets) do
local x, y, w, h, _
local w, h = width - x, height - y
if is_y then
x, y = 0, pos
w, h = width, height - pos
if k ~= #self._private.widgets or not self._private.fill_space then
_, h = base.fit_widget(self, context, v, w, h);
if k ~= widgets_nr or not self._private.fill_space then
h = select(2, base.fit_widget(self, context, v, w, h))
end
if y - spacing >= height then
-- pop the spacing widget added in previous iteration if used
if spacing_widget then
table.remove(result)
end
break
end
pos = pos + h + spacing
else
x, y = pos, 0
w, h = width - pos, height
if k ~= #self._private.widgets or not self._private.fill_space then
w, _ = base.fit_widget(self, context, v, w, h);
if k ~= widgets_nr or not self._private.fill_space then
w = select(1, base.fit_widget(self, context, v, w, h))
end
if x - spacing >= width then
-- pop the spacing widget added in previous iteration if used
if spacing_widget then
table.remove(result)
end
break
end
pos = pos + w + spacing
end
if (is_y and pos-spacing > height) or
(is_x and pos-spacing > width) then
break
end
-- Place widget
table.insert(result, base.place_widget_at(v, x, y, w, h))
-- Add the spacing widget
if k > 1 and abspace > 0 and spacing_widget then
x = is_x and x + w + spacing or x
y = is_y and y + h + spacing or y
-- Add the spacing widget (if needed)
if k < widgets_nr and spacing_widget then
table.insert(result, base.place_widget_at(
spacing_widget, is_x and (x - spoffset) or x, is_y and (y - spoffset) or y,
is_x and abspace or w, is_y and abspace or h
))
end
table.insert(result, base.place_widget_at(v, x, y, w, h))
end
return result
end

View File

@ -78,7 +78,6 @@ describe("wibox.layout.fixed", function()
assert.widget_layout(layout, { 100, 20 }, {
p(first, 0, 0, 100, 10),
p(second, 0, 10, 100, 10),
p(third, 0, 20, 100, 0),
})
end)
end)
@ -169,6 +168,14 @@ describe("wibox.layout.fixed", function()
it("fit", function()
assert.widget_fit(layout, { 15, 20 }, { 15, 20 })
end)
it("layout", function()
assert.widget_layout(layout, { 15, 20 }, {
p(first, 0, 0, 15, 10),
p(spacing_widget, 0, 5, 15, 5),
p(second, 0, 5, 15, 15),
})
end)
end)
end) -- , negative spacing
end) -- with spacing