Draft mode

This commit is contained in:
Xinhao Yuan 2019-07-31 21:00:38 -04:00
parent 11c804266c
commit 752539bba5
5 changed files with 340 additions and 163 deletions

View File

@ -53,11 +53,12 @@ The editor is keyboard driven, each command is a key with optional digits (namel
1. `Up`/`Down`: restore to the history command sequence
2. `h`/`v`: split the current region horizontally/vertically into `#D` regions. The split will respect the ratio of digits in `D`.
3. `w`: Take the last two digits from `D` as `D = ...AB` (1 if `D` is shorter than 2 digits), and split the current region equally into A rows and B columns. If no digits are provided at all, behave the same as `Space`.
4. `s`: shift the current editing region with other open regions. If digits are provided, shift for that many times.
5. `Space` or `-`: Without parameters, close the current region and move to the next open region. With digits, set the maximum depth of splitting (the default depth is 2).
6. `Enter`/`.`: close all open regions. When all regions are closed, press `Enter` will save the layout and exit the editor.
7. `Backspace`: undo the last command.
8. `Escape`: exit the editor without saving the layout.
4. `d`: Take the argument in the format of `A0B`, where `A` and `B` do not contain any `0`, apply `h` with argument `A` unless `A` is shorter than 2 digits. On each splitted region, apply `v` with argument `B` unless `B` is shorter than 2 digit. Does nothing if the argument is ill-formed.
5. `s`: shift the current editing region with other open regions. If digits are provided, shift for that many times.
6. `Space` or `-`: Without parameters, close the current region and move to the next open region. With digits, set the maximum depth of splitting (the default depth is 2).
7. `Enter`/`.`: close all open regions. When all regions are closed, press `Enter` will save the layout and exit the editor.
8. `Backspace`: undo the last command.
9. `Escape`: exit the editor without saving the layout.
For examples:
@ -107,6 +108,32 @@ Tada!
```
`12210121d`
```
11 2222 3333 44
11 2222 3333 44
55 6666 7777 88
55 6666 7777 88
55 6666 7777 88
55 6666 7777 88
99 AAAA BBBB CC
99 AAAA BBBB CC
```
### Draft mode
__This mode is experimental. Its usage may change fast.__
Unlike the original machi layout, where a window fits in a single region, draft mode allows window to span across multiple regions.
Each tiled window is associated with a upper-left region (ULR) and a bottom-right region (BRR).
The geometry of the window is from the upper-left corner of the ULR to the bottom-right corner of the BRR.
This is suppose to work with regions produced with `d` command.
To enable draft mode in a layout, configure the layout with a command with a leading `d`, for example, `d12210121d`.
### Persistent history
By default, the last 100 command sequences are stored in `.cache/awesome/history_machi`.
@ -117,16 +144,12 @@ To change that, please refer to `editor.lua`. (XXX more documents)
Calling `machi.switcher.start()` will create a switcher supporting the following keys:
- Arrow keys: move focus into other regions by the direction.
- `Shift` + arrow keys: move the focused window to other regions by the direction.
- `Tab`: switch windows in the same regions.
- `Shift` + arrow keys: move the focused window to other regions by the direction. In draft mode, move the upper-left region by direction.
- `Control` + arrow keys: move the bottom-right region of the focused window by direction. Only work in draft mode.
- `Tab`: switch beteen windows covering the current regions.
So far, the key binding is not configurable. One has to modify the source code to change it.
## Other functions
`machi.editor.fit_region(c, cycle = false)` will fit a floating client into the closest region.
If `cycle` is true, it then moves the window by cycling all regions.
## Advanced
### `name` as a function in `machi.layout.create`
@ -142,10 +165,6 @@ To differentiate tags with the same name, you may need a more advanced naming fu
2. True transparency is required. Otherwise switcher and editor will block the clients.
## TODO
- Tabs on regions?
## License
Apache 2.0 --- See LICENSE

View File

@ -62,42 +62,6 @@ local function max(a, b)
if a < b then return b else return a end
end
local function set_region(c, r)
c.floating = false
c.maximized = false
c.fullscreen = false
c.machi_region = r
api.layout.arrange(c.screen)
end
--- fit the client into the machi of the screen
-- @param c the client to fit
-- @param cycle whether to cycle the region if the window is already in machi
-- @return whether any actions have been taken on the client
local function fit_region(c, cycle)
local layout = api.layout.get(c.screen)
local regions = layout.machi_get_regions and layout.machi_get_regions(c.screen.workarea, c.screen.selected_tag)
if type(regions) ~= "table" or #regions < 1 then
return false
end
local current_region = c.machi_region or 1
if not is_tiling(c) then
-- find out which region has the most intersection, calculated by a cap b / a cup b
c.machi_region = machi.layout.find_region(c, regions)
set_tiling(c)
elseif cycle then
if current_region >= #regions then
c.machi_region = 1
else
c.machi_region = current_region + 1
end
api.layout.arrange(c.screen)
else
return false
end
return true
end
local function _area_tostring(wa)
return "{x:" .. tostring(wa.x) .. ",y:" .. tostring(wa.y) .. ",w:" .. tostring(wa.width) .. ",h:" .. tostring(wa.height) .. "}"
end
@ -385,6 +349,64 @@ local function create(data)
open_areas[#open_areas + 1] = children[i]
end
elseif method == "d" then
local x_shares = {}
local y_shares = {}
local current = x_shares
for i = 1, #args do
local arg
if not alt then
arg = tonumber(args:sub(i, i))
else
arg = tonumber(args:sub(#args - i + 1, #args - i + 1))
end
if arg == 0 then
if current == x_shares then current = y_shares else break end
else
current[#current + 1] = arg
end
end
if #x_shares == 0 or #y_shares == 0 then
open_areas[#open_areas + 1] = a
return
end
x_shares = fair_split(a.width, x_shares)
y_shares = fair_split(a.height, y_shares)
local children = {}
for y_index = 1, #y_shares do
for x_index = 1, #x_shares do
local r = {
x = x_index == 1 and a.x or children[#children].x + children[#children].width,
y = y_index == 1 and a.y or (x_index == 1 and children[#children].y + children[#children].height or children[#children].y),
width = x_shares[x_index],
height = y_shares[y_index],
depth = a.depth + 1,
group_id = split_count,
}
if x_index == 1 then r.bl = a.bl else r.bl = false end
if x_index == #x_shares then r.br = a.br else r.br = false end
if y_index == 1 then r.bu = a.bu else r.bu = false end
if y_index == #y_shares then r.bd = a.bd else r.bd = false end
children[#children + 1] = r
end
end
for i = #children, 1, -1 do
if children[i].x ~= math.floor(children[i].x)
or children[i].y ~= math.floor(children[i].y)
or children[i].width ~= math.floor(children[i].width)
or children[i].height ~= math.floor(children[i].height)
then
print("warning, splitting yields floating area " .. _area_tostring(children[i]))
end
open_areas[#open_areas + 1] = children[i]
end
elseif method == "p" then
-- XXX
end
@ -407,6 +429,8 @@ local function create(data)
else
handle_split("w", key == "W")
end
elseif key == "d" or key == "D" then
handle_split("d", key == "D")
elseif key == "p" or key == "P" then
handle_split("p", key == "P")
elseif key == "s" or key == "S" then
@ -578,7 +602,6 @@ local function create(data)
infobox.bgimage = draw_info
end
print("interactive layout editing starts")
init(screen.workarea)
@ -760,8 +783,6 @@ end
return
{
set_region = set_region,
fit_region = fit_region,
create = create,
restore_data = restore_data,
}

View File

@ -1,17 +1,16 @@
local layout = require(... .. ".layout")
local editor = require(... .. ".editor")
local switcher = require(... .. ".switcher")
local default_editor = editor.create()
local default_layout = layout.create(
function (tag)
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, true
end,
default_editor)
end
local default_editor = editor.create()
local default_layout = layout.create(default_name, default_editor)
local gcolor = require("gears.color")
local beautiful = require("beautiful")

View File

@ -44,6 +44,42 @@ local function find_region(c, regions)
return choice
end
local function distance(x1, y1, x2, y2)
-- use d1
return math.abs(x1 - x2) + math.abs(y1 - y2)
end
local function find_lu(c, regions)
local lu = nil
for i, a in ipairs(regions) do
if lu == nil or distance(c.x, c.y, a.x, a.y) < distance(c.x, c.y, regions[lu].x, regions[lu].y) then
lu = i
end
end
return lu
end
local function find_rd(c, regions, lu)
assert(lu ~= nil)
local rd = nil
for i, a in ipairs(regions) do
if a.x + a.width > regions[lu].x and a.y + a.height > regions[lu].y then
if rd == nil or distance(c.x + c.width, c.y + c.height, a.x + a.width, a.y + a.height) < distance(c.x + c.width, c.y + c.height, regions[rd].x + regions[rd].width, regions[rd].y + regions[rd].height) then
rd = i
end
end
end
return rd
end
local function set_geometry(c, region_lu, region_rd, useless_gap, border_width)
-- We try to negate the gap of outer layer8
c.x = region_lu.x - useless_gap
c.y = region_lu.y - useless_gap
c.width = region_rd.x + region_rd.width - region_lu.x + (useless_gap - border_width) * 2
c.height = region_rd.y + region_rd.height - region_lu.y + (useless_gap - border_width) * 2
end
local function create(name, editor)
local instances = {}
@ -67,13 +103,13 @@ local function create(name, editor)
local function get_regions(workarea, tag)
local instance = get_instance(tag)
if instance.cmd == nil then return {} end
if instance.cmd == nil then return {}, false end
local key = tostring(workarea.width) .. "x" .. tostring(workarea.height) .. "+" .. tostring(workarea.x) .. "+" .. tostring(workarea.y)
if instance.regions_cache[key] == nil then
instance.regions_cache[key] = editor.run_cmd(workarea, instance.cmd)
end
return instance.regions_cache[key]
return instance.regions_cache[key], instance.cmd:sub(1,1) == "d"
end
local function set_cmd(cmd, tag)
@ -88,40 +124,95 @@ local function create(name, editor)
local useless_gap = p.useless_gap
local wa = get_screen(p.screen).workarea -- get the real workarea without the gap (instead of p.workarea)
local cls = p.clients
local regions = get_regions(wa, get_screen(p.screen).selected_tag)
local regions, draft_mode = get_regions(wa, get_screen(p.screen).selected_tag)
if #regions == 0 then return end
if draft_mode then
for i, c in ipairs(cls) do
if c.floating then
else
local skip = false
if c.machi_lu ~= nil and c.machi_rd ~= nil and
c.machi_lu <= #regions and c.machi_rd <= #regions
then
if regions[c.machi_lu].x == c.x and
regions[c.machi_lu].y == c.y and
regions[c.machi_rd].x + regions[c.machi_rd].width - c.border_width * 2 == c.x + c.width and
regions[c.machi_rd].y + regions[c.machi_rd].height - c.border_width * 2 == c.y + c.height
then
skip = true
end
end
local lu = nil
local rd = nil
if not skip then
print("Compute regions for " .. c.name)
lu = find_lu(c, regions)
if lu ~= nil then
rd = find_rd(c, regions, lu)
end
end
if lu ~= nil and rd ~= nil then
c.machi_lu, c.machi_rd = lu, rd
p.geometries[c] = {}
set_geometry(p.geometries[c], regions[lu], regions[rd], useless_gap, 0)
end
end
end
else
for i, c in ipairs(cls) do
if c.floating then
print("Ignore client " .. tostring(c))
else
if c.machi_region == nil then
c.machi_region = find_region(c, regions)
elseif c.machi_region > #regions then
c.machi_region = #regions
elseif c.machi_region <= 1 then
c.machi_region = 1
if c.machi_region ~= nil and
regions[c.machi_region].x == c.x and
regions[c.machi_region].y == c.y and
regions[c.machi_region].width - c.border_width * 2 == c.width and
regions[c.machi_region].height - c.border_width * 2 == c.height
then
else
print("Compute regions for " .. c.name)
local region = find_region(c, regions)
c.machi_region = region
p.geometries[c] = {}
set_geometry(p.geometries[c], regions[region], regions[region], useless_gap, 0)
end
end
local region = c.machi_region
-- Editor already handled useless_gap in the stored regions.
-- We try to negate the gap of outer layer.
p.geometries[c] = {
x = regions[region].x - useless_gap,
y = regions[region].y - useless_gap,
width = regions[region].width + useless_gap * 2,
height = regions[region].height + useless_gap * 2,
}
print("Put client " .. tostring(c) .. " to region " .. region)
end
end
end
-- move the closest region regardingly to the center distance
local function resize_handler (c, context, h)
local workarea = c.screen.workarea
local regions, draft_mode = get_regions(workarea, c.screen.selected_tag)
if #regions == 0 then return end
if draft_mode then
local lu = find_lu(h, regions)
local rd = nil
if lu ~= nil then
if context == "mouse.move" then
local hh = {}
hh.x = regions[lu].x
hh.y = regions[lu].y
hh.width = h.width
hh.height = h.height
rd = find_rd(hh, regions, lu)
else
rd = find_rd(h, regions, lu)
end
if rd ~= nil then
c.machi_lu = lu
c.machi_rd = rd
set_geometry(c, regions[lu], regions[rd], 0, c.border_width)
end
end
else
if context ~= "mouse.move" then return end
local workarea = c.screen.workarea
@ -147,10 +238,8 @@ local function create(name, editor)
if c.machi_region ~= choice then
c.machi_region = choice
c.x = regions[choice].x
c.y = regions[choice].y
c.width = max(1, regions[choice].width - 2 * c.border_width)
c.height = max(1, regions[choice].height - 2 * c.border_width)
set_geometry(c, regions[choice], regions[choice], 0, c.border_width)
end
end
end
@ -166,5 +255,5 @@ end
return {
create = create,
find_region = find_region,
set_geometry = set_geometry,
}

View File

@ -1,3 +1,7 @@
local machi = {
layout = require((...):match("(.-)[^%.]+$") .. "layout"),
}
local api = {
client = client,
beautiful = require("beautiful"),
@ -11,19 +15,6 @@ local api = {
dpi = require("beautiful.xresources").apply_dpi,
}
-- -- Seems not needed?
-- local focus_timer = 0
-- api.client.connect_signal(
-- "focus",
-- function (c)
-- if c.focus_timer == nil or c.focus_timer < focus_timer then
-- c.focus_timer = focus_timer
-- end
-- focus_timer = c.focus_timer + 1
-- c.focus_timer = focus_timer
-- end
-- )
local function min(a, b)
if a < b then return a else return b end
end
@ -59,7 +50,7 @@ local function start(c)
local layout = api.layout.get(screen)
if c.floating or layout.machi_get_regions == nil then return end
local regions = layout.machi_get_regions(c.screen.workarea, c.screen.selected_tag)
local regions, draft_mode = layout.machi_get_regions(c.screen.workarea, c.screen.selected_tag)
local infobox = api.wibox({
screen = screen,
@ -73,6 +64,7 @@ local function start(c)
})
infobox.visible = true
local tablist_region = nil
local tablist = nil
local tablist_index = nil
@ -83,14 +75,15 @@ local function start(c)
if tablist == nil then
tablist = {}
for _, tc in ipairs(screen.tiled_clients) do
if tc.machi_region == c.machi_region
and not tc.maximized
and not tc.maximized_horizontal
and not tc.maximized_vertical
if not (tc.floating or tc.maximized or tc.maximized_horizontal or tc.maximized_vertical)
then
if tc.x <= traverse_x and traverse_x < tc.x + tc.width and
tc.y <= traverse_y and traverse_y < tc.y + tc.height
then
tablist[#tablist + 1] = tc
end
end
end
tablist_index = 1
end
@ -108,7 +101,8 @@ local function start(c)
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
cr:clip()
if i == c.machi_region then
if a.x <= traverse_x and traverse_x < a.x + a.width and
a.y <= traverse_y and traverse_y < a.y + a.height then
local pl = api.lgi.Pango.Layout.create(cr)
pl:set_font_description(tablist_font_desc)
@ -132,7 +126,9 @@ local function start(c)
local x_offset = a.x + a.width / 2 - start_x
local y_offset = a.y + a.height / 2 - list_height / 2 + vpadding - start_y
cr:rectangle(a.x - start_x, y_offset - vpadding - start_y, a.width, list_height)
-- cr:rectangle(a.x - start_x, y_offset - vpadding - start_y, a.width, list_height)
-- cover the entire region
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
cr:set_source(fill_color)
cr:fill()
@ -192,10 +188,33 @@ local function start(c)
infobox.bgimage = draw_info
end
elseif key == "Up" or key == "Down" or key == "Left" or key == "Right" then
local shift = false
local ctrl = false
for i, m in ipairs(mod) do
if m == "Shift" then shift = true
elseif m == "Control" then ctrl = true
end
end
if shift then
traverse_x = c.x + traverse_radius
traverse_y = c.y + traverse_radius
elseif ctrl then
traverse_x = c.x + c.width - c.border_width * 2 - traverse_radius
traverse_y = c.y + c.height - c.border_width * 2 - traverse_radius
end
local choice = nil
local choice_value
local current_region = nil
for i, a in ipairs(regions) do
if a.x <= traverse_x and traverse_x < a.x + a.width and
a.y <= traverse_y and traverse_y < a.y + a.height
then
current_region = i
end
local v
if key == "Up" then
if a.x < traverse_x + threshold
@ -233,43 +252,73 @@ local function start(c)
end
end
if choice ~= nil and choice_value > threshold then
local shift = false
for i, m in ipairs(mod) do
if m == "Shift" then shift = true end
if choice == nil then
choice = current_region
if key == "Up" then
traverse_y = screen.workarea.y
elseif key == "Down" then
traverse_y = screen.workarea.y + screen.workarea.height
elseif key == "Left" then
traverse_x = screen.workarea.x
else
traverse_x = screen.workarea.x + screen.workarea.width
end
end
local move_traverse = false
if choice ~= nil then
if shift then
if draft_mode then
-- move the left-up region
local lu = choice
local rd = c.machi_rd
if regions[rd].x + regions[rd].width <= regions[lu].x or
regions[rd].y + regions[rd].height <= regions[lu].y
then
rd = lu
end
machi.layout.set_geometry(c, regions[lu], regions[rd], 0, c.border_width)
c.machi_lu = lu
c.machi_rd = rd
else
-- move the window
machi.layout.set_geometry(c, regions[choice], regions[choice], 0, c.border_width)
c.machi_region = choice
end
c:emit_signal("request::activate", "mouse.move", {raise=false})
c:raise()
api.layout.arrange(screen)
move_traverse = true
tablist = nil
elseif ctrl and draft_mode then
-- move the right-down region
local lu = c.machi_lu
local rd = choice
if regions[rd].x + regions[rd].width <= regions[lu].x or
regions[rd].y + regions[rd].height <= regions[lu].y
then
lu = rd
end
machi.layout.set_geometry(c, regions[lu], regions[rd], 0, c.border_width)
c.machi_lu = lu
c.machi_rd = rd
c:emit_signal("request::activate", "mouse.move", {raise=false})
c:raise()
api.layout.arrange(screen)
tablist = nil
else
-- move the focus
for _, tc in ipairs(screen.tiled_clients) do
if tc.machi_region == choice
and not tc.maximized
and not tc.maximized_horizontal
and not tc.maximized_vertical
then
c = tc
tablist = nil
ensure_tablist()
if #tablist > 0 and tablist[1] ~= c then
c = tablist[1]
api.client.focus = c
break
end
end
move_traverse = true
end
if move_traverse then
traverse_x = max(regions[choice].x + traverse_radius, min(regions[choice].x + regions[choice].width - traverse_radius, traverse_x))
traverse_y = max(regions[choice].y + traverse_radius, min(regions[choice].y + regions[choice].height - traverse_radius, traverse_y))
tablist = nil
end
infobox.bgimage = draw_info
end
elseif key == "Escape" or key == "Return" then