OpenTTD Source 20250524-master-gc366e6a48e
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;
38static CargoTypes _legend_excluded_cargo_payment_rates;
39static CargoTypes _legend_excluded_cargo_production_history;
40
41/* Apparently these don't play well with enums. */
42static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
43static const uint INVALID_DATAPOINT_POS = UINT_MAX; // Used to determine if the previous point was drawn.
44
45constexpr double INT64_MAX_IN_DOUBLE = static_cast<double>(INT64_MAX - 512);
46static_assert(static_cast<int64_t>(INT64_MAX_IN_DOUBLE) < INT64_MAX);
47
48/****************/
49/* GRAPH LEGEND */
50/****************/
51
54 {
55 this->InitNested(window_number);
56
57 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
58 if (!_legend_excluded_companies.Test(c)) this->LowerWidget(WID_GL_FIRST_COMPANY + c);
59
60 this->OnInvalidateData(c.base());
61 }
62 }
63
64 void DrawWidget(const Rect &r, WidgetID widget) const override
65 {
66 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
67
69
70 if (!Company::IsValidID(cid)) return;
71
72 bool rtl = _current_text_dir == TD_RTL;
73
74 const Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
75 Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
76 DrawCompanyIcon(cid, rtl ? ir.right - d.width : ir.left, CentreBounds(ir.top, ir.bottom, d.height));
77
78 const Rect tr = ir.Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl);
79 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);
80 }
81
82 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
83 {
84 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
85
86 _legend_excluded_companies.Flip(static_cast<CompanyID>(widget - WID_GL_FIRST_COMPANY));
87 this->ToggleWidgetLoweredState(widget);
88 this->SetDirty();
94 }
95
101 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
102 {
103 if (!gui_scope) return;
104 if (Company::IsValidID(data)) return;
105
106 _legend_excluded_companies.Set(static_cast<CompanyID>(data));
107 this->RaiseWidget(data + WID_GL_FIRST_COMPANY);
108 }
109};
110
115static std::unique_ptr<NWidgetBase> MakeNWidgetCompanyLines()
116{
117 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
118 vert->SetPadding(2, 2, 2, 2);
119 uint sprite_height = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZoomLevel::Normal).height;
120
121 for (WidgetID widnum = WID_GL_FIRST_COMPANY; widnum <= WID_GL_LAST_COMPANY; widnum++) {
122 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
123 panel->SetMinimalSize(246, sprite_height + WidgetDimensions::unscaled.framerect.Vertical());
124 panel->SetMinimalTextLines(1, WidgetDimensions::unscaled.framerect.Vertical(), FS_NORMAL);
125 panel->SetFill(1, 1);
126 panel->SetToolTip(STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
127 vert->Add(std::move(panel));
128 }
129 return vert;
130}
131
132static constexpr NWidgetPart _nested_graph_legend_widgets[] = {
134 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
135 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_KEY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
136 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
137 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
138 EndContainer(),
139 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GL_BACKGROUND),
141 EndContainer(),
142};
143
144static WindowDesc _graph_legend_desc(
145 WDP_AUTO, "graph_legend", 0, 0,
147 {},
148 _nested_graph_legend_widgets
149);
150
151static void ShowGraphLegend()
152{
153 AllocateWindowDescFront<GraphLegendWindow>(_graph_legend_desc, 0);
154}
155
161
162/******************/
163/* BASE OF GRAPHS */
164/*****************/
165
167protected:
168 static const int GRAPH_MAX_DATASETS = 64;
169 static const int GRAPH_BASE_COLOUR = GREY_SCALE(2);
170 static const int GRAPH_GRID_COLOUR = GREY_SCALE(3);
171 static const int GRAPH_AXIS_LINE_COLOUR = GREY_SCALE(1);
172 static const int GRAPH_ZERO_LINE_COLOUR = GREY_SCALE(8);
173 static const int GRAPH_YEAR_LINE_COLOUR = GREY_SCALE(5);
174 static const int GRAPH_NUM_MONTHS = 24;
175 static const int GRAPH_PAYMENT_RATE_STEPS = 20;
176 static const int PAYMENT_GRAPH_X_STEP_DAYS = 10;
177 static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20;
178 static const int ECONOMY_QUARTER_MINUTES = 3;
179 static const int ECONOMY_MONTH_MINUTES = 1;
180
181 static const TextColour GRAPH_AXIS_LABEL_COLOUR = TC_BLACK;
182
183 static const int MIN_GRAPH_NUM_LINES_Y = 9;
184 static const int MIN_GRID_PIXEL_SIZE = 20;
185
186 uint64_t excluded_data = 0;
187 uint64_t excluded_range = 0;
188 uint8_t num_on_x_axis = 0;
189 uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
190
191 /* The starting month and year that values are plotted against. */
194 uint8_t month_increment = 3;
195
196 bool draw_dates = true;
197
198 /* These values are used if the graph is being plotted against values
199 * rather than the dates specified by month and year. */
200 bool x_values_reversed = true;
201 int16_t x_values_increment = ECONOMY_QUARTER_MINUTES;
202
203 StringID format_str_y_axis{};
204
205 struct DataSet {
206 std::array<OverflowSafeInt64, GRAPH_NUM_MONTHS> values;
207 uint8_t colour;
208 uint8_t exclude_bit;
209 uint8_t range_bit;
210 uint8_t dash;
211 };
212 std::vector<DataSet> data{};
213
214 std::span<const StringID> ranges = {};
215
221 std::span<const OverflowSafeInt64> GetDataSetRange(const DataSet &dataset) const
222 {
223 return {std::begin(dataset.values), std::begin(dataset.values) + this->num_on_x_axis};
224 }
225
232 ValuesInterval GetValuesInterval(int num_hori_lines) const
233 {
234 assert(num_hori_lines > 0);
235
236 ValuesInterval current_interval;
237 current_interval.highest = INT64_MIN;
238 current_interval.lowest = INT64_MAX;
239
240 for (const DataSet &dataset : this->data) {
241 if (HasBit(this->excluded_data, dataset.exclude_bit)) continue;
242 if (HasBit(this->excluded_range, dataset.range_bit)) continue;
243
244 for (const OverflowSafeInt64 &datapoint : this->GetDataSetRange(dataset)) {
245 if (datapoint != INVALID_DATAPOINT) {
246 current_interval.highest = std::max(current_interval.highest, datapoint);
247 current_interval.lowest = std::min(current_interval.lowest, datapoint);
248 }
249 }
250 }
251
252 /* Always include zero in the shown range. */
253 double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest);
254 double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest;
255
256 /* Prevent showing values too close to the graph limits. */
257 abs_higher = (11.0 * abs_higher) / 10.0;
258 abs_lower = (11.0 * abs_lower) / 10.0;
259
260 int num_pos_grids;
261 OverflowSafeInt64 grid_size;
262
263 if (abs_lower != 0 || abs_higher != 0) {
264 /* The number of grids to reserve for the positive part is: */
265 num_pos_grids = (int)floor(0.5 + num_hori_lines * abs_higher / (abs_higher + abs_lower));
266
267 /* If there are any positive or negative values, force that they have at least one grid. */
268 if (num_pos_grids == 0 && abs_higher != 0) num_pos_grids++;
269 if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--;
270
271 /* Get the required grid size for each side and use the maximum one. */
272
273 OverflowSafeInt64 grid_size_higher = 0;
274 if (abs_higher > 0) {
275 grid_size_higher = abs_higher > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_higher);
276 grid_size_higher = (grid_size_higher + num_pos_grids - 1) / num_pos_grids;
277 }
278
279 OverflowSafeInt64 grid_size_lower = 0;
280 if (abs_lower > 0) {
281 grid_size_lower = abs_lower > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_lower);
282 grid_size_lower = (grid_size_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids);
283 }
284
285 grid_size = std::max(grid_size_higher, grid_size_lower);
286 } else {
287 /* If both values are zero, show an empty graph. */
288 num_pos_grids = num_hori_lines / 2;
289 grid_size = 1;
290 }
291
292 current_interval.highest = num_pos_grids * grid_size;
293 current_interval.lowest = -(num_hori_lines - num_pos_grids) * grid_size;
294 return current_interval;
295 }
296
302 uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
303 {
304 /* draw text strings on the y axis */
305 int64_t y_label = current_interval.highest;
306 int64_t y_label_separation = (current_interval.highest - current_interval.lowest) / num_hori_lines;
307
308 uint max_width = 0;
309
310 for (int i = 0; i < (num_hori_lines + 1); i++) {
311 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label));
312 if (d.width > max_width) max_width = d.width;
313
314 y_label -= y_label_separation;
315 }
316
317 return max_width;
318 }
319
324 void DrawGraph(Rect r) const
325 {
326 uint x, y;
327 ValuesInterval interval;
328 int x_axis_offset;
329
330 /* the colours and cost array of GraphDrawer must accommodate
331 * both values for cargo and companies. So if any are higher, quit */
332 static_assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
333 assert(this->num_vert_lines > 0);
334
335 bool rtl = _current_text_dir == TD_RTL;
336
337 /* Rect r will be adjusted to contain just the graph, with labels being
338 * placed outside the area. */
339 r.top += ScaleGUITrad(5) + GetCharacterHeight(FS_SMALL) / 2;
340 r.bottom -= (this->draw_dates ? 2 : 1) * GetCharacterHeight(FS_SMALL) + ScaleGUITrad(4);
341 r.left += ScaleGUITrad(rtl ? 5 : 9);
342 r.right -= ScaleGUITrad(rtl ? 9 : 5);
343
344 /* Initial number of horizontal lines. */
345 int num_hori_lines = 160 / ScaleGUITrad(MIN_GRID_PIXEL_SIZE);
346 /* For the rest of the height, the number of horizontal lines will increase more slowly. */
347 int resize = (r.bottom - r.top - 160) / (2 * ScaleGUITrad(MIN_GRID_PIXEL_SIZE));
348 if (resize > 0) num_hori_lines += resize;
349
350 interval = GetValuesInterval(num_hori_lines);
351
352 int label_width = GetYLabelWidth(interval, num_hori_lines);
353
354 if (rtl) {
355 r.right -= label_width;
356 } else {
357 r.left += label_width;
358 }
359
360 int x_sep = (r.right - r.left) / this->num_vert_lines;
361 int y_sep = (r.bottom - r.top) / num_hori_lines;
362
363 /* Redetermine right and bottom edge of graph to fit with the integer
364 * separation values. */
365 if (rtl) {
366 r.left = r.right - x_sep * this->num_vert_lines;
367 } else {
368 r.right = r.left + x_sep * this->num_vert_lines;
369 }
370 r.bottom = r.top + y_sep * num_hori_lines;
371
372 OverflowSafeInt64 interval_size = interval.highest + abs(interval.lowest);
373 /* Where to draw the X axis. Use floating point to avoid overflowing and results of zero. */
374 x_axis_offset = (int)((r.bottom - r.top) * (double)interval.highest / (double)interval_size);
375
376 /* Draw the background of the graph itself. */
377 GfxFillRect(r.left, r.top, r.right, r.bottom, GRAPH_BASE_COLOUR);
378
379 /* Draw the vertical grid lines. */
380
381 /* Don't draw the first line, as that's where the axis will be. */
382 if (rtl) {
383 x_sep = -x_sep;
384 x = r.right + x_sep;
385 } else {
386 x = r.left + x_sep;
387 }
388
389 int grid_colour = GRAPH_GRID_COLOUR;
390 for (int i = 1; i < this->num_vert_lines + 1; i++) {
391 /* If using wallclock units, we separate periods with a lighter line. */
393 grid_colour = (i % 4 == 0) ? GRAPH_YEAR_LINE_COLOUR : GRAPH_GRID_COLOUR;
394 }
395 GfxFillRect(x, r.top, x, r.bottom, grid_colour);
396 x += x_sep;
397 }
398
399 /* Draw the horizontal grid lines. */
400 y = r.bottom;
401
402 for (int i = 0; i < (num_hori_lines + 1); i++) {
403 if (rtl) {
404 GfxFillRect(r.right + 1, y, r.right + ScaleGUITrad(3), y, GRAPH_AXIS_LINE_COLOUR);
405 } else {
406 GfxFillRect(r.left - ScaleGUITrad(3), y, r.left - 1, y, GRAPH_AXIS_LINE_COLOUR);
407 }
408 GfxFillRect(r.left, y, r.right, y, GRAPH_GRID_COLOUR);
409 y -= y_sep;
410 }
411
412 /* Draw the y axis. */
413 GfxFillRect(r.left, r.top, r.left, r.bottom, GRAPH_AXIS_LINE_COLOUR);
414
415 /* Draw the x axis. */
416 y = x_axis_offset + r.top;
417 GfxFillRect(r.left, y, r.right, y, GRAPH_ZERO_LINE_COLOUR);
418
419 /* Find the largest value that will be drawn. */
420 if (this->num_on_x_axis == 0) return;
421
422 assert(this->num_on_x_axis > 0);
423
424 /* draw text strings on the y axis */
425 int64_t y_label = interval.highest;
426 int64_t y_label_separation = abs(interval.highest - interval.lowest) / num_hori_lines;
427
428 y = r.top - GetCharacterHeight(FS_SMALL) / 2;
429
430 for (int i = 0; i < (num_hori_lines + 1); i++) {
431 if (rtl) {
432 DrawString(r.right + ScaleGUITrad(4), r.right + label_width + ScaleGUITrad(4), y,
433 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
435 } else {
436 DrawString(r.left - label_width - ScaleGUITrad(4), r.left - ScaleGUITrad(4), y,
437 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
439 }
440
441 y_label -= y_label_separation;
442 y += y_sep;
443 }
444
445 x = rtl ? r.right : r.left;
446 y = r.bottom + ScaleGUITrad(2);
447
448 /* if there are not enough datapoints to fill the graph, align to the right */
449 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
450
451 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
452 if (this->draw_dates) {
453 TimerGameEconomy::Month month = this->month;
454 TimerGameEconomy::Year year = this->year;
455 for (int i = 0; i < this->num_on_x_axis; i++) {
456 if (rtl) {
457 DrawStringMultiLineWithClipping(x + x_sep, x, y, this->height,
458 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
460 } else {
461 DrawStringMultiLineWithClipping(x, x + x_sep, y, this->height,
462 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
464 }
465
466 month += this->month_increment;
467 if (month >= 12) {
468 month = 0;
469 year++;
470
471 /* Draw a lighter grid line between years. Top and bottom adjustments ensure we don't draw over top and bottom horizontal grid lines. */
472 GfxFillRect(x + x_sep, r.top + 1, x + x_sep, r.bottom - 1, GRAPH_YEAR_LINE_COLOUR);
473 }
474 x += x_sep;
475 }
476 } else {
477 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates, and all graphs when using wallclock units). */
478 int16_t iterator;
479 uint16_t label;
480 if (this->x_values_reversed) {
481 label = this->x_values_increment * this->num_on_x_axis;
482 iterator = -this->x_values_increment;
483 } else {
484 label = this->x_values_increment;
485 iterator = this->x_values_increment;
486 }
487
488 for (int i = 0; i < this->num_on_x_axis; i++) {
489 if (rtl) {
490 DrawString(x + x_sep + 1, x - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
491 } else {
492 DrawString(x + 1, x + x_sep - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
493 }
494
495 label += iterator;
496 x += x_sep;
497 }
498 }
499
500 /* draw lines and dots */
502 uint pointoffs1 = (linewidth + 1) / 2;
503 uint pointoffs2 = linewidth + 1 - pointoffs1;
504
505 for (const DataSet &dataset : this->data) {
506 if (HasBit(this->excluded_data, dataset.exclude_bit)) continue;
507 if (HasBit(this->excluded_range, dataset.range_bit)) continue;
508
509 /* Centre the dot between the grid lines. */
510 if (rtl) {
511 x = r.right + (x_sep / 2);
512 } else {
513 x = r.left + (x_sep / 2);
514 }
515
516 /* if there are not enough datapoints to fill the graph, align to the right */
517 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
518
519 uint prev_x = INVALID_DATAPOINT_POS;
520 uint prev_y = INVALID_DATAPOINT_POS;
521
522 const uint dash = ScaleGUITrad(dataset.dash);
523 for (OverflowSafeInt64 datapoint : this->GetDataSetRange(dataset)) {
524 if (datapoint != INVALID_DATAPOINT) {
525 /*
526 * Check whether we need to reduce the 'accuracy' of the
527 * datapoint value and the highest value to split overflows.
528 * And when 'drawing' 'one million' or 'one million and one'
529 * there is no significant difference, so the least
530 * significant bits can just be removed.
531 *
532 * If there are more bits needed than would fit in a 32 bits
533 * integer, so at about 31 bits because of the sign bit, the
534 * least significant bits are removed.
535 */
536 int mult_range = FindLastBit<uint32_t>(x_axis_offset) + FindLastBit<uint64_t>(abs(datapoint));
537 int reduce_range = std::max(mult_range - 31, 0);
538
539 /* Handle negative values differently (don't shift sign) */
540 if (datapoint < 0) {
541 datapoint = -(abs(datapoint) >> reduce_range);
542 } else {
543 datapoint >>= reduce_range;
544 }
545 y = r.top + x_axis_offset - ((r.bottom - r.top) * datapoint) / (interval_size >> reduce_range);
546
547 /* Draw the point. */
548 GfxFillRect(x - pointoffs1, y - pointoffs1, x + pointoffs2, y + pointoffs2, dataset.colour);
549
550 /* Draw the line connected to the previous point. */
551 if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, dataset.colour, linewidth, dash);
552
553 prev_x = x;
554 prev_y = y;
555 } else {
556 prev_x = INVALID_DATAPOINT_POS;
557 prev_y = INVALID_DATAPOINT_POS;
558 }
559
560 x += x_sep;
561 }
562 }
563 }
564
565 BaseGraphWindow(WindowDesc &desc, StringID format_str_y_axis) :
566 Window(desc),
567 format_str_y_axis(format_str_y_axis)
568 {
570 }
571
572 void InitializeWindow(WindowNumber number)
573 {
574 /* Initialise the dataset */
575 this->UpdateStatistics(true);
576
577 this->CreateNestedTree();
578
579 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
580 if (wid != nullptr && TimerGameEconomy::UsingWallclockUnits()) {
581 wid->SetString(STR_GRAPH_LAST_72_MINUTES_TIME_LABEL);
582 }
583
584 this->FinishInitNested(number);
585 }
586
587public:
588 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
589 {
590 switch (widget) {
592 for (const StringID &str : this->ranges) {
593 size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
594 }
595
598
599 /* Set fixed height for number of ranges. */
600 size.height *= static_cast<uint>(std::size(this->ranges));
601
602 resize.width = 0;
603 resize.height = 0;
604 this->GetWidget<NWidgetCore>(WID_GRAPH_RANGE_MATRIX)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(this->ranges)));
605 break;
606
607 case WID_GRAPH_GRAPH: {
608 uint x_label_width = 0;
609
610 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
611 if (this->draw_dates) {
612 TimerGameEconomy::Month month = this->month;
613 TimerGameEconomy::Year year = this->year;
614 for (int i = 0; i < this->num_on_x_axis; i++) {
615 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);
616
617 month += this->month_increment;
618 if (month >= 12) {
619 month = 0;
620 year++;
621 }
622 }
623 } else {
624 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates). */
625 uint64_t max_value = GetParamMaxValue((this->num_on_x_axis + 1) * this->x_values_increment, 0, FS_SMALL);
626 x_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL_NUMBER, max_value)).width;
627 }
628
629 uint y_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, INT64_MAX)).width;
630
631 size.width = std::max<uint>(size.width, ScaleGUITrad(5) + y_label_width + this->num_vert_lines * (x_label_width + ScaleGUITrad(5)) + ScaleGUITrad(9));
632 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));
633 size.height = std::max<uint>(size.height, size.width / 3);
634 break;
635 }
636
637 default: break;
638 }
639 }
640
641 void DrawWidget(const Rect &r, WidgetID widget) const override
642 {
643 switch (widget) {
644 case WID_GRAPH_GRAPH:
645 this->DrawGraph(r);
646 break;
647
650 uint index = 0;
651 Rect line = r.WithHeight(line_height);
652 for (const auto &str : this->ranges) {
653 bool lowered = !HasBit(this->excluded_range, index);
654
655 /* Redraw frame if lowered */
656 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
657
658 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
659 DrawString(text, str, TC_BLACK, SA_CENTER, false, FS_SMALL);
660
661 line = line.Translate(0, line_height);
662 ++index;
663 }
664 break;
665 }
666
667 default: break;
668 }
669 }
670
671 virtual OverflowSafeInt64 GetGraphData(const Company *, int)
672 {
673 return INVALID_DATAPOINT;
674 }
675
676 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
677 {
678 /* Clicked on legend? */
679 switch (widget) {
681 ShowGraphLegend();
682 break;
683
685 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
686
687 ToggleBit(this->excluded_range, row);
688 this->SetDirty();
689 break;
690 }
691
692 default: break;
693 }
694 }
695
696 void OnGameTick() override
697 {
698 this->UpdateStatistics(false);
699 }
700
706 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
707 {
708 if (!gui_scope) return;
709 this->UpdateStatistics(true);
710 }
711
716 virtual void UpdateStatistics(bool initialize)
717 {
718 CompanyMask excluded_companies = _legend_excluded_companies;
719
720 /* Exclude the companies which aren't valid */
721 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
722 if (!Company::IsValidID(c)) excluded_companies.Set(c);
723 }
724
725 uint8_t nums = 0;
726 for (const Company *c : Company::Iterate()) {
727 nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent));
728 }
729
730 int mo = (TimerGameEconomy::month / this->month_increment - nums) * this->month_increment;
731 auto yr = TimerGameEconomy::year;
732 while (mo < 0) {
733 yr--;
734 mo += 12;
735 }
736
737 if (!initialize && this->excluded_data == excluded_companies.base() && this->num_on_x_axis == nums &&
738 this->year == yr && this->month == mo) {
739 /* There's no reason to get new stats */
740 return;
741 }
742
743 this->excluded_data = excluded_companies.base();
744 this->num_on_x_axis = nums;
745 this->year = yr;
746 this->month = mo;
747
748 this->data.clear();
749 for (CompanyID k = CompanyID::Begin(); k < MAX_COMPANIES; ++k) {
750 const Company *c = Company::GetIfValid(k);
751 if (c == nullptr) continue;
752
753 DataSet &dataset = this->data.emplace_back();
754 dataset.colour = GetColourGradient(c->colour, SHADE_LIGHTER);
755 dataset.exclude_bit = k.base();
756
757 for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
758 if (j >= c->num_valid_stat_ent) {
759 dataset.values[i] = INVALID_DATAPOINT;
760 } else {
761 /* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
762 * Instead, use the value just under it. Hopefully nobody will notice. */
763 dataset.values[i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
764 }
765 i++;
766 }
767 }
768 }
769};
770
771
772/********************/
773/* OPERATING PROFIT */
774/********************/
775
778 BaseGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
779 {
780 this->num_on_x_axis = GRAPH_NUM_MONTHS;
781 this->num_vert_lines = GRAPH_NUM_MONTHS;
782 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
783
784 this->InitializeWindow(window_number);
785 }
786
787 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
788 {
789 return c->old_economy[j].income + c->old_economy[j].expenses;
790 }
791};
792
793static constexpr NWidgetPart _nested_operating_profit_widgets[] = {
795 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
796 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
797 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
798 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
799 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
800 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
801 EndContainer(),
802 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
804 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 160), SetFill(1, 1), SetResize(1, 1),
806 NWidget(NWID_SPACER), SetMinimalSize(12, 0), SetFill(1, 0), SetResize(1, 0),
807 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetStringTip(STR_EMPTY),
808 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
809 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
810 EndContainer(),
811 EndContainer(),
812 EndContainer(),
813};
814
815static WindowDesc _operating_profit_desc(
816 WDP_AUTO, "graph_operating_profit", 0, 0,
818 {},
819 _nested_operating_profit_widgets
820);
821
822
823void ShowOperatingProfitGraph()
824{
825 AllocateWindowDescFront<OperatingProfitGraphWindow>(_operating_profit_desc, 0);
826}
827
828
829/****************/
830/* INCOME GRAPH */
831/****************/
832
835 BaseGraphWindow(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;
847 }
848};
849
850static constexpr NWidgetPart _nested_income_graph_widgets[] = {
852 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
853 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_INCOME_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),
861 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
863 NWidget(NWID_SPACER), SetMinimalSize(12, 0), SetFill(1, 0), SetResize(1, 0),
864 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetStringTip(STR_EMPTY),
865 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
866 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
867 EndContainer(),
868 EndContainer(),
869 EndContainer(),
870};
871
872static WindowDesc _income_graph_desc(
873 WDP_AUTO, "graph_income", 0, 0,
875 {},
876 _nested_income_graph_widgets
877);
878
879void ShowIncomeGraph()
880{
881 AllocateWindowDescFront<IncomeGraphWindow>(_income_graph_desc, 0);
882}
883
884/*******************/
885/* DELIVERED CARGO */
886/*******************/
887
890 BaseGraphWindow(desc, STR_JUST_COMMA)
891 {
892 this->num_on_x_axis = GRAPH_NUM_MONTHS;
893 this->num_vert_lines = GRAPH_NUM_MONTHS;
894 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
895
896 this->InitializeWindow(window_number);
897 }
898
899 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
900 {
901 return c->old_economy[j].delivered_cargo.GetSum<OverflowSafeInt64>();
902 }
903};
904
905static constexpr NWidgetPart _nested_delivered_cargo_graph_widgets[] = {
907 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
908 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
909 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
910 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
911 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
912 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
913 EndContainer(),
914 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
916 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
918 NWidget(NWID_SPACER), SetMinimalSize(12, 0), SetFill(1, 0), SetResize(1, 0),
919 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetStringTip(STR_EMPTY),
920 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
921 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
922 EndContainer(),
923 EndContainer(),
924 EndContainer(),
925};
926
927static WindowDesc _delivered_cargo_graph_desc(
928 WDP_AUTO, "graph_delivered_cargo", 0, 0,
930 {},
931 _nested_delivered_cargo_graph_widgets
932);
933
934void ShowDeliveredCargoGraph()
935{
936 AllocateWindowDescFront<DeliveredCargoGraphWindow>(_delivered_cargo_graph_desc, 0);
937}
938
939/***********************/
940/* PERFORMANCE HISTORY */
941/***********************/
942
945 BaseGraphWindow(desc, STR_JUST_COMMA)
946 {
947 this->num_on_x_axis = GRAPH_NUM_MONTHS;
948 this->num_vert_lines = GRAPH_NUM_MONTHS;
949 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
950
951 this->InitializeWindow(window_number);
952 }
953
954 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
955 {
956 return c->old_economy[j].performance_history;
957 }
958
959 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
960 {
961 if (widget == WID_PHG_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
962 this->BaseGraphWindow::OnClick(pt, widget, click_count);
963 }
964};
965
966static constexpr NWidgetPart _nested_performance_history_widgets[] = {
968 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
969 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
970 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_PHG_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetStringTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
971 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
972 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
973 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
974 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
975 EndContainer(),
976 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
978 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
980 NWidget(NWID_SPACER), SetMinimalSize(12, 0), SetFill(1, 0), SetResize(1, 0),
981 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetStringTip(STR_EMPTY),
982 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
983 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
984 EndContainer(),
985 EndContainer(),
986 EndContainer(),
987};
988
989static WindowDesc _performance_history_desc(
990 WDP_AUTO, "graph_performance", 0, 0,
992 {},
993 _nested_performance_history_widgets
994);
995
996void ShowPerformanceHistoryGraph()
997{
998 AllocateWindowDescFront<PerformanceHistoryGraphWindow>(_performance_history_desc, 0);
999}
1000
1001/*****************/
1002/* COMPANY VALUE */
1003/*****************/
1004
1007 BaseGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1008 {
1009 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1010 this->num_vert_lines = GRAPH_NUM_MONTHS;
1011 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1012
1013 this->InitializeWindow(window_number);
1014 }
1015
1016 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1017 {
1018 return c->old_economy[j].company_value;
1019 }
1020};
1021
1022static constexpr NWidgetPart _nested_company_value_graph_widgets[] = {
1024 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1025 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1026 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1027 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1028 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1029 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1030 EndContainer(),
1031 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1033 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1035 NWidget(NWID_SPACER), SetMinimalSize(12, 0), SetFill(1, 0), SetResize(1, 0),
1036 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetStringTip(STR_EMPTY),
1037 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1038 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1039 EndContainer(),
1040 EndContainer(),
1041 EndContainer(),
1042};
1043
1044static WindowDesc _company_value_graph_desc(
1045 WDP_AUTO, "graph_company_value", 0, 0,
1047 {},
1048 _nested_company_value_graph_widgets
1049);
1050
1051void ShowCompanyValueGraph()
1052{
1053 AllocateWindowDescFront<CompanyValueGraphWindow>(_company_value_graph_desc, 0);
1054}
1055
1056/*****************/
1057/* PAYMENT RATES */
1058/*****************/
1059
1061 uint line_height = 0;
1062 Scrollbar *vscroll = nullptr;
1063 uint legend_width = 0;
1064
1066 BaseGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1067 {
1068 this->num_on_x_axis = GRAPH_PAYMENT_RATE_STEPS;
1069 this->num_vert_lines = GRAPH_PAYMENT_RATE_STEPS;
1070 this->draw_dates = false;
1071
1072 this->x_values_reversed = false;
1073 /* The x-axis is labeled in either seconds or days. A day is two seconds, so we adjust the label if needed. */
1075
1076 this->CreateNestedTree();
1077 this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
1078 this->vscroll->SetCount(_sorted_standard_cargo_specs.size());
1079
1080 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1081 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS : STR_GRAPH_CARGO_PAYMENT_RATES_DAYS);
1082
1083 /* Initialise the dataset */
1084 this->UpdatePaymentRates();
1085
1086 this->FinishInitNested(window_number);
1087 }
1088
1089 void OnInit() override
1090 {
1091 /* Width of the legend blob. */
1092 this->legend_width = GetCharacterHeight(FS_SMALL) * 9 / 6;
1093 }
1094
1095 void UpdateExcludedData()
1096 {
1097 this->excluded_data = _legend_excluded_cargo_payment_rates;
1098 }
1099
1100 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1101 {
1102 if (widget != WID_GRAPH_MATRIX) {
1103 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1104 return;
1105 }
1106
1108
1109 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1110 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1111 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1114 size = maxdim(d, size);
1115 }
1116
1117 this->line_height = size.height;
1118 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1119 resize.width = 0;
1120 resize.height = this->line_height;
1121 }
1122
1123 void DrawWidget(const Rect &r, WidgetID widget) const override
1124 {
1125 if (widget != WID_GRAPH_MATRIX) {
1126 BaseGraphWindow::DrawWidget(r, widget);
1127 return;
1128 }
1129
1130 bool rtl = _current_text_dir == TD_RTL;
1131
1132 auto [first, last] = this->vscroll->GetVisibleRangeIterators(_sorted_standard_cargo_specs);
1133
1134 Rect line = r.WithHeight(this->line_height);
1135 for (auto it = first; it != last; ++it) {
1136 const CargoSpec *cs = *it;
1137
1138 bool lowered = !HasBit(_legend_excluded_cargo_payment_rates, cs->Index());
1139
1140 /* Redraw frame if lowered */
1141 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
1142
1143 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1144
1145 /* Cargo-colour box with outline */
1146 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1147 GfxFillRect(cargo, PC_BLACK);
1148 GfxFillRect(cargo.Shrink(WidgetDimensions::scaled.bevel), cs->legend_colour);
1149
1150 /* Cargo name */
1151 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1152
1153 line = line.Translate(0, this->line_height);
1154 }
1155 }
1156
1157 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1158 {
1159 switch (widget) {
1161 /* Remove all cargoes from the excluded lists. */
1162 _legend_excluded_cargo_payment_rates = 0;
1163 this->excluded_data = 0;
1164 this->SetDirty();
1165 break;
1166
1168 /* Add all cargoes to the excluded lists. */
1169 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1170 SetBit(_legend_excluded_cargo_payment_rates, cs->Index());
1171 SetBit(this->excluded_data, cs->Index());
1172 }
1173 this->SetDirty();
1174 break;
1175 }
1176
1177 case WID_GRAPH_MATRIX: {
1178 auto it = this->vscroll->GetScrolledItemFromWidget(_sorted_standard_cargo_specs, pt.y, this, WID_GRAPH_MATRIX);
1179 if (it != _sorted_standard_cargo_specs.end()) {
1180 ToggleBit(_legend_excluded_cargo_payment_rates, (*it)->Index());
1181 this->UpdateExcludedData();
1182 this->SetDirty();
1183 }
1184 break;
1185 }
1186
1187 default:
1188 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1189 break;
1190 }
1191 }
1192
1193 void OnResize() override
1194 {
1195 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1196 }
1197
1198 void OnGameTick() override
1199 {
1200 /* Override default OnGameTick */
1201 }
1202
1208 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1209 {
1210 if (!gui_scope) return;
1211 this->UpdatePaymentRates();
1212 }
1213
1215 const IntervalTimer<TimerWindow> update_payment_interval = {std::chrono::seconds(3), [this](auto) {
1216 this->UpdatePaymentRates();
1217 }};
1218
1223 {
1224 this->UpdateExcludedData();
1225
1226 this->data.clear();
1227 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1228 DataSet &dataset = this->data.emplace_back();
1229 dataset.colour = cs->legend_colour;
1230 dataset.exclude_bit = cs->Index();
1231
1232 for (uint j = 0; j != this->num_on_x_axis; j++) {
1233 dataset.values[j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
1234 }
1235 }
1236 }
1237};
1238
1239static constexpr NWidgetPart _nested_cargo_payment_rates_widgets[] = {
1241 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1242 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1243 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1244 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1245 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1246 EndContainer(),
1247 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1249 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1250 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_HEADER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_TITLE),
1251 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1252 EndContainer(),
1254 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1256 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1257 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1258 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1261 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),
1263 EndContainer(),
1264 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1265 EndContainer(),
1266 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1267 EndContainer(),
1269 NWidget(NWID_SPACER), SetMinimalSize(12, 0), SetFill(1, 0), SetResize(1, 0),
1270 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0),
1271 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1272 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1273 EndContainer(),
1274 EndContainer(),
1275};
1276
1277static WindowDesc _cargo_payment_rates_desc(
1278 WDP_AUTO, "graph_cargo_payment_rates", 0, 0,
1280 {},
1281 _nested_cargo_payment_rates_widgets
1282);
1283
1284
1285void ShowCargoPaymentRates()
1286{
1287 AllocateWindowDescFront<PaymentRatesGraphWindow>(_cargo_payment_rates_desc, 0);
1288}
1289
1290/*****************************/
1291/* PERFORMANCE RATING DETAIL */
1292/*****************************/
1293
1295 static CompanyID company;
1296 int timeout = 0;
1297 uint score_info_left = 0;
1298 uint score_info_right = 0;
1299 uint bar_left = 0;
1300 uint bar_right = 0;
1301 uint bar_width = 0;
1302 uint bar_height = 0;
1303 uint score_detail_left = 0;
1304 uint score_detail_right = 0;
1305
1307 {
1308 this->UpdateCompanyStats();
1309
1310 this->InitNested(window_number);
1311 this->OnInvalidateData(CompanyID::Invalid().base());
1312 }
1313
1314 void UpdateCompanyStats()
1315 {
1316 /* Update all company stats with the current data
1317 * (this is because _score_info is not saved to a savegame) */
1318 for (Company *c : Company::Iterate()) {
1320 }
1321
1322 this->timeout = Ticks::DAY_TICKS * 5;
1323 }
1324
1325 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1326 {
1327 switch (widget) {
1330 size.height = this->bar_height + WidgetDimensions::scaled.matrix.Vertical();
1331
1332 uint score_info_width = 0;
1333 for (uint i = SCORE_BEGIN; i < SCORE_END; i++) {
1334 score_info_width = std::max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + i).width);
1335 }
1336 score_info_width += GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(1000))).width + WidgetDimensions::scaled.hsep_wide;
1337
1338 this->bar_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_PERCENT, GetParamMaxValue(100))).width + WidgetDimensions::scaled.hsep_indent * 2; // Wide bars!
1339
1340 /* At this number we are roughly at the max; it can become wider,
1341 * but then you need at 1000 times more money. At that time you're
1342 * not that interested anymore in the last few digits anyway.
1343 * The 500 is because 999 999 500 to 999 999 999 are rounded to
1344 * 1 000 M, and not 999 999 k. Use negative numbers to account for
1345 * the negative income/amount of money etc. as well. */
1346 int max = -(999999999 - 500);
1347
1348 /* Scale max for the display currency. Prior to rendering the value
1349 * is converted into the display currency, which may cause it to
1350 * raise significantly. We need to compensate for that since {{CURRCOMPACT}}
1351 * is used, which can produce quite short renderings of very large
1352 * values. Otherwise the calculated width could be too narrow.
1353 * Note that it doesn't work if there was a currency with an exchange
1354 * rate greater than max.
1355 * When the currency rate is more than 1000, the 999 999 k becomes at
1356 * least 999 999 M which roughly is equally long. Furthermore if the
1357 * exchange rate is that high, 999 999 k is usually not enough anymore
1358 * to show the different currency numbers. */
1359 if (GetCurrency().rate < 1000) max /= GetCurrency().rate;
1360 uint score_detail_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, max, max)).width;
1361
1362 size.width = WidgetDimensions::scaled.frametext.Horizontal() + score_info_width + WidgetDimensions::scaled.hsep_wide + this->bar_width + WidgetDimensions::scaled.hsep_wide + score_detail_width;
1364 uint right = size.width - WidgetDimensions::scaled.frametext.right;
1365
1366 bool rtl = _current_text_dir == TD_RTL;
1367 this->score_info_left = rtl ? right - score_info_width : left;
1368 this->score_info_right = rtl ? right : left + score_info_width;
1369
1370 this->score_detail_left = rtl ? left : right - score_detail_width;
1371 this->score_detail_right = rtl ? left + score_detail_width : right;
1372
1373 this->bar_left = left + (rtl ? score_detail_width : score_info_width) + WidgetDimensions::scaled.hsep_wide;
1374 this->bar_right = this->bar_left + this->bar_width - 1;
1375 break;
1376 }
1377 }
1378
1379 void DrawWidget(const Rect &r, WidgetID widget) const override
1380 {
1381 /* No need to draw when there's nothing to draw */
1382 if (this->company == CompanyID::Invalid()) return;
1383
1385 if (this->IsWidgetDisabled(widget)) return;
1386 CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1387 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
1388 DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height));
1389 return;
1390 }
1391
1392 if (!IsInsideMM(widget, WID_PRD_SCORE_FIRST, WID_PRD_SCORE_LAST + 1)) return;
1393
1394 ScoreID score_type = (ScoreID)(widget - WID_PRD_SCORE_FIRST);
1395
1396 /* The colours used to show how the progress is going */
1397 int colour_done = GetColourGradient(COLOUR_GREEN, SHADE_NORMAL);
1398 int colour_notdone = GetColourGradient(COLOUR_RED, SHADE_NORMAL);
1399
1400 /* Draw all the score parts */
1401 int64_t val = _score_part[company][score_type];
1402 int64_t needed = _score_info[score_type].needed;
1403 int score = _score_info[score_type].score;
1404
1405 /* SCORE_TOTAL has its own rules ;) */
1406 if (score_type == SCORE_TOTAL) {
1407 for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) score += _score_info[i].score;
1408 needed = SCORE_MAX;
1409 }
1410
1411 uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height);
1412 uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL));
1413
1414 DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type);
1415
1416 /* Draw the score */
1417 DrawString(this->score_info_left, this->score_info_right, text_top, GetString(STR_JUST_COMMA, score), TC_BLACK, SA_RIGHT);
1418
1419 /* Calculate the %-bar */
1420 uint x = Clamp<int64_t>(val, 0, needed) * this->bar_width / needed;
1421 bool rtl = _current_text_dir == TD_RTL;
1422 if (rtl) {
1423 x = this->bar_right - x;
1424 } else {
1425 x = this->bar_left + x;
1426 }
1427
1428 /* Draw the bar */
1429 if (x != this->bar_left) GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height - 1, rtl ? colour_notdone : colour_done);
1430 if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height - 1, rtl ? colour_done : colour_notdone);
1431
1432 /* Draw it */
1433 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);
1434
1435 /* SCORE_LOAN is inversed */
1436 if (score_type == SCORE_LOAN) val = needed - val;
1437
1438 /* Draw the amount we have against what is needed
1439 * For some of them it is in currency format */
1440 switch (score_type) {
1441 case SCORE_MIN_PROFIT:
1442 case SCORE_MIN_INCOME:
1443 case SCORE_MAX_INCOME:
1444 case SCORE_MONEY:
1445 case SCORE_LOAN:
1446 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, val, needed));
1447 break;
1448 default:
1449 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_INT, val, needed));
1450 break;
1451 }
1452 }
1453
1454 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1455 {
1456 /* Check which button is clicked */
1458 /* Is it no on disable? */
1459 if (!this->IsWidgetDisabled(widget)) {
1460 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1461 this->company = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1462 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1463 this->SetDirty();
1464 }
1465 }
1466 }
1467
1468 void OnGameTick() override
1469 {
1470 /* Update the company score every 5 days */
1471 if (--this->timeout == 0) {
1472 this->UpdateCompanyStats();
1473 this->SetDirty();
1474 }
1475 }
1476
1482 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1483 {
1484 if (!gui_scope) return;
1485 /* Disable the companies who are not active */
1486 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
1488 }
1489
1490 /* Check if the currently selected company is still active. */
1491 if (this->company != CompanyID::Invalid() && !Company::IsValidID(this->company)) {
1492 /* Raise the widget for the previous selection. */
1493 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1494 this->company = CompanyID::Invalid();
1495 }
1496
1497 if (this->company == CompanyID::Invalid()) {
1498 for (const Company *c : Company::Iterate()) {
1499 this->company = c->index;
1500 break;
1501 }
1502 }
1503
1504 /* Make sure the widget is lowered */
1505 if (this->company != CompanyID::Invalid()) {
1506 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1507 }
1508 }
1509};
1510
1511CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
1512
1513/*******************************/
1514/* INDUSTRY PRODUCTION HISTORY */
1515/*******************************/
1516
1518 uint line_height = 0;
1519 Scrollbar *vscroll = nullptr;
1520 uint legend_width = 0;
1521
1522 static inline constexpr StringID RANGE_LABELS[] = {
1523 STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
1524 STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED
1525 };
1526
1528 BaseGraphWindow(desc, STR_JUST_COMMA)
1529 {
1530 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1531 this->num_vert_lines = GRAPH_NUM_MONTHS;
1532 this->month_increment = 1;
1533 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1534 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1535 this->ranges = RANGE_LABELS;
1536
1537 this->CreateNestedTree();
1538 this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
1539
1540 int count = 0;
1542 for (const auto &p : i->produced) {
1543 if (!IsValidCargoType(p.cargo)) continue;
1544 count++;
1545 }
1546 this->vscroll->SetCount(count);
1547
1548 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1549 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_LAST_24_MINUTES_TIME_LABEL : STR_EMPTY);
1550
1551 this->FinishInitNested(window_number);
1552
1553 /* Initialise the dataset */
1554 this->UpdateStatistics(true);
1555 }
1556
1557 void OnInit() override
1558 {
1559 /* Width of the legend blob. */
1560 this->legend_width = GetCharacterHeight(FS_SMALL) * 9 / 6;
1561 }
1562
1563 void UpdateExcludedData()
1564 {
1565 this->excluded_data = 0;
1566
1567 const Industry *i = Industry::Get(this->window_number);
1568 for (const auto &p : i->produced) {
1569 if (!IsValidCargoType(p.cargo)) continue;
1570 if (HasBit(_legend_excluded_cargo_production_history, p.cargo)) SetBit(this->excluded_data, p.cargo);
1571 }
1572 }
1573
1574 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1575 {
1576 if (widget != WID_GRAPH_MATRIX) {
1577 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1578 return;
1579 }
1580
1581 const Industry *i = Industry::Get(this->window_number);
1582 const CargoSpec *cs;
1583 for (const auto &p : i->produced) {
1584 if (!IsValidCargoType(p.cargo)) continue;
1585
1586 cs = CargoSpec::Get(p.cargo);
1587 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1588 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1591 size = maxdim(d, size);
1592 }
1593
1594 this->line_height = size.height;
1595 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1596 resize.width = 0;
1597 resize.height = this->line_height;
1598 }
1599
1600 void DrawWidget(const Rect &r, WidgetID widget) const override
1601 {
1602 if (widget != WID_GRAPH_MATRIX) {
1603 BaseGraphWindow::DrawWidget(r, widget);
1604 return;
1605 }
1606
1607 bool rtl = _current_text_dir == TD_RTL;
1608
1609 int pos = this->vscroll->GetPosition();
1610 int max = pos + this->vscroll->GetCapacity();
1611
1612 Rect line = r.WithHeight(this->line_height);
1613 const Industry *i = Industry::Get(this->window_number);
1614 const CargoSpec *cs;
1615
1616 for (const auto &p : i->produced) {
1617 if (!IsValidCargoType(p.cargo)) continue;
1618
1619 if (pos-- > 0) continue;
1620 if (--max < 0) break;
1621
1622 cs = CargoSpec::Get(p.cargo);
1623
1624 bool lowered = !HasBit(_legend_excluded_cargo_production_history, p.cargo);
1625
1626 /* Redraw frame if lowered */
1627 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
1628
1629 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1630
1631 /* Cargo-colour box with outline */
1632 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1633 GfxFillRect(cargo, PC_BLACK);
1634 GfxFillRect(cargo.Shrink(WidgetDimensions::scaled.bevel), cs->legend_colour);
1635
1636 /* Cargo name */
1637 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1638
1639 line = line.Translate(0, this->line_height);
1640 }
1641 }
1642
1643 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1644 {
1645 switch (widget) {
1647 /* Remove all cargoes from the excluded lists. */
1648 _legend_excluded_cargo_production_history = 0;
1649 this->excluded_data = 0;
1650 this->SetDirty();
1651 break;
1652
1654 /* Add all cargoes to the excluded lists. */
1655 const Industry *i = Industry::Get(this->window_number);
1656 for (const auto &p : i->produced) {
1657 if (!IsValidCargoType(p.cargo)) continue;
1658
1659 SetBit(_legend_excluded_cargo_production_history, p.cargo);
1660 SetBit(this->excluded_data, p.cargo);
1661 }
1662 this->SetDirty();
1663 break;
1664 }
1665
1666 case WID_GRAPH_MATRIX: {
1667 int row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GRAPH_MATRIX);
1668 if (row >= this->vscroll->GetCount()) return;
1669
1670 const Industry *i = Industry::Get(this->window_number);
1671 for (const auto &p : i->produced) {
1672 if (!IsValidCargoType(p.cargo)) continue;
1673 if (row-- > 0) continue;
1674
1675 ToggleBit(_legend_excluded_cargo_production_history, p.cargo);
1676 this->UpdateExcludedData();
1677 this->SetDirty();
1678 break;
1679 }
1680 break;
1681 }
1682
1683 default:
1684 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1685 break;
1686 }
1687 }
1688
1689 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1690 {
1691 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_PRODUCTION_CAPTION, this->window_number);
1692
1693 return this->Window::GetWidgetString(widget, stringid);
1694 }
1695
1696 void OnResize() override
1697 {
1698 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1699 }
1700
1701 void UpdateStatistics(bool initialize) override
1702 {
1703 CargoTypes excluded_cargo = this->excluded_data;
1704 this->UpdateExcludedData();
1705
1706 int mo = TimerGameEconomy::month - this->num_vert_lines;
1707 auto yr = TimerGameEconomy::year;
1708 while (mo < 0) {
1709 yr--;
1710 mo += 12;
1711 }
1712
1713 if (!initialize && this->excluded_data == excluded_cargo && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1714 /* There's no reason to get new stats */
1715 return;
1716 }
1717
1718 this->year = yr;
1719 this->month = mo;
1720
1721 const Industry *i = Industry::Get(this->window_number);
1722
1723 this->data.clear();
1724 for (const auto &p : i->produced) {
1725 if (!IsValidCargoType(p.cargo)) continue;
1726 const CargoSpec *cs = CargoSpec::Get(p.cargo);
1727
1728 DataSet &produced = this->data.emplace_back();
1729 produced.colour = cs->legend_colour;
1730 produced.exclude_bit = cs->Index();
1731 produced.range_bit = 0;
1732
1733 for (uint j = 0; j < GRAPH_NUM_MONTHS; j++) {
1734 produced.values[j] = p.history[GRAPH_NUM_MONTHS - j].production;
1735 }
1736
1737 DataSet &transported = this->data.emplace_back();
1738 transported.colour = cs->legend_colour;
1739 transported.exclude_bit = cs->Index();
1740 transported.range_bit = 1;
1741 transported.dash = 2;
1742
1743 for (uint j = 0; j < GRAPH_NUM_MONTHS; j++) {
1744 transported.values[j] = p.history[GRAPH_NUM_MONTHS - j].transported;
1745 }
1746 }
1747
1748 this->vscroll->SetCount(std::size(this->data));
1749
1750 this->SetDirty();
1751 }
1752};
1753
1754static constexpr NWidgetPart _nested_industry_production_widgets[] = {
1756 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1757 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_GRAPH_CAPTION),
1758 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1759 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1760 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1761 EndContainer(),
1762 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1764 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1766 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1767 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
1769 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1770 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1773 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),
1775 EndContainer(),
1776 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1777 EndContainer(),
1778 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1779 EndContainer(),
1781 NWidget(NWID_SPACER), SetMinimalSize(12, 0), SetFill(1, 0), SetResize(1, 0),
1782 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetStringTip(STR_EMPTY),
1783 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1784 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1785 EndContainer(),
1786 EndContainer(),
1787};
1788
1789static WindowDesc _industry_production_desc(
1790 WDP_AUTO, "graph_industry_production", 0, 0,
1792 {},
1793 _nested_industry_production_widgets
1794);
1795
1796void ShowIndustryProductionGraph(WindowNumber window_number)
1797{
1798 AllocateWindowDescFront<IndustryProductionGraphWindow>(_industry_production_desc, window_number);
1799}
1800
1805static std::unique_ptr<NWidgetBase> MakePerformanceDetailPanels()
1806{
1807 auto realtime = TimerGameEconomy::UsingWallclockUnits();
1808 const StringID performance_tips[] = {
1809 realtime ? STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_YEARS,
1810 STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
1811 realtime ? STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_YEARS,
1812 STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
1813 STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
1814 STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
1815 STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
1816 STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
1817 STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
1818 STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
1819 };
1820
1821 static_assert(lengthof(performance_tips) == SCORE_END - SCORE_BEGIN);
1822
1823 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
1824 for (WidgetID widnum = WID_PRD_SCORE_FIRST; widnum <= WID_PRD_SCORE_LAST; widnum++) {
1825 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
1826 panel->SetFill(1, 1);
1827 panel->SetToolTip(performance_tips[widnum - WID_PRD_SCORE_FIRST]);
1828 vert->Add(std::move(panel));
1829 }
1830 return vert;
1831}
1832
1834std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsGraphGUI()
1835{
1836 return MakeCompanyButtonRows(WID_PRD_COMPANY_FIRST, WID_PRD_COMPANY_LAST, COLOUR_BROWN, 8, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
1837}
1838
1839static constexpr NWidgetPart _nested_performance_rating_detail_widgets[] = {
1841 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1842 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1843 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1844 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1845 EndContainer(),
1846 NWidget(WWT_PANEL, COLOUR_BROWN),
1848 EndContainer(),
1850};
1851
1852static WindowDesc _performance_rating_detail_desc(
1853 WDP_AUTO, "league_details", 0, 0,
1855 {},
1856 _nested_performance_rating_detail_widgets
1857);
1858
1859void ShowPerformanceRatingDetail()
1860{
1861 AllocateWindowDescFront<PerformanceRatingDetailWindow>(_performance_rating_detail_desc, 0);
1862}
1863
1864void InitializeGraphGui()
1865{
1866 _legend_excluded_companies = CompanyMask{};
1867 _legend_excluded_cargo_payment_rates = 0;
1868 _legend_excluded_cargo_production_history = 0;
1869}
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 T ToggleBit(T &x, const uint8_t y)
Toggles a bit in a variable.
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.
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.
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.
auto GetScrolledItemFromWidget(Tcontainer &container, int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Return an iterator pointing to the element of a scrolled widget that a user clicked in.
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:2482
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:2556
size_type GetCount() const
Gets the number of elements in the list.
auto GetVisibleRangeIterators(Tcontainer &container) const
Get a pair of iterators for the range of visible elements in a container.
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:45
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 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 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
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:59
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 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.
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 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.
virtual void UpdateStatistics(bool initialize)
Update the statistics.
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.
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:82
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
Definition graph_gui.cpp:64
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void UpdateStatistics(bool initialize) override
Update the statistics.
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 OnResize() override
Called after the window got resized.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
uint line_height
Pixel height of each cargo type row.
uint legend_width
Width of legend 'blob'.
void OnInit() override
Notification that the nested widget tree gets initialized.
Scrollbar * vscroll
Cargo list scrollbar.
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.
uint line_height
Pixel height of each cargo type row.
void OnResize() override
Called after the window got resized.
void OnInit() override
Notification that the nested widget tree gets initialized.
void OnGameTick() override
Called once per (game) tick.
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 UpdatePaymentRates()
Update the payment rates according to the latest information.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
uint legend_width
Width of legend 'blob'.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
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.
Scrollbar * vscroll
Cargo list scrollbar.
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.
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 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.
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:3492
@ 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.