nyaray.com

Brain to hand, hand to keyboard.


Scenic Mini-Tip #1

Thursday, 30 May 2019 Tags: elixirscenic

So, you’re finally over the hump and are starting to feel productive with your Scenic skills and have just started to get more than a handful of different components and primitives into your Scene when you notice that your graph is a bit messy.

When making a Scene (hehe) you might end up with something like this at the top-level of your graph definition …

@graph Graph.build(font: :roboto, font_size: 16)
       |> group(
         &(&1
           |> group(
             fn g ->
               g
               |> button("button 1", translate: {0, 0})
               |> button("button 2", translate: {50, 0})
             end,
             translate: {500, 0}
           )
           |> group(
             fn g ->
               g
               |> button("button A", translate: {0, 0})
               |> button("button B", translate: {50, 0})
             end,
             translate: {600, 0}
           )),
         translate: {0, 500}
       )
       |> group(
         &(&1
           |> group(@chan_q, translate: {0, 110}) # Similar to the group below ...
           |> group(@chan_f, translate: {0, 160}) # Similar to the group below ...
           |> group(
             fn g ->
               g
               |> Rotary.add_to_graph(0.50, id: {:chan, {:lf, :db}}, mode: :center, translate: {0, 0})
               |> Rotary.add_to_graph(0.50, id: {:chan, {:lmf, :db}}, mode: :center, translate: {40, 0})
               |> Rotary.add_to_graph(0.50, id: {:chan, {:hmf, :db}}, mode: :center, translate: {80, 0})
               |> Rotary.add_to_graph(0.50, id: {:chan, {:hf, :db}}, mode: :center, translate: {120, 0})
             end,
             translate: {0, 350}
           )),
         translate: {140, 0}
       )

… possibly with more similar blocks above.

If you are anything like me, it already bothers you that the code above mixes (gasp!) the two types of anonymous functions. This is in order to save one line per level of nesting when running mix format, as the capture-style (&(&1 * &1)) is allowed to keep its start and first expression terms on the same line whereas the arrow-style (fn x -> x*x end) isn’t. Unfortunately, it is not allowed to nest captures, so they can only be used on the first level.

For those that prefer consistency or just arrow-style anonymous functions, the following version might be more appealing, although still not that pleasant:

@graph Graph.build(font: :roboto, font_size: 16)
       |> group(
         fn g ->
           g
           |> group(
             fn g ->
               g
               |> button("button 1", translate: {0, 0})
               |> button("button 2", translate: {50, 0})
             end,
             translate: {500, 0}
           )
           |> group(
             fn g ->
               g
               |> button("button A", translate: {0, 0})
               |> button("button B", translate: {50, 0})
             end,
             translate: {600, 0}
           )
         end,
         translate: {0, 500}
       )
       |> group(
         fn g ->
           g
           |> group(@chan_q, translate: {0, 110}) # Similar to the group below ...
           |> group(@chan_f, translate: {0, 160}) # Similar to the group below ...
           |> group(
             fn g ->
               g
               |> Rotary.add_to_graph(0.50, id: {:chan, {:lf, :db}}, mode: :center, translate: {0, 0})
               |> Rotary.add_to_graph(0.50, id: {:chan, {:lmf, :db}}, mode: :center, translate: {40, 0})
               |> Rotary.add_to_graph(0.50, id: {:chan, {:hmf, :db}}, mode: :center, translate: {80, 0})
               |> Rotary.add_to_graph(0.50, id: {:chan, {:hf, :db}}, mode: :center, translate: {120, 0})
             end,
             translate: {0, 350}
           )
         end,
         translate: {140, 0}
       )

Don’t even think about editing that big ‘ol mess to change nesting (I did), it’s not fun.

Deferred Helpers

Ok, enough of this rubbish, let’s write a cleaner version — enter deferred specs!

  @graph Graph.build(font: :roboto, font_size: 16)
         |> add_specs_to_graph(@top_groups)

See? That’s much cleaner! Thank you, bye bye.

Yeah, you probably can see where this is going; we’ll be sacrificing the general overview in order to gain a proper understanding of what we’re doing at each level and with what.

# Not factored apart enough?
# A stylistic choice?
# You decide!
@top_button_grid [
  group_spec(
    [
      button_spec("button 1", translate: {0, 0}),
      button_spec("button 2", translate: {50, 0})
    ],
    translate: {500, 0}
  ),
  group_spec(
    [
      button_spec("button A", translate: {0, 0}),
      button_spec("button B", translate: {50, 0})
    ],
    translate: {600, 0}
  )
]

@chan_q ...

@chan_f ...

@chan_db [
  Rotary.rotary_spec(0.50, id: {:chan, {:lf, :db}}, mode: :center, translate: {0, 0}),
  Rotary.rotary_spec(0.50, id: {:chan, {:lmf, :db}}, mode: :center, translate: {40, 0}),
  Rotary.rotary_spec(0.50, id: {:chan, {:hmf, :db}}, mode: :center, translate: {80, 0}),
  Rotary.rotary_spec(0.50, id: {:chan, {:hf, :db}}, mode: :center, translate: {120, 0})
]

