lib/afk/keycode/modifier.ex

defmodule AFK.Keycode.Modifier do
  @moduledoc """
  Represents a basic modifier keycode, like control, shift, etc.

  All standard modifiers on a keyboard can be represented by `Modifier`
  keycodes. The currently supported modifiers are `t:modifier/0`.
  """

  @enforce_keys [:modifier]
  defstruct [:modifier]

  @type modifier ::
          unquote(
            AFK.Scancode.modifiers()
            |> Enum.map(&elem(&1, 1))
            |> Enum.reverse()
            |> Enum.reduce(&{:|, [], [&1, &2]})
          )

  @type t :: %__MODULE__{
          modifier: modifier
        }

  @doc """
  Creates a basic modifier keycode.

  ## Examples

      iex> new(:left_control)
      %AFK.Keycode.Modifier{modifier: :left_control}

      iex> new(:right_super)
      %AFK.Keycode.Modifier{modifier: :right_super}
  """
  @spec new(modifier) :: t
  def new(modifier)

  for {_value, modifier} <- AFK.Scancode.modifiers() do
    def new(unquote(modifier)), do: struct!(__MODULE__, modifier: unquote(modifier))
  end

  defimpl AFK.Scancode.Protocol do
    @spec scancode(keycode :: AFK.Keycode.Modifier.t()) :: AFK.Scancode.t()
    def scancode(keycode)

    for {value, modifier} <- AFK.Scancode.modifiers() do
      def scancode(%AFK.Keycode.Modifier{modifier: unquote(modifier)}), do: unquote(value)
    end
  end

  defimpl AFK.ApplyKeycode, for: AFK.Keycode.Modifier do
    @spec apply_keycode(keycode :: AFK.Keycode.Modifier.t(), state :: AFK.State.t(), key :: atom) :: AFK.State.t()
    def apply_keycode(keycode, state, key) do
      modifier_used? =
        Enum.any?(state.modifiers, fn
          {_key, ^keycode} -> true
          {_key, _keycode} -> false
        end)

      if modifier_used? do
        state
      else
        modifiers = [{key, keycode} | state.modifiers]

        %{state | modifiers: modifiers}
      end
    end

    @spec unapply_keycode(keycode :: AFK.Keycode.Modifier.t(), state :: AFK.State.t(), key :: atom) :: AFK.State.t()
    def unapply_keycode(keycode, state, key) do
      modifiers =
        Enum.filter(state.modifiers, fn
          {^key, ^keycode} -> false
          {_key, _keycode} -> true
        end)

      %{state | modifiers: modifiers}
    end
  end
end