OpenTTD Source 20250814-master-g3d806d6a65
graph_gui.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include <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 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 TimerGameEconomy::Month month = this->month;
681 TimerGameEconomy::Year year = this->year;
682 for (int i = 0; i < this->num_on_x_axis; i++) {
683 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);
684
685 month += this->month_increment;
686 if (month >= 12) {
687 month = 0;
688 year++;
689 }
690 }
691 } else {
692 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates). */
693 uint64_t max_value = GetParamMaxValue((this->num_on_x_axis + 1) * this->x_values_increment, 0, FS_SMALL);
694 x_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL_NUMBER, max_value)).width;
695 }
696
697 uint y_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, INT64_MAX)).width;
698
699 size.width = std::max<uint>(size.width, ScaleGUITrad(5) + y_label_width + this->num_vert_lines * (x_label_width + ScaleGUITrad(5)) + ScaleGUITrad(9));
700 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));
701 size.height = std::max<uint>(size.height, size.width / 3);
702 break;
703 }
704
705 default: break;
706 }
707 }
708
709 void DrawWidget(const Rect &r, WidgetID widget) const override
710 {
711 switch (widget) {
712 case WID_GRAPH_GRAPH:
713 this->DrawGraph(r);
714 break;
715
718 uint index = 0;
719 Rect line = r.WithHeight(line_height);
720 for (const auto &str : this->ranges) {
721 bool lowered = !HasBit(this->excluded_range, index) && !HasBit(this->masked_range, index);
722
723 /* Redraw frame if lowered */
724 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
725
726 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
727 DrawString(text, str, (this->highlight_state && this->highlight_range == index) ? TC_WHITE : TC_BLACK, SA_CENTER, false, FS_SMALL);
728
729 if (HasBit(this->masked_range, index)) {
731 }
732
733 line = line.Translate(0, line_height);
734 ++index;
735 }
736 break;
737 }
738
741 uint8_t selected_month_increment = this->scales[this->selected_scale].month_increment;
742 Rect line = r.WithHeight(line_height);
743 for (const auto &scale : this->scales) {
744 /* Redraw frame if selected */
745 if (selected_month_increment == scale.month_increment) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
746
748
749 line = line.Translate(0, line_height);
750 }
751 break;
752 }
753
754 default: break;
755 }
756 }
757
758 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
759 {
760 /* Clicked on legend? */
761 switch (widget) {
763 ShowGraphLegend();
764 break;
765
767 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
768
769 if (HasBit(this->masked_range, row)) break;
770 ToggleBit(this->excluded_range, row);
771 SndClickBeep();
772 this->SetDirty();
773 break;
774 }
775
777 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
778 const auto &scale = this->scales[row];
779 if (this->selected_scale != row) {
780 this->selected_scale = row;
781 this->month_increment = scale.month_increment;
782 this->x_values_increment = scale.x_values_increment;
783 this->InvalidateData();
784 }
785 break;
786 }
787
788 default: break;
789 }
790 }
791
792 void OnMouseOver(Point pt, WidgetID widget) override
793 {
794 /* Test if a range should be highlighted. */
795 uint8_t new_highlight_range = UINT8_MAX;
796 if (widget == WID_GRAPH_RANGE_MATRIX) {
797 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
798 if (!HasBit(this->excluded_range, row)) new_highlight_range = static_cast<uint8_t>(row);
799 }
800
801 /* Test if a dataset should be highlighted. */
802 uint8_t new_highlight_data = UINT8_MAX;
803 if (widget == WID_GRAPH_MATRIX) {
804 auto dataset_index = this->GetDatasetIndex(pt.y);
805 if (dataset_index.has_value() && !HasBit(this->excluded_data, *dataset_index)) new_highlight_data = *dataset_index;
806 }
807
808 if (this->highlight_data == new_highlight_data && this->highlight_range == new_highlight_range) return;
809
810 /* Range or data set highlight has changed, set and redraw. */
811 this->highlight_data = new_highlight_data;
812 this->highlight_range = new_highlight_range;
813 this->highlight_state = true;
814 this->SetDirty();
815 }
816
817 void OnGameTick() override
818 {
819 this->UpdateStatistics(false);
820 }
821
827 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
828 {
829 if (!gui_scope) return;
830 this->UpdateStatistics(true);
831 }
832
833 virtual void UpdateStatistics(bool initialize) = 0;
834
835 virtual std::optional<uint8_t> GetDatasetIndex(int) { return std::nullopt; }
836};
837
839public:
840 BaseCompanyGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
841
842 void InitializeWindow(WindowNumber number)
843 {
844 /* Initialise the dataset */
845 this->UpdateStatistics(true);
846
847 this->CreateNestedTree();
848
849 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
850 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_LAST_72_MINUTES_TIME_LABEL : STR_EMPTY);
851
852 this->FinishInitNested(number);
853 }
854
859 void UpdateStatistics(bool initialize) override
860 {
861 CompanyMask excluded_companies = _legend_excluded_companies;
862
863 /* Exclude the companies which aren't valid */
864 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
865 if (!Company::IsValidID(c)) excluded_companies.Set(c);
866 }
867
868 uint8_t nums = 0;
869 for (const Company *c : Company::Iterate()) {
870 nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent));
871 }
872
873 int mo = (TimerGameEconomy::month / this->month_increment - nums) * this->month_increment;
874 auto yr = TimerGameEconomy::year;
875 while (mo < 0) {
876 yr--;
877 mo += 12;
878 }
879
880 if (!initialize && this->excluded_data == excluded_companies.base() && this->num_on_x_axis == nums &&
881 this->year == yr && this->month == mo) {
882 /* There's no reason to get new stats */
883 return;
884 }
885
886 this->excluded_data = excluded_companies.base();
887 this->num_on_x_axis = nums;
888 this->year = yr;
889 this->month = mo;
890
891 this->data.clear();
892 for (CompanyID k = CompanyID::Begin(); k < MAX_COMPANIES; ++k) {
893 const Company *c = Company::GetIfValid(k);
894 if (c == nullptr) continue;
895
896 DataSet &dataset = this->data.emplace_back();
897 dataset.colour = GetColourGradient(c->colour, SHADE_LIGHTER);
898 dataset.exclude_bit = k.base();
899
900 for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
901 if (j >= c->num_valid_stat_ent) {
902 dataset.values[i] = INVALID_DATAPOINT;
903 } else {
904 /* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
905 * Instead, use the value just under it. Hopefully nobody will notice. */
906 dataset.values[i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
907 }
908 i++;
909 }
910 }
911 }
912
913 virtual OverflowSafeInt64 GetGraphData(const Company *, int) = 0;
914};
915
916
917/********************/
918/* OPERATING PROFIT */
919/********************/
920
923 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
924 {
925 this->num_on_x_axis = GRAPH_NUM_MONTHS;
926 this->num_vert_lines = GRAPH_NUM_MONTHS;
927 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
928
929 this->InitializeWindow(window_number);
930 }
931
932 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
933 {
934 return c->old_economy[j].income + c->old_economy[j].expenses;
935 }
936};
937
938static constexpr NWidgetPart _nested_operating_profit_widgets[] = {
940 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
941 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
942 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
943 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
944 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
945 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
946 EndContainer(),
947 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
948 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 160), SetFill(1, 1), SetResize(1, 1),
950 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),
951 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
952 EndContainer(),
953 EndContainer(),
954};
955
956static WindowDesc _operating_profit_desc(
957 WDP_AUTO, "graph_operating_profit", 0, 0,
959 {},
960 _nested_operating_profit_widgets
961);
962
963
964void ShowOperatingProfitGraph()
965{
966 AllocateWindowDescFront<OperatingProfitGraphWindow>(_operating_profit_desc, 0);
967}
968
969
970/****************/
971/* INCOME GRAPH */
972/****************/
973
976 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
977 {
978 this->num_on_x_axis = GRAPH_NUM_MONTHS;
979 this->num_vert_lines = GRAPH_NUM_MONTHS;
980 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
981
982 this->InitializeWindow(window_number);
983 }
984
985 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
986 {
987 return c->old_economy[j].income;
988 }
989};
990
991static constexpr NWidgetPart _nested_income_graph_widgets[] = {
993 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
994 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
995 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
996 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
997 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
998 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
999 EndContainer(),
1000 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1001 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
1003 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),
1004 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1005 EndContainer(),
1006 EndContainer(),
1007};
1008
1009static WindowDesc _income_graph_desc(
1010 WDP_AUTO, "graph_income", 0, 0,
1012 {},
1013 _nested_income_graph_widgets
1014);
1015
1016void ShowIncomeGraph()
1017{
1018 AllocateWindowDescFront<IncomeGraphWindow>(_income_graph_desc, 0);
1019}
1020
1021/*******************/
1022/* DELIVERED CARGO */
1023/*******************/
1024
1027 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1028 {
1029 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1030 this->num_vert_lines = GRAPH_NUM_MONTHS;
1031 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1032
1033 this->InitializeWindow(window_number);
1034 }
1035
1036 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1037 {
1038 return c->old_economy[j].delivered_cargo.GetSum<OverflowSafeInt64>();
1039 }
1040};
1041
1042static constexpr NWidgetPart _nested_delivered_cargo_graph_widgets[] = {
1044 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1045 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1046 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1047 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1048 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1049 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1050 EndContainer(),
1051 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1052 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
1054 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),
1055 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1056 EndContainer(),
1057 EndContainer(),
1058};
1059
1060static WindowDesc _delivered_cargo_graph_desc(
1061 WDP_AUTO, "graph_delivered_cargo", 0, 0,
1063 {},
1064 _nested_delivered_cargo_graph_widgets
1065);
1066
1067void ShowDeliveredCargoGraph()
1068{
1069 AllocateWindowDescFront<DeliveredCargoGraphWindow>(_delivered_cargo_graph_desc, 0);
1070}
1071
1072/***********************/
1073/* PERFORMANCE HISTORY */
1074/***********************/
1075
1078 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1079 {
1080 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1081 this->num_vert_lines = GRAPH_NUM_MONTHS;
1082 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1083
1084 this->InitializeWindow(window_number);
1085 }
1086
1087 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1088 {
1089 return c->old_economy[j].performance_history;
1090 }
1091
1092 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1093 {
1094 if (widget == WID_PHG_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
1095 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1096 }
1097};
1098
1099static constexpr NWidgetPart _nested_performance_history_widgets[] = {
1101 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1102 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1103 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_PHG_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetStringTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
1104 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1105 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1106 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1107 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1108 EndContainer(),
1109 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1110 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1112 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),
1113 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1114 EndContainer(),
1115 EndContainer(),
1116};
1117
1118static WindowDesc _performance_history_desc(
1119 WDP_AUTO, "graph_performance", 0, 0,
1121 {},
1122 _nested_performance_history_widgets
1123);
1124
1125void ShowPerformanceHistoryGraph()
1126{
1127 AllocateWindowDescFront<PerformanceHistoryGraphWindow>(_performance_history_desc, 0);
1128}
1129
1130/*****************/
1131/* COMPANY VALUE */
1132/*****************/
1133
1136 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1137 {
1138 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1139 this->num_vert_lines = GRAPH_NUM_MONTHS;
1140 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1141
1142 this->InitializeWindow(window_number);
1143 }
1144
1145 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1146 {
1147 return c->old_economy[j].company_value;
1148 }
1149};
1150
1151static constexpr NWidgetPart _nested_company_value_graph_widgets[] = {
1153 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1154 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1155 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1156 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1157 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1158 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1159 EndContainer(),
1160 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1161 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1163 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),
1164 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1165 EndContainer(),
1166 EndContainer(),
1167};
1168
1169static WindowDesc _company_value_graph_desc(
1170 WDP_AUTO, "graph_company_value", 0, 0,
1172 {},
1173 _nested_company_value_graph_widgets
1174);
1175
1176void ShowCompanyValueGraph()
1177{
1178 AllocateWindowDescFront<CompanyValueGraphWindow>(_company_value_graph_desc, 0);
1179}
1180
1182 Scrollbar *vscroll = nullptr;
1183 uint line_height = 0;
1184 uint legend_width = 0;
1185
1186 CargoTypes cargo_types{};
1187
1188 BaseCargoGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
1189
1190 void InitializeWindow(WindowNumber number, StringID footer_wallclock = STR_NULL, StringID footer_calendar = STR_NULL)
1191 {
1192 this->CreateNestedTree();
1193
1194 this->excluded_range = this->masked_range;
1195 this->cargo_types = this->GetCargoTypes(number);
1196
1197 this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
1198 this->vscroll->SetCount(CountBits(this->cargo_types));
1199
1200 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1201 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? footer_wallclock : footer_calendar);
1202
1203 this->FinishInitNested(number);
1204
1205 /* Initialise the dataset */
1206 this->InvalidateData();
1207 }
1208
1209 virtual CargoTypes GetCargoTypes(WindowNumber number) const = 0;
1210 virtual CargoTypes &GetExcludedCargoTypes() const = 0;
1211
1212 std::optional<uint8_t> GetDatasetIndex(int y) override
1213 {
1214 int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_GRAPH_MATRIX);
1215 if (row >= this->vscroll->GetCount()) return std::nullopt;
1216
1217 for (const CargoSpec *cs : _sorted_cargo_specs) {
1218 if (!HasBit(this->cargo_types, cs->Index())) continue;
1219 if (row-- > 0) continue;
1220
1221 return cs->Index();
1222 }
1223
1224 return std::nullopt;
1225 }
1226
1227 void OnInit() override
1228 {
1229 /* Width of the legend blob. */
1230 this->legend_width = GetCharacterHeight(FS_SMALL) * 9 / 6;
1231 }
1232
1233 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1234 {
1235 if (widget != WID_GRAPH_MATRIX) {
1236 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1237 return;
1238 }
1239
1241
1242 for (CargoType cargo_type : SetCargoBitIterator(this->cargo_types)) {
1243 const CargoSpec *cs = CargoSpec::Get(cargo_type);
1244
1245 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1246 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1249 size = maxdim(d, size);
1250 }
1251
1252 this->line_height = size.height;
1253 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1254 resize.width = 0;
1255 fill.height = resize.height = this->line_height;
1256 }
1257
1258 void DrawWidget(const Rect &r, WidgetID widget) const override
1259 {
1260 if (widget != WID_GRAPH_MATRIX) {
1261 BaseGraphWindow::DrawWidget(r, widget);
1262 return;
1263 }
1264
1265 bool rtl = _current_text_dir == TD_RTL;
1266
1267 int pos = this->vscroll->GetPosition();
1268 int max = pos + this->vscroll->GetCapacity();
1269
1270 Rect line = r.WithHeight(this->line_height);
1271
1272 for (const CargoSpec *cs : _sorted_cargo_specs) {
1273 if (!HasBit(this->cargo_types, cs->Index())) continue;
1274
1275 if (pos-- > 0) continue;
1276 if (--max < 0) break;
1277
1278 bool lowered = !HasBit(this->excluded_data, cs->Index());
1279
1280 /* Redraw frame if lowered */
1281 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
1282
1283 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1284
1285 /* Cargo-colour box with outline */
1286 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1287 GfxFillRect(cargo, PC_BLACK);
1288 PixelColour pc = cs->legend_colour;
1289 if (this->highlight_data == cs->Index()) pc = this->highlight_state ? PC_WHITE : PC_BLACK;
1291
1292 /* Cargo name */
1293 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1294
1295 line = line.Translate(0, this->line_height);
1296 }
1297 }
1298
1299 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1300 {
1301 switch (widget) {
1303 /* Remove all cargoes from the excluded lists. */
1304 this->GetExcludedCargoTypes() = {};
1305 this->excluded_data = this->GetExcludedCargoTypes();
1306 this->SetDirty();
1307 break;
1308
1310 /* Add all cargoes to the excluded lists. */
1311 this->GetExcludedCargoTypes() = this->cargo_types;
1312 this->excluded_data = this->GetExcludedCargoTypes();
1313 this->SetDirty();
1314 break;
1315 }
1316
1317 case WID_GRAPH_MATRIX: {
1318 int row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GRAPH_MATRIX);
1319 if (row >= this->vscroll->GetCount()) return;
1320
1321 SndClickBeep();
1322
1323 for (const CargoSpec *cs : _sorted_cargo_specs) {
1324 if (!HasBit(this->cargo_types, cs->Index())) continue;
1325 if (row-- > 0) continue;
1326
1327 ToggleBit(this->GetExcludedCargoTypes(), cs->Index());
1328 this->excluded_data = this->GetExcludedCargoTypes();
1329 this->SetDirty();
1330 break;
1331 }
1332 break;
1333 }
1334
1335 default:
1336 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1337 break;
1338 }
1339 }
1340
1341 void OnResize() override
1342 {
1343 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1344 }
1345};
1346
1347/*****************/
1348/* PAYMENT RATES */
1349/*****************/
1350
1352 static inline CargoTypes excluded_cargo_types{};
1353
1355 {
1356 this->num_on_x_axis = GRAPH_PAYMENT_RATE_STEPS;
1357 this->num_vert_lines = GRAPH_PAYMENT_RATE_STEPS;
1358 this->draw_dates = false;
1359
1360 this->x_values_reversed = false;
1361 /* The x-axis is labeled in either seconds or days. A day is two seconds, so we adjust the label if needed. */
1363
1364 this->InitializeWindow(window_number, STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS, STR_GRAPH_CARGO_PAYMENT_RATES_DAYS);
1365 }
1366
1367 CargoTypes GetCargoTypes(WindowNumber) const override
1368 {
1369 return _standard_cargo_mask;
1370 }
1371
1372 CargoTypes &GetExcludedCargoTypes() const override
1373 {
1374 return PaymentRatesGraphWindow::excluded_cargo_types;
1375 }
1376
1382 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1383 {
1384 if (!gui_scope) return;
1385 this->UpdatePaymentRates();
1386 }
1387
1389 const IntervalTimer<TimerWindow> update_payment_interval = {std::chrono::seconds(3), [this](auto) {
1390 this->UpdatePaymentRates();
1391 }};
1392
1393 void UpdateStatistics(bool) override {}
1394
1399 {
1400 this->excluded_data = this->GetExcludedCargoTypes();
1401
1402 this->data.clear();
1403 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1404 DataSet &dataset = this->data.emplace_back();
1405 dataset.colour = cs->legend_colour;
1406 dataset.exclude_bit = cs->Index();
1407
1408 for (uint j = 0; j != this->num_on_x_axis; j++) {
1409 dataset.values[j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
1410 }
1411 }
1412 }
1413};
1414
1415static constexpr NWidgetPart _nested_cargo_payment_rates_widgets[] = {
1417 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1418 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1419 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1420 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1421 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1422 EndContainer(),
1423 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1424 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),
1426 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1428 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1429 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1430 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1433 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),
1435 EndContainer(),
1436 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1437 EndContainer(),
1438 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1439 EndContainer(),
1441 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),
1442 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1443 EndContainer(),
1444 EndContainer(),
1445};
1446
1447static WindowDesc _cargo_payment_rates_desc(
1448 WDP_AUTO, "graph_cargo_payment_rates", 0, 0,
1450 {},
1451 _nested_cargo_payment_rates_widgets
1452);
1453
1454
1455void ShowCargoPaymentRates()
1456{
1457 AllocateWindowDescFront<PaymentRatesGraphWindow>(_cargo_payment_rates_desc, 0);
1458}
1459
1460/*****************************/
1461/* PERFORMANCE RATING DETAIL */
1462/*****************************/
1463
1465 static CompanyID company;
1466 int timeout = 0;
1467 uint score_info_left = 0;
1468 uint score_info_right = 0;
1469 uint bar_left = 0;
1470 uint bar_right = 0;
1471 uint bar_width = 0;
1472 uint bar_height = 0;
1473 uint score_detail_left = 0;
1474 uint score_detail_right = 0;
1475
1477 {
1478 this->UpdateCompanyStats();
1479
1480 this->InitNested(window_number);
1481 this->OnInvalidateData(CompanyID::Invalid().base());
1482 }
1483
1484 void UpdateCompanyStats()
1485 {
1486 /* Update all company stats with the current data
1487 * (this is because _score_info is not saved to a savegame) */
1488 for (Company *c : Company::Iterate()) {
1490 }
1491
1492 this->timeout = Ticks::DAY_TICKS * 5;
1493 }
1494
1495 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1496 {
1497 switch (widget) {
1500 size.height = this->bar_height + WidgetDimensions::scaled.matrix.Vertical();
1501
1502 uint score_info_width = 0;
1503 for (uint i = SCORE_BEGIN; i < SCORE_END; i++) {
1504 score_info_width = std::max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + i).width);
1505 }
1506 score_info_width += GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(1000))).width + WidgetDimensions::scaled.hsep_wide;
1507
1508 this->bar_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_PERCENT, GetParamMaxValue(100))).width + WidgetDimensions::scaled.hsep_indent * 2; // Wide bars!
1509
1510 /* At this number we are roughly at the max; it can become wider,
1511 * but then you need at 1000 times more money. At that time you're
1512 * not that interested anymore in the last few digits anyway.
1513 * The 500 is because 999 999 500 to 999 999 999 are rounded to
1514 * 1 000 M, and not 999 999 k. Use negative numbers to account for
1515 * the negative income/amount of money etc. as well. */
1516 int max = -(999999999 - 500);
1517
1518 /* Scale max for the display currency. Prior to rendering the value
1519 * is converted into the display currency, which may cause it to
1520 * raise significantly. We need to compensate for that since {{CURRCOMPACT}}
1521 * is used, which can produce quite short renderings of very large
1522 * values. Otherwise the calculated width could be too narrow.
1523 * Note that it doesn't work if there was a currency with an exchange
1524 * rate greater than max.
1525 * When the currency rate is more than 1000, the 999 999 k becomes at
1526 * least 999 999 M which roughly is equally long. Furthermore if the
1527 * exchange rate is that high, 999 999 k is usually not enough anymore
1528 * to show the different currency numbers. */
1529 if (GetCurrency().rate < 1000) max /= GetCurrency().rate;
1530 uint score_detail_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, max, max)).width;
1531
1532 size.width = WidgetDimensions::scaled.frametext.Horizontal() + score_info_width + WidgetDimensions::scaled.hsep_wide + this->bar_width + WidgetDimensions::scaled.hsep_wide + score_detail_width;
1534 uint right = size.width - WidgetDimensions::scaled.frametext.right;
1535
1536 bool rtl = _current_text_dir == TD_RTL;
1537 this->score_info_left = rtl ? right - score_info_width : left;
1538 this->score_info_right = rtl ? right : left + score_info_width;
1539
1540 this->score_detail_left = rtl ? left : right - score_detail_width;
1541 this->score_detail_right = rtl ? left + score_detail_width : right;
1542
1543 this->bar_left = left + (rtl ? score_detail_width : score_info_width) + WidgetDimensions::scaled.hsep_wide;
1544 this->bar_right = this->bar_left + this->bar_width - 1;
1545 break;
1546 }
1547 }
1548
1549 void DrawWidget(const Rect &r, WidgetID widget) const override
1550 {
1551 /* No need to draw when there's nothing to draw */
1552 if (this->company == CompanyID::Invalid()) return;
1553
1555 if (this->IsWidgetDisabled(widget)) return;
1556 CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1557 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
1558 DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height));
1559 return;
1560 }
1561
1562 if (!IsInsideMM(widget, WID_PRD_SCORE_FIRST, WID_PRD_SCORE_LAST + 1)) return;
1563
1564 ScoreID score_type = (ScoreID)(widget - WID_PRD_SCORE_FIRST);
1565
1566 /* The colours used to show how the progress is going */
1567 PixelColour colour_done = GetColourGradient(COLOUR_GREEN, SHADE_NORMAL);
1568 PixelColour colour_notdone = GetColourGradient(COLOUR_RED, SHADE_NORMAL);
1569
1570 /* Draw all the score parts */
1571 int64_t val = _score_part[company][score_type];
1572 int64_t needed = _score_info[score_type].needed;
1573 int score = _score_info[score_type].score;
1574
1575 /* SCORE_TOTAL has its own rules ;) */
1576 if (score_type == SCORE_TOTAL) {
1577 for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) score += _score_info[i].score;
1578 needed = SCORE_MAX;
1579 }
1580
1581 uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height);
1582 uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL));
1583
1584 DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type);
1585
1586 /* Draw the score */
1587 DrawString(this->score_info_left, this->score_info_right, text_top, GetString(STR_JUST_COMMA, score), TC_BLACK, SA_RIGHT);
1588
1589 /* Calculate the %-bar */
1590 uint x = Clamp<int64_t>(val, 0, needed) * this->bar_width / needed;
1591 bool rtl = _current_text_dir == TD_RTL;
1592 if (rtl) {
1593 x = this->bar_right - x;
1594 } else {
1595 x = this->bar_left + x;
1596 }
1597
1598 /* Draw the bar */
1599 if (x != this->bar_left) GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height - 1, rtl ? colour_notdone : colour_done);
1600 if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height - 1, rtl ? colour_done : colour_notdone);
1601
1602 /* Draw it */
1603 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);
1604
1605 /* SCORE_LOAN is inversed */
1606 if (score_type == SCORE_LOAN) val = needed - val;
1607
1608 /* Draw the amount we have against what is needed
1609 * For some of them it is in currency format */
1610 switch (score_type) {
1611 case SCORE_MIN_PROFIT:
1612 case SCORE_MIN_INCOME:
1613 case SCORE_MAX_INCOME:
1614 case SCORE_MONEY:
1615 case SCORE_LOAN:
1616 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, val, needed));
1617 break;
1618 default:
1619 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_INT, val, needed));
1620 break;
1621 }
1622 }
1623
1624 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1625 {
1626 /* Check which button is clicked */
1628 /* Is it no on disable? */
1629 if (!this->IsWidgetDisabled(widget)) {
1630 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1631 this->company = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1632 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1633 this->SetDirty();
1634 }
1635 }
1636 }
1637
1638 void OnGameTick() override
1639 {
1640 /* Update the company score every 5 days */
1641 if (--this->timeout == 0) {
1642 this->UpdateCompanyStats();
1643 this->SetDirty();
1644 }
1645 }
1646
1652 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1653 {
1654 if (!gui_scope) return;
1655 /* Disable the companies who are not active */
1656 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
1658 }
1659
1660 /* Check if the currently selected company is still active. */
1661 if (this->company != CompanyID::Invalid() && !Company::IsValidID(this->company)) {
1662 /* Raise the widget for the previous selection. */
1663 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1664 this->company = CompanyID::Invalid();
1665 }
1666
1667 if (this->company == CompanyID::Invalid()) {
1668 for (const Company *c : Company::Iterate()) {
1669 this->company = c->index;
1670 break;
1671 }
1672 }
1673
1674 /* Make sure the widget is lowered */
1675 if (this->company != CompanyID::Invalid()) {
1676 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1677 }
1678 }
1679};
1680
1681CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
1682
1683/*******************************/
1684/* INDUSTRY PRODUCTION HISTORY */
1685/*******************************/
1686
1688 static inline constexpr StringID RANGE_LABELS[] = {
1689 STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
1690 STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED,
1691 STR_GRAPH_INDUSTRY_RANGE_DELIVERED,
1692 STR_GRAPH_INDUSTRY_RANGE_WAITING,
1693 };
1694
1695 static inline CargoTypes excluded_cargo_types{};
1696
1698 BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1699 {
1700 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1701 this->num_vert_lines = GRAPH_NUM_MONTHS;
1702 this->month_increment = 1;
1703 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1704 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1705 this->ranges = RANGE_LABELS;
1706
1708 if (!i->IsCargoProduced()) this->masked_range = (1U << 0) | (1U << 1);
1709 if (!i->IsCargoAccepted()) this->masked_range = (1U << 2) | (1U << 3);
1710
1711 this->InitializeWindow(window_number);
1712 }
1713
1714 void OnInit() override
1715 {
1717
1718 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1719 }
1720
1721 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1722 {
1723 CargoTypes cargo_types{};
1725 for (const auto &a : i->accepted) {
1726 if (IsValidCargoType(a.cargo)) SetBit(cargo_types, a.cargo);
1727 }
1728 for (const auto &p : i->produced) {
1729 if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo);
1730 }
1731 return cargo_types;
1732 }
1733
1734 CargoTypes &GetExcludedCargoTypes() const override
1735 {
1736 return IndustryProductionGraphWindow::excluded_cargo_types;
1737 }
1738
1739 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1740 {
1741 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_CAPTION, this->window_number);
1742
1743 return this->Window::GetWidgetString(widget, stringid);
1744 }
1745
1746 void UpdateStatistics(bool initialize) override
1747 {
1748 int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
1749 auto yr = TimerGameEconomy::year;
1750 while (mo < 0) {
1751 yr--;
1752 mo += 12;
1753 }
1754
1755 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1756 /* There's no reason to get new stats */
1757 return;
1758 }
1759
1760 this->excluded_data = this->GetExcludedCargoTypes();
1761 this->year = yr;
1762 this->month = mo;
1763
1764 const Industry *i = Industry::Get(this->window_number);
1765
1766 this->data.clear();
1767 for (const auto &p : i->produced) {
1768 if (!IsValidCargoType(p.cargo)) continue;
1769 const CargoSpec *cs = CargoSpec::Get(p.cargo);
1770
1771 this->data.reserve(this->data.size() + 2);
1772
1773 DataSet &produced = this->data.emplace_back();
1774 produced.colour = cs->legend_colour;
1775 produced.exclude_bit = cs->Index();
1776 produced.range_bit = 0;
1777
1778 DataSet &transported = this->data.emplace_back();
1779 transported.colour = cs->legend_colour;
1780 transported.exclude_bit = cs->Index();
1781 transported.range_bit = 1;
1782 transported.dash = 2;
1783
1784 FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, *this->scales[this->selected_scale].history_range,
1785 Filler{{produced}, &Industry::ProducedHistory::production},
1786 Filler{{transported}, &Industry::ProducedHistory::transported});
1787 }
1788
1789 for (const auto &a : i->accepted) {
1790 if (!IsValidCargoType(a.cargo)) continue;
1791 const CargoSpec *cs = CargoSpec::Get(a.cargo);
1792
1793 this->data.reserve(this->data.size() + 2);
1794
1795 DataSet &accepted = this->data.emplace_back();
1796 accepted.colour = cs->legend_colour;
1797 accepted.exclude_bit = cs->Index();
1798 accepted.range_bit = 2;
1799 accepted.dash = 1;
1800
1801 DataSet &waiting = this->data.emplace_back();
1802 waiting.colour = cs->legend_colour;
1803 waiting.exclude_bit = cs->Index();
1804 waiting.range_bit = 3;
1805 waiting.dash = 4;
1806
1807 FillFromHistory<GRAPH_NUM_MONTHS>(a.history.get(), i->valid_history, *this->scales[this->selected_scale].history_range,
1808 Filler{{accepted}, &Industry::AcceptedHistory::accepted},
1809 Filler{{waiting}, &Industry::AcceptedHistory::waiting});
1810 }
1811
1812 this->SetDirty();
1813 }
1814};
1815
1816static constexpr NWidgetPart _nested_industry_production_widgets[] = {
1818 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1819 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_GRAPH_CAPTION),
1820 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1821 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1822 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1823 EndContainer(),
1824 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1826 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1828 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1829 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_TOGGLE_RANGE),
1831 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1832 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1835 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),
1837 EndContainer(),
1839 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_SELECT_SCALE),
1840 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1841 EndContainer(),
1842 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1843 EndContainer(),
1845 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),
1846 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1847 EndContainer(),
1848 EndContainer(),
1849};
1850
1851static WindowDesc _industry_production_desc(
1852 WDP_AUTO, "graph_industry_production", 0, 0,
1854 {},
1855 _nested_industry_production_widgets
1856);
1857
1858void ShowIndustryProductionGraph(WindowNumber window_number)
1859{
1860 AllocateWindowDescFront<IndustryProductionGraphWindow>(_industry_production_desc, window_number);
1861}
1862
1864 static inline constexpr StringID RANGE_LABELS[] = {
1865 STR_GRAPH_TOWN_RANGE_PRODUCED,
1866 STR_GRAPH_TOWN_RANGE_TRANSPORTED,
1867 };
1868
1869 static inline CargoTypes excluded_cargo_types{};
1870
1872 {
1873 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1874 this->num_vert_lines = GRAPH_NUM_MONTHS;
1875 this->month_increment = 1;
1876 this->x_values_reversed = true;
1877 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1878 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1879 this->ranges = RANGE_LABELS;
1880
1881 this->InitializeWindow(window_number);
1882 }
1883
1884 void OnInit() override
1885 {
1887
1888 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1889 }
1890
1891 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1892 {
1893 CargoTypes cargo_types{};
1894 const Town *t = Town::Get(window_number);
1895 for (const auto &s : t->supplied) {
1896 if (IsValidCargoType(s.cargo)) SetBit(cargo_types, s.cargo);
1897 }
1898 return cargo_types;
1899 }
1900
1901 CargoTypes &GetExcludedCargoTypes() const override
1902 {
1903 return TownCargoGraphWindow::excluded_cargo_types;
1904 }
1905
1906 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1907 {
1908 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_TOWN_CARGO_CAPTION, this->window_number);
1909
1910 return this->Window::GetWidgetString(widget, stringid);
1911 }
1912
1913 void UpdateStatistics(bool initialize) override
1914 {
1915 int mo = TimerGameEconomy::month - this->num_vert_lines;
1916 auto yr = TimerGameEconomy::year;
1917 while (mo < 0) {
1918 yr--;
1919 mo += 12;
1920 }
1921
1922 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1923 /* There's no reason to get new stats */
1924 return;
1925 }
1926
1927 this->excluded_data = this->GetExcludedCargoTypes();
1928 this->year = yr;
1929 this->month = mo;
1930
1931 const Town *t = Town::Get(this->window_number);
1932
1933 this->data.clear();
1934 for (const auto &s : t->supplied) {
1935 if (!IsValidCargoType(s.cargo)) continue;
1936 const CargoSpec *cs = CargoSpec::Get(s.cargo);
1937
1938 this->data.reserve(this->data.size() + 2);
1939
1940 DataSet &produced = this->data.emplace_back();
1941 produced.colour = cs->legend_colour;
1942 produced.exclude_bit = cs->Index();
1943 produced.range_bit = 0;
1944
1945 DataSet &transported = this->data.emplace_back();
1946 transported.colour = cs->legend_colour;
1947 transported.exclude_bit = cs->Index();
1948 transported.range_bit = 1;
1949 transported.dash = 2;
1950
1951 FillFromHistory<GRAPH_NUM_MONTHS>(s.history, t->valid_history, *this->scales[this->selected_scale].history_range,
1952 Filler{{produced}, &Town::SuppliedHistory::production},
1953 Filler{{transported}, &Town::SuppliedHistory::transported});
1954 }
1955
1956 this->SetDirty();
1957 }
1958};
1959
1960static constexpr NWidgetPart _nested_town_cargo_graph_widgets[] = {
1962 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1963 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_GRAPH_CAPTION),
1964 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1965 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1966 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1967 EndContainer(),
1968 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1970 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1972 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1973 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
1975 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1976 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1979 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),
1981 EndContainer(),
1983 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
1984 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1985 EndContainer(),
1986 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1987 EndContainer(),
1989 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),
1990 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1991 EndContainer(),
1992 EndContainer(),
1993};
1994
1995static WindowDesc _town_cargo_graph_desc(
1996 WDP_AUTO, "graph_town_cargo", 0, 0,
1998 {},
1999 _nested_town_cargo_graph_widgets
2000);
2001
2002void ShowTownCargoGraph(WindowNumber window_number)
2003{
2004 AllocateWindowDescFront<TownCargoGraphWindow>(_town_cargo_graph_desc, window_number);
2005}
2006
2011static std::unique_ptr<NWidgetBase> MakePerformanceDetailPanels()
2012{
2013 auto realtime = TimerGameEconomy::UsingWallclockUnits();
2014 const StringID performance_tips[] = {
2015 realtime ? STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_YEARS,
2016 STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
2017 realtime ? STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_YEARS,
2018 STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
2019 STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
2020 STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
2021 STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
2022 STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
2023 STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
2024 STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
2025 };
2026
2027 static_assert(lengthof(performance_tips) == SCORE_END - SCORE_BEGIN);
2028
2029 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
2030 for (WidgetID widnum = WID_PRD_SCORE_FIRST; widnum <= WID_PRD_SCORE_LAST; widnum++) {
2031 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
2032 panel->SetFill(1, 1);
2033 panel->SetToolTip(performance_tips[widnum - WID_PRD_SCORE_FIRST]);
2034 vert->Add(std::move(panel));
2035 }
2036 return vert;
2037}
2038
2040std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsGraphGUI()
2041{
2042 return MakeCompanyButtonRows(WID_PRD_COMPANY_FIRST, WID_PRD_COMPANY_LAST, COLOUR_BROWN, 8, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
2043}
2044
2045static constexpr NWidgetPart _nested_performance_rating_detail_widgets[] = {
2047 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
2048 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2049 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
2050 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
2051 EndContainer(),
2052 NWidget(WWT_PANEL, COLOUR_BROWN),
2054 EndContainer(),
2056};
2057
2058static WindowDesc _performance_rating_detail_desc(
2059 WDP_AUTO, "league_details", 0, 0,
2061 {},
2062 _nested_performance_rating_detail_widgets
2063);
2064
2065void ShowPerformanceRatingDetail()
2066{
2067 AllocateWindowDescFront<PerformanceRatingDetailWindow>(_performance_rating_detail_desc, 0);
2068}
2069
2070void InitializeGraphGui()
2071{
2072 _legend_excluded_companies = CompanyMask{};
2073 PaymentRatesGraphWindow::excluded_cargo_types = {};
2074 IndustryProductionGraphWindow::excluded_cargo_types = {};
2075}
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
constexpr uint CountBits(T value)
Counts the number of set bits in a variable.
constexpr T ToggleBit(T &x, const uint8_t y)
Toggles a bit in a variable.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:106
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
std::span< const CargoSpec * > _sorted_standard_cargo_specs
Standard cargo specifications sorted alphabetically by name.
CargoTypes _standard_cargo_mask
Bitmask of real cargo types available.
Definition cargotype.cpp:36
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
Types/functions related to cargoes.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Tstorage base() const noexcept
Retrieve the raw value behind this bit set.
constexpr Timpl & Flip(Tvalue_type value)
Flip the value-th bit.
constexpr Timpl & Set()
Set all bits.
void UpdateStatistics(bool initialize) override
Update the statistics.
An interval timer will fire every interval, and will continue to fire until it is deleted.
Definition timer.h:76
Scrollbar data structure.
size_type GetCapacity() const
Gets the number of visible elements of the scrollbar.
void SetCount(size_t num)
Sets the number of elements in the list.
size_type GetScrolledRowFromWidget(int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition widget.cpp:2436
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:2510
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.
Definition fontcache.cpp:87
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:966
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:895
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:666
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:868
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:116
Functions related to the gfx engine.
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:251
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:250
@ SA_LEFT
Left align the text.
Definition gfx_type.h:389
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:391
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:390
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:401
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:399
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:308
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:347
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 NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart SetAlignment(StringAlignment align)
Widget part function for setting the alignment of text/images.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:957
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
Partial widget specification to allow NWidgets to be written nested.
void UpdatePaymentRates()
Update the payment rates according to the latest information.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
const IntervalTimer< TimerWindow > update_payment_interval
Update the payment rates on a regular interval.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void OnGameTick() override
Called once per (game) tick.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
Colour for pixel/line drawing.
Definition gfx_type.h:406
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:80
uint32_t production
Total produced.
Definition town.h:79
Town data structure.
Definition town.h:53
ValidHistoryMask valid_history
Mask of valid history records.
Definition town.h:102
Contains the interval of a graph's data.
OverflowSafeInt64 lowest
Lowest value of this interval. Must be zero or less.
OverflowSafeInt64 highest
Highest value of this interval. Must be zero or greater.
High level window description.
Definition window_gui.h:167
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:273
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1780
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
Definition window.cpp:3207
void RaiseWidget(WidgetID widget_index)
Marks a widget as raised.
Definition window_gui.h:469
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:504
ResizeInfo resize
Resize information.
Definition window_gui.h:314
int scale
Scale of this window – used to determine how to resize.
Definition window_gui.h:304
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1770
bool IsWidgetDisabled(WidgetID widget_index) const
Gets the enabled/disabled status of a widget.
Definition window_gui.h:410
int left
x position of left edge of the window
Definition window_gui.h:309
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
Definition window.cpp:212
void LowerWidget(WidgetID widget_index)
Marks a widget as lowered.
Definition window_gui.h:460
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1793
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:381
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:450
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:302
Definition of Interval and OneShot timers.
Definition of the game-economy-timer.
Definition of the tick-based game-timer.
Definition of the Window system.
static constexpr std::chrono::milliseconds TIMER_BLINK_INTERVAL
Interval used by blinking interface elements.
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:298
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:49
std::unique_ptr< NWidgetBase > MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
Make a number of rows with button-like graphics, for enabling/disabling each company.
Definition widget.cpp:3446
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:71
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:67
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:58
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:51
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:53
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:77
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:69
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:61
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:38
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:60
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:57
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:50
@ EqualSize
Containers should keep all their (resizing) children equally large.
@ RWV_HIDE_BEVEL
Bevel of resize box is hidden.
Definition widget_type.h:30
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3267
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting)
Definition window.cpp:3149
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ Lowered
If set the frame is lowered and the background colour brighter (ie. buttons when pressed)
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h:144
int WidgetID
Widget ID.
Definition window_type.h:20
@ WC_PERFORMANCE_HISTORY
Performance history graph; Window numbers:
@ WC_PERFORMANCE_DETAIL
Performance detail window; Window numbers:
@ WC_PAYMENT_RATES
Payment rates graph; Window numbers:
@ WC_GRAPH_LEGEND
Legend for graphs; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:47
@ WC_OPERATING_PROFIT
Operating profit graph; Window numbers:
@ WC_INDUSTRY_PRODUCTION
Industry production history graph; Window numbers:
@ WC_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.