1 /** 2 Copyright: Copyright (c) 2017-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module voxelman.gui.components; 8 9 import voxelman.math; 10 import voxelman.gui; 11 import cbor : ignore; 12 import voxelman.graphics : Color4ub; 13 import datadriven.entityman : EntityManager; 14 15 void registerComponents(ref EntityManager widgets) 16 { 17 widgets.registerComponent!WidgetTransform; 18 widgets.registerComponent!WidgetStyle; 19 widgets.registerComponent!WidgetName; 20 widgets.registerComponent!WidgetType; 21 widgets.registerComponent!WidgetContainer; 22 widgets.registerComponent!WidgetIsFocusable; 23 widgets.registerComponent!WidgetEvents; 24 widgets.registerComponent!hidden; 25 } 26 27 enum WidgetFlags 28 { 29 hexpand = 0b0000_0001, 30 vexpand = 0b0000_0010, 31 } 32 33 /// Mandatory component 34 @Component("gui.WidgetTransform", Replication.none) 35 struct WidgetTransform 36 { 37 ivec2 relPos; // set by user/layout 38 ivec2 size; // set by user/layout 39 ivec2 minSize;// set by user 40 WidgetId parent; // 0 in root widget 41 42 uint flags; // flags from WidgetFlags 43 ivec2 absPos; // updated in layout phase from root to leaves by parent layout 44 ivec2 measuredSize; // updated in measure phase from leaves to root by widget's layout 45 ivec2 constrainedSize() { return vector_max(measuredSize, minSize); } 46 bool hasHexpand() { return !!(flags & WidgetFlags.hexpand); } 47 bool hasVexpand() { return !!(flags & WidgetFlags.vexpand); } 48 void hexpand(bool val) { flags = set_flag(flags, val, WidgetFlags.hexpand); } 49 void vexpand(bool val) { flags = set_flag(flags, val, WidgetFlags.vexpand); } 50 void hvexpand(bool val) { hexpand(val); vexpand(val); } 51 52 bool contains(ivec2 pos) { return irect(absPos, size).contains(pos); } 53 } 54 55 /// Convenience setters 56 WidgetProxy minSize(WidgetProxy widget, ivec2 minSize) { widget.getOrCreate!WidgetTransform.minSize = minSize; return widget; } /// ditto 57 WidgetProxy minSize(WidgetProxy widget, int w, int h) { widget.getOrCreate!WidgetTransform.minSize = ivec2(w, h); return widget; } /// ditto 58 WidgetProxy size(WidgetProxy widget, int w, int h) { widget.getOrCreate!WidgetTransform.size = ivec2(w, h); return widget; } /// ditto 59 WidgetProxy pos(WidgetProxy widget, ivec2 p) { widget.getOrCreate!WidgetTransform.relPos = p; return widget; } /// ditto 60 WidgetProxy pos(WidgetProxy widget, int x, int y) { widget.getOrCreate!WidgetTransform.relPos = ivec2(x, y); return widget; } /// ditto 61 WidgetProxy hexpand(WidgetProxy widget) { widget.getOrCreate!WidgetTransform.hexpand = true; return widget; } /// ditto 62 WidgetProxy vexpand(WidgetProxy widget) { widget.getOrCreate!WidgetTransform.vexpand = true; return widget; } /// ditto 63 WidgetProxy hvexpand(WidgetProxy widget) { widget.getOrCreate!WidgetTransform.hvexpand = true; return widget; } /// ditto 64 WidgetProxy measuredSize(WidgetProxy widget, ivec2 ms) { widget.getOrCreate!WidgetTransform.measuredSize = ms; return widget; } /// ditto 65 WidgetProxy measuredSize(WidgetProxy widget, int w, int h) { widget.getOrCreate!WidgetTransform.measuredSize = ivec2(w, h); return widget; } /// ditto 66 67 bool hasHexpand(WidgetProxy widget) { return widget.getOrCreate!WidgetTransform.hasHexpand; } /// ditto 68 bool hasVexpand(WidgetProxy widget) { return widget.getOrCreate!WidgetTransform.hasVexpand; } /// ditto 69 70 @Component("gui.hidden", Replication.none) 71 struct hidden {} 72 73 @Component("gui.WidgetStyle", Replication.none) 74 struct WidgetStyle 75 { 76 Color4ub color; 77 Color4ub borderColor; 78 } 79 80 @Component("gui.WidgetName", Replication.none) 81 struct WidgetName 82 { 83 string name; 84 } 85 86 @Component("gui.WidgetType", Replication.none) 87 struct WidgetType 88 { 89 string name; 90 } 91 92 string widgetType(WidgetProxy widget) { 93 if (auto type = widget.get!WidgetType) return type.name; 94 return "Widget"; 95 } 96 97 string widgetType(GuiContext ctx, WidgetId wid) { 98 if (auto type = ctx.widgets.get!WidgetType(wid)) return type.name; 99 return "Widget"; 100 } 101 102 @Component("gui.WidgetContainer", Replication.none) 103 struct WidgetContainer 104 { 105 WidgetId[] children; 106 void put(WidgetId wid) { 107 children ~= wid; 108 } 109 void removeChild(WidgetId wid) 110 { 111 import std.algorithm : remove, countUntil; 112 auto index = countUntil(children, wid); 113 if (index != -1) children = remove(children, index); 114 } 115 void bringToFront(WidgetId wid) { 116 foreach(index, child; children) 117 { 118 if (child == wid) 119 { 120 moveItemToEnd(index, children); 121 return; 122 } 123 } 124 } 125 } 126 127 /// 128 ChildrenRange children(WidgetProxy w) { 129 if (auto container = w.ctx.widgets.get!WidgetContainer(w.wid)) return ChildrenRange(w.ctx, container.children); 130 return ChildrenRange(w.ctx, null); 131 } 132 133 void moveItemToEnd(T)(size_t index, T[] array) 134 { 135 if (index+1 < array.length) // dont use length-1 due to size_t underflow 136 { 137 T item = array[index]; 138 foreach(i; index..array.length-1) 139 { 140 array[i] = array[i+1]; 141 } 142 array[$-1] = item; 143 } 144 } 145 146 unittest 147 { 148 void test(T)(size_t index, T[] array, T[] expected) { 149 auto initial = array.dup; 150 moveItemToEnd(index, array); 151 import std..string : format; 152 assert(array == expected, format( 153 "for (%s, %s) expected %s, got %s", 154 index, initial, expected, array)); 155 } 156 test(0, cast(int[])[], cast(int[])[]); 157 test(0, [3], [3]); 158 test(0, [3,2], [2,3]); 159 test(0, [3,2,1], [2,1,3]); 160 test(1, [3], [3]); 161 test(1, [3,2], [3,2]); 162 test(2, [3,2,1], [3,2,1]); 163 } 164 165 size_t numberOfChildren(WidgetProxy widget) 166 { 167 if (auto container = widget.get!WidgetContainer) return container.children.length; 168 return 0; 169 } 170 171 size_t numberOfChildren(GuiContext ctx, WidgetId wid) 172 { 173 if (auto container = ctx.widgets.get!WidgetContainer(wid)) return container.children.length; 174 return 0; 175 } 176 177 void bringToFront(WidgetProxy widget) 178 { 179 auto parentId = widget.get!WidgetTransform.parent; 180 if (auto container = widget.ctx.get!WidgetContainer(parentId)) 181 container.bringToFront(widget.wid); 182 } 183 184 @Component("gui.WidgetIsFocusable", Replication.none) 185 struct WidgetIsFocusable {} 186 187 @Component("gui.WidgetEvents", Replication.none) 188 struct WidgetEvents 189 { 190 import std.stdio; 191 this(Handlers...)(Handlers handlers) 192 { 193 addEventHandlers(handlers); 194 } 195 private alias EventHandler = void delegate(WidgetProxy widget, ref void* event); 196 197 @ignore EventHandler[][TypeInfo] eventHandlers; 198 199 void addEventHandlers(Handlers...)(Handlers handlers) 200 { 201 foreach(h; handlers) addEventHandler(h); 202 } 203 204 private enum void* FUNCTION_CONTEXT_VALUE = cast(void*)42; 205 private static struct DelegatePayload 206 { 207 void* contextPtr; 208 void* funcPtr; 209 } 210 211 void addEventHandler(EventType)(void delegate(WidgetProxy, ref EventType) handler) 212 { 213 eventHandlers[typeid(EventType)] ~= cast(EventHandler)handler; 214 } 215 216 void addEventHandler(EventType)(void function(WidgetProxy, ref EventType) handler) 217 { 218 // We use non-null value because null if a valid context pointer in delegates 219 DelegatePayload fakeDelegate = {FUNCTION_CONTEXT_VALUE, handler}; 220 eventHandlers[typeid(EventType)] ~= *cast(EventHandler*)&fakeDelegate; 221 } 222 223 void replaceEventHandler(EventType)(void delegate(WidgetProxy, ref EventType) handler) 224 { 225 eventHandlers[typeid(EventType)] = [cast(EventHandler)handler]; 226 } 227 228 void replaceEventHandler(EventType)(void function(WidgetProxy, ref EventType) handler) 229 { 230 // We use non-null value because null if a valid context pointer in delegates 231 DelegatePayload fakeDelegate = {FUNCTION_CONTEXT_VALUE, handler}; 232 eventHandlers[typeid(EventType)] = [*cast(EventHandler*)&fakeDelegate]; 233 } 234 235 void removeEventHandlers(T)() 236 { 237 eventHandlers.remove(typeid(T)); 238 } 239 240 /// Returns true if any handlers were called 241 /// This handler will be called by Gui twice, before and after visiting its children. 242 /// In first case sinking flag will be true; 243 bool postEvent(Event)(WidgetProxy widget, auto ref Event event) 244 { 245 if (auto handlers = typeid(Event) in eventHandlers) 246 { 247 foreach(handler; *handlers) 248 { 249 auto payload = *cast(DelegatePayload*)&handler; 250 if (payload.contextPtr == FUNCTION_CONTEXT_VALUE) 251 (cast(void function(WidgetProxy, ref Event))payload.funcPtr)(widget, event); 252 else 253 (cast(void delegate(WidgetProxy, ref Event))handler)(widget, event); 254 } 255 return true; 256 } 257 else 258 return false; 259 } 260 }