OpenTTD Source 20251213-master-g1091fa6071
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
10#include "stdafx.h"
11#include <ranges>
12#include "misc/history_func.hpp"
13#include "graph_gui.h"
14#include "window_gui.h"
15#include "company_base.h"
16#include "company_gui.h"
17#include "economy_func.h"
18#include "cargotype.h"
19#include "strings_func.h"
20#include "window_func.h"
21#include "sound_func.h"
22#include "gfx_func.h"
24#include "currency.h"
25#include "timer/timer.h"
26#include "timer/timer_window.h"
29#include "zoom_func.h"
30#include "industry.h"
31#include "town.h"
32
34
35#include "table/strings.h"
36#include "table/sprites.h"
37
38#include "safeguards.h"
39
40/* Bitmasks of company and cargo indices that shouldn't be drawn. */
41static CompanyMask _legend_excluded_companies;
42
43/* Apparently these don't play well with enums. */
44static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
45static const uint INVALID_DATAPOINT_POS = UINT_MAX; // Used to determine if the previous point was drawn.
46
47constexpr double INT64_MAX_IN_DOUBLE = static_cast<double>(INT64_MAX - 512);
48static_assert(static_cast<int64_t>(INT64_MAX_IN_DOUBLE) < INT64_MAX);
49
50/****************/
51/* GRAPH LEGEND */
52/****************/
53
56 {
57 this->InitNested(window_number);
58
59 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
60 if (!_legend_excluded_companies.Test(c)) this->LowerWidget(WID_GL_FIRST_COMPANY + c);
61
62 this->OnInvalidateData(c.base());
63 }
64 }
65
66 void DrawWidget(const Rect &r, WidgetID widget) const override
67 {
68 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
69
71
72 if (!Company::IsValidID(cid)) return;
73
74 bool rtl = _current_text_dir == TD_RTL;
75
76 const Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
77 Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
78 DrawCompanyIcon(cid, rtl ? ir.right - d.width : ir.left, CentreBounds(ir.top, ir.bottom, d.height));
79
80 const Rect tr = ir.Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl);
81 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);
82 }
83
84 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
85 {
86 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
87
88 _legend_excluded_companies.Flip(static_cast<CompanyID>(widget - WID_GL_FIRST_COMPANY));
89 this->ToggleWidgetLoweredState(widget);
90 this->SetDirty();
96
98 }
99
105 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
106 {
107 if (!gui_scope) return;
108 if (Company::IsValidID(data)) return;
109
110 _legend_excluded_companies.Set(static_cast<CompanyID>(data));
111 this->RaiseWidget(data + WID_GL_FIRST_COMPANY);
112 }
113};
114
119static std::unique_ptr<NWidgetBase> MakeNWidgetCompanyLines()
120{
121 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
122 vert->SetPadding(2, 2, 2, 2);
123 uint sprite_height = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZoomLevel::Normal).height;
124
125 for (WidgetID widnum = WID_GL_FIRST_COMPANY; widnum <= WID_GL_LAST_COMPANY; widnum++) {
126 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
127 panel->SetMinimalSize(246, sprite_height + WidgetDimensions::unscaled.framerect.Vertical());
128 panel->SetMinimalTextLines(1, WidgetDimensions::unscaled.framerect.Vertical(), FS_NORMAL);
129 panel->SetFill(1, 1);
130 panel->SetToolTip(STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
131 vert->Add(std::move(panel));
132 }
133 return vert;
134}
135
136static constexpr std::initializer_list<NWidgetPart> _nested_graph_legend_widgets = {
138 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
139 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_KEY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
140 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
141 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
142 EndContainer(),
143 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GL_BACKGROUND),
145 EndContainer(),
146};
147
148static WindowDesc _graph_legend_desc(
149 WDP_AUTO, "graph_legend", 0, 0,
151 {},
152 _nested_graph_legend_widgets
153);
154
155static void ShowGraphLegend()
156{
157 AllocateWindowDescFront<GraphLegendWindow>(_graph_legend_desc, 0);
158}
159
165
166/******************/
167/* BASE OF GRAPHS */
168/*****************/
169
171protected:
172 static const int GRAPH_MAX_DATASETS = 64;
173 static constexpr PixelColour GRAPH_BASE_COLOUR = GREY_SCALE(2);
174 static constexpr PixelColour GRAPH_GRID_COLOUR = GREY_SCALE(3);
175 static constexpr PixelColour GRAPH_AXIS_LINE_COLOUR = GREY_SCALE(1);
176 static constexpr PixelColour GRAPH_ZERO_LINE_COLOUR = GREY_SCALE(8);
177 static constexpr PixelColour GRAPH_YEAR_LINE_COLOUR = GREY_SCALE(5);
178 static const int GRAPH_NUM_MONTHS = 24;
179 static const int GRAPH_PAYMENT_RATE_STEPS = 20;
180 static const int PAYMENT_GRAPH_X_STEP_DAYS = 10;
181 static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20;
182 static const int ECONOMY_YEAR_MINUTES = 12;
183 static const int ECONOMY_QUARTER_MINUTES = 3;
184 static const int ECONOMY_MONTH_MINUTES = 1;
185
186 static const TextColour GRAPH_AXIS_LABEL_COLOUR = TC_BLACK;
187
188 static const int MIN_GRAPH_NUM_LINES_Y = 9;
189 static const int MIN_GRID_PIXEL_SIZE = 20;
190
191 struct GraphScale {
192 StringID label = STR_NULL;
193 uint8_t month_increment = 0;
194 int16_t x_values_increment = 0;
195 const HistoryRange *history_range = nullptr;
196 };
197
198 static inline constexpr GraphScale MONTHLY_SCALE_WALLCLOCK[] = {
199 {STR_GRAPH_LAST_24_MINUTES_TIME_LABEL, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
200 {STR_GRAPH_LAST_72_MINUTES_TIME_LABEL, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
201 {STR_GRAPH_LAST_288_MINUTES_TIME_LABEL, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
202 };
203
204 static inline constexpr GraphScale MONTHLY_SCALE_CALENDAR[] = {
205 {STR_GRAPH_LAST_24_MONTHS, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
206 {STR_GRAPH_LAST_24_QUARTERS, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
207 {STR_GRAPH_LAST_24_YEARS, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
208 };
209
210 uint64_t excluded_data = 0;
211 uint64_t excluded_range = 0;
212 uint64_t masked_range = 0;
213 uint8_t num_on_x_axis = 0;
214 uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
215
216 /* The starting month and year that values are plotted against. */
219 uint8_t month_increment = 3;
220
221 bool draw_dates = true;
222
223 /* These values are used if the graph is being plotted against values
224 * rather than the dates specified by month and year. */
225 bool x_values_reversed = true;
226 int16_t x_values_increment = ECONOMY_QUARTER_MINUTES;
227
228 StringID format_str_y_axis{};
229
230 struct DataSet {
231 std::array<OverflowSafeInt64, GRAPH_NUM_MONTHS> values;
232 PixelColour colour;
233 uint8_t exclude_bit;
234 uint8_t range_bit;
235 uint8_t dash;
236 };
237 std::vector<DataSet> data{};
238
239 std::span<const StringID> ranges{};
240 std::span<const GraphScale> scales{};
241 uint8_t selected_scale = 0;
242
243 uint8_t highlight_data = UINT8_MAX;
244 uint8_t highlight_range = UINT8_MAX;
245 bool highlight_state = false;
246
247 struct BaseFiller {
249
250 inline void MakeZero(uint i) const { this->dataset.values[i] = 0; }
251 inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
252 };
253
254 template <typename Tprojection>
256 Tprojection proj;
257
258 inline void Fill(uint i, const auto &data) const { this->dataset.values[i] = std::invoke(this->proj, data); }
259 };
260
266 std::span<const OverflowSafeInt64> GetDataSetRange(const DataSet &dataset) const
267 {
268 return {std::begin(dataset.values), std::begin(dataset.values) + this->num_on_x_axis};
269 }
270
277 ValuesInterval GetValuesInterval(int num_hori_lines) const
278 {
279 assert(num_hori_lines > 0);
280
281 ValuesInterval current_interval;
282 current_interval.highest = INT64_MIN;
283 current_interval.lowest = INT64_MAX;
284
285 for (const DataSet &dataset : this->data) {
286 if (HasBit(this->excluded_data, dataset.exclude_bit)) continue;
287 if (HasBit(this->excluded_range, dataset.range_bit)) continue;
288
289 for (const OverflowSafeInt64 &datapoint : this->GetDataSetRange(dataset)) {
290 if (datapoint != INVALID_DATAPOINT) {
291 current_interval.highest = std::max(current_interval.highest, datapoint);
292 current_interval.lowest = std::min(current_interval.lowest, datapoint);
293 }
294 }
295 }
296
297 /* Always include zero in the shown range. */
298 double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest);
299 double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest;
300
301 /* Prevent showing values too close to the graph limits. */
302 abs_higher = (11.0 * abs_higher) / 10.0;
303 abs_lower = (11.0 * abs_lower) / 10.0;
304
305 int num_pos_grids;
306 OverflowSafeInt64 grid_size;
307
308 if (abs_lower != 0 || abs_higher != 0) {
309 /* The number of grids to reserve for the positive part is: */
310 num_pos_grids = (int)floor(0.5 + num_hori_lines * abs_higher / (abs_higher + abs_lower));
311
312 /* If there are any positive or negative values, force that they have at least one grid. */
313 if (num_pos_grids == 0 && abs_higher != 0) num_pos_grids++;
314 if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--;
315
316 /* Get the required grid size for each side and use the maximum one. */
317
318 OverflowSafeInt64 grid_size_higher = 0;
319 if (abs_higher > 0) {
320 grid_size_higher = abs_higher > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_higher);
321 grid_size_higher = (grid_size_higher + num_pos_grids - 1) / num_pos_grids;
322 }
323
324 OverflowSafeInt64 grid_size_lower = 0;
325 if (abs_lower > 0) {
326 grid_size_lower = abs_lower > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_lower);
327 grid_size_lower = (grid_size_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids);
328 }
329
330 grid_size = std::max(grid_size_higher, grid_size_lower);
331 } else {
332 /* If both values are zero, show an empty graph. */
333 num_pos_grids = num_hori_lines / 2;
334 grid_size = 1;
335 }
336
337 current_interval.highest = num_pos_grids * grid_size;
338 current_interval.lowest = -(num_hori_lines - num_pos_grids) * grid_size;
339 return current_interval;
340 }
341
347 uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
348 {
349 /* draw text strings on the y axis */
350 int64_t y_label = current_interval.highest;
351 int64_t y_label_separation = (current_interval.highest - current_interval.lowest) / num_hori_lines;
352
353 uint max_width = 0;
354
355 for (int i = 0; i < (num_hori_lines + 1); i++) {
356 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label));
357 if (d.width > max_width) max_width = d.width;
358
359 y_label -= y_label_separation;
360 }
361
362 return max_width;
363 }
364
369 void DrawGraph(Rect r) const
370 {
371 uint x, y;
372 ValuesInterval interval;
373 int x_axis_offset;
374
375 /* the colours and cost array of GraphDrawer must accommodate
376 * both values for cargo and companies. So if any are higher, quit */
377 static_assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
378 assert(this->num_vert_lines > 0);
379
380 bool rtl = _current_text_dir == TD_RTL;
381
382 /* Rect r will be adjusted to contain just the graph, with labels being
383 * placed outside the area. */
384 r.top += ScaleGUITrad(5) + GetCharacterHeight(FS_SMALL) / 2;
385 r.bottom -= (this->draw_dates ? 2 : 1) * GetCharacterHeight(FS_SMALL) + ScaleGUITrad(4);
386 r.left += ScaleGUITrad(rtl ? 5 : 9);
387 r.right -= ScaleGUITrad(rtl ? 9 : 5);
388
389 /* Initial number of horizontal lines. */
390 int num_hori_lines = 160 / ScaleGUITrad(MIN_GRID_PIXEL_SIZE);
391 /* For the rest of the height, the number of horizontal lines will increase more slowly. */
392 int resize = (r.bottom - r.top - 160) / (2 * ScaleGUITrad(MIN_GRID_PIXEL_SIZE));
393 if (resize > 0) num_hori_lines += resize;
394
395 interval = GetValuesInterval(num_hori_lines);
396
397 int label_width = GetYLabelWidth(interval, num_hori_lines);
398
399 if (rtl) {
400 r.right -= label_width;
401 } else {
402 r.left += label_width;
403 }
404
405 int x_sep = (r.right - r.left) / this->num_vert_lines;
406 int y_sep = (r.bottom - r.top) / num_hori_lines;
407
408 /* Redetermine right and bottom edge of graph to fit with the integer
409 * separation values. */
410 if (rtl) {
411 r.left = r.right - x_sep * this->num_vert_lines;
412 } else {
413 r.right = r.left + x_sep * this->num_vert_lines;
414 }
415 r.bottom = r.top + y_sep * num_hori_lines;
416
417 OverflowSafeInt64 interval_size = interval.highest + abs(interval.lowest);
418 /* Where to draw the X axis. Use floating point to avoid overflowing and results of zero. */
419 x_axis_offset = (int)((r.bottom - r.top) * (double)interval.highest / (double)interval_size);
420
421 /* Draw the background of the graph itself. */
422 GfxFillRect(r.left, r.top, r.right, r.bottom, GRAPH_BASE_COLOUR);
423
424 /* Draw the grid lines. */
425 int gridline_width = WidgetDimensions::scaled.bevel.top;
426 PixelColour grid_colour = GRAPH_GRID_COLOUR;
427
428 /* Don't draw the first line, as that's where the axis will be. */
429 if (rtl) {
430 x_sep = -x_sep;
431 x = r.right + x_sep;
432 } else {
433 x = r.left + x_sep;
434 }
435
436 for (int i = 1; i < this->num_vert_lines + 1; i++) {
437 /* If using wallclock units, we separate periods with a lighter line. */
439 grid_colour = (i % 4 == 0) ? GRAPH_YEAR_LINE_COLOUR : GRAPH_GRID_COLOUR;
440 }
441 GfxFillRect(x, r.top, x + gridline_width - 1, r.bottom, grid_colour);
442 x += x_sep;
443 }
444
445 /* Draw the horizontal grid lines. */
446 y = r.bottom;
447
448 for (int i = 0; i < (num_hori_lines + 1); i++) {
449 if (rtl) {
450 GfxFillRect(r.right + 1, y, r.right + ScaleGUITrad(3), y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
451 } else {
452 GfxFillRect(r.left - ScaleGUITrad(3), y, r.left - 1, y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
453 }
454 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_GRID_COLOUR);
455 y -= y_sep;
456 }
457
458 /* Draw the y axis. */
459 GfxFillRect(r.left, r.top, r.left + gridline_width - 1, r.bottom + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
460
461 /* Draw the x axis. */
462 y = x_axis_offset + r.top;
463 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_ZERO_LINE_COLOUR);
464
465 /* Find the largest value that will be drawn. */
466 if (this->num_on_x_axis == 0) return;
467
468 assert(this->num_on_x_axis > 0);
469
470 /* draw text strings on the y axis */
471 int64_t y_label = interval.highest;
472 int64_t y_label_separation = abs(interval.highest - interval.lowest) / num_hori_lines;
473
474 y = r.top - GetCharacterHeight(FS_SMALL) / 2;
475
476 for (int i = 0; i < (num_hori_lines + 1); i++) {
477 if (rtl) {
478 DrawString(r.right + ScaleGUITrad(4), r.right + label_width + ScaleGUITrad(4), y,
479 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
481 } else {
482 DrawString(r.left - label_width - ScaleGUITrad(4), r.left - ScaleGUITrad(4), y,
483 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
485 }
486
487 y_label -= y_label_separation;
488 y += y_sep;
489 }
490
491 x = rtl ? r.right : r.left;
492 y = r.bottom + ScaleGUITrad(2);
493
494 /* if there are not enough datapoints to fill the graph, align to the right */
495 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
496
497 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
498 if (this->draw_dates) {
499 TimerGameEconomy::Month month = this->month;
500 TimerGameEconomy::Year year = this->year;
501 for (int i = 0; i < this->num_on_x_axis; i++) {
502 if (rtl) {
503 DrawStringMultiLineWithClipping(x + x_sep, x, y, this->height,
504 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
506 } else {
507 DrawStringMultiLineWithClipping(x, x + x_sep, y, this->height,
508 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
510 }
511
512 month += this->month_increment;
513 if (month >= 12) {
514 month = 0;
515 year++;
516
517 /* Draw a lighter grid line between years. Top and bottom adjustments ensure we don't draw over top and bottom horizontal grid lines. */
518 GfxFillRect(x + x_sep, r.top + gridline_width, x + x_sep + gridline_width - 1, r.bottom - 1, GRAPH_YEAR_LINE_COLOUR);
519 }
520 x += x_sep;
521 }
522 } else {
523 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates, and all graphs when using wallclock units). */
524 int16_t iterator;
525 uint16_t label;
526 if (this->x_values_reversed) {
527 label = this->x_values_increment * this->num_on_x_axis;
528 iterator = -this->x_values_increment;
529 } else {
530 label = this->x_values_increment;
531 iterator = this->x_values_increment;
532 }
533
534 for (int i = 0; i < this->num_on_x_axis; i++) {
535 if (rtl) {
536 DrawString(x + x_sep + 1, x - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
537 } else {
538 DrawString(x + 1, x + x_sep - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
539 }
540
541 label += iterator;
542 x += x_sep;
543 }
544 }
545
546 /* Draw lines and dots. */
549 uint pointoffs1 = pointwidth / 2;
550 uint pointoffs2 = pointwidth - pointoffs1;
551
552 auto draw_dataset = [&](const DataSet &dataset, PixelColour colour) {
553 if (HasBit(this->excluded_data, dataset.exclude_bit)) return;
554 if (HasBit(this->excluded_range, dataset.range_bit)) return;
555
556 /* Centre the dot between the grid lines. */
557 if (rtl) {
558 x = r.right + (x_sep / 2);
559 } else {
560 x = r.left + (x_sep / 2);
561 }
562
563 /* if there are not enough datapoints to fill the graph, align to the right */
564 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
565
566 uint prev_x = INVALID_DATAPOINT_POS;
567 uint prev_y = INVALID_DATAPOINT_POS;
568
569 const uint dash = ScaleGUITrad(dataset.dash);
570 for (OverflowSafeInt64 datapoint : this->GetDataSetRange(dataset)) {
571 if (datapoint != INVALID_DATAPOINT) {
572 /*
573 * Check whether we need to reduce the 'accuracy' of the
574 * datapoint value and the highest value to split overflows.
575 * And when 'drawing' 'one million' or 'one million and one'
576 * there is no significant difference, so the least
577 * significant bits can just be removed.
578 *
579 * If there are more bits needed than would fit in a 32 bits
580 * integer, so at about 31 bits because of the sign bit, the
581 * least significant bits are removed.
582 */
583 int mult_range = FindLastBit<uint32_t>(x_axis_offset) + FindLastBit<uint64_t>(abs(datapoint));
584 int reduce_range = std::max(mult_range - 31, 0);
585
586 /* Handle negative values differently (don't shift sign) */
587 if (datapoint < 0) {
588 datapoint = -(abs(datapoint) >> reduce_range);
589 } else {
590 datapoint >>= reduce_range;
591 }
592 y = r.top + x_axis_offset - ((r.bottom - r.top) * datapoint) / (interval_size >> reduce_range);
593
594 /* Draw the point. */
595 GfxFillRect(x - pointoffs1, y - pointoffs1, x + pointoffs2, y + pointoffs2, colour);
596
597 /* Draw the line connected to the previous point. */
598 if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, colour, linewidth, dash);
599
600 prev_x = x;
601 prev_y = y;
602 } else {
603 prev_x = INVALID_DATAPOINT_POS;
604 prev_y = INVALID_DATAPOINT_POS;
605 }
606
607 x += x_sep;
608 }
609 };
610
611 /* Draw unhighlighted datasets. */
612 for (const DataSet &dataset : this->data) {
613 if (dataset.exclude_bit != this->highlight_data && dataset.range_bit != this->highlight_range) {
614 draw_dataset(dataset, dataset.colour);
615 }
616 }
617
618 /* If any dataset or range is highlighted, draw separately after the rest so they appear on top of all other
619 * data. Highlighted data is only drawn when highlight_state is set, otherwise it is invisible. */
620 if (this->highlight_state && (this->highlight_data != UINT8_MAX || this->highlight_range != UINT8_MAX)) {
621 for (const DataSet &dataset : this->data) {
622 if (dataset.exclude_bit == this->highlight_data || dataset.range_bit == this->highlight_range) {
623 draw_dataset(dataset, PC_WHITE);
624 }
625 }
626 }
627 }
628
629 BaseGraphWindow(WindowDesc &desc, StringID format_str_y_axis) :
630 Window(desc),
631 format_str_y_axis(format_str_y_axis)
632 {
634 }
635
636 const IntervalTimer<TimerWindow> blink_interval = {TIMER_BLINK_INTERVAL, [this](auto) {
637 /* If nothing is highlighted then no redraw is needed. */
638 if (this->highlight_data == UINT8_MAX && this->highlight_range == UINT8_MAX) return;
639
640 /* Toggle the highlight state and redraw. */
641 this->highlight_state = !this->highlight_state;
642 this->SetDirty();
643 }};
644
645 void UpdateMatrixSize(WidgetID widget, Dimension &size, Dimension &resize, auto labels)
646 {
647 size = {};
648 for (const StringID &str : labels) {
649 size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
650 }
651
654
655 /* Set fixed height for number of ranges. */
656 size.height *= static_cast<uint>(std::size(labels));
657
658 resize.width = 0;
659 resize.height = 0;
660 this->GetWidget<NWidgetCore>(widget)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(labels)));
661 }
662
663public:
664 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
665 {
666 switch (widget) {
668 this->UpdateMatrixSize(widget, size, resize, this->ranges);
669 break;
670
672 this->UpdateMatrixSize(widget, size, resize, this->scales | std::views::transform(&GraphScale::label));
673 break;
674
675 case WID_GRAPH_GRAPH: {
676 uint x_label_width = 0;
677
678 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
679 if (this->draw_dates) {
680 uint year = GetParamMaxValue(this->year.base(), 4, FS_SMALL);
681 for (uint month = 0; month < 12; ++month) {
682 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);
683 }
684 } else {
685 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates). */
686 uint64_t max_value = GetParamMaxValue((this->num_on_x_axis + 1) * this->x_values_increment, 0, FS_SMALL);
687 x_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL_NUMBER, max_value)).width;
688 }
689
690 uint y_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, INT64_MAX)).width;
691
692 size.width = std::max<uint>(size.width, ScaleGUITrad(5) + y_label_width + this->num_vert_lines * (x_label_width + ScaleGUITrad(5)) + ScaleGUITrad(9));
693 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));
694 size.height = std::max<uint>(size.height, size.width / 3);
695 break;
696 }
697
698 default: break;
699 }
700 }
701
702 void DrawWidget(const Rect &r, WidgetID widget) const override
703 {
704 switch (widget) {
705 case WID_GRAPH_GRAPH:
706 this->DrawGraph(r);
707 break;
708
711 uint index = 0;
712 Rect line = r.WithHeight(line_height);
713 for (const auto &str : this->ranges) {
714 bool lowered = !HasBit(this->excluded_range, index) && !HasBit(this->masked_range, index);
715
716 /* Redraw frame if lowered */
717 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
718
719 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
720 DrawString(text, str, (this->highlight_state && this->highlight_range == index) ? TC_WHITE : TC_BLACK, SA_CENTER, false, FS_SMALL);
721
722 if (HasBit(this->masked_range, index)) {
724 }
725
726 line = line.Translate(0, line_height);
727 ++index;
728 }
729 break;
730 }
731
734 uint8_t selected_month_increment = this->scales[this->selected_scale].month_increment;
735 Rect line = r.WithHeight(line_height);
736 for (const auto &scale : this->scales) {
737 /* Redraw frame if selected */
738 if (selected_month_increment == scale.month_increment) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
739
741
742 line = line.Translate(0, line_height);
743 }
744 break;
745 }
746
747 default: break;
748 }
749 }
750
751 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
752 {
753 /* Clicked on legend? */
754 switch (widget) {
756 ShowGraphLegend();
757 break;
758
760 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
761
762 if (HasBit(this->masked_range, row)) break;
763 ToggleBit(this->excluded_range, row);
764 SndClickBeep();
765 this->SetDirty();
766 break;
767 }
768
770 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
771 const auto &scale = this->scales[row];
772 if (this->selected_scale != row) {
773 this->selected_scale = row;
774 this->month_increment = scale.month_increment;
775 this->x_values_increment = scale.x_values_increment;
776 this->InvalidateData();
777 }
778 break;
779 }
780
781 default: break;
782 }
783 }
784
785 void OnMouseOver(Point pt, WidgetID widget) override
786 {
787 /* Test if a range should be highlighted. */
788 uint8_t new_highlight_range = UINT8_MAX;
789 if (widget == WID_GRAPH_RANGE_MATRIX) {
790 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
791 if (!HasBit(this->excluded_range, row)) new_highlight_range = static_cast<uint8_t>(row);
792 }
793
794 /* Test if a dataset should be highlighted. */
795 uint8_t new_highlight_data = UINT8_MAX;
796 if (widget == WID_GRAPH_MATRIX) {
797 auto dataset_index = this->GetDatasetIndex(pt.y);
798 if (dataset_index.has_value() && !HasBit(this->excluded_data, *dataset_index)) new_highlight_data = *dataset_index;
799 }
800
801 if (this->highlight_data == new_highlight_data && this->highlight_range == new_highlight_range) return;
802
803 /* Range or data set highlight has changed, set and redraw. */
804 this->highlight_data = new_highlight_data;
805 this->highlight_range = new_highlight_range;
806 this->highlight_state = true;
807 this->SetDirty();
808 }
809
810 void OnGameTick() override
811 {
812 this->UpdateStatistics(false);
813 }
814
820 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
821 {
822 if (!gui_scope) return;
823 this->UpdateStatistics(true);
824 }
825
826 virtual void UpdateStatistics(bool initialize) = 0;
827
828 virtual std::optional<uint8_t> GetDatasetIndex(int) { return std::nullopt; }
829};
830
832public:
833 BaseCompanyGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
834
835 void InitializeWindow(WindowNumber number)
836 {
837 /* Initialise the dataset */
838 this->UpdateStatistics(true);
839
840 this->CreateNestedTree();
841
842 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
843 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_LAST_72_MINUTES_TIME_LABEL : STR_EMPTY);
844
845 this->FinishInitNested(number);
846 }
847
852 void UpdateStatistics(bool initialize) override
853 {
854 CompanyMask excluded_companies = _legend_excluded_companies;
855
856 /* Exclude the companies which aren't valid */
857 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
858 if (!Company::IsValidID(c)) excluded_companies.Set(c);
859 }
860
861 uint8_t nums = 0;
862 for (const Company *c : Company::Iterate()) {
863 nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent));
864 }
865
866 int mo = (TimerGameEconomy::month / this->month_increment - nums) * this->month_increment;
867 auto yr = TimerGameEconomy::year;
868 while (mo < 0) {
869 yr--;
870 mo += 12;
871 }
872
873 if (!initialize && this->excluded_data == excluded_companies.base() && this->num_on_x_axis == nums &&
874 this->year == yr && this->month == mo) {
875 /* There's no reason to get new stats */
876 return;
877 }
878
879 this->excluded_data = excluded_companies.base();
880 this->num_on_x_axis = nums;
881 this->year = yr;
882 this->month = mo;
883
884 this->data.clear();
885 for (CompanyID k = CompanyID::Begin(); k < MAX_COMPANIES; ++k) {
886 const Company *c = Company::GetIfValid(k);
887 if (c == nullptr) continue;
888
889 DataSet &dataset = this->data.emplace_back();
890 dataset.colour = GetColourGradient(c->colour, SHADE_LIGHTER);
891 dataset.exclude_bit = k.base();
892
893 for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
894 if (j >= c->num_valid_stat_ent) {
895 dataset.values[i] = INVALID_DATAPOINT;
896 } else {
897 /* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
898 * Instead, use the value just under it. Hopefully nobody will notice. */
899 dataset.values[i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
900 }
901 i++;
902 }
903 }
904 }
905
906 virtual OverflowSafeInt64 GetGraphData(const Company *, int) = 0;
907};
908
909
910/********************/
911/* OPERATING PROFIT */
912/********************/
913
916 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
917 {
918 this->num_on_x_axis = GRAPH_NUM_MONTHS;
919 this->num_vert_lines = GRAPH_NUM_MONTHS;
920 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
921
922 this->InitializeWindow(window_number);
923 }
924
925 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
926 {
927 return c->old_economy[j].income + c->old_economy[j].expenses;
928 }
929};
930
931static constexpr std::initializer_list<NWidgetPart> _nested_operating_profit_widgets = {
933 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
934 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
935 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
936 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
937 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
938 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
939 EndContainer(),
940 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
941 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 160), SetFill(1, 1), SetResize(1, 1),
943 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),
944 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
945 EndContainer(),
946 EndContainer(),
947};
948
949static WindowDesc _operating_profit_desc(
950 WDP_AUTO, "graph_operating_profit", 0, 0,
952 {},
953 _nested_operating_profit_widgets
954);
955
956
957void ShowOperatingProfitGraph()
958{
959 AllocateWindowDescFront<OperatingProfitGraphWindow>(_operating_profit_desc, 0);
960}
961
962
963/****************/
964/* INCOME GRAPH */
965/****************/
966
969 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
970 {
971 this->num_on_x_axis = GRAPH_NUM_MONTHS;
972 this->num_vert_lines = GRAPH_NUM_MONTHS;
973 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
974
975 this->InitializeWindow(window_number);
976 }
977
978 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
979 {
980 return c->old_economy[j].income;
981 }
982};
983
984static constexpr std::initializer_list<NWidgetPart> _nested_income_graph_widgets = {
986 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
987 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
988 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
989 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
990 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
991 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
992 EndContainer(),
993 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
994 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
996 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),
997 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
998 EndContainer(),
999 EndContainer(),
1000};
1001
1002static WindowDesc _income_graph_desc(
1003 WDP_AUTO, "graph_income", 0, 0,
1005 {},
1006 _nested_income_graph_widgets
1007);
1008
1009void ShowIncomeGraph()
1010{
1011 AllocateWindowDescFront<IncomeGraphWindow>(_income_graph_desc, 0);
1012}
1013
1014/*******************/
1015/* DELIVERED CARGO */
1016/*******************/
1017
1020 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1021 {
1022 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1023 this->num_vert_lines = GRAPH_NUM_MONTHS;
1024 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1025
1026 this->InitializeWindow(window_number);
1027 }
1028
1029 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1030 {
1031 return c->old_economy[j].delivered_cargo.GetSum<OverflowSafeInt64>();
1032 }
1033};
1034
1035static constexpr std::initializer_list<NWidgetPart> _nested_delivered_cargo_graph_widgets = {
1037 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1038 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1039 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1040 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1041 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1042 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1043 EndContainer(),
1044 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1045 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
1047 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),
1048 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1049 EndContainer(),
1050 EndContainer(),
1051};
1052
1053static WindowDesc _delivered_cargo_graph_desc(
1054 WDP_AUTO, "graph_delivered_cargo", 0, 0,
1056 {},
1057 _nested_delivered_cargo_graph_widgets
1058);
1059
1060void ShowDeliveredCargoGraph()
1061{
1062 AllocateWindowDescFront<DeliveredCargoGraphWindow>(_delivered_cargo_graph_desc, 0);
1063}
1064
1065/***********************/
1066/* PERFORMANCE HISTORY */
1067/***********************/
1068
1071 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1072 {
1073 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1074 this->num_vert_lines = GRAPH_NUM_MONTHS;
1075 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1076
1077 this->InitializeWindow(window_number);
1078 }
1079
1080 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1081 {
1082 return c->old_economy[j].performance_history;
1083 }
1084
1085 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1086 {
1087 if (widget == WID_PHG_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
1088 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1089 }
1090};
1091
1092static constexpr std::initializer_list<NWidgetPart> _nested_performance_history_widgets = {
1094 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1095 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1096 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_PHG_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetStringTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
1097 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1098 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1099 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1100 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1101 EndContainer(),
1102 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1103 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1105 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),
1106 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1107 EndContainer(),
1108 EndContainer(),
1109};
1110
1111static WindowDesc _performance_history_desc(
1112 WDP_AUTO, "graph_performance", 0, 0,
1114 {},
1115 _nested_performance_history_widgets
1116);
1117
1118void ShowPerformanceHistoryGraph()
1119{
1120 AllocateWindowDescFront<PerformanceHistoryGraphWindow>(_performance_history_desc, 0);
1121}
1122
1123/*****************/
1124/* COMPANY VALUE */
1125/*****************/
1126
1129 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1130 {
1131 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1132 this->num_vert_lines = GRAPH_NUM_MONTHS;
1133 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1134
1135 this->InitializeWindow(window_number);
1136 }
1137
1138 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1139 {
1140 return c->old_economy[j].company_value;
1141 }
1142};
1143
1144static constexpr std::initializer_list<NWidgetPart> _nested_company_value_graph_widgets = {
1146 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1147 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1148 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1149 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1150 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1151 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1152 EndContainer(),
1153 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1154 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1156 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),
1157 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1158 EndContainer(),
1159 EndContainer(),
1160};
1161
1162static WindowDesc _company_value_graph_desc(
1163 WDP_AUTO, "graph_company_value", 0, 0,
1165 {},
1166 _nested_company_value_graph_widgets
1167);
1168
1169void ShowCompanyValueGraph()
1170{
1171 AllocateWindowDescFront<CompanyValueGraphWindow>(_company_value_graph_desc, 0);
1172}
1173
1175 Scrollbar *vscroll = nullptr;
1176 uint line_height = 0;
1177 uint legend_width = 0;
1178
1179 CargoTypes cargo_types{};
1180
1181 BaseCargoGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
1182
1183 void InitializeWindow(WindowNumber number, StringID footer_wallclock = STR_NULL, StringID footer_calendar = STR_NULL)
1184 {
1185 this->CreateNestedTree();
1186
1187 this->excluded_range = this->masked_range;
1188 this->cargo_types = this->GetCargoTypes(number);
1189
1190 this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
1191 this->vscroll->SetCount(CountBits(this->cargo_types));
1192
1193 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1194 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? footer_wallclock : footer_calendar);
1195
1196 this->FinishInitNested(number);
1197
1198 /* Initialise the dataset */
1199 this->InvalidateData();
1200 }
1201
1202 virtual CargoTypes GetCargoTypes(WindowNumber number) const = 0;
1203 virtual CargoTypes &GetExcludedCargoTypes() const = 0;
1204
1205 std::optional<uint8_t> GetDatasetIndex(int y) override
1206 {
1207 int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_GRAPH_MATRIX);
1208 if (row >= this->vscroll->GetCount()) return std::nullopt;
1209
1210 for (const CargoSpec *cs : _sorted_cargo_specs) {
1211 if (!HasBit(this->cargo_types, cs->Index())) continue;
1212 if (row-- > 0) continue;
1213
1214 return cs->Index();
1215 }
1216
1217 return std::nullopt;
1218 }
1219
1220 void OnInit() override
1221 {
1222 /* Width of the legend blob. */
1223 this->legend_width = GetCharacterHeight(FS_SMALL) * 9 / 6;
1224 }
1225
1226 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1227 {
1228 if (widget != WID_GRAPH_MATRIX) {
1229 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1230 return;
1231 }
1232
1234
1235 for (CargoType cargo_type : SetCargoBitIterator(this->cargo_types)) {
1236 const CargoSpec *cs = CargoSpec::Get(cargo_type);
1237
1238 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1239 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1242 size = maxdim(d, size);
1243 }
1244
1245 this->line_height = size.height;
1246 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1247 resize.width = 0;
1248 fill.height = resize.height = this->line_height;
1249 }
1250
1251 void DrawWidget(const Rect &r, WidgetID widget) const override
1252 {
1253 if (widget != WID_GRAPH_MATRIX) {
1254 BaseGraphWindow::DrawWidget(r, widget);
1255 return;
1256 }
1257
1258 bool rtl = _current_text_dir == TD_RTL;
1259
1260 int pos = this->vscroll->GetPosition();
1261 int max = pos + this->vscroll->GetCapacity();
1262
1263 Rect line = r.WithHeight(this->line_height);
1264
1265 for (const CargoSpec *cs : _sorted_cargo_specs) {
1266 if (!HasBit(this->cargo_types, cs->Index())) continue;
1267
1268 if (pos-- > 0) continue;
1269 if (--max < 0) break;
1270
1271 bool lowered = !HasBit(this->excluded_data, cs->Index());
1272
1273 /* Redraw frame if lowered */
1274 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
1275
1276 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1277
1278 /* Cargo-colour box with outline */
1279 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1280 GfxFillRect(cargo, PC_BLACK);
1281 PixelColour pc = cs->legend_colour;
1282 if (this->highlight_data == cs->Index()) pc = this->highlight_state ? PC_WHITE : PC_BLACK;
1284
1285 /* Cargo name */
1286 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1287
1288 line = line.Translate(0, this->line_height);
1289 }
1290 }
1291
1292 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1293 {
1294 switch (widget) {
1296 /* Remove all cargoes from the excluded lists. */
1297 this->GetExcludedCargoTypes() = {};
1298 this->excluded_data = this->GetExcludedCargoTypes();
1299 this->SetDirty();
1300 break;
1301
1303 /* Add all cargoes to the excluded lists. */
1304 this->GetExcludedCargoTypes() = this->cargo_types;
1305 this->excluded_data = this->GetExcludedCargoTypes();
1306 this->SetDirty();
1307 break;
1308 }
1309
1310 case WID_GRAPH_MATRIX: {
1311 int row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GRAPH_MATRIX);
1312 if (row >= this->vscroll->GetCount()) return;
1313
1314 SndClickBeep();
1315
1316 for (const CargoSpec *cs : _sorted_cargo_specs) {
1317 if (!HasBit(this->cargo_types, cs->Index())) continue;
1318 if (row-- > 0) continue;
1319
1320 ToggleBit(this->GetExcludedCargoTypes(), cs->Index());
1321 this->excluded_data = this->GetExcludedCargoTypes();
1322 this->SetDirty();
1323 break;
1324 }
1325 break;
1326 }
1327
1328 default:
1329 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1330 break;
1331 }
1332 }
1333
1334 void OnResize() override
1335 {
1336 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1337 }
1338};
1339
1340/*****************/
1341/* PAYMENT RATES */
1342/*****************/
1343
1345 static inline CargoTypes excluded_cargo_types{};
1346
1348 {
1349 this->num_on_x_axis = GRAPH_PAYMENT_RATE_STEPS;
1350 this->num_vert_lines = GRAPH_PAYMENT_RATE_STEPS;
1351 this->draw_dates = false;
1352
1353 this->x_values_reversed = false;
1354 /* The x-axis is labeled in either seconds or days. A day is two seconds, so we adjust the label if needed. */
1356
1357 this->InitializeWindow(window_number, STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS, STR_GRAPH_CARGO_PAYMENT_RATES_DAYS);
1358 }
1359
1360 CargoTypes GetCargoTypes(WindowNumber) const override
1361 {
1362 return _standard_cargo_mask;
1363 }
1364
1365 CargoTypes &GetExcludedCargoTypes() const override
1366 {
1367 return PaymentRatesGraphWindow::excluded_cargo_types;
1368 }
1369
1375 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1376 {
1377 if (!gui_scope) return;
1378 this->UpdatePaymentRates();
1379 }
1380
1382 const IntervalTimer<TimerWindow> update_payment_interval = {std::chrono::seconds(3), [this](auto) {
1383 this->UpdatePaymentRates();
1384 }};
1385
1386 void UpdateStatistics(bool) override {}
1387
1392 {
1393 this->excluded_data = this->GetExcludedCargoTypes();
1394
1395 this->data.clear();
1396 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1397 DataSet &dataset = this->data.emplace_back();
1398 dataset.colour = cs->legend_colour;
1399 dataset.exclude_bit = cs->Index();
1400
1401 for (uint j = 0; j != this->num_on_x_axis; j++) {
1402 dataset.values[j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
1403 }
1404 }
1405 }
1406};
1407
1408static constexpr std::initializer_list<NWidgetPart> _nested_cargo_payment_rates_widgets = {
1410 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1411 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1412 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1413 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1414 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1415 EndContainer(),
1416 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1417 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),
1419 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1421 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1422 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1423 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1426 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),
1428 EndContainer(),
1429 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1430 EndContainer(),
1431 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1432 EndContainer(),
1434 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),
1435 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1436 EndContainer(),
1437 EndContainer(),
1438};
1439
1440static WindowDesc _cargo_payment_rates_desc(
1441 WDP_AUTO, "graph_cargo_payment_rates", 0, 0,
1443 {},
1444 _nested_cargo_payment_rates_widgets
1445);
1446
1447
1448void ShowCargoPaymentRates()
1449{
1450 AllocateWindowDescFront<PaymentRatesGraphWindow>(_cargo_payment_rates_desc, 0);
1451}
1452
1453/*****************************/
1454/* PERFORMANCE RATING DETAIL */
1455/*****************************/
1456
1458 static CompanyID company;
1459 int timeout = 0;
1460 uint score_info_left = 0;
1461 uint score_info_right = 0;
1462 uint bar_left = 0;
1463 uint bar_right = 0;
1464 uint bar_width = 0;
1465 uint bar_height = 0;
1466 uint score_detail_left = 0;
1467 uint score_detail_right = 0;
1468
1470 {
1471 this->UpdateCompanyStats();
1472
1473 this->InitNested(window_number);
1474 this->OnInvalidateData(CompanyID::Invalid().base());
1475 }
1476
1477 void UpdateCompanyStats()
1478 {
1479 /* Update all company stats with the current data
1480 * (this is because _score_info is not saved to a savegame) */
1481 for (Company *c : Company::Iterate()) {
1483 }
1484
1485 this->timeout = Ticks::DAY_TICKS * 5;
1486 }
1487
1488 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1489 {
1490 switch (widget) {
1493 size.height = this->bar_height + WidgetDimensions::scaled.matrix.Vertical();
1494
1495 uint score_info_width = 0;
1496 for (uint i = SCORE_BEGIN; i < SCORE_END; i++) {
1497 score_info_width = std::max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + i).width);
1498 }
1499 score_info_width += GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(1000))).width + WidgetDimensions::scaled.hsep_wide;
1500
1501 this->bar_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_PERCENT, GetParamMaxValue(100))).width + WidgetDimensions::scaled.hsep_indent * 2; // Wide bars!
1502
1503 /* At this number we are roughly at the max; it can become wider,
1504 * but then you need at 1000 times more money. At that time you're
1505 * not that interested anymore in the last few digits anyway.
1506 * The 500 is because 999 999 500 to 999 999 999 are rounded to
1507 * 1 000 M, and not 999 999 k. Use negative numbers to account for
1508 * the negative income/amount of money etc. as well. */
1509 int max = -(999999999 - 500);
1510
1511 /* Scale max for the display currency. Prior to rendering the value
1512 * is converted into the display currency, which may cause it to
1513 * raise significantly. We need to compensate for that since {{CURRCOMPACT}}
1514 * is used, which can produce quite short renderings of very large
1515 * values. Otherwise the calculated width could be too narrow.
1516 * Note that it doesn't work if there was a currency with an exchange
1517 * rate greater than max.
1518 * When the currency rate is more than 1000, the 999 999 k becomes at
1519 * least 999 999 M which roughly is equally long. Furthermore if the
1520 * exchange rate is that high, 999 999 k is usually not enough anymore
1521 * to show the different currency numbers. */
1522 if (GetCurrency().rate < 1000) max /= GetCurrency().rate;
1523 uint score_detail_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, max, max)).width;
1524
1525 size.width = WidgetDimensions::scaled.frametext.Horizontal() + score_info_width + WidgetDimensions::scaled.hsep_wide + this->bar_width + WidgetDimensions::scaled.hsep_wide + score_detail_width;
1527 uint right = size.width - WidgetDimensions::scaled.frametext.right;
1528
1529 bool rtl = _current_text_dir == TD_RTL;
1530 this->score_info_left = rtl ? right - score_info_width : left;
1531 this->score_info_right = rtl ? right : left + score_info_width;
1532
1533 this->score_detail_left = rtl ? left : right - score_detail_width;
1534 this->score_detail_right = rtl ? left + score_detail_width : right;
1535
1536 this->bar_left = left + (rtl ? score_detail_width : score_info_width) + WidgetDimensions::scaled.hsep_wide;
1537 this->bar_right = this->bar_left + this->bar_width - 1;
1538 break;
1539 }
1540 }
1541
1542 void DrawWidget(const Rect &r, WidgetID widget) const override
1543 {
1544 /* No need to draw when there's nothing to draw */
1545 if (this->company == CompanyID::Invalid()) return;
1546
1548 if (this->IsWidgetDisabled(widget)) return;
1549 CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1550 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
1551 DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height));
1552 return;
1553 }
1554
1555 if (!IsInsideMM(widget, WID_PRD_SCORE_FIRST, WID_PRD_SCORE_LAST + 1)) return;
1556
1557 ScoreID score_type = (ScoreID)(widget - WID_PRD_SCORE_FIRST);
1558
1559 /* The colours used to show how the progress is going */
1560 PixelColour colour_done = GetColourGradient(COLOUR_GREEN, SHADE_NORMAL);
1561 PixelColour colour_notdone = GetColourGradient(COLOUR_RED, SHADE_NORMAL);
1562
1563 /* Draw all the score parts */
1564 int64_t val = _score_part[company][score_type];
1565 int64_t needed = _score_info[score_type].needed;
1566 int score = _score_info[score_type].score;
1567
1568 /* SCORE_TOTAL has its own rules ;) */
1569 if (score_type == SCORE_TOTAL) {
1570 for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) score += _score_info[i].score;
1571 needed = SCORE_MAX;
1572 }
1573
1574 uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height);
1575 uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL));
1576
1577 DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type);
1578
1579 /* Draw the score */
1580 DrawString(this->score_info_left, this->score_info_right, text_top, GetString(STR_JUST_COMMA, score), TC_BLACK, SA_RIGHT);
1581
1582 /* Calculate the %-bar */
1583 uint x = Clamp<int64_t>(val, 0, needed) * this->bar_width / needed;
1584 bool rtl = _current_text_dir == TD_RTL;
1585 if (rtl) {
1586 x = this->bar_right - x;
1587 } else {
1588 x = this->bar_left + x;
1589 }
1590
1591 /* Draw the bar */
1592 if (x != this->bar_left) GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height - 1, rtl ? colour_notdone : colour_done);
1593 if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height - 1, rtl ? colour_done : colour_notdone);
1594
1595 /* Draw it */
1596 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);
1597
1598 /* SCORE_LOAN is inverted */
1599 if (score_type == SCORE_LOAN) val = needed - val;
1600
1601 /* Draw the amount we have against what is needed
1602 * For some of them it is in currency format */
1603 switch (score_type) {
1604 case SCORE_MIN_PROFIT:
1605 case SCORE_MIN_INCOME:
1606 case SCORE_MAX_INCOME:
1607 case SCORE_MONEY:
1608 case SCORE_LOAN:
1609 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, val, needed));
1610 break;
1611 default:
1612 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_INT, val, needed));
1613 break;
1614 }
1615 }
1616
1617 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1618 {
1619 /* Check which button is clicked */
1621 /* Is it no on disable? */
1622 if (!this->IsWidgetDisabled(widget)) {
1623 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1624 this->company = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1625 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1626 this->SetDirty();
1627 }
1628 }
1629 }
1630
1631 void OnGameTick() override
1632 {
1633 /* Update the company score every 5 days */
1634 if (--this->timeout == 0) {
1635 this->UpdateCompanyStats();
1636 this->SetDirty();
1637 }
1638 }
1639
1645 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1646 {
1647 if (!gui_scope) return;
1648 /* Disable the companies who are not active */
1649 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
1651 }
1652
1653 /* Check if the currently selected company is still active. */
1654 if (this->company != CompanyID::Invalid() && !Company::IsValidID(this->company)) {
1655 /* Raise the widget for the previous selection. */
1656 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1657 this->company = CompanyID::Invalid();
1658 }
1659
1660 if (this->company == CompanyID::Invalid()) {
1661 for (const Company *c : Company::Iterate()) {
1662 this->company = c->index;
1663 break;
1664 }
1665 }
1666
1667 /* Make sure the widget is lowered */
1668 if (this->company != CompanyID::Invalid()) {
1669 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1670 }
1671 }
1672};
1673
1674CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
1675
1676/*******************************/
1677/* INDUSTRY PRODUCTION HISTORY */
1678/*******************************/
1679
1681 static inline constexpr StringID RANGE_LABELS[] = {
1682 STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
1683 STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED,
1684 STR_GRAPH_INDUSTRY_RANGE_DELIVERED,
1685 STR_GRAPH_INDUSTRY_RANGE_WAITING,
1686 };
1687
1688 static inline CargoTypes excluded_cargo_types{};
1689
1691 BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1692 {
1693 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1694 this->num_vert_lines = GRAPH_NUM_MONTHS;
1695 this->month_increment = 1;
1696 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1697 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1698 this->ranges = RANGE_LABELS;
1699
1701 if (!i->IsCargoProduced()) this->masked_range = (1U << 0) | (1U << 1);
1702 if (!i->IsCargoAccepted()) this->masked_range = (1U << 2) | (1U << 3);
1703
1704 this->InitializeWindow(window_number);
1705 }
1706
1707 void OnInit() override
1708 {
1710
1711 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1712 }
1713
1714 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1715 {
1716 CargoTypes cargo_types{};
1718 for (const auto &a : i->accepted) {
1719 if (IsValidCargoType(a.cargo)) SetBit(cargo_types, a.cargo);
1720 }
1721 for (const auto &p : i->produced) {
1722 if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo);
1723 }
1724 return cargo_types;
1725 }
1726
1727 CargoTypes &GetExcludedCargoTypes() const override
1728 {
1729 return IndustryProductionGraphWindow::excluded_cargo_types;
1730 }
1731
1732 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1733 {
1734 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_CAPTION, this->window_number);
1735
1736 return this->Window::GetWidgetString(widget, stringid);
1737 }
1738
1739 void UpdateStatistics(bool initialize) override
1740 {
1741 int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
1742 auto yr = TimerGameEconomy::year;
1743 while (mo < 0) {
1744 yr--;
1745 mo += 12;
1746 }
1747
1748 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1749 /* There's no reason to get new stats */
1750 return;
1751 }
1752
1753 this->excluded_data = this->GetExcludedCargoTypes();
1754 this->year = yr;
1755 this->month = mo;
1756
1757 const Industry *i = Industry::Get(this->window_number);
1758
1759 this->data.clear();
1760 for (const auto &p : i->produced) {
1761 if (!IsValidCargoType(p.cargo)) continue;
1762 const CargoSpec *cs = CargoSpec::Get(p.cargo);
1763
1764 this->data.reserve(this->data.size() + 2);
1765
1766 DataSet &produced = this->data.emplace_back();
1767 produced.colour = cs->legend_colour;
1768 produced.exclude_bit = cs->Index();
1769 produced.range_bit = 0;
1770
1771 DataSet &transported = this->data.emplace_back();
1772 transported.colour = cs->legend_colour;
1773 transported.exclude_bit = cs->Index();
1774 transported.range_bit = 1;
1775 transported.dash = 2;
1776
1777 FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, *this->scales[this->selected_scale].history_range,
1778 Filler{{produced}, &Industry::ProducedHistory::production},
1779 Filler{{transported}, &Industry::ProducedHistory::transported});
1780 }
1781
1782 for (const auto &a : i->accepted) {
1783 if (!IsValidCargoType(a.cargo)) continue;
1784 const CargoSpec *cs = CargoSpec::Get(a.cargo);
1785
1786 this->data.reserve(this->data.size() + 2);
1787
1788 DataSet &accepted = this->data.emplace_back();
1789 accepted.colour = cs->legend_colour;
1790 accepted.exclude_bit = cs->Index();
1791 accepted.range_bit = 2;
1792 accepted.dash = 1;
1793
1794 DataSet &waiting = this->data.emplace_back();
1795 waiting.colour = cs->legend_colour;
1796 waiting.exclude_bit = cs->Index();
1797 waiting.range_bit = 3;
1798 waiting.dash = 4;
1799
1800 FillFromHistory<GRAPH_NUM_MONTHS>(a.history.get(), i->valid_history, *this->scales[this->selected_scale].history_range,
1801 Filler{{accepted}, &Industry::AcceptedHistory::accepted},
1802 Filler{{waiting}, &Industry::AcceptedHistory::waiting});
1803 }
1804
1805 this->SetDirty();
1806 }
1807};
1808
1809static constexpr std::initializer_list<NWidgetPart> _nested_industry_production_widgets = {
1811 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1812 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_GRAPH_CAPTION),
1813 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1814 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1815 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1816 EndContainer(),
1817 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1819 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1821 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1822 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_TOGGLE_RANGE),
1824 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1825 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1828 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),
1830 EndContainer(),
1832 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_SELECT_SCALE),
1833 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1834 EndContainer(),
1835 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1836 EndContainer(),
1838 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),
1839 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1840 EndContainer(),
1841 EndContainer(),
1842};
1843
1844static WindowDesc _industry_production_desc(
1845 WDP_AUTO, "graph_industry_production", 0, 0,
1847 {},
1848 _nested_industry_production_widgets
1849);
1850
1851void ShowIndustryProductionGraph(WindowNumber window_number)
1852{
1853 AllocateWindowDescFront<IndustryProductionGraphWindow>(_industry_production_desc, window_number);
1854}
1855
1857 static inline constexpr StringID RANGE_LABELS[] = {
1858 STR_GRAPH_TOWN_RANGE_PRODUCED,
1859 STR_GRAPH_TOWN_RANGE_TRANSPORTED,
1860 };
1861
1862 static inline CargoTypes excluded_cargo_types{};
1863
1865 {
1866 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1867 this->num_vert_lines = GRAPH_NUM_MONTHS;
1868 this->month_increment = 1;
1869 this->x_values_reversed = true;
1870 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1871 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1872 this->ranges = RANGE_LABELS;
1873
1874 this->InitializeWindow(window_number);
1875 }
1876
1877 void OnInit() override
1878 {
1880
1881 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1882 }
1883
1884 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1885 {
1886 CargoTypes cargo_types{};
1887 const Town *t = Town::Get(window_number);
1888 for (const auto &s : t->supplied) {
1889 if (IsValidCargoType(s.cargo)) SetBit(cargo_types, s.cargo);
1890 }
1891 return cargo_types;
1892 }
1893
1894 CargoTypes &GetExcludedCargoTypes() const override
1895 {
1896 return TownCargoGraphWindow::excluded_cargo_types;
1897 }
1898
1899 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1900 {
1901 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_TOWN_CARGO_CAPTION, this->window_number);
1902
1903 return this->Window::GetWidgetString(widget, stringid);
1904 }
1905
1906 void UpdateStatistics(bool initialize) override
1907 {
1908 int mo = TimerGameEconomy::month - this->num_vert_lines;
1909 auto yr = TimerGameEconomy::year;
1910 while (mo < 0) {
1911 yr--;
1912 mo += 12;
1913 }
1914
1915 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1916 /* There's no reason to get new stats */
1917 return;
1918 }
1919
1920 this->excluded_data = this->GetExcludedCargoTypes();
1921 this->year = yr;
1922 this->month = mo;
1923
1924 const Town *t = Town::Get(this->window_number);
1925
1926 this->data.clear();
1927 for (const auto &s : t->supplied) {
1928 if (!IsValidCargoType(s.cargo)) continue;
1929 const CargoSpec *cs = CargoSpec::Get(s.cargo);
1930
1931 this->data.reserve(this->data.size() + 2);
1932
1933 DataSet &produced = this->data.emplace_back();
1934 produced.colour = cs->legend_colour;
1935 produced.exclude_bit = cs->Index();
1936 produced.range_bit = 0;
1937
1938 DataSet &transported = this->data.emplace_back();
1939 transported.colour = cs->legend_colour;
1940 transported.exclude_bit = cs->Index();
1941 transported.range_bit = 1;
1942 transported.dash = 2;
1943
1944 FillFromHistory<GRAPH_NUM_MONTHS>(s.history, t->valid_history, *this->scales[this->selected_scale].history_range,
1945 Filler{{produced}, &Town::SuppliedHistory::production},
1946 Filler{{transported}, &Town::SuppliedHistory::transported});
1947 }
1948
1949 this->SetDirty();
1950 }
1951};
1952
1953static constexpr std::initializer_list<NWidgetPart> _nested_town_cargo_graph_widgets = {
1955 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1956 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_GRAPH_CAPTION),
1957 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1958 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1959 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1960 EndContainer(),
1961 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1963 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1965 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1966 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
1968 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1969 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1972 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),
1974 EndContainer(),
1976 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
1977 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1978 EndContainer(),
1979 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1980 EndContainer(),
1982 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),
1983 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1984 EndContainer(),
1985 EndContainer(),
1986};
1987
1988static WindowDesc _town_cargo_graph_desc(
1989 WDP_AUTO, "graph_town_cargo", 0, 0,
1991 {},
1992 _nested_town_cargo_graph_widgets
1993);
1994
1995void ShowTownCargoGraph(WindowNumber window_number)
1996{
1997 AllocateWindowDescFront<TownCargoGraphWindow>(_town_cargo_graph_desc, window_number);
1998}
1999
2004static std::unique_ptr<NWidgetBase> MakePerformanceDetailPanels()
2005{
2006 auto realtime = TimerGameEconomy::UsingWallclockUnits();
2007 const StringID performance_tips[] = {
2008 realtime ? STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_YEARS,
2009 STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
2010 realtime ? STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_YEARS,
2011 STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
2012 STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
2013 STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
2014 STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
2015 STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
2016 STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
2017 STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
2018 };
2019
2020 static_assert(lengthof(performance_tips) == SCORE_END - SCORE_BEGIN);
2021
2022 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
2023 for (WidgetID widnum = WID_PRD_SCORE_FIRST; widnum <= WID_PRD_SCORE_LAST; widnum++) {
2024 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
2025 panel->SetFill(1, 1);
2026 panel->SetToolTip(performance_tips[widnum - WID_PRD_SCORE_FIRST]);
2027 vert->Add(std::move(panel));
2028 }
2029 return vert;
2030}
2031
2033std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsGraphGUI()
2034{
2035 return MakeCompanyButtonRows(WID_PRD_COMPANY_FIRST, WID_PRD_COMPANY_LAST, COLOUR_BROWN, 8, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
2036}
2037
2038static constexpr std::initializer_list<NWidgetPart> _nested_performance_rating_detail_widgets = {
2040 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
2041 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2042 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
2043 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
2044 EndContainer(),
2045 NWidget(WWT_PANEL, COLOUR_BROWN),
2047 EndContainer(),
2049};
2050
2051static WindowDesc _performance_rating_detail_desc(
2052 WDP_AUTO, "league_details", 0, 0,
2054 {},
2055 _nested_performance_rating_detail_widgets
2056);
2057
2058void ShowPerformanceRatingDetail()
2059{
2060 AllocateWindowDescFront<PerformanceRatingDetailWindow>(_performance_rating_detail_desc, 0);
2061}
2062
2063void InitializeGraphGui()
2064{
2065 _legend_excluded_companies = CompanyMask{};
2066 PaymentRatesGraphWindow::excluded_cargo_types = {};
2067 IndustryProductionGraphWindow::excluded_cargo_types = {};
2068}
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 bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
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:21
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:104
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:73
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:2425
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:2499
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:119
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.
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:962
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:891
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:662
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:864
void GfxFillRect(int left, int top, int right, int bottom, const std::variant< PixelColour, PaletteID > &colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
Functions related to the gfx engine.
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:250
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:249
@ SA_LEFT
Left align the text.
Definition gfx_type.h:388
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:390
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:389
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:400
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:398
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:307
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:346
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:47
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_SCALE_MATRIX
Horizontal axis scale list.
@ 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 EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=INVALID_WIDGET)
Widget part function for starting a new 'real' widget.
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:966
Functions for storing historical data.
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
PixelColour GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:388
constexpr PixelColour GREY_SCALE(uint8_t level)
Return the colour for a particular greyscale level.
static constexpr PixelColour PC_BLACK
Black palette colour.
static constexpr PixelColour PC_WHITE
White palette colour.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
void SndClickBeep()
Play a beep sound for a click event if enabled in settings.
Definition sound.cpp:253
Functions related to sound.
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:236
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
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.
DataSet & dataset
Dataset to fill.
Tprojection proj
Projection to apply.
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 datasets hidden by the player.
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 hidden by the player.
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".
static const int ECONOMY_YEAR_MINUTES
Minutes per economic year.
ValuesInterval GetValuesInterval(int num_hori_lines) const
Get the interval that contains the graph's data.
uint64_t masked_range
bitmask of ranges that are not available for the current 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.
T y
Y coordinate.
uint16_t rate
The conversion rate compared to the base currency.
Definition currency.h:78
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:84
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
Definition graph_gui.cpp:66
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void OnInit() override
Notification that the nested widget tree gets initialized.
uint16_t waiting
Total accepted.
Definition industry.h:82
uint16_t transported
Total transported.
Definition industry.h:65
uint16_t production
Total produced.
Definition industry.h:64
Defines the internal data of a functional industry.
Definition industry.h:62
bool IsCargoAccepted() const
Test if this industry accepts any cargo.
Definition industry.h:223
ValidHistoryMask valid_history
Mask of valid history records.
Definition industry.h:109
bool IsCargoProduced() const
Test if this industry produces any cargo.
Definition industry.h:229
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.
Colour for pixel/line drawing.
Definition gfx_type.h:405
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.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
void OnInit() override
Notification that the nested widget tree gets initialized.
uint32_t transported
Total transported.
Definition town.h:90
uint32_t production
Total produced.
Definition town.h:89
Town data structure.
Definition town.h:63
ValidHistoryMask valid_history
Mask of valid history records.
Definition town.h:112
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:168
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:274
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1807
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
Definition window.cpp:3234
void RaiseWidget(WidgetID widget_index)
Marks a widget as raised.
Definition window_gui.h:470
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:504
ResizeInfo resize
Resize information.
Definition window_gui.h:315
int scale
Scale of this window – used to determine how to resize.
Definition window_gui.h:305
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1797
bool IsWidgetDisabled(WidgetID widget_index) const
Gets the enabled/disabled status of a widget.
Definition window_gui.h:411
int left
x position of left edge of the window
Definition window_gui.h:310
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:212
void LowerWidget(WidgetID widget_index)
Marks a widget as lowered.
Definition window_gui.h:461
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1820
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:313
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:382
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:313
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:312
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:451
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:303
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.
Base of the town class.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:289
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:3435
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:70
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:66
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:39
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:57
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:50
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:55
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:52
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:76
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:68
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:60
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:37
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:59
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:56
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:49
@ EqualSize
Containers should keep all their (resizing) children equally large.
@ RWV_HIDE_BEVEL
Bevel of resize box is hidden.
Definition widget_type.h:29
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:3294
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting)
Definition window.cpp:3176
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:50
@ WC_OPERATING_PROFIT
Operating profit graph; Window numbers:
@ WC_INDUSTRY_PRODUCTION
Industry production history graph; Window numbers:
@ WC_TOWN_VIEW
Town view; 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:
@ WC_TOWN_CARGO_GRAPH
Town cargo history graph; Window numbers:
Functions related to zooming.
@ Normal
The normal zoom level.