First commit

This is Radical, a new menu implementation for AwesomeWM. It make it easy to abstract look and behavior or various types of menu with object oriented abstractions. While slower, this make it easier to maintain and extend.
This commit is contained in:
Emmanuel Lepage Vallee 2013-05-11 15:02:42 -04:00
commit 88d7ad2ff1
17 changed files with 1178 additions and 0 deletions

319
base.lua Normal file
View File

@ -0,0 +1,319 @@
local setmetatable = setmetatable
local pairs,ipairs = pairs, ipairs
local type,string = type,string
local print,unpack = print, unpack
local beautiful = require( "beautiful" )
local util = require( "awful.util" )
local object = require( "radical.object" )
local item_style = require( "radical.item_style" )
local capi = { mouse = mouse, screen = screen , keygrabber = keygrabber }
local module = {
arrow_type = {
NONE = 0,
PRETTY = 1,
CENTERED = 2,
}}
function filter(data)
local fs,visible_counter = data.filter_string:lower(),0
for k,v in pairs(data.items) do
local tmp = v[1]._filter_out
v[1]._filter_out = (v[1].text:lower():find(fs) == nil)-- or (fs ~= "")
if tmp ~= v[1]._filter_out then
v[1].widget:emit_signal("widget::updated")
end
if not v[1]._filter_out then
visible_counter = visible_counter + v[1].height
end
end
data._total_item_height = visible_counter
local w,h = data._internal.layout:fit()
data.height = h
end
------------------------------------KEYBOARD HANDLING-----------------------------------
local function activateKeyboard(data)
if not data then return end
if not data or grabKeyboard == true then return end
if (not (data.keyboardnav == false)) and data.visible == true then
capi.keygrabber.run(function(mod, key, event)
for k,v in pairs(data._internal.filter_hooks or {}) do --TODO modkeys
if k.key == "Mod4" and (key == "End" or key == "Super_L") then
local found = false
for k3,v3 in ipairs(mod) do
if v3 == "Mod4" and event == k.event then
local retval,self = v(data,mod)
if self and type(self) == "table" then
data = self
end
end
end
end
if k.key == key and k.event == event then
local retval, self = v(data,mod)
if self and type(self) == "table" then
data = self
end
return retval
end
end
if event == "release" then
return true
end
if (key == 'Return') and data._current_item and data._current_item.button1 then
data._current_item.button1()
data.visible = false
elseif key == 'Escape' or (key == 'Tab' and data.filter_string == "") then
data.visible = false
capi.keygrabber.stop()
elseif (key == 'BackSpace') and data.filter_string ~= "" and data.filter == true then
data.filter_string = data.filter_string:sub(1,-2)
filter(data)
-- data:filter(data.filter_string:lower())
-- if getFilterWidget() ~= nil then
-- getFilterWidget().textbox:set_markup(getFilterWidget().textbox._layout.text:sub(1,-2))
-- end
elseif data.filter == true and key:len() == 1 then
data.filter_string = data.filter_string .. key:lower()
-- local fw = getFilterWidget()
-- if fw ~= nil then
-- fw.textbox:set_markup(fw.textbox._layout.text .. key:lower())
-- if data.settings.autoresize and fw.textbox._layout:get_pixel_extents().width > data.settings.itemWidth then
-- data.settings.itemWidth = fw.textbox._layout:get_pixel_extents().width + 40
-- data.hasChanged = true
-- data:set_coords()
-- end
-- end
filter(data)
else
data.visible = false
capi.keygrabber.stop()
end
return true
end)
end
end
---------------------------------ITEM HANDLING----------------------------------
function add_item(data,args)
local args = args or {}
local item,set_map,get_map,private_data = object({
private_data = {
text = args.text or "" ,
height = args.height or beautiful.menu_height or 30 ,
icon = args.icon or nil ,
prefix = args.prefix or "" ,
suffix = args.suffix or "" ,
bg = args.bg or nil ,
fg = args.fg or data.fg or beautiful.menu_fg_normal or beautiful.fg_normal,
fg_focus = args.fg_focus or data.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus ,
bg_focus = args.bg_focus or data.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus ,
sub_menu_m = (args.sub_menu and type(args.sub_menu) == "table" and args.sub_menu.is_menu) and args.sub_menu or nil,
sub_menu_f = (args.sub_menu and type(args.sub_menu) == "function") and args.sub_menu or nil,
selected = false,
},
force_private = {
visible = true,
selected = true,
},
get_map = {
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
},
autogen_getmap = true,
autogen_setmap = true,
autogen_signals = true,
})
item._private_data = private_data
for i=1,10 do
item["button"..i] = args["button"..i]
end
set_map.selected = function(value)
private_data.selected = value
if value == false then
data.item_style(data,item,false,false)
return
end
if data._current_item and data._current_item ~= item then
if data._current_item._tmp_menu then
data._current_item._tmp_menu.visible = false
data._current_item._tmp_menu = nil
data._tmp_menu = nil
end
data._current_item.selected = false
end
if (private_data.sub_menu_f or private_data.sub_menu_m)and data._current_item ~= item then
local sub_menu = private_data.sub_menu_m or private_data.sub_menu_f()
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
data.item_style(data,item,true,false)
data._current_item = item
end
data._internal.items[#data._internal.items+1] = {}
data._internal.items[#data._internal.items][1] = item
data._internal.setup_item(data,item,args)
if args.selected == true then
item.selected = true
end
return item
end
---------------------------------MENU HANDLING----------------------------------
local function new(args)
local internal,args = args.internal or {},args or {}
if not internal.items then internal.items = {} end
-- All the magic in the universe
local data,set_map,get_map,private_data = object({
private_data = {
-- Default settings
bg = args.bg or beautiful.menu_bg_normal or beautiful.bg_normal or "#000000",
fg = args.fg or beautiful.menu_fg_normal or beautiful.fg_normal or "#ffffff",
bg_focus = args.bg_focus or beautiful.menu_bg_focus or beautiful.bg_focus or "#ffffff",
fg_forcus = args.fg_focus or beautiful.menu_fg_focus or beautiful.fg_focus or "#000000",
border_color = args.border_color or beautiful.menu_border_color or beautiful.border_color or "#333333",
border_width = args.border_width or beautiful.menu_border_width or beautiful.border_width or 3,
item_height = args.item_height or beautiful.menu_height or 30,
width = args.width or beautiful.menu_width or 130,
default_width = args.width or beautiful.menu_width or 130,
auto_resize = args.auto_resize or true,
parent_geometry = args.parent or nil,
arrow_type = args.arrow_type or beautiful.menu_arrow_type or module.arrow_type.PRETTY,
visible = args.visible or false,
direction = args.direction or "top",
has_changed = false,
row = args.row or nil,
column = args.column or nil,
layout = args.layout or nil,
screen = args.screen or nil,
style = args.style or nil,
item_style = args.item_style or item_style.basic,
filter = args.filter or true,
show_filter = args.show_filter or false,
filter_string = args.filter_string or "",
suffix_widget = args.suffix_widget or nil,
prefix_widget = args.prefix_widget or nil,
fkeys_prefix = args.fkeys_prefix or false,
},
get_map = {
is_menu = function() return true end,
margin = function() return {left=0,bottom=0,right=0,left=0} end,
items = function() return internal.items end,
rowcount = function() return #internal.items end,
columncount = function() return (#internal.items > 0) and #(internal.items[1]) or 0 end,
},
set_map = {
auto_resize = function(val) private_data[""] = val end,
},
force_private = {
parent = true,
visible = true,
},
always_handle = {
width = true,
height = true,
},
autogen_getmap = true,
autogen_setmap = true,
autogen_signals = true,
})
internal.get_map,internal.set_map,internal.private_data = get_map,set_map,private_data
data.add_item,data._internal = add_item,internal
set_map.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
end
set_map.visible = function(value)
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
internal.set_position(data)
end
if internal.set_visible then
internal:set_visible(value)
end
private_data.visible = value
if value and not capi.keygrabber.isrunning() then
activateKeyboard(data)
elseif data.parent_geometry and not data.parent_geometry.is_menu then
capi.keygrabber.stop()
end
end
set_map.layout = function(value)
if value then
value:setup_key_hooks(data)
end
private_data.layout = value
end
-- set_map.auto_resize = function(value)
-- for k,v in ipairs(internal.items) do
-- TODO check all items size, ajustthe fit and global width
-- end
-- end
get_map.current_index = function()
if data._current_item then
for k,v in ipairs(internal.items) do --rows
for k2,v2 in ipairs(v) do --columns
if data._current_item == v2 then
return k,k2 --row, column as row is expected in most configurations
end
end
end
end
end
get_map.previous_item = function() return (internal.items[(data.current_index or 0)-1] or internal.items[data.rowcount])[1] end
get_map.next_item = function() return ((internal.items[(data.current_index or 0)+1]or{})[1] or internal.items[1][1]) end
--Repaint when appearance properties change
for k,v in ipairs({"bg","fg","border_color","border_width","item_height","width","arrow_type"}) do
data:connect_signal(v.."::changed",function()
if data.visible and data.style then
-- data.style(data,{arrow_x=20,margin=internal.margin})
else
data.has_changed = true
end
end)
end
function data:add_key_hook(mod, key, event, func)
if key and event and func then
internal.filter_hooks = internal.filter_hooks or {}
internal.filter_hooks[{key = key, event = event, mod = mod}] = func
end
end
if private_data.layout then
private_data.layout:setup_key_hooks(data)
end
data._internal.setup_drawable(data)
return data
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

23
box.lua Normal file
View File

@ -0,0 +1,23 @@
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
local geom = capi.screen[s].geometry
data.wibox.x = geom.x + (geom.width/2) - data.width/2
data.wibox.y = geom.y + (geom.height/2) - data.height/2
end
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
local ret = context(args)
return ret
end
return setmetatable({}, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

248
context.lua Normal file
View File

@ -0,0 +1,248 @@
local base = require( "radical.base" )
local print = print
local unpack = unpack
local debug = debug
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 button = require( "awful.button" )
local layout = require( "radical.layout" )
local arrow_style = require( "radical.style.arrow" )
local capi,module = { mouse = mouse , screen = screen , 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 drawable_geom = parent_geometry.drawable.drawable.geometry(parent_geometry.drawable.drawable)
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"
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"
end
end
end
local function set_position(self)
local ret,parent = {x=self.wibox.x,y=self.wibox.y},self.parent_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
ret={x=parent.x+parent.width,y=parent.y+(self.parent_item.y)}
if ret.y+self.height > capi.screen[capi.mouse.screen].geometry.height then
ret.y = ret.y - self.height + self.item_height
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.wibox.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.wibox.height or drawable_geom.y+drawable_geom.height}
end
elseif not self.parent_geometry then --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 > capi.screen[capi.mouse.screen].geometry.height then
self.direction = "bottom"
ret.y = geometry.y-self.height
end
end
end
end
self.wibox.x = ret.x
self.wibox.y = ret.y - 2*(self.wibox.border_width or 0)
end
local function setup_drawable(data)
local internal = data._internal
local get_map,set_map,private_data = internal.get_map,internal.set_map,internal.private_data
--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)
--Getters
get_map.wibox = function() return internal.w end
get_map.x = function() return internal.w.x end
get_map.y = function() return internal.w.y end
get_map.width = function() return internal.w.width end
get_map.height = function() return internal.w.height end
get_map.visible = function() return internal.w.visible end
get_map.direction = function() return private_data.direction end
get_map.margins = function()
local ret = {left=data.border_width,right=data.border_width,top=data.style.margins.TOP,bottom=data.style.margins.BOTTOM}
if data.arrow_type ~= base.arrow_type.NONE then
ret[data.direction] = ret[data.direction]+13
end
return ret
end
--Setters
set_map.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
set_map.x = function(value) internal.w.x = value end
set_map.y = function(value) internal.w.y = value end
set_map.width = function(value)
local need_update = internal.w.width == (value + 2*data.border_width)
local margins = data.margins
internal.w.width = value + data.margins.left + data.margins.right
if need_update then
data.style(data)
end
end
set_map.height = function(value)
local margins = data.margins
local need_update = (internal.w.height ~= (value + margins.top + margins.bottom))
internal.w.height = value + margins.top + margins.bottom
if need_update then
data.style(data)
internal.set_position(data)
end
end
function internal:set_visible(value)
internal.w.visible = value
end
end
local function setup_item(data,item,args)
--Create the background
item.widget = wibox.widget.background()
data.item_style(data,item,false,false)
item.widget:set_fg(item._private_data.fg)
--Event handling
item.widget:connect_signal("mouse::enter", function() item.selected = true end)
item.widget:connect_signal("mouse::leave", function() item.selected = false 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)
item.widget:buttons( util.table.join(unpack(buttons)))
--Create the main item layout
local l,la,lr = wibox.layout.fixed.horizontal(),wibox.layout.align.horizontal(),wibox.layout.fixed.horizontal()
local m = wibox.layout.margin(la)
m:set_margins (0)
m:set_left ( data.item_style.margins.LEFT )
m:set_right ( data.item_style.margins.RIGHT )
m:set_top ( data.item_style.margins.TOP )
m:set_bottom( data.item_style.margins.BOTTOM )
local text_w = wibox.widget.textbox()
item._private_data._fit = wibox.widget.background.fit
m.fit = function(...)
if item.visible == false or item._filter_out == true then
return 0,0
end
return data._internal.layout.item_fit(data,item,...)
end
if data.fkeys_prefix == true then
local pref = wibox.widget.textbox()
pref.draw = function(self,w, cr, width, height)
cr:set_source(color(beautiful.fg_normal))
cr:paint()
wibox.widget.textbox.draw(self,w, cr, width, height)
end
pref:set_markup("<span fgcolor='".. beautiful.bg_normal .."'><tt><b>F11</b></tt></span>")
l:add(pref)
m:set_left ( 0 )
end
if args.prefix_widget then
l:add(args.prefix_widget)
end
if args.icon then
local icon = wibox.widget.imagebox()
icon:set_image(args.icon)
l:add(icon)
end
text_w:set_markup(item._private_data.text)
l:add(text_w)
if item._private_data.sub_menu_f or item._private_data.sub_menu_m then
local subArrow = wibox.widget.imagebox() --TODO, make global
subArrow.fit = function(box, w, h) return subArrow._image:get_width(),item.height end
subArrow:set_image( beautiful.menu_submenu_icon )
lr:add(subArrow)
item.widget.fit = function(box,w,h,...)
args.y = data.height-h-data.margins.top
return wibox.widget.background.fit(box,w,h,...)
end
end
if args.suffix_widget then
lr:add(args.suffix_widget)
end
la:set_left(l)
la:set_right(lr)
item.widget:set_widget(m)
local fit_w,fit_h = data._internal.layout:fit()
data.height = fit_h
-- data.width = fit_w
data.style(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
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 arrow_style
local ret = base(args)
return ret
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

10
init.lua Normal file
View File

@ -0,0 +1,10 @@
return {
layout = require( "radical.layout" ),
object = require( "radical.object" ),
base = require( "radical.base" ),
radial = require( "radical.radial" ),
context = require( "radical.context" ),
box = require( "radical.box" ),
style = require( "radical.style" ),
item_style = require( "radical.item_style" ),
}

22
item_style/basic.lua Normal file
View File

@ -0,0 +1,22 @@
local setmetatable = setmetatable
local print = print
local module = {
margins = {
TOP = 2,
BOTTOM = 2,
RIGHT = 2,
LEFT = 4
}
}
local function draw(data,item,is_focussed,is_pressed)
if is_focussed then
item.widget:set_bg(data.bg_focus)
else
item.widget:set_bg(data.bg)
end
end
return setmetatable(module, { __call = function(_, ...) return draw(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

47
item_style/classic.lua Normal file
View File

@ -0,0 +1,47 @@
local setmetatable = setmetatable
local color = require( "gears.color" )
local cairo = require( "lgi" ).cairo
local print = print
local module = {
margins = {
TOP = 2,
BOTTOM = 2,
RIGHT = 0,
LEFT = 4
}
}
local focussed,default = nil, nil
local function gen(item_height,bg_color,border_color)
local img = cairo.ImageSurface(cairo.Format.ARGB32, 800,item_height)
local cr = cairo.Context(img)
cr:set_source( color(bg_color) )
cr:paint()
cr:set_source( color(border_color) )
cr:rectangle(0,item_height-1,800,1)
cr:fill()
return cairo.Pattern.create_for_surface(img)
end
local function draw(data,item,is_focussed,is_pressed)
local ih = data.item_height
if not focussed or not focussed[ih] then
if not focussed then
focussed,default={},{}
end
local bc = data.border_color
focussed[ih] = gen(ih,data.bg_focus,bc)
default [ih] = gen(ih,data.bg,bc)
end
if is_focussed then
item.widget:set_bg(focussed[ih])
else
item.widget:set_bg(default[ih])
end
end
return setmetatable(module, { __call = function(_, ...) return draw(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

5
item_style/init.lua Normal file
View File

@ -0,0 +1,5 @@
return {
basic = require("radical.item_style.basic"),
classic = require("radical.item_style.classic"),
rounded = require("radical.item_style.rounded"),
}

53
item_style/rounded.lua Normal file
View File

@ -0,0 +1,53 @@
local setmetatable = setmetatable
local math = math
local color = require( "gears.color" )
local cairo = require( "lgi" ).cairo
local print = print
local module = {
margins = {
TOP = 4,
BOTTOM = 4,
RIGHT = 4,
LEFT = 4
}
}
local focussed,default = nil, nil
local function gen(item_height,bg_color,border_color)
local img = cairo.ImageSurface(cairo.Format.ARGB32, item_height,item_height)
local cr = cairo.Context(img)
local rad = corner_radius or 3
cr:set_source(color(bg_color))
cr:arc(rad,rad,rad,0,2*math.pi)
cr:arc(item_height-rad,rad,rad,0,2*math.pi)
cr:arc(rad,item_height-rad,rad,0,2*math.pi)
cr:arc(item_height-rad,item_height-rad,rad,0,2*math.pi)
cr:fill()
cr:rectangle(0,rad, item_height, item_height-2*rad)
cr:rectangle(rad,0, item_height-2*rad, item_height)
cr:fill()
return cairo.Pattern.create_for_surface(img)
end
local function draw(data,item,is_focussed,is_pressed)
local ih = data.item_height
if not focussed or not focussed[ih] then
if not focussed then
focussed,default={},{}
end
local bc = data.border_color
focussed[ih] = gen(ih,data.bg_focus,bc)
default [ih] = gen(ih,data.bg,bc)
end
if is_focussed then
item.widget:set_bg(focussed[ih])
else
item.widget:set_bg(default[ih])
end
end
return setmetatable(module, { __call = function(_, ...) return draw(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

86
layout/grid.lua Normal file
View File

@ -0,0 +1,86 @@
local setmetatable = setmetatable
local print = print
local ipairs = ipairs
local math = math
local wibox = require( "wibox" )
local module = {}
local function left(data)
data.next_item.selected = true
end
local function right(data)
data.previous_item.selected = true
end
local function up(data)
local idx,rc,col = data.current_index,data.rowcount,data.column
idx = idx-col
if idx <= 0 then
idx = rc + idx + 1
end
data.items[idx][1].selected = true
end
local function down(data)
local idx,rc,col = data.current_index,data.rowcount,data.column
idx = idx+col
if idx > rc then
idx = idx - rc - 1
end
data.items[idx][1].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
--Get preferred item geometry
local function item_fit(data,item,...)
return data.item_height, data.item_height
end
local function new(data)
local counter = 0
local mode = data.column ~= nil
local rows = {}
local l = wibox.layout.fixed[mode and "horizontal" or "vertical"]()
local constraint = mode and data.column or data.row or 2
for i=1,constraint do
local l2 = wibox.layout.fixed[mode and "vertical" or "horizontal"]()
print("adding",(mode and "vertical" or "horizontal"))
l:add(l2)
rows[#rows+1] = l2
end
l.fit = function(a1,a2,a3)
local r1,r2 = data.item_height*math.ceil(data.rowcount/constraint),data.item_height*constraint
print("geo",(mode and r2 or r1),(mode and r1 or r2))
return (mode and r2 or r1),(mode and r1 or r2)
end
l.add = function(l,item)
for k,v in ipairs(rows) do
v:reset()
end
local rc = data.rowcount+1
for i=1,rc do
-- local r = math.ceil(i/math.ceil(rc/constraint))
-- if r == math.inf or r == 0 then r =1 end
rows[((i-1)%constraint)+1]:add((rc == i and item.widget or data.items[i][1].widget))
end
return true
end
--TODO only load the layouts when draw() is called
l.item_fit = item_fit
return l
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

67
layout/horizontal.lua Normal file
View File

@ -0,0 +1,67 @@
local setmetatable = setmetatable
local print = print
local wibox = require( "wibox" )
local module = {}
local function left(data)
if data._current_item._tmp_menu then
data = data._current_item._tmp_menu
data.items[1][1].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[1]._tmp_menu == data or v[1].sub_menu_m == data then
v[1].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
--Get preferred item geometry
local function item_fit(data,item,...)
local w, h = item._private_data._fit(...)
return 70, item._private_data.height or h
end
local function new(data)
local l = wibox.layout.fixed.horizontal()
l.fit = function(a1,a2,a3)
local result,r2 = wibox.layout.fixed.fit(a1,99999,99999)
return data.rowcount*data.default_width,data.item_height
end
l.add = function(l,item)
return wibox.layout.fixed.add(l,item.widget)
end
l.item_fit = item_fit
return l
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

5
layout/init.lua Normal file
View File

@ -0,0 +1,5 @@
return {
vertical = require( "radical.layout.vertical" ),
horizontal = require( "radical.layout.horizontal" ),
grid = require( "radical.layout.grid" ),
}

88
layout/vertical.lua Normal file
View File

@ -0,0 +1,88 @@
local setmetatable = setmetatable
local print = print
local beautiful = require("beautiful")
local wibox = require( "wibox" )
local module = {}
local function left(data)
if data._current_item._tmp_menu then
data = data._current_item._tmp_menu
data.items[1][1].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[1]._tmp_menu == data or v[1].sub_menu_m == data then
v[1].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
--Get preferred item geometry
local function item_fit(data,item,...)
local w, h = item._private_data._fit(...)
return w, item._private_data.height or h
end
local function new(data)
local l,real_l = wibox.layout.fixed.vertical(),nil
local filter_tb = nil
if data.show_filter then
real_l = wibox.layout.fixed.vertical()
real_l:add(l)
filter_tb = wibox.widget.textbox()
local bg = wibox.widget.background()
bg:set_bg(beautiful.bg_highlight)
bg:set_widget(filter_tb)
filter_tb:set_markup("<b>Filter:</b>")
filter_tb.fit = function(tb,width,height)
return width,data.item_height
end
data:connect_signal("filter_string::changed",function()
filter_tb:set_markup("<b>Filter:</b> "..data.filter_string)
end)
real_l:add(bg)
else
real_l = l
end
real_l.fit = function(a1,a2,a3)
local result,r2 = wibox.layout.fixed.fit(a1,99999,99999)
local total = data._total_item_height
return data.default_width, (total and total > 0 and total or data.rowcount*data.item_height) + (filter_tb and data.item_height or 0)
end
real_l.add = function(real_l,item)
return wibox.layout.fixed.add(l,item.widget)
end
real_l.item_fit = item_fit
return real_l
end
return setmetatable(module, { __call = function(_, ...) return new(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

73
object.lua Normal file
View File

@ -0,0 +1,73 @@
local setmetatable = setmetatable
local table = table
local rawset = rawset
local rawget = rawget
local pairs = pairs
local function setup_object(args)
local data,args,private_data,signals = {},args or {},private_data or {},{}
local get_map,set_map,private_data = args.get_map or {},args.set_map or {},args.private_data or {}
function data:connect_signal(name,func)
signals[name] = signals[name] or {}
table.insert(signals[name],func)
end
function data:remove_signal(name,func)
for k,v in pairs(signals[name] or {}) do
if v == func then
signals[name][k] = nil
end
end
end
function data:emit_signal(name,...)
for k,v in pairs(signals[name] or {}) do
v(data,...)
end
end
function data:add_autosignal_field(name)
args.force_private = args.force_private or {}
table.insert(args.force_private,name)
end
local function return_data(tab, key)
if get_map[key] ~= nil then
return get_map[key]()
elseif args.autogen_getmap == true and private_data[key] ~= nil then
return private_data[key]
elseif args.other_get_callback then
local to_return = args.other_get_callback(key)
if to_return then return to_return end
end
return rawget(tab,key)
end
local function auto_signal(key)
if args.autogen_signals == true then
data:emit_signal(key.."::changed")
end
end
local function catch_changes(tab, key,value)
if set_map[key] == false then
--print("This is not a setter",debug.traceback()) --In some case, it may be called "normally", having this print is only good for debug
elseif (data[key] ~= value or (args.always_handle ~= nil and args.always_handle[key] == true)) and set_map[key] ~= nil then
set_map[key](value)
auto_signal(key)
elseif (args.force_private or {})[key] == true or (args.autogen_setmap and (private_data[key] ~= nil)) then
private_data[key] = value
auto_signal(key)
elseif set_map[key] == nil then
rawset(data,key,value)
end
if args.auto_signal_changed == true then
data:emit_signal("changed")
end
end
setmetatable(data, { __index = return_data, __newindex = catch_changes, __len = function() return #data + #private_data end, })
return data,set_map,get_map,private_data
end
return setmetatable({}, { __call = function(_, ...) return setup_object(...) end })

0
radial.lua Normal file
View File

108
style/arrow.lua Normal file
View File

@ -0,0 +1,108 @@
local setmetatable = setmetatable
local beautiful = require( "beautiful" )
local color = require( "gears.color" )
local cairo = require( "lgi" ).cairo
local base = require( "radical.base" )
local module = {
margins = {
BOTTOM = 10,
TOP = 10,
LEFT = 0 ,
RIGHT = 0 ,
}
}
local function rotate(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
local function do_gen_menu_top(data, width, height, radius,padding,args)
local img = cairo.ImageSurface(cairo.Format.ARGB32, width,height)
local cr = cairo.Context(img)
local no_arrow = data.arrow_type == base.arrow_type.NONE
local top_padding = (data.arrow_type == base.arrow_type.NONE) and 0 or 13
cr:set_operator(cairo.Operator.SOURCE)
cr:set_source( color(args.bg) )
cr:paint()
cr:set_source( color(args.fg) )
cr:rectangle(10, top_padding+padding, width - 20 +1 , 10)
if not no_arrow then
for i=1,13 do
cr:rectangle((data._arrow_x or 20) + 13 - i, i+padding , 2*i , 1)
end
end
cr:rectangle(padding or 0,no_arrow and 10 or 23, width-2*padding, height-33 + (no_arrow and 13 or 0))
cr:rectangle(10+padding-1,height-10, width-20, 10-padding)
cr:fill()
cr:arc(10,10+top_padding,(radius-padding),0,2*math.pi)
cr:arc(width-10, 10+top_padding + (pdding or 0),(radius-padding),0,2*math.pi)
cr:arc(10,height-(radius-padding)-padding,(radius-padding),0,2*math.pi)
cr:arc(width-10,height-(radius-padding)-padding,(radius-padding),0,2*math.pi)
cr:fill()
return img
end
local function set_direction(data,direction)
local geometry = (direction == "left" or direction == "right") and {width = data.wibox.height, height = data.wibox.width} or {height = data.wibox.height, width = data.wibox.width}
local top_clip_surface = do_gen_menu_top(data,geometry.width,geometry.height,10,data.border_width,{bg=beautiful.fg_normal or "#0000ff",fg=beautiful.bg_normal or "#00ffff"})
local top_bounding_surface = do_gen_menu_top(data,geometry.width,geometry.height,10,0,{bg="#00000000",fg="#ffffffff"})
local arr_margin,angle,mar_func = (data.arrow_type == base.arrow_type.NONE) and 0 or 13,0
if direction == "bottom" then
angle,swap = math.pi,false
elseif direction == "left" then
angle,swap = math.pi/2,true
elseif direction == "right" then
angle,swap = 3*math.pi/2,true
end
if angle ~= 0 then
top_bounding_surface = rotate(top_bounding_surface,geometry,angle,swap)
top_clip_surface = rotate(top_clip_surface,geometry,angle,swap)
end
data.wibox.shape_bounding = top_bounding_surface._native
data.wibox:set_bg(cairo.Pattern.create_for_surface(top_clip_surface))
end
local function draw(data,args)
local args = args or {}
local direction = data.direction or "top"
--BEGIN set_arrow, this used to be a function, but was only called once
local at = data.arrow_type
if at == base.arrow_type.PRETTY or not at then
if direction == "left" then
data._arrow_x = data.wibox.height -20 - (data.arrow_x or 20)
elseif direction == "right" then
--TODO
elseif direction == "bottom" then
data._arrow_x = data.wibox.width -20 - (data.arrow_x or 20)
elseif direction == "top" then
--TODO
end
elseif at == base.arrow_type.CENTERED then
data._arrow_x = data.wibox.width/2 - 13
end
--END set_arrow
set_direction(data,direction)
data._internal.set_position(data)
local margins = data.margins
local margin = data._internal.margin
for k,v in pairs(margins) do
margin["set_"..k](margin,v)
end
return w,w2
end
return setmetatable(module, { __call = function(_, ...) return draw(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

20
style/classic.lua Normal file
View File

@ -0,0 +1,20 @@
local setmetatable = setmetatable
local base = require( "radical.base" )
local module = {
margins = {
TOP = 0 ,
BOTTOM = 0 ,
LEFT = 0 ,
BOTTOM = 0 ,
}
}
local function draw(data)
data.arrow_type = base.arrow_type.NONE
data.wibox.border_width = 1
data.wibox.border_color = data.border_color
end
return setmetatable(module, { __call = function(_, ...) return draw(...) end })
-- kate: space-indent on; indent-width 2; replace-tabs on;

4
style/init.lua Normal file
View File

@ -0,0 +1,4 @@
return {
arrow = require( "radical.style.arrow" ),
classic = require( "radical.style.classic" ),
}