diff --git a/README.md b/README.md index cf47f01..a10264f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/init.lua b/init.lua index 30dad74..0074062 100644 --- a/init.lua +++ b/init.lua @@ -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, diff --git a/layout.lua b/layout.lua index 6b2e2a6..a9dda5c 100644 --- a/layout.lua +++ b/layout.lua @@ -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 (""))) - 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 (""))) - 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 diff --git a/switcher.lua b/switcher.lua index ff3ddf2..35e1fed 100644 --- a/switcher.lua +++ b/switcher.lua @@ -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,