Power Mode is a concept originally imaged by the wise @JoelBesada for the Code in the Dark competition. Glorious particles would fill the screen, highlighting your unmatched intellect as the characters flow from your fingers. A glowing counter would scroll upwards endlessly, marking your every milestone with a fighting game-esque compliment.
At least, that's how I remembered it.
Once I swapped from Visual Studio Code, I craved that satisfaction the more I burnt out. WakaTime could only do so much, and I have even less in cash to give them, so I had to use my secret weapon: Attention Deficit Hyperactivity Disorder.
Activate 2 of these effects. Must be done at the same time.-
Target 1 face-up Bug-like monster your opponent controls.
Send that monster to the graveyard. If this action is chosen and this card is brought back into play, any damage dealt by this monster will do 1.5x damage due to immense confusion and anger. All forms of damage will target this monster. - Remove up to two Burnout cards from your field. Two turns after, if a Bug-like monster has not been sent to the graveyard, add four Burnout cards to the field.
- Remove up to four Bug-like cards from the field. Every turn, one Feature Creep card is added to the field. Every three turns, the opponent may place down a Bug-like card from their deck.
You can probably guess which two I chose. Instead of having the same-old-same-old "everyone-has-the-same-design," why not instead provide the resources to make their own custom design and supply semi-customizable presets for those too lazy (which I completely understand)?
As of writing, there aren't any particles. However, there is an example that shows how to get started with using some of the bars. The library is fairly well-documented, so I shouldn't need to touch on that very much here.
Hello, fellow approval-deprived traveler! To first get started with presets, you'll have to require it.
As of writing, there is only one preset: the Boss preset. This preset is shown at the top of the README page.
It consists of a stacked health bar, a percentage, and a randomized name that can be modified through the fields. This class is well-documented.
To use a preset, require the file and create
a new object from the constructor. The default
group name is a field of the power-mode
module.
local PowerMode = require("power-mode");
local BossFactory = require("power-mode.presets.boss");
local Boss = BossFactory.new(PowerMode.__group_name);
Afterwards, run your init
function to have
preset do all the internal hooking-up for you.
When that's done, you'll need to have some kind of loop to make the good-ol' guy tick:
local PowerMode = require("power-mode");
local BossFactory = require("power-mode.presets.boss");
local Boss = BossFactory.new(PowerMode.__group_name);
vim.api.vim_set_decoration_provider(PowerMode.__namespace, {
on_start = function(...)
return Boss:tick()
end
});
With that said, the first thing we have to get through is the setup. First and foremost, require all of your modules!
local MyPreset = {};
local Scorekeep = require("power-mode.scorekeep");
local PowerWindow = require("power-mode.power-window");
local PowerLayer = require("power-mode.power-layer");
A brief description of these modules:
-
Scorekeep
-
Keeps track of every individual score across many buffers.
-
Keeps track of every individual score across many buffers.
-
PowerWindow
-
The PowerWindow module is responsible for everything related to the floating window
system. Without it, nothing shows.
In short, it gets your doohickeys going.
-
The PowerWindow module is responsible for everything related to the floating window
-
PowerLayer
-
The PowerLayer module is responsible for displaying your beautiful pixelated graphics
onto the window.
When all is done, bind it to the window, and woah! Technology! πΊ π
-
The PowerLayer module is responsible for displaying your beautiful pixelated graphics
Due to my being a Roblox developer at heart, we'll be using an object-oriented approach for this. It's nice to promote customizability without affecting other instances.
local Scorekeep = require("power-mode.scorekeep");
local PowerWindow = require("power-mode.power-window");
local PowerLayer = require("power-mode.power-layer");
local Preset = {};
Preset.__prototype = {};
Preset.__index = Preset.__prototype;
function Preset.new(namespace)
local Object = {};
return setmetatable(Object, Preset);
end
All preset objects must have a tick class. To accentuate this, we will make missing prototype methods fallback to an erroneous function.
function Preset.__prototype:tick()
error("This function is not implemented.");
end
function Preset.__prototype:on_start()
error("This function is not implemented.");
end
function Preset.__prototype:init()
error("This function is not implemented.");
end
function Preset.__prototype:deinit()
error("This function is not implemented.");
end
You should always create the window in the init
method. Anywhere else
and you sacrifice end-user control for (usually) no real reason.
To get started with making your window, you should primarily keep in mind that everything in this library is a class. Oh, and also that data belonging to the object stays inside the object. Thus, we construct a new PowerWindow class and store the reference inside of the new Object.
Oh, and let's customize it a little bit! Why not?
local PowerWindow = require("power-mode.power-window");
local AnchorType = require("power-mode.power-window.anchortype"); -- π
[...]
function Preset.new(namespace)
local self = setmetatable({}, Preset);
function self:init()
self.window = PowerWindow.new();
self.window:SetAnchorType(AnchorType.CURSOR);
self.window:BindToNamespace(namespace);
self.window.Width = "8%";
self.window.Height = 2;
self.window:Show();
end
return self;
end
It's now time to add some bells and whistles to our window! Let's just have a simple bar with a background:
local PowerWindow = require("power-mode.power-window");
local PowerLayer = require("power-mode.power-layer");
local AnchorType = require("power-mode.power-window.anchortype");
local unpack = unpack or table.unpack; -- π
[...]
function Preset.new(namespace)
local self = setmetatable({}, Preset);
function self:init()
self.window = PowerWindow.new();
self.window:SetAnchorType(AnchorType.CURSOR);
self.window:BindToNamespace(namespace);
self.window.Width = "8%";
self.window.Height = 2;
self.window.Y = -self.window.Height - 1; -- Offset one more cell because of a potential outline.
local background = PowerLayer.new("Background", namespace, self.window.__buf);
local bar = PowerLayer.new("Bar", namespace, self.window.__buf);
background:Background("#DF2935");
bar:Bar(0, 0, 0.5, "#FFFFFF");
self.window:AddLayer(background, bar);
self.window:Show();
end
return self;
end
We have our window! However, it won't show if you don't
render the window and its components. Yes, that's
right. You even have to do this yourself.
function Preset.new(namespace)
local self = setmetatable({}, Preset);
local timer = vim.loop.new_timer();
function self:init()
self.window = PowerWindow.new();
self.window:SetAnchorType(AnchorType.CURSOR);
self.window:BindToNamespace(namespace);
self.window.Width = "8%";
self.window.Height = 2;
self.window.Y = -self.window.Height - 1; -- Offset one more cell because of a potential outline.
local background = PowerLayer.new("Background", namespace, self.window.__buf);
local bar = PowerLayer.new("Bar", namespace, self.window.__buf);
background:Background("#DF2935");
bar:Bar(0, 0, 0.5, "#FFFFFF");
self.window:AddLayer(background, bar);
self.window:Show();
timer:start(0, 100, function() -- π
vim.schedule(function()
self.window:AddLayer(background, bar);
self.window:RenderWindow();
self.window:RenderComponents();
end)
end)
end
return self;
end
It is highly important to call vim.schedule
when
dealing with anything related to updating the UI
in Power Mode - even if it's something as simple as
score calculation. Updating windows and buffers
inside a timer does not mix well with Vim.
It's not very free to call AddLayer so flippantly as
layers are not a dictionary but an array that is
iterated over. Sometime later down the line I'll
get making layers a dictionary where the values
contain the order instead of the keys.
With that out of the way, we have a functional Preset!
All that's needed to get this preset running is a hook-up
to a decoration provider and all is well.
But what if we wanted to get this bar moving? It's not
too hard, surprisingly enough. All that needs to be done
is to use some upvalues and move the layer painting into
the renderer. Oh, and to hook up the Scorekeeper.
Be sure to clear the layer first, otherwise
artifacts may appear!
function Preset.new(namespace)
local self = setmetatable({}, Preset);
local timer = vim.loop.new_timer();
local background, bar;
function self:init()
self.window = PowerWindow.new();
self.window:SetAnchorType(AnchorType.CURSOR);
self.window:BindToNamespace(namespace);
self.window.Width = "8%";
self.window.Height = 2;
self.window.Y = -self.window.Height - 1; -- Offset one more cell because of a potential outline.
background = PowerLayer.new("Background", namespace, self.window.__buf);
bar = PowerLayer.new("Bar", namespace, self.window.__buf);
self.window:AddLayer(background, bar);
self.window:Show();
timer:start(0, 100, function()
vim.schedule(function()
background:Clear();-- π
bar:Clear();
background:Background("#DF2935");
bar:Bar(0, 0, 0.5, "#FFFFFF");
self.window:AddLayer(background, bar);
self.window:RenderWindow();
self.window:RenderComponents();
end)
end)
end
return self;
end
Now to get that scorekeeper up and running. It's fairly
simple, make a new scorekeeper instance that's bound
to a group name and use the Ensure
method with
no arguments to guarantee that the current buffer
is being tracked.
Then, call the ScoreHandler
method with the
returned ScoreEntry
item as the argument. This
method modifies the ScoreEntry itself, so no need
to worry about calling Ensure
again.
I'll be lazy here by tostring-ing the namespace
for the group.
function Preset.new(namespace)
local self = setmetatable({}, Preset);
local timer = vim.loop.new_timer();
local scorekeeper = Scorekeep.new(tostring(namespace)); -- π
local background, bar;
function self:init()
self.window = PowerWindow.new();
self.window:SetAnchorType(AnchorType.CURSOR);
self.window:BindToNamespace(namespace);
self.window.Width = "8%";
self.window.Height = 2;
self.window.Y = -self.window.Height - 1; -- Offset one more cell because of a potential outline.
background = PowerLayer.new("Background", namespace, self.window.__buf);
bar = PowerLayer.new("Bar", namespace, self.window.__buf);
self.window:AddLayer(background, bar);
self.window:Show();
timer:start(0, 100, function()
vim.schedule(function()
local scoreEntry = scorekeeper:Ensure(); -- π
background:Clear();
bar:Clear();
background:Background("#DF2935");
bar:Bar(0, 0, scoreEntry.score / scorekeeper.scoreCap --[[ π ]], "#FFFFFF");
self.window:AddLayer(background, bar);
self.window:RenderWindow();
self.window:RenderComponents();
end)
end)
end
return self;
end
And that's it! There's your functional preset, made from scratch!
You totally, most definitely did not copy-paste this! If you did,
that's okay - it's probably because you know what you're doing.
Go ahead and hook that tick()
function to a decoration provider
and watch your world go wild!
And just like that, your preset is ready to go!
Most presets are very customizable and allow you
unparalleled freedom. Good luck!