1 /** 2 Copyright: Copyright (c) 2013-2014 Andrey Penechko. 3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module anchovy.gui.widget; 8 9 import std.traits; 10 import anchovy.gui; 11 public import anchovy.utils.flexibleobject; 12 13 enum defaultAnchor = Sides.left | Sides.top; 14 15 /// Used to specify Widget.anchor. 16 enum Sides 17 { 18 left = 1, 19 right = 2, 20 top = 4, 21 bottom = 8, 22 } 23 24 //version = Debug_widget; 25 26 /// Container for common properties 27 class Widget : FlexibleObject 28 { 29 public: 30 31 this() 32 { 33 properties["type"] = new ValueProperty(this, "widget"); 34 properties["anchor"] = anchor = new ValueProperty(this, defaultAnchor); 35 properties["children"] = children = new ValueProperty(this, cast(Widget[])[]); 36 properties["logicalChildren"] = logicalChildren = new ValueProperty(this, cast(Widget[])[]); 37 properties["parent"] = parent = new ValueProperty(this, null); 38 39 properties["position"] = position = new ValueProperty(this, ivec2(0,0)); 40 properties["staticPosition"] = staticPosition = new ValueProperty(this, ivec2(0,0)); 41 42 properties["minSize"] = minSize = new ValueProperty(this, ivec2(0,0)); 43 properties["size"] = size = new ValueProperty(this, ivec2(0,0)); 44 properties["prefSize"] = prefferedSize = new ValueProperty(this, ivec2(0,0)); 45 properties["staticRect"] = staticRect = new ValueProperty(this, Rect(0,0,0,0)); 46 47 properties["state"] = state = new ValueProperty(this, "normal"); 48 properties["style"] = style = new ValueProperty(this, ""); 49 properties["geometry"] = geometry = new ValueProperty(this, (TexRectArray[string]).init); 50 51 properties["hasBack"] = hasBack = new ValueProperty(this, true); 52 properties["isVisible"] = isVisible = new ValueProperty(this, true); 53 properties["isFocusable"] = isFocusable = new ValueProperty(this, false); 54 properties["isEnabled"] = isEnabled = new ValueProperty(this, true); 55 properties["isHovered"] = isHovered = new ValueProperty(this, false); 56 properties["respondsToPointer"] = respondsToPointer = new ValueProperty(this, true); 57 properties["context"] = context = new ValueProperty(this, null); 58 59 auto onParentChanged = (FlexibleObject obj, Variant newParent){ 60 (cast(Widget)obj).invalidateLayout; 61 }; 62 63 auto onPositionChanged = (FlexibleObject obj, Variant newPosition){ 64 (cast(Widget)obj).invalidateLayout; 65 }; 66 67 property("parent").valueChanged.connect(onParentChanged); 68 property("position").valueChanged.connect(onPositionChanged); 69 70 auto onStaticPositionChanged = (FlexibleObject obj, Variant newStaticPosition){ 71 obj["staticRect"] = Rect(newStaticPosition.get!ivec2, obj.getPropertyAs!("size", ivec2)); 72 }; 73 74 auto onSizeChanged = (FlexibleObject obj, Variant newSize){ 75 obj["staticRect"] = Rect(obj.getPropertyAs!("staticPosition", ivec2), newSize.get!ivec2); 76 77 obj.setProperty!("geometry", TexRectArray[string])(null); 78 79 (cast(Widget)obj).invalidateLayout; 80 }; 81 82 auto onVisibilityChanged = (FlexibleObject obj, Variant isVisible) 83 { 84 Widget parent = obj.getPropertyAs!("parent", Widget); 85 if (parent is null) return; 86 87 Widget child = cast(Widget)obj; 88 89 import std.algorithm : remove; 90 91 if (obj["isVisible"] == true) 92 parent.setProperty!"children"(parent["children"] ~ child); // FIX, sholud insert in the same position 93 else 94 parent["children"] = parent["children"].get!(Widget[]).remove!((a) => a == child); 95 }; 96 97 property("staticPosition").valueChanged.connect(onStaticPositionChanged); 98 property("size").valueChanged.connect(onSizeChanged); 99 property("isVisible").valueChanged.connect(onVisibilityChanged); 100 101 addEventHandler(&handleDraw); 102 addEventHandler(&handleUpdatePosition); 103 addEventHandler(&handleExpand); 104 addEventHandler(&handleMinimize); 105 } 106 107 bool handleExpand(Widget widget, ExpandLayoutEvent event) 108 { 109 if (auto layout = widget.peekPropertyAs!("layout", ILayout)) 110 { 111 layout.expand(widget); 112 } 113 114 return true; 115 } 116 117 bool handleMinimize(Widget widget, MinimizeLayoutEvent event) 118 { 119 if (auto layout = widget.peekPropertyAs!("layout", ILayout)) 120 { 121 layout.minimize(widget); 122 } 123 124 return true; 125 } 126 127 bool handleUpdatePosition(Widget widget, UpdatePositionEvent event) 128 { 129 if (auto parent = widget.peekPropertyAs!("parent", Widget)) 130 { 131 if (*parent !is null) 132 { 133 ivec2 parentPos = (*parent).getPropertyAs!("staticPosition", ivec2); 134 ivec2 childPos = widget.getPropertyAs!("position", ivec2); 135 ivec2 newStaticPosition = parentPos + childPos; // Workaround bug with direct summ. 136 widget["staticPosition"] = newStaticPosition; 137 widget["staticRect"] = Rect(newStaticPosition, widget.getPropertyAs!("size", ivec2)); 138 } 139 } 140 141 return true; 142 } 143 144 bool handleDraw(Widget widget, DrawEvent event) 145 { 146 bool clipContent = widget.hasProperty!("clipContent"); 147 148 if (event.sinking) 149 { 150 Rect staticRect = widget.getPropertyAs!("staticRect", Rect); 151 152 if (clipContent) 153 { 154 event.guiRenderer.pushClientArea(staticRect); 155 } 156 157 if (widget.getPropertyAs!("hasBack", bool)) 158 event.guiRenderer.drawControlBack(widget, staticRect); 159 } 160 else if (clipContent) 161 event.guiRenderer.popClientArea; 162 163 return true; 164 } 165 166 167 ValueProperty anchor; 168 169 ValueProperty children; // visible children 170 ValueProperty logicalChildren; // all children 171 ValueProperty parent; 172 ValueProperty context; 173 174 ValueProperty position; 175 ValueProperty staticPosition; 176 177 ValueProperty minSize; 178 ValueProperty size; 179 ValueProperty prefferedSize; 180 ValueProperty staticRect; 181 182 ValueProperty state; 183 ValueProperty style; 184 ValueProperty geometry; 185 186 ValueProperty isFocusable; 187 ValueProperty isEnabled; 188 ValueProperty isHovered; 189 ValueProperty isVisible; 190 ValueProperty hasBack; 191 ValueProperty respondsToPointer; 192 193 void addEventHandler(T)(T handler) 194 { 195 static assert(isDelegate!T, "handler must be a delegate, not " ~ T.stringof); 196 alias widgetType = ParameterTypeTuple!T[0]; 197 alias eventType = ParameterTypeTuple!T[1]; 198 static assert(!is(eventType == Event), "handler's parameter must not be Event class but inherited one"); 199 static assert(is(eventType : Event), "handler's parameter must be inherited from Event class"); 200 static assert(is(widgetType : Widget), "handler must accept Widget as first parameter"); 201 static assert(ParameterTypeTuple!T.length == 2, "handler must have only two parameters, Widget's and Event's descendant"); 202 _eventHandlers[typeid(eventType)] ~= cast(bool delegate(Widget, Event))handler; 203 } 204 205 void removeEventHandlers(T)() 206 { 207 _eventHandlers[typeid(T)] = null; 208 } 209 210 /// Returns true if event was handled 211 /// This handler will be called by Gui class twice, before and after visiting its children. 212 /// In first case sinking flag will be true; 213 bool handleEvent(Event e) 214 { 215 bool result = false; 216 if (auto handlers = typeid(e) in _eventHandlers) 217 { 218 foreach(h; *handlers) 219 { 220 result |= h(this, e); 221 } 222 } 223 return result; 224 } 225 226 /// Event handlers. 227 bool delegate(Widget, Event)[][TypeInfo] _eventHandlers; 228 } 229 230 Widget getParentFromWidget(Widget root) 231 { 232 Widget* container; 233 container = root.peekPropertyAs!("container", Widget); 234 235 if (container is null) container = &root; 236 assert(*container); 237 238 return *container; 239 } 240 241 Behavior getWidgetBehavior(Behavior)(Widget widget) 242 { 243 IWidgetBehavior[] behaviors = widget.getPropertyAs!("behaviors", IWidgetBehavior[]); 244 245 foreach(b; behaviors) 246 { 247 if (auto beh = cast(Behavior)b) 248 return beh; 249 } 250 251 return null; 252 } 253 254 import std.range : only; 255 void addChildBefore(Widget root, Widget child, Widget before) 256 { 257 import std.algorithm : findSplitBefore; 258 259 Widget parent = getParentFromWidget(root); 260 261 child.setProperty!"parent"(parent); 262 263 Widget[] logicalChildren = parent.getPropertyAs!("logicalChildren", Widget[]); 264 auto result1 = findSplitBefore(logicalChildren, only(before)); 265 parent.setProperty!"logicalChildren"(result1[0] ~ child ~ result1[1]); 266 267 if (child.isVisible.value == true) 268 { 269 Widget[] children = parent.getPropertyAs!("children", Widget[]); 270 auto result2 = findSplitBefore(children, only(before)); 271 parent.setProperty!"children"(result2[0] ~ child ~ result2[1]); 272 } 273 } 274 275 void addChildAfter(Widget root, Widget child, Widget after) 276 { 277 import std.algorithm : findSplitAfter; 278 279 Widget parent = getParentFromWidget(root); 280 281 child.setProperty!"parent"(parent); 282 283 Widget[] logicalChildren = parent.getPropertyAs!("logicalChildren", Widget[]); 284 auto result1 = findSplitAfter(logicalChildren, only(after)); 285 parent.setProperty!"logicalChildren"(result1[0] ~ child ~ result1[1]); 286 287 if (child.isVisible.value == true) 288 { 289 Widget[] children = parent.getPropertyAs!("children", Widget[]); 290 auto result2 = findSplitAfter(children, only(after)); 291 parent.setProperty!"children"(result2[0] ~ child ~ result2[1]); 292 } 293 } 294 295 void replaceChildBy(Widget root, Widget child, Widget replacement) 296 { 297 assert(root); 298 assert(child); 299 assert(replacement); 300 301 import std.algorithm : findSplit; 302 303 Widget parent = getParentFromWidget(root); 304 305 replacement.setProperty!"parent"(parent); 306 child.setProperty!"parent"(null); 307 308 Widget[] logicalChildren = parent.getPropertyAs!("logicalChildren", Widget[]); 309 auto result1 = findSplit(logicalChildren, only(child)); 310 parent.setProperty!"logicalChildren"(result1[0] ~ replacement ~ result1[2]); 311 312 if (replacement.isVisible.value == true) 313 { 314 Widget[] children = parent.getPropertyAs!("children", Widget[]); 315 auto result2 = findSplit(children, only(child)); 316 parent.setProperty!"children"(result2[0] ~ replacement ~ result2[2]); 317 } 318 } 319 320 void addChild(Widget root, Widget child) 321 in 322 { 323 assert(root); 324 assert(child); 325 } 326 body 327 { 328 Widget parent = getParentFromWidget(root); 329 330 parent.setProperty!"logicalChildren"(parent["logicalChildren"] ~ child); 331 child.setProperty!"parent"(parent); 332 if (child.isVisible.value == true) 333 parent.setProperty!"children"(parent["children"] ~ child); 334 } 335 336 void detachFromParent(Widget child) 337 { 338 assert(child); 339 340 import std.algorithm : remove; 341 342 Widget parent = child.getPropertyAs!("parent", Widget); 343 344 child["parent"] = null; 345 parent["children"] = parent["children"].get!(Widget[]).remove!((a) => a == child); 346 parent["logicalChildren"] = parent["logicalChildren"].get!(Widget[]).remove!((a) => a == child); 347 } 348 349 void removeChild(Widget root, Widget child) 350 { 351 if (child is null) return; 352 353 import std.algorithm : remove; 354 355 Widget parent = getParentFromWidget(root); 356 child["parent"] = null; 357 parent["children"] = parent["children"].get!(Widget[]).remove!((a) => a == child); 358 parent["logicalChildren"] = parent["logicalChildren"].get!(Widget[]).remove!((a) => a == child); 359 } 360 361 /// Says to global layout manager that this widget needs layout update. 362 void invalidateLayout(Widget widget) 363 { 364 widget.getPropertyAs!("context", GuiContext).eventDispatcher.invalidateWidgetLayout(widget); 365 }