GUI Konzept

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
xq
Establishment
Beiträge: 1582
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

GUI Konzept

Beitrag von xq »

Hallo Menschen,

ich stehe grade vor der Aufgabe, eine GUI für mein aktuelles Projekt / dessen Engine zu programmieren. Da ich mir den Aufwand nur einmal machen möchte, dachte ich, ich frag mal in die Runde, was ihr so von meinen aktuellen Ideen haltet.
  1. Positionierung durch einen speziellen Skalarwert (Relative Größe zum Bildschirm + Pixel-Offset)
  2. Erstellen der Oberfläche via Code oder durch Definition in einer XML-Datei
  3. Zugriff auf Elemente durch Pfade ( gui.Get<Button>("window/buttonContainer/createButton") )
  4. Grundlegende API orientiert sich an Windows.Forms (Parenting, Controls, ...)
  5. Eventbasiertes UI-Feedback
Ein Beispiellisting wäre in etwa so:

Code: Alles auswählen

private void InitializeComponents()
{
    // Load a layout from the asset folder
    GUILayout layout = this.assets.Load<GUILayout>("gui/mainMenu");

    // Create the GUI
    this.gui = new GUI();
    this.gui.Load(layout);

    // Assign some events
    Button startButton = this.gui.Get<Button>("buttonContainer/startButton");
    startButton.Click += this.startButton_Click;

    Button exitButton = this.gui.Get<Button>("buttonContainer/startButton");
    exitButton.Click += this.exitButton_Click;

    Button newButton = new Button("Click me!");
    newButton.X = new Scalar(0.2f, -40); // 20% * parentWidth - 40 pixel
    newButton.Y = new Scalar(0.2f, -15); // 20% * parentHeight - 15 pixel
    newButton.Size = new ScalarSize(new Scalar(0.0f, 80), new Scalar(0.0f, 30)); // 80x30 pixel size
    
    // Use ui base class here
    Control container = this.gui.Get<Control>("buttonContainer");
    container.Controls.Add(newButton); // Add the button
}
Der Code oben ist nicht implementiert, nur aus dem Kopf so meine Wunschvorstellung.

Würdet ihr mit so etwas arbeiten können oder gäbe es irgendwelche absoluten No-Gos?

Grüße
Felix

PS.:
Die Engine ist in C# mit OpenTK und OpenGL 3.3 geschrieben.
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: GUI Konzept

Beitrag von Niki »

Hi MasterQ32,

GUIs sind so eine Sache, da hat jeder seine eigene Vorstellung. Folgendes ist meine Vorstellung, und verträgt sich möglicherweise nicht mit Vorstellungen anderer ZFX'ler hier. Also bitte mit Skepsis genießen :-)

(1) Positionierung durch einen speziellen Skalarwert (Relative Größe zum Bildschirm + Pixel-Offset)

Mit dem Thema kann man wohl ein Buch füllen. Relative Größe zum Bildschirm kann in gewissen Fällen gut sein, in anderen aber sehr hinderlich, da sich nicht alle GUI-Elemente mit der Bildschirmauflösung skalieren sollten. Ich persönlich würde da ein System mit Layouts, horizontalem und vertikalem Alignment, Padding, Margin, und Thickness vorziehen. Als Warnung sage ich aber auch gleich dazu, dass das eine Menge Aufwand ist, und generell keine einfache Aufgabe.
Wahrscheinlich bist du mit Alignment, Padding, Margin, und Thickness bereits vertraut. Deshalb schreibe ich jetzt nur was zum Thema Layouts.

Jedes Fenster hat ein Layout. Dieses Layout beschreibt, wie die Kindfenster positioniert und ausgerichtet werden. Das grundlegendste Layout wäre das DockLayout, welches stumpf per Alignment, Padding, Margin und Thickness frei ausrichtet. Dann wäre da das StackLayout, welches Kindfenster entweder horizontal nebeneinander oder vertikal übereinander ausrichtet (wie eine Art Liste). Ein GridLayout erlaubt es dir die Kindfenster in einem unregelmäßigen Raster zu positionieren (colum, columnSpan, row, rowSpan). Und all die anderen Layouts kannst du dir dazu erfinden.

(2) Erstellen der Oberfläche via Code oder durch Definition in einer XML-Datei

Erstaml via Code, denn wenn du es geschickt anstellst, dann ist XML nur ein Aufsatz auf das bereits existierende. Mehr dazu in (3).

(3) Zugriff auf Elemente durch Pfade ( gui.Get<Button>("window/buttonContainer/createButton") )

Einen solchen Ansatz würde ich in C++ benutzen. In C# würde ich jedoch den Zugriff (nicht die Verwaltung) auf GUI-Elemente via Member-Variablen lösen. Nimm als Beispiel mal Silverlight, anstelle von Windows Forms. In einer XAML-Datei kannst du deine GUI skripten, wobei jedes GUI-Element einen Namen haben kann. Dieser Name ist gleichzeitig der Name der entsprechenden Member-Variablen im C#-Backend. Du kannst im C#-Backend also direkt auf jedes Element zugreifen das einen Namen hat.
Später dann, dank C#'s partial Klassen, kannst du ein Tool schreiben, welches aus einer XML-Datei eine solche partial Klasse erstellt, die generierten GUI-Code enthält (einschließlich der oben genannten Member-Variablen und Event-Anbindungen).
Auf diese Weise kannst du deine GUI per Code schreiben, diese später mit XML erweitern, und gleichzeitig kannst du direkt auf Elemente zugreifen, ohne erst danach suchen zu müssen.

