Skip to content

Nowaaru/power-mode.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

85 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

nowaaru presents...

power πŸ’₯ mode



What the heck is this?! Why the heck is this?!

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.

Description
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.


Getting Started: Presets

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
});

Getting Started: Manual

Hello, fellow happiness-deprived traveler! I really wouldn't advise this without a good pair of headphones or IEMs.

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.
  • 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.
  • 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! πŸ“Ί 🌈

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
Hweh?! What is this HERESY? I'm setting what's SUPPOSED to be an integer to a string?
In this library, spatial fields can be set to integer percentage values similarly to CSS.

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.

I hope your music is going great!
So, let's set a timer for our renderer so we don't crush our NeoVim's performance by using a decoration provider. If you believe it won't, either you're wrong or your computer is just that much better than mine.
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!

About

The Vimdicator watches over your every keystroke.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published