The book is currently only available in German.
Grafische Oberflächen — `nutze gui`
Bis jetzt haben deine Programme im Terminal gelaufen. Mit dem gui-Modul baust du richtige Fenster mit Knöpfen, Eingabefeldern, Listen und Tabellen — eine eigene Desktop-App.
Wichtig: GUI-Programme laufen nur über den nativen Compiler (
kiste build), nicht überkiste run. Eine GUI ist eine echte native App. Bau sie also so:kiste build meine_app.ki -o meine_app.exe ./meine_app.exe
34.1 Dein erstes Fenster
nutze gui
nimm fenster = gui.fenster_öffne("Hallo", 320, 200) // Titel, Breite, Höhe
nimm knopf = gui.knopf_neu("Klick mich")
gui.zeige(fenster, knopf) // Knopf ins Fenster
gui.starte() // Fenster anzeigen + warten
gui.starte() öffnet das Fenster und läuft, bis du es schließt. Es muss die letzte Zeile sein.
34.2 Auf Klicks reagieren — Handler
Ein Handler ist eine Funktion, die aufgerufen wird, wenn etwas passiert. Sie bekommt das Widget, auf das sie reagiert:
nutze gui
funktion bei_klick(knopf) {
gui.setze_text(knopf, "Geklickt! ✓") // ändert die Knopf-Beschriftung
}
nimm fenster = gui.fenster_öffne("Klick-Demo", 320, 200)
nimm knopf = gui.knopf_neu("Drück mich")
gui.bei_klick(knopf, bei_klick) // Handler an den Knopf binden
gui.zeige(fenster, knopf)
gui.starte()
34.3 Etiketten und Layout
Ein Etikett (etikett_neu) ist ein Text-Schild. Mehrere Widgets ordnest du mit vertikal (Spalte), horizontal (Reihe) oder raster (Gitter) an:
nutze gui
nimm titel = gui.etikett_neu("Meine App")
nimm knopf = gui.knopf_neu("OK")
nimm inhalt = gui.vertikal([titel, knopf]) // untereinander
// nimm inhalt = gui.horizontal([titel, knopf]) // nebeneinander
// nimm inhalt = gui.raster(2, [a, b, c, d]) // Gitter mit 2 Spalten
nimm fenster = gui.fenster_öffne("Layout", 320, 200)
gui.zeige(fenster, inhalt) // den Container anzeigen
gui.starte()
34.4 Farben und Aussehen anpassen
Kiste-Apps haben von Haus aus ein stimmiges dunkles Design. Du kannst es frei ändern:
gui.thema("dunkel") // oder "hell"
gui.setze_farbe("akzent", "#3b82f6") // eigenes Blau für primäre Elemente
gui.setze_farbe("hintergrund", "#0f172a")
Farb-Rollen: akzent, hintergrund, flaeche, text, rahmen, gefahr (Hex-Werte "#rrggbb").
Live ändern: gui.setze_farbe(...) und gui.thema(...) wirken auch während die App läuft — rufst du sie aus einem Handler auf, wechselt die Oberfläche sofort die Farbe. So baust du z.B. einen Knopf, der das Theme umschaltet.
Knöpfe können verschiedene Farben tragen — je nach Wichtigkeit der Aktion:
gui.setze_wichtigkeit(knopf, "primär") // akzent-farbig — die Haupt-Aktion
gui.setze_wichtigkeit(knopf, "gefahr") // rot — Löschen, Abbrechen
gui.setze_wichtigkeit(knopf, "erfolg") // grün — Bestätigen, Speichern
gui.setze_wichtigkeit(knopf, "warnung") // gelb — Vorsicht
gui.setze_wichtigkeit(knopf, "normal") // grau (Standard)
So zeigt ein Fenster mehrere unterschiedlich gefärbte Knöpfe nebeneinander. Beispiel: beispiele/gui_farbwechsel.ki — vier farbige Knöpfe, jeder wechselt live den Hintergrund.
34.5 Text-Eingabe
Ein Eingabefeld (eingabefeld) nimmt Text vom Nutzer entgegen:
nimm feld = gui.eingabefeld("Dein Name …") // Platzhalter-Text
gui.setze_wert(feld, "Welt") // Startwert setzen
nimm name = gui.wert(feld) // aktuellen Text lesen
Auf jede Änderung reagieren — der Handler bekommt das Feld, den neuen Text holst du mit gui.wert:
funktion bei_eingabe(feld) {
sag "Neuer Text: {gui.wert(feld)}"
}
gui.bei_änderung(feld, bei_eingabe)
Mehrzeilig — für Notizen, Editoren oder längere Texte nimmst du gui.textbereich:
nimm notiz = gui.textbereich("Schreib hier …") // Standard-Höhe (8 Zeilen)
nimm gross = gui.textbereich("Langer Text …", 15) // 15 Zeilen hoch
Ein Textbereich verhält sich wie ein Eingabefeld — gui.wert, gui.setze_wert und gui.bei_änderung funktionieren genauso. Mit der Enter-Taste machst du neue Zeilen; lange Zeilen brechen automatisch um, und über die Höhe hinaus wird gescrollt. Die Zeilenzahl ist optional (zweiter Parameter). Beispiele: beispiele/gui_editor.ki, beispiele/gui_notizen.ki.
34.6 Zustand teilen — globale Variablen in Handlern
Ein Handler bekommt nur das Widget, auf das er reagiert. Will er ein anderes Widget ändern (z.B. Eingabe ins Etikett spiegeln), legst du dieses als globale Variable an — Handler dürfen Top-Level-Variablen lesen und schreiben:
nutze gui
nimm anzeige = gui.etikett_neu("Hallo, Welt") // global, oben deklariert
nimm feld = gui.eingabefeld("Dein Name …")
funktion bei_eingabe(f) {
gui.setze_text(anzeige, "Hallo, {gui.wert(f)}") // greift auf `anzeige` zu
}
gui.setze_wert(feld, "Welt")
gui.bei_änderung(feld, bei_eingabe)
nimm inhalt = gui.vertikal([anzeige, feld])
nimm fenster = gui.fenster_öffne("Live-Formular", 360, 160)
gui.zeige(fenster, inhalt)
gui.starte()
Tippst du ins Feld, aktualisiert sich das Etikett live. So baust du echte interaktive Apps — auch einen Zähler:
nimm stand = 0
funktion hoch(knopf) {
stand = stand + 1
gui.setze_text(knopf, "Geklickt: {stand}")
}
34.7 Auswahl, Listen und Tabellen
Auswahl-Box (Dropdown) — eine Option aus wenigen wählen:
nimm box = gui.auswahl_box(["Apfel", "Birne", "Kirsche"])
funktion bei_wahl(b) { sag "Gewählt: {gui.gewählt(b)}" }
gui.bei_wahl(box, bei_wahl)
gui.gewählt(box) gibt den aktuell gewählten Text.
Liste — einen Eintrag aus einer scrollbaren Liste wählen:
nimm liste = gui.liste_anzeige(["Eins", "Zwei", "Drei"])
funktion bei_auswahl(l) {
sag "Eintrag {gui.gewählter_index(l)}: {gui.gewählter_wert(l)}"
}
gui.bei_auswahl(liste, bei_auswahl)
gui.gewählter_index(liste) → Position (0-basiert, -1 wenn keiner), gui.gewählter_wert(liste) → der Text.
Tabelle — 2D-Daten anzeigen und direkt bearbeiten. Du legst die Spalten an und hängst Zeilen an (jede Zeile eine liste<Text>):
nimm tab = gui.tabelle(["Artikel", "Menge"]) // Spalten-Überschriften
gui.zeile_anhängen(tab, ["Schrauben", "120"]) // Zeilen anhängen
gui.zeile_anhängen(tab, ["Muttern", "80"])
nimm m = gui.zell_wert(tab, 0, 1) // Zelle lesen: Zeile 0, Spalte 1 → "120"
gui.setze_zell_wert(tab, 0, 1, "150") // Zelle setzen
funktion bei_zelle(t) {
nimm z = gui.gewählte_zeile(t)
nimm s = gui.gewählte_spalte(t)
sag "Zelle [{z},{s}] = {gui.zell_wert(t, z, s)}"
}
gui.bei_zell_auswahl(tab, bei_zelle) // Klick auf eine Zelle
// gui.bei_zell_änderung(tab, ...) // wenn der Nutzer eine Zelle editiert
Die Zellen sind editierbar — der Nutzer kann direkt hineintippen, gui.zell_wert liest dann den neuen Wert.
Zeilen zählen und entfernen — die Tabelle kann auch schrumpfen und ausgelesen werden:
nimm n = gui.zeilen_anzahl(tab) // Anzahl der Datenzeilen
gui.zeile_entfernen(tab, 0) // erste Zeile löschen (ungültiger Index = passiert nichts)
Damit kannst du eine Tabelle live neu befüllen (alle Zeilen weg, dann neue anhängen):
solange gui.zeilen_anzahl(tab) > 0 {
gui.zeile_entfernen(tab, 0)
}
// … jetzt mit gui.zeile_anhängen neu füllen …
Wählbare Tabelle (Master-Detail) — gui.tabelle hat editierbare Zellen; ein Klick landet
dann im Textfeld der Zelle, nicht in der Zeilen-Auswahl. Wenn du auf Zeilen-Klick reagieren
willst (z. B. „Zeile wählen → oben bearbeiten"), nimm gui.tabelle_wählbar — eine read-only
Tabelle, bei der ein Klick die ganze Zeile auswählt:
nimm tab = gui.tabelle_wählbar(["Name", "Betrag"])
gui.zeile_anhängen(tab, ["Buch", "25"])
funktion bei_zeile(t) {
nimm z = gui.gewählte_zeile(t) // -1 wenn keine
wenn z >= 0 { sag "Zeile {z}: {gui.zell_wert(t, z, 0)}" }
}
gui.bei_zell_auswahl(tab, bei_zeile)
Faustregel:
tabelle= direkt in Zellen tippen ·tabelle_wählbar= Zeilen anklicken (Bearbeiten dann über separate Eingabefelder). Beide nutzen dieselben Funktionen (zeile_anhängen/zell_wert/zeilen_anzahl/zeile_entfernen).
Spaltenbreite selbst bestimmen — normalerweise wählt Kiste die Spaltenbreiten passend zum
Inhalt (und deckelt sie, damit eine Tabelle nicht endlos breit wird). Bei längeren Texten (z. B.
einer Referenz-/Cheat-Sheet-Tabelle) willst du eine Spalte vielleicht bewusst breiter machen,
damit nichts abgeschnitten wird — dafür gibt es gui.spalten_breite:
nimm tab = gui.tabelle(["Taste", "Funktion"])
gui.zeile_anhängen(tab, ["Strg+S", "Speichern"])
gui.zeile_anhängen(tab, ["Strg+Z", "Rückgängig"])
gui.spalten_breite(tab, 0, 140) // Spalte 0 (Taste) schmal
gui.spalten_breite(tab, 1, 420) // Spalte 1 (Funktion) breit → langer Text bleibt lesbar
Die Spaltennummer ist 0-basiert, die Breite in Pixeln. Spalten ohne spalten_breite behalten die
automatische, inhaltsabhängige Breite.
Links öffnen und Text kopieren — für ein „Über"-Fenster oder Hilfe-Seiten willst du oft einen anklickbaren Link (öffnet den Browser) und eine Mailadresse zum Kopieren anbieten:
nimm k = gui.knopf_neu("kiste-lang.org")
gui.bei_klick(k, funktion(x) { gui.öffne_url("https://kiste-lang.org") })
gui.öffne_url(url) öffnet die URL im Standard-Browser; beginnt sie mit mailto:, öffnet sich das
Standard-Mailprogramm. Weil du aber nicht weisst, welches Mailprogramm deine Nutzer:innen verwenden,
ist es oft freundlicher, die Adresse nur zum Kopieren anzubieten:
nimm m = gui.knopf_neu("hallo@example.com ⧉")
nimm hinweis = gui.etikett_neu("")
gui.bei_klick(m, funktion(x) {
gui.kopiere_text("hallo@example.com") // in die Zwischenablage
gui.setze_text(hinweis, "✓ kopiert")
})
gui.kopiere_text(text) legt beliebigen Text in die System-Zwischenablage — die Nutzer:in fügt ihn
dann in ihrem eigenen Programm mit Strg+V ein.
Ein eigenes Programm-Icon — statt des neutralen Standard-Symbols in der Titelleiste und der Taskleiste kannst du dein eigenes Logo setzen:
gui.setze_app_icon("logo.png") // VOR gui.starte() aufrufen
gui.setze_app_icon(pfad) lädt eine Bilddatei (PNG empfohlen) und macht sie zum Fenster- und
Taskleisten-Icon. Ruf es vor gui.starte() auf; die Datei muss beim Programmstart auffindbar sein
(am einfachsten neben der .exe).
Ein Fenster nicht maximierbar machen — ein kleines Dialog- oder Hilfefenster sieht maximiert
oft schlecht aus (die Widgets kleben oben links, der Rest ist leer). Mit gui.fenster_fest fixierst
du ein Fenster auf seine Inhaltsgröße: der Maximieren-Knopf wird deaktiviert und die Größe lässt sich
nicht mehr ziehen.
nimm hilfe = gui.fenster_öffne("Hilfe", 600, 400)
gui.fenster_fest(hilfe, wahr) // kein Maximieren; passt sich dem Inhalt an
gui.fenster_fest(fenster, fest) erwartet als zweites eine Wahrheit (wahr/falsch). Setz es am
besten direkt nach gui.fenster_öffne (also vor dem ersten Anzeigen). Das Hauptfenster lässt
man üblicherweise frei größenänderbar — fenster_fest ist für die kleinen Zweitfenster gedacht.
Doppelklick zum Öffnen — neben dem Einfachklick (Auswahl) kannst du auf eine Liste oder
eine tabelle_wählbar einen Doppelklick-Handler legen — die gewohnte „Öffnen"-Geste aus
Datei-Managern:
nimm liste = gui.liste_anzeige(["Ordner A", "Datei B"])
funktion auswahl(l) { sag "gewählt: {gui.gewählter_wert(l)}" } // Einfachklick
funktion öffnen(l) { sag "ÖFFNE: {gui.gewählter_wert(l)}" } // Doppelklick
gui.bei_auswahl(liste, auswahl)
gui.bei_doppelklick(liste, öffnen) // feuert NUR beim Doppelklick
Einfach- und Doppelklick koexistieren: ein einzelner Klick feuert bei_auswahl /
bei_zell_auswahl, ein Doppelklick feuert bei_doppelklick. Im Doppelklick-Handler geben
gui.gewählter_index / gui.gewählte_zeile schon die doppelt-geklickte Zeile zurück.
Tipp (Discoverability): Lass neben dem Doppelklick einen sichtbaren „Öffnen"-Knopf stehen — nicht jeder errät die Doppelklick-Geste. Beide dürfen denselben Handler nutzen. Hinweis:
bei_doppelklickgilt fürliste,tabelle_wählbar(read-only) und die Leinwand. Die editierbaretabellehat keinen Doppelklick — dort gehört der Doppeltap dem Text-Cursor. Bei jeder anderen Bedienelement-Art meldet Kiste einen Fehler, statt den Handler stillschweigend zu ignorieren.
Doppelklick auf der Leinwand. Malst du deine Oberfläche selbst (Kapitel 34.10), bekommst
du den Doppelklick genauso — gui.maus_x / gui.maus_y sagen dir, wo geklickt wurde. Der
Einfachklick feuert weiter, und zwar sofort: erst wählt der Nutzer etwas aus, dann öffnet
er es mit dem zweiten Klick.
funktion bei_klick(l) { sag "gewählt bei {gui.maus_x(l)}, {gui.maus_y(l)}" }
funktion bei_öffnen(l) { sag "geöffnet!" }
nimm lw = gui.leinwand(300, 200)
gui.bei_maus_klick(lw, bei_klick)
gui.bei_doppelklick(lw, bei_öffnen)
34.8 Dialoge, Menüs und Leisten
Eine echte App hat mehr als ein Fenster mit Widgets: kleine Dialoge zum Bestätigen oder Fragen, eine Menü-Leiste oben und eine Status-Leiste unten.
Dialoge — alle brauchen das Fenster, zu dem sie gehören:
funktion über_zeigen(id) {
gui.dialog_info(fenster, "Über", "Mein Programm — Version 1.")
}
funktion beenden(id) {
gui.dialog_frage(fenster, "Beenden", "Wirklich schließen?", antwort)
}
funktion antwort(ja) {
wenn ja == 1 {
gui.setze_text(status, "Bestätigt.")
} sonst {
gui.setze_text(status, "Abgebrochen.")
}
}
gui.dialog_info(fenster, titel, text)zeigt eine Meldung mit OK-Knopf.gui.dialog_frage(fenster, titel, text, handler)fragt Ja/Nein. Der Handler bekommt die Antwort als Zahl: 1 = ja, 0 = nein.
Unter Windows sind diese beiden Dialoge — wie die Datei-Dialoge weiter unten — echte Betriebssystem-Fenster. Das heißt: sie funktionieren auch, wenn dein Programm gerade im Vollbild läuft.
gui.dialog_datei(fenster, handler)öffnet einen Datei-Auswahl-Dialog. Der Handler bekommt das Fenster und liest den gewählten Pfad mitgui.gewählter_pfad(fenster)(""wenn abgebrochen):
funktion datei_öffnen(id) {
gui.dialog_datei(fenster, datei_gewählt)
}
funktion datei_gewählt(fenster) {
nimm pfad = gui.gewählter_pfad(fenster)
gui.setze_text(status, "Geöffnet: {pfad}")
}
gui.dialog_speichern(fenster, handler)öffnet einen Speichern-Dialog — genau wiedialog_datei, nur dass der Nutzer einen Ziel-Pfad wählt/eingibt. Den Pfad liest du wieder mitgui.gewählter_pfad(fenster).
Wirklich laden und speichern — kombiniere die Dialoge mit dem datei-Modul.
Datei-Operationen können fehlschlagen (nicht gefunden, gesperrt) — pack sie in
versuche/fange, dann landet ein Fehler in der Status-Leiste statt das
Programm abzubrechen:
nutze gui
nutze datei
funktion speichern(id) {
gui.dialog_speichern(fenster, speichern_gewählt)
}
funktion speichern_gewählt(fenster) {
nimm pfad = gui.gewählter_pfad(fenster)
wenn pfad != "" {
versuche {
datei.schreibe(pfad, gui.wert(feld))
gui.setze_text(status, "Gespeichert: {pfad}")
} fange e {
gui.setze_text(status, "Konnte nicht speichern: {e}")
}
}
}
Laden geht spiegelbildlich mit gui.dialog_datei + datei.inhalt(pfad) →
gui.setze_wert(feld, …). Vollständig in beispiele/gui_editor.ki und
beispiele/gui_notizen.ki.
Menü-Leiste — du baust sie Schritt für Schritt auf: Leiste → Menü → Einträge. Jeder Eintrag bekommt einen Handler (wie ein Knopf):
nimm leiste = gui.menü_leiste(fenster)
nimm m_datei = gui.menü_hinzufügen(leiste, "Datei")
gui.menü_eintrag(m_datei, "Öffnen …", datei_öffnen)
gui.menü_trenner(m_datei)
gui.menü_eintrag(m_datei, "Beenden", beenden)
Menü nachträglich umbeschriften — willst du einen Menütitel oder -Eintrag nach dem Start ändern (z. B. für eine Sprachumschaltung), merke dir sein Handle (
nimm m_datei = …/nimm eintrag = gui.menü_eintrag(…)), ändere den Text mitgui.setze_text(handle, neuer_text)und wende die Leiste dann mitgui.menü_anwenden(fenster)neu an — erst dann zeigt das Menü den neuen Text.
Werkzeug-Leiste — eine Reihe von Knöpfen für schnelle Aktionen:
nimm knopf = gui.knopf_neu("Neu")
gui.bei_klick(knopf, neu_dokument)
nimm werkzeuge = gui.werkzeug_leiste([knopf])
Status-Leiste — eine Textzeile unten, die du laufend aktualisierst. Du legst
sie selbst ans Ende deines Layouts und änderst sie mit gui.setze_text:
nimm status = gui.status_leiste("Bereit.")
// … gui.setze_text(status, "Gespeichert.")
nimm inhalt = gui.vertikal([werkzeuge, feld, status])
Ein vollständiger Mini-Editor, der alles zusammenbringt, steht in
beispiele/gui_editor.ki.
34.8b Ein zweites Fenster öffnen
Die eingebauten Dialoge (dialog_info, dialog_frage, dialog_datei) decken die
häufigsten Fälle ab. Manchmal brauchst du aber ein eigenes zweites Fenster mit
deinen eigenen Widgets — ein Werkzeug-, Einstellungs- oder Farbwähler-Fenster, das
neben dem Hauptfenster steht und das Hauptprogramm nicht blockiert.
Das geht mit gui.zeige — derselben Funktion, mit der du den Hauptfenster-Inhalt
setzt. Der Trick: rufst du gui.zeige(fenster, inhalt) nach gui.starte() auf
(also aus einem Handler, z. B. einem Knopf-bei_klick), dann öffnet Kiste dieses
Fenster live als eigenes, zweites Fenster:
nutze gui
nimm haupt = gui.fenster_öffne("Haupt", 480, 300)
nimm dlg = gui.fenster_öffne("Zweites Fenster", 320, 200) // zweites Fenster anlegen …
// Inhalt des zweiten Fensters vorbereiten (tut noch nichts sichtbar):
nimm regler = gui.schieberegler(0, 100)
nimm anzeige = gui.etikett_neu("Wert: 0")
nimm zu = gui.knopf_neu("Schließen")
gui.bei_schieben(regler, funktion(r) {
nimm w = gui.schieber_wert(r)
gui.setze_text(anzeige, "Wert: {w}")
})
gui.bei_klick(zu, funktion(k) {
gui.fenster_schließe(dlg) // nur dieses Fenster zu — App läuft weiter
})
nimm dlg_inhalt = gui.vertikal([anzeige, regler, zu])
// Ein Knopf im Hauptfenster öffnet das zweite Fenster LIVE:
nimm auf = gui.knopf_neu("Zweites Fenster öffnen")
gui.bei_klick(auf, funktion(k) {
gui.zeige(dlg, dlg_inhalt) // … erst hier geht es auf
})
gui.zeige(haupt, gui.vertikal([auf]))
gui.starte()
- Vor
gui.starte()setztgui.zeige(fenster, inhalt)wie gewohnt den Inhalt des Hauptfensters. Nachgui.starte()öffnet derselbe Aufruf das Fenster als eigenständiges zweites Fenster (nicht-blockierend — das Hauptfenster bleibt bedienbar). gui.fenster_schließe(fenster)schließt ein so geöffnetes Fenster wieder — ideal für „Übernehmen"/„Abbrechen"-Knöpfe. Danach kannst du es mitgui.zeigeerneut öffnen. Das Hauptfenster zu schließen (dessen X oderfenster_schließe) beendet weiterhin die ganze App.- Das zweite Fenster hat eine eigene Titelleiste und ein eigenes X; alle Widgets darin (Knöpfe, Regler, Leinwand mit Maus, …) funktionieren genauso wie im Hauptfenster.
Merke: Lege beide Fenster mit
gui.fenster_öffnean, bevor dugui.starte()aufrufst (das Hauptfenster ist einfach das zuerst angelegte). Das zweite Fenster bleibt unsichtbar, bis du es pergui.zeigeaus einem Handler öffnest.
34.9 Weitere Widgets: Kästchen, Radio, Schieberegler, Fortschritt, Reiter
Fünf weitere Standard-Bausteine — jeder folgt demselben Muster wie bisher: Handler bekommt sein Widget, den Wert holst du mit einem Getter.
Kontrollkästchen (an/aus). gui.angehakt(k) liefert 1 (an) oder 0 (aus):
nimm haken = gui.kontrollkästchen("Benachrichtigungen aktivieren")
gui.bei_umschaltung(haken, bei_haken)
// gui.setze_angehakt(haken, 1) // vorab anhaken
funktion bei_haken(k) {
wenn gui.angehakt(k) == 1 {
sag "an"
} sonst {
sag "aus"
}
}
Radio-Knöpfe (eine Option aus mehreren). Sie benutzen dieselben Funktionen
wie die Auswahl-Box: gui.gewählt und gui.bei_wahl:
nimm größe = gui.radio_knöpfe(["Klein", "Mittel", "Groß"])
gui.bei_wahl(größe, bei_größe)
funktion bei_größe(r) {
sag "Schriftgröße: {gui.gewählt(r)}"
}
Schieberegler (eine Zahl in einem Bereich). gui.schieber_wert liefert die
aktuelle Ganzzahl:
nimm laut = gui.schieberegler(0, 100) // min, max
gui.bei_schieben(laut, bei_lautstärke)
// gui.setze_schieber_wert(laut, 42) // vorab setzen
funktion bei_lautstärke(s) {
sag "Lautstärke: {gui.schieber_wert(s)} %"
}
Fortschrittsbalken (reine Anzeige, 0–100 %). Du füllst ihn mit
gui.setze_fortschritt — z. B. aus einem Schieberegler-Handler heraus:
nimm balken = gui.fortschritt_neu()
gui.setze_fortschritt(balken, 70) // 70 %
Reiter (Tabs) — mehrere Seiten in einem Fenster. Du baust den Reiter- Container und hängst Seiten an (jede Seite ist ein Layout):
nimm seite1 = gui.vertikal([haken, größe])
nimm seite2 = gui.vertikal([laut, balken])
nimm reiter = gui.reiter_neu()
gui.reiter_hinzufügen(reiter, "Allgemein", seite1)
gui.reiter_hinzufügen(reiter, "Audio", seite2)
Ein vollständiges „Einstellungen"-Fenster mit allen fünf Widgets steht in
beispiele/gui_widgets.ki.
34.10 Alles zusammen — ein kleines Formular
nutze gui
nimm info = gui.etikett_neu("Gib deinen Namen ein:")
nimm feld = gui.eingabefeld("Name …")
funktion bei_übernehmen(knopf) {
gui.setze_text(info, "Hallo, {gui.wert(feld)}!")
}
gui.setze_farbe("akzent", "#22c55e")
nimm knopf = gui.knopf_neu("Übernehmen")
gui.setze_wichtigkeit(knopf, "primär")
gui.bei_klick(knopf, bei_übernehmen)
nimm inhalt = gui.vertikal([info, feld, knopf])
nimm fenster = gui.fenster_öffne("Mein Formular", 360, 200)
gui.zeige(fenster, inhalt)
gui.starte()
34.10a Formulare & Eingabe-Profi
Für echte Eingabe-Masken (Anmeldung, Einstellungen, Datenerfassung) gibt es eigene Bausteine — ein Formular richtet Etiketten und Felder sauber aus und sperrt den Absenden-Knopf automatisch, solange ein Feld ungültig ist.
Passwortfeld, Combobox, Datumsfeld — Entry-Verwandte, sie nutzen dieselben
Getter wie das Eingabefeld (gui.wert, gui.setze_wert, gui.bei_änderung):
nimm pw = gui.passwortfeld("Passwort") // maskiert (•••)
nimm ort = gui.combofeld(["Bern", "Zürich"]) // frei tippbar + Vorschläge
nimm tag = gui.datumsfeld() // Kalender; Wert via gui.datum_wert(tag)
Validierung — prüfe eine Eingabe mit einem Muster (regulärer Ausdruck). Ist sie ungültig, erscheint dein Fehlertext am Feld und ein Formular sperrt sein Absenden:
nimm mail = gui.eingabefeld("E-Mail")
gui.setze_prüfung(mail, ".+@.+", "Bitte eine gültige E-Mail")
gui.setze_pflicht(pw, "Passwort darf nicht leer sein") // Kurzform für „nicht leer"
Hinweis: In Kiste-Texten startet
{ … }eine Einsetzung. Enthält dein Prüf-Muster geschweifte Klammern (z. B.\d{4}), umgehe das, indem du das Muster ohne{}schreibst (etwa\d\d\d\d).
Komma-Schieberegler — ein Schieberegler mit Nachkommastellen. Komma-Zahlen
schreibt man in Kiste mit dem Suffix f (z. B. 0.1f):
nimm regler = gui.schieberegler_komma(0.0f, 1.0f, 0.1f) // min, max, Schrittweite
gui.setze_schieber_komma_wert(regler, 0.7f)
nimm laut = gui.schieber_komma_wert(regler) // Komma-Wert lesen
Das Formular — Zeilen anhängen, Absenden/Abbrechen verdrahten:
nimm name = gui.eingabefeld("z. B. Anna")
gui.setze_pflicht(name, "Bitte Namen eingeben")
nimm f = gui.formular()
gui.formular_zeile(f, "Name", name)
gui.formular_zeile(f, "Passwort", pw)
gui.formular_zeile(f, "Ort", ort)
funktion abgesendet(formular) {
gui.dialog_info(fenster, "Gespeichert", "Willkommen, {gui.wert(name)}!")
}
gui.formular_absenden(f, "Speichern", abgesendet)
gui.formular_abbrechen(f, "Abbrechen", abgesendet)
gui.zeige(fenster, f)
Der Speichern-Knopf bleibt ausgegraut, bis alle geprüften Felder gültig sind — so kann kein unvollständiges Formular abgeschickt werden.
34.10a-2 Dialoge & Kontextmenüs
Neben den einfachen Dialogen (dialog_info, dialog_frage, dialog_datei) gibt es flexible
Dialoge für echte Programme. Ihr Ergebnis liest du wie beim Datei-Dialog über einen Getter am
Fenster — der Handler bekommt das Fenster, du liest den Wert darin aus:
funktion bei_name(fenster) {
nimm name = gui.eingabe_wert(fenster) // eingegebener Text ("" = abgebrochen)
gui.dialog_info(fenster, "Hallo", "Willkommen, {name}!")
}
gui.dialog_eingabe(fenster, "Name", "Wie heißt du?", bei_name) // Eingabe-Dialog
Farbwähler — gui.dialog_farbe(fenster, titel, handler), Ergebnis als Hex über
gui.farbe_wert(fenster):
funktion bei_farbe(fenster) {
gui.setze_farbe("akzent", gui.farbe_wert(fenster)) // z. B. "#3b82f6"
}
gui.dialog_farbe(fenster, "Akzentfarbe wählen", bei_farbe)
Weitere Dialoge:
gui.dialog_inhalt(fenster, "Über", mein_widget, "Schließen") // beliebiges Widget modal
gui.dialog_ordner(fenster, bei_ordner) // Ordner wählen (Pfad via gewählter_pfad)
gui.dialog_datei_gefiltert(fenster, [".png", ".jpg"], bei_bild) // nur Bilder zeigen
nimm warten = gui.dialog_warten(fenster, "Bitte warten", "lädt …") // Spinner
// … Arbeit …
gui.dialog_schließe(warten)
Kontextmenü — ein Rechtsklick-Menü auf einem beliebigen Widget. Du wickelst das Widget mit
gui.kontext_menü ein und hängst Einträge an; der Menü-Handle kommt statt des Widgets ins Layout:
nimm liste = gui.liste_anzeige(["Apfel", "Birne", "Kirsche"])
nimm menü = gui.kontext_menü(liste)
funktion bei_kopieren(m) { … }
funktion bei_löschen(m) { … }
gui.kontext_eintrag(menü, "Kopieren", bei_kopieren)
gui.kontext_trenner(menü)
gui.kontext_eintrag(menü, "Löschen", bei_löschen)
gui.zeige(fenster, menü) // der Wrapper (menü) kommt ins Layout, nicht die liste
Rechtsklickst du nun auf die Liste, erscheint das Menü an der Mausposition; der Handler eines Eintrags feuert bei dessen Auswahl.
34.10a-3 Inhalt & Branding: Markdown, Karten, Symbole, Accordion
Für formatierte Inhalte und ein eigenes Aussehen gibt es weitere Bausteine.
Markdown — gui.markdown(text) zeigt formatierten Text: Überschriften mit #, fett,
kursiv, Code, Aufzählungen und Links. Ideal für Hilfe- und Infotexte:
nimm hilfe = gui.markdown("# Hilfe\n\nDrücke **Speichern**, um zu sichern.\n\n- Punkt eins\n- Punkt zwei")
Hyperlink — gui.verweis(text, url) ist ein anklickbarer Link, der die Adresse im Browser öffnet:
nimm link = gui.verweis("Zur Webseite", "https://kiste-lang.org")
Titel-Karte — gui.titelkarte(titel, untertitel, inhalt) ist eine Karte mit Titelzeile,
Untertitel und einem Inhalts-Widget (die Fyne-„Card"). Anders als gui.karte (gemalter, farbiger
Hintergrund) hat sie einen Textkopf:
nimm karte = gui.titelkarte("Einstellungen", "Anzeige", gui.vertikal([regler1, regler2]))
Symbol — gui.symbol(alias) zeigt ein einzelnes Icon (dieselben Aliasse wie knopf_icon, z. B.
"info", "warnung"); gui.symbol_bild(pfad) nimmt eine eigene Bilddatei.
Trennlinie — gui.trennlinie() ist ein dünner Strich, um in einer vertikal/horizontal
Bereiche optisch zu trennen.
Accordion — gui.akkordeon() mit gui.akkordeon_abschnitt(akk, titel, inhalt) baut aufklappbare
Abschnitte (immer nur einer offen — spart Platz):
nimm akk = gui.akkordeon()
gui.akkordeon_abschnitt(akk, "Allgemein", allgemein_inhalt)
gui.akkordeon_abschnitt(akk, "Erweitert", erweitert_inhalt)
Eigenes Aussehen (Branding) — gui.knopf_bild(text, bildpfad) gibt einem Knopf ein Icon aus
einer eigenen Datei (statt der eingebauten Aliasse). In einer werkzeug_leiste gruppieren
gui.werkzeug_trenner() und gui.werkzeug_abstand() die Werkzeuge (Trennstrich bzw. dehnbarer
Abstand, der die folgenden Symbole nach rechts schiebt).
34.10a-4 Baum & sortierbare Tabellen
Ein Baum zeigt eine Hierarchie mit auf- und zuklappbaren Ästen — perfekt für einen
Datei-Browser oder eine Gliederung. Du baust ihn Knoten für Knoten: jeder Knoten hat eine
eindeutige ID, die ID seines Elternteils ("" = ganz oben) und eine Beschriftung. Ein
Knoten mit Kindern wird automatisch zum aufklappbaren Ast.
nimm baum = gui.baum()
gui.baum_knoten(baum, "projekt", "", "Mein Projekt")
gui.baum_knoten(baum, "src", "projekt", "src")
gui.baum_knoten(baum, "src/main.ki", "src", "main.ki")
gui.baum_knoten(baum, "docs", "projekt", "docs")
funktion bei_wahl(b) {
sag "Gewählt: {gui.baum_gewählt(b)}" // liefert die ID des Knotens
}
gui.bei_baum_auswahl(baum, bei_wahl)
gui.baum_gewählt(baum) gibt die ID des angeklickten Knotens zurück; gui.baum_leeren(baum) entfernt
alle Knoten (z. B. um den Baum neu aufzubauen).
Sortierbare Tabelle — gui.tabelle_sortierbar(tabelle) macht die Spaltenköpfe anklickbar: ein
Klick sortiert die Zeilen nach dieser Spalte, ein zweiter Klick kehrt die Reihenfolge um (ein Pfeil
▲/▼ zeigt die aktive Spalte). Die Spaltenbreiten kannst du ohnehin mit der Maus ziehen.
nimm tab = gui.tabelle_wählbar(["Datei", "Größe"])
gui.zeile_anhängen(tab, ["zebra.txt", "300"])
gui.zeile_anhängen(tab, ["apfel.txt", "100"])
gui.tabelle_sortierbar(tab) // Klick auf „Datei" sortiert alphabetisch
34.10a-5 App-Integration: Benachrichtigung, Zwischenablage, Schließen, Reiter
Damit sich ein Programm „fertig" anfühlt, gibt es ein paar Bausteine rund um das Betriebssystem und das Fenster.
Benachrichtigung — gui.benachrichtige(titel, text) zeigt eine Desktop-Benachrichtigung des
Betriebssystems (z. B. „Fertig gespeichert").
Zwischenablage — gui.kopiere_text(text) schreibt, gui.hole_text() liest die Zwischenablage:
gui.kopiere_text("Hallo")
nimm zurück = gui.hole_text() // "Hallo"
Schließen abfangen — mit gui.bei_fenster_schließen(fenster, handler) läuft dein Handler, wenn der
Nutzer auf ✕ klickt — statt das Fenster zu schließen. So kannst du „Ungespeicherte Änderungen?"
abfragen. Erst wenn du gui.fenster_schließe(fenster) aufrufst, schließt es wirklich:
funktion beim_schließen(fenster) {
gui.dialog_frage(fenster, "Beenden?", "Wirklich schließen?", funktion(ja) {
wenn ja == 1 { gui.fenster_schließe(fenster) }
})
}
gui.bei_fenster_schließen(fenster, beim_schließen)
(gui.bei_fenster_geschlossen(fenster, handler) läuft dagegen NACH dem Schließen — zum Aufräumen.)
Reiter steuern — bei einem reiter liest gui.gewählter_reiter(reiter) den aktiven Tab (0 =
erster), gui.setze_reiter(reiter, index) wählt einen, und gui.bei_reiter_wechsel(reiter, handler)
feuert bei jedem Wechsel. Mit gui.reiter_hinzufügen_icon(reiter, titel, icon, inhalt) bekommt eine
Lasche ein Icon:
nimm reiter = gui.reiter_neu()
gui.reiter_hinzufügen(reiter, "Allgemein", allgemein)
gui.reiter_hinzufügen_icon(reiter, "Info", "info", info_inhalt)
gui.bei_reiter_wechsel(reiter, funktion(r) { sag "Jetzt Tab {gui.gewählter_reiter(r)}" })
34.10b Politur: Icons, Teiler, Rand-Layout, Tastatur-Kürzel, Farb-Rahmen
Für „runde" Apps gibt es vier Bausteine:
Icons — Knöpfe und Werkzeug-Leisten mit Symbolen (deutsche Alias-Namen wie
"speichern", "öffnen", "neu", "suchen", "einstellungen"):
nimm k = gui.knopf_icon("Speichern", "speichern") // Icon + Text
// Echtes Icon-Toolbar:
nimm tb1 = gui.werkzeug_knopf("neu", funktion(i) { … })
nimm tb2 = gui.werkzeug_knopf("speichern", funktion(i) { … })
nimm leiste = gui.werkzeug_leiste([tb1, tb2])
Die verfügbaren Icon-Namen (deutsche Aliasse): "speichern", "öffnen",
"neu", "löschen", "hinzufügen", "entfernen", "kopieren", "einfügen",
"einstellungen", "suchen", "start", "stopp", "pause", "info",
"warnung", "fehler", "frage", "zurück", "vor", "rückgängig",
"wiederholen", "hoch", "runter", "home", "menü", "schließen",
"bestätigen", "aktualisieren". Unbekannte Namen ergeben einfach kein Icon
(kein Fehler). "rückgängig"/"wiederholen" sind die geschwungenen Undo/Redo-
Pfeile (↶ ↷) — ideal für Knöpfe gui.knopf_icon("", "rückgängig").
Tooltips — ein nacktes Symbol ist ein Rätsel. gui.setze_tooltip(widget, text)
hängt einem beliebigen Widget einen Hovertext an: bleibt die Maus kurz darüber,
erscheint der Text. Ideal für Icon-Knöpfe (vor gui.zeige setzen) — es geht an
jedem Widget, auch an einer gui.leinwand:
nimm k = gui.knopf_icon("", "speichern")
gui.setze_tooltip(k, "Speichern (Strg+S)")
Den Tooltip-Text darfst du jederzeit ändern — auch nach gui.zeige (z. B. wenn
deine App die Sprache umschaltet): ein erneutes gui.setze_tooltip(k, "Save …") wirkt
sofort beim nächsten Überfahren.
Teiler (ziehbar) und Rand-Layout:
nimm geteilt = gui.teiler_h(links, rechts, 30) // 30 % links; 0 = mittig
nimm fenster_inhalt = gui.rand_layout(mitte, [leiste, fuss, 0, 0])
// mitte, [oben, unten, links, rechts] (0 = leer)
Zentrieren und Verteilen — gui.zentriert und gui.dehnbar: In einer
vertikal/horizontal-Reihe sitzen die Bausteine sonst links bzw. oben
gepackt, der Rest bleibt als Leerraum. Zwei Helfer rücken das gerade:
// Ein Widget mittig im verfügbaren Platz:
gui.zeige(fenster, gui.zentriert(gui.etikett_neu("Titel")))
// Eine Gruppe zentrieren — dehnbare Platzhalter LINKS und RECHTS:
nimm leiste = gui.horizontal([gui.dehnbar(), knopf_a, knopf_b, gui.dehnbar()])
// Elemente an die beiden Enden schieben — ein Platzhalter in der Mitte:
nimm zeile = gui.horizontal([links, gui.dehnbar(), rechts])
gui.dehnbar() ist ein dehnbarer Platzhalter: er frisst den übrigen Platz.
Ein Platzhalter zwischen zwei Widgets drückt sie auseinander; je einer außen
zentriert die Gruppe; mehrere teilen sich den Rest gleichmäßig. So baust du
zentrierte oder über die Breite verteilte Leisten ohne feste Pixelmaße.
Tastatur-Kürzel — Strg/Umschalt/Alt:
gui.taste(fenster, "Strg+S", funktion(f) { … })
Auch die „Standard"-Kürzel
Strg+Z,Strg+Y,Strg+C,Strg+V,Strg+XundStrg+Alassen sich so belegen und lösen zuverlässig aus (z. B. Rückgängig/Wiederholen oder Kopieren/Einfügen im eigenen Programm). Ein gerade fokussiertes Eingabefeld bekommt sein eigenes Kopieren/Einfügen — dein Kürzel greift, wenn kein Eingabefeld den Fokus hat.
Farb-Rahmen — ein Widget mit farbigem Hintergrund (Karte). Die Farbe lässt sich auch zur Laufzeit ändern (z. B. ein Saldo grün/rot je nach Vorzeichen):
nimm karte = gui.farb_rahmen(status, "#1e3a2f")
gui.setze_rahmen_farbe(karte, "#3a1e1e") // live umfärben (rot)
Für einen weicheren Look gibt es gui.karte — wie farb_rahmen, aber die Hintergrund-Karte ist
abgerundet und sanft berandet (schön zum Gruppieren, z. B. Panels/Werkzeug-Bereiche):
nimm gruppe = gui.karte(inhalt, "#141f33", "#334155", 12, 1) // füllfarbe, randfarbe, radius, randdicke
Auch eine gui.karte färbst du live mit gui.setze_rahmen_farbe um.
Bildlauf (Scrollen) — wird ein Inhalt größer als der Platz im Fenster, wickelst du ihn in einen Scroll-Bereich. Dann erscheint eine Bildlaufleiste, statt dass das Fenster überläuft:
// Eine lange Spalte in einem begrenzten, scrollbaren Bereich:
nimm lang = gui.vertikal([ /* … viele Zeilen … */ ])
nimm bereich = gui.scroll_vertikal(lang) // nur vertikal
gui.zeige(fenster, gui.feste_größe(bereich, 240, 300))
Drei Varianten: gui.scroll(widget) (beide Richtungen), gui.scroll_vertikal(widget) (nur hoch/
runter) und gui.scroll_horizontal(widget) (nur links/rechts). Wichtig: Ein Scroll-Bereich
braucht einen begrenzenden Rahmen — meist gui.feste_größe(...) oder einen Teiler/rand_layout.
Ohne Begrenzung wächst er einfach mit dem Inhalt und scrollt nie.
Kachel-Raster — gui.kachel_raster(zellbreite, zellhöhe, [kinder]) ordnet gleich große Zellen an
und bricht automatisch um, wenn die Reihe voll ist. Anders als raster (feste Spaltenzahl) passt
sich die Spaltenzahl der Fensterbreite an — ideal für Galerien, Dashboards oder Werkzeug-Paletten:
nimm galerie = gui.kachel_raster(80, 44, [knopf_a, knopf_b, knopf_c, knopf_d, knopf_e])
gui.zeige(fenster, gui.scroll_vertikal(galerie)) // wird's zu viel, scrollt es
Teiler-Position merken — die Position eines teiler_h/teiler_v liest du mit
gui.teiler_wert(teiler) (Prozent 1–99) und setzt sie mit gui.setze_teiler_wert(teiler, prozent).
So kannst du z. B. die Breite einer Seitenleiste speichern und beim nächsten Start wiederherstellen:
nimm t = gui.teiler_h(seitenleiste, inhalt, 30)
gui.setze_teiler_wert(t, 25) // Seitenleiste schmaler
nimm jetzt = gui.teiler_wert(t) // aktuelle Position ablesen (z. B. nach dem Ziehen)
Tipp: Die Handler dürfen Closures sein, die lokale Variablen fangen (siehe Kap. 8.7) — kein Umweg über globale Variablen nötig. Beispiel:
beispiele/gui_politur.ki.Farben — was heute geht: die 5 Knopf-Rollen (
gui.setze_wichtigkeit(knopf, "primär"/ "normal"/"gefahr"/"erfolg"/"warnung")), globale Farben (gui.setze_farbe(rolle, hex)+gui.thema(...)) und der Farb-Rahmen (farb_rahmen+setze_rahmen_farbe). Beliebige freie Hintergrund-/Textfarbe pro Standard-Widget ist eine Fyne-Grenze (kommt als eigene Phase).
34.10c Leinwand: frei zeichnen mit eigenen Farben
Bisher hast du fertige Widgets benutzt (Knöpfe, Listen, Tabellen). Manchmal willst du aber selbst zeichnen — ein Diagramm, ein Spielfeld, ein Muster — und dabei jede beliebige Farbe wählen, nicht nur die Theme-Rollen. Dafür gibt es die Leinwand:
nutze gui
nimm bild = gui.leinwand(400, 300) // Zeichenfläche (Breite, Höhe in Pixel)
gui.rechteck(bild, 10, 10, 100, 50, "#3b82f6") // x, y (Ecke), breite, höhe, farbe
gui.kreis(bild, 200, 150, 30, "#dc2626") // x, y (Mittelpunkt), radius, farbe
gui.linie(bild, 50, 50, 300, 250, "#16a34a", 2) // x1, y1, x2, y2, farbe, dicke
gui.text_an(bild, 20, 270, "Hallo", "#e4e4e7", 14) // x, y, text, farbe, größe
nimm f = gui.fenster_öffne("Leinwand", 420, 320)
gui.zeige(f, bild) // Leinwand wie jedes Widget einhängen
gui.starte()
Farben gibst du als Hex-Text an ("#rrggbb", z. B. "#3b82f6" = Blau). Das sind
beliebige Farben — du bist nicht auf die fünf Theme-Rollen beschränkt.
Text zentrieren: gui.text_breite(text, größe) gibt die Pixelbreite eines Textes in
der jeweiligen Schriftgröße zurück (dieselbe Schrift wie gui.text_an). Damit platzierst du
Text exakt mittig statt an einer geschätzten Stelle — die halbe Breite links vom Mittelpunkt:
nimm txt = "Spiel vorbei"
gui.text_an(bild, 200 - gui.text_breite(txt, 24) div 2, 100, txt, "#e4e4e7", 24) // mittig um x=200
Abgerundete Rechtecke für einen weicheren Look zeichnest du mit gui.rechteck_rund:
gui.rechteck_rund(bild, 10, 10, 100, 60, 12, "#3b82f6", "", 0) // rundes Feld, Radius 12
gui.rechteck_rund(bild, 130, 10, 100, 60, 12, "#3b82f6", "#0f172a", 2) // + dunkler Rand (2 px)
gui.rechteck_rund(bild, 250, 10, 100, 60, 12, "", "#0f172a", 2) // NUR Rand (leere Füllung "")
Die Reihenfolge ist (leinwand, x, y, breite, höhe, radius, füllfarbe, randfarbe, randdicke). Eine
leere Farbe "" (oder randdicke 0) lässt Füllung bzw. Rand weg — so malst du runde Farbflächen
ebenso wie reine Umrisse.
Koordinaten zählen ab oben-links: x nach rechts, y nach unten, in Pixeln.
Neu zeichnen: Mit gui.leeren(bild) wischst du alles weg und zeichnest neu — auch
aus einem Handler heraus, z. B. auf Knopfdruck:
nimm bild = gui.leinwand(300, 200)
gui.rechteck(bild, 20, 20, 120, 80, "#3b82f6")
nimm knopf = gui.knopf_neu("Umfärben")
gui.bei_klick(knopf, funktion(k) {
gui.leeren(bild) // altes Bild weg
gui.rechteck(bild, 20, 20, 120, 80, "#f59e0b") // neu in Bernstein
})
So baust du heute schon rundenbasierte Spiele (Brett neu zeichnen pro Zug) und freie Mal-Programme. (Animation pro Zeit-Takt kommt mit einem späteren Timer-Baustein.)
Vollständiges Beispiel:
beispiele/gui_leinwand.ki.
34.10d Animation: die Leinwand bewegen mit bei_zeit
Mit der Leinwand kannst du zeichnen — mit einem Timer kannst du das Bild
immer wieder neu zeichnen und so bewegte Grafik machen. gui.bei_zeit(ms, handler) ruft deinen Handler alle ms Millisekunden auf:
nutze gui
nimm bild = gui.leinwand(360, 200)
nimm x = 20
nimm dx = 5
funktion takt() {
x = x + dx
wenn x > 340 { dx = 0 - 5 } // am rechten Rand umkehren
wenn x < 20 { dx = 5 } // am linken Rand umkehren
gui.leeren(bild)
gui.rechteck(bild, 0, 0, 360, 200, "#0f172a") // Hintergrund
gui.kreis(bild, x, 100, 16, "#38bdf8") // wandernde Kugel
}
nimm uhr = gui.bei_zeit(16, funktion(t) { // ~60-mal pro Sekunde
takt()
})
nimm stop = gui.knopf_neu("Stop")
gui.bei_klick(stop, funktion(k) {
gui.stoppe_zeit(uhr) // Animation anhalten
})
nimm f = gui.fenster_öffne("Animation", 380, 270)
gui.zeige(f, gui.vertikal([bild, stop]))
gui.starte()
gui.bei_zeit(ms, handler)gibt ein Handle zurück — damit kannst du den Timer später mitgui.stoppe_zeit(handle)anhalten.16Millisekunden ≈ 60 Bilder pro Sekunde (flüssig). Größere Zahl = langsamer.gui.nach_zeit(ms, handler)ruft den Handler nur einmal nachmsauf — praktisch für „nach 3 Sekunden einblenden" o. Ä.
So entstehen kleine Spiele und Animationen: jeder Takt bewegt etwas und zeichnet die Leinwand neu.
Vollständiges Beispiel:
beispiele/gui_animation.ki.
34.10e Eingabe: Tastatur und Maus
Für Spiele willst du steuern — mit der Tastatur und der Maus. Beide folgen dem gleichen Muster wie die anderen Handler: dein Handler bekommt das Fenster bzw. die Leinwand, und du liest den Wert mit einem Getter.
Tastatur (auf dem Fenster):
gui.bei_taste(fenster, funktion(f) {
nimm taste = gui.letzte_taste(f)
wenn taste == "links" { x = x - 10 }
wenn taste == "rechts" { x = x + 10 }
wenn taste == "hoch" { y = y - 10 }
wenn taste == "runter" { y = y + 10 }
zeichne()
})
gui.letzte_taste(fenster) gibt einen deutschen Namen für die Spiel-Tasten
zurück: "links", "rechts", "hoch", "runter", "leer" (Leertaste),
"escape", "eingabe". Buchstaben kommen als Großbuchstabe ("W", "A", "S",
"D", …).
Maus (auf der Leinwand):
gui.bei_maus_klick(bild, funktion(b) {
nimm mx = gui.maus_x(b)
nimm my = gui.maus_y(b)
gui.kreis(bild, mx, my, 6, "#f59e0b") // Punkt an die Klickstelle
})
gui.maus_x(leinwand) / gui.maus_y(leinwand) geben die Klickposition in
Pixeln (Leinwand-lokal, oben-links = 0,0) — dieselbe Zählweise wie beim Zeichnen.
Neben dem Klick gibt es drei weitere Maus-Ereignisse auf der Leinwand:
-
gui.bei_maus_bewegung(leinwand, handler)— feuert, wenn die Maus sich über der Leinwand bewegt (ohne gedrückte Taste). Position wieder übergui.maus_x/gui.maus_y. -
gui.bei_maus_ziehen(leinwand, handler)— feuert beim Ziehen (Bewegung mit gedrückter Taste). Zusätzlich lieferngui.zieh_dx(leinwand)/gui.zieh_dy(leinwand)die Verschiebung seit dem letzten Schritt (praktisch fürs Schieben). -
gui.bei_maus_los(leinwand, handler)— feuert einmal beim Loslassen nach einem Ziehen (Drag-Ende); die Endposition steht ingui.maus_x/gui.maus_y. Damit baust du Werkzeuge, die man aufzieht: drücken (bei_maus_klick), ziehen mit Live-Vorschau (bei_maus_ziehen), loslassen und festschreiben (bei_maus_los) — genau das Muster hinter Linie und Rechteck in Mal-Programmen. -
gui.bei_maus_rechts(leinwand, handler)(seit 0.9.24) — feuert beim Rechtsklick auf die Leinwand; die Position steht wie immer ingui.maus_x/gui.maus_y. -
gui.maus_taste(leinwand)(seit 0.9.24) — welche Taste die laufende Aktion ausgelöst hat: 1 = links, 2 = rechts, 0 = noch keine. Lesbar in allen Maus-Handlern. Auch Ziehen mit der rechten Taste funktioniert: es feuertbei_maus_ziehen(mitmaus_taste= 2) und am Endebei_maus_los. Nach so einem Rechts-Zug bleibtbei_maus_rechtsbewusst aus — sonst setzte jeder Strich am Endpunkt noch einen Punkt.gui.bei_maus_ziehen(leinwand, funktion(l) { wenn gui.maus_taste(l) == 2 { radiere(l) } sonst { male(l) } })So bekommt ein Mal-Programm die vertraute Belegung: links malen, rechts radieren.
-
gui.bei_maus_rad(leinwand, handler)— feuert beim Drehen des Mausrads über der Leinwand. Wie weit gedreht wurde, lieferngui.rad_dy(leinwand)(senkrecht, grösser 0 = nach oben/vorwärts) undgui.rad_dx(leinwand)(waagerecht, meist 0). Praktisch zum Zoomen oder Scrollen:wenn gui.rad_dy(l) > 0 { hineinzoomen } sonst { herauszoomen }. -
gui.leinwand_elastisch()— eine Leinwand ohne feste Größe: stattgui.leinwand(b, h)füllt sie ihren Platz und wächst mit dem Fenster/Vollbild mit. Platziere sie in einen füllenden Slot (z. B. die Mitte vongui.rand_layout). Ihre aktuelle Größe holst du mitgui.leinwand_breite(l)/gui.leinwand_höhe(l), undgui.bei_größe(l, handler)feuert bei jeder Größenänderung — dort rechnest du Zoom/Zentrierung neu und zeichnest passend. Mitgui.vollbild(fenster, an)(an= 1/0) schaltest du Vollbild ein/aus. -
gui.bildschirm_breite()/gui.bildschirm_höhe()— die Größe des Bildschirms (genauer: die Arbeitsfläche des primären Monitors, also ohne Taskleiste), in denselben logischen Pixeln, die auchgui.fenster_öffneerwartet. Damit passt du die Fenstergröße an jeden Monitor an: auf kleinen Bildschirmen deckelst du das Fenster (es soll nicht über den Rand ragen), auf großen nutzt du den Platz. Beide sind schon vorgui.starte()aufrufbar — genau dann, wenn du die Startgröße festlegst. Ist keine Bildschirminfo verfügbar, liefern sie0(dann nimmst du eine feste Größe):nimm h = gui.bildschirm_höhe() nimm fensterhöhe = 1000 // Wunschgröße wenn h > 0 und fensterhöhe > h - 60 { fensterhöhe = h - 60 } // auf kleinen Schirmen deckeln nimm fenster = gui.fenster_öffne("Meine App", gui.bildschirm_breite(), fensterhöhe)Beide liefern den primären Monitor. Bei mehreren, unterschiedlich skalierten Monitoren kann das Fenster auf einem anderen landen — willst du sicher den aktuellen Monitor füllen (egal welcher), nutze
gui.maximiereodergui.vollbild(siehe nächster Punkt). -
gui.maximiere(fenster)— maximiert das Fenster wie der Maximieren-Knopf: es füllt die Arbeitsfläche des aktuellen Monitors (mit richtiger Skala), behält aber die Titelleiste mit Minimieren/Maximieren/Schließen und bleibt verschieb-, verkleiner- und wiederherstellbar. Das ist der Unterschied zugui.vollbild(fenster, 1)(randlos, ohne Titelleiste/X — gut für ein spielfüllendes Fenster, aber der Nutzer kommt nur über einen Umschalter wieder heraus). Für „groß, aber ein normales Fenster" istgui.maximieredie richtige Wahl. Rufe es auf, wenn das Fenster existiert (nachgui.starte()), am besten als Reaktion auf eine Nutzeraktion (Knopf/ Menü):nimm knopf = gui.knopf_neu("Fenster füllen") gui.bei_klick(knopf, funktion(k) { gui.maximiere(fenster) })Hinweis: Ruf
gui.maximierenicht im Wettlauf mit dem allerersten Fenster-Aufbau (unmittelbar beim Start) auf — auf Systemen mit hoher DPI-Skalierung kann das GUI-Backend die Größe während seines Init-Layouts sonst wieder zurücksetzen. Für ein großes Fenster gleich beim Start ist der Maximieren-Knopf der Titelleiste (den jedes normale Fenster hat) am zuverlässigsten. -
gui.setze_cursor(leinwand, name)— welchen Mauszeiger die Leinwand zeigt. Praktisch für Mal-Programme, wo jedes Werkzeug einen eigenen Zeiger hat:"kreuz"(Fadenkreuz zum Zeichnen/ Auswählen),"hand"(Hand zum Schwenken),"pipette"(ein Pipetten-Symbol zum Farbe-Aufnehmen),"text"(Text-Cursor) und"standard"(der normale Pfeil). Der Zeiger wechselt, sobald die Maus die Leinwand berührt. Nur Leinwände mit einem Maus-Handler (bei_maus_klick& Co.) tragen einen eigenen Cursor. Ruf es beim Werkzeugwechsel auf:gui.bei_maus_klick(bild, funktion(b) { … }) // macht die Leinwand cursor-fähig gui.setze_cursor(bild, "pipette") // zeigt jetzt das Pipetten-Symbol -
gui.setze_cursor_bild(leinwand, pfad, hotx, hoty)— der Cursor wird ein eigenes Bild: eine SVG- oder PNG-Datei (relativ neben dem Programm gesucht, genau wie beigui.bild_an). So kann jedes Werkzeug sein eigenes Icon als Cursor zeigen — z. B. denselben Icon-Satz wie in der Werkzeug- leiste.hotx/hotysagen, welcher Punkt im Bild genau unter der Mausspitze liegt (bei einem Stift z. B. die Spitze). SVGs werden scharf gerastert und bekommen einen feinen weißen Saum, damit sie auf hellem und dunklem Grund sichtbar sind:gui.setze_cursor_bild(bild, "icons/stift.svg", 3, 21) // das Stift-Icon als Cursor, Spitze unten linksBeide wirken sofort — auch wenn du den Cursor per Tastenkürzel wechselst, ohne die Maus zu bewegen.
Damit baust du echte steuerbare Spiele: die Tastatur bewegt die Spielfigur, die
Maus klickt aufs Spielfeld. (Tipp: in zeichne() immer erst gui.leeren(bild),
dann alles neu malen.)
Wichtig für flüssige Bewegung: Bewege die Figur nicht direkt im bei_taste-Handler
(x = x - 10). Eine gehaltene Taste löst beim Betriebssystem erst einen Druck aus,
dann eine Pause, dann die Tastenwiederholung — das ruckelt. Der Trick aus echten
Spielen: der Timer (gui.bei_zeit, Abschnitt 34.10d) bewegt die Figur jeden Takt,
und mit gui.taste_gedrückt(fenster, taste) (gibt 1 wenn die Taste gerade
gehalten wird, sonst 0) bewegst du sie nur solange gedrückt — sie stoppt beim
Loslassen:
gui.bei_zeit(16, funktion(t) { // ~60×/Sekunde
wenn gui.taste_gedrückt(fenster, "links") == 1 { x = x - 4 }
wenn gui.taste_gedrückt(fenster, "rechts") == 1 { x = x + 4 }
wenn gui.taste_gedrückt(fenster, "hoch") == 1 { y = y - 4 }
wenn gui.taste_gedrückt(fenster, "runter") == 1 { y = y + 4 }
zeichne()
})
So fühlt sich die Steuerung flüssig und natürlich an (bewegen beim Halten, stoppen beim
Loslassen). Für Snake-artige Spiele (drücken → Richtung wechseln, Figur läuft selbst weiter)
setzt du stattdessen im bei_taste-Handler nur eine Richtungs-Variable, die der Timer nutzt.
Vollständiges Beispiel:
beispiele/gui_eingabe.ki.
34.10f Bilder und Sprites auf der Leinwand
Bis jetzt hast du mit Rechtecken, Kreisen, Linien und Text gemalt. Für echte Spiele
willst du Bilder — eine Spielfigur, eine Münze, ein Hintergrund. Mit
gui.bild_an lädst du eine Bilddatei (PNG, JPG oder GIF) und zeichnest sie wie jedes
andere Primitiv auf die Leinwand:
gui.bild_an(bild, "bilder/münze.png", 100, 80, 40, 40)
// Leinwand Pfad x y breite höhe
- Pfad ist der Dateiname, relativ zu deiner
.ki-Datei — leg die Bilder einfach neben (oder in einen Unterordner zu) dein Programm, z.B.bilder/münze.png. (Ein absoluter Pfad geht auch.) Das funktioniert egal, von wo aus du startest. - x, y ist die obere-linke Ecke (gleiche Zählweise wie beim Rechteck), in Pixeln.
- breite, höhe ist die Zielgröße: das Bild wird in diese Box gezeichnet. So kannst du dasselbe kleine Sprite groß oder klein zeichnen. Beim Vergrößern bleibt Pixel-Grafik scharf (Nearest-Neighbor) — kleine Pixel werden zu klaren Kästchen statt verwaschen, genau richtig für Pixel-Kunst.
Ein bewegtes Sprite baust du genau wie eine bewegte Form — pro Timer-Takt
leeren und an der neuen Position neu zeichnen:
nimm bild = gui.leinwand(400, 300)
nimm x = 40
nimm y = 40
funktion zeichne() {
gui.leeren(bild)
gui.rechteck(bild, 0, 0, 400, 300, "#0f172a") // Hintergrund
gui.bild_an(bild, "bilder/münze.png", x, y, 40, 40) // Sprite (Pfad relativ zur .ki)
}
zeichne()
nimm f = gui.fenster_öffne("Sprites", 401, 301)
gui.bei_zeit(16, funktion(t) {
x = x + 2
wenn x > 360 { x = 0 }
zeichne()
})
gui.zeige(f, bild)
gui.starte()
Schnell von Haus aus: Kiste lädt jede Bilddatei nur einmal und merkt sie sich. Auch wenn du dasselbe Bild 60-mal pro Sekunde neu zeichnest (wie oben im Timer), wird die Datei nur beim ersten Mal von der Festplatte gelesen — danach ist sie schnell im Speicher. Du musst dich um nichts kümmern.
Findet Kiste die Datei nicht (Tippfehler im Pfad, Datei fehlt), wird einfach nichts gezeichnet und eine kurze Warnung erscheint — dein Spiel stürzt nicht ab.
Vollständiges Beispiel:
beispiele/gui_sprites.ki(hüpfende Münze + festes Sprite).
Ein selbst gebautes Bild zeichnen — gui.leinwand_bild. gui.bild_an lädt eine
Datei. Manchmal hast du das Bild aber schon im Speicher — z.B. eines, das du
mit dem bild-Modul (nutze bild) selbst zusammengesetzt hast. Dann zeichnest du es
direkt aus seinem Handle, ohne den Umweg über die Festplatte:
nutze gui
nutze bild
nimm b = bild.neu(4, 4) // ein winziges 4×4-Bild im Speicher …
bild.setze_block(b, 1, 1, 2, 2, 40, 90, 200) // … einen blauen 2×2-Block hinein
nimm lein = gui.leinwand(200, 200)
gui.leinwand_bild(lein, b, 0, 0, 200, 200) // 50× vergrößert, scharf, EIN Objekt
// Leinwand Bild x y breite höhe
- Der zweite Wert ist ein
bild-Handle (aus dembild-Modul), kein Dateipfad. - x, y, breite, höhe sind wie bei
gui.bild_an: obere-linke Ecke und Zielgröße, in die das Bild gestreckt wird. - Das Bild wird pixelgenau vergrößert (Nearest-Neighbor): kleine Pixel bleiben scharfe Kästchen statt verwaschen zu werden — genau richtig für Pixel-Grafik.
Warum das schnell ist. Eine ganze Pixel-Fläche (etwa ein Spielfeld oder eine
Mal-Leinwand) könntest du auch Zelle für Zelle mit vielen gui.rechteck malen — aber
das sind schnell Tausende Zeichenbefehle pro Bild, und ab ~64×64 Zellen ruckelt es
bei jeder Mausbewegung. gui.leinwand_bild zeichnet den ganzen Inhalt als ein
einziges Objekt: du baust dein bild einmal auf und zeigst es mit einem Aufruf —
gleich schnell, egal ob 16×16 oder 256×256. (Genau so rendert der Pixel-Editor KistePix
seine Leinwand.)
Ein stehendes Bild für sehr große Leinwände — gui.leinwand_bild_fest. Bei jedem
Neuzeichnen einer Leinwand baut das Programm normalerweise alle Objekte neu auf. Für
ein großes Leinwandbild (z. B. 1500×1000 Pixel) heißt das: das ganze Bild wird jedes Mal
neu übernommen — auch wenn du nur einen Auswahlrahmen ziehst und der Bildinhalt sich gar
nicht ändert. gui.leinwand_bild_fest macht das Bild zu einer stehenden Ebene: dasselbe
Objekt bleibt erhalten, und neu übernommen wird nur, wenn du es sagst.
// neu = 1 -> Bild wurde geändert, neu übernehmen; neu = 0 -> unverändert lassen
gui.leinwand_bild_fest(lein, b, 0, 0, 200, 200, 1)
// Leinwand Bild x y breite höhe neu
Der letzte Wert neu steuert alles: setze ihn auf 1, wenn sich der Bildinhalt oder
die Größe geändert hat (dann wird das Bild frisch übernommen), sonst auf 0 (dann
bleibt das schon gezeigte Bild stehen). Vorschau, Gitter und Ähnliches malst du wie gewohnt
darüber — die sind leicht und dürfen jedes Mal neu entstehen; nur das teure Leinwandbild
bleibt liegen. So bleibt das Ziehen von Formen oder das Schwenken auch bei riesigen Leinwänden
flüssig. (KistePix nutzt das für Aseprite-große Bilder.)
34.10g Klang und Musik — nutze klang
Ein Spiel ohne Ton ist halb so schön. Das klang-Modul spielt Klänge ab —
kurze Effekte (Münze gefangen, Sprung) und Hintergrundmusik. Es ist ein eigenes
Modul neben gui:
nutze gui
nutze klang
nimm ding = klang.lade("klaenge/muenze.wav") // einmal laden → Handle
klang.spiele(ding) // Effekt abspielen
klang.lade(pfad)lädt eine Audiodatei (WAV, MP3 oder OGG) und gibt ein Handle zurück. Lade einen Klang einmal (z.B. am Programmstart) und spiele ihn dann beliebig oft — das ist schnell, weil die Datei nur einmal von der Festplatte gelesen wird. Der Pfad ist relativ zu deiner.ki-Datei (wie bei Bildern).klang.spiele(handle)spielt den Klang ab. Es wartet nicht — dein Programm läuft sofort weiter, und mehrere Klänge dürfen sich überlagern (Effekt über Musik).klang.spiele_schleife(handle)spielt in Endlosschleife — ideal für Hintergrundmusik.klang.stoppe(handle)hält die Schleife wieder an.- Kurzform
klang.spiele_datei(pfad)lädt + spielt in einem Rutsch (ohne Handle) — praktisch für einen einmaligen Klang.
Im Spiel rufst du den Effekt einfach an der passenden Stelle auf, z.B. wenn die Münze gefangen wird:
wenn gefangen {
punkte = punkte + 1
klang.spiele(ding)
naechste_muenze()
}
Klang braucht eine laufende App. Spiele Klänge in einem GUI-Programm (mit
gui.starte()) — in einem Programm, das sofort endet, wird der Ton abgeschnitten.
Findet Kiste die Datei nicht, bleibt es einfach still (kurze Warnung) — kein Absturz.
Audio ANALYSIEREN statt abspielen (seit 2026-06-12): klang.lese_samples(pfad)
gibt die dekodierten Samples als liste<komma> heraus (Mono, Werte −1 bis +1) und
klang.lese_abtastrate(pfad) die Abtastrate der Datei — damit kannst du z.B.
Schläge in einem Lied erkennen (siehe beispiele/taktgeber/). Beide werfen einen
fangbaren Fehler bei kaputten/fehlenden Dateien (anders als lade: ein Analyse-Werkzeug
muss den Fehler zeigen können). Achtung Speicher: ein 3-Minuten-Song sind ~63 MB.
Vollständige Beispiele:
beispiele/gui_klang.ki(Effekt + Musik-Schleife) undbeispiele/gui_spiel.ki(„Fang die Münze" mit Ding beim Fangen).
34.11 Spickzettel gui
| Aufgabe | Funktion |
|---|---|
| Fenster | gui.fenster_öffne(titel, breite, höhe) · gui.zeige(fenster, inhalt) (vor starte() = Hauptfenster-Inhalt; nach starte() = öffnet ein eigenes zweites Fenster) · gui.fenster_schließe(fenster) (zweites Fenster wieder zu; erneut per zeige öffenbar) · gui.starte() |
| Knopf | gui.knopf_neu(text) · gui.knopf_icon(text, icon) · gui.bei_klick(knopf, handler) |
| Etikett | gui.etikett_neu(text) · gui.setze_text(widget, text) |
| Icons/Leiste | gui.werkzeug_knopf(icon, handler) · gui.werkzeug_leiste([…]) · gui.setze_tooltip(widget, text) (Hovertext beim längeren Darüberfahren; vor gui.zeige setzen) |
| Layout-Politur | gui.teiler_h/teiler_v(a, b, pos) · gui.rand_layout(mitte, [o,u,l,r]) · gui.farb_rahmen(widget, hex) · gui.karte(widget, füllfarbe, randfarbe, radius, randdicke) (abgerundete, sanft berandete Karte) · gui.setze_rahmen_farbe(rahmen, hex) (live) |
| Inhalt & Branding | gui.markdown(text) (formatierter Text) · gui.verweis(text, url) (Link-Widget) · gui.titelkarte(titel, untertitel, inhalt) (Card mit Textkopf) · gui.symbol(alias) / gui.symbol_bild(pfad) · gui.trennlinie() · gui.akkordeon() + gui.akkordeon_abschnitt(akk, titel, inhalt) · gui.knopf_bild(text, bildpfad) · gui.werkzeug_trenner() / gui.werkzeug_abstand() (in werkzeug_leiste) |
| Tastatur | gui.taste(fenster, "Strg+S", handler) |
| Layout | gui.vertikal([…]) · gui.horizontal([…]) · gui.raster(spalten, […]) · gui.kachel_raster(zellbreite, zellhöhe, […]) (umbrechendes Gitter, Spaltenzahl passt sich der Breite an) · gui.zentriert(widget) (mittig im verfügbaren Platz) · gui.dehnbar() (dehnbarer Platzhalter: [dehnbar(), …, dehnbar()] zentriert eine Gruppe, [a, dehnbar(), b] schiebt auseinander) · gui.feste_größe(widget, breite, höhe) → Wrapper-Handle: das Widget bekommt EXAKT diese Größe (zentriert); der Wrapper kommt ins Layout, Events/Setter bleiben am Original |
| Bildlauf & Teiler | gui.scroll(widget) (beide Richtungen) · gui.scroll_vertikal(widget) · gui.scroll_horizontal(widget) — bildlaufbarer Bereich; braucht einen begrenzenden Rahmen (feste_größe/Teiler/rand_layout), sonst wächst er mit dem Inhalt · gui.teiler_h/teiler_v(a, b, pos) · gui.teiler_wert(teiler) (Position 1–99 % lesen) · gui.setze_teiler_wert(teiler, prozent) (live setzen) |
| Farben | gui.thema("dunkel"/"hell") · gui.setze_farbe(rolle, hex) (live aus Handlern) · gui.setze_wichtigkeit(knopf, "primär"/"gefahr"/"erfolg"/"warnung"/"normal") · gui.setze_knopf_farbe(knopf, hex) (eigene Hintergrundfarbe für EINEN Knopf, live umfärbbar; ersetzt optisch die Wichtigkeit) |
| Eingabe | gui.eingabefeld(platzhalter) · gui.textbereich(platzhalter[, zeilen]) (mehrzeilig) · gui.passwortfeld(platzhalter) (maskiert) · gui.wert(feld) · gui.setze_wert(feld, text) · gui.bei_änderung(feld, handler) |
| Validierung | gui.setze_prüfung(feld, muster, fehlertext) (Regexp; ungültig → Fehler am Feld + Formular-Absenden gesperrt) · gui.setze_pflicht(feld, fehlertext) (Feld darf nicht leer sein). Muster mit {} meiden (Interpolation) — z. B. \d\d\d\d statt \d{4} |
| Combobox & Datum | gui.combofeld([…]) (frei tippbar + Vorschläge) · gui.datumsfeld() · gui.datum_wert(feld) (Text „TT.MM.JJJJ") — beide mit wert/setze_wert/bei_änderung |
| Formular | gui.formular() · gui.formular_zeile(f, "Etikett", widget) · gui.formular_absenden(f, "Text", handler) (Absenden erst aktiv, wenn alle geprüften Felder gültig) · gui.formular_abbrechen(f, "Text", handler) |
| Komma-Schieberegler | gui.schieberegler_komma(min, max, schritt) (Komma-Literale mit f: 0.1f) · gui.schieber_komma_wert(s) · gui.setze_schieber_komma_wert(s, wert) · gui.bei_schieben(s, handler) |
| Auswahl-Box | gui.auswahl_box([…]) · gui.gewählt(box) · gui.bei_wahl(box, handler) |
| Liste | gui.liste_anzeige([…]) · gui.gewählter_index(l) · gui.gewählter_wert(l) · gui.bei_auswahl(l, handler) · gui.bei_doppelklick(l, handler) · gui.setze_einträge(l, […]) (ersetzt alle, hebt Auswahl auf) · gui.eintrag_anhängen(l, text) · gui.eintrag_entfernen(l, index) · gui.einträge_anzahl(l) — Listen wachsen jetzt zur Laufzeit wie Tabellen |
| Tabelle | gui.tabelle([spalten]) (editierbar) · gui.tabelle_wählbar([spalten]) (Zeilen-Auswahl) · gui.zeile_anhängen(t, [zellen]) · gui.zeile_entfernen(t, index) · gui.zeilen_anzahl(t) · gui.zell_wert(t,z,s) · gui.setze_zell_wert(t,z,s,text) · gui.gewählte_zeile/_spalte(t) · gui.bei_zell_auswahl/_änderung(t, handler) · gui.bei_doppelklick(t, handler) (tabelle_wählbar) · gui.tabelle_sortierbar(t) (Klick auf Spaltenkopf sortiert, ▲/▼) |
| Baum | gui.baum() · gui.baum_knoten(baum, id, eltern_id, titel) (eltern_id "" = Wurzel) · gui.baum_gewählt(baum) (ID des gewählten Knotens) · gui.bei_baum_auswahl(baum, handler) · gui.baum_leeren(baum) |
| Dialoge | gui.dialog_info(fenster, titel, text) · gui.dialog_frage(fenster, titel, text, handler) · gui.dialog_datei(fenster, handler) · gui.dialog_speichern(fenster, handler) · gui.gewählter_pfad(fenster) |
| Dialoge (Profi) | gui.dialog_eingabe(fenster, titel, text, handler) + gui.eingabe_wert(fenster) · gui.dialog_farbe(fenster, titel, handler) + gui.farbe_wert(fenster) · gui.dialog_inhalt(fenster, titel, widget, schließen_text) · gui.dialog_ordner(fenster, handler) · gui.dialog_datei_gefiltert/speichern_gefiltert(fenster, [endungen], handler) · gui.dialog_warten(fenster, titel, text) / gui.dialog_fortschritt(...) + gui.setze_dialog_fortschritt(d, prozent) + gui.dialog_schließe(d) |
| Kontextmenü | gui.kontext_menü(widget) (Rechtsklick-Wrapper; der Menü-Handle kommt ins Layout) · gui.kontext_eintrag(menü, "Titel", handler) · gui.kontext_trenner(menü) |
| Menü | gui.menü_leiste(fenster) · gui.menü_hinzufügen(leiste, titel) · gui.menü_eintrag(menü, titel, handler) · gui.menü_trenner(menü) · gui.menü_anwenden(fenster) (Leiste neu anwenden nach Umbeschriften) |
| Leisten | gui.werkzeug_leiste([knöpfe]) · gui.status_leiste(text) |
| Kontrollkästchen | gui.kontrollkästchen(text) · gui.angehakt(k) (1/0) · gui.setze_angehakt(k, an) · gui.bei_umschaltung(k, handler) |
| Radio-Knöpfe | gui.radio_knöpfe([…]) · gui.gewählt(r) · gui.bei_wahl(r, handler) |
| Schieberegler | gui.schieberegler(min, max) · gui.schieber_wert(s) · gui.setze_schieber_wert(s, wert) · gui.bei_schieben(s, handler) |
| Fortschritt | gui.fortschritt_neu() · gui.setze_fortschritt(balken, prozent) (0–100) |
| Reiter | gui.reiter_neu() · gui.reiter_hinzufügen(reiter, titel, inhalt) · gui.reiter_hinzufügen_icon(reiter, titel, icon, inhalt) · gui.gewählter_reiter(r) · gui.setze_reiter(r, index) · gui.bei_reiter_wechsel(r, handler) |
| App-Integration | gui.benachrichtige(titel, text) (Desktop-Benachrichtigung) · gui.kopiere_text(text) / gui.hole_text() (Zwischenablage schreiben/lesen) · gui.öffne_url(url) (Standard-Browser/Mailprogramm) · gui.zeige_im_ordner(pfad) (Datei-Manager öffnen, Datei markieren — z. B. nach einem Export) · gui.bei_fenster_schließen(fenster, handler) (SetCloseIntercept — Handler schließt via gui.fenster_schließe) · gui.bei_fenster_geschlossen(fenster, handler) (nach dem Schließen) |
| Leinwand | gui.leinwand(breite, höhe) · gui.rechteck(b, x, y, breite, höhe, farbe) · gui.kreis(b, x, y, radius, farbe) · gui.linie(b, x1, y1, x2, y2, farbe, dicke) · gui.text_an(b, x, y, text, farbe, größe) · gui.bild_an(b, pfad, x, y, breite, höhe) (Bild/Sprite aus Datei, scharf/Nearest-Neighbor) · gui.leinwand_bild(b, bild, x, y, breite, höhe) (In-Memory-bild-Handle, scharf/Nearest-Neighbor, EIN Objekt) · gui.rechteck_rund(b, x, y, breite, höhe, radius, füllfarbe, randfarbe, randdicke) (abgerundet/berandet; leere Farbe "" = weglassen) · gui.leeren(b) — freie Hex-Farben |
| Leinwand (responsive) | gui.leinwand_elastisch() (füllt ihren Platz, wächst mit Fenster/Vollbild) · gui.leinwand_breite(l) / gui.leinwand_höhe(l) (aktuelle Pixelgröße) · gui.bei_größe(l, handler) (feuert bei Größenänderung) · gui.vollbild(fenster, an) (an = 1/0) |
| Bildschirm | gui.bildschirm_breite() / gui.bildschirm_höhe() (logische Größe der Arbeitsfläche des primären Monitors, ohne Taskleiste; schon vor gui.starte() aufrufbar; 0 = keine Info → feste Größe nehmen) — Fenster-Startgröße an jeden Monitor anpassen |
| Fenster füllen | gui.maximiere(fenster) (maximiert im Fenstermodus: füllt den aktuellen Monitor, behält Titelleiste/X, bleibt verschieb-/verkleinerbar; nach gui.starte()) · gui.vollbild(fenster, 1) (randloses Vollbild, ohne Titelleiste) |
| Timer/Animation | gui.bei_zeit(ms, handler) (periodisch, gibt Handle) · gui.nach_zeit(ms, handler) (einmalig) · gui.stoppe_zeit(handle) |
| Eingabe (Spiele) | gui.bei_taste(fenster, handler) + gui.letzte_taste(fenster) (Text: "links"/"rechts"/"hoch"/"runter"/"leer"/Buchstabe) · gui.taste_gedrückt(fenster, taste) (Ganz 1/0, gehalten — für flüssige Bewegung im Game-Loop) · gui.bei_maus_klick(leinwand, handler) + gui.maus_x(b) / gui.maus_y(b) (Ganz) · gui.bei_maus_bewegung(leinwand, handler) (ohne Taste) · gui.bei_maus_ziehen(leinwand, handler) + gui.zieh_dx(b) / gui.zieh_dy(b) (Verschiebung) · gui.bei_maus_los(leinwand, handler) (Loslassen nach Ziehen → Gummiband-Werkzeuge) · gui.bei_maus_rad(leinwand, handler) + gui.rad_dy(b) / gui.rad_dx(b) (Mausrad → Zoom/Scroll) · gui.setze_cursor(leinwand, name) (Mauszeiger je Werkzeug: "standard"/"kreuz"/"hand"/"text"/"pipette"; nur Leinwände mit Maus-Handler) · gui.setze_cursor_bild(leinwand, pfad, hotx, hoty) (Cursor aus eigener SVG/PNG-Datei; hotx/hoty = Punkt unter der Mausspitze) |
Klang/Sound (nutze klang) |
klang.lade(pfad) → Handle (WAV/MP3/OGG, Pfad relativ zur .ki) · klang.spiele(handle) (nicht-blockierend, überlappend) · klang.spiele_schleife(handle) (Musik) · klang.stoppe(handle) · klang.spiele_datei(pfad) (Kurzform) · klang.lese_samples(pfad) → liste<komma> (Analyse, wirft) · klang.lese_abtastrate(pfad) → ganz (wirft) |
Merke: Widget-Handler sehen nur ihr eigenes Widget als Parameter. Für alles andere (andere Widgets, Zähler, Zustand) nutzt du globale Variablen (Abschnitt 34.6).
Vollständige Beispiele findest du in beispiele/gui_hallo.ki, beispiele/gui_farben.ki, beispiele/gui_farbwechsel.ki (bunte Knöpfe + Live-Farbwechsel), beispiele/gui_formular.ki, beispiele/gui_auswahl.ki, beispiele/gui_editor.ki, beispiele/gui_notizen.ki (Notiz-App), beispiele/gui_quiz.ki, beispiele/gui_leinwand.ki (frei zeichnen mit eigenen Farben) beispiele/gui_animation.ki (bewegte Kugel mit Timer), beispiele/gui_eingabe.ki (Tastatur + Maus steuern die Leinwand) und beispiele/gui_sprites.ki (hüpfendes Bild-Sprite).
34.12 Zwei vollständige Beispiel-Apps
Diese zwei größeren Apps führen viele Bausteine zusammen — gute Vorlagen für eigene Programme:
Haushaltsbuch (beispiele/gui_buchhaltung.ki) — eine echte kleine Buchhaltung:
- Einnahmen/Ausgaben (Kontrollkästchen), Saldo = Einnahmen − Ausgaben (kann negativ werden);
- wählbare Tabelle im Master-Detail: Zeile anklicken → oben in Feldern bearbeiten → „Aktualisieren"/„Löschen";
- exakte Geldbeträge (
geld+ Dezimal, 5-Rappen-Rundung), getippter Betrag viaals_dezimal; - CSV-Persistenz: automatisch in den Profil-Ordner speichern + beim Start laden, plus „Speichern unter…"/„Öffnen…";
- der Saldo-Rahmen färbt sich live grün/rot (
setze_rahmen_farbe).
Datei-Browser (beispiele/gui_datei_browser.ki) — durch das Datei-System navigieren:
- Split-Ansicht (
teiler_h): links eine wählbare Tabelle (Name/Typ/Größe), rechts Text-Vorschau; - Live-Navigation durch Leeren + Neufüllen der Tabelle (
zeilen_anzahl/zeile_entfernen/zeile_anhängen) ausdatei.einträge; - Vorschau via
datei.inhalt(Binärdateien/zu große werden sauber abgefangen); - read-only — nur ansehen und navigieren.
Bauen + starten (Beispiel):
kiste build beispiele/gui_buchhaltung.ki -o gui_buchhaltung.exe && ./gui_buchhaltung.exe
34.13 Dein erstes Spiel — „Fang die Münze"
Jetzt führen wir alles aus den Spiele-Abschnitten zusammen: Leinwand (zeichnen, 34.10c), Timer (Game-Loop, 34.10d) und Eingabe (gehaltene Tasten, 34.10e). Das Spiel: Du steuerst eine blaue Kugel mit den Pfeiltasten und fängst eine goldene Münze. Jede gefangene Münze gibt einen Punkt, dann springt sie zur nächsten Ecke.
Schritt 1 — der Zustand. Ein Spiel ist im Kern ein Häufchen globaler Variablen, die sich über die Zeit ändern: die Spielerposition, die Münzposition, der Punktestand.
nutze gui
nimm bild = gui.leinwand(400, 300)
nimm x = 200 // Spieler
nimm y = 150
nimm muenze_x = 60 // Münze
nimm muenze_y = 60
nimm phase = 0 // welche Ecke als Nächstes
nimm punkte = 0
Schritt 2 — das Bild zeichnen. Eine Funktion malt den aktuellen Zustand. Immer
erst leeren, dann alles neu — Hintergrund, Münze, Spieler, Punktestand:
funktion zeichne() {
gui.leeren(bild)
gui.rechteck(bild, 0, 0, 400, 300, "#0f172a")
gui.kreis(bild, muenze_x, muenze_y, 12, "#fbbf24") // Münze (gold)
gui.kreis(bild, x, y, 18, "#38bdf8") // Spieler (blau)
gui.text_an(bild, 12, 12, "Punkte: {punkte}", "#e4e4e7", 18)
}
Schritt 3 — die Münze versetzen. Wenn die Münze gefangen wird, soll sie woanders auftauchen. Wir wechseln einfach zwischen vier festen Ecken (kein Zufall nötig):
funktion naechste_muenze() {
phase = phase + 1
wenn phase > 3 { phase = 0 }
wenn phase == 0 { muenze_x = 60
muenze_y = 60 }
wenn phase == 1 { muenze_x = 340
muenze_y = 60 }
wenn phase == 2 { muenze_x = 340
muenze_y = 240 }
wenn phase == 3 { muenze_x = 60
muenze_y = 240 }
}
Schritt 4 — der Game-Loop. Das Herz des Spiels: ~60-mal pro Sekunde Eingabe lesen, Spielfigur bewegen, auf Treffer prüfen, neu zeichnen.
zeichne()
nimm fenster = gui.fenster_öffne("Kiste — Fang die Münze", 420, 360)
gui.bei_zeit(16, funktion(t) {
// bewegen — nur solange gedrückt
wenn gui.taste_gedrückt(fenster, "links") == 1 { x = x - 4 }
wenn gui.taste_gedrückt(fenster, "rechts") == 1 { x = x + 4 }
wenn gui.taste_gedrückt(fenster, "hoch") == 1 { y = y - 4 }
wenn gui.taste_gedrückt(fenster, "runter") == 1 { y = y + 4 }
// im Spielfeld halten
wenn x < 18 { x = 18 }
wenn x > 382 { x = 382 }
wenn y < 18 { y = 18 }
wenn y > 282 { y = 282 }
// Münze gefangen? (ein Kasten rund um die Münze)
wenn x > muenze_x - 26 und x < muenze_x + 26 und y > muenze_y - 26 und y < muenze_y + 26 {
punkte = punkte + 1
naechste_muenze()
}
zeichne()
})
gui.zeige(fenster, bild)
gui.starte()
Das ist ein vollständiges Spiel in ~40 Zeilen. Das Muster gilt für fast jedes
Spiel: Zustand in globalen Variablen, eine zeichne()-Funktion, und ein
bei_zeit-Game-Loop, der Eingabe lesen → Zustand ändern → neu zeichnen macht.
Ideen zum Weiterbauen: eine Zeitbegrenzung (gui.nach_zeit), mehrere Münzen, ein
Gegner, der dir folgt, oder die Maus statt der Tastatur (gui.bei_maus_klick).
Vollständiges Beispiel:
beispiele/gui_spiel.ki.
34.14 Ein richtiges Spiel — „Schlange" (Snake)
„Fang die Münze" hat einen festen Zustand: zwei Positionen, ein Punktestand. Bei Snake wächst der Zustand mit — die Schlange wird mit jedem Futter länger. Dafür brauchen wir eine Liste, die während des Spiels wächst und schrumpft. Damit sind alle Spiele-Bausteine versammelt: Leinwand, Timer, Tasten, Listen, Zufall und Klang.
Das Spielfeld ist ein Gitter. 20 × 15 Zellen, jede Zelle 20 Pixel → Leinwand
400 × 300. Die Schlange bewegt sich nicht pixelweise, sondern zellenweise — pro
Game-Loop-Tick einen Schritt. Deshalb passt hier die diskrete Tastenbehandlung
(gui.bei_taste, 34.10e): Ein Tastendruck ändert die Richtung, die Bewegung selbst
macht der Timer.
Die Schlange lebt in zwei parallelen Listen. schlange_x[i]/schlange_y[i] sind
die Gitter-Koordinaten des i-ten Glieds; der Kopf ist das letzte Element:
nutze gui
nutze klang
nutze liste
nutze zufall
nutze wert
nimm schlange_x = [5, 6, 7] // drei Glieder, Kopf rechts
nimm schlange_y = [7, 7, 7]
nimm dx = 1 // Richtung: nach rechts
nimm dy = 0
Bewegen = vorn anfügen, hinten abschneiden. Das ist der Kern von Snake — kein Glied „bewegt sich", die Liste verschiebt sich:
liste.hänge_an(schlange_x, kopf_x) // neuer Kopf wächst voran …
liste.hänge_an(schlange_y, kopf_y)
wenn kopf_x == futter_x und kopf_y == futter_y {
punkte = punkte + 1 // gefressen: Schwanz BLEIBT → Schlange wächst
klang.spiele(ding)
neues_futter()
} sonst {
liste.entferne(schlange_x, 0) // sonst rückt der Schwanz nach
liste.entferne(schlange_y, 0)
}
Listen, die wachsen, speichern
beliebig. Sobald du eine Liste mitliste.hänge_an/liste.entferneveränderst, speichert sie im Build-Modus Werte vom Typbeliebig. Beim Herauslesen wandelst du eine Koordinate mitwert.als_ganz(...)wieder in eine Zahl — sonst sagt dir der Compiler beim Rechnen oder Vergleichen genau das. Im Spiel ist das in zwei kleine Helfer gekapselt, danach bleibt der Code sauber:funktion glied_x(i) { gib wert.als_ganz(schlange_x[i]) } funktion glied_y(i) { gib wert.als_ganz(schlange_y[i]) }
Kollision = Liste absuchen. Wand ist ein Bereichs-Check; für „beißt sich selbst" und „Futter liegt frei" reicht eine Funktion, die das Gitter gegen die Schlange prüft:
funktion auf_schlange(zx, zy) {
für i = 0 bis liste.länge(schlange_x) - 1 {
wenn glied_x(i) == zx und glied_y(i) == zy { gib 1 }
}
gib 0
}
Futter per Zufall (zufall.ganz(0, 19) / zufall.ganz(0, 14)) — und falls die
Zelle besetzt ist, einfach neu würfeln (solange auf_schlange(...) == 1).
Richtungswechsel mit Regel. Die 180°-Wende ist verboten — sonst beißt sich die Schlange sofort selbst:
gui.bei_taste(fenster, funktion(f) {
nimm taste = gui.letzte_taste(f)
wenn taste == "links" und dx != 1 { dx = -1
dy = 0 }
// … rechts/hoch/runter analog …
wenn taste == "leer" und lebt == 0 { neustart() }
})
Spiel vorbei + Neustart. Statt das Fenster zu schließen, setzen wir lebt = 0 —
der Tick tut dann nichts mehr, zeichne() blendet „Spiel vorbei!" ein, und die
Leertaste ruft neustart(): Zustand zurücksetzen (schlange_x = [5, 6, 7] — einer
Liste darf eine frische Liste zugewiesen werden), neues Futter, weiter geht's.
Das Muster zum Mitnehmen: Fester Zustand (Richtung, Punkte) lebt in Zahlen- Variablen, wachsender Zustand (die Schlange) in Listen. Der Game-Loop liest die Richtung, verschiebt die Liste, prüft Kollisionen, zeichnet neu — genau das gleiche Eingabe → Zustand → Bild-Muster wie in 34.13, nur mit einer Liste im Kern.
Vollständiger Spielkern (~130 Zeilen):
beispiele/gui_schlange.ki.
Spiel das ruhig ein paar Runden — und achte dabei auf das, was stört: Das Spiel rast sofort los, wenn du das Programm startest. Die Punkte stehen mitten im Spielfeld, die Schlange läuft drunter durch. Es gibt keine Pause, und der Rekord ist nach dem Schließen weg. Genau diese vier Ärgernisse beheben wir im nächsten Abschnitt — Schritt für Schritt.
34.15 Snake ausbauen — vom Spielkern zum richtigen Spiel
Der Spielkern aus 34.14 funktioniert, fühlt sich aber noch nicht wie ein Spiel
an. Mit fünf Umbauten wird er zu einem: Titelbild, Punkte außerhalb des Felds,
Pause, eine Rangliste mit Namen und Hintergrundmusik. Jeder Schritt nutzt nur
Bausteine, die du aus diesem Kapitel schon kennst. Das fertige Ergebnis steht in
beispiele/gui_schlange_deluxe.ki (auf doppelt so großem Feld: 40 × 30 Zellen,
Leinwand 800 × 600).
Schritt 1 — Titelbild statt Sofort-Start. Das Grundproblem: gui.bei_zeit
tickt ab Programmstart, also rennt die Schlange sofort los. Die Lösung ist ein
modus-Global, das sagt, in welchem Zustand das Spiel ist:
nimm modus = 0 // 0 = Titelbild · 1 = Spiel läuft · 2 = Spiel vorbei · 3 = Pause
Der Game-Loop-Tick prüft als Allererstes wenn modus == 1 { … } — auf dem
Titelbild tut er nichts. zeichne() verzweigt nach Modus: Titel (großer
Schriftzug, Rangliste), Spielfeld, oder Spielfeld + „Spiel vorbei"-Panel.
Gestartet wird per Klick aufs Spielfeld (gui.bei_maus_klick, 34.10e):
funktion bei_feld_klick(b) {
wenn modus == 0 oder modus == 2 { starte_spiel() }
}
gui.bei_maus_klick(bild, bei_feld_klick)
Während des Spiels (und in der Pause) tut der Klick bewusst nichts — sonst würde ein versehentlicher Klick die laufende Runde wegwerfen.
Schritt 2 — Punkte raus aus dem Spielfeld. Statt gui.text_an auf die
Leinwand zu malen, bekommt der Punktestand ein Etikett in einer Kopfzeile
über dem Spielfeld — echte Widgets und die Leinwand, gemischt in einem Layout:
nimm punkte_etikett = gui.etikett_neu("Punkte: 0")
// … beim Fressen:
gui.setze_text(punkte_etikett, "Punkte: {punkte}")
// … beim Fenster-Aufbau — Rand-Layout: die MITTE (der Lautstärke-Regler) bekommt
// die restliche Breite, links bleiben Punktestand + Musik-Haken kompakt:
nimm links = gui.horizontal([punkte_etikett, musik_haken])
nimm kopf = gui.rand_layout(musik_regler, [0, 0, links, 0])
gui.zeige(fenster, gui.vertikal([kopf, bild]))
In einem schlichten
gui.horizontalwürde der Regler auf seine Minimalbreite zusammenfallen — für „ein Element soll sich dehnen" istgui.rand_layoutdas richtige Werkzeug.
Schritt 3 — Pause. Mit dem modus-Muster fast geschenkt: Die Taste P
schaltet zwischen 1 (läuft) und 3 (Pause) um; der Tick läuft nur bei modus == 1,
und zeichne() legt bei Pause ein kleines Panel übers Feld. Buchstaben-Tasten
kommen in gui.letzte_taste großgeschrieben an:
wenn taste == "P" {
wenn modus == 1 { modus = 3
zeichne() } sonstwenn modus == 3 { modus = 1 }
}
Schritt 4 — Rangliste mit Namen. Drei Zutaten: der Name, den der Spieler
nach dem Spiel direkt auf den Game-Over-Schirm tippt (kein Eingabefeld, das mitten
im Spiel die Tasten wegschnappen könnte — die Buchstaben liest der Tasten-Handler
selbst, so wie in 34.10e), zwei parallele Listen rang_punkte/rang_namen
(dasselbe Muster wie die Schlange!), und eine Datei, damit der Rekord den
Neustart überlebt.
Neue Ergebnisse werden an der richtigen Stelle eingefügt — die Listen bleiben dadurch immer aufsteigend sortiert, ganz ohne Sortier-Funktion:
funktion trage_ein(p, name) {
nimm pos = 0
für i = 0 bis liste.länge(rang_punkte) - 1 {
wenn rang(i) < p { pos = i + 1 } // hinter alle kleineren Ergebnisse
}
liste.füge_ein(rang_punkte, pos, p)
liste.füge_ein(rang_namen, pos, name)
solange liste.länge(rang_punkte) > 6 { // nur die besten 5 behalten
liste.entferne(rang_punkte, 0) // kleinster fliegt vorne raus
liste.entferne(rang_namen, 0)
}
// … dann mit datei.schreibe sichern (zwei Zeilen pro Eintrag: Punkte, Name)
}
Die Anzeige läuft rückwärts durch die Liste (größte zuerst). Beim Programmstart
liest datei.zeilen die Einträge paarweise wieder ein — über dieselbe
Einfüge-Funktion, damit die Listen garantiert sortiert bleiben, egal in welcher
Reihenfolge die Datei daherkommt. Die erste Zeile der Datei ist ein
Format-Marker (schlange-v2): Steht da etwas anderes (etwa eine Datei von einer
älteren Spielversion), wird sie schlicht ignoriert statt Unsinn anzuzeigen.
Tippt niemand einen Namen, trägt das Spiel „Anonym" ein.
Schritt 5 — Hintergrundmusik. Ein Kontrollkästchen in der Kopfzeile schaltet
eine Musik-Endlosschleife (34.10g) an und aus, ein Schieberegler daneben regelt
die Lautstärke — live, also auch während die Schleife schon läuft.
klang.lautstärke wirkt pro Klang-Handle; der Regler setzt einfach beide
Handles und wird so zur Master-Lautstärke des Spiels (0 = alles stumm):
funktion bei_musik(k) {
wenn gui.angehakt(k) == 1 { klang.spiele_schleife(musik) }
sonst { klang.stoppe(musik) }
}
funktion bei_lautstärke(s) {
nimm w = gui.schieber_wert(s) // 0–100
klang.lautstärke(musik, w) // Musik (auch die laufende Schleife)
klang.lautstärke(ding, w) // Fress-Ton
}
Das Muster zum Mitnehmen: Vom Spielkern zum richtigen Spiel führt kein neuer
Baustein — nur das modus-Global als Zustands-Weiche, Widgets + Leinwand in
einem Layout, und Listen + Datei für alles, was bleiben soll.
Vollständiges Spiel (~250 Zeilen):
beispiele/gui_schlange_deluxe.ki.
34.16 Der GUI-Designer der Kiste-IDE — klicken statt tippen
Die Kiste-IDE hat einen GUI-Designer: Du klickst deine Oberfläche zusammen,
und der Designer erzeugt daraus den Kiste-Code aus diesem Kapitel. Du musst also
nichts Neues lernen — der Designer schreibt genau die gui.-Aufrufe, die du
schon kennst, nur eben für dich.
So startest du: In der IDE-Seitenleiste das Raster-Symbol (vier Quadrate)
klicken → ein neuer Entwurf öffnet sich (Datei-Endung .kid). Links die
Palette (alle Widgets und Container), in der Mitte der Aufbau-Baum und
die Fenster-/Thema-Einstellungen, rechts die Eigenschaften des gewählten
Elements.
Die wichtigste Regel: Der Baum IST dein Layout.
Die Einrückung im Aufbau-Baum zeigt die Verschachtelung — was eingerückt unter
einer horizontal-Reihe steht, liegt nebeneinander; was unter einer
vertikal-Spalte steht, liegt untereinander. Die Wurzel ist immer eine
vertikal-Spalte namens inhalt.
Klick-Logik der Palette: Ein Klick auf ein Palette-Element fügt es in den gerade ausgewählten Container ein (bzw. hinter die Auswahl, wenn ein Widget ausgewählt ist). Mit ▲/▼ sortierst du innerhalb des Containers, mit „▶ rein" schiebst du die Auswahl in den Container, der im Baum direkt darüber steht, mit „◀ raus" wieder heraus.
Rezept: Zwei Knöpfe nebeneinander mit 50 Pixel Abstand
- Wurzel
vertikal »inhalt«im Baum anklicken. - Palette (Gruppe CONTAINER): „Reihe (horizontal)" klicken.
- Die neue Reihe ist ausgewählt — jetzt aus der Palette nacheinander: Knopf → Abstand (Platzhalter) → Knopf.
- F5 (oder „▶ Vorschau") → das echte Fenster zeigt beide Knöpfe nebeneinander.
Der Baum sieht dann so aus — und erzeugt diesen Code:
vertikal »inhalt« nimm knopf_1 = gui.knopf_neu("Knopf")
horizontal »horizontal_1« nimm abstand_1 = gui.etikett_neu("")
knopf »knopf_1« nimm abstand_1_größe = gui.feste_größe(abstand_1, 50, 1)
abstand »abstand_1« ⌗50×1 nimm knopf_2 = gui.knopf_neu("Knopf")
knopf »knopf_2« nimm horizontal_1 = gui.horizontal([knopf_1, abstand_1_größe, knopf_2])
Sollen die Knöpfe die Breite gleichmäßig teilen (je 50 %), nimm statt der Reihe ein Raster mit 2 Spalten — Raster-Zellen teilen den Platz gleich auf.
Feste Größe
Jedes Widget hat im Eigenschaften-Panel den Abschnitt „Feste Größe"
(Breite/Höhe). Leer = das Layout entscheidet (wie bisher). Beide gesetzt =
das Widget bekommt exakt diese Größe und sitzt zentriert im übrigen Platz.
Im Code steckt dahinter gui.feste_größe(widget, breite, höhe) — der Designer
erledigt das Wrapping automatisch.
Knöpfe färben — Wichtigkeit
Knöpfe färbst du über die Eigenschaft Wichtigkeit: normal, primär
(Akzentfarbe — die wählst du frei unter Thema!), gefahr (rot), erfolg
(grün), warnung (gelb). Soll ein Knopf eine ganz eigene Farbe bekommen,
setz den Haken bei „Eigene Hintergrundfarbe" und wähl sie im Picker — im
Code steckt dahinter gui.setze_knopf_farbe(knopf, "#rrggbb"), das auch live
aus Handlern umfärben kann (die eigene Farbe ersetzt die Wichtigkeit). Dazu
Farb-Rahmen aus der Palette: ein Container, der seinem Inhalt einen frei
wählbaren Hintergrund gibt. Die sechs Thema-Rollen (akzent, hintergrund,
fläche, text, rahmen, gefahr) stellst du im Thema-Abschnitt mit Farb-Pickern
ein — siehe 34.4. Merke: Thema-Rollen färben global (alle Knöpfe, das
ganze Fenster), setze_knopf_farbe färbt einen Knopf.
Events und eigener Code — die Marker-Regel
- Widget anwählen → rechts unter Events beim passenden Event (z. B.
bei_klick) auf ✚ klicken — der Standard-Handlername wird eingetragen. - „Generieren" klicken → neben der
.kidentstehtname.kimit deinem Layout und einem leeren Funktions-Rumpf pro Handler. - In der
.kideine Logik in die Handler schreiben — alles zwischen den beiden Marker-Blöcken gehört dir und bleibt beim nächsten Generieren unangetastet. Die Marker-Blöcke selbst (VOM DESIGNER GENERIERT …) nie von Hand ändern — sie werden bei jedem Generieren ersetzt. - F5 im Designer = Vorschau: speichert, generiert, baut und startet das echte Fenster in einem Schritt.
Rezept: Liste, die zur Laufzeit wächst
Eingabefeld + Knopf „Hinzufügen" + Liste in den Baum, beim Knopf bei_klick
mit ✚ anlegen, Generieren — und im Handler:
funktion bei_klick_knopf_1(knopf) {
gui.eintrag_anhängen(liste_1, gui.wert(eingabefeld_1))
gui.setze_wert(eingabefeld_1, "")
}
Mehr Listen-Werkzeug: gui.setze_einträge(l, […]) (alles ersetzen),
gui.eintrag_entfernen(l, index), gui.einträge_anzahl(l) — siehe Spickzettel 34.11.
Menü und Tastatur-Kürzel
Beides sind eigene Abschnitte unter dem Aufbau (Titel anklicken zum Auf-/Zuklappen):
- Menü: „+ Menü" → Titel
Datei→ „+ Eintrag" → TitelNeu→ ✚ vergibt den Handler-Namen (neu_gewählt); „+ Trenner" für den Strich, ▲/▼ sortiert. Generiert wird die komplette Leiste (gui.menü_leiste→menü_hinzufügen→menü_eintrag). - Tastatur-Kürzel: „+ Kürzel" → Taste (
Strg+S) + Handler (✚ macht darausstrg_s_gedrückt). Generiertgui.taste(fenster, …). - Der →-Knopf neben jedem Handler (Events, Kürzel, Menü-Einträge) öffnet die
generierte
.kidirekt an der Funktion — der kürzeste Weg vom Designer zur Logik.
Die Schema-Vorschau und Platz auf kleinen Bildschirmen
Die Vorschau (Schema) über dem Baum zeigt dein Fenster als Anordnung von Kästen — mit echten Layout-Regeln (Spalte dehnt auf volle Breite, Reihe nicht, Teiler teilt im eingestellten Verhältnis, Reiter-Köpfe sind klickbar). Klicken wählt aus, Ziehen verschiebt Widgets zwischen Containern. Es bleibt ein Schema — die Pixel-Wahrheit zeigt F5.
Platz-Tipps: jeden Abschnitts-Titel anklicken = einklappen (wird gemerkt) · Strg+B
klappt den Explorer ein · ⛶ Fokus im Designer-Kopf räumt Explorer + Ausgabe mit
einem Klick weg · Speichern unter (Diskette mit Stift, Strg+Umschalt+S) legt eine
Ausprobier-Kopie an — bei Entwürfen wandert die generierte .ki mit deiner
Handler-Logik automatisch mit.
Der Designer kann (noch) nicht alles, was das
gui-Modul kann — Dialoge, Leinwand-Zeichnen, Timer und Sound schreibst du wie gewohnt in die Handler. Genau dafür ist die Marker-Regel da: Designer fürs Gerüst, du für das Verhalten.