First of all, you need to have AwesomeWM installed and running.
I'm using awesome-git
, the development release.
I'm not sure whether this would work on the stable release.
We will be creating the effect displayed in the GIF. We will build the widget from scratch, as well as the animations.
Note: My file structure may be different from yours, but I'll make sure this configuration works on all systems. But you also should not blindly follow me.
Note: AwesomeWM doesn't have any built-in standard animation libraries, there are some animation libraries built by the community like Awesome Animation Framework, but we won't use any of these.
Note: You will find the final file links at the bottom.
- Building The Widget
- Animating The Widget
- Adding Text
- Cleaning Up
- Adding To Keybindings
- Bonus: Encapsulating And Cleaning Up
Let's start by creating some directories.
effects/
|- tagswitch.lua
animations/
|- init.lua
|- fade.lua
Now let's start by writing:
animations/init.lua
local M = {}
return M
animations/fade.lua
return function (widget, delay)
end
We will modify these two files later when we get to the animating step. But now let's focus on building the widget.
Let's open effects/tagswitch.lua
and import some standard libraries:
effects/tagswitch.lua
local awful = require("awful")
local wibox = require("wibox")
But before we continue, you need to write this in your rc.lua
:
rc.lua
require("effects.tagswitch")
Then let's create the widget:
effects/tagswitch.lua
local M = wibox {
}
return M
Let's add some properties.
local M = wibox {
visible = true,
opacity = 1,
bg = "#000",
fg = "#fff",
ontop = true,
height = 90,
width = 180,
}
...
Let's display it on the screen.
...
M:setup {
valign = "center",
halign = "center",
layout = wibox.container.place,
}
awful.placement.centered(M, { parent = awful.screen.focused() })
tagswitch.lua
should now look like this:
local awful = require("awful")
local wibox = require("wibox")
local M = wibox {
visible = true,
opacity = 1,
bg = "#000",
fg = "#fff",
ontop = true,
height = 90,
width = 180,
}
M:setup {
valign = "center",
halign = "center",
layout = wibox.container.place,
}
awful.placement.centered(M, { parent = awful.screen.focused() })
return M
And a black square in the middle of your screen should appear.
Let's quickly add the text:
M:setup {
{
id = "text",
markup = "<b>dev</b>",
font = "JetBrainsMono Nerd Font 37",
widget = wibox.widget.textbox,
},
...
}
We just used the text dev
, that doesn't mean anything.
Note: I used the font Jet Brains Mono
, change the font to your liking, but make it big.
tagswitch.lua
should now look like this:
local awful = require("awful")
local wibox = require("wibox")
local M = wibox {
visible = true,
opacity = 1,
bg = "#000",
fg = "#fff",
ontop = true,
height = 90,
width = 180,
}
M:setup {
{
id = "text",
markup = "<b>dev</b>",
font = "JetBrainsMono Nerd Font 37",
widget = wibox.widget.textbox,
},
valign = "center",
halign = "center",
layout = wibox.container.place,
}
awful.placement.centered(M, { parent = awful.screen.focused() })
return M
And your square should like like this:
And now we are done building the widget! Next Step: Animating The Widget
Now let's continue to the hardest part in creating the effect, it is the animation. AwesomeWM doesn't have any standard animation libraries, there are some community-made libraries, but we will be making the animations from scratch.
First, let's import some standard libraries.
animations/fade.lua
local gears = require("gears")
Let's create some useful functions that we'll use later.
local function hide(w)
w.visible = false
w.opacity = 0
end
local function show(w)
w.visible = true
w.opacity = 1
end
Now let's write in the returned function:
...
return function (widget, delay)
show(widget)
gears.timer.start_new(delay, function ()
hide(widget)
end)
end
Now, in order to see our changes, we need to write in rc.lua
:
rc.lua
local tagswitch = require("effects.tagswitch")
local fade = require("animations.fade")
And let's run our function:
rc.lua
fade(tagswitch, 1)
Here we give it the delay of 1 second, you can see that the square appears for 1 second and then disappears.
animations/fade.lua
should now look like this:
local gears = require("gears")
local function hide(w)
w.visible = false
w.opacity = 0
end
local function show(w)
w.visible = true
w.opacity = 1
end
return function (widget, delay)
show(widget)
gears.timer.start_new(delay, function ()
hide(widget)
end)
end
We still didn't finish, but before we continue, let's alias the function
gears.timer.start_new
to something shorter as we will use it a lot in this tutorial:
local gears = require("gears")
local timeout = gears.timer.start_new
...
We can now change all occurrences of gears.timer.start_new
to just timeout
.
Let's add the animation:
...
timeout(delay, function ()
-- The maximum number of frames
local max = 10
for f = 1, max do
timeout((f / 10), function ()
f = f + 1
-- Decrease the opacity to make fading effect
widget.opacity = widget.opacity - 0.1
-- This runs when the animation is finished
if f == max then
hide(widget)
end
end)
end
end)
You can play with the values to understand the code more. The animation should look like this:
And animations/fade.lua
should look like this:
local gears = require("gears")
local timeout = gears.timer.start_new
local function hide(w)
w.visible = false
w.opacity = 0
end
local function show(w)
w.visible = true
w.opacity = 1
end
return function (widget, delay)
show(widget)
timeout(delay, function ()
-- The maximum number of frames
local max = 10
for f = 1, max do
timeout((f / 10), function ()
f = f + 1
-- Decrease the opacity to make fading effect
widget.opacity = widget.opacity - 0.1
-- This runs when the animation is finished
if f == max then
hide(widget)
end
end)
end
end)
end
Now let's make the animation more smooth by playing with the values:
local max = 50
for f = 1, max do
timeout((f / 200), function ()
f = f + 1
-- Decrease the opacity to make fading effect
widget.opacity = widget.opacity - 0.02
...
Now we get a much smoother (and faster) animation:
Before we continue, let's make the widget hidden by default:
effects/tagswitch.lua
local M = wibox {
visible = false,
opacity = 0,
...
}
Now, what if we want to bind this effect to a keybinding:
rc.lua
awful.keyboard.append_global_keybindings({
awful.key({ "Mod4" }, "o", function ()
fade(tagswitch, 0.25)
end)
})
Here we binded it to Super + o. Restart your window manager and try it. At first, you will think that it works perfectly, but when you start to spam it, you will find that the effect is kinda broken.
Let's fix that! Open animations/init.lua
and add this (before the return
statement):
animations/init.lua
M.busy = false
Now let's write this in animations/fade.lua
:
animations/fade.lua
local gears = require("gears")
local animations = require("animations")
...
return function (widget, delay)
if animations.busy then
return
end
timeout(delay, function ()
animations.busy = true
...
end)
end
And write this in the finish if statement
:
...
if f == max then
animations.busy = false
hide(widget)
end
We can see that we've partially fixed the bug, and when you spam Super + o, the effect doesn't spam. But that's not what we want.
Go to animations/init.lua
and write:
animations/init.lua
M.busy = false
M.timers = {}
Basically what we want to do, is that if the animation is busy, we want to
stop all gears.timer
s and start all over again. We can do that by registering all
the timers by appending them to animations.timers
, and then we can write a
basic for
loop to loop over animations.timers
(list) and stop all the timers, one by one.
We can append to the list by using Lua's builtin function table.insert
. So
replace all occurences of:
animations/fade.lua
timeout(delay, function()
end)
to
table.insert(animations.timer, timeout(delay, function ()
end))
And add this:
return function (widget, delay)
if animations.busy then
for _, v in ipairs(animations.timers) do
v:stop()
end
end
...
end
animations/init.lua
should look like this:
local M = {}
M.busy = false
M.timers = {}
return M
and animations/fade.lua
should look like this:
local gears = require("gears")
local animations = require("animations")
local timeout = gears.timer.start_new
local function hide(w)
w.visible = false
w.opacity = 0
end
local function show(w)
w.visible = true
w.opacity = 1
end
return function (widget, delay)
if animations.busy then
for _, v in ipairs(animations.timers) do
v:stop()
end
end
show(widget)
table.insert(animations.timers, timeout(delay, function ()
animations.busy = true
local max = 50
for f = 1, max do
table.insert(animations.timers, timeout((f / 200), function ()
f = f + 1
widget.opacity = widget.opacity - 0.02
-- This runs when the animation is finished
if f == max then
animations.busy = false
hide(widget)
end
end))
end
end))
end
You can now see a much better behavior:
Now we are done with animating! Next Step: Adding Text
This is a fairly easy step, we will be adding the ability to change the text
in the widget. Let's start by opening effects/tagswitch.lua
and adding this function:
effects/tagswitch.lua
M.changeText = function (text)
M:get_children_by_id("text")[1]:set_markup("<b>" .. text .. "</b>")
end
What are we doing is that we are calling the function get_children_by_id
on the widget (M
), the function recieves a string,
which is the id for the child. Since we are using nested widgets, the declarative style, we can access child widgets (children) by using get_children_by_id
. Note that the id of the child widget is determined by the id
property (see effects/tagswitch.lua
).
For some reason, we have to select the first element of the returned list (as seen in the docs), and then we run the function set_markup
. Note that we used markup
instead of text
to make the text bold by using the <b>
HTML tags. So we are passing the
concatenated text with the bold (<b>
) tags to set_markup
.
Let's test it by going to the rc.lua
and modifying the keybinding:
awful.keyboard.append_global_keybindings({
awful.key({ "Mod4" }, "o", function ()
tagswitch.changeText("this")
...
end)
})
We changed the text to "this"
, which again, doesn't mean anything, here's how the widget should look like:
Now we are done with adding the text! Next Step: Cleaning Up
This is an easy, but very important step. Let's start by opening effects/tagswitch.lua
.
Add this function:
effects/tagswitch.lua
local fade = require("animations.fade")
...
M.animate = function (text)
M.changeText(text)
fade(M, 0.25)
end
But now arises a problem, we of course need to require
the effects.tagswitch
module with require
, this will run the
code in effects/tagswitch.lua
, which creates a widget.
Now if we have multiple files (say you split the keybindings into two files), you may have to require it two times, once on each file, that creates two widgets at the same time, and we don't want this.
We can fix this by creating a new file in effects/
directory. Name it
init.lua
. And write:
effects/init.lua
local M = {}
local effects = {
{ require("effects.tagswitch"), "tagswitch" }
}
M.request_effect = function (name)
for _, e in ipairs(effects) do
if name == e[2] then
return e[1]
end
end
return false
end
return M
This is just a function that we call when we want to access a certain effect. If you have multiple effects, you can add them here, and the function will take care of all of that.
And to make sure the effects run only once, you will need
to write this in your rc.lua
(write this before your keybindings code, not at the end! Order is very important!):
rc.lua
Effects = require("effects")
Effects
is a global variable, Now everytime we need to access an effect,
we will write:
Effects.request_effect("effect")
Now let's update our rc.lua
:
rc.lua
local tagswitch = Effects.request_effect("tagswitch")
awful.keyboard.append_global_keybindings({
awful.key({ "Mod4" }, "o", function ()
if tagswitch then
tagswitch.animate("yes")
end
end)
})
Make sure to 'null-check' when using the effect returned by Effects.request_widget
by wrapping the code with if statements
, as we did in the example, to prevent errors if by somehow the effect cannot be found (if Effects.request_widget
returned false
):
if tagswitch then
-- your code <---
end
We are done with cleaning up! Next Step: Adding To Keybindings
Let's start off by deleting the keybinding code in rc.lua
:
Note: Leave the statement:
Effects = require("effects")
and don't delete it.
rc.lua
-- local tagswitch = Effects.request_effect("tagswitch")
--
-- awful.keyboard.append_global_keybindings({
-- awful.key({ "Mod4" }, "o", function ()
-- if tagswitch then
-- tagswitch.animate("yes")
-- end
-- end)
-- })
And add this to your keybindings section:
local tagswitch = Effects.request_effect("tagswitch")
...
awful.key {
...
on_press = function (index)
...
if tagswitch then
local t = awful.screen.focused().selected_tag
tagswitch.animate(tostring(t.index))
end
end,
},
Add this for all tag related keybindings. Unfortunately, I can't show you exact code examples as we have different file structures and code.
Notice that we used
t.index
instead oft.name
, I prefert.index
because some people use icons like Font Awesome and Nerd Fonts. And some people may even have all their tags named 'o' or 'O', so it is convenient to use tag index instead.
You can change the dimensions of the widget as it can be too wide for the numbers:
effects/taglist.lua
local M = wibox {
...
height = 90,
width = 100,
}
And that concludes our tutorial, hope you learned something!
If you are still here, let me show you one more trick.
Let's make the fade
function more pure and encapsulated.
animations/fade.lua
return function (widget, speed, delay)
...
table.insert(animations.timers, timeout((f / speed), function ()
...
end))
end
We just added a speed argument.
effects/taglist.lua
local beautiful = require("beautiful")
-- DPI
local xresources = require("beautiful.xresources")
local dpi = xresources.apply_dpi
...
local M = wibox {
...
bg = beautiful.bg_tagswitch or beautiful.bg_normal,
fg = beautiful.fg_tagswitch or beautiful.fg_normal,
height = beautiful.tagswitch_height or dpi(90),
width = beautiful.tagswitch_width or dpi(180),
}
M:setup {
{
id = "text",
font = beautiful.tagswitch_font or beautiful.font,
...
},
...
}
M.animate = function (text)
M.changeText(text)
fade(M, beautiful.tagswitch_speed or 200, beautiful.tagswitch_delay or 0.25)
end
We just added theming options to the widget. I can add a section for that in my theme.lua
:
theme.lua
-- ## EFFECTS ## --
-- Tagswitch
theme.bg_tagswitch = "#000"
theme.fg_tagswitch = "#fff"
theme.tagswitch_width = dpi(100)
theme.tagswitch_font = "JetBrainsMono Nerd Font 37"
theme.tagswitch_speed = 200
theme.tagswitch_delay = 0.25
Isn't that cool?!
Final effects/init.lua
: link
Final effects/tagswitch.lua
: link
Final animations/init.lua
: link
Final animations/fade.lua
: link