From b49a53f6dd5c0e9b68a734272ff8a7a145a2ef3e Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Thu, 30 Dec 2021 16:22:31 -0500 Subject: [PATCH] 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 https://github.com/awesomeWM/awesome/blob/e7a21947e6785f53042338c684b9b96cc9b0f500/lib/wibox/widget/base.lua#L547. But still it can cause problems e.g. when used inside wibox.container.place. --- lib/wibox/widget/systray.lua | 38 +++++++---- spec/wibox/widget/systray_spec.lua | 102 +++++++++++++++++++++++++++-- systray.c | 39 +++++++---- 3 files changed, 149 insertions(+), 30 deletions(-) diff --git a/lib/wibox/widget/systray.lua b/lib/wibox/widget/systray.lua index 255bba1e..f7a20361 100644 --- a/lib/wibox/widget/systray.lua +++ b/lib/wibox/widget/systray.lua @@ -30,6 +30,13 @@ local display_on_screen = "primary" -- @beautiful beautiful.bg_systray -- @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. -- -- @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 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 spacing = beautiful.systray_icon_spacing or 0 @@ -69,18 +79,17 @@ function systray:draw(context, cr, width, height) end -- The formula for a given base, spacing, and num_entries for the necessary -- space is (draw a picture to convince yourself; this assumes horizontal): - -- height = base - -- width = (base + spacing) * num_entries - spacing + -- height = (base + spacing) * rows - spacing + -- width = (base + spacing) * cols - spacing -- Now, we check if we are limited by horizontal or vertical space: Which of -- the two limits the base size more? - if (ortho + spacing) * num_entries - spacing <= in_dir then - base = ortho - else + base = (ortho + spacing) / rows - spacing + if (base + spacing) * cols - spacing > in_dir then -- 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 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 -- Private API. Does not appear in LDoc on purpose. This function is called @@ -96,23 +105,28 @@ function systray:fit(context, width, height) end 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 spacing = beautiful.systray_icon_spacing or 0 if num_entries == 0 then return 0, 0 end if base == nil then - if width < height then - base = width + if horizontal then + base = math.min(math.floor((height + spacing) / rows) - spacing, + math.floor((width + spacing) / cols) - spacing) else - base = height + base = math.min(math.floor((width + spacing) / rows) - spacing, + math.floor((height + spacing) / cols) - spacing) end end base = base + spacing if horizontal then - return base * num_entries - spacing, base + return base * cols - spacing, base * rows - spacing end - return base, base * num_entries - spacing + return base * rows - spacing, base * cols - spacing end -- Check if the function was called like :foo() or .foo() and do the right thing diff --git a/spec/wibox/widget/systray_spec.lua b/spec/wibox/widget/systray_spec.lua index 06542d17..b7bc5e41 100644 --- a/spec/wibox/widget/systray_spec.lua +++ b/spec/wibox/widget/systray_spec.lua @@ -48,14 +48,18 @@ describe("wibox.widget.systray", function() local context = { wibox = { drawin = true } } 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 local spacing = beautiful_mock.systray_icon_spacing or 0 assert.widget_fit(widget, available_size, expected_size) widget:draw(context, cr, unpack(available_size)) 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 describe("no spacing", function() @@ -74,18 +78,23 @@ describe("wibox.widget.systray", function() it("two icons", function() num_systray_icons = 2 test_systray({ 100, 10 }, { 20, 10 }, 10) - test_systray({ 100, 100 }, { 100, 100 }, 100 / 2) + test_systray({ 100, 100 }, { 100, 50 }, 100 / 2) end) it("three icons", function() num_systray_icons = 3 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) 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() 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 -- 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. - test_systray({ 100, 100 }, { 100, 100 }, (100 - 10) / 2) + test_systray({ 100, 100 }, { 100, 45 }, (100 - 10) / 2) end) it("three icons", function() num_systray_icons = 3 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) diff --git a/systray.c b/systray.c index c3919ee3..99986549 100644 --- a/systray.c +++ b/systray.c @@ -272,18 +272,22 @@ luaA_systray_invalidate(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) return; /* Give the systray window the correct size */ int num_entries = systray_num_visible_entries(); - uint32_t config_vals[4] = { base_size, base_size, 0, 0 }; - if(horizontal) - config_vals[0] = base_size * num_entries + spacing * (num_entries - 1); - else - config_vals[1] = base_size * num_entries + spacing * (num_entries - 1); + int cols = (num_entries + rows - 1) / rows; + uint32_t config_vals[4] = { 0, 0, 0, 0 }; + if(horizontal) { + config_vals[0] = base_size * cols + spacing * (cols - 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, globalconf.systray.window, 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); if (force_redraw) xcb_clear_area(globalconf.connection, 1, em->win, 0, 0, 0, 0); - if(horizontal) - config_vals[0] += base_size + spacing; - else - config_vals[1] += base_size + spacing; + if (i % rows == rows - 1) { + if (horizontal) { + config_vals[0] += 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 revers If true, the systray icon order will be reversed, else default. * \lparam spacing The size of the spacing between icons. + * \lparam rows Number of rows to display. */ int luaA_systray(lua_State *L) @@ -352,6 +368,7 @@ luaA_systray(lua_State *L) const char *bg = luaL_checklstring(L, 6, &bg_len); bool revers = lua_toboolean(L, 7); 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; bool force_redraw = false; @@ -385,7 +402,7 @@ luaA_systray(lua_State *L) 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, globalconf.systray.window); }