1 module fluid.slot; 2 3 import std.traits; 4 5 import fluid.node; 6 import fluid.utils; 7 import fluid.style; 8 import fluid.backend; 9 import fluid.structs; 10 11 import fluid.io.canvas; 12 13 @safe: 14 15 16 /// A "node slot" node, which displays the node given to it. Allows safely swapping nodes in the layout by reference, 17 /// even during drawing. Useful for creating tabs and menus. 18 /// 19 /// Because NodeSlot does not inherit from T, it uses the single-parameter overload of simpleConstructor. 20 alias nodeSlot(alias T) = simpleConstructor!(NodeSlot!T); 21 22 /// ditto 23 class NodeSlot(T : Node) : Node { 24 25 CanvasIO canvasIO; 26 27 public { 28 29 /// Node placed in the slot. 30 T value; 31 32 } 33 34 /// Create a new node slot and optionally place a node within. 35 this(T node = null) { 36 37 this.value = node; 38 39 } 40 41 /// Change the node in the slot. 42 /// 43 /// This function is a little bit more convenient than setting the value directly, as it'll mark itself as 44 /// needing an update. Additionally, it returns the slot, not the given node, so you can assign a value to a 45 /// constructed slot while adding it to the scene tree. 46 typeof(this) opAssign(T value) { 47 48 updateSize(); 49 50 this.value = value; 51 52 return this; 53 54 } 55 56 /// Remove the assigned node from the slot. 57 void clear() { 58 59 value = null; 60 updateSize(); 61 62 } 63 64 protected override void resizeImpl(Vector2 space) { 65 66 use(canvasIO); 67 68 minSize = Vector2(); 69 70 // Don't resize if there's no child node 71 if (!value) return; 72 73 // Remove the value if requested 74 if (value.toRemove) { 75 value = null; 76 return; 77 } 78 79 resizeChild(value, space); 80 minSize = value.minSize; 81 82 } 83 84 protected override void drawImpl(Rectangle outer, Rectangle inner) { 85 86 pickStyle().drawBackground(io, canvasIO, outer); 87 88 if (!value) return; 89 90 drawChild(value, inner); 91 92 } 93 94 protected override bool hoveredImpl(Rectangle rect, Vector2 position) { 95 96 if (!value) return false; 97 98 // If the child has ignoreMouse set, we should ignore it as well 99 if (value.ignoreMouse) return false; 100 101 // hoveredImpl may be private... uhhh 102 return (cast(Node) value).hoveredImpl(rect, position); 103 104 } 105 106 /// Swap contents of the two slots. 107 void swapSlots(Slot : Node)(Slot other) { 108 109 static if (is(Slot : NodeSlot!U, U)) { 110 111 import std.format; 112 import std.algorithm; 113 114 updateSize(); 115 116 static if (is(T == U)) { 117 118 swap(value, other.value); 119 120 } 121 122 else static if (is(T : U) || is(U : T)) { 123 124 auto theirs = cast(T) other.value; 125 auto ours = cast(U) value; 126 127 const canAcceptTheirs = theirs || other.value is null; 128 const canAcceptOurs = ours || value is null; 129 130 assert(canAcceptTheirs, format!"Can't swap: This slot doesn't accept %s"(typeid(theirs))); 131 assert(canAcceptOurs, format!"Can't swap: Other slot doesn't accept %s"(typeid(ours))); 132 133 // Perform the swap 134 value = theirs; 135 other.value = ours; 136 137 } 138 139 else static assert(false, "Slots given to swapSlots are not compatible"); 140 141 } 142 143 else static assert(false, "The other item is not a node"); 144 145 } 146 147 } 148 149 /// 150 unittest { 151 152 import fluid; 153 154 NodeSlot!Label slot1, slot2; 155 156 // Slots can be empty, with no node inside 157 auto root = vspace( 158 label("Hello, "), 159 slot1 = nodeSlot!Label(.layout!"fill"), 160 label(" and "), 161 slot2 = nodeSlot!Label(.layout!"fill"), 162 ); 163 164 // Slots can be assigned other nodes 165 slot1 = label("John"); 166 167 slot2 = button("Jane", delegate { 168 169 // Slot contents can be swapped with a single call 170 slot1.swapSlots(slot2); 171 172 }); 173 174 // Slots can be reassigned at any time 175 slot1 = label("Joe"); 176 177 // Their values can also be removed 178 slot1.clear(); 179 180 }