placement: Re-introduce many placements features lost during the re-write

The menu can be attached to a widget or the mouse again, and more

This commit also fix some tasklist/taglist issues and introduce minor
features like item.shape.
This commit is contained in:
Emmanuel Lepage Vallee 2016-03-14 17:52:28 -04:00
parent ac402846a3
commit cbc5e6919f
15 changed files with 306 additions and 129 deletions

View File

@ -13,7 +13,7 @@ local function set_visible(i, value)
local w, pg = i.w, i.private_data.parent_geometry
if value then
w:move_by_parent(pg, true)
w:move_by_parent(pg, "widget")
end
w.visible = value
@ -66,7 +66,7 @@ local function new(args)
local ret = base(args)
ret:connect_signal("parent_geometry::changed", function()
args.internal.w:move_by_parent(ret.parent_geometry, true)
args.internal.w:move_by_parent(ret.parent_geometry, "widget")
end)
-- Init the style

View File

@ -95,7 +95,14 @@ local function get_wibox(data, screen)
adapt_size(data, w.width, w.height, 1)
end)
placement.pin(w, placement.corner, "left", screen or 1)
local f = nil
if beautiful.dock_always_show then
f = placement.attach_struts
else
f = placement.attach
end
f(w, placement.align, "left", screen or 1)
return w
end

View File

@ -6,6 +6,8 @@ local placement = require( "radical.placement" )
local module = {}
--TODO add "wrap cursor mode"
local wibox_to_req = {}
local corners_geo = {
@ -43,7 +45,7 @@ local function create_hot_corner(corner, s)
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)
placement.align(w, corner, s, false, false)
local req = {wibox = w, screen = s, corner = corner}
@ -123,4 +125,4 @@ end
--TODO watch for workarea changes
return module
return module

View File

