Added/Implemented machi-editor
This commit is contained in:
parent
9d91ceabcc
commit
f2094af979
13
lib/layout-machi/LICENSE
Normal file
13
lib/layout-machi/LICENSE
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright 2019 Xinhao Yuan
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
534
lib/layout-machi/README.md
Normal file
534
lib/layout-machi/README.md
Normal file
@ -0,0 +1,534 @@
|
||||
# Fork
|
||||
|
||||
Personal fork, not planning to get this merged upstream as some changes
|
||||
are too personalised. Just patch what you like.
|
||||
|
||||
Will try to make atomic commits you can easily rebase on upstream.
|
||||
|
||||
## Changes
|
||||
|
||||
### switcher
|
||||
|
||||
- hjkl shifts focus, super+hjkl moves (previously wasd/arrow keys)
|
||||
- prevent overlapping windows as much as possible (called tabs in source)
|
||||
- don't require switching with the switching UI (super+/ by default) for simple window operations like swap/tab-cycle/... (see examples)
|
||||
- ui tweaks
|
||||
- no transparency => no picom required
|
||||
|
||||
### editor
|
||||
|
||||
- cursor => editing of command
|
||||
- ctrl-c => clear command
|
||||
- ui tweaks
|
||||
- contrasting bg
|
||||
- highlight active area properly
|
||||
|
||||
### engine
|
||||
|
||||
- nothing
|
||||
|
||||
### layout
|
||||
|
||||
- prevent overlapping when changing layouts if new layout has less areas
|
||||
- ui tweaks
|
||||
- overlapping client callback (so you can theme them how you want) (see examples)
|
||||
|
||||
## Planned
|
||||
|
||||
- editor: perhaps do same changes as done in switcher but seems hardly worth it. I don't need to seem my windows while updating the layout
|
||||
- rewrite/refactor: too much duplicate code, no api layer, functions with side effects, ...
|
||||
|
||||
## Demo
|
||||
|
||||
https://user-images.githubusercontent.com/823696/152007355-9a87b606-fff9-4adf-b4b2-125075358d20.mp4
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```lua
|
||||
---
|
||||
--- Style overlapping clients
|
||||
--- I personal add a titlebar to the bottom of each client and change that color
|
||||
--- since it's more robust than borders and allows setting focus/normal color.
|
||||
--- Which you can't really do with borders (cleanly)
|
||||
---
|
||||
local theme = {
|
||||
machi_style_tabbed = function (client, tabbed)
|
||||
if tabbed then
|
||||
client.border_color = "#ff0000"
|
||||
return
|
||||
end
|
||||
client.border_color = "#000000"
|
||||
end,
|
||||
...
|
||||
}
|
||||
beautiful.init(theme)
|
||||
|
||||
---
|
||||
--- Start switcher ui
|
||||
---
|
||||
awful.key({modkey}, "/", function () machi.switcher.start(client.focus).ui() end),
|
||||
|
||||
---
|
||||
--- An awful.client.setmaster alternative for machi.
|
||||
---
|
||||
awful.key({modkey, "Control"}, "Return", function() machi.switcher.start(client.focus).master_swap(true) end),
|
||||
|
||||
---
|
||||
--- Tab through overlapping clients (no switcher ui needed)
|
||||
---
|
||||
awful.key({modkey}, "Tab", function () machi.switcher.start(client.focus).tab() end),
|
||||
|
||||
---
|
||||
--- Move by direction (no switcher ui needed)
|
||||
---
|
||||
awful.key({modkey, "Shift"}, "h", function() machi.switcher.start(client.focus).move("left") end),
|
||||
awful.key({modkey, "Shift"}, "j", function() machi.switcher.start(client.focus).move("down") end),
|
||||
awful.key({modkey, "Shift"}, "k", function() machi.switcher.start(client.focus).move("up") end),
|
||||
awful.key({modkey, "Shift"}, "l", function() machi.switcher.start(client.focus).move("right") end),
|
||||
|
||||
---
|
||||
--- Focus by direction (no switcher ui needed)
|
||||
---
|
||||
awful.key({modkey}, "h", function() machi.switcher.start(client.focus).focus("left") end),
|
||||
awful.key({modkey}, "j", function() machi.switcher.start(client.focus).focus("down") end),
|
||||
awful.key({modkey}, "k", function() machi.switcher.start(client.focus).focus("up") end),
|
||||
awful.key({modkey}, "l", function() machi.switcher.start(client.focus).focus("right") end),
|
||||
|
||||
---
|
||||
--- Focus by direction (what I use)
|
||||
---
|
||||
|
||||
function focus(dir)
|
||||
-- focus on the current screen (floating windows are ignored)
|
||||
if machi.switcher.start(client.focus).focus(dir) then
|
||||
return
|
||||
end
|
||||
-- we're at the edge of the screen
|
||||
-- might focus a floating window
|
||||
-- or switch to a screen in the given direction
|
||||
awful.client.focus.global_bydirection(dir)
|
||||
local c = client.focus
|
||||
local screen = awful.screen.focused()
|
||||
if c ~= nil and c.screen ~= screen then
|
||||
awful.screen.focus_bydirection(dir)
|
||||
end
|
||||
end
|
||||
awful.key({modkey}, "h", function() focus("left") end),
|
||||
awful.key({modkey}, "j", function() focus("down") end),
|
||||
awful.key({modkey}, "k", function() focus("up") end),
|
||||
awful.key({modkey}, "l", function() focus("right") end),
|
||||
|
||||
--
|
||||
-- Cycle predefined layouts
|
||||
--
|
||||
awful.key({ modkey, }, "z", function() nextMachiLayout() end),
|
||||
|
||||
function setMachiLayout(cmd)
|
||||
local screen = awful.screen.focused()
|
||||
local tag = screen.selected_tags[1]
|
||||
machi_layout.machi_set_cmd(cmd, tag, true)
|
||||
end
|
||||
|
||||
local machi_layouts = {
|
||||
"v1,4t3h1,4,1-h1,1cct3h1,4,1-t3v2.",
|
||||
"v13h1221h1331.",
|
||||
}
|
||||
local machi_layout_n = 0
|
||||
|
||||
function nextMachiLayout()
|
||||
machi_layout_n = machi_layout_n + 1
|
||||
if machi_layout_n > #machi_layouts then
|
||||
machi_layout_n = 1
|
||||
end
|
||||
local l = machi_layouts[machi_layout_n]
|
||||
setMachiLayout(l)
|
||||
end
|
||||
|
||||
--
|
||||
-- Swap by direction (credits: @basaran https://github.com/xinhaoyuan/layout-machi/issues/13)
|
||||
-- Inferior to moving imo but prevents overlapping.
|
||||
--
|
||||
function swap(dir)
|
||||
local cltbl = awful.client.visible(client.focus.screen, true)
|
||||
local grect = require("gears.geometry").rectangle
|
||||
local geomtbl = {}
|
||||
|
||||
for i,cl in ipairs(cltbl) do
|
||||
geomtbl[i] = cl:geometry()
|
||||
end
|
||||
|
||||
local target = grect.get_in_direction(dir, geomtbl, client.focus:geometry())
|
||||
|
||||
tobe = cltbl[target]:geometry()
|
||||
is = client.focus:geometry()
|
||||
|
||||
client.focus:geometry(tobe)
|
||||
cltbl[target]:geometry(is)
|
||||
end
|
||||
|
||||
|
||||
awful.key({ modkey, "Shift" }, "h", function() swap("left") end),
|
||||
awful.key({ modkey, "Shift" }, "j", function() swap("down") end),
|
||||
awful.key({ modkey, "Shift" }, "k", function() swap("up") end),
|
||||
awful.key({ modkey, "Shift" }, "l", function() swap("right") end),
|
||||
|
||||
```
|
||||
|
||||
Original readme:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#  layout-machi
|
||||
|
||||
A manual layout for Awesome with a rapid interactive editor.
|
||||
|
||||
Demos: https://imgur.com/a/OlM60iw
|
||||
|
||||
Draft mode: https://imgur.com/a/BOvMeQL
|
||||
|
||||
__`ng` is merged into `master`. Checkout `legacy` tag for the previous master checkpoint.__
|
||||
|
||||
## 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.
|
||||
4. There is no more per-layout setting of "draft mode". Every window has its own setting.
|
||||
|
||||
### New features & enhancements
|
||||
|
||||
1. Areas are protected by a minimum size (not configurable for now).
|
||||
2. More tolerating "safer" error handling. If the screen cannot fit the minimum size of the layout, areas out of the screen will be hidden, but it will not crash the layout logic.
|
||||
3. Dynamic size adjustment with propagation.
|
||||
4. Editor can be used on areas instead of entire screens.
|
||||
|
||||
## Why?
|
||||
|
||||
TL;DR --- To bring back the control of the window layout.
|
||||
|
||||
1. Dynamic tiling can be an overkill, since tiling is only useful for persistent windows, and people extensively use hibernate/sleep these days.
|
||||
2. Having window moving around can be annoying whenever a new window shows up.
|
||||
3. I want a flexible layout such that I can quickly adjust to whatever I need.
|
||||
|
||||
## Compatibilities
|
||||
|
||||
I developed it with Awesome git version. Hopefully it works with 4.3 stable.
|
||||
Please let me know if it does not work in 4.3 or older versions.
|
||||
|
||||
## Really quick usage
|
||||
|
||||
See `rc.patch` for adding layout-machi to the default 4.3 config.
|
||||
|
||||
## Quick usage
|
||||
|
||||
Suppose this git is checked out at `~/.config/awesome/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.
|
||||
|
||||
The package comes with the icon for `layoutbox`, which can be set with the following statement (after a theme has been loaded):
|
||||
|
||||
`require("beautiful").layout_machi = machi.get_icon()`
|
||||
|
||||
By default, any machi layout will use the layout command from `machi.layout.default_cmd`, which is initialized as `w66.` (see interpretation below).
|
||||
You can change it after loading the module.
|
||||
|
||||
## Use the layout
|
||||
|
||||
Use `local layout = machi.layout.create(args)` to instantiate the layout with an editor object. `args` is a table of arguments, where the followings can be used:
|
||||
|
||||
- `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`).
|
||||
- `new_placement_cb`: a callback `function(c, instance, areas, geometry)` that fits new client `c` into the areas.
|
||||
This is a new and experimental feature. The interface is subject to changes.
|
||||
|
||||
If `name` and `name_func` are both nil, a default name function will be used, which depends on the tag names, screen geometries, and `icon_name`.
|
||||
|
||||
The function is compatible with the previous `machi.layout.create(name, editor, default_cmd)` calls.
|
||||
|
||||
For `new_placement_cb` the arguments are:
|
||||
- `c`: the new client to be placed.
|
||||
- `instance`: a layout and tag depedent table with the following fields available:
|
||||
- `cmd`: the current layout command.
|
||||
- `client_data`: a mapping from previously managed clients to their layout related settings and assigned areas.
|
||||
Note that it may contain some clients that are no longer in the layout. You can filter them using `screen.tiled_clients`.
|
||||
Each entry is a table with fields:
|
||||
- `.placement`: If true, the client has been placed by the layout, otherwise `new_placement_cb` will be called on the client in the further.
|
||||
- `.area`: If it is non-nil, the window is fit in the area.
|
||||
- `.lu`, `.rd`: If those are non-nil, the window is in draft mode and the fields are for the areas of its corners.
|
||||
- `.draft`: if non-nil, this is the overriding perference of draft mode for the window.
|
||||
- `tag_data`: a mapping from area ids to their fake tag data. This is for nested layouts.
|
||||
- `areas`: the current array of areas produced by `instance.cmd`. Each area is a table with the following fields available:
|
||||
- `id`: self index of the array.
|
||||
- `habitable`: if true, the area is for placing windows. It could be false for a parent area, or an area disabled by command `/`.
|
||||
- `x`, `y`, `width`, `height`: area geometry.
|
||||
- `layout`: the string used to index the nested layout, if any.
|
||||
- `geometry`: the output table the client geometry. Note that the geometry _includes_ the borders.
|
||||
|
||||
The callback places the new client by changing its geometry and client data.
|
||||
Note that after the callback machi will validate the geometry and fit into the areas.
|
||||
So no need to set the `.area`, `.lu`, or `.rd` field of the client data in the callback.
|
||||
For example, to place new client in the largest area among empty areas, create the layout with
|
||||
```
|
||||
machi.layout.create{ new_placement_cb = machi.layout.placement.empty }
|
||||
```
|
||||
|
||||
## The layout editor and commands
|
||||
|
||||
### Starting editor in lua
|
||||
|
||||
Call `local editor = machi.editor.create()` to create an editor.
|
||||
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 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. If `Shift` is hold, restores to the current (maybe transcoded) command of the layout.
|
||||
3. `Escape`: exit the editor without saving 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
|
||||
|
||||
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. 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 or string.
|
||||
|
||||
- `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 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.
|
||||
|
||||
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:
|
||||
|
||||
`h-v`
|
||||
|
||||
```
|
||||
11 22
|
||||
11 22
|
||||
11
|
||||
11 33
|
||||
11 33
|
||||
```
|
||||
|
||||
|
||||
`hvv` (or `22w`)
|
||||
|
||||
```
|
||||
11 33
|
||||
11 33
|
||||
|
||||
22 44
|
||||
22 44
|
||||
```
|
||||
|
||||
|
||||
`131h2v-12v`
|
||||
|
||||
Details:
|
||||
|
||||
- `131h`: horizontally split the initial area (entire desktop) to the ratio of 1:3:1
|
||||
- For the first `1` part:
|
||||
- `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
|
||||
|
||||
Tada!
|
||||
|
||||
```
|
||||
11 3333 44
|
||||
11 3333 44
|
||||
11 3333
|
||||
11 3333 55
|
||||
3333 55
|
||||
22 3333 55
|
||||
22 3333 55
|
||||
```
|
||||
|
||||
|
||||
`12210121d`
|
||||
|
||||
```
|
||||
11 2222 3333 44
|
||||
11 2222 3333 44
|
||||
|
||||
55 6666 7777 88
|
||||
55 6666 7777 88
|
||||
55 6666 7777 88
|
||||
55 6666 7777 88
|
||||
|
||||
99 AAAA BBBB CC
|
||||
99 AAAA BBBB CC
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
`d` command works similarly after the inital grid is defined, such as `d1221012210221212121222`.
|
||||
|
||||
### Draft mode
|
||||
|
||||
Unlike the regular placement, where a window fits in a single area, windows in draft mode can span across multiple areas.
|
||||
Each drafting 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.
|
||||
|
||||
Draft mode is suppose to work well with grid areas (produced by `d` or `w` operations), but it is not limited to those.
|
||||
Draft mode is enabled for a newly placed window if
|
||||
(a) `new_placement_cb` returns so, or
|
||||
(b) `new_placement_cb` is unspecified and the window's UL and BR corners fit different areas.
|
||||
Resizing a window to a single area disables drafting, otherwise resizing across areas enables drafting.
|
||||
You can also use `f` or `.` key in switcher UI to manually cycle through modes despit how the window previously spans areas.
|
||||
|
||||
### Nested layouts
|
||||
|
||||
__This feature is a toy. It may come with performance and usability issues - you have been warned.__
|
||||
|
||||
Known caveats include:
|
||||
|
||||
1. `arrange()` of the nested layouts are always called when the machi `arrange()` is called. This could be optimized with caching.
|
||||
2. `client.*wfact` and other layout related operations don't work as machi fakes tag data to the nested layout engine.
|
||||
But it hopefully works if one changes the fields in the faked tag data.
|
||||
|
||||
__This feature is not available for windows 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 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.
|
||||
|
||||

