OpenTTD Source 20250716-master-g6b6caa6fa8
graph_gui.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "misc/history_func.hpp"
12#include "graph_gui.h"
13#include "window_gui.h"
14#include "company_base.h"
15#include "company_gui.h"
16#include "economy_func.h"
17#include "cargotype.h"
18#include "strings_func.h"
19#include "window_func.h"
20#include "gfx_func.h"
22#include "currency.h"
23#include "timer/timer.h"
24#include "timer/timer_window.h"
27#include "zoom_func.h"
28#include "industry.h"
29
31
32#include "table/strings.h"
33#include "table/sprites.h"
34
35#include "safeguards.h"
36
37/* Bitmasks of company and cargo indices that shouldn't be drawn. */
38static CompanyMask _legend_excluded_companies;
39
40/* Apparently these don't play well with enums. */
41static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
42static const uint INVALID_DATAPOINT_POS = UINT_MAX; // Used to determine if the previous point was drawn.
43
44constexpr double INT64_MAX_IN_DOUBLE = static_cast<double>(INT64_MAX - 512);
45static_assert(static_cast<int64_t>(INT64_MAX_IN_DOUBLE) < INT64_MAX);
46
47/****************/
48/* GRAPH LEGEND */
49/****************/
50
53 {
54 this->InitNested(window_number);
55
56 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
57 if (!_legend_excluded_companies.Test(c)) this->LowerWidget(WID_GL_FIRST_COMPANY + c);
58
59 this->OnInvalidateData(c.base());
60 }
61 }
62
63 void DrawWidget(const Rect &r, WidgetID widget) const override
64 {
65 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
66
68
69 if (!Company::IsValidID(cid)) return;
70
71 bool rtl = _current_text_dir == TD_RTL;
72
73 const Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
74 Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
75 DrawCompanyIcon(cid, rtl ? ir.right - d.width : ir.left, CentreBounds(ir.top, ir.bottom, d.height));
76
77 const Rect tr = ir.Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl);
78 DrawString(tr.left, tr.right, CentreBounds(tr.top, tr.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_COMPANY_NAME_COMPANY_NUM, cid, cid), _legend_excluded_companies.Test(cid) ? TC_BLACK : TC_WHITE);
79 }
80
81 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
82 {
83 if (!IsInsideMM(widget, WID_GL_FIRST_COMPANY, WID_GL_FIRST_COMPANY + MAX_COMPANIES)) return;
84
85 _legend_excluded_companies.Flip(static_cast<CompanyID>(widget - WID_GL_FIRST_COMPANY));
86 this->ToggleWidgetLoweredState(widget);
87 this->SetDirty();
93 }
94
100 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
101 {
102 if (!gui_scope) return;
103 if (Company::IsValidID(data)) return;
104
105 _legend_excluded_companies.Set(static_cast<CompanyID>(data));
106 this->RaiseWidget(data + WID_GL_FIRST_COMPANY);
107 }
108};
109
114static std::unique_ptr<NWidgetBase> MakeNWidgetCompanyLines()
115{
116 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
117 vert->SetPadding(2, 2, 2, 2);
118 uint sprite_height = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZoomLevel::Normal).height;
119
120 for (WidgetID widnum = WID_GL_FIRST_COMPANY; widnum <= WID_GL_LAST_COMPANY; widnum++) {
121 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
122 panel->SetMinimalSize(246, sprite_height + WidgetDimensions::unscaled.framerect.Vertical());
123 panel->SetMinimalTextLines(1, WidgetDimensions::unscaled.framerect.Vertical(), FS_NORMAL);
124 panel->SetFill(1, 1);
125 panel->SetToolTip(STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP);
126 vert->Add(std::move(panel));
127 }
128 return vert;
129}
130
131static constexpr NWidgetPart _nested_graph_legend_widgets[] = {
133 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
134 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_KEY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
135 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
136 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
137 EndContainer(),
138 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GL_BACKGROUND),
140 EndContainer(),
141};
142
143static WindowDesc _graph_legend_desc(
144 WDP_AUTO, "graph_legend", 0, 0,
146 {},
147 _nested_graph_legend_widgets
148);
149
150static void ShowGraphLegend()
151{
152 AllocateWindowDescFront<GraphLegendWindow>(_graph_legend_desc, 0);
153}
154
160
161/******************/
162/* BASE OF GRAPHS */
163/*****************/
164
166protected:
167 static const int GRAPH_MAX_DATASETS = 64;
168 static const int GRAPH_BASE_COLOUR = GREY_SCALE(2);
169 static const int GRAPH_GRID_COLOUR = GREY_SCALE(3);
170 static const int GRAPH_AXIS_LINE_COLOUR = GREY_SCALE(1);
171 static const int GRAPH_ZERO_LINE_COLOUR = GREY_SCALE(8);
172 static const int GRAPH_YEAR_LINE_COLOUR = GREY_SCALE(5);
173 static const int GRAPH_NUM_MONTHS = 24;
174 static const int GRAPH_PAYMENT_RATE_STEPS = 20;
175 static const int PAYMENT_GRAPH_X_STEP_DAYS = 10;
176 static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20;
177 static const int ECONOMY_QUARTER_MINUTES = 3;
178 static const int ECONOMY_MONTH_MINUTES = 1;
179
180 static const TextColour GRAPH_AXIS_LABEL_COLOUR = TC_BLACK;
181
182 static const int MIN_GRAPH_NUM_LINES_Y = 9;
183 static const int MIN_GRID_PIXEL_SIZE = 20;
184
185 uint64_t excluded_data = 0;
186 uint64_t excluded_range = 0;
187 uint8_t num_on_x_axis = 0;
188 uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
189
190 /* The starting month and year that values are plotted against. */
193 uint8_t month_increment = 3;
194
195 bool draw_dates = true;
196
197 /* These values are used if the graph is being plotted against values
198 * rather than the dates specified by month and year. */
199 bool x_values_reversed = true;
200 int16_t x_values_increment = ECONOMY_QUARTER_MINUTES;
201
202 StringID format_str_y_axis{};
203
204 struct DataSet {
205 std::array<OverflowSafeInt64, GRAPH_NUM_MONTHS> values;
206 uint8_t colour;
207 uint8_t exclude_bit;
208 uint8_t range_bit;
209 uint8_t dash;
210 };
211 std::vector<DataSet> data{};
212
213 std::span<const StringID> ranges = {};
214
215 uint8_t highlight_data = UINT8_MAX;
216 uint8_t highlight_range = UINT8_MAX;
217 bool highlight_state = false;
218
219 template <typename Tprojection>
220 struct Filler {
222 const Tprojection &proj;
223
224 inline void Fill(uint i, const auto &data) const { this->dataset.values[i] = std::invoke(this->proj, data); }
225 inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
226 };
227
233 std::span<const OverflowSafeInt64> GetDataSetRange(const DataSet &dataset) const
234 {
235 return {std::begin(dataset.values), std::begin(dataset.values) + this->num_on_x_axis};
236 }
237
244 ValuesInterval GetValuesInterval(int num_hori_lines) const
245 {
246 assert(num_hori_lines > 0);
247
248 ValuesInterval current_interval;
249 current_interval.highest = INT64_MIN;
250 current_interval.lowest = INT64_MAX;
251
252 for (const DataSet &dataset : this->data) {
253 if (HasBit(this->excluded_data, dataset.exclude_bit)) continue;
254 if (HasBit(this->excluded_range, dataset.range_bit)) continue;
255
256 for (const OverflowSafeInt64 &datapoint : this->GetDataSetRange(dataset)) {
257 if (datapoint != INVALID_DATAPOINT) {
258 current_interval.highest = std::max(current_interval.highest, datapoint);
259 current_interval.lowest = std::min(current_interval.lowest, datapoint);
260 }
261 }
262 }
263
264 /* Always include zero in the shown range. */
265 double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest);
266 double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest;
267
268 /* Prevent showing values too close to the graph limits. */
269 abs_higher = (11.0 * abs_higher) / 10.0;
270 abs_lower = (11.0 * abs_lower) / 10.0;
271
272 int num_pos_grids;
273 OverflowSafeInt64 grid_size;
274
275 if (abs_lower != 0 || abs_higher != 0) {
276 /* The number of grids to reserve for the positive part is: */
277 num_pos_grids = (int)floor(0.5 + num_hori_lines * abs_higher / (abs_higher + abs_lower));
278
279 /* If there are any positive or negative values, force that they have at least one grid. */
280 if (num_pos_grids == 0 && abs_higher != 0) num_pos_grids++;
281 if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--;
282
283 /* Get the required grid size for each side and use the maximum one. */
284
285 OverflowSafeInt64 grid_size_higher = 0;
286 if (abs_higher > 0) {
287 grid_size_higher = abs_higher > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_higher);
288 grid_size_higher = (grid_size_higher + num_pos_grids - 1) / num_pos_grids;
289 }
290
291 OverflowSafeInt64 grid_size_lower = 0;
292 if (abs_lower > 0) {
293 grid_size_lower = abs_lower > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_lower);
294 grid_size_lower = (grid_size_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids);
295 }
296
297 grid_size = std::max(grid_size_higher, grid_size_lower);
298 } else {
299 /* If both values are zero, show an empty graph. */
300 num_pos_grids = num_hori_lines / 2;
301 grid_size = 1;
302 }
303
304 current_interval.highest = num_pos_grids * grid_size;
305 current_interval.lowest = -(num_hori_lines - num_pos_grids) * grid_size;
306 return current_interval;
307 }
308
314 uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
315 {
316 /* draw text strings on the y axis */
317 int64_t y_label = current_interval.highest;
318 int64_t y_label_separation = (current_interval.highest - current_interval.lowest) / num_hori_lines;
319
320 uint max_width = 0;
321
322 for (int i = 0; i < (num_hori_lines + 1); i++) {
323 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label));
324 if (d.width > max_width) max_width = d.width;
325
326 y_label -= y_label_separation;
327 }
328
329 return max_width;
330 }
331
336 void DrawGraph(Rect r) const
337 {
338 uint x, y;
339 ValuesInterval interval;
340 int x_axis_offset;
341
342 /* the colours and cost array of GraphDrawer must accommodate
343 * both values for cargo and companies. So if any are higher, quit */
344 static_assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_COMPANIES);
345 assert(this->num_vert_lines > 0);
346
347 bool rtl = _current_text_dir == TD_RTL;
348
349 /* Rect r will be adjusted to contain just the graph, with labels being
350 * placed outside the area. */
351 r.top += ScaleGUITrad(5) + GetCharacterHeight(FS_SMALL) / 2;
352 r.bottom -= (this->draw_dates ? 2 : 1) * GetCharacterHeight(FS_SMALL) + ScaleGUITrad(4);
353 r.left += ScaleGUITrad(rtl ? 5 : 9);
354 r.right -= ScaleGUITrad(rtl ? 9 : 5);
355
356 /* Initial number of horizontal lines. */
357 int num_hori_lines = 160 / ScaleGUITrad(MIN_GRID_PIXEL_SIZE);
358 /* For the rest of the height, the number of horizontal lines will increase more slowly. */
359 int resize = (r.bottom - r.top - 160) / (2 * ScaleGUITrad(MIN_GRID_PIXEL_SIZE));
360 if (resize > 0) num_hori_lines += resize;
361
362 interval = GetValuesInterval(num_hori_lines);
363
364 int label_width = GetYLabelWidth(interval, num_hori_lines);
365
366 if (rtl) {
367 r.right -= label_width;
368 } else {
369 r.left += label_width;
370 }
371
372 int x_sep = (r.right - r.left) / this->num_vert_lines;
373 int y_sep = (r.bottom - r.top) / num_hori_lines;
374
375 /* Redetermine right and bottom edge of graph to fit with the integer
376 * separation values. */
377 if (rtl) {
378 r.left = r.right - x_sep * this->num_vert_lines;
379 } else {
380 r.right = r.left + x_sep * this->num_vert_lines;
381 }
382 r.bottom = r.top + y_sep * num_hori_lines;
383
384 OverflowSafeInt64 interval_size = interval.highest + abs(interval.lowest);
385 /* Where to draw the X axis. Use floating point to avoid overflowing and results of zero. */
386 x_axis_offset = (int)((r.bottom - r.top) * (double)interval.highest / (double)interval_size);
387
388 /* Draw the background of the graph itself. */
389 GfxFillRect(r.left, r.top, r.right, r.bottom, GRAPH_BASE_COLOUR);
390
391 /* Draw the grid lines. */
392 int gridline_width = WidgetDimensions::scaled.bevel.top;
393 int grid_colour = GRAPH_GRID_COLOUR;
394
395 /* Don't draw the first line, as that's where the axis will be. */
396 if (rtl) {
397 x_sep = -x_sep;
398 x = r.right + x_sep;
399 } else {
400 x = r.left + x_sep;
401 }
402
403 for (int i = 1; i < this->num_vert_lines + 1; i++) {
404 /* If using wallclock units, we separate periods with a lighter line. */
406 grid_colour = (i % 4 == 0) ? GRAPH_YEAR_LINE_COLOUR : GRAPH_GRID_COLOUR;
407 }
408 GfxFillRect(x, r.top, x + gridline_width - 1, r.bottom, grid_colour);
409 x += x_sep;
410 }
411
412 /* Draw the horizontal grid lines. */
413 y = r.bottom;
414
415 for (int i = 0; i < (num_hori_lines + 1); i++) {
416 if (rtl) {
417 GfxFillRect(r.right + 1, y, r.right + ScaleGUITrad(3), y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
418 } else {
419 GfxFillRect(r.left - ScaleGUITrad(3), y, r.left - 1, y + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
420 }
421 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_GRID_COLOUR);
422 y -= y_sep;
423 }
424
425 /* Draw the y axis. */
426 GfxFillRect(r.left, r.top, r.left + gridline_width - 1, r.bottom + gridline_width - 1, GRAPH_AXIS_LINE_COLOUR);
427
428 /* Draw the x axis. */
429 y = x_axis_offset + r.top;
430 GfxFillRect(r.left, y, r.right + gridline_width - 1, y + gridline_width - 1, GRAPH_ZERO_LINE_COLOUR);
431
432 /* Find the largest value that will be drawn. */
433 if (this->num_on_x_axis == 0) return;
434
435 assert(this->num_on_x_axis > 0);
436
437 /* draw text strings on the y axis */
438 int64_t y_label = interval.highest;
439 int64_t y_label_separation = abs(interval.highest - interval.lowest) / num_hori_lines;
440
441 y = r.top - GetCharacterHeight(FS_SMALL) / 2;
442
443 for (int i = 0; i < (num_hori_lines + 1); i++) {
444 if (rtl) {
445 DrawString(r.right + ScaleGUITrad(4), r.right + label_width + ScaleGUITrad(4), y,
446 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
448 } else {
449 DrawString(r.left - label_width - ScaleGUITrad(4), r.left - ScaleGUITrad(4), y,
450 GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, y_label),
452 }
453
454 y_label -= y_label_separation;
455 y += y_sep;
456 }
457
458 x = rtl ? r.right : r.left;
459 y = r.bottom + ScaleGUITrad(2);
460
461 /* if there are not enough datapoints to fill the graph, align to the right */
462 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
463
464 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
465 if (this->draw_dates) {
466 TimerGameEconomy::Month month = this->month;
467 TimerGameEconomy::Year year = this->year;
468 for (int i = 0; i < this->num_on_x_axis; i++) {
469 if (rtl) {
470 DrawStringMultiLineWithClipping(x + x_sep, x, y, this->height,
471 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
473 } else {
474 DrawStringMultiLineWithClipping(x, x + x_sep, y, this->height,
475 GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year),
477 }
478
479 month += this->month_increment;
480 if (month >= 12) {
481 month = 0;
482 year++;
483
484 /* Draw a lighter grid line between years. Top and bottom adjustments ensure we don't draw over top and bottom horizontal grid lines. */
485 GfxFillRect(x + x_sep, r.top + gridline_width, x + x_sep + gridline_width - 1, r.bottom - 1, GRAPH_YEAR_LINE_COLOUR);
486 }
487 x += x_sep;
488 }
489 } else {
490 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates, and all graphs when using wallclock units). */
491 int16_t iterator;
492 uint16_t label;
493 if (this->x_values_reversed) {
494 label = this->x_values_increment * this->num_on_x_axis;
495 iterator = -this->x_values_increment;
496 } else {
497 label = this->x_values_increment;
498 iterator = this->x_values_increment;
499 }
500
501 for (int i = 0; i < this->num_on_x_axis; i++) {
502 if (rtl) {
503 DrawString(x + x_sep + 1, x - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
504 } else {
505 DrawString(x + 1, x + x_sep - 1, y, GetString(STR_GRAPH_Y_LABEL_NUMBER, label), GRAPH_AXIS_LABEL_COLOUR, SA_HOR_CENTER);
506 }
507
508 label += iterator;
509 x += x_sep;
510 }
511 }
512
513 /* Draw lines and dots. */
516 uint pointoffs1 = pointwidth / 2;
517 uint pointoffs2 = pointwidth - pointoffs1;
518
519 auto draw_dataset = [&](const DataSet &dataset, uint8_t colour) {
520 if (HasBit(this->excluded_data, dataset.exclude_bit)) return;
521 if (HasBit(this->excluded_range, dataset.range_bit)) return;
522
523 /* Centre the dot between the grid lines. */
524 if (rtl) {
525 x = r.right + (x_sep / 2);
526 } else {
527 x = r.left + (x_sep / 2);
528 }
529
530 /* if there are not enough datapoints to fill the graph, align to the right */
531 x += (this->num_vert_lines - this->num_on_x_axis) * x_sep;
532
533 uint prev_x = INVALID_DATAPOINT_POS;
534 uint prev_y = INVALID_DATAPOINT_POS;
535
536 const uint dash = ScaleGUITrad(dataset.dash);
537 for (OverflowSafeInt64 datapoint : this->GetDataSetRange(dataset)) {
538 if (datapoint != INVALID_DATAPOINT) {
539 /*
540 * Check whether we need to reduce the 'accuracy' of the
541 * datapoint value and the highest value to split overflows.
542 * And when 'drawing' 'one million' or 'one million and one'
543 * there is no significant difference, so the least
544 * significant bits can just be removed.
545 *
546 * If there are more bits needed than would fit in a 32 bits
547 * integer, so at about 31 bits because of the sign bit, the
548 * least significant bits are removed.
549 */
550 int mult_range = FindLastBit<uint32_t>(x_axis_offset) + FindLastBit<uint64_t>(abs(datapoint));
551 int reduce_range = std::max(mult_range - 31, 0);
552
553 /* Handle negative values differently (don't shift sign) */
554 if (datapoint < 0) {
555 datapoint = -(abs(datapoint) >> reduce_range);
556 } else {
557 datapoint >>= reduce_range;
558 }
559 y = r.top + x_axis_offset - ((r.bottom - r.top) * datapoint) / (interval_size >> reduce_range);
560
561 /* Draw the point. */
562 GfxFillRect(x - pointoffs1, y - pointoffs1, x + pointoffs2, y + pointoffs2, colour);
563
564 /* Draw the line connected to the previous point. */
565 if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, colour, linewidth, dash);
566
567 prev_x = x;
568 prev_y = y;
569 } else {
570 prev_x = INVALID_DATAPOINT_POS;
571 prev_y = INVALID_DATAPOINT_POS;
572 }
573
574 x += x_sep;
575 }
576 };
577
578 /* Draw unhighlighted datasets. */
579 for (const DataSet &dataset : this->data) {
580 if (dataset.exclude_bit != this->highlight_data && dataset.range_bit != this->highlight_range) {
581 draw_dataset(dataset, dataset.colour);
582 }
583 }
584
585 /* If any dataset or range is highlighted, draw separately after the rest so they appear on top of all other
586 * data. Highlighted data is only drawn when highlight_state is set, otherwise it is invisible. */
587 if (this->highlight_state && (this->highlight_data != UINT8_MAX || this->highlight_range != UINT8_MAX)) {
588 for (const DataSet &dataset : this->data) {
589 if (dataset.exclude_bit == this->highlight_data || dataset.range_bit == this->highlight_range) {
590 draw_dataset(dataset, PC_WHITE);
591 }
592 }
593 }
594 }
595
596 BaseGraphWindow(WindowDesc &desc, StringID format_str_y_axis) :
597 Window(desc),
598 format_str_y_axis(format_str_y_axis)
599 {
601 }
602
603 const IntervalTimer<TimerWindow> blink_interval = {TIMER_BLINK_INTERVAL, [this](auto) {
604 /* If nothing is highlighted then no redraw is needed. */
605 if (this->highlight_data == UINT8_MAX && this->highlight_range == UINT8_MAX) return;
606
607 /* Toggle the highlight state and redraw. */
608 this->highlight_state = !this->highlight_state;
609 this->SetDirty();
610 }};
611
612public:
613 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
614 {
615 switch (widget) {
617 for (const StringID &str : this->ranges) {
618 size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
619 }
620
623
624 /* Set fixed height for number of ranges. */
625 size.height *= static_cast<uint>(std::size(this->ranges));
626
627 resize.width = 0;
628 resize.height = 0;
629 this->GetWidget<NWidgetCore>(WID_GRAPH_RANGE_MATRIX)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(this->ranges)));
630 break;
631
632 case WID_GRAPH_GRAPH: {
633 uint x_label_width = 0;
634
635 /* Draw x-axis labels and markings for graphs based on financial quarters and years. */
636 if (this->draw_dates) {
637 TimerGameEconomy::Month month = this->month;
638 TimerGameEconomy::Year year = this->year;
639 for (int i = 0; i < this->num_on_x_axis; i++) {
640 x_label_width = std::max(x_label_width, GetStringBoundingBox(GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year)).width);
641
642 month += this->month_increment;
643 if (month >= 12) {
644 month = 0;
645 year++;
646 }
647 }
648 } else {
649 /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates). */
650 uint64_t max_value = GetParamMaxValue((this->num_on_x_axis + 1) * this->x_values_increment, 0, FS_SMALL);
651 x_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL_NUMBER, max_value)).width;
652 }
653
654 uint y_label_width = GetStringBoundingBox(GetString(STR_GRAPH_Y_LABEL, this->format_str_y_axis, INT64_MAX)).width;
655
656 size.width = std::max<uint>(size.width, ScaleGUITrad(5) + y_label_width + this->num_vert_lines * (x_label_width + ScaleGUITrad(5)) + ScaleGUITrad(9));
657 size.height = std::max<uint>(size.height, ScaleGUITrad(5) + (1 + MIN_GRAPH_NUM_LINES_Y * 2 + (this->draw_dates ? 3 : 1)) * GetCharacterHeight(FS_SMALL) + ScaleGUITrad(4));
658 size.height = std::max<uint>(size.height, size.width / 3);
659 break;
660 }
661
662 default: break;
663 }
664 }
665
666 void DrawWidget(const Rect &r, WidgetID widget) const override
667 {
668 switch (widget) {
669 case WID_GRAPH_GRAPH:
670 this->DrawGraph(r);
671 break;
672
675 uint index = 0;
676 Rect line = r.WithHeight(line_height);
677 for (const auto &str : this->ranges) {
678 bool lowered = !HasBit(this->excluded_range, index);
679
680 /* Redraw frame if lowered */
681 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
682
683 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
684 DrawString(text, str, TC_BLACK, SA_CENTER, false, FS_SMALL);
685
686 line = line.Translate(0, line_height);
687 ++index;
688 }
689 break;
690 }
691
692 default: break;
693 }
694 }
695
696 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
697 {
698 /* Clicked on legend? */
699 switch (widget) {
701 ShowGraphLegend();
702 break;
703
705 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
706
707 ToggleBit(this->excluded_range, row);
708 this->SetDirty();
709 break;
710 }
711
712 default: break;
713 }
714 }
715
716 void OnMouseOver(Point pt, WidgetID widget) override
717 {
718 /* Test if a range should be highlighted. */
719 uint8_t new_highlight_range = UINT8_MAX;
720 if (widget == WID_GRAPH_RANGE_MATRIX) {
721 int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
722 if (!HasBit(this->excluded_range, row)) new_highlight_range = static_cast<uint8_t>(row);
723 }
724
725 /* Test if a dataset should be highlighted. */
726 uint8_t new_highlight_data = UINT8_MAX;
727 if (widget == WID_GRAPH_MATRIX) {
728 auto dataset_index = this->GetDatasetIndex(pt.y);
729 if (dataset_index.has_value() && !HasBit(this->excluded_data, *dataset_index)) new_highlight_data = *dataset_index;
730 }
731
732 if (this->highlight_data == new_highlight_data && this->highlight_range == new_highlight_range) return;
733
734 /* Range or data set highlight has changed, set and redraw. */
735 this->highlight_data = new_highlight_data;
736 this->highlight_range = new_highlight_range;
737 this->highlight_state = true;
738 this->SetDirty();
739 }
740
741 void OnGameTick() override
742 {
743 this->UpdateStatistics(false);
744 }
745
751 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
752 {
753 if (!gui_scope) return;
754 this->UpdateStatistics(true);
755 }
756
757 virtual void UpdateStatistics(bool initialize) = 0;
758
759 virtual std::optional<uint8_t> GetDatasetIndex(int) { return std::nullopt; }
760};
761
763public:
764 BaseCompanyGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
765
766 void InitializeWindow(WindowNumber number)
767 {
768 /* Initialise the dataset */
769 this->UpdateStatistics(true);
770
771 this->CreateNestedTree();
772
773 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
774 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? STR_GRAPH_LAST_72_MINUTES_TIME_LABEL : STR_EMPTY);
775
776 this->FinishInitNested(number);
777 }
778
783 void UpdateStatistics(bool initialize) override
784 {
785 CompanyMask excluded_companies = _legend_excluded_companies;
786
787 /* Exclude the companies which aren't valid */
788 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
789 if (!Company::IsValidID(c)) excluded_companies.Set(c);
790 }
791
792 uint8_t nums = 0;
793 for (const Company *c : Company::Iterate()) {
794 nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent));
795 }
796
797 int mo = (TimerGameEconomy::month / this->month_increment - nums) * this->month_increment;
798 auto yr = TimerGameEconomy::year;
799 while (mo < 0) {
800 yr--;
801 mo += 12;
802 }
803
804 if (!initialize && this->excluded_data == excluded_companies.base() && this->num_on_x_axis == nums &&
805 this->year == yr && this->month == mo) {
806 /* There's no reason to get new stats */
807 return;
808 }
809
810 this->excluded_data = excluded_companies.base();
811 this->num_on_x_axis = nums;
812 this->year = yr;
813 this->month = mo;
814
815 this->data.clear();
816 for (CompanyID k = CompanyID::Begin(); k < MAX_COMPANIES; ++k) {
817 const Company *c = Company::GetIfValid(k);
818 if (c == nullptr) continue;
819
820 DataSet &dataset = this->data.emplace_back();
821 dataset.colour = GetColourGradient(c->colour, SHADE_LIGHTER);
822 dataset.exclude_bit = k.base();
823
824 for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
825 if (j >= c->num_valid_stat_ent) {
826 dataset.values[i] = INVALID_DATAPOINT;
827 } else {
828 /* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
829 * Instead, use the value just under it. Hopefully nobody will notice. */
830 dataset.values[i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
831 }
832 i++;
833 }
834 }
835 }
836
837 virtual OverflowSafeInt64 GetGraphData(const Company *, int) = 0;
838};
839
840
841/********************/
842/* OPERATING PROFIT */
843/********************/
844
847 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
848 {
849 this->num_on_x_axis = GRAPH_NUM_MONTHS;
850 this->num_vert_lines = GRAPH_NUM_MONTHS;
851 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
852
853 this->InitializeWindow(window_number);
854 }
855
856 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
857 {
858 return c->old_economy[j].income + c->old_economy[j].expenses;
859 }
860};
861
862static constexpr NWidgetPart _nested_operating_profit_widgets[] = {
864 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
865 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_OPERATING_PROFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
866 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
867 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
868 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
869 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
870 EndContainer(),
871 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
872 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 160), SetFill(1, 1), SetResize(1, 1),
874 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
875 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
876 EndContainer(),
877 EndContainer(),
878};
879
880static WindowDesc _operating_profit_desc(
881 WDP_AUTO, "graph_operating_profit", 0, 0,
883 {},
884 _nested_operating_profit_widgets
885);
886
887
888void ShowOperatingProfitGraph()
889{
890 AllocateWindowDescFront<OperatingProfitGraphWindow>(_operating_profit_desc, 0);
891}
892
893
894/****************/
895/* INCOME GRAPH */
896/****************/
897
900 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
901 {
902 this->num_on_x_axis = GRAPH_NUM_MONTHS;
903 this->num_vert_lines = GRAPH_NUM_MONTHS;
904 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
905
906 this->InitializeWindow(window_number);
907 }
908
909 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
910 {
911 return c->old_economy[j].income;
912 }
913};
914
915static constexpr NWidgetPart _nested_income_graph_widgets[] = {
917 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
918 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_INCOME_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
919 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
920 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
921 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
922 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
923 EndContainer(),
924 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
925 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
927 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
928 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
929 EndContainer(),
930 EndContainer(),
931};
932
933static WindowDesc _income_graph_desc(
934 WDP_AUTO, "graph_income", 0, 0,
936 {},
937 _nested_income_graph_widgets
938);
939
940void ShowIncomeGraph()
941{
942 AllocateWindowDescFront<IncomeGraphWindow>(_income_graph_desc, 0);
943}
944
945/*******************/
946/* DELIVERED CARGO */
947/*******************/
948
951 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
952 {
953 this->num_on_x_axis = GRAPH_NUM_MONTHS;
954 this->num_vert_lines = GRAPH_NUM_MONTHS;
955 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
956
957 this->InitializeWindow(window_number);
958 }
959
960 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
961 {
962 return c->old_economy[j].delivered_cargo.GetSum<OverflowSafeInt64>();
963 }
964};
965
966static constexpr NWidgetPart _nested_delivered_cargo_graph_widgets[] = {
968 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
969 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_DELIVERED_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
970 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
971 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
972 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
973 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
974 EndContainer(),
975 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
976 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 128), SetFill(1, 1), SetResize(1, 1),
978 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
979 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
980 EndContainer(),
981 EndContainer(),
982};
983
984static WindowDesc _delivered_cargo_graph_desc(
985 WDP_AUTO, "graph_delivered_cargo", 0, 0,
987 {},
988 _nested_delivered_cargo_graph_widgets
989);
990
991void ShowDeliveredCargoGraph()
992{
993 AllocateWindowDescFront<DeliveredCargoGraphWindow>(_delivered_cargo_graph_desc, 0);
994}
995
996/***********************/
997/* PERFORMANCE HISTORY */
998/***********************/
999
1002 BaseCompanyGraphWindow(desc, STR_JUST_COMMA)
1003 {
1004 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1005 this->num_vert_lines = GRAPH_NUM_MONTHS;
1006 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1007
1008 this->InitializeWindow(window_number);
1009 }
1010
1011 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1012 {
1013 return c->old_economy[j].performance_history;
1014 }
1015
1016 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1017 {
1018 if (widget == WID_PHG_DETAILED_PERFORMANCE) ShowPerformanceRatingDetail();
1019 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1020 }
1021};
1022
1023static constexpr NWidgetPart _nested_performance_history_widgets[] = {
1025 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1026 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_PERFORMANCE_RATINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1027 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_PHG_DETAILED_PERFORMANCE), SetMinimalSize(50, 0), SetStringTip(STR_PERFORMANCE_DETAIL_KEY, STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP),
1028 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1029 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1030 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1031 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1032 EndContainer(),
1033 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1034 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1036 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1037 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1038 EndContainer(),
1039 EndContainer(),
1040};
1041
1042static WindowDesc _performance_history_desc(
1043 WDP_AUTO, "graph_performance", 0, 0,
1045 {},
1046 _nested_performance_history_widgets
1047);
1048
1049void ShowPerformanceHistoryGraph()
1050{
1051 AllocateWindowDescFront<PerformanceHistoryGraphWindow>(_performance_history_desc, 0);
1052}
1053
1054/*****************/
1055/* COMPANY VALUE */
1056/*****************/
1057
1060 BaseCompanyGraphWindow(desc, STR_JUST_CURRENCY_SHORT)
1061 {
1062 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1063 this->num_vert_lines = GRAPH_NUM_MONTHS;
1064 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1065
1066 this->InitializeWindow(window_number);
1067 }
1068
1069 OverflowSafeInt64 GetGraphData(const Company *c, int j) override
1070 {
1071 return c->old_economy[j].company_value;
1072 }
1073};
1074
1075static constexpr NWidgetPart _nested_company_value_graph_widgets[] = {
1077 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1078 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_COMPANY_VALUES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1079 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_KEY_BUTTON), SetMinimalSize(50, 0), SetStringTip(STR_GRAPH_KEY_BUTTON, STR_GRAPH_KEY_TOOLTIP),
1080 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1081 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1082 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1083 EndContainer(),
1084 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND),
1085 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(576, 224), SetFill(1, 1), SetResize(1, 1),
1087 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1088 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1089 EndContainer(),
1090 EndContainer(),
1091};
1092
1093static WindowDesc _company_value_graph_desc(
1094 WDP_AUTO, "graph_company_value", 0, 0,
1096 {},
1097 _nested_company_value_graph_widgets
1098);
1099
1100void ShowCompanyValueGraph()
1101{
1102 AllocateWindowDescFront<CompanyValueGraphWindow>(_company_value_graph_desc, 0);
1103}
1104
1106 Scrollbar *vscroll = nullptr;
1107 uint line_height = 0;
1108 uint legend_width = 0;
1109
1110 CargoTypes cargo_types{};
1111
1112 BaseCargoGraphWindow(WindowDesc &desc, StringID format_str_y_axis) : BaseGraphWindow(desc, format_str_y_axis) {}
1113
1114 void InitializeWindow(WindowNumber number, StringID footer_wallclock = STR_EMPTY, StringID footer_calendar = STR_EMPTY)
1115 {
1116 this->CreateNestedTree();
1117
1118 this->cargo_types = this->GetCargoTypes(number);
1119
1120 this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
1121 this->vscroll->SetCount(CountBits(this->cargo_types));
1122
1123 auto *wid = this->GetWidget<NWidgetCore>(WID_GRAPH_FOOTER);
1124 wid->SetString(TimerGameEconomy::UsingWallclockUnits() ? footer_wallclock : footer_calendar);
1125
1126 this->FinishInitNested(number);
1127
1128 /* Initialise the dataset */
1129 this->InvalidateData();
1130 }
1131
1132 virtual CargoTypes GetCargoTypes(WindowNumber number) const = 0;
1133 virtual CargoTypes &GetExcludedCargoTypes() const = 0;
1134
1135 std::optional<uint8_t> GetDatasetIndex(int y) override
1136 {
1137 int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_GRAPH_MATRIX);
1138 if (row >= this->vscroll->GetCount()) return std::nullopt;
1139
1140 for (const CargoSpec *cs : _sorted_cargo_specs) {
1141 if (!HasBit(this->cargo_types, cs->Index())) continue;
1142 if (row-- > 0) continue;
1143
1144 return cs->Index();
1145 }
1146
1147 return std::nullopt;
1148 }
1149
1150 void OnInit() override
1151 {
1152 /* Width of the legend blob. */
1153 this->legend_width = GetCharacterHeight(FS_SMALL) * 9 / 6;
1154 }
1155
1156 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1157 {
1158 if (widget != WID_GRAPH_MATRIX) {
1159 BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1160 return;
1161 }
1162
1164
1165 for (CargoType cargo_type : SetCargoBitIterator(this->cargo_types)) {
1166 const CargoSpec *cs = CargoSpec::Get(cargo_type);
1167
1168 Dimension d = GetStringBoundingBox(GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1169 d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
1172 size = maxdim(d, size);
1173 }
1174
1175 this->line_height = size.height;
1176 size.height = this->line_height * 11; /* Default number of cargo types in most climates. */
1177 resize.width = 0;
1178 fill.height = resize.height = this->line_height;
1179 }
1180
1181 void DrawWidget(const Rect &r, WidgetID widget) const override
1182 {
1183 if (widget != WID_GRAPH_MATRIX) {
1184 BaseGraphWindow::DrawWidget(r, widget);
1185 return;
1186 }
1187
1188 bool rtl = _current_text_dir == TD_RTL;
1189
1190 int pos = this->vscroll->GetPosition();
1191 int max = pos + this->vscroll->GetCapacity();
1192
1193 Rect line = r.WithHeight(this->line_height);
1194
1195 for (const CargoSpec *cs : _sorted_cargo_specs) {
1196 if (!HasBit(this->cargo_types, cs->Index())) continue;
1197
1198 if (pos-- > 0) continue;
1199 if (--max < 0) break;
1200
1201 bool lowered = !HasBit(this->excluded_data, cs->Index());
1202
1203 /* Redraw frame if lowered */
1204 if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
1205
1206 const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
1207
1208 /* Cargo-colour box with outline */
1209 const Rect cargo = text.WithWidth(this->legend_width, rtl);
1210 GfxFillRect(cargo, PC_BLACK);
1211 uint8_t pc = cs->legend_colour;
1212 if (this->highlight_data == cs->Index()) pc = this->highlight_state ? PC_WHITE : PC_BLACK;
1214
1215 /* Cargo name */
1216 DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), GetString(STR_GRAPH_CARGO_PAYMENT_CARGO, cs->name));
1217
1218 line = line.Translate(0, this->line_height);
1219 }
1220 }
1221
1222 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1223 {
1224 switch (widget) {
1226 /* Remove all cargoes from the excluded lists. */
1227 this->GetExcludedCargoTypes() = {};
1228 this->excluded_data = this->GetExcludedCargoTypes();
1229 this->SetDirty();
1230 break;
1231
1233 /* Add all cargoes to the excluded lists. */
1234 this->GetExcludedCargoTypes() = this->cargo_types;
1235 this->excluded_data = this->GetExcludedCargoTypes();
1236 this->SetDirty();
1237 break;
1238 }
1239
1240 case WID_GRAPH_MATRIX: {
1241 int row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GRAPH_MATRIX);
1242 if (row >= this->vscroll->GetCount()) return;
1243
1244 for (const CargoSpec *cs : _sorted_cargo_specs) {
1245 if (!HasBit(this->cargo_types, cs->Index())) continue;
1246 if (row-- > 0) continue;
1247
1248 ToggleBit(this->GetExcludedCargoTypes(), cs->Index());
1249 this->excluded_data = this->GetExcludedCargoTypes();
1250 this->SetDirty();
1251 break;
1252 }
1253 break;
1254 }
1255
1256 default:
1257 this->BaseGraphWindow::OnClick(pt, widget, click_count);
1258 break;
1259 }
1260 }
1261
1262 void OnResize() override
1263 {
1264 this->vscroll->SetCapacityFromWidget(this, WID_GRAPH_MATRIX);
1265 }
1266};
1267
1268/*****************/
1269/* PAYMENT RATES */
1270/*****************/
1271
1273 static inline CargoTypes excluded_cargo_types{};
1274
1276 {
1277 this->num_on_x_axis = GRAPH_PAYMENT_RATE_STEPS;
1278 this->num_vert_lines = GRAPH_PAYMENT_RATE_STEPS;
1279 this->draw_dates = false;
1280
1281 this->x_values_reversed = false;
1282 /* The x-axis is labeled in either seconds or days. A day is two seconds, so we adjust the label if needed. */
1284
1285 this->InitializeWindow(window_number, STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS, STR_GRAPH_CARGO_PAYMENT_RATES_DAYS);
1286 }
1287
1288 CargoTypes GetCargoTypes(WindowNumber) const override
1289 {
1290 return _standard_cargo_mask;
1291 }
1292
1293 CargoTypes &GetExcludedCargoTypes() const override
1294 {
1295 return PaymentRatesGraphWindow::excluded_cargo_types;
1296 }
1297
1303 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1304 {
1305 if (!gui_scope) return;
1306 this->UpdatePaymentRates();
1307 }
1308
1310 const IntervalTimer<TimerWindow> update_payment_interval = {std::chrono::seconds(3), [this](auto) {
1311 this->UpdatePaymentRates();
1312 }};
1313
1314 void UpdateStatistics(bool) override {}
1315
1320 {
1321 this->excluded_data = this->GetExcludedCargoTypes();
1322
1323 this->data.clear();
1324 for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1325 DataSet &dataset = this->data.emplace_back();
1326 dataset.colour = cs->legend_colour;
1327 dataset.exclude_bit = cs->Index();
1328
1329 for (uint j = 0; j != this->num_on_x_axis; j++) {
1330 dataset.values[j] = GetTransportedGoodsIncome(10, 20, j * 4 + 4, cs->Index());
1331 }
1332 }
1333 }
1334};
1335
1336static constexpr NWidgetPart _nested_cargo_payment_rates_widgets[] = {
1338 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1339 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1340 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1341 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1342 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1343 EndContainer(),
1344 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1345 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_HEADER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetStringTip(STR_GRAPH_CARGO_PAYMENT_RATES_TITLE), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1347 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1349 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1350 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1351 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1354 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
1356 EndContainer(),
1357 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1358 EndContainer(),
1359 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1360 EndContainer(),
1362 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1363 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1364 EndContainer(),
1365 EndContainer(),
1366};
1367
1368static WindowDesc _cargo_payment_rates_desc(
1369 WDP_AUTO, "graph_cargo_payment_rates", 0, 0,
1371 {},
1372 _nested_cargo_payment_rates_widgets
1373);
1374
1375
1376void ShowCargoPaymentRates()
1377{
1378 AllocateWindowDescFront<PaymentRatesGraphWindow>(_cargo_payment_rates_desc, 0);
1379}
1380
1381/*****************************/
1382/* PERFORMANCE RATING DETAIL */
1383/*****************************/
1384
1386 static CompanyID company;
1387 int timeout = 0;
1388 uint score_info_left = 0;
1389 uint score_info_right = 0;
1390 uint bar_left = 0;
1391 uint bar_right = 0;
1392 uint bar_width = 0;
1393 uint bar_height = 0;
1394 uint score_detail_left = 0;
1395 uint score_detail_right = 0;
1396
1398 {
1399 this->UpdateCompanyStats();
1400
1401 this->InitNested(window_number);
1402 this->OnInvalidateData(CompanyID::Invalid().base());
1403 }
1404
1405 void UpdateCompanyStats()
1406 {
1407 /* Update all company stats with the current data
1408 * (this is because _score_info is not saved to a savegame) */
1409 for (Company *c : Company::Iterate()) {
1411 }
1412
1413 this->timeout = Ticks::DAY_TICKS * 5;
1414 }
1415
1416 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1417 {
1418 switch (widget) {
1421 size.height = this->bar_height + WidgetDimensions::scaled.matrix.Vertical();
1422
1423 uint score_info_width = 0;
1424 for (uint i = SCORE_BEGIN; i < SCORE_END; i++) {
1425 score_info_width = std::max(score_info_width, GetStringBoundingBox(STR_PERFORMANCE_DETAIL_VEHICLES + i).width);
1426 }
1427 score_info_width += GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(1000))).width + WidgetDimensions::scaled.hsep_wide;
1428
1429 this->bar_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_PERCENT, GetParamMaxValue(100))).width + WidgetDimensions::scaled.hsep_indent * 2; // Wide bars!
1430
1431 /* At this number we are roughly at the max; it can become wider,
1432 * but then you need at 1000 times more money. At that time you're
1433 * not that interested anymore in the last few digits anyway.
1434 * The 500 is because 999 999 500 to 999 999 999 are rounded to
1435 * 1 000 M, and not 999 999 k. Use negative numbers to account for
1436 * the negative income/amount of money etc. as well. */
1437 int max = -(999999999 - 500);
1438
1439 /* Scale max for the display currency. Prior to rendering the value
1440 * is converted into the display currency, which may cause it to
1441 * raise significantly. We need to compensate for that since {{CURRCOMPACT}}
1442 * is used, which can produce quite short renderings of very large
1443 * values. Otherwise the calculated width could be too narrow.
1444 * Note that it doesn't work if there was a currency with an exchange
1445 * rate greater than max.
1446 * When the currency rate is more than 1000, the 999 999 k becomes at
1447 * least 999 999 M which roughly is equally long. Furthermore if the
1448 * exchange rate is that high, 999 999 k is usually not enough anymore
1449 * to show the different currency numbers. */
1450 if (GetCurrency().rate < 1000) max /= GetCurrency().rate;
1451 uint score_detail_width = GetStringBoundingBox(GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, max, max)).width;
1452
1453 size.width = WidgetDimensions::scaled.frametext.Horizontal() + score_info_width + WidgetDimensions::scaled.hsep_wide + this->bar_width + WidgetDimensions::scaled.hsep_wide + score_detail_width;
1455 uint right = size.width - WidgetDimensions::scaled.frametext.right;
1456
1457 bool rtl = _current_text_dir == TD_RTL;
1458 this->score_info_left = rtl ? right - score_info_width : left;
1459 this->score_info_right = rtl ? right : left + score_info_width;
1460
1461 this->score_detail_left = rtl ? left : right - score_detail_width;
1462 this->score_detail_right = rtl ? left + score_detail_width : right;
1463
1464 this->bar_left = left + (rtl ? score_detail_width : score_info_width) + WidgetDimensions::scaled.hsep_wide;
1465 this->bar_right = this->bar_left + this->bar_width - 1;
1466 break;
1467 }
1468 }
1469
1470 void DrawWidget(const Rect &r, WidgetID widget) const override
1471 {
1472 /* No need to draw when there's nothing to draw */
1473 if (this->company == CompanyID::Invalid()) return;
1474
1476 if (this->IsWidgetDisabled(widget)) return;
1477 CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1478 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
1479 DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height));
1480 return;
1481 }
1482
1483 if (!IsInsideMM(widget, WID_PRD_SCORE_FIRST, WID_PRD_SCORE_LAST + 1)) return;
1484
1485 ScoreID score_type = (ScoreID)(widget - WID_PRD_SCORE_FIRST);
1486
1487 /* The colours used to show how the progress is going */
1488 int colour_done = GetColourGradient(COLOUR_GREEN, SHADE_NORMAL);
1489 int colour_notdone = GetColourGradient(COLOUR_RED, SHADE_NORMAL);
1490
1491 /* Draw all the score parts */
1492 int64_t val = _score_part[company][score_type];
1493 int64_t needed = _score_info[score_type].needed;
1494 int score = _score_info[score_type].score;
1495
1496 /* SCORE_TOTAL has its own rules ;) */
1497 if (score_type == SCORE_TOTAL) {
1498 for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) score += _score_info[i].score;
1499 needed = SCORE_MAX;
1500 }
1501
1502 uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height);
1503 uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL));
1504
1505 DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type);
1506
1507 /* Draw the score */
1508 DrawString(this->score_info_left, this->score_info_right, text_top, GetString(STR_JUST_COMMA, score), TC_BLACK, SA_RIGHT);
1509
1510 /* Calculate the %-bar */
1511 uint x = Clamp<int64_t>(val, 0, needed) * this->bar_width / needed;
1512 bool rtl = _current_text_dir == TD_RTL;
1513 if (rtl) {
1514 x = this->bar_right - x;
1515 } else {
1516 x = this->bar_left + x;
1517 }
1518
1519 /* Draw the bar */
1520 if (x != this->bar_left) GfxFillRect(this->bar_left, bar_top, x, bar_top + this->bar_height - 1, rtl ? colour_notdone : colour_done);
1521 if (x != this->bar_right) GfxFillRect(x, bar_top, this->bar_right, bar_top + this->bar_height - 1, rtl ? colour_done : colour_notdone);
1522
1523 /* Draw it */
1524 DrawString(this->bar_left, this->bar_right, text_top, GetString(STR_PERFORMANCE_DETAIL_PERCENT, Clamp<int64_t>(val, 0, needed) * 100 / needed), TC_FROMSTRING, SA_HOR_CENTER);
1525
1526 /* SCORE_LOAN is inversed */
1527 if (score_type == SCORE_LOAN) val = needed - val;
1528
1529 /* Draw the amount we have against what is needed
1530 * For some of them it is in currency format */
1531 switch (score_type) {
1532 case SCORE_MIN_PROFIT:
1533 case SCORE_MIN_INCOME:
1534 case SCORE_MAX_INCOME:
1535 case SCORE_MONEY:
1536 case SCORE_LOAN:
1537 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_CURRENCY, val, needed));
1538 break;
1539 default:
1540 DrawString(this->score_detail_left, this->score_detail_right, text_top, GetString(STR_PERFORMANCE_DETAIL_AMOUNT_INT, val, needed));
1541 break;
1542 }
1543 }
1544
1545 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1546 {
1547 /* Check which button is clicked */
1549 /* Is it no on disable? */
1550 if (!this->IsWidgetDisabled(widget)) {
1551 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1552 this->company = (CompanyID)(widget - WID_PRD_COMPANY_FIRST);
1553 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1554 this->SetDirty();
1555 }
1556 }
1557 }
1558
1559 void OnGameTick() override
1560 {
1561 /* Update the company score every 5 days */
1562 if (--this->timeout == 0) {
1563 this->UpdateCompanyStats();
1564 this->SetDirty();
1565 }
1566 }
1567
1573 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1574 {
1575 if (!gui_scope) return;
1576 /* Disable the companies who are not active */
1577 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
1579 }
1580
1581 /* Check if the currently selected company is still active. */
1582 if (this->company != CompanyID::Invalid() && !Company::IsValidID(this->company)) {
1583 /* Raise the widget for the previous selection. */
1584 this->RaiseWidget(WID_PRD_COMPANY_FIRST + this->company);
1585 this->company = CompanyID::Invalid();
1586 }
1587
1588 if (this->company == CompanyID::Invalid()) {
1589 for (const Company *c : Company::Iterate()) {
1590 this->company = c->index;
1591 break;
1592 }
1593 }
1594
1595 /* Make sure the widget is lowered */
1596 if (this->company != CompanyID::Invalid()) {
1597 this->LowerWidget(WID_PRD_COMPANY_FIRST + this->company);
1598 }
1599 }
1600};
1601
1602CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
1603
1604/*******************************/
1605/* INDUSTRY PRODUCTION HISTORY */
1606/*******************************/
1607
1609 static inline constexpr StringID RANGE_LABELS[] = {
1610 STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
1611 STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED
1612 };
1613
1614 static inline CargoTypes excluded_cargo_types{};
1615
1617 BaseCargoGraphWindow(desc, STR_JUST_COMMA)
1618 {
1619 this->num_on_x_axis = GRAPH_NUM_MONTHS;
1620 this->num_vert_lines = GRAPH_NUM_MONTHS;
1621 this->month_increment = 1;
1622 this->x_values_increment = ECONOMY_MONTH_MINUTES;
1623 this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
1624 this->ranges = RANGE_LABELS;
1625
1626 this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
1627 }
1628
1629 CargoTypes GetCargoTypes(WindowNumber window_number) const override
1630 {
1631 CargoTypes cargo_types{};
1633 for (const auto &p : i->produced) {
1634 if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo);
1635 }
1636 return cargo_types;
1637 }
1638
1639 CargoTypes &GetExcludedCargoTypes() const override
1640 {
1641 return IndustryProductionGraphWindow::excluded_cargo_types;
1642 }
1643
1644 std::string GetWidgetString(WidgetID widget, StringID stringid) const override
1645 {
1646 if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_PRODUCTION_CAPTION, this->window_number);
1647
1648 return this->Window::GetWidgetString(widget, stringid);
1649 }
1650
1651 void UpdateStatistics(bool initialize) override
1652 {
1653 int mo = TimerGameEconomy::month - this->num_vert_lines;
1654 auto yr = TimerGameEconomy::year;
1655 while (mo < 0) {
1656 yr--;
1657 mo += 12;
1658 }
1659
1660 if (!initialize && this->excluded_data == this->GetExcludedCargoTypes() && this->num_on_x_axis == this->num_vert_lines && this->year == yr && this->month == mo) {
1661 /* There's no reason to get new stats */
1662 return;
1663 }
1664
1665 this->excluded_data = this->GetExcludedCargoTypes();
1666 this->year = yr;
1667 this->month = mo;
1668
1669 const Industry *i = Industry::Get(this->window_number);
1670
1671 this->data.clear();
1672 for (const auto &p : i->produced) {
1673 if (!IsValidCargoType(p.cargo)) continue;
1674 const CargoSpec *cs = CargoSpec::Get(p.cargo);
1675
1676 this->data.reserve(this->data.size() + 2);
1677
1678 DataSet &produced = this->data.emplace_back();
1679 produced.colour = cs->legend_colour;
1680 produced.exclude_bit = cs->Index();
1681 produced.range_bit = 0;
1682 auto produced_filler = Filler{produced, &Industry::ProducedHistory::production};
1683
1684 DataSet &transported = this->data.emplace_back();
1685 transported.colour = cs->legend_colour;
1686 transported.exclude_bit = cs->Index();
1687 transported.range_bit = 1;
1688 transported.dash = 2;
1689 auto transported_filler = Filler{transported, &Industry::ProducedHistory::transported};
1690
1691 FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, produced_filler, transported_filler);
1692 }
1693
1694 this->SetDirty();
1695 }
1696};
1697
1698static constexpr NWidgetPart _nested_industry_production_widgets[] = {
1700 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1701 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_GRAPH_CAPTION),
1702 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1703 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
1704 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1705 EndContainer(),
1706 NWidget(WWT_PANEL, COLOUR_BROWN, WID_GRAPH_BACKGROUND), SetMinimalSize(568, 128),
1708 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
1710 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1711 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
1713 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
1714 NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
1717 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
1719 EndContainer(),
1720 NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
1721 EndContainer(),
1722 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
1723 EndContainer(),
1725 NWidget(WWT_TEXT, INVALID_COLOUR, WID_GRAPH_FOOTER), SetFill(1, 0), SetResize(1, 0), SetPadding(2, 0, 2, 0), SetTextStyle(TC_BLACK, FS_SMALL), SetAlignment(SA_CENTER),
1726 NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_GRAPH_RESIZE), SetResizeWidgetTypeTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1727 EndContainer(),
1728 EndContainer(),
1729};
1730
1731static WindowDesc _industry_production_desc(
1732 WDP_AUTO, "graph_industry_production", 0, 0,
1734 {},
1735 _nested_industry_production_widgets
1736);
1737
1738void ShowIndustryProductionGraph(WindowNumber window_number)
1739{
1740 AllocateWindowDescFront<IndustryProductionGraphWindow>(_industry_production_desc, window_number);
1741}
1742
1747static std::unique_ptr<NWidgetBase> MakePerformanceDetailPanels()
1748{
1749 auto realtime = TimerGameEconomy::UsingWallclockUnits();
1750 const StringID performance_tips[] = {
1751 realtime ? STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_VEHICLES_TOOLTIP_YEARS,
1752 STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP,
1753 realtime ? STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_PERIODS : STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP_YEARS,
1754 STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP,
1755 STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP,
1756 STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP,
1757 STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP,
1758 STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP,
1759 STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP,
1760 STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP,
1761 };
1762
1763 static_assert(lengthof(performance_tips) == SCORE_END - SCORE_BEGIN);
1764
1765 auto vert = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
1766 for (WidgetID widnum = WID_PRD_SCORE_FIRST; widnum <= WID_PRD_SCORE_LAST; widnum++) {
1767 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_BROWN, widnum);
1768 panel->SetFill(1, 1);
1769 panel->SetToolTip(performance_tips[widnum - WID_PRD_SCORE_FIRST]);
1770 vert->Add(std::move(panel));
1771 }
1772 return vert;
1773}
1774
1776std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsGraphGUI()
1777{
1778 return MakeCompanyButtonRows(WID_PRD_COMPANY_FIRST, WID_PRD_COMPANY_LAST, COLOUR_BROWN, 8, STR_PERFORMANCE_DETAIL_SELECT_COMPANY_TOOLTIP);
1779}
1780
1781static constexpr NWidgetPart _nested_performance_rating_detail_widgets[] = {
1783 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
1784 NWidget(WWT_CAPTION, COLOUR_BROWN), SetStringTip(STR_PERFORMANCE_DETAIL, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1785 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
1786 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
1787 EndContainer(),
1788 NWidget(WWT_PANEL, COLOUR_BROWN),
1790 EndContainer(),
1792};
1793
1794static WindowDesc _performance_rating_detail_desc(
1795 WDP_AUTO, "league_details", 0, 0,
1797 {},
1798 _nested_performance_rating_detail_widgets
1799);
1800
1801void ShowPerformanceRatingDetail()
1802{
1803 AllocateWindowDescFront<PerformanceRatingDetailWindow>(_performance_rating_detail_desc, 0);
1804}
1805
1806void InitializeGraphGui()
1807{
1808 _legend_excluded_companies = CompanyMask{};
1809 PaymentRatesGraphWindow::excluded_cargo_types = {};
1810 IndustryProductionGraphWindow::excluded_cargo_types = {};
1811}
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
constexpr uint CountBits(T value)
Counts the number of set bits in a variable.
constexpr T ToggleBit(T &x, const uint8_t y)
Toggles a bit in a variable.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:106
static const CargoType NUM_CARGO
Maximum number of cargo types in a game.
Definition cargo_type.h:75
std::span< const CargoSpec * > _sorted_standard_cargo_specs
Standard cargo specifications sorted alphabetically by name.
CargoTypes _standard_cargo_mask
Bitmask of real cargo types available.
Definition cargotype.cpp:36
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
Types/functions related to cargoes.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Tstorage base() const noexcept
Retrieve the raw value behind this bit set.
constexpr Timpl & Flip(Tvalue_type value)
Flip the value-th bit.
constexpr Timpl & Set()
Set all bits.
void UpdateStatistics(bool initialize) override
Update the statistics.
An interval timer will fire every interval, and will continue to fire until it is deleted.
Definition timer.h:76
Scrollbar data structure.
size_type GetCapacity() const
Gets the number of visible elements of the scrollbar.
void SetCount(size_t num)
Sets the number of elements in the list.
size_type GetScrolledRowFromWidget(int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition widget.cpp:2436
void SetCapacityFromWidget(Window *w, WidgetID widget, int padding=0)
Set capacity of visible elements from the size and resize properties of a widget.
Definition widget.cpp:2510
size_type GetCount() const
Gets the number of elements in the list.
size_type GetPosition() const
Gets the position of the first visible element in the list.
static constexpr TimerGameTick::Ticks DAY_TICKS
1 day is 74 ticks; TimerGameCalendar::date_fract used to be uint16_t and incremented by 885.
static Year year
Current year, starting at 0.
static Month month
Current month (0..11).
static bool UsingWallclockUnits(bool newgame=false)
Check if we are using wallclock units.
uint8_t Month
Type for the month, note: 0 based, i.e.
RectPadding framerect
Standard padding inside many panels.
Definition window_gui.h:40
RectPadding frametext
Padding inside frame with text.
Definition window_gui.h:41
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
int hsep_wide
Wide horizontal spacing.
Definition window_gui.h:62
RectPadding fullbevel
Always-scaled bevel thickness.
Definition window_gui.h:39
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
RectPadding matrix
Padding of WWT_MATRIX items.
Definition window_gui.h:42
int hsep_normal
Normal horizontal spacing.
Definition window_gui.h:61
RectPadding bevel
Bevel thickness, affected by "scaled bevels" game option.
Definition window_gui.h:38
int hsep_indent
Width of indentation for tree layouts.
Definition window_gui.h:63
Definition of stuff that is very close to a company, like the company struct itself.
void DrawCompanyIcon(CompanyID c, int x, int y)
Draw the icon of a company.
GUI Functions related to companies.
Functions to handle different currencies.
const CurrencySpec & GetCurrency()
Get the currently selected currency.
Definition currency.h:118
int UpdateCompanyRatingAndValue(Company *c, bool update)
if update is set to true, the economy is updated with this score (also the house is updated,...
Definition economy.cpp:202
const ScoreInfo _score_info[]
Score info, values used for computing the detailed performance rating.
Definition economy.cpp:91
Functions related to the economy.
ScoreID
Score categories in the detailed performance rating.
@ SCORE_END
How many scores are there..
@ SCORE_TOTAL
This must always be the last entry.
static constexpr int SCORE_MAX
The max score that can be in the performance history.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:77
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
int CentreBounds(int min, int max, int size)
Determine where to position a centred object.
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:958
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:887
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:658
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
bool DrawStringMultiLineWithClipping(int left, int right, int top, int bottom, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw a multiline string, possibly over multiple lines, if the region is within the current display cl...
Definition gfx.cpp:860
Functions related to the gfx engine.
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:252
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:251
@ SA_LEFT
Left align the text.
Definition gfx_type.h:383
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:385
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:384
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:395
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:393
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:302
constexpr double INT64_MAX_IN_DOUBLE
The biggest double that when cast to int64_t still fits in a int64_t.
Definition graph_gui.cpp:44
static std::unique_ptr< NWidgetBase > MakeNWidgetCompanyLines()
Construct a vertical list of buttons, one for each company.
std::unique_ptr< NWidgetBase > MakeCompanyButtonRowsGraphGUI()
Make a number of rows with buttons for each company for the performance rating detail window.
static std::unique_ptr< NWidgetBase > MakePerformanceDetailPanels()
Make a vertical list of panels for outputting score details.
Graph GUI functions.
Types related to the graph widgets.
@ WID_GRAPH_FOOTER
Footer.
@ WID_GRAPH_RESIZE
Resize button.
@ WID_GRAPH_BACKGROUND
Background of the window.
@ WID_GRAPH_GRAPH
Graph itself.
@ WID_PHG_DETAILED_PERFORMANCE
Detailed performance.
@ WID_GRAPH_HEADER
Header.
@ WID_GRAPH_MATRIX_SCROLLBAR
Cargo list scrollbar.
@ WID_GRAPH_DISABLE_CARGOES
Disable cargoes button.
@ WID_GRAPH_CAPTION
Caption.
@ WID_GRAPH_KEY_BUTTON
Key button.
@ WID_GRAPH_MATRIX
Cargo list.
@ WID_GRAPH_ENABLE_CARGOES
Enable cargoes button.
@ WID_GRAPH_RANGE_MATRIX
Range list.
@ WID_GL_FIRST_COMPANY
First company in the legend.
@ WID_GL_LAST_COMPANY
Last company in the legend.
@ WID_GL_BACKGROUND
Background of the window.
@ WID_PRD_COMPANY_FIRST
First company.
@ WID_PRD_SCORE_FIRST
First entry in the score list.
@ WID_PRD_SCORE_LAST
Last entry in the score list.
@ WID_PRD_COMPANY_LAST
Last company.
constexpr NWidgetPart SetMatrixDataTip(uint32_t cols, uint32_t rows, StringID tip={})
Widget part function for setting the data and tooltip of WWT_MATRIX widgets.
constexpr NWidgetPart NWidgetFunction(NWidgetFunctionType *func_ptr)
Obtain a nested widget (sub)tree from an external source.
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
constexpr NWidgetPart SetScrollbar(WidgetID index)
Attach a scrollbar to a widget.
constexpr NWidgetPart SetPadding(uint8_t top, uint8_t right, uint8_t bottom, uint8_t left)
Widget part function for setting additional space around a widget.
constexpr NWidgetPart SetStringTip(StringID string, StringID tip={})
Widget part function for setting the string and tooltip.
constexpr NWidgetPart SetTextStyle(TextColour colour, FontSize size=FS_NORMAL)
Widget part function for setting the text style.
constexpr NWidgetPart SetMinimalSize(int16_t x, int16_t y)
Widget part function for setting the minimal size.
constexpr NWidgetPart SetResizeWidgetTypeTip(ResizeWidgetValues widget_type, StringID tip)
Widget part function for setting the resize widget type and tooltip.
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=-1)
Widget part function for starting a new 'real' widget.
constexpr NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart SetAlignment(StringAlignment align)
Widget part function for setting the alignment of text/images.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:955
Functions for storing historical data.
Base of all industries.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
constexpr T abs(const T a)
Returns the absolute value of (scalar) variable.
Definition math_func.hpp:23
uint8_t GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:388
static const uint8_t PC_WHITE
White palette colour.
constexpr uint8_t GREY_SCALE(uint8_t level)
Return the colour for a particular greyscale level.
static const uint8_t PC_BLACK
Black palette colour.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
This file contains all sprite-related enums and defines.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
uint64_t GetParamMaxValue(uint64_t max_value, uint min_count, FontSize size)
Get some number that is suitable for string size computations.
Definition strings.cpp:237
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:425
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:57
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_RTL
Text is written right-to-left by default.
uint legend_width
Width of legend 'blob'.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void OnInit() override
Notification that the nested widget tree gets initialized.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
CargoTypes cargo_types
Cargo types that can be selected.
void OnResize() override
Called after the window got resized.
Scrollbar * vscroll
Cargo list scrollbar.
uint line_height
Pixel height of each cargo type row.
DataSet & dataset
Dataset to fill.
const Tprojection & proj
Projection to apply.
uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
Get width for Y labels.
void OnGameTick() override
Called once per (game) tick.
static const int MIN_GRID_PIXEL_SIZE
Minimum distance between graph lines.
static const int GRAPH_NUM_MONTHS
Number of months displayed in the graph.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
static const int ECONOMY_QUARTER_MINUTES
Minutes per economic quarter.
static const int GRAPH_PAYMENT_RATE_STEPS
Number of steps on Payment rate graph.
void OnMouseOver(Point pt, WidgetID widget) override
The mouse is currently moving over the window or has just moved outside of the window.
std::span< const OverflowSafeInt64 > GetDataSetRange(const DataSet &dataset) const
Get appropriate part of dataset values for the current number of horizontal points.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
static const int ECONOMY_MONTH_MINUTES
Minutes per economic month.
uint64_t excluded_data
bitmask of the datasets that shouldn't be displayed.
bool draw_dates
Should we draw months and years on the time axis?
static const int MIN_GRAPH_NUM_LINES_Y
Minimal number of horizontal lines to draw.
uint64_t excluded_range
bitmask of ranges that should not be displayed.
uint8_t highlight_range
Data range that should be highlighted, or UINT8_MAX for none.
bool highlight_state
Current state of highlight, toggled every TIMER_BLINK_INTERVAL period.
uint8_t month_increment
month increment between vertical lines. must be divisor of 12.
static const int PAYMENT_GRAPH_X_STEP_DAYS
X-axis step label for cargo payment rates "Days in transit".
ValuesInterval GetValuesInterval(int num_hori_lines) const
Get the interval that contains the graph's data.
static const TextColour GRAPH_AXIS_LABEL_COLOUR
colour of the graph axis label.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void DrawGraph(Rect r) const
Actually draw the graph.
static const int PAYMENT_GRAPH_X_STEP_SECONDS
X-axis step label for cargo payment rates "Seconds in transit".
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
uint8_t highlight_data
Data set that should be highlighted, or UINT8_MAX for none.
Specification of a cargo type.
Definition cargotype.h:74
static CargoSpec * Get(size_t index)
Retrieve cargo details for the given cargo type.
Definition cargotype.h:137
CargoType Index() const
Determines index of this cargospec.
Definition cargotype.h:108
StringID name
Name of this type of cargo.
Definition cargotype.h:91
GUISettings gui
settings related to the GUI
Colours colour
Company colour.
std::array< CompanyEconomyEntry, MAX_HISTORY_QUARTERS > old_economy
Economic data of the company of the last MAX_HISTORY_QUARTERS quarters.
uint8_t num_valid_stat_ent
Number of valid statistical entries in old_economy.
uint16_t rate
The conversion rate compared to the base currency.
Definition currency.h:77
Dimensions (a width and height) of a rectangle in 2D.
uint8_t graph_line_thickness
the thickness of the lines in the various graph guis
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
Definition graph_gui.cpp:81
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
Definition graph_gui.cpp:63
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
Get the raw string for a widget.
uint16_t transported
Total transported.
Definition industry.h:65
uint16_t production
Total produced.
Definition industry.h:64
Defines the internal data of a functional industry.
Definition industry.h:62
ValidHistoryMask valid_history
Mask of valid history records.
Definition industry.h:92
ProducedCargoes produced
produced cargo slots
Definition industry.h:93
Partial widget specification to allow NWidgets to be written nested.
void UpdatePaymentRates()
Update the payment rates according to the latest information.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
const IntervalTimer< TimerWindow > update_payment_interval
Update the payment rates on a regular interval.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
void OnGameTick() override
Called once per (game) tick.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Some data on this window has become invalid.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
Update size and resize step of a widget in the window.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
Coordinates of a point in 2D.
static Pool::IterateWrapper< Titem > Iterate(size_t from=0)
Returns an iterable ensemble of all valid Titem.
static Titem * Get(auto index)
Returns Titem with given index.
static bool IsValidID(auto index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
static Titem * GetIfValid(auto index)
Returns Titem with given index.
constexpr uint Horizontal() const
Get total horizontal padding of RectPadding.
constexpr uint Vertical() const
Get total vertical padding of RectPadding.
Specification of a rectangle with absolute coordinates of all edges.
Rect WithWidth(int width, bool end) const
Copy Rect and set its width.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect WithHeight(int height, bool end=false) const
Copy Rect and set its height.
Rect Indent(int indent, bool end) const
Copy Rect and indent it from its position.
Rect Translate(int x, int y) const
Copy and translate Rect by x,y pixels.
int needed
How much you need to get the perfect score.
int score
How much score it will give.
Iterable ensemble of each set bit in a value.
Templated helper to make a type-safe 'typedef' representing a single POD value.
Contains the interval of a graph's data.
OverflowSafeInt64 lowest
Lowest value of this interval. Must be zero or less.
OverflowSafeInt64 highest
Highest value of this interval. Must be zero or greater.
High level window description.
Definition window_gui.h:167
Number to differentiate different windows of the same class.
Data structure for an opened window.
Definition window_gui.h:273
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
Definition window.cpp:1778
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
Definition window.cpp:3205
void RaiseWidget(WidgetID widget_index)
Marks a widget as raised.
Definition window_gui.h:469
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:503
ResizeInfo resize
Resize information.
Definition window_gui.h:314
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
Definition window.cpp:1768
bool IsWidgetDisabled(WidgetID widget_index) const
Gets the enabled/disabled status of a widget.
Definition window_gui.h:410
int left
x position of left edge of the window
Definition window_gui.h:309
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
Definition window.cpp:211
void LowerWidget(WidgetID widget_index)
Marks a widget as lowered.
Definition window_gui.h:460
void InitNested(WindowNumber number=0)
Perform complete initialization of the Window with nested widgets, to allow use.
Definition window.cpp:1791
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
Definition window.cpp:312
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:381
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:450
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:302
Definition of Interval and OneShot timers.
Definition of the game-economy-timer.
Definition of the tick-based game-timer.
Definition of the Window system.
static constexpr std::chrono::milliseconds TIMER_BLINK_INTERVAL
Interval used by blinking interface elements.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:298
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:49
std::unique_ptr< NWidgetBase > MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
Make a number of rows with button-like graphics, for enabling/disabling each company.
Definition widget.cpp:3446
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:71
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:67
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:58
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:51
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:53
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:77
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:69
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:61
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:38
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:60
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:57
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:50
@ EqualSize
Containers should keep all their (resizing) children equally large.
@ RWV_HIDE_BEVEL
Bevel of resize box is hidden.
Definition widget_type.h:30
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3265
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting)
Definition window.cpp:3147
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ Lowered
If set the frame is lowered and the background colour brighter (ie. buttons when pressed)
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h:144
int WidgetID
Widget ID.
Definition window_type.h:20
@ WC_PERFORMANCE_HISTORY
Performance history graph; Window numbers:
@ WC_PERFORMANCE_DETAIL
Performance detail window; Window numbers:
@ WC_PAYMENT_RATES
Payment rates graph; Window numbers:
@ WC_GRAPH_LEGEND
Legend for graphs; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:47
@ WC_OPERATING_PROFIT
Operating profit graph; Window numbers:
@ WC_INDUSTRY_PRODUCTION
Industry production history graph; Window numbers:
@ WC_INDUSTRY_VIEW
Industry view; Window numbers:
@ WC_INCOME_GRAPH
Income graph; Window numbers:
@ WC_DELIVERED_CARGO
Delivered cargo graph; Window numbers:
@ WC_COMPANY_VALUE
Company value graph; Window numbers:
Functions related to zooming.
@ Normal
The normal zoom level.