Wir bauen einen Commander – das Fenster
Schön, dass du da bist! Hol dir einen Kaffee, rück deinen Stuhl heran. In dieser Werkstatt bauen wir zusammen ein echtes Programm: einen Dateimanager, mit dem du Dateien per FTP und SFTP zwischen deinem Rechner und einem Server hin- und herschiebst. Aussehen soll er wie der gute alte Norton Commander. Heute legen wir den Grundstein. Am Ende dieser Etappe hast du ein Fenster, das schon genau nach Commander aussieht.
Bevor wir loslegen
Was ist eigentlich der Norton Commander?
Der Norton Commander kam 1986 für DOS heraus, und wer in den 90ern an einem PC saß, kennt ihn: ein blauer Bildschirm, zwei Listen mit Dateien nebeneinander, und unten eine Leiste mit den Funktionstasten F1 bis F10. Links ein Ordner, rechts ein Ordner, und mit F5 kopierst du die markierte Datei von der einen Seite auf die andere. Das Prinzip war so gut, dass es bis heute weiterlebt, zum Beispiel im Midnight Commander unter Linux oder im Total Commander unter Windows.
Für unser Programm ist diese Idee perfekt: Links ist dein eigener Rechner, rechts der Server. Dateien hochladen heißt dann einfach: links markieren, F5 drücken. Deshalb heißt unser Programm auch Fährmann. Er bringt deine Dateien vom einen Ufer ans andere.
Was du brauchst
- Rust, installiert über
rustup. Falls du das noch nicht hast: Buch, Kapitel 1 zeigt dir Schritt für Schritt, wie es geht. - Einen Editor, am besten VS Code mit der Erweiterung
rust-analyzer. - Ein Terminal, in dem du Befehle eintippst.
iced musst du noch nicht kennen. Ich erkläre alles, was wir brauchen, genau in dem Moment, in dem wir es brauchen. Wo es sich lohnt, verlinke ich auf das passende Kapitel im Kurs, falls du etwas genauer wissen willst.
So liest du die Code-Kästen
Wir schreiben nicht bei jedem Schritt das ganze Programm neu. Stattdessen zeige ich dir genau die Stelle, an der sich etwas ändert. Damit du sie in deiner Datei wiederfindest, stehen oben im Kasten immer der Dateiname und ein kurzer Hinweis, wo die Stelle liegt. Die Zeilen im Kasten sind farbig markiert:
Nach fast jedem Schritt machen wir einen Probelauf und schauen, ob das Programm tut, was es soll. Kleine Schritte, oft prüfen: So findest du Tippfehler sofort und nicht erst zwanzig Minuten später.
Das Projekt anlegen
Jedes Rust-Programm beginnt mit einem Projektordner. Den legt cargo für uns
an. Öffne ein Terminal, geh in den Ordner, in dem du deine Projekte sammelst, und tippe:
cargo new faehrmann
cd faehrmann
cargo new erzeugt einen Ordner faehrmann mit allem, was ein
Rust-Projekt braucht. Mit cd gehen wir hinein. Als Nächstes holen wir uns
iced dazu, die Bibliothek, mit der wir Fenster, Texte und Knöpfe
bauen:
cargo add iced@0.14
Wirf danach einen Blick in die Datei Cargo.toml. Das ist sozusagen der
Personalausweis deines Projekts. Ganz unten, unter [dependencies], steht jetzt
iced:
[package]
name = "faehrmann"
version = "0.1.0"
edition = "2024"
[dependencies]
iced = "0.14"
ae statt ä: Rust erlaubt in
Projektnamen nur einfache Buchstaben, Ziffern, - und _. Im
Fenstertitel darf später natürlich „Fährmann“ stehen. Und mit @0.14 legen wir
die Version von iced fest. So funktioniert diese Anleitung auch dann noch, wenn irgendwann
eine neuere iced-Version erscheint, die manches anders macht.Das erste Fenster
Jetzt wird es spannend: Wir bringen ein Fenster auf den Bildschirm. Öffne
src/main.rs. Dort steht schon ein kleines „Hello, world!“-Programm, das
cargo new für uns angelegt hat. Das brauchen wir nicht. Lösch den
ganzen Inhalt der Datei und tippe stattdessen Folgendes ein. Das ist übrigens das
einzige Mal, dass wir eine Datei komplett schreiben. Ab Schritt 3 kommen nur noch
einzelne Stellen dazu.
use iced::widget::text;
use iced::Element;
fn main() -> iced::Result {
iced::application(Faehrmann::default, Faehrmann::update, Faehrmann::view)
.title("Fährmann")
.run()
}
#[derive(Default)]
struct Faehrmann {}
#[derive(Debug, Clone)]
enum Message {}
impl Faehrmann {
fn update(&mut self, message: Message) {
match message {}
}
fn view(&self) -> Element<'_, Message> {
text("Ahoi! Hier entsteht der Fährmann.").into()
}
}
Das sind nur gut 20 Zeilen, aber es steckt schon das ganze Prinzip von iced darin. Gehen wir sie gemeinsam durch, von oben nach unten:
use …holt Dinge aus iced in unsere Datei, damit wir sie mit ihrem kurzen Namen benutzen können.textist das Widget, das einen Text anzeigt,Elementist der Oberbegriff für „irgendetwas, das man anzeigen kann“.fn main()ist der Startpunkt jedes Rust-Programms. Hier sagen wir iced: „Starte eine Anwendung.“ Dafür bekommt iced drei Dinge von uns: wie man den Anfangszustand erzeugt (Faehrmann::default), was bei einem Ereignis passieren soll (Faehrmann::update) und wie der Bildschirm aussieht (Faehrmann::view). Mit.title(…)setzen wir den Fenstertitel, und.run()startet das Ganze.struct Faehrmannist das Gedächtnis unseres Programms. Hier wird später stehen, welcher Ordner gerade offen ist oder welche Datei markiert ist. Im Moment ist es noch leer, und das ist völlig in Ordnung.enum Messageist der Briefkasten. Jedes Ereignis, auf das unser Programm reagieren soll (eine Taste wurde gedrückt, eine Datei ist fertig kopiert), wird ein Brief in diesem Kasten. Noch gibt es keine Ereignisse, also ist auch der Briefkasten leer.updateöffnet die Briefe und entscheidet, was passiert. Weil es noch keine Briefe gibt, steht inmatch message {}nichts drin.viewbeschreibt, was auf dem Bildschirm zu sehen ist. Im Moment: ein einziger Text. Das.into()am Ende verpackt den Text in einElement, denn genau das will iced vonviewzurückbekommen.
Dieses Dreieck aus Zustand, update und view wird uns durch das ganze Projekt begleiten. Im Buch gibt es dazu eine ausführliche Erklärung: Buch, Kapitel 17.
Faehrmann::default, Faehrmann::update und
Faehrmann::view stehen keine runden Klammern dahinter. Wir
rufen die Funktionen hier nicht selbst auf, sondern geben sie iced in die Hand, und iced ruft
sie dann auf, wann immer es sie braucht. Das kleine #[derive(Default)] über dem
struct sorgt übrigens dafür, dass es Faehrmann::default überhaupt
gibt.cargo run
Der erste Start dauert ein paar Minuten, weil cargo iced und alles, was dazugehört,
einmal übersetzt. Das passiert nur beim ersten Mal. Danach geht jeder Start nach einer
Änderung in wenigen Sekunden. Wenn alles klappt, öffnet sich ein Fenster mit dem Titel
„Fährmann“ und dem Text Ahoi! Hier entsteht der Fährmann. oben links. Schließ es
einfach wieder, bevor wir weitermachen.Schrift und Fenstergröße
Beim Norton Commander ist jeder Buchstabe genau gleich breit, ein „i“ nimmt genauso viel Platz ein wie ein „M“. So eine Schrift heißt Monospace- oder Festbreitenschrift, und sie ist nicht nur Nostalgie: Später, wenn wir Dateinamen und Dateigrößen in Spalten anzeigen, stehen dadurch alle Zahlen sauber untereinander. Also stellen wir die Schrift gleich jetzt für das ganze Programm um. Und wenn wir schon dabei sind, geben wir dem Fenster eine Größe, in der zwei Panels später gut nebeneinander passen.
Zuerst brauchen wir Font aus iced. Ändere dazu die zweite
use-Zeile ganz oben in der Datei:
use iced::widget::text;use iced::Element;use iced::{Element, Font};
Die geschweiften Klammern bedeuten: „Hol mir aus iced gleich mehrere
Dinge auf einmal.“ Jetzt ergänzen wir in main zwei Zeilen direkt unter dem
Titel:
fn main() -> iced::Result { iced::application(Faehrmann::default, Faehrmann::update, Faehrmann::view) .title("Fährmann").default_font(Font::MONOSPACE) .window_size((1000.0, 640.0)).run() }
default_font legt die Schrift fest, die überall verwendet wird, wenn
wir nichts anderes sagen. Font::MONOSPACE ist die Festbreitenschrift deines
Betriebssystems. window_size bekommt Breite und Höhe in Pixeln. Die doppelten
runden Klammern sind kein Tippfehler: Die inneren Klammern packen die zwei Zahlen zu einem
Paar zusammen, die äußeren gehören zum Funktionsaufruf.
application(…) ist die Lok, .title(…),
.default_font(…) und .window_size(…) sind Waggons, und
.run() ist der letzte Wagen. Ein Semikolon mittendrin würde den Zug
auseinanderreißen, und Rust würde meckern.cargo run
Das Fenster ist jetzt deutlich größer, und der Text sieht aus wie von einer alten
Schreibmaschine getippt. Genau so soll es sein.Die Commander-Farben
Jetzt kommt das Blau. Der Norton Commander stammt aus einer Zeit, in der Grafikkarten nur 16 Farben kannten. Das typische Commander-Blau war genau eine davon, und auch das Türkis der Menüleiste. In Zahlen: Jede Farbe auf dem Bildschirm mischt sich aus Rot, Grün und Blau, jeweils mit einem Wert von 0 bis 255. Das alte Blau war kein Rot, kein Grün, Blau auf 170.
Wir brauchen diese Farben an mehreren Stellen im Programm. Statt die Zahlen jedes Mal
neu hinzuschreiben, geben wir ihnen einmal einen Namen. Dafür gibt es in Rust
const, also eine Konstante: ein Wert mit Namen, der sich nie
ändert. Zuerst holen wir Color dazu:
use iced::widget::text;use iced::{Element, Font};use iced::{Color, Element, Font};
Dann schreiben wir unsere Farbpalette zwischen die use-Zeilen und
fn main():
use iced::{Color, Element, Font};// Die Farben des Norton Commander const BLAU: Color = Color::from_rgb8(0, 0, 170); const TUERKIS: Color = Color::from_rgb8(0, 170, 170); const SCHWARZ: Color = Color::BLACK; const WEISS: Color = Color::WHITE;fn main() -> iced::Result {
Jede Zeile liest sich so: „Es gibt eine Konstante namens BLAU, sie ist
vom Typ Color, und ihr Wert ist die Farbe mit Rot 0, Grün 0, Blau 170.“ Für
Schwarz und Weiß hat iced schon fertige Farben, die wir einfach übernehmen. Die
Großbuchstaben sind in Rust die übliche Schreibweise für Konstanten. So siehst du später
an jeder Stelle im Code sofort, dass es sich um einen festen Wert handelt.
TUERKIS statt TÜRKIS. Rust würde Umlaute in Namen sogar
erlauben, aber es ist unter Rust-Programmierern weltweit üblich, Namen nur aus einfachen
Buchstaben zu bilden, und auf englischen Tastaturen lassen sie sich schlecht tippen. Die
Faustregel: Umlaute gern in Texten, die der Benutzer sieht, nicht in Namen im Code.cargo run
Das Fenster sieht noch genauso aus wie vorher, denn wir haben die Farben ja nur
bereitgelegt, aber noch nicht benutzt. Genau darauf weist dich Rust auch hin: Im
Terminal erscheinen vier gelbe Warnungen wie constant `BLAU` is never used
(„die Konstante BLAU wird nie benutzt“). Keine Sorge: Gelb heißt
Warnung, nicht Fehler. Das Programm läuft trotzdem. Rust ist hier nur aufmerksam und sagt:
„Du hast da etwas vorbereitet, das du noch nicht verwendest.“ Im nächsten Schritt ändern
wir das.Die blaue Fläche
Ein Text allein hat keinen Hintergrund. Um ihm einen zu geben, packen wir ihn in einen Container. Stell dir einen Container als Kiste vor: Die Kiste kann eine Farbe haben, eine bestimmte Größe, und sie kann ihren Inhalt in die Mitte schieben. Genau das brauchen wir: eine große blaue Kiste, die das ganze Fenster ausfüllt.
Dafür brauchen wir wieder ein paar neue Dinge aus iced. Diesmal ändern sich
beide use-Zeilen:
use iced::widget::text;use iced::widget::{container, text};use iced::{Color, Element, Font};use iced::{Background, Color, Element, Fill, Font, Theme};
Jetzt geht es an die Funktion view. Die bisherige Zeile mit dem
einzelnen Text ersetzen wir durch unsere Kiste:
fn view(&self) -> Element<'_, Message> {text("Ahoi! Hier entsteht der Fährmann.").into()let arbeitsflaeche = container(text("Ahoi! Hier entsteht der Fährmann.")) .center(Fill) .style(|_theme: &Theme| container::Style { background: Some(Background::Color(BLAU)), text_color: Some(WEISS), ..container::Style::default() }); arbeitsflaeche.into()} }
Das ist der längste Code-Brocken bisher, also schauen wir ihn uns in Ruhe an:
let arbeitsflaeche = container(…)baut die Kiste mit unserem Text darin und gibt ihr den Namenarbeitsflaeche..center(Fill)sagt: „Mach die Kiste so groß wie möglich (Fillheißt: den ganzen verfügbaren Platz ausfüllen) und stell den Inhalt genau in die Mitte.“.style(…)legt das Aussehen fest. Was in den Klammern steht, ist eine kleine Funktion ohne Namen, eine sogenannte Closure. Zwischen den senkrechten Strichen|…|steht, was sie bekommt: das aktuelle Farbschema (Theme). Das brauchen wir nicht, deshalb der Unterstrich vor dem Namen. Er sagt Rust: „Ich weiß, dass ich das nicht benutze, alles gut.“ Mehr über Closures: Buch, Kapitel 13.- Die Closure gibt einen
container::Stylezurück, also eine Beschreibung des Aussehens: Hintergrund blau, Textfarbe weiß. DasSome(…)heißt: „Ja, hier gibt es einen Wert“ (das Gegenstück wäreNone, „nichts festgelegt“). ..container::Style::default()ist ein praktischer Trick: „Alles, was ich nicht ausdrücklich genannt habe (Rahmen, Schatten und so weiter), nimm bitte so wie üblich.“- Die letzte Zeile
arbeitsflaeche.into()gibt die Kiste an iced zurück, wieder verpackt alsElement.
.style(…):
Dort endet der let-Befehl. In der letzten Zeile,
arbeitsflaeche.into(), steht dagegen kein Semikolon. In
Rust ist die letzte Zeile ohne Semikolon das, was eine Funktion zurückgibt. Mit Semikolon
würde view nichts zurückgeben, und der Compiler würde sich beschweren.cargo run
Das ganze Fenster ist jetzt blau, und der Text steht weiß in der Mitte. Das sieht
schon ziemlich nach Commander aus! Von den vier gelben Warnungen sind noch zwei übrig:
TUERKIS und SCHWARZ warten noch auf ihren Einsatz.
Die Menüzeile
Ganz oben hatte der Norton Commander eine türkise Zeile mit den Menüs. Die bauen wir jetzt, und zwar nach genau demselben Rezept wie eben: ein Container mit Text, nur diesmal mit türkisem Hintergrund und schwarzer Schrift.
Dabei stoßen wir auf eine neue Frage: Wie bekommen wir zwei Dinge
untereinander auf den Bildschirm, die Menüzeile oben und die blaue Fläche darunter? Die
Antwort heißt column!, eine Spalte. Alles, was du in eine Spalte packst,
stapelt iced von oben nach unten übereinander. Dafür brauchen wir column in
der ersten use-Zeile:
use iced::widget::{container, text};use iced::widget::{column, container, text};
Nun bauen wir in view die Menüzeile, und zwar direkt
über der Arbeitsfläche. Außerdem ändern wir die letzte Zeile, sodass
beide Teile zusammen in einer Spalte landen:
fn view(&self) -> Element<'_, Message> {let menuzeile = container(text(" Links Datei Befehle Optionen Rechts")) .width(Fill) .style(|_theme: &Theme| container::Style { background: Some(Background::Color(TUERKIS)), text_color: Some(SCHWARZ), ..container::Style::default() });let arbeitsflaeche = container(text("Ahoi! Hier entsteht der Fährmann."))⋮});arbeitsflaeche.into()column![menuzeile, arbeitsflaeche].into()}
Bei der Menüzeile ist .width(Fill) wichtig: Sie soll so breit sein wie das
ganze Fenster, aber nur so hoch wie eine Textzeile. Deshalb sagen wir hier nur etwas über
die Breite. Die Leerzeichen im Text sorgen für den Abstand zwischen den Menüs. Weil jeder
Buchstabe gleich breit ist (danke, Monospace-Schrift!), klappt das zuverlässig.
Das Ausrufezeichen hinter column zeigt dir, dass es ein
Makro ist. Ein Makro ist eine Art Abkürzung, die Rust vor dem
Übersetzen in längeren Code verwandelt. Für dich heißt das nur: Ausrufezeichen und
eckige Klammern nicht vergessen.
.style(…)-Teile von Menüzeile und Arbeitsfläche fast
gleich aussehen. Das ist tatsächlich doppelter Code. Den räumen wir in einer späteren
Etappe auf, wenn wir sehen, welche Farbkombinationen wir insgesamt brauchen. Erst bauen,
dann aufräumen, das ist beim Programmieren oft der bessere Weg.cargo run
Oben erscheint die türkise Menüzeile mit Links, Datei, Befehle, Optionen,
Rechts, darunter die blaue Fläche. Die Menüs lassen sich noch nicht anklicken, sie
sind vorerst nur Dekoration. Die Warnung zu TUERKIS ist verschwunden, nur
SCHWARZ ist noch übrig.Eine einzelne Funktionstaste
Jetzt zur berühmtesten Zeile des Commanders: der Funktionstastenleiste ganz unten. Sie besteht aus zehn Tasten, und jede sieht gleich aus: eine weiße Zahl auf schwarzem Grund, daneben ein türkises Feld mit der Beschriftung.
Wenn etwas zehnmal gleich aussieht, schreiben wir es nicht zehnmal hin. Stattdessen bauen wir eine Funktion, die eine Taste baut. Der geben wir die Nummer und die Beschriftung, und sie liefert uns die fertige Taste. Wie eine Plätzchen-Ausstechform: einmal gebaut, beliebig oft benutzt.
Um die Zahl und das Feld nebeneinander zu stellen, brauchen wir das Gegenstück
zu column!: row!, eine Reihe von links nach rechts. Also erst
mal wieder die use-Zeile:
use iced::widget::{column, container, text};use iced::widget::{column, container, row, text};
Die neue Funktion kommt ganz ans Ende der Datei, hinter die letzte
schließende geschweifte Klammer von impl Faehrmann. Sie gehört also nicht zu
unserem Faehrmann, sondern steht für sich allein:
column![menuzeile, arbeitsflaeche].into() } }// Baut eine einzelne Taste der Funktionstastenleiste, z. B. "5 Kopie". fn f_taste(nummer: u8, beschriftung: &'static str) -> Element<'static, Message> { let zahl = text(nummer).color(WEISS); let feld = container(text(beschriftung)) .width(Fill) .style(|_theme: &Theme| container::Style { background: Some(Background::Color(TUERKIS)), text_color: Some(SCHWARZ), ..container::Style::default() }); row![zahl, feld].width(Fill).into() }
Die erste Zeile ist die spannendste, also Stück für Stück:
nummer: u8ist die Tastennummer.u8ist eine kleine ganze Zahl von 0 bis 255, mehr als genug für zehn Tasten.beschriftung: &'static strist der Text auf der Taste. Das&strkennst du vielleicht schon: ein geliehener Text. Das'staticbedeutet: „Dieser Text lebt so lange wie das ganze Programm.“ Das stimmt für alles, was direkt in Anführungszeichen im Code steht, also auch für unsere Tastenbeschriftungen.-> Element<'static, Message>heißt: Die Funktion liefert ein Element zurück, also etwas Anzeigbares. Weil es nur Texte enthält, die ewig leben, darf auch das Element ewig leben.
Im Inneren passiert nichts Neues: text(nummer) macht aus der Zahl einen
Text, .color(WEISS) färbt ihn weiß, das Feld mit der Beschriftung ist wieder
ein türkiser Container. Mit row![zahl, feld] kommen beide nebeneinander, und
.width(Fill) sorgt dafür, dass sich die Taste so breit macht, wie Platz da
ist. Das wird im nächsten Schritt wichtig, wenn sich zehn Tasten den Platz teilen.
Eine Ausstechform nützt nur etwas, wenn man sie benutzt. Also probieren wir unsere Funktion gleich aus und hängen eine einzelne Taste unten an die Spalte an:
column![menuzeile, arbeitsflaeche].into()column![menuzeile, arbeitsflaeche, f_taste(5, "Kopie")].into()
f_taste muss außerhalb von
impl Faehrmann { … } stehen. Wenn du sie aus Versehen vor die letzte
schließende Klammer schreibst, wird sie zu einer Methode von Faehrmann, und
der Aufruf f_taste(5, "Kopie") findet sie nicht mehr. Die Fehlermeldung
lautet dann cannot find function `f_taste`. Zähl im Zweifel die Klammern
nach, oder lass deinen Editor den Code mit cargo fmt einrücken, dann sieht
man es sofort.cargo run
Ganz unten im Fenster erscheint eine einzelne, fensterbreite türkise Taste mit
der Beschriftung Kopie. Und die letzte gelbe Warnung ist verschwunden, denn
SCHWARZ ist jetzt die Schriftfarbe der Beschriftung.Die ganze Tastenleiste
Unsere Ausstechform funktioniert. Jetzt stechen wir alle zehn Tasten aus und legen sie in eine Reihe. Die Beschriftungen orientieren sich am Original, das auf Englisch Help, Menu, View, Edit, Copy, RenMov, Mkdir, Delete, PullDn und Quit hieß, nur eben auf Deutsch und kurz genug, dass sie in die Felder passen.
Die neue Leiste schreiben wir in view direkt über die letzte Zeile, und in
der letzten Zeile tauschen wir die Probe-Taste gegen die ganze Leiste aus:
⋮..container::Style::default() });let tastenleiste = row![ f_taste(1, "Hilfe"), f_taste(2, "Menü"), f_taste(3, "Ansicht"), f_taste(4, "Bearb."), f_taste(5, "Kopie"), f_taste(6, "Versch."), f_taste(7, "Ordner"), f_taste(8, "Lösch."), f_taste(9, "Leiste"), f_taste(10, "Ende"), ] .spacing(4); let tastenleiste = container(tastenleiste) .width(Fill) .style(|_theme: &Theme| container::Style { background: Some(Background::Color(SCHWARZ)), ..container::Style::default() });column![menuzeile, arbeitsflaeche, f_taste(5, "Kopie")].into()column![menuzeile, arbeitsflaeche, tastenleiste].into()}
Zwei Dinge sind hier neu:
.spacing(4)lässt zwischen den Tasten jeweils 4 Pixel Abstand. Weil die Leiste einen schwarzen Hintergrund bekommt, sieht man in diesen Lücken Schwarz, genau wie beim Original. Und hinter den weißen Zahlen ist jetzt ebenfalls Schwarz: Die verschwundene 5 aus Schritt 7 taucht wieder auf.- Wir schreiben zweimal
let tastenleiste. Beim zweiten Mal nehmen wir die fertige Reihe, packen sie in einen schwarzen Container und geben dem Ergebnis denselben Namen. Das nennt man in Rust Shadowing (Überschatten): Der neue Name verdeckt den alten. Das ist praktisch, wenn man etwas Schritt für Schritt verfeinert und sich keine Namen wietastenleiste2ausdenken möchte.
Weil jede Taste .width(Fill) hat, teilen sich die zehn Tasten die Breite
gerecht auf. Machst du das Fenster breiter oder schmaler, wachsen und schrumpfen alle
Tasten gleichmäßig mit. Probier es ruhig aus!
cargo run
Da ist er: der Fährmann im Commander-Look! Türkise Menüzeile oben, blaue Fläche in
der Mitte, und unten die komplette Leiste von 1 Hilfe bis 10 Ende. Im
Terminal sollte jetzt keine einzige gelbe Warnung mehr stehen.
Was passiert, wenn du jetzt F10 drückst? Noch gar nichts. Die Tasten sind bisher nur gemalt, das Programm hört noch nicht auf die Tastatur. Das und die ersten echten Dateilisten kommen in den nächsten Etappen.
Der komplette Code nach Etappe 1
Hier ist der vollständige Stand, so wie deine Dateien jetzt aussehen sollten. Nimm ihn zum Vergleichen, wenn irgendwo ein Fehler steckt, den du nicht findest, oder als Einstiegspunkt, wenn du erst bei Etappe 2 dazustößt.
Cargo.toml und src/main.rs anzeigen
Cargo.toml
[package]
name = "faehrmann"
version = "0.1.0"
edition = "2024"
[dependencies]
iced = "0.14"
src/main.rs
use iced::widget::{column, container, row, text};
use iced::{Background, Color, Element, Fill, Font, Theme};
// Die Farben des Norton Commander
const BLAU: Color = Color::from_rgb8(0, 0, 170);
const TUERKIS: Color = Color::from_rgb8(0, 170, 170);
const SCHWARZ: Color = Color::BLACK;
const WEISS: Color = Color::WHITE;
fn main() -> iced::Result {
iced::application(Faehrmann::default, Faehrmann::update, Faehrmann::view)
.title("Fährmann")
.default_font(Font::MONOSPACE)
.window_size((1000.0, 640.0))
.run()
}
#[derive(Default)]
struct Faehrmann {}
#[derive(Debug, Clone)]
enum Message {}
impl Faehrmann {
fn update(&mut self, message: Message) {
match message {}
}
fn view(&self) -> Element<'_, Message> {
let menuzeile = container(text(" Links Datei Befehle Optionen Rechts"))
.width(Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(TUERKIS)),
text_color: Some(SCHWARZ),
..container::Style::default()
});
let arbeitsflaeche = container(text("Ahoi! Hier entsteht der Fährmann."))
.center(Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(BLAU)),
text_color: Some(WEISS),
..container::Style::default()
});
let tastenleiste = row![
f_taste(1, "Hilfe"),
f_taste(2, "Menü"),
f_taste(3, "Ansicht"),
f_taste(4, "Bearb."),
f_taste(5, "Kopie"),
f_taste(6, "Versch."),
f_taste(7, "Ordner"),
f_taste(8, "Lösch."),
f_taste(9, "Leiste"),
f_taste(10, "Ende"),
]
.spacing(4);
let tastenleiste = container(tastenleiste)
.width(Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(SCHWARZ)),
..container::Style::default()
});
column![menuzeile, arbeitsflaeche, tastenleiste].into()
}
}
// Baut eine einzelne Taste der Funktionstastenleiste, z. B. "5 Kopie".
fn f_taste(nummer: u8, beschriftung: &'static str) -> Element<'static, Message> {
let zahl = text(nummer).color(WEISS);
let feld = container(text(beschriftung))
.width(Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(TUERKIS)),
text_color: Some(SCHWARZ),
..container::Style::default()
});
row![zahl, feld].width(Fill).into()
}
struct), einer
update-Funktion für Ereignisse (Message) und einer
view-Funktion für den Bildschirm. Mit container geben wir
Dingen Farbe und Größe, mit column! stapeln wir sie übereinander, mit
row! legen wir sie nebeneinander. Wiederkehrende Bausteine wie die
Funktionstasten bauen wir einmal als Funktion und benutzen sie dann beliebig oft. Und
Farben, die wir mehrfach brauchen, stehen als const an einer einzigen
Stelle.Zum Ausprobieren
Die Übungen sind freiwillig, aber sie helfen enorm. Mach nach jeder Übung einen Probelauf, und stell danach den alten Zustand wieder her, damit Etappe 2 bei dir genauso weitergeht wie bei allen anderen.
- Farbspiel: Ändere den Wert von
BLAUaufColor::from_rgb8(40, 40, 40). Was ändert sich im Fenster, was nicht? Und warum musstest du dafür nur eine einzige Zeile anfassen? - Lücken: Nimm in
viewdas.spacing(4)bei der Tastenleiste heraus. Was siehst du? Warum sieht die Leiste danach weniger nach Commander aus? - Leerer Briefkasten: In
updatestehtmatch message {}, ganz ohne Fälle. Normalerweise verlangt Rust, dass einmatchalle Möglichkeiten abdeckt. Warum ist Rust hier trotzdem zufrieden?
Lösungen anzeigen
1. Die große Fläche wird dunkelgrau, die Menüzeile und die
Tastenleiste bleiben, wie sie sind. Weil die Farbe als Konstante an genau einer
Stelle steht, ändert sich überall dort, wo BLAU verwendet wird, die
Farbe automatisch mit. Das ist der große Vorteil von Konstanten: eine Stelle ändern,
überall wirksam.
2. Die türkisen Felder stoßen direkt aneinander und verschmelzen zu einem langen Balken. Man sieht nicht mehr, wo eine Taste aufhört und die nächste anfängt, nur die schwarzen Zahlenfelder trennen sie noch. Erst die schmalen schwarzen Lücken machen aus dem Balken wieder zehn einzelne Tasten.
3. Weil Message (noch) gar keine Varianten hat. Ein
match muss alle möglichen Fälle abdecken, und bei null möglichen Fällen
ist das mit null Zweigen erledigt. Rust weiß sogar, dass es von einem leeren Enum nie
einen Wert geben kann, update also im Moment nie aufgerufen wird. Sobald
wir in einer späteren Etappe die erste Variante hinzufügen, verlangt Rust von uns
auch den passenden Zweig. Und genau das ist gut so: So kann man keinen Fall
vergessen.
Etappe 1 geschafft ⛴️
Du hast ein Rust-Projekt angelegt, dein erstes iced-Fenster gebaut und ihm den Look des Norton Commanders gegeben. In der nächsten Etappe kommt Leben in die blaue Fläche: Ein Panel zeigt die Dateien aus deinem eigenen Ordner an.
Hat dir das Mitbauen Spaß gemacht? Ein Kaffee hilft, dass die nächste Etappe ein Stück schneller fertig wird.
☕ Kaffee spendieren