@ -62,7 +62,7 @@ end
function module.screenshot(clients,geo)
if not clients then return end
local prev_menu= radical.context({layout=radical.layout.horizontal,item_width=140,item_height=140,icon_size=100,
local prev_menu= radical.context({layout=radical.layout.horizontal,item_layout=radical.item.layout.centerred,item_width=140,item_height=140,icon_size=100,
arrow_type=radical.base.arrow_type.CENTERED,enable_keyboard=false,item_style=radical.item.style.rounded})
local t = type(clients)
if t == "client" then

View File

@ -8,21 +8,6 @@ local tag_list = nil
local module = {}
local fallback_layouts = {
suits.floating,
suits.tile,
suits.tile.left,
suits.tile.bottom,
suits.tile.top,
suits.fair,
suits.fair.horizontal,
suits.spiral,
suits.spiral.dwindle,
suits.max,
suits.max.fullscreen,
suits.magnifier
}
local function createTagList(aScreen,args)
if not tag_list then
tag_list = require("radical.impl.taglist")
@ -62,7 +47,8 @@ end
function module.layouts(menu,layouts)
local cur = awful.layout.get(awful.tag.getscreen(awful.tag.selected(capi.client.focus and capi.client.focus.screen)))
local screenSelect = menu or radical.context {}
local layouts = layouts or awful.layout.layouts or fallback_layouts
local layouts = layouts or awful.layout.layouts
for i, layout_real in ipairs(layouts) do
local layout2 = awful.layout.getname(layout_real)
local is_current = cur and ((layout_real == cur) or (layout_real.name == cur.name))
@ -107,14 +93,16 @@ function module.layout_item(menu,args)
local function update()
local layout = awful.layout.getname(awful.layout.get(screen))
local ic = beautiful["layout_small_" ..layout] or beautiful["layout_" ..layout]
local ic = beautiful["layout_" ..layout]
item.icon = ic
end
update()
awful.tag.attached_connect_signal(screen, "property::selected", update)
awful.tag.attached_connect_signal(screen, "property::layout" , update)
return item
end
return setmetatable(module, { __call = function(_, ...) return module.listTags(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;
-- kate: space-indent on; indent-width 2; replace-tabs on;

View File

@ -38,9 +38,11 @@ local cache = setmetatable({}, { __mode = 'k' })
module.buttons = { [1] = awful.tag.viewonly,
[2] = awful.tag.viewtoggle,
[3] = function(q,w,e,r)
local menu = tag_menu(q)
[3] = function(t,menu,item,button_id,mod,geo)
local menu = tag_menu(t)
menu.parent_geometry = geo
menu.visible = true
menu._internal.w:move_by_parent(geo, "cursor")
end,
[4] = function(t) awful.tag.viewnext(awful.tag.getscreen(t)) end,
[5] = function(t) awful.tag.viewprev(awful.tag.getscreen(t)) end,
@ -117,10 +119,10 @@ local function create_item(t,s)
end
-- menu:move(item,index)
menu:connect_signal("button::press",function(menu,item,button_id,mod)
menu:connect_signal("button::press",function(menu,item,button_id,mod,geo)
if module.buttons and module.buttons[button_id] then
if item.tag[1] then
module.buttons[button_id](item.tag[1],menu,item,button_id,mod)
module.buttons[button_id](item.tag[1],menu,item,button_id,mod,geo)
else
print("Invalid tag")
end

View File

@ -120,7 +120,7 @@ local function new(t)
local mainMenu2 = menu{layout=radical.layout.grid,column=6,}
-- TODO port to async
local f = io.popen('find '..config.iconPath .. "tags/ -maxdepth 1 -iname \"*.png\" -type f","r")
local f = io.popen('find '..config.iconPath .. "tags_invert/ -maxdepth 1 -iname \"*.png\" -type f","r")
local counter = 0
while true do
local file = f:read("*line")

View File

@ -26,7 +26,7 @@ theme.register_color(MINIMIZED , "minimized" , "tasklist_minimized" , true )
-- Default button implementation
module.buttons = {
[1] = function (c)
[1] = function(c,menu,item,button_id,mod, geo)
if c == capi.client.focus then
c.minimized = true
else
@ -42,16 +42,18 @@ module.buttons = {
c:raise()
end
end,
[3] = function(c)
[3] = function(c,menu,item,button_id,mod, geo)
client_menu.client = c
local menu = client_menu()
menu.parent_geometry = geo
menu.visible = not menu.visible
menu._internal.w:move_by_parent(geo, "cursor")
end,
[4] = function ()
[4] = function(c,menu,item,button_id,mod, geo)
client.focus.byidx(1)
if capi.client.focus then capi.client.focus:raise() end
end,
[5] = function ()
[5] = function(c,menu,item,button_id,mod, geo)
client.focus.byidx(-1)
if capi.client.focus then capi.client.focus:raise() end
end
@ -203,14 +205,14 @@ local function create_client_item(c,screen)
item.state[MINIMIZED] = true
end
item:connect_signal("mouse::enter", function()
item.infoshapes = {
{text = "1:23:45", bg = beautiful.tasklist_bg_overlay, align = "center"},
{text = c.pid , bg = beautiful.tasklist_bg_overlay, align = "center"}
}
end)
-- item:connect_signal("mouse::enter", function()
-- item.infoshapes = {
-- {text = "1:23:45", bg = beautiful.tasklist_bg_overlay, align = "center"},
-- {text = c.pid , bg = beautiful.tasklist_bg_overlay, align = "center"}
-- }
-- end)
item:connect_signal("mouse::leave", function()
item.infoshapes = {}
-- item.infoshapes = {}
end)
item.add_suffix = function(w,w2)
@ -334,9 +336,9 @@ local function new(screen)
load_clients(tag.selected(screen))
menu:connect_signal("button::press",function(menu,item,button_id,mod)
menu:connect_signal("button::press",function(menu,item,button_id,mod,geo)
if module.buttons and module.buttons[button_id] then
module.buttons[button_id](item.client,menu,item,button_id,mod)
module.buttons[button_id](item.client,menu,item,button_id,mod,geo)
end
end)
@ -344,7 +346,7 @@ local function new(screen)
display_screenshot(i.client,geo,true)
end)
return menu,menu._internal.layout
return menu,menu._internal.widget
end
function module.item(client)

View File

@ -22,10 +22,12 @@ end
-- @tparam[opt="button1::pressed"] string event The event trigger for showing
-- the menu.
-- @tparam[opt=1] button_id The mouse button 1 (1= left, 3=right)
local function set_menu(self,menu, event, button_id)
-- @tparam[opt=widget] The position mode (see `radical.placement`)
local function set_menu(self,menu, event, button_id, mode)
if not menu then return end
local event = event or "button::pressed"
local button_id = button_id or 1
mode = mode or "widget"
local function trigger(_, geo)
@ -47,7 +49,7 @@ local function set_menu(self,menu, event, button_id)
if not m then return end
m.parent_geometry = geo
m._internal.w:move_by_parent(geo)
m._internal.w:move_by_parent(geo, mode)
m.visible = not m.visible
end

View File

@ -120,6 +120,7 @@ local function new_item(data,args)
style = args.style or data.item_style ,
layout = args.layout or args.item_layout or nil ,
infoshapes = args.infoshapes or nil ,
shape = args.shape or data.item_shape ,
overlay_draw= args.overlay_draw or data.overlay_draw ,
item_border_color = args.item_border_color or data.item_border_color or nil ,
},

View File

@ -19,7 +19,7 @@ local function generic(_, args)
}
local function draw(item)
item.widget:set_shape(args.shape, unpack(args.shape_args or {}))
item.widget:set_shape(args.shape or item.shape, unpack(args.shape_args or {}))
item.widget:set_shape_border_width(item.border_width)
theme.update_colors(item)
end

View File

@ -9,17 +9,22 @@ 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,
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,
centered = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=sh/2-dh/2} end,
center_vertical = function(sw, sh, dw, dh) return {x= nil , y=sh-dh } end,
center_horizontal = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y= nil } end,
}
-- Store function -> keys
local reverse_map = {}
-- 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,
@ -48,115 +53,268 @@ local function fit_in_screen(s, geo)
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()
--- Move the drawable (client or wibox) `d` to a screen position or side.
--
-- Supported positions are:
--
-- * top_left
-- * top_right
-- * bottom_left
-- * bottom_right
-- * left
-- * right
-- * top
-- * bottom
-- * centered
-- * center_vertical
-- * center_horizontal
--
-- The valid other arguments are:
--
-- * *honor_workarea*: Take workarea into account when placing the drawable (default: false)
--
-- @param drawable A drawable (like `client` or `wibox`)
-- @tparam string position One of the position mentionned above
-- @param[opt=d.screen or capi.mouse.screen] parent The parent geometry
-- @tparam[opt={}] table args Other arguments
function module.align(drawable, position, parent, args)
args = args or {}
parent = parent or drawable.screen or capi.mouse.screen
local pos = map[corner](sgeo.width, sgeo.height, dgeo.width, dgeo.height)
local sgeo = nil
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 ) ,
-- Get the parent geometry
local parent_type = type(parent)
if parent_type == "screen" or parent_type == "number" then
sgeo = capi.screen[parent][args.honor_workarea and "workarea" or "geometry"]
else
sgeo = parent:geometry()
end
local dgeo = drawable:geometry()
local pos = map[position](sgeo.width, sgeo.height, dgeo.width, dgeo.height)
drawable : geometry {
x = pos.x and math.ceil(sgeo.x + pos.x) or dgeo.x,
y = pos.y and math.ceil(sgeo.y + pos.y) or dgeo.y,
width = math.ceil(dgeo.width ) ,
height = math.ceil(dgeo.height ) ,
}
end
--TODO update_wa
-- Create many placement functions
for k,v in pairs(map) do
module[k] = function(d, p, args)
module.align(d, k, p, args)
end
reverse_map[module[k]] = k
end
--- Pin a drawable to a placement function.
-- Auto update the position when the size change
function module.pin(d, f, ...)
--TODO memory leak
-- Automatically update the position when the size change.
-- All other arguments will be passed to the `position` function (if any)
-- @param drawable A drawable (like `client` or `wibox`)
-- @param position A position name (see `align`) or a position function
function module.attach(drawable, position, ...)
if type(position) == "string" then
position = module[position]
end
if not position then return end
local args = {...}
local function tracker()
f(d, unpack(args))
position(drawable, unpack(args))
end
d:connect_signal("property::width" , tracker)
d:connect_signal("property::height", tracker)
drawable:connect_signal("property::width" , tracker)
drawable:connect_signal("property::height", tracker)
tracker()
end
-- Update the workarea
local function wibox_update_strut(d, position)
-- If the drawable isn't visible, remove the struts
if not d.visible then
d:struts { left = 0, right = 0, bottom = 0, top = 0 }
return
end
-- Detect horizontal or vertical drawables
local geo = d:geometry()
local vertical = geo.width < geo.height
-- Look into the `position` string to find the relevants sides to crop from
-- the workarea
local struts = { left = 0, right = 0, bottom = 0, top = 0 }
if vertical then
for k, v in ipairs {"right", "left"} do
if (not position) or position:match(v) then
struts[v] = geo.width + 2 * d.border_width
end
end
else
for k, v in ipairs {"top", "bottom"} do
if (not position) or position:match(v) then
struts[v] = geo.height + 2 * d.border_width
end
end
end
-- Update the workarea
d:struts(struts)
end
function module.attach_struts(d, f, ...)
module.attach(d, f, ...)
--TODO if there is multiple attach_struts, update them, see `raise_attached_struts`
local function tracker()
wibox_update_strut(d, reverse_map[f])
end
d:connect_signal("property::geometry" , tracker)
d:connect_signal("property::visible" , tracker)
tracker()
end
--- Move a drawable to the "top priority" of attached_structs
function module.raise_attached_struts()
end
-- Create a pair of rectangles used to set the relative areas.
-- v=vertical, h=horizontal
local function get_cross_sections(abs_geo, mode)
if not mode or mode == "cursor" then
-- A 1px cross section centered around the mouse position
local coords = capi.mouse.coords()
return {
h = {
x = abs_geo.drawable_geo.x ,
y = coords.y ,
width = abs_geo.drawable_geo.width ,
height = 1 ,
},
v = {
x = coords.x ,
y = abs_geo.drawable_geo.y ,
width = 1 ,
height = abs_geo.drawable_geo.height,
}
}
elseif mode == "widget" then
-- The widget geometry extended to reach the end of the drawable
return {
h = {
x = abs_geo.drawable_geo.x ,
y = abs_geo.y ,
width = abs_geo.drawable_geo.width ,
height = abs_geo.height ,
},
v = {
x = abs_geo.x ,
y = abs_geo.drawable_geo.y ,
width = abs_geo.width ,
height = abs_geo.drawable_geo.height,
}
}
elseif mode == "cursor_inside" then
-- A 1x1 rectangle centered around the mouse position
local coords = capi.mouse.coords()
coords.width,coords.height = 1,1
return {h=coords, v=coords}
elseif mode == "widget_inside" then
-- The widget absolute geometry, unchanged
return {h=abs_geo, v=abs_geo}
end
assert(false)
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 = {}
function module.get_relative_points(geo, mode) --TODO rename regions
mode = mode or "widget"
-- 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
elseif (not geo.drawable) and 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
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
-- Get the drawable geometry
local dpos = geo.drawable and geo.drawable.drawable:geometry() or {x=0, y=0}
local dgeo = geo.drawable.drawable:geometry()
-- Compute the absolute widget geometry
local abs_widget_geo = {
x = dpos.x + geo.x ,
y = dpos.y + geo.y ,
width = geo.width ,
height = geo.height ,
drawable = geo.drawable ,
drawable_geo = geo.drawable and dpos or geo,
}
-- 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 = mode:match("cursor") 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 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 widget regions for both axis
local cs = get_cross_sections(abs_widget_geo, mode)
-- 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.x , y = dgeo.y + dgeo.height },
}
local s = geo.drawable.screen or screen.getbycoord(
center_point.x,
center_point.y
)
-- Get the 4 closest points from `center_point` around the wibox
local regions = {
left = {x = cs.h.x , y = cs.h.y },
right = {x = cs.h.x+cs.h.width, y = cs.h.y },
top = {x = cs.v.x , y = cs.v.y },
bottom = {x = cs.v.x , y = cs.v.y+cs.v.height},
}
-- 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
-- Assume the section is part of a single screen until someone complain.
-- It is much faster to compute and getting it wrong probably have no side
-- effects.
local s = geo.drawable and geo.drawable.screen or screen.getbycoord(
center_point.x,
center_point.y
)
else
-- Case 2: A random geometry
--TODO
-- Compute the distance (dp) between the `center_point` and the sides.
-- This is only relevant for "cursor" and "cursor_inside" modes.
for k, v in pairs(regions) do
local dx, dy = v.x - center_point.x, v.y - center_point.y
v.distance = math.sqrt(dx*dx + dy*dy)
v.width = cs.v.width
v.height = cs.h.height
v.screen = s
end
return dps
return regions
end
-- @tparam drawable d A wibox or client
@ -164,7 +322,7 @@ end
-- @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)
function module.move_relative(d, points, preferred_positions) --TODO inside/outside
local w,h = d.width, d.height
local pref_idx, pref_name = 99, nil

View File

@ -14,6 +14,7 @@ local wibox = require( "wibox" )
local tag = require( "awful.tag" )
local color = require( "gears.color" )
local cairo = require( "lgi" ).cairo
local shape = require( "gears.shape" )
local default = {width=90,height=30,radius=40,base_radius=60}
@ -114,14 +115,28 @@ function module.radial_client_select(args)
data.indicator.cr:set_operator(cairo.Operator.SOURCE)
end
data.indicator.cr:set_source_rgb(1,0,0)
data.indicator.cr:arc ( data.width/2 + (default.base_radius-20)*math.cos(angle),data.width/2 + (default.base_radius-20)*math.sin(angle),5,0,2*math.pi )
data.indicator.cr:close_path()
-- The Inner dot around the dotted circle
local littledot_rad = 5
local dot = shape.transform(shape.circle) : translate(
data.width/2 + (default.base_radius-20)*math.cos(angle) -littledot_rad,
data.width/2 + (default.base_radius-20)*math.sin(angle) -littledot_rad
)
dot(data.indicator.cr, 2*littledot_rad, 2*littledot_rad)
data.indicator.cr:fill()
-- The little arc on top of the border
data.indicator.cr:set_line_width(4)
data.indicator.cr:arc( data.width/2,data.height/2,default.radius + default.base_radius ,angle-0.15,angle+0.15 )
data.indicator.cr:stroke()
data.indicator.cr:arc ( data.width/2+170,data.height/2,10,0,2*math.pi )
data.indicator.cr:close_path()
-- The Big red dot TODO make it move
dot = shape.transform(shape.circle) : translate(
data.width/2+170 -2*littledot_rad,
data.height/2 -2*littledot_rad
)
dot(data.indicator.cr, 4*littledot_rad, 4*littledot_rad)
data.indicator.cr:fill()
data.angle_cache = data.angle
end

View File

@ -162,9 +162,9 @@ end
-- 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
-- @param mode Use the mouse position instead of the widget center as
-- reference point.
function wb_func:move_by_parent(geo, use_mouse)
function wb_func:move_by_parent(geo, mode)
if rawget(self, "is_relative") == false then return end
local dps = placement.get_relative_points(geo, mode)

View File

@ -41,7 +41,7 @@ end
local function get_extents(text, height)
local l = init_pango(height)
l.text = text
l.text = text or ""
return l:get_pixel_extents()
end