Add new helper modules.
This commit begin to merge the Radical "view" rewrite. Those module API replace previously hardcoded positioning logic. Some code may eventually be upstreamed once mature enough.
This commit is contained in:
parent
dafac374fa
commit
1711d28dcd
|
@ -0,0 +1,126 @@
|
|||
local capi = {screen = screen}
|
||||
local wibox = require( "wibox" )
|
||||
local util = require( "awful.util" )
|
||||
local timer = require( "gears.timer" )
|
||||
local placement = require( "radical.placement" )
|
||||
|
||||
local module = {}
|
||||
|
||||
local wibox_to_req = {}
|
||||
|
||||
local corners_geo = {
|
||||
-- Corners
|
||||
top_left = function(geo, wa) return {width = 1, height = 1} end,
|
||||
top_right = function(geo, wa) return {width = 1, height = 1} end,
|
||||
bottom_left = function(geo, wa) return {width = 1, height = 1} end,
|
||||
bottom_right = function(geo, wa) return {width = 1, height = 1} end,
|
||||
|
||||
-- Sides
|
||||
left = function(geo, wa) return {width = 1 , height = wa.height } end,
|
||||
right = function(geo, wa) return {width = 1 , height = wa.height } end,
|
||||
top = function(geo, wa) return {width = wa.width, height = 1 } end,
|
||||
bottom = function(geo, wa) return {width = wa.width, height = 1 } end,
|
||||
}
|
||||
|
||||
local function mouse_enter(w)
|
||||
local req = wibox_to_req[w]
|
||||
if req and req.enter then
|
||||
req:enter()
|
||||
end
|
||||
end
|
||||
|
||||
local function mouse_leave(w)
|
||||
local req = wibox_to_req[w]
|
||||
if req and req.leave then
|
||||
req:leave()
|
||||
end
|
||||
end
|
||||
|
||||
local function create_hot_corner(corner, s)
|
||||
local s = s or 1
|
||||
local s_geo = capi.screen[s].geometry
|
||||
local w_geo = capi.screen[s].workarea
|
||||
local size = corners_geo[corner](s_geo, w_geo)
|
||||
local w = wibox(util.table.crush(size, {ontop=true, opacity = 0, visible=true}))
|
||||
|
||||
placement.corner(w, corner, s, false, false)
|
||||
|
||||
local req = {wibox = w, screen = s, corner = corner}
|
||||
|
||||
w:connect_signal("mouse::enter", mouse_enter)
|
||||
|
||||
wibox_to_req[w] = req
|
||||
|
||||
return req
|
||||
end
|
||||
|
||||
local function create_visible_timer(w, time, req)
|
||||
local t = timer{}
|
||||
t.timeout = time
|
||||
t:connect_signal("timeout", function()
|
||||
w.visible = false
|
||||
req.wibox.visible = true
|
||||
t:stop()
|
||||
end)
|
||||
t:start()
|
||||
end
|
||||
|
||||
function module.register_function(corner, f, s)
|
||||
if not f then return end
|
||||
local req = create_hot_corner(corner, s)
|
||||
req.enter = f
|
||||
|
||||
return req
|
||||
end
|
||||
|
||||
--- Show a wibox when `corner` is hit.
|
||||
--
|
||||
-- Valid corners are:
|
||||
--
|
||||
-- * left
|
||||
-- * right
|
||||
-- * top
|
||||
-- * bottom
|
||||
-- * top_left
|
||||
-- * top_right
|
||||
-- * bottom_left
|
||||
-- * bottom_right
|
||||
--
|
||||
-- @tparam string corner A corner name
|
||||
-- @param w The wibox or a function returning a wibox in case lazy loading is
|
||||
-- desirable
|
||||
-- @tparam[opt=all] number s The screen
|
||||
-- @tparam[opt=0] number timeout The timeout (in seconds)
|
||||
-- @return A request handler
|
||||
function module.register_wibox(corner, w, s, timeout)
|
||||
if not w then return end
|
||||
local req = create_hot_corner(corner, s)
|
||||
local connected = false
|
||||
|
||||
function req.enter()
|
||||
|
||||
if type(w) == "function" then
|
||||
w = w(req)
|
||||
end
|
||||
|
||||
if not connected then
|
||||
w:connect_signal("mouse::leave", mouse_leave)
|
||||
wibox_to_req[w] = req --FIXME leak
|
||||
connected = true
|
||||
end
|
||||
|
||||
w.visible = true
|
||||
end
|
||||
|
||||
if not timeout then
|
||||
function req.leave() w.visible = false; req.wibox.visible = true end
|
||||
else
|
||||
function req.leave() create_visible_timer(w, timeout, req) end
|
||||
end
|
||||
|
||||
return req
|
||||
end
|
||||
|
||||
--TODO watch for workarea changes
|
||||
|
||||
return module
|
|
@ -0,0 +1,205 @@
|
|||
local capi = {screen=screen, mouse = mouse}
|
||||
local unpack = unpack or table.unpack
|
||||
local mouse = require( "awful.mouse" )
|
||||
local screen = require( "awful.screen" )
|
||||
local cairo = require( "lgi" ).cairo
|
||||
|
||||
local module = {}
|
||||
|
||||
-- Compute the new `x` and `y`.
|
||||
-- The workarea position need to be applied by the caller
|
||||
local map = {
|
||||
-- Corners
|
||||
top_left = function(sw, sh, dw, dh) return {x=0 , y=0 } end,
|
||||
top_right = function(sw, sh, dw, dh) return {x=sw-dw , y=0 } end,
|
||||
bottom_left = function(sw, sh, dw, dh) return {x=0 , y=sh-dh } end,
|
||||
bottom_right = function(sw, sh, dw, dh) return {x=sw-dw , y=sh-dh } end,
|
||||
left = function(sw, sh, dw, dh) return {x=0 , y=sh/2-dh/2} end,
|
||||
right = function(sw, sh, dw, dh) return {x=sw-dw , y=sh/2-dh/2} end,
|
||||
top = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=0 } end,
|
||||
bottom = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=sh-dh } end,
|
||||
}
|
||||
|
||||
-- Create the geometry rectangle 1=best case, 2=fallback
|
||||
local positions = {
|
||||
left1 = function(x, y, w, h) return {x = x - w, y = y , width = w, height = h} end,
|
||||
left2 = function(x, y, w, h) return {x = x - w, y = y - h , width = w, height = h} end,
|
||||
right1 = function(x, y, w, h) return {x = x , y = y , width = w, height = h} end,
|
||||
right2 = function(x, y, w, h) return {x = x , y = y - h , width = w, height = h} end,
|
||||
top1 = function(x, y, w, h) return {x = x , y = y - h , width = w, height = h} end,
|
||||
top2 = function(x, y, w, h) return {x = x - w, y = y - h , width = w, height = h} end,
|
||||
bottom1 = function(x, y, w, h) return {x = x , y = y , width = w, height = h} end,
|
||||
bottom2 = function(x, y, w, h) return {x = x - w, y = y , width = w, height = h} end,
|
||||
}
|
||||
|
||||
-- Check if the proposed geometry fit in the screen
|
||||
local function fit_in_screen(s, geo)
|
||||
local sgeo = capi.screen[s].geometry
|
||||
local region = cairo.Region.create_rectangle(cairo.RectangleInt(sgeo))
|
||||
|
||||
region:intersect(cairo.Region.create_rectangle(
|
||||
cairo.RectangleInt(geo)
|
||||
))
|
||||
|
||||
local geo2 = region:get_rectangle(0)
|
||||
|
||||
-- If the geometry is the same, then it fit, else, it will be cropped
|
||||
--TODO in case all directions are cropped, keep the least cropped one
|
||||
return geo2.width == geo.width and geo2.height == geo.height
|
||||
end
|
||||
|
||||
--- Move the drawable (client or wibox) `d` to a screen corner or side.
|
||||
function module.corner(d, corner, s, honor_wa, update_wa)
|
||||
local sgeo = capi.screen[s][honor_wa and "workarea" or "geometry"]
|
||||
local dgeo = d:geometry()
|
||||
|
||||
local pos = map[corner](sgeo.width, sgeo.height, dgeo.width, dgeo.height)
|
||||
|
||||
d : geometry {
|
||||
x = math.ceil(sgeo.x + pos.x) ,
|
||||
y = math.ceil(sgeo.y + pos.y) ,
|
||||
width = math.ceil(dgeo.width ) ,
|
||||
height = math.ceil(dgeo.height ) ,
|
||||
}
|
||||
|
||||
--TODO update_wa
|
||||
end
|
||||
|
||||
--- Pin a drawable to a placement function.
|
||||
-- Auto update the position when the size change
|
||||
function module.pin(d, f, ...)
|
||||
--TODO memory leak
|
||||
|
||||
local args = {...}
|
||||
|
||||
local function tracker()
|
||||
f(d, unpack(args))
|
||||
end
|
||||
|
||||
d:connect_signal("property::width" , tracker)
|
||||
d:connect_signal("property::height", tracker)
|
||||
|
||||
tracker()
|
||||
end
|
||||
|
||||
--- Get the possible 2D anchor points around a widget geometry.
|
||||
-- This take into account the widget drawable (wibox) and try to avoid
|
||||
-- overlapping.
|
||||
function module.get_relative_points(geo, mode)
|
||||
local use_mouse = true --TODO support modes
|
||||
|
||||
-- The closest points around the geometry
|
||||
local dps = {}
|
||||
|
||||
-- Use the mouse position and the wibox/client under it
|
||||
if not geo then
|
||||
local draw = mouse.drawin_under_pointer()
|
||||
geo = draw and draw:geometry() or capi.mouse.coords()
|
||||
geo.drawable = draw
|
||||
elseif geo.x and geo.width then
|
||||
local coords = capi.mouse.coords()
|
||||
|
||||
-- Check id the mouse is in the rect
|
||||
if coords.x > geo.x and coords.x < geo.x+geo.width and
|
||||
coords.y > geo.y and coords.y < geo.y+geo.height then
|
||||
geo.drawable = mouse.drawin_under_pointer()
|
||||
end
|
||||
--TODO add drawin_at(x,y) in the C core
|
||||
end
|
||||
|
||||
if geo.drawable then
|
||||
-- Case 1: A widget
|
||||
|
||||
local dgeo = geo.drawable.drawable:geometry()
|
||||
|
||||
-- Compute the absolute widget geometry
|
||||
local abs_widget_geo = {
|
||||
x = dgeo.x + geo.x,
|
||||
y = dgeo.y + geo.y,
|
||||
width = geo.width ,
|
||||
height = geo.height ,
|
||||
drawable = geo.drawable ,
|
||||
}
|
||||
|
||||
-- Get the comparaison point
|
||||
local center_point = use_mouse and capi.mouse.coords() or {
|
||||
x = abs_widget_geo.x + abs_widget_geo.width / 2,
|
||||
y = abs_widget_geo.y + abs_widget_geo.height / 2,
|
||||
}
|
||||
|
||||
-- Get the 4 cloest points from `center_point` around the wibox
|
||||
local points = {
|
||||
left = {x = dgeo.x , y = center_point.y },
|
||||
right = {x = dgeo.x + dgeo.width , y = center_point.y },
|
||||
top = {x = center_point.x , y = dgeo.y },
|
||||
bottom = {x = center_point.y , y = dgeo.y + dgeo.height },
|
||||
}
|
||||
|
||||
local s = geo.drawable.screen or screen.getbycoord(
|
||||
center_point.x,
|
||||
center_point.y
|
||||
)
|
||||
|
||||
-- Compute the distance (dp) between the `center_point` and the sides
|
||||
for k, v in pairs(points) do
|
||||
local dx, dy = v.x - center_point.x, v.y - center_point.y
|
||||
dps[k] = {
|
||||
distance = math.sqrt(dx*dx + dy*dy),
|
||||
x = v.x,
|
||||
y = v.y,
|
||||
screen = s
|
||||
}
|
||||
end
|
||||
|
||||
else
|
||||
-- Case 2: A random geometry
|
||||
--TODO
|
||||
end
|
||||
|
||||
return dps
|
||||
end
|
||||
|
||||
-- @tparam drawable d A wibox or client
|
||||
-- @tparam table points A table with position as key and points (x,y) as value
|
||||
-- @tparam[opt={}] table preferred_positions The preferred positions (position as key,
|
||||
-- and index as value)
|
||||
-- @treturn string The choosen position
|
||||
function module.move_relative(d, points, preferred_positions)
|
||||
local w,h = d.width, d.height
|
||||
|
||||
local pref_idx, pref_name = 99, nil
|
||||
|
||||
local does_fit = {}
|
||||
for k,v in pairs(points) do
|
||||
local geo = positions[k..1](v.x, v.y, w, h)
|
||||
local fit = fit_in_screen(v.screen, geo)
|
||||
|
||||
-- Try the other compatible geometry
|
||||
if not fit then
|
||||
geo = positions[k..2](v.x, v.y, w, h)
|
||||
fit = fit_in_screen(v.screen, geo)
|
||||
end
|
||||
|
||||
does_fit[k] = fit and geo or nil
|
||||
|
||||
if fit and preferred_positions[k] and preferred_positions[k] < pref_idx then
|
||||
pref_idx = preferred_positions[k]
|
||||
pref_name = k
|
||||
end
|
||||
|
||||
-- No need to continue
|
||||
if preferred_positions[k] == 1 then break end
|
||||
end
|
||||
|
||||
local pos_name = pref_name or next(does_fit)
|
||||
local pos = does_fit[pos_name]
|
||||
|
||||
if pos then
|
||||
d.x = math.ceil(pos.x)
|
||||
d.y = math.ceil(pos.y)
|
||||
end
|
||||
|
||||
return pos_name
|
||||
end
|
||||
|
||||
return module
|
|
@ -0,0 +1,237 @@
|
|||
---------------------------------------------------------------------------
|
||||
--- A rather hacky way to create free-floating widgets.
|
||||
--
|
||||
-- Hopefully this will be more maintainable then the old Radical hard-coded
|
||||
-- positioning code.
|
||||
--
|
||||
-- @author Emmanuel Lepage Vallee
|
||||
-- @copyright 2016 Emmanuel Lepage Vallee
|
||||
-- @release @AWESOME_VERSION@
|
||||
-- @module radical.smart_wibox
|
||||
---------------------------------------------------------------------------
|
||||
local capi = {mouse = mouse, screen = screen}
|
||||
local wibox = require( "wibox" )
|
||||
local util = require( "awful.util" )
|
||||
local surface = require( "gears.surface" )
|
||||
local glib = require( "lgi" ).GLib
|
||||
local beautiful = require( "beautiful" )
|
||||
local color = require( "gears.color" )
|
||||
local screen = require( "awful.screen" )
|
||||
local mouse = require( "awful.mouse" )
|
||||
local placement = require( "radical.placement")
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local module = {}
|
||||
|
||||
local main_widget = {}
|
||||
|
||||
-- Get the optimal direction for the wibox
|
||||
-- This (try to) avoid going offscreen
|
||||
local function set_position(self)
|
||||
local points = rawget(self, "possible_positions") or {}
|
||||
local preferred_positions = rawget(self, "_preferred_directions") or {}
|
||||
|
||||
local pos_name = placement.move_relative(self, points, preferred_positions)
|
||||
|
||||
if pos_name ~= rawget(self, "position") then
|
||||
self:emit_signal("property::position", pos_name)
|
||||
rawset(self, "position", pos_name)
|
||||
end
|
||||
end
|
||||
|
||||
--- Fit this widget into the given area
|
||||
function main_widget:fit(context, width, height)
|
||||
if not self.widget then
|
||||
return 0, 0
|
||||
end
|
||||
|
||||
return wibox.widget.base.fit_widget(self, context, self.widget, width, height)
|
||||
end
|
||||
|
||||
--- Layout this widget
|
||||
function main_widget:layout(context, width, height)
|
||||
if self.widget then
|
||||
local w, h = wibox.widget.base.fit_widget(self, context, self.widget, 9999, 9999)
|
||||
glib.idle_add(glib.PRIORITY_HIGH_IDLE, function()
|
||||
self._wb.width = math.ceil(w or 1)
|
||||
self._wb.height = math.ceil(h or 1)
|
||||
set_position(self._wb)
|
||||
end)
|
||||
return { wibox.widget.base.place_widget_at(self.widget, 0, 0, width, height) }
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the widget that is drawn on top of the background
|
||||
function main_widget:set_widget(widget)
|
||||
if widget then
|
||||
wibox.widget.base.check_widget(widget)
|
||||
end
|
||||
self.widget = widget
|
||||
self:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
--- Get the number of children element
|
||||
-- @treturn table The children
|
||||
function main_widget:get_children()
|
||||
return {self.widget}
|
||||
end
|
||||
|
||||
--- Replace the layout children
|
||||
-- This layout only accept one children, all others will be ignored
|
||||
-- @tparam table children A table composed of valid widgets
|
||||
function main_widget:set_children(children)
|
||||
self.widget = children and children[1]
|
||||
self:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
function main_widget:before_draw_children(context, cr, width, height)
|
||||
-- Update the wibox shape bounding. This module use custom painter instead
|
||||
-- of a shape clip to get antialiasing.
|
||||
if self._wb._shape and (width ~= self.prev_width or height ~= self.prev_height) then
|
||||
surface.apply_shape_bounding(self._wb, self._wb._shape, unpack(self._wb._shape_args))
|
||||
self.prev_width = width
|
||||
self.prev_height = height
|
||||
end
|
||||
|
||||
-- There is nothing else to do. The wibox background painter will do
|
||||
end
|
||||
|
||||
-- Draw the border after the content to emulate the shape_clip
|
||||
function main_widget:after_draw_children(context, cr, width, height)
|
||||
local border_width = self._wb._shape_border_width
|
||||
|
||||
if not border_width then return end
|
||||
|
||||
cr:translate(border_width/2, border_width/2)
|
||||
cr:set_line_width(border_width)
|
||||
|
||||
cr:set_source(self._wb._shape_border_color)
|
||||
self._wb._shape(cr, width-border_width, height-border_width, unpack(self._wb._shape_args or {}))
|
||||
cr:stroke()
|
||||
end
|
||||
|
||||
local wb_func = {}
|
||||
|
||||
--- Set the wibox shape.
|
||||
-- All other paramaters will be passed to the `s` function
|
||||
-- @param s A `gears.shape` compatible function
|
||||
function wb_func:set_shape(s, ...)
|
||||
|
||||
rawset(self, "_shape" , s )
|
||||
rawset(self, "_shape_args" , {...} )
|
||||
|
||||
self.widget:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
--- Set the wibox shape border color.
|
||||
-- Note that this is independant from the wibox border_color.
|
||||
-- The default are `beautiful.menu_border_color` or `beautiful.border_color`.
|
||||
-- The there is no border, then this function will do nothing.
|
||||
-- @param The border color or nil
|
||||
function wb_func:set_shape_border_color(col)
|
||||
rawset(self,"_shape_border_color", col and color(col) or color(beautiful.menu_border_color or beautiful.border_color))
|
||||
self.widget:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
--- Set the shape border (clip) width.
|
||||
-- The shape will be used to draw the border. Any content within the border
|
||||
-- will be hidden.
|
||||
-- @tparam number width The border width
|
||||
function wb_func:set_shape_border_width(width)
|
||||
rawset(self,"_shape_border_width", width)
|
||||
self.widget:emit_signal("widget::layout_changed")
|
||||
end
|
||||
|
||||
--- Set the preferred wibox directions relative to its parent.
|
||||
-- Valid directions are:
|
||||
-- * left
|
||||
-- * right
|
||||
-- * top
|
||||
-- * bottom
|
||||
-- @tparam string ... One of more directions (in the preferred order)
|
||||
function wb_func:set_preferred_positions(...)
|
||||
local dirs = {}
|
||||
for k, v in ipairs{...} do
|
||||
dirs[v] = k
|
||||
end
|
||||
rawset(self, "_preferred_directions", dirs)
|
||||
end
|
||||
|
||||
--- Move the wibox to a position relative to `geo`.
|
||||
-- This will try to avoid overlapping the source wibox and auto-detect the right
|
||||
-- direction to avoid going off-screen.
|
||||
-- @param[opt=mouse.coords()] geo A geometry table. It is given as parameter
|
||||
-- from buttons callbacks and signals such as `mouse::enter`.
|
||||
-- @param use_mouse Use the mouse position instead of the widget center as
|
||||
-- reference point.
|
||||
function wb_func:move_by_parent(geo, use_mouse)
|
||||
local dps = placement.get_relative_points(d, rgeo, mode)
|
||||
|
||||
rawset(self, "possible_positions", dps)
|
||||
|
||||
set_position(self)
|
||||
end
|
||||
|
||||
function wb_func:move_by_mouse()
|
||||
--TODO
|
||||
end
|
||||
|
||||
function wb_func:set_voffset(offset)
|
||||
|
||||
end
|
||||
|
||||
function wb_func:set_hoffset(offset)
|
||||
|
||||
end
|
||||
|
||||
--- A brilliant idea to totally turn the whole hierarchy on its head
|
||||
-- and create a widget that own a wibox...
|
||||
local function create_auto_resize_widget(self, wdg, args)
|
||||
assert(wdg)
|
||||
local ii = wibox.widget.base.make_widget()
|
||||
util.table.crush(ii, main_widget)
|
||||
|
||||
ii:set_widget(wdg)
|
||||
|
||||
-- Create a wibox to host the widget
|
||||
local w = wibox(args or {})
|
||||
|
||||
-- Wibox use metatable inheritance, rawset is necessary
|
||||
for k, v in pairs(wb_func) do
|
||||
rawset(w, k, v)
|
||||
end
|
||||
|
||||
-- Cross-link the wibox and widget
|
||||
ii._wb = w
|
||||
w:set_widget(ii)
|
||||
rawset(w, "widget", wdg)
|
||||
|
||||
-- Changing the widget is not supported
|
||||
rawset(w, "set_widget", function()end)
|
||||
|
||||
w:set_shape_border_color()
|
||||
|
||||
w:add_signal("property::position")
|
||||
|
||||
if args and args.preferred_positions then
|
||||
if type(args.preferred_positions) == "table" then
|
||||
w:set_preferred_positions(unpack(args.preferred_positions))
|
||||
else
|
||||
w:set_preferred_positions(args.preferred_positions)
|
||||
end
|
||||
end
|
||||
|
||||
if args.shape then
|
||||
w:set_shape(args.shape, unpack(args.shape_args or {}))
|
||||
end
|
||||
|
||||
for k,v in ipairs{"shape_border_color", "shape_border_width"} do
|
||||
if args[v] then
|
||||
w["set_"..v](w, args[v])
|
||||
end
|
||||
end
|
||||
|
||||
return w
|
||||
end
|
||||
|
||||
return setmetatable(module, {__call = create_auto_resize_widget})
|
Loading…
Reference in New Issue