API changes for new_placement_cb and bug fixes.

This commit is contained in:
Xinhao Yuan 2021-02-28 09:32:19 -05:00
parent c8fa41f367
commit 07adcbdad0
4 changed files with 101 additions and 54 deletions

View File

@ -69,9 +69,9 @@ Use `local layout = machi.layout.create(args)` to instantiate the layout with an
- `default_cmd`: the command to use if there is no persistent history for this layout. - `default_cmd`: the command to use if there is no persistent history for this layout.
- `editor`: the editor used for the layout. The default is `machi.default_editor` (or `machi.editor.default_editor`). - `editor`: the editor used for the layout. The default is `machi.default_editor` (or `machi.editor.default_editor`).
- `new_placement_cb`: a callback `function(c, instance, areas, geometry)` that fits new client `c` into the areas. - `new_placement_cb`: a callback `function(c, instance, areas, geometry)` that fits new client `c` into the areas.
Returns whether the new client is in draft mode. This is a new and experimental feature. This is a new and experimental feature. The interface is subject to changes.
Either `name` or `name_func` must be set - others are optional. If `name` and `name_func` are both nil a default name function will be used, which splits the state based on the tag names.
The function is compatible with the previous `machi.layout.create(name, editor, default_cmd)` calls. The function is compatible with the previous `machi.layout.create(name, editor, default_cmd)` calls.
@ -80,16 +80,24 @@ For `new_placement_cb` the arguments are:
- `instance`: a layout and tag depedent table with the following fields available: - `instance`: a layout and tag depedent table with the following fields available:
- `cmd`: the current layout command. - `cmd`: the current layout command.
- `client_data`: a mapping from previously managed clients to their layout related settings and assigned areas. - `client_data`: a mapping from previously managed clients to their layout related settings and assigned areas.
Drafting windows are located using `.lu` and `.rd` fields, otherwise located uisng `.area` field; Drafting override is in `.draft` field. Each entry is a table with fields:
- `.placement`: If true, the client has been placed by the layout, otherwise `new_placement_cb` will be called on the client.
- `.area`: If it is non-nil, the window is fit in the area.
- `.lu`, `.rd`: If those are non-nil, the window is in draft mode and the fields are for the areas of its corners.
- `.draft`: if non-nil, this is the overriding perference of draft mode for the window.
Note that it may contains some clients that are no longer in the layout. You can filter using `screen.tiled_clients`. Note that it may contains some clients that are no longer in the layout. You can filter using `screen.tiled_clients`.
- `tag_data`: a mapping from area ids to their fake tag data. This is for nested layouts. - `tag_data`: a mapping from area ids to their fake tag data. This is for nested layouts.
- `areas`: the current array of areas produced by `instance.cmd`. Each area is a table with the following fields available: - `areas`: the current array of areas produced by `instance.cmd`. Each area is a table with the following fields available:
- `id`: self index of the array. - `id`: self index of the array.
- `inhabitable`: if true, the area is not for placing windows. It could be a parent area, or area disabled by command `/`.
- `x`, `y`, `width`, `height`: area geometry. - `x`, `y`, `width`, `height`: area geometry.
- `layout`: the string used to index the nested layout, if any. - `layout`: the string used to index the nested layout, if any.
- `geometry`: the output geometry of the client. - `geometry`: the output table the client geometry. Note that the geometry _includes_ the borders.
The callback places the new client by changing its geometry, and returns its draft perference for further area fitting. The callback places the new client by changing its geometry and client data.
Note that after the callback machi will validate the geometry and fit into the areas.
So no need to set the `.area`, `.lu`, or `.rd` field of the client data in the callback.
See `placement.fair` in `layout.lua` for an example.
## The layout editor and commands ## The layout editor and commands

View File

