grid: Add border support.
This is long overdue. A bit of historical context. The grid API is losely somewhat based on the old `radical` module, but was heavily improved by @getzze. That version had row_span and col_span. This made the way the previous implementation coded the border incompatible. I spent some time back then trying to bolt it back on, but the complexity is quite high and never made it work right. This commit goes in another direction. Rather than draw the border, it creates a mask where the border should *not* be, then bucket fill the widget. This is the equivalent of CSS `border-collapse`. It also support custom borders. This allows dashed lines and partial borders. The main use case will be to add border support to the calendar. It was previously possible to partially do it using custom cell painters, but was pretty hacky. Now that the calendar will deprecate the custom painters in favor of `widget_template`s, a more robust alternative was required. The drawback of this commit is obviously the added complexity to the most complex layout. This is why it adds many tests to cover the various corner cases.
This commit is contained in:
parent
2d5c4b64c5
commit
f8f5e2c69b
|
@ -7,8 +7,7 @@
|
|||
--
|
||||
--@DOC_wibox_layout_grid_imperative_EXAMPLE@
|
||||
--
|
||||
-- The same can be done using the declarative syntax. When using this mode,
|
||||
-- all widgets will again the `row_span` and `col_span` properties:
|
||||
-- The same can be done using the declarative syntax:
|
||||
--
|
||||
--@DOC_wibox_layout_grid_declarative1_EXAMPLE@
|
||||
--
|
||||
|
@ -31,7 +30,10 @@ local pairs = pairs
|
|||
local ipairs = ipairs
|
||||
local math = math
|
||||
local gtable = require("gears.table")
|
||||
local gmath = require("gears.math")
|
||||
local gcolor = require("gears.color")
|
||||
local base = require("wibox.widget.base")
|
||||
local cairo = require("lgi").cairo
|
||||
|
||||
local grid = { mt = {} }
|
||||
|
||||
|
@ -74,6 +76,7 @@ local dir_properties = { "spacing", "homogeneous", "expand" }
|
|||
-- @propertyunit rows
|
||||
-- @negativeallowed false
|
||||
-- @see forced_num_cols
|
||||
-- @see row_count
|
||||
|
||||
--- Force the number of columns of the layout.
|
||||
-- @property forced_num_cols
|
||||
|
@ -82,6 +85,7 @@ local dir_properties = { "spacing", "homogeneous", "expand" }
|
|||
-- @propertyunit columns
|
||||
-- @negativeallowed false
|
||||
-- @see forced_num_rows
|
||||
-- @see column_count
|
||||
|
||||
--- Set the minimum size for the columns.
|
||||
--
|
||||
|
@ -123,6 +127,12 @@ local dir_properties = { "spacing", "homogeneous", "expand" }
|
|||
-- preferred `orientation`.
|
||||
--
|
||||
--@DOC_wibox_layout_grid_spacing_EXAMPLE@
|
||||
--
|
||||
-- When a border is present, the spacing is applied on both side of the border,
|
||||
-- thus is twice as large:
|
||||
--
|
||||
-- @DOC_wibox_layout_grid_border_width3_EXAMPLE@
|
||||
--
|
||||
-- @tparam[opt=0] number spacing
|
||||
-- @property spacing
|
||||
-- @negativeallowed false
|
||||
|
@ -186,6 +196,25 @@ local dir_properties = { "spacing", "homogeneous", "expand" }
|
|||
-- @see vertical_homogeneous
|
||||
-- @see horizontal_homogeneous
|
||||
|
||||
--- The number of rows.
|
||||
--
|
||||
-- If `forced_num_rows` is set, then its value is returned, otherwise it will
|
||||
-- return the maximum actual number of widgets in a row.
|
||||
--
|
||||
-- @property row_count
|
||||
-- @tparam integer row_count
|
||||
-- @readonly
|
||||
-- @see forced_num_rows
|
||||
|
||||
--- The number of columns.
|
||||
--
|
||||
-- If `forced_num_cols` is set, then its value is returned, otherwise it will
|
||||
-- return the maximum actual number of widgets in a column.
|
||||
--
|
||||
-- @property column_count
|
||||
-- @readonly
|
||||
-- @tparam integer column_count
|
||||
-- @see forced_num_cols
|
||||
|
||||
--- Child widget position. Return of `get_widget_position`.
|
||||
-- @field row Top row index
|
||||
|
@ -322,7 +351,7 @@ end
|
|||
-- The widgets are assumed to span one cell.
|
||||
--
|
||||
-- If the widgets have a `row_index`, `col_index`, `col_span`
|
||||
-- or `row_span` property, it will be honorred.
|
||||
-- or `row_span` property, it will be honored.
|
||||
--
|
||||
-- @method add
|
||||
-- @tparam wibox.widget ... Widgets that should be added (must at least be one)
|
||||
|
@ -659,6 +688,90 @@ function grid:remove_row(index)
|
|||
end
|
||||
|
||||
|
||||
--- Add row border.
|
||||
--
|
||||
-- This method allows to set the width/color or a specific row rather than use
|
||||
-- the same values for all the rows.
|
||||
--
|
||||
-- @DOC_wibox_layout_grid_add_row_border1_EXAMPLE@
|
||||
--
|
||||
-- @method add_row_border
|
||||
-- @tparam integer index The row index. `1` is the top border (outer) border.
|
||||
-- @tparam[opt=nil] integer|nil height The border height. If `nil` is passed,
|
||||
-- then the `border_width.outer` will be user for index `1` and
|
||||
-- `row_count + 1`, otherwise, `border_width.inner` will be used.
|
||||
-- @tparam[opt={}] table args
|
||||
-- @tparam[opt=nil] color args.color The border color. If `nil` is passed,
|
||||
-- then the `border_color.outer` will be user for index `1` and
|
||||
-- `row_count + 1`, otherwise, `border_color.inner` will be used.
|
||||
-- @tparam[opt={1}] table args.dashes The dash pattern used for the line. By default,
|
||||
-- it is a solid line.
|
||||
-- @tparam[opt=0] number args.dash_offset If the line has `dashes`, then this is the
|
||||
-- initial offset. Note that line are draw left to right and top to bottom.
|
||||
-- @tparam[opt="butt"] string args.caps How the dashes ends are drawn. Either
|
||||
-- `"butt"` (default), `"round"` or `"square"`
|
||||
-- @noreturn
|
||||
-- @see add_column_border
|
||||
|
||||
--- Add column border.
|
||||
--
|
||||
-- This method allows to set the width/color or a specific column rather than use
|
||||
-- the same values for all the columns.
|
||||
--
|
||||
-- @DOC_wibox_layout_grid_add_column_border1_EXAMPLE@
|
||||
--
|
||||
-- @method add_column_border
|
||||
-- @tparam integer index The column index. `1` is the top border (outer) border.
|
||||
-- @tparam[opt=nil] integer|nil height The border height. If `nil` is passed,
|
||||
-- then the `border_width.outer` will be user for index `1` and
|
||||
-- `column_count + 1`, otherwise, `border_width.inner` will be used.
|
||||
-- @tparam[opt={}] table args
|
||||
-- @tparam[opt=nil] color args.color The border color. If `nil` is passed,
|
||||
-- then the `border_color.outer` will be user for index `1` and
|
||||
-- `row_count + 1`, otherwise, `border_color.inner` will be used.
|
||||
-- @tparam[opt={1}] table args.dashes The dash pattern used for the line. By default,
|
||||
-- it is a solid line.
|
||||
-- @tparam[opt=0] number args.dash_offset If the line has `dashes`, then this is the
|
||||
-- initial offset. Note that line are draw left to right and top to bottom.
|
||||
-- @tparam[opt="butt"] string args.caps How the dashes ends are drawn. Either
|
||||
-- `"butt"` (default), `"round"` or `"square"`
|
||||
-- @noreturn
|
||||
-- @see add_column_border
|
||||
|
||||
--- The border width.
|
||||
--
|
||||
-- @DOC_wibox_layout_grid_border_width1_EXAMPLE@
|
||||
--
|
||||
-- If `add_row_border` or `add_column_border` is used, it takes precedence and
|
||||
-- is drawn on top of the `border_color` mask. Using both `border_width` and
|
||||
-- `add_row_border` at the same time makes little sense:
|
||||
--
|
||||
-- @DOC_wibox_layout_grid_border_width2_EXAMPLE@
|
||||
--
|
||||
-- It is also possible to set the inner and outer borders separately:
|
||||
--
|
||||
-- @DOC_wibox_layout_grid_border_width4_EXAMPLE@
|
||||
--
|
||||
-- @property border_width
|
||||
-- @tparam[opt=0] integer|table border_width
|
||||
-- @tparam integer border_width.inner
|
||||
-- @tparam integer border_width.outer
|
||||
-- @propertytype integer Use the same value for inner and outer borders.
|
||||
-- @propertytype table Specify a different value for the inner and outer borders.
|
||||
-- @negativeallowed false
|
||||
-- @see border_color
|
||||
-- @see add_column_border
|
||||
-- @see add_row_border
|
||||
|
||||
--- The border color for the table outer border.
|
||||
-- @property border_color
|
||||
-- @tparam[opt=0] color|table border_color
|
||||
-- @tparam color border_color.inner
|
||||
-- @tparam color border_color.outer
|
||||
-- @propertytype color Use the same value for inner and outer borders.
|
||||
-- @propertytype table Specify a different value for the inner and outer borders.
|
||||
-- @see border_width
|
||||
|
||||
-- Return list of children
|
||||
function grid:get_children()
|
||||
local ret = {}
|
||||
|
@ -715,6 +828,78 @@ function grid:set_forced_num_rows(val)
|
|||
end
|
||||
end
|
||||
|
||||
function grid:get_row_count()
|
||||
return self._private.num_rows
|
||||
end
|
||||
|
||||
function grid:get_column_count()
|
||||
return self._private.num_cols
|
||||
end
|
||||
|
||||
function grid:set_border_width(val)
|
||||
self._private.border_width = type(val) == "table" and val or {
|
||||
inner = val or 0,
|
||||
outer = val or 0,
|
||||
}
|
||||
|
||||
-- Enforce integers. Not doing so makes the masking code more complex. Also,
|
||||
-- most of the time, not using integer is probably an user mistake (DPI
|
||||
-- related or ratio related).
|
||||
self._private.border_width.inner = gmath.round(self._private.border_width.inner)
|
||||
self._private.border_width.outer = gmath.round(self._private.border_width.outer)
|
||||
|
||||
-- Drawing the border takes both a lot of memory (for the cached masks)
|
||||
-- and CPU, so make sure it is no-op for the 99% of cases where there is
|
||||
-- no border.
|
||||
self._private.has_border = self._private.border_width.inner ~= 0
|
||||
or self._private.border_width.outer ~= 0
|
||||
|
||||
self:emit_signal("property::border_width", self._private.border_width)
|
||||
self:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
function grid:set_border_color(val)
|
||||
if type(val) == "table" then
|
||||
self._private.border_color = {
|
||||
inner = gcolor(val.inner),
|
||||
outer = gcolor(val.outer),
|
||||
}
|
||||
else
|
||||
self._private.border_color = {
|
||||
inner = gcolor(val),
|
||||
outer = gcolor(val),
|
||||
}
|
||||
end
|
||||
self:emit_signal("property::border_color", self._private.border_color)
|
||||
self:emit_signal("widget::redraw_needed")
|
||||
end
|
||||
|
||||
function grid:add_row_border(index, height, args)
|
||||
self._private.has_border = true
|
||||
self._private.custom_border_width.rows[index] = {
|
||||
size = height,
|
||||
color = args.color and gcolor(args.color),
|
||||
dashes = args.dashes,
|
||||
offset = args.dash_offset,
|
||||
caps = args.caps,
|
||||
}
|
||||
|
||||
self:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
function grid:add_column_border(index, width, args)
|
||||
self._private.has_border = true
|
||||
self._private.custom_border_width.cols[index] = {
|
||||
size = width,
|
||||
color = args.color and gcolor(args.color),
|
||||
dashes = args.dashes,
|
||||
offset = args.dash_offset,
|
||||
caps = args.caps,
|
||||
}
|
||||
|
||||
self:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
-- Set the grid properties
|
||||
for _, prop in ipairs(properties) do
|
||||
if not grid["set_" .. prop] then
|
||||
|
@ -796,6 +981,41 @@ local function get_grid_sizes(self, context, orig_width, orig_height)
|
|||
return rows_size, cols_size
|
||||
end
|
||||
|
||||
-- All the code to get the width of a specific border.
|
||||
--
|
||||
-- This table module supports partial borders and "just add a border" modes.
|
||||
local function setup_border_widths(self)
|
||||
self._private.border_width = {inner = 0, outer = 0}
|
||||
self._private.custom_border_width = {rows = {}, cols = {}}
|
||||
self._private.border_color = {}
|
||||
|
||||
-- Use a metatable to get the defaults.
|
||||
local function meta_border_common(custom, row_or_col)
|
||||
return setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
-- Handle custom borders.
|
||||
if custom[k] then
|
||||
return custom[k].size
|
||||
end
|
||||
|
||||
local size = self[row_or_col.."_count"]
|
||||
if k == 1 or k == size + 1 then
|
||||
return self._private.border_width.outer
|
||||
else
|
||||
return self._private.border_width.inner
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local hfb = self._private.custom_border_width.rows
|
||||
local vfb = self._private.custom_border_width.cols
|
||||
|
||||
self._private.meta_borders = {
|
||||
rows = meta_border_common(hfb, "row"),
|
||||
cols = meta_border_common(vfb, "column"),
|
||||
}
|
||||
end
|
||||
|
||||
-- Fit the grid layout into the given space.
|
||||
-- @param context The context in which we are fit.
|
||||
|
@ -805,18 +1025,42 @@ function grid:fit(context, orig_width, orig_height)
|
|||
local width, height = orig_width, orig_height
|
||||
|
||||
-- Calculate the space needed
|
||||
local function fit_direction(dir, sizes)
|
||||
local m = 0
|
||||
local function fit_direction(dir, sizes, border_widths)
|
||||
local m = border_widths[1]
|
||||
local space = self._private[dir .. "_spacing"]
|
||||
|
||||
-- First border
|
||||
m = m > 0 and m + space or m
|
||||
|
||||
if self._private[dir .. "_homogeneous"] then
|
||||
local max = max_value(sizes)
|
||||
|
||||
-- all the columns/rows have the same size
|
||||
m = #sizes * max_value(sizes) + (#sizes - 1) * self._private[dir .. "_spacing"]
|
||||
if self._private.has_border then
|
||||
|
||||
-- Not all borders are identical, so the loop is required.
|
||||
for i in ipairs(sizes) do
|
||||
local bw = border_widths[i+1]
|
||||
|
||||
-- When there is a border, it needs the spacing on both sides.
|
||||
|
||||
m = m + max + (space*(bw > 0 and 2 or 1)) + bw
|
||||
end
|
||||
else
|
||||
-- Much simpler.
|
||||
m = #sizes * max + (#sizes - 1) * space
|
||||
end
|
||||
else
|
||||
-- sum the columns/rows size
|
||||
for _,s in ipairs(sizes) do
|
||||
m = m + s + self._private[dir .. "_spacing"]
|
||||
for i, s in ipairs(sizes) do
|
||||
local bw = border_widths[i+1]
|
||||
|
||||
-- When there is a border, it needs the spacing on both sides.
|
||||
m = m + s + (space * (bw > 0 and 2 or 1)) + bw
|
||||
end
|
||||
m = m - self._private[dir .. "_spacing"]
|
||||
|
||||
end
|
||||
|
||||
return m
|
||||
end
|
||||
|
||||
|
@ -824,69 +1068,117 @@ function grid:fit(context, orig_width, orig_height)
|
|||
local rows_size, cols_size = get_grid_sizes(self, context, width, height)
|
||||
|
||||
-- compute the width
|
||||
local used_width_max = fit_direction("horizontal", cols_size)
|
||||
local used_height_max = fit_direction("vertical", rows_size)
|
||||
local borders = self._private.meta_borders
|
||||
local used_width_max = fit_direction("horizontal", cols_size, borders.cols)
|
||||
local used_height_max = fit_direction("vertical", rows_size, borders.rows)
|
||||
|
||||
return used_width_max, used_height_max
|
||||
end
|
||||
|
||||
-- Layout a grid layout.
|
||||
-- @param context The context in which we are drawn.
|
||||
-- @param width The available width.
|
||||
-- @param height The available height.
|
||||
function grid:layout(context, width, height)
|
||||
local result = {}
|
||||
local function layout_common(self, context, width, height, h_homogeneous, v_homogeneous)
|
||||
local result, areas = {}, {}
|
||||
local hspacing, vspacing = self._private.horizontal_spacing, self._private.vertical_spacing
|
||||
|
||||
-- Fit matrix cells
|
||||
local rows_size, cols_size = get_grid_sizes(self, context, width, height)
|
||||
local total_expected_width, total_expected_height = sum_values(cols_size), sum_values(rows_size)
|
||||
|
||||
local h_bw, v_bw = self._private.meta_borders.cols, self._private.meta_borders.rows
|
||||
|
||||
-- Do it once, the result wont change unless widgets are added.
|
||||
if self._private.has_border and not self._private.area_cache.total_horizontal_border_width then
|
||||
-- Also add the "second" spacing here. This avoid having some `if` below.
|
||||
local total_h = h_bw[1] + h_bw[#cols_size+1] + 1*hspacing
|
||||
local total_v = v_bw[1] + v_bw[#rows_size+1] + 1*vspacing
|
||||
|
||||
for j = 1, #cols_size do
|
||||
local bw = h_bw[j+1]
|
||||
total_h = total_h + bw + hspacing*(bw > 0 and 1 or 0)
|
||||
end
|
||||
|
||||
for i = 1, #rows_size do
|
||||
local bw = v_bw[i+1]
|
||||
total_v = total_v + bw + vspacing*(bw > 0 and 1 or 0)
|
||||
end
|
||||
|
||||
self._private.area_cache.total_horizontal_border_width = total_h - h_bw[1]
|
||||
self._private.area_cache.total_vertical_border_width = total_v - v_bw[1]
|
||||
end
|
||||
|
||||
local total_h = self._private.area_cache.total_horizontal_border_width or 0
|
||||
local total_v = self._private.area_cache.total_vertical_border_width or 0
|
||||
|
||||
-- Figure out the maximum size we can give out to sub-widgets
|
||||
local single_width, single_height = max_value(cols_size), max_value(rows_size)
|
||||
|
||||
if self._private.horizontal_expand then
|
||||
single_width = (width - (self._private.num_cols-1)*hspacing) / self._private.num_cols
|
||||
single_width = (width - (self._private.num_cols-1)*hspacing - total_h) / self._private.num_cols
|
||||
end
|
||||
|
||||
if self._private.vertical_expand then
|
||||
single_height = (height - (self._private.num_rows-1)*vspacing) / self._private.num_rows
|
||||
single_height = (height - (self._private.num_rows-1)*vspacing - total_v) / self._private.num_rows
|
||||
end
|
||||
|
||||
-- Calculate the position and size to place the widgets
|
||||
local cumul_width, cumul_height = {}, {}
|
||||
local cw, ch = 0, 0
|
||||
local c_hor, c_ver = h_bw[1], v_bw[1]
|
||||
|
||||
-- If there is an outer border, then it needs inner spacing too.
|
||||
c_hor, c_ver = c_hor > 0 and c_hor + hspacing or 0, c_ver > 0 and c_ver + vspacing or 0
|
||||
|
||||
for j = 1, #cols_size do
|
||||
cumul_width[j] = cw
|
||||
if self._private.horizontal_homogeneous then
|
||||
cumul_width[j] = c_hor
|
||||
|
||||
if h_homogeneous then
|
||||
cols_size[j] = math.max(self._private.min_cols_size, single_width)
|
||||
elseif self._private.horizontal_expand then
|
||||
local hpercent = self._private.num_cols * single_width * cols_size[j] / total_expected_width
|
||||
cols_size[j] = math.max(self._private.min_cols_size, hpercent)
|
||||
end
|
||||
cw = cw + cols_size[j] + hspacing
|
||||
|
||||
local bw = h_bw[j+1]
|
||||
c_hor = c_hor + cols_size[j] + (bw > 0 and 2 or 1)*hspacing + bw
|
||||
end
|
||||
cumul_width[#cols_size + 1] = cw
|
||||
|
||||
cumul_width[#cols_size + 1] = c_hor
|
||||
|
||||
for i = 1, #rows_size do
|
||||
cumul_height[i] = ch
|
||||
if self._private.vertical_homogeneous then
|
||||
cumul_height[i] = c_ver
|
||||
|
||||
if v_homogeneous then
|
||||
rows_size[i] = math.max(self._private.min_rows_size, single_height)
|
||||
elseif self._private.vertical_expand then
|
||||
local vpercent = self._private.num_rows * single_height * rows_size[i] / total_expected_height
|
||||
rows_size[i] = math.max(self._private.min_rows_size, vpercent)
|
||||
end
|
||||
ch = ch + rows_size[i] + vspacing
|
||||
|
||||
local bw = v_bw[i+1]
|
||||
c_ver = c_ver + rows_size[i] + (bw > 0 and 2 or 1)*vspacing + bw
|
||||
end
|
||||
cumul_height[#rows_size + 1] = ch
|
||||
|
||||
cumul_height[#rows_size + 1] = c_ver
|
||||
|
||||
-- Place widgets
|
||||
local fill_space = true -- should be fill_space property?
|
||||
for _, v in pairs(self._private.widgets) do
|
||||
local x, y, w, h
|
||||
|
||||
-- If there is a border, then the spacing is needed on both sides.
|
||||
local col_bw, row_bw = h_bw[v.col+v.col_span], v_bw[v.row+v.row_span]
|
||||
local col_spacing = hspacing * (col_bw > 0 and 2 or 1)
|
||||
local row_spacing = vspacing * (row_bw > 0 and 2 or 1)
|
||||
|
||||
-- Round numbers to avoid decimals error, force to place tight widgets
|
||||
-- and avoid redraw glitches
|
||||
x = math.floor(cumul_width[v.col])
|
||||
y = math.floor(cumul_height[v.row])
|
||||
w = math.floor(cumul_width[v.col + v.col_span] - hspacing - x)
|
||||
h = math.floor(cumul_height[v.row + v.row_span] - vspacing - y)
|
||||
w = math.floor(cumul_width[v.col + v.col_span] - col_spacing - x - col_bw)
|
||||
h = math.floor(cumul_height[v.row + v.row_span] - row_spacing - y - row_bw)
|
||||
|
||||
-- Handle large spacing and/or border_width. The grid doesn't support
|
||||
-- dropping widgets. It would be very hard to implement.
|
||||
w, h = math.max(0, w), math.max(0, h)
|
||||
|
||||
-- Recalculate the width so the last widget fits
|
||||
if (fill_space or self._private.horizontal_expand) and x + w > width then
|
||||
w = math.floor(math.max(self._private.min_cols_size, width - x))
|
||||
|
@ -898,11 +1190,179 @@ function grid:layout(context, width, height)
|
|||
-- Place the widget if it fits in the area
|
||||
if x + w <= width and y + h <= height then
|
||||
table.insert(result, base.place_widget_at(v.widget, x, y, w, h))
|
||||
table.insert(areas, {
|
||||
x = x - hspacing,
|
||||
y = y - vspacing,
|
||||
width = w + col_spacing,
|
||||
height = h + row_spacing,
|
||||
})
|
||||
end
|
||||
end
|
||||
return result
|
||||
|
||||
-- Sometime, the `:fit()` size and `:layout()` size are different, thus it's
|
||||
-- important to say where the widget actually ends.
|
||||
areas.end_x = cumul_width[#cumul_width] - hspacing
|
||||
areas.end_y = cumul_height[#cumul_height] - vspacing
|
||||
areas.column_count = #cols_size
|
||||
areas.row_count = #rows_size
|
||||
areas.cols = cumul_width
|
||||
areas.rows = cumul_height
|
||||
|
||||
return result, areas
|
||||
end
|
||||
|
||||
local function get_area_cache_hash(width, height)
|
||||
return width*1.5+height*15
|
||||
end
|
||||
|
||||
-- Layout a grid layout.
|
||||
-- @param context The context in which we are drawn.
|
||||
-- @param width The available width.
|
||||
-- @param height The available height.
|
||||
function grid:layout(context, width, height)
|
||||
local l, areas = layout_common(
|
||||
self,
|
||||
context,
|
||||
width,
|
||||
height,
|
||||
self._private.horizontal_homogeneous,
|
||||
self._private.vertical_homogeneous
|
||||
)
|
||||
|
||||
self._private.area_cache[get_area_cache_hash(width, height)] = areas
|
||||
|
||||
return l
|
||||
end
|
||||
|
||||
local function create_border_mask(self, areas, default_color)
|
||||
if areas.surface then return areas.surface end
|
||||
|
||||
local meta = self._private.meta_borders
|
||||
|
||||
local top, bottom = meta.rows[1], meta.rows[areas.row_count+1]
|
||||
local left, right = meta.cols[1], meta.cols[areas.column_count+1]
|
||||
|
||||
-- A1 is fine because :layout() aligns to pixel boundary and `border_width`
|
||||
-- are integers.
|
||||
local img = cairo.RecordingSurface(cairo.Content.COLOR_ALPHA, cairo.Rectangle {
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = areas.end_x + right,
|
||||
height = areas.end_y + bottom
|
||||
})
|
||||
local cr = cairo.Context(img)
|
||||
cr:set_source(default_color)
|
||||
|
||||
local bw_i, bw_o = self._private.border_width.inner, self._private.border_width.outer
|
||||
|
||||
if bw_i ~= bw_o then
|
||||
if bw_o then
|
||||
if self._private.border_color.outer then
|
||||
cr:set_source(self._private.border_color.outer)
|
||||
end
|
||||
|
||||
-- Clip the outside region. It cannot use `cr:set_line_width()` because
|
||||
-- each border might be different.
|
||||
cr:rectangle(0, 0, areas.end_x, top)
|
||||
cr:rectangle(0, areas.end_y - bottom, areas.end_x, bottom)
|
||||
cr:rectangle(0, top, left, areas.end_y - top - bottom)
|
||||
cr:rectangle(areas.end_x - right, top, right, areas.end_y - top - bottom)
|
||||
cr:clip()
|
||||
cr:paint()
|
||||
cr:reset_clip()
|
||||
end
|
||||
|
||||
cr:rectangle(left, top, areas.end_x - top - bottom, areas.end_y - left - right)
|
||||
cr:clip()
|
||||
else
|
||||
cr:rectangle(0,0, areas.end_x, areas.end_y)
|
||||
cr:clip()
|
||||
end
|
||||
|
||||
if bw_i then
|
||||
if self._private.border_color.inner then
|
||||
cr:set_source(self._private.border_color.inner)
|
||||
end
|
||||
cr:rectangle(0, 0, areas.end_x, areas.end_y)
|
||||
cr:fill()
|
||||
end
|
||||
|
||||
-- Add the custom horizontal and borders.
|
||||
-- This is a lifeline for users who want borders only on specific places.
|
||||
-- Implementing word processing style borders would be overkill and
|
||||
-- too hard to maintain.
|
||||
for _, orientation in ipairs { "rows", "cols" } do
|
||||
for row, args in pairs(self._private.custom_border_width[orientation]) do
|
||||
local line_height = meta[orientation][row]
|
||||
cr:save()
|
||||
cr:rectangle(0,0, areas.end_x, areas.end_y)
|
||||
cr:clip()
|
||||
cr:set_line_width(line_height)
|
||||
|
||||
if args.dashes then
|
||||
cr:set_dash(args.dashes, #args.dashes, args.offset or 0)
|
||||
end
|
||||
|
||||
if args.caps then
|
||||
cr:set_line_cap(cairo.LineCap[args.caps:upper()])
|
||||
end
|
||||
|
||||
cr:set_source(args.color)
|
||||
|
||||
-- Cairo draw the stroke equally on both side, for `line_height/2` is
|
||||
-- needed.
|
||||
local y = (row == 1 and line_height or areas[orientation][row] or 0) - math.ceil(line_height/2)
|
||||
|
||||
if orientation == "rows" then
|
||||
cr:move_to(0, y)
|
||||
cr:line_to(areas.end_x, y)
|
||||
else
|
||||
cr:move_to(y, 0)
|
||||
cr:line_to(y, areas.end_y)
|
||||
end
|
||||
|
||||
cr:stroke()
|
||||
cr:restore()
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove the area used by widgets. This needs to be done regardless of the
|
||||
-- border mode to handle row/col span.
|
||||
cr:set_operator(cairo.Operator.CLEAR)
|
||||
|
||||
for _, area in ipairs(areas) do
|
||||
cr:rectangle(area.x, area.y, area.width, area.height)
|
||||
end
|
||||
|
||||
cr:fill()
|
||||
|
||||
areas.surface = img
|
||||
|
||||
return img
|
||||
end
|
||||
|
||||
-- Draw the border.
|
||||
function grid:after_draw_children(ctx, cr, width, height)
|
||||
if not self._private.has_border then return end
|
||||
|
||||
local hash = get_area_cache_hash(width, height)
|
||||
|
||||
if not self._private.area_cache[hash] then
|
||||
self._private.area_cache[hash] = select(2, layout_common(
|
||||
self,
|
||||
ctx,
|
||||
width,
|
||||
height,
|
||||
self._private.horizontal_homogeneous,
|
||||
self._private.vertical_homogeneous
|
||||
))
|
||||
end
|
||||
|
||||
local areas = self._private.area_cache[hash]
|
||||
|
||||
cr:set_source_surface(create_border_mask(self, areas, cr:get_source()), 0 ,0)
|
||||
cr:paint()
|
||||
end
|
||||
|
||||
--- Reset the grid layout.
|
||||
-- Remove all widgets and reset row and column counts
|
||||
|
@ -963,6 +1423,14 @@ local function new(orientation)
|
|||
ret._private.horizontal_spacing = 0
|
||||
ret._private.vertical_spacing = 0
|
||||
|
||||
ret._private.area_cache, ret._private.border_color = {}, {}
|
||||
|
||||
ret:connect_signal("widget::layout_changed", function(self)
|
||||
self._private.area_cache = {}
|
||||
end)
|
||||
|
||||
setup_border_widths(ret)
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
--DOC_GEN_IMAGE --DOC_HIDE_START
|
||||
local generic_widget_ = ...
|
||||
local wibox = require("wibox")
|
||||
local beautiful = require("beautiful")
|
||||
|
||||
local l = wibox.widget {
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.horizontal
|
||||
}
|
||||
|
||||
local function generic_widget(txt)
|
||||
return generic_widget_(txt, nil, 0)
|
||||
end
|
||||
|
||||
--DOC_HIDE_END
|
||||
local w = wibox.widget {
|
||||
--DOC_HIDE_START
|
||||
-- [...] Some widgets here.
|
||||
{
|
||||
text = "none",
|
||||
row_index = 1,
|
||||
col_index = 1,
|
||||
col_span = 3,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "first",
|
||||
col_span = 2,
|
||||
row_index = 2,
|
||||
col_index = 1,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "third",
|
||||
row_index = 2,
|
||||
col_index = 3,
|
||||
row_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "second",
|
||||
row_index = 3,
|
||||
col_index = 1,
|
||||
col_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "fourth",
|
||||
row_index = 4,
|
||||
col_index = 1,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "fifth",
|
||||
row_index = 4,
|
||||
col_index = 2,
|
||||
col_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "sixth",
|
||||
row_index = 1,
|
||||
col_index = 4,
|
||||
row_span = 4,
|
||||
widget = generic_widget
|
||||
},
|
||||
--DOC_HIDE_END
|
||||
homogeneous = true,
|
||||
spacing = 0,
|
||||
border_width = 4,
|
||||
border_color = beautiful.border_color,
|
||||
min_cols_size = 10,
|
||||
min_rows_size = 10,
|
||||
layout = wibox.layout.grid,
|
||||
}
|
||||
|
||||
--DOC_NEWLINE
|
||||
w:add_column_border(1, 5 , { color = "purple" })
|
||||
w:add_column_border(2, 10, { color = "cyan" })
|
||||
w:add_column_border(3, 5 , { color = "magenta"})
|
||||
w:add_column_border(4, 5 , { color = "black" })
|
||||
w:add_column_border(5, 10, { color = "grey" })
|
||||
|
||||
l:add(w) --DOC_HIDE
|
||||
return l, l:fit({dpi=96}, 9999, 9999) --DOC_HIDE
|
|
@ -0,0 +1,85 @@
|
|||
--DOC_GEN_IMAGE --DOC_HIDE_START
|
||||
local generic_widget_ = ...
|
||||
local wibox = require("wibox")
|
||||
local beautiful = require("beautiful")
|
||||
|
||||
local l = wibox.widget {
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.horizontal
|
||||
}
|
||||
|
||||
local function generic_widget(txt)
|
||||
return generic_widget_(txt, nil, 0)
|
||||
end
|
||||
|
||||
--DOC_HIDE_END
|
||||
local w = wibox.widget {
|
||||
-- [...] Some widgets here.
|
||||
--DOC_HIDE_START
|
||||
{
|
||||
text = "none",
|
||||
row_index = 1,
|
||||
col_index = 1,
|
||||
col_span = 3,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "first",
|
||||
col_span = 2,
|
||||
row_index = 2,
|
||||
col_index = 1,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "third",
|
||||
row_index = 2,
|
||||
col_index = 3,
|
||||
row_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "second",
|
||||
row_index = 3,
|
||||
col_index = 1,
|
||||
col_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "fourth",
|
||||
row_index = 4,
|
||||
col_index = 1,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "fifth",
|
||||
row_index = 4,
|
||||
col_index = 2,
|
||||
col_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "sixth",
|
||||
row_index = 1,
|
||||
col_index = 4,
|
||||
row_span = 4,
|
||||
widget = generic_widget
|
||||
},
|
||||
--DOC_HIDE_END
|
||||
homogeneous = true,
|
||||
spacing = 0,
|
||||
border_width = 4,
|
||||
border_color = beautiful.border_color,
|
||||
min_cols_size = 10,
|
||||
min_rows_size = 10,
|
||||
layout = wibox.layout.grid,
|
||||
}
|
||||
|
||||
--DOC_NEWLINE
|
||||
w:add_row_border(1, 40, { color = "red" })
|
||||
w:add_row_border(2, 5 , { color = "green" , dashes = {5, 3, 10, 3}})
|
||||
w:add_row_border(3, 10, { color = "blue" , dashes = {5, 3, 10, 3}, dash_offset = 5})
|
||||
w:add_row_border(4, 30, { color = "orange", dashes = {5, 40}, caps = "round"})
|
||||
w:add_row_border(5, 10, { color = "yellow"})
|
||||
|
||||
l:add(w) --DOC_HIDE
|
||||
return l, l:fit({dpi=96}, 9999, 9999) --DOC_HIDE
|
|
@ -0,0 +1,62 @@
|
|||
--DOC_GEN_IMAGE --DOC_HIDE_START --DOC_NO_USAGE
|
||||
local generic_widget_ = ...
|
||||
local wibox = require("wibox")
|
||||
|
||||
local l = wibox.widget {
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
}
|
||||
|
||||
local function generic_widget(txt)
|
||||
return generic_widget_(txt, nil, 0)
|
||||
end
|
||||
|
||||
--DOC_HIDE_END
|
||||
|
||||
for _, expand in ipairs { true, false } do
|
||||
for _, homogeneous in ipairs { true, false } do
|
||||
--DOC_HIDE_START
|
||||
local row = wibox.widget {
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.horizontal
|
||||
}
|
||||
l:add(wibox.widget.textbox(
|
||||
"<b>`expand = "..tostring(expand).."`, `homogeneous = "..tostring(homogeneous).."`</b>:"
|
||||
))
|
||||
l:add(row)
|
||||
--DOC_HIDE_END
|
||||
for _, width in ipairs { 0, 1, 2, 4, 10 } do
|
||||
local w = wibox.widget {
|
||||
generic_widget( "first" ),
|
||||
generic_widget( "second" ),
|
||||
generic_widget( "third" ),
|
||||
generic_widget( "fourth" ),
|
||||
generic_widget( "fifth" ),
|
||||
generic_widget( "sixth" ),
|
||||
forced_num_cols = 2,
|
||||
forced_num_rows = 2,
|
||||
homogeneous = homogeneous,
|
||||
spacing = 10,
|
||||
border_width = {
|
||||
inner = width,
|
||||
outer = 1.5 * width,
|
||||
},
|
||||
border_color = "red",
|
||||
expand = expand,
|
||||
forced_height = expand and 200 or nil,
|
||||
layout = wibox.layout.grid,
|
||||
}
|
||||
|
||||
--DOC_HIDE_START
|
||||
row:add(wibox.widget {
|
||||
wibox.widget.textbox("<i> `border_width = ".. width .."`</i>: "),
|
||||
w,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
})
|
||||
--DOC_HIDE_END
|
||||
end
|
||||
end
|
||||
end
|
||||
--DOC_HIDE_START
|
||||
|
||||
return l, l:fit({dpi=96}, 9999, 9999)
|
|
@ -0,0 +1,91 @@
|
|||
--DOC_GEN_IMAGE --DOC_HIDE_START --DOC_NO_USAGE
|
||||
local generic_widget_ = ...
|
||||
local wibox = require("wibox")
|
||||
local beautiful = require("beautiful")
|
||||
|
||||
local l = wibox.widget {
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.horizontal
|
||||
}
|
||||
|
||||
local function generic_widget(txt)
|
||||
return generic_widget_(txt, nil, 0)
|
||||
end
|
||||
|
||||
--DOC_HIDE_END
|
||||
local w = wibox.widget {
|
||||
--DOC_HIDE_START
|
||||
-- [...] Some widgets here.
|
||||
{
|
||||
text = "none",
|
||||
row_index = 1,
|
||||
col_index = 1,
|
||||
col_span = 3,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "first",
|
||||
col_span = 2,
|
||||
row_index = 2,
|
||||
col_index = 1,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "third",
|
||||
row_index = 2,
|
||||
col_index = 3,
|
||||
row_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "second",
|
||||
row_index = 3,
|
||||
col_index = 1,
|
||||
col_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "fourth",
|
||||
row_index = 4,
|
||||
col_index = 1,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "fifth",
|
||||
row_index = 4,
|
||||
col_index = 2,
|
||||
col_span = 2,
|
||||
widget = generic_widget
|
||||
},
|
||||
{
|
||||
text = "sixth",
|
||||
row_index = 1,
|
||||
col_index = 4,
|
||||
row_span = 4,
|
||||
widget = generic_widget
|
||||
},
|
||||
--DOC_HIDE_END
|
||||
homogeneous = true,
|
||||
spacing = 0,
|
||||
border_width = 4,
|
||||
border_color = beautiful.border_color,
|
||||
min_cols_size = 10,
|
||||
min_rows_size = 10,
|
||||
layout = wibox.layout.grid,
|
||||
}
|
||||
|
||||
--DOC_NEWLINE
|
||||
w:add_row_border(1, 40, { color = "red" })
|
||||
w:add_row_border(2, 5 , { color = "green" , dashes = {5, 3, 10, 3}})
|
||||
w:add_row_border(3, 10, { color = "blue" , dashes = {5, 3, 10, 3}, dash_offset = 5})
|
||||
w:add_row_border(4, 30, { color = "orange", dashes = {5, 40}, caps = "round"})
|
||||
w:add_row_border(5, 10, { color = "yellow"})
|
||||
--DOC_NEWLINE
|
||||
w:add_column_border(1, 5, { color = "purple"})
|
||||
w:add_column_border(2, 10, { color = "cyan"})
|
||||
w:add_column_border(3, 5, { color = "magenta"})
|
||||
w:add_column_border(4, 5, { color = "black"})
|
||||
w:add_column_border(5, 10, { color = "grey"})
|
||||
|
||||
l:add(w) --DOC_HIDE
|
||||
return l, l:fit({dpi=96}, 9999, 9999) --DOC_HIDE
|
|
@ -0,0 +1,40 @@
|
|||
--DOC_GEN_IMAGE --DOC_HIDE_START --DOC_NO_USAGE
|
||||
local generic_widget_ = ...
|
||||
local wibox = require("wibox") --DOC_HIDE
|
||||
|
||||
local l = wibox.widget {
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.horizontal
|
||||
}
|
||||
|
||||
local function generic_widget(txt)
|
||||
return generic_widget_(txt, nil, 0)
|
||||
end
|
||||
|
||||
--DOC_HIDE_END
|
||||
for _, width in ipairs { 0, 1, 2, 4, 10 } do
|
||||
local w = wibox.widget {
|
||||
generic_widget( "first" ),
|
||||
generic_widget( "second" ),
|
||||
generic_widget( "third" ),
|
||||
generic_widget( "fourth" ),
|
||||
forced_num_cols = 2,
|
||||
forced_num_rows = 2,
|
||||
homogeneous = true,
|
||||
spacing = width,
|
||||
border_width = 1,
|
||||
border_color = "red",
|
||||
layout = wibox.layout.grid,
|
||||
}
|
||||
|
||||
--DOC_HIDE_START
|
||||
l:add(wibox.widget {
|
||||
wibox.widget.textbox("<b> `spacing = ".. width .."`</b>: "),
|
||||
w,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
})
|
||||
--DOC_HIDE_END
|
||||
end
|
||||
--DOC_HIDE_START
|
||||
|
||||
return l, l:fit({dpi=96}, 9999, 9999)
|
|
@ -0,0 +1,67 @@
|
|||
--DOC_GEN_IMAGE --DOC_HIDE_START --DOC_NO_USAGE
|
||||
local generic_widget_ = ...
|
||||
local wibox = require("wibox")
|
||||
local gears = { color = require("gears.color") }
|
||||
|
||||
local l = wibox.widget {
|
||||
spacing = 10,
|
||||
layout = wibox.layout.fixed.horizontal
|
||||
}
|
||||
|
||||
local function generic_widget(txt)
|
||||
return generic_widget_(txt, nil, 0)
|
||||
end
|
||||
|
||||
--DOC_HIDE_END
|
||||
for _, width in ipairs { 0, 1, 2, 4, 10 } do
|
||||
local w = wibox.widget {
|
||||
generic_widget( "first" ),
|
||||
generic_widget( "second" ),
|
||||
generic_widget( "third" ),
|
||||
generic_widget( "fourth" ),
|
||||
forced_num_cols = 2,
|
||||
forced_num_rows = 2,
|
||||
homogeneous = true,
|
||||
spacing = 10,
|
||||
border_width = {
|
||||
inner = width,
|
||||
outer = 10 - width,
|
||||
},
|
||||
border_color = {
|
||||
inner = gears.color {
|
||||
type = "linear",
|
||||
from = { 0 , 0 },
|
||||
to = { 100, 100 },
|
||||
stops = {
|
||||
{ 0, "#ff0000" },
|
||||
{ 1, "#ffff00" },
|
||||
}
|
||||
},
|
||||
outer = gears.color {
|
||||
type = "linear",
|
||||
from = { 0 , 0 },
|
||||
to = { 100, 100 },
|
||||
stops = {
|
||||
{ 0, "#0000ff" },
|
||||
{ 1, "#ff0000" },
|
||||
}
|
||||
},
|
||||
},
|
||||
layout = wibox.layout.grid,
|
||||
}
|
||||
|
||||
--DOC_HIDE_START
|
||||
l:add(wibox.widget {
|
||||
wibox.widget.textbox(
|
||||
"<b>"..
|
||||
"`border_width.outer = ".. (10-width) .."`\n"..
|
||||
"`border_width.inner = ".. width .."`"..
|
||||
"</b>:"),
|
||||
w,
|
||||
layout = wibox.layout.fixed.vertical
|
||||
})
|
||||
--DOC_HIDE_STOP
|
||||
end
|
||||
--DOC_HIDE_START
|
||||
|
||||
return l, l:fit({dpi=96}, 9999, 9999)
|
|
@ -1,6 +1,7 @@
|
|||
--DOC_GEN_IMAGE --DOC_HIDE_START
|
||||
local generic_widget = ... --DOC_NO_USAGE
|
||||
local wibox = require("wibox")
|
||||
local beautiful = require("beautiful")
|
||||
|
||||
--DOC_HIDE_END
|
||||
|
||||
|
@ -53,9 +54,11 @@ local wibox = require("wibox")
|
|||
},
|
||||
homogeneous = true,
|
||||
spacing = 5,
|
||||
border_width = 1,
|
||||
border_color = beautiful.border_color,
|
||||
min_cols_size = 10,
|
||||
min_rows_size = 10,
|
||||
layout = wibox.layout.grid,
|
||||
}
|
||||
|
||||
return l, l:fit({dpi=96}, 300, 200) --DOC_HIDE
|
||||
return l, l:fit({dpi=96}, 400, 200) --DOC_HIDE
|
||||
|
|
|
@ -66,6 +66,7 @@ wibox.widget {
|
|||
},
|
||||
layout = wibox.layout.fixed.vertical
|
||||
},
|
||||
spacing = 5,
|
||||
layout = wibox.layout.fixed.horizontal
|
||||
}
|
||||
, 300, 90 --DOC_HIDE
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
--DOC_GEN_IMAGE
|
||||
local generic_widget = ... --DOC_HIDE_ALL
|
||||
local generic_widget_ = ... --DOC_HIDE_ALL
|
||||
local wibox = require("wibox") --DOC_HIDE
|
||||
local beautiful = require("beautiful") --DOC_HIDE
|
||||
|
||||
local function generic_widget(txt)
|
||||
return generic_widget_(txt, nil, 0)
|
||||
end
|
||||
|
||||
local w = wibox.widget {
|
||||
{
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue