From c8d2b5fd4532c17af522053b9e270baf2b7f3a51 Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Sat, 10 Aug 2019 12:25:51 -0400 Subject: [PATCH] big refactoring; advanced grid mode; readme --- README.md | 127 ++++++++++++++++++--- editor.lua | 313 ++++++++++++++++++++++++++++++++++----------------- switcher.lua | 1 - 3 files changed, 319 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 563e193..20230bc 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Please let me know if it does not work in other versions. Suppose this git is checked out at `~/.config/awesome/layout-machi` -`machi = require("layout-machi")` +Use `local machi = require("layout-machi")` to load the module. The package provide a default layout `machi.default_layout` and editor `machi.default_editor`, which can be added into the layout list. @@ -33,7 +33,7 @@ The package comes with the icon for `layoutbox`, which can be set with the follo ## Use the layout -Use `layout = machi.layout.create(name, editor)` to instantiate the layout with an editor object. +Use `local layout = machi.layout.create(name, editor)` to instantiate the layout with an editor object. `name` can be a string or a function returning a string (see `init.lua` and "Advanced" below). This is used for having different actual layout dependent on tags. @@ -42,25 +42,46 @@ This is used for having different actual layout dependent on tags. `machi.default_editor` can be used, or see below on creating editors. You can create multiple layouts with different names and share the same editor. -## Editor +## The layout editor and commands -Call `editor = machi.editor.create()` to create an 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 = awful.screen.focused(), l = awful.layout.get(s))`. -### The layout editing command +### Basic usage -The editing starts with the open area of the entire workarea, takes commands to split the current area into multiple sub-areas, then recursively edits each of them. -The editor is keyboard driven, each command is a key with optional digits (namely `D`) before it as parameter (or multiple parameters depending on the command). +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. +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 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. `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. +1. `Up`/`Down`: restore to the history command +2. `Backspace`: undo the last command. +3. `Escape`: exit the editor without saving the layout. +4. `Enter`: when all regions are defined, hit enter will save the layout. + +### Layout command + +As aforementioned, command a sequence of operations. +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) + +2. Operations taking argument string as a single number. + + `s` (shifting active region) + +3. Operation not taking argument. + + `.` (Finish all regions), `-` (Finish the current region), `;` (No-op) + +Argument string 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. + +Each operation may take argument string either from before (such as `22w`) or after (such as `w22`). +When any ambiguity arises, operation before always take the argument after. So `h11v` is interpreted as `h11` and `v`. For examples: @@ -125,6 +146,76 @@ Tada! 99 AAAA BBBB CC ``` +### Advanced grid layout + +__More document coming soon. For now there is only a running example.__ + +Simple grid, `w44`: +``` +0 1 2 3 + +4 5 6 7 + +8 9 A B + +C D E F +``` + +Merge grid from the top-left corner, size 3x1, `w4431`: +``` +0-0-0 1 + +2 3 4 5 + +6 7 8 9 + +A B C D +``` + +Another merge, size 1x3, `w443113`: +``` +0-0-0 1 + | +2 3 4 1 + | +5 6 7 1 + +8 9 A B +``` + +Another merge, size 1x3, `w44311313`: +``` +0-0-0 1 + | +2 3 4 1 +| | +2 5 6 1 +| +2 7 8 9 +``` + +Another merge, size 2x2, `w4431131322`: +``` +0-0-0 1 + | +2 3-3 1 +| | | | +2 3-3 1 +| +2 4 5 6 +``` + +Final merge, size 3x1, `w443113132231`: +``` +0-0-0 1 + | +2 3-3 1 +| | | | +2 3-3 1 +| +2 4-4-4 +``` + ### Draft mode __This mode is experimental. Its usage may change fast.__ @@ -133,8 +224,8 @@ Unlike the original machi layout, where a window fits in a single region, draft 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`. +This is suppose to work with regions 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`. ### Persistent history diff --git a/editor.lua b/editor.lua index 99f35f3..eb324d8 100644 --- a/editor.lua +++ b/editor.lua @@ -201,20 +201,23 @@ local function create(data) max_depth = init_max_depth current_info = "" current_cmd = "" + pending_op = nil to_exit = false to_apply = false end local function push_history() - history[#history + 1] = {#closed_areas, #open_areas, {}, current_info, current_cmd, max_depth, arg_str} + if history == nil then return end + history[#history + 1] = {#closed_areas, #open_areas, {}, current_info, current_cmd, pending_op, max_depth, arg_str} end local function discard_history() + if history == nil then return end table.remove(history, #history) end local function pop_history() - if #history == 0 then return end + if history == nil or #history == 0 then return end for i = history[#history][1] + 1, #closed_areas do table.remove(closed_areas, #closed_areas) end @@ -229,8 +232,9 @@ local function create(data) current_info = history[#history][4] current_cmd = history[#history][5] - max_depth = history[#history][6] - arg_str = history[#history][7] + pending_op = history[#history][6] + max_depth = history[#history][7] + arg_str = history[#history][8] table.remove(history, #history) end @@ -238,6 +242,8 @@ local function create(data) local function pop_open_area() local a = open_areas[#open_areas] table.remove(open_areas, #open_areas) + if history == nil or #history == 0 then return a end + local idx = history[#history][2] - #open_areas -- only save when the position has been firstly poped if idx > #history[#history][3] then @@ -246,17 +252,38 @@ local function create(data) return a end - local split_count = 0 + local function push_area() + closed_areas[#closed_areas + 1] = pop_open_area() + end - local function handle_split(method, alt) - split_count = split_count + 1 + local function push_children(c) + for i = #c, 1, -1 do + if c[i] ~= false then + if c[i].x ~= math.floor(c[i].x) + or c[i].y ~= math.floor(c[i].y) + or c[i].width ~= math.floor(c[i].width) + or c[i].height ~= math.floor(c[i].height) + then + print("warning, splitting yields floating area " .. _area_tostring(c[i])) + end + open_areas[#open_areas + 1] = c[i] + end + end + end - local a = pop_open_area() + local op_count = 0 - print("split " .. method .. " " .. tostring(alt) .. " " .. arg_str .. " " .. _area_tostring(a)) + local function handle_op(method) + op_count = op_count + 1 + local l = method:lower() + local alt = method ~= l + method = l + + print("op " .. method .. " " .. tostring(alt) .. " " .. arg_str) if method == "h" or method == "v" then + local a = pop_open_area() local args = parse_arg_string(arg_str, 0) if #args == 0 then args = {1, 1} @@ -288,7 +315,7 @@ local function create(data) width = shares[i], height = a.height, depth = a.depth + 1, - group_id = split_count, + group_id = op_count, bl = i == 1 and a.bl or false, br = i == #shares and a.br or false, bu = a.bu, @@ -305,7 +332,7 @@ local function create(data) width = a.width, height = shares[i], depth = a.depth + 1, - group_id = split_count, + group_id = op_count, bl = a.bl, br = a.br, bu = i == 1 and a.bu or false, @@ -315,19 +342,11 @@ local function create(data) 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 + push_children(children) elseif method == "w" then + local a = pop_open_area() local args = parse_arg_string(arg_str, 0) if #args == 0 then args = {1, 1} @@ -335,15 +354,13 @@ local function create(data) args[2] = 1 end - if alt then arg_str = table.reverse(arg_str) end - local h_split, v_split if alt then - h_split = args[#args] - v_split = args[#args - 1] + h_split = args[2] + v_split = args[1] else - h_split = args[#args - 1] - v_split = args[#args] + h_split = args[1] + v_split = args[2] end if h_split < 1 then h_split = 1 end if v_split < 1 then v_split = 1 end @@ -365,7 +382,7 @@ local function create(data) width = x_shares[x_index], height = y_shares[y_index], depth = a.depth + 1, - group_id = split_count, + group_id = op_count, } if x_index == 1 then r.bl = a.bl else r.bl = false end if x_index == h_split then r.br = a.br else r.br = false end @@ -375,19 +392,78 @@ local function create(data) 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])) + local merged_children = {} + local start_index = 1 + for i = 3, #args, 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) % h_split + local y = math.floor((start_index - 1) / h_split) + local w = args[i] + local h = args[i + 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 > h_split then w = h_split - x end + if y + h > v_split then h = v_split - 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 * h_split + tx + 1] == false then + succ = false + break + elseif ty == y then + end_index = ty * h_split + tx + 1 + end + end + + if not succ then + break + elseif ty > y then + end_index = ty * h_split + x + w + end + end + + local r = { + x = children[start_index].x, y = children[start_index].y, + width = children[end_index].x + children[end_index].width - children[start_index].x, + height = children[end_index].y + children[end_index].height - children[start_index].y, + bu = children[start_index].bu, bl = children[start_index].bl, + bd = children[end_index].bd, br = children[end_index].br, + depth = a.depth + 1, + group_id = op_count + } + 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 * h_split + tx + 1 + if index <= end_index then + children[index] = false + else + break + end + end end - open_areas[#open_areas + 1] = children[i] end + push_children(merged_children) + push_children(children) + elseif method == "d" then + local a = pop_open_area() local shares = parse_arg_string(arg_str, 0) local x_shares = {} local y_shares = {} @@ -406,9 +482,11 @@ local function create(data) end end - if #x_shares == 0 or #y_shares == 0 then + if #x_shares == 0 then open_areas[#open_areas + 1] = a return + elseif #y_shares == 0 then + y_shares = {1} end x_shares = fair_split(a.width, x_shares) @@ -423,7 +501,7 @@ local function create(data) width = x_shares[x_index], height = y_shares[y_index], depth = a.depth + 1, - group_id = split_count, + group_id = op_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 @@ -433,46 +511,11 @@ local function create(data) 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 + push_children(children) - elseif method == "p" then - -- XXX - end + elseif method == "s" then - arg_str = "" - end - - local function push_area() - closed_areas[#closed_areas + 1] = pop_open_area() - end - - local function handle_command(key) - if key == "h" or key == "H" then - handle_split("h", key == "H") - elseif key == "v" or key == "V" then - handle_split("v", key == "V") - elseif key == "w" or key == "W" then - if arg_str == "" then - push_area() - 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 if #open_areas > 0 then - key = "s" local times = arg_str == "" and 1 or tonumber(arg_str) local t = {} while #open_areas > 0 do @@ -481,34 +524,76 @@ local function create(data) for i = #t, 1, -1 do open_areas[#open_areas + 1] = t[(i + times - 1) % #t + 1] end - arg_str = "" - else - return nil end - elseif key == " " or key == "-" then - key = "-" - if arg_str == "" then - push_area() - else - max_depth = tonumber(arg_str) - arg_str = "" - end - elseif key == "Return" or key == "." then - key = "." + + elseif method == "-" then + + push_area() + + elseif method == "." then + while #open_areas > 0 do push_area() end - arg_str = "" - elseif key == "," or tonumber(key) ~= nil then - arg_str = arg_str .. key - else - return nil + + elseif method == ";" then + + -- nothing + end while #open_areas > 0 and open_areas[#open_areas].depth >= max_depth do push_area() end + arg_str = "" + end + + local key_translate_tab = { + ["Return"] = ".", + [" "] = "-", + } + + local ch_info = { + ["h"] = 2, ["H"] = 2, + ["v"] = 2, ["V"] = 2, + ["w"] = 2, ["W"] = 2, + ["d"] = 2, ["D"] = 2, + ["s"] = 2, ["S"] = 2, + ["-"] = 1, + ["."] = 1, + [";"] = 1, + ["0"] = 0, ["1"] = 0, ["2"] = 0, ["3"] = 0, ["4"] = 0, + ["5"] = 0, ["6"] = 0, ["7"] = 0, ["8"] = 0, ["9"] = 0, + [","] = 0, + } + + local function handle_ch(key) + if key_translate_tab[key] ~= nil then + key = key_translate_tab[key] + end + local t = ch_info[key] + if t == nil then + return nil + elseif t == 2 then + if pending_op ~= nil then + handle_op(pending_op) + pending_op = key + elseif arg_str == "" then + pending_op = key + else + handle_op(key) + end + elseif t == 1 then + if pending_op ~= nil then + handle_op(pending_op) + pending_op = nil + end + handle_op(key) + elseif t == 0 then + arg_str = arg_str .. key + end + return key end @@ -575,9 +660,17 @@ local function create(data) for i, a in ipairs(closed_areas) do local sa = shrink_area_with_gap(a, inner_gap, outer_gap) + local to_highlight = false + if pending_op ~= nil then + to_highlight = a.group_id == op_count + end cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) cr:clip() - cr:set_source(closed_color) + if to_highlight then + cr:set_source(active_color) + else + cr:set_source(closed_color) + end cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) cr:fill() cr:set_source(border_color) @@ -589,12 +682,18 @@ local function create(data) for i, a in ipairs(open_areas) do local sa = shrink_area_with_gap(a, inner_gap, outer_gap) + local to_highlight = false + if pending_op == nil then + to_highlight = i == #open_areas + else + to_highlight = a.group_id == op_count + end cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) cr:clip() if i == #open_areas then - cr:set_source(api.gears.color(active_color)) + cr:set_source(active_color) else - cr:set_source(api.gears.color(open_color)) + cr:set_source(open_color) end cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) cr:fill() @@ -602,12 +701,12 @@ local function create(data) cr:set_source(border_color) cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height) cr:set_line_width(10.0) - if i ~= #open_areas then + if to_highlight then + cr:stroke() + else cr:set_dash({5, 5}, 0) cr:stroke() cr:set_dash({}, 0) - else - cr:stroke() end cr:reset_clip() end @@ -653,6 +752,10 @@ local function create(data) local ok, err = pcall( function () + if pending_op ~= nil then + pop_history() + end + if key == "BackSpace" then pop_history() elseif key == "Escape" then @@ -675,7 +778,7 @@ local function create(data) local cmd = data.cmds[cmd_index]:sub(i, i) push_history() - local ret = handle_command(cmd) + local ret = handle_ch(cmd) if ret == nil then print("warning: ret is nil") @@ -690,7 +793,7 @@ local function create(data) end elseif #open_areas > 0 then push_history() - local ret = handle_command(key) + local ret = handle_ch(key) if ret ~= nil then current_info = current_info .. ret current_cmd = current_cmd .. ret @@ -745,6 +848,11 @@ local function create(data) end end + if not to_exit and pending_op ~= nil then + push_history() + handle_op(pending_op) + end + refresh() if to_exit then @@ -781,10 +889,9 @@ local function create(data) local outer_gap = data.outer_gap or data.gap or api.beautiful.useless_gap * 2 or 0 local inner_gap = data.inner_gap or data.gap or api.beautiful.useless_gap * 2 or 0 init(init_area) - push_history() for i = 1, #cmd do - local key = handle_command(cmd:sub(i, i)) + handle_ch(cmd:sub(i, i)) end local areas_with_gap = {} diff --git a/switcher.lua b/switcher.lua index 66c118f..472eace 100644 --- a/switcher.lua +++ b/switcher.lua @@ -29,7 +29,6 @@ local function with_alpha(col, alpha) return api.lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha) end - local function start(c) local tablist_font_desc = api.beautiful.get_merged_font( api.beautiful.mono_font or api.beautiful.font, api.dpi(10))