Modul: Nui/Model (Bibliotheksziel nui_model)
Ein geöffnetes .nui-Dokument ist im Speicher ein sehr flaches Modell — genau wie im Original-Dateiformat:
struct NuiDocument
{
NuiNode root; // das genwnd-Wurzelfenster
std::vector<NuiNode> children; // flache Liste aller Kind-Controls
DocumentDiagnostics diagnostics;
bool genwndAppearedOutOfOrder = false;
int trailingBlankLines = 0;
bool finalEndHasTrailingNewline = true;
};
struct NuiNode
{
std::string typeName; // z. B. "static", "simplebutton"
std::vector<PropertyEntry> entries; // originalgetreu geordnet
std::vector<Diagnostic> diagnostics; // nur für diesen Block
int blankLinesBefore = 0; // erhält Leerzeilen-Formatierung
const PropertyEntry* FindProperty(std::string_view key) const;
};
Tiefere Hierarchien (z. B. Listeneinträge oder Scrollbar-Teile) existieren im Originalformat nicht als eigene Blöcke in der Datei — sie werden zur Laufzeit vom UI-System aus dem flachen Modell konstruiert, nicht im Dateiformat selbst gespeichert.
Das ist der zentrale Mechanismus, der verlustfreies Round-Tripping überhaupt erst möglich macht:
struct PropertyEntry
{
std::string key;
std::optional<std::string> originalRawText; // exakter Text aus der Datei
std::optional<PropertyValue> typedValue; // nur gesetzt, wenn vom Schema erkannt
bool isKnownToSchema = false;
bool dirty = false; // true, sobald der Nutzer den Wert ändert
};
Beim Laden: Jede Zeile eines Blocks wird zu genau einem PropertyEntry. Ist der Schlüssel dem Control-Typ-Schema bekannt, wird zusätzlich zum Rohtext ein typisierter Wert erzeugt. Ist er unbekannt, bleibt typedValue leer — der Eintrag existiert trotzdem, an derselben Position wie im Original, mit demselben Rohtext.
Beim Speichern: Für jeden Eintrag mit dirty == false wird originalRawText unverändert ausgegeben. Nur für tatsächlich bearbeitete Einträge (dirty == true) formatiert der Serializer den typedValue über denselben Codec neu, den auch der Parser beim Lesen verwendet hat.
Das ist der konkrete Unterschied zum Original-Editor: Dort gibt es keinen Rohtext-Fallback — jede nicht im hartkodierten Formular abgebildete Eigenschaft geht beim Speichern verloren. Hier ist "unbekannt" ein normaler, verlustfrei erhaltener Zustand.
enum class PropertyType
{
Int, Float, String, Bool, Rect, Color,
Enum, ResourceRef, BitflagSet, StringList,
};
BitflagSet verdient besondere Erwähnung, weil .nuis Style-/Flag-/Anchor-Bits kontextabhängig sind (siehe Architektur-Übersicht):
struct BitflagSet
{
uint32_t bits = 0;
constexpr bool Has(uint32_t bit) const { return (bits & bit) != 0; }
};
Ein bloßer BitflagSet ist absichtlich nicht selbstbeschreibend — welcher Bit-Name zu welchem Bit gehört, ergibt sich immer erst aus der BitflagTable des jeweiligen (Control-Typ, Eigenschaft)-Paars.
Eine einzige, datengetriebene Tabelle — kein globales Singleton, sondern von EditorSession/DocumentContext per Referenz durchgereicht an Parser, Serializer, Inspector und Palette, sodass genau eine Quelle der Wahrheit existiert (im Original driften die reale Control-Factory und die im UI-Dropdown hartkodierte Liste tatsächlich auseinander).
class ControlTypeRegistry
{
public:
bool Register(ControlTypeDescriptor descriptor);
const ControlTypeDescriptor* Find(std::string_view typeName) const;
// ...
};
Jeder ControlTypeDescriptor trägt:
PropertyDescriptors (Name, PropertyType, UI-Kategorie: Transform/Appearance/Text/Behavior),BitflagTable für style (typ-spezifisch),BitflagTable für flag und anchor (im Original tatsächlich über alle Typen identisch),AniLayout-Kennzeichnung für Typen mit zusammengesetzten Animationsnamen (z. B. Scrollbars, Tab-Köpfe).Beispiel — so wird der Typ static registriert (Nui/Schema/CorePack/src/CorePack.cpp):
auto staticType = MakeDescriptor("static", flagTable, anchorTable);
staticType.styleTable = BitflagTable({
BitflagEntry{"KSTYLE_STRETCH_VERTICAL", 1u << 1}
});
add(std::move(staticType));
Manche Typnamen im Originalformat sind zusammengesetzt, z. B. list^item_string^h (eine horizontale Liste von Text-Einträgen):
struct CompoundTypeName
{
std::string container; // "list"
std::string itemType; // "item_string"
std::string orientation; // "h"
static CompoundTypeName Parse(std::string_view typeName);
};