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:
- 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.
- 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:
-
Det bliver sendt til den første
Color
pid som før. -
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.