Rewrite the view layer

Radical is a model-view framework. There is a common API to
set data, then view modules to display it. Those modules have
been rewritten on top of new APIs.

Not all features are yet re-integrated, most are. Future commits
will address the various regressions and missing features. This
time, the code should be maintainable. The whole separation of
concerns analysis has been re-done. Allowing code de-duplication
and generalization.

The API remain the same, only `set_menu` has changed a little to
allow merging the 2 sub-menu code paths.
This commit is contained in:
Emmanuel Lepage Vallee 2016-02-21 02:34:05 -05:00
parent 1711d28dcd
commit f239ee9a24
14 changed files with 764 additions and 1455 deletions

217
bar.lua
View File

@ -1,202 +1,61 @@
local setmetatable,unpack,table = setmetatable,unpack,table
local base = require( "radical.base" )
local color = require( "gears.color" )
local wibox = require( "wibox" )
local beautiful = require( "beautiful" )
local cairo = require( "lgi" ).cairo
local awful = require( "awful" )
local util = require( "awful.util" )
local fkey = require( "radical.widgets.fkey" )
local button = require( "awful.button" )
local checkbox = require( "radical.widgets.checkbox" )
local base = require( "radical.base" )
local color = require( "gears.color" )
local wibox = require( "wibox" )
local beautiful = require( "beautiful" )
local item_style = require( "radical.item.style.arrow_single" )
-- local vertical = require( "radical.layout.vertical" )
local item_layout= require( "radical.item.layout.horizontal" )
local margins2 = require("radical.margins" )
local item_layout= require( "radical.item.layout.horizontal" )
local common = require( "radical.common" )
local capi,module = { mouse = mouse , screen = screen, keygrabber = keygrabber },{}
local function get_direction(data)
return "left" -- Nothing to do
end
local function set_position(self)
return --Nothing to do
end
-- Draw the menu background
local function bg_draw(self, context, cr, width, height)--w, cr, width, height)
cr:save()
cr:set_source(color(self._data.bg))
cr:rectangle(0,0,width,height)
cr:fill()
cr:restore()
end
local function proxy_draw(_,...)
local l = _._internal and _._internal.layout or _
return wibox.layout.fixed.draw(l,...)
end
local function proxy_fit(_,...)
local l = _._internal and _._internal.layout or _
return wibox.layout.fixed.fit(l,...)
end
local module = {}
local function setup_drawable(data)
local internal = data._internal
local private_data = internal.private_data
local internal = data._internal
internal.layout = internal.layout_func or wibox.layout.fixed.horizontal()
internal.layout = internal.layout_func or wibox.layout.fixed.horizontal()
internal.margin = wibox.layout.margin(internal.layout)
internal.layout._data = data
--internal.layout.draw = bg_draw --TODO Dead code?
internal.layout._data = data
if internal.layout.set_spacing and data.spacing then
internal.layout:set_spacing(data.spacing)
end
--Getters
data.get_x = function() return 0 end
data.get_y = function() return 0 end
data.get_width = function() return internal.layout:get_preferred_size() end
data.get_height = function() return beautiful.default_height end
data.get_visible = function() return true end
data.get_direction = function() return "left" end
-- Setup the margins
local m = wibox.layout.margin()
m:set_widget(internal.layout)
internal.margin = m
local mrgns = margins2(m,data.default_margins or {})
data._internal.mrgns = mrgns
data.get_margins = function()
return data._internal.mrgns or {}
end
if data.style then
data.style(data)
end
-- This widget do not use wibox, so setup correct widget interface
data.fit = internal.layout
data.draw = internal.layout
-- Swap / Move / Remove
data:connect_signal("item::swapped",function(_,item1,item2,index1,index2)
internal.layout.widgets[index1],internal.layout.widgets[index2] = internal.layout.widgets[index2],internal.layout.widgets[index1]
internal.layout:emit_signal("widget::updated")
end)
data:connect_signal("item::moved",function(_,item,new_idx,old_idx)
table.insert(internal.layout.widgets,new_idx,table.remove(internal.layout.widgets,old_idx))
internal.layout:emit_signal("widget::updated")
end)
data:connect_signal("item::removed",function(_,item,old_idx)
table.remove(internal.layout.widgets,old_idx)
if internal.layout._emit_updated then
item.widget:disconnect_signal("widget::updated", internal.layout._emit_updated)
if internal.layout.set_spacing and data.spacing then
internal.layout:set_spacing(data.spacing)
end
internal.layout:emit_signal("widget::updated")
end)
data:connect_signal("item::appended",function(_,item)
internal.layout:add(item.widget)
internal.layout:emit_signal("widget::updated")
end)
data:connect_signal("widget::added",function(_,item,widget)
internal.layout:add(widget)
internal.layout:emit_signal("widget::updated")
end)
end
local function setup_buttons(data,item,args)
local buttons = {}
for i=1,10 do
if args["button"..i] then
buttons[i] = args["button"..i]
--Getters
data.get_visible = function() return true end
data.get_margins = common.get_margins
if data.style then
data.style(data)
end
end
-- Setup sub_menu
if (item.sub_menu_m or item.sub_menu_f) and data.sub_menu_on >= base.event.BUTTON1 and data.sub_menu_on <= base.event.BUTTON3 then
item.widget:set_menu(item.sub_menu_m or item.sub_menu_f,data.sub_menu_on)
end
-- Scrool up
if not buttons[4] then
buttons[4] = function()
data:scroll_up()
end
end
-- Scroll down
if not buttons[5] then
buttons[5] = function()
data:scroll_down()
end
end
item:connect_signal("button::release",function(_m,_i,button_id,mods,geo)
if #mods == 0 and buttons[button_id] then
buttons[button_id](_m,_i,mods,geo)
end
end)
end
local function setup_item(data,item,args)
-- Add widgets
data._internal.layout:add(data.item_layout(item,data,args))
if data.select_on == base.event.HOVER then
item.widget:connect_signal("mouse::enter", function() item.selected = true end)
item.widget:connect_signal("mouse::leave", function() item.selected = false end)
else
item.widget:connect_signal("mouse::enter", function() item.hover = true end)
item.widget:connect_signal("mouse::leave", function() item.hover = false end)
end
-- Setup buttons
setup_buttons(data,item,args)
-- Tooltip
item.widget:set_tooltip(item.tooltip)
common.setup_item_move_events(data)
end
local function new(args)
local args = args or {}
args.internal = args.internal or {}
args.internal.get_direction = args.internal.get_direction or get_direction
args.internal.set_position = args.internal.set_position or set_position
local args = args or {}
args.internal = args.internal or {}
args.internal.setup_drawable = args.internal.setup_drawable or setup_drawable
args.internal.setup_item = args.internal.setup_item or setup_item
args.item_style = args.item_style or item_style
args.item_layout = args.item_layout or item_layout
args.sub_menu_on = args.sub_menu_on or base.event.BUTTON1
local ret = base(args)
ret:connect_signal("clear::menu",function(_,vis)
ret._internal.layout:reset()
end)
ret:connect_signal("_hidden::changed",function(_,item)
item.widget:emit_signal("widget::updated")
end)
args.internal.setup_item = args.internal.setup_item or common.setup_item
args.item_style = args.item_style or item_style
args.item_layout = args.item_layout or item_layout
args.sub_menu_on = args.sub_menu_on or base.event.BUTTON1
local ret = base(args)
-- rawset(ret,"fit", proxy_fit)
-- rawset(ret,"draw", proxy_draw)
-- rawset(ret,"_fit_geometry_cache", ret._internal.layout._fit_geometry_cache)
-- rawset(ret,"add_signal", function()end)
-- ret._internal.layout:connect_signal("widget::updated",function()
-- ret:emit_signal("widget::updated")
-- end)
return ret,ret._internal.margin
end
function module.flex(args)
local args = args or {}
args.internal = args.internal or {}
args.internal.layout_func = wibox.layout.flex.horizontal()
local data = new(args)
data._internal.text_fit = function(self,width,height) return width,height end
return data,data._internal.margin
local args = args or {}
args.internal = args.internal or {}
args.internal.layout_func = wibox.layout.flex.horizontal()
local data = new(args)
function data._internal.text_fit(self,width,height) return width,height end
return data,data._internal.margin
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;
-- kate: space-indent on; indent-width 4; replace-tabs on;

View File

