OpenTTD Source 20260512-master-g20b387b91f
graph_gui.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
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
42
43/* Apparently these don't play well with enums. */
44static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX);
45static const uint INVALID_DATAPOINT_POS = UINT_MAX;
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
54struct GraphLegendWindow : Window {
55 GraphLegendWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc)
56 {
57 this->InitNested(window_number);
58
59 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++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
70 CompanyID cid = (CompanyID)(widget - WID_GL_FIRST_COMPANY);
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(FontSize::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, Colours::Brown, widnum);
127 panel->SetMinimalSize(246, sprite_height + WidgetDimensions::unscaled.framerect.Vertical());
128 panel->SetMinimalTextLines(1, WidgetDimensions::unscaled.framerect.Vertical(), FontSize::Normal);
129 panel->SetFill(1, 1);
130 panel->SetToolTip(STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
131 vert->Add(std::move(panel));
132 }
133 return vert;
134}
135
136static constexpr std::initializer_list<NWidgetPart> _nested_graph_legend_widgets = {
139 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_KEY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
142 EndContainer(),
145 EndContainer(),
146};
147
150 WindowPosition::Automatic, "graph_legend", 0, 0,
152 {},
153 _nested_graph_legend_widgets
154);
155
156static void ShowGraphLegend()
157{
159}
160
163 OverflowSafeInt64 highest;
164 OverflowSafeInt64 lowest;
165};
166
167/******************/
168/* BASE OF GRAPHS */
169/*****************/
170
171struct BaseGraphWindow : Window {
172protected:
173 static const int GRAPH_MAX_DATASETS = 64;
174 static constexpr PixelColour GRAPH_BASE_COLOUR = GREY_SCALE(2);
175 static constexpr PixelColour GRAPH_GRID_COLOUR = GREY_SCALE(3);
176 static constexpr PixelColour GRAPH_AXIS_LINE_COLOUR = GREY_SCALE(1);
177 static constexpr PixelColour GRAPH_ZERO_LINE_COLOUR = GREY_SCALE(8);
178 static constexpr PixelColour GRAPH_YEAR_LINE_COLOUR = GREY_SCALE(5);
179 static const int GRAPH_NUM_MONTHS = 24;
180 static const int GRAPH_PAYMENT_RATE_STEPS = 20;
181 static const int PAYMENT_GRAPH_X_STEP_DAYS = 10;
182 static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20;
183 static const int ECONOMY_YEAR_MINUTES = 12;
184 static const int ECONOMY_QUARTER_MINUTES = 3;
185 static const int ECONOMY_MONTH_MINUTES = 1;
186
187 static const TextColour GRAPH_AXIS_LABEL_COLOUR = TC_BLACK;
188
189 static const int MIN_GRAPH_NUM_LINES_Y = 9;
190 static const int MIN_GRID_PIXEL_SIZE = 20;
191
192 struct GraphScale {
193 StringID label = STR_NULL;
194 uint8_t month_increment = 0;
195 int16_t x_values_increment = 0;
196 const HistoryRange *history_range = nullptr;
197 };
198
199 static inline constexpr GraphScale MONTHLY_SCALE_WALLCLOCK[] = {
200 {STR_GRAPH_LAST_24_MINUTES_TIME_LABEL, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
201 {STR_GRAPH_LAST_72_MINUTES_TIME_LABEL, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
202 {STR_GRAPH_LAST_288_MINUTES_TIME_LABEL, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
203 };
204
205 static inline constexpr GraphScale MONTHLY_SCALE_CALENDAR[] = {
206 {STR_GRAPH_LAST_24_MONTHS, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
207 {STR_GRAPH_LAST_24_QUARTERS, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
208 {STR_GRAPH_LAST_24_YEARS, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
209 };
210
211 uint64_t excluded_data = 0;
212 uint64_t excluded_range = 0;
213 uint64_t masked_range = 0;
214 uint8_t num_on_x_axis = 0;
215 uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
216
219 uint8_t month_increment = 3;
220
221 bool draw_dates = true;
222
228 bool x_values_reversed = true;
231
232 StringID format_str_y_axis{};
233
234 struct DataSet {
235 std::array<OverflowSafeInt64, GRAPH_NUM_MONTHS> values;
236 PixelColour colour;
237 uint8_t exclude_bit;
238 uint8_t range_bit;
239 uint8_t dash;
240 };
241 std::vector<DataSet> data{};
242
243 std::span<const StringID> ranges{};
244 std::span<const GraphScale> scales{};
245 uint8_t selected_scale = 0;
246
247 uint8_t highlight_data = UINT8_MAX;
248 uint8_t highlight_range = UINT8_MAX;
249 bool highlight_state = false;
250
251 struct BaseFiller {
253
254 inline void MakeZero(uint i) const { this->dataset.values[i] = 0; }
255 inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
256 };
257
258 template <typename Tprojection>
260 Tprojection proj;
261
262 inline void Fill(uint i, const auto &data) const { this->dataset.values[i] = std::invoke(this->proj, data); }
263 };
264
270 std::span<const OverflowSafeInt64> GetDataSetRange(const DataSet &dataset) const
271 {
272 return {std::begin(dataset.values), std::begin(dataset.values) + this->num_on_x_axis};
273 }
274
281 ValuesInterval GetValuesInterval(int num_hori_lines) const
282 {
283 assert(num_hori_lines > 0);
284
285 ValuesInterval current_interval;
286 current_interval.highest = INT64_MIN;
287 current_interval.lowest = INT64_MAX;
288
289 for (const DataSet &dataset : this->data) {
290 if (HasBit(this->excluded_data, dataset.exclude_bit)) continue;
291 if (HasBit(this->excluded_range, dataset.range_bit)) continue;
292
293 for (const OverflowSafeInt64 &datapoint : this->GetDataSetRange(dataset)) {
294 if (datapoint != INVALID_DATAPOINT) {
295 current_interval.highest = std::max(current_interval.highest, datapoint);
296 current_interval.lowest = std::min(current_interval.lowest, datapoint);
297 }
298 }
299 }
300
301 /* Always include zero in the shown range. */
302 double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest);
303 double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest;
304
305 /* Prevent showing values too close to the graph limits. */
306 abs_higher = (11.0 * abs_higher) / 10.0;
307 abs_lower = (11.0 * abs_lower) / 10.0;
308
309 int num_pos_grids;
310 OverflowSafeInt64 grid_size;
311
312 if (abs_lower != 0 || abs_higher != 0) {
313 /* The number of grids to reserve for the positive part is: */
314 num_pos_grids = (int)floor(0.5 + num_hori_lines * abs_higher / (abs_higher + abs_lower));
315
316 /* If there are any positive or negative values, force that they have at least one grid. */
317 if (num_pos_grids == 0 && abs_higher != 0) num_pos_grids++;
318 if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--;
319
320 /* Get the required grid size for each side and use the maximum one. */
321
322 OverflowSafeInt64 grid_size_higher = 0;
323 if (abs_higher > 0) {
324 grid_size_higher = abs_higher > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_higher);
325 grid_size_higher = (grid_size_higher + num_pos_grids - 1) / num_pos_grids;
326 }
327
328 OverflowSafeInt64 grid_size_lower = 0;
329 if (abs_lower > 0) {
330 grid_size_lower = abs_lower > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_lower);
331 grid_size_lower = (grid_size_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids);
332 }
333
334 grid_size = std::max(grid_size_higher, grid_size_lower);
335 } else {
336 /* If both values are zero, show an empty graph. */
337 num_pos_grids = num_hori_lines / 2;
338 grid_size = 1;
339 }
340
341 current_interval.highest = num_pos_grids * grid_size;
342 current_interval.lowest = -(num_hori_lines - num_pos_grids) * grid_size;
343 return current_interval;
344 }
345
352 uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
353 {
354 /* draw text strings on the y axis */
355 int64_t y_label = current_interval.highest;
356 int64_t y_label_separation = (current_interval.highest - current_interval.lowest) / num_hori_lines;
357
358 uint max_width = 0;
359
360 for (int i = 0; i < (num_hori_lines + 1); i++) {
361 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label));
362 if (d.width > max_width) max_width = d.width;
363
364 y_label -= y_label_separation;
365 }
366
367 return max_width;
368 }
369
374 void DrawGraph(Rect r) const
375 {
376 uint x, y;
377 ValuesInterval interval;
378 int x_axis_offset;
379
380 /* the colours and cost array of GraphDrawer must accommodate
381 * both values for cargo and companies. So if any are higher, quit */
382 static_assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
383 assert(this->num_vert_lines > 0);
384
385 bool rtl = _current_text_dir == TD_RTL;
386
387 /* Rect r will be adjusted to contain just the graph, with labels being
388 * placed outside the area. */
390 r.bottom -= (this->draw_dates ? 2 : 1) * GetCharacterHeight(FontSize::Small) + ScaleGUITrad(4);
391 r.left += ScaleGUITrad(rtl ? 5 : 9);
392 r.right -= ScaleGUITrad(rtl ? 9 : 5);
393
394 /* Initial number of horizontal lines. */
395 int num_hori_lines = 160 / ScaleGUITrad(MIN_GRID_PIXEL_SIZE);
396 /* For the rest of the height, the number of horizontal lines will increase more slowly. */
397 int resize = (r.bottom - r.top - 160) / (2 * ScaleGUITrad(MIN_GRID_PIXEL_SIZE));
398 if (resize > 0) num_hori_lines += resize;
399
400 interval = GetValuesInterval(num_hori_lines);
401
402 int label_width = GetYLabelWidth(interval, num_hori_lines);
403
404 if (rtl) {
405 r.right -= label_width;
406 } else {
407 r.left += label_width;
408 }
409
410 int x_sep = (r.right - r.left) / this->num_vert_lines;
411 int y_sep = (r.bottom - r.top) / num_hori_lines;
412
413 /* Redetermine right and bottom edge of graph to fit with the integer
414 * separation values. */
415 if (rtl) {
416 r.left = r.right - x_sep * this->num_vert_lines;
417 } else {
418 r.right = r.left + x_sep * this->num_vert_lines;
419 }
420 r.bottom = r.top + y_sep * num_hori_lines;
421
422 OverflowSafeInt64 interval_size = interval.highest + abs(interval.lowest);
423 /* Where to draw the X axis. Use floating point to avoid overflowing and results of zero. */
424 x_axis_offset = (int)((r.bottom - r.top) * (double)interval.highest / (double)interval_size);
425
426 /* Draw the background of the graph itself. */
427 GfxFillRect(r.left, r.top, r.right, r.bottom, GRAPH_BASE_COLOUR);
428
429 /* Draw the grid lines. */
430 int gridline_width = WidgetDimensions::scaled.bevel.top;
431 PixelColour grid_colour = GRAPH_GRID_COLOUR;
432
433 /* Don't draw the first line, as that's where the axis will be. */
434 if (rtl) {
435 x_sep = -x_sep;
436 x = r.right + x_sep;
437 } else {
438 x = r.left + x_sep;
439 }
440
441 for (int i = 1; i < this->num_vert_lines + 1; i++) {
442 /* If using wallclock units, we separate periods with a lighter line. */
444 grid_colour = (i % 4 == 0) ? GRAPH_YEAR_LINE_COLOUR : GRAPH_GRID_COLOUR;
445 }
446 GfxFillRect(x, r.top, x + gridline_width - 1, r.bottom, grid_colour);
447 x += x_sep;
448 }
449
450 /* Draw the horizontal grid lines. */
451 y = r.bottom;
452
453 for (int i = 0; i < (num_hori_lines + 1); i++) {
454 if (rtl) {
455 GfxFillRect(r.right + 1, y, r.right + ScaleGUITrad(3), y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
456 } else {
457 GfxFillRect(r.left - ScaleGUITrad(3), y, r.left - 1, y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
458 }
459 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_GRID_COLOUR);
460 y -= y_sep;
461 }
462
463 /* Draw the y axis. */
464 GfxFillRect(r.left, r.top, r.left + gridline_width - 1, r.bottom + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
465
466 /* Draw the x axis. */
467 y = x_axis_offset + r.top;
468 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_ZERO_LINE_COLOUR);
469
470 /* Find the largest value that will be drawn. */
471 if (this->num_on_x_axis == 0) return;
472
473 assert(this->num_on_x_axis > 0);
474
475 /* draw text strings on the y axis */
476 int64_t y_label = interval.highest;
477 int64_t y_label_separation = abs(interval.highest - interval.lowest) / num_hori_lines;
478
479 y = r.top - GetCharacterHeight(FontSize::Small) / 2;
480
481 for (int i = 0; i < (num_hori_lines + 1); i++) {
482 if (rtl) {
483 DrawString(r.right + ScaleGUITrad(4), r.right + label_width + ScaleGUITrad(4), y,
484 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
486 } else {
487 DrawString(r.left - label_width - ScaleGUITrad(4), r.left - ScaleGUITrad(4), y,
488 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
490 }
491
492 y_label -= y_label_separation;
493 y += y_sep;
494 }
495
496 x = rtl ? r.right : r.left;
497 y = r.bottom + ScaleGUITrad(2);
498
499 /* if there are not enough datapoints to fill the graph, align to the right */
500 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
501
502 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
503 if (this->draw_dates) {
504 TimerGameEconomy::Month mo = this->month;
505 TimerGameEconomy::Year yr = this->year;
506 for (int i = 0; i < this->num_on_x_axis; i++) {
507 if (rtl) {
508 DrawStringMultiLineWithClipping(x + x_sep, x, y, this->height,
509 GetString(mo == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + mo, yr),
511 } else {
512 DrawStringMultiLineWithClipping(x, x + x_sep, y, this->height,
513 GetString(mo == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + mo, yr),
515 }
516
517 mo += this->month_increment;
518 if (mo >= 12) {
519 mo = 0;
520 yr++;
521
522 /* Draw a lighter grid line between years. Top and bottom adjustments ensure we don't draw over top and bottom horizontal grid lines. */
523 GfxFillRect(x + x_sep, r.top + gridline_width, x + x_sep + gridline_width - 1, r.bottom - 1, GRAPH_YEAR_LINE_COLOUR);
524 }
525 x += x_sep;
526 }
527 } else {
528 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates, and all graphs when using wallclock units). */
529 int16_t iterator;
530 uint16_t label;
531 if (this->x_values_reversed) {
532 label = this->x_values_increment * this->num_on_x_axis;
533 iterator = -this->x_values_increment;
534 } else {
535 label = this->x_values_increment;
536 iterator = this->x_values_increment;
537 }
538
539 for (int i = 0; i < this->num_on_x_axis; i++) {
540 if (rtl) {
541 DrawString(x + x_sep + 1, x - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
542 } else {
543 DrawString(x + 1, x + x_sep - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
544 }
545
546 label += iterator;
547 x += x_sep;
548 }
549 }
550
551 /* Draw lines and dots. */
552 uint linewidth = ScaleGUITrad(_settings_client.gui.graph_line_thickness);
553 uint pointwidth = ScaleGUITrad(_settings_client.gui.graph_line_thickness + 1);
554 uint pointoffs1 = pointwidth / 2;
555 uint pointoffs2 = pointwidth - pointoffs1;
556
557 auto draw_dataset = [&](const DataSet &dataset, PixelColour colour) {
558 if (HasBit(this->excluded_data, dataset.exclude_bit)) return;
559 if (HasBit(this->excluded_range, dataset.range_bit)) return;
560
561 /* Centre the dot between the grid lines. */
562 if (rtl) {
563 x = r.right + (x_sep / 2);
564 } else {
565 x = r.left + (x_sep / 2);
566 }
567
568 /* if there are not enough datapoints to fill the graph, align to the right */
569 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
570
571 uint prev_x = INVALID_DATAPOINT_POS;
572 uint prev_y = INVALID_DATAPOINT_POS;
573
574 const uint dash = ScaleGUITrad(dataset.dash);
575 for (OverflowSafeInt64 datapoint : this->GetDataSetRange(dataset)) {
576 if (datapoint != INVALID_DATAPOINT) {
577 /*
578 * Check whether we need to reduce the 'accuracy' of the
579 * datapoint value and the highest value to split overflows.
580 * And when 'drawing' 'one million' or 'one million and one'
581 * there is no significant difference, so the least
582 * significant bits can just be removed.
583 *
584 * If there are more bits needed than would fit in a 32 bits
585 * integer, so at about 31 bits because of the sign bit, the
586 * least significant bits are removed.
587 */
588 int mult_range = FindLastBit<uint32_t>(x_axis_offset) + FindLastBit<uint64_t>(abs(datapoint));
589 int reduce_range = std::max(mult_range - 31, 0);
590
591 /* Handle negative values differently (don't shift sign) */
592 if (datapoint < 0) {
593 datapoint = -(abs(datapoint) >> reduce_range);
594 } else {
595 datapoint >>= reduce_range;
596 }
597 y = r.top + x_axis_offset - ((r.bottom - r.top) * datapoint) / (interval_size >> reduce_range);
598
599 /* Draw the point. */
600 GfxFillRect(x - pointoffs1, y - pointoffs1, x + pointoffs2, y + pointoffs2, colour);
601
602 /* Draw the line connected to the previous point. */
603 if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, colour, linewidth, dash);
604
605 prev_x = x;
606 prev_y = y;
607 } else {
608 prev_x = INVALID_DATAPOINT_POS;
609 prev_y = INVALID_DATAPOINT_POS;
610 }
611
612 x += x_sep;
613 }
614 };
615
616 /* Draw unhighlighted datasets. */
617 for (const DataSet &dataset : this->data) {
618 if (dataset.exclude_bit != this->highlight_data && dataset.range_bit != this->highlight_range) {
619 draw_dataset(dataset, dataset.colour);
620 }
621 }
622
623 /* If any dataset or range is highlighted, draw separately after the rest so they appear on top of all other
624 * data. Highlighted data is only drawn when highlight_state is set, otherwise it is invisible. */
625 if (this->highlight_state && (this->highlight_data != UINT8_MAX || this->highlight_range != UINT8_MAX)) {
626 for (const DataSet &dataset : this->data) {
627 if (dataset.exclude_bit == this->highlight_data || dataset.range_bit == this->highlight_range) {
628 draw_dataset(dataset, PC_WHITE);
629 }
630 }
631 }
632 }
633
634 BaseGraphWindow(WindowDesc &desc, StringID format_str_y_axis) :
635 Window(desc),
636 format_str_y_axis(format_str_y_axis)
637 {
639 }
640
641 const IntervalTimer<TimerWindow> blink_interval = {TIMER_BLINK_INTERVAL, [this](auto) {
642 /* If nothing is highlighted then no redraw is needed. */
643 if (this->highlight_data == UINT8_MAX && this->highlight_range == UINT8_MAX) return;
644
645 /* Toggle the highlight state and redraw. */
646 this->highlight_state = !this->highlight_state;
647 this->SetDirty();
648 }};
649
650 void UpdateMatrixSize(WidgetID widget, Dimension &size, Dimension &resize, auto labels)
651 {
652 size = {};
653 for (const StringID &str : labels) {
654 size = maxdim(size, GetStringBoundingBox(str, FontSize::Small));
655 }
656
659
660 /* Set fixed height for number of ranges. */
661 size.height *= static_cast<uint>(std::size(labels));
662
663 resize.width = 0;
664 resize.height = 0;
665 this->GetWidget<NWidgetCore>(widget)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(labels)));
666 }
667
668public:
669 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
670 {
671 switch (widget) {
673 this->UpdateMatrixSize(widget, size, resize, this->ranges);
674 break;
675
677 this->UpdateMatrixSize(widget, size, resize, this->scales | std::views::transform(&GraphScale::label));
678 break;
679
680 case WID_GRAPH_GRAPH: {
681 uint x_label_width = 0;
682
683 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
684 if (this->draw_dates) {
685 uint yr = GetParamMaxValue(this->year.base(), 4, FontSize::Small);
686 for (uint mo = 0; mo < 12; ++mo) {
687 x_label_width = std::max(x_label_width, GetStringBoundingBox(GetString(mo == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + mo, yr)).width);
688 }
689 } else {
690 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates). */
691 uint64_t max_value = GetParamMaxValue((this->num_on_x_axis + 1) * this->x_values_increment, 0, FontSize::Small);
692 x_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL_NUMBER, max_value)).width;
693 }
694
695 uint y_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, INT64_MAX)).width;
696
697 size.width = std::max<uint>(size.width, ScaleGUITrad(5) + y_label_width + this->num_vert_lines * (x_label_width + ScaleGUITrad(5)) + ScaleGUITrad(9));
698 size.height = std::max<uint>(size.height, ScaleGUITrad(5) + (1 + MIN_GRAPH_NUM_LINES_Y * 2 + (this->draw_dates ? 3 : 1)) * GetCharacterHeight(FontSize::Small) + ScaleGUITrad(4));
699 size.height = std::max<uint>(size.height, size.width / 3);
700 break;
701 }
702
703 default: break;
704 }
705 }
706
707 void DrawWidget(const Rect &r, WidgetID widget) const override
708 {
709 switch (widget) {
710 case WID_GRAPH_GRAPH:
711 this->DrawGraph(r);
712 break;
713
715 uint line_height = GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical();
716 uint index = 0;
717 Rect line = r.WithHeight(line_height);
718 for (const auto &str : this->ranges) {
719 bool lowered = !HasBit(this->excluded_range, index) && !HasBit(this->masked_range, index);
720
721 /* Redraw frame if lowered */
723
724 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
725 DrawString(text, str, (this->highlight_state && this->highlight_range == index) ? TC_WHITE : TC_BLACK, SA_CENTER, false, FontSize::Small);
726
727 if (HasBit(this->masked_range, index)) {
729 }
730
731 line = line.Translate(0, line_height);
732 ++index;
733 }
734 break;
735 }
736
738 uint line_height = GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical();
739 uint8_t selected_month_increment = this->scales[this->selected_scale].month_increment;
740 Rect line = r.WithHeight(line_height);
741 for (const auto &scale : this->scales) {
742 /* Redraw frame if selected */
743 if (selected_month_increment == scale.month_increment) DrawFrameRect(line, Colours::Brown, FrameFlag::Lowered);
744
745 DrawString(line.Shrink(WidgetDimensions::scaled.framerect), scale.label, TC_BLACK, SA_CENTER, false, FontSize::Small);
746
747 line = line.Translate(0, line_height);
748 }
749 break;
750 }
751
752 default: break;
753 }
754 }
755
756 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
757 {
758 /* Clicked on legend? */
759 switch (widget) {
761 ShowGraphLegend();
762 break;
763
765 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical());
766
767 if (HasBit(this->masked_range, row)) break;
768 ToggleBit(this->excluded_range, row);
769 SndClickBeep();
770 this->SetDirty();
771 break;
772 }
773
775 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical());
776 const auto &scale = this->scales[row];
777 if (this->selected_scale != row) {
778 this->selected_scale = row;
779 this->month_increment = scale.month_increment;
780 this->x_values_increment = scale.x_values_increment;
781 this->InvalidateData();
782 }
783 break;
784 }
785
786 default: break;
787 }
788 }
789
790 void OnMouseOver(Point pt, WidgetID widget) override
791 {
792 /* Test if a range should be highlighted. */
793 uint8_t new_highlight_range = UINT8_MAX;
794 if (widget == WID_GRAPH_RANGE_MATRIX) {
795 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical());
796 if (!HasBit(this->excluded_range, row)) new_highlight_range = static_cast<uint8_t>(row);
797 }
798
799 /* Test if a dataset should be highlighted. */
800 uint8_t new_highlight_data = UINT8_MAX;
801 if (widget == WID_GRAPH_MATRIX) {
802 auto dataset_index = this->GetDatasetIndex(pt.y);
803 if (dataset_index.has_value() && !HasBit(this->excluded_data, *dataset_index)) new_highlight_data = *dataset_index;
804 }
805
806 if (this->highlight_data == new_highlight_data && this->highlight_range == new_highlight_range) return;
807
808 /* Range or data set highlight has changed, set and redraw. */
809 this->highlight_data = new_highlight_data;
810 this->highlight_range = new_highlight_range;
811 this->highlight_state = true;
812 this->SetDirty();
813 }
814
815 void OnGameTick() override
816 {
817 this->UpdateStatistics(false);
818 }
819
825 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
826 {
827 if (!gui_scope) return;
828 this->UpdateStatistics(true);
829 }
830
835 virtual void UpdateStatistics(bool initialize) = 0;
836
842 virtual std::optional<uint8_t> GetDatasetIndex([[maybe_unused]] int y) { return std::nullopt; }
843};
844
845class BaseCompanyGraphWindow : public BaseGraphWindow {
846public:
847 BaseCompanyGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
848
849 void InitializeWindow(WindowNumber number)
850 {
851 /* Initialise the dataset */
852 this->UpdateStatistics(true);
853
854 this->CreateNestedTree();
855
856 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
857 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_LAST_72_MINUTES_TIME_LABEL : STR_EMPTY);
858
859 this->FinishInitNested(number);
860 }
861
862 void UpdateStatistics(bool initialize) override
863 {
864 CompanyMask excluded_companies = _legend_excluded_companies;
865
866 /* Exclude the companies which aren't valid */
867 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
868 if (!Company::IsValidID(c)) excluded_companies.Set(c);
869 }
870
871 uint8_t nums = 0;
872 for (const Company *c : Company::Iterate()) {
873 nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent));
874 }
875
876 int mo = (TimerGameEconomy::month / this->month_increment - nums) * this->month_increment;
877 auto yr = TimerGameEconomy::year;
878 while (mo < 0) {
879 yr--;
880 mo += 12;
881 }
882
883 if (!initialize && this->excluded_data == excluded_companies.base() && this->num_on_x_axis == nums &&
884 this->year == yr && this->month == mo) {
885 /* There's no reason to get new stats */
886 return;
887 }
888
889 this->excluded_data = excluded_companies.base();
890 this->num_on_x_axis = nums;
891 this->year = yr;
892 this->month = mo;
893
894 this->data.clear();
895 for (CompanyID k = CompanyID::Begin(); k < MAX_COMPANIES; ++k) {
896 const Company *c = Company::GetIfValid(k);
897 if (c == nullptr) continue;
898
899 DataSet &dataset = this->data.emplace_back();
900 dataset.colour = GetColourGradient(c->colour, Shade::Lighter);
901 dataset.exclude_bit = k.base();
902
903 for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
904 if (j >= c->num_valid_stat_ent) {
905 dataset.values[i] = INVALID_DATAPOINT;
906 } else {
907 /* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
908 * Instead, use the value just under it. Hopefully nobody will notice. */
909 dataset.values[i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
910 }
911 i++;
912 }
913 }
914 }
915
922 virtual OverflowSafeInt64 GetGraphData(const Company *c, int j) = 0;
923};
924
925
926/********************/
927/* OPERATING PROFIT */
928/********************/
929
930struct OperatingProfitGraphWindow : BaseCompanyGraphWindow {
931 OperatingProfitGraphWindow(WindowDesc &desc, WindowNumber window_number) :
932 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
933 {
934 this->num_on_x_axis = GRAPH_NUM_MONTHS;
935 this->num_vert_lines = GRAPH_NUM_MONTHS;
936 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
937
938 this->InitializeWindow(window_number);
939 }
940
941 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
942 {
943 return c->old_economy[j].income + c->old_economy[j].expenses;
944 }
945};
946
947static constexpr std::initializer_list<NWidgetPart> _nested_operating_profit_widgets = {
950 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
951 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
955 EndContainer(),
961 EndContainer(),
962 EndContainer(),
963};
964
967 WindowPosition::Automatic, "graph_operating_profit", 0, 0,
969 {},
970 _nested_operating_profit_widgets
971);
972
973
974void ShowOperatingProfitGraph()
975{
977}
978
979
980/****************/
981/* INCOME GRAPH */
982/****************/
983
984struct IncomeGraphWindow : BaseCompanyGraphWindow {
985 IncomeGraphWindow(WindowDesc &desc, WindowNumber window_number) :
986 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
987 {
988 this->num_on_x_axis = GRAPH_NUM_MONTHS;
989 this->num_vert_lines = GRAPH_NUM_MONTHS;
990 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
991
992 this->InitializeWindow(window_number);
993 }
994
995 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
996 {
997 return c->old_economy[j].income;
998 }
999};
1000
1001static constexpr std::initializer_list<NWidgetPart> _nested_income_graph_widgets = {
1004 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1005 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1009 EndContainer(),
1015 EndContainer(),
1016 EndContainer(),
1017};
1018
1021 WindowPosition::Automatic, "graph_income", 0, 0,
1023 {},
1024 _nested_income_graph_widgets
1025);
1026
1027void ShowIncomeGraph()
1028{
1030}
1031
1032/*******************/
1033/* DELIVERED CARGO */
1034/*******************/
1035
1036struct DeliveredCargoGraphWindow : BaseCompanyGraphWindow {
1037 DeliveredCargoGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1038 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1039 {
1040 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1041 this->num_vert_lines = GRAPH_NUM_MONTHS;
1042 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1043
1044 this->InitializeWindow(window_number);
1045 }
1046
1047 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1048 {
1049 return c->old_economy[j].delivered_cargo.GetSum<OverflowSafeInt64>();
1050 }
1051};
1052
1053static constexpr std::initializer_list<NWidgetPart> _nested_delivered_cargo_graph_widgets = {
1056 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1057 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1061 EndContainer(),
1067 EndContainer(),
1068 EndContainer(),
1069};
1070
1073 WindowPosition::Automatic, "graph_delivered_cargo", 0, 0,
1075 {},
1076 _nested_delivered_cargo_graph_widgets
1077);
1078
1079void ShowDeliveredCargoGraph()
1080{
1082}
1083
1084/***********************/
1085/* PERFORMANCE HISTORY */
1086/***********************/
1087
1088struct PerformanceHistoryGraphWindow : BaseCompanyGraphWindow {
1089 PerformanceHistoryGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1090 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1091 {
1092 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1093 this->num_vert_lines = GRAPH_NUM_MONTHS;
1094 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1095
1096 this->InitializeWindow(window_number);
1097 }
1098
1099 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1100 {
1101 return c->old_economy[j].performance_history;
1102 }
1103
1104 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1105 {
1106 if (widget == WID_PHG_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
1107 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1108 }
1109};
1110
1111static constexpr std::initializer_list<NWidgetPart> _nested_performance_history_widgets = {
1114 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1115 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_PHG_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetStringTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
1116 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1120 EndContainer(),
1126 EndContainer(),
1127 EndContainer(),
1128};
1129
1132 WindowPosition::Automatic, "graph_performance", 0, 0,
1134 {},
1135 _nested_performance_history_widgets
1136);
1137
1138void ShowPerformanceHistoryGraph()
1139{
1141}
1142
1143/*****************/
1144/* COMPANY VALUE */
1145/*****************/
1146
1147struct CompanyValueGraphWindow : BaseCompanyGraphWindow {
1148 CompanyValueGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1149 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1150 {
1151 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1152 this->num_vert_lines = GRAPH_NUM_MONTHS;
1153 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1154
1155 this->InitializeWindow(window_number);
1156 }
1157
1158 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1159 {
1160 return c->old_economy[j].company_value;
1161 }
1162};
1163
1164static constexpr std::initializer_list<NWidgetPart> _nested_company_value_graph_widgets = {
1167 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1168 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1172 EndContainer(),
1178 EndContainer(),
1179 EndContainer(),
1180};
1181
1184 WindowPosition::Automatic, "graph_company_value", 0, 0,
1186 {},
1187 _nested_company_value_graph_widgets
1188);
1189
1190void ShowCompanyValueGraph()
1191{
1193}
1194
1195struct BaseCargoGraphWindow : BaseGraphWindow {
1196 Scrollbar *vscroll = nullptr;
1197 uint line_height = 0;
1198 uint legend_width = 0;
1199
1200 CargoTypes cargo_types{};
1201
1202 BaseCargoGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
1203
1204 void InitializeWindow(WindowNumber number, StringID footer_wallclock = STR_NULL, StringID footer_calendar = STR_NULL)
1205 {
1206 this->CreateNestedTree();
1207
1208 this->excluded_range = this->masked_range;
1209 this->cargo_types = this->GetCargoTypes(number);
1210
1212 this->vscroll->SetCount(this->cargo_types.Count());
1213
1214 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1215 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? footer_wallclock : footer_calendar);
1216
1217 this->FinishInitNested(number);
1218
1219 /* Initialise the dataset */
1220 this->InvalidateData();
1221 }
1222
1228 virtual CargoTypes GetCargoTypes(WindowNumber number) const = 0;
1229
1234 virtual CargoTypes &GetExcludedCargoTypes() const = 0;
1235
1236 std::optional<uint8_t> GetDatasetIndex(int y) override
1237 {
1238 int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_GRAPH_MATRIX);
1239 if (row >= this->vscroll->GetCount()) return std::nullopt;
1240
1241 for (const CargoSpec *cs : _sorted_cargo_specs) {
1242 if (!this->cargo_types.Test(cs->Index())) continue;
1243 if (row-- > 0) continue;
1244
1245 return cs->Index();
1246 }
1247
1248 return std::nullopt;
1249 }
1250
1251 void OnInit() override
1252 {
1253 /* Width of the legend blob. */
1254 this->legend_width = GetCharacterHeight(FontSize::Small) * 9 / 6;
1255 }
1256
1257 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1258 {
1259 if (widget != WID_GRAPH_MATRIX) {
1260 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1261 return;
1262 }
1263
1264 size.height = GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical();
1265
1266 for (CargoType cargo_type : this->cargo_types) {
1267 const CargoSpec *cs = CargoSpec::Get(cargo_type);
1268
1269 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1270 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1271 d.width += WidgetDimensions::scaled.framerect.Horizontal();
1272 d.height += WidgetDimensions::scaled.framerect.Vertical();
1273 size = maxdim(d, size);
1274 }
1275
1276 this->line_height = size.height;
1277 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1278 resize.width = 0;
1279 fill.height = resize.height = this->line_height;
1280 }
1281
1282 void DrawWidget(const Rect &r, WidgetID widget) const override
1283 {
1284 if (widget != WID_GRAPH_MATRIX) {
1285 BaseGraphWindow::DrawWidget(r, widget);
1286 return;
1287 }
1288
1289 bool rtl = _current_text_dir == TD_RTL;
1290
1291 int pos = this->vscroll->GetPosition();
1292 int max = pos + this->vscroll->GetCapacity();
1293
1294 Rect line = r.WithHeight(this->line_height);
1295
1296 for (const CargoSpec *cs : _sorted_cargo_specs) {
1297 if (!this->cargo_types.Test(cs->Index())) continue;
1298
1299 if (pos-- > 0) continue;
1300 if (--max < 0) break;
1301
1302 bool lowered = !CargoTypes{this->excluded_data}.Test(cs->Index());
1303
1304 /* Redraw frame if lowered */
1305 if (lowered) DrawFrameRect(line, Colours::Brown, FrameFlag::Lowered);
1306
1307 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1308
1309 /* Cargo-colour box with outline */
1310 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1311 GfxFillRect(cargo, PC_BLACK);
1312 PixelColour pc = cs->legend_colour;
1313 if (this->highlight_data == cs->Index()) pc = this->highlight_state ? PC_WHITE : PC_BLACK;
1315
1316 /* Cargo name */
1317 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1318
1319 line = line.Translate(0, this->line_height);
1320 }
1321 }
1322
1323 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1324 {
1325 switch (widget) {
1327 /* Remove all cargoes from the excluded lists. */
1328 this->GetExcludedCargoTypes() = {};
1329 this->excluded_data = this->GetExcludedCargoTypes().base();
1330 this->SetDirty();
1331 break;
1332
1334 /* Add all cargoes to the excluded lists. */
1335 this->GetExcludedCargoTypes() = this->cargo_types;
1336 this->excluded_data = this->GetExcludedCargoTypes().base();
1337 this->SetDirty();
1338 break;
1339 }
1340
1341 case WID_GRAPH_MATRIX: {
1342 int row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GRAPH_MATRIX);
1343 if (row >= this->vscroll->GetCount()) return;
1344
1345 SndClickBeep();
1346
1347 for (const CargoSpec *cs : _sorted_cargo_specs) {
1348 if (!this->cargo_types.Test(cs->Index())) continue;
1349 if (row-- > 0) continue;
1350
1351 this->GetExcludedCargoTypes().Flip(cs->Index());
1352 this->excluded_data = this->GetExcludedCargoTypes().base();
1353 this->SetDirty();
1354 break;
1355 }
1356 break;
1357 }
1358
1359 default:
1360 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1361 break;
1362 }
1363 }
1364
1365 void OnResize() override
1366 {
1367 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1368 }
1369};
1370
1371/*****************/
1372/* PAYMENT RATES */
1373/*****************/
1374
1375struct PaymentRatesGraphWindow : BaseCargoGraphWindow {
1376 static inline CargoTypes excluded_cargo_types{};
1377
1378 PaymentRatesGraphWindow(WindowDesc &desc, WindowNumber window_number) : BaseCargoGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1379 {
1380 this->num_on_x_axis = GRAPH_PAYMENT_RATE_STEPS;
1381 this->num_vert_lines = GRAPH_PAYMENT_RATE_STEPS;
1382 this->draw_dates = false;
1383
1384 this->x_values_reversed = false;
1385 /* The x-axis is labeled in either seconds or days. A day is two seconds, so we adjust the label if needed. */
1387
1388 this->InitializeWindow(window_number, STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS, STR_GRAPH_CARGO_PAYMENT_RATES_DAYS);
1389 }
1390
1391 CargoTypes GetCargoTypes(WindowNumber) const override
1392 {
1393 return _standard_cargo_mask;
1394 }
1395
1396 CargoTypes &GetExcludedCargoTypes() const override
1397 {
1398 return PaymentRatesGraphWindow::excluded_cargo_types;
1399 }
1400
1406 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1407 {
1408 if (!gui_scope) return;
1409 this->UpdatePaymentRates();
1410 }
1411
1413 const IntervalTimer<TimerWindow> update_payment_interval = {std::chrono::seconds(3), [this](auto) {
1414 this->UpdatePaymentRates();
1415 }};
1416
1417 void UpdateStatistics(bool) override {}
1418
1423 {
1424 this->excluded_data = this->GetExcludedCargoTypes().base();
1425
1426 this->data.clear();
1427 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1428 DataSet &dataset = this->data.emplace_back();
1429 dataset.colour = cs->legend_colour;
1430 dataset.exclude_bit = cs->Index();
1431
1432 for (uint j = 0; j != this->num_on_x_axis; j++) {
1433 dataset.values[j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
1434 }
1435 }
1436 }
1437};
1438
1439static constexpr std::initializer_list<NWidgetPart> _nested_cargo_payment_rates_widgets = {
1442 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1446 EndContainer(),
1448 NWidget(WWT_TEXT, Colours::Invalid, WID_GRAPH_HEADER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_TITLE), SetTextStyle(TC_BLACK, FontSize::Small), SetAlignment(SA_CENTER),
1452 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1453 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1454 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1457 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
1459 EndContainer(),
1460 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1461 EndContainer(),
1462 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1463 EndContainer(),
1467 EndContainer(),
1468 EndContainer(),
1469};
1470
1473 WindowPosition::Automatic, "graph_cargo_payment_rates", 0, 0,
1475 {},
1476 _nested_cargo_payment_rates_widgets
1477);
1478
1479
1480void ShowCargoPaymentRates()
1481{
1483}
1484
1485/*****************************/
1486/* PERFORMANCE RATING DETAIL */
1487/*****************************/
1488
1489struct PerformanceRatingDetailWindow : Window {
1490 static CompanyID company;
1491 int timeout = 0;
1492 uint score_info_left = 0;
1493 uint score_info_right = 0;
1494 uint bar_left = 0;
1495 uint bar_right = 0;
1496 uint bar_width = 0;
1497 uint bar_height = 0;
1498 uint score_detail_left = 0;
1499 uint score_detail_right = 0;
1500
1501 PerformanceRatingDetailWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc)
1502 {
1503 this->UpdateCompanyStats();
1504
1505 this->InitNested(window_number);
1506 this->OnInvalidateData(CompanyID::Invalid().base());
1507 }
1508
1509 void UpdateCompanyStats()
1510 {
1511 /* Update all company stats with the current data
1512 * (this is because _score_info is not saved to a savegame) */
1513 for (Company *c : Company::Iterate()) {
1515 }
1516
1517 this->timeout = Ticks::DAY_TICKS * 5;
1518 }
1519
1520 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1521 {
1522 switch (widget) {
1524 this->bar_height = GetCharacterHeight(FontSize::Normal) + WidgetDimensions::scaled.fullbevel.Vertical();
1525 size.height = this->bar_height + WidgetDimensions::scaled.matrix.Vertical();
1526
1527 uint score_info_width = 0;
1528 for (ScoreID i = ScoreID::Begin; i < ScoreID::End; i++) {
1529 score_info_width = std::max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + to_underlying(i)).width);
1530 }
1531 score_info_width += GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(1000))).width + WidgetDimensions::scaled.hsep_wide;
1532
1533 this->bar_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_PERCENT, GetParamMaxValue(100))).width + WidgetDimensions::scaled.hsep_indent * 2; // Wide bars!
1534
1535 /* At this number we are roughly at the max; it can become wider,
1536 * but then you need at 1000 times more money. At that time you're
1537 * not that interested anymore in the last few digits anyway.
1538 * The 500 is because 999 999 500 to 999 999 999 are rounded to
1539 * 1 000 M, and not 999 999 k. Use negative numbers to account for
1540 * the negative income/amount of money etc. as well. */
1541 int max = -(999999999 - 500);
1542
1543 /* Scale max for the display currency. Prior to rendering the value
1544 * is converted into the display currency, which may cause it to
1545 * raise significantly. We need to compensate for that since {{CURRCOMPACT}}
1546 * is used, which can produce quite short renderings of very large
1547 * values. Otherwise the calculated width could be too narrow.
1548 * Note that it doesn't work if there was a currency with an exchange
1549 * rate greater than max.
1550 * When the currency rate is more than 1000, the 999 999 k becomes at
1551 * least 999 999 M which roughly is equally long. Furthermore if the
1552 * exchange rate is that high, 999 999 k is usually not enough anymore
1553 * to show the different currency numbers. */
1554 if (GetCurrency().rate < 1000) max /= GetCurrency().rate;
1555 uint score_detail_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, max, max)).width;
1556
1557 size.width = WidgetDimensions::scaled.frametext.Horizontal() + score_info_width + WidgetDimensions::scaled.hsep_wide + this->bar_width + WidgetDimensions::scaled.hsep_wide + score_detail_width;
1558 uint left = WidgetDimensions::scaled.frametext.left;
1559 uint right = size.width - WidgetDimensions::scaled.frametext.right;
1560
1561 bool rtl = _current_text_dir == TD_RTL;
1562 this->score_info_left = rtl ? right - score_info_width : left;
1563 this->score_info_right = rtl ? right : left + score_info_width;
1564
1565 this->score_detail_left = rtl ? left : right - score_detail_width;
1566 this->score_detail_right = rtl ? left + score_detail_width : right;
1567
1568 this->bar_left = left + (rtl ? score_detail_width : score_info_width) + WidgetDimensions::scaled.hsep_wide;
1569 this->bar_right = this->bar_left + this->bar_width - 1;
1570 break;
1571 }
1572 }
1573
1574 void DrawWidget(const Rect &r, WidgetID widget) const override
1575 {
1576 /* No need to draw when there's nothing to draw */
1577 if (this->company == CompanyID::Invalid()) return;
1578
1580 if (this->IsWidgetDisabled(widget)) return;
1581 CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1582 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
1583 DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height));
1584 return;
1585 }
1586
1587 if (!IsInsideMM(widget, WID_PRD_SCORE_FIRST, WID_PRD_SCORE_LAST + 1)) return;
1588
1589 ScoreID score_type = (ScoreID)(widget - WID_PRD_SCORE_FIRST);
1590
1591 /* The colours used to show how the progress is going */
1594
1595 /* Draw all the score parts */
1596 int64_t val = _score_part[company][score_type];
1597 int64_t needed = _score_info[score_type].needed;
1598 int score = _score_info[score_type].score;
1599
1600 /* ScoreID::Total has its own rules ;) */
1601 if (score_type == ScoreID::Total) {
1602 for (ScoreID i = ScoreID::Begin; i < ScoreID::End; i++) score += _score_info[i].score;
1603 needed = SCORE_MAX;
1604 }
1605
1606 uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height);
1607 uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FontSize::Normal));
1608
1609 DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + to_underlying(score_type));
1610
1611 /* Draw the score */
1612 DrawString(this->score_info_left, this->score_info_right, text_top, GetString(STR_JUST_COMMA, score), TC_BLACK, SA_RIGHT);
1613
1614 /* Calculate the %-bar */
1615 uint x = Clamp<int64_t>(val, 0, needed) * this->bar_width / needed;
1616 bool rtl = _current_text_dir == TD_RTL;
1617 if (rtl) {
1618 x = this->bar_right - x;
1619 } else {
1620 x = this->bar_left + x;
1621 }
1622
1623 /* Draw the bar */
1624 if (x != this->bar_left) GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height - 1, rtl ? colour_notdone : colour_done);
1625 if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height - 1, rtl ? colour_done : colour_notdone);
1626
1627 /* Draw it */
1628 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);
1629
1630 /* ScoreID::Loan is inverted */
1631 if (score_type == ScoreID::Loan) val = needed - val;
1632
1633 /* Draw the amount we have against what is needed
1634 * For some of them it is in currency format */
1635 switch (score_type) {
1636 case ScoreID::MinProfit:
1637 case ScoreID::MinIncome:
1638 case ScoreID::MaxIncome:
1639 case ScoreID::Money:
1640 case ScoreID::Loan:
1641 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, val, needed));
1642 break;
1643 default:
1644 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_INT, val, needed));
1645 break;
1646 }
1647 }
1648
1649 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1650 {
1651 /* Check which button is clicked */
1653 /* Is it no on disable? */
1654 if (!this->IsWidgetDisabled(widget)) {
1655 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1656 this->company = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1657 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1658 this->SetDirty();
1659 }
1660 }
1661 }
1662
1663 void OnGameTick() override
1664 {
1665 /* Update the company score every 5 days */
1666 if (--this->timeout == 0) {
1667 this->UpdateCompanyStats();
1668 this->SetDirty();
1669 }
1670 }
1671
1677 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1678 {
1679 if (!gui_scope) return;
1680 /* Disable the companies who are not active */
1681 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
1683 }
1684
1685 /* Check if the currently selected company is still active. */
1686 if (this->company != CompanyID::Invalid() && !Company::IsValidID(this->company)) {
1687 /* Raise the widget for the previous selection. */
1688 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1689 this->company = CompanyID::Invalid();
1690 }
1691
1692 if (this->company == CompanyID::Invalid()) {
1693 for (const Company *c : Company::Iterate()) {
1694 this->company = c->index;
1695 break;
1696 }
1697 }
1698
1699 /* Make sure the widget is lowered */
1700 if (this->company != CompanyID::Invalid()) {
1701 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1702 }
1703 }
1704};
1705
1706CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
1707
1708/*******************************/
1709/* INDUSTRY PRODUCTION HISTORY */
1710/*******************************/
1711
1712struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
1713 static inline constexpr StringID RANGE_LABELS[] = {
1714 STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
1715 STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED,
1716 STR_GRAPH_INDUSTRY_RANGE_DELIVERED,
1717 STR_GRAPH_INDUSTRY_RANGE_WAITING,
1718 };
1719
1720 static inline CargoTypes excluded_cargo_types{};
1721
1722 IndustryProductionGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1723 BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1724 {
1725 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1726 this->num_vert_lines = GRAPH_NUM_MONTHS;
1727 this->month_increment = 1;
1728 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1729 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1730 this->ranges = RANGE_LABELS;
1731
1733 if (!i->IsCargoProduced()) this->masked_range = (1U << 0) | (1U << 1);
1734 if (!i->IsCargoAccepted()) this->masked_range = (1U << 2) | (1U << 3);
1735
1736 this->InitializeWindow(window_number);
1737 }
1738
1739 void OnInit() override
1740 {
1742
1743 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1744 }
1745
1746 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1747 {
1748 CargoTypes cargo_types{};
1750 for (const auto &a : i->accepted) {
1751 if (IsValidCargoType(a.cargo)) cargo_types.Set(a.cargo);
1752 }
1753 for (const auto &p : i->produced) {
1754 if (IsValidCargoType(p.cargo)) cargo_types.Set(p.cargo);
1755 }
1756 return cargo_types;
1757 }
1758
1759 CargoTypes &GetExcludedCargoTypes() const override
1760 {
1761 return IndustryProductionGraphWindow::excluded_cargo_types;
1762 }
1763
1764 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1765 {
1766 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_CAPTION, this->window_number);
1767
1768 return this->Window::GetWidgetString(widget, stringid);
1769 }
1770
1771 void UpdateStatistics(bool initialize) override
1772 {
1773 int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
1774 auto yr = TimerGameEconomy::year;
1775 while (mo < 0) {
1776 yr--;
1777 mo += 12;
1778 }
1779
1780 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes().base() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1781 /* There's no reason to get new stats */
1782 return;
1783 }
1784
1785 this->excluded_data = this->GetExcludedCargoTypes().base();
1786 this->year = yr;
1787 this->month = mo;
1788
1789 const Industry *i = Industry::Get(this->window_number);
1790
1791 this->data.clear();
1792 this->data.reserve(
1793 2 * std::ranges::count_if(i->produced, &IsValidCargoType, &Industry::ProducedCargo::cargo) +
1794 2 * std::ranges::count_if(i->accepted, &IsValidCargoType, &Industry::AcceptedCargo::cargo));
1795
1796 for (const auto &p : i->produced) {
1797 if (!IsValidCargoType(p.cargo)) continue;
1798 const CargoSpec *cs = CargoSpec::Get(p.cargo);
1799
1800 DataSet &produced = this->data.emplace_back();
1801 produced.colour = cs->legend_colour;
1802 produced.exclude_bit = cs->Index();
1803 produced.range_bit = 0;
1804
1805 DataSet &transported = this->data.emplace_back();
1806 transported.colour = cs->legend_colour;
1807 transported.exclude_bit = cs->Index();
1808 transported.range_bit = 1;
1809 transported.dash = 2;
1810
1811 FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, *this->scales[this->selected_scale].history_range,
1814 }
1815
1816 for (const auto &a : i->accepted) {
1817 if (!IsValidCargoType(a.cargo)) continue;
1818 const CargoSpec *cs = CargoSpec::Get(a.cargo);
1819
1820 DataSet &accepted = this->data.emplace_back();
1821 accepted.colour = cs->legend_colour;
1822 accepted.exclude_bit = cs->Index();
1823 accepted.range_bit = 2;
1824 accepted.dash = 1;
1825
1826 DataSet &waiting = this->data.emplace_back();
1827 waiting.colour = cs->legend_colour;
1828 waiting.exclude_bit = cs->Index();
1829 waiting.range_bit = 3;
1830 waiting.dash = 4;
1831
1832 FillFromHistory<GRAPH_NUM_MONTHS>(a.history.get(), i->valid_history, *this->scales[this->selected_scale].history_range,
1833 Filler{{accepted}, &Industry::AcceptedHistory::accepted},
1835 }
1836
1837 this->SetDirty();
1838 }
1839};
1840
1841static constexpr std::initializer_list<NWidgetPart> _nested_industry_production_widgets = {
1848 EndContainer(),
1853 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1854 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_TOGGLE_RANGE),
1856 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1857 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1860 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
1862 EndContainer(),
1864 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_SELECT_SCALE),
1865 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1866 EndContainer(),
1867 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1868 EndContainer(),
1872 EndContainer(),
1873 EndContainer(),
1874};
1875
1878 WindowPosition::Automatic, "graph_industry_production", 0, 0,
1880 {},
1881 _nested_industry_production_widgets
1882);
1883
1884void ShowIndustryProductionGraph(WindowNumber window_number)
1885{
1887}
1888
1889struct TownCargoGraphWindow : BaseCargoGraphWindow {
1890 static inline constexpr StringID RANGE_LABELS[] = {
1891 STR_GRAPH_TOWN_RANGE_PRODUCED,
1892 STR_GRAPH_TOWN_RANGE_TRANSPORTED,
1893 STR_GRAPH_TOWN_RANGE_DELIVERED,
1894 };
1895
1896 static inline CargoTypes excluded_cargo_types{};
1897
1898 TownCargoGraphWindow(WindowDesc &desc, WindowNumber window_number) : BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1899 {
1900 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1901 this->num_vert_lines = GRAPH_NUM_MONTHS;
1902 this->month_increment = 1;
1903 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1904 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1905 this->ranges = RANGE_LABELS;
1906
1907 this->InitializeWindow(window_number);
1908 }
1909
1910 void OnInit() override
1911 {
1913
1914 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1915 }
1916
1917 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1918 {
1919 CargoTypes cargo_types{};
1920 const Town *t = Town::Get(window_number);
1921 for (const auto &s : t->supplied) {
1922 if (IsValidCargoType(s.cargo)) cargo_types.Set(s.cargo);
1923 }
1924 for (const auto &a : t->accepted) {
1925 if (IsValidCargoType(a.cargo)) cargo_types.Set(a.cargo);
1926 }
1927 return cargo_types;
1928 }
1929
1930 CargoTypes &GetExcludedCargoTypes() const override
1931 {
1932 return TownCargoGraphWindow::excluded_cargo_types;
1933 }
1934
1935 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1936 {
1937 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_TOWN_CARGO_CAPTION, this->window_number);
1938
1939 return this->Window::GetWidgetString(widget, stringid);
1940 }
1941
1942 void UpdateStatistics(bool initialize) override
1943 {
1944 int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
1945 auto yr = TimerGameEconomy::year;
1946 while (mo < 0) {
1947 yr--;
1948 mo += 12;
1949 }
1950
1951 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes().base() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1952 /* There's no reason to get new stats */
1953 return;
1954 }
1955
1956 this->excluded_data = this->GetExcludedCargoTypes().base();
1957 this->year = yr;
1958 this->month = mo;
1959
1960 const Town *t = Town::Get(this->window_number);
1961
1962 this->data.clear();
1963 this->data.reserve(
1964 2 * std::ranges::count_if(t->supplied, &IsValidCargoType, &Town::SuppliedCargo::cargo) +
1965 1 * std::ranges::count_if(t->accepted, &IsValidCargoType, &Town::AcceptedCargo::cargo));
1966
1967 for (const auto &s : t->supplied) {
1968 if (!IsValidCargoType(s.cargo)) continue;
1969 const CargoSpec *cs = CargoSpec::Get(s.cargo);
1970
1971 DataSet &produced = this->data.emplace_back();
1972 produced.colour = cs->legend_colour;
1973 produced.exclude_bit = cs->Index();
1974 produced.range_bit = 0;
1975
1976 DataSet &transported = this->data.emplace_back();
1977 transported.colour = cs->legend_colour;
1978 transported.exclude_bit = cs->Index();
1979 transported.range_bit = 1;
1980 transported.dash = 2;
1981
1982 FillFromHistory<GRAPH_NUM_MONTHS>(s.history, t->valid_history, *this->scales[this->selected_scale].history_range,
1985 }
1986
1987 for (const auto &a : t->accepted) {
1988 if (!IsValidCargoType(a.cargo)) continue;
1989 const CargoSpec *cs = CargoSpec::Get(a.cargo);
1990
1991 DataSet &accepted = this->data.emplace_back();
1992 accepted.colour = cs->legend_colour;
1993 accepted.exclude_bit = cs->Index();
1994 accepted.range_bit = 2;
1995 accepted.dash = 1;
1996
1997 FillFromHistory<GRAPH_NUM_MONTHS>(a.history, t->valid_history, *this->scales[this->selected_scale].history_range,
1998 Filler{{accepted}, &Town::AcceptedHistory::accepted});
1999 }
2000
2001 this->SetDirty();
2002 }
2003};
2004
2005static constexpr std::initializer_list<NWidgetPart> _nested_town_cargo_graph_widgets = {
2012 EndContainer(),
2017 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
2018 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
2020 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
2021 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
2024 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
2026 EndContainer(),
2028 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
2029 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
2030 EndContainer(),
2031 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
2032 EndContainer(),
2036 EndContainer(),
2037 EndContainer(),
2038};
2039
2042 WindowPosition::Automatic, "graph_town_cargo", 0, 0,
2044 {},
2045 _nested_town_cargo_graph_widgets
2046);
2047
2048void ShowTownCargoGraph(WindowNumber window_number)
2049{
2051}
2052
2057static std::unique_ptr<NWidgetBase> MakePerformanceDetailPanels()
2058{
2059 auto realtime = TimerGameEconomy::UsingWallclockUnits();
2060 const StringID performance_tips[] = {
2061 realtime ? STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_YEARS,
2062 STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
2063 realtime ? STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_YEARS,
2064 STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
2065 STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
2066 STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
2067 STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
2068 STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
2069 STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
2070 STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
2071 };
2072
2073 static_assert(lengthof(performance_tips) == to_underlying(ScoreID::End));
2074
2075 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
2076 for (WidgetID widnum = WID_PRD_SCORE_FIRST; widnum <= WID_PRD_SCORE_LAST; widnum++) {
2077 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, Colours::Brown, widnum);
2078 panel->SetFill(1, 1);
2079 panel->SetToolTip(performance_tips[widnum - WID_PRD_SCORE_FIRST]);
2080 vert->Add(std::move(panel));
2081 }
2082 return vert;
2083}
2084
2086std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsGraphGUI()
2087{
2088 return MakeCompanyButtonRows(WID_PRD_COMPANY_FIRST, WID_PRD_COMPANY_LAST, Colours::Brown, 8, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
2089}
2090
2091static constexpr std::initializer_list<NWidgetPart> _nested_performance_rating_detail_widgets = {
2094 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2097 EndContainer(),
2100 EndContainer(),
2102};
2103
2106 WindowPosition::Automatic, "league_details", 0, 0,
2108 {},
2109 _nested_performance_rating_detail_widgets
2110);
2111
2112void ShowPerformanceRatingDetail()
2113{
2115}
2116
2117void InitializeGraphGui()
2118{
2120 PaymentRatesGraphWindow::excluded_cargo_types = {};
2121 IndustryProductionGraphWindow::excluded_cargo_types = {};
2122}
constexpr uint8_t FindLastBit(T x)
Search the last set bit in a value.
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr T ToggleBit(T &x, const uint8_t y)
Toggles a bit in a variable.
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:110
static constexpr CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:22
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:35
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()
Flip all bits.
constexpr Timpl & Set()
Set all bits.
void UpdateStatistics(bool initialize) override
Update the statistics.
virtual OverflowSafeInt64 GetGraphData(const Company *c, int j)=0
Get the data to show in the graph for a given company at a location along the X-axis.
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.
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:2458
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:2532
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.
StrongType::Typedef< int32_t, struct YearTag< struct Economy >, StrongType::Compare, StrongType::Integer > Year
RectPadding framerect
Standard padding inside many panels.
Definition window_gui.h:40
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
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 EnumIndexArray< ScoreInfo, ScoreID, ScoreID::End > _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.
@ Begin
The lowest valid value.
@ MinIncome
Income in the quarter with the lowest profit of the last 12 quarters.
@ End
Score ID end marker.
@ Total
Total points out of possible points ,must always be the last entry.
@ Loan
The amount of money company can take as a loan.
@ MaxIncome
Income in the quarter with the highest profit of the last 12 quarters.
@ MinProfit
The profit of the vehicle with the lowest income.
@ Money
Amount of money company has in the bank.
static constexpr int SCORE_MAX
The max score that can be in the performance history.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Definition enum_type.hpp:21
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:88
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:972
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:900
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:669
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:873
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.
@ Small
Index of the small font in the font tables.
Definition gfx_type.h:250
@ Normal
Index of the normal font in the font tables.
Definition gfx_type.h:249
@ SA_LEFT
Left align the text.
Definition gfx_type.h:388
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:390
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:389
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:400
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:398
@ Invalid
Invalid marker.
Definition gfx_type.h:302
@ Green
Green.
Definition gfx_type.h:291
@ Brown
Brown.
Definition gfx_type.h:298
@ Red
Red.
Definition gfx_type.h:289
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:307
@ Checker
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:346
static WindowDesc _town_cargo_graph_desc(WindowPosition::Automatic, "graph_town_cargo", 0, 0, WC_TOWN_CARGO_GRAPH, WC_TOWN_VIEW, {}, _nested_town_cargo_graph_widgets)
Window definition for the town cargo graph window.
static WindowDesc _cargo_payment_rates_desc(WindowPosition::Automatic, "graph_cargo_payment_rates", 0, 0, WC_PAYMENT_RATES, WC_NONE, {}, _nested_cargo_payment_rates_widgets)
Window definition for the cargo payment rates graph window.
static WindowDesc _graph_legend_desc(WindowPosition::Automatic, "graph_legend", 0, 0, WC_GRAPH_LEGEND, WC_NONE, {}, _nested_graph_legend_widgets)
Window definition for the graph legend window.
static WindowDesc _industry_production_desc(WindowPosition::Automatic, "graph_industry_production", 0, 0, WC_INDUSTRY_PRODUCTION, WC_INDUSTRY_VIEW, {}, _nested_industry_production_widgets)
Window definition for the industry production graph window.
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.
static CompanyMask _legend_excluded_companies
Bitmasks of company and cargo indices that shouldn't be drawn.
Definition graph_gui.cpp:41
std::unique_ptr< NWidgetBase > MakeCompanyButtonRowsGraphGUI()
Make a number of rows with buttons for each company for the performance rating detail window.
static WindowDesc _operating_profit_desc(WindowPosition::Automatic, "graph_operating_profit", 0, 0, WC_OPERATING_PROFIT, WC_NONE, {}, _nested_operating_profit_widgets)
Window definition for the operating profit graph window.
static WindowDesc _delivered_cargo_graph_desc(WindowPosition::Automatic, "graph_delivered_cargo", 0, 0, WC_DELIVERED_CARGO, WC_NONE, {}, _nested_delivered_cargo_graph_widgets)
Window definition for the delivered cargo graph window.
static WindowDesc _performance_rating_detail_desc(WindowPosition::Automatic, "league_details", 0, 0, WC_PERFORMANCE_DETAIL, WC_NONE, {}, _nested_performance_rating_detail_widgets)
Window definition for the performance rating details window.
static WindowDesc _company_value_graph_desc(WindowPosition::Automatic, "graph_company_value", 0, 0, WC_COMPANY_VALUE, WC_NONE, {}, _nested_company_value_graph_widgets)
Window definition for the company value graph window.
static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX)
Value used for a datapoint that shouldn't be drawn.
static WindowDesc _performance_history_desc(WindowPosition::Automatic, "graph_performance", 0, 0, WC_PERFORMANCE_HISTORY, WC_NONE, {}, _nested_performance_history_widgets)
Window definition for the performance history graph window.
static const uint INVALID_DATAPOINT_POS
Used to determine if the previous point was drawn.
Definition graph_gui.cpp:45
static std::unique_ptr< NWidgetBase > MakePerformanceDetailPanels()
Make a vertical list of panels for outputting score details.
static WindowDesc _income_graph_desc(WindowPosition::Automatic, "graph_income", 0, 0, WC_INCOME_GRAPH, WC_NONE, {}, _nested_income_graph_widgets)
Window definition for the income graph window.
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 SetResizeWidgetTypeTip(ResizeWidgetType widget_type, StringID tip)
Widget part function for setting the resize widget type and tooltip.
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
constexpr NWidgetPart SetPadding(uint8_t top, uint8_t right, uint8_t bottom, uint8_t left)
Widget part function for setting additional space around a widget.
constexpr NWidgetPart SetStringTip(StringID string, StringID tip={})
Widget part function for setting the string and tooltip.
constexpr NWidgetPart SetMinimalSize(int16_t x, int16_t y)
Widget part function for setting the minimal size.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart SetTextStyle(TextColour colour, FontSize size=FontSize::Normal)
Widget part function for setting the text style.
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=INVALID_WIDGET)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart SetAlignment(StringAlignment align)
Widget part function for setting the alignment of text/images.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint).
Definition window.cpp:980
Functions for storing historical data.
void FillFromHistory(const HistoryData< T > &history, ValidHistoryMask valid_history, const HistoryRange &hr, Tfillers &&... fillers)
Fill some data with historical data.
Base of all industries.
#define Point
Macro that prevents name conflicts between included headers.
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
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
constexpr To ClampTo(From value)
Clamp the given value down to lie within the requested type.
PixelColour GetColourGradient(Colours colour, Shade shade)
Get colour gradient palette index.
Definition palette.cpp:393
@ Darker
Darker colour shade.
@ Lighter
Lighter colour shade.
@ Normal
Normal colour shade.
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'.
virtual CargoTypes & GetExcludedCargoTypes() const =0
Get a reference to the cargo types that should not be shown.
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.
std::optional< uint8_t > GetDatasetIndex(int y) override
Get the dataset associated with a given Y-location within WID_GRAPH_MATRIX.
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.
virtual CargoTypes GetCargoTypes(WindowNumber number) const =0
Get the CargoTypes to show in this 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.
TimerGameEconomy::Year year
The starting year that values are plotted against.
void OnGameTick() override
Called once per (game) tick.
virtual void UpdateStatistics(bool initialize)=0
Update the statistics.
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.
TimerGameEconomy::Month month
The starting month that values are plotted against.
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.
virtual std::optional< uint8_t > GetDatasetIndex(int y)
Get the dataset associated with a given Y-location within WID_GRAPH_MATRIX.
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.
bool x_values_reversed
These values are used if the graph is being plotted against values rather than the dates specified by...
int16_t x_values_increment
These values are used if the graph is being plotted against values rather than the dates specified by...
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:75
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:139
CargoType Index() const
Determines index of this cargospec.
Definition cargotype.h:109
StringID name
Name of this type of cargo.
Definition cargotype.h:92
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.
OverflowSafeInt64 GetGraphData(const Company *c, int j) override
Get the data to show in the graph for a given company at a location along the X-axis.
T y
Y coordinate.
uint16_t rate
The conversion rate compared to the base currency.
Definition currency.h:78
OverflowSafeInt64 GetGraphData(const Company *c, int j) override
Get the data to show in the graph for a given company at a location along the X-axis.
Dimensions (a width and height) of a rectangle in 2D.
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.
OverflowSafeInt64 GetGraphData(const Company *c, int j) override
Get the data to show in the graph for a given company at a location along the X-axis.
void UpdateStatistics(bool initialize) override
Update the statistics.
CargoTypes GetCargoTypes(WindowNumber window_number) const override
Get the CargoTypes to show in this window.
CargoTypes & GetExcludedCargoTypes() const override
Get a reference to the cargo types that should not be shown.
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.
CargoType cargo
Cargo type.
Definition industry.h:86
uint16_t accepted
Total accepted.
Definition industry.h:81
uint16_t waiting
Average waiting.
Definition industry.h:82
CargoType cargo
Cargo type.
Definition industry.h:74
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
ProducedCargoes produced
produced cargo slots
Definition industry.h:110
AcceptedCargoes accepted
accepted cargo slots
Definition industry.h:111
bool IsCargoProduced() const
Test if this industry produces any cargo.
Definition industry.h:229
OverflowSafeInt64 GetGraphData(const Company *c, int j) override
Get the data to show in the graph for a given company at a location along the X-axis.
void UpdateStatistics(bool) override
Update the statistics.
void UpdatePaymentRates()
Update the payment rates according to the latest information.
CargoTypes & GetExcludedCargoTypes() const override
Get a reference to the cargo types that should not be shown.
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.
CargoTypes GetCargoTypes(WindowNumber) const override
Get the CargoTypes to show in this window.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
OverflowSafeInt64 GetGraphData(const Company *c, int j) override
Get the data to show in the graph for a given company at a location along the X-axis.
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:414
static Pool::IterateWrapper< Company > Iterate(size_t from=0)
static Industry * Get(auto index)
static Company * GetIfValid(auto 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.
CargoTypes & GetExcludedCargoTypes() const override
Get a reference to the cargo types that should not be shown.
CargoTypes GetCargoTypes(WindowNumber window_number) const override
Get the CargoTypes to show in this window.
void UpdateStatistics(bool initialize) override
Update the statistics.
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.
CargoType cargo
Cargo type of accepted cargo.
Definition town.h:116
uint32_t accepted
Total accepted.
Definition town.h:111
uint32_t transported
Total transported.
Definition town.h:92
uint32_t production
Total produced.
Definition town.h:91
Town data structure.
Definition town.h:63
SuppliedCargoes supplied
Cargo statistics about supplied cargo.
Definition town.h:130
ValidHistoryMask valid_history
Mask of valid history records.
Definition town.h:134
AcceptedCargoes accepted
Cargo statistics about accepted cargo.
Definition town.h:131
Contains the interval of a graph's data.
OverflowSafeInt64 lowest
Lowest value of this interval. Must be zero or less.
OverflowSafeInt64 highest
Highest value of this interval. Must be zero or greater.
High level window description.
Definition window_gui.h:168
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:274
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1822
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing).
Definition window.cpp:3262
void RaiseWidget(WidgetID widget_index)
Marks a widget as raised.
Definition window_gui.h:470
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:518
ResizeInfo resize
Resize information.
Definition window_gui.h:315
int scale
Scale of this window – used to determine how to resize.
Definition window_gui.h:305
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1812
bool IsWidgetDisabled(WidgetID widget_index) const
Gets the enabled/disabled status of a widget.
Definition window_gui.h:411
int left
x position of left edge of the window
Definition window_gui.h:310
Window(WindowDesc &desc)
Empty constructor, initialization has been moved to InitNested() called from the constructor of the d...
Definition window.cpp:1846
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:223
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:990
void LowerWidget(WidgetID widget_index)
Marks a widget as lowered.
Definition window_gui.h:461
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1836
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:327
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:382
int height
Height of the window (number of pixels down in y direction).
Definition window_gui.h:313
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:312
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:451
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:303
Definition of Interval and OneShot timers.
Definition of the game-economy-timer.
Definition of the tick-based game-timer.
Definition of the Window system.
static constexpr std::chrono::milliseconds TIMER_BLINK_INTERVAL
Interval used by blinking interface elements.
Base of the town class.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:309
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:3476
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:70
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:66
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:39
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX).
Definition widget_type.h:57
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:50
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX).
Definition widget_type.h:55
@ WWT_CAPTION
Window caption (window title between closebox and stickybox).
Definition widget_type.h:52
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:76
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:68
@ WWT_CLOSEBOX
Close box (at top-left of a window).
Definition widget_type.h:60
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:37
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window).
Definition widget_type.h:59
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX).
Definition widget_type.h:56
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:49
@ EqualSize
Containers should keep all their (resizing) children equally large.
@ HideBevel
Bevel of resize box is hidden.
Definition widget_type.h:29
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3322
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting).
Definition window.cpp:3200
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).
Definition window_gui.h:27
Twindow * AllocateWindowDescFront(WindowDesc &desc, WindowNumber window_number, Targs... extra_arguments)
Open a new window.
@ Automatic
Find a place automatically.
Definition window_gui.h:144
int WidgetID
Widget ID.
Definition window_type.h:21
@ 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:51
@ 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.
Definition zoom_type.h:26