diff --git a/configuration/init.lua b/configuration/init.lua
index 30b897b..b40f2e0 100644
--- a/configuration/init.lua
+++ b/configuration/init.lua
@@ -12,6 +12,8 @@ rc_configuration.menu = require 'rc.configuration.menu'
rc_configuration.rules = require 'rc.configuration.rules'
+rc_configuration.prompt_commands = require 'rc.configuration.prompt_commands'
+
rc_configuration.tag_layouts = require 'rc.configuration.tag_layouts'
return rc_configuration
diff --git a/configuration/prompt_commands.lua b/configuration/prompt_commands.lua
new file mode 100644
index 0000000..40ca2d8
--- /dev/null
+++ b/configuration/prompt_commands.lua
@@ -0,0 +1,50 @@
+local atag = require 'awful.tag'
+local layout_suit = require 'awful.layout.suit'
+
+local commands = {}
+
+commands['o'] = {
+ callback = function (parameters)
+ local tag_name = parameters[1] or 'New-Tag'
+
+ atag.add(tag_name, {
+ layout = layout_suit.tile
+ }):view_only()
+ end
+}
+
+commands['O'] = {
+ callback = function (parameters)
+ local aspawn = require 'awful.spawn'
+
+ local application = parameters[1]
+ local tag_name = parameters[2] or application
+
+ local t = atag.add(tag_name, {
+ layout = layout_suit.tile,
+ volatile = true,
+ })
+ t:view_only()
+ aspawn(application, { tag = t })
+ end
+}
+
+commands['q'] = {
+ callback = function ()
+ local ascreen = require 'awful.screen'
+
+ local tags = ascreen.focused().selected_tags
+
+ for _,tag in ipairs(tags) do
+ tag.volatile = true
+
+ for _,client in ipairs(tag:clients()) do
+ client:kill()
+ end
+
+ tag:delete()
+ end
+ end
+}
+
+return commands
diff --git a/theme/assets/icons/apps.svg b/theme/assets/icons/apps.svg
new file mode 100644
index 0000000..479fbca
--- /dev/null
+++ b/theme/assets/icons/apps.svg
@@ -0,0 +1 @@
+
diff --git a/theme/assets/icons/battery-outline.svg b/theme/assets/icons/battery-outline.svg
new file mode 100644
index 0000000..85cb00b
--- /dev/null
+++ b/theme/assets/icons/battery-outline.svg
@@ -0,0 +1 @@
+
diff --git a/theme/init.lua b/theme/init.lua
index bcb47f7..12a182f 100644
--- a/theme/init.lua
+++ b/theme/init.lua
@@ -7,13 +7,52 @@ local icons_dir = assets_dir .. 'icons/'
local theme = {}
-theme.border_color_normal = '#0000ff'
+--- Basic
+theme.font = 'Noto Mono 9'
--- Tags
theme.hometag_master_width_factor = 0.65
--- Icons
theme.icon_hometag = icons_dir .. 'home-circle.svg'
+theme.icon_apps = icons_dir .. 'apps.svg'
+theme.icon_battery_outline = icons_dir .. 'battery-outline.svg'
+
+--- Wibar
+-- My wibar is a transparent dock at the bottom of the screen.
+-- Height and width ahve to be set by the constructor to use screen DPI and
+-- screen percentage values.
+theme.wibar_stretch = false
+theme.wibar_border_width = 0
+theme.wibar_border_color = nil
+theme.wibar_ontop = false
+theme.wibar_opacity = 1
+theme.wibar_type = 'dock'
+theme.wibar_bg = '#0000'
+
+--- Spacing and margins
+-- Naming convention:
+-- __
+theme.spacing = 8
+theme.margin = 4
+theme.padding = 4
+
+theme.bar_height = 32 + (theme.margin * 2) -- This is the outer size of the bar
+theme.bar_width = '80%' -- Make the bar size relative to the screen
+theme.bar_margin_x = theme.margin * 2
+theme.bar_margin_y = theme.margin
+theme.bar_padding_x = theme.padding * 2
+theme.bar_padding_y = 0
+
+theme.bar_box_margin_x = 0
+theme.bar_box_margin_y = theme.margin
+theme.bar_box_padding_x = theme.padding * 2
+theme.bar_box_padding_y = theme.padding
+theme.bar_box_spacing = theme.spacing
+
+--- Widgets specific
+theme.bg_systray = '#455A64'
+theme.systray_icon_spacing = 3
--- Wallpaper
theme.wallpaper = function (screen)
diff --git a/ui/desktop_decoration/bar.lua b/ui/desktop_decoration/bar.lua
index 0f12403..264982f 100644
--- a/ui/desktop_decoration/bar.lua
+++ b/ui/desktop_decoration/bar.lua
@@ -1,27 +1,92 @@
local awibar = require 'awful.wibar'
local awful = require 'awful'
+local battery_widget = require 'battery-widget'
local beautiful = require 'beautiful'
-local wibox = require 'wibox'
+local container_background = require 'wibox.container.background'
+local container_margin = require 'wibox.container.margin'
+local container_place = require 'wibox.container.place'
+
+local gshape = require 'gears.shape'
+
+local layout_align = require 'wibox.layout.align'
+local layout_fixed = require 'wibox.layout.fixed'
+
+local systray = require 'wibox.widget.systray'
+local textclock = require 'wibox.widget.textclock'
+local widget = require 'wibox.widget'
+
+local mybattery = require 'rc.ui.desktop_decoration.widgets.battery'
+local mycommands = require 'rc.configuration.prompt_commands'
local mymainmenu = require 'rc.ui.menu.mymainmenu'
+local myprompt = require 'rc.ui.desktop_decoration.widgets.prompt'
+local mytaglist = require 'MyTagListWidget'
local capi = {
- client = _G.client,
screen = _G.screen
}
-local modkey = 'Mod4'
+local abs = math.abs
+local dpi = beautiful.xresources.apply_dpi
-local bar = { _private = { instances = {} }, mt = {} }
-
-local function get_screen_id(screen)
- local s = capi.screen[screen] or screen
+local function get_screen_id (screen)
+ local s = capi.screen[screen or 1]
return s.index
end
--- Get the bar instance for a given screen
-function bar:instance(screen)
+--- Build a widget box in the bar.
+-- A widget box is a combinaision of "shaped" `wibox.container/background` and
+-- `wibox.container.margin` to create this nice round-colored-buttons I like.
+-- @tparam args table
+-- @tparam args.screen The screen where the widget will be drawn.
+-- @tparam args.widget The widget to draw.
+-- @tparam[opt] args.bg The box's background.
+-- @tpram[opt] args.fg The box's foreground (used mainly for textbox text color).
+-- @treturn wibox.container.background The builded widget.
+local function build_widget (args)
+ local screen_id = get_screen_id(args.screen)
+
+ -- Callback for the shape function.
+ -- If the widget is almost a square: draw a circle. Otherwise, draw a
+ -- rounded_bar.
+ local shape_callback = function (cr, width, height)
+ local shape = gshape.circle
+
+ -- 10 is an arbitrary value I found after some tests :shrug:
+ if abs(width - height) > 10 then
+ shape = gshape.rounded_bar
+ end
+
+ return shape(cr, width, height)
+ end
+
+ local box_widget = widget {
+ widget = container_background,
+ shape = shape_callback,
+ bg = args.bg,
+ fg = args.fg,
+ {
+ widget = container_margin,
+ draw_empty = false,
+ top = dpi(beautiful.bar_box_padding_y, screen_id),
+ bottom = dpi(beautiful.bar_box_padding_y, screen_id),
+ right = dpi(beautiful.bar_box_padding_x, screen_id),
+ left = dpi(beautiful.bar_box_padding_x, screen_id),
+ args.widget
+ }
+ }
+
+ return box_widget
+end
+
+local bar = { _private = { instances = {} }, mt = {} }
+
+--- Get the bar instance for a given screen.
+-- If no instance was found, we build a new one.
+-- @tparam screen screen|integer The bar's screen.
+-- @treturn wibox.wibar
+function bar:instance (screen)
local screen_id = get_screen_id(screen)
local instance = self._private.instances[screen_id]
@@ -36,94 +101,124 @@ end
function bar.new (screen)
local my_bar = {}
+ my_bar.launcher = build_widget {
+ screen = screen,
+ bg = '#2196F3',
+ widget = awful.widget.launcher {
+ image = beautiful.icon_apps,
+ menu = mymainmenu()
+ }
+ }
+
+ my_bar.textclock = build_widget {
+ bg = '#FF5722',
+ fg = '#ECEFF1',
+ widget = textclock('%l:%M %p')
+ }
+
+ my_bar.promptbox = myprompt {
+ commands = mycommands
+ }
+
+ -- This widget needs to be reworded and integrated inside the project.
+ my_bar.taglist = mytaglist.new {
+ screen = screen
+ }
+
+ my_bar.battery = build_widget {
+ screen = screen,
+ bg = '#673AB7',
+ widget = mybattery {
+ screen = screen,
+ device_path = battery_widget.get_BAT0_device_path(),
+ color = '#ECEFF1'
+ }
+ }
+
+ my_bar.systray = build_widget {
+ screen = screen,
+ bg = '#455A64',
+ widget = systray()
+ }
+
my_bar.wibar = awibar {
screen = screen,
- position = 'top'
- }
-
- my_bar.launcher = awful.widget.launcher {
- image = beautiful.awesome_icon,
- menu = mymainmenu()
- }
-
- my_bar.keyboardlayout = awful.widget.keyboardlayout()
-
- my_bar.textclock = wibox.widget.textclock()
-
- my_bar.promptbox = awful.widget.prompt()
-
- my_bar.layoutbox = awful.widget.layoutbox {
- screen = screen,
- buttons = {
- awful.button({ }, 1, function () awful.layout.inc( 1) end),
- awful.button({ }, 3, function () awful.layout.inc(-1) end),
- awful.button({ }, 4, function () awful.layout.inc(-1) end),
- awful.button({ }, 5, function () awful.layout.inc( 1) end),
- }
- }
-
- my_bar.taglist = awful.widget.taglist {
- screen = screen,
- filter = awful.widget.taglist.filter.all,
- buttons = {
- awful.button({}, 1, function(tag)
- tag:view_only()
- end),
- awful.button({ modkey }, 1, function(tag)
- if capi.client.focus then
- capi.client.focus:move_to_tag(tag)
- end
- end),
- awful.button({}, 3, awful.tag.viewtoggle),
- awful.button({ modkey }, 3, function(tag)
- if capi.client.focus then
- capi.client.focus:toggle_tag(tag)
- end
- end),
- awful.button({ }, 4, function(tag)
- awful.tag.viewprev(tag.screen)
- end),
- awful.button({ }, 5, function(tag)
- awful.tag.viewnext(tag.screen)
- end)
- }
- }
-
- my_bar.tasklist = awful.widget.tasklist {
- screen = screen,
- filter = awful.widget.tasklist.filter.currenttags,
- buttons = {
- awful.button({ }, 1, function (c)
- c:activate { context = "tasklist", action = "toggle_minimization" }
- end),
- awful.button({ }, 3, function() awful.menu.client_list { theme = { width = 250 } } end),
- awful.button({ }, 4, function() awful.client.focus.byidx(-1) end),
- awful.button({ }, 5, function() awful.client.focus.byidx( 1) end),
- }
+ position = 'bottom',
+ height = dpi(beautiful.bar_height, screen),
+ width = beautiful.bar_width -- Width is a percentage of the screen size
}
my_bar.wibar:setup {
- layout = wibox.layout.align.horizontal,
- { -- Left widgets
- layout = wibox.layout.fixed.horizontal,
- my_bar.launcher,
- my_bar.taglist,
- my_bar.promptbox,
- },
- screen.mytasklist, -- Middle widget
- { -- Right widgets
- layout = wibox.layout.fixed.horizontal,
- my_bar.keyboardlayout,
- wibox.widget.systray(),
- my_bar.textclock,
- my_bar.layoutbox,
+ -- Bar margins
+ widget = container_margin,
+ top = dpi(beautiful.bar_margin_y, screen),
+ bottom = dpi(beautiful.bar_margin_y, screen),
+ right = dpi(beautiful.bar_margin_x, screen),
+ left = dpi(beautiful.bar_margin_x, screen),
+ {
+ -- Physical bar
+ widget = container_background,
+ bg = '#263238D9',
+ shape = function (cr, width, height)
+ gshape.rounded_rect(cr, width, height, 10)
+ end,
+ {
+ -- Bar paddings
+ widget = container_margin,
+ top = dpi(beautiful.bar_padding_y, screen),
+ bottom = dpi(beautiful.bar_padding_y, screen),
+ right = dpi(beautiful.bar_padding_x, screen),
+ left = dpi(beautiful.bar_padding_x, screen),
+ {
+ -- Bar content
+ layout = layout_align.horizontal,
+ {
+ -- Left side of the bar, box margins
+ widget = container_margin,
+ top = dpi(beautiful.bar_box_margin_y, screen),
+ bottom = dpi(beautiful.bar_box_margin_y, screen),
+ right = dpi(beautiful.bar_box_margin_x, screen),
+ left = dpi(beautiful.bar_box_margin_x, screen),
+ {
+ -- Left widget boxes
+ layout = layout_fixed.horizontal,
+ spacing = dpi(beautiful.bar_box_spacing, screen),
+ my_bar.launcher,
+ my_bar.promptbox
+ }
+ },
+
+ -- Middle widget is the custom taglist
+ -- it doesn't need box/margins/...
+ {
+ widget = container_place,
+ my_bar.taglist
+ },
+ {
+ -- Right side of the bar, box margins
+ widget = container_margin,
+ top = dpi(beautiful.bar_box_margin_y, screen),
+ bottom = dpi(beautiful.bar_box_margin_y, screen),
+ right = dpi(beautiful.bar_box_margin_x, screen),
+ left = dpi(beautiful.bar_box_margin_x, screen),
+ {
+ -- Right widget boxes
+ layout = layout_fixed.horizontal,
+ spacing = dpi(beautiful.bar_box_spacing, screen),
+ my_bar.systray,
+ my_bar.battery,
+ my_bar.textclock
+ }
+ }
+ }
+ }
}
}
return my_bar
end
-function bar.mt:__call(...)
+function bar.mt:__call (...)
return self:instance(...)
end
diff --git a/ui/desktop_decoration/widgets/battery.lua b/ui/desktop_decoration/widgets/battery.lua
new file mode 100644
index 0000000..547a665
--- /dev/null
+++ b/ui/desktop_decoration/widgets/battery.lua
@@ -0,0 +1,100 @@
+---------------------------------------------------------------------------
+-- An implementation of my battery_widget widget with a dynamicly generated
+-- icon based on the battery percentage level.
+--
+-- Find more about my battery_widget widget at
+-- https://github.com/Aire-One/awesome-battery_widget
+--
+-- @author Aire-One
+-- @copyright 2020 Aire-One
+---------------------------------------------------------------------------
+local lgi = require 'lgi'
+local cairo = lgi.cairo
+local rsvg = lgi.Rsvg
+
+local atooltip = require 'awful.tooltip'
+local beautiful = require 'beautiful'
+local gcolor = require 'gears.color'
+local imagebox = require 'wibox.widget.imagebox'
+
+local battery_widget = require 'battery-widget'
+
+local my_battery = {}
+local mt = {}
+
+--- Generate a drawing for the battery icon.
+-- @tparam number percentage The percentage of battery remaining.
+-- @tparam gears.color|string color The color of the drawing.
+-- @treturn cairo.ImageSurface The generated surface for the drawing.
+function my_battery.draw_battery (percentage, color)
+ local svg_handle = rsvg.Handle.new_from_file(beautiful.icon_battery_outline)
+ if not svg_handle then return end
+ local surface = cairo.ImageSurface.create(cairo.Format.ARGB32, 24, 24)
+ local cr = cairo.Context(surface)
+ svg_handle:render_cairo(cr)
+
+ local max_height = 14
+ local x = 8
+ local width = 8
+ local height = percentage / 100 * max_height
+ local y = 6 + max_height - height
+
+ cr:set_source(gcolor(color))
+ cr:rectangle(x, y, width, height)
+ cr:fill()
+
+ return surface
+end
+
+--- Update handler for the battery widget. This is the function called
+-- by the signal system on battery update.
+-- @tparam my_battery widget The battery widget to update.
+-- @tparam UPowerGLib.Device device The UPower device to monitor.
+function my_battery.update (widget, device)
+ widget.image = my_battery.draw_battery(device.percentage, widget.color)
+end
+
+--- Give the widget template for the battery widget.
+-- (It's a basic `wibox.widget.imagebox` for my battery implementation)
+function my_battery.widget_template ()
+ local widget = imagebox()
+ widget.resize = true
+
+ return widget
+end
+
+--- Constructor of my battery widget!
+-- @tparam table args Table of parameters.
+-- @tparam screen|number args.screen The screen of the wodget.
+-- @tparam string args.device_path The path of the UPower device to monitor.
+-- @tparam gears.color|string args.color Color to use to draw the battery.
+-- @treturn my_battery The instantiated widget.
+function my_battery.new (args)
+ local widget = battery_widget {
+ screen = args.screen,
+ device_path = args.device_path,
+ widget_template = my_battery.widget_template(),
+ instant_update = true
+ }
+
+ widget.color = args.color
+
+ widget:connect_signal('upower::update', function (w, device)
+ my_battery.update(w, device)
+ end)
+
+ widget.tooltip = atooltip {
+ objects = { widget },
+ timer_function = function ()
+ return string.format('%3d', widget.device.percentage) .. '%'
+ end
+ }
+
+ return widget
+end
+
+function mt.__call (self, ...)
+ return my_battery.new(...)
+end
+
+return setmetatable(my_battery, mt)
diff --git a/ui/desktop_decoration/widgets/prompt.lua b/ui/desktop_decoration/widgets/prompt.lua
new file mode 100644
index 0000000..69017e1
--- /dev/null
+++ b/ui/desktop_decoration/widgets/prompt.lua
@@ -0,0 +1,67 @@
+local acompletion = require 'awful.completion'
+local aspawn = require 'awful.spawn'
+local gstring = require 'gears.string'
+local gtable = require 'gears.table'
+local prompt = require 'awful.widget.prompt'
+
+local string = string
+
+local function completion_cb (self, command_before_comp, cur_pos_before_comp, ncomp)
+ return acompletion.generic(
+ command_before_comp, cur_pos_before_comp, ncomp,
+ self.completion_keywords)
+end
+
+local function exe_cb (self, input)
+ -- Exit if the input is empty
+ if not input or #input == 0 then
+ return
+ end
+
+ -- Trim
+ input = string.gsub(input, "^%s*(.-)%s*$", "%1")
+
+ -- If the input is not a VI command, spawn it
+ if input:sub(1,1) ~= ':' then
+ aspawn(input)
+ return
+ end
+
+ -- Parse the custom command
+ local command, parameters = input:gmatch(':([%w-]+)%s*(.*)')()
+ command = command:sub(1,1)
+ parameters = gstring.split(parameters, '%s')
+
+ -- Quit if the command doesn't exist
+ if not self.commands[command] then
+ print('":' .. command .. '" not reconized as a command')
+ return
+ end
+
+ self.commands[command].callback(parameters)
+end
+
+local my_prompt = { mt = {} }
+
+function my_prompt.new (args)
+ local prompt_widget = nil
+ prompt_widget = prompt {
+ prompt = ' ', -- needs the 3 spaces
+ completion_callback = function (...) return completion_cb(prompt_widget, ...) end,
+ exe_callback = function (...) return exe_cb(prompt_widget, ...) end
+ }
+
+ prompt_widget.commands = args.commands
+ prompt_widget.completion_keywords = gtable.join(
+ -- TODO: find applications list
+ gtable.find_keys(args.commands, function () return true end, false),
+ args.completion_keywords or {})
+
+ return prompt_widget
+end
+
+function my_prompt.mt.__call (self, ...)
+ return my_prompt.new(...)
+end
+
+return setmetatable(my_prompt, my_prompt.mt)