Awesome: Improper layout in aweful.taglist when using neagative spacing and a fixed horizontal layout in the containing widget

Created on 27 Dec 2020  Â·  4Comments  Â·  Source: awesomeWM/awesome

Output of awesome --version:

awesome v4.3 (Too long)
• Compiled against Lua 5.3.5 (running with Lua 5.3)
• D-Bus support: ✔
• execinfo support: ✔
• xcb-randr version: 1.6
• LGI version: 0.9.2

How to reproduce the issue:

Copy the example at the top of the documentation for awful.taglist into the default rc.lua:

   s.mytaglist = awful.widget.taglist {
        screen  = s,
        filter  = awful.widget.taglist.filter.all,
        style   = {
            shape = gears.shape.powerline
        },
        layout   = {
            spacing = -12,
            spacing_widget = {
                color  = '#dddddd',
                shape  = gears.shape.powerline,
                widget = wibox.widget.separator,
            },
            layout  = wibox.layout.fixed.horizontal
        },
        widget_template = {
            {
                {
                    {
                        {
                            {
                                id     = 'index_role',
                                widget = wibox.widget.textbox,
                            },
                            margins = 4,
                            widget  = wibox.container.margin,
                        },
                        bg     = '#dddddd',
                        shape  = gears.shape.circle,
                        widget = wibox.container.background,
                    },
                    {
                        {
                            id     = 'icon_role',
                            widget = wibox.widget.imagebox,
                        },
                        margins = 2,
                        widget  = wibox.container.margin,
                    },
                    {
                        id     = 'text_role',
                        widget = wibox.widget.textbox,
                    },
                    layout = wibox.layout.fixed.horizontal,
                },
                left  = 18,
                right = 18,
                widget = wibox.container.margin
            },
            id     = 'background_role',
            widget = wibox.container.background
        },
        buttons = taglist_buttons
    }

I cut down version of the example with only the "text role" is sufficient to reproduce the results.

Actual result:

The taglist is drawn with the last two tags clipped, however it appears that enough space has been allocated on the wibar for them, they are just no being drawn correctly. I have set the background colour to bright green to show the space that has been allocated on the wibar for the taglist.

sc2

Expected result:

If you remove all of the wigets from the left side of the default wibar configuration (including the layout), except the taglist, the taglist will be drawn properly:

sc3

Adding the fixed horizontal layout back into the the left side of the default wibar is enough to cause the erroneous behaviour. Switching the outer layout to anything other than fixed horizontal removes this layout issue. So I think that this might be an issue with interactions between the instanced of the fixed horizontal layout.

bug confirmed doc

Most helpful comment

Dug into it a little.
It seems like fit function is called twice in row.

  • The first time it gets the maximum available width, and returns how much of it it needs (with 0 spacing it would be 477)
  • The second time the available width it gets is the same as it returned last phase (and probably is expected to return the same result again)

The width fit requests is the total width of all widgets together + the spacing (e.g. if each tag widget is 53 px and spacing is -10 then the requested width 53 * 9 - 80).

The function tries to first fit all its widgets (the tag numbers) in the amount of width it received, and only then adds the spacing to it. This is problematic because in the second phase the widgets need to fit themselves in the same width they requested earlier minus the spacing (in case of negative spacing).
This is of course impossible and so some widgets are just not being drawn correctly.

Possible fix is to take the spacing into account while placing the widgets and not afterwards. This diff seems to fix the problem


diff --git a/lib/wibox/layout/fixed.lua b/lib/wibox/layout/fixed.lua
index f6da5f47..99ddc17d 100644
--- a/lib/wibox/layout/fixed.lua
+++ b/lib/wibox/layout/fixed.lua
@@ -263,8 +263,9 @@ end
 function fixed:fit(context, orig_width, orig_height)
     local width, height = orig_width, orig_height
     local used_in_dir, used_max = 0, 0
+    local spacing = self._private.spacing

-    for _, v in pairs(self._private.widgets) do
+    for k, v in pairs(self._private.widgets) do
         local w, h = base.fit_widget(self, context, v, width, height)
         local in_dir, max
         if self._private.dir == "y" then
@@ -279,6 +280,15 @@ function fixed:fit(context, orig_width, orig_height)
         end
         used_in_dir = used_in_dir + in_dir

+        if k < #self._private.widgets then
+            used_in_dir = used_in_dir + spacing
+            if self._private.dir == "y" then
+                height = height - spacing
+            else
+                width = width - spacing
+            end
+        end
+
         if width <= 0 or height <= 0 then
             if self._private.dir == "y" then
                 used_in_dir = orig_height
