OpenTTD Source 20260206-master-g4d4e37dbf1
linkgraph_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 "../window_gui.h"
12#include "../window_func.h"
13#include "../company_base.h"
14#include "../company_gui.h"
17#include "../viewport_func.h"
18#include "../zoom_func.h"
19#include "../smallmap_gui.h"
22#include "../strings_func.h"
23#include "linkgraph_gui.h"
24
25#include "table/strings.h"
26
27#include "../safeguards.h"
28
34{
35 PixelColour{0x0f}, PixelColour{0xd1}, PixelColour{0xd0}, PixelColour{0x57},
36 PixelColour{0x55}, PixelColour{0x53}, PixelColour{0xbf}, PixelColour{0xbd},
37 PixelColour{0xba}, PixelColour{0xb9}, PixelColour{0xb7}, PixelColour{0xb5}
38},
39{
40 PixelColour{0x0f}, PixelColour{0xd1}, PixelColour{0xd0}, PixelColour{0x57},
41 PixelColour{0x55}, PixelColour{0x53}, PixelColour{0x96}, PixelColour{0x95},
42 PixelColour{0x94}, PixelColour{0x93}, PixelColour{0x92}, PixelColour{0x91}
43},
44{
45 PixelColour{0x0f}, PixelColour{0x0b}, PixelColour{0x09}, PixelColour{0x07},
46 PixelColour{0x05}, PixelColour{0x03}, PixelColour{0xbf}, PixelColour{0xbd},
47 PixelColour{0xba}, PixelColour{0xb9}, PixelColour{0xb7}, PixelColour{0xb5}
48},
49{
50 PixelColour{0x0f}, PixelColour{0x0b}, PixelColour{0x0a}, PixelColour{0x09},
51 PixelColour{0x08}, PixelColour{0x07}, PixelColour{0x06}, PixelColour{0x05},
52 PixelColour{0x04}, PixelColour{0x03}, PixelColour{0x02}, PixelColour{0x01}
53}
54};
55
61{
62 const NWidgetBase *wi = this->window->GetWidget<NWidgetBase>(this->widget_id);
63 dpi->left = dpi->top = 0;
64 dpi->width = wi->current_x;
65 dpi->height = wi->current_y;
66}
67
72{
73 this->cached_links.clear();
74 this->cached_stations.clear();
75 if (this->company_mask.None()) return;
76
77 DrawPixelInfo dpi;
78 this->GetWidgetDpi(&dpi);
79
80 for (const Station *sta : Station::Iterate()) {
81 if (sta->rect.IsEmpty()) continue;
82
83 Point pta = this->GetStationMiddle(sta);
84
85 StationID from = sta->index;
86 StationLinkMap &seen_links = this->cached_links[from];
87
88 uint supply = 0;
89 for (CargoType cargo : SetCargoBitIterator(this->cargo_mask)) {
90 if (!CargoSpec::Get(cargo)->IsValid()) continue;
91 if (!LinkGraph::IsValidID(sta->goods[cargo].link_graph)) continue;
92 const LinkGraph &lg = *LinkGraph::Get(sta->goods[cargo].link_graph);
93
94 ConstNode &from_node = lg[sta->goods[cargo].node];
95 supply += lg.Monthly(from_node.supply);
96 for (const Edge &edge : from_node.edges) {
97 StationID to = lg[edge.dest_node].station;
98 assert(from != to);
99 if (!Station::IsValidID(to) || seen_links.find(to) != seen_links.end()) {
100 continue;
101 }
102 const Station *stb = Station::Get(to);
103 assert(sta != stb);
104
105 /* Show links between stations of selected companies or "neutral" ones like oilrigs. */
106 if (stb->owner != OWNER_NONE && sta->owner != OWNER_NONE && !this->company_mask.Test(stb->owner)) continue;
107 if (stb->rect.IsEmpty()) continue;
108
109 if (!this->IsLinkVisible(pta, this->GetStationMiddle(stb), &dpi)) continue;
110
111 this->AddLinks(sta, stb);
112 seen_links[to]; // make sure it is created and marked as seen
113 }
114 }
115 if (this->IsPointVisible(pta, &dpi)) {
116 this->cached_stations.emplace_back(from, supply);
117 }
118 }
119}
120
128inline bool LinkGraphOverlay::IsPointVisible(Point pt, const DrawPixelInfo *dpi, int padding) const
129{
130 return pt.x > dpi->left - padding && pt.y > dpi->top - padding &&
131 pt.x < dpi->left + dpi->width + padding &&
132 pt.y < dpi->top + dpi->height + padding;
133}
134
143inline bool LinkGraphOverlay::IsLinkVisible(Point pta, Point ptb, const DrawPixelInfo *dpi, int padding) const
144{
145 const int left = dpi->left - padding;
146 const int right = dpi->left + dpi->width + padding;
147 const int top = dpi->top - padding;
148 const int bottom = dpi->top + dpi->height + padding;
149
150 /*
151 * This method is an implementation of the Cohen-Sutherland line-clipping algorithm.
152 * See: https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
153 */
154
155 const uint8_t INSIDE = 0; // 0000
156 const uint8_t LEFT = 1; // 0001
157 const uint8_t RIGHT = 2; // 0010
158 const uint8_t BOTTOM = 4; // 0100
159 const uint8_t TOP = 8; // 1000
160
161 int x0 = pta.x;
162 int y0 = pta.y;
163 int x1 = ptb.x;
164 int y1 = ptb.y;
165
166 auto out_code = [&](int x, int y) -> uint8_t {
167 uint8_t out = INSIDE;
168 if (x < left) {
169 out |= LEFT;
170 } else if (x > right) {
171 out |= RIGHT;
172 }
173 if (y < top) {
174 out |= TOP;
175 } else if (y > bottom) {
176 out |= BOTTOM;
177 }
178 return out;
179 };
180
181 uint8_t c0 = out_code(x0, y0);
182 uint8_t c1 = out_code(x1, y1);
183
184 while (true) {
185 if (c0 == 0 || c1 == 0) return true;
186 if ((c0 & c1) != 0) return false;
187
188 if (c0 & TOP) { // point 0 is above the clip window
189 x0 = x0 + (int)(((int64_t) (x1 - x0)) * ((int64_t) (top - y0)) / ((int64_t) (y1 - y0)));
190 y0 = top;
191 } else if (c0 & BOTTOM) { // point 0 is below the clip window
192 x0 = x0 + (int)(((int64_t) (x1 - x0)) * ((int64_t) (bottom - y0)) / ((int64_t) (y1 - y0)));
193 y0 = bottom;
194 } else if (c0 & RIGHT) { // point 0 is to the right of clip window
195 y0 = y0 + (int)(((int64_t) (y1 - y0)) * ((int64_t) (right - x0)) / ((int64_t) (x1 - x0)));
196 x0 = right;
197 } else if (c0 & LEFT) { // point 0 is to the left of clip window
198 y0 = y0 + (int)(((int64_t) (y1 - y0)) * ((int64_t) (left - x0)) / ((int64_t) (x1 - x0)));
199 x0 = left;
200 }
201
202 c0 = out_code(x0, y0);
203 }
204
205 NOT_REACHED();
206}
207
213void LinkGraphOverlay::AddLinks(const Station *from, const Station *to)
214{
215 for (CargoType cargo : SetCargoBitIterator(this->cargo_mask)) {
216 if (!CargoSpec::Get(cargo)->IsValid()) continue;
217 const GoodsEntry &ge = from->goods[cargo];
219 ge.link_graph != to->goods[cargo].link_graph) {
220 continue;
221 }
222 const LinkGraph &lg = *LinkGraph::Get(ge.link_graph);
223 if (lg[ge.node].HasEdgeTo(to->goods[cargo].node)) {
224 ConstEdge &edge = lg[ge.node][to->goods[cargo].node];
225 this->AddStats(cargo, lg.Monthly(edge.capacity), lg.Monthly(edge.usage),
226 ge.HasData() ? ge.GetData().flows.GetFlowVia(to->index) : 0,
228 from->owner == OWNER_NONE || to->owner == OWNER_NONE,
229 this->cached_links[from->index][to->index]);
230 }
231 }
232}
233
246/* static */ void LinkGraphOverlay::AddStats(CargoType new_cargo, uint new_cap, uint new_usg, uint new_plan, uint32_t time, bool new_shared, LinkProperties &cargo)
247{
248 /* multiply the numbers by 32 in order to avoid comparing to 0 too often. */
249 if (cargo.capacity == 0 ||
250 cargo.Usage() * 32 / (cargo.capacity + 1) < std::max(new_usg, new_plan) * 32 / (new_cap + 1)) {
251 cargo.cargo = new_cargo;
252 cargo.capacity = new_cap;
253 cargo.usage = new_usg;
254 cargo.planned = new_plan;
255 cargo.time = time;
256 }
257 if (new_shared) cargo.shared = true;
258}
259
265{
266 if (this->dirty) {
267 this->RebuildCache();
268 this->dirty = false;
269 }
270 this->DrawLinks(dpi);
271 this->DrawStationDots(dpi);
272}
273
279{
280 int width = ScaleGUITrad(this->scale);
281 for (const auto &i : this->cached_links) {
282 if (!Station::IsValidID(i.first)) continue;
283 Point pta = this->GetStationMiddle(Station::Get(i.first));
284 for (const auto &j : i.second) {
285 if (!Station::IsValidID(j.first)) continue;
286 Point ptb = this->GetStationMiddle(Station::Get(j.first));
287 if (!this->IsLinkVisible(pta, ptb, dpi, width + 2)) continue;
288 this->DrawContent(pta, ptb, j.second);
289 }
290 }
291}
292
299void LinkGraphOverlay::DrawContent(Point pta, Point ptb, const LinkProperties &cargo) const
300{
301 uint usage_or_plan = std::min(cargo.capacity * 2 + 1, cargo.Usage());
302 PixelColour colour = LinkGraphOverlay::LINK_COLOURS[_settings_client.gui.linkgraph_colours][usage_or_plan * lengthof(LinkGraphOverlay::LINK_COLOURS[0]) / (cargo.capacity * 2 + 2)];
303 int width = ScaleGUITrad(this->scale);
304 int dash = cargo.shared ? width * 4 : 0;
305
306 /* Move line a bit 90° against its dominant direction to prevent it from
307 * being hidden below the grey line. */
308 int side = _settings_game.vehicle.road_side ? 1 : -1;
309 if (abs(pta.x - ptb.x) < abs(pta.y - ptb.y)) {
310 int offset_x = (pta.y > ptb.y ? 1 : -1) * side * width;
311 GfxDrawLine(pta.x + offset_x, pta.y, ptb.x + offset_x, ptb.y, colour, width, dash);
312 } else {
313 int offset_y = (pta.x < ptb.x ? 1 : -1) * side * width;
314 GfxDrawLine(pta.x, pta.y + offset_y, ptb.x, ptb.y + offset_y, colour, width, dash);
315 }
316
317 GfxDrawLine(pta.x, pta.y, ptb.x, ptb.y, GetColourGradient(COLOUR_GREY, SHADE_DARKEST), width);
318}
319
325{
326 int width = ScaleGUITrad(this->scale);
327 for (const auto &i : this->cached_stations) {
328 const Station *st = Station::GetIfValid(i.first);
329 if (st == nullptr) continue;
330 Point pt = this->GetStationMiddle(st);
331 if (!this->IsPointVisible(pt, dpi, 3 * width)) continue;
332
333 uint r = width * 2 + width * 2 * std::min(200U, i.second) / 200;
334
337 Company::Get(st->owner)->colour : COLOUR_GREY, SHADE_LIGHT),
338 GetColourGradient(COLOUR_GREY, SHADE_DARKEST));
339 }
340}
341
350/* static */ void LinkGraphOverlay::DrawVertex(int x, int y, int size, PixelColour colour, PixelColour border_colour)
351{
352 size--;
353 int w1 = size / 2;
354 int w2 = size / 2 + size % 2;
355 int borderwidth = ScaleGUITrad(1);
356
357 GfxFillRect(x - w1 - borderwidth, y - w1 - borderwidth, x + w2 + borderwidth, y + w2 + borderwidth, border_colour);
358 GfxFillRect(x - w1, y - w1, x + w2, y + w2, colour);
359}
360
361bool LinkGraphOverlay::ShowTooltip(Point pt, TooltipCloseCondition close_cond)
362{
363 for (auto i(this->cached_links.crbegin()); i != this->cached_links.crend(); ++i) {
364 if (!Station::IsValidID(i->first)) continue;
365 Point pta = this->GetStationMiddle(Station::Get(i->first));
366 for (auto j(i->second.crbegin()); j != i->second.crend(); ++j) {
367 if (!Station::IsValidID(j->first)) continue;
368 if (i->first == j->first) continue;
369
370 /* Check the distance from the cursor to the line defined by the two stations. */
371 Point ptb = this->GetStationMiddle(Station::Get(j->first));
372 float dist = std::abs((int64_t)(ptb.x - pta.x) * (int64_t)(pta.y - pt.y) - (int64_t)(pta.x - pt.x) * (int64_t)(ptb.y - pta.y)) /
373 std::sqrt((int64_t)(ptb.x - pta.x) * (int64_t)(ptb.x - pta.x) + (int64_t)(ptb.y - pta.y) * (int64_t)(ptb.y - pta.y));
374 const auto &link = j->second;
375 if (dist <= 4 && link.Usage() > 0 &&
376 pt.x + 2 >= std::min(pta.x, ptb.x) &&
377 pt.x - 2 <= std::max(pta.x, ptb.x) &&
378 pt.y + 2 >= std::min(pta.y, ptb.y) &&
379 pt.y - 2 <= std::max(pta.y, ptb.y)) {
380 static std::string tooltip_extension;
381 tooltip_extension.clear();
382 /* Fill buf with more information if this is a bidirectional link. */
383 uint32_t back_time = 0;
384 auto k = this->cached_links[j->first].find(i->first);
385 if (k != this->cached_links[j->first].end()) {
386 const auto &back = k->second;
387 back_time = back.time;
388 if (back.Usage() > 0) {
389 tooltip_extension = GetString(STR_LINKGRAPH_STATS_TOOLTIP_RETURN_EXTENSION,
390 back.cargo, back.Usage(), back.Usage() * 100 / (back.capacity + 1));
391 }
392 }
393 /* Add information about the travel time if known. */
394 const auto time = link.time ? back_time ? ((link.time + back_time) / 2) : link.time : back_time;
395 if (time > 0) {
396 auto params = MakeParameters(time);
397 AppendStringWithArgsInPlace(tooltip_extension, STR_LINKGRAPH_STATS_TOOLTIP_TIME_EXTENSION, params);
398 }
400 GetEncodedString(TimerGameEconomy::UsingWallclockUnits() ? STR_LINKGRAPH_STATS_TOOLTIP_MINUTE : STR_LINKGRAPH_STATS_TOOLTIP_MONTH,
401 link.cargo, link.Usage(), i->first, j->first, link.Usage() * 100 / (link.capacity + 1), tooltip_extension),
402 close_cond);
403 return true;
404 }
405 }
406 }
407 GuiShowTooltips(this->window, {}, close_cond);
408 return false;
409}
410
417{
418 if (this->window->viewport != nullptr) {
419 return GetViewportStationMiddle(*this->window->viewport, st);
420 } else {
421 /* assume this is a smallmap */
422 return GetSmallMapStationMiddle(this->window, st);
423 }
424}
425
431{
432 this->cargo_mask = cargo_mask;
433 this->RebuildCache();
434 this->window->GetWidget<NWidgetBase>(this->widget_id)->SetDirty(this->window);
435}
436
442{
443 this->company_mask = company_mask;
444 this->RebuildCache();
445 this->window->GetWidget<NWidgetBase>(this->widget_id)->SetDirty(this->window);
446}
447
449std::unique_ptr<NWidgetBase> MakeCompanyButtonRowsLinkGraphGUI()
450{
451 return MakeCompanyButtonRows(WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST, COLOUR_GREY, 3, STR_NULL);
452}
453
454std::unique_ptr<NWidgetBase> MakeSaturationLegendLinkGraphGUI()
455{
456 auto panel = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
457 for (uint i = 0; i < lengthof(LinkGraphOverlay::LINK_COLOURS[0]); ++i) {
458 auto wid = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_DARK_GREEN, i + WID_LGL_SATURATION_FIRST);
459 wid->SetMinimalSize(50, 0);
460 wid->SetMinimalTextLines(1, 0, FS_SMALL);
461 wid->SetFill(1, 1);
462 wid->SetResize(0, 0);
463 panel->Add(std::move(wid));
464 }
465 return panel;
466}
467
468std::unique_ptr<NWidgetBase> MakeCargoesLegendLinkGraphGUI()
469{
470 uint num_cargo = static_cast<uint>(_sorted_cargo_specs.size());
471 static const uint ENTRIES_PER_COL = 5;
472 auto panel = std::make_unique<NWidgetHorizontal>(NWidContainerFlag::EqualSize);
473 std::unique_ptr<NWidgetVertical> col = nullptr;
474
475 for (uint i = 0; i < num_cargo; ++i) {
476 if (i % ENTRIES_PER_COL == 0) {
477 if (col != nullptr) panel->Add(std::move(col));
478 col = std::make_unique<NWidgetVertical>(NWidContainerFlag::EqualSize);
479 }
480 auto wid = std::make_unique<NWidgetBackground>(WWT_PANEL, COLOUR_GREY, i + WID_LGL_CARGO_FIRST);
481 wid->SetMinimalSize(25, 0);
482 wid->SetMinimalTextLines(1, 0, FS_SMALL);
483 wid->SetFill(1, 1);
484 wid->SetResize(0, 0);
485 col->Add(std::move(wid));
486 }
487 /* Fill up last row */
488 for (uint i = num_cargo; i < Ceil(num_cargo, ENTRIES_PER_COL); ++i) {
489 auto spc = std::make_unique<NWidgetSpacer>(25, 0);
490 spc->SetMinimalTextLines(1, 0, FS_SMALL);
491 spc->SetFill(1, 1);
492 spc->SetResize(0, 0);
493 col->Add(std::move(spc));
494 }
495 /* If there are no cargo specs defined, then col won't have been created so don't add it. */
496 if (col != nullptr) panel->Add(std::move(col));
497 return panel;
498}
499
500
501static constexpr std::initializer_list<NWidgetPart> _nested_linkgraph_legend_widgets = {
503 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
504 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_LGL_CAPTION), SetStringTip(STR_LINKGRAPH_LEGEND_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
505 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
506 NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
507 EndContainer(),
508 NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
510 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_LGL_SATURATION),
511 NWidgetFunction(MakeSaturationLegendLinkGraphGUI),
512 EndContainer(),
513 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_LGL_COMPANIES),
516 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_COMPANIES_ALL), SetStringTip(STR_LINKGRAPH_LEGEND_ALL),
517 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_COMPANIES_NONE), SetStringTip(STR_LINKGRAPH_LEGEND_NONE),
518 EndContainer(),
519 EndContainer(),
520 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_LGL_CARGOES),
522 NWidgetFunction(MakeCargoesLegendLinkGraphGUI),
523 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_CARGOES_ALL), SetStringTip(STR_LINKGRAPH_LEGEND_ALL),
524 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_LGL_CARGOES_NONE), SetStringTip(STR_LINKGRAPH_LEGEND_NONE),
525 EndContainer(),
526 EndContainer(),
527 EndContainer(),
529};
530
531static_assert(WID_LGL_SATURATION_LAST - WID_LGL_SATURATION_FIRST ==
533
534static WindowDesc _linkgraph_legend_desc(
535 WDP_AUTO, "toolbar_linkgraph", 0, 0,
537 {},
538 _nested_linkgraph_legend_widgets
539);
540
545{
546 AllocateWindowDescFront<LinkGraphLegendWindow>(_linkgraph_legend_desc, 0);
547}
548
549LinkGraphLegendWindow::LinkGraphLegendWindow(WindowDesc &desc, int window_number) : Window(desc), num_cargo(_sorted_cargo_specs.size())
550{
551 this->InitNested(window_number);
552 this->InvalidateData(0);
553 this->SetOverlay(GetMainWindow()->viewport->overlay);
554}
555
560void LinkGraphLegendWindow::SetOverlay(std::shared_ptr<LinkGraphOverlay> overlay)
561{
562 this->overlay = std::move(overlay);
563 CompanyMask companies = this->overlay->GetCompanyMask();
564 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
565 if (!this->IsWidgetDisabled(WID_LGL_COMPANY_FIRST + c)) {
566 this->SetWidgetLoweredState(WID_LGL_COMPANY_FIRST + c, companies.Test(c));
567 }
568 }
569 CargoTypes cargoes = this->overlay->GetCargoMask();
570 for (uint c = 0; c < this->num_cargo; c++) {
571 this->SetWidgetLoweredState(WID_LGL_CARGO_FIRST + c, HasBit(cargoes, _sorted_cargo_specs[c]->Index()));
572 }
573}
574
575void LinkGraphLegendWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize)
576{
577 if (IsInsideMM(widget, WID_LGL_SATURATION_FIRST, WID_LGL_SATURATION_LAST + 1)) {
578 StringID str = STR_NULL;
579 if (widget == WID_LGL_SATURATION_FIRST) {
580 str = STR_LINKGRAPH_LEGEND_UNUSED;
581 } else if (widget == WID_LGL_SATURATION_LAST) {
582 str = STR_LINKGRAPH_LEGEND_OVERLOADED;
583 } else if (widget == (WID_LGL_SATURATION_LAST + WID_LGL_SATURATION_FIRST) / 2) {
584 str = STR_LINKGRAPH_LEGEND_SATURATED;
585 }
586 if (str != STR_NULL) {
588 dim.width += padding.width;
589 dim.height += padding.height;
590 size = maxdim(size, dim);
591 }
592 }
593 if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
594 const CargoSpec *cargo = _sorted_cargo_specs[widget - WID_LGL_CARGO_FIRST];
596 dim.width += padding.width;
597 dim.height += padding.height;
598 size = maxdim(size, dim);
599 }
600}
601
603{
605 if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
606 if (this->IsWidgetDisabled(widget)) return;
607 CompanyID cid = (CompanyID)(widget - WID_LGL_COMPANY_FIRST);
608 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
609 DrawCompanyIcon(cid, CentreBounds(br.left, br.right, sprite_size.width), CentreBounds(br.top, br.bottom, sprite_size.height));
610 }
611 if (IsInsideMM(widget, WID_LGL_SATURATION_FIRST, WID_LGL_SATURATION_LAST + 1)) {
612 PixelColour colour = LinkGraphOverlay::LINK_COLOURS[_settings_client.gui.linkgraph_colours][widget - WID_LGL_SATURATION_FIRST];
613 GfxFillRect(br, colour);
614 StringID str = STR_NULL;
615 if (widget == WID_LGL_SATURATION_FIRST) {
616 str = STR_LINKGRAPH_LEGEND_UNUSED;
617 } else if (widget == WID_LGL_SATURATION_LAST) {
618 str = STR_LINKGRAPH_LEGEND_OVERLOADED;
619 } else if (widget == (WID_LGL_SATURATION_LAST + WID_LGL_SATURATION_FIRST) / 2) {
620 str = STR_LINKGRAPH_LEGEND_SATURATED;
621 }
622 if (str != STR_NULL) {
623 DrawString(br.left, br.right, CentreBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), str, GetContrastColour(colour) | TC_FORCED, SA_HOR_CENTER, false, FS_SMALL);
624 }
625 }
626 if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
627 const CargoSpec *cargo = _sorted_cargo_specs[widget - WID_LGL_CARGO_FIRST];
628 GfxFillRect(br, cargo->legend_colour);
629 DrawString(br.left, br.right, CentreBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), cargo->abbrev, GetContrastColour(cargo->legend_colour, 73), SA_HOR_CENTER, false, FS_SMALL);
630 }
631}
632
633bool LinkGraphLegendWindow::OnTooltip([[maybe_unused]] Point, WidgetID widget, TooltipCloseCondition close_cond)
634{
635 if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
636 if (this->IsWidgetDisabled(widget)) {
637 GuiShowTooltips(this, GetEncodedString(STR_LINKGRAPH_LEGEND_SELECT_COMPANIES), close_cond);
638 } else {
639 GuiShowTooltips(this,
640 GetEncodedString(STR_LINKGRAPH_LEGEND_COMPANY_TOOLTIP,
641 STR_LINKGRAPH_LEGEND_SELECT_COMPANIES,
642 widget - WID_LGL_COMPANY_FIRST),
643 close_cond);
644 }
645 return true;
646 }
647 if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
648 const CargoSpec *cargo = _sorted_cargo_specs[widget - WID_LGL_CARGO_FIRST];
649 GuiShowTooltips(this, GetEncodedString(cargo->name), close_cond);
650 return true;
651 }
652 return false;
653}
654
659{
660 CompanyMask mask;
661 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
662 if (this->IsWidgetDisabled(WID_LGL_COMPANY_FIRST + c)) continue;
663 if (!this->IsWidgetLowered(WID_LGL_COMPANY_FIRST + c)) continue;
664 mask.Set(c);
665 }
666 this->overlay->SetCompanyMask(mask);
667}
668
673{
674 CargoTypes mask = 0;
675 for (uint c = 0; c < num_cargo; c++) {
676 if (!this->IsWidgetLowered(WID_LGL_CARGO_FIRST + c)) continue;
677 SetBit(mask, _sorted_cargo_specs[c]->Index());
678 }
679 this->overlay->SetCargoMask(mask);
680}
681
682void LinkGraphLegendWindow::OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count)
683{
684 /* Check which button is clicked */
685 if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
686 if (!this->IsWidgetDisabled(widget)) {
687 this->ToggleWidgetLoweredState(widget);
689 }
690 } else if (widget == WID_LGL_COMPANIES_ALL || widget == WID_LGL_COMPANIES_NONE) {
691 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
692 if (this->IsWidgetDisabled(WID_LGL_COMPANY_FIRST + c)) continue;
693 this->SetWidgetLoweredState(WID_LGL_COMPANY_FIRST + c, widget == WID_LGL_COMPANIES_ALL);
694 }
696 this->SetDirty();
697 } else if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
698 this->ToggleWidgetLoweredState(widget);
699 this->UpdateOverlayCargoes();
700 } else if (widget == WID_LGL_CARGOES_ALL || widget == WID_LGL_CARGOES_NONE) {
701 for (uint c = 0; c < this->num_cargo; c++) {
702 this->SetWidgetLoweredState(WID_LGL_CARGO_FIRST + c, widget == WID_LGL_CARGOES_ALL);
703 }
704 this->UpdateOverlayCargoes();
705 }
706 this->SetDirty();
707}
708
714void LinkGraphLegendWindow::OnInvalidateData([[maybe_unused]] int data, [[maybe_unused]] bool gui_scope)
715{
716 if (this->num_cargo != _sorted_cargo_specs.size()) {
717 this->Close();
718 return;
719 }
720
721 /* Disable the companies who are not active */
722 for (CompanyID i = CompanyID::Begin(); i < MAX_COMPANIES; ++i) {
723 this->SetWidgetDisabledState(WID_LGL_COMPANY_FIRST + i, !Company::IsValidID(i));
724 }
725}
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:21
std::vector< const CargoSpec * > _sorted_cargo_specs
Cargo specifications sorted alphabetically by name.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Timpl & Set()
Set all bits.
uint GetFlowVia(StationID via) const
Get the sum of flows via a specific station from this FlowStatMap.
bool IsPointVisible(Point pt, const DrawPixelInfo *dpi, int padding=0) const
Determine if a certain point is inside the given DPI, with some lee way.
void SetCargoMask(CargoTypes cargo_mask)
Set a new cargo mask and rebuild the cache.
CargoTypes cargo_mask
Bitmask of cargos to be displayed.
Window * window
Window to be drawn into.
void DrawLinks(const DrawPixelInfo *dpi) const
Draw the cached links or part of them into the given area.
static void DrawVertex(int x, int y, int size, PixelColour colour, PixelColour border_colour)
Draw a square symbolizing a producer of cargo.
void SetCompanyMask(CompanyMask company_mask)
Set a new company mask and rebuild the cache.
static const PixelColour LINK_COLOURS[][12]
Colours for the various "load" states of links.
static void AddStats(CargoType new_cargo, uint new_cap, uint new_usg, uint new_flow, uint32_t time, bool new_shared, LinkProperties &cargo)
Add information from a given pair of link stat and flow stat to the given link properties.
StationSupplyList cached_stations
Cache for stations to be drawn.
LinkMap cached_links
Cache for links to reduce recalculation.
bool IsLinkVisible(Point pta, Point ptb, const DrawPixelInfo *dpi, int padding=0) const
Determine if a certain link crosses through the area given by the dpi with some lee way.
Point GetStationMiddle(const Station *st) const
Determine the middle of a station in the current window.
void Draw(const DrawPixelInfo *dpi)
Draw the linkgraph overlay or some part of it, in the area given.
bool dirty
Set if overlay should be rebuilt.
const WidgetID widget_id
ID of Widget in Window to be drawn to.
void DrawContent(Point pta, Point ptb, const LinkProperties &cargo) const
Draw one specific link.
CompanyMask company_mask
Bitmask of companies to be displayed.
uint scale
Width of link lines.
void GetWidgetDpi(DrawPixelInfo *dpi) const
Get a DPI for the widget we will be drawing to.
void AddLinks(const Station *sta, const Station *stb)
Add all "interesting" links between the given stations to the cache.
void DrawStationDots(const DrawPixelInfo *dpi) const
Draw dots for stations into the smallmap.
void SetDirty()
Mark the linkgraph dirty to be rebuilt next time Draw() is called.
void RebuildCache()
Rebuild the cache and recalculate which links and stations to be shown.
A connected component of a link graph.
Definition linkgraph.h:37
uint Monthly(uint base) const
Scale a value to its monthly equivalent, based on last compression.
Definition linkgraph.h:249
Baseclass for nested widgets.
uint current_x
Current horizontal size (after resizing).
uint current_y
Current vertical size (after resizing).
static constexpr TimerGameTick::Ticks DAY_TICKS
1 day is 74 ticks; TimerGameCalendar::date_fract used to be uint16_t and incremented by 885.
static bool UsingWallclockUnits(bool newgame=false)
Check if we are using wallclock units.
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:93
Definition of stuff that is very close to a company, like the company struct itself.
void DrawCompanyIcon(CompanyID c, int x, int y)
Draw the icon of a company.
GUI Functions related to companies.
static constexpr Owner OWNER_NONE
The tile has no ownership.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:87
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
int CentreBounds(int min, int max, int size)
Determine where to position a centred object.
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:972
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:900
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:669
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
@ FS_SMALL
Index of the small font in the font tables.
Definition gfx_type.h:250
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:389
@ TC_FORCED
Ignore colour changes from strings.
Definition gfx_type.h:332
constexpr NWidgetPart NWidgetFunction(NWidgetFunctionType *func_ptr)
Obtain a nested widget (sub)tree from an external source.
constexpr NWidgetPart SetPIP(uint8_t pre, uint8_t inter, uint8_t post)
Widget part function for setting a pre/inter/post spaces.
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 EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME,...
constexpr NWidgetPart NWidget(WidgetType tp, Colours col, WidgetID idx=INVALID_WIDGET)
Widget part function for starting a new 'real' widget.
void SetDirty() const
Mark entire window as dirty (in need of re-paint).
Definition window.cpp:969
std::unique_ptr< NWidgetBase > MakeCompanyButtonRowsLinkGraphGUI()
Make a number of rows with buttons for each company for the linkgraph legend window.
void ShowLinkGraphLegend()
Open a link graph legend window.
Declaration of linkgraph overlay GUI.
#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 uint Ceil(uint a, uint b)
Computes ceil(a / b) * b for non-negative a and b.
void GuiShowTooltips(Window *parent, EncodedString &&text, TooltipCloseCondition close_tooltip)
Shows a tooltip.
Definition misc_gui.cpp:694
TextColour GetContrastColour(PixelColour background, uint8_t threshold)
Determine a contrasty text colour for a coloured background.
Definition palette.cpp:361
PixelColour GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:388
A number of safeguards to prevent using unsafe methods.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Point GetSmallMapStationMiddle(const Window *w, const Station *st)
Determine the middle of a station in the smallmap window.
Smallmap GUI functions.
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
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
Functions related to OTTD's strings.
auto MakeParameters(Args &&... args)
Helper to create the StringParameters with its own buffer with the given parameter values.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
Owner owner
The owner of this station.
StationRect rect
NOSAVE: Station spread out rectangle maintained by StationRect::xxx() functions.
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
StringID abbrev
Two letter abbreviation for this cargo type.
Definition cargotype.h:95
StringID name
Name of this type of cargo.
Definition cargotype.h:91
T y
Y coordinate.
T x
X coordinate.
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h:157
FlowStatMap flows
Planned flows through this station.
Stores station stats for a single cargo.
NodeID node
ID of node in link graph referring to this goods entry.
const GoodsEntryData & GetData() const
Get optional cargo packet/flow data.
LinkGraphID link_graph
Link graph this station belongs to.
bool HasData() const
Test if this goods entry has optional cargo packet/flow data.
void OnInvalidateData(int data=0, bool gui_scope=true) override
Invalidate the data of this window if the cargoes or companies have changed.
void SetOverlay(std::shared_ptr< LinkGraphOverlay > overlay)
Set the overlay belonging to this menu and import its company/cargo settings.
void UpdateOverlayCompanies()
Update the overlay with the new company selection.
void OnClick(Point pt, WidgetID widget, int click_count) override
A click with the left mouse button has been made on the window.
bool OnTooltip(Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
Event to display a custom tooltip.
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 UpdateOverlayCargoes()
Update the overlay with the new cargo selection.
void DrawWidget(const Rect &r, WidgetID widget) const override
Draw the contents of a nested widget.
uint32_t TravelTime() const
Get edge's average travel time.
Definition linkgraph.h:56
NodeID dest_node
Destination of the edge.
Definition linkgraph.h:48
uint usage
Usage of the link.
Definition linkgraph.h:44
uint capacity
Capacity of the link.
Definition linkgraph.h:43
std::vector< BaseEdge > edges
Sorted list of outgoing edges from this node.
Definition linkgraph.h:97
uint supply
Supply at the station.
Definition linkgraph.h:91
Monthly statistics for a link between two stations.
uint usage
Actual usage of the link.
bool shared
If this is a shared link to be drawn dashed.
CargoType cargo
Cargo type of the link.
uint planned
Planned usage of the link.
uint Usage() const
Return the usage of the link to display.
uint capacity
Capacity of the link.
uint32_t time
Travel time of the link.
Colour for pixel/line drawing.
Definition gfx_type.h:405
static LinkGraph * Get(auto index)
Specification of a rectangle with absolute coordinates of all edges.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
static Pool::IterateWrapper< Station > Iterate(size_t from=0)
static Station * Get(auto index)
static Station * GetIfValid(auto index)
Station data structure.
std::array< GoodsEntry, NUM_CARGO > goods
Goods at this station.
High level window description.
Definition window_gui.h:168
Data structure for an opened window.
Definition window_gui.h:274
virtual void Close(int data=0)
Hide the window and all its child windows, and mark them for a later deletion.
Definition window.cpp:1106
ResizeInfo resize
Resize information.
Definition window_gui.h:315
bool IsWidgetLowered(WidgetID widget_index) const
Gets the lowered state of a widget.
Definition window_gui.h:492
bool IsWidgetDisabled(WidgetID widget_index) const
Gets the enabled/disabled status of a widget.
Definition window_gui.h:411
void SetWidgetLoweredState(WidgetID widget_index, bool lowered_stat)
Sets the lowered/raised status of a widget.
Definition window_gui.h:442
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:382
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
Definition window_gui.h:451
Definition of the game-calendar-timer.
Definition of the tick-based game-timer.
Functions related to (drawing on) viewports.
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:3436
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ 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_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_VERTICAL
Vertical container.
Definition widget_type.h:68
@ WWT_CLOSEBOX
Close box (at top-left of a window).
Definition widget_type.h:60
@ EqualSize
Containers should keep all their (resizing) children equally large.
Window * GetMainWindow()
Get the main window, i.e.
Definition window.cpp:1184
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
Twindow * AllocateWindowDescFront(WindowDesc &desc, WindowNumber window_number, Targs... extra_arguments)
Open a new window.
@ WDP_AUTO
Find a place automatically.
Definition window_gui.h:144
int WidgetID
Widget ID.
Definition window_type.h:20
@ WC_LINKGRAPH_LEGEND
Linkgraph legend; Window numbers:
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
Definition window_type.h:50
Functions related to zooming.