Interaktion I: GUI

controlP5, Ausgangspunkt: Schriftagenten, Bestimmung der Parameter, Zuordnung von Variablen, controlP5 verwenden, Slider, Texteingabe, Buttons

controlP5

Neben dem direkten Zugriff auf die Maus (mousePressed()) und die Tastatur erleichtern Libraries das Erzeugen und Benutzen von GUIs. Z.B. controlP5 (Sketch/Import Library.../Add Library...). Einen Überblick über die Funktionalität liefert Open/Examples/Contributed Libraries/ControlP5.

Die Library ist relativ komplex. Wie steigen ein indem wir die Schriftagenten um eine einfache GUI erweitern.

Ausgangspunkt: Schriftagenten

Setup und draw erzeugen eine Schriftform und zeichnen sie nach, mit Hilfe einer Menge von Agenten.

import geomerative.*;

Mover[] agenten;
RShape textForm;

void setup() {
  size(600, 400);
  
  RG.init(this);
  RFont font;
  font = new RFont("cour.ttf", 400, RFont.LEFT);

  String text = "A";
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);
  
  background(255);
  
  agenten = new Mover[10];
  for (int i=0; i<agenten.length; i++) {
    agenten[i] = new Mover();
  }
}

void draw() {
  translate(160, 280);
  for (int i=0; i<agenten.length; i++) {
    agenten[i].update();
    agenten[i].display();
  }
}

Die eigentlichen Agenten bewegen sich zufällig, wobei jede Bewegung gestoppt wird, die die Grenzen der Schrift (contains()) verlässt.

class Mover {
  float x;
  float y;
  float xp;
  float yp;
  
  Mover() {
    x = random(width);
    y = random(-400, 0);
    while (!textForm.contains (new RPoint (x, y))) {
      x = random(width);
      y = random(-400, 0);
    }
  }
  
  void display() {
    strokeWeight(1);
    stroke(0,32);
    if (x!=xp && y != yp) {
      line(x,y,xp,yp);
    }
  }
  
  void update() {
    xp = x;
    yp = y;
    float r = random(4);
    float dx = 0;
    float dy = 0;
    dx = random(-15,15);
    dy = random(-15,15);
    x = x + dx;
    y = y + dy;
    if (!textForm.contains(new RPoint(x,y))) {
      x = x - dx;
      y = y - dy;
    }
  }
}

Bestimmung der Parameter

Interessante Parameter dieses Programms sind:

Wie lang sind die Linien, die die Agenten Zeichnen??

dx = random(-15,15);
dy = random(-15,15);

Welcher Buchstabe/Text wird gezeichnet?

String text = "A";

Wie schnell wird gezeichnet?

void draw() {
  translate(30, 270);
  for (int i=0; i<agenten.length; i++) {
    agenten[i].update();
    agenten[i].display();
  }
}

Zuordnung von Variablen

Wir ordnen den Parametern Variablen zu.

Wie lang sind die Linien, die die Agenten Zeichnen?

float linienLaenge = 15;
dx = random(-linienLaenge,linienLaenge);
dy = random(-linienLaenge,linienLaenge);

Welcher Buchstabe/Text wird gezeichnet?

String text;
text = "A";

Wie schnell wird gezeichnet?

int geschwindigkeit = 30;
void draw() {
  translate(160, 280);
  if (frameCount%geschwindigkeit==0) {
    for (int i=0; i<agenten.length; i++) {
      agenten[i].update();
      agenten[i].display();
    }
  }
}

(Der Modulo-Operator (%) sorgt hier dafür, dass nur in den Frames etwas passiert, die durch geschwindigkeit teilbar sind. Also zunächst in jedem dreissigsten.)

controlP5 verwenden

controlP5 einbinden über Sketch/Import Library..., eine Variable erzeugen und initialisieren, die die GUI verwaltet (cp5). Zur besseren Übersichtlichkeit benutzen wir eine eigene Methode erzeugeGUI, die das Erzeugen der GUI übernimmt.

import controlP5.*;
import geomerative.*;

ControlP5 cp5;
int anzahlAgenten = 10;
float linienLaenge = 15;
int geschwindigkeit = 30;

Mover[] agenten;
RShape textForm;