(5) Eventbasiertes UI-Feedback

In C# auf jeden Fall, zumal dir die Sprache schon diese genialen event-Container anbietet. Wichtig ist, dass du events und on-Methoden strikt trennst. Nimm beispielsweise die Methode onMouseMove(). Diese wird durch die Klassenhierarchie vererbt, damit abgeleitete Klassen auf einen Mouse-Move reagieren können bevor ein Event zum Nutzer der GUI geschickt wird.
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: GUI Konzept

Beitrag von Niki »

Ich sehe gerade, dass Thickness bei Microsoft anders funktioniert als ich das in Erinnerung hatte. Ist halt schon ein bisserl her :-) Dehalb hier kurz was ich unter Thickness verstehe.

Bei mir besteht eine Thickness aus einem Skalar und einem ThicknessType, welche zusammen eine Breite oder Höhe definieren. Beispiele:

Code: Alles auswählen

// 10 Pixel breites Element
width = new Thickness(10, ThicknessType.Pixels);

// Breite des Elements ist 25% des verfügbaren Platzes
width = new Thickness(0.25, ThicknessType.Fraction);

// Breite des Elements ist abhängig vom Content des Elements (z.B. Textbreite eines Labels)
width = new Thickness(0, ThicknessType.Auto);
Das bedeutet nicht, dass man Thickness nun überall benutzt wo Breiten und Höhen ins Spiel kommen. Da muss man vorher genau drüber nachdenken. Ein Anwendungsgebiet sind die Spalten- und Zeilen-Definitionen eines GridLayouts.
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: GUI Konzept

Beitrag von kimmi »

Trenne auf jeden fall den Render-Code der UI-Elements von dem Controlle-Code, wo dann deine Buissness-Logik implementiert werden soll. Wenn der UI-Rendercode entkoppelt ist, kannst du auch viel schneller Mockups bauen und das Ganze vernünftig testen.

Gruß Kimmi
Benutzeravatar
xq
Establishment
Beiträge: 1582
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: GUI Konzept

Beitrag von xq »

Vielen Dank euch beiden.

@Niki:
Zu (1):
Ich hab jetzt mal rumexperimentiert mit verschiedenen Sachen. Am besten und intuitivsten fand ich jetzt eine Lösung mit einem ähnlichen Ansatz wie Thickness (Das was ich oben beschrieben habe, eine Mischung aus relativ und absolut. Damit konnte ich jetzt doch recht angenehm Objekte an beliebigen Bildschirm-Positionen befestigen). Meine Steuerelemente werden zudem noch ein Padding für enthaltene Controls haben (ClientSize).
Ich persönlich bin nicht unbedingt so ein Fan von diesen Layout-Methoden, bin auch nie wirklich mit WPF/Silverlight oder den GTK-Layouts durchgestiegen, wo man doch das gleiche Ergebnis auch mit Positionen und Anchors bekommt,

Zu (3):
Ich hab mich hier gegen Auto-Code entschieden, da mein Spiel, welches auf der Engine basiert, die zu mindestens 80% aus Mods und data-driven Programming besteht (Scriptsprachen, XML-Definition für alles was im Spiel so vorkommen kann, ...)

Zu (4):
Okay, warum habe ich überhaupt gefragt :P

@kimmi:
Ja, das war so angedacht. Funktioniert auch eigentlich ganz gut. Außerdem kann ich so ja auch Batching und ähnliches gut implementieren.

Aktuelle Implementierung sieht so aus:
Bild 1 Bild 2

Code: Alles auswählen

this.gui = new GUI();
this.gui.ClientArea = new ScalarRectangle(
	new Scalar(0.0f, 16.0f), new Scalar(0.0f, 16.0f),
	new Scalar(1.0f, -32.0f), new Scalar(1.0f, -32.0f));

Control control = new Control();
//control.Bounds = ScalarRectangle.FullScreen;
control.Bounds = new ScalarRectangle(
	new Scalar(0.1f, 0.0f), new Scalar(0.05f, 0.0f),
	new Scalar(0.8f, 0.0f), new Scalar(0.45f, 0.0f));
control.ClientArea = new ScalarRectangle(
	new Scalar(0.0f, 10.0f), new Scalar(0.0f, 10.0f),
	new Scalar(1.0f, -20.0f), new Scalar(1.0f, -20.0f));
this.gui.Controls.Add(control);

Control subcontrol = new Control();
subcontrol.Bounds = new ScalarRectangle(
	new Scalar(0.0f, 4.0f), new Scalar(0.0f, 4.0f),
	new Scalar(0.5f, -6.0f), new Scalar(0.5f, -6.0f));
control.Controls.Add(subcontrol);

subcontrol = new Control();
subcontrol.Bounds = new ScalarRectangle(
	new Scalar(0.5f, 4.0f), new Scalar(0.0f, 4.0f),
	new Scalar(0.5f, -6.0f), new Scalar(0.5f, -6.0f));
control.Controls.Add(subcontrol);

subcontrol = new Control();
subcontrol.Bounds = new ScalarRectangle(
	new Scalar(0.0f, 4.0f), new Scalar(0.5f, 4.0f),
	new Scalar(0.5f, -6.0f), new Scalar(0.5f, -6.0f));
control.Controls.Add(subcontrol);

subcontrol = new Control();
subcontrol.Bounds = new ScalarRectangle(
	new Scalar(0.5f, 4.0f), new Scalar(0.5f, 4.0f),
	new Scalar(0.5f, -6.0f), new Scalar(0.5f, -6.0f));
control.Controls.Add(subcontrol);
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Antworten