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 }