1 /// Size locking allows placing restrictions on the size of a node. Using the `SizeLock` node
2 /// template, one can set limits on the maximum size of a node.
3 ///
4 /// Normally, nodes default to using the least space they can: the bare minimum they need
5 /// to display correctly. If their `Node.layout.align` property is set to `fill`, they will
6 /// instead attempt to use all of the space they're given. Using `SizeLock` allows
7 /// for a compromise by placing a limit on how much space a node can use.
8 ///
9 /// Note:
10 ///     Using `layout!"fill"` with `SizeLock` will negate the lock's effect. Use a different
11 ///     alignment like `"start"`, `"center"` or `"end"`.
12 module fluid.size_lock;
13 
14 /// Size limit can be used to center content on a wide screen. By using `sizeLock!node`,
15 /// `.layout!"center"` and `.sizeLimitX`, we can create space around the node for comfortable
16 /// reading.
17 @("SizeLock starter example compiles")
18 unittest {
19 
20     import fluid;
21 
22     sizeLock!vframe(
23         .sizeLimitX(400),       // Maximum width of 800 px
24         .layout!(1, "center"),  // Use excess space to center the node
25         label(
26             "By using sizeLock and setting the right limit, a node can be "
27             ~ "forced to use a specific amount of space. This can make your "
28             ~ "app easier to use on a wide screen, without affecting smaller "
29             ~ "windows or displays."
30         ),
31     );
32 
33 }
34 
35 import std.algorithm;
36 
37 import fluid.node;
38 import fluid.utils;
39 import fluid.style;
40 import fluid.structs;
41 import fluid.backend;
42 
43 @safe:
44 
45 /// The `sizeLimit` node property sets the maximum amount of space a `SizeLock` node can use.
46 /// `sizeLimit` can only be used with `SizeLock`.
47 ///
48 /// Params:
49 ///     x = Maximum width of the node in pixels.
50 ///     y = Maximum height of the node in pixels.
51 /// Returns:
52 ///     A configured node parameter struct, which can be passed into the `sizeLock` node builder.
53 ///     This will be a `SizeBounds` struct if the input parameters are `float` (preferred),
54 ///     or `SizeLimit` if they are `size_t` like `uint` or `ulong`.
55 SizeLimit sizeLimit(size_t x, size_t y) {
56     return SizeLimit(x, y);
57 }
58 
59 /// ditto
60 SizeLimit sizeLimitX(size_t x) {
61     return SizeLimit(x, 0);
62 }
63 
64 /// ditto
65 SizeLimit sizeLimitY(size_t y) {
66     return SizeLimit(0, y);
67 }
68 
69 /// ditto
70 FloatSizeLimit sizeLimit(float x, float y) {
71     return FloatSizeLimit(
72         Vector2(x, y)
73     );
74 }
75 
76 /// ditto
77 FloatSizeLimit sizeLimitX(float x) {
78     return FloatSizeLimit(
79         Vector2(x, float.infinity)
80     );
81 }
82 
83 /// ditto
84 FloatSizeLimit sizeLimitY(float y) {
85     return FloatSizeLimit(
86         Vector2(float.infinity, y)
87     );
88 }
89 
90 struct SizeLimit {
91 
92     size_t x;
93     size_t y;
94 
95     void apply(T)(SizeLock!T node) {
96         node.limit = this;
97     }
98 
99 }
100 
101 /// This node property defines the maximum size for a `SizeLock` node. Nodes can be given a limit
102 /// by setting either `width` or `height`.
103 ///
104 /// The `init` value defaults to no restrictions.
105 struct FloatSizeLimit {
106 
107     import std.math : isFinite;
108 
109     /// The imposed limit as a vector. The `x` field is the maximum width,
110     /// and `y` is the maximum height. They both default to `infinity`, effectively not
111     /// setting any limit.
112     auto limit = Vector2(float.infinity, float.infinity);
113 
114     /// Returns:
115     ///     The maximum width imposed on the node.
116     ref inout(float) width() inout return {
117         return limit.x;
118     }
119 
120     /// Returns:
121     ///     True if there is a limit applied to node width.
122     bool isWidthLimited() const {
123         return isFinite(width);
124     }
125 
126     /// Returns:
127     ///     The maximum height imposed on the node.
128     ref inout(float) height() inout return {
129         return limit.y;
130     }
131 
132     /// Returns:
133     ///     True, if there is a limit applied to node height.
134     bool isHeightLimited() const {
135         return isFinite(height);
136     }
137 
138     void apply(T)(SizeLock!T node) {
139         node.sizeLimit = this;
140     }
141 
142 }
143 
144 /// A node builder that constructs a `SizeLock` node. `sizeLock` can be used with other node
145 /// builders, for example `sizeLock!vframe()` will use a vertical frame as its base,
146 /// while `sizeLock!hframe()` will use a horizontal frame.
147 alias sizeLock(alias T) = simpleConstructor!(SizeLock, T);
148 
149 /// `SizeLock` "locks" the size of a node, limiting the amount of space it will use from the space
150 /// it is given.
151 ///
152 /// Size locks are extremely useful for responsible applications, as they can make sure the
153 /// content doesn't span too much space on large screens. For example, a width limit can be
154 /// set with `sizeLimitX`, preventing nodes from spanning the entire width of the screen.
155 class SizeLock(T : Node) : T {
156 
157     /// The maximum size of this node.
158     /// If a value on either axis is `0`, limit will not be applied on the axis.
159     ///
160     /// `limit` has been superseded by `sizeLimit`, which uses floats instead of integers, like
161     /// the rest of Fluid. For now, it takes priority over `sizeLimit` if set.
162     SizeLimit limit;
163 
164     /// The maximum size of the node.
165     FloatSizeLimit sizeLimit;
166 
167     this(T...)(T args) {
168         super(args);
169     }
170 
171     override void resizeImpl(Vector2 space) {
172 
173         // Virtually limit the available space
174         if (limit.x != 0) space.x = min(space.x, limit.x);
175         else space.x = min(space.x, sizeLimit.width);
176 
177         if (limit.y != 0) space.y = min(space.y, limit.y);
178         else space.y = min(space.y, sizeLimit.height);
179 
180         // Resize the child
181         super.resizeImpl(space);
182 
183         // Apply the limit to the resulting value; fill in remaining space if available
184         if (limit.x != 0) minSize.x = max(space.x, min(limit.x, minSize.x));
185         else if (sizeLimit.isWidthLimited) {
186             minSize.x = max(space.x, min(sizeLimit.width, minSize.x));
187         }
188         if (limit.y != 0) minSize.y = max(space.y, min(limit.y, minSize.y));
189         else if (sizeLimit.isHeightLimited) {
190             minSize.y = max(space.y, min(sizeLimit.height, minSize.y));
191         }
192 
193     }
194 
195 }
196 
197 ///
198 unittest {
199 
200     import fluid;
201 
202     // The frame will appear horizontally-centered in the parent node,
203     // and will fill it vertically
204     sizeLock!vframe(
205         .layout!(1, "center", "fill"),
206         .sizeLimitX(400),
207         label("Hello, World!")
208     );
209 
210 }