void setup() {
  size(600, 400);
  
  erzeugeGUI();
  
  RG.init(this);
  RFont font;
  font = new RFont("cour.ttf", 400, RFont.LEFT);

  String text = "A";
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);
  
  background(128);
  
  agenten = new Mover[10];
  for (int i=0; i<tagenten.length; i++) {
    agenten[i] = new Mover();
  }
}

void erzeugeGUI() {
  cp5 = new ControlP5(this);
}

// usw...

Damit unsere Transformationen, wie z.B. translate(...) die GUI nicht durcheinanderbrigen, werden sie zwischen pushMatrix() und popMatrix() eingepackt.

void draw() {
  pushMatrix();
  translate(160, 280);
  if (frameCount%geschwindigkeit==0) {
    for (int i=0; i<agenten.length; i++) {
      agenten[i].update();
      agenten[i].display();
    }
  }
  popMatrix();
}

Slider

Die GUI bekommt zwei Slider - je einen für die Länge der Linien und die Zeichengeschwindigkeit.

void erzeugeGUI() {
  cp5 = new ControlP5(this);
  cp5.addSlider("linienLaenge")
    .setPosition(20, 60)
    .setRange(1, 50);
  cp5.addSlider("geschwindigkeit")
    .setPosition(20, 90)
    .setRange(1, 30);
}

Die Slider funktionieren sofort: controlP5 verknüpft sie über ihren Namen mit den dazugehörigen Variablen.

Wir verlegen die Beschriftung nach oben und vergrößern die Slider...

void erzeugeGUI() {
  cp5 = new ControlP5(this);
  cp5.addSlider("linienLaenge")
    .setPosition(20, 60)
    .setRange(1, 50)
    .setSize(100,15)
    .setColorCaptionLabel(0)
    .getCaptionLabel().align(ControlP5.BOTTOM, ControlP5.TOP_OUTSIDE);
  cp5.addSlider("geschwindigkeit")
    .setPosition(20, 120)
    .setRange(1, 30)
    .setSize(100,15)
    .setColorCaptionLabel(0)
    .getCaptionLabel().align(ControlP5.BOTTOM, ControlP5.TOP_OUTSIDE);
}

Texteingabe

Texteingabe übernimmt ein TextField:

  cp5.addTextfield("texteingabe")
    .setPosition(20, 170)
    .setSize(100, 40)
    .setFont(createFont("cour.ttf", 30))
    .setText("A")
    .setCaptionLabel("Text")
    .setColorCaptionLabel(0)
    .getCaptionLabel().align(ControlP5.BOTTOM, ControlP5.TOP_OUTSIDE);

Das Textfeld wird jetzt nicht mit einer Variable verknüpft, sondern mit einer gleichnamigen Methode, die reagiert, wenn im Textfeld Enter gedrückt wird. In der Methode wird die Eingabe in eine neue Schriftform verwandelt. Dann werden die Agenten neu gestartet.

void texteingabe(String input) {
  text = input;
  RFont font;
  font = new RFont("cour.ttf", 400, RFont.LEFT);
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);
  agenten = new Mover[10];
  for (int i=0; i<agenten.length; i++) {
    agenten[i] = new Mover();
  }
}

Buttons

Ein Button löscht den Bildschirm. Einfache Buttons heißen Bang.

  cp5.addBang("clear")
    .setPosition(70, 240)
    .setSize(60, 40)
    .setCaptionLabel("Clear!")
    .getCaptionLabel().align(ControlP5.CENTER, ControlP5.CENTER);

Auch die Bangs werden über eine gleichnamige Methode mit einer Aktion verknüpft.

void clear() {
  background(255);
}

Ein weiterer Button stoppt das Zeichnen. Dazu wird eine zusätzliche Variable nötig: stop, die in das if in draw() aufegnommen wird.

int stop = 0;
void draw() {
  pushMatrix();
  translate(160, 280);
  if (frameCount%geschwindigkeit==0 && stop==0) {
    for (int i=0; i<agenten.length; i++) {
      agenten[i].update();
      agenten[i].display();
    }
  }
  popMatrix();
}

Die Methode stop() schaltet die Variable um: wenn sie 1 ist wird sie 0 und umgekehrt. Zusätzlich ändert sich die Beschriftung des Buttons (Start/Stop):

