1 /// Implementation of `OverlayIO` using its own space to lay out its children.
2 module fluid.overlay_chain;
3 
4 import fluid.node;
5 import fluid.utils;
6 import fluid.types;
7 import fluid.children;
8 import fluid.node_chain;
9 
10 import fluid.io.overlay;
11 
12 @safe:
13 
14 /// The usual node builder for `OverlayChain`.
15 alias overlayChain = nodeBuilder!OverlayChain;
16 
17 /// This node implements the `OverlayIO` interface by using its own space to lay out its children.
18 ///
19 /// As this node is based on `NodeChain`, it accepts a single regular node to draw inside. It will always be drawn
20 /// first, before the overlay nodes. The usual, "regular" content can thus be placed as a regular child, and overlays
21 /// can then be spawned and be drawn above.
22 ///
23 /// The `OverlayChain` can be considered a more modern alternative to `MapFrame`, however it is not guaranteed
24 /// to be compatible with the old backend. For future code, `OverlayChain` should generally be preferred, but it has
25 /// some drawbacks:
26 ///
27 /// * Overlay nodes drawn on `OverlayChain` must implement `Overlayable`.
28 /// * `OverlayChain` is not a `Frame`, and cannot be used as one.
29 /// * It is not stylable; background color, border or decorations cannot be used, and margins may not work.
30 /// * Overlays are not considered in the chain's `minSize`.
31 /// * Position is relative to the window, not to the node.
32 class OverlayChain : NodeChain, OverlayIO {
33 
34     mixin controlIO;
35 
36     protected struct Child {
37         Node node;
38         Overlayable overlayable;
39         Overlayable parent;
40     }
41 
42     protected {
43 
44         /// Overlay nodes of the chain.
45         Child[] children;
46 
47     }
48 
49     this(Node next = null) {
50         super(next);
51     }
52 
53     override void beforeResize(Vector2) {
54         startIO();
55     }
56 
57     override void afterResize(Vector2 space) {
58         foreach (child; children) {
59             resizeChild(child.node, space);
60         }
61         stopIO();
62     }
63 
64     override void afterDraw(Rectangle, Rectangle inner) {
65         size_t newIndex;
66         foreach (child; children) {
67 
68             // Filter removed nodes out
69             if (child.node.toRemove) {
70                 child.node.toRemove = false;
71                 continue;
72             }
73             scope (exit) children[newIndex++] = child;
74 
75             const size = child.node.minSize;
76             const nodeAlign = child.node.layout.nodeAlign;
77 
78             // Calculate the node's position based on the anchor
79             const anchor = child.overlayable.getAnchor(inner);
80             const layout = Vector2(
81                 alignLayout!'x'(nodeAlign[0], inner, anchor, size),
82                 alignLayout!'y'(nodeAlign[1], inner, anchor, size),
83             );
84             const anchorPoint = anchor.end
85                 - Vector2(layout.x * anchor.size.x, layout.y * anchor.size.y)
86                 - Vector2(layout.x * size.x,        layout.y * size.y);
87 
88             drawChild(child.node, Rectangle(
89                 anchorPoint.tupleof,
90                 size.tupleof,
91             ));
92 
93         }
94         children.length = newIndex;
95     }
96 
97     private static alignLayout(char axis)(NodeAlign alignment, Rectangle inner, Rectangle anchor, Vector2 size) {
98 
99         import std.algorithm : predSwitch;
100 
101         if (alignment == NodeAlign.fill) {
102 
103             // +---- inner ---+
104             // | . .|  <- end |
105             // |. . |   space |
106             // +----+====+    |
107             // |    | . .|    |
108             // |    |. . | <- anchor
109             // |    +====+----+
110             // | start   | . .|
111             // | space-> |. . |
112             // +---------+----+
113 
114             const startSpace = inner.end - anchor.end;
115             const endSpace   = anchor.start - inner.start;
116 
117             static if (axis == 'x') {
118                 if (size.x <= startSpace.x) return 0;
119                 if (size.x <= endSpace.x)   return 1;
120                 return 0.5;
121             }
122 
123             static if (axis == 'y') {
124                 if (size.y <= startSpace.y) return 0;
125                 if (size.y <= endSpace.y)   return 1;
126                 return 0.5;
127             }
128 
129         }
130 
131         else return alignment.predSwitch(
132             NodeAlign.start,  0,
133             NodeAlign.center, 0.5,
134             NodeAlign.end,    1,
135         );
136 
137     }
138 
139     override void addOverlay(Overlayable overlayable, OverlayType[] type...) nothrow {
140 
141         auto node = cast(Node) overlayable;
142         assert(node, "Given overlay is not a Node");
143 
144         children ~= Child(node, overlayable);
145         updateSize();
146 
147         // Technically, updateSize could be avoided by resizing nothing but the newly added overlay,
148         // and storing the relevant I/O systems. Right now, I find this approach easier.
149 
150     }
151 
152     override void addChildOverlay(Overlayable parent, Overlayable overlayable, OverlayType[] type...) nothrow {
153 
154         auto node = cast(Node) overlayable;
155         assert(node, "Given overlay is not a Node");
156 
157         children ~= Child(node, overlayable, parent);
158         updateSize();
159 
160     }
161 
162 }