@@ -289,12 +299,11 @@ function fixed:fit(context, orig_width, orig_height)
         end
     end

-    local spacing = self._private.spacing * (#self._private.widgets-1)

     if self._private.dir == "y" then
-        return used_max, used_in_dir + spacing
+        return used_max, used_in_dir
     end
-    return used_in_dir + spacing, used_max
+    return used_in_dir, used_max
 end

 function fixed:reset()

All 4 comments

The following (broken) diff makes the example work (this is just meant as a starting point for whoever wants to figure this out fully). It seems like the fixed layout asks for too few space and... I am not quite sure.

diff --git a/lib/wibox/layout/fixed.lua b/lib/wibox/layout/fixed.lua
index f6da5f477..a73d21832 100644
--- a/lib/wibox/layout/fixed.lua
+++ b/lib/wibox/layout/fixed.lua
@@ -26,6 +26,8 @@ function fixed:layout(context, width, height)
     local is_x = not is_y
     local abspace = math.abs(spacing)
     local spoffset = spacing < 0 and 0 or spacing
+    local extra_space_due_to_spacing = spacing < 0 and abspace or 0
+    extra_space_due_to_spacing = 0 -- Hrm. Just the change to :fit() is enough to make the example work. I guess because my hack there asks for too much space...?

     for k, v in pairs(self._private.widgets) do
         local x, y, w, h, _
@@ -33,14 +35,14 @@ function fixed:layout(context, width, height)
             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);
+                _, h = base.fit_widget(self, context, v, w, h + extra_space_due_to_spacing);
             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);
+                w, _ = base.fit_widget(self, context, v, w + extra_space_due_to_spacing, h);
             end
             pos = pos + w + spacing
         end
@@ -290,6 +292,7 @@ function fixed:fit(context, orig_width, orig_height)
     end

     local spacing = self._private.spacing * (#self._private.widgets-1)
+    spacing = 0 -- FIXME how does this work for negative spacings?

     if self._private.dir == "y" then
         return used_max, used_in_dir + spacing

Dug into it a little.
It seems like fit function is called twice in row.

  • The first time it gets the maximum available width, and returns how much of it it needs (with 0 spacing it would be 477)
  • The second time the available width it gets is the same as it returned last phase (and probably is expected to return the same result again)

The width fit requests is the total width of all widgets together + the spacing (e.g. if each tag widget is 53 px and spacing is -10 then the requested width 53 * 9 - 80).

The function tries to first fit all its widgets (the tag numbers) in the amount of width it received, and only then adds the spacing to it. This is problematic because in the second phase the widgets need to fit themselves in the same width they requested earlier minus the spacing (in case of negative spacing).
This is of course impossible and so some widgets are just not being drawn correctly.

Possible fix is to take the spacing into account while placing the widgets and not afterwards. This diff seems to fix the problem


diff --git a/lib/wibox/layout/fixed.lua b/lib/wibox/layout/fixed.lua
index f6da5f47..99ddc17d 100644
--- a/lib/wibox/layout/fixed.lua
+++ b/lib/wibox/layout/fixed.lua
@@ -263,8 +263,9 @@ end
 function fixed:fit(context, orig_width, orig_height)
     local width, height = orig_width, orig_height
     local used_in_dir, used_max = 0, 0
+    local spacing = self._private.spacing

-    for _, v in pairs(self._private.widgets) do
+    for k, v in pairs(self._private.widgets) do
         local w, h = base.fit_widget(self, context, v, width, height)
         local in_dir, max
         if self._private.dir == "y" then
@@ -279,6 +280,15 @@ function fixed:fit(context, orig_width, orig_height)
         end
         used_in_dir = used_in_dir + in_dir

+        if k < #self._private.widgets then
+            used_in_dir = used_in_dir + spacing
+            if self._private.dir == "y" then
+                height = height - spacing
+            else
+                width = width - spacing
+            end
+        end
+
         if width <= 0 or height <= 0 then
             if self._private.dir == "y" then
                 used_in_dir = orig_height
@@ -289,12 +299,11 @@ function fixed:fit(context, orig_width, orig_height)
         end
     end

-    local spacing = self._private.spacing * (#self._private.widgets-1)

     if self._private.dir == "y" then
-        return used_max, used_in_dir + spacing
+        return used_max, used_in_dir
     end
-    return used_in_dir + spacing, used_max
+    return used_in_dir, used_max
 end

 function fixed:reset()

Thanks for digging. That patch seems sensible to me. Do you think you could write a unit test for this (see spec/wibox/layout/fixed_spec.lua) and submit a PR?

@psychon Sure (: Already started working on one

Was this page helpful?
0 / 5 - 0 ratings