void stop() {
  if (stop==0) {
    stop = 1;
    cp5.getController("stop").setCaptionLabel("Start");
  } else {
    stop = 0;
    cp5.getController("stop").setCaptionLabel("Stop");
  }
}
  cp5.addBang("stop")
    .setPosition(20, 240)
    .setSize(40, 40)
    .setCaptionLabel("Stop")
    .getCaptionLabel().align(ControlP5.CENTER, ControlP5.CENTER);

Das gesamte Programm, in zwei Tabs:

import controlP5.*;
import geomerative.*;

ControlP5 cp5;
float linienLaenge = 15;
int geschwindigkeit = 30;
int stop = 0;
String text;

Mover[] agenten;
RShape textForm;

void setup() {
  size(600, 400);

  erzeugeGUI();

  RG.init(this);
  RFont font;
  font = new RFont("cour.ttf", 400, RFont.LEFT);

  text = "A";
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);

  background(255);

  agenten = new Mover[10];
  for (int i=0; i<agenten.length; i++) {
    agenten[i] = new Mover();
  }
}

void erzeugeGUI() {
  cp5 = new ControlP5(this);
  cp5.addSlider("linienLaenge")
    .setPosition(20, 60)
    .setRange(1, 50)
    .setSize(100,15)
    .setColorCaptionLabel(0)
    .getCaptionLabel().align(ControlP5.BOTTOM, ControlP5.TOP_OUTSIDE);
  cp5.addSlider("geschwindigkeit")
    .setPosition(20, 100)
    .setRange(1, 30)
    .setSize(100,15)
    .setColorCaptionLabel(0)
    .getCaptionLabel().align(ControlP5.BOTTOM, ControlP5.TOP_OUTSIDE);
  cp5.addTextfield("texteingabe")
    .setPosition(20, 170)
    .setSize(100, 40)
    .setFont(createFont("cour.ttf", 30))
    .setText("A")
    .setCaptionLabel("Text")
    .setColorCaptionLabel(0)
    .getCaptionLabel().align(ControlP5.BOTTOM, ControlP5.TOP_OUTSIDE);
  cp5.addBang("clear")
    .setPosition(70, 240)
    .setSize(40, 40)
    .setCaptionLabel("Clear")
    .getCaptionLabel().align(ControlP5.CENTER, ControlP5.CENTER);
  cp5.addBang("stop")
    .setPosition(20, 240)
    .setSize(40, 40)
    .setCaptionLabel("Stop")
    .getCaptionLabel().align(ControlP5.CENTER, ControlP5.CENTER);
}

void draw() {
  pushMatrix();
  translate(160, 280);
  if (frameCount%geschwindigkeit==0 && stop==0) {
    for (int i=0; i<agenten.length; i++) {
      agenten[i].update();
      agenten[i].display();
    }
  }
  popMatrix();
}

void texteingabe(String input) {
  text = input;
  RFont font;
  font = new RFont("cour.ttf", 400, RFont.LEFT);
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);
  agenten = new Mover[10];
  for (int i=0; i<agenten.length; i++) {
    agenten[i] = new Mover();
  }
}

void clear() {
  background(255);
}

void stop() {
  if (stop==0) {
    stop = 1;
    cp5.getController("stop").setCaptionLabel("Start");
  } else {
    stop = 0;
    cp5.getController("stop").setCaptionLabel("Stop");
  }
}
class Mover {
  float x;
  float y;
  float xp;
  float yp;
  
  Mover() {
    x = random(width);
    y = random(-400, 0);
    while (!textForm.contains (new RPoint (x, y))) {
      x = random(width);
      y = random(-400, 0);
    }
  }
  
  void display() {
    strokeWeight(1);
    stroke(0,32);
    if (x!=xp && y != yp) {
      line(x,y,xp,yp);
    }
  }
  
  void update() {
    xp = x;
    yp = y;
    float r = random(4);
    float dx = 0;
    float dy = 0;
    dx = random(-linienLaenge,linienLaenge);
    dy = random(-linienLaenge,linienLaenge);
    x = x + dx;
    y = y + dy;
    if (!textForm.contains(new RPoint(x,y))) {
      x = x - dx;
      y = y - dy;
    }
  }
}