layout-machi/switcher.lua

460 lines
15 KiB
Lua
Raw Normal View History

2019-08-01 03:00:38 +02:00
local machi = {
layout = require((...):match("(.-)[^%.]+$") .. "layout"),
}
2019-07-06 17:35:12 +02:00
local api = {
2019-07-07 19:05:42 +02:00
client = client,
2019-07-06 17:35:12 +02:00
beautiful = require("beautiful"),
wibox = require("wibox"),
awful = require("awful"),
screen = require("awful.screen"),
layout = require("awful.layout"),
naughty = require("naughty"),
gears = require("gears"),
2019-07-06 23:06:14 +02:00
lgi = require("lgi"),
2019-07-06 17:35:12 +02:00
dpi = require("beautiful.xresources").apply_dpi,
}
2019-08-20 05:46:49 +02:00
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
2019-07-07 23:36:48 +02:00
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
2019-07-06 23:06:14 +02:00
local function with_alpha(col, alpha)
local r, g, b
_, r, g, b, _ = col:get_rgba()
2019-07-06 23:06:14 +02:00
return api.lgi.cairo.SolidPattern.create_rgba(r, g, b, alpha)
end
2019-10-13 17:41:17 +02:00
function module.start(c, exit_keys)
local tablist_font_desc = api.beautiful.get_merged_font(
api.beautiful.font, api.dpi(10))
local font_color = with_alpha(api.gears.color(api.beautiful.fg_normal), 1)
2019-07-16 15:28:10 +02:00
local font_color_hl = with_alpha(api.gears.color(api.beautiful.fg_focus), 1)
local label_size = api.dpi(30)
2019-08-23 01:05:07 +02:00
local border_color = with_alpha(api.gears.color(api.beautiful.border_focus), 0.25)
local fill_color = with_alpha(api.gears.color(api.beautiful.bg_normal), 0.25)
2019-08-29 03:18:40 +02:00
local box_bg = with_alpha(api.gears.color(api.beautiful.bg_normal), 0.85)
local fill_color_hl = with_alpha(api.gears.color(api.beautiful.bg_focus), 1)
-- for comparing floats
local threshold = 0.1
local traverse_radius = api.dpi(5)
2019-10-06 18:17:01 +02:00
local screen = c and c.screen or api.screen.focused()
local start_x = screen.workarea.x
local start_y = screen.workarea.y
2019-07-08 19:15:05 +02:00
2019-07-06 17:35:12 +02:00
local layout = api.layout.get(screen)
2019-10-06 18:17:01 +02:00
if (c ~= nil and c.floating) or layout.machi_get_regions == nil then return end
2019-07-06 17:35:12 +02:00
2019-10-06 18:17:01 +02:00
local regions, draft_mode = layout.machi_get_regions(screen.workarea, screen.selected_tag)
2019-08-27 04:11:06 +02:00
if regions == nil or #regions == 0 then
return
end
2019-07-06 18:22:19 +02:00
2019-07-06 17:35:12 +02:00
local infobox = api.wibox({
2019-07-06 18:22:19 +02:00
screen = screen,
2019-07-06 17:35:12 +02:00
x = screen.workarea.x,
y = screen.workarea.y,
width = screen.workarea.width,
height = screen.workarea.height,
bg = "#ffffff00",
opacity = 1,
2019-08-05 00:52:50 +02:00
ontop = true,
type = "dock",
2019-07-06 17:35:12 +02:00
})
infobox.visible = true
2019-08-01 03:00:38 +02:00
local tablist_region = nil
2019-07-07 19:30:05 +02:00
local tablist = nil
local tablist_index = nil
2019-10-06 18:17:01 +02:00
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
2019-07-06 18:22:19 +02:00
local function maintain_tablist()
2019-07-09 23:34:06 +02:00
if tablist == nil then
tablist = {}
2019-07-09 23:34:06 +02:00
for _, tc in ipairs(screen.tiled_clients) do
2019-08-01 03:00:38 +02:00
if not (tc.floating or tc.maximized or tc.maximized_horizontal or tc.maximized_vertical)
2019-07-09 23:34:06 +02:00
then
2019-08-24 05:00:15 +02:00
if tc.x <= traverse_x and traverse_x < tc.x + tc.width + tc.border_width * 2 and
tc.y <= traverse_y and traverse_y < tc.y + tc.height + tc.border_width * 2
2019-08-01 03:00:38 +02:00
then
tablist[#tablist + 1] = tc
end
2019-07-09 23:34:06 +02:00
end
end
tablist_index = 1
else
local j = 0
for i = 1, #tablist do
if tablist[i].valid then
j = j + 1
tablist[j] = tablist[i]
elseif i <= tablist_index and tablist_index > 0 then
tablist_index = tablist_index - 1
end
end
for i = #tablist, j + 1, -1 do
table.remove(tablist, i)
end
end
if c and not c.valid then c = nil end
if c == nil and #tablist > 0 then
c = tablist[tablist_index]
2019-07-09 23:34:06 +02:00
end
end
2019-07-06 17:35:12 +02:00
local function draw_info(context, cr, width, height)
maintain_tablist()
2019-07-09 23:34:06 +02:00
2019-07-06 17:35:12 +02:00
cr:set_source_rgba(0, 0, 0, 0)
cr:rectangle(0, 0, width, height)
cr:fill()
2019-08-24 05:00:15 +02:00
local msg, ext, active_region
2019-07-06 17:35:12 +02:00
for i, a in ipairs(regions) do
2019-07-07 23:18:10 +02:00
2019-08-01 20:53:13 +02:00
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
cr:clip()
2019-08-23 01:05:07 +02:00
cr:set_source(fill_color)
2019-10-06 18:17:01 +02:00
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
2019-08-23 01:05:07 +02:00
cr:fill()
2019-07-06 23:06:14 +02:00
cr:set_source(border_color)
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
2019-07-06 23:06:14 +02:00
cr:set_line_width(10.0)
cr:stroke()
cr:reset_clip()
2019-08-24 05:00:15 +02:00
if a.x <= traverse_x and traverse_x < a.x + a.width and
a.y <= traverse_y and traverse_y < a.y + a.height
then
active_region = i
end
end
2019-09-09 05:28:35 +02:00
if #tablist > 0 then
2019-08-24 05:00:15 +02:00
local a = regions[active_region]
local pl = api.lgi.Pango.Layout.create(cr)
pl:set_font_description(tablist_font_desc)
local vpadding = api.dpi(10)
local list_height = vpadding
2019-08-29 03:18:40 +02:00
local list_width = 2 * vpadding
2019-08-24 05:00:15 +02:00
local exts = {}
for index, tc in ipairs(tablist) do
local label = tc.name
pl:set_text(label)
local w, h
w, h = pl:get_size()
w = w / api.lgi.Pango.SCALE
h = h / api.lgi.Pango.SCALE
local ext = { width = w, height = h, x_bearing = 0, y_bearing = 0 }
exts[#exts + 1] = ext
list_height = list_height + ext.height + vpadding
2019-08-29 03:18:40 +02:00
list_width = max(list_width, w + 2 * vpadding)
2019-08-24 05:00:15 +02:00
end
local x_offset = a.x + a.width / 2 - start_x
local y_offset = a.y + a.height / 2 - list_height / 2 + vpadding - start_y
-- cr:rectangle(a.x - start_x, y_offset - vpadding - start_y, a.width, list_height)
-- cover the entire region
cr:rectangle(a.x - start_x, a.y - start_y, a.width, a.height)
cr:set_source(fill_color)
cr:fill()
2019-08-29 03:18:40 +02:00
cr:rectangle(a.x + (a.width - list_width) / 2 - start_x, a.y + (a.height - list_height) / 2 - start_y, list_width, list_height)
cr:set_source(box_bg)
cr:fill()
2019-08-24 05:00:15 +02:00
for index, tc in ipairs(tablist) do
local label = tc.name
local ext = exts[index]
if index == tablist_index then
cr:rectangle(x_offset - ext.width / 2 - vpadding / 2, y_offset - vpadding / 2, ext.width + vpadding, ext.height + vpadding)
cr:set_source(fill_color_hl)
cr:fill()
pl:set_text(label)
cr:move_to(x_offset - ext.width / 2 - ext.x_bearing, y_offset - ext.y_bearing)
cr:set_source(font_color_hl)
cr:show_layout(pl)
else
pl:set_text(label)
cr:move_to(x_offset - ext.width / 2 - ext.x_bearing, y_offset - ext.y_bearing)
cr:set_source(font_color)
cr:show_layout(pl)
end
y_offset = y_offset + ext.height + vpadding
end
2019-07-06 17:35:12 +02:00
end
2019-07-06 18:22:19 +02:00
2019-07-06 23:06:14 +02:00
-- show the traverse point
cr:rectangle(traverse_x - start_x - traverse_radius, traverse_y - start_y - traverse_radius, traverse_radius * 2, traverse_radius * 2)
2019-07-06 23:06:14 +02:00
cr:set_source_rgba(1, 1, 1, 1)
cr:fill()
2019-07-06 17:35:12 +02:00
end
infobox.bgimage = draw_info
2019-08-06 04:29:03 +02:00
local key_translate_tab = {
["w"] = "Up",
["a"] = "Left",
["s"] = "Down",
["d"] = "Right",
}
2019-10-13 01:44:40 +02:00
api.awful.client.focus.history.disable_tracking()
2019-07-06 17:35:12 +02:00
local kg
2019-10-13 17:41:17 +02:00
local function exit()
if api.client.focus then
api.awful.client.focus.history.add(api.client.focus)
end
api.awful.client.focus.history.enable_tracking()
infobox.visible = false
api.awful.keygrabber.stop(kg)
end
local function handle_key(mod, key, event)
if event == "release" then
if exit_keys and exit_keys[key] then
exit()
2019-08-06 04:29:03 +02:00
end
return
end
if key_translate_tab[key] ~= nil then
key = key_translate_tab[key]
end
2019-07-07 19:30:05 +02:00
maintain_tablist()
assert(tablist ~= nil)
2019-08-09 21:54:41 +02:00
if key == "Tab" then
if #tablist > 0 then
tablist_index = tablist_index % #tablist + 1
c = tablist[tablist_index]
c:emit_signal("request::activate", "mouse.move", {raise=false})
c:raise()
2019-08-09 21:54:41 +02:00
infobox.bgimage = draw_info
end
elseif key == "Up" or key == "Down" or key == "Left" or key == "Right" then
local shift = false
local ctrl = false
for i, m in ipairs(mod) do
if m == "Shift" then shift = true
elseif m == "Control" then ctrl = true
2019-08-01 03:00:38 +02:00
end
end
2019-08-01 03:00:38 +02:00
local current_region = nil
2019-07-06 18:22:19 +02:00
if c and (shift or ctrl) then
2019-07-06 18:22:19 +02:00
for i, a in ipairs(regions) do
2019-08-01 03:00:38 +02:00
if a.x <= traverse_x and traverse_x < a.x + a.width and
a.y <= traverse_y and traverse_y < a.y + a.height
then
current_region = i
break
2019-08-01 03:00:38 +02:00
end
end
2019-08-01 03:00:38 +02:00
if shift then
if current_region == nil or
regions[current_region].x ~= c.x or
regions[current_region].y ~= c.y
then
traverse_x = c.x + traverse_radius
traverse_y = c.y + traverse_radius
current_region = nil
2019-07-06 18:22:19 +02:00
end
elseif ctrl then
local ex = c.x + c.width + c.border_width * 2
local ey = c.y + c.height + c.border_width * 2
if current_region == nil or
regions[current_region].x + regions[current_region].width ~= ex or
regions[current_region].y + regions[current_region].height ~= ey
then
traverse_x = ex - traverse_radius
traverse_y = ey - traverse_radius
current_region = nil
2019-07-06 18:22:19 +02:00
end
end
end
local choice = nil
local choice_value
for i, a in ipairs(regions) do
if a.x <= traverse_x and traverse_x < a.x + a.width and
a.y <= traverse_y and traverse_y < a.y + a.height
then
current_region = i
end
2019-07-06 18:22:19 +02:00
local v
if key == "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 key == "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 key == "Left" then
if a.y < traverse_y + threshold
and traverse_y < a.y + a.height + threshold then
v = traverse_x - a.x - a.width
2019-08-01 03:00:38 +02:00
else
v = -1
end
elseif key == "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
2019-07-07 19:05:42 +02:00
end
2019-08-01 03:00:38 +02:00
end
2019-07-07 19:05:42 +02:00
if (v > threshold) and (choice_value == nil or choice_value > v) then
choice = i
choice_value = v
end
end
2019-08-01 04:46:19 +02:00
if choice == nil then
choice = current_region
if key == "Up" then
traverse_y = screen.workarea.y
elseif key == "Down" then
traverse_y = screen.workarea.y + screen.workarea.height
elseif key == "Left" then
traverse_x = screen.workarea.x
else
traverse_x = screen.workarea.x + screen.workarea.width
end
end
if choice ~= nil then
traverse_x = max(regions[choice].x + traverse_radius, min(regions[choice].x + regions[choice].width - traverse_radius, traverse_x))
traverse_y = max(regions[choice].y + traverse_radius, min(regions[choice].y + regions[choice].height - traverse_radius, traverse_y))
tablist = nil
2019-08-06 04:29:03 +02:00
if c and ctrl and draft_mode then
local lu = c.machi_lu
local rd = c.machi_rd
if shift then
lu = choice
if regions[rd].x + regions[rd].width <= regions[lu].x or
regions[rd].y + regions[rd].height <= regions[lu].y
then
rd = nil
end
else
rd = choice
if regions[rd].x + regions[rd].width <= regions[lu].x or
regions[rd].y + regions[rd].height <= regions[lu].y
then
lu = nil
2019-08-01 03:00:38 +02:00
end
end
2019-08-01 03:00:38 +02:00
if lu ~= nil and rd ~= nil then
machi.layout.set_geometry(c, regions[lu], regions[rd], 0, c.border_width)
elseif lu ~= nil then
machi.layout.set_geometry(c, regions[lu], nil, 0, c.border_width)
elseif rd ~= nil then
c.x = min(c.x, regions[rd].x)
c.y = min(c.y, regions[rd].y)
machi.layout.set_geometry(c, nil, regions[rd], 0, c.border_width)
end
c.machi_lu = lu
c.machi_rd = rd
c:emit_signal("request::activate", "mouse.move", {raise=false})
c:raise()
api.layout.arrange(screen)
elseif c and shift then
-- move the window
if draft_mode then
c.x = regions[choice].x
c.y = regions[choice].y
2019-08-01 03:00:38 +02:00
else
machi.layout.set_geometry(c, regions[choice], regions[choice], 0, c.border_width)
c.machi_region = choice
2019-07-07 19:05:42 +02:00
end
c:emit_signal("request::activate", "mouse.move", {raise=false})
c:raise()
api.layout.arrange(screen)
2019-07-06 18:22:19 +02:00
tablist = nil
else
maintain_tablist()
-- move the focus
if #tablist > 0 and tablist[1] ~= c then
c = tablist[1]
api.client.focus = c
end
2019-07-06 18:22:19 +02:00
end
infobox.bgimage = draw_info
2019-07-06 17:35:12 +02:00
end
elseif key == "Escape" or key == "Return" then
exit()
else
log(DEBUG, "Unhandled key " .. key)
end
end
kg = api.awful.keygrabber.run(
function (...)
ok, _ = pcall(handle_key, ...)
if not ok then exit() end
2019-07-06 17:35:12 +02:00
end
)
end
2019-08-20 05:46:49 +02:00
return module