Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Beginner 6

src/livebook/beginner6.livemd

Beginner 6

Mix.install([
  {:kino, "~> 0.14.1"}
])

Forberedelse

Grænseflade

Lad os først definere nogle slidere, der kan bruges til at vælge tre værdier mellem 0 og 255 (begge inklusive). Vi arrangerer disse i et “grid”. Dette skal vi bruge til at udpege en farve ved hjælp af dennes rød, grøn og blå komponenter:

kinos = [
  Kino.Input.range("Rød", min: 0, max: 255),
  Kino.Input.range("Grøn", min: 0, max: 255),
  Kino.Input.range("Blå", min: 0, max: 255),
]
Kino.Layout.grid(kinos)

Nu opretter vi en “frame”. Dette er et sted hvor man kan placere forskellie elementer.

frame = Kino.Frame.new() |> Kino.render()
nil

Bagvedliggende logik

Følgende modul implementerer en GenServer til at modtage opdateringer om justeringer på hver af de tre farvekanaler (en: channel). En GenServer er en separat proces (inde i Elixir) som man kan starte, og derefter sende beskeder til. Når den modtager en sådan besked behandler den beskeden og derefter kan den eventuelt sende en besked tilbage. Her sørger vi for at tegne en firkant i ovenstående frame. To kommentarer i koden indikere at der er to typer funktioner:

  1. Interface funktioner: Interface funktionerne er dem man kalder fra den proces man står i. Dette vil starte en GenServer eller sende en besked til en (forhåbentligt allerede) startet GenServer.
  2. Callback funktioner: Når en GenServer modtager sådan en besked, vil en matchende callback funktion blive kaldt (i denne GenServer’s proces).
defmodule Color do
  use GenServer

  @dim 64

  # interface

  def start_link(widget) do
    color = %{r: 0, g: 0, b: 0}
    GenServer.start_link(__MODULE__, {widget, color})
  end

  def update(pid, channel, value) do
    GenServer.cast(pid, {:update, channel, value})
  end

  # callbacks

  @impl true
  def init({widget,_}=state) do
    Kino.Frame.render(widget, generate_svg(0, 0, 0))
    {:ok, state}
  end

  @impl true
  def handle_cast({:update, channel, value}, {widget, c} = _state) when is_atom(channel) do
    c = Map.put(c, channel, value)
    %{r: r, g: g, b: b} = c
    
    Kino.Frame.render(widget, generate_svg(r, g, b))
    {:noreply, {widget, c}}
  end

  # helpers

  defp generate_svg(r,g,b) do
    """
    
      
    
    """
    |> Kino.Image.new(:svg)
  end
end

Når vi nu har defineret hvad vores Color GenServer er, kan vi starte den. Går denne handling godt vil det resultere i en proces id (aka, en pid). Dette er hvad vi skal bruge til at udpege den nye Color process (vi kan nemlig godt have flere).

Det ser sådan her ud:

{:ok, pid} = Color.start_link(frame)

Slutteligt skal vi hooke os på de events som vores slidere genererer når vi justerer dem. Det gør vi med Kino.listen. Funktionen trunc runder sit input ned til nærmeste heltal og konverterer typen til heltal.

[kino_r, kino_g, kino_b] = kinos

[{:r, kino_r}, {:g, kino_g}, {:b, kino_b}]
|> Enum.map(
  fn {channel, kino} ->
    Kino.listen(kino, fn event -> Color.update(pid, channel, trunc(event.value)) end)
  end
)

Øvelse

Prøv at scrolle op til toppen af denne notebook, og klik på Notebook dependencies and setup. Hvad du ser er en særlig celle hvor man kan tilføje pakker. Lige over denne celle ude til højre er der en Add package (sp) knap. Her kan man søge efter allerede eksisterende pakker som man kan bygge ovenpå. I dette tilfælde har vi valgt at installere pakken kino der giver adgang til en række brugergrænseflade elementer bag ved namespacet Kino.

Scroll nu til de tre slidere (for rød, grøn og blå), og prøv at træk i dem. Hos mig skal man give slip før handlingen registreres. Hvad sker der med kassen nedenfor?

Prøv at klik dig igennem de forskellige celler for at få en forståelse for hvad der foregår.

Lav nu en ny frame. Du kan eventuelt arrangere de to frames i et grid. Start en ny Color GenServer og bind en variabel til dens pid. Udvid nu koden for at håndtere events, sådan at hvert event forårsager to thing:

  1. Det bliver sendt til den første Color pid som før.
  2. Det bliver også sendt til den anden Color pid, men her skal værdien trækkes fra $255$. Hvis den oprindelige værdi er $100$, skal der således sendes $255-100=155$ til denne pid.

Hvis du har styr på din farvelære kan du også lave en tredje frame som viser komplimentærfarven, men det er en del mere kompliceret og her skal du definere en separat GenServer.

Næste trin …

Når du er færdig går du til næste øvelse her.