Multi-row support of systray by taking a beautiful variable specifying the maximum number of rows to fill per column. (#3520)

Also, fix the fitting logic so that the result width and height are fitted in the available space. Previously the result dimensions can be larger than the input, but cropped in e7a21947e6/lib/wibox/widget/base.lua (L547). But still it can cause problems e.g. when used inside wibox.container.place.
This commit is contained in:
Xinhao Yuan 2021-12-30 16:22:31 -05:00 committed by GitHub
parent 7451c6952e
commit b49a53f6dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 149 additions and 30 deletions

View File

@ -30,6 +30,13 @@ local display_on_screen = "primary"
-- @beautiful beautiful.bg_systray -- @beautiful beautiful.bg_systray
-- @param string The color (string like "#ff0000" only) -- @param string The color (string like "#ff0000" only)
--- The maximum number of rows for systray icons. Icons will fill the
-- current column (orthogonally to the systray direction) before
-- filling the next column.
--
-- @beautiful beautiful.systray_max_rows
-- @tparam[opt=1] integer The positive number of rows
--- The systray icon spacing. --- The systray icon spacing.
-- --
-- @beautiful beautiful.systray_icon_spacing -- @beautiful beautiful.systray_icon_spacing
@ -49,6 +56,9 @@ function systray:draw(context, cr, width, height)
local x, y, _, _ = wbase.rect_to_device_geometry(cr, 0, 0, width, height) local x, y, _, _ = wbase.rect_to_device_geometry(cr, 0, 0, width, height)
local num_entries = capi.awesome.systray() local num_entries = capi.awesome.systray()
local max_rows = math.floor(tonumber(beautiful.systray_max_rows) or 1)
local rows = math.max(math.min(num_entries, max_rows), 1)
local cols = math.ceil(num_entries / rows)
local bg = beautiful.bg_systray or beautiful.bg_normal or "#000000" local bg = beautiful.bg_systray or beautiful.bg_normal or "#000000"
local spacing = beautiful.systray_icon_spacing or 0 local spacing = beautiful.systray_icon_spacing or 0
@ -69,18 +79,17 @@ function systray:draw(context, cr, width, height)
end end
-- The formula for a given base, spacing, and num_entries for the necessary -- The formula for a given base, spacing, and num_entries for the necessary
-- space is (draw a picture to convince yourself; this assumes horizontal): -- space is (draw a picture to convince yourself; this assumes horizontal):
-- height = base -- height = (base + spacing) * rows - spacing
-- width = (base + spacing) * num_entries - spacing -- width = (base + spacing) * cols - spacing
-- Now, we check if we are limited by horizontal or vertical space: Which of -- Now, we check if we are limited by horizontal or vertical space: Which of
-- the two limits the base size more? -- the two limits the base size more?
if (ortho + spacing) * num_entries - spacing <= in_dir then base = (ortho + spacing) / rows - spacing
base = ortho if (base + spacing) * cols - spacing > in_dir then
else
-- Solving the "width" formula above for "base" (with width=in_dir): -- Solving the "width" formula above for "base" (with width=in_dir):
base = (in_dir + spacing) / num_entries - spacing base = (in_dir + spacing) / cols - spacing
end end
capi.awesome.systray(context.wibox.drawin, math.ceil(x), math.ceil(y), capi.awesome.systray(context.wibox.drawin, math.ceil(x), math.ceil(y),
base, is_rotated, bg, reverse, spacing) base, is_rotated, bg, reverse, spacing, rows)
end end
-- Private API. Does not appear in LDoc on purpose. This function is called -- Private API. Does not appear in LDoc on purpose. This function is called
@ -96,23 +105,28 @@ function systray:fit(context, width, height)
end end
local num_entries = capi.awesome.systray() local num_entries = capi.awesome.systray()
local max_rows = math.floor(tonumber(beautiful.systray_max_rows) or 1)
local rows = math.max(math.min(num_entries, max_rows), 1)
local cols = math.ceil(num_entries / rows)
local base = base_size local base = base_size
local spacing = beautiful.systray_icon_spacing or 0 local spacing = beautiful.systray_icon_spacing or 0
if num_entries == 0 then if num_entries == 0 then
return 0, 0 return 0, 0
end end
if base == nil then if base == nil then
if width < height then if horizontal then
base = width base = math.min(math.floor((height + spacing) / rows) - spacing,
math.floor((width + spacing) / cols) - spacing)
else else
base = height base = math.min(math.floor((width + spacing) / rows) - spacing,
math.floor((height + spacing) / cols) - spacing)
end end
end end
base = base + spacing base = base + spacing
if horizontal then if horizontal then
return base * num_entries - spacing, base return base * cols - spacing, base * rows - spacing
end end
return base, base * num_entries - spacing return base * rows - spacing, base * cols - spacing
end end
-- Check if the function was called like :foo() or .foo() and do the right thing -- Check if the function was called like :foo() or .foo() and do the right thing

View File

@ -48,14 +48,18 @@ describe("wibox.widget.systray", function()
local context = { wibox = { drawin = true } } local context = { wibox = { drawin = true } }
local cr = { user_to_device_distance = function() return 1, 0 end } local cr = { user_to_device_distance = function() return 1, 0 end }
local function test_systray(available_size, expected_size, expected_base) local function test_systray(available_size, expected_size, expected_base, expected_rows)
systray_arguments = nil systray_arguments = nil
local spacing = beautiful_mock.systray_icon_spacing or 0 local spacing = beautiful_mock.systray_icon_spacing or 0
assert.widget_fit(widget, available_size, expected_size) assert.widget_fit(widget, available_size, expected_size)
widget:draw(context, cr, unpack(available_size)) widget:draw(context, cr, unpack(available_size))
assert.is.near(systray_arguments[4], expected_base, 0.000001) assert.is.near(systray_arguments[4], expected_base, 0.000001)
assert.is.same(systray_arguments, {true, 0, 0, systray_arguments[4], true, '#000000', false, spacing}) assert.is.same(systray_arguments, {
true, 0, 0, systray_arguments[4],
true, '#000000', false, spacing,
expected_rows or 1
})
end end
describe("no spacing", function() describe("no spacing", function()
@ -74,18 +78,23 @@ describe("wibox.widget.systray", function()
it("two icons", function() it("two icons", function()
num_systray_icons = 2 num_systray_icons = 2
test_systray({ 100, 10 }, { 20, 10 }, 10) test_systray({ 100, 10 }, { 20, 10 }, 10)
test_systray({ 100, 100 }, { 100, 100 }, 100 / 2) test_systray({ 100, 100 }, { 100, 50 }, 100 / 2)
end) end)
it("three icons", function() it("three icons", function()
num_systray_icons = 3 num_systray_icons = 3
test_systray({ 100, 10 }, { 30, 10 }, 10) test_systray({ 100, 10 }, { 30, 10 }, 10)
test_systray({ 100, 100 }, { 100, 100 }, 100 / 3) test_systray({ 100, 100 }, { 99, 33 }, 100 / 3)
end) end)
end) end)
describe("10 spacing", function() describe("10 spacing", function()
beautiful_mock.systray_icon_spacing = 10 setup(function()
beautiful_mock.systray_icon_spacing = 10
end)
teardown(function()
beautiful_mock.systray_icon_spacing = nil
end)
it("no icons", function() it("no icons", function()
num_systray_icons = 0 num_systray_icons = 0
@ -105,13 +114,92 @@ describe("wibox.widget.systray", function()
-- Okay, so we want to place to icons next to each other in a square -- Okay, so we want to place to icons next to each other in a square
-- of size 100x100. Between them, there should be 10 pixels of -- of size 100x100. Between them, there should be 10 pixels of
-- space. So, we got 90 pixels for the icons, so 45 px per icon. -- space. So, we got 90 pixels for the icons, so 45 px per icon.
test_systray({ 100, 100 }, { 100, 100 }, (100 - 10) / 2) test_systray({ 100, 100 }, { 100, 45 }, (100 - 10) / 2)
end) end)
it("three icons", function() it("three icons", function()
num_systray_icons = 3 num_systray_icons = 3
test_systray({ 100, 10 }, { 50, 10 }, 10) test_systray({ 100, 10 }, { 50, 10 }, 10)
test_systray({ 100, 100 }, { 100, 100 }, (100 - 2 * 10) / 3) test_systray({ 100, 100 }, { 98, 26 }, (100 - 2 * 10) / 3)
end)
end)
describe("max two rows", function()
setup(function()
beautiful_mock.systray_max_rows = 2
end)
teardown(function()
beautiful_mock.systray_max_rows = nil
end)
describe("no spacing", function()
it("no icons", function()
num_systray_icons = 0
test_systray({ 100, 10 }, { 0, 0 }, 10, 1)
test_systray({ 100, 100 }, { 0, 0 }, 100, 1)
end)
it("one icon", function()
num_systray_icons = 1
-- +---+
-- | 1 |
-- +---+
test_systray({ 100, 10 }, { 10, 10 }, 10, 1)
test_systray({ 100, 100 }, { 100, 100 }, 100, 1)
end)
it("two icons", function()
num_systray_icons = 2
-- +---+
-- | 1 |
-- | 2 |
-- +---+
test_systray({ 100, 10 }, { 5, 10 }, 5, 2)
test_systray({ 100, 100 }, { 50, 100 }, 50, 2)
end)
it("three icons", function()
num_systray_icons = 3
-- +------+
-- | 1 3 |
-- | 2 |
-- +------+
test_systray({ 100, 10 }, { 10, 10 }, 5, 2)
test_systray({ 100, 100 }, { 100, 100 }, 50, 2)
end)
end)
describe("10 spacing", function()
setup(function()
beautiful_mock.systray_icon_spacing = 10
end)
teardown(function()
beautiful_mock.systray_icon_spacing = nil
end)
it("no icons", function()
num_systray_icons = 0
test_systray({ 100, 20 }, { 0, 0 }, 20, 1)
test_systray({ 100, 100 }, { 0, 0 }, 100, 1)
end)
it("one icon", function()
num_systray_icons = 1
test_systray({ 100, 20 }, { 20, 20 }, 20, 1)
test_systray({ 100, 100 }, { 100, 100 }, 100, 1)
end)
it("two icons", function()
num_systray_icons = 2
test_systray({ 100, 20 }, { 5, 20 }, 5, 2)
test_systray({ 100, 100 }, { 45, 100 }, 45, 2)
end)
it("three icons", function()
num_systray_icons = 3
test_systray({ 100, 20 }, { 20, 20 }, 5, 2)
test_systray({ 100, 100 }, { 100, 100 }, 45, 2)
end)
end) end)
end) end)
end) end)

View File

@ -272,18 +272,22 @@ luaA_systray_invalidate(void)
} }
static void static void
systray_update(int base_size, bool horizontal, bool reverse, int spacing, bool force_redraw) systray_update(int base_size, bool horizontal, bool reverse, int spacing, bool force_redraw, int rows)
{ {
if(base_size <= 0) if(base_size <= 0)
return; return;
/* Give the systray window the correct size */ /* Give the systray window the correct size */
int num_entries = systray_num_visible_entries(); int num_entries = systray_num_visible_entries();
uint32_t config_vals[4] = { base_size, base_size, 0, 0 }; int cols = (num_entries + rows - 1) / rows;
if(horizontal) uint32_t config_vals[4] = { 0, 0, 0, 0 };
config_vals[0] = base_size * num_entries + spacing * (num_entries - 1); if(horizontal) {
else config_vals[0] = base_size * cols + spacing * (cols - 1);
config_vals[1] = base_size * num_entries + spacing * (num_entries - 1); config_vals[1] = base_size * rows + spacing * (rows - 1);
} else {
config_vals[0] = base_size * rows + spacing * (rows - 1);
config_vals[1] = base_size * cols + spacing * (cols - 1);
}
xcb_configure_window(globalconf.connection, xcb_configure_window(globalconf.connection,
globalconf.systray.window, globalconf.systray.window,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
@ -313,10 +317,21 @@ systray_update(int base_size, bool horizontal, bool reverse, int spacing, bool f
xcb_map_window(globalconf.connection, em->win); xcb_map_window(globalconf.connection, em->win);
if (force_redraw) if (force_redraw)
xcb_clear_area(globalconf.connection, 1, em->win, 0, 0, 0, 0); xcb_clear_area(globalconf.connection, 1, em->win, 0, 0, 0, 0);
if(horizontal) if (i % rows == rows - 1) {
config_vals[0] += base_size + spacing; if (horizontal) {
else config_vals[0] += base_size + spacing;
config_vals[1] += base_size + spacing; config_vals[1] = 0;
} else {
config_vals[0] = 0;
config_vals[1] += base_size + spacing;
}
} else {
if (horizontal) {
config_vals[1] += base_size + spacing;
} else {
config_vals[0] += base_size + spacing;
}
}
} }
} }
@ -332,6 +347,7 @@ systray_update(int base_size, bool horizontal, bool reverse, int spacing, bool f
* \lparam bg Color of the systray background. * \lparam bg Color of the systray background.
* \lparam revers If true, the systray icon order will be reversed, else default. * \lparam revers If true, the systray icon order will be reversed, else default.
* \lparam spacing The size of the spacing between icons. * \lparam spacing The size of the spacing between icons.
* \lparam rows Number of rows to display.
*/ */
int int
luaA_systray(lua_State *L) luaA_systray(lua_State *L)
@ -352,6 +368,7 @@ luaA_systray(lua_State *L)
const char *bg = luaL_checklstring(L, 6, &bg_len); const char *bg = luaL_checklstring(L, 6, &bg_len);
bool revers = lua_toboolean(L, 7); bool revers = lua_toboolean(L, 7);
int spacing = ceil(luaA_checknumber_range(L, 8, 0, MAX_X11_COORDINATE)); int spacing = ceil(luaA_checknumber_range(L, 8, 0, MAX_X11_COORDINATE));
int rows = ceil(luaA_checknumber_range(L, 9, 1, INT16_MAX));
color_t bg_color; color_t bg_color;
bool force_redraw = false; bool force_redraw = false;
@ -385,7 +402,7 @@ luaA_systray(lua_State *L)
if(systray_num_visible_entries() != 0) if(systray_num_visible_entries() != 0)
{ {
systray_update(base_size, horiz, revers, spacing, force_redraw); systray_update(base_size, horiz, revers, spacing, force_redraw, rows);
xcb_map_window(globalconf.connection, xcb_map_window(globalconf.connection,
globalconf.systray.window); globalconf.systray.window);
} }