OpenTTD Source 20260721-master-g25ec12c62d
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_func.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);
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) ? TextColour::Black : TextColour::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();
91 InvalidateWindowData(WindowClass::IncomeGraph, 0);
92 InvalidateWindowData(WindowClass::OperatingProfitGraph, 0);
93 InvalidateWindowData(WindowClass::DeliveredCargoGraph, 0);
94 InvalidateWindowData(WindowClass::PerformanceGraph, 0);
95 InvalidateWindowData(WindowClass::CompanyValueGraph, 0);
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,
151 WindowClass::GraphLegend, WindowClass::None,
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
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
204
205 static inline constexpr GraphScale MONTHLY_SCALE_WALLCLOCK[] = {
206 {STR_GRAPH_LAST_24_MINUTES_TIME_LABEL, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
207 {STR_GRAPH_LAST_72_MINUTES_TIME_LABEL, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
208 {STR_GRAPH_LAST_288_MINUTES_TIME_LABEL, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
209 };
210
211 static inline constexpr GraphScale MONTHLY_SCALE_CALENDAR[] = {
212 {STR_GRAPH_LAST_24_MONTHS, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
213 {STR_GRAPH_LAST_24_QUARTERS, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
214 {STR_GRAPH_LAST_24_YEARS, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
215 };
216
217 uint64_t excluded_data = 0;
218 uint64_t excluded_range = 0;
219 uint64_t masked_range = 0;
220 uint8_t num_on_x_axis = 0;
221 uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
222
225 uint8_t month_increment = 3;
226
227 bool draw_dates = true;
228
234 bool x_values_reversed = true;
237
238 StringID format_str_y_axis{};
239
240 struct DataSet {
241 std::array<OverflowSafeInt64, GRAPH_NUM_MONTHS> values;
242 PixelColour colour;
243 uint8_t exclude_bit;
244 uint8_t range_bit;
245 uint8_t dash;
246 };
247 std::vector<DataSet> data{};
248
249 std::span<const GraphRange> ranges{};
250 std::span<const GraphScale> scales{};
251 uint8_t selected_scale = 0;
252
253 uint8_t highlight_data = UINT8_MAX;
254 uint8_t highlight_range = UINT8_MAX;
255 bool highlight_state = false;
256
257 struct BaseFiller {
259
260 inline void MakeZero(uint i) const { this->dataset.values[i] = 0; }
261 inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
262 };
263
264 template <typename Tprojection>
266 Tprojection proj;
267
268 inline void Fill(uint i, const auto &data) const { this->dataset.values[i] = std::invoke(this->proj, data); }
269 };
270
276 std::span<const OverflowSafeInt64> GetDataSetRange(const DataSet &dataset) const
277 {
278 return {std::begin(dataset.values), std::begin(dataset.values) + this->num_on_x_axis};
279 }
280
287 ValuesInterval GetValuesInterval(int num_hori_lines) const
288 {
289 assert(num_hori_lines > 0);
290
291 ValuesInterval current_interval;
292 current_interval.highest = INT64_MIN;
293 current_interval.lowest = INT64_MAX;
294
295 for (const DataSet &dataset : this->data) {
296 if (HasBit(this->excluded_data, dataset.exclude_bit)) continue;
297 if (HasBit(this->excluded_range, dataset.range_bit)) continue;
298
299 for (const OverflowSafeInt64 &datapoint : this->GetDataSetRange(dataset)) {
300 if (datapoint != INVALID_DATAPOINT) {
301 current_interval.highest = std::max(current_interval.highest, datapoint);
302 current_interval.lowest = std::min(current_interval.lowest, datapoint);
303 }
304 }
305 }
306
307 /* Always include zero in the shown range. */
308 double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest);
309 double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest;
310
311 /* Prevent showing values too close to the graph limits. */
312 abs_higher = (11.0 * abs_higher) / 10.0;
313 abs_lower = (11.0 * abs_lower) / 10.0;
314
315 int num_pos_grids;
316 OverflowSafeInt64 grid_size;
317
318 if (abs_lower != 0 || abs_higher != 0) {
319 /* The number of grids to reserve for the positive part is: */
320 num_pos_grids = (int)floor(0.5 + num_hori_lines * abs_higher / (abs_higher + abs_lower));
321
322 /* If there are any positive or negative values, force that they have at least one grid. */
323 if (num_pos_grids == 0 && abs_higher != 0) num_pos_grids++;
324 if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--;
325
326 /* Get the required grid size for each side and use the maximum one. */
327
328 OverflowSafeInt64 grid_size_higher = 0;
329 if (abs_higher > 0) {
330 grid_size_higher = abs_higher > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_higher);
331 grid_size_higher = (grid_size_higher + num_pos_grids - 1) / num_pos_grids;
332 }
333
334 OverflowSafeInt64 grid_size_lower = 0;
335 if (abs_lower > 0) {
336 grid_size_lower = abs_lower > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_lower);
337 grid_size_lower = (grid_size_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids);
338 }
339
340 grid_size = std::max(grid_size_higher, grid_size_lower);
341 } else {
342 /* If both values are zero, show an empty graph. */
343 num_pos_grids = num_hori_lines / 2;
344 grid_size = 1;
345 }
346
347 current_interval.highest = num_pos_grids * grid_size;
348 current_interval.lowest = -(num_hori_lines - num_pos_grids) * grid_size;
349 return current_interval;
350 }
351
358 uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
359 {
360 /* draw text strings on the y axis */
361 int64_t y_label = current_interval.highest;
362 int64_t y_label_separation = (current_interval.highest - current_interval.lowest) / num_hori_lines;
363
364 uint max_width = 0;
365
366 for (int i = 0; i < (num_hori_lines + 1); i++) {
367 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label));
368 if (d.width > max_width) max_width = d.width;
369
370 y_label -= y_label_separation;
371 }
372
373 return max_width;
374 }
375
380 void DrawGraph(Rect r) const
381 {
382 uint x, y;
383 ValuesInterval interval;
384 int x_axis_offset;
385
386 /* the colours and cost array of GraphDrawer must accommodate
387 * both values for cargo and companies. So if any are higher, quit */
388 static_assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
389 assert(this->num_vert_lines > 0);
390
391 bool rtl = _current_text_dir == TD_RTL;
392
393 /* Rect r will be adjusted to contain just the graph, with labels being
394 * placed outside the area. */
396 r.bottom -= (this->draw_dates ? 2 : 1) * GetCharacterHeight(FontSize::Small) + ScaleGUITrad(4);
397 r.left += ScaleGUITrad(rtl ? 5 : 9);
398 r.right -= ScaleGUITrad(rtl ? 9 : 5);
399
400 /* Initial number of horizontal lines. */
401 int num_hori_lines = 160 / ScaleGUITrad(MIN_GRID_PIXEL_SIZE);
402 /* For the rest of the height, the number of horizontal lines will increase more slowly. */
403 int resize = (r.bottom - r.top - 160) / (2 * ScaleGUITrad(MIN_GRID_PIXEL_SIZE));
404 if (resize > 0) num_hori_lines += resize;
405
406 interval = GetValuesInterval(num_hori_lines);
407
408 int label_width = GetYLabelWidth(interval, num_hori_lines);
409
410 if (rtl) {
411 r.right -= label_width;
412 } else {
413 r.left += label_width;
414 }
415
416 int x_sep = (r.right - r.left) / this->num_vert_lines;
417 int y_sep = (r.bottom - r.top) / num_hori_lines;
418
419 /* Redetermine right and bottom edge of graph to fit with the integer
420 * separation values. */
421 if (rtl) {
422 r.left = r.right - x_sep * this->num_vert_lines;
423 } else {
424 r.right = r.left + x_sep * this->num_vert_lines;
425 }
426 r.bottom = r.top + y_sep * num_hori_lines;
427
428 OverflowSafeInt64 interval_size = interval.highest + abs(interval.lowest);
429 /* Where to draw the X axis. Use floating point to avoid overflowing and results of zero. */
430 x_axis_offset = (int)((r.bottom - r.top) * (double)interval.highest / (double)interval_size);
431
432 /* Draw the background of the graph itself. */
433 GfxFillRect(r.left, r.top, r.right, r.bottom, GRAPH_BASE_COLOUR);
434
435 /* Draw the grid lines. */
436 int gridline_width = WidgetDimensions::scaled.bevel.top;
437 PixelColour grid_colour = GRAPH_GRID_COLOUR;
438
439 /* Don't draw the first line, as that's where the axis will be. */
440 if (rtl) {
441 x_sep = -x_sep;
442 x = r.right + x_sep;
443 } else {
444 x = r.left + x_sep;
445 }
446
447 for (int i = 1; i < this->num_vert_lines + 1; i++) {
448 /* If using wallclock units, we separate periods with a lighter line. */
450 grid_colour = (i % 4 == 0) ? GRAPH_YEAR_LINE_COLOUR : GRAPH_GRID_COLOUR;
451 }
452 GfxFillRect(x, r.top, x + gridline_width - 1, r.bottom, grid_colour);
453 x += x_sep;
454 }
455
456 /* Draw the horizontal grid lines. */
457 y = r.bottom;
458
459 for (int i = 0; i < (num_hori_lines + 1); i++) {
460 if (rtl) {
461 GfxFillRect(r.right + 1, y, r.right + ScaleGUITrad(3), y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
462 } else {
463 GfxFillRect(r.left - ScaleGUITrad(3), y, r.left - 1, y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
464 }
465 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_GRID_COLOUR);
466 y -= y_sep;
467 }
468
469 /* Draw the y axis. */
470 GfxFillRect(r.left, r.top, r.left + gridline_width - 1, r.bottom + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
471
472 /* Draw the x axis. */
473 y = x_axis_offset + r.top;
474 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_ZERO_LINE_COLOUR);
475
476 /* Find the largest value that will be drawn. */
477 if (this->num_on_x_axis == 0) return;
478
479 assert(this->num_on_x_axis > 0);
480
481 /* draw text strings on the y axis */
482 int64_t y_label = interval.highest;
483 int64_t y_label_separation = abs(interval.highest - interval.lowest) / num_hori_lines;
484
485 y = r.top - GetCharacterHeight(FontSize::Small) / 2;
486
487 for (int i = 0; i < (num_hori_lines + 1); i++) {
488 if (rtl) {
489 DrawString(r.right + ScaleGUITrad(4), r.right + label_width + ScaleGUITrad(4), y,
490 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
492 } else {
493 DrawString(r.left - label_width - ScaleGUITrad(4), r.left - ScaleGUITrad(4), y,
494 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
496 }
497
498 y_label -= y_label_separation;
499 y += y_sep;
500 }
501
502 x = rtl ? r.right : r.left;
503 y = r.bottom + ScaleGUITrad(2);
504
505 /* if there are not enough datapoints to fill the graph, align to the right */
506 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
507
508 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
509 if (this->draw_dates) {
510 TimerGameEconomy::Month mo = this->month;
511 TimerGameEconomy::Year yr = this->year;
512 for (int i = 0; i < this->num_on_x_axis; i++) {
513 if (rtl) {
514 DrawStringMultiLineWithClipping(x + x_sep, x, y, this->height,
515 GetString(mo == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + mo, yr),
517 } else {
518 DrawStringMultiLineWithClipping(x, x + x_sep, y, this->height,
519 GetString(mo == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + mo, yr),
521 }
522
523 mo += this->month_increment;
524 if (mo >= 12) {
525 mo = 0;
526 yr++;
527
528 /* Draw a lighter grid line between years. Top and bottom adjustments ensure we don't draw over top and bottom horizontal grid lines. */
529 GfxFillRect(x + x_sep, r.top + gridline_width, x + x_sep + gridline_width - 1, r.bottom - 1, GRAPH_YEAR_LINE_COLOUR);
530 }
531 x += x_sep;
532 }
533 } else {
534 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates, and all graphs when using wallclock units). */
535 int16_t iterator;
536 uint16_t label;
537 if (this->x_values_reversed) {
538 label = this->x_values_increment * this->num_on_x_axis;
539 iterator = -this->x_values_increment;
540 } else {
541 label = this->x_values_increment;
542 iterator = this->x_values_increment;
543 }
544
545 for (int i = 0; i < this->num_on_x_axis; i++) {
546 if (rtl) {
547 DrawString(x + x_sep + 1, x - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, AlignmentH::Centre);
548 } else {
549 DrawString(x + 1, x + x_sep - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, AlignmentH::Centre);
550 }
551
552 label += iterator;
553 x += x_sep;
554 }
555 }
556
557 /* Draw lines and dots. */
558 uint linewidth = ScaleGUITrad(_settings_client.gui.graph_line_thickness);
559 uint pointwidth = ScaleGUITrad(_settings_client.gui.graph_line_thickness + 1);
560 uint pointoffs1 = pointwidth / 2;
561 uint pointoffs2 = pointwidth - pointoffs1;
562
563 auto draw_dataset = [&](const DataSet &dataset, PixelColour colour) {
564 if (HasBit(this->excluded_data, dataset.exclude_bit)) return;
565 if (HasBit(this->excluded_range, dataset.range_bit)) return;
566
567 /* Centre the dot between the grid lines. */
568 if (rtl) {
569 x = r.right + (x_sep / 2);
570 } else {
571 x = r.left + (x_sep / 2);
572 }
573
574 /* if there are not enough datapoints to fill the graph, align to the right */
575 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
576
577 uint prev_x = INVALID_DATAPOINT_POS;
578 uint prev_y = INVALID_DATAPOINT_POS;
579
580 const uint dash = ScaleGUITrad(dataset.dash);
581 for (OverflowSafeInt64 datapoint : this->GetDataSetRange(dataset)) {
582 if (datapoint != INVALID_DATAPOINT) {
583 /*
584 * Check whether we need to reduce the 'accuracy' of the
585 * datapoint value and the highest value to split overflows.
586 * And when 'drawing' 'one million' or 'one million and one'
587 * there is no significant difference, so the least
588 * significant bits can just be removed.
589 *
590 * If there are more bits needed than would fit in a 32 bits
591 * integer, so at about 31 bits because of the sign bit, the
592 * least significant bits are removed.
593 */
594 int mult_range = FindLastBit<uint32_t>(x_axis_offset) + FindLastBit<uint64_t>(abs(datapoint));
595 int reduce_range = std::max(mult_range - 31, 0);
596
597 /* Handle negative values differently (don't shift sign) */
598 if (datapoint < 0) {
599 datapoint = -(abs(datapoint) >> reduce_range);
600 } else {
601 datapoint >>= reduce_range;
602 }
603 y = r.top + x_axis_offset - ((r.bottom - r.top) * datapoint) / (interval_size >> reduce_range);
604
605 /* Draw the point. */
606 GfxFillRect(x - pointoffs1, y - pointoffs1, x + pointoffs2, y + pointoffs2, colour);
607
608 /* Draw the line connected to the previous point. */
609 if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, colour, linewidth, dash);
610
611 prev_x = x;
612 prev_y = y;
613 } else {
614 prev_x = INVALID_DATAPOINT_POS;
615 prev_y = INVALID_DATAPOINT_POS;
616 }
617
618 x += x_sep;
619 }
620 };
621
622 /* Draw unhighlighted datasets. */
623 for (const DataSet &dataset : this->data) {
624 if (dataset.exclude_bit != this->highlight_data && dataset.range_bit != this->highlight_range) {
625 draw_dataset(dataset, dataset.colour);
626 }
627 }
628
629 /* If any dataset or range is highlighted, draw separately after the rest so they appear on top of all other
630 * data. Highlighted data is only drawn when highlight_state is set, otherwise it is invisible. */
631 if (this->highlight_state && (this->highlight_data != UINT8_MAX || this->highlight_range != UINT8_MAX)) {
632 for (const DataSet &dataset : this->data) {
633 if (dataset.exclude_bit == this->highlight_data || dataset.range_bit == this->highlight_range) {
634 draw_dataset(dataset, PC_WHITE);
635 }
636 }
637 }
638 }
639
640 BaseGraphWindow(WindowDesc &desc, StringID format_str_y_axis) :
641 Window(desc),
642 format_str_y_axis(format_str_y_axis)
643 {
644 SetWindowDirty(WindowClass::GraphLegend, 0);
645 }
646
647 const IntervalTimer<TimerWindow> blink_interval = {TIMER_BLINK_INTERVAL, [this](auto) {
648 /* If nothing is highlighted then no redraw is needed. */
649 if (this->highlight_data == UINT8_MAX && this->highlight_range == UINT8_MAX) return;
650
651 /* Toggle the highlight state and redraw. */
652 this->highlight_state = !this->highlight_state;
653 this->SetDirty();
654 }};
655
656 void UpdateMatrixSize(WidgetID widget, Dimension &size, Dimension &resize, auto labels)
657 {
658 size = {};
659 for (const StringID &str : labels) {
660 size = maxdim(size, GetStringBoundingBox(str, FontSize::Small));
661 }
662
665
666 /* Set fixed height for number of ranges. */
667 size.height *= static_cast<uint>(std::size(labels));
668
669 resize.width = 0;
670 resize.height = 0;
671 this->GetWidget<NWidgetCore>(widget)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(labels)));
672 }
673
674public:
675 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
676 {
677 switch (widget) {
679 this->UpdateMatrixSize(widget, size, resize, this->ranges | std::views::transform(&GraphRange::label));
680 break;
681
683 this->UpdateMatrixSize(widget, size, resize, this->scales | std::views::transform(&GraphScale::label));
684 break;
685
686 case WID_GRAPH_GRAPH: {
687 uint x_label_width = 0;
688
689 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
690 if (this->draw_dates) {
691 uint yr = GetParamMaxValue(this->year.base(), 4, FontSize::Small);
692 for (uint mo = 0; mo < 12; ++mo) {
693 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);
694 }
695 } else {
696 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates). */
697 uint64_t max_value = GetParamMaxValue((this->num_on_x_axis + 1) * this->x_values_increment, 0, FontSize::Small);
698 x_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL_NUMBER, max_value)).width;
699 }
700
701 uint y_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, INT64_MAX)).width;
702
703 size.width = std::max<uint>(size.width, ScaleGUITrad(5) + y_label_width + this->num_vert_lines * (x_label_width + ScaleGUITrad(5)) + ScaleGUITrad(9));
704 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));
705 size.height = std::max<uint>(size.height, size.width / 3);
706 break;
707 }
708
709 default: break;
710 }
711 }
712
713 bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
714 {
715 if (widget != WID_GRAPH_RANGE_MATRIX) return false;
716
717 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical());
718 if (row == INT_MAX) return false;
719
720 GuiShowTooltips(this, GetEncodedString(this->ranges[row].tooltip), close_cond);
721 return true;
722 }
723
724 void DrawWidget(const Rect &r, WidgetID widget) const override
725 {
726 switch (widget) {
727 case WID_GRAPH_GRAPH:
728 this->DrawGraph(r);
729 break;
730
732 uint line_height = GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical();
733 uint index = 0;
734 Rect line = r.WithHeight(line_height);
735 for (const auto &[label, tooltip] : this->ranges) {
736 bool lowered = !HasBit(this->excluded_range, index) && !HasBit(this->masked_range, index);
737
738 /* Redraw frame if lowered */
740
741 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
742 DrawString(text, label, (this->highlight_state && this->highlight_range == index) ? TextColour::White : TextColour::Black, {AlignmentH::Centre, AlignmentV::Middle}, false, FontSize::Small);
743
744 if (HasBit(this->masked_range, index)) {
746 }
747
748 line = line.Translate(0, line_height);
749 ++index;
750 }
751 break;
752 }
753
755 uint line_height = GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical();
756 uint8_t selected_month_increment = this->scales[this->selected_scale].month_increment;
757 Rect line = r.WithHeight(line_height);
758 for (const auto &scale : this->scales) {
759 /* Redraw frame if selected */
760 if (selected_month_increment == scale.month_increment) DrawFrameRect(line, Colours::Brown, FrameFlag::Lowered);
761
762 DrawString(line.Shrink(WidgetDimensions::scaled.framerect), scale.label, TextColour::Black, {AlignmentH::Centre, AlignmentV::Middle}, false, FontSize::Small);
763
764 line = line.Translate(0, line_height);
765 }
766 break;
767 }
768
769 default: break;
770 }
771 }
772
773 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
774 {
775 /* Clicked on legend? */
776 switch (widget) {
778 ShowGraphLegend();
779 break;
780
782 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical());
783
784 if (HasBit(this->masked_range, row)) break;
785 ToggleBit(this->excluded_range, row);
786 SndClickBeep();
787 this->SetDirty();
788 break;
789 }
790
792 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical());
793 const auto &scale = this->scales[row];
794 if (this->selected_scale != row) {
795 this->selected_scale = row;
796 this->month_increment = scale.month_increment;
797 this->x_values_increment = scale.x_values_increment;
798 this->InvalidateData();
799 }
800 break;
801 }
802
803 default: break;
804 }
805 }
806
807 void OnMouseOver(Point pt, WidgetID widget) override
808 {
809 /* Test if a range should be highlighted. */
810 uint8_t new_highlight_range = UINT8_MAX;
811 if (widget == WID_GRAPH_RANGE_MATRIX) {
812 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical());
813 if (!HasBit(this->excluded_range, row)) new_highlight_range = static_cast<uint8_t>(row);
814 }
815
816 /* Test if a dataset should be highlighted. */
817 uint8_t new_highlight_data = UINT8_MAX;
818 if (widget == WID_GRAPH_MATRIX) {
819 auto dataset_index = this->GetDatasetIndex(pt.y);
820 if (dataset_index.has_value() && !HasBit(this->excluded_data, *dataset_index)) new_highlight_data = *dataset_index;
821 }
822
823 if (this->highlight_data == new_highlight_data && this->highlight_range == new_highlight_range) return;
824
825 /* Range or data set highlight has changed, set and redraw. */
826 this->highlight_data = new_highlight_data;
827 this->highlight_range = new_highlight_range;
828 this->highlight_state = true;
829 this->SetDirty();
830 }
831
832 void OnGameTick() override
833 {
834 this->UpdateStatistics(false);
835 }
836
842 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
843 {
844 if (!gui_scope) return;
845 this->UpdateStatistics(true);
846 }
847
852 virtual void UpdateStatistics(bool initialize) = 0;
853
859 virtual std::optional<uint8_t> GetDatasetIndex([[maybe_unused]] int y) { return std::nullopt; }
860};
861
862class BaseCompanyGraphWindow : public BaseGraphWindow {
863public:
864 BaseCompanyGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
865
866 void InitializeWindow(WindowNumber number)
867 {
868 /* Initialise the dataset */
869 this->UpdateStatistics(true);
870
871 this->CreateNestedTree();
872
873 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
874 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_LAST_72_MINUTES_TIME_LABEL : STR_EMPTY);
875
876 this->FinishInitNested(number);
877 }
878
879 void UpdateStatistics(bool initialize) override
880 {
881 CompanyMask excluded_companies = _legend_excluded_companies;
882
883 /* Exclude the companies which aren't valid */
884 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
885 if (!Company::IsValidID(c)) excluded_companies.Set(c);
886 }
887
888 uint8_t nums = 0;
889 for (const Company *c : Company::Iterate()) {
890 nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent));
891 }
892
893 int mo = (TimerGameEconomy::month / this->month_increment - nums) * this->month_increment;
894 auto yr = TimerGameEconomy::year;
895 while (mo < 0) {
896 yr--;
897 mo += 12;
898 }
899
900 if (!initialize && this->excluded_data == excluded_companies.base() && this->num_on_x_axis == nums &&
901 this->year == yr && this->month == mo) {
902 /* There's no reason to get new stats */
903 return;
904 }
905
906 this->excluded_data = excluded_companies.base();
907 this->num_on_x_axis = nums;
908 this->year = yr;
909 this->month = mo;
910
911 this->data.clear();
912 for (CompanyID k = CompanyID::Begin(); k < MAX_COMPANIES; ++k) {
913 const Company *c = Company::GetIfValid(k);
914 if (c == nullptr) continue;
915
916 DataSet &dataset = this->data.emplace_back();
917 dataset.colour = GetColourGradient(c->colour, Shade::Lighter);
918 dataset.exclude_bit = k.base();
919
920 for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
921 if (j >= c->num_valid_stat_ent) {
922 dataset.values[i] = INVALID_DATAPOINT;
923 } else {
924 /* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
925 * Instead, use the value just under it. Hopefully nobody will notice. */
926 dataset.values[i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
927 }
928 i++;
929 }
930 }
931 }
932
939 virtual OverflowSafeInt64 GetGraphData(const Company *c, int j) = 0;
940};
941
942
943/********************/
944/* OPERATING PROFIT */
945/********************/
946
947struct OperatingProfitGraphWindow : BaseCompanyGraphWindow {
948 OperatingProfitGraphWindow(WindowDesc &desc, WindowNumber window_number) :
949 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
950 {
951 this->num_on_x_axis = GRAPH_NUM_MONTHS;
952 this->num_vert_lines = GRAPH_NUM_MONTHS;
953 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
954
955 this->InitializeWindow(window_number);
956 }
957
958 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
959 {
960 return c->old_economy[j].income + c->old_economy[j].expenses;
961 }
962};
963
964static constexpr std::initializer_list<NWidgetPart> _nested_operating_profit_widgets = {
967 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
968 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
972 EndContainer(),
978 EndContainer(),
979 EndContainer(),
980};
981
984 WindowPosition::Automatic, "graph_operating_profit", 0, 0,
985 WindowClass::OperatingProfitGraph, WindowClass::None,
986 {},
987 _nested_operating_profit_widgets
988);
989
990
991void ShowOperatingProfitGraph()
992{
994}
995
996
997/****************/
998/* INCOME GRAPH */
999/****************/
1000
1001struct IncomeGraphWindow : BaseCompanyGraphWindow {
1002 IncomeGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1003 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1004 {
1005 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1006 this->num_vert_lines = GRAPH_NUM_MONTHS;
1007 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1008
1009 this->InitializeWindow(window_number);
1010 }
1011
1012 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1013 {
1014 return c->old_economy[j].income;
1015 }
1016};
1017
1018static constexpr std::initializer_list<NWidgetPart> _nested_income_graph_widgets = {
1021 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1022 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1026 EndContainer(),
1032 EndContainer(),
1033 EndContainer(),
1034};
1035
1038 WindowPosition::Automatic, "graph_income", 0, 0,
1039 WindowClass::IncomeGraph, WindowClass::None,
1040 {},
1041 _nested_income_graph_widgets
1042);
1043
1044void ShowIncomeGraph()
1045{
1047}
1048
1049/*******************/
1050/* DELIVERED CARGO */
1051/*******************/
1052
1053struct DeliveredCargoGraphWindow : BaseCompanyGraphWindow {
1054 DeliveredCargoGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1055 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1056 {
1057 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1058 this->num_vert_lines = GRAPH_NUM_MONTHS;
1059 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1060
1061 this->InitializeWindow(window_number);
1062 }
1063
1064 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1065 {
1066 return c->old_economy[j].delivered_cargo.GetSum<OverflowSafeInt64>();
1067 }
1068};
1069
1070static constexpr std::initializer_list<NWidgetPart> _nested_delivered_cargo_graph_widgets = {
1073 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1074 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1078 EndContainer(),
1084 EndContainer(),
1085 EndContainer(),
1086};
1087
1090 WindowPosition::Automatic, "graph_delivered_cargo", 0, 0,
1091 WindowClass::DeliveredCargoGraph, WindowClass::None,
1092 {},
1093 _nested_delivered_cargo_graph_widgets
1094);
1095
1096void ShowDeliveredCargoGraph()
1097{
1099}
1100
1101/***********************/
1102/* PERFORMANCE HISTORY */
1103/***********************/
1104
1105struct PerformanceHistoryGraphWindow : BaseCompanyGraphWindow {
1106 PerformanceHistoryGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1107 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1108 {
1109 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1110 this->num_vert_lines = GRAPH_NUM_MONTHS;
1111 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1112
1113 this->InitializeWindow(window_number);
1114 }
1115
1116 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1117 {
1118 return c->old_economy[j].performance_history;
1119 }
1120
1121 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1122 {
1123 if (widget == WID_PHG_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
1124 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1125 }
1126};
1127
1128static constexpr std::initializer_list<NWidgetPart> _nested_performance_history_widgets = {
1131 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1132 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_PHG_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetStringTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
1133 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1137 EndContainer(),
1143 EndContainer(),
1144 EndContainer(),
1145};
1146
1149 WindowPosition::Automatic, "graph_performance", 0, 0,
1150 WindowClass::PerformanceGraph, WindowClass::None,
1151 {},
1152 _nested_performance_history_widgets
1153);
1154
1155void ShowPerformanceHistoryGraph()
1156{
1158}
1159
1160/*****************/
1161/* COMPANY VALUE */
1162/*****************/
1163
1164struct CompanyValueGraphWindow : BaseCompanyGraphWindow {
1165 CompanyValueGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1166 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1167 {
1168 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1169 this->num_vert_lines = GRAPH_NUM_MONTHS;
1170 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1171
1172 this->InitializeWindow(window_number);
1173 }
1174
1175 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1176 {
1177 return c->old_economy[j].company_value;
1178 }
1179};
1180
1181static constexpr std::initializer_list<NWidgetPart> _nested_company_value_graph_widgets = {
1184 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1185 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1189 EndContainer(),
1195 EndContainer(),
1196 EndContainer(),
1197};
1198
1201 WindowPosition::Automatic, "graph_company_value", 0, 0,
1202 WindowClass::CompanyValueGraph, WindowClass::None,
1203 {},
1204 _nested_company_value_graph_widgets
1205);
1206
1207void ShowCompanyValueGraph()
1208{
1210}
1211
1212struct BaseCargoGraphWindow : BaseGraphWindow {
1213 Scrollbar *vscroll = nullptr;
1214 uint line_height = 0;
1215 uint legend_width = 0;
1216
1218
1219 BaseCargoGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
1220
1221 void InitializeWindow(WindowNumber number, StringID footer_wallclock = STR_NULL, StringID footer_calendar = STR_NULL)
1222 {
1223 this->CreateNestedTree();
1224
1225 this->excluded_range = this->masked_range;
1226 this->cargo_types = this->GetCargoTypes(number);
1227
1229 this->vscroll->SetCount(this->cargo_types.Count());
1230
1231 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1232 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? footer_wallclock : footer_calendar);
1233
1234 this->FinishInitNested(number);
1235
1236 /* Initialise the dataset */
1237 this->InvalidateData();
1238 }
1239
1245 virtual CargoTypes GetCargoTypes(WindowNumber number) const = 0;
1246
1251 virtual CargoTypes &GetExcludedCargoTypes() const = 0;
1252
1253 std::optional<uint8_t> GetDatasetIndex(int y) override
1254 {
1255 int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_GRAPH_MATRIX);
1256 if (row >= this->vscroll->GetCount()) return std::nullopt;
1257
1258 for (const CargoSpec *cs : _sorted_cargo_specs) {
1259 if (!this->cargo_types.Test(cs->Index())) continue;
1260 if (row-- > 0) continue;
1261
1262 return cs->Index();
1263 }
1264
1265 return std::nullopt;
1266 }
1267
1268 void OnInit() override
1269 {
1270 /* Width of the legend blob. */
1271 this->legend_width = GetCharacterHeight(FontSize::Small) * 9 / 6;
1272 }
1273
1274 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1275 {
1276 if (widget != WID_GRAPH_MATRIX) {
1277 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1278 return;
1279 }
1280
1281 size.height = GetCharacterHeight(FontSize::Small) + WidgetDimensions::scaled.framerect.Vertical();
1282
1283 for (CargoType cargo_type : this->cargo_types) {
1284 const CargoSpec *cs = CargoSpec::Get(cargo_type);
1285
1286 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1287 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1288 d.width += WidgetDimensions::scaled.framerect.Horizontal();
1289 d.height += WidgetDimensions::scaled.framerect.Vertical();
1290 size = maxdim(d, size);
1291 }
1292
1293 this->line_height = size.height;
1294 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1295 resize.width = 0;
1296 fill.height = resize.height = this->line_height;
1297 }
1298
1299 void DrawWidget(const Rect &r, WidgetID widget) const override
1300 {
1301 if (widget != WID_GRAPH_MATRIX) {
1302 BaseGraphWindow::DrawWidget(r, widget);
1303 return;
1304 }
1305
1306 bool rtl = _current_text_dir == TD_RTL;
1307
1308 int pos = this->vscroll->GetPosition();
1309 int max = pos + this->vscroll->GetCapacity();
1310
1311 Rect line = r.WithHeight(this->line_height);
1312
1313 for (const CargoSpec *cs : _sorted_cargo_specs) {
1314 if (!this->cargo_types.Test(cs->Index())) continue;
1315
1316 if (pos-- > 0) continue;
1317 if (--max < 0) break;
1318
1319 bool lowered = !CargoTypes{this->excluded_data}.Test(cs->Index());
1320
1321 /* Redraw frame if lowered */
1322 if (lowered) DrawFrameRect(line, Colours::Brown, FrameFlag::Lowered);
1323
1324 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1325
1326 /* Cargo-colour box with outline */
1327 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1328 GfxFillRect(cargo, PC_BLACK);
1329 PixelColour pc = cs->legend_colour;
1330 if (this->highlight_data == cs->Index()) pc = this->highlight_state ? PC_WHITE : PC_BLACK;
1332
1333 /* Cargo name */
1334 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1335
1336 line = line.Translate(0, this->line_height);
1337 }
1338 }
1339
1340 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1341 {
1342 switch (widget) {
1344 /* Remove all cargoes from the excluded lists. */
1345 this->GetExcludedCargoTypes() = {};
1346 this->excluded_data = this->GetExcludedCargoTypes().base();
1347 this->SetDirty();
1348 break;
1349
1351 /* Add all cargoes to the excluded lists. */
1352 this->GetExcludedCargoTypes() = this->cargo_types;
1353 this->excluded_data = this->GetExcludedCargoTypes().base();
1354 this->SetDirty();
1355 break;
1356 }
1357
1358 case WID_GRAPH_MATRIX: {
1359 int row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GRAPH_MATRIX);
1360 if (row >= this->vscroll->GetCount()) return;
1361
1362 SndClickBeep();
1363
1364 for (const CargoSpec *cs : _sorted_cargo_specs) {
1365 if (!this->cargo_types.Test(cs->Index())) continue;
1366 if (row-- > 0) continue;
1367
1368 this->GetExcludedCargoTypes().Flip(cs->Index());
1369 this->excluded_data = this->GetExcludedCargoTypes().base();
1370 this->SetDirty();
1371 break;
1372 }
1373 break;
1374 }
1375
1376 default:
1377 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1378 break;
1379 }
1380 }
1381
1382 void OnResize() override
1383 {
1384 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1385 }
1386};
1387
1388/*****************/
1389/* PAYMENT RATES */
1390/*****************/
1391
1392struct PaymentRatesGraphWindow : BaseCargoGraphWindow {
1393 static inline CargoTypes excluded_cargo_types{};
1394
1395 PaymentRatesGraphWindow(WindowDesc &desc, WindowNumber window_number) : BaseCargoGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1396 {
1397 this->num_on_x_axis = GRAPH_PAYMENT_RATE_STEPS;
1398 this->num_vert_lines = GRAPH_PAYMENT_RATE_STEPS;
1399 this->draw_dates = false;
1400
1401 this->x_values_reversed = false;
1402 /* The x-axis is labeled in either seconds or days. A day is two seconds, so we adjust the label if needed. */
1404
1405 this->InitializeWindow(window_number, STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS, STR_GRAPH_CARGO_PAYMENT_RATES_DAYS);
1406 }
1407
1409 {
1410 return _standard_cargo_mask;
1411 }
1412
1414 {
1415 return PaymentRatesGraphWindow::excluded_cargo_types;
1416 }
1417
1423 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1424 {
1425 if (!gui_scope) return;
1426 this->UpdatePaymentRates();
1427 }
1428
1430 const IntervalTimer<TimerWindow> update_payment_interval = {std::chrono::seconds(3), [this](auto) {
1431 this->UpdatePaymentRates();
1432 }};
1433
1434 void UpdateStatistics(bool) override {}
1435
1440 {
1441 this->excluded_data = this->GetExcludedCargoTypes().base();
1442
1443 this->data.clear();
1444 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1445 DataSet &dataset = this->data.emplace_back();
1446 dataset.colour = cs->legend_colour;
1447 dataset.exclude_bit = cs->Index();
1448
1449 for (uint j = 0; j != this->num_on_x_axis; j++) {
1450 dataset.values[j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
1451 }
1452 }
1453 }
1454};
1455
1456static constexpr std::initializer_list<NWidgetPart> _nested_cargo_payment_rates_widgets = {
1459 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1463 EndContainer(),
1469 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1470 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1471 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1474 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),
1476 EndContainer(),
1477 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1478 EndContainer(),
1479 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1480 EndContainer(),
1484 EndContainer(),
1485 EndContainer(),
1486};
1487
1490 WindowPosition::Automatic, "graph_cargo_payment_rates", 0, 0,
1491 WindowClass::CargoPaymentRatesGraph, WindowClass::None,
1492 {},
1493 _nested_cargo_payment_rates_widgets
1494);
1495
1496
1497void ShowCargoPaymentRates()
1498{
1500}
1501
1502/*****************************/
1503/* PERFORMANCE RATING DETAIL */
1504/*****************************/
1505
1506struct PerformanceRatingDetailWindow : Window {
1507 static CompanyID company;
1508 int timeout = 0;
1509 uint score_info_left = 0;
1510 uint score_info_right = 0;
1511 uint bar_left = 0;
1512 uint bar_right = 0;
1513 uint bar_width = 0;
1514 uint bar_height = 0;
1515 uint score_detail_left = 0;
1516 uint score_detail_right = 0;
1517
1518 PerformanceRatingDetailWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc)
1519 {
1520 this->UpdateCompanyStats();
1521
1522 this->InitNested(window_number);
1523 this->OnInvalidateData(CompanyID::Invalid().base());
1524 }
1525
1526 void UpdateCompanyStats()
1527 {
1528 /* Update all company stats with the current data
1529 * (this is because _score_info is not saved to a savegame) */
1530 for (Company *c : Company::Iterate()) {
1532 }
1533
1534 this->timeout = Ticks::DAY_TICKS * 5;
1535 }
1536
1537 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1538 {
1539 switch (widget) {
1541 this->bar_height = GetCharacterHeight(FontSize::Normal) + WidgetDimensions::scaled.fullbevel.Vertical();
1542 size.height = this->bar_height + WidgetDimensions::scaled.matrix.Vertical();
1543
1544 uint score_info_width = 0;
1545 for (ScoreID i : EnumRange(ScoreID::End)) {
1546 score_info_width = std::max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + to_underlying(i)).width);
1547 }
1548 score_info_width += GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(1000))).width + WidgetDimensions::scaled.hsep_wide;
1549
1550 this->bar_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_PERCENT, GetParamMaxValue(100))).width + WidgetDimensions::scaled.hsep_indent * 2; // Wide bars!
1551
1552 /* At this number we are roughly at the max; it can become wider,
1553 * but then you need at 1000 times more money. At that time you're
1554 * not that interested anymore in the last few digits anyway.
1555 * The 500 is because 999 999 500 to 999 999 999 are rounded to
1556 * 1 000 M, and not 999 999 k. Use negative numbers to account for
1557 * the negative income/amount of money etc. as well. */
1558 int max = -(999999999 - 500);
1559
1560 /* Scale max for the display currency. Prior to rendering the value
1561 * is converted into the display currency, which may cause it to
1562 * raise significantly. We need to compensate for that since {{CURRCOMPACT}}
1563 * is used, which can produce quite short renderings of very large
1564 * values. Otherwise the calculated width could be too narrow.
1565 * Note that it doesn't work if there was a currency with an exchange
1566 * rate greater than max.
1567 * When the currency rate is more than 1000, the 999 999 k becomes at
1568 * least 999 999 M which roughly is equally long. Furthermore if the
1569 * exchange rate is that high, 999 999 k is usually not enough anymore
1570 * to show the different currency numbers. */
1571 if (GetCurrency().rate < 1000) max /= GetCurrency().rate;
1572 uint score_detail_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, max, max)).width;
1573
1574 size.width = WidgetDimensions::scaled.frametext.Horizontal() + score_info_width + WidgetDimensions::scaled.hsep_wide + this->bar_width + WidgetDimensions::scaled.hsep_wide + score_detail_width;
1575 uint left = WidgetDimensions::scaled.frametext.left;
1576 uint right = size.width - WidgetDimensions::scaled.frametext.right;
1577
1578 bool rtl = _current_text_dir == TD_RTL;
1579 this->score_info_left = rtl ? right - score_info_width : left;
1580 this->score_info_right = rtl ? right : left + score_info_width;
1581
1582 this->score_detail_left = rtl ? left : right - score_detail_width;
1583 this->score_detail_right = rtl ? left + score_detail_width : right;
1584
1585 this->bar_left = left + (rtl ? score_detail_width : score_info_width) + WidgetDimensions::scaled.hsep_wide;
1586 this->bar_right = this->bar_left + this->bar_width - 1;
1587 break;
1588 }
1589 }
1590
1591 void DrawWidget(const Rect &r, WidgetID widget) const override
1592 {
1593 /* No need to draw when there's nothing to draw */
1594 if (this->company == CompanyID::Invalid()) return;
1595
1597 if (this->IsWidgetDisabled(widget)) return;
1598 CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1600 DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height));
1601 return;
1602 }
1603
1604 if (!IsInsideMM(widget, WID_PRD_SCORE_FIRST, WID_PRD_SCORE_LAST + 1)) return;
1605
1606 ScoreID score_type = (ScoreID)(widget - WID_PRD_SCORE_FIRST);
1607
1608 /* The colours used to show how the progress is going */
1611
1612 /* Draw all the score parts */
1613 int64_t val = _score_part[company][score_type];
1614 int64_t needed = _score_info[score_type].needed;
1615 int score = _score_info[score_type].score;
1616
1617 /* ScoreID::Total has its own rules ;) */
1618 if (score_type == ScoreID::Total) {
1619 for (ScoreID i : EnumRange(ScoreID::End)) score += _score_info[i].score;
1620 needed = SCORE_MAX;
1621 }
1622
1623 uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height);
1624 uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FontSize::Normal));
1625
1626 DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + to_underlying(score_type));
1627
1628 /* Draw the score */
1629 DrawString(this->score_info_left, this->score_info_right, text_top, GetString(STR_JUST_COMMA, score), TextColour::Black, AlignmentH::End);
1630
1631 /* Calculate the %-bar */
1632 uint x = Clamp<int64_t>(val, 0, needed) * this->bar_width / needed;
1633 bool rtl = _current_text_dir == TD_RTL;
1634 if (rtl) {
1635 x = this->bar_right - x;
1636 } else {
1637 x = this->bar_left + x;
1638 }
1639
1640 /* Draw the bar */
1641 if (x != this->bar_left) GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height - 1, rtl ? colour_notdone : colour_done);
1642 if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height - 1, rtl ? colour_done : colour_notdone);
1643
1644 /* Draw it */
1645 DrawString(this->bar_left, this->bar_right, text_top, GetString(STR_PERFORMANCE_DETAIL_PERCENT, Clamp<int64_t>(val, 0, needed) * 100 / needed), TextColour::FromString, AlignmentH::Centre);
1646
1647 /* ScoreID::Loan is inverted */
1648 if (score_type == ScoreID::Loan) val = needed - val;
1649
1650 /* Draw the amount we have against what is needed
1651 * For some of them it is in currency format */
1652 switch (score_type) {
1653 case ScoreID::MinProfit:
1654 case ScoreID::MinIncome:
1655 case ScoreID::MaxIncome:
1656 case ScoreID::Money:
1657 case ScoreID::Loan:
1658 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, val, needed));
1659 break;
1660 default:
1661 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_INT, val, needed));
1662 break;
1663 }
1664 }
1665
1666 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1667 {
1668 /* Check which button is clicked */
1670 /* Is it no on disable? */
1671 if (!this->IsWidgetDisabled(widget)) {
1672 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1673 this->company = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1674 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1675 this->SetDirty();
1676 }
1677 }
1678 }
1679
1680 void OnGameTick() override
1681 {
1682 /* Update the company score every 5 days */
1683 if (--this->timeout == 0) {
1684 this->UpdateCompanyStats();
1685 this->SetDirty();
1686 }
1687 }
1688
1694 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1695 {
1696 if (!gui_scope) return;
1697 /* Disable the companies who are not active */
1698 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
1700 }
1701
1702 /* Check if the currently selected company is still active. */
1703 if (this->company != CompanyID::Invalid() && !Company::IsValidID(this->company)) {
1704 /* Raise the widget for the previous selection. */
1705 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1706 this->company = CompanyID::Invalid();
1707 }
1708
1709 if (this->company == CompanyID::Invalid()) {
1710 for (const Company *c : Company::Iterate()) {
1711 this->company = c->index;
1712 break;
1713 }
1714 }
1715
1716 /* Make sure the widget is lowered */
1717 if (this->company != CompanyID::Invalid()) {
1718 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1719 }
1720 }
1721};
1722
1723CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
1724
1725/*******************************/
1726/* INDUSTRY PRODUCTION HISTORY */
1727/*******************************/
1728
1729struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
1730 static inline constexpr GraphRange RANGE_LABELS[] = {
1731 {STR_GRAPH_INDUSTRY_RANGE_PRODUCED, STR_GRAPH_INDUSTRY_RANGE_PRODUCED_TOOLTIP},
1732 {STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED, STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED_TOOLTIP},
1733 {STR_GRAPH_INDUSTRY_RANGE_DELIVERED, STR_GRAPH_INDUSTRY_RANGE_DELIVERED_TOOLTIP},
1734 {STR_GRAPH_INDUSTRY_RANGE_WAITING, STR_GRAPH_INDUSTRY_RANGE_WAITING_TOOLTIP},
1735 };
1736
1737 static inline CargoTypes excluded_cargo_types{};
1738
1739 IndustryProductionGraphWindow(WindowDesc &desc, WindowNumber window_number) :
1740 BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1741 {
1742 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1743 this->num_vert_lines = GRAPH_NUM_MONTHS;
1744 this->month_increment = 1;
1745 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1746 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1747 this->ranges = RANGE_LABELS;
1748
1750 if (!i->IsCargoProduced()) this->masked_range = (1U << 0) | (1U << 1);
1751 if (!i->IsCargoAccepted()) this->masked_range = (1U << 2) | (1U << 3);
1752
1753 this->InitializeWindow(window_number);
1754 }
1755
1756 void OnInit() override
1757 {
1759
1760 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1761 }
1762
1764 {
1767 for (const auto &a : i->accepted) {
1768 if (IsValidCargoType(a.cargo)) cargo_types.Set(a.cargo);
1769 }
1770 for (const auto &p : i->produced) {
1771 if (IsValidCargoType(p.cargo)) cargo_types.Set(p.cargo);
1772 }
1773 return cargo_types;
1774 }
1775
1777 {
1778 return IndustryProductionGraphWindow::excluded_cargo_types;
1779 }
1780
1781 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1782 {
1783 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_CAPTION, this->window_number);
1784
1785 return this->Window::GetWidgetString(widget, stringid);
1786 }
1787
1788 void UpdateStatistics(bool initialize) override
1789 {
1790 int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
1791 auto yr = TimerGameEconomy::year;
1792 while (mo < 0) {
1793 yr--;
1794 mo += 12;
1795 }
1796
1797 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes().base() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1798 /* There's no reason to get new stats */
1799 return;
1800 }
1801
1802 this->excluded_data = this->GetExcludedCargoTypes().base();
1803 this->year = yr;
1804 this->month = mo;
1805
1806 const Industry *i = Industry::Get(this->window_number);
1807
1808 this->data.clear();
1809 this->data.reserve(
1810 2 * std::ranges::count_if(i->produced, &IsValidCargoType, &Industry::ProducedCargo::cargo) +
1811 2 * std::ranges::count_if(i->accepted, &IsValidCargoType, &Industry::AcceptedCargo::cargo));
1812
1813 for (const auto &p : i->produced) {
1814 if (!IsValidCargoType(p.cargo)) continue;
1815 const CargoSpec *cs = CargoSpec::Get(p.cargo);
1816
1817 DataSet &produced = this->data.emplace_back();
1818 produced.colour = cs->legend_colour;
1819 produced.exclude_bit = cs->Index();
1820 produced.range_bit = 0;
1821
1822 DataSet &transported = this->data.emplace_back();
1823 transported.colour = cs->legend_colour;
1824 transported.exclude_bit = cs->Index();
1825 transported.range_bit = 1;
1826 transported.dash = 2;
1827
1828 FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, *this->scales[this->selected_scale].history_range,
1831 }
1832
1833 for (const auto &a : i->accepted) {
1834 if (!IsValidCargoType(a.cargo)) continue;
1835 const CargoSpec *cs = CargoSpec::Get(a.cargo);
1836
1837 DataSet &accepted = this->data.emplace_back();
1838 accepted.colour = cs->legend_colour;
1839 accepted.exclude_bit = cs->Index();
1840 accepted.range_bit = 2;
1841 accepted.dash = 1;
1842
1843 DataSet &waiting = this->data.emplace_back();
1844 waiting.colour = cs->legend_colour;
1845 waiting.exclude_bit = cs->Index();
1846 waiting.range_bit = 3;
1847 waiting.dash = 4;
1848
1849 FillFromHistory<GRAPH_NUM_MONTHS>(a.history.get(), i->valid_history, *this->scales[this->selected_scale].history_range,
1850 Filler{{accepted}, &Industry::AcceptedHistory::accepted},
1852 }
1853
1854 this->SetDirty();
1855 }
1856};
1857
1858static constexpr std::initializer_list<NWidgetPart> _nested_industry_production_widgets = {
1865 EndContainer(),
1870 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1873 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1874 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1877 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),
1879 EndContainer(),
1881 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_SELECT_SCALE),
1882 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1883 EndContainer(),
1884 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1885 EndContainer(),
1889 EndContainer(),
1890 EndContainer(),
1891};
1892
1895 WindowPosition::Automatic, "graph_industry_production", 0, 0,
1896 WindowClass::IndustryProductionGraph, WindowClass::IndustryView,
1897 {},
1898 _nested_industry_production_widgets
1899);
1900
1901void ShowIndustryProductionGraph(WindowNumber window_number)
1902{
1904}
1905
1906struct TownCargoGraphWindow : BaseCargoGraphWindow {
1907 static inline constexpr GraphRange RANGE_LABELS[] = {
1908 {STR_GRAPH_TOWN_RANGE_PRODUCED, STR_GRAPH_TOWN_RANGE_PRODUCED_TOOLTIP},
1909 {STR_GRAPH_TOWN_RANGE_TRANSPORTED, STR_GRAPH_TOWN_RANGE_TRANSPORTED_TOOLTIP},
1910 {STR_GRAPH_TOWN_RANGE_DELIVERED, STR_GRAPH_TOWN_RANGE_DELIVERED_TOOLTIP},
1911 };
1912
1913 static inline CargoTypes excluded_cargo_types{};
1914
1915 TownCargoGraphWindow(WindowDesc &desc, WindowNumber window_number) : BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1916 {
1917 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1918 this->num_vert_lines = GRAPH_NUM_MONTHS;
1919 this->month_increment = 1;
1920 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1921 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1922 this->ranges = RANGE_LABELS;
1923
1924 this->InitializeWindow(window_number);
1925 }
1926
1927 void OnInit() override
1928 {
1930
1931 this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
1932 }
1933
1935 {
1937 const Town *t = Town::Get(window_number);
1938 for (const auto &s : t->supplied) {
1939 if (IsValidCargoType(s.cargo)) cargo_types.Set(s.cargo);
1940 }
1941 for (const auto &a : t->accepted) {
1942 if (IsValidCargoType(a.cargo)) cargo_types.Set(a.cargo);
1943 }
1944 return cargo_types;
1945 }
1946
1948 {
1949 return TownCargoGraphWindow::excluded_cargo_types;
1950 }
1951
1952 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1953 {
1954 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_TOWN_CARGO_CAPTION, this->window_number);
1955
1956 return this->Window::GetWidgetString(widget, stringid);
1957 }
1958
1959 void UpdateStatistics(bool initialize) override
1960 {
1961 int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
1962 auto yr = TimerGameEconomy::year;
1963 while (mo < 0) {
1964 yr--;
1965 mo += 12;
1966 }
1967
1968 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes().base() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1969 /* There's no reason to get new stats */
1970 return;
1971 }
1972
1973 this->excluded_data = this->GetExcludedCargoTypes().base();
1974 this->year = yr;
1975 this->month = mo;
1976
1977 const Town *t = Town::Get(this->window_number);
1978
1979 this->data.clear();
1980 this->data.reserve(
1981 2 * std::ranges::count_if(t->supplied, &IsValidCargoType, &Town::SuppliedCargo::cargo) +
1982 1 * std::ranges::count_if(t->accepted, &IsValidCargoType, &Town::AcceptedCargo::cargo));
1983
1984 for (const auto &s : t->supplied) {
1985 if (!IsValidCargoType(s.cargo)) continue;
1986 const CargoSpec *cs = CargoSpec::Get(s.cargo);
1987
1988 DataSet &produced = this->data.emplace_back();
1989 produced.colour = cs->legend_colour;
1990 produced.exclude_bit = cs->Index();
1991 produced.range_bit = 0;
1992
1993 DataSet &transported = this->data.emplace_back();
1994 transported.colour = cs->legend_colour;
1995 transported.exclude_bit = cs->Index();
1996 transported.range_bit = 1;
1997 transported.dash = 2;
1998
1999 FillFromHistory<GRAPH_NUM_MONTHS>(s.history, t->valid_history, *this->scales[this->selected_scale].history_range,
2002 }
2003
2004 for (const auto &a : t->accepted) {
2005 if (!IsValidCargoType(a.cargo)) continue;
2006 const CargoSpec *cs = CargoSpec::Get(a.cargo);
2007
2008 DataSet &accepted = this->data.emplace_back();
2009 accepted.colour = cs->legend_colour;
2010 accepted.exclude_bit = cs->Index();
2011 accepted.range_bit = 2;
2012 accepted.dash = 1;
2013
2014 FillFromHistory<GRAPH_NUM_MONTHS>(a.history, t->valid_history, *this->scales[this->selected_scale].history_range,
2015 Filler{{accepted}, &Town::AcceptedHistory::accepted});
2016 }
2017
2018 this->SetDirty();
2019 }
2020};
2021
2022static constexpr std::initializer_list<NWidgetPart> _nested_town_cargo_graph_widgets = {
2029 EndContainer(),
2034 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
2037 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
2038 NWidget(WWT_PUSHTXTBTN, Colours::Brown, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
2041 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),
2043 EndContainer(),
2045 NWidget(WWT_MATRIX, Colours::Brown, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
2046 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
2047 EndContainer(),
2048 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
2049 EndContainer(),
2053 EndContainer(),
2054 EndContainer(),
2055};
2056
2059 WindowPosition::Automatic, "graph_town_cargo", 0, 0,
2060 WindowClass::TownCargoGraph, WindowClass::TownView,
2061 {},
2062 _nested_town_cargo_graph_widgets
2063);
2064
2065void ShowTownCargoGraph(WindowNumber window_number)
2066{
2068}
2069
2074static std::unique_ptr<NWidgetBase> MakePerformanceDetailPanels()
2075{
2076 auto realtime = TimerGameEconomy::UsingWallclockUnits();
2077 const StringID performance_tips[] = {
2078 realtime ? STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_YEARS,
2079 STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
2080 realtime ? STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_YEARS,
2081 STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
2082 STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
2083 STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
2084 STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
2085 STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
2086 STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
2087 STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
2088 };
2089
2090 static_assert(lengthof(performance_tips) == to_underlying(ScoreID::End));
2091
2092 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
2093 for (WidgetID widnum = WID_PRD_SCORE_FIRST; widnum <= WID_PRD_SCORE_LAST; widnum++) {
2094 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, Colours::Brown, widnum);
2095 panel->SetFill(1, 1);
2096 panel->SetToolTip(performance_tips[widnum - WID_PRD_SCORE_FIRST]);
2097 vert->Add(std::move(panel));
2098 }
2099 return vert;
2100}
2101
2103std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsGraphGUI()
2104{
2105 return MakeCompanyButtonRows(WID_PRD_COMPANY_FIRST, WID_PRD_COMPANY_LAST, Colours::Brown, 8, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
2106}
2107
2108static constexpr std::initializer_list<NWidgetPart> _nested_performance_rating_detail_widgets = {
2111 NWidget(WWT_CAPTION, Colours::Brown), SetStringTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2114 EndContainer(),
2117 EndContainer(),
2119};
2120
2123 WindowPosition::Automatic, "league_details", 0, 0,
2124 WindowClass::PerformanceDetail, WindowClass::None,
2125 {},
2126 _nested_performance_rating_detail_widgets
2127);
2128
2129void ShowPerformanceRatingDetail()
2130{
2132}
2133
2134void InitializeGraphGui()
2135{
2137 PaymentRatesGraphWindow::excluded_cargo_types = {};
2138 IndustryProductionGraphWindow::excluded_cargo_types = {};
2139}
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
EnumBitSet< CargoType, uint64_t > CargoTypes
Bitset of CargoType elements.
Definition cargo_type.h:113
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.
Iterate a range of enum values.
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:2462
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:2536
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:42
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:95
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.
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.
@ 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.
@ ForceRight
Force align to the right.
@ Centre
Align to the centre.
@ End
Align to the end, LTR/RTL aware.
@ Start
Align to the start, LTR/RTL aware.
int CentreBounds(int min, int max, int size)
Determine where to position a centred object.
@ Middle
Align to the middle.
bool DrawStringMultiLineWithClipping(int left, int right, int top, int bottom, std::string_view str, ExtendedTextColour colour, Alignment 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:872
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:971
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:899
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
int DrawString(int left, int right, int top, std::string_view str, ExtendedTextColour colour, Alignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:668
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
@ 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:315
@ White
White colour.
Definition gfx_type.h:330
@ FromString
Marker for telling to use the colour from the string.
Definition gfx_type.h:317
@ Black
Black colour.
Definition gfx_type.h:334
@ Checker
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:393
static WindowDesc _operating_profit_desc(WindowPosition::Automatic, "graph_operating_profit", 0, 0, WindowClass::OperatingProfitGraph, WindowClass::None, {}, _nested_operating_profit_widgets)
Window definition for the operating profit graph window.
static WindowDesc _graph_legend_desc(WindowPosition::Automatic, "graph_legend", 0, 0, WindowClass::GraphLegend, WindowClass::None, {}, _nested_graph_legend_widgets)
Window definition for the graph legend 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 _industry_production_desc(WindowPosition::Automatic, "graph_industry_production", 0, 0, WindowClass::IndustryProductionGraph, WindowClass::IndustryView, {}, _nested_industry_production_widgets)
Window definition for the industry production graph window.
static WindowDesc _performance_history_desc(WindowPosition::Automatic, "graph_performance", 0, 0, WindowClass::PerformanceGraph, WindowClass::None, {}, _nested_performance_history_widgets)
Window definition for the performance history graph window.
static WindowDesc _town_cargo_graph_desc(WindowPosition::Automatic, "graph_town_cargo", 0, 0, WindowClass::TownCargoGraph, WindowClass::TownView, {}, _nested_town_cargo_graph_widgets)
Window definition for the town cargo graph window.
static WindowDesc _delivered_cargo_graph_desc(WindowPosition::Automatic, "graph_delivered_cargo", 0, 0, WindowClass::DeliveredCargoGraph, WindowClass::None, {}, _nested_delivered_cargo_graph_widgets)
Window definition for the delivered cargo graph window.
static WindowDesc _cargo_payment_rates_desc(WindowPosition::Automatic, "graph_cargo_payment_rates", 0, 0, WindowClass::CargoPaymentRatesGraph, WindowClass::None, {}, _nested_cargo_payment_rates_widgets)
Window definition for the cargo payment rates graph window.
static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX)
Value used for a datapoint that shouldn't be drawn.
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 _performance_rating_detail_desc(WindowPosition::Automatic, "league_details", 0, 0, WindowClass::PerformanceDetail, WindowClass::None, {}, _nested_performance_rating_detail_widgets)
Window definition for the performance rating details window.
static WindowDesc _income_graph_desc(WindowPosition::Automatic, "graph_income", 0, 0, WindowClass::IncomeGraph, WindowClass::None, {}, _nested_income_graph_widgets)
Window definition for the income graph window.
static WindowDesc _company_value_graph_desc(WindowPosition::Automatic, "graph_company_value", 0, 0, WindowClass::CompanyValueGraph, WindowClass::None, {}, _nested_company_value_graph_widgets)
Window definition for the company value 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(Alignment 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:972
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.
void GuiShowTooltips(Window *parent, EncodedString &&text, TooltipCloseCondition close_tooltip)
Shows a tooltip.
Definition misc_gui.cpp:690
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:254
Functions related to sound.
This file contains all sprite-related enums and defines.
static const SpriteID SPR_COMPANY_ICON
Icon showing company colour.
Definition sprites.h:385
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:261
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
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
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.
Label and tooltip for a graph range.
StringID label
Label for this range.
StringID tooltip
Tooltip for this range.
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.
bool OnTooltip(Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
Event to display a custom tooltip.
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:77
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:141
CargoType Index() const
Determines index of this cargospec.
Definition cargotype.h:111
StringID name
Name of this type of cargo.
Definition cargotype.h:94
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.
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:88
uint16_t accepted
Total accepted.
Definition industry.h:83
uint16_t waiting
Average waiting.
Definition industry.h:84
CargoType cargo
Cargo type.
Definition industry.h:76
uint16_t transported
Total transported.
Definition industry.h:67
uint16_t production
Total produced.
Definition industry.h:66
Defines the internal data of a functional industry.
Definition industry.h:64
bool IsCargoAccepted() const
Test if this industry accepts any cargo.
Definition industry.h:225
ValidHistoryMask valid_history
Mask of valid history records.
Definition industry.h:111
ProducedCargoes produced
produced cargo slots
Definition industry.h:112
AcceptedCargoes accepted
accepted cargo slots
Definition industry.h:113
bool IsCargoProduced() const
Test if this industry produces any cargo.
Definition industry.h:231
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:307
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:117
uint32_t accepted
Total accepted.
Definition town.h:112
uint32_t transported
Total transported.
Definition town.h:93
uint32_t production
Total produced.
Definition town.h:92
Town data structure.
Definition town.h:64
SuppliedCargoes supplied
Cargo statistics about supplied cargo.
Definition town.h:131
ValidHistoryMask valid_history
Mask of valid history records.
Definition town.h:135
AcceptedCargoes accepted
Cargo statistics about accepted cargo.
Definition town.h:132
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:172
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:273
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1814
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing).
Definition window.cpp:3255
void RaiseWidget(WidgetID widget_index)
Marks a widget as raised.
Definition window_gui.h:469
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:510
ResizeInfo resize
Resize information.
Definition window_gui.h:314
int scale
Scale of this window – used to determine how to resize.
Definition window_gui.h:304
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1804
bool IsWidgetDisabled(WidgetID widget_index) const
Gets the enabled/disabled status of a widget.
Definition window_gui.h:410
int left
x position of left edge of the window
Definition window_gui.h:309
Window(WindowDesc &desc)
Empty constructor, initialization has been moved to InitNested() called from the constructor of the d...
Definition window.cpp:1838
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:215
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:989
void LowerWidget(WidgetID widget_index)
Marks a widget as lowered.
Definition window_gui.h:460
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1828
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:319
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:381
int height
Height of the window (number of pixels down in y direction).
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:450
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:302
Definition of Interval and OneShot timers.
Definition of the game-economy-timer.
Definition of the tick-based game-timer.
Definition of the Window system.
static constexpr std::chrono::milliseconds TIMER_BLINK_INTERVAL
Interval used by blinking interface elements.
Base of the town class.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:308
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:3480
@ 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:3315
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting).
Definition window.cpp:3193
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
TooltipCloseCondition
Definition window_gui.h:263
@ 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:146
int WidgetID
Widget ID.
Definition window_type.h:21
Functions related to zooming.
@ Normal
The normal zoom level.
Definition zoom_type.h:26