-- This template draw the screens with detailed information about the size
-- of various reserved areas such as struts, workareas, padding and *boxes.
local file_path, image_path = ...
require("_common_template")(...)
screen._track_workarea = true
local cairo = require("lgi").cairo
local Pango = require("lgi").Pango
local PangoCairo = require("lgi").PangoCairo
local color = require("gears.color")
-- Let the test request a size and file format
local args = loadfile(file_path)() or 10
args = args or {}
args.factor = args.factor or 10
local factor, img, cr = 1/args.factor
require("gears.timer").run_delayed_calls_now()
-- A second pass will add rulers with measurements on top.
local vrulers, hrulers = {}, {}
local function sur_to_pat(img2)
local pat = cairo.Pattern.create_for_surface(img2)
pat:set_extend(cairo.Extend.REPEAT)
return pat
end
-- Imported for elv13/blind/pattern.lua
local function stripe_pat(col, angle, line_width, spacing)
col = color(col)
angle = angle or math.pi/4
line_width = line_width or 1
spacing = spacing or line_width
local hy = line_width + 2*spacing
-- Get the necessary width and height so the line repeat itself correctly
local a, o = math.cos(angle)*hy, math.sin(angle)*hy
--FIXME spacing need to be in "w", not "hy"
local w, h = math.ceil(a + (line_width - 1)), math.ceil(o + (line_width - 1))
-- adjust_size(self, w, h) --FIXME need a "force_size" method
-- Create the pattern
local img2 = cairo.SvgSurface.create(nil, w, h)
local cr2 = cairo.Context(img2)
-- Avoid artefacts caused by anti-aliasing
local offset = line_width
-- Setup
cr2:set_source(color(col))
cr2:set_line_width(line_width)
-- The central line
cr2:move_to(-offset, -offset)
cr2:line_to(w+offset, h+offset)
cr2:stroke()
--FIXME sin/cos required for this to work with other angles than 45 degree
-- Top right
cr2:move_to(-offset + w - spacing/2+line_width, -offset)
cr2:line_to(2*w+offset - spacing/2+line_width, h+offset)
cr2:stroke()
-- Bottom left
cr2:move_to(-offset + spacing/2-line_width, -offset + h)
cr2:line_to(w+offset + spacing/2-line_width, 2*h+offset)
cr2:stroke()
return sur_to_pat(img2)
end
local colors = {
geometry = "#000000",
workarea = "#0000ff",
tiling_area = "#ff0000",
padding_area = "#ff0000",
wibar = "#000000",
tiling_client = "#ff0000",
gaps = "#9900ff",
}
local function draw_area(_, rect, name, offset, highlight)
local x, y = rect.x*factor+offset, rect.y*factor+offset
cr:rectangle(x, y, rect.width*factor, rect.height*factor)
if highlight then
cr:save()
cr:set_source(stripe_pat(colors[name].."22", nil, 1, 3))
cr:fill_preserve()
cr:restore()
end
cr:set_source(color(colors[name].."44"))
cr:stroke()
end
local function draw_bounding_area(_, rect, hole, name, offset)
draw_area(_, rect, name, offset, true)
local x, y = hole.x*factor+offset, hole.y*factor+offset
cr:set_operator(cairo.Operator.CLEAR)
cr:rectangle(x, y, hole.width*factor, hole.height*factor)
cr:set_source_rgb(1, 1, 1)
cr:fill()
cr:set_operator(cairo.Operator.OVER)
end
local function draw_solid_area(_, rect, name, offset, alpha)
alpha = alpha or "59" -- Defaults to 35%
local x, y = rect.x*factor+offset, rect.y*factor+offset
cr:rectangle(x, y, rect.width*factor, rect.height*factor)
cr:save()
cr:set_source(color.create_solid_pattern(colors[name] .. alpha))
cr:fill_preserve()
cr:restore()
cr:set_source(color(colors[name].."44"))
cr:stroke()
end
local function write_on_area_middle(rect, text, offset)
-- Prepare to write on the rect area
local pctx = PangoCairo.font_map_get_default():create_context()
local playout = Pango.Layout.new(pctx)
local pdesc = Pango.FontDescription()
pdesc:set_absolute_size(11 * Pango.SCALE)
playout:set_font_description(pdesc)
-- Write 'text' on the rect area
playout.attributes, playout.text = Pango.parse_markup(text, -1, 0)
local _, logical = playout:get_pixel_extents()
local dx = (rect.x*factor+offset) + (rect.width*factor - logical.width) / 2
local dy = (rect.y*factor+offset) + (rect.height*factor - logical.height) / 2
cr:set_source_rgb(0, 0, 0)
cr:move_to(dx, dy)
cr:show_layout(playout)
end
-- For clients/wibars with struts only.
local function draw_struct(_, struct, name, offset, label)
draw_solid_area(_, struct, name, offset)
if type(label) == 'string' then
write_on_area_middle(struct, label, offset)
end
end
-- For floating or tiled clients.
local function draw_client(_, c, name, offset, label, alpha)
draw_solid_area(_, c, name, offset, alpha)
if type(label) == 'string' then
write_on_area_middle(c, label, offset)
end
end
local function compute_ruler(_, rect, name)
table.insert(hrulers, {
label = name, x = rect.x, width = rect.width
})
table.insert(vrulers, {
label = name, y = rect.y, height = rect.height
})
end
local function bubble_sort(t, var1, var2)
for i=1, #t do
if t[i+1] and t[i][var1] < t[i+1][var1] then
t[i+1], t[i] = t[i], t[i+1]
return false
end
if t[i+1] and t[i][var1] == t[i+1][var1] and t[i][var2] < t[i+1][var2] then
t[i+1], t[i] = t[i], t[i+1]
return false
end
end
return true
end
local dx = 5
local playout, playout_height = nil, nil
local function get_playout()
if playout then return playout end
local pctx = PangoCairo.font_map_get_default():create_context()
playout = Pango.Layout.new(pctx)
local pdesc = Pango.FontDescription()
pdesc:set_absolute_size(11 * Pango.SCALE)
playout:set_font_description(pdesc)
return playout
end
local function get_text_height()
if playout_height then return playout_height end
local l = get_playout()
local attr, parsed = Pango.parse_markup("GeometryWorkareaPaddingMargins", -1, 0)
l.attributes, l.text = attr, parsed
local _, logical = playout:get_pixel_extents()
playout_height = logical.height
return playout_height
end
local function show_ruler_label(offset, padding, ruler, playout2)
if not ruler.label then return end
local lbl
if ruler.x then
lbl = ""..ruler.label..": x = "..
ruler.x.." width = "..ruler.width..""
else
lbl = ""..ruler.label..": y = "..
ruler.y.." height = "..ruler.height..""
end
local attr, parsed = Pango.parse_markup(lbl, -1, 0)
playout2.attributes, playout2.text = attr, parsed
local _, logical = playout2:get_pixel_extents()
cr:move_to((offset.x - logical.width) /2, offset.y+padding)
cr:show_layout(playout2)
end
local function show_aligned_label(dx2, offset, padding, ruler)
local lbl
if ruler.x then
lbl = ruler.width
else
lbl = ruler.height
end
local attr, parsed = Pango.parse_markup(lbl, -1, 0)
playout.attributes, playout.text = attr, parsed
local _, logical = playout:get_pixel_extents()
if ruler.x then
local off = (ruler.width*factor - logical.width)/2
cr:move_to(ruler.x*factor+off, offset.y+padding)
else
local off = (ruler.height*factor - logical.width)/2
cr:move_to(-ruler.y*factor-dx2*factor-off, padding)
end
cr:show_layout(playout)
end
local function draw_vruler(s, dx2, sx, ruler, layer)
local pad = 5+(layer-1)*dx2
cr:set_source(color(ruler.color or (colors[ruler.label].."66")))
cr:move_to(sx+layer*dx2, ruler.y*factor)
cr:line_to(sx+layer*dx2, ruler.y*factor+ruler.height*factor)
cr:stroke()
cr:move_to(sx+layer*dx2-2.5,ruler.y*factor)
cr:line_to(sx+layer*dx2+2.5, ruler.y*factor)
cr:stroke()
cr:move_to(sx+layer*dx2-2.5,ruler.y*factor+ruler.height*factor)
cr:line_to(sx+layer*dx2+2.5, ruler.y*factor+ruler.height*factor)
cr:stroke()
cr:save()
cr:move_to(sx+layer*dx2-2.5,ruler.y*factor)
cr:rotate(-math.pi/2)
if ruler and ruler.label then
show_ruler_label({x=-s.geometry.height*factor, y=sx}, pad, ruler, get_playout())
elseif ruler and ruler.align then
show_aligned_label(dx2, {x=s.geometry.width*factor, y=0}, pad, ruler)
end
cr:restore()
end
local function draw_hruler(s, dx2, sy, ruler, layer)
local pad = 10+(layer-1)*(dx2 or 0)
cr:set_source(color(ruler.color or (colors[ruler.label].."66")))
cr:move_to(ruler.x*factor, sy+pad)
cr:line_to(ruler.x*factor+ruler.width*factor, sy+pad)
cr:stroke()
cr:move_to(ruler.x*factor, sy+pad-2.5)
cr:line_to(ruler.x*factor, sy+pad+2.5)
cr:stroke()
cr:move_to(ruler.x*factor+ruler.width*factor, sy+pad-2.5)
cr:line_to(ruler.x*factor+ruler.width*factor, sy+pad+2.5)
cr:stroke()
if ruler and ruler.label then
show_ruler_label({x=s.geometry.width*factor, y=sy}, pad, ruler, get_playout())
elseif ruler and ruler.align then
show_aligned_label(dx2, {x=s.geometry.width*factor, y=sy}, pad, ruler)
end
end
local function draw_rulers(s)
-- The table has a maximum of 4 entries, the sort algorithm is irrelevant.
while not bubble_sort(hrulers, "x", "width" ) do end
while not bubble_sort(vrulers, "y", "height") do end
cr:set_line_width(1)
cr:set_dash(nil)
local sx = (s.geometry.x+s.geometry.width )*factor
local sy = (s.geometry.y+s.geometry.height)*factor
dx = get_text_height() + 10
for k, ruler in ipairs(vrulers) do
draw_vruler(s, dx, sx, ruler, k)
end
for k, ruler in ipairs(hrulers) do
draw_hruler(s, dx, sy, ruler, k)
end
end
local tr_x, tr_y = 0, 0
-- Not a very efficient way to do this, but at least it is simple.
local function deduplicate_gaps(gaps)
for _, gap1 in ipairs(gaps) do
for k, gap2 in ipairs(gaps) do
if gap1[2] == gap2[1] then
gap1[2] = gap2[2]
table.remove(gaps, k)
return true
elseif gap2[1] >= gap1[1] and gap2[2] <= gap1[2] and gap1 ~= gap2 then
table.remove(gaps, k)
return true
elseif gap1[1] == gap2[1] and gap2[2] == gap2[2] and gap1 ~= gap2 then
table.remove(gaps, k)
return true
end
end
end
return false
end
local function get_gaps(s)
local ret = {vertical={gaps={}, content={}}, horizontal={gaps={}, content={}}}
local gap = s.selected_tag.gap
if gap == 0 then return ret end
if s.selected_tag.gap_single_client == false and #s.tiled_clients == 1 then
return ret
end
-- First, get all gaps.
for _, c in ipairs(s.tiled_clients) do
local bw = c.border_width
table.insert(ret.horizontal.gaps, {c.x-gap , c.x })
table.insert(ret.vertical.gaps , {c.y-gap , c.y })
table.insert(ret.horizontal.gaps, {c.x+c.width +2*bw, c.x+c.width+gap +2*bw})
table.insert(ret.vertical.gaps , {c.y+c.height+2*bw, c.y+c.height+gap+2*bw})
end
-- Merge continuous gaps.
while deduplicate_gaps(ret.vertical.gaps ) do end
while deduplicate_gaps(ret.horizontal.gaps) do end
return ret
end
local function evaluate_translation(draw_gaps, draw_struts, draw_mwfact, draw_client_snap)
for s in screen do
if (draw_gaps and s.selected_tag and s.selected_tag.gap > 0) then
local gaps = get_gaps(s)
-- Only add the space if there is something to display.
if #gaps.horizontal.gaps > 0 then
tr_y = math.max(tr_y, 3 * get_text_height())
end
if #gaps.vertical.gaps > 0 then
tr_x = math.max(tr_x, 2 * get_text_height())
end
end
if draw_client_snap or draw_struts then
tr_y = math.max(tr_y, 3 * get_text_height())
tr_x = math.max(tr_x, 2 * get_text_height())
end
if draw_mwfact then
tr_y = math.max(tr_y, 3 * get_text_height())
end
end
end
local function translate()
cr:translate(tr_x, tr_y * 0.66)
end
local function draw_gaps(s)
cr:translate(-tr_x, -tr_y)
local gaps = get_gaps(s)
local offset = s.tiling_area
for _, hgap in ipairs(gaps.horizontal.gaps) do
draw_hruler(
s,
offset.x,
get_text_height(),
{
x = offset.x+hgap[1]+tr_x,
width = math.ceil(hgap[2]-hgap[1]),
label = nil,
color = colors.gaps.."66",
align = true
},
1
)
end
for _, vgap in ipairs(gaps.vertical.gaps) do
draw_vruler(
s,
get_text_height()*1.5,
0,
{
y = offset.y+vgap[1]+tr_y,
height = math.ceil(vgap[2]-vgap[1]),
label = nil,
color = colors.gaps.."66",
align = true
},
1
)
end
cr:translate(tr_x, tr_y)
end
local function has_struts(s)
for k, v in pairs(s.workarea) do
if s.geometry[k] ~= v then
return true
end
end
return false
end
local function draw_struts(s)
local left = s.workarea.x - s.geometry.x
local right = (s.geometry.x + s.geometry.width) - (s.workarea.x + s.workarea.width)
local top = s.workarea.y - s.geometry.y
local bottom = (s.geometry.y + s.geometry.height) - (s.workarea.y + s.workarea.height)
cr:translate(-tr_x, -tr_y)
if left > 0 then
draw_hruler(
s,
0,
get_text_height(),
{x = s.geometry.x+tr_x*2, width = left, color = colors.gaps.."66", align = true},
1
)
end
if top > 0 then
draw_vruler(
s,
get_text_height()*1.5,
0,
{y=s.geometry.y+tr_y*(1/factor), height = top, color = colors.gaps.."66", align = true},
1
)
end
if right > 0 then
draw_hruler(
s,
0,
get_text_height(),
{x = s.geometry.x, width = left, color = colors.gaps.."66", align = true},
1
)
end
if bottom > 0 then
draw_vruler(
s,
get_text_height()*1.5,
0,
{
y = s.geometry.y+tr_y*(1/factor)+s.geometry.height - bottom,
height = bottom,
color = colors.gaps.."66",
align = true
},
1
)
end
cr:translate(tr_x, tr_y)
end
local function draw_mwfact(s)
cr:translate(-tr_x, -tr_y)
local mwfact = s.selected_tag.master_width_factor
local offset = s.tiling_area.x
local width = s.tiling_area.width
local w1, w2 = math.ceil(width*mwfact), math.ceil(width*(1-mwfact))
draw_hruler(s, offset, get_text_height(), {x=offset,width=w1,color = colors.gaps.."66", align=true}, 1)
draw_hruler(s, offset, get_text_height(), {x=offset+w1,width=w2,color = colors.gaps.."66", align=true}, 1)
cr:translate(tr_x, tr_y)
end
local function draw_client_snap(s)
cr:translate(-tr_x, -tr_y)
local snap_areas = {
vertical ={},
horizontal={}
}
local d = require("awful.mouse.snap").default_distance
for _, c in ipairs(s.clients) do
if c.floating then
table.insert(snap_areas.horizontal, {c.x-d, c.x})
table.insert(snap_areas.horizontal, {c.x+c.width, c.x+c.width+d})
table.insert(snap_areas.vertical , {c.y-d, c.y})
table.insert(snap_areas.vertical , {c.y+c.height, c.y+c.height+d})
end
end
while deduplicate_gaps(snap_areas.horizontal) do end
while deduplicate_gaps(snap_areas.vertical ) do end
--FIXME
--[[for _, hgap in ipairs(snap_areas.horizontal) do
draw_hruler(
s,
0,
get_text_height(),
{x=tr_x+s.workarea.x+hgap[1],width=hgap[2]-hgap[1],label="gaps"},
1
)
end
for _, vgap in ipairs(snap_areas.vertical) do
draw_vruler(
s,
get_text_height()*1.5,
0,
{y=tr_y+vgap[1],height=vgap[2]-vgap[1],label="gaps"},
1
)
end]]--
cr:translate(tr_x, tr_y)
end
-- local function draw_screen_snap(s)
-- local d = require("awful.mouse.snap").aerosnap_distance
--
--
-- local proxy = {
-- x = c.x - sd,
-- y = c.y - sd,
-- width = c.width + 2*sd,
-- height = c.height + 2*sd,
-- }
--
-- draw_client(s, proxy, 'gaps', (k-1)*10, nil, "11")
-- end
local function draw_info(s)
cr:set_source_rgb(0, 0, 0)
local pctx = PangoCairo.font_map_get_default():create_context()
local playout2 = Pango.Layout.new(pctx)
local pdesc = Pango.FontDescription()
pdesc:set_absolute_size(11 * Pango.SCALE)
playout2:set_font_description(pdesc)
local rows = {
"primary", "index", "geometry", "dpi", "dpi range", "outputs"
}
local dpi_range = s.minimum_dpi and s.preferred_dpi and s.maximum_dpi
and (s.minimum_dpi.."-"..s.preferred_dpi.."-"..s.maximum_dpi)
or s.dpi.."-"..s.dpi
local geo = s.geometry
local values = {
screen.primary == s and "true" or "false",
s.index,
geo.x..":"..geo.y.." "..geo.width.."x"..geo.height,
s.dpi,
dpi_range,
"",
}
for n, o in pairs(s.outputs) do
table.insert(rows, " "..n)
table.insert(values,
math.ceil(o.mm_width).."mm x "..math.ceil(o.mm_height).."mm"
)
end
local col1_width, col2_width, height = 0, 0, 0
-- Get the extents of the longest label.
for k, label in ipairs(rows) do
local attr, parsed = Pango.parse_markup(label..":", -1, 0)
playout2.attributes, playout2.text = attr, parsed
local _, logical = playout2:get_pixel_extents()
col1_width = math.max(col1_width, logical.width+10)
attr, parsed = Pango.parse_markup(values[k], -1, 0)
playout2.attributes, playout2.text = attr, parsed
_, logical = playout2:get_pixel_extents()
col2_width = math.max(col2_width, logical.width+10)
height = math.max(height, logical.height)
end
local dx2 = (s.geometry.width*factor - col1_width - col2_width - 5)/2
local dy = (s.geometry.height*factor - #values*height)/2 - height
-- Draw everything.
for k, label in ipairs(rows) do
local attr, parsed = Pango.parse_markup(label..":", -1, 0)
playout2.attributes, playout2.text = attr, parsed
cr:move_to(dx2, dy)
cr:show_layout(playout2)
attr, parsed = Pango.parse_markup(values[k], -1, 0)
playout2.attributes, playout2.text = attr, parsed
local _, logical = playout2:get_pixel_extents()
cr:move_to( dx2+col1_width+5, dy)
cr:show_layout(playout2)
dy = dy + 5 + logical.height
end
end
-- Compute the rulers size.
for _=1, screen.count() do
local s = screen[1]
-- The padding.
compute_ruler(s, s.tiling_area, "tiling_area")
-- The workarea.
compute_ruler(s, s.workarea, "workarea")
-- The outer geometry.
compute_ruler(s, s.geometry, "geometry")
end
-- If there is some rulers on the left/top, add a global translation.
evaluate_translation(
args.draw_gaps,
args.draw_struts,
args.draw_mwfact,
args.draw_client_snap
)
-- Get the final size of the image.
local sew, seh = screen._get_extents()
sew, seh = sew/args.factor + (screen.count()-1)*10+2, seh/args.factor+2
sew, seh = sew + tr_x, seh + 0.66*tr_y
sew, seh = sew + 5*get_text_height(), seh + 5*get_text_height()
img = cairo.SvgSurface.create(image_path..".svg", sew, seh)
cr = cairo.Context(img)
cr:set_line_width(1.5)
cr:set_dash({10,4},1)
-- Instead of adding origin offset everywhere, translate the viewport.
translate()
-- Draw the various areas.
for k=1, screen.count() do
local s = screen[1]
-- The outer geometry.
draw_area(s, s.geometry, "geometry", (k-1)*10, args.highlight_geometry)
-- The workarea.
draw_area(s, s.workarea, "workarea", (k-1)*10, args.highlight_workarea)
-- The padding.
if args.highlight_padding_area then
draw_bounding_area(s, s.workarea, s.tiling_area, "padding_area", (k-1)*10)
end
draw_area(s, s.tiling_area, "tiling_area", (k-1)*10, args.highlight_tiling_area)
-- Draw the ruler.
draw_rulers(s)
-- Draw the wibar.
if args.draw_wibar then
draw_struct(s, args.draw_wibar, 'wibar', (k-1)*10, 'Wibar')
end
local skip_gaps = s.selected_tag
and s.selected_tag.gap_single_client == false
and #s.tiled_clients == 1
local sd = require("awful.mouse.snap").default_distance
-- Draw clients.
if args.draw_clients then
for label,c in pairs(args.draw_clients) do
local gap = c:tags()[1].gap
if args.draw_gaps and gap > 0 and (not c.floating) and not skip_gaps then
local proxy = {
x = c.x - gap,
y = c.y - gap,
width = c.width + 2*gap,
height = c.height + 2*gap,
}
draw_client(s, proxy, 'gaps', (k-1)*10, nil, "11")
elseif args.draw_client_snap and c.floating then
local proxy = {
x = c.x - sd,
y = c.y - sd,
width = c.width + 2*sd,
height = c.height + 2*sd,
}
draw_client(s, proxy, 'gaps', (k-1)*10, nil, "11")
end
draw_client(s, c, 'tiling_client', (k-1)*10, label)
end
end
if args.draw_struts and has_struts(s) then
draw_struts(s)
end
-- Draw the informations.
if args.display_screen_info ~= false then
draw_info(s)
end
-- Draw the useless gaps.
if args.draw_gaps and not skip_gaps then
draw_gaps(s)
end
-- Draw the useless gaps.
if args.draw_mwfact then
draw_mwfact(s)
end
-- Draw the snapping areas of floating clients.
if args.draw_client_snap then
draw_client_snap(s)
end
-- Draw the screen edge areas.
--if args.draw_screen_snap then
-- draw_screen_snap(s)
--end
end
img:finish()
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80