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.
- `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.
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.
@ -80,16 +80,24 @@ For `new_placement_cb` the arguments are:
- `instance`: a layout and tag depedent table with the following fields available:
- `cmd`: the current layout command.
- `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`.
- `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:
- `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.
- `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

View File

@ -2,14 +2,6 @@ local engine = require(... .. ".engine")
local layout = require(... .. ".layout")
local editor = require(... .. ".editor")
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_layout = layout.create{ name_func = default_name }
local gcolor = require("gears.color")
@ -34,7 +26,6 @@ return {
layout = layout,
editor = editor,
switcher = switcher,
default_name = default_name,
default_editor = default_editor,
default_layout = default_layout,
icon_raw = icon_raw,

View File

@ -91,10 +91,10 @@ local function find_lu(c, areas, rd)
return lu
end
local function find_rd(c, areas, lu)
local function find_rd(c, border_width, areas, lu)
local x, y
x = c.x + c.width + (c.border_width or 0)
y = c.y + c.height + (c.border_width or 0)
x = c.x + c.width + (border_width or 0) * 2
y = c.y + c.height + (border_width or 0) * 2
local rd = nil
for i, a in ipairs(areas) do
if not a.inhabitable then
@ -121,6 +121,16 @@ function module.set_geometry(c, area_lu, area_rd, useless_gap, border_width)
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)
local args
if type(args_or_name) == "string" then
@ -136,13 +146,8 @@ function module.create(args_or_name, editor, default_cmd)
else
return nil
end
args.name = args.name or function (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
if args.name == nil and args.name_func == nil then
args.name_func = module.default_name_func
end
args.editor = args.editor or editor or machi_editor.default_editor
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
clients[#clients + 1] = c
else
p.geometries[c] = {}
module.set_geometry(p.geometries[c], areas[area], areas[area], useless_gap, 0)
end
end
-- Make new clients appear in the end.
-- Make clients calling new_placement_cb appear in the end.
local j = 0
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
cls[j], cls[i] = cls[i], cls[j]
end
@ -232,8 +239,17 @@ function module.create(args_or_name, editor, default_cmd)
if c.floating or c.immobilized then
log(DEBUG, "Ignore client " .. tostring(c))
else
cd[c] = cd[c] or {}
p.geometries[c] = {}
local geo = {
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
if cd[c].draft ~= nil then
@ -242,27 +258,21 @@ function module.create(args_or_name, editor, default_cmd)
in_draft = true
elseif cd[c].area then
in_draft = false
elseif new_placement_cb then
in_draft = new_placement_cb(c, instance, areas, p.geometries[c])
else
in_draft = nil
end
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 cd[c].lu ~= nil and cd[c].rd ~= nil and
cd[c].lu <= #areas and cd[c].rd <= #areas and
not areas[cd[c].lu].inhabitable and not areas[cd[c].rd].inhabitable
then
if areas[cd[c].lu].x == cx and
areas[cd[c].lu].y == cy and
areas[cd[c].rd].x + areas[cd[c].rd].width - c.border_width * 2 == cx + cw and
areas[cd[c].rd].y + areas[cd[c].rd].height - c.border_width * 2 == cy + ch
if areas[cd[c].lu].x == geo.x and
areas[cd[c].lu].y == geo.y 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 == geo.y + geo.height
then
skip = true
end
@ -272,11 +282,11 @@ function module.create(args_or_name, editor, default_cmd)
local rd = nil
if not skip then
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
cx = areas[lu].x
cy = areas[lu].y
rd = find_rd(c, areas, lu)
geo.x = areas[lu].x
geo.y = areas[lu].y
rd = find_rd(geo, 0, areas, lu)
end
end
@ -288,26 +298,37 @@ function module.create(args_or_name, editor, default_cmd)
cd[c].lu = lu
cd[c].rd = rd
cd[c].area = nil
p.geometries[c] = {}
module.set_geometry(p.geometries[c], areas[lu], areas[rd], useless_gap, 0)
end
end
else
if cd[c].area ~= nil and
cd[c].area < #areas and
cd[c].area <= #areas and
not areas[cd[c].area].inhabitable and
areas[cd[c].area].layout == nil and
areas[cd[c].area].x == cx and
areas[cd[c].area].y == cy and
areas[cd[c].area].width - c.border_width * 2 == cw and
areas[cd[c].area].height - c.border_width * 2 == ch
areas[cd[c].area].x == geo.x and
areas[cd[c].area].y == geo.y and
areas[cd[c].area].width == geo.width and
areas[cd[c].area].height == geo.height
then
skip = true
else
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
place_client_in_area(c, area)
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
@ -389,7 +410,7 @@ function module.create(args_or_name, editor, default_cmd)
hh.y = areas[lu].y
hh.width = c.full_width_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
(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.width = h.width
hh.height = h.height
hh.border_width = c.border_width
rd = find_rd(hh, areas, lu)
rd = find_rd(hh, c.border_width, areas, lu)
end
if lu ~= nil and rd ~= nil then
@ -462,13 +482,42 @@ function module.create(args_or_name, editor, default_cmd)
end
layout.name = args.icon_name or "machi"
layout.editor = args.editor
layout.arrange = arrange
layout.resize_handler = resize_handler
layout.machi_editor = args.editor
layout.machi_get_instance_info = get_instance_info
layout.machi_get_instance_data = get_instance_data
layout.machi_set_cmd = set_cmd
return layout
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

View File

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