@ -10,6 +10,7 @@ local object = require( "radical.object" )
local vertical = require( "radical.layout.vertical" )
local theme = require( "radical.theme" )
local item_mod = require( "radical.item" )
local common = require( "radical.common" )
local capi = { mouse = mouse, screen = screen , keygrabber = keygrabber, root=root, }
@ -97,7 +98,7 @@ local function filter(data)
end
end
data._total_item_height = visible_counter
local w,h = data._internal.layout:fit()
-- Make sure to select an item
if data._current_item and data._current_item._filter_out then
local n = data.next_item
@ -105,7 +106,7 @@ local function filter(data)
n.selected = true
end
end
data.height = h
end
end
@ -163,12 +164,10 @@ local function activateKeyboard(data)
end
if (key == 'Return') and data._current_item and data._current_item.button1 then
if data.sub_menu_on == module.event.BUTTON1 then
item_mod.execute_sub_menu(data,data._current_item)
else
data._current_item.button1(data,data._current_item)
data.visible = false
end
if data.sub_menu_on ~= module.event.BUTTON1 then
data.visible = false
end
elseif key == 'Escape' or (key == 'Tab' and data.filter_string == "") then
data.visible = false
capi.keygrabber.stop()
@ -187,7 +186,6 @@ local function activateKeyboard(data)
end
end
---------------------------------ITEM HANDLING----------------------------------
local function add_item(data,args)
local item = item_mod(data,args)
@ -195,6 +193,32 @@ local function add_item(data,args)
if args.selected == true then
item.selected = true
end
-- Show sub-menu
if item._private_data.sub_menu_f or item._private_data.sub_menu_m then
if data.sub_menu_on == module.event.SELECTED then
item.widget:set_menu(
function()
local sub_menu = item._private_data.sub_menu_m
if not sub_menu and item._private_data.sub_menu_f then
sub_menu = item._private_data.sub_menu_f(item.widget)
end
if sub_menu and (item._private_data.sub_menu_f or sub_menu.rowcount > 0) then
sub_menu.arrow_type = module.arrow_type.NONE
sub_menu.parent_item = item
item._tmp_menu = sub_menu
data._tmp_menu = sub_menu
end
return sub_menu
end,
"mouse::enter"
)
end
end
item.index = data.rowcount
data:emit_signal("item::added",item)
return item
@ -233,16 +257,10 @@ local function add_widget(data,widget,args)
})
item._private_data = private_data
item._internal = {}
item.get_y = function() return (args.y and args.y >= 0) and args.y or data.height - (data.margins.top or data.border_width) - data.item_height end --Hack around missing :fit call for last item
data._internal.widgets[#data._internal.widgets+1] = item
data._internal.items[#data._internal.items+1] = item
data:emit_signal("widget::added",item,widget)
if data.visible then
local fit_w,fit_h = data._internal.layout:get_preferred_size()
data.width = data._internal.width or fit_w
data.height = fit_h
end
end
local function add_widgets(data,widgets)
@ -360,8 +378,8 @@ local function new(args)
overlay = args.overlay or nil,
overlay_draw = args.overlay_draw or nil,
opacity = args.opacity or beautiful.menu_opacity or 1,
spacing = args.spacing or nil, --TODO add to README once merged upstream
default_margins = args.default_margins or {},
spacing = args.spacing or nil,
default_margins = args.default_margins or beautiful.menu_default_margins or {},
icon_per_state = args.icon_per_state or false,
default_item_margins = args.default_item_margins or {},
icon_transformation = args.icon_transformation or nil,
@ -407,33 +425,21 @@ local function new(args)
-- Setters
data.set_auto_resize = function(_,val) private_data[""] = val end
data.set_parent_geometry = function(_,value)
private_data.parent_geometry = value
if data._internal.get_direction then
data.direction = data._internal.get_direction(data)
end
if data._internal.set_position then
data._internal.set_position(data)
end
private_data.parent_geometry = value --TODO delete
end
data.set_visible = function(_,value)
private_data.visible = value
if value then
local fit_w,fit_h = data._internal.layout:get_preferred_size()
data.width = internal.width or fit_w
data.height = fit_h
elseif data._tmp_menu and data._current_item then
-- data._tmp_menu = nil
if data._tmp_menu and data._current_item then
data._current_item._tmp_menu = nil
-- data._current_item.selected = false
data.item_style(data._current_item,{})
end
if internal.has_changed and data.style then
data.style(data,{arrow_x=20,margin=internal.margin})
end
-- if not internal.parent_geometry and data._internal.set_position then
if internal.set_position then
internal.set_position(data)
-- end
end
if internal.set_visible then
internal:set_visible(value)
end
@ -442,6 +448,11 @@ local function new(args)
elseif data.parent_geometry and not data.parent_geometry.is_menu and data.enable_keyboard then
capi.keygrabber.stop()
end
-- Hide the sub menus when hiding
if data._tmp_menu and not value then
data._tmp_menu.visible = false
end
end
data.add_colors_group = function(data,section)
@ -450,7 +461,8 @@ local function new(args)
data.set_layout = function(_,value)
if value then
value:setup_key_hooks(data)
local f = value.setup_key_hooks or common.setup_key_hooks
f(value, data)
end
private_data.layout = value
end
@ -637,8 +649,9 @@ local function new(args)
function data:hide()
data.visible = false
if data.parent_geometry and data.parent_geometry.is_menu then
local parent = data.parent_geometry
if data.parent_geometry and data.parent_geometry.parent_menu then
local parent = data.parent_geometry.parent_menu
while parent do
parent.visible = false
parent = parent.parent_geometry and parent.parent_geometry.is_menu and parent.parent_geometry
@ -647,7 +660,8 @@ local function new(args)
end
if private_data.layout then
private_data.layout:setup_key_hooks(data)
local f = private_data.layout.setup_key_hooks or common.setup_key_hooks
f(value, data)
end
data._internal.setup_drawable(data)

25
box.lua
View File

@ -1,22 +1,21 @@
local setmetatable = setmetatable
local context = require("radical.context")
local base = require("radical.base")
local capi = { mouse = mouse, screen = screen }
local function set_position(data)
local s = data.screen or capi.mouse.screen
s = s > capi.screen.count() and 1 or s
local geom = capi.screen[s].geometry
data.wibox.x = math.ceil(geom.x + (geom.width/2) - data.width/2)
data.wibox.y = math.ceil(geom.y + (geom.height/2) - data.height/2)
end
local context = require( "radical.context" )
local base = require( "radical.base" )
local shape = require( "gears.shape" )
local placement = require( "awful.placement" )
local function new(args)
local args = args or {}
args.internal = args.internal or {}
args.arrow_type = base.arrow_type.NONE
args.internal.set_position = args.internal.set_position or set_position
return context(args)
local ret = context(args)
-- placement.centered (ret.wibox)
-- ret.wibox:set_valign("center")
-- ret.wibox:set_halign("center")
ret.wibox:set_shape (shape.rounded_rect, 10)
return ret
end
return setmetatable({}, { __call = function(_, ...) return new(...) end })

219
common.lua Normal file
View File

@ -0,0 +1,219 @@
-- Gather common code that can be shared between multiple sub-systems to widgets
local item_mod = require( "radical.item" )
local wibox = require( "wibox" )
local horizontal_item_layout = require( "radical.item.layout.horizontal" )
local margins2 = require( "radical.margins" )
local module = {}
local function left(data)
if data._current_item._tmp_menu then
data = data._current_item._tmp_menu
data.items[1].selected = true
return true,data
end
end
local function right(data)
if data.parent_geometry and data.parent_geometry.is_menu then
for k,v in ipairs(data.items) do
if v._tmp_menu == data or v.sub_menu_m == data then
v.selected = true
end
end
data.visible = false
data = data.parent_geometry
return true,data
else
return false
end
end
local function up(data)
if not data.previous_item then return end
data.previous_item.selected = true
end
local function down(data)
if not data.next_item then return end
data.next_item.selected = true
end
function module:setup_key_hooks(data)
data:add_key_hook({}, "Up" , "press", up )
data:add_key_hook({}, "Down" , "press", down )
data:add_key_hook({}, "Left" , "press", right )
data:add_key_hook({}, "Right" , "press", left )
end
function module.get_margins(data)
local internal = data._internal
if not internal._margins then
local ret = {
left = data.border_width+(data.default_margins.left
or (data.style and data.style.margins.LEFT ) or 0),
right = data.border_width+(data.default_margins.right
or (data.style and data.style.margins.RIGHT ) or 0),
top = data.border_width+(data.default_margins.top
or (data.style and data.style.margins.TOP ) or 0),
bottom = data.border_width+(data.default_margins.bottom
or (data.style and data.style.margins.BOTTOM ) or 0),
}
local m = margins2(internal.margin,ret)
rawset(m,"_reset",m.reset)
function m.reset(margins)
m.defaults = ret
m:_reset()
end
internal._margins = m
end
return internal._margins
end
function module.setup_buttons(data,item,args)
local buttons = {}
for i=1,10 do
if args["button"..i] then
buttons[i] = args["button"..i]
end
end
-- Setup sub_menu
if (item.sub_menu_m or item.sub_menu_f) and data.sub_menu_on >= item_mod.event.BUTTON1 and data.sub_menu_on <= item_mod.event.BUTTON3 then
item.widget:set_menu(item.sub_menu_m or item.sub_menu_f,"button::pressed",data.sub_menu_on)
end
--Hide on right click
if not buttons[3] then
buttons[3] = function()
data:hide()
end
end
-- Scroll up
if not buttons[4] then
buttons[4] = function()
data:scroll_up()
end
end
-- Scroll down
if not buttons[5] then
buttons[5] = function()
data:scroll_down()
end
end
item:connect_signal("button::release",function(_m,_i,button_id,mods,geo)
if #mods == 0 and buttons[button_id] then
buttons[button_id](_m,_i,mods,geo)
end
end)
end
function module.setup_item_move_events(data)
local internal = data._internal
local l = internal.content_layout or internal.layout
--SWAP / MOVE / REMOVE
data:connect_signal("item::swapped",function(_,item1,item2,index1,index2)
l:swap(index1, index2)
end)
data:connect_signal("item::moved",function(_,item,new_idx,old_idx)
local w = l:get_children()[old_idx]
l:remove(old_idx)
l:insert(new_idx, w)
end)
data:connect_signal("item::removed",function(_,item,old_idx)
l:remove(old_idx)
end)
data:connect_signal("item::appended",function(_,item)
l:add(item.widget)
end)
data:connect_signal("item::added", function(_,item)
l:add(item.widget)
end)
data:connect_signal("widget::added",function(_,item,widget)
wibox.layout.fixed.add(l,item.widget)
l:emit_signal("widget::updated")
end)
data:connect_signal("prefix_widget::added",function(_,widget,args)
data._internal.pref_l:insert(1,widget)
end)
data:connect_signal("suffix_widget::added",function(_,widget,args)
data._internal.suf_l:add(widget)
end)
data._internal.text_fit = function(self, context, width, height)
return width,height
end
data:connect_signal("clear::menu",function(_,vis)
l:reset()
end)
data:connect_signal("_hidden::changed",function(_,item)
item.widget:emit_signal("widget::updated")
end)
end
function module.setup_state_events(data, item)
if data.select_on == item_mod.event.HOVER then
item.widget:connect_signal("mouse::enter", function() item.selected = true end)
item.widget:connect_signal("mouse::leave", function() item.selected = false end)
else
item.widget:connect_signal("mouse::enter", function() item.hover = true end)
item.widget:connect_signal("mouse::leave", function() item.hover = false end)
end
end
function module.setup_item(data,item,args)
--Create the background
local item_layout = item.layout or data.item_layout or horizontal_item_layout
item_layout(item,data,args)
-- Layout
if data._internal.layout.setup_item then
data._internal.layout.setup_item(data._internal.layout,data,item,args)
end
-- Buttons
module.setup_buttons(data,item,args)
-- Tooltip
item.widget:set_tooltip(item.tooltip)
-- Set the correct item state
module.setup_state_events(data, item)
-- Setup tooltip
item.widget:set_tooltip(item.tooltip)
-- Apply item style
local item_style = item.item_style or data.item_style
item_style(item,{})
-- Enable scrollbar (if necessary)
if data._internal.scroll_w and data.rowcount > data.max_items then
data._internal.scroll_w.visible = true
data._internal.scroll_w["up"]:emit_signal("widget::updated")
data._internal.scroll_w["down"]:emit_signal("widget::updated")
end
item.widget:emit_signal("widget::updated")
end
return module

View File

@ -1,307 +1,81 @@
local base = require( "radical.base" )
local print = print
local unpack = unpack or table.unpack
local debug = debug
local rawset = rawset
local type = type
local unpack = unpack or table.unpack
local rawset = rawset
local setmetatable = setmetatable
local color = require( "gears.color" )
local wibox = require( "wibox" )
local beautiful = require( "beautiful" )
local cairo = require( "lgi" ).cairo
local awful = require( "awful" )
local util = require( "awful.util" )
local layout = require( "radical.layout" )
local checkbox = require( "radical.widgets.checkbox" )
local arrow_style = require( "radical.style.arrow" )
local item_mod = require("radical.item")
local glib = require("lgi").GLib
local margins2 = require("radical.margins")
local base = require( "radical.base" )
local wibox = require( "wibox" )
local beautiful = require( "beautiful" )
local layout = require( "radical.layout" )
local arrow_style = require( "radical.style.arrow" )
local smart_wibox = require( "radical.smart_wibox" )
local common = require( "radical.common" )
local capi,module = { mouse = mouse , screen = screen, keygrabber = keygrabber },{}
local capi, module = { keygrabber = keygrabber },{}
local function get_direction(data)
local parent_geometry = data.parent_geometry --Local cache to avoid always calling the object hooks
if not parent_geometry or not parent_geometry.drawable then return "bottom" end
local function set_visible(i, value)
local w, pg = i.w, i.private_data.parent_geometry
-- The border width is not included in the geometry
local bw = data.wibox.border_width or 0
local drawable_geom = parent_geometry.drawable.drawable.geometry(parent_geometry.drawable.drawable)
drawable_geom.x, drawable_geom.height = drawable_geom.x - bw, drawable_geom.height + 2*bw
if parent_geometry.y+parent_geometry.height < drawable_geom.height then --Vertical wibox
if drawable_geom.x >= capi.screen[capi.mouse.screen].geometry.width - (drawable_geom.x+drawable_geom.width) then
return "right"
else
return "left"
if value then
w:move_by_parent(pg, true)
end
else --Horizontal wibox
if drawable_geom.y >= capi.screen[capi.mouse.screen].geometry.height - (drawable_geom.y+drawable_geom.height) then
return "bottom"
else
return "top"
w.visible = value
if not value and (not pg or not pg.is_menu) then
capi.keygrabber.stop()
end
end
end
local function set_geometry_real(data)
local geo = data._internal._next_geometry
if geo then
for k,v in pairs(geo) do
data.wibox[k] = math.ceil(v)
end
end
data._internal._next_geometry = nil
data._internal._need_geometry_reload = nil
end
-- This will update the position before the next loop cycle
-- This avoid tons of round trips with X for dynamic menus
local function change_geometry_idle(data, x, y, w, h)
data._internal._next_geometry = data._internal._next_geometry or {}
local geo = data._internal._next_geometry
-- The border_width is not included in the geometry
geo.x = x and (x - data.wibox.border_width) or geo.x
geo.y = y and (y - data.wibox.border_width) or geo.y
geo.width = w and (w - data.wibox.border_width) or geo.width
geo.height = h and (h - data.wibox.border_width) or geo.height
if not data._internal._need_geometry_reload then
glib.idle_add(0, function() set_geometry_real(data) end)
data._internal._need_geometry_reload = true
end
end
local function set_position(self)
if not self.visible then return end
local ret,parent = {x=self.x,y=self.y},self.parent_geometry
local prefx,prefy = self._internal.private_data.x,self._internal.private_data.y
local src_geo = capi.screen[capi.mouse.screen].geometry
if parent and parent.is_menu then
if parent.direction == "right" then
ret={x=parent.x-self.width,y=parent.y+(self.parent_item.y)}
else
local margins = self.margins
ret={x=parent.x+parent.width,y=parent.y+(self.parent_item.y)-(margins and self.margins.top or data.default_margins.top or self.style.margins.TOP)}
--Handle when the menu doesn't fit in the srceen horizontally
if ret.x+self.width > src_geo.x + src_geo.width then
ret.x = parent.x - self.width
end
-- Handle when the menu doesn't fit on the screen vertically
if ret.y+self.height > src_geo.y + src_geo.height then
ret.y = ret.y - self.height + self.item_height + 2*(margins and self.margins.top or data.default_margins.top or self.style.margins.TOP)
end
end
elseif parent then
local drawable_geom = parent.drawable.drawable.geometry(parent.drawable.drawable)
if (self.direction == "left") or (self.direction == "right") then
ret = {
x=drawable_geom.x+((self.direction == "right") and - self.width or drawable_geom.width),
y=drawable_geom.y+parent.y--[[+((self.arrow_type ~= base.arrow_type.NONE) and parent.height/2-(self.arrow_x or 20)-6 or 0)]]
}
else
ret = {
x=drawable_geom.x+parent.x-((self.arrow_type ~= base.arrow_type.NONE) and (self.arrow_x or 20)+11-parent.width/2 or 0),
y=(self.direction == "bottom") and drawable_geom.y-self.height or drawable_geom.y+drawable_geom.height}
end
elseif prefx ~= 0 or prefy ~= 0 then
ret = capi.mouse.coords()
if prefx then
ret.x = prefx
end
if prefy then
ret.y = prefy
end
else --Use mouse position to set position --TODO it is called too often
ret = capi.mouse.coords()
local draw = awful.mouse.wibox_under_pointer and awful.mouse.wibox_under_pointer() or awful.mouse.drawin_under_pointer and awful.mouse.drawin_under_pointer()
if draw then
local geometry = draw.geometry(draw)
if self.direction == "top" or self.direction == "bottom" then
ret.x = ret.x - (self.arrow_x or 20) - 13
ret.y = geometry.y+geometry.height
if ret.y+self.height > src_geo.height then
self.direction = "bottom"
ret.y = geometry.y-self.height
end
end
end
end
--Handle when menu doesn't fit horizontally (if not handled earlier)
if ret.x+self.width > src_geo.x + src_geo.width then
ret.x = ret.x - (ret.x+self.width - (src_geo.x + src_geo.width))
elseif ret.x < 0 then
ret.x = 0
end
change_geometry_idle(self,ret.x,ret.y - 2*(self.wibox.border_width or 0))
end
local function setup_drawable(data)
local internal = data._internal
local private_data = internal.private_data
local internal = data._internal
--Init
internal.w = wibox({})
internal.margin = wibox.layout.margin()
if not data.layout then
data.layout = layout.vertical
end
internal.layout = data.layout(data)
internal.w.visible = false
internal.w.ontop = true
internal.margin:set_widget(internal.layout)
internal.w:set_widget(internal.margin)
internal.w:set_fg(data.fg)
internal.w.opacity = data.opacity
internal.w.border_color = data.border_color or beautiful.menu_outline_color or beautiful.menu_border_color or beautiful.fg_normal
internal.w:set_bg(data.bg)
-- Create the layout
data.layout = data.layout or layout.vertical
--Getters
data.get_wibox = function() return internal.w end
data.get_x = function() return data._internal._next_geometry and data._internal._next_geometry.x or internal.w.x end
data.get_y = function() return data._internal._next_geometry and data._internal._next_geometry.y or internal.w.y end
data.get_width = function() return data._internal._next_geometry and data._internal._next_geometry.width or internal.w.width end
data.get_height = function() return data._internal._next_geometry and data._internal._next_geometry.height or internal.w.height end
data.get_visible = function() return private_data.visible end
data.get_direction = function() return private_data.direction end
data.get_margins = function()
if not internal._margins then
local ret = {
left = data.border_width+(data.default_margins.left or data.style.margins.LEFT or 0),
right = data.border_width+(data.default_margins.right or data.style.margins.RIGHT or 0),
top = data.border_width+(data.default_margins.top or data.style.margins.TOP or 0),
bottom = data.border_width+(data.default_margins.bottom or data.style.margins.BOTTOM or 0),
}
local m = margins2(internal.margin,ret)
rawset(m,"_reset",m.reset)
m.reset = function(margins)
m.defaults = ret
m:_reset()
end
internal._margins = m
end
return internal._margins
end
internal.layout = data.layout(data)
internal.margin = wibox.layout.margin(internal.layout)
--Setters
data.set_direction = function(_,value)
if private_data.direction ~= value and (value == "top" or value == "bottom" or value == "left" or value == "right") then
private_data.direction = value
local fit_w,fit_h = internal.layout:fit()
data.height = fit_h
data.width = fit_w
end
end
data.set_x = function(_,value) change_geometry_idle(data,value) end
data.set_y = function(_,value) change_geometry_idle(data,nil,value) end
data.set_width = function(_,value)
local need_update = internal.w.width == (value + 2*data.border_width)
local margins = data.margins
change_geometry_idle(data,nil,nil,value + data.margins.left + data.margins.right)
if need_update then
data.style(data)
end
end
data.set_height = function(_,value)
local margins = data.margins
local need_update = (internal.w.height ~= (value + margins.top + margins.bottom))
local new_height = (value + margins.top + margins.bottom) or 1
change_geometry_idle(data,nil,nil,nil,new_height > 0 and new_height or 1)
if need_update then
data.style(data)
internal.set_position(data)
end
end
function internal:set_visible(value)
internal.w.visible = value
if not value and (not data.parent_geometry or not data.parent_geometry.is_menu) then
capi.keygrabber.stop()
end
end
-- Init
internal.w = smart_wibox(internal.margin, {
visible = false ,
ontop = true ,
opacity = data.opacity ,
bg = data.bg ,
fg = data.fg ,
preferred_positions = {"right", "bottom"},
border_color = data.border_color
or beautiful.menu_outline_color
or beautiful.menu_border_color
or beautiful.fg_normal
})
if data.visible then
local fit_w,fit_h = data._internal.layout:fit()
data.width = fit_w
data.height = fit_h
end
end
-- Accessors
data.get_wibox = function() return internal.w end
data.get_visible = function() return internal.private_data.visible end
data.get_margins = common.get_margins
internal.set_visible = set_visible
local function setup_buttons(data,item,args)
local buttons = {}
for i=1,10 do
if args["button"..i] then
buttons[i] = args["button"..i]
end
end
-- Click to open sub_menu
if not buttons[1] and data.sub_menu_on == base.event.BUTTON1 then
buttons[1] = function() item_mod.execute_sub_menu(data,item) end
end
--Hide on right click
if not buttons[3] then
buttons[3] = function()
data:hide()
end
end
-- Scroll up
if not buttons[4] then
buttons[4] = function()
data:scroll_up()
end
end
-- Scroll down
if not buttons[5] then
buttons[5] = function()
data:scroll_down()
end
end
item:connect_signal("button::release",function(_m,_i,button_id,mods,geo)
if #mods == 0 and buttons[button_id] then
buttons[button_id](_m,_i,mods,geo)
end
end)
end
local function setup_item(data,item,args)
-- Layout
local f = (data._internal.layout.setup_item) or (layout.vertical.setup_item)
f(data._internal.layout,data,item,args)
-- Buttons
setup_buttons(data,item,args)
-- Tooltip
item.widget:set_tooltip(item.tooltip)
-- Support remove, swap, insert, append...
common.setup_item_move_events(data)
end
local function new(args)
local args = args or {}
args.internal = args.internal or {}
args.internal.get_direction = args.internal.get_direction or get_direction
args.internal.set_position = args.internal.set_position or set_position
local args = args or {}
args.internal = args.internal or {}
args.internal.setup_drawable = args.internal.setup_drawable or setup_drawable
args.internal.setup_item = args.internal.setup_item or setup_item
args.style = args.style or beautiful.menu_default_style or arrow_style
args.internal.setup_item = args.internal.setup_item or common.setup_item
args.style = args.style or beautiful.menu_default_style or arrow_style
local ret = base(args)
ret:connect_signal("clear::menu",function(_,vis)
ret._internal.layout:reset()
end)
ret:connect_signal("_hidden::changed",function(_,item)
item.widget:emit_signal("widget::updated")
ret:connect_signal("parent_geometry::changed", function()
args.internal.w:move_by_parent(ret.parent_geometry, true)
end)
-- Init the style
args.style(ret)
return ret
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;
-- kate: space-indent on; indent-width 4; replace-tabs on;

519
dock.lua
View File

@ -1,421 +1,180 @@
local setmetatable,unpack,table = setmetatable,unpack,table
local setmetatable = setmetatable
local math = math
local base = require( "radical.base" )
local color = require( "gears.color" )
local wibox = require( "wibox" )
local beautiful = require( "beautiful" )
local cairo = require( "lgi" ).cairo
local awful = require( "awful" )
local util = require( "awful.util" )
local fkey = require( "radical.widgets.fkey" )
local button = require( "awful.button" )
local checkbox = require( "radical.widgets.checkbox" )
local vertical = require( "radical.layout.vertical" )
local horizontal = require( "radical.layout.horizontal" )
local item_layout= require( "radical.item.layout.icon" )
local item_style = require( "radical.item.style.rounded" )
local glib = require( "lgi" ).GLib
local margins2 = require("radical.margins" )
local base = require( "radical.base" )
local color = require( "gears.color" )
local wibox = require( "wibox" )
local beautiful = require( "beautiful" )
local vertical = require( "radical.layout.vertical" )
local horizontal = require( "radical.layout.horizontal" )
local item_layout = require( "radical.item.layout.icon" )
local item_style = require( "radical.item.style.rounded" )
local hot_corner = require( "radical.hot_corner" )
local shape = require( "gears.shape" )
local common = require( "radical.common" )
local smart_wibox = require( "radical.smart_wibox" )
local placement = require( "radical.placement" )
local default_radius = 10
local rad = beautiful.dock_corner_radius or default_radius
local default_shape = function(cr, width, height) shape.partially_rounded_rect(cr, width, height, false, true, true, false, rad) end
local capi,module = { mouse = mouse , screen = screen, keygrabber = keygrabber },{}
local max_size = {height={},width={}}
local default_radius = 10
local dir_to_deg = {left=0,bottom=math.pi/2,right=math.pi,top=3*(math.pi/2)}
local function get_direction(data)
local dir = data._internal.position or "left"
return dir,dir_to_deg[dir]--"left" -- Nothing to do
end
--No, screen 1 is not always at x=0, yet
local function get_first_screen()
for i=1,capi.screen.count() do
if capi.screen[i].geometry.x == 0 then
return i
end
end
end
------------------------------------
-- Drawing related code --
------------------------------------
local function rotate2(img, geometry, angle,swap_size)
geometry = swap_size and {width = geometry.height, height=geometry.width} or geometry
local matrix,pattern,img2 = cairo.Matrix(),cairo.Pattern.create_for_surface(img),cairo.ImageSurface(cairo.Format.ARGB32, geometry.width, geometry.height)
cairo.Matrix.init_rotate(matrix,angle)
matrix:translate((angle == math.pi/2) and 0 or -geometry.width, (angle == 3*(math.pi/2)) and 0 or -geometry.height)
pattern:set_matrix(matrix)
local cr2 = cairo.Context(img2)
cr2:set_source(pattern)
cr2:paint()
return img2
end
-- Draw the round corners
local function mask(rotate,width,height,radius,offset,anti,bg,fg)
local invert = (rotate ~= 0) and (rotate ~= math.pi)
local width,height = invert and height or width,invert and width or height
local img = cairo.ImageSurface.create(cairo.Format.ARGB32, width, height)
local cr = cairo.Context(img)
cr:set_operator(cairo.Operator.SOURCE)
cr:set_antialias(anti)
cr:rectangle(0, 0, width, height)
cr:set_source(bg)
cr:fill()
cr:set_source(fg)
cr:arc(width-radius-1-offset,radius+offset*2,radius,0,2*math.pi)
cr:arc(width-radius-1-offset,height-radius-2*offset,radius,0,2*math.pi)
cr:rectangle(0, offset, width-radius-1, height-2*offset)
cr:rectangle(width-radius-1-offset, radius+2*offset, radius, height-2*radius-2*offset)
cr:fill()
return rotate~=0 and rotate2(img,{width=width,height=height},rotate,true) or img
end
-- Do not draw over the boder, ever
local function dock_draw(self, context, cr, width, height)
local w = context.wibox
-- Generate the border surface
if not self.mask or self.mask_hash ~= width*1000+height then
local dir,rotation = get_direction(self.data)
self.mask = mask(rotation,w.width,w.height,(beautiful.dock_corner_radius or default_radius) - 2,1,0,color(self.data.border_color or seld.data.fg),color("#FF000000"))
self.mask_hash = width*1000+height
end
cr:save()
--Draw the border
--self.__draw(self, context, cr, width, height)
cr:set_source_surface(self.mask)
cr:paint()
cr:restore()
end
--Make sure the wibox is at the center of the screen
local function align_wibox(w,direction,screen)
local axis = (direction == "left" or direction == "right") and "height" or "width"
local offset = axis == "height" and "y" or "x"
local src_geom = capi.screen[screen].geometry
local scr_size = src_geom[offset] + (src_geom[axis] - w[axis]) /2
w[offset] = math.ceil(scr_size)
if direction == "left" then
w.x = math.ceil(src_geom.x)
elseif direction == "right" then
w.x = math.ceil(src_geom.x + src_geom.width - w.width)
elseif direction == "bottom" then
w.y = math.ceil(src_geom.y+src_geom.height-w.height)
else
w.y = math.ceil(src_geom.y)
end
end
-----------------------------------------
-- Size and position related code --
-----------------------------------------
-- Change the position, TODO
local function set_position(self,value)
self._internal.position = value
end
local function get_position(self,value)
return self._internal.position
end
-- Compute the optimal maxmimum size
local function get_max_size(data,screen)
local dir = get_direction(data)
local w_or_h = ((dir == "left" or dir == "right") and "height" or "width")
local x_or_y = w_or_h == "height" and "y" or "x"
local res = max_size[w_or_h][screen]
if not res then
local full,wa = capi.screen[screen].geometry[w_or_h],capi.screen[screen].workarea
local top,bottom = wa[x_or_y],full-(wa.y+wa[w_or_h])
local biggest = top > bottom and top or bottom
--res = full - biggest*2 - 52 -- 26px margins
res = wa[w_or_h] - 52 -- 26px margins
max_size[w_or_h][screen] = res
end
return res
local dir = "left"
local w_or_h = ((dir == "left" or dir == "right") and "height" or "width")
local x_or_y = w_or_h == "height" and "y" or "x"
local res = max_size[w_or_h][screen]
if not res then
local full,wa = capi.screen[screen].geometry[w_or_h],capi.screen[screen].workarea
local top,bottom = wa[x_or_y],full-(wa.y+wa[w_or_h])
local biggest = top > bottom and top or bottom
--res = full - biggest*2 - 52 -- 26px margins
local margin = beautiful.dock_margin or 52
res = wa[w_or_h] - margin
max_size[w_or_h][screen] = res
end
return res
end
-- local function get_size(data,screen)
-- local max = get_max_size(data,screen)
-- if data._internal.orientation == "vertical" and h > max then
--
-- elseif data._internal.orientation == "horizontal" and w > max then
--
-- end
-- return w,h,max
-- end
--TODO it still works, but rewrite this code anyway
-- The dock always have to be shorter than the screen
local function adapt_size(data,w,h,screen)
local max = get_max_size(data,screen)
local max = get_max_size(data,screen)
-- Get the current size, then compare and ajust
local fit_w,fit_h = data._internal.layout:get_preferred_size({dpi=96,force_values=true},beautiful.default_height)
-- Get the number of items minus the number of widgets
-- This can be used to approximate the number of pixel to remove
local visible_item = data.visible_row_count - #data._internal.widgets + 1
-- Get the current size, then compare and ajust
local fit_w,fit_h = data._internal.layout:get_preferred_size({dpi=96,force_values=true},beautiful.default_height)
if data._internal.orientation == "vertical" and h > max then
local wdg_height = data.widget_fit_height_sum
--TODO this assume the widget size wont change
-- data.item_height = math.ceil((data.item_height*max)/h) --OLD
data.item_height = math.ceil((max-wdg_height)/visible_item)
w = data.item_height
h = max
data.item_width = w
data.menu_width = w
w = w + data.margins.left+data.margins.right
data.default_width = w
data._internal.private_data.width = w
elseif data._internal.orientation == "horizontal" and w > max then
--TODO merge this with above
local wdg_width = data.widget_fit_width_sum
data.item_width = math.ceil((data.item_height*max)/w)
data._internal.private_data = data.item_width
w = max
h = data.item_width
data.item_height = h
data.menu_height = h
h = h + data.margins.bottom+data.margins.top
-- data.default_width = h
end
if data.icon_size and data.icon_size > w then
data.icon_size = w
end
data._internal._geom_vals = nil
-- Get the number of items minus the number of widgets
-- This can be used to approximate the number of pixel to remove
local visible_item = data.visible_row_count - #data._internal.widgets + 1
return w == 0 and 1 or w, h == 0 and 1 or h
local orientation = "vertical"
if orientation == "vertical" and h > max then
local wdg_height = data.widget_fit_height_sum
--TODO this assume the widget size wont change
-- data.item_height = math.ceil((data.item_height*max)/h) --OLD
data.item_height = math.ceil((max-wdg_height)/visible_item)
w = data.item_height
h = max
data.item_width = w
data.menu_width = w
w = w + data.margins.left+data.margins.right
data.default_width = w
data._internal.private_data.width = w
elseif orientation == "horizontal" and w > max then
--TODO merge this with above
local wdg_width = data.widget_fit_width_sum
data.item_width = math.ceil((data.item_height*max)/w)
data._internal.private_data = data.item_width
w = max
h = data.item_width
data.item_height = h
data.menu_height = h
h = h + data.margins.bottom+data.margins.top
-- data.default_width = h
end
if data.icon_size and data.icon_size > w then
data.icon_size = w
end
data._internal.margin:emit_signal("widget::layout_changed")
data._internal.margin:emit_signal("widget::redraw_needed")
return w == 0 and 1 or w, h == 0 and 1 or h
end
-- Create the auto hiding wibox
-- Create the main wibox (lazy-loading)
local function get_wibox(data, screen)
if data._internal.w then return data._internal.w end
data:emit_signal("dock::request")
if data._internal.w then return data._internal.w end
local dir,rotation = get_direction(data)
local geo_src = data._internal._geom_vals or data
data._internal.margin = wibox.layout.margin(data._internal.layout)
-- Need to be created befoce calling align_wibox
local m = wibox.layout.margin()
m:set_widget(data._internal.layout)
m:set_margins(0)
data._internal.mrgns.widget = m
local w = smart_wibox(data._internal.margin, {
screen = screen ,
ontop = true ,
shape = beautiful.dock_shape or default_shape ,
shape_border_width = 1 ,
shape_border_color = color(data.border_color or data.fg ),
bg = color(beautiful.bg_dock or beautiful.bg_normal),
})
-- Make sure the down will fit on the screen
geo_src.width,geo_src.height = adapt_size(data,geo_src.width,geo_src.height,screen)
data._internal.w = w
local w = wibox{ screen = screen, width = geo_src.width, height = geo_src.height,ontop=true}
align_wibox(w,dir,screen)
data:emit_signal("visible::changed",true)
w:set_widget(m)
data._internal.w = w
w:connect_signal("property::height",function()
adapt_size(data, w.width, w.height, 1)
end)
-- Create the rounded corner mask
w:set_bg(cairo.Pattern.create_for_surface(mask(rotation,w.width,w.height,(beautiful.dock_corner_radius or default_radius)-2,1,0,color(beautiful.fg_normal),color(beautiful.bg_dock or beautiful.bg_normal))))
w.shape_bounding = mask(rotation,w.width,w.height,beautiful.dock_corner_radius or default_radius,0,1,color("#00000000"),color("#FFFFFFFF"))._native
local function prop_change()
w:set_bg(cairo.Pattern.create_for_surface(mask(rotation,w.width,w.height,(beautiful.dock_corner_radius or default_radius) -2,1,0,color(beautiful.fg_normal),color(beautiful.bg_dock or beautiful.bg_normal))))
w.shape_bounding = mask(rotation,w.width,w.height,beautiful.dock_corner_radius or default_radius,0,1,color("#00000000"),color("#FFFFFFFF"))._native
end
w:connect_signal("property::height",prop_change)
w:connect_signal("property::width" ,prop_change)
-- Hide the dock when the mouse leave
w:connect_signal("mouse::leave",function()
if not (data._tmp_menu and data._tmp_menu.visible) then
data.visible = false
end
end)
data:emit_signal("visible::changed",true)
-- Bring back the placeholder when hiding
data:connect_signal("visible::changed",function(data,val)
if not val then
data._internal.placeholder.visible = true
end
end)
return w
end
-- Create the "hidden" wibox that display the first one on command
local function create_placeholder(data)
local screen,dir = data.screen or get_first_screen() or 1,get_direction(data)
local h_or_w = (dir == "left" or dir == "right") and "width" or "height"
local hw_invert = h_or_w == "height" and "width" or "height"
local placeholder = wibox{ screen = screen, [h_or_w] = 1,[hw_invert] = 1,bg="#00000000", ontop = true,visible=true }
placeholder:geometry({ [h_or_w] = 1, [hw_invert] = capi.screen[screen].geometry.height -100, x = dir == "right" and capi.screen[screen].geometry.width -1 or 0, y = capi.screen[screen].geometry.y + 50})
-- Raise of create the main dock wibox
placeholder:connect_signal("mouse::enter", function()
get_wibox(data,screen).visible = true
placeholder.visible = false
end)
-- Move the placeholder when the wibox is resized
data:connect_signal(((dir == "left" or dir == "right") and "height" or "width").."::changed",function()
-- placeholder[hw_invert] = data[hw_invert]
align_wibox(placeholder,dir,screen)
end)
data._internal.placeholder = placeholder
-- Adapt the size when new items are added
data:connect_signal("layout_size",function(_,w,h)
if not data._internal._has_changed then
glib.idle_add(glib.PRIORITY_DEFAULT_IDLE, function()
if not data._internal._geom_vals then return end
local w,h,internal = data._internal._geom_vals.width,data._internal._geom_vals.height,data._internal
if h == 0 then
h = 1
end
if w == 0 then
w = 1
end
-- Resize the placeholder
internal.placeholder[hw_invert] = (hw_invert == "height") and h or w
align_wibox(internal.placeholder,dir,screen)
-- Resize the dock wibox
if internal.w then
w,h=adapt_size(data,w,h,screen) --TODO place holder need to do
internal.w.height = h
internal.w.width = w
align_wibox(internal.w,dir,screen)
end
data._internal._has_changed = false
end)
end
data._internal._geom_vals = {height=h,width=w}
data._internal._has_changed = true
end)
placement.pin(w, placement.corner, "left", screen or 1)
return w
end
local function setup_drawable(data)
local internal = data._internal
local private_data = internal.private_data
local internal = data._internal
-- Create the layout
internal.layout = data.layout(data)
internal.layout.__draw = internal.layout.draw
internal.layout.draw = dock_draw
internal.layout.data = data
-- Create the layout
internal.layout = data.layout(data)
-- Getters
data.get_x = function() return 0 end
data.get_y = function() return 0 end
data.get_width = function()
return internal.w and internal.w.width or data._internal.layout:get_preferred_size({force_values=true})
end
data.get_height = function()
if internal.orientation == "horizontal" then
return beautiful.default_height
else
local w,h = internal.layout:get_preferred_size()
return h
-- Getters
data.get_visible = function() return true end
data.get_margins = common.get_margins
function data:set_visible(value)
if internal.w then
internal.w.visible = value or false
end
end
end
data.get_visible = function() return true end
data.get_direction = get_direction
local mrgns = margins2(nil,{})
data._internal.mrgns = mrgns
data.get_margins = function()
return data._internal.mrgns or {}
end
function data:set_visible(value)
if internal.w then
internal.w.visible = value or false
end
end
-- This widget do not use wibox, so setup correct widget interface
data.fit = internal.layout
data.draw = internal.layout
end
local function setup_item(data,item,args)
-- Add widgets
local f = (data._internal.layout.setup_item) or (vertical.setup_item)
f(data._internal.layout,data,item,args)
-- Buttons
local buttons = {}
for i=1,10 do
if args["button"..i] then
buttons[i] = args["button"..i]
end
end
item:connect_signal("button::release",function(_m,_i,button_id,mods,geo)
if #mods == 0 and buttons[button_id] then
buttons[button_id](_m,_i,mods,geo)
end
end)
-- Tooltip
item.widget:set_tooltip(item.tooltip)
common.setup_item_move_events(data)
end
local function new(args)
local args = args or {}
local orientation = (not args.position or args.position == "left" or args.position == "right") and "vertical" or "horizontal"
local length_inv = orientation == "vertical" and "width" or "height"
local args = args or {}
local orientation = (not args.position or args.position == "left" or args.position == "right") and "vertical" or "horizontal"
local length_inv = orientation == "vertical" and "width" or "height"
-- The the Radical arguments
args.internal = args.internal or {}
args.internal.orientation = orientation
args.internal.get_direction = args.internal.get_direction or get_direction
args.internal.set_position = args.internal.set_position or set_position
args.internal.setup_drawable = args.internal.setup_drawable or setup_drawable
args.internal.setup_item = args.internal.setup_item or setup_item
args.item_style = args.item_style or item_style
args.bg = color("#00000000") --Use the dock bg instead
args.item_height = 40
args.item_width = 40
args.sub_menu_on = args.sub_menu_on or base.event.BUTTON1
args.internal = args.internal or {}
args.internal.layout_func = orientation == "vertical" and vertical or horizontal
args.layout = args.layout or args.internal.layout_func
args.item_style = args.item_style or item.style
-- args.item_layout = args.item_layout or item_layout
args[length_inv] = args[length_inv] or 40
-- The the Radical arguments
args.internal = args.internal or {}
args.internal.orientation = orientation
args.internal.setup_drawable = args.internal.setup_drawable or setup_drawable
args.internal.setup_item = args.internal.setup_item or common.setup_item
args.item_style = args.item_style or item_style
args.bg = color("#00000000") --Use the dock bg instead
args.item_height = 40
args.item_width = 40
args.sub_menu_on = args.sub_menu_on or base.event.BUTTON1
args.internal = args.internal or {}
args.internal.layout_func = orientation == "vertical" and vertical or horizontal
args.layout = args.layout or args.internal.layout_func
args.item_style = args.item_style or item.style
-- args.item_layout = args.item_layout or item_layout
args[length_inv] = args[length_inv] or 40
-- Create the dock
local ret = base(args)
ret.set_position = set_position
ret.get_position = get_position
ret.position = args.position or "left"
ret.screen = args.screen
-- Create the dock
local ret = base(args)
ret.position = args.position or "left"
ret.screen = args.screen or 1
-- Add a 1px placeholder to trigger it
create_placeholder(ret)
-- Add a 1px placeholder to trigger it
if not beautiful.dock_always_show then
hot_corner.register_wibox(ret.position, function()
return get_wibox(ret, 1)
end, ret.screen, 1)
else
timer.delayed_call(function()
local w = get_wibox(ret, 1)
w.visible = true
end)
end
return ret
return ret
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;
-- kate: space-indent on; indent-width 4; replace-tabs on;

View File

@ -14,6 +14,7 @@ local button = require( "awful.button" )
local layout = require( "radical.layout" )
local checkbox = require( "radical.widgets.checkbox" )
local classic_style = require( "radical.style.classic" )
local common = require( "radical.common" )
local capi,module = { mouse = mouse , screen = screen , keygrabber = keygrabber },{}
@ -26,14 +27,11 @@ local function setup_drawable(data)
data.set_visible = function(_,v) if data._embeded_parent then data._embeded_parent.visible = v end end
-- Enumate geometry --BUG this is fake, but better than nothing
data.get_width = function() return data._embeded_parent and (data._embeded_parent.width)end
data.get_y = function() return data._embeded_parent and (data._embeded_parent.y) end
data.get_x = function() return data._embeded_parent and (data._embeded_parent.x) end
if not data.layout then
data.layout = layout.vertical
end
internal.layout = data.layout(data)
data.width,data.height = data._internal.layout:fit()
data.margins={left=0,right=0,bottom=0,top=0}
internal.layout:connect_signal("mouse::enter",function(_,geo)
if data._embeded_parent._current_item then
@ -47,49 +45,11 @@ local function setup_drawable(data)
end)
end
local function setup_item(data,item,args)
-- Create the layout
local f = (data._internal.layout.setup_item) or (layout.vertical.setup_item)
f(data._internal.layout,data,item,args)
local buttons = {}
for i=1,10 do
if args["button"..i] then
buttons[i] = args["button"..i]
end
end
if not buttons[3] then --Hide on right click
buttons[3] = function()
data.visible = false
if data.parent_geometry and data.parent_geometry.is_menu then
data.parent_geometry.visible = false
end
end
end
if not buttons[4] then
buttons[4] = function()
data:scroll_up()
end
end
if not buttons[5] then
buttons[5] = function()
data:scroll_down()
end
end
item:connect_signal("button::release",function(_m,_i,button_id,mods,geo)
if #mods == 0 and buttons[button_id] then
buttons[button_id](_m,_i,mods,geo)
end
end)
end
local function new(args)
local args = args or {}
args.internal = args.internal or {}
args.internal.setup_drawable = args.internal.setup_drawable or setup_drawable
args.internal.setup_item = args.internal.setup_item or setup_item
args.internal.setup_item = args.internal.setup_item or common.setup_item
args.style = args.style or classic_style
local ret = base(args)
ret:connect_signal("clear::menu",function(_,vis)

View File

@ -98,10 +98,6 @@ function module.screenshot(clients,geo)
end
if geo then
prev_menu.parent_geometry = geo
end
prev_menu.visible = true
return prev_menu
end

View File

@ -11,11 +11,32 @@ local function set_tooltip(self, text, args)
self._tooltip = tooltip(self,text, args)
end
local function set_menu(self,menu,button)
--- Set a menu for widget "self".
-- This function is available for all widgets.
-- Any signals can be used as trigger, the common ones are:
--
-- * "button::press" (default) Left mouse button (normal click_
-- * "mouse::enter" When the mouse enter the widget
--
-- @param self A widget (implicit parameter)
-- @param menu A radical menu or a function returning one (for lazy-loading)
-- @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)
if not menu then return end
local b = button or 1
local current,bt = self:buttons(),aw_button({},b,function(geo)
local event = event or "button::pressed"
local button_id = button_id or 1
local function trigger(_, geo)
local geo = geo or _
local m = menu
if self._data and self._data.is_menu then
geo.parent_menu = self._data
end
if type(menu) == "function" then
if self._tmp_menu and self._tmp_menu.visible then
self._tmp_menu.visible = false
@ -26,21 +47,19 @@ local function set_menu(self,menu,button)
end
if not m then return end
local dgeo = geo.drawable.drawable:geometry()
-- The geometry is a mix of the drawable and widget one
local geo2 = {
x = dgeo.x + geo.x,
y = dgeo.y + geo.y,
width = geo.width ,
height = geo.height ,
drawable = geo.drawable ,
}
m.parent_geometry = geo
m._internal.w:move_by_parent(geo)
m.parent_geometry = geo2
m.visible = not m.visible
end)
for k, v in pairs(bt) do
current[type(k) == "number" and (#current+1) or k] = v
end
if event == "button::pressed" then
local current,bt = self:buttons(),aw_button({},b,trigger)
for k, v in pairs(bt) do
current[type(k) == "number" and (#current+1) or k] = v
end
else
self:connect_signal(event, trigger)
end
self._menu = menu
return bt

View File

@ -66,20 +66,6 @@ local function load_async(tab,key)
return rawget(module,key)
end
function module.execute_sub_menu(data,item)
if (item._private_data.sub_menu_f or item._private_data.sub_menu_m) then
local sub_menu = item._private_data.sub_menu_m or item._private_data.sub_menu_f(data,item)
if sub_menu and (item._private_data.sub_menu_f or sub_menu.rowcount > 0) then
sub_menu.arrow_type = module.arrow_type.NONE
sub_menu.parent_item = item
sub_menu.parent_geometry = data
sub_menu.visible = true
item._tmp_menu = sub_menu
data._tmp_menu = sub_menu
end
end
end
local function hide_sub_menu(item,data)
if item._tmp_menu then
item._tmp_menu.visible = false
@ -212,15 +198,18 @@ local function new_item(data,args)
item.state[module.item_flags.SELECTED] = nil
return
end
-- Select the new one
if data.sub_menu_on == module.event.SELECTED and (current_item ~= item or not item._tmp_menu)then
module.execute_sub_menu(data,item)
end
item.state[module.item_flags.SELECTED] = true
data._current_item = item
end
item.set_icon = function (_,value)
local icon_w = item._internal.icon_w
if not icon_w then return end
icon_w:set_image(value)
end
-- Listen to signals
item:connect_signal("state::changed",function()
item:style()

View File

@ -1,7 +1,33 @@
local holo = require("radical.item.style.holo")
local rounded = require("radical.item.style.rounded" )
local theme = require( "radical.theme" )
local unpack = unpack or table.unpack
return {
-- Create a generic item_style
local function generic(_, args)
local args = args or {}
args.margins = args.margins or {
TOP = 0,
BOTTOM = 0,
RIGHT = 0,
LEFT = 0
}
local module = {
margins = args.margins
}
local function draw(item)
item.widget:set_shape(args.shape, unpack(args.shape_args or {}))
item.widget:set_shape_border_width(item.border_width)
theme.update_colors(item)
end
return setmetatable(module, { __call = function(_, ...) return draw(...) end })
end
return setmetatable({
basic = require("radical.item.style.basic" ),
classic = require("radical.item.style.classic" ),
subtle = require("radical.item.style.subtle" ),
@ -15,4 +41,4 @@ return {
arrow_3d = require("radical.item.style.arrow_3d" ),
slice_prefix = require("radical.item.style.slice_prefix" ),
line_3d = require("radical.item.style.line_3d" ),
}
}, {__call=generic})

View File

@ -5,101 +5,13 @@ local util = require( "awful.util" )
local button = require( "awful.button" )
local checkbox = require( "radical.widgets.checkbox" )
local wibox = require( "wibox" )
local common = require( "radical.common" )
local item_layout = require("radical.item.layout.icon")
local base = nil
local module = {}
local function left(data)
if data._current_item._tmp_menu then
data = data._current_item._tmp_menu
data.items.selected = true
return true,data
end
end
local function right(data)
if data.parent_geometry.is_menu then
for k,v in ipairs(data.items) do
if v._tmp_menu == data or v.sub_menu_m == data then
v.selected = true
end
end
data.visible = false
data = data.parent_geometry
return true,data
end
end
local function up(data)
data.previous_item.selected = true
end
local function down(data)
data.next_item.selected = true
end
function module:setup_key_hooks(data)
data:add_key_hook({}, "Up" , "press", up )
data:add_key_hook({}, "&" , "press", up )
data:add_key_hook({}, "Down" , "press", down )
data:add_key_hook({}, "KP_Enter", "press", down )
data:add_key_hook({}, "Left" , "press", left )
data:add_key_hook({}, "\"" , "press", left )
data:add_key_hook({}, "Right" , "press", right )
data:add_key_hook({}, "#" , "press", right )
end
local function setup_event(data,item,args)
--Event handling
if data.select_on == base.event.HOVER then
item.widget:connect_signal("mouse::enter", function() item.selected = true end)
item.widget:connect_signal("mouse::leave", function() item.selected = false end)
end
data._internal.layout:add(item)
local buttons = {}
for i=1,10 do
if args["button"..i] then
buttons[#buttons+1] = button({},i,args["button"..i])
end
end
if not buttons[3] then --Hide on right click
buttons[#buttons+1] = button({},3,function()
data.visible = false
if data.parent_geometry and data.parent_geometry.is_menu then
data.parent_geometry.visible = false
end
end)
end
--Be sure to always hide sub menus, even when data.visible is set manually
data:connect_signal("visible::changed",function(_,vis)
if data._tmp_menu and data.visible == false then
data._tmp_menu.visible = false
end
end)
data:connect_signal("parent_geometry::changed",function(_,vis)
local fit_w,fit_h = data._internal.layout:fit()
data.height = fit_h
if data.style then
data.style(data)
end
end)
item.widget:buttons( util.table.join(unpack(buttons)))
end
function module:setup_item(data,item,args)
local bg = item_layout(item,data,args)
-- Set size
local fit_w,fit_h = data._internal.layout:fit()
data.width = fit_w
data.height = fit_h
if data.style then
data.style(data)
end
local text_w = item._internal.text_w
local icon_w = item._internal.icon_w
-- Setup text
item.set_text = function (_,value)
@ -118,50 +30,29 @@ function module:setup_item(data,item,args)
end
end
end
item.set_icon = function (_,value)
icon_w:set_image(value)
end
item:set_text(item._private_data.text)
-- Setup tooltip
bg:set_tooltip(item.tooltip)
-- Set widget
item.widget = bg
data.item_style(item,{})
setup_event(data,item,args)
end
--Get preferred item geometry
local function item_fit(data,item,self, content, width, height)
if not data.visible then return 1,1 end
local w, h = item._private_data._fit(self,content,width,height) --TODO port to new context API
return data.item_width or 70, item._private_data.height or h
return data.item_width or 70, item._private_data.height or h --TODO broken
end
local function new(data)
if not base then
base = require( "radical.base" )
end
-- Define the item layout
local real_l = wibox.widget.base.make_widget_declarative {
spacing = data.spacing ,
item_fit = item_fit ,
setup_key_hooks = module.setup_key_hooks ,
setup_key_hooks = common.setup_key_hooks ,
setup_item = module.setup_item ,
layout = wibox.layout.fixed.horizontal,
}
data:connect_signal("widget::added",function(_,item,widget)
wibox.layout.fixed.add(real_l, item.widget)
real_l:emit_signal("widget::updated")
end)
function real_l:add(item)
return wibox.layout.fixed.add(self, item.widget)
end
-- Hack fit
local new_fit
new_fit = function(self,context,w,h,force_values) --TODO use the context instead of extra argument

View File

@ -4,194 +4,93 @@ local scroll = require( "radical.widgets.scroll" )
local filter = require( "radical.widgets.filter" )
local wibox = require( "wibox" )
local cairo = require( "lgi" ).cairo
local base = nil
local common = require( "radical.common" )
local horizontal_item_layout= require( "radical.item.layout.horizontal" )
local module = {}
local function left(data)
if data._current_item._tmp_menu then
data = data._current_item._tmp_menu
data.items[1].selected = true
return true,data
end
end
local function right(data)
if data.parent_geometry and data.parent_geometry.is_menu then
for k,v in ipairs(data.items) do
if v._tmp_menu == data or v.sub_menu_m == data then
v.selected = true
end
end
data.visible = false
data = data.parent_geometry
return true,data
else
return false
end
end
local function up(data)
data.previous_item.selected = true
end
local function down(data)
data.next_item.selected = true
end
function module:setup_key_hooks(data)
data:add_key_hook({}, "Up" , "press", up )
data:add_key_hook({}, "&" , "press", up ) -- Xephyr bug
data:add_key_hook({}, "Down" , "press", down )
data:add_key_hook({}, "KP_Enter", "press", down ) -- Xephyr bug
data:add_key_hook({}, "Left" , "press", right )
data:add_key_hook({}, "\"" , "press", right ) -- Xephyr bug
data:add_key_hook({}, "Right" , "press", left )
data:add_key_hook({}, "#" , "press", left ) -- Xephyr bug
end
--Get preferred item geometry
local function item_fit(data,item,self,context, width,height)
local w, h = 0,0--item._internal.cache_w or 1,item._internal.cache_h or 1
if data.visible then
w, h = item._private_data._fit({},self,context,width,height)
item._internal.pix_cache = {} --Clear the pimap cache
end
local w, h = 0,0--item._internal.cache_w or 1,item._internal.cache_h or 1
if data.visible then
w, h = item._private_data._fit({},self,context,width,height)
end
return w, item.height or h
return w, item.height or h
end
function module:setup_text(item,data,text_w)
local text_w = item._internal.text_w
function module.setup_text(item,data,text_w)
local text_w = item._internal.text_w
if not text_w then return end
text_w.draw = function(self,context, cr, width, height)
if item.underlay then
horizontal_item_layout.paint_underlay(data,item,cr,width,height)
text_w.draw = function(self,context, cr, width, height)
if item.underlay then
horizontal_item_layout.paint_underlay(data,item,cr,width,height)
end
wibox.widget.textbox.draw(self, context, cr, width, height)
end
wibox.widget.textbox.draw(self, context, cr, width, height)
end
text_w.fit = function(self,context,width,height) return width,height end
text_w.fit = function(self,context,width,height) return width,height end
item.set_text = function (_,value)
if data.disable_markup then
text_w:set_text(value)
else
text_w:set_markup(value)
item.set_text = function (_,value)
if data.disable_markup then
text_w:set_text(value)
else
text_w:set_markup(value)
end
item._private_data.text = value
end
item._private_data.text = value
end
item:set_text(item._private_data.text)
return text_w
item:set_text(item._private_data.text)
return text_w
end
function module:setup_item(data,item,args)
if not base then
base = require( "radical.base" )
end
--Create the background
local item_layout = item.layout or data.item_layout or horizontal_item_layout
item.widget = item_layout(item,data,args)
--Event handling
if data.select_on == base.event.HOVER then
item.widget:connect_signal("mouse::enter", function(_,geo)
item.y = geo.y
item.selected = true
end)
item.widget:connect_signal("mouse::leave", function()
item.selected = false
end)
else
item.widget:connect_signal("mouse::enter", function(_,geo)
item.y = geo.y
item.hover = true
end)
item.widget:connect_signal("mouse::leave", function()
item.hover = false
end)
end
data._internal.layout:add(item)
--Be sure to always hide sub menus, even when data.visible is set manually
data:connect_signal("visible::changed",function(_,vis)
if data._tmp_menu and data.visible == false then
data._tmp_menu.visible = false
item._private_data._fit = wibox.widget.background.fit
if item._internal.margin_w then
item._internal.margin_w.fit = function(...)
if (item.visible == false or item._filter_out == true or item._hidden == true) then
return 0,0
end
return item_fit(data,item,...)
end
end
end)
data:connect_signal("parent_geometry::changed",function(_,vis)
local fit_w,fit_h = data._internal.layout:fit()
data.height = fit_h
data.style(data)
end)
item._private_data._fit = wibox.widget.background.fit
if item._internal.margin_w then
item._internal.margin_w.fit = function(...)
if (item.visible == false or item._filter_out == true or item._hidden == true) then
return 0,0
end
return data._internal.layout.item_fit(data,item,...)
-- Text need to take as much space as possible, override default
module.setup_text(item,data)
-- Compute the minimum width
if data.auto_resize and item._internal.margin_w then
local fit_w = wibox.layout.margin.fit(item._internal.margin_w,{dpi=96},9999,9999)
local is_largest = item == data._internal.largest_item_w
if fit_w < 1000 and (not data._internal.largest_item_w_v or data._internal.largest_item_w_v < fit_w) then
data._internal.largest_item_w = item
data._internal.largest_item_w_v = fit_w
end
end
end
-- Text need to take as much space as possible, override default
module:setup_text(item,data)
-- Necessary for :set_position()
local fit_w,fit_h = data._internal.layout:fit()
data.width = fit_w
data.height = fit_h
-- Enable scrollbar if necessary
if data._internal.scroll_w and data.rowcount > data.max_items then
data._internal.scroll_w.visible = true
data._internal.scroll_w["up"]:emit_signal("widget::updated")
data._internal.scroll_w["down"]:emit_signal("widget::updated")
end
-- Setup tooltip
item.widget:set_tooltip(item.tooltip)
-- Apply item style
local item_style = item.item_style or data.item_style
item_style(item,{})
-- Compute the minimum width
if data.auto_resize and item._internal.margin_w then
local fit_w = wibox.layout.margin.fit(item._internal.margin_w,{},9999,9999)
local is_largest = item == data._internal.largest_item_w
if fit_w < 1000 and (not data._internal.largest_item_w_v or data._internal.largest_item_w_v < fit_w) then
data._internal.largest_item_w = item
data._internal.largest_item_w_v = fit_w
end
end
item.widget:emit_signal("widget::updated")
end
local function compute_geo(data,width,height,force_values)
local w = data.default_width
if data.auto_resize and data._internal.largest_item_w then
w = data._internal.largest_item_w_v > data.default_width and data._internal.largest_item_w_v or data.default_width
end
local w = data.default_width
if data.auto_resize and data._internal.largest_item_w then
w = data._internal.largest_item_w_v > data.default_width and data._internal.largest_item_w_v or data.default_width
end
local visblerow = data.visible_row_count
local visblerow = data.visible_row_count
local sw,sh = data._internal.suf_l:get_preferred_size()
local pw,ph = data._internal.pref_l:get_preferred_size()
if not data._internal.has_widget then
return w,(total and total > 0 and total or visblerow*data.item_height) + ph + sh
else
local sumh = data.widget_fit_height_sum
local h = (visblerow-#data._internal.widgets)*data.item_height + sumh
return w,h
end
local sw,sh = data._internal.suf_l:get_preferred_size()
local pw,ph = data._internal.pref_l:get_preferred_size()
if not data._internal.has_widget then
return w,(total and total > 0 and total or visblerow*data.item_height) + ph + sh
else
local sumh = data.widget_fit_height_sum
local h = (visblerow-#data._internal.widgets)*data.item_height + sumh
return w,h
end
end
local function new(data)
base = base or require( "radical.base" )
local function real_fit(self,context,o_w,o_h,force_values)
if not data.visible then return 1,1 end
@ -200,10 +99,6 @@ local function new(data)
return w,h
end
local function real_add(self, item)
return wibox.layout.fixed.add(data._internal.content_layout, item.widget)
end
-- Create the scroll widgets
if data.max_items then
data._internal.scroll_w = scroll(data)
@ -250,7 +145,7 @@ local function new(data)
-- Methods
item_fit = item_fit ,
setup_key_hooks = module.setup_key_hooks,
setup_key_hooks = common.setup_key_hooks,
setup_item = module.setup_item ,
}
@ -262,46 +157,9 @@ local function new(data)
-- Set the overloaded methods
real_l.fit = real_fit
real_l.add = real_add
local l = data._internal.content_layout
--SWAP / MOVE / REMOVE
data:connect_signal("item::swapped",function(_,item1,item2,index1,index2)
l:swap(index1, index2)
end)
data:connect_signal("item::moved",function(_,item,new_idx,old_idx)
local w = l:get_children()[old_idx]
l:remove(old_idx)
l:insert(new_idx, w)
end)
data:connect_signal("item::removed",function(_,item,old_idx)
l:remove(old_idx)
end)
data:connect_signal("item::appended",function(_,item)
l:add(item.widget)
end)
data:connect_signal("widget::added",function(_,item,widget)
wibox.layout.fixed.add(l,item.widget)
l:emit_signal("widget::updated")
end)
data:connect_signal("prefix_widget::added",function(_,widget,args)
data._internal.pref_l:insert(1,widget)
end)
data:connect_signal("suffix_widget::added",function(_,widget,args)
data._internal.suf_l:add(widget)
end)
data._internal.text_fit = function(self, context, width, height)
return width,height
end
return real_l
end

View File

@ -1,194 +1,140 @@
local setmetatable = setmetatable
local beautiful = require( "beautiful" )
local color = require( "gears.color" )
local surface = require( "gears.surface" )
local cairo = require( "lgi" ).cairo
local base = require( "radical.base" )
local glib = require("lgi").GLib
local shape = require( "gears.shape" )
local unpack = unpack or table.unpack
local beautiful = require( "beautiful" )
local color = require( "gears.color" )
local cairo = require( "lgi" ).cairo
local base = require( "radical.base" )
local shape = require( "gears.shape" )
local module = {
margins = {
BOTTOM = 10,
TOP = 10,
LEFT = 0 ,
RIGHT = 0 ,
}
margins = {
BOTTOM = 10,
TOP = 10,
LEFT = 0 ,
RIGHT = 0 ,
}
}
-- Matrix rotation per direction
local angles = {
top = math.pi , -- 180
bottom = 0 , -- 0
left = math.pi/2 , -- 90
right = 3*math.pi/2 , -- 270
}
-- If width and height need to be swapped
local swaps = {
top = false,
bottom= false,
right = true ,
left = true ,
}
local invert = {
top = "bottom",
bottom = "top" ,
right = "left" , --FIXME this is wrong
left = "right" , --FIXME this is wrong
}
-- Constants
local radius = 10
local arrow_height = 13
-- Matrix rotation per direction
local angles = {
top = 0 , -- 0
bottom = math.pi , -- 180
left = 3*math.pi/2 , -- 270
right = math.pi/2 , -- 90
}
-- If width and height need to be swapped
local swaps = {
top = false,
bottom= false,
right = true ,
left = true ,
}
-- Generate the arrow position
local function gen_arrow_x(data,direction)
local at = data.arrow_type
local par_center_x = data.parent_geometry and (data.parent_geometry.x + data.parent_geometry.width/2) or -1
local menu_beg_x = data.x
local function gen_arrow_x(data,direction, width, height)
local at = data.arrow_type
local par_center_x = data.wibox.width/2
if at == base.arrow_type.PRETTY or not at then
if direction == "left" then
data._arrow_x = data._internal.w.height -20 - (data.arrow_x_orig or 20)
elseif direction == "right" then
--TODO
elseif direction == "bottom" then
data._arrow_x = data.width -20 - (data.arrow_x_orig or 20)
if par_center_x >= menu_beg_x then
data._arrow_x = data.width - (par_center_x - menu_beg_x) - arrow_height
end
elseif direction == "top" then
--TODO
if at == base.arrow_type.PRETTY or not at then
if direction == "left" then
data._arrow_x = data._internal.w.height -20 - (data.arrow_x_orig or 20)
elseif direction == "right" then
--TODO
elseif direction == "bottom" then
data._arrow_x = width -20 - (data.arrow_x_orig or 20)
if par_center_x >= 0 then
data._arrow_x = width - (par_center_x - 0) - arrow_height
end
elseif direction == "top" then
--TODO
end
elseif at == base.arrow_type.CENTERED then
if direction == "left" or direction == "right" then
data._arrow_x = height/2 - arrow_height
else
data._arrow_x = width/2 - arrow_height
end
end
elseif at == base.arrow_type.CENTERED then
if direction == "left" or direction == "right" then
data._arrow_x = data.height/2 - arrow_height
else
data._arrow_x = data.width/2 - arrow_height
end
local function update_margins(data, pos)
-- Set the margins correctly
if data._internal.margin then
data.margins.left = module.margins.LEFT
data.margins.right = module.margins.RIGHT
data.margins.top = module.margins.TOP
data.margins.bottom = module.margins.BOTTOM
-- Add enough room for the arrow
if pos and data.arrow_type ~= base.arrow_type.NONE then
data.margins[invert[pos]] = data.margins[invert[pos]] + arrow_height
end
end
end
end
-- Generate a rounded cairo path with the arrow
local function draw_roundedrect_path(cr, width, height, radius, data, padding, angle, swap_size)
local no_arrow = data.arrow_type == base.arrow_type.NONE
local padding = padding or 0
local arrow_offset = no_arrow and 0 or padding/2
local width, height = width - 2*padding - (swap_size and arrow_offset or 0), height - 2*padding - (swap_size and 0 or arrow_offset)
if swap_size then
width, height = height - arrow_offset, width
end
-- Use rounded rext for sub-menu and
local s = shape.transform(no_arrow and shape.rounded_rect or shape.infobubble)
-- Apply transformations
s = s : rotate_at(width / 2, height / 2, angle)
if padding > 0 then
s = s : translate(padding + (swap_size and arrow_offset or 0), padding + (angle == 0 and arrow_offset or 0))
end
-- Avoid a race condition
if (not data._arrow_x) and (not no_arrow) then
gen_arrow_x(data, data.direction)
end
-- the (swap_size and 2 or 1) indicate a bug elsewhere
s(cr, width, height, radius, arrow_height - arrow_offset, (data._arrow_x or 20) - arrow_offset*(swap_size and 2 or 1))
end
local function _set_direction(data,direction)
local hash = data.height*1000 + data.width
-- Try not to waste time for nothing
if data._internal._last_direction == direction..(hash) then return end
-- Avoid recomputing the arrow_x value
if not data._arrow_x or data._internal.last_size ~= hash then
gen_arrow_x(data,direction)
data._internal.last_size = hash
end
local angle, swap = angles[direction],swaps[direction]
data._internal._need_direction_reload = false
data._internal._last_direction = direction..(hash)
surface.apply_shape_bounding(data.wibox, draw_roundedrect_path, radius, data, 0, angle, swap)
-- surface.apply_shape_clip (data.wibox, draw_roundedrect_path, radius, data, data.border_width, angle, swap)
end
-- Try to avoid useless repaint, this function is heavy
local function set_direction(data,direction)
data._internal._need_direction = direction
if not data._internal._need_direction_reload and data._internal._last_direction ~= direction..(data.height*1000+data.width) then
glib.idle_add(glib.PRIORITY_HIGH_IDLE, function() _set_direction(data,data._internal._need_direction) end)
data._internal._need_direction_reload = true
end
-- Margins need to set manually, a reset will override user changes
if data.arrow_type ~= base.arrow_type.NONE and (not (data.parent_geometry and data.parent_geometry.is_menu)) and data._internal.former_direction ~= direction then
if data._internal.former_direction then
data.margins[data._internal.former_direction] = data.border_width + module.margins[data._internal.former_direction:upper()]
local function draw_roundedrect_path(cr, width, height, radius, data, position)
if data.arrow_type == base.arrow_type.NONE then
return shape.rounded_rect(cr, width, height, radius)
end
data.margins[direction] = arrow_height + 2*data.border_width
end
data._internal.former_direction = direction
local angle, swap = angles[position], swaps[position]
-- Invert width and height to avoid distortion
if swap then
width, height = height, width
end
-- Use rounded rext for sub-menu and
local s = shape.transform(shape.infobubble)
-- Apply transformations
s = s : rotate_at(width / 2, height / 2, angle)
-- Decide where the arrow will be
gen_arrow_x(data, data.direction, width, height)
-- Forward to the real shape
local ax = swap and width - (data._arrow_x or 20) or (data._arrow_x or 20)
s(cr, width, height, radius, arrow_height, ax)
end
local function get_arrow_x(data)
local height,width = data.height,data.width
local hash = height*1000+width
if not data._arrow_x or data._internal.last_size ~= hash then
gen_arrow_x(data,direction)
data._internal.last_size = hash
end
return data._arrow_x
end
-- As the menus have a rounded border, rectangle elements will draw over the
-- corner border. To fix this, this method re-draw the border on top of the
-- content
local function after_draw_children(self, context, cr, width, height)
local data = self._data
local dir = data.direction
local angle, swap = angles[dir], swaps[dir]
cr:translate(data.border_width/2,data.border_width/2)
-- Generate the path
draw_roundedrect_path(cr, width, height, beautiful.menu_corner_radius or radius, data, data.border_width/2, angle, swap)
cr:set_source(color(beautiful.menu_outline_color or beautiful.menu_border_color or beautiful.fg_normal))
cr:set_line_width(data.border_width)
cr:stroke()
end
local function draw(data,args)
local args = args or {}
local direction = data.direction or "top"
if not data.get_arrow_x then
rawset(data,"arrow_x_orig",data.arrow_x)
rawset(data,"arrow_x_orig",nil)
data.get_arrow_x = get_arrow_x
-- Prevent sharp corners from being over the border
if data._internal.margin then
data._internal.margin.__draw = data._internal.margin.draw
local args = args or {}
--TODO eventually restart work on upstreaming this, for now it pull too
-- much trouble along with it
data._internal.margin._data = data
data._internal.margin.after_draw_children = after_draw_children
if not data._internal.arrow_setup then
data._internal.w:set_shape_border_width(data.border_width or 1)
data._internal.w:set_shape_border_color(color(beautiful.menu_outline_color or beautiful.menu_border_color or beautiful.fg_normal))
data._internal.w:set_shape(data.shape or shape.infobubble, unpack(data.shape_args or {}))
if not data._internal.margin._data then
data._internal.margin._data = data
end
data._internal.w:connect_signal("property::position", function(_, pos)
data._internal.w:set_shape(function(cr, w, h) draw_roundedrect_path(cr, w, h, radius, data, pos) end)
update_margins(data, pos)
end)
local pos = data._internal.w.position
if pos then
data._internal.w:set_shape(function(cr, w, h) draw_roundedrect_path(cr, w, h, radius, data, data._internal.w.position) end)
end
update_margins(data, pos)
data._internal.arrow_setup = true
end
end
set_direction(data,direction)
--TODO call this less often
return w,w2
end
return setmetatable(module, { __call = function(_, ...) return draw(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;
-- kate: space-indent on; indent-width 4; replace-tabs on;