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 }