|
||||
|
||||
### Persistent history
|
||||
|
||||
By default, the last 100 command sequences are stored in `.cache/awesome/history_machi`.
|
||||
To change that, please refer to `editor.lua`. (XXX more documents)
|
||||
|
||||
## Switcher
|
||||
|
||||
Calling `machi.switcher.start()` will create a switcher supporting the following keys:
|
||||
|
||||
- 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.
|
||||
- `q` or `PageUp` (`Prior`): select the parent of the current area. Hold `Control` to resize the current window accordingly.
|
||||
- `e` or `PageDown` (`Next`): select the previous child of the current area, if `q` or `PageUp` was used. Hold `Control` to resize the current window accordingly.
|
||||
- `f` or `.`: toggle the per-window setting of draft mode.
|
||||
- `/`: open 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
|
||||
|
||||
A compositor (e.g. picom, compton, xcompmgr) is required. Otherwise switcher and editor will block the clients.
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 --- See LICENSE
|
672
lib/layout-machi/editor.lua
Normal file
672
lib/layout-machi/editor.lua
Normal file
@ -0,0 +1,672 @@
|
||||
local this_package = ... and (...):match("(.-)[^%.]+$") or ""
|
||||
local machi_engine = require(this_package.."engine")
|
||||
local beautiful = require("beautiful")
|
||||
local awful = require("awful")
|
||||
local wibox = require("wibox")
|
||||
local naughty = require("naughty")
|
||||
local gears = require("gears")
|
||||
local gfs = require("gears.filesystem")
|
||||
local lgi = require("lgi")
|
||||
local dpi = require("beautiful.xresources").apply_dpi
|
||||
|
||||
local ERROR = 2
|
||||
local WARNING = 1
|
||||
local INFO = 0
|
||||
local DEBUG = -1
|
||||
|
||||
local module = {
|
||||
log_level = WARNING,
|
||||
nested_layouts = {
|
||||
["0"] = awful.layout.suit.tile,
|
||||
["1"] = awful.layout.suit.spiral,
|
||||
["2"] = awful.layout.suit.fair,
|
||||
["3"] = awful.layout.suit.fair.horizontal,
|
||||
},
|
||||
}
|
||||
|
||||
local function log(level, msg)
|
||||
if level > module.log_level then
|
||||
print(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function with_alpha(col, alpha)
|
||||
local r, g, b
|
||||
_, r, g, b, _ = col:get_rgba()
|
||||
return lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha)
|
||||
end
|
||||
|
||||
local function max(a, b)
|
||||
if a < b then return b else return a end
|
||||
end
|
||||
|
||||
local function is_tiling(c)
|
||||
return
|
||||
not (c.tomb_floating or c.floating or c.maximized_horizontal or c.maximized_vertical or c.maximized or c.fullscreen)
|
||||
end
|
||||
|
||||
local function set_tiling(c)
|
||||
c.floating = false
|
||||
c.maximized = false
|
||||
c.maximized_vertical = false
|
||||
c.maximized_horizontal = false
|
||||
c.fullscreen = false
|
||||
end
|
||||
|
||||
local function _area_tostring(wa)
|
||||
return "{x:" .. tostring(wa.x) .. ",y:" .. tostring(wa.y) .. ",w:" .. tostring(wa.width) .. ",h:" .. tostring(wa.height) .. "}"
|
||||
end
|
||||
|
||||
local function shrink_area_with_gap(a, gap)
|
||||
return {
|
||||
x = a.x + gap,
|
||||
y = a.y + gap,
|
||||
width = a.width - gap * 2,
|
||||
height = a.height - gap * 2,
|
||||
}
|
||||
end
|
||||
|
||||
function module.restore_data(data)
|
||||
if data.history_file then
|
||||
local file, err = io.open(data.history_file, "r")
|
||||
if err then
|
||||
log(INFO, "cannot read history from " .. data.history_file)
|
||||
else
|
||||
data.cmds = {}
|
||||
data.last_cmd = {}
|
||||
local last_layout_name
|
||||
for line in file:lines() do
|
||||
if line:sub(1, 1) == "+" then
|
||||
last_layout_name = line:sub(2, #line)
|
||||
else
|
||||
if last_layout_name ~= nil then
|
||||
log(DEBUG, "restore last cmd " .. line .. " for " .. last_layout_name)
|
||||
data.last_cmd[last_layout_name] = line
|
||||
last_layout_name = nil
|
||||
else
|
||||
log(DEBUG, "restore cmd " .. line)
|
||||
data.cmds[#data.cmds + 1] = line
|
||||
end
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function module.create(data)
|
||||
if data == nil then
|
||||
data = module.restore_data({
|
||||
history_file = gfs.get_cache_dir() .. "/history_machi",
|
||||
history_save_max = 100,
|
||||
})
|
||||
end
|
||||
|
||||
data.cmds = data.cmds or {}
|
||||
data.last_cmd = data.last_cmd or {}
|
||||
data.minimum_size = data.minimum_size or 100
|
||||
|
||||
|
||||
local function add_cmd(instance_name, cmd)
|
||||
-- remove duplicated entries
|
||||
local j = 1
|
||||
for i = 1, #data.cmds do
|
||||
if data.cmds[i] ~= cmd then
|
||||
data.cmds[j] = data.cmds[i]
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
for i = #data.cmds, j, -1 do
|
||||
table.remove(data.cmds, i)
|
||||
end
|
||||
|
||||
data.cmds[#data.cmds + 1] = cmd
|
||||
data.last_cmd[instance_name] = cmd
|
||||
if data.history_file then
|
||||
local file, err = io.open(data.history_file, "w")
|
||||
if err then
|
||||
log(ERROR, "cannot save history to " .. data.history_file)
|
||||
else
|
||||
for i = max(1, #data.cmds - data.history_save_max + 1), #data.cmds do
|
||||
if data.cmds[i] ~= "." then
|
||||
log(DEBUG, "save cmd " .. data.cmds[i])
|
||||
file:write(data.cmds[i] .. "\n")
|
||||
end
|
||||
end
|
||||
for name, cmd in pairs(data.last_cmd) do
|
||||
log(DEBUG, "save last cmd " .. cmd .. " for " .. name)
|
||||
file:write("+" .. name .. "\n" .. cmd .. "\n")
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function start_interactive(screen, embed_args)
|
||||
local info_size = dpi(60)
|
||||
-- colors are in rgba
|
||||
local border_color = with_alpha(
|
||||
gears.color(beautiful.machi_editor_border_color or beautiful.border_focus),
|
||||
beautiful.machi_editor_border_opacity or 0.75)
|
||||
local active_color = with_alpha(
|
||||
gears.color(beautiful.machi_editor_active_color or beautiful.bg_focus),
|
||||
beautiful.machi_editor_active_opacity or 0.5)
|
||||
local open_color = with_alpha(
|
||||
gears.color(beautiful.machi_editor_open_color or beautiful.bg_normal),
|
||||
beautiful.machi_editor_open_opacity or 0.5)
|
||||
local done_color = with_alpha(
|
||||
gears.color(beautiful.machi_editor_done_color or beautiful.bg_focus),
|
||||
beautiful.machi_editor_done_opacity or 0.5)
|
||||
local closed_color = open_color
|
||||
|
||||
if to_save == nil then
|
||||
to_save = true
|
||||
end
|
||||
|
||||
screen = screen or awful.screen.focused()
|
||||
local tag = screen.selected_tag
|
||||
local prev_layout = nil
|
||||
|
||||
if tag.layout.machi_set_cmd == nil then
|
||||
prev_layout = tag.layout
|
||||
for _, l in ipairs(awful.layout.layouts) do
|
||||
if l.machi_set_cmd ~= nil then
|
||||
awful.layout.set(l, tag)
|
||||
naughty.notify {
|
||||
text = 'Switched layout to machi',
|
||||
timeout = 2
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local gap = tag.gap or 0
|
||||
local layout = tag.layout
|
||||
|
||||
if layout.machi_set_cmd == nil then
|
||||
naughty.notify {
|
||||
text = "The layout to edit is not machi",
|
||||
timeout = 3
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local cmd_index = #data.cmds + 1
|
||||
data.cmds[cmd_index] = ""
|
||||
|
||||
local start_x = screen.workarea.x
|
||||
local start_y = screen.workarea.y
|
||||
|
||||
local kg
|
||||
local infobox = wibox({
|
||||
screen = screen,
|
||||
x = screen.workarea.x,
|
||||
y = screen.workarea.y,
|
||||
width = screen.workarea.width,
|
||||
height = screen.workarea.height,
|
||||
bg = "#ffffff00",
|
||||
opacity = 1,
|
||||
ontop = true,
|
||||
type = "dock",
|
||||
})
|
||||
infobox.visible = true
|
||||
|
||||
workarea = embed_args and embed_args.workarea or screen.workarea
|
||||
|
||||
local closed_areas
|
||||
local open_areas
|
||||
local pending_op
|
||||
local current_cmd
|
||||
local to_exit
|
||||
local to_apply
|
||||
|
||||
local key_translate_tab = {
|
||||
["Return"] = ".",
|
||||
[" "] = "-",
|
||||
}
|
||||
|
||||
local curpos = 0
|
||||
local current_info_pre = ""
|
||||
local current_info_post = ""
|
||||
local current_msg
|
||||
|
||||
local function set_cmd(cmd, arbitrary_input)
|
||||
local new_closed_areas, new_open_areas, new_pending_op = machi_engine.areas_from_command(
|
||||
cmd,
|
||||
{
|
||||
x = workarea.x + gap,
|
||||
y = workarea.y + gap,
|
||||
width = workarea.width - gap * 2,
|
||||
height = workarea.height - gap * 2
|
||||
},
|
||||
gap * 2 + data.minimum_size)
|
||||
if new_closed_areas or not arbitrary_input then
|
||||
if new_closed_areas then
|
||||
closed_areas, open_areas, pending_op =
|
||||
new_closed_areas, new_open_areas, new_pending_op
|
||||
end
|
||||
current_cmd = cmd
|
||||
|
||||
current_info_pre = current_cmd:sub(0, curpos)
|
||||
current_info_post = current_cmd:sub(curpos+1, #current_cmd)
|
||||
if embed_args then
|
||||
current_info_pre = embed_args.cmd_prefix.."["..current_info_pre
|
||||
current_info_post = current_info_post.."]"..embed_args.cmd_suffix
|
||||
end
|
||||
|
||||
current_msg = ""
|
||||
if new_closed_areas and #open_areas == 0 and not pending_op then
|
||||
current_msg = "(enter to apply)"
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local move_cursor = function(n)
|
||||
curpos = curpos + n
|
||||
if curpos > #current_cmd then
|
||||
curpos = #current_cmd
|
||||
elseif curpos < 0 then
|
||||
curpos = 0
|
||||
end
|
||||
-- trigger refresh
|
||||
set_cmd(current_cmd)
|
||||
end
|
||||
|
||||
|
||||
local function handle_key(key)
|
||||
if key_translate_tab[key] ~= nil then
|
||||
key = key_translate_tab[key]
|
||||
end
|
||||
|
||||
return set_cmd(current_cmd:sub(0, curpos)..key..current_cmd:sub(curpos+1, #current_cmd), true)
|
||||
end
|
||||
|
||||
|
||||
local function cleanup()
|
||||
infobox.visible = false
|
||||
if prev_layout ~= nil then
|
||||
awful.layout.set(prev_layout, tag)
|
||||
end
|
||||
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
|
||||
if a.habitable then
|
||||
local sa = shrink_area_with_gap(a, 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()
|
||||
if to_highlight then
|
||||
cr:set_source(done_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)
|
||||
cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height)
|
||||
cr:set_line_width(10.0)
|
||||
cr:stroke()
|
||||
cr:reset_clip()
|
||||
end
|
||||
end
|
||||
|
||||
for i, a in ipairs(open_areas) do
|
||||
local sa = shrink_area_with_gap(a, gap)
|
||||
local to_highlight = false
|
||||
if not pending_op 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(active_color)
|
||||
else
|
||||
cr:set_source(open_color)
|
||||
end
|
||||
cr:rectangle(sa.x - start_x, sa.y - start_y, sa.width, sa.height)
|
||||
cr:fill()
|
||||
|
||||
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 to_highlight then
|
||||
cr:stroke()
|
||||
else
|
||||
cr:set_dash({5, 5}, 0)
|
||||
cr:stroke()
|
||||
cr:set_dash({}, 0)
|
||||
end
|
||||
cr:reset_clip()
|
||||
end
|
||||
|
||||
local pl = lgi.Pango.Layout.create(cr)
|
||||
pl:set_font_description(beautiful.get_merged_font(beautiful.font, info_size))
|
||||
pl:set_alignment("CENTER")
|
||||
pl:set_text(current_info_pre)
|
||||
local w0, _ = pl:get_pixel_size()
|
||||
pl:set_text(current_info_pre..current_info_post)
|
||||
local w, h = pl:get_pixel_size()
|
||||
|
||||
local pl_msg = lgi.Pango.Layout.create(cr)
|
||||
pl_msg:set_font_description(beautiful.get_merged_font(beautiful.font, info_size))
|
||||
pl_msg:set_alignment("CENTER")
|
||||
pl_msg:set_text(current_msg)
|
||||
local w_msg, h_msg = pl_msg:get_pixel_size()
|
||||
local lh = pl_msg:get_line_spacing()
|
||||
|
||||
local draw = function(pl, w, h, y_offset, color)
|
||||
local ext = { width = w, height = h, x_bearing = 0, y_bearing = 0 }
|
||||
cr:move_to(
|
||||
width / 2 - ext.width / 2 - ext.x_bearing,
|
||||
y_offset + height / 2 - ext.height / 2 - ext.y_bearing
|
||||
)
|
||||
if color then
|
||||
cr:set_source(color)
|
||||
else
|
||||
cr:set_source_rgba(1, 1, 1, 1)
|
||||
end
|
||||
cr:show_layout(pl)
|
||||
cr:fill()
|
||||
end
|
||||
local wpad, hpad = dpi(50), dpi(5)
|
||||
local mw, mh = max(w, w_msg) + wpad, h + hpad
|
||||
if current_msg ~= "" then
|
||||
mh = mh + h_msg + lh
|
||||
end
|
||||
if mw < dpi(120) then
|
||||
mw = dpi(120)
|
||||
end
|
||||
cr:rectangle(width / 2 - mw / 2, height / 2 - (h + hpad) / 2, mw, mh)
|
||||
cr:set_source_rgba(0, 0, 0, 1)
|
||||
cr:fill()
|
||||
local cursor_border = 0
|
||||
local cursor_width = 1
|
||||
if cursor_border >= 0 then
|
||||
cr:rectangle(
|
||||
width / 2 - w / 2 + w0,
|
||||
height / 2 - h / 2 + lh,
|
||||
2 * cursor_border + cursor_width,
|
||||
h
|
||||
)
|
||||
cr:set_source_rgba(0, 0, 0, 0.8)
|
||||
cr:fill()
|
||||
end
|
||||
cr:rectangle(
|
||||
cursor_border + width / 2 - w / 2 + w0,
|
||||
cursor_border + height / 2 - h / 2 + lh,
|
||||
cursor_width,
|
||||
h - 2 * cursor_border
|
||||
)
|
||||
cr:set_source_rgba(1, 1, 1, 1)
|
||||
cr:fill()
|
||||
draw(pl, w, h, lh)
|
||||
draw(pl_msg, w_msg, h_msg, h + lh)
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
log(DEBUG, "closed areas:")
|
||||
for i, a in ipairs(closed_areas) do
|
||||
log(DEBUG, " " .. _area_tostring(a))
|
||||
end
|
||||
log(DEBUG, "open areas:")
|
||||
for i, a in ipairs(open_areas) do
|
||||
log(DEBUG, " " .. _area_tostring(a))
|
||||
end
|
||||
infobox.bgimage = draw_info
|
||||
end
|
||||
|
||||
local function get_final_cmd()
|
||||
local final_cmd = current_cmd
|
||||
if embed_args then
|
||||
final_cmd = embed_args.cmd_prefix ..
|
||||
machi_engine.areas_to_command(closed_areas, true) ..
|
||||
embed_args.cmd_suffix
|
||||
end
|
||||
return final_cmd
|
||||
end
|
||||
|
||||
log(DEBUG, "interactive layout editing starts")
|
||||
|
||||
set_cmd("")
|
||||
refresh()
|
||||
|
||||
kg = awful.keygrabber.run(
|
||||
function (mod, key, event)
|
||||
if event == "release" then
|
||||
return
|
||||
end
|
||||
|
||||
local ok, err = pcall(
|
||||
function ()
|
||||
if key == "Left" then
|
||||
move_cursor(-1)
|
||||
elseif key == "Right" then
|
||||
move_cursor(1)
|
||||
elseif key == "BackSpace" then
|
||||
local alt = false
|
||||
for _, m in ipairs(mod) do
|
||||
if m == "Shift" then
|
||||
alt = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if alt then
|
||||
if embed_args then
|
||||
set_cmd(embed_args.original_cmd or "")
|
||||
else
|
||||
local _cd, _td, areas = layout.machi_get_instance_data(screen, tag)
|
||||
set_cmd(machi_engine.areas_to_command(areas))
|
||||
end
|
||||
else
|
||||
local s = curpos - 1
|
||||
if s < 0 then
|
||||
s = 0
|
||||
end
|
||||
set_cmd(current_cmd:sub(0, s)..current_cmd:sub(curpos+1, #current_cmd))
|
||||
move_cursor(-1)
|
||||
end
|
||||
elseif key == "Escape" then
|
||||
table.remove(data.cmds, #data.cmds)
|
||||
to_exit = true
|
||||
elseif key == "Up" or key == "Down" then
|
||||
if current_cmd ~= data.cmds[cmd_index] then
|
||||
data.cmds[#data.cmds] = current_cmd
|
||||
end
|
||||
|
||||
if key == "Up" and cmd_index > 1 then
|
||||
cmd_index = cmd_index - 1
|
||||
elseif key == "Down" and cmd_index < #data.cmds then
|
||||
cmd_index = cmd_index + 1
|
||||
end
|
||||
|
||||
log(DEBUG, "restore history #" .. tostring(cmd_index) .. ":" .. data.cmds[cmd_index])
|
||||
set_cmd(data.cmds[cmd_index])
|
||||
move_cursor(#data.cmds[cmd_index])
|
||||
elseif #open_areas > 0 or pending_op or curpos < #current_cmd then
|
||||
if key == "." or key == "Return" then
|
||||
move_cursor(#current_cmd)
|
||||
end
|
||||
if handle_key(key) then
|
||||
move_cursor(1)
|
||||
end
|
||||
else
|
||||
if key == "Return" then
|
||||
local alt = false
|
||||
for _, m in ipairs(mod) do
|
||||
if m == "Shift" then
|
||||
alt = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local instance_name, persistent = layout.machi_get_instance_info(tag)
|
||||
if not alt and persistent then
|
||||
table.remove(data.cmds, #data.cmds)
|
||||
add_cmd(instance_name, get_final_cmd())
|
||||
current_msg = "Saved!"
|
||||
else
|
||||
current_msg = "Applied"
|
||||
end
|
||||
|
||||
to_exit = true
|
||||
to_apply = true
|
||||
end
|
||||
end
|
||||
|
||||
refresh()
|
||||
|
||||
if to_exit then
|
||||
log(DEBUG, "interactive layout editing ends")
|
||||
if to_apply then
|
||||
layout.machi_set_cmd(get_final_cmd(), tag)
|
||||
awful.layout.arrange(screen)
|
||||
gears.timer{
|
||||
timeout = 1,
|
||||
autostart = true,
|
||||
singleshot = true,
|
||||
callback = cleanup,
|
||||
}
|
||||
else
|
||||
cleanup()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
log(ERROR, "Getting error in keygrabber: " .. err)
|
||||
to_exit = true
|
||||
cleanup()
|
||||
end
|
||||
|
||||
if to_exit then
|
||||
awful.keygrabber.stop(kg)
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local function run_cmd(cmd, screen, tag)
|
||||
local gap = tag.gap
|
||||
local areas, closed = machi_engine.areas_from_command(
|
||||
cmd,
|
||||
{
|
||||
x = screen.workarea.x + gap,
|
||||
y = screen.workarea.y + gap,
|
||||
width = screen.workarea.width - gap * 2,
|
||||
height = screen.workarea.height - gap * 2
|
||||
},
|
||||
gap * 2 + data.minimum_size)
|
||||
if not areas or #closed > 0 then
|
||||
return nil
|
||||
end
|
||||
for _, a in ipairs(areas) do
|
||||
a.x = a.x + gap
|
||||
a.y = a.y + gap
|
||||
a.width = a.width - gap * 2
|
||||
a.height = a.height - gap * 2
|
||||
end
|
||||
return areas
|
||||
end
|
||||
|
||||
local function get_last_cmd(name)
|
||||
return data.last_cmd[name]
|
||||
end
|
||||
|
||||
function adjust_shares(c, axis, adj)
|
||||
if not c:isvisible() or c.floating or c.immobilized then
|
||||
return
|
||||
end
|
||||
local screen = c.screen
|
||||
local tag = screen.selected_tag
|
||||
local layout = tag.layout
|
||||
if not layout.machi_get_instance_data then return end
|
||||
local cd, _td, areas = layout.machi_get_instance_data(screen, tag)
|
||||
local key_shares = axis.."_shares"
|
||||
local key_spare = axis.."_spare"
|
||||
local key_parent_shares = "parent_"..axis.."_shares"
|
||||
|
||||
if not cd[c] or not cd[c].area then
|
||||
return
|
||||
end
|
||||
|
||||
if adj < 0 then
|
||||
if axis == "x" and c.width + adj < data.minimum_size then
|
||||
adj = data.minimum_size - c.width
|
||||
elseif axis == "y" and c.height + adj < data.minimum_size then
|
||||
adj = data.minimum_size - c.height
|
||||
end
|
||||
end
|
||||
|
||||
local function adjust(parent_id, shares, adj)
|
||||
-- The propagation part is questionable. But it is not critical anyway..
|
||||
if type(shares) ~= "table" then
|
||||
local old = areas[parent_id].split[key_shares][shares][2] or 0
|
||||
areas[parent_id].split[key_shares][shares][2] = old + adj
|
||||
else
|
||||
local acc = 0
|
||||
for i = 1, #shares do
|
||||
local old = areas[parent_id].split[key_shares][shares[i]][2] or 0
|
||||
local adj_split = i == #shares and adj - acc or math.floor(adj * i / #shares - acc + 0.5)
|
||||
areas[parent_id].split[key_shares][shares[i]][2] = old + adj_split
|
||||
acc = acc + adj_split
|
||||
end
|
||||
end
|
||||
if adj <= 0 then
|
||||
return #areas[parent_id].split[key_shares] > 1
|
||||
else
|
||||
return areas[parent_id].split[key_spare] >= adj
|
||||
end
|
||||
end
|
||||
|
||||
local area = cd[c].area
|
||||
while areas[area].parent_id do
|
||||
if adjust(areas[area].parent_id, areas[area][key_parent_shares], adj) then
|
||||
break
|
||||
end
|
||||
area = areas[area].parent_id
|
||||
end
|
||||
|
||||
layout.machi_set_cmd(machi_engine.areas_to_command(areas), tag, true)
|
||||
awful.layout.arrange(screen)
|
||||
end
|
||||
|
||||
function adjust_x_shares(c, adj)
|
||||
adjust_shares(c, "x", adj)
|
||||
end
|
||||
|
||||
function adjust_y_shares(c, adj)
|
||||
adjust_shares(c, "y", adj)
|
||||
end
|
||||
|
||||
return {
|
||||
start_interactive = start_interactive,
|
||||
run_cmd = run_cmd,
|
||||
get_last_cmd = get_last_cmd,
|
||||
adjust_x_shares = adjust_x_shares,
|
||||
adjust_y_shares = adjust_y_shares,
|
||||
}
|
||||
end
|
||||
|
||||
module.default_editor = module.create()
|
||||
|
||||
return module
|
942
lib/layout-machi/engine.lua
Normal file
942
lib/layout-machi/engine.lua
Normal file
@ -0,0 +1,942 @@
|
||||
-- area {
|
||||
-- x, y, width, height
|
||||
-- parent_id
|
||||
-- parent_cid
|
||||
-- parent_x_shares
|
||||
-- parent_y_shares
|
||||
-- habitable
|
||||
-- hole (unique)
|
||||
-- }
|
||||
--
|
||||
-- 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
|
||||
local need_recompute
|
||||
repeat
|
||||
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 b = require("beautiful")
|
||||
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
|
||||
a.habitable = true
|
||||
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.habitable = false
|
||||
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.habitable = false
|
||||
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 j = s, e do r[#r+1] = j 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().habitable = false
|
||||
|
||||
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
|
||||
|
||||
local 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)
|
||||
|
||||
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].habitable = false
|
||||
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].habitable = false
|
||||
end
|
||||
end
|
||||
|
||||
return closed_areas, open_areas, pending_op ~= nil
|
||||
end
|
||||
|
||||
local function areas_to_command(areas, to_embed, root_area)
|
||||
root_area = root_area or 1
|
||||
if #areas < root_area 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] or share[2] == 0 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 == nil then
|
||||
return ""
|
||||
end
|
||||
|
||||
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
|
||||
r = method..r..(#m == 0 and m or (method == "w" and "," or ",,"))..m
|
||||
end
|
||||
local acc_dashes = 0
|
||||
if a.expansion > 1 then
|
||||
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
|
||||
end
|
||||
|
||||
if area_id ~= root_area 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
|
||||
elseif a.disabled then
|
||||
r = "/"
|
||||
elseif a.layout then
|
||||
r = "x"..a.layout
|
||||
else
|
||||
r = "-"
|
||||
end
|
||||
|
||||
return r
|
||||
end
|
||||
|
||||
local r = get_command(root_area)
|
||||
if not to_embed then
|
||||
if r == "-" then
|
||||
r = "."
|
||||
else
|
||||
-- The last . may be redundant, but it makes sure no pending op.
|
||||
r = r:gsub("[\\c]+$", "").."."
|
||||
end
|
||||
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(".", ".")
|
||||
check_transcoded_command("3t.", ".")
|
||||
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.", "t3dw6,6.")
|
||||
print("Passed.")
|
||||
end
|
||||
|
||||
return {
|
||||
areas_from_command = areas_from_command,
|
||||
areas_to_command = areas_to_command,
|
||||
}
|
BIN
lib/layout-machi/icon.png
Normal file
BIN
lib/layout-machi/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
33
lib/layout-machi/init.lua
Normal file
33
lib/layout-machi/init.lua
Normal file
@ -0,0 +1,33 @@
|
||||
local engine = require(... .. ".engine")
|
||||
local layout = require(... .. ".layout")
|
||||
local editor = require(... .. ".editor")
|
||||
local switcher = require(... .. ".switcher")
|
||||
local default_editor = editor.default_editor
|
||||
local default_layout = layout.create{ name_func = default_name }
|
||||
local gcolor = require("gears.color")
|
||||
local beautiful = require("beautiful")
|
||||
|
||||
local icon_raw
|
||||
local source = debug.getinfo(1, "S").source
|
||||
if source:sub(1, 1) == "@" then
|
||||
icon_raw = source:match("^@(.-)[^/]+$") .. "icon.png"
|
||||
end
|
||||
|
||||
local function get_icon()
|
||||
if icon_raw ~= nil then
|
||||
return gcolor.recolor_image(icon_raw, beautiful.fg_normal)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
engine = engine,
|
||||
layout = layout,
|
||||
editor = editor,
|
||||
switcher = switcher,
|
||||
default_editor = default_editor,
|
||||
default_layout = default_layout,
|
||||
icon_raw = icon_raw,
|
||||
get_icon = get_icon,
|
||||
}
|
647
lib/layout-machi/layout.lua
Normal file
647
lib/layout-machi/layout.lua
Normal file
@ -0,0 +1,647 @@
|
||||
local this_package = ... and (...):match("(.-)[^%.]+$") or ""
|
||||
local machi_editor = require(this_package.."editor")
|
||||
local awful = require("awful")
|
||||
local gobject = require("gears.object")
|
||||
local capi = {
|
||||
screen = screen
|
||||
}
|
||||
|
||||
local ERROR = 2
|
||||
local WARNING = 1
|
||||
local INFO = 0
|
||||
local DEBUG = -1
|
||||
|
||||
local module = {
|
||||
log_level = WARNING,
|
||||
global_default_cmd = "w66.",
|
||||
allow_shrinking_by_mouse_moving = false,
|
||||
}
|
||||
|
||||
local function log(level, msg)
|
||||
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
|
||||
end
|
||||
|
||||
local function max(a, b)
|
||||
if a < b then return b else return a end
|
||||
end
|
||||
|
||||
local function get_screen(s)
|
||||
return s and capi.screen[s]
|
||||
end
|
||||
|
||||
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 unique (read empty) area for the area-like object
|
||||
--- unless all spots are taken.
|
||||
-- @param c area-like object - table with properties x, y, width, and height
|
||||
-- @param areas array of area objects
|
||||
-- @param uniq array that will keep track of matched (read: non empty) areas
|
||||
-- @return the index of the best area
|
||||
local function find_uniq_area(c, areas, uniq)
|
||||
local choice = 1
|
||||
local choice_value = nil
|
||||
|
||||
local room = false
|
||||
for i, a in ipairs(areas) do
|
||||
if not uniq[i] then
|
||||
room = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for i, a in ipairs(areas) do
|
||||
if a.habitable and (not room or (room and not uniq[i])) 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
|
||||
if choice_value == nil or choice_value < cap then
|
||||
choice = i
|
||||
choice_value = cap
|
||||
end
|
||||
end
|
||||
end
|
||||
uniq[choice] = true
|
||||
return choice
|
||||
end
|
||||
|
||||
--- find the best area for the area-like object
|
||||
-- @param c area-like object - table with properties x, y, width, and height
|
||||
-- @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 a.habitable 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)
|
||||
end
|
||||
|
||||
local function find_lu(c, areas, rd)
|
||||
local lu = nil
|
||||
for i, a in ipairs(areas) do
|
||||
if a.habitable 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, border_width, areas, lu)
|
||||
local x, y
|
||||
x = c.x + c.width + (border_width or 0) * 2
|
||||
y = c.y + c.height + (border_width or 0) * 2
|
||||
local rd = nil
|
||||
for i, a in ipairs(areas) do
|
||||
if a.habitable 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, 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 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
|
||||
|
||||
-- TODO: the string need to be updated when its screen geometry changed.
|
||||
local function get_machi_tag_string(tag)
|
||||
return tostring(tag.screen.geometry.width) .. "x" .. tostring(tag.screen.geometry.height) .. "+" ..
|
||||
tostring(tag.screen.geometry.x) .. "+" .. tostring(tag.screen.geometry.y) .. '+' .. tag.name
|
||||
end
|
||||
|
||||
function module.create(args_or_name, editor, default_cmd)
|
||||
local args
|
||||
if type(args_or_name) == "string" then
|
||||
args = {
|
||||
name = args_or_name
|
||||
}
|
||||
elseif type(args_or_name) == "function" then
|
||||
args = {
|
||||
name_func = args_or_name
|
||||
}
|
||||
elseif type(args_or_name) == "table" then
|
||||
args = args_or_name
|
||||
else
|
||||
return nil
|
||||
end
|
||||
if args.name == nil and args.name_func == nil then
|
||||
local prefix = args.icon_name and (args.icon_name.."-") or ""
|
||||
args.name_func = function (tag)
|
||||
return prefix..get_machi_tag_string(tag)
|
||||
end
|
||||
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
|
||||
|
||||
local layout = {}
|
||||
local instances = {}
|
||||
|
||||
local function get_instance_info(tag)
|
||||
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,
|
||||
areas_cache = {},
|
||||
tag_data = {},
|
||||
client_data = setmetatable({}, {__mode="k"}),
|
||||
}
|
||||
if instances[name].cmd == nil then
|
||||
instances[name].cmd = args.default_cmd
|
||||
end
|
||||
end
|
||||
return instances[name]
|
||||
end
|
||||
|
||||
local function get_instance_data(screen, tag)
|
||||
if screen == nil then return end
|
||||
local workarea = screen.workarea
|
||||
local instance = get_instance_(tag)
|
||||
local cmd = instance.cmd or module.global_default_cmd
|
||||
if cmd == nil then return 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)
|
||||
if instance.areas_cache[key] == nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
return instance.client_data, instance.tag_data, instance.areas_cache[key], instance, args.new_placement_cb
|
||||
end
|
||||
|
||||
local function set_cmd(cmd, tag, keep_instance_data)
|
||||
local instance = get_instance_(tag)
|
||||
if instance.cmd ~= cmd then
|
||||
instance.cmd = cmd
|
||||
instance.areas_cache = {}
|
||||
tag:emit_signal("property::layout")
|
||||
if not keep_instance_data then
|
||||
instance.tag_data = {}
|
||||
instance.client_data = setmetatable({}, {__mode="k"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local clean_up
|
||||
local tag_data = setmetatable({}, {__mode = "k"})
|
||||
|
||||
clean_up = function (tag)
|
||||
local screen = tag.screen
|
||||
if not screen then return end
|
||||
local _cd, _td, _areas, instance, _new_placement_cb = get_instance_data(screen, tag)
|
||||
|
||||
if tag_data[tag].regsitered then
|
||||
tag_data[tag].regsitered = false
|
||||
tag:disconnect_signal("property::layout", clean_up)
|
||||
tag:disconnect_signal("property::selected", clean_up)
|
||||
for _, tag in pairs(instance.tag_data) do
|
||||
tag:emit_signal("property::layout")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
clean_up_on_selected_change = function (tag)
|
||||
if not tag.selected then clean_up(tag) end
|
||||
end
|
||||
|
||||
local function arrange(p)
|
||||
local useless_gap = p.useless_gap
|
||||
local screen = get_screen(p.screen)
|
||||
local cls = p.clients
|
||||
local tag = p.tag or screen.selected_tag
|
||||
local cd, td, areas, instance, new_placement_cb = get_instance_data(screen, tag)
|
||||
|
||||
if not tag_data[tag] then tag_data[tag] = {} end
|
||||
if not tag_data[tag].registered then
|
||||
tag_data[tag].regsitered = true
|
||||
tag:connect_signal("property::layout", clean_up)
|
||||
tag:connect_signal("property::selected", clean_up)
|
||||
end
|
||||
|
||||
if areas == nil then return end
|
||||
local nested_clients = {}
|
||||
|
||||
local function place_client_in_area(c, area)
|
||||
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
|
||||
p.geometries[c] = {}
|
||||
module.set_geometry(p.geometries[c], areas[area], areas[area], useless_gap, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- Make clients calling new_placement_cb appear in the end.
|
||||
local j = 0
|
||||
for i = 1, #cls do
|
||||
cd[cls[i]] = cd[cls[i]] or {}
|
||||
if cd[cls[i]].placement then
|
||||
j = j + 1
|
||||
cls[j], cls[i] = cls[i], cls[j]
|
||||
end
|
||||
end
|
||||
|
||||
local empty_areas = {}
|
||||
for i, c in ipairs(cls) do
|
||||
if c.floating or c.immobilized then
|
||||
log(DEBUG, "Ignore client " .. tostring(c))
|
||||
else
|
||||
local geo = {
|
||||
x = c.x,
|
||||
y = c.y,
|
||||
width = c.width + c.border_width * 2,
|
||||
height = c.height + c.border_width * 2,
|
||||
}
|
||||
|
||||
if not cd[c].placement and new_placement_cb then
|
||||
cd[c].placement = true
|
||||
new_placement_cb(c, instance, areas, geo)
|
||||
end
|
||||
|
||||
local in_draft = cd[c].draft
|
||||
if cd[c].draft ~= nil then
|
||||
in_draft = cd[c].draft
|
||||
elseif cd[c].lu then
|
||||
in_draft = true
|
||||
elseif cd[c].area then
|
||||
in_draft = false
|
||||
else
|
||||
in_draft = nil
|
||||
end
|
||||
|
||||
local skip = false
|
||||
|
||||
if in_draft ~= false then
|
||||
if cd[c].lu ~= nil and cd[c].rd ~= nil and
|
||||
cd[c].lu <= #areas and cd[c].rd <= #areas and
|
||||
areas[cd[c].lu].habitable and areas[cd[c].rd].habitable
|
||||
then
|
||||
if areas[cd[c].lu].x == geo.x and
|
||||
areas[cd[c].lu].y == geo.y and
|
||||
areas[cd[c].rd].x + areas[cd[c].rd].width == geo.x + geo.width and
|
||||
areas[cd[c].rd].y + areas[cd[c].rd].height == geo.y + geo.height
|
||||
then
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
local lu = nil
|
||||
local rd = nil
|
||||
if not skip then
|
||||
log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
|
||||
lu = find_lu(geo, areas)
|
||||
if lu ~= nil then
|
||||
geo.x = areas[lu].x
|
||||
geo.y = areas[lu].y
|
||||
rd = find_rd(geo, 0, areas, lu)
|
||||
end
|
||||
end
|
||||
|
||||
if lu ~= nil and rd ~= nil then
|
||||
if lu == rd and cd[c].lu == nil then
|
||||
cd[c].area = lu
|
||||
place_client_in_area(c, lu)
|
||||
else
|
||||
cd[c].lu = lu
|
||||
cd[c].rd = rd
|
||||
cd[c].area = nil
|
||||
p.geometries[c] = {}
|
||||
module.set_geometry(p.geometries[c], areas[lu], areas[rd], useless_gap, 0)
|
||||
end
|
||||
end
|
||||
else
|
||||
if cd[c].area ~= nil and
|
||||
cd[c].area <= #areas and
|
||||
areas[cd[c].area].habitable and
|
||||
areas[cd[c].area].layout == nil and
|
||||
areas[cd[c].area].x == geo.x and
|
||||
areas[cd[c].area].y == geo.y and
|
||||
areas[cd[c].area].width == geo.width and
|
||||
areas[cd[c].area].height == geo.height
|
||||
then
|
||||
skip = true
|
||||
else
|
||||
log(DEBUG, "Compute areas for " .. (c.name or ("<untitled:" .. tostring(c) .. ">")))
|
||||
local area = find_uniq_area(geo, areas, empty_areas)
|
||||
cd[c].area, cd[c].lu, cd[c].rd = area, nil, nil
|
||||
place_client_in_area(c, area)
|
||||
end
|
||||
end
|
||||
|
||||
if skip then
|
||||
if geo.x ~= c.x or geo.y ~= c.y or
|
||||
geo.width ~= c.width + c.border_width * 2 or
|
||||
geo.height ~= c.height + c.border_width * 2 then
|
||||
p.geometries[c] = {}
|
||||
module.set_geometry(p.geometries[c], geo, geo, useless_gap, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local arranged_area = {}
|
||||
local function arrange_nested_layout(area, clients)
|
||||
local nested_layout = machi_editor.nested_layouts[areas[area].layout]
|
||||
if not nested_layout then return end
|
||||
if td[area] == nil then
|
||||
local tag = gobject{}
|
||||
td[area] = tag
|
||||
-- TODO: Make the default more flexible.
|
||||
tag.layout = nested_layout
|
||||
tag.column_count = 1
|
||||
tag.master_count = 1
|
||||
tag.master_fill_policy = "expand"
|
||||
tag.gap = 0
|
||||
tag.master_width_factor = 0.5
|
||||
tag._private = {
|
||||
awful_tag_properties = {
|
||||
},
|
||||
}
|
||||
end
|
||||
local nested_params = {
|
||||
tag = td[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 = {},
|
||||
}
|
||||
nested_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
|
||||
for area, clients in pairs(nested_clients) do
|
||||
arranged_area[area] = true
|
||||
arrange_nested_layout(area, clients)
|
||||
end
|
||||
-- Also rearrange empty nested layouts.
|
||||
-- TODO Iterate through only if the area has a nested layout
|
||||
for area, data in pairs(areas) do
|
||||
if not arranged_area[area] and areas[area].layout then
|
||||
arrange_nested_layout(area, {})
|
||||
end
|
||||
end
|
||||
|
||||
local b = require("beautiful")
|
||||
local style_tabbed = b.machi_style_tabbed
|
||||
if style_tabbed == nil then
|
||||
return
|
||||
end
|
||||
local area_client_count = {}
|
||||
for _, oc in ipairs(screen.tiled_clients) do
|
||||
local cd = instance.client_data[oc]
|
||||
if cd and cd.placement and cd.area then
|
||||
if area_client_count[cd.area] == nil then
|
||||
area_client_count[cd.area] = {}
|
||||
end
|
||||
table.insert(area_client_count[cd.area], oc)
|
||||
end
|
||||
end
|
||||
for i, v in pairs(area_client_count) do
|
||||
local tabbed = #v > 1
|
||||
for _, c in pairs(v) do
|
||||
style_tabbed(c, tabbed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function resize_handler (c, context, h)
|
||||
local tag = c.screen.selected_tag
|
||||
local instance = get_instance_(tag)
|
||||
local cd = instance.client_data
|
||||
local cd, td, areas, _placement_cb = get_instance_data(c.screen, tag)
|
||||
|
||||
if areas == nil then return end
|
||||
|
||||
if context == "mouse.move" then
|
||||
local in_draft = cd[c].draft
|
||||
if cd[c].draft ~= nil then
|
||||
in_draft = cd[c].draft
|
||||
elseif cd[c].lu then
|
||||
in_draft = true
|
||||
elseif cd[c].area then
|
||||
in_draft = false
|
||||
else
|
||||
log(ERROR, "Assuming in_draft for unhandled client "..tostring(c))
|
||||
in_draft = true
|
||||
end
|
||||
if in_draft then
|
||||
local lu = find_lu(h, areas)
|
||||
local rd = nil
|
||||
if lu ~= nil 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, 0, areas, lu)
|
||||
|
||||
if rd ~= nil and not module.allowing_shrinking_by_mouse_moving and
|
||||
(areas[rd].x + areas[rd].width - areas[lu].x < c.full_width_before_move or
|
||||
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
|
||||
|
||||
if lu ~= nil and rd ~= nil then
|
||||
cd[c].lu = lu
|
||||
cd[c].rd = rd
|
||||
cd[c].area = nil
|
||||
module.set_geometry(c, areas[lu], areas[rd], 0, c.border_width)
|
||||
end
|
||||
end
|
||||
else
|
||||
local center_x = h.x + h.width / 2
|
||||
local center_y = h.y + h.height / 2
|
||||
|
||||
local choice = nil
|
||||
local choice_value = nil
|
||||
|
||||
for i, a in ipairs(areas) do
|
||||
if a.habitable then
|
||||
local ac_x = a.x + a.width / 2
|
||||
local ac_y = a.y + a.height / 2
|
||||
local dis = (ac_x - center_x) * (ac_x - center_x) + (ac_y - center_y) * (ac_y - center_y)
|
||||
if choice_value == nil or choice_value > dis then
|
||||
choice = i
|
||||
choice_value = dis
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if choice and cd[c].area ~= choice then
|
||||
cd[c].lu = nil
|
||||
cd[c].rd = nil
|
||||
cd[c].area = choice
|
||||
module.set_geometry(c, areas[choice], areas[choice], 0, c.border_width)
|
||||
end
|
||||
end
|
||||
elseif cd[c].draft ~= false then
|
||||
local lu = find_lu(h, areas)
|
||||
local rd = nil
|
||||
if lu ~= nil then
|
||||
local hh = {}
|
||||
hh.x = h.x
|
||||
hh.y = h.y
|
||||
hh.width = h.width
|
||||
hh.height = h.height
|
||||
rd = find_rd(hh, c.border_width, areas, lu)
|
||||
end
|
||||
|
||||
if lu ~= nil and rd ~= nil then
|
||||
if lu == rd and cd[c].draft ~= true then
|
||||
cd[c].lu = nil
|
||||
cd[c].rd = nil
|
||||
cd[c].area = lu
|
||||
awful.layout.arrange(c.screen)
|
||||
else
|
||||
cd[c].lu = lu
|
||||
cd[c].rd = rd
|
||||
cd[c].area = nil
|
||||
module.set_geometry(c, areas[lu], areas[rd], 0, c.border_width)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
layout.name = args.icon_name or "machi"
|
||||
layout.arrange = arrange
|
||||
layout.resize_handler = resize_handler
|
||||
layout.machi_editor = args.editor
|
||||
layout.machi_get_instance_info = get_instance_info
|
||||
layout.machi_get_instance_data = get_instance_data
|
||||
layout.machi_set_cmd = set_cmd
|
||||
return layout
|
||||
end
|
||||
|
||||
module.placement = {}
|
||||
|
||||
local function empty_then_maybe_fair(c, instance, areas, geometry, do_fair)
|
||||
local area_client_count = {}
|
||||
for _, oc in ipairs(c.screen.tiled_clients) do
|
||||
local cd = instance.client_data[oc]
|
||||
if cd and cd.placement and cd.area then
|
||||
area_client_count[cd.area] = (area_client_count[cd.area] or 0) + 1
|
||||
end
|
||||
end
|
||||
local choice_client_count = nil
|
||||
local choice_spare_score = nil
|
||||
local choice = nil
|
||||
for i = 1, #areas do
|
||||
local a = areas[i]
|
||||
if a.habitable then
|
||||
-- +1 for the new client
|
||||
local client_count = (area_client_count[i] or 0) + 1
|
||||
local spare_score = a.width * a.height / client_count
|
||||
if choice == nil or (choice_client_count > 1 and client_count == 1) then
|
||||
choice_client_count = client_count
|
||||
choice_spare_score = spare_score
|
||||
choice = i
|
||||
elseif (choice_client_count > 1) == (client_count > 1) and choice_spare_score < spare_score then
|
||||
choice_client_count = client_count
|
||||
choice_spare_score = spare_score
|
||||
choice = i
|
||||
end
|
||||
end
|
||||
end
|
||||
if choice_client_count > 1 and not do_fair then
|
||||
return
|
||||
end
|
||||
instance.client_data[c].lu = nil
|
||||
instance.client_data[c].rd = nil
|
||||
instance.client_data[c].area = choice
|
||||
geometry.x = areas[choice].x
|
||||
geometry.y = areas[choice].y
|
||||
geometry.width = areas[choice].width
|
||||
geometry.height = areas[choice].height
|
||||
end
|
||||
|
||||
function module.placement.empty(c, instance, areas, geometry)
|
||||
empty_then_maybe_fair(c, instance, areas, geometry, false)
|
||||
end
|
||||
|
||||
function module.placement.empty_then_fair(c, instance, areas, geometry)
|
||||
empty_then_maybe_fair(c, instance, areas, geometry, true)
|
||||
end
|
||||
|
||||
return module
|
BIN
lib/layout-machi/nested_layout_screenshot.png
Normal file
BIN
lib/layout-machi/nested_layout_screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 637 KiB |
38
lib/layout-machi/rc.patch
Normal file
38
lib/layout-machi/rc.patch
Normal file
@ -0,0 +1,38 @@
|
||||
--- /usr/etc/xdg/awesome/rc.lua 2019-10-02 22:20:36.000000000 -0400
|
||||
+++ rc.lua 2019-10-06 12:13:41.090197230 -0400
|
||||
@@ -17,6 +17,7 @@
|
||||
-- Enable hotkeys help widget for VIM and other apps
|
||||
-- when client with a matching name is opened:
|
||||
require("awful.hotkeys_popup.keys")
|
||||
+local machi = require("layout-machi")
|
||||
|
||||
-- {{{ Error handling
|
||||
-- Check if awesome encountered an error during startup and fell back to
|
||||
@@ -34,6 +35,8 @@
|
||||
-- Themes define colours, icons, font and wallpapers.
|
||||
beautiful.init(gears.filesystem.get_themes_dir() .. "default/theme.lua")
|
||||
|
||||
+beautiful.layout_machi = machi.get_icon()
|
||||
+
|
||||
-- This is used later as the default terminal and editor to run.
|
||||
terminal = "xterm"
|
||||
editor = os.getenv("EDITOR") or "nano"
|
||||
@@ -48,6 +51,7 @@
|
||||
|
||||
-- Table of layouts to cover with awful.layout.inc, order matters.
|
||||
awful.layout.layouts = {
|
||||
+ machi.default_layout,
|
||||
awful.layout.suit.floating,
|
||||
awful.layout.suit.tile,
|
||||
awful.layout.suit.tile.left,
|
||||
@@ -262,6 +266,10 @@
|
||||
awful.key({ modkey, "Shift" }, "q", awesome.quit,
|
||||
{description = "quit awesome", group = "awesome"}),
|
||||
|
||||
+ awful.key({ modkey, }, ".", function () machi.default_editor.start_interactive() end,
|
||||
+ {description = "edit the current layout if it is a machi layout", group = "layout"}),
|
||||
+ awful.key({ modkey, }, "/", function () machi.switcher.start(client.focus) end,
|
||||
+ {description = "switch between windows for a machi layout", group = "layout"}),
|
||||
awful.key({ modkey, }, "l", function () awful.tag.incmwfact( 0.05) end,
|
||||
{description = "increase master width factor", group = "layout"}),
|
||||
awful.key({ modkey, }, "h", function () awful.tag.incmwfact(-0.05) end,
|
841
lib/layout-machi/switcher.lua
Normal file
841
lib/layout-machi/switcher.lua
Normal file
@ -0,0 +1,841 @@
|
||||
local machi = {
|
||||
layout = require((...):match("(.-)[^%.]+$") .. "layout"),
|
||||
engine = require((...):match("(.-)[^%.]+$") .. "engine"),
|
||||
}
|
||||
|
||||
local capi = {
|
||||
client = client,
|
||||
screen = screen,
|
||||
}
|
||||
|
||||
local beautiful = require("beautiful")
|
||||
local wibox = require("wibox")
|
||||
local awful = require("awful")
|
||||
local gears = require("gears")
|
||||
local lgi = require("lgi")
|
||||
local dpi = require("beautiful.xresources").apply_dpi
|
||||
local gtimer = require("gears.timer")
|
||||
|
||||
local ERROR = 2
|
||||
local WARNING = 1
|
||||
local INFO = 0
|
||||
local DEBUG = -1
|
||||
|
||||
local module = {
|
||||
log_level = WARNING,
|
||||
}
|
||||
|
||||
local function log(level, msg)
|
||||
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
|
||||
end
|
||||
|
||||
local function max(a, b)
|
||||
if a < b then return b else return a end
|
||||
end
|
||||
|
||||
local function with_alpha(col, alpha)
|
||||
local r, g, b
|
||||
_, r, g, b, _ = col:get_rgba()
|
||||
return lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha)
|
||||
end
|
||||
|
||||
function module.start(c, exit_keys)
|
||||
local tablist_font_desc = beautiful.get_merged_font(
|
||||
beautiful.font, dpi(10))
|
||||
local font_color = with_alpha(gears.color(beautiful.fg_normal), 1)
|
||||
local font_color_hl = with_alpha(gears.color(beautiful.fg_focus), 1)
|
||||
local label_size = dpi(30)
|
||||
local border_color = with_alpha(
|
||||
gears.color(beautiful.machi_switcher_border_color or beautiful.border_focus),
|
||||
beautiful.machi_switcher_border_opacity or 0.25)
|
||||
local border_color_hl = with_alpha(
|
||||
gears.color(beautiful.machi_switcher_border_hl_color or beautiful.border_focus),
|
||||
beautiful.machi_switcher_border_hl_opacity or 0.75)
|
||||
local fill_color = with_alpha(
|
||||
gears.color(beautiful.machi_switcher_fill_color or beautiful.bg_normal),
|
||||
beautiful.machi_switcher_fill_opacity or 0.25)
|
||||
local box_bg = with_alpha(
|
||||
gears.color(beautiful.machi_switcher_box_bg or beautiful.bg_normal),
|
||||
beautiful.machi_switcher_box_opacity or 0.85)
|
||||
local fill_color_hl = with_alpha(
|
||||
gears.color(beautiful.machi_switcher_fill_color_hl or beautiful.bg_focus),
|
||||
beautiful.machi_switcher_fill_hl_opacity or 1)
|
||||
-- for comparing floats
|
||||
local threshold = 0.1
|
||||
local traverse_radius = dpi(5)
|
||||
|
||||
local screen = c and c.screen or awful.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
|
||||
|
||||
if layout.machi_get_instance_data == nil then return end
|
||||
|
||||
local cd, td, areas, _new_placement_cb = layout.machi_get_instance_data(screen, screen.selected_tag)
|
||||
if areas == nil or #areas == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local infobox, infotabbox
|
||||
local tablist = nil
|
||||
|
||||
local traverse_x, traverse_y
|
||||
if c then
|
||||
traverse_x = c.x + traverse_radius
|
||||
traverse_y = c.y + traverse_radius
|
||||
else
|
||||
traverse_x = screen.workarea.x + screen.workarea.width / 2
|
||||
traverse_y = screen.workarea.y + screen.workarea.height / 2
|
||||
end
|
||||
|
||||
local selected_area_ = nil
|
||||
local function set_selected_area(area)
|
||||
selected_area_ = area
|
||||
if area then
|
||||
traverse_x = max(areas[area].x + traverse_radius, min(areas[area].x + areas[area].width - traverse_radius, traverse_x))
|
||||
traverse_y = max(areas[area].y + traverse_radius, min(areas[area].y + areas[area].height - traverse_radius, traverse_y))
|
||||
end
|
||||
end
|
||||
|
||||
local function selected_area()
|
||||
if selected_area_ == nil then
|
||||
local min_dis = nil
|
||||
for i, a in ipairs(areas) do
|
||||
if a.habitable then
|
||||
local dis =
|
||||
math.abs(a.x + traverse_radius - traverse_x) + math.abs(a.x + a.width - traverse_radius - traverse_x) - a.width +
|
||||
math.abs(a.y + traverse_radius - traverse_y) + math.abs(a.y + a.height - traverse_radius - traverse_y) - a.height +
|
||||
traverse_radius * 4
|
||||
if min_dis == nil or min_dis > dis then
|
||||
min_dis = dis
|
||||
selected_area_ = i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
set_selected_area(selected_area_)
|
||||
end
|
||||
return selected_area_
|
||||
end
|
||||
|
||||
local parent_stack = {}
|
||||
|
||||
local function get_tablist(area)
|
||||
local list = {}
|
||||
for _, tc in ipairs(screen.tiled_clients) do
|
||||
if not (tc.floating or tc.immobilized)
|
||||
then
|
||||
if areas[area].x <= tc.x + tc.width + tc.border_width * 2 and tc.x <= areas[area].x + areas[area].width and
|
||||
areas[area].y <= tc.y + tc.height + tc.border_width * 2 and tc.y <= areas[area].y + areas[area].height
|
||||
then
|
||||
list[#list + 1] = tc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
local function maintain_tablist()
|
||||
tablist = get_tablist(selected_area())
|
||||
if #tablist ~= 0 then
|
||||
c = tablist[1]
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_tab_info(context, cr, width, height)
|
||||
maintain_tablist()
|
||||
|
||||
local wi = { x = 0, y = 0, w = 1, h = 1 }
|
||||
local ext
|
||||
local active_area = selected_area()
|
||||
if #tablist > 0 then
|
||||
local a = areas[active_area]
|
||||
local pl = lgi.Pango.Layout.create(cr)
|
||||
pl:set_font_description(tablist_font_desc)
|
||||
|
||||
local vpadding = dpi(10)
|
||||
local hpadding = dpi(15)
|
||||
local vborder = dpi(5)
|
||||
local hborder = dpi(5)
|
||||
local list_height = 2 * vborder
|
||||
local list_width = 2 * hpadding + 2 * hborder
|
||||
local exts = {}
|
||||
|
||||
local tl = {}
|
||||
local hw = math.floor((#tablist-1)/2)
|
||||
for i = hw+1,2,-1 do
|
||||
table.insert(tl, tablist[i])
|
||||
end
|
||||
table.insert(tl, tablist[1])
|
||||
local active = #tl
|
||||
for i = #tablist,hw+2,-1 do
|
||||
table.insert(tl, tablist[i])
|
||||
end
|
||||
|
||||
for index, tc in ipairs(tl) do
|
||||
local label = tc.name or "<unnamed>"
|
||||
pl:set_text(label)
|
||||
local w, h
|
||||
w, h = pl:get_size()
|
||||
w = w / lgi.Pango.SCALE
|
||||
h = h / lgi.Pango.SCALE
|
||||
local ext = { width = w, height = h }
|
||||
exts[#exts + 1] = ext
|
||||
list_height = list_height + ext.height + vpadding
|
||||
list_width = max(list_width, w + 2 * hpadding + 2 * hborder)
|
||||
end
|
||||
|
||||
local y_offset = vborder
|
||||
wi.x = a.x + (a.width - list_width) / 2
|
||||
wi.y = a.y + (a.height - list_height) / 2
|
||||
wi.w = list_width
|
||||
wi.h = list_height
|
||||
|
||||
cr:rectangle(0, 0, list_width, list_height)
|
||||
cr:set_source(border_color_hl)
|
||||
cr:fill()
|
||||
|
||||
for index, tc in ipairs(tl) do
|
||||
local label = tc.name or "<unnamed>"
|
||||
local ext = exts[index]
|
||||
if index == active then
|
||||
cr:rectangle(hborder, y_offset, list_width - 2 * hborder, ext.height + vpadding)
|
||||
cr:set_source(fill_color_hl)
|
||||
cr:fill()
|
||||
pl:set_text(label)
|
||||
cr:move_to(hborder + hpadding, vpadding / 2 + y_offset)
|
||||
cr:set_source(font_color_hl)
|
||||
cr:show_layout(pl)
|
||||
else
|
||||
pl:set_text(label)
|
||||
cr:move_to(hborder + hpadding, vpadding / 2 + y_offset)
|
||||
cr:set_source(font_color)
|
||||
cr:show_layout(pl)
|
||||
end
|
||||
|
||||
y_offset = y_offset + ext.height + vpadding
|
||||
end
|
||||
end
|
||||
|
||||
if infotabbox.x ~= wi.x or infotabbox.y ~= wi.y or
|
||||
infotabbox.width ~= wi.w or infotabbox.height ~= wi.h
|
||||
then
|
||||
infotabbox.x = wi.x
|
||||
infotabbox.y = wi.y
|
||||
infotabbox.width = wi.w
|
||||
infotabbox.height = wi.h
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_info(context, cr, width, height)
|
||||
maintain_tablist()
|
||||
|
||||
cr:set_source_rgba(0, 0, 0, 0)
|
||||
cr:rectangle(0, 0, width, height)
|
||||
cr:fill()
|
||||
|
||||
local padx = 5
|
||||
local pady = 5
|
||||
local msg
|
||||
local active_area = selected_area()
|
||||
for i, a in ipairs(areas) do
|
||||
if a.habitable or i == active_area then
|
||||
cr:rectangle(a.x - padx - start_x, a.y - pady - start_y, a.width + 2*padx, a.height + 2*pady)
|
||||
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 infobox.width ~= screen.workarea.width or infobox.height ~= screen.workarea.height then
|
||||
gtimer.delayed_call(function ()
|
||||
infobox.width = screen.workarea.width
|
||||
infobox.height = screen.workarea.height
|
||||
infobox.visible = true
|
||||
end)
|
||||
end
|
||||
|
||||
-- show the traverse point
|
||||
-- cr:rectangle(traverse_x - start_x - traverse_radius, traverse_y - start_y - traverse_radius, traverse_radius * 2, traverse_radius * 2)
|
||||
-- cr:set_source_rgba(1, 1, 1, 1)
|
||||
-- cr:fill()
|
||||
end
|
||||
|
||||
local draw = function()
|
||||
if infobox ~= nil then
|
||||
infobox.bgimage = draw_info
|
||||
end
|
||||
if infotabbox ~= nil then
|
||||
infotabbox.bgimage = draw_tab_info
|
||||
end
|
||||
end
|
||||
|
||||
local largest_area = function(areas)
|
||||
local largest = nil
|
||||
local size = 0
|
||||
for i, a in ipairs(areas) do
|
||||
local s = a.width * a.height
|
||||
if a.habitable and (largest == nil or s > size) then
|
||||
largest = i
|
||||
size = s
|
||||
end
|
||||
end
|
||||
|
||||
return largest
|
||||
end
|
||||
|
||||
local snap_area = function(x, y, width, height)
|
||||
local bestD, d
|
||||
local best
|
||||
local dist = function(x0, y0, x1, y1)
|
||||
local a, b = x0-x1, y0-y1
|
||||
return math.sqrt(a*a+b*b)
|
||||
end
|
||||
for i, a in ipairs(areas) do
|
||||
d = dist(a.x, a.y, x, y) +
|
||||
dist(a.x+a.width, a.y+a.height, x+width, y+height)
|
||||
if bestD == nil or d < bestD then
|
||||
bestD = d
|
||||
best = i
|
||||
end
|
||||
end
|
||||
|
||||
return best
|
||||
end
|
||||
|
||||
|
||||
-- area is the index in areas, should be made consistent after el big refactor
|
||||
-- draft is reset
|
||||
local move_client
|
||||
move_client = function(c, area)
|
||||
if area == nil or areas[area] == nil or c == nil or not c.valid or c.floating then
|
||||
return
|
||||
end
|
||||
machi.layout.set_geometry(c, areas[area], areas[area], 0, c.border_width)
|
||||
if cd[c] == nil then
|
||||
return
|
||||
end
|
||||
cd[c].lu = nil
|
||||
cd[c].rd = nil
|
||||
cd[c].area = area
|
||||
end
|
||||
|
||||
local snap_client = function()
|
||||
if c == nil then
|
||||
return
|
||||
end
|
||||
a = snap_area(c.x, c.y, c.width, c.height)
|
||||
if a == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
move_client(c, a)
|
||||
return true
|
||||
end
|
||||
|
||||
local master_add = function()
|
||||
if c == nil or c.floating then
|
||||
return
|
||||
end
|
||||
|
||||
local dst = largest_area(areas)
|
||||
if dst == nil then
|
||||
return
|
||||
end
|
||||
move_client(c, dst)
|
||||
awful.layout.arrange(screen)
|
||||
end
|
||||
|
||||
local master_swap = function(all)
|
||||
if c == nil or c.floating then
|
||||
return
|
||||
end
|
||||
|
||||
local src = selected_area()
|
||||
local dst = largest_area(areas)
|
||||
if dst == nil or dst == src then
|
||||
return
|
||||
end
|
||||
local srclist = get_tablist(src)
|
||||
local dstlist = get_tablist(dst)
|
||||
|
||||
for i, c in ipairs(dstlist) do
|
||||
if all or i == 1 then
|
||||
move_client(c, src)
|
||||
end
|
||||
end
|
||||
for i, c in ipairs(srclist) do
|
||||
if all or i == 1 then
|
||||
move_client(c, dst)
|
||||
end
|
||||
end
|
||||
awful.layout.arrange(screen)
|
||||
end
|
||||
|
||||
local traverse_clients = function(dir)
|
||||
dir = string.lower(dir)
|
||||
local current_area = selected_area()
|
||||
local current = areas[current_area]
|
||||
if current == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
local candidates = {}
|
||||
for i, a in ipairs(areas) do
|
||||
local data = {i = i, a = a}
|
||||
if not a.habitable or i == current_area then
|
||||
elseif dir == "up" and
|
||||
a.y + a.height < current.y and
|
||||
#get_tablist(i) ~= 0 then
|
||||
table.insert(candidates, data)
|
||||
elseif dir == "down" and
|
||||
a.y > current.y + current.height and
|
||||
#get_tablist(i) ~= 0 then
|
||||
table.insert(candidates, data)
|
||||
elseif dir == "left" and
|
||||
a.x + a.width < current.x and
|
||||
#get_tablist(i) ~= 0 then
|
||||
table.insert(candidates, data)
|
||||
elseif dir == "right" and
|
||||
a.x > current.x + current.width and
|
||||
#get_tablist(i) ~= 0 then
|
||||
table.insert(candidates, data)
|
||||
end
|
||||
end
|
||||
|
||||
local best_area
|
||||
local score
|
||||
for i, d in ipairs(candidates) do
|
||||
local a = d.a
|
||||
local dist, overlap
|
||||
|
||||
if dir == "up" or dir == "down" then
|
||||
overlap = min(current.x+current.width, a.x+a.width) - max(current.x, a.x)
|
||||
elseif dir == "left" or dir == "right" then
|
||||
overlap = min(current.y+current.height, a.y+a.height) - max(current.y, a.y)
|
||||
end
|
||||
|
||||
if dir == "up" then
|
||||
dist = current.y - a.y - a.height
|
||||
elseif dir == "down" then
|
||||
dist = a.y - current. y - current.height
|
||||
elseif dir == "left" then
|
||||
dist = current.x - a.x - a.width
|
||||
elseif dir == "right" then
|
||||
dist = a.x - current.x - current.width
|
||||
end
|
||||
|
||||
-- TODO this is probably not optimal
|
||||
local s = overlap - dist
|
||||
if score == nil or s > score then
|
||||
best_area = d.i
|
||||
score = s
|
||||
end
|
||||
end
|
||||
|
||||
if best_area == nil then
|
||||
if #candidates == 0 then
|
||||
return false
|
||||
end
|
||||
best_area = candidates[1].i
|
||||
end
|
||||
|
||||
set_selected_area(best_area)
|
||||
local tablist = get_tablist(best_area)
|
||||
if #tablist == 0 or tablist[1] == c then
|
||||
return false
|
||||
end
|
||||
c = tablist[1]
|
||||
capi.client.focus = c
|
||||
return true
|
||||
end
|
||||
|
||||
local traverse_areas = function(dir, move, draft)
|
||||
dir = string.lower(dir)
|
||||
local current_area = selected_area()
|
||||
|
||||
if c and move then
|
||||
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
|
||||
set_selected_area(nil)
|
||||
end
|
||||
elseif c and draft then
|
||||
local ex = c.x + c.width + c.border_width * 2
|
||||
local ey = c.y + c.height + c.border_width * 2
|
||||
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
|
||||
set_selected_area(nil)
|
||||
end
|
||||
end
|
||||
|
||||
local choice = nil
|
||||
local choice_value
|
||||
|
||||
current_area = selected_area()
|
||||
|
||||
for i, a in ipairs(areas) do
|
||||
if not a.habitable then goto continue end
|
||||
|
||||
local v
|
||||
if dir == "up" then
|
||||
if a.x < traverse_x + threshold
|
||||
and traverse_x < a.x + a.width + threshold then
|
||||
v = traverse_y - a.y - a.height
|
||||
else
|
||||
v = -1
|
||||
end
|
||||
elseif dir == "down" then
|
||||
if a.x < traverse_x + threshold
|
||||
and traverse_x < a.x + a.width + threshold then
|
||||
v = a.y - traverse_y
|
||||
else
|
||||
v = -1
|
||||
end
|
||||
elseif dir == "left" then
|
||||
if a.y < traverse_y + threshold
|
||||
and traverse_y < a.y + a.height + threshold then
|
||||
v = traverse_x - a.x - a.width
|
||||
else
|
||||
v = -1
|
||||
end
|
||||
elseif dir == "right" then
|
||||
if a.y < traverse_y + threshold
|
||||
and traverse_y < a.y + a.height + threshold then
|
||||
v = a.x - traverse_x
|
||||
else
|
||||
v = -1
|
||||
end
|
||||
end
|
||||
|
||||
if (v > threshold) and (choice_value == nil or choice_value > v) then
|
||||
choice = i
|
||||
choice_value = v
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
|
||||
if choice == nil then
|
||||
choice = current_area
|
||||
if dir == "up" then
|
||||
traverse_y = screen.workarea.y
|
||||
elseif dir == "down" then
|
||||
traverse_y = screen.workarea.y + screen.workarea.height
|
||||
elseif dir == "left" then
|
||||
traverse_x = screen.workarea.x
|
||||
else
|
||||
traverse_x = screen.workarea.x + screen.workarea.width
|
||||
end
|
||||
end
|
||||
|
||||
if choice ~= nil then
|
||||
tablist = nil
|
||||
set_selected_area(choice)
|
||||
|
||||
if c and draft and cd[c].draft ~= false then
|
||||
local lu = cd[c].lu or cd[c].area
|
||||
local rd = cd[c].rd or cd[c].area
|
||||
|
||||
if draft and move then
|
||||
lu = choice
|
||||
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 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, areas[lu], areas[rd], 0, c.border_width)
|
||||
elseif lu ~= nil then
|
||||
machi.layout.set_geometry(c, areas[lu], nil, 0, c.border_width)
|
||||
elseif rd ~= nil then
|
||||
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
|
||||
|
||||
if lu == rd and cd[c].draft ~= true then
|
||||
cd[c].lu = nil
|
||||
cd[c].rd = nil
|
||||
cd[c].area = lu
|
||||
else
|
||||
cd[c].lu = lu
|
||||
cd[c].rd = rd
|
||||
cd[c].area = nil
|
||||
end
|
||||
|
||||
c:emit_signal("request::activate", "mouse.move", {raise=false})
|
||||
c:raise()
|
||||
awful.layout.arrange(screen)
|
||||
elseif c and move then
|
||||
-- move the window
|
||||
local in_draft = cd[c].draft
|
||||
if cd[c].draft ~= nil then
|
||||
in_draft = cd[c].draft
|
||||
elseif cd[c].lu then
|
||||
in_draft = true
|
||||
elseif cd[c].area then
|
||||
in_draft = false
|
||||
else
|
||||
log(ERROR, "Assuming in_draft for unhandled client "..tostring(c))
|
||||
in_draft = true
|
||||
end
|
||||
if in_draft then
|
||||
c.x = areas[choice].x
|
||||
c.y = areas[choice].y
|
||||
else
|
||||
machi.layout.set_geometry(c, areas[choice], areas[choice], 0, c.border_width)
|
||||
cd[c].lu = nil
|
||||
cd[c].rd = nil
|
||||
cd[c].area = choice
|
||||
end
|
||||
c:emit_signal("request::activate", "mouse.move", {raise=false})
|
||||
c:raise()
|
||||
awful.layout.arrange(screen)
|
||||
|
||||
tablist = nil
|
||||
else
|
||||
maintain_tablist()
|
||||
-- move the focus
|
||||
if #tablist > 0 and tablist[1] ~= c then
|
||||
c = tablist[1]
|
||||
capi.client.focus = c
|
||||
end
|
||||
end
|
||||
|
||||
draw()
|
||||
end
|
||||
end
|
||||
|
||||
local tab = function()
|
||||
maintain_tablist()
|
||||
if #tablist > 1 then
|
||||
c = tablist[#tablist]
|
||||
c:emit_signal("request::activate", "mouse.move", {raise=false})
|
||||
c:raise()
|
||||
|
||||
draw()
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_key(mod, key, event)
|
||||
local key_translate_tab = {
|
||||
["k"] = "Up",
|
||||
["h"] = "Left",
|
||||
["j"] = "Down",
|
||||
["l"] = "Right",
|
||||
["K"] = "Up",
|
||||
["H"] = "Left",
|
||||
["J"] = "Down",
|
||||
["L"] = "Right",
|
||||
}
|
||||
|
||||
if event == "release" then
|
||||
if exit_keys and exit_keys[key] then
|
||||
exit()
|
||||
end
|
||||
return
|
||||
end
|
||||
if key_translate_tab[key] ~= nil then
|
||||
key = key_translate_tab[key]
|
||||
end
|
||||
|
||||
maintain_tablist()
|
||||
assert(tablist ~= nil)
|
||||
|
||||
local super = false
|
||||
local shift = false
|
||||
local ctrl = false
|
||||
for i, m in ipairs(mod) do
|
||||
if m == "Mod4" then super = true
|
||||
elseif m == "Shift" then shift = true
|
||||
elseif m == "Control" then ctrl = true
|
||||
end
|
||||
end
|
||||
|
||||
if key == "Tab" then
|
||||
tab()
|
||||
elseif key == "Up" or key == "Down" or key == "Left" or key == "Right" then
|
||||
traverse_areas(key, shift or super, ctrl)
|
||||
elseif (key == "q" or key == "Prior") then
|
||||
local current_area = selected_area()
|
||||
if areas[current_area].parent_id == nil then
|
||||
return
|
||||
end
|
||||
|
||||
set_selected_area(areas[current_area].parent_id)
|
||||
if #parent_stack == 0 or
|
||||
parent_stack[#parent_stack] ~= current_area then
|
||||
parent_stack = {current_area}
|
||||
end
|
||||
parent_stack[#parent_stack + 1] = areas[current_area].parent_id
|
||||
current_area = parent_stack[#parent_stack]
|
||||
|
||||
if c and ctrl and cd[c].draft ~= false then
|
||||
if cd[c].area then
|
||||
cd[c].lu, cd[c].rd, cd[c].area = cd[c].area, cd[c].area, nil
|
||||
end
|
||||
machi.layout.set_geometry(c, areas[current_area], areas[current_area], 0, c.border_width)
|
||||
awful.layout.arrange(screen)
|
||||
end
|
||||
|
||||
draw()
|
||||
elseif (key =="e" or key == "Next") then
|
||||
local current_area = selected_area()
|
||||
if #parent_stack <= 1 or parent_stack[#parent_stack] ~= current_area then
|
||||
return
|
||||
end
|
||||
|
||||
set_selected_area(parent_stack[#parent_stack - 1])
|
||||
table.remove(parent_stack, #parent_stack)
|
||||
current_area = parent_stack[#parent_stack]
|
||||
|
||||
if c and ctrl then
|
||||
if areas[current_area].habitable and cd[c].draft ~= true then
|
||||
cd[c].lu, cd[c].rd, cd[c].area = nil, nil, current_area
|
||||
end
|
||||
machi.layout.set_geometry(c, areas[current_area], areas[current_area], 0, c.border_width)
|
||||
awful.layout.arrange(screen)
|
||||
end
|
||||
|
||||
draw()
|
||||
elseif key == "/" then
|
||||
local current_area = selected_area()
|
||||
local original_cmd = machi.engine.areas_to_command(areas, true, current_area)
|
||||
areas[current_area].hole = true
|
||||
local prefix, suffix = machi.engine.areas_to_command(
|
||||
areas, false):match("(.*)|(.*)")
|
||||
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 ()
|
||||
layout.machi_editor.start_interactive(
|
||||
screen,
|
||||
{
|
||||
workarea = workarea,
|
||||
original_cmd = original_cmd,
|
||||
cmd_prefix = prefix,
|
||||
cmd_suffix = suffix,
|
||||
}
|
||||
)
|
||||
end
|
||||
)
|
||||
exit()
|
||||
elseif (key == "f" or key == ".") and c then
|
||||
if cd[c].draft == nil then
|
||||
cd[c].draft = true
|
||||
elseif cd[c].draft == true then
|
||||
cd[c].draft = false
|
||||
else
|
||||
cd[c].draft = nil
|
||||
end
|
||||
awful.layout.arrange(screen)
|
||||
elseif key == "Escape" or key == "Return" then
|
||||
exit()
|
||||
else
|
||||
log(DEBUG, "Unhandled key " .. key)
|
||||
end
|
||||
end
|
||||
|
||||
local ui = function()
|
||||
infobox = wibox({
|
||||
screen = screen,
|
||||
x = screen.workarea.x,
|
||||
y = screen.workarea.y,
|
||||
width = 1, -- screen.workarea.width,
|
||||
height = 1, --screen.workarea.height,
|
||||
bg = "#ffffff00",
|
||||
opacity = 1,
|
||||
ontop = false,
|
||||
type = "normal",
|
||||
})
|
||||
infotabbox = wibox({
|
||||
screen = screen,
|
||||
x = screen.workarea.x,
|
||||
y = screen.workarea.y,
|
||||
width = 1,
|
||||
height = 1,
|
||||
bg = "#ffffff00",
|
||||
ontop = true,
|
||||
type = "normal",
|
||||
visible = true,
|
||||
})
|
||||
|
||||
draw()
|
||||
|
||||
awful.client.focus.history.disable_tracking()
|
||||
local kg
|
||||
local function exit()
|
||||
awful.client.focus.history.enable_tracking()
|
||||
if capi.client.focus then
|
||||
capi.client.emit_signal("focus", capi.client.focus)
|
||||
end
|
||||
infobox.visible = false
|
||||
infotabbox.visible = false
|
||||
awful.keygrabber.stop(kg)
|
||||
end
|
||||
|
||||
kg = awful.keygrabber.run(
|
||||
function (...)
|
||||
ok, _ = pcall(handle_key, ...)
|
||||
if not ok then exit() end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
return {
|
||||
ui = ui,
|
||||
tab = tab,
|
||||
snap_client = snap_client,
|
||||
master_add = master_add,
|
||||
--- swap current window with 'master' or moves it to the master position if it's unoccupied (i.e.: largest area/client)
|
||||
-- @param all swap/move all clients in both areas, not just the top ones.
|
||||
master_swap = master_swap,
|
||||
--- focus client by direction
|
||||
-- @param dir direction (up|right|down|left)
|
||||
-- @returns true if a client was found
|
||||
traverse_clients = traverse_clients,
|
||||
traverse_areas = traverse_areas,
|
||||
move = function(dir) traverse_areas(dir, true, false) end,
|
||||
-- like traverse_clients, returns true if a client was found
|
||||
focus = function(dir) return traverse_clients(dir) end,
|
||||
}
|
||||
end
|
||||
|
||||
return module
|
@ -1,14 +1,15 @@
|
||||
local gears = require('gears')
|
||||
local awful = require('awful')
|
||||
local lain = require('lain')
|
||||
local hotkeys_popup = require('awful.hotkeys_popup')
|
||||
local machi = require('layout-machi')
|
||||
local lain = require('lain')
|
||||
local globals = require('src.globals')
|
||||
local mpris = require('src.util.mpris')
|
||||
local volume = require('src.util.volume')
|
||||
|
||||
local modkey = globals.modkey
|
||||
local altkey = globals.altkey
|
||||
|
||||
local mpris = require('src.util.mpris')
|
||||
local volume = require('src.util.volume')
|
||||
|
||||
local quake_terminal_name = 'QuakeTerminal'
|
||||
local quake = lain.util.quake({
|
||||
@ -102,6 +103,8 @@ local globalkeys = gears.table.join(
|
||||
awful.key({ modkey, 'Control' }, 'l', function() awful.tag.incncol(-1, nil, true) end, { description = 'decrease the number of columns', group = 'layout' }),
|
||||
awful.key({ modkey }, 'space', function() awful.layout.inc(1) end, { description = 'select next', group = 'layout' }),
|
||||
awful.key({ modkey, 'Shift' }, 'space', function() awful.layout.inc(-1) end, { description = 'select previous', group = 'layout' }),
|
||||
awful.key({ modkey }, '.', function () machi.default_editor.start_interactive() end, { description = 'start machi editor', group = 'layout' }),
|
||||
awful.key({ modkey }, '+', function () machi.default_editor.start_interactive() end, { description = 'switch between windows for a machi', group = 'layout' }),
|
||||
|
||||
---- AUDIO KEYS ----
|
||||
awful.key({}, "XF86AudioMute", volume.toggle_mute),
|
||||
|
@ -1,9 +1,7 @@
|
||||
local awful = require('awful')
|
||||
local fair_col = require('src.layouts.fair_col')
|
||||
local tile_col = require('src.layouts.tile_col')
|
||||
|
||||
tag.connect_signal("request::default_layouts", function()
|
||||
awful.layout.append_default_layouts({
|
||||
awful.layout.append_default_layouts {
|
||||
awful.layout.suit.fair,
|
||||
awful.layout.suit.fair.horizontal,
|
||||
awful.layout.suit.tile,
|
||||
@ -11,7 +9,8 @@ tag.connect_signal("request::default_layouts", function()
|
||||
awful.layout.suit.tile.bottom,
|
||||
awful.layout.suit.tile.top,
|
||||
awful.layout.suit.floating,
|
||||
fair_col,
|
||||
--tile_col
|
||||
})
|
||||
require('src.layouts.fair_col'),
|
||||
--require('src.layouts.tile_col'),
|
||||
require('layout-machi.layout').create {}
|
||||
}
|
||||
end)
|
||||
|
@ -40,6 +40,11 @@ return {
|
||||
fg_focus = '#ff8c00',
|
||||
fg_urgent = '#af1d18',
|
||||
fg_minimize = '#ffffff',
|
||||
machi_editor_border_color = '#606060',
|
||||
machi_editor_active_color = '#002200',
|
||||
machi_editor_open_color = '#000000',
|
||||
machi_editor_done_color = '#000022',
|
||||
machi_editor_closed_color = '#000022',
|
||||
border_width = 1,
|
||||
border_normal = '#1c2022',
|
||||
border_focus = '#606060',
|
||||
@ -86,6 +91,7 @@ return {
|
||||
layout_magnifier = confdir .. '/icons/magnifier.png',
|
||||
layout_floating = confdir .. '/icons/floating.png',
|
||||
layout_fairc = confdir .. '/icons/fairc.png',
|
||||
layout_machi = confdir .. '/lib/layout-machi/icon.png',
|
||||
titlebar_close_button_normal = confdir .. '/icons/titlebar/close_normal.png',
|
||||
titlebar_close_button_focus = confdir .. '/icons/titlebar/close_focus.png',
|
||||
titlebar_minimize_button_normal = confdir .. '/icons/titlebar/minimize_normal.png',
|
||||
|
Loading…
x
Reference in New Issue
Block a user