1 ///2 modulefluid.progress_bar;
3 4 importfluid.text;
5 importfluid.node;
6 importfluid.utils;
7 importfluid.backend;
8 importfluid.structs;
9 10 11 @safe:
12 13 14 /// Progress bar node for communicating the program is actively working on something, and needs time to process. The15 /// progress bar draws a styleable `ProgressBarFill` node inside, spanning a fraction of its content, usually starting16 /// from left. Text is drawn over the node to display current progress.17 ///18 /// The progress bar will use text height for its own height, but it needs extra horizontal space to be functional. To19 /// make sure it displays, ensure its horizontal alignment is always set to "fill" — this is the default, if the layout20 /// is not changed.21 ///22 /// ## Styling23 ///24 /// The `progressBar` component is split into two nodes, `ProgressBar` and `ProgressBarFill`. When styling, the25 /// former defines the background, greyed out part of the node, and the latter defines the foreground, the fill26 /// that appears as progress is accumulated. Usually, the background for `ProgressBar` will have low saturation or27 /// be grayed out, and `ProgressBarFill` will be colorful.28 ///29 /// Text currently uses the `ProgressBar` color, but it's possible it will blend the colors of both sides in the30 /// future.31 ///32 /// ---33 /// Theme(34 /// rule!ProgressBar(35 /// backgroundColor = color("#eee"),36 /// textColor = color("#000"),37 /// ),38 /// rule!ProgressBarFill(39 /// backgroundColor = color("#17b117"),40 /// )41 /// )42 /// ---43 ///44 /// ## Text format45 ///46 /// `ProgressBar` does not currently offer the possibility to change text format, but it can be accomplished by47 /// subclassing and overriding the `buildText` method, like so:48 ///49 /// ---50 /// class MyProgressBar : ProgressBar {51 ///52 /// override string buildText() const {53 ///54 /// return format!"%s/%s"(value, maxValue);55 ///56 /// }57 ///58 /// }59 /// ---60 ///61 /// Importantly, should `buildText` return an empty string, the progress bar will disappear, since its size depends on62 /// the text itself. If text is not desired, one can set `textColor` to a transparent value like `color("#0000")`.63 /// Alternatively `resizeImpl` can also be overrided to change the sizing behavior.64 aliasprogressBar = simpleConstructor!ProgressBar;
65 66 /// ditto67 classProgressBar : Node {
68 69 public {
70 71 /// `value`, along with `maxValue` indicate the current progress, defined as the fraction of `value` over72 /// `maxValue`. If 0, the progress bar is empty. If equal to `maxValue`, the progress bar is full.73 intvalue;
74 75 /// ditto.76 intmaxValue;
77 78 /// Text used by the node.79 Texttext;
80 81 /// Node used as the filling for this progress bar.82 ProgressBarFillfill;
83 84 }
85 86 /// Set the `value` and `maxValue` of the progressBar.87 ///88 /// If initialized with no arguments, the progress bar starts empty, with `maxValue` set to 100.89 ///90 /// Params:91 /// value = Current value. Defaults to 0, making the progress bar empty.92 /// maxValue = Maximum value for the progress bar.93 this(intvalue, intmaxValue) {
94 95 this.layout = .layout!("fill", "start");
96 this.value = value;
97 this.maxValue = maxValue;
98 this.fill = newProgressBarFill(this);
99 this.text = Text(this, "");
100 101 }
102 103 /// ditto104 this(intmaxValue = 100) {
105 106 this(0, maxValue);
107 108 }
109 110 overridevoidresizeImpl(Vector2space) {
111 112 text = buildText();
113 text.resize();
114 fill.resize(tree, theme, space);
115 minSize = text.size;
116 117 }
118 119 overridevoiddrawImpl(RectanglepaddingBox, RectanglecontentBox) {
120 121 autostyle = pickStyle();
122 style.drawBackground(io, paddingBox);
123 124 // Draw the filling125 fill.draw(contentBox);
126 127 // Draw the text128 consttextPosition = center(contentBox) - text.size / 2;
129 text.draw(style, textPosition);
130 131 }
132 133 /// Get text that displays on top of the progress bar.134 ///135 /// This function can be overrided to adjust the text and its formatting, or to remove the text completely. Keep in136 /// mind that since `ProgressBar` uses the text as reference for its own size, if the text is removed, the progress137 /// bar will disappear — `minSize` has to be adjusted accordingly.138 stringbuildText() const {
139 140 importstd.format : format;
141 142 constintpercentage = 100 * value / maxValue;
143 144 returnformat!"%s%%"(percentage);
145 146 }
147 148 }
149 150 ///151 unittest {
152 153 conststeps = 24;
154 155 // Create a progress bar.156 autobar = progressBar(steps);
157 158 // Keep the user updated on the progress.159 foreach (i; 0 .. steps) {
160 161 bar.value = i;
162 bar.updateSize();
163 164 }
165 166 }
167 168 unittest {
169 170 importfluid.theme;
171 importfluid.default_theme;
172 173 conststeps = 24;
174 175 autoio = newHeadlessBackend;
176 autotheme = nullTheme.derive(
177 rule!ProgressBar(
178 backgroundColor = color("#eee"),
179 textColor = color("#000"),
180 ),
181 rule!ProgressBarFill(
182 backgroundColor = color("#17b117"),
183 )
184 );
185 autobar = progressBar(theme, steps);
186 187 bar.io = io;
188 bar.draw();
189 190 assert(bar.text == "0%");
191 io.assertRectangle(Rectangle(0, 0, 800, 27), color("#eee"));
192 io.assertRectangle(Rectangle(0, 0, 0, 27), color("#17b117"));
193 io.assertTexture(bar.text.texture.chunks[0].texture, Vector2(387, 0), color("#fff"));
194 195 io.nextFrame;
196 bar.value = 2;
197 bar.updateSize();
198 bar.draw();
199 200 assert(bar.text == "8%");
201 io.assertRectangle(Rectangle(0, 0, 800, 27), color("#eee"));
202 io.assertRectangle(Rectangle(0, 0, 66.66, 27), color("#17b117"));
203 io.assertTexture(bar.text.texture.chunks[0].texture, Vector2(387.5, 0), color("#fff"));
204 205 io.nextFrame;
206 bar.value = steps;
207 bar.updateSize();
208 bar.draw();
209 210 assert(bar.text == "100%");
211 io.assertRectangle(Rectangle(0, 0, 800, 27), color("#eee"));
212 io.assertRectangle(Rectangle(0, 0, 800, 27), color("#17b117"));
213 io.assertTexture(bar.text.texture.chunks[0].texture, Vector2(377, 0), color("#fff"));
214 215 }
216 217 unittest {
218 219 importfluid.style;
220 importfluid.theme;
221 222 autoio = newHeadlessBackend;
223 autotheme = nullTheme.derive(
224 rule!ProgressBar(
225 backgroundColor = color("#eee"),
226 ),
227 rule!ProgressBarFill(
228 backgroundColor = color("#17b117"),
229 )
230 );
231 autobar = newclassProgressBar {
232 233 overridevoidresizeImpl(Vector2space) {
234 235 super.resizeImpl(space);
236 minSize = Vector2(0, 4);
237 238 }
239 240 overridestringbuildText() const {
241 242 return"";
243 244 }
245 246 };
247 248 bar.io = io;
249 bar.theme = theme;
250 bar.maxValue = 20;
251 bar.draw();
252 253 assert(bar.text == "");
254 io.assertRectangle(Rectangle(0, 0, 800, 4), color("#eee"));
255 io.assertRectangle(Rectangle(0, 0, 0, 4), color("#17b117"));
256 257 io.nextFrame;
258 bar.value = 2;
259 bar.updateSize();
260 bar.draw();
261 262 assert(bar.text == "");
263 io.assertRectangle(Rectangle(0, 0, 800, 4), color("#eee"));
264 io.assertRectangle(Rectangle(0, 0, 80, 4), color("#17b117"));
265 266 }
267 268 /// Content for the progress bar. Used for styling. See `ProgressBar` for usage instructions.269 classProgressBarFill : Node {
270 271 public {
272 273 /// Progress bar the node belongs to.274 ProgressBarbar;
275 276 }
277 278 this(ProgressBarbar) {
279 280 this.layout = .layout!"fill";
281 this.bar = bar;
282 283 }
284 285 overridevoidresizeImpl(Vector2space) {
286 287 minSize = Vector2(0, 0);
288 289 }
290 291 overridevoiddrawImpl(RectanglepaddingBox, RectanglecontentBox) {
292 293 // Use a fraction of the padding box corresponding to the fill value294 paddingBox.width *= cast(float) bar.value / bar.maxValue;
295 296 autostyle = pickStyle();
297 style.drawBackground(io, paddingBox);
298 299 }
300 301 }