collision/areamap.lua

460 lines
13 KiB
Lua
Raw Permalink Normal View History

local awful = require("awful")
local wibox = require("wibox")
local shape = require("gears.shape")
local placement = require("awful.placement")
local title_image = require("collision.widgets.titled_imagebox")
local screenshot = require("collision.widgets.screenshot")
local client_grid = require("collision.widgets.client_grid")
local tag_header = require("collision.widgets.tag_header")
local capi = {client = client, screen = screen}
local module = {}
-- Swaps cannot be performed on groups until Awesome supports better Z-indexes
-- for both wibox and client
local delayed_swap = nil
local function on_exit_common(c)
if delayed_swap then
c:swap(delayed_swap.client and delayed_swap.client or delayed_swap)
end
delayed_swap = nil
end
local function has_selected(tags, screen)
if #tags == 0 then return false end
for _, t in ipairs(screen.selected_tags) do
if awful.util.table.hasitem(tags, t) then return true end
end
return false
end
local function get_minimized_clients(clients)
local ret = {}
for _, c in ipairs(clients) do
if c.minimized and has_selected(c:tags(), c.screen) then
table.insert(ret, c)
end
end
return ret
end
local function get_tiled_clients(clients)
local ret = {}
for _, c in ipairs(clients) do
if c.floating or c.maximized_horizontal or c.maximized_vertical then
table.insert(ret, c)
end
end
return ret
end
local function get_floating_clients(clients)
local ret = {}
for _, c in ipairs(clients) do
if not c.floating
and not c.fullscreen
and not c.maximized_vertical
and not c.maximized_horizontal then
table.insert(ret, c)
end
end
return ret
end
--- Free a small area if there is minimized clients on the screen
local function crop_rect(elements, rect)
local y2 = rect.y + rect.height
local height = rect.y < elements.min_y and (
y2 - elements.min_y
) or rect.height
local y = math.max(elements.min_y, rect.y)
height = (rect.y+height > elements.max_y) and (elements.max_y-y) or height
return { --TODO just mutate the original
x = rect.x,
y = y,
width = rect.width,
height = height,
client = rect.client,
tag = rect.tag,
on_exit = rect.on_exit,
on_select = rect.on_select,
on_swap = rect.on_swap,
group = rect.group,
geometry = function(self) return self end,
}
end
local function get_areas_static(elements)
if elements.dynamic then return end
for k, c in ipairs(elements.tiled) do
local geo = c:geometry()
geo.client = c
function geo:geometry() return self end
table.insert(elements.rects, crop_rect(elements, geo))
end
end
local function simple_widget(c, orientation)
local ret = title_image(c.name, c.icon
and wibox.widget.imagebox(c.icon) or screenshot(c), nil, orientation
)
ret.client = c
return ret
end
local function add_areas_groups(elements, wb)
local geo = wb:geometry()
wb._drawable:_do_redraw()
local add_x, add_y = geo.x, geo.y
local function handle_hierarchy(h, force_rect)
local widget = h:get_widget()
-- All sub-areas of a stack are to be displayed in a "fair" grid.
if widget.client then
local matrix = h:get_matrix_to_device()
local x, y = matrix:transform_point(0, 0)
local width, height = h:get_size()
-- As there is an overlay on top of the client, there is no point
-- to focus it already (beside the tasklist update).
local function on_exit()
capi.client.focus = widget.client
widget.client:raise()
on_exit_common(widget.client)
end
local rect = nil
-- Swap cannot be done on a stack as the widget may be below the
-- client
local function on_swap(other_c)
rect.client:swap(other_c.client)
other_c.client, rect.client = rect.client, other_c.client
--TODO update the screenshot
end
rect = crop_rect(elements, {
x = x + add_x,
y = y + add_y,
width = width,
height = height,
client = widget.client,
group = force_rect,
on_exit = on_exit,
geometry = function(self) return self end,
})
table.insert(elements.rects, rect)
end
for _, child in ipairs(h:get_children()) do
handle_hierarchy(child, force_rect)
end
end
assert(wb._drawable._widget_hierarchy)
handle_hierarchy(wb._drawable._widget_hierarchy)
end
--- Create a "fair" layout (a flex grid) and place the elements of the groups
local function get_areas_groups(elements, groups)
for _, g in ipairs(groups) do
local w = g.widget.nav_wibox
if w then
w.widget:reload(g)
w.visible = true
table.insert(elements.wiboxes, w)
else
local wdg = client_grid(g)
w = wibox {
visible = true,
ontop = true,
widget = wdg,
}
table.insert(elements.wiboxes, w)
g.widget.nav_wibox = w
end
placement.maximize(w, {parent=crop_rect(elements,g.rect)})
add_areas_groups(elements, w)
end
end
local function get_areas_dynamic(elements)
if not elements.dynamic then return end
--Move to the work area
local workarea = elements.handler.param.workarea
local add_x, add_y = 0, 0
local groups = {}
local function handle_hierarchy(h, force_rect)
local widget = h:get_widget()
-- All sub-areas of a stack are to be displayed in a "fair" grid.
if (not force_rect) and widget.is_stack then
local matrix = h:get_matrix_to_device()
local x, y = matrix:transform_point(0, 0)
x, y = x + workarea.x, y + workarea.y
local width, height = h:get_size()
force_rect = {
widget = widget,
rect = {
geometry = function(self) return self end,
x = x + add_x,
y = y + add_y,
width = width,
height = (y + add_y + height<= elements.max_y) and height
or height - (y + add_y + height - elements.max_y),
}
}
table.insert(groups, force_rect)
elseif widget._client then
local matrix = h:get_matrix_to_device()
local x, y = matrix:transform_point(0, 0)
x, y = x + workarea.x, y + workarea.y
local width, height = h:get_size()
-- It is safe to focus the client when selecting it unless the
-- layout ask otherwise[TODO]
local function on_select()
capi.client.focus = widget._client
end
local rect = nil
-- Swap is safe as long as it doesn't enter a stack
local function on_swap(other_c)
rect.client:swap(other_c.client)
other_c.client, rect.client = rect.client, other_c.client
end
-- Assume the client have just been focused, now, raise
local function on_exit()
widget._client:raise()
on_exit_common(widget._client)
end
rect = crop_rect(elements, {
x = x + add_x,
y = y + add_y,
width = width,
height = height,
client = widget._client,
group = force_rect,
on_select = on_select,
on_exit = on_exit,
on_swap = on_swap,
geometry = function(self) return self end,
})
table.insert(force_rect or elements.rects, rect)
end
for _, child in ipairs(h:get_children()) do
handle_hierarchy(child, force_rect)
end
end
handle_hierarchy(elements.handler.hierarchy)
get_areas_groups(elements, groups)
end
local function get_areas_minimzed(elements)
if #elements.minimized == 0 then return end
local w = wibox {
x = elements.workarea.x,
y = elements.max_y,
height = 40,
width = elements.workarea.width,
visible = true,
ontop = true,
}
table.insert(elements.wiboxes, w)
local wdg = wibox.layout.flex.horizontal()
local width = elements.workarea.width/#elements.minimized
for k, c in ipairs(elements.minimized) do
-- Only unminimize when we are sure nothing else will be selected
local function on_exit()
capi.client.focus = c
c:raise()
on_exit_common(c)
end
-- Show some visual clues
local function on_select()
--TODO
end
-- Obviously, swapping something minimized will mess everything up
local function on_swap(other_c)
delayed_swap = other_c
-- c:swap(other_c.client)
--TODO update the screenshot
end
table.insert(elements.rects, {
x = elements.workarea.x + (k-1)*width,
width = width,
height = 40,
y = elements.max_y,
client = c,
on_exit = on_exit,
on_select = on_select,
on_swap = on_swap,
geometry = function(self) return self end,
})
wdg:add(simple_widget(c, "horizontal"))
end
w.widget = wdg
end
local function get_area_tag(elements)
local w, _, rects = tag_header(capi.screen[1])
if not w then return end
table.insert(elements.wiboxes, w)
for k, t in ipairs(rects) do
local function on_exit()
t.tag:view_only()
w.visible = false
print("\n\n\nHIDE",w.visible)
on_exit_common(c)
end
table.insert(elements.rects, {
x = t.x,
width = t.width,
height = t.height,
y = t.y,
tag = t.tag,
-- client = nil,
on_exit = on_exit,
-- on_select = on_select,
-- on_swap = on_swap,
geometry = function(self) return self end,
})
end
end
local function debug_rects(s, elements)
local wdg = wibox.widget.base.make_widget()
function wdg:draw(context, cr, width, height)
for _, v in ipairs(elements.rects) do
cr:set_source_rgba(1,0,0,1)
cr:rectangle(v.x, v.y, v.width, v.height)
cr:stroke_preserve()
cr:set_source_rgba(1,0,0,0.1)
cr:fill()
end
end
function wdg:fit(context, width, height)
return width, height
end
local w = wibox {
x = elements.workarea.x,
y = elements.workarea.y,
width = elements.workarea.width,
height = elements.workarea.height,
visible = true,
widget = wdg,
}
table.insert(elements.wiboxes, w)
end
local function hide(self)
for _, w in ipairs(self.wiboxes) do
w.visible = false
end
end
--- Build a list of all areas that can be focued
--
-- * All tiled client
-- * All stacked clients
-- * All minimized clients
--
local function get_areas()
local self = {
hide = hide,
wiboxes = {},
rects = {}
}
for s in capi.screen do
local clients = capi.client.get(s, false)
local l = awful.layout.get(s)
-- My Awesome fork support tabbing and dynamic layouts, this provide
-- much more meta-data about the current tiled clients
local has_dynamic_layout = l.is_dynamic
-- Get the static elements (clients)
local elements = {
minimized = get_minimized_clients(clients),
--floating= get_floating_clients(clients),
tiled = has_dynamic_layout and {} or get_tiled_clients(clients),
workarea = s.workarea,
handler = l,
rects = self.rects,
dynamic = has_dynamic_layout,
wiboxes = self.wiboxes,
}
elements.has_minimized = #elements.minimized > 0
elements.max_y = elements.workarea.y + elements.workarea.height
- (elements.has_minimized and 40 or 0)
elements.min_y = elements.workarea.y + 40
-- Get the rectangles
get_areas_minimzed(elements)
get_areas_static (elements)
get_areas_dynamic (elements)
get_area_tag (elements)
--TODO add a rect for empty screens
-- debug_rects(s, elements)
end
return self
end
return setmetatable(module, { __call = function(_, ...) return get_areas(...) end })