OpenTTD Source 20250612-master-gb012d9e3dc
graph_gui.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "graph_gui.h"
12#include "window_gui.h"
13#include "company_base.h"
14#include "company_gui.h"
15#include "economy_func.h"
16#include "cargotype.h"
17#include "strings_func.h"
18#include "window_func.h"
19#include "gfx_func.h"
21#include "currency.h"
22#include "timer/timer.h"
23#include "timer/timer_window.h"
26#include "zoom_func.h"
27#include "industry.h"
28
30
31#include "table/strings.h"
32#include "table/sprites.h"
33
34#include "safeguards.h"
35
36/* Bitmasks of company and cargo indices that shouldn't be drawn. */
37static CompanyMask _legend_excluded_companies;
38
39/* Apparently these don't play well with enums. */
40static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
41static const uint INVALID_DATAPOINT_POS = UINT_MAX; // Used to determine if the previous point was drawn.
42
43constexpr double INT64_MAX_IN_DOUBLE = static_cast<double>(INT64_MAX - 512);
44static_assert(static_cast<int64_t>(INT64_MAX_IN_DOUBLE) < INT64_MAX);
45
46/****************/
47/* GRAPH LEGEND */
48/****************/
49
52 {
53 this->InitNested(window_number);
54
55 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
56 if (!_legend_excluded_companies.Test(c)) this->LowerWidget(WID_GL_FIRST_COMPANY + c);
57
58 this->OnInvalidateData(c.base());
59 }
60 }
61
62 void DrawWidget(const Rect &r, WidgetID widget) const override
63 {
64 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
65
67
68 if (!Company::IsValidID(cid)) return;
69
70 bool rtl = _current_text_dir == TD_RTL;
71
72 const Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
73 Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
74 DrawCompanyIcon(cid, rtl ? ir.right - d.width : ir.left, CentreBounds(ir.top, ir.bottom, d.height));
75
76 const Rect tr = ir.Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl);
77 DrawString(tr.left, tr.right, CentreBounds(tr.top, tr.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_COMPANY_NAME_COMPANY_NUM, cid, cid), _legend_excluded_companies.Test(cid) ? TC_BLACK : TC_WHITE);
78 }
79
80 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
81 {
82 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
83
84 _legend_excluded_companies.Flip(static_cast<CompanyID>(widget - WID_GL_FIRST_COMPANY));
85 this->ToggleWidgetLoweredState(widget);
86 this->SetDirty();
92 }
93
99 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
100 {
101 if (!gui_scope) return;
102 if (Company::IsValidID(data)) return;
103
104 _legend_excluded_companies.Set(static_cast<CompanyID>(data));
105 this->RaiseWidget(data + WID_GL_FIRST_COMPANY);
106 }
107};
108
113static std::unique_ptr<NWidgetBase> MakeNWidgetCompanyLines()
114{
115 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
116 vert->SetPadding(2, 2, 2, 2);
117 uint sprite_height = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZoomLevel::Normal).height;
118
119 for (WidgetID widnum = WID_GL_FIRST_COMPANY; widnum <= WID_GL_LAST_COMPANY; widnum++) {
120 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
121 panel->SetMinimalSize(246, sprite_height + WidgetDimensions::unscaled.framerect.Vertical());
122 panel->SetMinimalTextLines(1, WidgetDimensions::unscaled.framerect.Vertical(), FS_NORMAL);
123 panel->SetFill(1, 1);
124 panel->SetToolTip(STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
125 vert->Add(std::move(panel));
126 }
127 return vert;
128}
129
130static constexpr NWidgetPart _nested_graph_legend_widgets[] = {
132 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
133 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_KEY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
134 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
135 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
136 EndContainer(),
137 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GL_BACKGROUND),
139 EndContainer(),
140};
141
142static WindowDesc _graph_legend_desc(
143 WDP_AUTO, "graph_legend", 0, 0,
145 {},
146 _nested_graph_legend_widgets
147);
148
149static void ShowGraphLegend()
150{
151 AllocateWindowDescFront<GraphLegendWindow>(_graph_legend_desc, 0);
152}
153
159
160/******************/
161/* BASE OF GRAPHS */
162/*****************/
163
165protected:
166 static const int GRAPH_MAX_DATASETS = 64;
167 static const int GRAPH_BASE_COLOUR = GREY_SCALE(2);
168 static const int GRAPH_GRID_COLOUR = GREY_SCALE(3);
169 static const int GRAPH_AXIS_LINE_COLOUR = GREY_SCALE(1);
170 static const int GRAPH_ZERO_LINE_COLOUR = GREY_SCALE(8);
171 static const int GRAPH_YEAR_LINE_COLOUR = GREY_SCALE(5);
172 static const int GRAPH_NUM_MONTHS = 24;
173 static const int GRAPH_PAYMENT_RATE_STEPS = 20;
174 static const int PAYMENT_GRAPH_X_STEP_DAYS = 10;
175 static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20;
176 static const int ECONOMY_QUARTER_MINUTES = 3;
177 static const int ECONOMY_MONTH_MINUTES = 1;
178
179 static const TextColour GRAPH_AXIS_LABEL_COLOUR = TC_BLACK;
180
181 static const int MIN_GRAPH_NUM_LINES_Y = 9;
182 static const int MIN_GRID_PIXEL_SIZE = 20;
183
184 uint64_t excluded_data = 0;
185 uint64_t excluded_range = 0;
186 uint8_t num_on_x_axis = 0;
187 uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
188
189 /* The starting month and year that values are plotted against. */
192 uint8_t month_increment = 3;
193
194 bool draw_dates = true;
195
196 /* These values are used if the graph is being plotted against values
197 * rather than the dates specified by month and year. */
198 bool x_values_reversed = true;
199 int16_t x_values_increment = ECONOMY_QUARTER_MINUTES;
200
201 StringID format_str_y_axis{};
202
203 struct DataSet {
204 std::array<OverflowSafeInt64, GRAPH_NUM_MONTHS> values;
205 uint8_t colour;
206 uint8_t exclude_bit;
207 uint8_t range_bit;
208 uint8_t dash;
209 };
210 std::vector<DataSet> data{};
211
212 std::span<const StringID> ranges = {};
213
214 uint8_t highlight_data = UINT8_MAX;
215 uint8_t highlight_range = UINT8_MAX;
216 bool highlight_state = false;
217
223 std::span<const OverflowSafeInt64> GetDataSetRange(const DataSet &dataset) const
224 {
225 return {std::begin(dataset.values), std::begin(dataset.values) + this->num_on_x_axis};
226 }
227
234 ValuesInterval GetValuesInterval(int num_hori_lines) const
235 {
236 assert(num_hori_lines > 0);
237
238 ValuesInterval current_interval;
239 current_interval.highest = INT64_MIN;
240 current_interval.lowest = INT64_MAX;
241
242 for (const DataSet &dataset : this->data) {
243 if (HasBit(this->excluded_data, dataset.exclude_bit)) continue;
244 if (HasBit(this->excluded_range, dataset.range_bit)) continue;
245
246 for (const OverflowSafeInt64 &datapoint : this->GetDataSetRange(dataset)) {
247 if (datapoint != INVALID_DATAPOINT) {
248 current_interval.highest = std::max(current_interval.highest, datapoint);
249 current_interval.lowest = std::min(current_interval.lowest, datapoint);
250 }
251 }
252 }
253
254 /* Always include zero in the shown range. */
255 double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest);
256 double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest;
257
258 /* Prevent showing values too close to the graph limits. */
259 abs_higher = (11.0 * abs_higher) / 10.0;
260 abs_lower = (11.0 * abs_lower) / 10.0;
261
262 int num_pos_grids;
263 OverflowSafeInt64 grid_size;
264
265 if (abs_lower != 0 || abs_higher != 0) {
266 /* The number of grids to reserve for the positive part is: */
267 num_pos_grids = (int)floor(0.5 + num_hori_lines * abs_higher / (abs_higher + abs_lower));
268
269 /* If there are any positive or negative values, force that they have at least one grid. */
270 if (num_pos_grids == 0 && abs_higher != 0) num_pos_grids++;
271 if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--;
272
273 /* Get the required grid size for each side and use the maximum one. */
274
275 OverflowSafeInt64 grid_size_higher = 0;
276 if (abs_higher > 0) {
277 grid_size_higher = abs_higher > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_higher);
278 grid_size_higher = (grid_size_higher + num_pos_grids - 1) / num_pos_grids;
279 }
280
281 OverflowSafeInt64 grid_size_lower = 0;
282 if (abs_lower > 0) {
283 grid_size_lower = abs_lower > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_lower);
284 grid_size_lower = (grid_size_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids);
285 }
286
287 grid_size = std::max(grid_size_higher, grid_size_lower);
288 } else {
289 /* If both values are zero, show an empty graph. */
290 num_pos_grids = num_hori_lines / 2;
291 grid_size = 1;
292 }
293
294 current_interval.highest = num_pos_grids * grid_size;
295 current_interval.lowest = -(num_hori_lines - num_pos_grids) * grid_size;
296 return current_interval;
297 }
298
304 uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
305 {
306 /* draw text strings on the y axis */
307 int64_t y_label = current_interval.highest;
308 int64_t y_label_separation = (current_interval.highest - current_interval.lowest) / num_hori_lines;
309
310 uint max_width = 0;
311
312 for (int i = 0; i < (num_hori_lines + 1); i++) {
313 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label));
314 if (d.width > max_width) max_width = d.width;
315
316 y_label -= y_label_separation;
317 }
318
319 return max_width;
320 }
321
326 void DrawGraph(Rect r) const
327 {
328 uint x, y;
329 ValuesInterval interval;
330 int x_axis_offset;
331
332 /* the colours and cost array of GraphDrawer must accommodate
333 * both values for cargo and companies. So if any are higher, quit */
334 static_assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
335 assert(this->num_vert_lines > 0);
336
337 bool rtl = _current_text_dir == TD_RTL;
338
339 /* Rect r will be adjusted to contain just the graph, with labels being
340 * placed outside the area. */
341 r.top += ScaleGUITrad(5) + GetCharacterHeight(FS_SMALL) / 2;
342 r.bottom -= (this->draw_dates ? 2 : 1) * GetCharacterHeight(FS_SMALL) + ScaleGUITrad(4);
343 r.left += ScaleGUITrad(rtl ? 5 : 9);
344 r.right -= ScaleGUITrad(rtl ? 9 : 5);
345
346 /* Initial number of horizontal lines. */
347 int num_hori_lines = 160 / ScaleGUITrad(MIN_GRID_PIXEL_SIZE);
348 /* For the rest of the height, the number of horizontal lines will increase more slowly. */
349 int resize = (r.bottom - r.top - 160) / (2 * ScaleGUITrad(MIN_GRID_PIXEL_SIZE));
350 if (resize > 0) num_hori_lines += resize;
351
352 interval = GetValuesInterval(num_hori_lines);
353
354 int label_width = GetYLabelWidth(interval, num_hori_lines);
355
356 if (rtl) {
357 r.right -= label_width;
358 } else {
359 r.left += label_width;
360 }
361
362 int x_sep = (r.right - r.left) / this->num_vert_lines;
363 int y_sep = (r.bottom - r.top) / num_hori_lines;
364
365 /* Redetermine right and bottom edge of graph to fit with the integer
366 * separation values. */
367 if (rtl) {
368 r.left = r.right - x_sep * this->num_vert_lines;
369 } else {
370 r.right = r.left + x_sep * this->num_vert_lines;
371 }
372 r.bottom = r.top + y_sep * num_hori_lines;
373
374 OverflowSafeInt64 interval_size = interval.highest + abs(interval.lowest);
375 /* Where to draw the X axis. Use floating point to avoid overflowing and results of zero. */
376 x_axis_offset = (int)((r.bottom - r.top) * (double)interval.highest / (double)interval_size);
377
378 /* Draw the background of the graph itself. */
379 GfxFillRect(r.left, r.top, r.right, r.bottom, GRAPH_BASE_COLOUR);
380
381 /* Draw the vertical grid lines. */
382
383 /* Don't draw the first line, as that's where the axis will be. */
384 if (rtl) {
385 x_sep = -x_sep;
386 x = r.right + x_sep;
387 } else {
388 x = r.left + x_sep;
389 }
390
391 int grid_colour = GRAPH_GRID_COLOUR;
392 for (int i = 1; i < this->num_vert_lines + 1; i++) {
393 /* If using wallclock units, we separate periods with a lighter line. */
395 grid_colour = (i % 4 == 0) ? GRAPH_YEAR_LINE_COLOUR : GRAPH_GRID_COLOUR;
396 }
397 GfxFillRect(x, r.top, x, r.bottom, grid_colour);
398 x += x_sep;
399 }
400
401 /* Draw the horizontal grid lines. */
402 y = r.bottom;
403
404 for (int i = 0; i < (num_hori_lines + 1); i++) {
405 if (rtl) {
406 GfxFillRect(r.right + 1, y, r.right + ScaleGUITrad(3), y, GRAPH_AXIS_LINE_COLOUR);
407 } else {
408 GfxFillRect(r.left - ScaleGUITrad(3), y, r.left - 1, y, GRAPH_AXIS_LINE_COLOUR);
409 }
410 GfxFillRect(r.left, y, r.right, y, GRAPH_GRID_COLOUR);
411 y -= y_sep;
412 }
413
414 /* Draw the y axis. */
415 GfxFillRect(r.left, r.top, r.left, r.bottom, GRAPH_AXIS_LINE_COLOUR);
416
417 /* Draw the x axis. */
418 y = x_axis_offset + r.top;
419 GfxFillRect(r.left, y, r.right, y, GRAPH_ZERO_LINE_COLOUR);
420
421 /* Find the largest value that will be drawn. */
422 if (this->num_on_x_axis == 0) return;
423
424 assert(this->num_on_x_axis > 0);
425
426 /* draw text strings on the y axis */
427 int64_t y_label = interval.highest;
428 int64_t y_label_separation = abs(interval.highest - interval.lowest) / num_hori_lines;
429
430 y = r.top - GetCharacterHeight(FS_SMALL) / 2;
431
432 for (int i = 0; i < (num_hori_lines + 1); i++) {
433 if (rtl) {
434 DrawString(r.right + ScaleGUITrad(4), r.right + label_width + ScaleGUITrad(4), y,
435 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
437 } else {
438 DrawString(r.left - label_width - ScaleGUITrad(4), r.left - ScaleGUITrad(4), y,
439 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
441 }
442
443 y_label -= y_label_separation;
444 y += y_sep;
445 }
446
447 x = rtl ? r.right : r.left;
448 y = r.bottom + ScaleGUITrad(2);
449
450 /* if there are not enough datapoints to fill the graph, align to the right */
451 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
452
453 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
454 if (this->draw_dates) {
455 TimerGameEconomy::Month month = this->month;
456 TimerGameEconomy::Year year = this->year;
457 for (int i = 0; i < this->num_on_x_axis; i++) {
458 if (rtl) {
459 DrawStringMultiLineWithClipping(x + x_sep, x, y, this->height,
460 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
462 } else {
463 DrawStringMultiLineWithClipping(x, x + x_sep, y, this->height,
464 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
466 }
467
468 month += this->month_increment;
469 if (month >= 12) {
470 month = 0;
471 year++;
472
473 /* Draw a lighter grid line between years. Top and bottom adjustments ensure we don't draw over top and bottom horizontal grid lines. */
474 GfxFillRect(x + x_sep, r.top + 1, x + x_sep, r.bottom - 1, GRAPH_YEAR_LINE_COLOUR);
475 }
476 x += x_sep;
477 }
478 } else {
479 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates, and all graphs when using wallclock units). */
480 int16_t iterator;
481 uint16_t label;
482 if (this->x_values_reversed) {
483 label = this->x_values_increment * this->num_on_x_axis;
484 iterator = -this->x_values_increment;
485 } else {
486 label = this->x_values_increment;
487 iterator = this->x_values_increment;
488 }
489
490 for (int i = 0; i < this->num_on_x_axis; i++) {
491 if (rtl) {
492 DrawString(x + x_sep + 1, x - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
493 } else {
494 DrawString(x + 1, x + x_sep - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
495 }
496
497 label += iterator;
498 x += x_sep;
499 }
500 }
501
502 /* draw lines and dots */
504 uint pointoffs1 = (linewidth + 1) / 2;
505 uint pointoffs2 = linewidth + 1 - pointoffs1;
506
507 auto draw_dataset = [&](const DataSet &dataset, uint8_t colour) {
508 if (HasBit(this->excluded_data, dataset.exclude_bit)) return;
509 if (HasBit(this->excluded_range, dataset.range_bit)) return;
510
511 /* Centre the dot between the grid lines. */
512 if (rtl) {
513 x = r.right + (x_sep / 2);
514 } else {
515 x = r.left + (x_sep / 2);
516 }
517
518 /* if there are not enough datapoints to fill the graph, align to the right */
519 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
520
521 uint prev_x = INVALID_DATAPOINT_POS;
522 uint prev_y = INVALID_DATAPOINT_POS;
523
524 const uint dash = ScaleGUITrad(dataset.dash);
525 for (OverflowSafeInt64 datapoint : this->GetDataSetRange(dataset)) {
526 if (datapoint != INVALID_DATAPOINT) {
527 /*
528 * Check whether we need to reduce the 'accuracy' of the
529 * datapoint value and the highest value to split overflows.
530 * And when 'drawing' 'one million' or 'one million and one'
531 * there is no significant difference, so the least
532 * significant bits can just be removed.
533 *
534 * If there are more bits needed than would fit in a 32 bits
535 * integer, so at about 31 bits because of the sign bit, the
536 * least significant bits are removed.
537 */
538 int mult_range = FindLastBit<uint32_t>(x_axis_offset) + FindLastBit<uint64_t>(abs(datapoint));
539 int reduce_range = std::max(mult_range - 31, 0);
540
541 /* Handle negative values differently (don't shift sign) */
542 if (datapoint < 0) {
543 datapoint = -(abs(datapoint) >> reduce_range);
544 } else {
545 datapoint >>= reduce_range;
546 }
547 y = r.top + x_axis_offset - ((r.bottom - r.top) * datapoint) / (interval_size >> reduce_range);
548
549 /* Draw the point. */
550 GfxFillRect(x - pointoffs1, y - pointoffs1, x + pointoffs2, y + pointoffs2, colour);
551
552 /* Draw the line connected to the previous point. */
553 if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, colour, linewidth, dash);
554
555 prev_x = x;
556 prev_y = y;
557 } else {
558 prev_x = INVALID_DATAPOINT_POS;
559 prev_y = INVALID_DATAPOINT_POS;
560 }
561
562 x += x_sep;
563 }
564 };
565
566 /* Draw unhighlighted datasets. */
567 for (const DataSet &dataset : this->data) {
568 if (dataset.exclude_bit != this->highlight_data && dataset.range_bit != this->highlight_range) {
569 draw_dataset(dataset, dataset.colour);
570 }
571 }
572
573 /* If any dataset or range is highlighted, draw separately after the rest so they appear on top of all other
574 * data. Highlighted data is only drawn when highlight_state is set, otherwise it is invisible. */
575 if (this->highlight_state && (this->highlight_data != UINT8_MAX || this->highlight_range != UINT8_MAX)) {
576 for (const DataSet &dataset : this->data) {
577 if (dataset.exclude_bit == this->highlight_data || dataset.range_bit == this->highlight_range) {
578 draw_dataset(dataset, PC_WHITE);
579 }
580 }
581 }
582 }
583
584 BaseGraphWindow(WindowDesc &desc, StringID format_str_y_axis) :
585 Window(desc),
586 format_str_y_axis(format_str_y_axis)
587 {
589 }
590
591 const IntervalTimer<TimerWindow> blink_interval = {TIMER_BLINK_INTERVAL, [this](auto) {
592 /* If nothing is highlighted then no redraw is needed. */
593 if (this->highlight_data == UINT8_MAX && this->highlight_range == UINT8_MAX) return;
594
595 /* Toggle the highlight state and redraw. */
596 this->highlight_state = !this->highlight_state;
597 this->SetDirty();
598 }};
599
600public:
601 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
602 {
603 switch (widget) {
605 for (const StringID &str : this->ranges) {
606 size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
607 }
608
611
612 /* Set fixed height for number of ranges. */
613 size.height *= static_cast<uint>(std::size(this->ranges));
614
615 resize.width = 0;
616 resize.height = 0;
617 this->GetWidget<NWidgetCore>(WID_GRAPH_RANGE_MATRIX)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(this->ranges)));
618 break;
619
620 case WID_GRAPH_GRAPH: {
621 uint x_label_width = 0;
622
623 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
624 if (this->draw_dates) {
625 TimerGameEconomy::Month month = this->month;
626 TimerGameEconomy::Year year = this->year;
627 for (int i = 0; i < this->num_on_x_axis; i++) {
628 x_label_width = std::max(x_label_width, GetStringBoundingBox(GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year)).width);
629
630 month += this->month_increment;
631 if (month >= 12) {
632 month = 0;
633 year++;
634 }
635 }
636 } else {
637 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates). */
638 uint64_t max_value = GetParamMaxValue((this->num_on_x_axis + 1) * this->x_values_increment, 0, FS_SMALL);
639 x_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL_NUMBER, max_value)).width;
640 }
641
642 uint y_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, INT64_MAX)).width;
643
644 size.width = std::max<uint>(size.width, ScaleGUITrad(5) + y_label_width + this->num_vert_lines * (x_label_width + ScaleGUITrad(5)) + ScaleGUITrad(9));
645 size.height = std::max<uint>(size.height, ScaleGUITrad(5) + (1 + MIN_GRAPH_NUM_LINES_Y * 2 + (this->draw_dates ? 3 : 1)) * GetCharacterHeight(FS_SMALL) + ScaleGUITrad(4));
646 size.height = std::max<uint>(size.height, size.width / 3);
647 break;
648 }
649
650 default: break;
651 }
652 }
653
654 void DrawWidget(const Rect &r, WidgetID widget) const override
655 {
656 switch (widget) {
657 case WID_GRAPH_GRAPH:
658 this->DrawGraph(r);
659 break;
660
663 uint index = 0;
664 Rect line = r.WithHeight(line_height);
665 for (const auto &str : this->ranges) {
666 bool lowered = !HasBit(this->excluded_range, index);
667
668 /* Redraw frame if lowered */
669 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
670
671 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
672 DrawString(text, str, TC_BLACK, SA_CENTER, false, FS_SMALL);
673
674 line = line.Translate(0, line_height);
675 ++index;
676 }
677 break;
678 }
679
680 default: break;
681 }
682 }
683
684 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
685 {
686 /* Clicked on legend? */
687 switch (widget) {
689 ShowGraphLegend();
690 break;
691
693 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
694
695 ToggleBit(this->excluded_range, row);
696 this->SetDirty();
697 break;
698 }
699
700 default: break;
701 }
702 }
703
704 void OnMouseOver(Point pt, WidgetID widget) override
705 {
706 /* Test if a range should be highlighted. */
707 uint8_t new_highlight_range = UINT8_MAX;
708 if (widget == WID_GRAPH_RANGE_MATRIX) {
709 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
710 if (!HasBit(this->excluded_range, row)) new_highlight_range = static_cast<uint8_t>(row);
711 }
712
713 /* Test if a dataset should be highlighted. */
714 uint8_t new_highlight_data = UINT8_MAX;
715 if (widget == WID_GRAPH_MATRIX) {
716 auto dataset_index = this->GetDatasetIndex(pt.y);
717 if (dataset_index.has_value() && !HasBit(this->excluded_data, *dataset_index)) new_highlight_data = *dataset_index;
718 }
719
720 if (this->highlight_data == new_highlight_data && this->highlight_range == new_highlight_range) return;
721
722 /* Range or data set highlight has changed, set and redraw. */
723 this->highlight_data = new_highlight_data;
724 this->highlight_range = new_highlight_range;
725 this->highlight_state = true;
726 this->SetDirty();
727 }
728
729 void OnGameTick() override
730 {
731 this->UpdateStatistics(false);
732 }
733
739 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
740 {
741 if (!gui_scope) return;
742 this->UpdateStatistics(true);
743 }
744
745 virtual void UpdateStatistics(bool initialize) = 0;
746
747 virtual std::optional<uint8_t> GetDatasetIndex(int) { return std::nullopt; }
748};
749
751public:
752 BaseCompanyGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
753
754 void InitializeWindow(WindowNumber number)
755 {
756 /* Initialise the dataset */
757 this->UpdateStatistics(true);
758
759 this->CreateNestedTree();
760
761 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
762 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_LAST_72_MINUTES_TIME_LABEL : STR_EMPTY);
763
764 this->FinishInitNested(number);
765 }
766
771 void UpdateStatistics(bool initialize) override
772 {
773 CompanyMask excluded_companies = _legend_excluded_companies;
774
775 /* Exclude the companies which aren't valid */
776 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
777 if (!Company::IsValidID(c)) excluded_companies.Set(c);
778 }
779
780 uint8_t nums = 0;
781 for (const Company *c : Company::Iterate()) {
782 nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent));
783 }
784
785 int mo = (TimerGameEconomy::month / this->month_increment - nums) * this->month_increment;
786 auto yr = TimerGameEconomy::year;
787 while (mo < 0) {
788 yr--;
789 mo += 12;
790 }
791
792 if (!initialize && this->excluded_data == excluded_companies.base() && this->num_on_x_axis == nums &&
793 this->year == yr && this->month == mo) {
794 /* There's no reason to get new stats */
795 return;
796 }
797
798 this->excluded_data = excluded_companies.base();
799 this->num_on_x_axis = nums;
800 this->year = yr;
801 this->month = mo;
802
803 this->data.clear();
804 for (CompanyID k = CompanyID::Begin(); k < MAX_COMPANIES; ++k) {
805 const Company *c = Company::GetIfValid(k);
806 if (c == nullptr) continue;
807
808 DataSet &dataset = this->data.emplace_back();
809 dataset.colour = GetColourGradient(c->colour, SHADE_LIGHTER);
810 dataset.exclude_bit = k.base();
811
812 for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
813 if (j >= c->num_valid_stat_ent) {
814 dataset.values[i] = INVALID_DATAPOINT;
815 } else {
816 /* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
817 * Instead, use the value just under it. Hopefully nobody will notice. */
818 dataset.values[i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
819 }
820 i++;
821 }
822 }
823 }
824
825 virtual OverflowSafeInt64 GetGraphData(const Company *, int) = 0;
826};
827
828
829/********************/
830/* OPERATING PROFIT */
831/********************/
832
835 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
836 {
837 this->num_on_x_axis = GRAPH_NUM_MONTHS;
838 this->num_vert_lines = GRAPH_NUM_MONTHS;
839 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
840
841 this->InitializeWindow(window_number);
842 }
843
844 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
845 {
846 return c->old_economy[j].income + c->old_economy[j].expenses;
847 }
848};
849
850static constexpr NWidgetPart _nested_operating_profit_widgets[] = {
852 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
853 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
854 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
855 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
856 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
857 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
858 EndContainer(),
859 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
860 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 160), SetFill(1, 1), SetResize(1, 1),
862 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
863 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
864 EndContainer(),
865 EndContainer(),
866};
867
868static WindowDesc _operating_profit_desc(
869 WDP_AUTO, "graph_operating_profit", 0, 0,
871 {},
872 _nested_operating_profit_widgets
873);
874
875
876void ShowOperatingProfitGraph()
877{
878 AllocateWindowDescFront<OperatingProfitGraphWindow>(_operating_profit_desc, 0);
879}
880
881
882/****************/
883/* INCOME GRAPH */
884/****************/
885
888 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
889 {
890 this->num_on_x_axis = GRAPH_NUM_MONTHS;
891 this->num_vert_lines = GRAPH_NUM_MONTHS;
892 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
893
894 this->InitializeWindow(window_number);
895 }
896
897 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
898 {
899 return c->old_economy[j].income;
900 }
901};
902
903static constexpr NWidgetPart _nested_income_graph_widgets[] = {
905 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
906 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
907 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
908 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
909 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
910 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
911 EndContainer(),
912 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
913 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
915 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
916 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
917 EndContainer(),
918 EndContainer(),
919};
920
921static WindowDesc _income_graph_desc(
922 WDP_AUTO, "graph_income", 0, 0,
924 {},
925 _nested_income_graph_widgets
926);
927
928void ShowIncomeGraph()
929{
930 AllocateWindowDescFront<IncomeGraphWindow>(_income_graph_desc, 0);
931}
932
933/*******************/
934/* DELIVERED CARGO */
935/*******************/
936
939 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
940 {
941 this->num_on_x_axis = GRAPH_NUM_MONTHS;
942 this->num_vert_lines = GRAPH_NUM_MONTHS;
943 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
944
945 this->InitializeWindow(window_number);
946 }
947
948 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
949 {
950 return c->old_economy[j].delivered_cargo.GetSum<OverflowSafeInt64>();
951 }
952};
953
954static constexpr NWidgetPart _nested_delivered_cargo_graph_widgets[] = {
956 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
957 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
958 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
959 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
960 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
961 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
962 EndContainer(),
963 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
964 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
966 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
967 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
968 EndContainer(),
969 EndContainer(),
970};
971
972static WindowDesc _delivered_cargo_graph_desc(
973 WDP_AUTO, "graph_delivered_cargo", 0, 0,
975 {},
976 _nested_delivered_cargo_graph_widgets
977);
978
979void ShowDeliveredCargoGraph()
980{
981 AllocateWindowDescFront<DeliveredCargoGraphWindow>(_delivered_cargo_graph_desc, 0);
982}
983
984/***********************/
985/* PERFORMANCE HISTORY */
986/***********************/
987
990 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
991 {
992 this->num_on_x_axis = GRAPH_NUM_MONTHS;
993 this->num_vert_lines = GRAPH_NUM_MONTHS;
994 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
995
996 this->InitializeWindow(window_number);
997 }
998
999 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1000 {
1001 return c->old_economy[j].performance_history;
1002 }
1003
1004 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1005 {
1006 if (widget == WID_PHG_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
1007 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1008 }
1009};
1010
1011static constexpr NWidgetPart _nested_performance_history_widgets[] = {
1013 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1014 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1015 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_PHG_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetStringTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
1016 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1017 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1018 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1019 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1020 EndContainer(),
1021 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1022 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1024 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1025 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1026 EndContainer(),
1027 EndContainer(),
1028};
1029
1030static WindowDesc _performance_history_desc(
1031 WDP_AUTO, "graph_performance", 0, 0,
1033 {},
1034 _nested_performance_history_widgets
1035);
1036
1037void ShowPerformanceHistoryGraph()
1038{
1039 AllocateWindowDescFront<PerformanceHistoryGraphWindow>(_performance_history_desc, 0);
1040}
1041
1042/*****************/
1043/* COMPANY VALUE */
1044/*****************/
1045
1048 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1049 {
1050 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1051 this->num_vert_lines = GRAPH_NUM_MONTHS;
1052 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1053
1054 this->InitializeWindow(window_number);
1055 }
1056
1057 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1058 {
1059 return c->old_economy[j].company_value;
1060 }
1061};
1062
1063static constexpr NWidgetPart _nested_company_value_graph_widgets[] = {
1065 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1066 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1067 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1068 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1069 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1070 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1071 EndContainer(),
1072 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1073 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1075 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1076 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1077 EndContainer(),
1078 EndContainer(),
1079};
1080
1081static WindowDesc _company_value_graph_desc(
1082 WDP_AUTO, "graph_company_value", 0, 0,
1084 {},
1085 _nested_company_value_graph_widgets
1086);
1087
1088void ShowCompanyValueGraph()
1089{
1090 AllocateWindowDescFront<CompanyValueGraphWindow>(_company_value_graph_desc, 0);
1091}
1092
1094 Scrollbar *vscroll = nullptr;
1095 uint line_height = 0;
1096 uint legend_width = 0;
1097
1098 CargoTypes cargo_types{};
1099
1100 BaseCargoGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
1101
1102 void InitializeWindow(WindowNumber number, StringID footer_wallclock = STR_EMPTY, StringID footer_calendar = STR_EMPTY)
1103 {
1104 this->CreateNestedTree();
1105
1106 this->cargo_types = this->GetCargoTypes(number);
1107
1108 this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
1109 this->vscroll->SetCount(CountBits(this->cargo_types));
1110
1111 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1112 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? footer_wallclock : footer_calendar);
1113
1114 this->FinishInitNested(number);
1115
1116 /* Initialise the dataset */
1117 this->InvalidateData();
1118 }
1119
1120 virtual CargoTypes GetCargoTypes(WindowNumber number) const = 0;
1121 virtual CargoTypes &GetExcludedCargoTypes() const = 0;
1122
1123 std::optional<uint8_t> GetDatasetIndex(int y) override
1124 {
1125 int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_GRAPH_MATRIX);
1126 if (row >= this->vscroll->GetCount()) return std::nullopt;
1127
1128 for (const CargoSpec *cs : _sorted_cargo_specs) {
1129 if (!HasBit(this->cargo_types, cs->Index())) continue;
1130 if (row-- > 0) continue;
1131
1132 return cs->Index();
1133 }
1134
1135 return std::nullopt;
1136 }
1137
1138 void OnInit() override
1139 {
1140 /* Width of the legend blob. */
1141 this->legend_width = GetCharacterHeight(FS_SMALL) * 9 / 6;
1142 }
1143
1144 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1145 {
1146 if (widget != WID_GRAPH_MATRIX) {
1147 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1148 return;
1149 }
1150
1152
1153 for (CargoType cargo_type : SetCargoBitIterator(this->cargo_types)) {
1154 const CargoSpec *cs = CargoSpec::Get(cargo_type);
1155
1156 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1157 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1160 size = maxdim(d, size);
1161 }
1162
1163 this->line_height = size.height;
1164 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1165 resize.width = 0;
1166 resize.height = this->line_height;
1167 }
1168
1169 void DrawWidget(const Rect &r, WidgetID widget) const override
1170 {
1171 if (widget != WID_GRAPH_MATRIX) {
1172 BaseGraphWindow::DrawWidget(r, widget);
1173 return;
1174 }
1175
1176 bool rtl = _current_text_dir == TD_RTL;
1177
1178 int pos = this->vscroll->GetPosition();
1179 int max = pos + this->vscroll->GetCapacity();
1180
1181 Rect line = r.WithHeight(this->line_height);
1182
1183 for (const CargoSpec *cs : _sorted_cargo_specs) {
1184 if (!HasBit(this->cargo_types, cs->Index())) continue;
1185
1186 if (pos-- > 0) continue;
1187 if (--max < 0) break;
1188
1189 bool lowered = !HasBit(this->excluded_data, cs->Index());
1190
1191 /* Redraw frame if lowered */
1192 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
1193
1194 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1195
1196 /* Cargo-colour box with outline */
1197 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1198 GfxFillRect(cargo, PC_BLACK);
1199 uint8_t pc = cs->legend_colour;
1200 if (this->highlight_data == cs->Index()) pc = this->highlight_state ? PC_WHITE : PC_BLACK;
1202
1203 /* Cargo name */
1204 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1205
1206 line = line.Translate(0, this->line_height);
1207 }
1208 }
1209
1210 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1211 {
1212 switch (widget) {
1214 /* Remove all cargoes from the excluded lists. */
1215 this->GetExcludedCargoTypes() = {};
1216 this->excluded_data = this->GetExcludedCargoTypes();
1217 this->SetDirty();
1218 break;
1219
1221 /* Add all cargoes to the excluded lists. */
1222 this->GetExcludedCargoTypes() = this->cargo_types;
1223 this->excluded_data = this->GetExcludedCargoTypes();
1224 this->SetDirty();
1225 break;
1226 }
1227
1228 case WID_GRAPH_MATRIX: {
1229 int row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GRAPH_MATRIX);
1230 if (row >= this->vscroll->GetCount()) return;
1231
1232 for (const CargoSpec *cs : _sorted_cargo_specs) {
1233 if (!HasBit(this->cargo_types, cs->Index())) continue;
1234 if (row-- > 0) continue;
1235
1236 ToggleBit(this->GetExcludedCargoTypes(), cs->Index());
1237 this->excluded_data = this->GetExcludedCargoTypes();
1238 this->SetDirty();
1239 break;
1240 }
1241 break;
1242 }
1243
1244 default:
1245 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1246 break;
1247 }
1248 }
1249
1250 void OnResize() override
1251 {
1252 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1253 }
1254};
1255
1256/*****************/
1257/* PAYMENT RATES */
1258/*****************/
1259
1261 static inline CargoTypes excluded_cargo_types{};
1262
1264 {
1265 this->num_on_x_axis = GRAPH_PAYMENT_RATE_STEPS;
1266 this->num_vert_lines = GRAPH_PAYMENT_RATE_STEPS;
1267 this->draw_dates = false;
1268
1269 this->x_values_reversed = false;
1270 /* The x-axis is labeled in either seconds or days. A day is two seconds, so we adjust the label if needed. */
1272
1273 this->InitializeWindow(window_number, STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS, STR_GRAPH_CARGO_PAYMENT_RATES_DAYS);
1274 }
1275
1276 CargoTypes GetCargoTypes(WindowNumber) const override
1277 {
1278 return _standard_cargo_mask;
1279 }
1280
1281 CargoTypes &GetExcludedCargoTypes() const override
1282 {
1283 return PaymentRatesGraphWindow::excluded_cargo_types;
1284 }
1285
1291 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1292 {
1293 if (!gui_scope) return;
1294 this->UpdatePaymentRates();
1295 }
1296
1298 const IntervalTimer<TimerWindow> update_payment_interval = {std::chrono::seconds(3), [this](auto) {
1299 this->UpdatePaymentRates();
1300 }};
1301
1302 void UpdateStatistics(bool) override {}
1303
1308 {
1309 this->excluded_data = this->GetExcludedCargoTypes();
1310
1311 this->data.clear();
1312 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1313 DataSet &dataset = this->data.emplace_back();
1314 dataset.colour = cs->legend_colour;
1315 dataset.exclude_bit = cs->Index();
1316
1317 for (uint j = 0; j != this->num_on_x_axis; j++) {
1318 dataset.values[j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
1319 }
1320 }
1321 }
1322};
1323
1324static constexpr NWidgetPart _nested_cargo_payment_rates_widgets[] = {
1326 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1327 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1328 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1329 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1330 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1331 EndContainer(),
1332 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1333 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_HEADER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_TITLE), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1335 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1337 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1338 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1339 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1342 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
1344 EndContainer(),
1345 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1346 EndContainer(),
1347 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1348 EndContainer(),
1350 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1351 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1352 EndContainer(),
1353 EndContainer(),
1354};
1355
1356static WindowDesc _cargo_payment_rates_desc(
1357 WDP_AUTO, "graph_cargo_payment_rates", 0, 0,
1359 {},
1360 _nested_cargo_payment_rates_widgets
1361);
1362
1363
1364void ShowCargoPaymentRates()
1365{
1366 AllocateWindowDescFront<PaymentRatesGraphWindow>(_cargo_payment_rates_desc, 0);
1367}
1368
1369/*****************************/
1370/* PERFORMANCE RATING DETAIL */
1371/*****************************/
1372
1374 static CompanyID company;
1375 int timeout = 0;
1376 uint score_info_left = 0;
1377 uint score_info_right = 0;
1378 uint bar_left = 0;
1379 uint bar_right = 0;
1380 uint bar_width = 0;
1381 uint bar_height = 0;
1382 uint score_detail_left = 0;
1383 uint score_detail_right = 0;
1384
1386 {
1387 this->UpdateCompanyStats();
1388
1389 this->InitNested(window_number);
1390 this->OnInvalidateData(CompanyID::Invalid().base());
1391 }
1392
1393 void UpdateCompanyStats()
1394 {
1395 /* Update all company stats with the current data
1396 * (this is because _score_info is not saved to a savegame) */
1397 for (Company *c : Company::Iterate()) {
1399 }
1400
1401 this->timeout = Ticks::DAY_TICKS * 5;
1402 }
1403
1404 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1405 {
1406 switch (widget) {
1409 size.height = this->bar_height + WidgetDimensions::scaled.matrix.Vertical();
1410
1411 uint score_info_width = 0;
1412 for (uint i = SCORE_BEGIN; i < SCORE_END; i++) {
1413 score_info_width = std::max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + i).width);
1414 }
1415 score_info_width += GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(1000))).width + WidgetDimensions::scaled.hsep_wide;
1416
1417 this->bar_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_PERCENT, GetParamMaxValue(100))).width + WidgetDimensions::scaled.hsep_indent * 2; // Wide bars!
1418
1419 /* At this number we are roughly at the max; it can become wider,
1420 * but then you need at 1000 times more money. At that time you're
1421 * not that interested anymore in the last few digits anyway.
1422 * The 500 is because 999 999 500 to 999 999 999 are rounded to
1423 * 1 000 M, and not 999 999 k. Use negative numbers to account for
1424 * the negative income/amount of money etc. as well. */
1425 int max = -(999999999 - 500);
1426
1427 /* Scale max for the display currency. Prior to rendering the value
1428 * is converted into the display currency, which may cause it to
1429 * raise significantly. We need to compensate for that since {{CURRCOMPACT}}
1430 * is used, which can produce quite short renderings of very large
1431 * values. Otherwise the calculated width could be too narrow.
1432 * Note that it doesn't work if there was a currency with an exchange
1433 * rate greater than max.
1434 * When the currency rate is more than 1000, the 999 999 k becomes at
1435 * least 999 999 M which roughly is equally long. Furthermore if the
1436 * exchange rate is that high, 999 999 k is usually not enough anymore
1437 * to show the different currency numbers. */
1438 if (GetCurrency().rate < 1000) max /= GetCurrency().rate;
1439 uint score_detail_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, max, max)).width;
1440
1441 size.width = WidgetDimensions::scaled.frametext.Horizontal() + score_info_width + WidgetDimensions::scaled.hsep_wide + this->bar_width + WidgetDimensions::scaled.hsep_wide + score_detail_width;
1443 uint right = size.width - WidgetDimensions::scaled.frametext.right;
1444
1445 bool rtl = _current_text_dir == TD_RTL;
1446 this->score_info_left = rtl ? right - score_info_width : left;
1447 this->score_info_right = rtl ? right : left + score_info_width;
1448
1449 this->score_detail_left = rtl ? left : right - score_detail_width;
1450 this->score_detail_right = rtl ? left + score_detail_width : right;
1451
1452 this->bar_left = left + (rtl ? score_detail_width : score_info_width) + WidgetDimensions::scaled.hsep_wide;
1453 this->bar_right = this->bar_left + this->bar_width - 1;
1454 break;
1455 }
1456 }
1457
1458 void DrawWidget(const Rect &r, WidgetID widget) const override
1459 {
1460 /* No need to draw when there's nothing to draw */
1461 if (this->company == CompanyID::Invalid()) return;
1462
1464 if (this->IsWidgetDisabled(widget)) return;
1465 CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1466 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
1467 DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height));
1468 return;
1469 }
1470
1471 if (!IsInsideMM(widget, WID_PRD_SCORE_FIRST, WID_PRD_SCORE_LAST + 1)) return;
1472
1473 ScoreID score_type = (ScoreID)(widget - WID_PRD_SCORE_FIRST);
1474
1475 /* The colours used to show how the progress is going */
1476 int colour_done = GetColourGradient(COLOUR_GREEN, SHADE_NORMAL);
1477 int colour_notdone = GetColourGradient(COLOUR_RED, SHADE_NORMAL);
1478
1479 /* Draw all the score parts */
1480 int64_t val = _score_part[company][score_type];
1481 int64_t needed = _score_info[score_type].needed;
1482 int score = _score_info[score_type].score;
1483
1484 /* SCORE_TOTAL has its own rules ;) */
1485 if (score_type == SCORE_TOTAL) {
1486 for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) score += _score_info[i].score;
1487 needed = SCORE_MAX;
1488 }
1489
1490 uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height);
1491 uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL));
1492
1493 DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type);
1494
1495 /* Draw the score */
1496 DrawString(this->score_info_left, this->score_info_right, text_top, GetString(STR_JUST_COMMA, score), TC_BLACK, SA_RIGHT);
1497
1498 /* Calculate the %-bar */
1499 uint x = Clamp<int64_t>(val, 0, needed) * this->bar_width / needed;
1500 bool rtl = _current_text_dir == TD_RTL;
1501 if (rtl) {
1502 x = this->bar_right - x;
1503 } else {
1504 x = this->bar_left + x;
1505 }
1506
1507 /* Draw the bar */
1508 if (x != this->bar_left) GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height - 1, rtl ? colour_notdone : colour_done);
1509 if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height - 1, rtl ? colour_done : colour_notdone);
1510
1511 /* Draw it */
1512 DrawString(this->bar_left, this->bar_right, text_top, GetString(STR_PERFORMANCE_DETAIL_PERCENT, Clamp<int64_t>(val, 0, needed) * 100 / needed), TC_FROMSTRING, SA_HOR_CENTER);
1513
1514 /* SCORE_LOAN is inversed */
1515 if (score_type == SCORE_LOAN) val = needed - val;
1516
1517 /* Draw the amount we have against what is needed
1518 * For some of them it is in currency format */
1519 switch (score_type) {
1520 case SCORE_MIN_PROFIT:
1521 case SCORE_MIN_INCOME:
1522 case SCORE_MAX_INCOME:
1523 case SCORE_MONEY:
1524 case SCORE_LOAN:
1525 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, val, needed));
1526 break;
1527 default:
1528 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_INT, val, needed));
1529 break;
1530 }
1531 }
1532
1533 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1534 {
1535 /* Check which button is clicked */
1537 /* Is it no on disable? */
1538 if (!this->IsWidgetDisabled(widget)) {
1539 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1540 this->company = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1541 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1542 this->SetDirty();
1543 }
1544 }
1545 }
1546
1547 void OnGameTick() override
1548 {
1549 /* Update the company score every 5 days */
1550 if (--this->timeout == 0) {
1551 this->UpdateCompanyStats();
1552 this->SetDirty();
1553 }
1554 }
1555
1561 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1562 {
1563 if (!gui_scope) return;
1564 /* Disable the companies who are not active */
1565 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
1567 }
1568
1569 /* Check if the currently selected company is still active. */
1570 if (this->company != CompanyID::Invalid() && !Company::IsValidID(this->company)) {
1571 /* Raise the widget for the previous selection. */
1572 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1573 this->company = CompanyID::Invalid();
1574 }
1575
1576 if (this->company == CompanyID::Invalid()) {
1577 for (const Company *c : Company::Iterate()) {
1578 this->company = c->index;
1579 break;
1580 }
1581 }
1582
1583 /* Make sure the widget is lowered */
1584 if (this->company != CompanyID::Invalid()) {
1585 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1586 }
1587 }
1588};
1589
1590CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
1591
1592/*******************************/
1593/* INDUSTRY PRODUCTION HISTORY */
1594/*******************************/
1595
1597 static inline constexpr StringID RANGE_LABELS[] = {
1598 STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
1599 STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED
1600 };
1601
1602 static inline CargoTypes excluded_cargo_types{};
1603
1605 BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1606 {
1607 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1608 this->num_vert_lines = GRAPH_NUM_MONTHS;
1609 this->month_increment = 1;
1610 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1611 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1612 this->ranges = RANGE_LABELS;
1613
1614 this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
1615 }
1616
1617 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1618 {
1619 CargoTypes cargo_types{};
1621 for (const auto &p : i->produced) {
1622 if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo);
1623 }
1624 return cargo_types;
1625 }
1626
1627 CargoTypes &GetExcludedCargoTypes() const override
1628 {
1629 return IndustryProductionGraphWindow::excluded_cargo_types;
1630 }
1631
1632 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1633 {
1634 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_PRODUCTION_CAPTION, this->window_number);
1635
1636 return this->Window::GetWidgetString(widget, stringid);
1637 }
1638
1639 void UpdateStatistics(bool initialize) override
1640 {
1641 int mo = TimerGameEconomy::month - this->num_vert_lines;
1642 auto yr = TimerGameEconomy::year;
1643 while (mo < 0) {
1644 yr--;
1645 mo += 12;
1646 }
1647
1648 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1649 /* There's no reason to get new stats */
1650 return;
1651 }
1652
1653 this->excluded_data = this->GetExcludedCargoTypes();
1654 this->year = yr;
1655 this->month = mo;
1656
1657 const Industry *i = Industry::Get(this->window_number);
1658
1659 this->data.clear();
1660 for (const auto &p : i->produced) {
1661 if (!IsValidCargoType(p.cargo)) continue;
1662 const CargoSpec *cs = CargoSpec::Get(p.cargo);
1663
1664 DataSet &produced = this->data.emplace_back();
1665 produced.colour = cs->legend_colour;
1666 produced.exclude_bit = cs->Index();
1667 produced.range_bit = 0;
1668
1669 for (uint j = 0; j < GRAPH_NUM_MONTHS; j++) {
1670 produced.values[j] = p.history[GRAPH_NUM_MONTHS - j].production;
1671 }
1672
1673 DataSet &transported = this->data.emplace_back();
1674 transported.colour = cs->legend_colour;
1675 transported.exclude_bit = cs->Index();
1676 transported.range_bit = 1;
1677 transported.dash = 2;
1678
1679 for (uint j = 0; j < GRAPH_NUM_MONTHS; j++) {
1680 transported.values[j] = p.history[GRAPH_NUM_MONTHS - j].transported;
1681 }
1682 }
1683
1684 this->SetDirty();
1685 }
1686};
1687
1688static constexpr NWidgetPart _nested_industry_production_widgets[] = {
1690 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1691 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_GRAPH_CAPTION),
1692 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1693 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1694 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1695 EndContainer(),
1696 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1698 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1700 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1701 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
1703 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1704 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1707 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
1709 EndContainer(),
1710 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1711 EndContainer(),
1712 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1713 EndContainer(),
1715 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1716 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1717 EndContainer(),
1718 EndContainer(),
1719};
1720
1721static WindowDesc _industry_production_desc(
1722 WDP_AUTO, "graph_industry_production", 0, 0,
1724 {},
1725 _nested_industry_production_widgets
1726);
1727
1728void ShowIndustryProductionGraph(WindowNumber window_number)
1729{
1730 AllocateWindowDescFront<IndustryProductionGraphWindow>(_industry_production_desc, window_number);
1731}
1732
1737static std::unique_ptr<NWidgetBase> MakePerformanceDetailPanels()
1738{
1739 auto realtime = TimerGameEconomy::UsingWallclockUnits();
1740 const StringID performance_tips[] = {
1741 realtime ? STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_YEARS,
1742 STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
1743 realtime ? STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_YEARS,
1744 STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
1745 STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
1746 STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
1747 STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
1748 STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
1749 STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
1750 STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
1751 };
1752
1753 static_assert(lengthof(performance_tips) == SCORE_END - SCORE_BEGIN);
1754
1755 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
1756 for (WidgetID widnum = WID_PRD_SCORE_FIRST; widnum <= WID_PRD_SCORE_LAST; widnum++) {
1757 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
1758 panel->SetFill(1, 1);
1759 panel->SetToolTip(performance_tips[widnum - WID_PRD_SCORE_FIRST]);
1760 vert->Add(std::move(panel));
1761 }
1762 return vert;
1763}
1764
1766std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsGraphGUI()
1767{
1768 return MakeCompanyButtonRows(WID_PRD_COMPANY_FIRST, WID_PRD_COMPANY_LAST, COLOUR_BROWN, 8, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
1769}
1770
1771static constexpr NWidgetPart _nested_performance_rating_detail_widgets[] = {
1773 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1774 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1775 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1776 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1777 EndContainer(),
1778 NWidget(WWT_PANEL, COLOUR_BROWN),
1780 EndContainer(),
1782};
1783
1784static WindowDesc _performance_rating_detail_desc(
1785 WDP_AUTO, "league_details", 0, 0,
1787 {},
1788 _nested_performance_rating_detail_widgets
1789);
1790
1791void ShowPerformanceRatingDetail()
1792{
1793 AllocateWindowDescFront<PerformanceRatingDetailWindow>(_performance_rating_detail_desc, 0);
1794}
1795
1796void InitializeGraphGui()
1797{
1798 _legend_excluded_companies = CompanyMask{};
1799 PaymentRatesGraphWindow::excluded_cargo_types = {};
1800 IndustryProductionGraphWindow::excluded_cargo_types = {};
1801}
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
constexpr uint CountBits(T value)
Counts the number of set bits in a variable.
constexpr T ToggleBit(T &x, const uint8_t y)
Toggles a bit in a variable.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:106
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
std::span< const CargoSpec * > _sorted_standard_cargo_specs
Standard cargo specifications sorted alphabetically by name.
CargoTypes _standard_cargo_mask
Bitmask of real cargo types available.
Definition cargotype.cpp:36
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
Types/functions related to cargoes.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Tstorage base() const noexcept
Retrieve the raw value behind this bit set.
constexpr Timpl & Flip(Tvalue_type value)
Flip the value-th bit.
constexpr Timpl & Set()
Set all bits.
void UpdateStatistics(bool initialize) override
Update the statistics.
An interval timer will fire every interval, and will continue to fire until it is deleted.
Definition timer.h:76
Scrollbar data structure.
size_type GetCapacity() const
Gets the number of visible elements of the scrollbar.
void SetCount(size_t num)
Sets the number of elements in the list.
size_type GetScrolledRowFromWidget(int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition widget.cpp:2434
void SetCapacityFromWidget(Window *w, WidgetID widget, int padding=0)
Set capacity of visible elements from the size and resize properties of a widget.
Definition widget.cpp:2508
size_type GetCount() const
Gets the number of elements in the list.
size_type GetPosition() const
Gets the position of the first visible element in the list.
static constexpr TimerGameTick::Ticks DAY_TICKS
1 day is 74 ticks; TimerGameCalendar::date_fract used to be uint16_t and incremented by 885.
static Year year
Current year, starting at 0.
static Month month
Current month (0..11).
static bool UsingWallclockUnits(bool newgame=false)
Check if we are using wallclock units.
uint8_t Month
Type for the month, note: 0 based, i.e.
RectPadding framerect
Standard padding inside many panels.
Definition window_gui.h:40
RectPadding frametext
Padding inside frame with text.
Definition window_gui.h:41
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
int hsep_wide
Wide horizontal spacing.
Definition window_gui.h:62
RectPadding fullbevel
Always-scaled bevel thickness.
Definition window_gui.h:39
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
RectPadding matrix
Padding of WWT_MATRIX items.
Definition window_gui.h:42
int hsep_normal
Normal horizontal spacing.
Definition window_gui.h:61
RectPadding bevel
Bevel thickness, affected by "scaled bevels" game option.
Definition window_gui.h:38
int hsep_indent
Width of indentation for tree layouts.
Definition window_gui.h:63
Definition of stuff that is very close to a company, like the company struct itself.
void DrawCompanyIcon(CompanyID c, int x, int y)
Draw the icon of a company.
GUI Functions related to companies.
Functions to handle different currencies.
const CurrencySpec & GetCurrency()
Get the currently selected currency.
Definition currency.h:118
int UpdateCompanyRatingAndValue(Company *c, bool update)
if update is set to true, the economy is updated with this score (also the house is updated,...
Definition economy.cpp:202
const ScoreInfo _score_info[]
Score info, values used for computing the detailed performance rating.
Definition economy.cpp:91
Functions related to the economy.
ScoreID
Score categories in the detailed performance rating.
@ SCORE_END
How many scores are there..
@ SCORE_TOTAL
This must always be the last entry.
static constexpr int SCORE_MAX
The max score that can be in the performance history.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:77
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
int CentreBounds(int min, int max, int size)
Determine where to position a centred object.
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:958
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:887
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:658
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
bool DrawStringMultiLineWithClipping(int left, int right, int top, int bottom, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw a multiline string, possibly over multiple lines, if the region is within the current display cl...
Definition gfx.cpp:860
Functions related to the gfx engine.
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:252
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:251
@ SA_LEFT
Left align the text.
Definition gfx_type.h:383
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:385
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:384
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:395
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:393
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:302
constexpr double INT64_MAX_IN_DOUBLE
The biggest double that when cast to int64_t still fits in a int64_t.
Definition graph_gui.cpp:43
static std::unique_ptr< NWidgetBase > MakeNWidgetCompanyLines()
Construct a vertical list of buttons, one for each company.
std::unique_ptr< NWidgetBase > MakeCompanyButtonRowsGraphGUI()
Make a number of rows with buttons for each company for the performance rating detail window.
static std::unique_ptr< NWidgetBase > MakePerformanceDetailPanels()
Make a vertical list of panels for outputting score details.
Graph GUI functions.
Types related to the graph widgets.
@ WID_GRAPH_FOOTER
Footer.
@ WID_GRAPH_RESIZE
Resize button.
@ WID_GRAPH_BACKGROUND
Background of the window.
@ WID_GRAPH_GRAPH
Graph itself.
@ WID_PHG_DETAILED_PERFORMANCE
Detailed performance.
@ WID_GRAPH_HEADER
Header.
@ WID_GRAPH_MATRIX_SCROLLBAR
Cargo list scrollbar.
@ WID_GRAPH_DISABLE_CARGOES
Disable cargoes button.
@ WID_GRAPH_CAPTION
Caption.
@ WID_GRAPH_KEY_BUTTON
Key button.
@ WID_GRAPH_MATRIX
Cargo list.
@ WID_GRAPH_ENABLE_CARGOES
Enable cargoes button.
@ WID_GRAPH_RANGE_MATRIX
Range list.
@ WID_GL_FIRST_COMPANY
First company in the legend.
@ WID_GL_LAST_COMPANY
Last company in the legend.
@ WID_GL_BACKGROUND
Background of the window.
@ WID_PRD_COMPANY_FIRST
First company.
@ WID_PRD_SCORE_FIRST
First entry in the score list.
@ WID_PRD_SCORE_LAST
Last entry in the score list.
@ WID_PRD_COMPANY_LAST
Last company.
constexpr NWidgetPart SetMatrixDataTip(uint32_t cols, uint32_t rows, StringID tip={})
Widget part function for setting the data and tooltip of WWT_MATRIX widgets.
constexpr NWidgetPart NWidgetFunction(NWidgetFunctionType *func_ptr)
Obtain a nested widget (sub)tree from an external source.
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
constexpr NWidgetPart SetPadding(uint8_t top, uint8_t right, uint8_t bottom, uint8_t left)
Widget part function for setting additional space around a widget.
constexpr NWidgetPart SetStringTip(StringID string, StringID tip={})
Widget part function for setting the string and tooltip.
constexpr NWidgetPart SetTextStyle(TextColour colour, FontSize size=FS_NORMAL)
Widget part function for setting the text style.
constexpr NWidgetPart SetMinimalSize(int16_t x, int16_t y)
Widget part function for setting the minimal size.
constexpr NWidgetPart SetResizeWidgetTypeTip(ResizeWidgetValues widget_type, StringID tip)
Widget part function for setting the resize widget type and tooltip.
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart SetAlignment(StringAlignment align)
Widget part function for setting the alignment of text/images.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:955
Base of all industries.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
constexpr T abs(const T a)
Returns the absolute value of (scalar) variable.
Definition math_func.hpp:23
uint8_t GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:388
static const uint8_t PC_WHITE
White palette colour.
constexpr uint8_t GREY_SCALE(uint8_t level)
Return the colour for a particular greyscale level.
static const uint8_t PC_BLACK
Black palette colour.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
This file contains all sprite-related enums and defines.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
uint64_t GetParamMaxValue(uint64_t max_value, uint min_count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:237
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:415
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:57
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_RTL
Text is written right-to-left by default.
uint legend_width
Width of legend 'blob'.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void OnInit() override
Notification that the nested widget tree gets initialized.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
CargoTypes cargo_types
Cargo types that can be selected.
void OnResize() override
Called after the window got resized.
Scrollbar * vscroll
Cargo list scrollbar.
uint line_height
Pixel height of each cargo type row.
uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
Get width for Y labels.
void OnGameTick() override
Called once per (game) tick.
static const int MIN_GRID_PIXEL_SIZE
Minimum distance between graph lines.
static const int GRAPH_NUM_MONTHS
Number of months displayed in the graph.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
static const int ECONOMY_QUARTER_MINUTES
Minutes per economic quarter.
static const int GRAPH_PAYMENT_RATE_STEPS
Number of steps on Payment rate graph.
void OnMouseOver(Point pt, WidgetID widget) override
The mouse is currently moving over the window or has just moved outside of the window.
std::span< const OverflowSafeInt64 > GetDataSetRange(const DataSet &dataset) const
Get appropriate part of dataset values for the current number of horizontal points.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
static const int ECONOMY_MONTH_MINUTES
Minutes per economic month.
uint64_t excluded_data
bitmask of the datasets that shouldn't be displayed.
bool draw_dates
Should we draw months and years on the time axis?
static const int MIN_GRAPH_NUM_LINES_Y
Minimal number of horizontal lines to draw.
uint64_t excluded_range
bitmask of ranges that should not be displayed.
uint8_t highlight_range
Data range that should be highlighted, or UINT8_MAX for none.
bool highlight_state
Current state of highlight, toggled every TIMER_BLINK_INTERVAL period.
uint8_t month_increment
month increment between vertical lines. must be divisor of 12.
static const int PAYMENT_GRAPH_X_STEP_DAYS
X-axis step label for cargo payment rates "Days in transit".
ValuesInterval GetValuesInterval(int num_hori_lines) const
Get the interval that contains the graph's data.
static const TextColour GRAPH_AXIS_LABEL_COLOUR
colour of the graph axis label.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void DrawGraph(Rect r) const
Actually draw the graph.
static const int PAYMENT_GRAPH_X_STEP_SECONDS
X-axis step label for cargo payment rates "Seconds in transit".
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
uint8_t highlight_data
Data set that should be highlighted, or UINT8_MAX for none.
Specification of a cargo type.
Definition cargotype.h:74
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:137
CargoType Index() const
Determines index of this cargospec.
Definition cargotype.h:108
StringID name
Name of this type of cargo.
Definition cargotype.h:91
GUISettings gui
settings related to the GUI
Colours colour
Company colour.
std::array< CompanyEconomyEntry, MAX_HISTORY_QUARTERS > old_economy
Economic data of the company of the last MAX_HISTORY_QUARTERS quarters.
uint8_t num_valid_stat_ent
Number of valid statistical entries in old_economy.
uint16_t rate
The conversion rate compared to the base currency.
Definition currency.h:77
Dimensions (a width and height) of a rectangle in 2D.
uint8_t graph_line_thickness
the thickness of the lines in the various graph guis
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
Definition graph_gui.cpp:80
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
Definition graph_gui.cpp:62
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
Definition graph_gui.cpp:99
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
Defines the internal data of a functional industry.
Definition industry.h:64
ProducedCargoes produced
produced cargo slots
Definition industry.h:95
Partial widget specification to allow NWidgets to be written nested.
void UpdatePaymentRates()
Update the payment rates according to the latest information.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
const IntervalTimer< TimerWindow > update_payment_interval
Update the payment rates on a regular interval.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void OnGameTick() override
Called once per (game) tick.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
Coordinates of a point in 2D.
static Pool::IterateWrapper< Titem > Iterate(size_t from=0)
Returns an iterable ensemble of all valid Titem.
static Titem * Get(auto index)
Returns Titem with given index.
static bool IsValidID(auto index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
static Titem * GetIfValid(auto index)
Returns Titem with given index.
constexpr uint Horizontal() const
Get total horizontal padding of RectPadding.
constexpr uint Vertical() const
Get total vertical padding of RectPadding.
Specification of a rectangle with absolute coordinates of all edges.
Rect WithWidth(int width, bool end) const
Copy Rect and set its width.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect WithHeight(int height, bool end=false) const
Copy Rect and set its height.
Rect Indent(int indent, bool end) const
Copy Rect and indent it from its position.
Rect Translate(int x, int y) const
Copy and translate Rect by x,y pixels.
int needed
How much you need to get the perfect score.
int score
How much score it will give.
Iterable ensemble of each set bit in a value.
Templated helper to make a type-safe 'typedef' representing a single POD value.
Contains the interval of a graph's data.
OverflowSafeInt64 lowest
Lowest value of this interval. Must be zero or less.
OverflowSafeInt64 highest
Highest value of this interval. Must be zero or greater.
High level window description.
Definition window_gui.h:167
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:273
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1778
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
Definition window.cpp:3205
void RaiseWidget(WidgetID widget_index)
Marks a widget as raised.
Definition window_gui.h:469
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:503
ResizeInfo resize
Resize information.
Definition window_gui.h:314
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1768
bool IsWidgetDisabled(WidgetID widget_index) const
Gets the enabled/disabled status of a widget.
Definition window_gui.h:410
int left
x position of left edge of the window
Definition window_gui.h:309
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
Definition window.cpp:211
void LowerWidget(WidgetID widget_index)
Marks a widget as lowered.
Definition window_gui.h:460
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1791
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:312
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:381
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:450
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:302
Definition of Interval and OneShot timers.
Definition of the game-economy-timer.
Definition of the tick-based game-timer.
Definition of the Window system.
static constexpr std::chrono::milliseconds TIMER_BLINK_INTERVAL
Interval used by blinking interface elements.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:298
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:49
std::unique_ptr< NWidgetBase > MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
Make a number of rows with button-like graphics, for enabling/disabling each company.
Definition widget.cpp:3444
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:71
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:67
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:58
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:51
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:53
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:77
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:69
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:61
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:38
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:60
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:57
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:50
@ EqualSize
Containers should keep all their (resizing) children equally large.
@ RWV_HIDE_BEVEL
Bevel of resize box is hidden.
Definition widget_type.h:30
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3265
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting)
Definition window.cpp:3147
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ Lowered
If set the frame is lowered and the background colour brighter (ie. buttons when pressed)
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h:144
int WidgetID
Widget ID.
Definition window_type.h:20
@ WC_PERFORMANCE_HISTORY
Performance history graph; Window numbers:
@ WC_PERFORMANCE_DETAIL
Performance detail window; Window numbers:
@ WC_PAYMENT_RATES
Payment rates graph; Window numbers:
@ WC_GRAPH_LEGEND
Legend for graphs; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:47
@ WC_OPERATING_PROFIT
Operating profit graph; Window numbers:
@ WC_INDUSTRY_PRODUCTION
Industry production history graph; Window numbers:
@ WC_INDUSTRY_VIEW
Industry view; Window numbers:
@ WC_INCOME_GRAPH
Income graph; Window numbers:
@ WC_DELIVERED_CARGO
Delivered cargo graph; Window numbers:
@ WC_COMPANY_VALUE
Company value graph; Window numbers:
Functions related to zooming.
@ Normal
The normal zoom level.