1 /*
2     Copyright © 2020, Luna Nielsen
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors: Luna Nielsen
6 */
7 module engine.ui.widget;
8 import engine;
9 import std.algorithm.mutation : remove;
10 import bindbc.opengl;
11 import std.exception;
12 import std.format;
13 
14 /**
15     A widget
16 */
17 abstract class Widget {
18 private:
19     string typeName;
20     Widget parent_;
21     Widget[] children;
22 
23     ptrdiff_t findSelfInParent() {
24         
25         // Can't find self if we don't have a parent
26         if (parent_ is null) return -1;
27 
28         // Iterate through parent's children and find our instance
29         foreach(i, widget; parent_.children) {
30             if (widget == this) return i;
31         }
32 
33         // We couldn't find ourselves?
34         AppLog.warn("UI", "Widget %s could not find self in parent", this);
35         return -1;
36     }
37 
38 protected:
39     /**
40         The parent widget
41     */
42     Widget parent() {
43         return parent_;
44     }
45 
46     /**
47         Base widget instance requires type name
48     */
49     this(string type) {
50         this.typeName = type;
51     }
52 
53     /**
54         Code run when updating the widget
55     */
56     abstract void onUpdate();
57 
58     /**
59         Code run when drawing
60     */
61     abstract void onDraw();
62 
63 public:
64 
65     /**
66         Whether the widget is visible
67     */
68     bool visible = true;
69 
70     /**
71         The position of the widget
72     */
73     vec2 position = vec2(0);
74 
75     /**
76         Changes the parent of a widget to the specified other widget.
77     */
78     void changeParent(Widget newParent) {
79 
80         // Once we're done we need to update our bounds
81         // We'll skip this if we threw an exception earlier
82         scope(success) update();
83         
84         // Remove ourselves from our current parent if we have any
85         if (parent_ !is null) {
86 
87             // Find ourselves in our parent
88             ptrdiff_t self = findSelfInParent();
89             enforce(self >= 0, "Invalid parent widget");
90 
91             // Remove ourselves from our current parent
92             parent_.children.remove(self);
93         }
94 
95         // If our new parent is null we'll end early
96         if (newParent is null) {
97             this.parent_ = null;
98             return;
99         }
100 
101         // Set our parent to our new parent and add ourselves to our new parent's list
102         this.parent_ = newParent;
103         this.parent_.children ~= this;
104     }
105 
106     /**
107         Update the widget
108 
109         Automatically updates all the children first
110     */
111     void update() {
112         
113         // Update all our children
114         foreach(child; children) {
115             child.update();
116         }
117 
118         // Update ourselves
119         this.onUpdate();
120     }
121 
122     /**
123         Draw the widget
124 
125         For widget implementing: override onDraw
126     */
127     final void draw() {
128 
129         // Don't draw this widget or its children if we're invisible
130         if (!visible) return;
131 
132         if (parent_ is null) UI.setScissor(vec4i(0, 0, GameWindow.width, GameWindow.height));
133 
134         // Draw ourselves first
135         this.onDraw();
136 
137         // We set our scissor rectangle to our rendering area
138         UI.setScissor(scissorArea);
139 
140         // Draw all the children
141         foreach(child; children) {
142             child.draw();
143         }
144     }
145 
146     /**
147         Gets the calculated position of the widget
148     */
149     final vec2 calculatedPosition() {
150         return parent_ !is null ? parent_.calculatedPosition+position : position;
151     }
152 
153     abstract {
154 
155         /**
156             Area of the widget
157         */
158         vec4 area();
159 
160         /**
161             Area in which the widget cuts out child widgets
162         */
163         vec4i scissorArea();
164     }
165 
166     override {
167 
168         /**
169             Gets this widget as a string
170 
171             This returns the tree for this instance of the widget ordered by type name.
172         */
173         final string toString() const {
174             return parent_ is null ? typeName : "%s->%s".format(parent_.toString, typeName);
175         }
176     }
177 }