Refactor the engine out of editor. Support adjustment with various tweaks. Need to reduce the diff...
This commit is contained in:
parent
a04e2d6e35
commit
40a20f08d2
76
README.md
76
README.md
|
@ -8,6 +8,23 @@ Draft mode: https://imgur.com/a/BOvMeQL
|
|||
|
||||
__Most of the development effort now happens in the ng branch, which introduces a few breaking changes but a ton of new features/enhancements.__
|
||||
|
||||
## Machi-ng
|
||||
|
||||
Machi-ng is a refactoring effort of machi with new features and enhancements.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
1. Added a max split (before merging) of 1,000 for all commands and a global cap of 10,000 areas.
|
||||
2. `t` command now applies to the current area and its further splits, instead of globally.
|
||||
3. `s` command now shifts inside the last group of pending areas that have the same parent, instead of all pending areas.
|
||||
|
||||
### New features & enhancements
|
||||
|
||||
1. Minimum area size.
|
||||
2. More tolerating "safer" error handling.
|
||||
3. Dynamic size adjustment with propagation.
|
||||
4. Editing non-global areas.
|
||||
|
||||
## Why?
|
||||
|
||||
TL;DR --- To bring back the control of the window layout.
|
||||
|
@ -46,6 +63,7 @@ Use `local layout = machi.layout.create(args)` to instantiate the layout with an
|
|||
|
||||
- `name`: the constant name of the layout.
|
||||
- `name_func`: a `function(t)` closure that returns a string for tag `t`. `name_func` overrides `name`.
|
||||
- `icon_name`: the "system" name used by Awesome to find the icon. The default value is `machi`.
|
||||
- `persistent`: whether to keep a history of the command for the layout. The default is `true`.
|
||||
- `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`).
|
||||
|
@ -59,19 +77,19 @@ The function is compatible with the previous `machi.layout.create(name, editor,
|
|||
### Starting editor in lua
|
||||
|
||||
Call `local editor = machi.editor.create()` to create an editor.
|
||||
To edit the layout `l` on screen `s`, call `editor.start_interactive(s, l)`.
|
||||
Calling it with no arguments would be the same as `editor.start_interactive(awful.screen.focused(), awful.layout.get(awful.screen.focused()))`.
|
||||
To edit the current machi layout on screen `s`, call `editor.start_interactive(s)`.
|
||||
Calling it with no arguments would be the same as `editor.start_interactive(awful.screen.focused())`.
|
||||
|
||||
### Basic usage
|
||||
|
||||
The editing command starts with the open region of the entire workarea, perform "operations" to split the current region into multiple sub-regions, then recursively edits each of them (by default, the maximum split depth is 2).
|
||||
The editing command starts with the open area of the entire workarea, perform "operations" to split the current area into multiple sub-areas, then recursively edits each of them (by default, the maximum split depth is 2).
|
||||
The layout is defined by a sequence of operations as a layout command.
|
||||
The layout editor allows users to interactively input their commands and shows the resulting layouts on screen, with the following auxiliary functions:
|
||||
|
||||
1. `Up`/`Down`: restore to the history command
|
||||
2. `Backspace`: undo the last command.
|
||||
2. `Backspace`: undo the last command. If the command is already empty, restores to the current (maybe transcoded) command of the layout.
|
||||
3. `Escape`: exit the editor without saving the layout.
|
||||
4. `Enter`: when all regions are defined, hit enter will save the layout.
|
||||
4. `Enter`: when all areas are defined, hit enter will save the layout. If shift is hold, only applies the command without saving it to the history.
|
||||
|
||||
### Layout command
|
||||
|
||||
|
@ -80,15 +98,24 @@ There are three kinds of operations:
|
|||
|
||||
1. Operations taking argument string and parsed as multiple numbers.
|
||||
|
||||
`h` horizontally split, `v` vertically split, `w` grid split, `d` draft split
|
||||
- `h`: horizontally split. Splits to two areas evenly without args.
|
||||
- `v`: vertically split. Splits to two areas evenly without args.
|
||||
- `w`: grid split. No splits without args.
|
||||
- `d`: draft split. No splits without args.
|
||||
|
||||
2. Operations taking argument string as a single number.
|
||||
2. Operations taking argument string as a single number or string.
|
||||
|
||||
`s` shift active region, `t` set the maximum split depth, `x` set the nested layout of the current region.
|
||||
- `s`: shift open areas within the same parent. Shifts one area without args.
|
||||
- `c`: finish the open areas within the same parent. Finishes all areas with the same parent without args.
|
||||
- `t`: set the number of further split of the curret area. Sets to the default (2) splits without args.
|
||||
- `x`: set the nested layout of the current area. Behaves like `-` without args.
|
||||
|
||||
3. Operation not taking argument.
|
||||
|
||||
`.` finish all regions, `-` finish the current region, `/` remove the current region, `;` no-op
|
||||
- `.`: finish all areas.
|
||||
- `-`: finish the current area
|
||||
- `/`: remove the current area
|
||||
- `;`: no-op
|
||||
|
||||
Argument strings are composed of numbers and `,`. If the string contains `,`, it will be used to split argument into multiple numbers.
|
||||
Otherwise, each digit in the string will be treated as a separated number in type 1 ops.
|
||||
|
@ -124,9 +151,9 @@ For examples:
|
|||
|
||||
Details:
|
||||
|
||||
- `131h`: horizontally split the initial region (entire desktop) to the ratio of 1:3:1
|
||||
- `131h`: horizontally split the initial area (entire desktop) to the ratio of 1:3:1
|
||||
- For the first `1` part:
|
||||
- `2v`: vertically split the region to the ratio of 2:1
|
||||
- `2v`: vertically split the area to the ratio of 2:1
|
||||
- `-`: skip the editing of the middle `3` part
|
||||
- For the right `1` part:
|
||||
- `12v`: split the right part vertically to the ratio of 1:2
|
||||
|
@ -229,15 +256,17 @@ Final merge, size 3x1, `w443113132231`:
|
|||
2 4-4-4
|
||||
```
|
||||
|
||||
`d` command works similarly after the inital grid is defined, such as `d1221012210221212121222`.
|
||||
|
||||
### Draft mode
|
||||
|
||||
__This mode is somewhat usable, yet it may change in the future.__
|
||||
|
||||
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.
|
||||
Unlike the original machi layout, where a window fits in a single area, draft mode allows window to span across multiple areas.
|
||||
Each tiled window is associated with a upper-left area (UL) and a bottom-right area (BR).
|
||||
The geometry of the window is from the upper-left corner of the UL to the bottom-right corner of the BR.
|
||||
|
||||
This is suppose to work with regions produced with `d` or `w` operation.
|
||||
This is suppose to work with areas produced with `d` or `w` operation.
|
||||
To enable draft mode in a layout, configure the layout with a command with a leading `d`, for example, `d12210121`, or `dw66`.
|
||||
|
||||
### Nested layouts
|
||||
|
@ -253,7 +282,7 @@ Known caveats include:
|
|||
__This feature is not available in draft mode.__
|
||||
|
||||
To set up nested layouts, you first need to check/modify `machi.editor.nested_layouts` array, which maps an argument string (`[0-9,]+`) to a layout object.
|
||||
In machi command, use the argument string with command `x` will set up the nested layout of the region to the mapped one.
|
||||
In machi command, use the argument string with command `x` will set up the nested layout of the area to the mapped one.
|
||||
|
||||
For example, since by default `machi.editor.nested_layouts["0"]` is `awful.layout.suit.tile` and `machi.editor.nested_layouts["1"]` is `awful.layout.suit.spiral`,
|
||||
the command `11h0x1x` will split the screen horizontally and apply the layouts accordingly - see the figure below.
|
||||
|
@ -269,18 +298,19 @@ 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. In draft mode, move the window while preserving its size.
|
||||
- `Control`[ + `Shift`] + arrow keys: move the bottom-right (or top-left window if `Shift` is pressed) region of the focused window by direction. Only works in draft mode.
|
||||
- `Tab`: switch beteen windows covering the current regions.
|
||||
- Arrow keys: move focus into other areas by the direction.
|
||||
- `Shift` + arrow keys: move the focused window to other areas by the direction. In draft mode, move the window while preserving its size.
|
||||
- `Control`[ + `Shift`] + arrow keys: move the bottom-right (or top-left window if `Shift` is pressed) area of the focused window by direction. Only works in draft mode.
|
||||
- `Tab`: switch beteen windows covering the current areas.
|
||||
- `u` or `PageUp` (`Prior`): In non-draft mode, you can select the parent of the current area.
|
||||
- `/`: In non-draft mode, this opens the editor to edit the selected area using the same command interpretation.
|
||||
Note the final command may be transcoded to be embeddable, but the areas shall be the same.
|
||||
|
||||
So far, the key binding is not configurable. One has to modify the source code to change it.
|
||||
|
||||
## Caveats
|
||||
|
||||
1. layout-machi handles `beautiful.useless_gap` slightly differently.
|
||||
|
||||
2. A compositor (e.g. picom, compton, xcompmgr) is required. Otherwise switcher and editor will block the clients.
|
||||
A compositor (e.g. picom, compton, xcompmgr) is required. Otherwise switcher and editor will block the clients.
|
||||
|
||||
## License
|
||||
|
||||
|
|
1035
editor.lua
1035
editor.lua
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,937 @@
|
|||
-- area {
|
||||
-- x, y, width, height
|
||||
-- parent_id
|
||||
-- parent_cid
|
||||
-- parent_x_shares
|
||||
-- parent_y_shares
|
||||
-- inhabitable
|
||||
-- hole (unique)
|
||||
-- draft_mode (root only)
|
||||
-- }
|
||||
--
|
||||
-- split {
|
||||
-- method
|
||||
-- x_shares
|
||||
-- y_shares
|
||||
-- children
|
||||
-- }
|
||||
--
|
||||
-- share {weight, adjustment, dynamic, minimum}
|
||||
local in_module = ...
|
||||
|
||||
-- Split a length by `measures`, such that each split respect the
|
||||
-- weight [1], adjustment (user [2] + engine [3]) without breaking the minimum size [4].
|
||||
--
|
||||
-- The split algorithm has a worst case of O(n^2) where n = #shares,
|
||||
-- which should be fine for practical usage of screen partitions.
|
||||
-- Using geometric algorithm this can be optimized to O(n log n), but
|
||||
-- I don't think it is worth.
|
||||
|
||||
-- Returns two values:
|
||||
-- 1. the (accumulative) result if it is possible to give every share its minimum size, otherwise nil.
|
||||
-- 2. any spare space to adjust without capping any share.
|
||||
local function fair_split(length, shares)
|
||||
local ret = {}
|
||||
local normalized_adj = nil
|
||||
local sum_weight
|
||||
local sum_adj
|
||||
local remaining = #shares
|
||||
local spare = nil
|
||||
repeat
|
||||
local need_recompute = false
|
||||
|
||||
sum_weight = 0
|
||||
sum_adj = 0
|
||||
for i = 1, #shares do
|
||||
if ret[i] == nil then
|
||||
sum_weight = sum_weight + shares[i][1]
|
||||
if normalized_adj then
|
||||
sum_adj = sum_adj + normalized_adj[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if normalized_adj == nil then
|
||||
normalized_adj = {}
|
||||
for i = 1, #shares do
|
||||
if sum_weight > shares[i][1] then
|
||||
normalized_adj[i] = ((shares[i][2] or 0) + (shares[i][3] or 0)) * sum_weight / (sum_weight - shares[i][1])
|
||||
else
|
||||
normalized_adj[i] = 0
|
||||
end
|
||||
sum_adj = sum_adj + normalized_adj[i]
|
||||
end
|
||||
|
||||
for i = 1, #shares do
|
||||
local required = (shares[i][4] - normalized_adj[i]) * sum_weight / shares[i][1] + sum_adj
|
||||
if spare == nil or spare > length - required then
|
||||
spare = length - required
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local capped_length = 0
|
||||
for i = 1, #shares do
|
||||
if ret[i] == nil then
|
||||
local split = (length - sum_adj) * shares[i][1] / sum_weight + normalized_adj[i]
|
||||
if split < shares[i][4] then
|
||||
ret[i] = shares[i][4]
|
||||
capped_length = capped_length + shares[i][4]
|
||||
need_recompute = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
length = length - capped_length
|
||||
until not need_recompute
|
||||
|
||||
if #shares == 1 or spare < 0 then
|
||||
spare = 0
|
||||
end
|
||||
|
||||
if remaining == 0 then
|
||||
return nil, spare
|
||||
end
|
||||
|
||||
local acc_weight = 0
|
||||
local acc_adj = 0
|
||||
local acc_ret = 0
|
||||
for i = 1, #shares do
|
||||
if ret[i] == nil then
|
||||
acc_weight = acc_weight + shares[i][1]
|
||||
acc_adj = acc_adj + normalized_adj[i]
|
||||
ret[i] = remaining == 1 and length - acc_ret or math.floor((length - sum_adj) / sum_weight * acc_weight + acc_adj - acc_ret + 0.5)
|
||||
acc_ret = acc_ret + ret[i]
|
||||
remaining = remaining - 1
|
||||
end
|
||||
end
|
||||
|
||||
ret[0] = 0
|
||||
for i = 1, #shares do
|
||||
ret[i] = ret[i - 1] + ret[i]
|
||||
end
|
||||
|
||||
return ret, spare
|
||||
end
|
||||
|
||||
-- Static data
|
||||
|
||||
-- Command character info
|
||||
-- 3 for taking the arg string and an open area
|
||||
-- 2 for taking an open area
|
||||
-- 1 for taking nothing
|
||||
-- 0 for args
|
||||
local ch_info = {
|
||||
["h"] = 3, ["H"] = 3,
|
||||
["v"] = 3, ["V"] = 3,
|
||||
["w"] = 3, ["W"] = 3,
|
||||
["d"] = 3, ["D"] = 3,
|
||||
["s"] = 3,
|
||||
["t"] = 3,
|
||||
["c"] = 3,
|
||||
["x"] = 3,
|
||||
["-"] = 2,
|
||||
["/"] = 2,
|
||||
["."] = 1,
|
||||
[";"] = 1,
|
||||
["0"] = 0, ["1"] = 0, ["2"] = 0, ["3"] = 0, ["4"] = 0,
|
||||
["5"] = 0, ["6"] = 0, ["7"] = 0, ["8"] = 0, ["9"] = 0,
|
||||
["_"] = 0, [","] = 0,
|
||||
}
|
||||
|
||||
local function parse_arg_str(arg_str, default)
|
||||
local ret = {}
|
||||
local current = {}
|
||||
if #arg_str == 0 then return ret end
|
||||
local index = 1
|
||||
local split_mode = arg_str:find("[,_]") ~= nil
|
||||
|
||||
local p = index
|
||||
while index <= #arg_str do
|
||||
local ch = arg_str:sub(index, index)
|
||||
if split_mode then
|
||||
if ch == "_" then
|
||||
local r = tonumber(arg_str:sub(p, index - 1))
|
||||
if r == nil then
|
||||
current[#current + 1] = default
|
||||
else
|
||||
current[#current + 1] = r
|
||||
end
|
||||
p = index + 1
|
||||
elseif ch == "," then
|
||||
local r = tonumber(arg_str:sub(p, index - 1))
|
||||
if r == nil then
|
||||
current[#current + 1] = default
|
||||
else
|
||||
current[#current + 1] = r
|
||||
end
|
||||
ret[#ret + 1] = current
|
||||
current = {}
|
||||
p = index + 1
|
||||
end
|
||||
else
|
||||
local r = tonumber(ch)
|
||||
if r == nil then
|
||||
ret[#ret + 1] = {default}
|
||||
else
|
||||
ret[#ret + 1] = {r}
|
||||
end
|
||||
end
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
if split_mode then
|
||||
local r = tonumber(arg_str:sub(p, index - 1))
|
||||
if r == nil then
|
||||
current[#current + 1] = default
|
||||
else
|
||||
current[#current + 1] = r
|
||||
end
|
||||
ret[#ret + 1] = current
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
if not in_module then
|
||||
print("Testing parse_arg_str")
|
||||
local x = parse_arg_str("1234", 0)
|
||||
assert(#x == 4)
|
||||
assert(#x[1] == 1 and x[1][1] == 1)
|
||||
assert(#x[2] == 1 and x[2][1] == 2)
|
||||
assert(#x[3] == 1 and x[3][1] == 3)
|
||||
assert(#x[4] == 1 and x[4][1] == 4)
|
||||
local x = parse_arg_str("12_34_,", -1)
|
||||
assert(#x == 2)
|
||||
assert(#x[1] == 3 and x[1][1] == 12 and x[1][2] == 34 and x[1][3] == -1)
|
||||
assert(#x[2] == 1 and x[2][1] == -1)
|
||||
local x = parse_arg_str("12_34,56_,78_90_", -1)
|
||||
assert(#x == 3)
|
||||
assert(#x[1] == 2 and x[1][1] == 12 and x[1][2] == 34)
|
||||
assert(#x[2] == 2 and x[2][1] == 56 and x[2][2] == -1)
|
||||
assert(#x[3] == 3 and x[3][1] == 78 and x[3][2] == 90 and x[3][3] == -1)
|
||||
print("Passed.")
|
||||
end
|
||||
|
||||
local max_split = 1000
|
||||
local max_areas = 10000
|
||||
local default_expansion = 2
|
||||
|
||||
-- Execute a (partial) command, returns:
|
||||
-- 1. Closed areas: areas that will not be further partitioned by further input.
|
||||
-- 2. Open areas: areas that can be further partitioned.
|
||||
-- 3. Pending: if the command can take more argument into the last command.
|
||||
local function areas_from_command(command, workarea, minimum)
|
||||
local pending_op = nil
|
||||
local arg_str = ""
|
||||
local closed_areas = {}
|
||||
local open_areas
|
||||
local root = {
|
||||
expansion = default_expansion,
|
||||
x = workarea.x,
|
||||
y = workarea.y,
|
||||
width = workarea.width,
|
||||
height = workarea.height,
|
||||
bl = true,
|
||||
br = true,
|
||||
bu = true,
|
||||
bd = true,
|
||||
}
|
||||
|
||||
local function close_area()
|
||||
local a = open_areas[#open_areas]
|
||||
table.remove(open_areas, #open_areas)
|
||||
local i = #closed_areas + 1
|
||||
closed_areas[i] = a
|
||||
a.id = i
|
||||
return a, i
|
||||
end
|
||||
|
||||
local function push_open_areas(areas)
|
||||
for i = #areas, 1, -1 do
|
||||
open_areas[#open_areas + 1] = areas[i]
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_op(method)
|
||||
local l = method:lower()
|
||||
local alt = method ~= l
|
||||
method = l
|
||||
|
||||
if method == "h" or method == "v" then
|
||||
|
||||
local args = parse_arg_str(arg_str, 0)
|
||||
if #args == 0 then
|
||||
args = {{1}, {1}}
|
||||
elseif #args == 1 then
|
||||
args[2] = {1}
|
||||
end
|
||||
|
||||
local total = 0
|
||||
local shares = { }
|
||||
for i = 1, #args do
|
||||
local arg
|
||||
if not alt then
|
||||
arg = args[i]
|
||||
else
|
||||
arg = args[#args - i + 1]
|
||||
end
|
||||
if arg[2] == 0 and arg[3] then arg[2], arg[3] = -arg[3], nil end
|
||||
shares[i] = arg
|
||||
end
|
||||
|
||||
if #shares > max_split then
|
||||
return nil
|
||||
end
|
||||
|
||||
local a, area_index = close_area()
|
||||
a.inhabitable = true
|
||||
a.split = {
|
||||
method = method,
|
||||
x_shares = method == "h" and shares or {{1}},
|
||||
y_shares = method == "v" and shares or {{1}},
|
||||
children = {}
|
||||
}
|
||||
local children = a.split.children
|
||||
|
||||
if method == "h" then
|
||||
for i = 1, #a.split.x_shares do
|
||||
local child = {
|
||||
parent_id = area_index,
|
||||
parent_cid = #children + 1,
|
||||
parent_x_shares = #children + 1,
|
||||
parent_y_shares = 1,
|
||||
expansion = a.expansion - 1,
|
||||
|
||||
bl = i == 1 and a.bl or false,
|
||||
br = i == #a.split.x_shares and a.br or false,
|
||||
bu = a.bu,
|
||||
bd = a.bd,
|
||||
}
|
||||
children[#children + 1] = child
|
||||
end
|
||||
else
|
||||
for i = 1, #a.split.y_shares do
|
||||
local child = {
|
||||
parent_id = area_index,
|
||||
parent_cid = #children + 1,
|
||||
parent_x_shares = 1,
|
||||
parent_y_shares = #children + 1,
|
||||
expansion = a.expansion - 1,
|
||||
|
||||
bl = a.bl,
|
||||
br = a.br,
|
||||
bu = i == 1 and a.bu or false,
|
||||
bd = i == #a.split.y_shares and a.bd or false,
|
||||
}
|
||||
children[#children + 1] = child
|
||||
end
|
||||
end
|
||||
|
||||
push_open_areas(children)
|
||||
|
||||
elseif method == "w" or method == "d" then
|
||||
|
||||
local args = parse_arg_str(arg_str, 0)
|
||||
|
||||
local x_shares = {}
|
||||
local y_shares = {}
|
||||
local m_start = #args + 1
|
||||
|
||||
if method == "w" then
|
||||
if #args == 0 then
|
||||
args = {{1}, {1}}
|
||||
elseif #args == 1 then
|
||||
args[2] = {1}
|
||||
end
|
||||
|
||||
local x_shares_count, y_shares_count
|
||||
if alt then
|
||||
x_shares_count = args[2][1]
|
||||
y_shares_count = args[1][1]
|
||||
else
|
||||
x_shares_count = args[1][1]
|
||||
y_shares_count = args[2][1]
|
||||
end
|
||||
if x_shares_count < 1 then x_shares_count = 1 end
|
||||
if y_shares_count < 1 then y_shares_count = 1 end
|
||||
|
||||
if x_shares_count * y_shares_count > max_split then
|
||||
return nil
|
||||
end
|
||||
|
||||
for i = 1, x_shares_count do x_shares[i] = {1} end
|
||||
for i = 1, y_shares_count do y_shares[i] = {1} end
|
||||
|
||||
m_start = 3
|
||||
else
|
||||
local current = x_shares
|
||||
for i = 1, #args do
|
||||
if not alt then
|
||||
arg = args[i]
|
||||
else
|
||||
arg = args[#args - i + 1]
|
||||
end
|
||||
if arg[1] == 0 then
|
||||
if current == x_shares then current = y_shares else
|
||||
m_start = i + 1
|
||||
break
|
||||
end
|
||||
else
|
||||
if arg[2] == 0 and arg[3] then arg[2], arg[3] = -arg[3], nil end
|
||||
current[#current + 1] = arg
|
||||
end
|
||||
end
|
||||
|
||||
if #x_shares == 0 then
|
||||
x_shares = {{1}}
|
||||
end
|
||||
|
||||
if #y_shares == 0 then
|
||||
y_shares = {{1}}
|
||||
end
|
||||
|
||||
if #x_shares * #y_shares > max_split then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local a, area_index = close_area()
|
||||
a.inhabitable = true
|
||||
a.split = {
|
||||
method = method,
|
||||
x_shares = x_shares,
|
||||
y_shares = y_shares,
|
||||
children = {},
|
||||
}
|
||||
local children = {}
|
||||
|
||||
for y_index = 1, #a.split.y_shares do
|
||||
for x_index = 1, #a.split.x_shares do
|
||||
local r = {
|
||||
parent_id = area_index,
|
||||
-- parent_cid will be filled later.
|
||||
parent_x_shares = x_index,
|
||||
parent_y_shares = y_index,
|
||||
expansion = a.expansion - 1
|
||||
}
|
||||
if x_index == 1 then r.bl = a.bl else r.bl = false end
|
||||
if x_index == #a.split.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 == #a.split.y_shares then r.bd = a.bd else r.bd = false end
|
||||
children[#children + 1] = r
|
||||
end
|
||||
end
|
||||
|
||||
local merged_children = {}
|
||||
local start_index = 1
|
||||
for i = m_start, #args - 1, 2 do
|
||||
-- find the first index that is not merged
|
||||
while start_index <= #children and children[start_index] == false do
|
||||
start_index = start_index + 1
|
||||
end
|
||||
if start_index > #children or children[start_index] == false then
|
||||
break
|
||||
end
|
||||
local x = (start_index - 1) % #x_shares
|
||||
local y = math.floor((start_index - 1) / #x_shares)
|
||||
local w = args[i][1]
|
||||
local h = args[i + 1][1]
|
||||
if w < 1 then w = 1 end
|
||||
if h == nil or h < 1 then h = 1 end
|
||||
if alt then
|
||||
local tmp = w
|
||||
w = h
|
||||
h = tmp
|
||||
end
|
||||
if x + w > #x_shares then w = #x_shares - x end
|
||||
if y + h > #y_shares then h = #y_shares - y end
|
||||
local end_index = start_index
|
||||
for ty = y, y + h - 1 do
|
||||
local succ = true
|
||||
for tx = x, x + w - 1 do
|
||||
if children[ty * #x_shares + tx + 1] == false then
|
||||
succ = false
|
||||
break
|
||||
elseif ty == y then
|
||||
end_index = ty * #x_shares + tx + 1
|
||||
end
|
||||
end
|
||||
|
||||
if not succ then
|
||||
break
|
||||
elseif ty > y then
|
||||
end_index = ty * #x_shares + x + w
|
||||
end
|
||||
end
|
||||
|
||||
local function generate_range(s, e)
|
||||
local r = {} for i = s, e do r[#r+1] = i end return r
|
||||
end
|
||||
|
||||
local r = {
|
||||
bu = children[start_index].bu, bl = children[start_index].bl,
|
||||
bd = children[end_index].bd, br = children[end_index].br,
|
||||
|
||||
parent_id = area_index,
|
||||
-- parent_cid will be filled later.
|
||||
parent_x_shares = generate_range(children[start_index].parent_x_shares, children[end_index].parent_x_shares),
|
||||
parent_y_shares = generate_range(children[start_index].parent_y_shares, children[end_index].parent_y_shares),
|
||||
expansion = a.expansion - 1
|
||||
}
|
||||
merged_children[#merged_children + 1] = r
|
||||
|
||||
for ty = y, y + h - 1 do
|
||||
local succ = true
|
||||
for tx = x, x + w - 1 do
|
||||
local index = ty * #x_shares + tx + 1
|
||||
if index <= end_index then
|
||||
children[index] = false
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #merged_children do
|
||||
a.split.children[#a.split.children + 1] = merged_children[i]
|
||||
a.split.children[#a.split.children].parent_cid = #a.split.children
|
||||
end
|
||||
|
||||
-- clean up children, remove all `false'
|
||||
for i = 1, #children do
|
||||
if children[i] ~= false then
|
||||
a.split.children[#a.split.children + 1] = children[i]
|
||||
a.split.children[#a.split.children].parent_cid = #a.split.children
|
||||
end
|
||||
end
|
||||
|
||||
push_open_areas(a.split.children)
|
||||
|
||||
elseif method == "s" then
|
||||
|
||||
if #open_areas > 0 then
|
||||
local times = arg_str == "" and 1 or tonumber(arg_str)
|
||||
local t = {}
|
||||
local c = #open_areas
|
||||
local p = open_areas[c].parent_id
|
||||
while c > 0 and open_areas[c].parent_id == p do
|
||||
t[#t + 1] = open_areas[c]
|
||||
open_areas[c] = nil
|
||||
c = c - 1
|
||||
end
|
||||
for i = #t, 1, -1 do
|
||||
open_areas[c + 1] = t[(i + times - 1) % #t + 1]
|
||||
c = c + 1
|
||||
end
|
||||
end
|
||||
|
||||
elseif method == "t" then
|
||||
|
||||
if #open_areas > 0 then
|
||||
open_areas[#open_areas].expansion = tonumber(arg_str) or default_expansion
|
||||
end
|
||||
|
||||
elseif method == "x" then
|
||||
|
||||
local a = close_area()
|
||||
a.layout = arg_str
|
||||
|
||||
elseif method == "-" then
|
||||
|
||||
close_area()
|
||||
|
||||
elseif method == "." then
|
||||
|
||||
while #open_areas > 0 do
|
||||
close_area()
|
||||
end
|
||||
|
||||
elseif method == "c" then
|
||||
|
||||
local limit = tonumber(arg_str)
|
||||
if limit == nil or limit > #open_areas then
|
||||
limit = #open_areas
|
||||
end
|
||||
local p = open_areas[#open_areas].parent_id
|
||||
while limit > 0 and open_areas[#open_areas].parent_id == p do
|
||||
close_area()
|
||||
limit = limit - 1
|
||||
end
|
||||
|
||||
elseif method == "/" then
|
||||
|
||||
close_area().inhabitable = true
|
||||
|
||||
elseif method == ";" then
|
||||
|
||||
-- nothing
|
||||
|
||||
end
|
||||
|
||||
if #open_areas + #closed_areas > max_areas then
|
||||
return nil
|
||||
end
|
||||
|
||||
while #open_areas > 0 and open_areas[#open_areas].expansion <= 0 do
|
||||
close_area()
|
||||
end
|
||||
|
||||
arg_str = ""
|
||||
return true
|
||||
end
|
||||
|
||||
open_areas = {root}
|
||||
|
||||
for i = 1, #command do
|
||||
local ch = command:sub(i, i)
|
||||
local t = ch_info[ch]
|
||||
local r = true
|
||||
if t == nil then
|
||||
return nil
|
||||
elseif t == 3 then
|
||||
if pending_op ~= nil then
|
||||
r = handle_op(pending_op)
|
||||
pending_op = nil
|
||||
end
|
||||
if #open_areas == 0 then return nil end
|
||||
if arg_str == "" then
|
||||
pending_op = ch
|
||||
else
|
||||
r = handle_op(ch)
|
||||
end
|
||||
elseif t == 2 or t == 1 then
|
||||
if pending_op ~= nil then
|
||||
handle_op(pending_op)
|
||||
pending_op = nil
|
||||
end
|
||||
if #open_areas == 0 and t == 2 then return nil end
|
||||
r = handle_op(ch)
|
||||
elseif t == 0 then
|
||||
arg_str = arg_str..ch
|
||||
end
|
||||
|
||||
if not r then return nil end
|
||||
end
|
||||
|
||||
if pending_op ~= nil then
|
||||
if not handle_op(pending_op) then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if #closed_areas == 0 then
|
||||
return closed_areas, open_areas, pending_op ~= nil
|
||||
end
|
||||
|
||||
local old_closed_areas = closed_areas
|
||||
closed_areas = {}
|
||||
local function reorder_and_fill_adj_min(old_id)
|
||||
local a = old_closed_areas[old_id]
|
||||
closed_areas[#closed_areas + 1] = a
|
||||
a.id = #closed_areas
|
||||
|
||||
if a.split then
|
||||
for i = 1, #a.split.x_shares do
|
||||
a.split.x_shares[i][3] = 0
|
||||
a.split.x_shares[i][4] = minimum
|
||||
end
|
||||
|
||||
for i = 1, #a.split.y_shares do
|
||||
a.split.y_shares[i][3] = 0
|
||||
a.split.y_shares[i][4] = minimum
|
||||
end
|
||||
|
||||
for _, c in ipairs(a.split.children) do
|
||||
if c.id then
|
||||
reorder_and_fill_adj_min(c.id)
|
||||
end
|
||||
|
||||
local x_minimum, y_minimum
|
||||
if c.split then
|
||||
x_minimum, y_minimum = c.x_minimum, c.y_minimum
|
||||
else
|
||||
x_minimum, y_minimum =
|
||||
minimum, minimum
|
||||
end
|
||||
|
||||
if type(c.parent_x_shares) == "table" then
|
||||
local x_minimum_split = math.ceil(x_minimum / #c.parent_x_shares)
|
||||
for i = 1, #c.parent_x_shares do
|
||||
if a.split.x_shares[c.parent_x_shares[i]][4] < x_minimum_split then
|
||||
a.split.x_shares[c.parent_x_shares[i]][4] = x_minimum_split
|
||||
end
|
||||
end
|
||||
else
|
||||
if a.split.x_shares[c.parent_x_shares][4] < x_minimum then
|
||||
a.split.x_shares[c.parent_x_shares][4] = x_minimum
|
||||
end
|
||||
end
|
||||
|
||||
if type(c.parent_y_shares) == "table" then
|
||||
local y_minimum_split = math.ceil(y_minimum / #c.parent_y_shares)
|
||||
for i = 1, #c.parent_y_shares do
|
||||
if a.split.y_shares[c.parent_y_shares[i]][4] < y_minimum_split then
|
||||
a.split.y_shares[c.parent_y_shares[i]][4] = y_minimum_split
|
||||
end
|
||||
end
|
||||
else
|
||||
if a.split.y_shares[c.parent_y_shares][4] < y_minimum then
|
||||
a.split.y_shares[c.parent_y_shares][4] = y_minimum
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
a.x_minimum = 0
|
||||
a.x_total_weight = 0
|
||||
for i = 1, #a.split.x_shares do
|
||||
a.x_minimum = a.x_minimum + a.split.x_shares[i][4]
|
||||
a.x_total_weight = a.x_total_weight + (a.split.x_shares[i][2] or 0)
|
||||
end
|
||||
a.y_minimum = 0
|
||||
a.y_total_weight = 0
|
||||
for i = 1, #a.split.y_shares do
|
||||
a.y_minimum = a.y_minimum + a.split.y_shares[i][4]
|
||||
a.y_total_weight = a.y_total_weight + (a.split.y_shares[i][2] or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
reorder_and_fill_adj_min(1)
|
||||
|
||||
-- For debugging
|
||||
-- for i = 1, #closed_areas do
|
||||
-- print(i, closed_areas[i].parent_id, closed_areas[i].parent_x_shares, closed_areas[i].parent_y_shares)
|
||||
-- if closed_areas[i].split then
|
||||
-- print("/", closed_areas[i].split.method, #closed_areas[i].split.x_shares, #closed_areas[i].split.y_shares)
|
||||
-- for j = 1, #closed_areas[i].split.children do
|
||||
-- print("->", closed_areas[i].split.children[j].id)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
local orig_width = root.width
|
||||
if root.x_minimum and root.width < root.x_minimum then
|
||||
root.width = root.x_minimum
|
||||
end
|
||||
local orig_height = root.height
|
||||
if root.y_minimum and root.height < root.y_minimum then
|
||||
root.height = root.y_minimum
|
||||
end
|
||||
|
||||
function split(id)
|
||||
local a = closed_areas[id]
|
||||
if a.split then
|
||||
local x_shares, y_shares
|
||||
x_shares, a.split.x_spare = fair_split(a.width, a.split.x_shares)
|
||||
y_shares, a.split.y_spare = fair_split(a.height, a.split.y_shares)
|
||||
|
||||
for _, c in ipairs(a.split.children) do
|
||||
|
||||
if type(c.parent_x_shares) == "table" then
|
||||
c.x = a.x + x_shares[c.parent_x_shares[1] - 1]
|
||||
c.width = 0
|
||||
for i = 1, #c.parent_x_shares do
|
||||
c.width = c.width + x_shares[c.parent_x_shares[i]] - x_shares[c.parent_x_shares[i] - 1]
|
||||
end
|
||||
else
|
||||
c.x = a.x + x_shares[c.parent_x_shares - 1]
|
||||
c.width = x_shares[c.parent_x_shares] - x_shares[c.parent_x_shares - 1]
|
||||
end
|
||||
|
||||
if type(c.parent_y_shares) == "table" then
|
||||
c.y = a.y + y_shares[c.parent_y_shares[1] - 1]
|
||||
c.height = 0
|
||||
for i = 1, #c.parent_y_shares do
|
||||
c.height = c.height + y_shares[c.parent_y_shares[i]] - y_shares[c.parent_y_shares[i] - 1]
|
||||
end
|
||||
else
|
||||
c.y = a.y + y_shares[c.parent_y_shares - 1]
|
||||
c.height = y_shares[c.parent_y_shares] - y_shares[c.parent_y_shares - 1]
|
||||
end
|
||||
|
||||
if c.id then
|
||||
split(c.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
split(1)
|
||||
|
||||
-- Compatibility workaround.
|
||||
if command:sub(1, 1) == "d" then
|
||||
root.draft_mode = true
|
||||
end
|
||||
|
||||
for i = 1, #closed_areas do
|
||||
if closed_areas[i].x + closed_areas[i].width > root.x + orig_width or
|
||||
closed_areas[i].y + closed_areas[i].height > root.y + orig_height
|
||||
then
|
||||
closed_areas[i].inhabitable = true
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #open_areas do
|
||||
if open_areas[i].x + open_areas[i].width > root.x + orig_width or
|
||||
open_areas[i].y + open_areas[i].height > root.y + orig_height
|
||||
then
|
||||
open_areas[i].inhabitable = true
|
||||
end
|
||||
end
|
||||
|
||||
return closed_areas, open_areas, pending_op ~= nil
|
||||
end
|
||||
|
||||
local function areas_to_command(areas, to_embed)
|
||||
if #areas == 0 then return nil end
|
||||
|
||||
local function shares_to_arg_str(shares)
|
||||
local arg_str = ""
|
||||
for _, share in ipairs(shares) do
|
||||
if #arg_str > 0 then arg_str = arg_str.."," end
|
||||
arg_str = arg_str..tostring(share[1])
|
||||
if not share[2] then
|
||||
-- nothing
|
||||
elseif share[2] > 0 then
|
||||
arg_str = arg_str.."_"..tostring(share[2])
|
||||
else
|
||||
arg_str = arg_str.."__"..tostring(-share[2])
|
||||
end
|
||||
end
|
||||
return arg_str
|
||||
end
|
||||
|
||||
local function get_command(area_id)
|
||||
local r
|
||||
local handled_options = {}
|
||||
local a = areas[area_id]
|
||||
|
||||
if a.hole then
|
||||
return "|"
|
||||
end
|
||||
|
||||
if a.split then
|
||||
for i = 1, #a.split.children do
|
||||
if a.split.children[i].hole then
|
||||
a.expansion = default_expansion + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local method = a.split.method
|
||||
if method == "h" then
|
||||
r = shares_to_arg_str(a.split.x_shares)
|
||||
r = "h"..r
|
||||
elseif method == "v" then
|
||||
r = shares_to_arg_str(a.split.y_shares)
|
||||
r = "v"..r
|
||||
elseif method == "d" or method == "w" then
|
||||
local simple = true
|
||||
for _, s in ipairs(a.split.x_shares) do
|
||||
if s[1] ~= 1 or s[2] then simple = false break end
|
||||
end
|
||||
if simple then
|
||||
for _, s in ipairs(a.split.y_shares) do
|
||||
if s[1] ~= 1 or s[2] then simple = false break end
|
||||
end
|
||||
end
|
||||
if method == "w" and simple then
|
||||
r = tostring(#a.split.x_shares)..","..tostring(#a.split.y_shares)
|
||||
else
|
||||
r = shares_to_arg_str(a.split.x_shares)..",,"..shares_to_arg_str(a.split.y_shares)
|
||||
method = "d"
|
||||
end
|
||||
local m = ""
|
||||
for _, c in ipairs(a.split.children) do
|
||||
if type(c.parent_x_shares) == "table" then
|
||||
if #m > 0 then m = m.."," end
|
||||
m = m..tostring(c.parent_x_shares[#c.parent_x_shares] - c.parent_x_shares[1] + 1)..","..
|
||||
tostring(c.parent_y_shares[#c.parent_y_shares] - c.parent_y_shares[1] + 1)
|
||||
end
|
||||
end
|
||||
if method == "d" and r == "1,,1" then
|
||||
r = ""
|
||||
end
|
||||
if method == "d" and area_id == 1 then
|
||||
r = (areas[1].draft_mode and "d" or ";d")..r
|
||||
else
|
||||
r = method..r..(#m == 0 and m or (method == "w" and "," or ",,"))..m
|
||||
end
|
||||
end
|
||||
local acc_dashes = 0
|
||||
for _, c in ipairs(a.split.children) do
|
||||
local cr = get_command(c.id)
|
||||
if cr == "-" then
|
||||
acc_dashes = acc_dashes + 1
|
||||
else
|
||||
if acc_dashes == 0 then
|
||||
elseif acc_dashes == 1 then
|
||||
r = r.."-"
|
||||
else
|
||||
r = r.."c"..tonumber(acc_dashes)
|
||||
end
|
||||
acc_dashes = 0
|
||||
r = r..cr
|
||||
end
|
||||
end
|
||||
if acc_dashes > 0 then
|
||||
r = r.."c"
|
||||
end
|
||||
elseif a.disabled then
|
||||
r = "/"
|
||||
elseif a.layout then
|
||||
r = "x"..a.layout
|
||||
else
|
||||
r = "-"
|
||||
end
|
||||
if a.split then
|
||||
if a.parent_id then
|
||||
if a.expansion ~= areas[a.parent_id].expansion - 1 then
|
||||
r = "t"..tostring(a.expansion)..r
|
||||
end
|
||||
else
|
||||
if a.expansion ~= default_expansion then
|
||||
r = "t"..tostring(a.expansion)..r
|
||||
end
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
local r = get_command(1)
|
||||
if not to_embed then
|
||||
r = r:gsub("[\\c]+$", ".")
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
if not in_module then
|
||||
print("Testing areas/command processing")
|
||||
local function check_transcoded_command(command, expectation)
|
||||
local areas, open_areas = areas_from_command(command, {x = 0, y = 0, width = 100, height = 100}, 0)
|
||||
if #open_areas > 0 then
|
||||
print("Found open areas after command "..command)
|
||||
assert(false)
|
||||
end
|
||||
local transcoded = areas_to_command(areas)
|
||||
if transcoded ~= expectation then
|
||||
print("Mismatched transcoding for "..command..": got "..transcoded..", expected "..expectation)
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
check_transcoded_command("t.", "-")
|
||||
check_transcoded_command("121h.", "h1,2,1.")
|
||||
check_transcoded_command("1_10,2,1h1s131v.", "h1_10,2,1-v1,3,1.")
|
||||
check_transcoded_command("332111w.", "w3,3,2,1,1,1.")
|
||||
check_transcoded_command("1310111d.", ";d1,3,1,,1,1,1.")
|
||||
check_transcoded_command("dw66.", "dw6,6.")
|
||||
check_transcoded_command(";dw66.", ";dw6,6.")
|
||||
check_transcoded_command("101dw66.", ";dw6,6.")
|
||||
check_transcoded_command("3tdw66.", "t3;dw6,6.")
|
||||
print("Passed.")
|
||||
end
|
||||
|
||||
return {
|
||||
areas_from_command = areas_from_command,
|
||||
areas_to_command = areas_to_command,
|
||||
}
|
3
init.lua
3
init.lua
|
@ -1,3 +1,4 @@
|
|||
local engine = require(... .. ".engine")
|
||||
local layout = require(... .. ".layout")
|
||||
local editor = require(... .. ".editor")
|
||||
local switcher = require(... .. ".switcher")
|
||||
|
@ -29,9 +30,11 @@ local function get_icon()
|
|||
end
|
||||
|
||||
return {
|
||||
engine = engine,
|
||||
layout = layout,
|
||||
editor = editor,
|
||||
switcher = switcher,
|
||||
default_name = default_name,
|
||||
default_editor = default_editor,
|
||||
default_layout = default_layout,
|
||||
icon_raw = icon_raw,
|
||||
|
|
646
layout.lua
646
layout.lua
|
@ -1,10 +1,8 @@
|
|||
local machi = {
|
||||
editor = require((...):match("(.-)[^%.]+$") .. "editor"),
|
||||
}
|
||||
|
||||
local api = {
|
||||
screen = screen,
|
||||
awful = require("awful"),
|
||||
local this_package = ... and (...):match("(.-)[^%.]+$") or ""
|
||||
local machi_editor = require(this_package.."editor")
|
||||
local awful = require("awful")
|
||||
local capi = {
|
||||
screen = screen
|
||||
}
|
||||
|
||||
local ERROR = 2
|
||||
|
@ -15,106 +13,112 @@ local DEBUG = -1
|
|||
local module = {
|
||||
log_level = WARNING,
|
||||
global_default_cmd = "dw66.",
|
||||
allowing_shrinking_by_mouse_moving = false,
|
||||
allow_shrinking_by_mouse_moving = false,
|
||||
}
|
||||
|
||||
local function log(level, msg)
|
||||
if level > module.log_level then
|
||||
print(msg)
|
||||
end
|
||||
if level > module.log_level then
|
||||
print(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function min(a, b)
|
||||
if a < b then return a else return b end
|
||||
if a < b then return a else return b end
|
||||
end
|
||||
|
||||
local function max(a, b)
|
||||
if a < b then return b else return a end
|
||||
if a < b then return b else return a end
|
||||
end
|
||||
|
||||
local function get_screen(s)
|
||||
return s and api.screen[s]
|
||||
return s and capi.screen[s]
|
||||
end
|
||||
|
||||
api.awful.mouse.resize.add_enter_callback(
|
||||
function (c)
|
||||
c.full_width_before_move = c.width + c.border_width * 2
|
||||
c.full_height_before_move = c.height + c.border_width * 2
|
||||
end, 'mouse.move')
|
||||
awful.mouse.resize.add_enter_callback(
|
||||
function (c)
|
||||
c.full_width_before_move = c.width + c.border_width * 2
|
||||
c.full_height_before_move = c.height + c.border_width * 2
|
||||
end, 'mouse.move')
|
||||
|
||||
--- find the best region for the area-like object
|
||||
--- find the best area for the area-like object
|
||||
-- @param c area-like object - table with properties x, y, width, and height
|
||||
-- @param regions array of area-like objects
|
||||
-- @return the index of the best region
|
||||
local function find_region(c, regions)
|
||||
local choice = 1
|
||||
local choice_value = nil
|
||||
local c_area = c.width * c.height
|
||||
for i, a in ipairs(regions) do
|
||||
local x_cap = max(0, min(c.x + c.width, a.x + a.width) - max(c.x, a.x))
|
||||
local y_cap = max(0, min(c.y + c.height, a.y + a.height) - max(c.y, a.y))
|
||||
local cap = x_cap * y_cap
|
||||
-- -- a cap b / a cup b
|
||||
-- local cup = c_area + a.width * a.height - cap
|
||||
-- if cup > 0 then
|
||||
-- local itx_ratio = cap / cup
|
||||
-- if choice_value == nil or choice_value < itx_ratio then
|
||||
-- choice_value = itx_ratio
|
||||
-- choice = i
|
||||
-- end
|
||||
-- end
|
||||
-- a cap b
|
||||
if choice_value == nil or choice_value < cap then
|
||||
choice = i
|
||||
choice_value = cap
|
||||
end
|
||||
end
|
||||
return choice
|
||||
-- @param areas array of area objects
|
||||
-- @return the index of the best area
|
||||
local function find_area(c, areas)
|
||||
local choice = 1
|
||||
local choice_value = nil
|
||||
local c_area = c.width * c.height
|
||||
for i, a in ipairs(areas) do
|
||||
if not a.inhabitable then
|
||||
local x_cap = max(0, min(c.x + c.width, a.x + a.width) - max(c.x, a.x))
|
||||
local y_cap = max(0, min(c.y + c.height, a.y + a.height) - max(c.y, a.y))
|
||||
local cap = x_cap * y_cap
|
||||
-- -- a cap b / a cup b
|
||||
-- local cup = c_area + a.width * a.height - cap
|
||||
-- if cup > 0 then
|
||||
-- local itx_ratio = cap / cup
|
||||
-- if choice_value == nil or choice_value < itx_ratio then
|
||||
-- choice_value = itx_ratio
|
||||
-- choice = i
|
||||
-- end
|
||||
-- end
|
||||
-- a cap b
|
||||
if choice_value == nil or choice_value < cap then
|
||||
choice = i
|
||||
choice_value = cap
|
||||
end
|
||||
end
|
||||
end
|
||||
return choice
|
||||
end
|
||||
|
||||
local function distance(x1, y1, x2, y2)
|
||||
-- use d1
|
||||
return math.abs(x1 - x2) + math.abs(y1 - y2)
|
||||
-- use d1
|
||||
return math.abs(x1 - x2) + math.abs(y1 - y2)
|
||||
end
|
||||
|
||||
local function find_lu(c, regions, rd)
|
||||
local lu = nil
|
||||
for i, a in ipairs(regions) do
|
||||
if rd == nil or (a.x < regions[rd].x + regions[rd].width and a.y < regions[rd].y + regions[rd].height) then
|
||||
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
|
||||
end
|
||||
return lu
|
||||
local function find_lu(c, areas, rd)
|
||||
local lu = nil
|
||||
for i, a in ipairs(areas) do
|
||||
if not a.inhabitable then
|
||||
if rd == nil or (a.x < areas[rd].x + areas[rd].width and a.y < areas[rd].y + areas[rd].height) then
|
||||
if lu == nil or distance(c.x, c.y, a.x, a.y) < distance(c.x, c.y, areas[lu].x, areas[lu].y) then
|
||||
lu = i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return lu
|
||||
end
|
||||
|
||||
local function find_rd(c, regions, lu)
|
||||
local x, y
|
||||
x = c.x + c.width + (c.border_width or 0)
|
||||
y = c.y + c.height + (c.border_width or 0)
|
||||
local rd = nil
|
||||
for i, a in ipairs(regions) do
|
||||
if lu == nil or (a.x + a.width > regions[lu].x and a.y + a.height > regions[lu].y) then
|
||||
if rd == nil or distance(x, y, a.x + a.width, a.y + a.height) < distance(x, y, regions[rd].x + regions[rd].width, regions[rd].y + regions[rd].height) then
|
||||
rd = i
|
||||
end
|
||||
end
|
||||
end
|
||||
return rd
|
||||
local function find_rd(c, 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)
|
||||
local rd = nil
|
||||
for i, a in ipairs(areas) do
|
||||
if not a.inhabitable then
|
||||
if lu == nil or (a.x + a.width > areas[lu].x and a.y + a.height > areas[lu].y) then
|
||||
if rd == nil or distance(x, y, a.x + a.width, a.y + a.height) < distance(x, y, areas[rd].x + areas[rd].width, areas[rd].y + areas[rd].height) then
|
||||
rd = i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return rd
|
||||
end
|
||||
|
||||
function module.set_geometry(c, region_lu, region_rd, useless_gap, border_width)
|
||||
-- We try to negate the gap of outer layer
|
||||
if region_lu ~= nil then
|
||||
c.x = region_lu.x - useless_gap
|
||||
c.y = region_lu.y - useless_gap
|
||||
end
|
||||
function module.set_geometry(c, area_lu, area_rd, useless_gap, border_width)
|
||||
-- We try to negate the gap of outer layer
|
||||
if area_lu ~= nil then
|
||||
c.x = area_lu.x - useless_gap
|
||||
c.y = area_lu.y - useless_gap
|
||||
end
|
||||
|
||||
if region_rd ~= nil then
|
||||
c.width = region_rd.x + region_rd.width - c.x + useless_gap - border_width * 2
|
||||
c.height = region_rd.y + region_rd.height - c.y + useless_gap - border_width * 2
|
||||
end
|
||||
if area_rd ~= nil then
|
||||
c.width = area_rd.x + area_rd.width - c.x + useless_gap - border_width * 2
|
||||
c.height = area_rd.y + area_rd.height - c.y + useless_gap - border_width * 2
|
||||
end
|
||||
end
|
||||
|
||||
function module.create(args_or_name, editor, default_cmd)
|
||||
|
@ -132,7 +136,15 @@ function module.create(args_or_name, editor, default_cmd)
|
|||
else
|
||||
return nil
|
||||
end
|
||||
args.editor = args.editor or editor or machi.editor.default_editor
|
||||
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
|
||||
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
|
||||
args.persistent = args.persistent == nil or args.persistent
|
||||
|
||||
|
@ -143,253 +155,259 @@ function module.create(args_or_name, editor, default_cmd)
|
|||
return (args.name_func and args.name_func(tag) or args.name), args.persistent
|
||||
end
|
||||
|
||||
local function get_instance_(tag)
|
||||
local name, persistent = get_instance_info(tag)
|
||||
if instances[name] == nil then
|
||||
instances[name] = {
|
||||
layout = layout,
|
||||
cmd = persistent and args.editor.get_last_cmd(name) or nil,
|
||||
regions_cache = {},
|
||||
tag_data = {},
|
||||
}
|
||||
if instances[name].cmd == nil then
|
||||
instances[name].cmd = args.default_cmd
|
||||
end
|
||||
end
|
||||
return instances[name]
|
||||
end
|
||||
|
||||
local function get_regions(workarea, tag)
|
||||
local instance = get_instance_(tag)
|
||||
local cmd = instance.cmd or module.global_default_cmd
|
||||
if 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] = args.editor.run_cmd(workarea, cmd)
|
||||
end
|
||||
return instance.regions_cache[key], cmd:sub(1,1) == "d"
|
||||
end
|
||||
|
||||
local function set_cmd(cmd, tag)
|
||||
local instance = get_instance_(tag)
|
||||
if instance.cmd ~= cmd then
|
||||
instance.cmd = cmd
|
||||
instance.regions_cache = {}
|
||||
instance.tag_data = {}
|
||||
end
|
||||
end
|
||||
|
||||
local function arrange(p)
|
||||
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 tag = get_screen(p.screen).selected_tag
|
||||
local instance = get_instance_(tag)
|
||||
local regions, draft_mode = get_regions(wa, tag)
|
||||
|
||||
if #regions == 0 then return end
|
||||
local nested_clients = {}
|
||||
|
||||
for _, c in ipairs(cls) do
|
||||
if c.machi == nil then
|
||||
c.machi = setmetatable({}, {__mode = "v"})
|
||||
end
|
||||
end
|
||||
|
||||
if draft_mode then
|
||||
for i, c in ipairs(cls) do
|
||||
if c.floating or c.immobilized then
|
||||
log(DEBUG, "Ignore client " .. tostring(c))
|
||||
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
|
||||
log(DEBUG, "Compute regions for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
|
||||
lu = find_lu(c, regions)
|
||||
if lu ~= nil then
|
||||
c.x = regions[lu].x
|
||||
c.y = regions[lu].y
|
||||
rd = find_rd(c, regions, lu)
|
||||
end
|
||||
end
|
||||
|
||||
if lu ~= nil and rd ~= nil then
|
||||
c.machi.instance = instance
|
||||
c.machi.region, c.machi.lu, c.machi.rd = nil, lu, rd
|
||||
p.geometries[c] = {}
|
||||
module.set_geometry(p.geometries[c], regions[lu], regions[rd], useless_gap, 0)
|
||||
end
|
||||
local function get_instance_(tag)
|
||||
local name, persistent = get_instance_info(tag)
|
||||
if instances[name] == nil then
|
||||
instances[name] = {
|
||||
layout = layout,
|
||||
cmd = persistent and args.editor.get_last_cmd(name) or nil,
|
||||
areas_cache = {},
|
||||
tag_data = {},
|
||||
}
|
||||
if instances[name].cmd == nil then
|
||||
instances[name].cmd = args.default_cmd
|
||||
end
|
||||
end
|
||||
else
|
||||
for i, c in ipairs(cls) do
|
||||
if c.floating or c.immobilized then
|
||||
log(DEBUG, "Ignore client " .. tostring(c))
|
||||
else
|
||||
if c.machi.region ~= nil and
|
||||
regions[c.machi.region].layout == 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
|
||||
log(DEBUG, "Compute regions for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
|
||||
local region = find_region(c, regions)
|
||||
c.machi.instance = instance
|
||||
c.machi.region, c.machi.lu, c.machi.rd = region, nil, nil
|
||||
p.geometries[c] = {}
|
||||
if regions[region].layout ~= nil then
|
||||
local clients = nested_clients[region]
|
||||
if clients == nil then clients = {}; nested_clients[region] = clients end
|
||||
clients[#clients + 1] = c
|
||||
else
|
||||
module.set_geometry(p.geometries[c], regions[region], regions[region], useless_gap, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
return instances[name]
|
||||
end
|
||||
|
||||
local function get_areas(screen, tag)
|
||||
local workarea = screen.workarea
|
||||
local instance = get_instance_(tag)
|
||||
local cmd = instance.cmd or module.global_default_cmd
|
||||
if cmd == nil then return {}, false end
|
||||
|
||||
local key = tostring(workarea.width) .. "x" .. tostring(workarea.height) .. "+" .. tostring(workarea.x) .. "+" .. tostring(workarea.y)
|
||||
if instance.areas_cache[key] == nil then
|
||||
instance.areas_cache[key] = args.editor.run_cmd(cmd, screen, tag)
|
||||
end
|
||||
local draft_mode = instance.areas_cache[key] and instance.areas_cache[key][1].draft_mode
|
||||
return instance.areas_cache[key], draft_mode
|
||||
end
|
||||
|
||||
local function set_cmd(cmd, tag)
|
||||
local instance = get_instance_(tag)
|
||||
if instance.cmd ~= cmd then
|
||||
instance.cmd = cmd
|
||||
instance.areas_cache = {}
|
||||
instance.tag_data = {}
|
||||
end
|
||||
end
|
||||
|
||||
local function arrange(p)
|
||||
local useless_gap = p.useless_gap
|
||||
local screen = get_screen(p.screen)
|
||||
local wa = screen.workarea -- get the real workarea without the gap (instead of p.workarea)
|
||||
local cls = p.clients
|
||||
local tag = screen.selected_tag
|
||||
local instance = get_instance_(tag)
|
||||
local areas, draft_mode = get_areas(screen, tag)
|
||||
|
||||
if #areas == 0 then return end
|
||||
local nested_clients = {}
|
||||
|
||||
for _, c in ipairs(cls) do
|
||||
if c.machi == nil then
|
||||
c.machi = setmetatable({}, {__mode = "v"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for region, clients in pairs(nested_clients) do
|
||||
if instance.tag_data[region] == nil then
|
||||
-- TODO: Make the default more flexible.
|
||||
instance.tag_data[region] = {
|
||||
column_count = 1,
|
||||
master_count = 1,
|
||||
master_fill_policy = "expand",
|
||||
useless_gap = 0,
|
||||
master_width_factor = 0.5,
|
||||
_private = {
|
||||
awful_tag_properties = {
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
local nested_params = {
|
||||
tag = instance.tag_data[region],
|
||||
screen = p.screen,
|
||||
clients = clients,
|
||||
padding = 0,
|
||||
geometry = {
|
||||
x = regions[region].x,
|
||||
y = regions[region].y,
|
||||
width = regions[region].width,
|
||||
height = regions[region].height,
|
||||
},
|
||||
-- Not sure how useless_gap adjustment works here. It seems to work anyway.
|
||||
workarea = {
|
||||
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,
|
||||
},
|
||||
useless_gap = useless_gap,
|
||||
geometries = {},
|
||||
}
|
||||
regions[region].layout.arrange(nested_params)
|
||||
for _, c in ipairs(clients) do
|
||||
p.geometries[c] = {
|
||||
x = nested_params.geometries[c].x,
|
||||
y = nested_params.geometries[c].y,
|
||||
width = nested_params.geometries[c].width,
|
||||
height = nested_params.geometries[c].height,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if draft_mode then
|
||||
for i, c in ipairs(cls) do
|
||||
if c.floating or c.immobilized then
|
||||
log(DEBUG, "Ignore client " .. tostring(c))
|
||||
else
|
||||
local skip = false
|
||||
if c.machi.lu ~= nil and c.machi.rd ~= nil and
|
||||
c.machi.lu <= #areas and c.machi.rd <= #areas and
|
||||
not areas[c.machi.lu].inhabitable and not areas[c.machi.rd].inhabitable
|
||||
then
|
||||
if areas[c.machi.lu].x == c.x and
|
||||
areas[c.machi.lu].y == c.y and
|
||||
areas[c.machi.rd].x + areas[c.machi.rd].width - c.border_width * 2 == c.x + c.width and
|
||||
areas[c.machi.rd].y + areas[c.machi.rd].height - c.border_width * 2 == c.y + c.height
|
||||
then
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
local function resize_handler (c, context, h)
|
||||
local workarea = c.screen.workarea
|
||||
local regions, draft_mode = get_regions(workarea, c.screen.selected_tag)
|
||||
local lu = nil
|
||||
local rd = nil
|
||||
if not skip then
|
||||
log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
|
||||
lu = find_lu(c, areas)
|
||||
if lu ~= nil then
|
||||
c.x = areas[lu].x
|
||||
c.y = areas[lu].y
|
||||
rd = find_rd(c, areas, lu)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
-- Use the initial width and height since it may change in undesired way.
|
||||
local hh = {}
|
||||
hh.x = regions[lu].x
|
||||
hh.y = regions[lu].y
|
||||
hh.width = c.full_width_before_move
|
||||
hh.height = c.full_height_before_move
|
||||
rd = find_rd(hh, regions, lu)
|
||||
|
||||
if rd ~= nil and not module.allowing_shrinking_by_mouse_moving and
|
||||
(regions[rd].x + regions[rd].width - regions[lu].x < c.full_width_before_move or
|
||||
regions[rd].y + regions[rd].height - regions[lu].y < c.full_height_before_move) then
|
||||
hh.x = regions[rd].x + regions[rd].width - c.full_width_before_move
|
||||
hh.y = regions[rd].y + regions[rd].height - c.full_height_before_move
|
||||
lu = find_lu(hh, regions, rd)
|
||||
end
|
||||
else
|
||||
local hh = {}
|
||||
hh.x = h.x
|
||||
hh.y = h.y
|
||||
hh.width = h.width
|
||||
hh.height = h.height
|
||||
hh.border_width = c.border_width
|
||||
rd = find_rd(hh, regions, lu)
|
||||
if lu ~= nil and rd ~= nil then
|
||||
c.machi.instance = instance
|
||||
c.machi.area, c.machi.lu, c.machi.rd = nil, lu, rd
|
||||
p.geometries[c] = {}
|
||||
module.set_geometry(p.geometries[c], areas[lu], areas[rd], useless_gap, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for i, c in ipairs(cls) do
|
||||
if c.floating or c.immobilized then
|
||||
log(DEBUG, "Ignore client " .. tostring(c))
|
||||
else
|
||||
if c.machi.area ~= nil and
|
||||
c.machi.area < #areas and
|
||||
not areas[c.machi.area].inhabitable and
|
||||
areas[c.machi.area].layout == nil and
|
||||
areas[c.machi.area].x == c.x and
|
||||
areas[c.machi.area].y == c.y and
|
||||
areas[c.machi.area].width - c.border_width * 2 == c.width and
|
||||
areas[c.machi.area].height - c.border_width * 2 == c.height
|
||||
then
|
||||
else
|
||||
log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
|
||||
local area = find_area(c, areas)
|
||||
c.machi.instance = instance
|
||||
c.machi.area, c.machi.lu, c.machi.rd = area, nil, nil
|
||||
p.geometries[c] = {}
|
||||
if machi_editor.nested_layouts[areas[area].layout] ~= nil then
|
||||
local clients = nested_clients[area]
|
||||
if clients == nil then clients = {}; nested_clients[area] = clients end
|
||||
clients[#clients + 1] = c
|
||||
else
|
||||
module.set_geometry(p.geometries[c], areas[area], areas[area], useless_gap, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if lu ~= nil and rd ~= nil then
|
||||
c.machi.lu = lu
|
||||
c.machi.rd = rd
|
||||
module.set_geometry(c, regions[lu], regions[rd], 0, c.border_width)
|
||||
for area, clients in pairs(nested_clients) do
|
||||
if instance.tag_data[area] == nil then
|
||||
-- TODO: Make the default more flexible.
|
||||
instance.tag_data[area] = {
|
||||
column_count = 1,
|
||||
master_count = 1,
|
||||
master_fill_policy = "expand",
|
||||
gap = 0,
|
||||
master_width_factor = 0.5,
|
||||
_private = {
|
||||
awful_tag_properties = {
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
local nested_params = {
|
||||
tag = instance.tag_data[area],
|
||||
screen = p.screen,
|
||||
clients = clients,
|
||||
padding = 0,
|
||||
geometry = {
|
||||
x = areas[area].x,
|
||||
y = areas[area].y,
|
||||
width = areas[area].width,
|
||||
height = areas[area].height,
|
||||
},
|
||||
-- Not sure how useless_gap adjustment works here. It seems to work anyway.
|
||||
workarea = {
|
||||
x = areas[area].x - useless_gap,
|
||||
y = areas[area].y - useless_gap,
|
||||
width = areas[area].width + useless_gap * 2,
|
||||
height = areas[area].height + useless_gap * 2,
|
||||
},
|
||||
useless_gap = useless_gap,
|
||||
geometries = {},
|
||||
}
|
||||
machi_editor.nested_layouts[areas[area].layout].arrange(nested_params)
|
||||
for _, c in ipairs(clients) do
|
||||
p.geometries[c] = {
|
||||
x = nested_params.geometries[c].x,
|
||||
y = nested_params.geometries[c].y,
|
||||
width = nested_params.geometries[c].width,
|
||||
height = nested_params.geometries[c].height,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if context ~= "mouse.move" then return end
|
||||
end
|
||||
end
|
||||
|
||||
if #regions == 0 then return end
|
||||
local function resize_handler (c, context, h)
|
||||
local areas, draft_mode = get_areas(c.screen, c.screen.selected_tag)
|
||||
|
||||
local center_x = h.x + h.width / 2
|
||||
local center_y = h.y + h.height / 2
|
||||
if #areas == 0 then return end
|
||||
|
||||
local choice = 1
|
||||
local choice_value = nil
|
||||
if draft_mode then
|
||||
local lu = find_lu(h, areas)
|
||||
local rd = nil
|
||||
if lu ~= nil then
|
||||
if context == "mouse.move" then
|
||||
-- Use the initial width and height since it may change in undesired way.
|
||||
local hh = {}
|
||||
hh.x = areas[lu].x
|
||||
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)
|
||||
|
||||
for i, r in ipairs(regions) do
|
||||
local r_x = r.x + r.width / 2
|
||||
local r_y = r.y + r.height / 2
|
||||
local dis = (r_x - center_x) * (r_x - center_x) + (r_y - center_y) * (r_y - center_y)
|
||||
if choice_value == nil or choice_value > dis then
|
||||
choice = i
|
||||
choice_value = dis
|
||||
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].y + areas[rd].height - areas[lu].y < c.full_height_before_move) then
|
||||
hh.x = areas[rd].x + areas[rd].width - c.full_width_before_move
|
||||
hh.y = areas[rd].y + areas[rd].height - c.full_height_before_move
|
||||
lu = find_lu(hh, areas, rd)
|
||||
end
|
||||
else
|
||||
local hh = {}
|
||||
hh.x = h.x
|
||||
hh.y = h.y
|
||||
hh.width = h.width
|
||||
hh.height = h.height
|
||||
hh.border_width = c.border_width
|
||||
rd = find_rd(hh, areas, lu)
|
||||
end
|
||||
|
||||
if lu ~= nil and rd ~= nil then
|
||||
c.machi.lu = lu
|
||||
c.machi.rd = rd
|
||||
module.set_geometry(c, areas[lu], areas[rd], 0, c.border_width)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if context ~= "mouse.move" then return end
|
||||
|
||||
if c.machi.region ~= choice then
|
||||
c.machi.region = choice
|
||||
module.set_geometry(c, regions[choice], regions[choice], 0, c.border_width)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #areas == 0 then return end
|
||||
|
||||
layout.name = "machi"
|
||||
layout.arrange = arrange
|
||||
layout.resize_handler = resize_handler
|
||||
layout.machi_get_instance_info = get_instance_info
|
||||
layout.machi_set_cmd = set_cmd
|
||||
layout.machi_get_regions = get_regions
|
||||
return layout
|
||||
local center_x = h.x + h.width / 2
|
||||
local center_y = h.y + h.height / 2
|
||||
|
||||
local choice = 1
|
||||
local choice_value = nil
|
||||
|
||||
for i, r in ipairs(areas) do
|
||||
local r_x = r.x + r.width / 2
|
||||
local r_y = r.y + r.height / 2
|
||||
local dis = (r_x - center_x) * (r_x - center_x) + (r_y - center_y) * (r_y - center_y)
|
||||
if choice_value == nil or choice_value > dis then
|
||||
choice = i
|
||||
choice_value = dis
|
||||
end
|
||||
end
|
||||
|
||||
if c.machi.area ~= choice then
|
||||
c.machi.area = choice
|
||||
module.set_geometry(c, areas[choice], areas[choice], 0, c.border_width)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
layout.name = args.icon_name or "machi"
|
||||
layout.editor = args.editor
|
||||
layout.arrange = arrange
|
||||
layout.resize_handler = resize_handler
|
||||
layout.machi_get_instance_info = get_instance_info
|
||||
layout.machi_set_cmd = set_cmd
|
||||
layout.machi_get_areas = get_areas
|
||||
return layout
|
||||
end
|
||||
|
||||
return module
|
||||
|
|
199
switcher.lua
199
switcher.lua
|
@ -1,5 +1,6 @@
|
|||
local machi = {
|
||||
layout = require((...):match("(.-)[^%.]+$") .. "layout"),
|
||||
layout = require((...):match("(.-)[^%.]+$") .. "layout"),
|
||||
engine = require((...):match("(.-)[^%.]+$") .. "engine"),
|
||||
}
|
||||
|
||||
local api = {
|
||||
|
@ -15,6 +16,8 @@ local api = {
|
|||
dpi = require("beautiful.xresources").apply_dpi,
|
||||
}
|
||||
|
||||
local gtimer = require("gears.timer")
|
||||
|
||||
local ERROR = 2
|
||||
local WARNING = 1
|
||||
local INFO = 0
|
||||
|
@ -53,6 +56,9 @@ function module.start(c, exit_keys)
|
|||
local border_color = with_alpha(api.gears.color(
|
||||
api.beautiful.machi_switcher_border_color or api.beautiful.border_focus),
|
||||
api.beautiful.machi_switcher_border_opacity or 0.25)
|
||||
local border_color_hl = with_alpha(api.gears.color(
|
||||
api.beautiful.machi_switcher_border_hl_color or api.beautiful.border_focus),
|
||||
api.beautiful.machi_switcher_border_hl_opacity or 0.75)
|
||||
local fill_color = with_alpha(api.gears.color(
|
||||
api.beautiful.machi_switcher_fill_color or api.beautiful.bg_normal),
|
||||
api.beautiful.machi_switcher_fill_opacity or 0.25)
|
||||
|
@ -67,14 +73,16 @@ function module.start(c, exit_keys)
|
|||
local traverse_radius = api.dpi(5)
|
||||
|
||||
local screen = c and c.screen or api.screen.focused()
|
||||
local tag = screen.selected_tag
|
||||
local layout = tag.layout
|
||||
local gap = tag.gap
|
||||
local start_x = screen.workarea.x
|
||||
local start_y = screen.workarea.y
|
||||
|
||||
local layout = api.layout.get(screen)
|
||||
if (c ~= nil and c.floating) or layout.machi_get_regions == nil then return end
|
||||
if (c ~= nil and c.floating) or layout.machi_get_areas == nil then return end
|
||||
|
||||
local regions, draft_mode = layout.machi_get_regions(screen.workarea, screen.selected_tag)
|
||||
if regions == nil or #regions == 0 then
|
||||
local areas, draft_mode = layout.machi_get_areas(screen, screen.selected_tag)
|
||||
if areas == nil or #areas == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -91,7 +99,6 @@ function module.start(c, exit_keys)
|
|||
})
|
||||
infobox.visible = true
|
||||
|
||||
local tablist_region = nil
|
||||
local tablist = nil
|
||||
local tablist_index = nil
|
||||
|
||||
|
@ -104,23 +111,35 @@ function module.start(c, exit_keys)
|
|||
traverse_y = screen.workarea.y + screen.workarea.height / 2
|
||||
end
|
||||
|
||||
local selected_area_ = nil
|
||||
local function selected_area()
|
||||
if selected_area_ == nil then
|
||||
for i, a in ipairs(areas) do
|
||||
if not a.inhabitable and
|
||||
a.x <= traverse_x and traverse_x < a.x + a.width and
|
||||
a.y <= traverse_y and traverse_y < a.y + a.height
|
||||
then
|
||||
selected_area_ = i
|
||||
end
|
||||
end
|
||||
end
|
||||
return selected_area_
|
||||
end
|
||||
|
||||
local function set_selected_area(a)
|
||||
selected_area_ = a
|
||||
end
|
||||
|
||||
local function maintain_tablist()
|
||||
if tablist == nil then
|
||||
tablist = {}
|
||||
|
||||
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
|
||||
active_region = i
|
||||
end
|
||||
end
|
||||
|
||||
local active_area = selected_area()
|
||||
for _, tc in ipairs(screen.tiled_clients) do
|
||||
if not (tc.floating or tc.immobilized)
|
||||
then
|
||||
if regions[active_region].x <= tc.x + tc.width + tc.border_width * 2 and tc.x <= regions[active_region].x + regions[active_region].width and
|
||||
regions[active_region].y <= tc.y + tc.height + tc.border_width * 2 and tc.y <= regions[active_region].y + regions[active_region].height
|
||||
if areas[active_area].x <= tc.x + tc.width + tc.border_width * 2 and tc.x <= areas[active_area].x + areas[active_area].width and
|
||||
areas[active_area].y <= tc.y + tc.height + tc.border_width * 2 and tc.y <= areas[active_area].y + areas[active_area].height
|
||||
then
|
||||
tablist[#tablist + 1] = tc
|
||||
end
|
||||
|
@ -159,30 +178,25 @@ function module.start(c, exit_keys)
|
|||
cr:rectangle(0, 0, width, height)
|
||||
cr:fill()
|
||||
|
||||
local msg, ext, active_region
|
||||
for i, a in ipairs(regions) do
|
||||
|
||||
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
|
||||
cr:clip()
|
||||
cr:set_source(fill_color)
|
||||
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
|
||||
cr:fill()
|
||||
cr:set_source(border_color)
|
||||
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
|
||||
cr:set_line_width(10.0)
|
||||
cr:stroke()
|
||||
cr:reset_clip()
|
||||
|
||||
-- TODO deduplicate this with code in maintain_tablist()
|
||||
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
|
||||
active_region = i
|
||||
local msg, ext
|
||||
local active_area = selected_area()
|
||||
for i, a in ipairs(areas) do
|
||||
if not a.inhabitable or i == active_area then
|
||||
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
|
||||
cr:clip()
|
||||
cr:set_source(fill_color)
|
||||
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
|
||||
cr:fill()
|
||||
cr:set_source(i == active_area and border_color_hl or border_color)
|
||||
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
|
||||
cr:set_line_width(10.0)
|
||||
cr:stroke()
|
||||
cr:reset_clip()
|
||||
end
|
||||
end
|
||||
|
||||
if #tablist > 0 then
|
||||
local a = regions[active_region]
|
||||
local a = areas[active_area]
|
||||
local pl = api.lgi.Pango.Layout.create(cr)
|
||||
pl:set_font_description(tablist_font_desc)
|
||||
|
||||
|
@ -192,7 +206,7 @@ function module.start(c, exit_keys)
|
|||
local exts = {}
|
||||
|
||||
for index, tc in ipairs(tablist) do
|
||||
local label = tc.name
|
||||
local label = tc.name or "<unnamed>"
|
||||
pl:set_text(label)
|
||||
local w, h
|
||||
w, h = pl:get_size()
|
||||
|
@ -208,7 +222,7 @@ function module.start(c, exit_keys)
|
|||
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)
|
||||
-- cover the entire region
|
||||
-- cover the entire area
|
||||
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
|
||||
cr:set_source(fill_color)
|
||||
cr:fill()
|
||||
|
@ -218,7 +232,7 @@ function module.start(c, exit_keys)
|
|||
cr:fill()
|
||||
|
||||
for index, tc in ipairs(tablist) do
|
||||
local label = tc.name
|
||||
local label = tc.name or "<unnamed>"
|
||||
local ext = exts[index]
|
||||
if index == tablist_index then
|
||||
cr:rectangle(x_offset - ext.width / 2 - vpadding / 2, y_offset - vpadding / 2, ext.width + vpadding, ext.height + vpadding)
|
||||
|
@ -298,37 +312,28 @@ function module.start(c, exit_keys)
|
|||
end
|
||||
end
|
||||
|
||||
local current_region = nil
|
||||
local current_area = selected_area()
|
||||
|
||||
if c and (shift or ctrl) then
|
||||
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
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if shift then
|
||||
if current_region == nil or
|
||||
regions[current_region].x ~= c.x or
|
||||
regions[current_region].y ~= c.y
|
||||
if current_area == nil or
|
||||
areas[current_area].x ~= c.x or
|
||||
areas[current_area].y ~= c.y
|
||||
then
|
||||
traverse_x = c.x + traverse_radius
|
||||
traverse_y = c.y + traverse_radius
|
||||
current_region = nil
|
||||
set_selected_area(nil)
|
||||
end
|
||||
elseif ctrl then
|
||||
local ex = c.x + c.width + c.border_width * 2
|
||||
local ey = c.y + c.height + c.border_width * 2
|
||||
if current_region == nil or
|
||||
regions[current_region].x + regions[current_region].width ~= ex or
|
||||
regions[current_region].y + regions[current_region].height ~= ey
|
||||
if current_area == nil or
|
||||
areas[current_area].x + areas[current_area].width ~= ex or
|
||||
areas[current_area].y + areas[current_area].height ~= ey
|
||||
then
|
||||
traverse_x = ex - traverse_radius
|
||||
traverse_y = ey - traverse_radius
|
||||
current_region = nil
|
||||
set_selected_area(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -336,12 +341,10 @@ function module.start(c, exit_keys)
|
|||
local choice = nil
|
||||
local choice_value
|
||||
|
||||
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
|
||||
current_area = selected_area()
|
||||
|
||||
for i, a in ipairs(areas) do
|
||||
if a.inhabitable then goto continue end
|
||||
|
||||
local v
|
||||
if key == "Up" then
|
||||
|
@ -378,10 +381,11 @@ function module.start(c, exit_keys)
|
|||
choice = i
|
||||
choice_value = v
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
|
||||
if choice == nil then
|
||||
choice = current_region
|
||||
choice = current_area
|
||||
if key == "Up" then
|
||||
traverse_y = screen.workarea.y
|
||||
elseif key == "Down" then
|
||||
|
@ -394,9 +398,10 @@ function module.start(c, exit_keys)
|
|||
end
|
||||
|
||||
if choice ~= nil 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))
|
||||
traverse_x = max(areas[choice].x + traverse_radius, min(areas[choice].x + areas[choice].width - traverse_radius, traverse_x))
|
||||
traverse_y = max(areas[choice].y + traverse_radius, min(areas[choice].y + areas[choice].height - traverse_radius, traverse_y))
|
||||
tablist = nil
|
||||
set_selected_area(nil)
|
||||
|
||||
if c and ctrl and draft_mode then
|
||||
local lu = c.machi.lu
|
||||
|
@ -404,28 +409,28 @@ function module.start(c, exit_keys)
|
|||
|
||||
if shift then
|
||||
lu = choice
|
||||
if regions[rd].x + regions[rd].width <= regions[lu].x or
|
||||
regions[rd].y + regions[rd].height <= regions[lu].y
|
||||
if areas[rd].x + areas[rd].width <= areas[lu].x or
|
||||
areas[rd].y + areas[rd].height <= areas[lu].y
|
||||
then
|
||||
rd = nil
|
||||
end
|
||||
else
|
||||
rd = choice
|
||||
if regions[rd].x + regions[rd].width <= regions[lu].x or
|
||||
regions[rd].y + regions[rd].height <= regions[lu].y
|
||||
if areas[rd].x + areas[rd].width <= areas[lu].x or
|
||||
areas[rd].y + areas[rd].height <= areas[lu].y
|
||||
then
|
||||
lu = nil
|
||||
end
|
||||
end
|
||||
|
||||
if lu ~= nil and rd ~= nil then
|
||||
machi.layout.set_geometry(c, regions[lu], regions[rd], 0, c.border_width)
|
||||
machi.layout.set_geometry(c, areas[lu], areas[rd], 0, c.border_width)
|
||||
elseif lu ~= nil then
|
||||
machi.layout.set_geometry(c, regions[lu], nil, 0, c.border_width)
|
||||
machi.layout.set_geometry(c, areas[lu], nil, 0, c.border_width)
|
||||
elseif rd ~= nil then
|
||||
c.x = min(c.x, regions[rd].x)
|
||||
c.y = min(c.y, regions[rd].y)
|
||||
machi.layout.set_geometry(c, nil, regions[rd], 0, c.border_width)
|
||||
c.x = min(c.x, areas[rd].x)
|
||||
c.y = min(c.y, areas[rd].y)
|
||||
machi.layout.set_geometry(c, nil, areas[rd], 0, c.border_width)
|
||||
end
|
||||
c.machi.lu = lu
|
||||
c.machi.rd = rd
|
||||
|
@ -436,11 +441,11 @@ function module.start(c, exit_keys)
|
|||
elseif c and shift then
|
||||
-- move the window
|
||||
if draft_mode then
|
||||
c.x = regions[choice].x
|
||||
c.y = regions[choice].y
|
||||
c.x = areas[choice].x
|
||||
c.y = areas[choice].y
|
||||
else
|
||||
machi.layout.set_geometry(c, regions[choice], regions[choice], 0, c.border_width)
|
||||
c.machi.region = choice
|
||||
machi.layout.set_geometry(c, areas[choice], areas[choice], 0, c.border_width)
|
||||
c.machi.area = choice
|
||||
end
|
||||
c:emit_signal("request::activate", "mouse.move", {raise=false})
|
||||
c:raise()
|
||||
|
@ -458,6 +463,42 @@ function module.start(c, exit_keys)
|
|||
|
||||
infobox.bgimage = draw_info
|
||||
end
|
||||
elseif (key == "u" or key == "Prior") and not draft_mode then
|
||||
local current_area = selected_area()
|
||||
if areas[current_area].parent_id then
|
||||
tablist = nil
|
||||
set_selected_area(areas[current_area].parent_id)
|
||||
infobox.bgimage = draw_info
|
||||
end
|
||||
elseif key == "/" and not draft_mode then
|
||||
local current_area = selected_area()
|
||||
areas[current_area].hole = true
|
||||
local prefix, suffix = machi.engine.areas_to_command(
|
||||
areas, true):match("(.*)|(.*)")
|
||||
print(prefix, suffix)
|
||||
|
||||
areas[current_area].hole = nil
|
||||
|
||||
workarea = {
|
||||
x = areas[current_area].x - gap * 2,
|
||||
y = areas[current_area].y - gap * 2,
|
||||
width = areas[current_area].width + gap * 4,
|
||||
height = areas[current_area].height + gap * 4,
|
||||
}
|
||||
gtimer.delayed_call(
|
||||
function ()
|
||||
print(layout.editor)
|
||||
layout.editor.start_interactive(
|
||||
screen,
|
||||
{
|
||||
workarea = workarea,
|
||||
cmd_prefix = prefix,
|
||||
cmd_suffix = suffix,
|
||||
}
|
||||
)
|
||||
end
|
||||
)
|
||||
exit()
|
||||
elseif key == "Escape" or key == "Return" then
|
||||
exit()
|
||||
else
|
||||
|
|
Loading…
Reference in New Issue