set layout directly by command. restore the last layout
This commit is contained in:
parent
358c2cee18
commit
8891f371a8
42
README.md
42
README.md
|
@ -12,19 +12,19 @@ TL;DR --- I want the control of my layout.
|
|||
|
||||
## Use the layout
|
||||
|
||||
Use `layout_machi.layout.create_layout([LAYOUT_NAME}, [DEFAULT_REGIONS])` to instantiate the layout.
|
||||
For example:
|
||||
|
||||
```
|
||||
layout_machi.layout.create_layout("default", {})
|
||||
```
|
||||
|
||||
Creates a layout with no regions
|
||||
Use `layotu = layout_machi.layout.create()` to instantiate the layout.
|
||||
|
||||
## Use the editor
|
||||
|
||||
Call `layout_machi.editor.start_editor(data)` to enter the editor for the current layout (given it is a machi instance).
|
||||
`data` is am object for storing the history of the editing, initially `{}`.
|
||||
Call `editor = layout_machi.editor.create(data)` to create an editor that can either
|
||||
|
||||
- Interactively edit layout by calling `editor.start_interactive()`
|
||||
- Set the layout with batched commands by calling `editor.set_by_cmd(cmd)`, where cmd is a string
|
||||
|
||||
`data` is an object for storing the history of the editing, initially `{}`.
|
||||
|
||||
### The layout editing command
|
||||
|
||||
The editor starts with the open area of the entire workarea, taking command to split the current area into multiple sub-areas, then editing each of them.
|
||||
The editor is keyboard driven, accepting a number of command keys.
|
||||
Before each command, you can optionally provide at most 2 digits for parameters (A, B) of the command.
|
||||
|
@ -39,10 +39,7 @@ Undefined parameters are (mostly) treated as 1.
|
|||
7. `Backspace`: undo the last command.
|
||||
8. `Escape`: exit the editor without saving the layout.
|
||||
|
||||
### Demos:
|
||||
|
||||
I used `Super + /` for the editor and `Super + Tab` for fitting the windows.
|
||||
|
||||
For examples:
|
||||
|
||||
h-v
|
||||
|
||||
|
@ -54,8 +51,6 @@ h-v
|
|||
11 33
|
||||
```
|
||||
|
||||
![](https://i.imgur.com/QbvMRTW.gif)
|
||||
|
||||
|
||||
hvv (or 22w)
|
||||
|
||||
|
@ -67,8 +62,6 @@ hvv (or 22w)
|
|||
22 44
|
||||
```
|
||||
|
||||
![](https://i.imgur.com/xJebxcF.gif)
|
||||
|
||||
|
||||
3-13h2v--2h-12v
|
||||
|
||||
|
@ -97,24 +90,17 @@ Tada!
|
|||
```
|
||||
|
||||
|
||||
history
|
||||
|
||||
![](https://i.imgur.com/gzFr48V.gif)
|
||||
|
||||
### Persistent history
|
||||
|
||||
You need to specify the path of the history file in the editor data, then restore the persistent history by `layout_machi.editor.restore_data`. For example,
|
||||
If you want all command persisted, you need to specify the path of the history file in the editor data.
|
||||
The persisted history can be restored by `layout_machi.editor.restore_data(...)`. For example,
|
||||
|
||||
```
|
||||
machi_layout_data = layout_machi.editor.restore_data({ history_file = ".machi-layout", history_save_max = 10 })
|
||||
machi_editor_data = layout_machi.editor.restore_data({ history_file = ".machi-layout", history_save_max = 10 })
|
||||
```
|
||||
|
||||
Then start the editor with the restored data.
|
||||
The last `history_save_max` commands are persisted.
|
||||
|
||||
## Other goodies
|
||||
|
||||
- Moving a window using the mouse will move it across regions
|
||||
|
||||
## Other functions
|
||||
|
||||
|
|
280
editor.lua
280
editor.lua
|
@ -107,40 +107,9 @@ function shrink_area_with_gap(a, gap)
|
|||
height = a.height - (a.bu and 0 or gap / 2) - (a.bd and 0 or gap / 2) }
|
||||
end
|
||||
|
||||
function start_editor(data)
|
||||
function create(data)
|
||||
local gap = data.gap or 0
|
||||
|
||||
if data.cmds == nil then
|
||||
data.cmds = {}
|
||||
end
|
||||
|
||||
local cmd_index = #data.cmds + 1
|
||||
data.cmds[cmd_index] = ""
|
||||
|
||||
local screen = api.screen.focused()
|
||||
local init_area = {
|
||||
x = screen.workarea.x,
|
||||
y = screen.workarea.y,
|
||||
width = screen.workarea.width,
|
||||
height = screen.workarea.height,
|
||||
border = 15,
|
||||
depth = 0,
|
||||
group_id = 0,
|
||||
-- we do not want to rely on BitOp
|
||||
bl = true, br = true, bu = true, bd = true,
|
||||
}
|
||||
local kg
|
||||
local infobox = api.wibox({
|
||||
x = screen.workarea.x,
|
||||
y = screen.workarea.y,
|
||||
width = screen.workarea.width,
|
||||
height = screen.workarea.height,
|
||||
bg = "#ffffff00",
|
||||
opacity = 1,
|
||||
ontop = true
|
||||
})
|
||||
infobox.visible = true
|
||||
|
||||
local closed_areas
|
||||
local open_areas
|
||||
local history
|
||||
|
@ -152,9 +121,21 @@ function start_editor(data)
|
|||
local to_exit
|
||||
local to_apply
|
||||
|
||||
local function init()
|
||||
local function init(init_area)
|
||||
closed_areas = {}
|
||||
open_areas = {init_area}
|
||||
open_areas = {
|
||||
{
|
||||
x = init_area.x,
|
||||
y = init_area.y,
|
||||
width = init_area.width,
|
||||
height = init_area.height,
|
||||
border = 15,
|
||||
depth = 0,
|
||||
group_id = 0,
|
||||
-- we do not want to rely on BitOp
|
||||
bl = true, br = true, bu = true, bd = true,
|
||||
}
|
||||
}
|
||||
history = {}
|
||||
num_1 = nil
|
||||
num_2 = nil
|
||||
|
@ -165,68 +146,6 @@ function start_editor(data)
|
|||
to_apply = false
|
||||
end
|
||||
|
||||
local function draw_info(context, cr, width, height)
|
||||
cr:set_source_rgba(0, 0, 0, 0)
|
||||
cr:rectangle(0, 0, width, height)
|
||||
cr:fill()
|
||||
|
||||
local msg, ext
|
||||
|
||||
for i, a in ipairs(closed_areas) do
|
||||
local sa = shrink_area_with_gap(a, gap)
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:clip()
|
||||
cr:set_source(api.gears.color(closed_color))
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:fill()
|
||||
cr:set_source(api.gears.color(border_color))
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:set_line_width(10.0)
|
||||
cr:stroke()
|
||||
cr:reset_clip()
|
||||
end
|
||||
|
||||
for i, a in ipairs(open_areas) do
|
||||
local sa = shrink_area_with_gap(a, gap)
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:clip()
|
||||
if i == #open_areas then
|
||||
cr:set_source(api.gears.color(active_color))
|
||||
else
|
||||
cr:set_source(api.gears.color(open_color))
|
||||
end
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:fill()
|
||||
|
||||
cr:set_source(api.gears.color(border_color))
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:set_line_width(10.0)
|
||||
if i ~= #open_areas then
|
||||
cr:set_dash({5, 5}, 0)
|
||||
cr:stroke()
|
||||
cr:set_dash({}, 0)
|
||||
else
|
||||
cr:stroke()
|
||||
end
|
||||
cr:reset_clip()
|
||||
end
|
||||
|
||||
cr:select_font_face(label_font_family, "normal", "normal")
|
||||
cr:set_font_size(info_size)
|
||||
cr:set_font_face(cr:get_font_face())
|
||||
msg = current_info
|
||||
ext = cr:text_extents(msg)
|
||||
cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing)
|
||||
cr:text_path(msg)
|
||||
cr:set_source_rgba(1, 1, 1, 1)
|
||||
cr:fill()
|
||||
cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing)
|
||||
cr:text_path(msg)
|
||||
cr:set_source_rgba(0, 0, 0, 1)
|
||||
cr:set_line_width(2.0)
|
||||
cr:stroke()
|
||||
end
|
||||
|
||||
local function push_history()
|
||||
history[#history + 1] = {#closed_areas, #open_areas, {}, current_info, current_cmd, max_depth, num_1, num_2}
|
||||
end
|
||||
|
@ -269,18 +188,6 @@ function start_editor(data)
|
|||
return a
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
print("closed areas:")
|
||||
for i, a in ipairs(closed_areas) do
|
||||
print(" " .. _area_tostring(a))
|
||||
end
|
||||
print("open areas:")
|
||||
for i, a in ipairs(open_areas) do
|
||||
print(" " .. _area_tostring(a))
|
||||
end
|
||||
infobox.bgimage = draw_info
|
||||
end
|
||||
|
||||
local split_count = 0
|
||||
|
||||
local function handle_split(method, alt)
|
||||
|
@ -362,13 +269,8 @@ function start_editor(data)
|
|||
num_2 = nil
|
||||
end
|
||||
|
||||
local function cleanup()
|
||||
infobox.visible = false
|
||||
end
|
||||
|
||||
local function push_area()
|
||||
closed_areas[#closed_areas + 1] = pop_open_area()
|
||||
infobox.bgimage = draw_info
|
||||
end
|
||||
|
||||
local function handle_command(key)
|
||||
|
@ -434,9 +336,109 @@ function start_editor(data)
|
|||
return key
|
||||
end
|
||||
|
||||
local function start_interactive()
|
||||
if data.cmds == nil then
|
||||
data.cmds = {}
|
||||
end
|
||||
|
||||
local cmd_index = #data.cmds + 1
|
||||
data.cmds[cmd_index] = ""
|
||||
|
||||
local screen = api.screen.focused()
|
||||
local kg
|
||||
local infobox = api.wibox({
|
||||
x = screen.workarea.x,
|
||||
y = screen.workarea.y,
|
||||
width = screen.workarea.width,
|
||||
height = screen.workarea.height,
|
||||
bg = "#ffffff00",
|
||||
opacity = 1,
|
||||
ontop = true
|
||||
})
|
||||
infobox.visible = true
|
||||
|
||||
local function cleanup()
|
||||
infobox.visible = false
|
||||
end
|
||||
|
||||
local function draw_info(context, cr, width, height)
|
||||
cr:set_source_rgba(0, 0, 0, 0)
|
||||
cr:rectangle(0, 0, width, height)
|
||||
cr:fill()
|
||||
|
||||
local msg, ext
|
||||
|
||||
for i, a in ipairs(closed_areas) do
|
||||
local sa = shrink_area_with_gap(a, gap)
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:clip()
|
||||
cr:set_source(api.gears.color(closed_color))
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:fill()
|
||||
cr:set_source(api.gears.color(border_color))
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:set_line_width(10.0)
|
||||
cr:stroke()
|
||||
cr:reset_clip()
|
||||
end
|
||||
|
||||
for i, a in ipairs(open_areas) do
|
||||
local sa = shrink_area_with_gap(a, gap)
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:clip()
|
||||
if i == #open_areas then
|
||||
cr:set_source(api.gears.color(active_color))
|
||||
else
|
||||
cr:set_source(api.gears.color(open_color))
|
||||
end
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:fill()
|
||||
|
||||
cr:set_source(api.gears.color(border_color))
|
||||
cr:rectangle(sa.x, sa.y, sa.width, sa.height)
|
||||
cr:set_line_width(10.0)
|
||||
if i ~= #open_areas then
|
||||
cr:set_dash({5, 5}, 0)
|
||||
cr:stroke()
|
||||
cr:set_dash({}, 0)
|
||||
else
|
||||
cr:stroke()
|
||||
end
|
||||
cr:reset_clip()
|
||||
end
|
||||
|
||||
cr:select_font_face(label_font_family, "normal", "normal")
|
||||
cr:set_font_size(info_size)
|
||||
cr:set_font_face(cr:get_font_face())
|
||||
msg = current_info
|
||||
ext = cr:text_extents(msg)
|
||||
cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing)
|
||||
cr:text_path(msg)
|
||||
cr:set_source_rgba(1, 1, 1, 1)
|
||||
cr:fill()
|
||||
cr:move_to(width / 2 - ext.width / 2 - ext.x_bearing, height / 2 - ext.height / 2 - ext.y_bearing)
|
||||
cr:text_path(msg)
|
||||
cr:set_source_rgba(0, 0, 0, 1)
|
||||
cr:set_line_width(2.0)
|
||||
cr:stroke()
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
print("closed areas:")
|
||||
for i, a in ipairs(closed_areas) do
|
||||
print(" " .. _area_tostring(a))
|
||||
end
|
||||
print("open areas:")
|
||||
for i, a in ipairs(open_areas) do
|
||||
print(" " .. _area_tostring(a))
|
||||
end
|
||||
infobox.bgimage = draw_info
|
||||
end
|
||||
|
||||
|
||||
print("interactive layout editing starts")
|
||||
|
||||
init()
|
||||
init(screen.workarea)
|
||||
refresh()
|
||||
|
||||
kg = keygrabber.run(function (mod, key, event)
|
||||
|
@ -461,7 +463,7 @@ function start_editor(data)
|
|||
end
|
||||
|
||||
print("restore history #" .. tostring(cmd_index) .. ":" .. data.cmds[cmd_index])
|
||||
init()
|
||||
init(screen.workarea)
|
||||
for i = 1, #data.cmds[cmd_index] do
|
||||
cmd = data.cmds[cmd_index]:sub(i, i)
|
||||
|
||||
|
@ -528,7 +530,7 @@ function start_editor(data)
|
|||
if to_exit then
|
||||
print("interactive layout editing ends")
|
||||
if to_apply then
|
||||
layout = api.layout.get(screen)
|
||||
local layout = api.layout.get(screen)
|
||||
if layout.set_regions then
|
||||
local areas_with_gap = {}
|
||||
for _, a in ipairs(closed_areas) do
|
||||
|
@ -546,6 +548,7 @@ function start_editor(data)
|
|||
end
|
||||
end
|
||||
)
|
||||
layout.cmd = current_cmd
|
||||
layout.set_regions(areas_with_gap)
|
||||
api.layout.arrange(screen)
|
||||
end
|
||||
|
@ -564,6 +567,49 @@ function start_editor(data)
|
|||
end)
|
||||
end
|
||||
|
||||
local function set_by_cmd(layout, screen, cmd)
|
||||
init(screen.workarea)
|
||||
push_history()
|
||||
|
||||
for i = 1, #cmd do
|
||||
local key = handle_command(cmd:sub(i, i))
|
||||
end
|
||||
|
||||
local areas_with_gap = {}
|
||||
for _, a in ipairs(closed_areas) do
|
||||
areas_with_gap[#areas_with_gap + 1] = shrink_area_with_gap(a, gap)
|
||||
end
|
||||
table.sort(
|
||||
areas_with_gap,
|
||||
function (a1, a2)
|
||||
local s1 = a1.width * a1.height
|
||||
local s2 = a2.width * a2.height
|
||||
if math.abs(s1 - s2) < 0.01 then
|
||||
return (a1.x + a1.y) < (a2.x + a2.y)
|
||||
else
|
||||
return s1 > s2
|
||||
end
|
||||
end
|
||||
)
|
||||
layout.cmd = cmd
|
||||
layout.set_regions(areas_with_gap)
|
||||
api.layout.arrange(screen)
|
||||
end
|
||||
|
||||
local function try_restore_last(layout, screen)
|
||||
local index = #data.cmds
|
||||
if index == 0 then return end
|
||||
|
||||
set_by_cmd(layout, screen, data.cmds[#data.cmds])
|
||||
end
|
||||
|
||||
return {
|
||||
start_interactive = start_interactive,
|
||||
set_by_cmd = set_by_cmd,
|
||||
try_restore_last = try_restore_last,
|
||||
}
|
||||
end
|
||||
|
||||
function restore_data(data)
|
||||
if data.history_file then
|
||||
local file, err = io.open(data.history_file, "r")
|
||||
|
@ -585,6 +631,6 @@ return
|
|||
{
|
||||
set_region = set_region,
|
||||
cycle_region = cycle_region,
|
||||
start_editor = start_editor,
|
||||
create = create,
|
||||
restore_data = restore_data,
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ function do_arrange(p, priv)
|
|||
end
|
||||
end
|
||||
|
||||
function create_layout(name, regions)
|
||||
function create()
|
||||
local priv = { regions = {} }
|
||||
|
||||
local function set_regions(regions)
|
||||
|
@ -72,10 +72,7 @@ function create_layout(name, regions)
|
|||
end
|
||||
end
|
||||
|
||||
set_regions(regions)
|
||||
|
||||
return {
|
||||
name = "machi[" .. name .. "]",
|
||||
arrange = function (p) do_arrange(p, priv) end,
|
||||
get_region_count = function () return #priv.regions end,
|
||||
set_regions = set_regions,
|
||||
|
@ -85,5 +82,5 @@ function create_layout(name, regions)
|
|||
end
|
||||
|
||||
return {
|
||||
create_layout = create_layout,
|
||||
create = create,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue