-- 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") local aclient = require("awful.client") local beautiful = require("beautiful") -- 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 SCALE_FACTOR = 0.66 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 = beautiful.fg_normal, workarea = "#0000ff", tiling_area = "#ff0000", padding_area = "#ff0000", wibar = beautiful.fg_normal, 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(color.change_opacity(colors[name], 0.0863), nil, 1, 3)) cr:fill_preserve() cr:restore() end cr:set_source(color.change_opacity(colors[name], 0.1725)) 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 0.2314 -- 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.change_opacity(colors[name], alpha)) cr:fill_preserve() cr:restore() cr:set_source(color.change_opacity(colors[name], 0.0863)) 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(beautiful.fg_normal) 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(s, rect, name) hrulers[s], vrulers[s] = hrulers[s] or {}, vrulers[s] or {} table.insert(hrulers[s], { label = name, x = rect.x, width = rect.width }) table.insert(vrulers[s], { 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 (color.change_opacity(colors[ruler.label], 0.2588)))) 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 (color.change_opacity(colors[ruler.label], 0.2588)))) 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[s], "x", "width" ) do end while not bubble_sort(vrulers[s], "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[s]) 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 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 = color.change_opacity(colors.gaps, 0.2588), 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 = color.change_opacity(colors.gaps, 0.2588), 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, s.geometry.y*SCALE_FACTOR, get_text_height(), { x = s.geometry.x+tr_x*2, width = left, color = color.change_opacity(colors.gaps, 0.2588), align = true }, 1 ) end if top > 0 then draw_vruler( s, get_text_height()*1.5, s.geometry.x*SCALE_FACTOR, { y = s.geometry.y+tr_y*(1/factor), height = top, color = color.change_opacity(colors.gaps, 0.2588), align = true }, 1 ) end if right > 0 then draw_hruler( s, s.geometry.y*SCALE_FACTOR, get_text_height(), { x = s.geometry.x, width = left, color = color.change_opacity(colors.gaps, 0.2588), align = true }, 1 ) end if bottom > 0 then draw_vruler( s, get_text_height()*1.5, s.geometry.x*SCALE_FACTOR, { y = s.geometry.y+tr_y*(1/factor)+s.geometry.height - bottom, height = bottom, color = color.change_opacity(colors.gaps, 0.2588), 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 = color.change_opacity(colors.gaps, 0.2588), align = true }, 1) draw_hruler(s, offset, get_text_height(), { x = offset+w1, width = w2, color = color.change_opacity(colors.gaps, 0.2588), align = true }, 1) cr:translate(tr_x, tr_y) end local function draw_wfact(s) cr:translate(-tr_x, -tr_y) local tags = s.selected_tags local windowfacts = s.selected_tag.windowfact local height = s.tiling_area.height / SCALE_FACTOR local sum, gap = 0, s.selected_tag.gap or 0 for _, t in ipairs(tags) do for _, c in ipairs(t:clients()) do local info = aclient.idx(c) sum = sum + windowfacts[info.col][info.idx] end end local offset = s.tiling_area.y * args.factor + tr_y + (2*gap) for i = 1, #windowfacts[1] do draw_vruler( s, 0, --s.geometry.x + s.geometry.width, s.geometry.width * factor + 5, { y = math.floor(offset), height =math.ceil( (height/sum) * windowfacts[1][i]), color = color.change_opacity(colors.gaps, 0.2588), align = true, }, 1 ) offset = offset + (height/sum * windowfacts[1][i]) + (2*gap) end 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(beautiful.fg_normal) 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 k=1, screen.count() do local s = screen[k] -- 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 + SCALE_FACTOR*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) -- Instead of adding origin offset everywhere, translate the viewport. cr:translate(tr_x, tr_y * SCALE_FACTOR) -- Draw the various areas. for k=1, screen.count() do local s = screen[k] cr:set_line_width(1.5) cr:set_dash({10,4},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. if args.draw_areas ~= false then draw_rulers(s) end -- Draw the wibar. for _, wibar in ipairs(args.draw_wibars or {}) do if wibar.screen == s then draw_struct(s, wibar, 'wibar', (k-1)*10, 'Wibar') end 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 if c.screen == s then 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, 0.0431) 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, 0.0431) end draw_client(s, c, 'tiling_client', (k-1)*10, label) end 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 master width factor gaps. if args.draw_mwfact then draw_mwfact(s) end -- Draw the (rows) width factor. if args.draw_wfact then draw_wfact(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