@ -2,14 +2,6 @@ local engine = require(... .. ".engine")
local layout = require(... .. ".layout") local layout = require(... .. ".layout")
local editor = require(... .. ".editor") local editor = require(... .. ".editor")
local switcher = require(... .. ".switcher") local switcher = require(... .. ".switcher")
local function default_name(tag)
if tag.machi_name_cache == nil then
tag.machi_name_cache =
tostring(tag.screen.geometry.width) .. "x" .. tostring(tag.screen.geometry.height) .. "+" ..
tostring(tag.screen.geometry.x) .. "+" .. tostring(tag.screen.geometry.y) .. '+' .. tag.name
end
return tag.machi_name_cache
end
local default_editor = editor.default_editor local default_editor = editor.default_editor
local default_layout = layout.create{ name_func = default_name } local default_layout = layout.create{ name_func = default_name }
local gcolor = require("gears.color") local gcolor = require("gears.color")
@ -34,7 +26,6 @@ return {
layout = layout, layout = layout,
editor = editor, editor = editor,
switcher = switcher, switcher = switcher,
default_name = default_name,
default_editor = default_editor, default_editor = default_editor,
default_layout = default_layout, default_layout = default_layout,
icon_raw = icon_raw, icon_raw = icon_raw,

View File

@ -91,10 +91,10 @@ local function find_lu(c, areas, rd)
return lu return lu
end end
local function find_rd(c, areas, lu) local function find_rd(c, border_width, areas, lu)
local x, y local x, y
x = c.x + c.width + (c.border_width or 0) x = c.x + c.width + (border_width or 0) * 2
y = c.y + c.height + (c.border_width or 0) y = c.y + c.height + (border_width or 0) * 2
local rd = nil local rd = nil
for i, a in ipairs(areas) do for i, a in ipairs(areas) do
if not a.inhabitable then if not a.inhabitable then
@ -121,6 +121,16 @@ function module.set_geometry(c, area_lu, area_rd, useless_gap, border_width)
end end
end end
function module.default_name_func(tag)
if tag.machi_name_cache == nil then
tag.machi_name_cache =
tostring(tag.screen.geometry.width) .. "x" .. tostring(tag.screen.geometry.height) .. "+" ..
tostring(tag.screen.geometry.x) .. "+" .. tostring(tag.screen.geometry.y) .. '+' .. tag.name
end
return tag.machi_name_cache
end
function module.create(args_or_name, editor, default_cmd) function module.create(args_or_name, editor, default_cmd)
local args local args
if type(args_or_name) == "string" then if type(args_or_name) == "string" then
@ -136,13 +146,8 @@ function module.create(args_or_name, editor, default_cmd)
else else
return nil return nil
end end
args.name = args.name or function (tag) if args.name == nil and args.name_func == nil then
if tag.machi_name_cache == nil then args.name_func = module.default_name_func
tag.machi_name_cache =
tostring(tag.screen.geometry.width) .. "x" .. tostring(tag.screen.geometry.height) .. "+" ..
tostring(tag.screen.geometry.x) .. "+" .. tostring(tag.screen.geometry.y) .. '+' .. tag.name
end
return tag.machi_name_cache
end end
args.editor = args.editor or editor or machi_editor.default_editor args.editor = args.editor or editor or machi_editor.default_editor
args.default_cmd = args.default_cmd or default_cmd or global_default_cmd args.default_cmd = args.default_cmd or default_cmd or global_default_cmd
@ -215,14 +220,16 @@ function module.create(args_or_name, editor, default_cmd)
if clients == nil then clients = {}; nested_clients[area] = clients end if clients == nil then clients = {}; nested_clients[area] = clients end
clients[#clients + 1] = c clients[#clients + 1] = c
else else
p.geometries[c] = {}
module.set_geometry(p.geometries[c], areas[area], areas[area], useless_gap, 0) module.set_geometry(p.geometries[c], areas[area], areas[area], useless_gap, 0)
end end
end end
-- Make new clients appear in the end. -- Make clients calling new_placement_cb appear in the end.
local j = 0 local j = 0
for i = 1, #cls do for i = 1, #cls do
if cd[cls[i]] ~= nil then cd[cls[i]] = cd[cls[i]] or {}
if cd[cls[i]].placement then
j = j + 1 j = j + 1
cls[j], cls[i] = cls[i], cls[j] cls[j], cls[i] = cls[i], cls[j]
end end
@ -232,8 +239,17 @@ function module.create(args_or_name, editor, default_cmd)
if c.floating or c.immobilized then if c.floating or c.immobilized then
log(DEBUG, "Ignore client " .. tostring(c)) log(DEBUG, "Ignore client " .. tostring(c))
else else
cd[c] = cd[c] or {} local geo = {
p.geometries[c] = {} x = c.x,
y = c.y,
width = c.width + c.border_width * 2,
height = c.height + c.border_width * 2,
}
if not cd[c].placement and new_placement_cb then
cd[c].placement = true
new_placement_cb(c, instance, areas, geo)
end
local in_draft = cd[c].draft local in_draft = cd[c].draft
if cd[c].draft ~= nil then if cd[c].draft ~= nil then
@ -242,27 +258,21 @@ function module.create(args_or_name, editor, default_cmd)
in_draft = true in_draft = true
elseif cd[c].area then elseif cd[c].area then
in_draft = false in_draft = false
elseif new_placement_cb then
in_draft = new_placement_cb(c, instance, areas, p.geometries[c])
else else
in_draft = nil in_draft = nil
end end
local skip = false local skip = false
local cx = p.geometries[c].x or c.x
local cy = p.geometries[c].y or c.y
local cw = p.geometries[c].w or c.width
local ch = p.geometries[c].h or c.height
if in_draft ~= false then if in_draft ~= false then
if cd[c].lu ~= nil and cd[c].rd ~= nil and if cd[c].lu ~= nil and cd[c].rd ~= nil and
cd[c].lu <= #areas and cd[c].rd <= #areas and cd[c].lu <= #areas and cd[c].rd <= #areas and
not areas[cd[c].lu].inhabitable and not areas[cd[c].rd].inhabitable not areas[cd[c].lu].inhabitable and not areas[cd[c].rd].inhabitable
then then
if areas[cd[c].lu].x == cx and if areas[cd[c].lu].x == geo.x and
areas[cd[c].lu].y == cy and areas[cd[c].lu].y == geo.y and
areas[cd[c].rd].x + areas[cd[c].rd].width - c.border_width * 2 == cx + cw and areas[cd[c].rd].x + areas[cd[c].rd].width == geo.x + geo.width and
areas[cd[c].rd].y + areas[cd[c].rd].height - c.border_width * 2 == cy + ch areas[cd[c].rd].y + areas[cd[c].rd].height == geo.y + geo.height
then then
skip = true skip = true
end end
@ -272,11 +282,11 @@ function module.create(args_or_name, editor, default_cmd)
local rd = nil local rd = nil
if not skip then if not skip then
log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">"))) log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
lu = find_lu(c, areas) lu = find_lu(geo, areas)
if lu ~= nil then if lu ~= nil then
cx = areas[lu].x geo.x = areas[lu].x
cy = areas[lu].y geo.y = areas[lu].y
rd = find_rd(c, areas, lu) rd = find_rd(geo, 0, areas, lu)
end end
end end
@ -288,26 +298,37 @@ function module.create(args_or_name, editor, default_cmd)
cd[c].lu = lu cd[c].lu = lu
cd[c].rd = rd cd[c].rd = rd
cd[c].area = nil cd[c].area = nil
p.geometries[c] = {}
module.set_geometry(p.geometries[c], areas[lu], areas[rd], useless_gap, 0) module.set_geometry(p.geometries[c], areas[lu], areas[rd], useless_gap, 0)
end end
end end
else else
if cd[c].area ~= nil and if cd[c].area ~= nil and
cd[c].area < #areas and cd[c].area <= #areas and
not areas[cd[c].area].inhabitable and not areas[cd[c].area].inhabitable and
areas[cd[c].area].layout == nil and areas[cd[c].area].layout == nil and
areas[cd[c].area].x == cx and areas[cd[c].area].x == geo.x and
areas[cd[c].area].y == cy and areas[cd[c].area].y == geo.y and
areas[cd[c].area].width - c.border_width * 2 == cw and areas[cd[c].area].width == geo.width and
areas[cd[c].area].height - c.border_width * 2 == ch areas[cd[c].area].height == geo.height
then then
skip = true
else else
log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">"))) log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
local area = find_area(c, areas) local area = find_area(geo, areas)
cd[c].area, cd[c].lu, cd[c].rd = area, nil, nil cd[c].area, cd[c].lu, cd[c].rd = area, nil, nil
place_client_in_area(c, area) place_client_in_area(c, area)
end end
end end
if skip then
if geo.x ~= c.x or geo.y ~= c.y or
geo.width ~= c.width + c.border_width * 2 or
geo.height ~= c.height + c.border_width * 2 then
p.geometries[c] = {}
module.set_geometry(p.geometries[c], geo, geo, useless_gap, 0)
end
end
end end
end end
@ -389,7 +410,7 @@ function module.create(args_or_name, editor, default_cmd)
hh.y = areas[lu].y hh.y = areas[lu].y
hh.width = c.full_width_before_move hh.width = c.full_width_before_move
hh.height = c.full_height_before_move hh.height = c.full_height_before_move
rd = find_rd(hh, areas, lu) rd = find_rd(hh, 0, areas, lu)
if rd ~= nil and not module.allowing_shrinking_by_mouse_moving and if rd ~= nil and not module.allowing_shrinking_by_mouse_moving and
(areas[rd].x + areas[rd].width - areas[lu].x < c.full_width_before_move or (areas[rd].x + areas[rd].width - areas[lu].x < c.full_width_before_move or
@ -441,8 +462,7 @@ function module.create(args_or_name, editor, default_cmd)
hh.y = h.y hh.y = h.y
hh.width = h.width hh.width = h.width
hh.height = h.height hh.height = h.height
hh.border_width = c.border_width rd = find_rd(hh, c.border_width, areas, lu)
rd = find_rd(hh, areas, lu)
end end
if lu ~= nil and rd ~= nil then if lu ~= nil and rd ~= nil then
@ -462,13 +482,42 @@ function module.create(args_or_name, editor, default_cmd)
end end
layout.name = args.icon_name or "machi" layout.name = args.icon_name or "machi"
layout.editor = args.editor
layout.arrange = arrange layout.arrange = arrange
layout.resize_handler = resize_handler layout.resize_handler = resize_handler
layout.machi_editor = args.editor
layout.machi_get_instance_info = get_instance_info layout.machi_get_instance_info = get_instance_info
layout.machi_get_instance_data = get_instance_data layout.machi_get_instance_data = get_instance_data
layout.machi_set_cmd = set_cmd layout.machi_set_cmd = set_cmd
return layout return layout
end end
module.placement = {}
function module.placement.fair(c, instance, areas, geometry)
local area_client_count = {}
for _, oc in ipairs(c.screen.tiled_clients) do
local cd = instance.client_data[oc]
if cd and cd.placement and cd.area then
area_client_count[cd.area] = (area_client_count[cd.area] or 0) + 1
end
end
local emptyness_max = nil
local choice = nil
for i = 1, #areas do
local a = areas[i]
if not a.inhabitable then
local emptyness = a.width * a.height / ((area_client_count[i] or 0) + 1)
if emptyness_max == nil or emptyness > emptyness_max then
emptyness_max = emptyness
choice = i
end
end
end
instance.client_data[c].area = choice
geometry.x = areas[choice].x
geometry.y = areas[choice].y
geometry.width = areas[choice].width
geometry.height = areas[choice].height
end
return module return module

View File

@ -554,8 +554,7 @@ function module.start(c, exit_keys)
} }
gtimer.delayed_call( gtimer.delayed_call(
function () function ()
print(layout.editor) layout.machi_editor.start_interactive(
layout.editor.start_interactive(
screen, screen,
{ {
workarea = workarea, workarea = workarea,