Architektur

Vega ist in vier Schichten aufgebaut. Jede kennt nur die unter ihr — das ist die Regel, an der sich entscheidet, ob eine neue Datei richtig liegt.

Die vier Schichten

Schicht Units Aufgabe
1 — Grundlagen Types, Events, Strings, Lists Farben, Rechtecke, Ereignisarten, Puffer, Listen
2 — Zeichnen Canvas, Font, TrueType, Picture, Theme, Style Pixel, Schrift, Bilder, Aussehen
3 — Steuerelemente Control, StdCtrls, Edits, ListCtrls, … Der Baum aus Controls und der Dispatcher
4 — Fenster Backend, Backend/*, Forms Die einzige Stelle mit Kontakt zum Betriebssystem

Eine Unit aus Schicht 2 darf nichts aus Schicht 3 wissen. Wo das doch nötig wird, steht ein Funktionszeiger-Haken statt eines Imports — siehe unten.

Eigenes Zeichnen

Jedes Control malt sich selbst in einen Pixelpuffer im Format TColor (Byte 0 = Blau, 1 = Grün, 2 = Rot, 3 = Alpha). Erst der fertige Puffer geht über Present ans Fenstersystem.

Das kostet Arbeit und bringt drei Dinge:

  • Gleiches Aussehen überall. Ein Knopf sieht unter X11 aus wie unter

Windows, weil es derselbe Code ist.

  • Themes bestimmen alles, bis hinunter zur Titelleiste — es gibt

keine Systemvorgabe, die dazwischenfunkt.

  • Die HAL bleibt klein. 21 Methoden, mehr braucht eine neue

Plattform nicht.

Die HAL

Vega/Backend.lyx beschreibt den Vertrag als Interface IVegaBackend: Fenster anlegen und zerstören, Titel, Lage und Größe, Rahmen an/aus, Maximieren, Ziehen am Rand, Present, PollEvent, Zeigerform, Zeichenflächengröße, Monitore, Bildschirmgröße, Zwischenablage, Skalierung.

Fensterhandles sind opake int64. Was dahintersteckt, weiß nur das Backend (X11: Window-XID, Win32: HWND). Der geteilte Code reicht sie durch und deutet sie nie.

Mehr dazu: Backends.

Der Dispatcher

Vega/Dispatch.lyx verteilt die Ereignisse eines Fensters auf die Controls: Treffertest, Fokus, Maus einfangen, Doppelklick, Tastatur samt Umschaltern, Ziehen und Ablegen, Kontextmenü, Hinweisfenster.

Er ist der Grund, warum ein Control nur DoMouseDown überschreiben muss und nichts über Fenster wissen darf.

Zwei Muster, die man kennen muss

Funktionszeiger-Haken gegen Zyklen

Lyx hat keine Methodenzeiger und keine zyklischen Imports. Wenn eine untere Schicht etwas aus einer oberen braucht, wird eine Funktionsvariable gesetzt, statt zu importieren:

// In der unteren Unit:
var _kontextmenueOeffner: TOeffnerFn;

pub fn SetContextMenuOpener(f: TOeffnerFn): void {
  _kontextmenueOeffner := f;
}

Die obere Unit trägt sich beim Start ein. Benutzt an drei Stellen: Backend-Verweis in TCustomEdit, SetActionRunner zwischen Menüs und Aktionen, SetContextMenuOpener zwischen Dispatcher und Formularen.

Verwandt: ActionPtr, PopupMenuPtr und ImagesPtr liegen als int64 an TControl — dieselbe Lösung für dasselbe Problem.

Eigenschaften als Getter/Setter

Lyx kennt kein property. Was Delphi als

property Left: Integer read FLeft write SetLeft;

schreibt, ist hier ein Paar GetLeft / SetLeft — genau das, was der Pascal-Compiler intern ohnehin daraus macht. Die Felder bleiben öffentlich und lesbar; für Schleifen und Rechnungen ist der direkte Zugriff schneller und völlig in Ordnung.

Der Unterschied: ctl.Left := 20; verschiebt das Control, ohne neu zu zeichnen. ctl.SetLeft(20) zeichnet neu.

Gebündelt im Interface IControlProps. Das zahlt sich zweimal aus: beim Formatieren fremder Controls und beim LFM-Format, wo eine einzige Funktion SetCommonProp jedes Control bedient, ohne dessen Klasse zu kennen.

Layout

  • AlignalTop, alBottom, alLeft, alRight,

alClient. Das Control bekommt die ganze Kante.

  • Anchors — Bitmaske aus ANCHOR_LEFT/TOP/RIGHT/BOTTOM.

Bestimmt, was bei einer Größenänderung des Elternteils passiert.

  • GrößengrenzenMinWidth, MaxWidth, MinHeight,

MaxHeight.

Die Ankerrechnung merkt sich die vorige Größe am Elternteil (_layoutW, _layoutH) statt vier Werte je Kind — ein Feld pro Container gegen Delphis vier pro Control.

Ohne Anker gilt eine Regel, die überrascht und trotzdem richtig ist: das Control wandert halb mit. Sonst klebte ein mittig gedachtes Control beim Vergrößern oben links fest.