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 }