diff --git a/lib/wibox/widget/systray.lua b/lib/wibox/widget/systray.lua index 255bba1e4..f7a20361b 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 06542d175..b7bc5e41b 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 c3919ee3b..99986549a 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); }