diff --git a/bar.lua b/bar.lua index 437cf2e..794e5e2 100644 --- a/bar.lua +++ b/bar.lua @@ -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; diff --git a/base.lua b/base.lua index 62532ab..e6e4203 100644 --- a/base.lua +++ b/base.lua @@ -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) diff --git a/box.lua b/box.lua index edbafb2..3f7fe62 100644 --- a/box.lua +++ b/box.lua @@ -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 }) diff --git a/common.lua b/common.lua new file mode 100644 index 0000000..ede8c61 --- /dev/null +++ b/common.lua @@ -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 \ No newline at end of file diff --git a/context.lua b/context.lua index 1f48a14..82b44e0 100644 --- a/context.lua +++ b/context.lua @@ -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; diff --git a/dock.lua b/dock.lua index 9c82a48..a54a76a 100644 --- a/dock.lua +++ b/dock.lua @@ -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; diff --git a/embed.lua b/embed.lua index 4c43ea0..d0d3a03 100644 --- a/embed.lua +++ b/embed.lua @@ -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) diff --git a/impl/common/client.lua b/impl/common/client.lua index e485119..bef44cb 100644 --- a/impl/common/client.lua +++ b/impl/common/client.lua @@ -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 diff --git a/init.lua b/init.lua index 04cd497..c73bf04 100644 --- a/init.lua +++ b/init.lua @@ -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 diff --git a/item/init.lua b/item/init.lua index a660ca1..b4b7c4d 100644 --- a/item/init.lua +++ b/item/init.lua @@ -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() diff --git a/item/style/init.lua b/item/style/init.lua index b087b73..49b18a8 100644 --- a/item/style/init.lua +++ b/item/style/init.lua @@ -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}) diff --git a/layout/horizontal.lua b/layout/horizontal.lua index 5aea297..3ed9c57 100644 --- a/layout/horizontal.lua +++ b/layout/horizontal.lua @@ -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 diff --git a/layout/vertical.lua b/layout/vertical.lua index ce22392..3d0afa6 100644 --- a/layout/vertical.lua +++ b/layout/vertical.lua @@ -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 diff --git a/style/arrow.lua b/style/arrow.lua index 497337f..58e2665 100644 --- a/style/arrow.lua +++ b/style/arrow.lua @@ -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;