From a64b440a4e2b4860832eb18b101e2d0207f07d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sat, 4 Jul 2020 17:13:07 -0700 Subject: [PATCH 1/8] Introduce Layout --- lib/layout.ex | 45 +++++++++++++++++++++++++++++++++ lib/layout/key.ex | 26 +++++++++++++++++++ lib/layout/led.ex | 20 +++++++++++++++ lib/rgb_matrix.ex | 2 ++ lib/rgb_matrix/engine.ex | 16 +++++++----- lib/xebow.ex | 35 +++++++++++++++++++++++++ lib/xebow/application.ex | 3 ++- test/rgb_matrix/engine_test.exs | 10 +++++--- test/xebow_test.exs | 7 +++++ 9 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 lib/layout.ex create mode 100644 lib/layout/key.ex create mode 100644 lib/layout/led.ex create mode 100644 lib/rgb_matrix.ex create mode 100644 test/xebow_test.exs diff --git a/lib/layout.ex b/lib/layout.ex new file mode 100644 index 0000000..d946513 --- /dev/null +++ b/lib/layout.ex @@ -0,0 +1,45 @@ +defmodule Layout do + @moduledoc """ + Describes a keyboard layout. + """ + + alias __MODULE__.{Key, LED} + + @type t :: %__MODULE__{ + keys: [Key.t()], + leds: [LED.t()], + leds_by_keys: %{atom => LED.t()}, + keys_by_leds: %{atom => Key.t()} + } + defstruct [:keys, :leds, :leds_by_keys, :keys_by_leds] + + def new(keys, leds \\ []) do + leds_map = Map.new(leds, &{&1.id, &1}) + + leds_by_keys = + keys + |> Enum.filter(& &1.led) + |> Map.new(&{&1.id, Map.fetch!(leds_map, &1.led)}) + + keys_by_leds = + keys + |> Enum.filter(& &1.led) + |> Map.new(&{&1.led, &1}) + + struct!(__MODULE__, + keys: keys, + leds: leds, + leds_by_keys: leds_by_keys, + keys_by_leds: keys_by_leds + ) + end + + def keys(layout), do: layout.keys + def leds(layout), do: layout.leds + + def led_for_key(%__MODULE__{} = layout, key_id) when is_atom(key_id), + do: Map.get(layout.leds_by_keys, key_id) + + def key_for_led(%__MODULE__{} = layout, led_id) when is_atom(led_id), + do: Map.get(layout.keys_by_leds, led_id) +end diff --git a/lib/layout/key.ex b/lib/layout/key.ex new file mode 100644 index 0000000..5bce377 --- /dev/null +++ b/lib/layout/key.ex @@ -0,0 +1,26 @@ +defmodule Layout.Key do + @moduledoc """ + Describes a physical key and its location. + """ + + @type t :: %__MODULE__{ + id: atom, + x: float, + y: float, + width: float, + height: float, + led: atom + } + defstruct [:id, :x, :y, :width, :height, :led] + + def new(id, x, y, opts \\ []) do + struct!(__MODULE__, + id: id, + x: x, + y: y, + width: Keyword.get(opts, :width, 1), + height: Keyword.get(opts, :height, 1), + led: Keyword.get(opts, :led) + ) + end +end diff --git a/lib/layout/led.ex b/lib/layout/led.ex new file mode 100644 index 0000000..18183d2 --- /dev/null +++ b/lib/layout/led.ex @@ -0,0 +1,20 @@ +defmodule Layout.LED do + @moduledoc """ + Describes a physical LED location. + """ + + @type t :: %__MODULE__{ + id: atom, + x: float, + y: float + } + defstruct [:id, :x, :y] + + def new(id, x, y) do + struct!(__MODULE__, + id: id, + x: x, + y: y + ) + end +end diff --git a/lib/rgb_matrix.ex b/lib/rgb_matrix.ex new file mode 100644 index 0000000..074bbfc --- /dev/null +++ b/lib/rgb_matrix.ex @@ -0,0 +1,2 @@ +defmodule RGBMatrix do +end diff --git a/lib/rgb_matrix/engine.ex b/lib/rgb_matrix/engine.ex index d1c27e7..542e3bc 100644 --- a/lib/rgb_matrix/engine.ex +++ b/lib/rgb_matrix/engine.ex @@ -6,11 +6,12 @@ defmodule RGBMatrix.Engine do use GenServer + alias Layout.LED alias RGBMatrix.Animation defmodule State do @moduledoc false - defstruct [:animation, :paintables] + defstruct [:leds, :animation, :paintables] end # Client @@ -22,15 +23,18 @@ defmodule RGBMatrix.Engine do a supervisor. This function accepts the following arguments as a tuple: + - `leds` - The list of LEDs to be painted on. - `initial_animation` - The animation that plays when the engine starts. - `paintables` - A list of modules to output `RGBMatrix.Frame` to that implement the `RGBMatrix.Paintable` behavior. If you want to register your paintables dynamically, set this to an empty list `[]`. """ - @spec start_link({initial_animation :: Animation.t(), paintables :: list(module)}) :: + @spec start_link( + {leds :: [LED.t()], initial_animation :: Animation.t(), paintables :: list(module)} + ) :: GenServer.on_start() - def start_link({initial_animation, paintables}) do - GenServer.start_link(__MODULE__, {initial_animation, paintables}, name: __MODULE__) + def start_link({leds, initial_animation, paintables}) do + GenServer.start_link(__MODULE__, {leds, initial_animation, paintables}, name: __MODULE__) end @doc """ @@ -72,10 +76,10 @@ defmodule RGBMatrix.Engine do # Server @impl GenServer - def init({initial_animation, paintables}) do + def init({leds, initial_animation, paintables}) do send(self(), :get_next_frame) - initial_state = %State{paintables: %{}} + initial_state = %State{leds: leds, paintables: %{}} state = Enum.reduce(paintables, initial_state, fn paintable, state -> diff --git a/lib/xebow.ex b/lib/xebow.ex index 395d736..c0d797d 100644 --- a/lib/xebow.ex +++ b/lib/xebow.ex @@ -1,2 +1,37 @@ defmodule Xebow do + alias Layout.{Key, LED} + + @leds [ + LED.new(:l001, 0, 0), + LED.new(:l002, 1, 0), + LED.new(:l003, 2, 0), + LED.new(:l004, 0, 1), + LED.new(:l005, 1, 1), + LED.new(:l006, 2, 1), + LED.new(:l007, 0, 2), + LED.new(:l008, 1, 2), + LED.new(:l009, 2, 2), + LED.new(:l010, 0, 3), + LED.new(:l011, 1, 3), + LED.new(:l012, 2, 3) + ] + + @keys [ + Key.new(:k001, 0, 0, led: :l001), + Key.new(:k002, 1, 0, led: :l002), + Key.new(:k003, 2, 0, led: :l003), + Key.new(:k004, 0, 1, led: :l004), + Key.new(:k005, 1, 1, led: :l005), + Key.new(:k006, 2, 1, led: :l006), + Key.new(:k007, 0, 2, led: :l007), + Key.new(:k008, 1, 2, led: :l008), + Key.new(:k009, 2, 2, led: :l009), + Key.new(:k010, 0, 3, led: :l010), + Key.new(:k011, 1, 3, led: :l011), + Key.new(:k012, 2, 3, led: :l012) + ] + + @layout Layout.new(@keys, @leds) + + def layout, do: @layout end diff --git a/lib/xebow/application.ex b/lib/xebow/application.ex index 945f9e8..962189d 100644 --- a/lib/xebow/application.ex +++ b/lib/xebow/application.ex @@ -5,6 +5,7 @@ defmodule Xebow.Application do use Application + @leds Xebow.layout() |> Layout.leds() @animation_type RGBMatrix.Animation.types() |> List.first() @animation RGBMatrix.Animation.new(type: @animation_type) @@ -39,7 +40,7 @@ defmodule Xebow.Application do # {Xebow.Worker, arg}, Xebow.HIDGadget, Xebow.LEDs, - {RGBMatrix.Engine, {@animation, [Xebow.LEDs]}}, + {RGBMatrix.Engine, {@leds, @animation, [Xebow.LEDs]}}, Xebow.Keyboard ] end diff --git a/test/rgb_matrix/engine_test.exs b/test/rgb_matrix/engine_test.exs index 79ce961..e0fb1ae 100644 --- a/test/rgb_matrix/engine_test.exs +++ b/test/rgb_matrix/engine_test.exs @@ -3,6 +3,8 @@ defmodule RGBMatrix.EngineTest do alias RGBMatrix.{Animation, Engine, Frame} + @leds Xebow.layout() |> Layout.leds() + # Creates a RGBMatrix.Paintable module that emits frames to the test suite process. defp paintable(%{test: test_name}) do process = self() @@ -45,7 +47,7 @@ defmodule RGBMatrix.EngineTest do test "renders a solid animation", %{paintable: paintable} do {animation, frame} = solid_animation() - start_supervised!({Engine, {animation, [paintable]}}) + start_supervised!({Engine, {@leds, animation, [paintable]}}) assert_receive {:frame, ^frame} end @@ -73,7 +75,7 @@ defmodule RGBMatrix.EngineTest do loop: 1 ) - start_supervised!({Engine, {animation, [paintable]}}) + start_supervised!({Engine, {@leds, animation, [paintable]}}) Enum.each(frames, fn frame -> assert_receive {:frame, ^frame} @@ -84,7 +86,7 @@ defmodule RGBMatrix.EngineTest do {animation, _frame} = solid_animation() {animation_2, frame_2} = solid_animation(127, 127, 127) - start_supervised!({Engine, {animation, [paintable]}}) + start_supervised!({Engine, {@leds, animation, [paintable]}}) :ok = Engine.play_animation(animation_2) @@ -95,7 +97,7 @@ defmodule RGBMatrix.EngineTest do {animation, frame} = solid_animation() {animation_2, frame_2} = solid_animation(127, 127, 127) - start_supervised!({Engine, {animation, []}}) + start_supervised!({Engine, {@leds, animation, []}}) :ok = Engine.register_paintable(paintable) diff --git a/test/xebow_test.exs b/test/xebow_test.exs new file mode 100644 index 0000000..fe576a3 --- /dev/null +++ b/test/xebow_test.exs @@ -0,0 +1,7 @@ +defmodule XebowTest do + use ExUnit.Case + + test "holds the xebow layout" do + assert %Layout{} = Xebow.layout() + end +end From e6b80981ab801d3c8aec673b4db733d3a4299705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sat, 4 Jul 2020 21:52:17 -0700 Subject: [PATCH 2/8] Effects refactor --- lib/layout/key.ex | 4 +- lib/layout/led.ex | 4 +- lib/rgb_matrix.ex | 8 ++ lib/rgb_matrix/animation.ex | 109 --------------- lib/rgb_matrix/animation/cycle_all.ex | 28 ---- .../animation/cycle_left_to_right.ex | 30 ---- lib/rgb_matrix/animation/pinwheel.ex | 45 ------ lib/rgb_matrix/animation/static.ex | 114 --------------- lib/rgb_matrix/effect.ex | 88 ++++++++++++ lib/rgb_matrix/effect/breathing.ex | 45 ++++++ lib/rgb_matrix/effect/config.ex | 83 +++++++++++ lib/rgb_matrix/effect/config/integer.ex | 48 +++++++ lib/rgb_matrix/effect/config/option.ex | 30 ++++ lib/rgb_matrix/effect/cycle_all.ex | 46 ++++++ lib/rgb_matrix/effect/hue_wave.ex | 103 ++++++++++++++ lib/rgb_matrix/effect/pinwheel.ex | 68 +++++++++ lib/rgb_matrix/effect/random_keypresses.ex | 52 +++++++ lib/rgb_matrix/effect/random_solid.ex | 43 ++++++ lib/rgb_matrix/effect/solid_color.ex | 39 ++++++ lib/rgb_matrix/effect/solid_reactive.ex | 113 +++++++++++++++ lib/rgb_matrix/effect/splash.ex | 94 +++++++++++++ lib/rgb_matrix/engine.ex | 131 +++++++++--------- lib/rgb_matrix/frame.ex | 34 ----- lib/rgb_matrix/paintable.ex | 12 +- lib/rgb_matrix/pixel.ex | 27 ---- lib/xebow/application.ex | 5 +- lib/xebow/keyboard.ex | 75 +++++----- lib/xebow/leds.ex | 23 ++- lib/xebow/utils.ex | 42 ------ 29 files changed, 993 insertions(+), 550 deletions(-) delete mode 100644 lib/rgb_matrix/animation.ex delete mode 100644 lib/rgb_matrix/animation/cycle_all.ex delete mode 100644 lib/rgb_matrix/animation/cycle_left_to_right.ex delete mode 100644 lib/rgb_matrix/animation/pinwheel.ex delete mode 100644 lib/rgb_matrix/animation/static.ex create mode 100644 lib/rgb_matrix/effect.ex create mode 100644 lib/rgb_matrix/effect/breathing.ex create mode 100644 lib/rgb_matrix/effect/config.ex create mode 100644 lib/rgb_matrix/effect/config/integer.ex create mode 100644 lib/rgb_matrix/effect/config/option.ex create mode 100644 lib/rgb_matrix/effect/cycle_all.ex create mode 100644 lib/rgb_matrix/effect/hue_wave.ex create mode 100644 lib/rgb_matrix/effect/pinwheel.ex create mode 100644 lib/rgb_matrix/effect/random_keypresses.ex create mode 100644 lib/rgb_matrix/effect/random_solid.ex create mode 100644 lib/rgb_matrix/effect/solid_color.ex create mode 100644 lib/rgb_matrix/effect/solid_reactive.ex create mode 100644 lib/rgb_matrix/effect/splash.ex delete mode 100644 lib/rgb_matrix/frame.ex delete mode 100644 lib/rgb_matrix/pixel.ex delete mode 100644 lib/xebow/utils.ex diff --git a/lib/layout/key.ex b/lib/layout/key.ex index 5bce377..d323886 100644 --- a/lib/layout/key.ex +++ b/lib/layout/key.ex @@ -3,8 +3,10 @@ defmodule Layout.Key do Describes a physical key and its location. """ + @type id :: atom + @type t :: %__MODULE__{ - id: atom, + id: id, x: float, y: float, width: float, diff --git a/lib/layout/led.ex b/lib/layout/led.ex index 18183d2..e06e382 100644 --- a/lib/layout/led.ex +++ b/lib/layout/led.ex @@ -3,8 +3,10 @@ defmodule Layout.LED do Describes a physical LED location. """ + @type id :: atom + @type t :: %__MODULE__{ - id: atom, + id: id, x: float, y: float } diff --git a/lib/rgb_matrix.ex b/lib/rgb_matrix.ex index 074bbfc..53ebaf7 100644 --- a/lib/rgb_matrix.ex +++ b/lib/rgb_matrix.ex @@ -1,2 +1,10 @@ defmodule RGBMatrix do + @type any_color_model :: + Chameleon.Color.RGB.t() + | Chameleon.Color.CMYK.t() + | Chameleon.Color.Hex.t() + | Chameleon.Color.HSL.t() + | Chameleon.Color.HSV.t() + | Chameleon.Color.Keyword.t() + | Chameleon.Color.Pantone.t() end diff --git a/lib/rgb_matrix/animation.ex b/lib/rgb_matrix/animation.ex deleted file mode 100644 index cdcedf7..0000000 --- a/lib/rgb_matrix/animation.ex +++ /dev/null @@ -1,109 +0,0 @@ -defmodule RGBMatrix.Animation do - @moduledoc """ - Provides a data structure and functions to define an RGBMatrix animation. - - There are currently two distinct ways to define an animation. - - You may define an animation with a predefined `:frames` field. Each frame will advance every `:delay_ms` milliseconds. - These animations should use the `RGBMatrix.Animation.Static` `:type`. See the moduledocs of that module for - examples. - - Alternatively, you may have a more dynamic animation which generates frames based on the current `:tick` of the - animation. See `RGBMatrix.Animation.{CycleAll, CycleLeftToRight, Pinwheel}` for examples. - """ - - alias __MODULE__ - alias RGBMatrix.Frame - - defmacro __using__(_) do - quote do - alias RGBMatrix.Animation - - @behaviour Animation - end - end - - @callback next_frame(animation :: Animation.t()) :: Frame.t() - - @type t :: %__MODULE__{ - type: animation_type, - tick: non_neg_integer, - speed: non_neg_integer, - loop: non_neg_integer | :infinite, - delay_ms: non_neg_integer, - frames: list(Frame.t()), - next_frame: Frame.t() | nil - } - defstruct [:type, :tick, :speed, :delay_ms, :loop, :next_frame, :frames] - - @type animation_type :: - __MODULE__.CycleAll - | __MODULE__.CycleLeftToRight - | __MODULE__.Pinwheel - | __MODULE__.Static - - @doc """ - Returns a list of the available types of animations. - """ - @spec types :: list(animation_type) - def types do - [ - __MODULE__.CycleAll, - __MODULE__.CycleLeftToRight, - __MODULE__.Pinwheel - ] - end - - @type animation_opt :: - {:type, animation_type} - | {:frames, list} - | {:tick, non_neg_integer} - | {:speed, non_neg_integer} - | {:delay_ms, non_neg_integer} - | {:loop, non_neg_integer | :infinite} - - @spec new(opts :: list(animation_opt)) :: Animation.t() - def new(opts) do - animation_type = Keyword.fetch!(opts, :type) - frames = Keyword.get(opts, :frames, []) - - %Animation{ - type: animation_type, - tick: opts[:tick] || 0, - speed: opts[:speed] || 100, - delay_ms: opts[:delay_ms] || 17, - loop: opts[:loop] || :infinite, - frames: frames, - next_frame: List.first(frames) - } - end - - @doc """ - Updates the state of an animation with the next tick of animation. - """ - @spec next_frame(animation :: Animation.t()) :: Animation.t() - def next_frame(animation) do - next_frame = animation.type.next_frame(animation) - %Animation{animation | next_frame: next_frame, tick: animation.tick + 1} - end - - @doc """ - Returns the frame count of a given animation, - - Note: this function returns :infinite for dynamic animations. - """ - @spec frame_count(animation :: Animation.t()) :: non_neg_integer | :infinite - def frame_count(%{loop: :infinite}), do: :infinite - - def frame_count(animation), do: length(animation.frames) * animation.loop - - @doc """ - Returns the expected duration of a given animation. - - Note: this function returns :infinite for dynamic animations. - """ - @spec duration(animation :: Animation.t()) :: non_neg_integer | :infinite - def duration(%{loop: :infinite}), do: :infinite - - def duration(animation), do: frame_count(animation) * animation.delay_ms -end diff --git a/lib/rgb_matrix/animation/cycle_all.ex b/lib/rgb_matrix/animation/cycle_all.ex deleted file mode 100644 index 11d3d97..0000000 --- a/lib/rgb_matrix/animation/cycle_all.ex +++ /dev/null @@ -1,28 +0,0 @@ -defmodule RGBMatrix.Animation.CycleAll do - @moduledoc """ - Cycles hue of all keys. - """ - - alias Chameleon.HSV - - alias RGBMatrix.{Animation, Frame} - - import RGBMatrix.Utils, only: [mod: 2] - - use Animation - - @impl Animation - def next_frame(animation) do - %Animation{tick: tick, speed: speed} = animation - time = div(tick * speed, 100) - - hue = mod(time, 360) - color = HSV.new(hue, 100, 100) - - # FIXME: no reaching into Xebow namespace - pixels = Xebow.Utils.pixels() - pixel_colors = Enum.map(pixels, fn {_x, _y} -> color end) - - Frame.new(pixels, pixel_colors) - end -end diff --git a/lib/rgb_matrix/animation/cycle_left_to_right.ex b/lib/rgb_matrix/animation/cycle_left_to_right.ex deleted file mode 100644 index 88d91f2..0000000 --- a/lib/rgb_matrix/animation/cycle_left_to_right.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule RGBMatrix.Animation.CycleLeftToRight do - @moduledoc """ - Cycles hue left to right. - """ - - alias Chameleon.HSV - - alias RGBMatrix.{Animation, Frame} - - import RGBMatrix.Utils, only: [mod: 2] - - use Animation - - @impl Animation - def next_frame(animation) do - %Animation{tick: tick, speed: speed} = animation - time = div(tick * speed, 100) - - # FIXME: no reaching into Xebow namespace - pixels = Xebow.Utils.pixels() - - pixel_colors = - for {x, _y} <- pixels do - hue = mod(x * 10 - time, 360) - HSV.new(hue, 100, 100) - end - - Frame.new(pixels, pixel_colors) - end -end diff --git a/lib/rgb_matrix/animation/pinwheel.ex b/lib/rgb_matrix/animation/pinwheel.ex deleted file mode 100644 index 4149c0e..0000000 --- a/lib/rgb_matrix/animation/pinwheel.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule RGBMatrix.Animation.Pinwheel do - @moduledoc """ - Cycles hue in a pinwheel pattern. - """ - - alias Chameleon.HSV - - alias RGBMatrix.{Animation, Frame} - - import RGBMatrix.Utils, only: [mod: 2] - - use Animation - - @center %{ - x: 1, - y: 1.5 - } - - @impl Animation - def next_frame(animation) do - %Animation{tick: tick, speed: speed} = animation - time = div(tick * speed, 100) - - # FIXME: no reaching into Xebow namespace - pixels = Xebow.Utils.pixels() - - pixel_colors = - for {x, y} <- pixels do - dx = x - @center.x - dy = y - @center.y - - hue = mod(atan2_8(dy, dx) + time, 360) - - HSV.new(hue, 100, 100) - end - - Frame.new(pixels, pixel_colors) - end - - defp atan2_8(x, y) do - atan = :math.atan2(x, y) - - trunc((atan + :math.pi()) * 359 / (2 * :math.pi())) - end -end diff --git a/lib/rgb_matrix/animation/static.ex b/lib/rgb_matrix/animation/static.ex deleted file mode 100644 index d28ae5b..0000000 --- a/lib/rgb_matrix/animation/static.ex +++ /dev/null @@ -1,114 +0,0 @@ -defmodule RGBMatrix.Animation.Static do - @moduledoc """ - Pre-defined animations that run as a one-shot or on a loop. - - The `RGBMatrix.Animation.Static` animation type is used to define animations with a pre-defined set of frames. These - animations can be played continiously or on a loop. This behavior is controlled by the `:loop` field on the animation. - A value of `:infinite` means to play the animation continuously, while a value greater than 0 means to loop the animation - `:loop` times. - - Note that the playback speed of these animations is controlled by the `:delay_ms` field in the animation struct. The - `:speed` field not used when rendering these animations. - - ## Examples - - ### Play a random color on each pixel for 10 frames, continuously. - ``` - gen_map = fn -> - Enum.into(Xebow.Utils.pixels(), %{}, fn pixel -> - {pixel, Chameleon.HSV.new(:random.uniform(360), 100, 100)} - end) - end - - generator = fn -> struct!(RGBMatrix.Frame, pixel_map: gen_map.()) end - frames = Stream.repeatedly(generator) |> Enum.take(10) - - animation = - %RGBMatrix.Animation{ - delay_ms: 100, - frames: frames, - tick: 0, - loop: :infinite, - type: RGBMatrix.Animation.Static - } - - Xebow.LEDs.play_animation(animation) - ``` - - ### Play a pre-defined animation, three times - ``` - frames = [ - %RGBMatrix.Frame{ - pixel_map: %{ - {0, 0} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {0, 1} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {0, 2} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {0, 3} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {1, 0} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {1, 1} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {1, 2} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {1, 3} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {2, 0} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {2, 1} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {2, 2} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {2, 3} => %Chameleon.HSV{h: 100, s: 100, v: 100} - } - }, - %RGBMatrix.Frame{ - pixel_map: %{ - {0, 0} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {0, 1} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {0, 2} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {0, 3} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {1, 0} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {1, 1} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {1, 2} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {1, 3} => %Chameleon.HSV{h: 100, s: 100, v: 100}, - {2, 0} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {2, 1} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {2, 2} => %Chameleon.HSV{h: 0, s: 0, v: 0}, - {2, 3} => %Chameleon.HSV{h: 0, s: 0, v: 0} - } - } - ] - - animation = %RGBMatrix.Animation{ - delay_ms: 200, - frames: frames, - tick: 0, - loop: 3, - type: RGBMatrix.Animation.Static - } - - Xebow.LEDs.play_animation(animation) - ``` - """ - - alias RGBMatrix.Animation - - import RGBMatrix.Utils, only: [mod: 2] - - use Animation - - @impl Animation - def next_frame(%{loop: :infinite} = animation) do - %Animation{frames: frames, tick: tick} = animation - - index = mod(tick, length(frames)) - Enum.at(frames, index) - end - - @impl Animation - def next_frame(animation) do - %Animation{frames: frames, tick: tick, loop: loop} = animation - - all_frames = all_frames(frames, loop) - index = mod(tick, length(all_frames)) - Enum.at(all_frames, index) - end - - defp all_frames(frames, loop) do - List.duplicate(frames, loop) - |> List.flatten() - end -end diff --git a/lib/rgb_matrix/effect.ex b/lib/rgb_matrix/effect.ex new file mode 100644 index 0000000..132041f --- /dev/null +++ b/lib/rgb_matrix/effect.ex @@ -0,0 +1,88 @@ +defmodule RGBMatrix.Effect do + alias Layout.LED + + @callback new(leds :: list(LED.t()), config :: any) :: {render_in, any} + @callback render(state :: any, config :: any) :: + {list(RGBMatrix.any_color_model()), render_in, any} + @callback key_pressed(state :: any, config :: any, led :: LED.t()) :: {render_in, any} + + @type t :: %__MODULE__{ + type: type, + config: any, + state: any + } + defstruct [:type, :config, :state] + + defmacro __using__(_) do + quote do + @behaviour RGBMatrix.Effect + end + end + + @type render_in :: non_neg_integer() | :never | :ignore + + @type type :: + __MODULE__.CycleAll + | __MODULE__.HueWave + | __MODULE__.Pinwheel + | __MODULE__.RandomSolid + | __MODULE__.RandomKeypresses + | __MODULE__.SolidColor + | __MODULE__.Breathing + | __MODULE__.SolidReactive + | __MODULE__.Splash + + @doc """ + Returns a list of the available types of animations. + """ + @spec types :: list(type) + def types do + [ + __MODULE__.CycleAll, + __MODULE__.HueWave, + __MODULE__.Pinwheel, + __MODULE__.RandomSolid, + __MODULE__.RandomKeypresses, + __MODULE__.SolidColor, + __MODULE__.Breathing, + __MODULE__.SolidReactive, + __MODULE__.Splash + ] + end + + @doc """ + Returns an effect's initial state. + """ + @spec new(effect_type :: type, leds :: list(LED.t())) :: {render_in, t} + def new(effect_type, leds) do + config_module = Module.concat([effect_type, Config]) + effect_config = config_module.new() + {render_in, effect_state} = effect_type.new(leds, effect_config) + + effect = %__MODULE__{ + type: effect_type, + config: effect_config, + state: effect_state + } + + {render_in, effect} + end + + @doc """ + Returns the next state of an effect based on its current state. + """ + @spec render(effect :: t) :: {list(RGBMatrix.any_color_model()), render_in, t} + def render(effect) do + {colors, render_in, effect_state} = effect.type.render(effect.state, effect.config) + {colors, render_in, %{effect | state: effect_state}} + end + + @doc """ + Sends a key pressed event to an effect. + """ + @spec key_pressed(effect :: t, led :: LED.t()) :: {render_in, t} + def key_pressed(effect, led) do + {render_in, effect_state} = effect.type.key_pressed(effect.state, effect.config, led) + {render_in, %{effect | state: effect_state}} + end +end diff --git a/lib/rgb_matrix/effect/breathing.ex b/lib/rgb_matrix/effect/breathing.ex new file mode 100644 index 0000000..2df71ba --- /dev/null +++ b/lib/rgb_matrix/effect/breathing.ex @@ -0,0 +1,45 @@ +defmodule RGBMatrix.Effect.Breathing do + @moduledoc """ + Single hue brightness cycling. + """ + + alias Chameleon.HSV + alias RGBMatrix.Effect + + use Effect + + defmodule Config do + use RGBMatrix.Effect.Config + end + + defmodule State do + defstruct [:color, :tick, :speed, :led_ids] + end + + @delay_ms 17 + + @impl true + def new(leds, _config) do + # TODO: configurable base color + color = HSV.new(40, 100, 100) + led_ids = Enum.map(leds, & &1.id) + {0, %State{color: color, tick: 0, speed: 100, led_ids: led_ids}} + end + + @impl true + def render(state, _config) do + %{color: base_color, tick: tick, speed: speed, led_ids: led_ids} = state + + value = trunc(abs(:math.sin(tick * speed / 5_000)) * base_color.v) + color = HSV.new(base_color.h, base_color.s, value) + + colors = Enum.map(led_ids, fn id -> {id, color} end) + + {colors, @delay_ms, %{state | tick: tick + 1}} + end + + @impl true + def key_pressed(state, _config, _led) do + {:ignore, state} + end +end diff --git a/lib/rgb_matrix/effect/config.ex b/lib/rgb_matrix/effect/config.ex new file mode 100644 index 0000000..d7ad6b6 --- /dev/null +++ b/lib/rgb_matrix/effect/config.ex @@ -0,0 +1,83 @@ +defmodule RGBMatrix.Effect.Config do + @callback schema() :: keyword(any) + @callback new(%{optional(atom) => any}) :: struct + @callback update(struct, %{optional(atom) => any}) :: struct + + @types %{ + integer: RGBMatrix.Effect.Config.Integer, + option: RGBMatrix.Effect.Config.Option + } + + defmacro __using__(_) do + quote do + import RGBMatrix.Effect.Config + + Module.register_attribute(__MODULE__, :fields, + accumulate: true, + persist: false + ) + + @before_compile RGBMatrix.Effect.Config + end + end + + defmacro field(name, type, opts \\ []) do + type = Map.fetch!(@types, type) + type_schema = Macro.escape(struct!(type, opts)) + + quote do + @fields {unquote(name), unquote(type_schema)} + end + end + + defmacro __before_compile__(env) do + schema = Module.get_attribute(env.module, :fields) + keys = Keyword.keys(schema) + schema = Macro.escape(schema) + + quote do + @behaviour RGBMatrix.Effect.Config + + @enforce_keys unquote(keys) + defstruct unquote(keys) + + @impl RGBMatrix.Effect.Config + def schema do + unquote(schema) + end + + @impl RGBMatrix.Effect.Config + def new(params \\ %{}) do + schema() + |> Map.new(fn {key, %mod{} = type} -> + value = Map.get(params, key, type.default) + + case mod.validate(type, value) do + :ok -> {key, value} + :error -> value_error!(value, key) + end + end) + |> (&struct!(__MODULE__, &1)).() + end + + @impl RGBMatrix.Effect.Config + def update(config, params) do + schema = schema() + + Enum.reduce(params, config, fn {key, value}, config -> + %mod{} = type = Keyword.fetch!(schema, key) + if mod.validate(type, value) == :error, do: value_error!(value, key) + + Map.put(config, key, value) + end) + end + + defp value_error!(value, key) do + message = + "#{__MODULE__}: value `#{inspect(value)}` is invalid for config option `#{key}`." + + raise ArgumentError, message: message + end + end + end +end diff --git a/lib/rgb_matrix/effect/config/integer.ex b/lib/rgb_matrix/effect/config/integer.ex new file mode 100644 index 0000000..3d206dc --- /dev/null +++ b/lib/rgb_matrix/effect/config/integer.ex @@ -0,0 +1,48 @@ +defmodule RGBMatrix.Effect.Config.Integer do + @enforce_keys [:default, :min, :max] + defstruct [:default, :min, :max, step: 1] + + import RGBMatrix.Utils, only: [mod: 2] + + def validate(integer, value) do + if value >= integer.min && + value <= integer.max && + mod(value, integer.step) == 0 do + :ok + else + :error + end + end + + def cast(integer, bin_value) when is_binary(bin_value) do + case Integer.parse(bin_value) do + {value, ""} -> + cast(integer, value) + + _else -> + case Float.parse(bin_value) do + {value, ""} -> cast(integer, value) + _else -> :error + end + end + end + + def cast(integer, value) when is_float(value) do + int_value = trunc(value) + + if int_value == value do + cast(integer, int_value) + else + :error + end + end + + def cast(integer, value) when is_integer(value) do + case validate(integer, value) do + :ok -> {:ok, value} + :error -> :error + end + end + + def cast(_integer, _value), do: :error +end diff --git a/lib/rgb_matrix/effect/config/option.ex b/lib/rgb_matrix/effect/config/option.ex new file mode 100644 index 0000000..1771348 --- /dev/null +++ b/lib/rgb_matrix/effect/config/option.ex @@ -0,0 +1,30 @@ +defmodule RGBMatrix.Effect.Config.Option do + @enforce_keys [:default, :options] + defstruct [:default, :options] + + def validate(option, value) do + if value in option.options do + :ok + else + :error + end + end + + def cast(option, bin_value) when is_binary(bin_value) do + try do + value = String.to_existing_atom(bin_value) + cast(option, value) + rescue + ArgumentError -> :error + end + end + + def cast(option, value) when is_atom(value) do + case validate(option, value) do + :ok -> {:ok, value} + :error -> :error + end + end + + def cast(_option, _value), do: :error +end diff --git a/lib/rgb_matrix/effect/cycle_all.ex b/lib/rgb_matrix/effect/cycle_all.ex new file mode 100644 index 0000000..9365b13 --- /dev/null +++ b/lib/rgb_matrix/effect/cycle_all.ex @@ -0,0 +1,46 @@ +defmodule RGBMatrix.Effect.CycleAll do + @moduledoc """ + Cycles the hue of all LEDs at the same time. + """ + + alias Chameleon.HSV + alias RGBMatrix.Effect + + use Effect + + import RGBMatrix.Utils, only: [mod: 2] + + defmodule Config do + use RGBMatrix.Effect.Config + end + + defmodule State do + defstruct [:tick, :speed, :led_ids] + end + + @delay_ms 17 + + @impl true + def new(leds, _config) do + led_ids = Enum.map(leds, & &1.id) + {0, %State{tick: 0, speed: 100, led_ids: led_ids}} + end + + @impl true + def render(state, _config) do + %{tick: tick, speed: speed, led_ids: led_ids} = state + + time = div(tick * speed, 100) + hue = mod(time, 360) + color = HSV.new(hue, 100, 100) + + colors = Enum.map(led_ids, fn id -> {id, color} end) + + {colors, @delay_ms, %{state | tick: tick + 1}} + end + + @impl true + def key_pressed(state, _config, _led) do + {:ignore, state} + end +end diff --git a/lib/rgb_matrix/effect/hue_wave.ex b/lib/rgb_matrix/effect/hue_wave.ex new file mode 100644 index 0000000..9d80583 --- /dev/null +++ b/lib/rgb_matrix/effect/hue_wave.ex @@ -0,0 +1,103 @@ +defmodule RGBMatrix.Effect.HueWave do + @moduledoc """ + Creates a wave of shifting hue that moves across the matrix. + """ + + alias Chameleon.HSV + alias Layout.LED + alias RGBMatrix.Effect + + use Effect + + import RGBMatrix.Utils, only: [mod: 2] + + defmodule Config do + use RGBMatrix.Effect.Config + + @doc name: "Speed", + description: """ + Controls the speed at which the wave moves across the matrix. + """ + field(:speed, :integer, default: 4, min: 0, max: 32) + + @doc name: "Width", + description: """ + The rate of change of the wave, higher values means it's more spread out. + """ + field(:width, :integer, default: 20, min: 10, max: 100, step: 10) + + @doc name: "Direction", + description: """ + The direction the wave travels across the matrix. + """ + field(:direction, :option, + default: :right, + options: [ + :right, + :left, + :up, + :down + ] + ) + end + + defmodule State do + defstruct [:tick, :leds, :steps] + end + + @delay_ms 17 + + @impl true + def new(leds, config) do + steps = 360 / config.width + {0, %State{tick: 0, leds: leds, steps: steps}} + end + + @impl true + def render(state, config) do + %{tick: tick, leds: leds, steps: _steps} = state + %{speed: speed, direction: direction} = config + + # TODO: fixme + steps = 360 / config.width + + time = div(tick * speed, 5) + + colors = render_colors(leds, steps, time, direction) + + {colors, @delay_ms, %{state | tick: tick + 1}} + end + + defp render_colors(leds, steps, time, :right) do + for %LED{id: id, x: x} <- leds do + hue = mod(trunc(x * steps) - time, 360) + {id, HSV.new(hue, 100, 100)} + end + end + + defp render_colors(leds, steps, time, :left) do + for %LED{id: id, x: x} <- leds do + hue = mod(trunc(x * steps) + time, 360) + {id, HSV.new(hue, 100, 100)} + end + end + + defp render_colors(leds, steps, time, :up) do + for %LED{id: id, y: y} <- leds do + hue = mod(trunc(y * steps) + time, 360) + {id, HSV.new(hue, 100, 100)} + end + end + + defp render_colors(leds, steps, time, :down) do + for %LED{id: id, y: y} <- leds do + hue = mod(trunc(y * steps) - time, 360) + {id, HSV.new(hue, 100, 100)} + end + end + + @impl true + def key_pressed(state, _config, _led) do + {:ignore, state} + end +end diff --git a/lib/rgb_matrix/effect/pinwheel.ex b/lib/rgb_matrix/effect/pinwheel.ex new file mode 100644 index 0000000..4ef9143 --- /dev/null +++ b/lib/rgb_matrix/effect/pinwheel.ex @@ -0,0 +1,68 @@ +defmodule RGBMatrix.Effect.Pinwheel do + @moduledoc """ + Cycles hue in a pinwheel pattern. + """ + + alias Chameleon.HSV + alias Layout.LED + alias RGBMatrix.Effect + + use Effect + + import RGBMatrix.Utils, only: [mod: 2] + + defmodule Config do + use RGBMatrix.Effect.Config + end + + defmodule State do + defstruct [:tick, :speed, :leds, :center] + end + + @delay_ms 17 + + @impl true + def new(leds, _config) do + {0, %State{tick: 0, speed: 100, leds: leds, center: determine_center(leds)}} + end + + defp determine_center(leds) do + {%{x: min_x}, %{x: max_x}} = Enum.min_max_by(leds, & &1.x) + {%{y: min_y}, %{y: max_y}} = Enum.min_max_by(leds, & &1.y) + + %{ + x: (max_x - min_x) / 2 + min_x, + y: (max_y - min_y) / 2 + min_y + } + end + + @impl true + def render(state, _config) do + %{tick: tick, speed: speed, leds: leds, center: center} = state + + time = div(tick * speed, 100) + + colors = + for %LED{id: id, x: x, y: y} <- leds do + dx = x - center.x + dy = y - center.y + + hue = mod(atan2_8(dy, dx) + time, 360) + + {id, HSV.new(hue, 100, 100)} + end + + {colors, @delay_ms, %{state | tick: tick + 1}} + end + + defp atan2_8(x, y) do + atan = :math.atan2(x, y) + + trunc((atan + :math.pi()) * 359 / (2 * :math.pi())) + end + + @impl true + def key_pressed(state, _config, %LED{x: x, y: y}) do + {:ignore, %{state | center: %{x: x, y: y}}} + end +end diff --git a/lib/rgb_matrix/effect/random_keypresses.ex b/lib/rgb_matrix/effect/random_keypresses.ex new file mode 100644 index 0000000..87ff149 --- /dev/null +++ b/lib/rgb_matrix/effect/random_keypresses.ex @@ -0,0 +1,52 @@ +defmodule RGBMatrix.Effect.RandomKeypresses do + @moduledoc """ + Changes every key pressed to a random color. + """ + + alias Chameleon.HSV + alias RGBMatrix.Effect + + use Effect + + defmodule Config do + use RGBMatrix.Effect.Config + end + + defmodule State do + defstruct [:led_ids, :dirty] + end + + @impl true + def new(leds, _config) do + led_ids = Enum.map(leds, & &1.id) + + {0, + %State{ + led_ids: led_ids, + # NOTE: as to not conflict with possible led ID of `:all` + dirty: {:all} + }} + end + + @impl true + def render(state, _config) do + %{led_ids: led_ids, dirty: dirty} = state + + colors = + case dirty do + {:all} -> Enum.map(led_ids, fn id -> {id, random_color()} end) + id -> [{id, random_color()}] + end + + {colors, :never, state} + end + + defp random_color do + HSV.new((:rand.uniform() * 360) |> trunc(), 100, 100) + end + + @impl true + def key_pressed(state, _config, led) do + {0, %{state | dirty: led.id}} + end +end diff --git a/lib/rgb_matrix/effect/random_solid.ex b/lib/rgb_matrix/effect/random_solid.ex new file mode 100644 index 0000000..8ef619c --- /dev/null +++ b/lib/rgb_matrix/effect/random_solid.ex @@ -0,0 +1,43 @@ +defmodule RGBMatrix.Effect.RandomSolid do + @moduledoc """ + A random solid color fills the entire matrix and changes every key-press. + """ + + alias Chameleon.HSV + alias RGBMatrix.Effect + + use Effect + + defmodule Config do + use RGBMatrix.Effect.Config + end + + defmodule State do + defstruct [:led_ids] + end + + @impl true + def new(leds, _config) do + {0, %State{led_ids: Enum.map(leds, & &1.id)}} + end + + @impl true + def render(state, _config) do + %{led_ids: led_ids} = state + + color = random_color() + + colors = Enum.map(led_ids, fn id -> {id, color} end) + + {colors, :never, state} + end + + defp random_color do + HSV.new((:rand.uniform() * 360) |> trunc(), 100, 100) + end + + @impl true + def key_pressed(state, _config, _led) do + {0, state} + end +end diff --git a/lib/rgb_matrix/effect/solid_color.ex b/lib/rgb_matrix/effect/solid_color.ex new file mode 100644 index 0000000..d8367d8 --- /dev/null +++ b/lib/rgb_matrix/effect/solid_color.ex @@ -0,0 +1,39 @@ +defmodule RGBMatrix.Effect.SolidColor do + @moduledoc """ + All LEDs are a solid color. + """ + + alias Chameleon.HSV + alias RGBMatrix.Effect + + use Effect + + defmodule Config do + use RGBMatrix.Effect.Config + end + + defmodule State do + defstruct [:color, :led_ids] + end + + @impl true + def new(leds, _config) do + # TODO: configurable base color + color = HSV.new(120, 100, 100) + {0, %State{color: color, led_ids: Enum.map(leds, & &1.id)}} + end + + @impl true + def render(state, _config) do + %{color: color, led_ids: led_ids} = state + + colors = Enum.map(led_ids, fn id -> {id, color} end) + + {colors, :never, state} + end + + @impl true + def key_pressed(state, _config, _led) do + {:ignore, state} + end +end diff --git a/lib/rgb_matrix/effect/solid_reactive.ex b/lib/rgb_matrix/effect/solid_reactive.ex new file mode 100644 index 0000000..0936491 --- /dev/null +++ b/lib/rgb_matrix/effect/solid_reactive.ex @@ -0,0 +1,113 @@ +defmodule RGBMatrix.Effect.SolidReactive do + @moduledoc """ + Static single hue, pulses keys hit to shifted hue then fades to current hue. + """ + + alias Chameleon.HSV + alias RGBMatrix.Effect + + use Effect + + import RGBMatrix.Utils, only: [mod: 2] + + defmodule Config do + use RGBMatrix.Effect.Config + + @doc name: "Speed", + description: """ + The speed at which the hue shifts back to base. + """ + field :speed, :integer, default: 4, min: 0, max: 32 + + @doc name: "Distance", + description: """ + The distance that the hue shifts on key-press. + """ + field :distance, :integer, default: 180, min: 0, max: 360, step: 10 + + @doc name: "Direction", + description: """ + The direction (through the color wheel) that the hue shifts on key-press. + """ + field :direction, :option, + default: :random, + options: [ + :random, + :negative, + :positive + ] + end + + defmodule State do + defstruct [:first_render, :paused, :tick, :color, :leds, :hits] + end + + @delay_ms 17 + + @impl true + def new(leds, _config) do + # TODO: configurable base color + color = HSV.new(190, 100, 100) + {0, %State{first_render: true, paused: false, tick: 0, color: color, leds: leds, hits: %{}}} + end + + @impl true + def render(%{first_render: true} = state, _config) do + %{color: color, leds: leds} = state + + colors = Enum.map(leds, &{&1.id, color}) + + {colors, :never, %{state | first_render: false, paused: true}} + end + + def render(%{paused: true} = state, _config), + do: {[], :never, state} + + def render(state, config) do + %{tick: tick, color: color, leds: leds, hits: hits} = state + %{speed: _speed, distance: distance} = config + + {colors, hits} = + Enum.map_reduce(leds, hits, fn led, hits -> + case hits do + %{^led => {hit_tick, direction_modifier}} -> + # TODO: take speed into account + if tick - hit_tick >= distance do + {{led.id, color}, Map.delete(hits, led)} + else + hue = mod(color.h + (tick - hit_tick - distance) * direction_modifier, 360) + {{led.id, HSV.new(hue, color.s, color.v)}, hits} + end + + _else -> + {{led.id, color}, hits} + end + end) + + # FIXME: leaves color 1 away from base + # TODO: we can optimize this by rewriting the above instead of filtering here: + colors = + Enum.filter(colors, fn {_id, this_color} -> + this_color != color + end) + + {colors, @delay_ms, %{state | tick: tick + 1, hits: hits, paused: hits == %{}}} + end + + @impl true + def key_pressed(state, config, led) do + direction = direction_modifier(config.direction) + + render_in = + case state.paused do + true -> 0 + false -> :ignore + end + + {render_in, %{state | paused: false, hits: Map.put(state.hits, led, {state.tick, direction})}} + end + + defp direction_modifier(:random), do: Enum.random([-1, 1]) + defp direction_modifier(:negative), do: -1 + defp direction_modifier(:positive), do: 1 +end diff --git a/lib/rgb_matrix/effect/splash.ex b/lib/rgb_matrix/effect/splash.ex new file mode 100644 index 0000000..d01d419 --- /dev/null +++ b/lib/rgb_matrix/effect/splash.ex @@ -0,0 +1,94 @@ +defmodule RGBMatrix.Effect.Splash do + @moduledoc """ + Full gradient & value pulse away from key hits then fades value out. + """ + + alias Chameleon.HSV + alias RGBMatrix.Effect + + use Effect + + # import RGBMatrix.Utils, only: [mod: 2] + + defmodule Config do + use RGBMatrix.Effect.Config + end + + defmodule State do + defstruct [:tick, :leds, :hits] + end + + @delay_ms 17 + + @impl true + def new(leds, _config) do + {0, %State{tick: 0, leds: leds, hits: %{}}} + end + + @impl true + def render(state, _config) do + %{tick: tick, leds: leds, hits: hits} = state + + {colors, hits} = + Enum.map_reduce(leds, hits, fn led, hits -> + color = HSV.new(0, 100, 0) + + {hits, color} = + Enum.reduce(hits, {hits, color}, fn {hit_led, hit_tick}, {hits, color} -> + dx = led.x - hit_led.x + dy = led.y - hit_led.y + dist = :math.sqrt(dx * dx + dy * dy) + color = effect(color, dist, tick - hit_tick) + + {hits, color} + end) + + {{led.id, color}, hits} + end) + + # for (uint8_t i = led_min; i < led_max; i++) { + # RGB_MATRIX_TEST_LED_FLAGS(); + # HSV hsv = rgb_matrix_config.hsv; + # hsv.v = 0; + # for (uint8_t j = start; j < count; j++) { + # int16_t dx = g_led_config.point[i].x - g_last_hit_tracker.x[j]; + # int16_t dy = g_led_config.point[i].y - g_last_hit_tracker.y[j]; + # uint8_t dist = sqrt16(dx * dx + dy * dy); + # uint16_t tick = scale16by8(g_last_hit_tracker.tick[j], rgb_matrix_config.speed); + # hsv = effect_func(hsv, dx, dy, dist, tick); + # } + # hsv.v = scale8(hsv.v, rgb_matrix_config.hsv.v); + # RGB rgb = hsv_to_rgb(hsv); + # rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); + # } + + {colors, @delay_ms, %{state | tick: tick + 1, hits: hits}} + end + + def effect(color, dist, hit_tick) do + # uint16_t effect = tick - dist; + # if (effect > 255) effect = 255; + # hsv.h += effect; + # hsv.v = qadd8(hsv.v, 255 - effect); + # return hsv; + + # effect = trunc(if effect > 360, do: 360, else: effect) + + value = 100 - hit_tick - trunc(dist * 20) + value = if value < 0, do: 0, else: value + + %{color | h: 0, v: value} + + # if dist < 5 do + # HSV.new(0, 100, 100) + # else + # color + # end + end + + @impl true + def key_pressed(state, _config, led) do + # {:ignore, %{state | hits: Map.put(state.hits, led, state.tick)}} + {:ignore, %{state | hits: %{led => state.tick}}} + end +end diff --git a/lib/rgb_matrix/engine.ex b/lib/rgb_matrix/engine.ex index 542e3bc..f192e28 100644 --- a/lib/rgb_matrix/engine.ex +++ b/lib/rgb_matrix/engine.ex @@ -1,17 +1,19 @@ +require Logger + defmodule RGBMatrix.Engine do @moduledoc """ - Renders [`Animation`](`RGBMatrix.Animation`)s and outputs - [`Frame`](`RGBMatrix.Frame`)s to be displayed. + Renders [`Effect`](`RGBMatrix.Effect`)s and outputs colors to be displayed by + [`Paintable`](`RGBMatrix.Paintable`)s. """ use GenServer alias Layout.LED - alias RGBMatrix.Animation + alias RGBMatrix.Effect defmodule State do @moduledoc false - defstruct [:leds, :animation, :paintables] + defstruct [:leds, :effect, :paintables, :last_frame, :timer] end # Client @@ -24,35 +26,26 @@ defmodule RGBMatrix.Engine do This function accepts the following arguments as a tuple: - `leds` - The list of LEDs to be painted on. - - `initial_animation` - The animation that plays when the engine starts. - - `paintables` - A list of modules to output `RGBMatrix.Frame` to that implement - the `RGBMatrix.Paintable` behavior. If you want to register your paintables + - `initial_effect` - The Effect type to initialize and play when the engine + starts. + - `paintables` - A list of modules to output colors to that implement the + `RGBMatrix.Paintable` behavior. If you want to register your paintables dynamically, set this to an empty list `[]`. """ @spec start_link( - {leds :: [LED.t()], initial_animation :: Animation.t(), paintables :: list(module)} + {leds :: [LED.t()], initial_effect_type :: Effect.type(), paintables :: list(module)} ) :: GenServer.on_start() - def start_link({leds, initial_animation, paintables}) do - GenServer.start_link(__MODULE__, {leds, initial_animation, paintables}, name: __MODULE__) + def start_link({leds, initial_effect_type, paintables}) do + GenServer.start_link(__MODULE__, {leds, initial_effect_type, paintables}, name: __MODULE__) end @doc """ - Play the given animation. - - Note that the animation can be played synchronously by passing `:false` for the `:async` option. However, only - looping (animations with `:loop` >= 1) animations may be played this way. This is to ensure that the caller is not - blocked forever. + Sets the given effect as the currently active effect. """ - @spec play_animation(animation :: Animation.t(), opts :: keyword()) :: :ok - def play_animation(animation, opts \\ []) do - async? = Keyword.get(opts, :async, true) - - if async? do - GenServer.cast(__MODULE__, {:play_animation, animation}) - else - GenServer.call(__MODULE__, {:play_animation, animation}) - end + @spec set_effect(effect_type :: Effect.type(), opts :: keyword()) :: :ok + def set_effect(effect_type) do + GenServer.cast(__MODULE__, {:set_effect, effect_type}) end @doc """ @@ -76,16 +69,17 @@ defmodule RGBMatrix.Engine do # Server @impl GenServer - def init({leds, initial_animation, paintables}) do - send(self(), :get_next_frame) + def init({leds, initial_effect_type, paintables}) do + black = Chameleon.HSV.new(0, 0, 0) + frame = Map.new(leds, &{&1.id, black}) - initial_state = %State{leds: leds, paintables: %{}} + initial_state = %State{leds: leds, last_frame: frame, paintables: %{}} state = Enum.reduce(paintables, initial_state, fn paintable, state -> add_paintable(paintable, state) end) - |> set_animation(initial_animation) + |> set_effect(initial_effect_type) {:ok, state} end @@ -100,64 +94,67 @@ defmodule RGBMatrix.Engine do %State{state | paintables: paintables} end - defp set_animation(state, animation) do - %State{state | animation: animation} - end + defp set_effect(state, effect_type) do + {render_in, effect} = Effect.new(effect_type, state.leds) - @impl GenServer - def handle_info(:get_next_frame, state) do - animation = Animation.next_frame(state.animation) + state = schedule_next_render(state, render_in) - state.paintables - |> Map.values() - |> Enum.each(fn paint_fn -> - paint_fn.(animation.next_frame) - end) + %State{state | effect: effect} + end - Process.send_after(self(), :get_next_frame, animation.delay_ms) - {:noreply, set_animation(state, animation)} + defp schedule_next_render(state, :ignore) do + state end - @impl GenServer - def handle_info({:reset_animation, reset_animation}, state) do - {:noreply, set_animation(state, reset_animation)} + defp schedule_next_render(state, :never) do + cancel_timer(state) end - @impl GenServer - def handle_info({:reply, from, reset_animation}, state) do - GenServer.reply(from, :ok) + defp schedule_next_render(state, 0) do + send(self(), :render) + cancel_timer(state) + end - {:noreply, set_animation(state, reset_animation)} + defp schedule_next_render(state, ms) when is_integer(ms) and ms > 0 do + state = cancel_timer(state) + %{state | timer: Process.send_after(self(), :render, ms)} end - @impl GenServer - def handle_cast({:play_animation, %{loop: loop} = animation}, state) - when is_integer(loop) and loop >= 1 do - current_animation = state.animation - expected_duration = Animation.duration(animation) - Process.send_after(self(), {:reset_animation, current_animation}, expected_duration) + defp cancel_timer(%{timer: nil} = state), do: state - {:noreply, set_animation(state, animation)} + defp cancel_timer(state) do + Process.cancel_timer(state.timer) + %{state | timer: nil} end - @impl GenServer - def handle_cast({:play_animation, %{loop: 0} = _animation}, state) do + @impl true + def handle_info(:render, state) do + {new_colors, render_in, effect} = Effect.render(state.effect) + + frame = update_frame(state.last_frame, new_colors) + + state.paintables + |> Map.values() + |> Enum.each(fn paint_fn -> + paint_fn.(frame) + end) + + state = schedule_next_render(state, render_in) + state = %State{state | effect: effect, last_frame: frame} + {:noreply, state} end - @impl GenServer - def handle_cast({:play_animation, animation}, state) do - {:noreply, set_animation(state, animation)} + defp update_frame(frame, new_colors) do + Enum.reduce(new_colors, frame, fn {led_id, color}, frame -> + Map.put(frame, led_id, color) + end) end @impl GenServer - def handle_call({:play_animation, %{loop: loop} = animation}, from, state) - when is_integer(loop) and loop >= 1 do - current_animation = state.animation - duration = Animation.duration(animation) - Process.send_after(self(), {:reply, from, current_animation}, duration) - - {:noreply, set_animation(state, animation)} + def handle_cast({:set_effect, effect_type}, state) do + state = set_effect(state, effect_type) + {:noreply, state} end @impl GenServer diff --git a/lib/rgb_matrix/frame.ex b/lib/rgb_matrix/frame.ex deleted file mode 100644 index 5fcf988..0000000 --- a/lib/rgb_matrix/frame.ex +++ /dev/null @@ -1,34 +0,0 @@ -defmodule RGBMatrix.Frame do - @moduledoc """ - Provides a data structure and functions for working with animation frames. - - An animation frame is a mapping of pixel coordinates to their corresponding color. An animation can be composed of a - list of frames or each frame can be dynamically generated based on the tick of some animation. - """ - - alias RGBMatrix.Pixel - - @type pixel_map :: %{required(Pixel.t()) => Pixel.color()} - - @type t :: %__MODULE__{ - pixel_map: pixel_map() - } - - defstruct [:pixel_map] - - @spec new(pixels :: list(Pixel.t()), pixel_colors :: list(Pixel.color())) :: - t() - def new(pixels, pixel_colors) do - pixel_map = - Enum.zip(pixels, pixel_colors) - |> Enum.into(%{}) - - %__MODULE__{pixel_map: pixel_map} - end - - @spec solid_color(pixels :: list(Pixel.t()), color :: Pixel.color()) :: t() - def solid_color(pixels, color) do - pixel_colors = List.duplicate(color, length(pixels)) - new(pixels, pixel_colors) - end -end diff --git a/lib/rgb_matrix/paintable.ex b/lib/rgb_matrix/paintable.ex index ad58a3d..12b6e01 100644 --- a/lib/rgb_matrix/paintable.ex +++ b/lib/rgb_matrix/paintable.ex @@ -1,14 +1,18 @@ defmodule RGBMatrix.Paintable do @moduledoc """ - A paintable module controls physical pixels. + A paintable module controls physical LEDs. """ + alias Layout.LED + + @type frame :: %{required(LED.id()) => RGBMatrix.any_color_model()} + @doc """ - Returns a function that can be called to paint the pixels for a given frame. + Returns a function that can be called to paint the LEDs for a given frame. The anonymous function's return value is unused. This callback makes any hardware implementation details opaque to the caller, - while allowing the paintable to retain control of the physical pixels. + while allowing the paintable to retain control of the physical LEDs. """ - @callback get_paint_fn :: (frame :: RGBMatrix.Frame.t() -> any) + @callback get_paint_fn :: (frame -> any) end diff --git a/lib/rgb_matrix/pixel.ex b/lib/rgb_matrix/pixel.ex deleted file mode 100644 index daa912a..0000000 --- a/lib/rgb_matrix/pixel.ex +++ /dev/null @@ -1,27 +0,0 @@ -defmodule RGBMatrix.Pixel do - @moduledoc """ - A pixel is a unit that has X and Y coordinates and displays a single color. - """ - - @typedoc """ - A tuple containing the X and Y coordinates of the pixel. - """ - @type t :: {x :: non_neg_integer, y :: non_neg_integer} - - @typedoc """ - The color of the pixel, represented as a `Chameleon.Color` color model. - """ - @type color :: any_color_model - - @typedoc """ - Shorthand for any `Chameleon.Color` color model. - """ - @type any_color_model :: - Chameleon.Color.RGB.t() - | Chameleon.Color.CMYK.t() - | Chameleon.Color.Hex.t() - | Chameleon.Color.HSL.t() - | Chameleon.Color.HSV.t() - | Chameleon.Color.Keyword.t() - | Chameleon.Color.Pantone.t() -end diff --git a/lib/xebow/application.ex b/lib/xebow/application.ex index 962189d..08f8416 100644 --- a/lib/xebow/application.ex +++ b/lib/xebow/application.ex @@ -6,8 +6,7 @@ defmodule Xebow.Application do use Application @leds Xebow.layout() |> Layout.leds() - @animation_type RGBMatrix.Animation.types() |> List.first() - @animation RGBMatrix.Animation.new(type: @animation_type) + @effect_type RGBMatrix.Effect.types() |> List.first() def start(_type, _args) do # See https://hexdocs.pm/elixir/Supervisor.html @@ -40,7 +39,7 @@ defmodule Xebow.Application do # {Xebow.Worker, arg}, Xebow.HIDGadget, Xebow.LEDs, - {RGBMatrix.Engine, {@leds, @animation, [Xebow.LEDs]}}, + {RGBMatrix.Engine, {@leds, @effect_type, [Xebow.LEDs]}}, Xebow.Keyboard ] end diff --git a/lib/xebow/keyboard.ex b/lib/xebow/keyboard.ex index 189d964..347e004 100644 --- a/lib/xebow/keyboard.ex +++ b/lib/xebow/keyboard.ex @@ -17,7 +17,7 @@ defmodule Xebow.Keyboard do use GenServer alias Circuits.GPIO - alias RGBMatrix.{Animation, Frame} + alias RGBMatrix.Effect # maps the physical GPIO pins to key IDs # TODO: re-number these keys so they map to the keyboard in X/Y natural order, @@ -74,7 +74,7 @@ defmodule Xebow.Keyboard do # Layer 2: %{ k001: AFK.Keycode.MFA.new({__MODULE__, :flash, ["red"]}), - k002: AFK.Keycode.MFA.new({__MODULE__, :previous_animation, []}), + k002: AFK.Keycode.MFA.new({__MODULE__, :previous_effect, []}), k003: AFK.Keycode.Transparent.new(), k004: AFK.Keycode.Transparent.new(), k005: AFK.Keycode.Transparent.new(), @@ -82,7 +82,7 @@ defmodule Xebow.Keyboard do k007: AFK.Keycode.Transparent.new(), k008: AFK.Keycode.Transparent.new(), k009: AFK.Keycode.MFA.new({__MODULE__, :flash, ["green"]}), - k010: AFK.Keycode.MFA.new({__MODULE__, :next_animation, []}), + k010: AFK.Keycode.MFA.new({__MODULE__, :next_effect, []}), k011: AFK.Keycode.Transparent.new(), k012: AFK.Keycode.Transparent.new() } @@ -96,19 +96,19 @@ defmodule Xebow.Keyboard do end @doc """ - Cycle to the next animation + Cycle to the next effect """ - @spec next_animation() :: :ok - def next_animation do - GenServer.cast(__MODULE__, :next_animation) + @spec next_effect() :: :ok + def next_effect do + GenServer.cast(__MODULE__, :next_effect) end @doc """ - Cycle to the previous animation + Cycle to the previous effect """ - @spec previous_animation() :: :ok - def previous_animation do - GenServer.cast(__MODULE__, :previous_animation) + @spec previous_effect() :: :ok + def previous_effect do + GenServer.cast(__MODULE__, :previous_effect) end # Server @@ -136,54 +136,51 @@ defmodule Xebow.Keyboard do poll_timer_ms = 15 :timer.send_interval(poll_timer_ms, self(), :update_pin_values) - animations = - Animation.types() - |> Enum.map(&Animation.new(type: &1)) - - {:ok, - %{ - pins: pins, - keyboard_state: keyboard_state, - hid: hid, - animations: animations, - current_animation_index: 0 - }} + state = %{ + pins: pins, + keyboard_state: keyboard_state, + hid: hid, + effect_types: Effect.types(), + current_effect_index: 0 + } + + {:ok, state} end @impl GenServer - def handle_cast(:next_animation, state) do - next_index = state.current_animation_index + 1 + def handle_cast(:next_effect, state) do + next_index = state.current_effect_index + 1 next_index = - case next_index < Enum.count(state.animations) do + case next_index < Enum.count(state.effect_types) do true -> next_index _ -> 0 end - animation = Enum.at(state.animations, next_index) + effect_type = Enum.at(state.effect_types, next_index) - RGBMatrix.Engine.play_animation(animation) + RGBMatrix.Engine.set_effect(effect_type) - state = %{state | current_animation_index: next_index} + state = %{state | current_effect_index: next_index} {:noreply, state} end @impl GenServer - def handle_cast(:previous_animation, state) do - previous_index = state.current_animation_index - 1 + def handle_cast(:previous_effect, state) do + previous_index = state.current_effect_index - 1 previous_index = case previous_index < 0 do - true -> Enum.count(state.animations) - 1 + true -> Enum.count(state.effect_types) - 1 _ -> previous_index end - animation = Enum.at(state.animations, previous_index) + effect_type = Enum.at(state.effect_types, previous_index) - RGBMatrix.Engine.play_animation(animation) + RGBMatrix.Engine.set_effect(effect_type) - state = %{state | current_animation_index: previous_index} + state = %{state | current_effect_index: previous_index} {:noreply, state} end @@ -228,12 +225,6 @@ defmodule Xebow.Keyboard do # Custom Key Functions def flash(color) do - pixels = Xebow.Utils.pixels() - color = Chameleon.Keyword.new(color) - frame = Frame.solid_color(pixels, color) - - animation = Animation.new(type: Animation.Static, frames: [frame], delay_ms: 250, loop: 1) - - RGBMatrix.Engine.play_animation(animation, async: false) + Logger.info("TODO: flash color #{IO.inspect(color)}") end end diff --git a/lib/xebow/leds.ex b/lib/xebow/leds.ex index 3c19e60..d078e83 100644 --- a/lib/xebow/leds.ex +++ b/lib/xebow/leds.ex @@ -1,3 +1,5 @@ +require Logger + defmodule Xebow.LEDs do @moduledoc """ GenServer that interacts with the SPI device that controls the RGB LEDs on the @@ -22,6 +24,22 @@ defmodule Xebow.LEDs do @spi_speed_hz 4_000_000 @sof <<0, 0, 0, 0>> @eof <<255, 255, 255, 255>> + # This is the hardware order that the LED colors need to be sent to the SPI + # device in. The LED IDs are the ones from `Xebow.layout/0`. + @spi_led_order [ + :l001, + :l004, + :l007, + :l010, + :l002, + :l005, + :l008, + :l011, + :l003, + :l006, + :l009, + :l012 + ] # Client @@ -56,9 +74,8 @@ defmodule Xebow.LEDs do defp paint(spidev, frame) do colors = - frame.pixel_map - |> Enum.sort() - |> Enum.map(fn {_cord, color} -> color end) + @spi_led_order + |> Enum.map(&Map.fetch!(frame, &1)) data = Enum.reduce(colors, @sof, fn color, acc -> diff --git a/lib/xebow/utils.ex b/lib/xebow/utils.ex deleted file mode 100644 index ea34253..0000000 --- a/lib/xebow/utils.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule Xebow.Utils do - @moduledoc """ - Shared utility functions that are generally useful. - """ - - @doc """ - Modulo operation that supports negative numbers. - - This is effectively `mod` as it exists in most other languages. Elixir's `rem` - doesn't act the same as other languages for negative numbers. - """ - @spec mod(integer, integer) :: non_neg_integer - def mod(number, modulus) when is_integer(number) and is_integer(modulus) do - case rem(number, modulus) do - remainder when (remainder > 0 and modulus < 0) or (remainder < 0 and modulus > 0) -> - remainder + modulus - - remainder -> - remainder - end - end - - # pixels on the xebow start in upper left corner and count down instead of - # across - @pixels [ - {0, 0}, - {0, 1}, - {0, 2}, - {0, 3}, - {1, 0}, - {1, 1}, - {1, 2}, - {1, 3}, - {2, 0}, - {2, 1}, - {2, 2}, - {2, 3} - ] - - @spec pixels() :: list(RGBMatrix.Pixel.t()) - def pixels, do: @pixels -end From d84aff364df7c185de9bc16f09799b9c0a0e9ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sun, 5 Jul 2020 01:21:47 -0700 Subject: [PATCH 3/8] Keyboard to Effect interaction --- lib/rgb_matrix/effect.ex | 10 +++++----- lib/rgb_matrix/effect/breathing.ex | 2 +- lib/rgb_matrix/effect/cycle_all.ex | 2 +- lib/rgb_matrix/effect/hue_wave.ex | 2 +- lib/rgb_matrix/effect/pinwheel.ex | 2 +- lib/rgb_matrix/effect/random_keypresses.ex | 2 +- lib/rgb_matrix/effect/random_solid.ex | 2 +- lib/rgb_matrix/effect/solid_color.ex | 2 +- lib/rgb_matrix/effect/solid_reactive.ex | 9 +++++---- lib/rgb_matrix/effect/splash.ex | 2 +- lib/rgb_matrix/engine.ex | 14 ++++++++++++++ lib/xebow/keyboard.ex | 10 +++++++++- 12 files changed, 41 insertions(+), 18 deletions(-) diff --git a/lib/rgb_matrix/effect.ex b/lib/rgb_matrix/effect.ex index 132041f..552cd52 100644 --- a/lib/rgb_matrix/effect.ex +++ b/lib/rgb_matrix/effect.ex @@ -4,7 +4,7 @@ defmodule RGBMatrix.Effect do @callback new(leds :: list(LED.t()), config :: any) :: {render_in, any} @callback render(state :: any, config :: any) :: {list(RGBMatrix.any_color_model()), render_in, any} - @callback key_pressed(state :: any, config :: any, led :: LED.t()) :: {render_in, any} + @callback interact(state :: any, config :: any, led :: LED.t()) :: {render_in, any} @type t :: %__MODULE__{ type: type, @@ -78,11 +78,11 @@ defmodule RGBMatrix.Effect do end @doc """ - Sends a key pressed event to an effect. + Sends an interaction event to an effect. """ - @spec key_pressed(effect :: t, led :: LED.t()) :: {render_in, t} - def key_pressed(effect, led) do - {render_in, effect_state} = effect.type.key_pressed(effect.state, effect.config, led) + @spec interact(effect :: t, led :: LED.t()) :: {render_in, t} + def interact(effect, led) do + {render_in, effect_state} = effect.type.interact(effect.state, effect.config, led) {render_in, %{effect | state: effect_state}} end end diff --git a/lib/rgb_matrix/effect/breathing.ex b/lib/rgb_matrix/effect/breathing.ex index 2df71ba..924d6c6 100644 --- a/lib/rgb_matrix/effect/breathing.ex +++ b/lib/rgb_matrix/effect/breathing.ex @@ -39,7 +39,7 @@ defmodule RGBMatrix.Effect.Breathing do end @impl true - def key_pressed(state, _config, _led) do + def interact(state, _config, _led) do {:ignore, state} end end diff --git a/lib/rgb_matrix/effect/cycle_all.ex b/lib/rgb_matrix/effect/cycle_all.ex index 9365b13..bf947c0 100644 --- a/lib/rgb_matrix/effect/cycle_all.ex +++ b/lib/rgb_matrix/effect/cycle_all.ex @@ -40,7 +40,7 @@ defmodule RGBMatrix.Effect.CycleAll do end @impl true - def key_pressed(state, _config, _led) do + def interact(state, _config, _led) do {:ignore, state} end end diff --git a/lib/rgb_matrix/effect/hue_wave.ex b/lib/rgb_matrix/effect/hue_wave.ex index 9d80583..d8e0be0 100644 --- a/lib/rgb_matrix/effect/hue_wave.ex +++ b/lib/rgb_matrix/effect/hue_wave.ex @@ -97,7 +97,7 @@ defmodule RGBMatrix.Effect.HueWave do end @impl true - def key_pressed(state, _config, _led) do + def interact(state, _config, _led) do {:ignore, state} end end diff --git a/lib/rgb_matrix/effect/pinwheel.ex b/lib/rgb_matrix/effect/pinwheel.ex index 4ef9143..d5961f3 100644 --- a/lib/rgb_matrix/effect/pinwheel.ex +++ b/lib/rgb_matrix/effect/pinwheel.ex @@ -62,7 +62,7 @@ defmodule RGBMatrix.Effect.Pinwheel do end @impl true - def key_pressed(state, _config, %LED{x: x, y: y}) do + def interact(state, _config, %LED{x: x, y: y}) do {:ignore, %{state | center: %{x: x, y: y}}} end end diff --git a/lib/rgb_matrix/effect/random_keypresses.ex b/lib/rgb_matrix/effect/random_keypresses.ex index 87ff149..e837aab 100644 --- a/lib/rgb_matrix/effect/random_keypresses.ex +++ b/lib/rgb_matrix/effect/random_keypresses.ex @@ -46,7 +46,7 @@ defmodule RGBMatrix.Effect.RandomKeypresses do end @impl true - def key_pressed(state, _config, led) do + def interact(state, _config, led) do {0, %{state | dirty: led.id}} end end diff --git a/lib/rgb_matrix/effect/random_solid.ex b/lib/rgb_matrix/effect/random_solid.ex index 8ef619c..ba7243d 100644 --- a/lib/rgb_matrix/effect/random_solid.ex +++ b/lib/rgb_matrix/effect/random_solid.ex @@ -37,7 +37,7 @@ defmodule RGBMatrix.Effect.RandomSolid do end @impl true - def key_pressed(state, _config, _led) do + def interact(state, _config, _led) do {0, state} end end diff --git a/lib/rgb_matrix/effect/solid_color.ex b/lib/rgb_matrix/effect/solid_color.ex index d8367d8..2d554a7 100644 --- a/lib/rgb_matrix/effect/solid_color.ex +++ b/lib/rgb_matrix/effect/solid_color.ex @@ -33,7 +33,7 @@ defmodule RGBMatrix.Effect.SolidColor do end @impl true - def key_pressed(state, _config, _led) do + def interact(state, _config, _led) do {:ignore, state} end end diff --git a/lib/rgb_matrix/effect/solid_reactive.ex b/lib/rgb_matrix/effect/solid_reactive.ex index 0936491..8468e00 100644 --- a/lib/rgb_matrix/effect/solid_reactive.ex +++ b/lib/rgb_matrix/effect/solid_reactive.ex @@ -17,25 +17,26 @@ defmodule RGBMatrix.Effect.SolidReactive do description: """ The speed at which the hue shifts back to base. """ - field :speed, :integer, default: 4, min: 0, max: 32 + field(:speed, :integer, default: 4, min: 0, max: 32) @doc name: "Distance", description: """ The distance that the hue shifts on key-press. """ - field :distance, :integer, default: 180, min: 0, max: 360, step: 10 + field(:distance, :integer, default: 180, min: 0, max: 360, step: 10) @doc name: "Direction", description: """ The direction (through the color wheel) that the hue shifts on key-press. """ - field :direction, :option, + field(:direction, :option, default: :random, options: [ :random, :negative, :positive ] + ) end defmodule State do @@ -95,7 +96,7 @@ defmodule RGBMatrix.Effect.SolidReactive do end @impl true - def key_pressed(state, config, led) do + def interact(state, config, led) do direction = direction_modifier(config.direction) render_in = diff --git a/lib/rgb_matrix/effect/splash.ex b/lib/rgb_matrix/effect/splash.ex index d01d419..bb7fce3 100644 --- a/lib/rgb_matrix/effect/splash.ex +++ b/lib/rgb_matrix/effect/splash.ex @@ -87,7 +87,7 @@ defmodule RGBMatrix.Effect.Splash do end @impl true - def key_pressed(state, _config, led) do + def interact(state, _config, led) do # {:ignore, %{state | hits: Map.put(state.hits, led, state.tick)}} {:ignore, %{state | hits: %{led => state.tick}}} end diff --git a/lib/rgb_matrix/engine.ex b/lib/rgb_matrix/engine.ex index f192e28..2bca5fa 100644 --- a/lib/rgb_matrix/engine.ex +++ b/lib/rgb_matrix/engine.ex @@ -66,6 +66,11 @@ defmodule RGBMatrix.Engine do GenServer.call(__MODULE__, {:unregister_paintable, paintable}) end + @spec interact(led :: LED.t()) :: :ok + def interact(led) do + GenServer.cast(__MODULE__, {:interact, led}) + end + # Server @impl GenServer @@ -157,6 +162,15 @@ defmodule RGBMatrix.Engine do {:noreply, state} end + @impl GenServer + def handle_cast({:interact, led}, state) do + {render_in, effect} = Effect.interact(state.effect, led) + state = schedule_next_render(state, render_in) + state = %State{state | effect: effect} + + {:noreply, %State{state | effect: effect}} + end + @impl GenServer def handle_call({:register_paintable, paintable}, _from, state) do state = add_paintable(paintable, state) diff --git a/lib/xebow/keyboard.ex b/lib/xebow/keyboard.ex index 347e004..c1ae01c 100644 --- a/lib/xebow/keyboard.ex +++ b/lib/xebow/keyboard.ex @@ -17,7 +17,7 @@ defmodule Xebow.Keyboard do use GenServer alias Circuits.GPIO - alias RGBMatrix.Effect + alias RGBMatrix.{Effect, Engine} # maps the physical GPIO pins to key IDs # TODO: re-number these keys so they map to the keyboard in X/Y natural order, @@ -215,6 +215,7 @@ defmodule Xebow.Keyboard do 0 -> Logger.debug("key pressed #{key_id}") AFK.State.press_key(keyboard_state, key_id) + rgb_matrix_interact(key_id) 1 -> Logger.debug("key released #{key_id}") @@ -222,6 +223,13 @@ defmodule Xebow.Keyboard do end end + defp rgb_matrix_interact(key_id) do + case Layout.led_for_key(Xebow.layout(), key_id) do + nil -> :noop + led -> Engine.interact(led) + end + end + # Custom Key Functions def flash(color) do From a311cea7e2cc1becbf14caca2fdea165454a01d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sun, 5 Jul 2020 01:38:43 -0700 Subject: [PATCH 4/8] Fix keymap --- lib/xebow/keyboard.ex | 72 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/lib/xebow/keyboard.ex b/lib/xebow/keyboard.ex index c1ae01c..478e823 100644 --- a/lib/xebow/keyboard.ex +++ b/lib/xebow/keyboard.ex @@ -20,21 +20,19 @@ defmodule Xebow.Keyboard do alias RGBMatrix.{Effect, Engine} # maps the physical GPIO pins to key IDs - # TODO: re-number these keys so they map to the keyboard in X/Y natural order, - # rather than keybow hardware order. @gpio_pins %{ + 6 => :k004, + 5 => :k009, + 27 => :k011, + 26 => :k003, + 24 => :k008, + 23 => :k012, + 22 => :k007, 20 => :k001, - 6 => :k002, - 22 => :k003, - 17 => :k004, - 16 => :k005, - 12 => :k006, - 24 => :k007, - 27 => :k008, - 26 => :k009, - 13 => :k010, - 5 => :k011, - 23 => :k012 + 17 => :k010, + 16 => :k002, + 13 => :k006, + 12 => :k005 } # this file exists because `Xebow.HIDGadget` set it up during boot. @@ -44,45 +42,45 @@ defmodule Xebow.Keyboard do # Layer 0: %{ k001: AFK.Keycode.Key.new(:"7"), - k002: AFK.Keycode.Key.new(:"4"), - k003: AFK.Keycode.Key.new(:"1"), - k004: AFK.Keycode.Key.new(:"0"), - k005: AFK.Keycode.Key.new(:"8"), - k006: AFK.Keycode.Key.new(:"5"), - k007: AFK.Keycode.Key.new(:"2"), - k008: AFK.Keycode.Layer.new(:hold, 1), - k009: AFK.Keycode.Key.new(:"9"), - k010: AFK.Keycode.Key.new(:"6"), - k011: AFK.Keycode.Key.new(:"3"), + k002: AFK.Keycode.Key.new(:"8"), + k003: AFK.Keycode.Key.new(:"9"), + k004: AFK.Keycode.Key.new(:"4"), + k005: AFK.Keycode.Key.new(:"5"), + k006: AFK.Keycode.Key.new(:"6"), + k007: AFK.Keycode.Key.new(:"1"), + k008: AFK.Keycode.Key.new(:"2"), + k009: AFK.Keycode.Key.new(:"3"), + k010: AFK.Keycode.Key.new(:"0"), + k011: AFK.Keycode.Layer.new(:hold, 1), k012: AFK.Keycode.Layer.new(:hold, 2) }, # Layer 1: %{ k001: AFK.Keycode.Transparent.new(), - k002: AFK.Keycode.Transparent.new(), - k003: AFK.Keycode.Transparent.new(), + k002: AFK.Keycode.Key.new(:mute), + k003: AFK.Keycode.Key.new(:volume_up), k004: AFK.Keycode.Transparent.new(), - k005: AFK.Keycode.Key.new(:mute), - k006: AFK.Keycode.Transparent.new(), + k005: AFK.Keycode.Transparent.new(), + k006: AFK.Keycode.Key.new(:volume_down), k007: AFK.Keycode.Transparent.new(), - k008: AFK.Keycode.None.new(), - k009: AFK.Keycode.Key.new(:volume_up), - k010: AFK.Keycode.Key.new(:volume_down), - k011: AFK.Keycode.Transparent.new(), + k008: AFK.Keycode.Transparent.new(), + k009: AFK.Keycode.Transparent.new(), + k010: AFK.Keycode.Transparent.new(), + k011: AFK.Keycode.None.new(), k012: AFK.Keycode.Transparent.new() }, # Layer 2: %{ k001: AFK.Keycode.MFA.new({__MODULE__, :flash, ["red"]}), - k002: AFK.Keycode.MFA.new({__MODULE__, :previous_effect, []}), - k003: AFK.Keycode.Transparent.new(), - k004: AFK.Keycode.Transparent.new(), + k002: AFK.Keycode.Transparent.new(), + k003: AFK.Keycode.MFA.new({__MODULE__, :flash, ["green"]}), + k004: AFK.Keycode.MFA.new({__MODULE__, :previous_effect, []}), k005: AFK.Keycode.Transparent.new(), - k006: AFK.Keycode.Transparent.new(), + k006: AFK.Keycode.MFA.new({__MODULE__, :next_effect, []}), k007: AFK.Keycode.Transparent.new(), k008: AFK.Keycode.Transparent.new(), - k009: AFK.Keycode.MFA.new({__MODULE__, :flash, ["green"]}), - k010: AFK.Keycode.MFA.new({__MODULE__, :next_effect, []}), + k009: AFK.Keycode.Transparent.new(), + k010: AFK.Keycode.Transparent.new(), k011: AFK.Keycode.Transparent.new(), k012: AFK.Keycode.Transparent.new() } From e003fe4197045cdb916579ca25a4dbd58d166597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sun, 5 Jul 2020 15:14:31 -0700 Subject: [PATCH 5/8] Fix formatting, remove unfinished effect --- .formatter.exs | 7 +- lib/rgb_matrix/effect/hue_wave.ex | 7 +- lib/rgb_matrix/effect/solid_reactive.ex | 7 +- lib/rgb_matrix/effect/splash.ex | 94 ------------------------- 4 files changed, 9 insertions(+), 106 deletions(-) delete mode 100644 lib/rgb_matrix/effect/splash.ex diff --git a/.formatter.exs b/.formatter.exs index a895ce4..5b2bcb7 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,8 +1,7 @@ # Used by "mix format" [ - inputs: [ - "{mix,.formatter}.exs", - "{config,lib,test}/**/*.{ex,exs}", - "dialyzer.ignore.exs" + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"], + locals_without_parens: [ + field: 3 ] ] diff --git a/lib/rgb_matrix/effect/hue_wave.ex b/lib/rgb_matrix/effect/hue_wave.ex index d8e0be0..834c992 100644 --- a/lib/rgb_matrix/effect/hue_wave.ex +++ b/lib/rgb_matrix/effect/hue_wave.ex @@ -18,19 +18,19 @@ defmodule RGBMatrix.Effect.HueWave do description: """ Controls the speed at which the wave moves across the matrix. """ - field(:speed, :integer, default: 4, min: 0, max: 32) + field :speed, :integer, default: 4, min: 0, max: 32 @doc name: "Width", description: """ The rate of change of the wave, higher values means it's more spread out. """ - field(:width, :integer, default: 20, min: 10, max: 100, step: 10) + field :width, :integer, default: 20, min: 10, max: 100, step: 10 @doc name: "Direction", description: """ The direction the wave travels across the matrix. """ - field(:direction, :option, + field :direction, :option, default: :right, options: [ :right, @@ -38,7 +38,6 @@ defmodule RGBMatrix.Effect.HueWave do :up, :down ] - ) end defmodule State do diff --git a/lib/rgb_matrix/effect/solid_reactive.ex b/lib/rgb_matrix/effect/solid_reactive.ex index 8468e00..e00bba4 100644 --- a/lib/rgb_matrix/effect/solid_reactive.ex +++ b/lib/rgb_matrix/effect/solid_reactive.ex @@ -17,26 +17,25 @@ defmodule RGBMatrix.Effect.SolidReactive do description: """ The speed at which the hue shifts back to base. """ - field(:speed, :integer, default: 4, min: 0, max: 32) + field :speed, :integer, default: 4, min: 0, max: 32 @doc name: "Distance", description: """ The distance that the hue shifts on key-press. """ - field(:distance, :integer, default: 180, min: 0, max: 360, step: 10) + field :distance, :integer, default: 180, min: 0, max: 360, step: 10 @doc name: "Direction", description: """ The direction (through the color wheel) that the hue shifts on key-press. """ - field(:direction, :option, + field :direction, :option, default: :random, options: [ :random, :negative, :positive ] - ) end defmodule State do diff --git a/lib/rgb_matrix/effect/splash.ex b/lib/rgb_matrix/effect/splash.ex deleted file mode 100644 index bb7fce3..0000000 --- a/lib/rgb_matrix/effect/splash.ex +++ /dev/null @@ -1,94 +0,0 @@ -defmodule RGBMatrix.Effect.Splash do - @moduledoc """ - Full gradient & value pulse away from key hits then fades value out. - """ - - alias Chameleon.HSV - alias RGBMatrix.Effect - - use Effect - - # import RGBMatrix.Utils, only: [mod: 2] - - defmodule Config do - use RGBMatrix.Effect.Config - end - - defmodule State do - defstruct [:tick, :leds, :hits] - end - - @delay_ms 17 - - @impl true - def new(leds, _config) do - {0, %State{tick: 0, leds: leds, hits: %{}}} - end - - @impl true - def render(state, _config) do - %{tick: tick, leds: leds, hits: hits} = state - - {colors, hits} = - Enum.map_reduce(leds, hits, fn led, hits -> - color = HSV.new(0, 100, 0) - - {hits, color} = - Enum.reduce(hits, {hits, color}, fn {hit_led, hit_tick}, {hits, color} -> - dx = led.x - hit_led.x - dy = led.y - hit_led.y - dist = :math.sqrt(dx * dx + dy * dy) - color = effect(color, dist, tick - hit_tick) - - {hits, color} - end) - - {{led.id, color}, hits} - end) - - # for (uint8_t i = led_min; i < led_max; i++) { - # RGB_MATRIX_TEST_LED_FLAGS(); - # HSV hsv = rgb_matrix_config.hsv; - # hsv.v = 0; - # for (uint8_t j = start; j < count; j++) { - # int16_t dx = g_led_config.point[i].x - g_last_hit_tracker.x[j]; - # int16_t dy = g_led_config.point[i].y - g_last_hit_tracker.y[j]; - # uint8_t dist = sqrt16(dx * dx + dy * dy); - # uint16_t tick = scale16by8(g_last_hit_tracker.tick[j], rgb_matrix_config.speed); - # hsv = effect_func(hsv, dx, dy, dist, tick); - # } - # hsv.v = scale8(hsv.v, rgb_matrix_config.hsv.v); - # RGB rgb = hsv_to_rgb(hsv); - # rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); - # } - - {colors, @delay_ms, %{state | tick: tick + 1, hits: hits}} - end - - def effect(color, dist, hit_tick) do - # uint16_t effect = tick - dist; - # if (effect > 255) effect = 255; - # hsv.h += effect; - # hsv.v = qadd8(hsv.v, 255 - effect); - # return hsv; - - # effect = trunc(if effect > 360, do: 360, else: effect) - - value = 100 - hit_tick - trunc(dist * 20) - value = if value < 0, do: 0, else: value - - %{color | h: 0, v: value} - - # if dist < 5 do - # HSV.new(0, 100, 100) - # else - # color - # end - end - - @impl true - def interact(state, _config, led) do - # {:ignore, %{state | hits: Map.put(state.hits, led, state.tick)}} - {:ignore, %{state | hits: %{led => state.tick}}} - end -end From c17128d644ebec1d1a05167e3ed8d1b0ce7d86ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sun, 5 Jul 2020 15:31:32 -0700 Subject: [PATCH 6/8] Rename Effect back to Animation --- lib/rgb_matrix/{effect.ex => animation.ex} | 52 +++++++-------- .../{effect => animation}/breathing.ex | 8 +-- .../{effect => animation}/config.ex | 18 +++--- .../{effect => animation}/config/integer.ex | 2 +- .../{effect => animation}/config/option.ex | 2 +- .../{effect => animation}/cycle_all.ex | 8 +-- .../{effect => animation}/hue_wave.ex | 8 +-- .../{effect => animation}/pinwheel.ex | 8 +-- .../random_keypresses.ex | 8 +-- .../{effect => animation}/random_solid.ex | 8 +-- .../{effect => animation}/solid_color.ex | 8 +-- .../{effect => animation}/solid_reactive.ex | 8 +-- lib/rgb_matrix/engine.ex | 63 ++++++++++--------- lib/rgb_matrix/paintable.ex | 4 +- lib/xebow/application.ex | 4 +- lib/xebow/keyboard.ex | 50 +++++++-------- lib/xebow/leds.ex | 2 +- 17 files changed, 131 insertions(+), 130 deletions(-) rename lib/rgb_matrix/{effect.ex => animation.ex} (50%) rename lib/rgb_matrix/{effect => animation}/breathing.ex (87%) rename lib/rgb_matrix/{effect => animation}/config.ex (81%) rename lib/rgb_matrix/{effect => animation}/config/integer.ex (95%) rename lib/rgb_matrix/{effect => animation}/config/option.ex (92%) rename lib/rgb_matrix/{effect => animation}/cycle_all.ex (86%) rename lib/rgb_matrix/{effect => animation}/hue_wave.ex (94%) rename lib/rgb_matrix/{effect => animation}/pinwheel.ex (91%) rename lib/rgb_matrix/{effect => animation}/random_keypresses.ex (87%) rename lib/rgb_matrix/{effect => animation}/random_solid.ex (84%) rename lib/rgb_matrix/{effect => animation}/solid_color.ex (83%) rename lib/rgb_matrix/{effect => animation}/solid_reactive.ex (95%) diff --git a/lib/rgb_matrix/effect.ex b/lib/rgb_matrix/animation.ex similarity index 50% rename from lib/rgb_matrix/effect.ex rename to lib/rgb_matrix/animation.ex index 552cd52..efb4992 100644 --- a/lib/rgb_matrix/effect.ex +++ b/lib/rgb_matrix/animation.ex @@ -1,4 +1,4 @@ -defmodule RGBMatrix.Effect do +defmodule RGBMatrix.Animation do alias Layout.LED @callback new(leds :: list(LED.t()), config :: any) :: {render_in, any} @@ -15,7 +15,7 @@ defmodule RGBMatrix.Effect do defmacro __using__(_) do quote do - @behaviour RGBMatrix.Effect + @behaviour RGBMatrix.Animation end end @@ -30,7 +30,6 @@ defmodule RGBMatrix.Effect do | __MODULE__.SolidColor | __MODULE__.Breathing | __MODULE__.SolidReactive - | __MODULE__.Splash @doc """ Returns a list of the available types of animations. @@ -45,44 +44,45 @@ defmodule RGBMatrix.Effect do __MODULE__.RandomKeypresses, __MODULE__.SolidColor, __MODULE__.Breathing, - __MODULE__.SolidReactive, - __MODULE__.Splash + __MODULE__.SolidReactive ] end @doc """ - Returns an effect's initial state. + Returns an animation's initial state. """ - @spec new(effect_type :: type, leds :: list(LED.t())) :: {render_in, t} - def new(effect_type, leds) do - config_module = Module.concat([effect_type, Config]) - effect_config = config_module.new() - {render_in, effect_state} = effect_type.new(leds, effect_config) + @spec new(animation_type :: type, leds :: list(LED.t())) :: {render_in, t} + def new(animation_type, leds) do + config_module = Module.concat([animation_type, Config]) + animation_config = config_module.new() + {render_in, animation_state} = animation_type.new(leds, animation_config) - effect = %__MODULE__{ - type: effect_type, - config: effect_config, - state: effect_state + animation = %__MODULE__{ + type: animation_type, + config: animation_config, + state: animation_state } - {render_in, effect} + {render_in, animation} end @doc """ - Returns the next state of an effect based on its current state. + Returns the next state of an animation based on its current state. """ - @spec render(effect :: t) :: {list(RGBMatrix.any_color_model()), render_in, t} - def render(effect) do - {colors, render_in, effect_state} = effect.type.render(effect.state, effect.config) - {colors, render_in, %{effect | state: effect_state}} + @spec render(animation :: t) :: {list(RGBMatrix.any_color_model()), render_in, t} + def render(animation) do + {colors, render_in, animation_state} = + animation.type.render(animation.state, animation.config) + + {colors, render_in, %{animation | state: animation_state}} end @doc """ - Sends an interaction event to an effect. + Sends an interaction event to an animation. """ - @spec interact(effect :: t, led :: LED.t()) :: {render_in, t} - def interact(effect, led) do - {render_in, effect_state} = effect.type.interact(effect.state, effect.config, led) - {render_in, %{effect | state: effect_state}} + @spec interact(animation :: t, led :: LED.t()) :: {render_in, t} + def interact(animation, led) do + {render_in, animation_state} = animation.type.interact(animation.state, animation.config, led) + {render_in, %{animation | state: animation_state}} end end diff --git a/lib/rgb_matrix/effect/breathing.ex b/lib/rgb_matrix/animation/breathing.ex similarity index 87% rename from lib/rgb_matrix/effect/breathing.ex rename to lib/rgb_matrix/animation/breathing.ex index 924d6c6..7b59c1c 100644 --- a/lib/rgb_matrix/effect/breathing.ex +++ b/lib/rgb_matrix/animation/breathing.ex @@ -1,15 +1,15 @@ -defmodule RGBMatrix.Effect.Breathing do +defmodule RGBMatrix.Animation.Breathing do @moduledoc """ Single hue brightness cycling. """ alias Chameleon.HSV - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config end defmodule State do diff --git a/lib/rgb_matrix/effect/config.ex b/lib/rgb_matrix/animation/config.ex similarity index 81% rename from lib/rgb_matrix/effect/config.ex rename to lib/rgb_matrix/animation/config.ex index d7ad6b6..3e4adcf 100644 --- a/lib/rgb_matrix/effect/config.ex +++ b/lib/rgb_matrix/animation/config.ex @@ -1,23 +1,23 @@ -defmodule RGBMatrix.Effect.Config do +defmodule RGBMatrix.Animation.Config do @callback schema() :: keyword(any) @callback new(%{optional(atom) => any}) :: struct @callback update(struct, %{optional(atom) => any}) :: struct @types %{ - integer: RGBMatrix.Effect.Config.Integer, - option: RGBMatrix.Effect.Config.Option + integer: RGBMatrix.Animation.Config.Integer, + option: RGBMatrix.Animation.Config.Option } defmacro __using__(_) do quote do - import RGBMatrix.Effect.Config + import RGBMatrix.Animation.Config Module.register_attribute(__MODULE__, :fields, accumulate: true, persist: false ) - @before_compile RGBMatrix.Effect.Config + @before_compile RGBMatrix.Animation.Config end end @@ -36,17 +36,17 @@ defmodule RGBMatrix.Effect.Config do schema = Macro.escape(schema) quote do - @behaviour RGBMatrix.Effect.Config + @behaviour RGBMatrix.Animation.Config @enforce_keys unquote(keys) defstruct unquote(keys) - @impl RGBMatrix.Effect.Config + @impl RGBMatrix.Animation.Config def schema do unquote(schema) end - @impl RGBMatrix.Effect.Config + @impl RGBMatrix.Animation.Config def new(params \\ %{}) do schema() |> Map.new(fn {key, %mod{} = type} -> @@ -60,7 +60,7 @@ defmodule RGBMatrix.Effect.Config do |> (&struct!(__MODULE__, &1)).() end - @impl RGBMatrix.Effect.Config + @impl RGBMatrix.Animation.Config def update(config, params) do schema = schema() diff --git a/lib/rgb_matrix/effect/config/integer.ex b/lib/rgb_matrix/animation/config/integer.ex similarity index 95% rename from lib/rgb_matrix/effect/config/integer.ex rename to lib/rgb_matrix/animation/config/integer.ex index 3d206dc..71a4cb0 100644 --- a/lib/rgb_matrix/effect/config/integer.ex +++ b/lib/rgb_matrix/animation/config/integer.ex @@ -1,4 +1,4 @@ -defmodule RGBMatrix.Effect.Config.Integer do +defmodule RGBMatrix.Animation.Config.Integer do @enforce_keys [:default, :min, :max] defstruct [:default, :min, :max, step: 1] diff --git a/lib/rgb_matrix/effect/config/option.ex b/lib/rgb_matrix/animation/config/option.ex similarity index 92% rename from lib/rgb_matrix/effect/config/option.ex rename to lib/rgb_matrix/animation/config/option.ex index 1771348..583eebb 100644 --- a/lib/rgb_matrix/effect/config/option.ex +++ b/lib/rgb_matrix/animation/config/option.ex @@ -1,4 +1,4 @@ -defmodule RGBMatrix.Effect.Config.Option do +defmodule RGBMatrix.Animation.Config.Option do @enforce_keys [:default, :options] defstruct [:default, :options] diff --git a/lib/rgb_matrix/effect/cycle_all.ex b/lib/rgb_matrix/animation/cycle_all.ex similarity index 86% rename from lib/rgb_matrix/effect/cycle_all.ex rename to lib/rgb_matrix/animation/cycle_all.ex index bf947c0..334d90c 100644 --- a/lib/rgb_matrix/effect/cycle_all.ex +++ b/lib/rgb_matrix/animation/cycle_all.ex @@ -1,17 +1,17 @@ -defmodule RGBMatrix.Effect.CycleAll do +defmodule RGBMatrix.Animation.CycleAll do @moduledoc """ Cycles the hue of all LEDs at the same time. """ alias Chameleon.HSV - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation import RGBMatrix.Utils, only: [mod: 2] defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config end defmodule State do diff --git a/lib/rgb_matrix/effect/hue_wave.ex b/lib/rgb_matrix/animation/hue_wave.ex similarity index 94% rename from lib/rgb_matrix/effect/hue_wave.ex rename to lib/rgb_matrix/animation/hue_wave.ex index 834c992..1134d9d 100644 --- a/lib/rgb_matrix/effect/hue_wave.ex +++ b/lib/rgb_matrix/animation/hue_wave.ex @@ -1,18 +1,18 @@ -defmodule RGBMatrix.Effect.HueWave do +defmodule RGBMatrix.Animation.HueWave do @moduledoc """ Creates a wave of shifting hue that moves across the matrix. """ alias Chameleon.HSV alias Layout.LED - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation import RGBMatrix.Utils, only: [mod: 2] defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config @doc name: "Speed", description: """ diff --git a/lib/rgb_matrix/effect/pinwheel.ex b/lib/rgb_matrix/animation/pinwheel.ex similarity index 91% rename from lib/rgb_matrix/effect/pinwheel.ex rename to lib/rgb_matrix/animation/pinwheel.ex index d5961f3..ccf7c03 100644 --- a/lib/rgb_matrix/effect/pinwheel.ex +++ b/lib/rgb_matrix/animation/pinwheel.ex @@ -1,18 +1,18 @@ -defmodule RGBMatrix.Effect.Pinwheel do +defmodule RGBMatrix.Animation.Pinwheel do @moduledoc """ Cycles hue in a pinwheel pattern. """ alias Chameleon.HSV alias Layout.LED - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation import RGBMatrix.Utils, only: [mod: 2] defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config end defmodule State do diff --git a/lib/rgb_matrix/effect/random_keypresses.ex b/lib/rgb_matrix/animation/random_keypresses.ex similarity index 87% rename from lib/rgb_matrix/effect/random_keypresses.ex rename to lib/rgb_matrix/animation/random_keypresses.ex index e837aab..30c78bd 100644 --- a/lib/rgb_matrix/effect/random_keypresses.ex +++ b/lib/rgb_matrix/animation/random_keypresses.ex @@ -1,15 +1,15 @@ -defmodule RGBMatrix.Effect.RandomKeypresses do +defmodule RGBMatrix.Animation.RandomKeypresses do @moduledoc """ Changes every key pressed to a random color. """ alias Chameleon.HSV - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config end defmodule State do diff --git a/lib/rgb_matrix/effect/random_solid.ex b/lib/rgb_matrix/animation/random_solid.ex similarity index 84% rename from lib/rgb_matrix/effect/random_solid.ex rename to lib/rgb_matrix/animation/random_solid.ex index ba7243d..6c510b4 100644 --- a/lib/rgb_matrix/effect/random_solid.ex +++ b/lib/rgb_matrix/animation/random_solid.ex @@ -1,15 +1,15 @@ -defmodule RGBMatrix.Effect.RandomSolid do +defmodule RGBMatrix.Animation.RandomSolid do @moduledoc """ A random solid color fills the entire matrix and changes every key-press. """ alias Chameleon.HSV - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config end defmodule State do diff --git a/lib/rgb_matrix/effect/solid_color.ex b/lib/rgb_matrix/animation/solid_color.ex similarity index 83% rename from lib/rgb_matrix/effect/solid_color.ex rename to lib/rgb_matrix/animation/solid_color.ex index 2d554a7..1ee7d6c 100644 --- a/lib/rgb_matrix/effect/solid_color.ex +++ b/lib/rgb_matrix/animation/solid_color.ex @@ -1,15 +1,15 @@ -defmodule RGBMatrix.Effect.SolidColor do +defmodule RGBMatrix.Animation.SolidColor do @moduledoc """ All LEDs are a solid color. """ alias Chameleon.HSV - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config end defmodule State do diff --git a/lib/rgb_matrix/effect/solid_reactive.ex b/lib/rgb_matrix/animation/solid_reactive.ex similarity index 95% rename from lib/rgb_matrix/effect/solid_reactive.ex rename to lib/rgb_matrix/animation/solid_reactive.ex index e00bba4..105e314 100644 --- a/lib/rgb_matrix/effect/solid_reactive.ex +++ b/lib/rgb_matrix/animation/solid_reactive.ex @@ -1,17 +1,17 @@ -defmodule RGBMatrix.Effect.SolidReactive do +defmodule RGBMatrix.Animation.SolidReactive do @moduledoc """ Static single hue, pulses keys hit to shifted hue then fades to current hue. """ alias Chameleon.HSV - alias RGBMatrix.Effect + alias RGBMatrix.Animation - use Effect + use Animation import RGBMatrix.Utils, only: [mod: 2] defmodule Config do - use RGBMatrix.Effect.Config + use RGBMatrix.Animation.Config @doc name: "Speed", description: """ diff --git a/lib/rgb_matrix/engine.ex b/lib/rgb_matrix/engine.ex index 2bca5fa..1641a30 100644 --- a/lib/rgb_matrix/engine.ex +++ b/lib/rgb_matrix/engine.ex @@ -2,18 +2,18 @@ require Logger defmodule RGBMatrix.Engine do @moduledoc """ - Renders [`Effect`](`RGBMatrix.Effect`)s and outputs colors to be displayed by - [`Paintable`](`RGBMatrix.Paintable`)s. + Renders [`Animation`](`RGBMatrix.Animation`)s and outputs colors to be + displayed by [`Paintable`](`RGBMatrix.Paintable`)s. """ use GenServer alias Layout.LED - alias RGBMatrix.Effect + alias RGBMatrix.Animation defmodule State do @moduledoc false - defstruct [:leds, :effect, :paintables, :last_frame, :timer] + defstruct [:leds, :animation, :paintables, :last_frame, :timer] end # Client @@ -21,31 +21,32 @@ defmodule RGBMatrix.Engine do @doc """ Start the engine. - This module registers its process globally and is expected to be started by - a supervisor. + This module registers its process globally and is expected to be started by a + supervisor. This function accepts the following arguments as a tuple: - `leds` - The list of LEDs to be painted on. - - `initial_effect` - The Effect type to initialize and play when the engine - starts. + - `initial_animation` - The Animation type to initialize and play when the + engine starts. - `paintables` - A list of modules to output colors to that implement the - `RGBMatrix.Paintable` behavior. If you want to register your paintables - dynamically, set this to an empty list `[]`. + `RGBMatrix.Paintable` behavior. If you want to register your paintables + dynamically, set this to an empty list `[]`. """ @spec start_link( - {leds :: [LED.t()], initial_effect_type :: Effect.type(), paintables :: list(module)} + {leds :: [LED.t()], initial_animation_type :: Animation.type(), + paintables :: list(module)} ) :: GenServer.on_start() - def start_link({leds, initial_effect_type, paintables}) do - GenServer.start_link(__MODULE__, {leds, initial_effect_type, paintables}, name: __MODULE__) + def start_link({leds, initial_animation_type, paintables}) do + GenServer.start_link(__MODULE__, {leds, initial_animation_type, paintables}, name: __MODULE__) end @doc """ - Sets the given effect as the currently active effect. + Sets the given animation as the currently active animation. """ - @spec set_effect(effect_type :: Effect.type(), opts :: keyword()) :: :ok - def set_effect(effect_type) do - GenServer.cast(__MODULE__, {:set_effect, effect_type}) + @spec set_animation(animation_type :: Animation.type(), opts :: keyword()) :: :ok + def set_animation(animation_type) do + GenServer.cast(__MODULE__, {:set_animation, animation_type}) end @doc """ @@ -58,8 +59,8 @@ defmodule RGBMatrix.Engine do end @doc """ - Unregister a `RGBMatrix.Paintable` so the engine no longer paints pixels to it. - This function is idempotent. + Unregister a `RGBMatrix.Paintable` so the engine no longer paints pixels to + it. This function is idempotent. """ @spec unregister_paintable(paintable :: module) :: :ok def unregister_paintable(paintable) do @@ -74,7 +75,7 @@ defmodule RGBMatrix.Engine do # Server @impl GenServer - def init({leds, initial_effect_type, paintables}) do + def init({leds, initial_animation_type, paintables}) do black = Chameleon.HSV.new(0, 0, 0) frame = Map.new(leds, &{&1.id, black}) @@ -84,7 +85,7 @@ defmodule RGBMatrix.Engine do Enum.reduce(paintables, initial_state, fn paintable, state -> add_paintable(paintable, state) end) - |> set_effect(initial_effect_type) + |> set_animation(initial_animation_type) {:ok, state} end @@ -99,12 +100,12 @@ defmodule RGBMatrix.Engine do %State{state | paintables: paintables} end - defp set_effect(state, effect_type) do - {render_in, effect} = Effect.new(effect_type, state.leds) + defp set_animation(state, animation_type) do + {render_in, animation} = Animation.new(animation_type, state.leds) state = schedule_next_render(state, render_in) - %State{state | effect: effect} + %State{state | animation: animation} end defp schedule_next_render(state, :ignore) do @@ -134,7 +135,7 @@ defmodule RGBMatrix.Engine do @impl true def handle_info(:render, state) do - {new_colors, render_in, effect} = Effect.render(state.effect) + {new_colors, render_in, animation} = Animation.render(state.animation) frame = update_frame(state.last_frame, new_colors) @@ -145,7 +146,7 @@ defmodule RGBMatrix.Engine do end) state = schedule_next_render(state, render_in) - state = %State{state | effect: effect, last_frame: frame} + state = %State{state | animation: animation, last_frame: frame} {:noreply, state} end @@ -157,18 +158,18 @@ defmodule RGBMatrix.Engine do end @impl GenServer - def handle_cast({:set_effect, effect_type}, state) do - state = set_effect(state, effect_type) + def handle_cast({:set_animation, animation_type}, state) do + state = set_animation(state, animation_type) {:noreply, state} end @impl GenServer def handle_cast({:interact, led}, state) do - {render_in, effect} = Effect.interact(state.effect, led) + {render_in, animation} = Animation.interact(state.animation, led) state = schedule_next_render(state, render_in) - state = %State{state | effect: effect} + state = %State{state | animation: animation} - {:noreply, %State{state | effect: effect}} + {:noreply, %State{state | animation: animation}} end @impl GenServer diff --git a/lib/rgb_matrix/paintable.ex b/lib/rgb_matrix/paintable.ex index 12b6e01..8d951cf 100644 --- a/lib/rgb_matrix/paintable.ex +++ b/lib/rgb_matrix/paintable.ex @@ -8,8 +8,8 @@ defmodule RGBMatrix.Paintable do @type frame :: %{required(LED.id()) => RGBMatrix.any_color_model()} @doc """ - Returns a function that can be called to paint the LEDs for a given frame. - The anonymous function's return value is unused. + Returns a function that can be called to paint the LEDs for a given frame. The + anonymous function's return value is unused. This callback makes any hardware implementation details opaque to the caller, while allowing the paintable to retain control of the physical LEDs. diff --git a/lib/xebow/application.ex b/lib/xebow/application.ex index 08f8416..a46957f 100644 --- a/lib/xebow/application.ex +++ b/lib/xebow/application.ex @@ -6,7 +6,7 @@ defmodule Xebow.Application do use Application @leds Xebow.layout() |> Layout.leds() - @effect_type RGBMatrix.Effect.types() |> List.first() + @animation_type RGBMatrix.Animation.types() |> List.first() def start(_type, _args) do # See https://hexdocs.pm/elixir/Supervisor.html @@ -39,7 +39,7 @@ defmodule Xebow.Application do # {Xebow.Worker, arg}, Xebow.HIDGadget, Xebow.LEDs, - {RGBMatrix.Engine, {@leds, @effect_type, [Xebow.LEDs]}}, + {RGBMatrix.Engine, {@leds, @animation_type, [Xebow.LEDs]}}, Xebow.Keyboard ] end diff --git a/lib/xebow/keyboard.ex b/lib/xebow/keyboard.ex index 478e823..83d7a71 100644 --- a/lib/xebow/keyboard.ex +++ b/lib/xebow/keyboard.ex @@ -17,7 +17,7 @@ defmodule Xebow.Keyboard do use GenServer alias Circuits.GPIO - alias RGBMatrix.{Effect, Engine} + alias RGBMatrix.{Animation, Engine} # maps the physical GPIO pins to key IDs @gpio_pins %{ @@ -74,9 +74,9 @@ defmodule Xebow.Keyboard do k001: AFK.Keycode.MFA.new({__MODULE__, :flash, ["red"]}), k002: AFK.Keycode.Transparent.new(), k003: AFK.Keycode.MFA.new({__MODULE__, :flash, ["green"]}), - k004: AFK.Keycode.MFA.new({__MODULE__, :previous_effect, []}), + k004: AFK.Keycode.MFA.new({__MODULE__, :previous_animation, []}), k005: AFK.Keycode.Transparent.new(), - k006: AFK.Keycode.MFA.new({__MODULE__, :next_effect, []}), + k006: AFK.Keycode.MFA.new({__MODULE__, :next_animation, []}), k007: AFK.Keycode.Transparent.new(), k008: AFK.Keycode.Transparent.new(), k009: AFK.Keycode.Transparent.new(), @@ -94,19 +94,19 @@ defmodule Xebow.Keyboard do end @doc """ - Cycle to the next effect + Cycle to the next animation """ - @spec next_effect() :: :ok - def next_effect do - GenServer.cast(__MODULE__, :next_effect) + @spec next_animation() :: :ok + def next_animation do + GenServer.cast(__MODULE__, :next_animation) end @doc """ - Cycle to the previous effect + Cycle to the previous animation """ - @spec previous_effect() :: :ok - def previous_effect do - GenServer.cast(__MODULE__, :previous_effect) + @spec previous_animation() :: :ok + def previous_animation do + GenServer.cast(__MODULE__, :previous_animation) end # Server @@ -138,47 +138,47 @@ defmodule Xebow.Keyboard do pins: pins, keyboard_state: keyboard_state, hid: hid, - effect_types: Effect.types(), - current_effect_index: 0 + animation_types: Animation.types(), + current_animation_index: 0 } {:ok, state} end @impl GenServer - def handle_cast(:next_effect, state) do - next_index = state.current_effect_index + 1 + def handle_cast(:next_animation, state) do + next_index = state.current_animation_index + 1 next_index = - case next_index < Enum.count(state.effect_types) do + case next_index < Enum.count(state.animation_types) do true -> next_index _ -> 0 end - effect_type = Enum.at(state.effect_types, next_index) + animation_type = Enum.at(state.animation_types, next_index) - RGBMatrix.Engine.set_effect(effect_type) + RGBMatrix.Engine.set_animation(animation_type) - state = %{state | current_effect_index: next_index} + state = %{state | current_animation_index: next_index} {:noreply, state} end @impl GenServer - def handle_cast(:previous_effect, state) do - previous_index = state.current_effect_index - 1 + def handle_cast(:previous_animation, state) do + previous_index = state.current_animation_index - 1 previous_index = case previous_index < 0 do - true -> Enum.count(state.effect_types) - 1 + true -> Enum.count(state.animation_types) - 1 _ -> previous_index end - effect_type = Enum.at(state.effect_types, previous_index) + animation_type = Enum.at(state.animation_types, previous_index) - RGBMatrix.Engine.set_effect(effect_type) + RGBMatrix.Engine.set_animation(animation_type) - state = %{state | current_effect_index: previous_index} + state = %{state | current_animation_index: previous_index} {:noreply, state} end diff --git a/lib/xebow/leds.ex b/lib/xebow/leds.ex index d078e83..edcf834 100644 --- a/lib/xebow/leds.ex +++ b/lib/xebow/leds.ex @@ -6,7 +6,7 @@ defmodule Xebow.LEDs do Keybow. It also implements the RGBMatrix.Paintable behavior so that the RGBMatrix - effects can be painted onto the keybow's RGB LEDs. + animations can be painted onto the keybow's RGB LEDs. """ @behaviour RGBMatrix.Paintable From 06c3aaabcf28b8f80127dbcad114566603bc718a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sat, 11 Jul 2020 12:59:16 -0700 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Jesse Van Volkinburg <42327429+vanvoljg@users.noreply.github.com> --- lib/layout.ex | 4 ++-- lib/rgb_matrix/animation.ex | 4 ++-- lib/rgb_matrix/engine.ex | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/layout.ex b/lib/layout.ex index d946513..a2c2e85 100644 --- a/lib/layout.ex +++ b/lib/layout.ex @@ -8,8 +8,8 @@ defmodule Layout do @type t :: %__MODULE__{ keys: [Key.t()], leds: [LED.t()], - leds_by_keys: %{atom => LED.t()}, - keys_by_leds: %{atom => Key.t()} + leds_by_keys: %{Key.id() => LED.t()}, + keys_by_leds: %{LED.id() => Key.t()} } defstruct [:keys, :leds, :leds_by_keys, :keys_by_leds] diff --git a/lib/rgb_matrix/animation.ex b/lib/rgb_matrix/animation.ex index efb4992..1c64255 100644 --- a/lib/rgb_matrix/animation.ex +++ b/lib/rgb_matrix/animation.ex @@ -71,10 +71,10 @@ defmodule RGBMatrix.Animation do """ @spec render(animation :: t) :: {list(RGBMatrix.any_color_model()), render_in, t} def render(animation) do - {colors, render_in, animation_state} = + {render_in, colors, animation_state} = animation.type.render(animation.state, animation.config) - {colors, render_in, %{animation | state: animation_state}} + {render_in, colors, %{animation | state: animation_state}} end @doc """ diff --git a/lib/rgb_matrix/engine.ex b/lib/rgb_matrix/engine.ex index 1641a30..7e57dea 100644 --- a/lib/rgb_matrix/engine.ex +++ b/lib/rgb_matrix/engine.ex @@ -44,7 +44,7 @@ defmodule RGBMatrix.Engine do @doc """ Sets the given animation as the currently active animation. """ - @spec set_animation(animation_type :: Animation.type(), opts :: keyword()) :: :ok + @spec set_animation(animation_type :: Animation.type()) :: :ok def set_animation(animation_type) do GenServer.cast(__MODULE__, {:set_animation, animation_type}) end @@ -135,7 +135,7 @@ defmodule RGBMatrix.Engine do @impl true def handle_info(:render, state) do - {new_colors, render_in, animation} = Animation.render(state.animation) + {render_in, new_colors, animation} = Animation.render(state.animation) frame = update_frame(state.last_frame, new_colors) From 6bd7b3d079a3697c41cf14fc1a32ef7873ba3342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Dos=C3=A9?= Date: Sat, 11 Jul 2020 13:50:11 -0700 Subject: [PATCH 8/8] Code review --- lib/layout.ex | 24 ++- lib/layout/key.ex | 4 +- lib/layout/led.ex | 4 +- lib/rgb_matrix/animation.ex | 24 ++- lib/rgb_matrix/animation/breathing.ex | 4 +- lib/rgb_matrix/animation/config.ex | 17 +- lib/rgb_matrix/animation/config/field_type.ex | 16 ++ .../animation/config/field_type/integer.ex | 61 +++++++ .../animation/config/field_type/option.ex | 49 ++++++ lib/rgb_matrix/animation/config/integer.ex | 48 ----- lib/rgb_matrix/animation/config/option.ex | 30 ---- lib/rgb_matrix/animation/cycle_all.ex | 4 +- lib/rgb_matrix/animation/hue_wave.ex | 4 +- lib/rgb_matrix/animation/pinwheel.ex | 4 +- lib/rgb_matrix/animation/random_keypresses.ex | 4 +- lib/rgb_matrix/animation/random_solid.ex | 4 +- lib/rgb_matrix/animation/solid_color.ex | 4 +- lib/rgb_matrix/animation/solid_reactive.ex | 4 +- lib/rgb_matrix/engine.ex | 11 +- lib/xebow.ex | 1 + lib/xebow/leds.ex | 2 - test/rgb_matrix/engine_test.exs | 166 +++++++++--------- test/xebow_test.exs | 2 +- 23 files changed, 288 insertions(+), 203 deletions(-) create mode 100644 lib/rgb_matrix/animation/config/field_type.ex create mode 100644 lib/rgb_matrix/animation/config/field_type/integer.ex create mode 100644 lib/rgb_matrix/animation/config/field_type/option.ex delete mode 100644 lib/rgb_matrix/animation/config/integer.ex delete mode 100644 lib/rgb_matrix/animation/config/option.ex diff --git a/lib/layout.ex b/lib/layout.ex index a2c2e85..cfc11b4 100644 --- a/lib/layout.ex +++ b/lib/layout.ex @@ -5,14 +5,17 @@ defmodule Layout do alias __MODULE__.{Key, LED} - @type t :: %__MODULE__{ - keys: [Key.t()], - leds: [LED.t()], - leds_by_keys: %{Key.id() => LED.t()}, - keys_by_leds: %{LED.id() => Key.t()} - } + # FIXME: what's wrong with this type? + # @type t :: %__MODULE__{ + # keys: [Key.t()], + # leds: [LED.t()], + # leds_by_keys: %{Key.id() => LED.t()}, + # keys_by_leds: %{LED.id() => Key.t()} + # } + @type t :: %__MODULE__{} defstruct [:keys, :leds, :leds_by_keys, :keys_by_leds] + @spec new(keys :: [Key.t()], leds :: [LED.t()]) :: t def new(keys, leds \\ []) do leds_map = Map.new(leds, &{&1.id, &1}) @@ -26,20 +29,25 @@ defmodule Layout do |> Enum.filter(& &1.led) |> Map.new(&{&1.led, &1}) - struct!(__MODULE__, + %__MODULE__{ keys: keys, leds: leds, leds_by_keys: leds_by_keys, keys_by_leds: keys_by_leds - ) + } end + @spec keys(layout :: t) :: [Key.t()] def keys(layout), do: layout.keys + + @spec leds(layout :: t) :: [LED.t()] def leds(layout), do: layout.leds + @spec led_for_key(layout :: t, Key.id()) :: LED.t() | nil def led_for_key(%__MODULE__{} = layout, key_id) when is_atom(key_id), do: Map.get(layout.leds_by_keys, key_id) + @spec key_for_led(layout :: t, LED.id()) :: Key.t() | nil def key_for_led(%__MODULE__{} = layout, led_id) when is_atom(led_id), do: Map.get(layout.keys_by_leds, led_id) end diff --git a/lib/layout/key.ex b/lib/layout/key.ex index d323886..bea7d0e 100644 --- a/lib/layout/key.ex +++ b/lib/layout/key.ex @@ -16,13 +16,13 @@ defmodule Layout.Key do defstruct [:id, :x, :y, :width, :height, :led] def new(id, x, y, opts \\ []) do - struct!(__MODULE__, + %__MODULE__{ id: id, x: x, y: y, width: Keyword.get(opts, :width, 1), height: Keyword.get(opts, :height, 1), led: Keyword.get(opts, :led) - ) + } end end diff --git a/lib/layout/led.ex b/lib/layout/led.ex index e06e382..dad6cf4 100644 --- a/lib/layout/led.ex +++ b/lib/layout/led.ex @@ -13,10 +13,10 @@ defmodule Layout.LED do defstruct [:id, :x, :y] def new(id, x, y) do - struct!(__MODULE__, + %__MODULE__{ id: id, x: x, y: y - ) + } end end diff --git a/lib/rgb_matrix/animation.ex b/lib/rgb_matrix/animation.ex index 1c64255..2d06f3d 100644 --- a/lib/rgb_matrix/animation.ex +++ b/lib/rgb_matrix/animation.ex @@ -1,18 +1,26 @@ defmodule RGBMatrix.Animation do + @moduledoc """ + Provides the behaviour and interface for working with animations. + """ + alias Layout.LED + alias RGBMatrix.Animation.Config - @callback new(leds :: list(LED.t()), config :: any) :: {render_in, any} - @callback render(state :: any, config :: any) :: - {list(RGBMatrix.any_color_model()), render_in, any} - @callback interact(state :: any, config :: any, led :: LED.t()) :: {render_in, any} + @type animation_state :: any @type t :: %__MODULE__{ type: type, - config: any, + config: Config.t(), state: any } defstruct [:type, :config, :state] + @callback new(leds :: [LED.t()], config :: Config.t()) :: {render_in, animation_state} + @callback render(state :: animation_state, config :: Config.t()) :: + {render_in, [RGBMatrix.any_color_model()], animation_state} + @callback interact(state :: animation_state, config :: Config.t(), led :: LED.t()) :: + {render_in, animation_state} + defmacro __using__(_) do quote do @behaviour RGBMatrix.Animation @@ -34,7 +42,7 @@ defmodule RGBMatrix.Animation do @doc """ Returns a list of the available types of animations. """ - @spec types :: list(type) + @spec types :: [type] def types do [ __MODULE__.CycleAll, @@ -51,7 +59,7 @@ defmodule RGBMatrix.Animation do @doc """ Returns an animation's initial state. """ - @spec new(animation_type :: type, leds :: list(LED.t())) :: {render_in, t} + @spec new(animation_type :: type, leds :: [LED.t()]) :: {render_in, t} def new(animation_type, leds) do config_module = Module.concat([animation_type, Config]) animation_config = config_module.new() @@ -69,7 +77,7 @@ defmodule RGBMatrix.Animation do @doc """ Returns the next state of an animation based on its current state. """ - @spec render(animation :: t) :: {list(RGBMatrix.any_color_model()), render_in, t} + @spec render(animation :: t) :: {render_in, [RGBMatrix.any_color_model()], t} def render(animation) do {render_in, colors, animation_state} = animation.type.render(animation.state, animation.config) diff --git a/lib/rgb_matrix/animation/breathing.ex b/lib/rgb_matrix/animation/breathing.ex index 7b59c1c..80ac56e 100644 --- a/lib/rgb_matrix/animation/breathing.ex +++ b/lib/rgb_matrix/animation/breathing.ex @@ -9,10 +9,12 @@ defmodule RGBMatrix.Animation.Breathing do use Animation defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config end defmodule State do + @moduledoc false defstruct [:color, :tick, :speed, :led_ids] end @@ -35,7 +37,7 @@ defmodule RGBMatrix.Animation.Breathing do colors = Enum.map(led_ids, fn id -> {id, color} end) - {colors, @delay_ms, %{state | tick: tick + 1}} + {@delay_ms, colors, %{state | tick: tick + 1}} end @impl true diff --git a/lib/rgb_matrix/animation/config.ex b/lib/rgb_matrix/animation/config.ex index 3e4adcf..a74c3bd 100644 --- a/lib/rgb_matrix/animation/config.ex +++ b/lib/rgb_matrix/animation/config.ex @@ -1,11 +1,20 @@ defmodule RGBMatrix.Animation.Config do + @moduledoc """ + Provides a behaviour and macros for defining animation configurations. + """ + + # An animation config is a struct, but we don't know ahead of time all the + # concrete types of struct it might be. (e.g.: + # RGBMatrix.Animation.HueWave.Config.t) + @type t :: struct + @callback schema() :: keyword(any) - @callback new(%{optional(atom) => any}) :: struct - @callback update(struct, %{optional(atom) => any}) :: struct + @callback new(%{optional(atom) => any}) :: t + @callback update(t, %{optional(atom) => any}) :: t @types %{ - integer: RGBMatrix.Animation.Config.Integer, - option: RGBMatrix.Animation.Config.Option + integer: RGBMatrix.Animation.Config.FieldType.Integer, + option: RGBMatrix.Animation.Config.FieldType.Option } defmacro __using__(_) do diff --git a/lib/rgb_matrix/animation/config/field_type.ex b/lib/rgb_matrix/animation/config/field_type.ex new file mode 100644 index 0000000..c3ddc5f --- /dev/null +++ b/lib/rgb_matrix/animation/config/field_type.ex @@ -0,0 +1,16 @@ +defmodule RGBMatrix.Animation.Config.FieldType do + @moduledoc """ + Provides a behaviour for defining animation configuration field types. + """ + + @type t :: __MODULE__.Integer.t() | __MODULE__.Option.t() + + @callback validate(t, any) :: :ok | :error + @callback cast(t, any) :: {:ok, any} | :error + + defmacro __using__(_) do + quote do + @behaviour RGBMatrix.Animation.Config.FieldType + end + end +end diff --git a/lib/rgb_matrix/animation/config/field_type/integer.ex b/lib/rgb_matrix/animation/config/field_type/integer.ex new file mode 100644 index 0000000..c6ef7cd --- /dev/null +++ b/lib/rgb_matrix/animation/config/field_type/integer.ex @@ -0,0 +1,61 @@ +defmodule RGBMatrix.Animation.Config.FieldType.Integer do + @moduledoc """ + An integer field type for use in animation configuration. + + Supports defining a minimum and a maximum, as well as a step value. + """ + + use RGBMatrix.Animation.Config.FieldType + + @type t :: %__MODULE__{ + default: integer, + min: integer, + max: integer + } + @enforce_keys [:default, :min, :max] + defstruct [:default, :min, :max, step: 1] + + import RGBMatrix.Utils, only: [mod: 2] + + @impl true + @spec validate(field_type :: t, value :: integer) :: :ok | :error + def validate(field_type, value) do + if value >= field_type.min && + value <= field_type.max && + mod(value, field_type.step) == 0 do + :ok + else + :error + end + end + + @impl true + @spec cast(field_type :: t, value :: any) :: {:ok, integer} | :error + def cast(field_type, value) do + with {:ok, casted_value} <- do_cast(value), + :ok <- validate(field_type, casted_value) do + {:ok, casted_value} + else + :error -> :error + end + end + + defp do_cast(value) when is_integer(value) do + {:ok, value} + end + + defp do_cast(value) when is_float(value) do + {:ok, trunc(value)} + end + + defp do_cast(value) when is_binary(value) do + case Integer.parse(value) do + {parsed_value, _remaining_string} -> {:ok, parsed_value} + :error -> :error + end + end + + defp do_cast(_) do + :error + end +end diff --git a/lib/rgb_matrix/animation/config/field_type/option.ex b/lib/rgb_matrix/animation/config/field_type/option.ex new file mode 100644 index 0000000..2bc06b2 --- /dev/null +++ b/lib/rgb_matrix/animation/config/field_type/option.ex @@ -0,0 +1,49 @@ +defmodule RGBMatrix.Animation.Config.FieldType.Option do + @moduledoc """ + An option field type for use in animation configuration. + + Supports defining a list of pre-defined options as atoms. + """ + + use RGBMatrix.Animation.Config.FieldType + + @type t :: %__MODULE__{ + default: atom, + options: [atom] + } + @enforce_keys [:default, :options] + defstruct [:default, :options] + + @impl true + @spec validate(field_type :: t, value :: atom) :: :ok | :error + def validate(option, value) do + if value in option.options do + :ok + else + :error + end + end + + @impl true + @spec cast(field_type :: t, value :: any) :: {:ok, atom} | :error + def cast(field_type, value) do + with {:ok, casted_value} <- do_cast(value), + :ok <- validate(field_type, casted_value) do + {:ok, casted_value} + else + :error -> :error + end + end + + defp do_cast(binary_value) when is_binary(binary_value) do + try do + {:ok, String.to_existing_atom(binary_value)} + rescue + ArgumentError -> :error + end + end + + defp do_cast(value) when is_atom(value), do: {:ok, value} + + defp do_cast(_value), do: :error +end diff --git a/lib/rgb_matrix/animation/config/integer.ex b/lib/rgb_matrix/animation/config/integer.ex deleted file mode 100644 index 71a4cb0..0000000 --- a/lib/rgb_matrix/animation/config/integer.ex +++ /dev/null @@ -1,48 +0,0 @@ -defmodule RGBMatrix.Animation.Config.Integer do - @enforce_keys [:default, :min, :max] - defstruct [:default, :min, :max, step: 1] - - import RGBMatrix.Utils, only: [mod: 2] - - def validate(integer, value) do - if value >= integer.min && - value <= integer.max && - mod(value, integer.step) == 0 do - :ok - else - :error - end - end - - def cast(integer, bin_value) when is_binary(bin_value) do - case Integer.parse(bin_value) do - {value, ""} -> - cast(integer, value) - - _else -> - case Float.parse(bin_value) do - {value, ""} -> cast(integer, value) - _else -> :error - end - end - end - - def cast(integer, value) when is_float(value) do - int_value = trunc(value) - - if int_value == value do - cast(integer, int_value) - else - :error - end - end - - def cast(integer, value) when is_integer(value) do - case validate(integer, value) do - :ok -> {:ok, value} - :error -> :error - end - end - - def cast(_integer, _value), do: :error -end diff --git a/lib/rgb_matrix/animation/config/option.ex b/lib/rgb_matrix/animation/config/option.ex deleted file mode 100644 index 583eebb..0000000 --- a/lib/rgb_matrix/animation/config/option.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule RGBMatrix.Animation.Config.Option do - @enforce_keys [:default, :options] - defstruct [:default, :options] - - def validate(option, value) do - if value in option.options do - :ok - else - :error - end - end - - def cast(option, bin_value) when is_binary(bin_value) do - try do - value = String.to_existing_atom(bin_value) - cast(option, value) - rescue - ArgumentError -> :error - end - end - - def cast(option, value) when is_atom(value) do - case validate(option, value) do - :ok -> {:ok, value} - :error -> :error - end - end - - def cast(_option, _value), do: :error -end diff --git a/lib/rgb_matrix/animation/cycle_all.ex b/lib/rgb_matrix/animation/cycle_all.ex index 334d90c..55942f4 100644 --- a/lib/rgb_matrix/animation/cycle_all.ex +++ b/lib/rgb_matrix/animation/cycle_all.ex @@ -11,10 +11,12 @@ defmodule RGBMatrix.Animation.CycleAll do import RGBMatrix.Utils, only: [mod: 2] defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config end defmodule State do + @moduledoc false defstruct [:tick, :speed, :led_ids] end @@ -36,7 +38,7 @@ defmodule RGBMatrix.Animation.CycleAll do colors = Enum.map(led_ids, fn id -> {id, color} end) - {colors, @delay_ms, %{state | tick: tick + 1}} + {@delay_ms, colors, %{state | tick: tick + 1}} end @impl true diff --git a/lib/rgb_matrix/animation/hue_wave.ex b/lib/rgb_matrix/animation/hue_wave.ex index 1134d9d..13c483b 100644 --- a/lib/rgb_matrix/animation/hue_wave.ex +++ b/lib/rgb_matrix/animation/hue_wave.ex @@ -12,6 +12,7 @@ defmodule RGBMatrix.Animation.HueWave do import RGBMatrix.Utils, only: [mod: 2] defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config @doc name: "Speed", @@ -41,6 +42,7 @@ defmodule RGBMatrix.Animation.HueWave do end defmodule State do + @moduledoc false defstruct [:tick, :leds, :steps] end @@ -64,7 +66,7 @@ defmodule RGBMatrix.Animation.HueWave do colors = render_colors(leds, steps, time, direction) - {colors, @delay_ms, %{state | tick: tick + 1}} + {@delay_ms, colors, %{state | tick: tick + 1}} end defp render_colors(leds, steps, time, :right) do diff --git a/lib/rgb_matrix/animation/pinwheel.ex b/lib/rgb_matrix/animation/pinwheel.ex index ccf7c03..c5813bb 100644 --- a/lib/rgb_matrix/animation/pinwheel.ex +++ b/lib/rgb_matrix/animation/pinwheel.ex @@ -12,10 +12,12 @@ defmodule RGBMatrix.Animation.Pinwheel do import RGBMatrix.Utils, only: [mod: 2] defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config end defmodule State do + @moduledoc false defstruct [:tick, :speed, :leds, :center] end @@ -52,7 +54,7 @@ defmodule RGBMatrix.Animation.Pinwheel do {id, HSV.new(hue, 100, 100)} end - {colors, @delay_ms, %{state | tick: tick + 1}} + {@delay_ms, colors, %{state | tick: tick + 1}} end defp atan2_8(x, y) do diff --git a/lib/rgb_matrix/animation/random_keypresses.ex b/lib/rgb_matrix/animation/random_keypresses.ex index 30c78bd..380e640 100644 --- a/lib/rgb_matrix/animation/random_keypresses.ex +++ b/lib/rgb_matrix/animation/random_keypresses.ex @@ -9,10 +9,12 @@ defmodule RGBMatrix.Animation.RandomKeypresses do use Animation defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config end defmodule State do + @moduledoc false defstruct [:led_ids, :dirty] end @@ -38,7 +40,7 @@ defmodule RGBMatrix.Animation.RandomKeypresses do id -> [{id, random_color()}] end - {colors, :never, state} + {:never, colors, state} end defp random_color do diff --git a/lib/rgb_matrix/animation/random_solid.ex b/lib/rgb_matrix/animation/random_solid.ex index 6c510b4..a604489 100644 --- a/lib/rgb_matrix/animation/random_solid.ex +++ b/lib/rgb_matrix/animation/random_solid.ex @@ -9,10 +9,12 @@ defmodule RGBMatrix.Animation.RandomSolid do use Animation defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config end defmodule State do + @moduledoc false defstruct [:led_ids] end @@ -29,7 +31,7 @@ defmodule RGBMatrix.Animation.RandomSolid do colors = Enum.map(led_ids, fn id -> {id, color} end) - {colors, :never, state} + {:never, colors, state} end defp random_color do diff --git a/lib/rgb_matrix/animation/solid_color.ex b/lib/rgb_matrix/animation/solid_color.ex index 1ee7d6c..09c34bf 100644 --- a/lib/rgb_matrix/animation/solid_color.ex +++ b/lib/rgb_matrix/animation/solid_color.ex @@ -9,10 +9,12 @@ defmodule RGBMatrix.Animation.SolidColor do use Animation defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config end defmodule State do + @moduledoc false defstruct [:color, :led_ids] end @@ -29,7 +31,7 @@ defmodule RGBMatrix.Animation.SolidColor do colors = Enum.map(led_ids, fn id -> {id, color} end) - {colors, :never, state} + {:never, colors, state} end @impl true diff --git a/lib/rgb_matrix/animation/solid_reactive.ex b/lib/rgb_matrix/animation/solid_reactive.ex index 105e314..bba9f0f 100644 --- a/lib/rgb_matrix/animation/solid_reactive.ex +++ b/lib/rgb_matrix/animation/solid_reactive.ex @@ -11,6 +11,7 @@ defmodule RGBMatrix.Animation.SolidReactive do import RGBMatrix.Utils, only: [mod: 2] defmodule Config do + @moduledoc false use RGBMatrix.Animation.Config @doc name: "Speed", @@ -39,6 +40,7 @@ defmodule RGBMatrix.Animation.SolidReactive do end defmodule State do + @moduledoc false defstruct [:first_render, :paused, :tick, :color, :leds, :hits] end @@ -91,7 +93,7 @@ defmodule RGBMatrix.Animation.SolidReactive do this_color != color end) - {colors, @delay_ms, %{state | tick: tick + 1, hits: hits, paused: hits == %{}}} + {@delay_ms, colors, %{state | tick: tick + 1, hits: hits, paused: hits == %{}}} end @impl true diff --git a/lib/rgb_matrix/engine.ex b/lib/rgb_matrix/engine.ex index 7e57dea..2566546 100644 --- a/lib/rgb_matrix/engine.ex +++ b/lib/rgb_matrix/engine.ex @@ -1,5 +1,3 @@ -require Logger - defmodule RGBMatrix.Engine do @moduledoc """ Renders [`Animation`](`RGBMatrix.Animation`)s and outputs colors to be @@ -33,8 +31,7 @@ defmodule RGBMatrix.Engine do dynamically, set this to an empty list `[]`. """ @spec start_link( - {leds :: [LED.t()], initial_animation_type :: Animation.type(), - paintables :: list(module)} + {leds :: [LED.t()], initial_animation_type :: Animation.type(), paintables :: [module]} ) :: GenServer.on_start() def start_link({leds, initial_animation_type, paintables}) do @@ -85,7 +82,7 @@ defmodule RGBMatrix.Engine do Enum.reduce(paintables, initial_state, fn paintable, state -> add_paintable(paintable, state) end) - |> set_animation(initial_animation_type) + |> init_and_set_animation(initial_animation_type) {:ok, state} end @@ -100,7 +97,7 @@ defmodule RGBMatrix.Engine do %State{state | paintables: paintables} end - defp set_animation(state, animation_type) do + defp init_and_set_animation(state, animation_type) do {render_in, animation} = Animation.new(animation_type, state.leds) state = schedule_next_render(state, render_in) @@ -159,7 +156,7 @@ defmodule RGBMatrix.Engine do @impl GenServer def handle_cast({:set_animation, animation_type}, state) do - state = set_animation(state, animation_type) + state = init_and_set_animation(state, animation_type) {:noreply, state} end diff --git a/lib/xebow.ex b/lib/xebow.ex index c0d797d..7441169 100644 --- a/lib/xebow.ex +++ b/lib/xebow.ex @@ -33,5 +33,6 @@ defmodule Xebow do @layout Layout.new(@keys, @leds) + @spec layout() :: Layout.t() def layout, do: @layout end diff --git a/lib/xebow/leds.ex b/lib/xebow/leds.ex index edcf834..e296f37 100644 --- a/lib/xebow/leds.ex +++ b/lib/xebow/leds.ex @@ -1,5 +1,3 @@ -require Logger - defmodule Xebow.LEDs do @moduledoc """ GenServer that interacts with the SPI device that controls the RGB LEDs on the diff --git a/test/rgb_matrix/engine_test.exs b/test/rgb_matrix/engine_test.exs index e0fb1ae..d2f69fd 100644 --- a/test/rgb_matrix/engine_test.exs +++ b/test/rgb_matrix/engine_test.exs @@ -1,111 +1,111 @@ defmodule RGBMatrix.EngineTest do - use ExUnit.Case + # use ExUnit.Case - alias RGBMatrix.{Animation, Engine, Frame} + # alias RGBMatrix.{Animation, Engine, Frame} - @leds Xebow.layout() |> Layout.leds() + # @leds Xebow.layout() |> Layout.leds() - # Creates a RGBMatrix.Paintable module that emits frames to the test suite process. - defp paintable(%{test: test_name}) do - process = self() - module_name = String.to_atom("#{test_name}-paintable") + # # Creates a RGBMatrix.Paintable module that emits frames to the test suite process. + # defp paintable(%{test: test_name}) do + # process = self() + # module_name = String.to_atom("#{test_name}-paintable") - Module.create( - module_name, - quote do - def get_paint_fn do - fn frame -> - send(unquote(process), {:frame, frame}) - end - end - end, - Macro.Env.location(__ENV__) - ) + # Module.create( + # module_name, + # quote do + # def get_paint_fn do + # fn frame -> + # send(unquote(process), {:frame, frame}) + # end + # end + # end, + # Macro.Env.location(__ENV__) + # ) - %{paintable: module_name} - end + # %{paintable: module_name} + # end - # Creates a single pixel, single frame animation. - defp solid_animation(red \\ 255, green \\ 127, blue \\ 0) do - pixels = [{0, 0}] - color = Chameleon.RGB.new(red, green, blue) - frame = Frame.solid_color(pixels, color) + # # Creates a single pixel, single frame animation. + # defp solid_animation(red \\ 255, green \\ 127, blue \\ 0) do + # pixels = [{0, 0}] + # color = Chameleon.RGB.new(red, green, blue) + # frame = Frame.solid_color(pixels, color) - animation = - Animation.new( - type: Animation.Static, - frames: [frame], - delay_ms: 10, - loop: 1 - ) + # animation = + # Animation.new( + # type: Animation.Static, + # frames: [frame], + # delay_ms: 10, + # loop: 1 + # ) - {animation, frame} - end + # {animation, frame} + # end - setup [:paintable] + # setup [:paintable] - test "renders a solid animation", %{paintable: paintable} do - {animation, frame} = solid_animation() + # test "renders a solid animation", %{paintable: paintable} do + # {animation, frame} = solid_animation() - start_supervised!({Engine, {@leds, animation, [paintable]}}) + # start_supervised!({Engine, {@leds, animation, [paintable]}}) - assert_receive {:frame, ^frame} - end + # assert_receive {:frame, ^frame} + # end - test "renders a multi-frame, multi-pixel animation", %{paintable: paintable} do - pixels = [ - {0, 0}, - {0, 1}, - {1, 0}, - {1, 1} - ] + # test "renders a multi-frame, multi-pixel animation", %{paintable: paintable} do + # pixels = [ + # {0, 0}, + # {0, 1}, + # {1, 0}, + # {1, 1} + # ] - frames = [ - Frame.solid_color(pixels, Chameleon.Keyword.new("red")), - Frame.solid_color(pixels, Chameleon.Keyword.new("green")), - Frame.solid_color(pixels, Chameleon.Keyword.new("blue")), - Frame.solid_color(pixels, Chameleon.Keyword.new("white")) - ] + # frames = [ + # Frame.solid_color(pixels, Chameleon.Keyword.new("red")), + # Frame.solid_color(pixels, Chameleon.Keyword.new("green")), + # Frame.solid_color(pixels, Chameleon.Keyword.new("blue")), + # Frame.solid_color(pixels, Chameleon.Keyword.new("white")) + # ] - animation = - Animation.new( - type: Animation.Static, - frames: frames, - delay_ms: 10, - loop: 1 - ) + # animation = + # Animation.new( + # type: Animation.Static, + # frames: frames, + # delay_ms: 10, + # loop: 1 + # ) - start_supervised!({Engine, {@leds, animation, [paintable]}}) + # start_supervised!({Engine, {@leds, animation, [paintable]}}) - Enum.each(frames, fn frame -> - assert_receive {:frame, ^frame} - end) - end + # Enum.each(frames, fn frame -> + # assert_receive {:frame, ^frame} + # end) + # end - test "can play a different animation", %{paintable: paintable} do - {animation, _frame} = solid_animation() - {animation_2, frame_2} = solid_animation(127, 127, 127) + # test "can play a different animation", %{paintable: paintable} do + # {animation, _frame} = solid_animation() + # {animation_2, frame_2} = solid_animation(127, 127, 127) - start_supervised!({Engine, {@leds, animation, [paintable]}}) + # start_supervised!({Engine, {@leds, animation, [paintable]}}) - :ok = Engine.play_animation(animation_2) + # :ok = Engine.play_animation(animation_2) - assert_receive {:frame, ^frame_2} - end + # assert_receive {:frame, ^frame_2} + # end - test "can register and unregister paintables", %{paintable: paintable} do - {animation, frame} = solid_animation() - {animation_2, frame_2} = solid_animation(127, 127, 127) + # test "can register and unregister paintables", %{paintable: paintable} do + # {animation, frame} = solid_animation() + # {animation_2, frame_2} = solid_animation(127, 127, 127) - start_supervised!({Engine, {@leds, animation, []}}) + # start_supervised!({Engine, {@leds, animation, []}}) - :ok = Engine.register_paintable(paintable) + # :ok = Engine.register_paintable(paintable) - assert_receive {:frame, ^frame} + # assert_receive {:frame, ^frame} - :ok = Engine.unregister_paintable(paintable) - :ok = Engine.play_animation(animation_2) - - refute_receive {:frame, ^frame_2} - end + # :ok = Engine.unregister_paintable(paintable) + # :ok = Engine.play_animation(animation_2) + + # refute_receive {:frame, ^frame_2} + # end end diff --git a/test/xebow_test.exs b/test/xebow_test.exs index fe576a3..daf7879 100644 --- a/test/xebow_test.exs +++ b/test/xebow_test.exs @@ -1,7 +1,7 @@ defmodule XebowTest do use ExUnit.Case - test "holds the xebow layout" do + test "has layout" do assert %Layout{} = Xebow.layout() end end