@top_controls [
  group_spec(@chan_q, translate: {0, 110}),
  group_spec(@chan_f, translate: {0, 160}),
  group_spec(@chan_db, translate: {0, 350})
]

@top_groups [
  group_spec(@top_button_grid, translate: {0, 500}),
  group_spec(@top_controls, translate: {140, 0})
]

@graph Graph.build(font: :roboto, font_size: 16)
       |> add_specs_to_graph(@top_groups)

So, this might be enough for you! But I’ve actually written a deferred group spec to improve things, ever so slightly.

Can we do Better? (Pull Request Pending!)

I’ve made a pull request, so it will hopefully be in Scenic soon.

I have a full screen-height of graph code in a project I am working on and noticed that it was tricky to grasp what was going on overall. The main issue was that mix format would enforce unfortunate formatting that wasted lines, which made me split it up quite heavily and lose track of how different groups related to each other.

@graph Graph.build(font: :roboto, font_size: 16)
       |> add_specs_to_graph([
         group_spec_r([translate: {0, 500}], [
           group_spec_r([translate: {500, 0}], [
             button_spec("button 1", translate: {0, 0}),
             button_spec("button 2", translate: {50, 0})
           ]),
           group_spec_r([translate: {600, 0}], [
             button_spec("button A", translate: {0, 0}),
             button_spec("button B", translate: {50, 0})
           ])
         ]),
         group_spec_r([translate: {140, 0}], [
           group_spec_r([translate: {0, 110}], [
             Rotary.rotary_spec(0.0, id: {:chan, {:lmf, :q}}, mode: :fill, translate: {180, 0}),
             Rotary.rotary_spec(0.0, id: {:chan, {:hmf, :q}}, mode: :fill, translate: {220, 0})
           ]),
           group_spec_r([translate: {0, 160}], [
             Rotary.add_to_graph(0.0, id: {:chan, {:hpf, :hz}}, mode: :fill, translate: {100, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:lf, :hz}}, mode: :fill, translate: {140, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:lmf, :hz}}, mode: :fill, translate: {180, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:hmf, :hz}}, mode: :fill, translate: {220, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:hf, :hz}}, mode: :fill, translate: {260, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:lpf, :hz}}, mode: :fill, translate: {300, 0})
           ]),
           group_spec_r([translate: {0, 350}], [
             Rotary.rotary_spec(0.50, id: {:chan, {:lf, :db}}, mode: :center, translate: {0, 0}),
             Rotary.rotary_spec(0.50, id: {:chan, {:lmf, :db}}, mode: :center, translate: {40, 0}),
             Rotary.rotary_spec(0.50, id: {:chan, {:hmf, :db}}, mode: :center, translate: {80, 0}),
             Rotary.rotary_spec(0.50, id: {:chan, {:hf, :db}}, mode: :center, translate: {120, 0})
           ])
         ])
       ])

The observant reader will have noticed that I bothered to include the @chan_q and @chan_f groups, which I did since they no longer shadow the issues being demonstrated. I hope I’ve made a compelling case for the use of the group_spec_r helper!

Otherwise, I’ll just have to implement a simple hex package or something, I guess.

Bonus Points for Brevity

Transformations (translate, scale, rotate, …) can be shortened to a single letter!

@graph Graph.build(font: :roboto, font_size: 16)
       |> add_specs_to_graph([
         group_spec_r([t: {0, 500}], [
           group_spec_r([t: {500, 0}], [
             button_spec("button 1", t: {0, 0}),
             button_spec("button 2", t: {50, 0})
           ]),
           group_spec_r([t: {600, 0}], [
             button_spec("button A", t: {0, 0}),
             button_spec("button B", t: {50, 0})
           ])
         ]),
         group_spec_r([t: {140, 0}], [
           group_spec_r([t: {0, 110}], [
             Rotary.rotary_spec(0.0, id: {:chan, {:lmf, :q}}, mode: :fill, t: {180, 0}),
             Rotary.rotary_spec(0.0, id: {:chan, {:hmf, :q}}, mode: :fill, t: {220, 0})
           ]),
           group_spec_r([t: {0, 160}], [
             Rotary.add_to_graph(0.0, id: {:chan, {:hpf, :hz}}, mode: :fill, t: {100, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:lf, :hz}}, mode: :fill, t: {140, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:lmf, :hz}}, mode: :fill, t: {180, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:hmf, :hz}}, mode: :fill, t: {220, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:hf, :hz}}, mode: :fill, t: {260, 0}),
             Rotary.add_to_graph(0.0, id: {:chan, {:lpf, :hz}}, mode: :fill, t: {300, 0})
           ]),
           group_spec_r([t: {0, 350}], [
             Rotary.rotary_spec(0.50, id: {:chan, {:lf, :db}}, mode: :center, t: {0, 0}),
             Rotary.rotary_spec(0.50, id: {:chan, {:lmf, :db}}, mode: :center, t: {40, 0}),
             Rotary.rotary_spec(0.50, id: {:chan, {:hmf, :db}}, mode: :center, t: {80, 0}),
             Rotary.rotary_spec(0.50, id: {:chan, {:hf, :db}}, mode: :center, t: {120, 0})
           ])
         ])
       ])

If you have read this far, pat yourself on the back and grab a cookie.

Thank you for your time :)