1 /// Base node for node chains. Used for I/O implementations. 2 module fluid.node_chain; 3 4 import fluid.node; 5 import fluid.types; 6 7 @safe: 8 9 /// Assemble a node chain out of `NodeChain` nodes. 10 /// Params: 11 /// chain = Nodes compromising the chain. 12 /// node = Last node in the chain. 13 /// Returns: 14 /// The first node in the chain, with all the remaining nodes added as children. 15 NodeChain chain(Ts...)(Ts chain, Node node) { 16 17 NodeChain tip = chain[0]; 18 19 foreach (NodeChain link; chain[1..$]) { 20 tip.next = link; 21 tip = link; 22 } 23 24 tip.next = node; 25 return chain[0]; 26 27 } 28 29 /// ditto 30 Node chain(Node node) { 31 return node; 32 } 33 34 /// A node chain makes it possible to create a stack of nodes — one node draws another, which draws another, and so on 35 /// — in a more efficient manner than nodes usually are drawn. 36 /// 37 /// The primary usecase of `NodeChain` is I/O stacks, in which each I/O implementation is chained to another. 38 /// All nodes in the chain but the last must also be subclasses of `NodeChain` for it to work. This means that all 39 /// components must be designed to support this class. For this reason, most I/O implementations provided by Fluid 40 /// extend from `NodeChain`. 41 /// 42 /// `NodeChain` disables some of the basic `Node` functionality. A chain link cannot set margin, border or padding 43 /// in a way that would make it work, and it also cannot control its own `minSize`. Effectively, a chain will only 44 /// use the margin/padding of the inner-most node, that is the first non-chain node. Some other functionality like 45 /// tint, inheriting `isDisabled` etc. may not work too. 46 abstract class NodeChain : Node { 47 48 private { 49 50 Node _next; 51 NodeChain _nextChain; 52 NodeChain _previousChain; 53 54 } 55 56 /// Create this node as the last link in chain. No node will be drawn unless changed with `next`. 57 this() { 58 59 } 60 61 /// Create this node and set the next node in chain. 62 /// Params: 63 /// next = Node to draw inside this node. 64 this(Node next) { 65 66 this.next = next; 67 68 } 69 70 /// Set the next node in chain. 71 /// Params: 72 /// value = Node to set. 73 /// Returns: 74 /// Assigned node. 75 Node next(Node value) { 76 77 if (auto chain = cast(NodeChain) value) { 78 _nextChain = chain; 79 } 80 81 return _next = value; 82 83 } 84 85 /// Returns: The next node in chain, if any. 86 inout(Node) next() inout { 87 return _next; 88 } 89 90 /// Returns: The next node in chain, but only if it's a node chain. 91 inout(NodeChain) nextChain() inout { 92 return _nextChain; 93 } 94 95 protected void beforeResize(Vector2 space) { } 96 protected void afterResize(Vector2 space) { } 97 98 protected void beforeDraw(Rectangle outer, Rectangle inner) { } 99 protected void afterDraw(Rectangle outer, Rectangle inner) { } 100 101 protected final override void resizeImpl(Vector2 space) { 102 103 // Call beforeResize on each part 104 NodeChain chain = this; 105 while (true) { 106 107 // Run tree actions 108 foreach (action; tree.filterActions) { 109 action.beforeResizeImpl(chain, space); 110 } 111 112 // Prepare the node and follow up with beforeResize 113 prepareChild(chain); 114 chain.beforeResize(space); 115 116 // Update the chain 117 if (chain.nextChain) { 118 chain.nextChain._previousChain = chain; 119 chain = chain.nextChain; 120 } 121 else break; 122 123 } 124 125 // Resize the innermost child 126 if (chain.next) { 127 resizeChild(chain.next, space); 128 minSize = chain.next.minSize; 129 } 130 131 // Call afterResize on each part 132 while (chain) { 133 chain.afterResize(space); 134 foreach (action; tree.filterActions) { 135 action.afterResizeImpl(chain, space); 136 } 137 chain = chain._previousChain; 138 } 139 140 } 141 142 protected final override void drawImpl(Rectangle outer, Rectangle inner) { 143 144 // Call beforeDraw on each part 145 NodeChain chain = this; 146 while (true) { 147 148 // Run tree actions 149 // tree actions were already called by `drawChild` for `this` 150 if (chain !is this) { 151 foreach (action; tree.filterActions) { 152 action.beforeDrawImpl(chain, outer, outer, inner); 153 } 154 } 155 156 chain.beforeDraw(outer, inner); 157 158 // Update the chain 159 if (chain.nextChain) { 160 chain.nextChain._previousChain = chain; 161 chain = chain.nextChain; 162 } 163 else break; 164 165 } 166 167 // Draw the innermost child 168 if (chain.next) { 169 drawChild(chain.next, inner); 170 } 171 172 // Call afterDraw on each part 173 while (chain) { 174 chain.afterDraw(outer, inner); 175 if (chain !is this) { 176 foreach (action; tree.filterActions) { 177 action.afterDrawImpl(chain, outer, outer, inner); 178 } 179 } 180 chain = chain._previousChain; 181 } 182 183 } 184 185 }