OpenTTD Source 20250528-master-g3aca5d62a8
viewport.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
63#include "stdafx.h"
64#include "core/backup_type.hpp"
65#include "landscape.h"
66#include "viewport_func.h"
67#include "station_base.h"
68#include "waypoint_base.h"
69#include "town.h"
70#include "signs_base.h"
71#include "signs_func.h"
72#include "vehicle_base.h"
73#include "vehicle_gui.h"
74#include "blitter/factory.hpp"
75#include "strings_func.h"
76#include "zoom_func.h"
77#include "vehicle_func.h"
78#include "company_func.h"
79#include "waypoint_func.h"
80#include "window_func.h"
81#include "tilehighlight_func.h"
82#include "window_gui.h"
84#include "viewport_kdtree.h"
85#include "town_kdtree.h"
87#include "bridge_map.h"
88#include "company_base.h"
89#include "command_func.h"
91#include "framerate_type.h"
92#include "viewport_cmd.h"
93
94#include <forward_list>
95#include <stack>
96
98
99#include "table/strings.h"
100#include "table/string_colours.h"
101
102#include "safeguards.h"
103
104Point _tile_fract_coords;
105
106
107ViewportSignKdtree _viewport_sign_kdtree{};
108static int _viewport_sign_maxwidth = 0;
109
110
111static const int MAX_TILE_EXTENT_LEFT = ZOOM_BASE * TILE_PIXELS;
112static const int MAX_TILE_EXTENT_RIGHT = ZOOM_BASE * TILE_PIXELS;
113static const int MAX_TILE_EXTENT_TOP = ZOOM_BASE * MAX_BUILDING_PIXELS;
114static const int MAX_TILE_EXTENT_BOTTOM = ZOOM_BASE * (TILE_PIXELS + 2 * TILE_HEIGHT);
115
117 std::string string;
118 uint16_t width;
119 Colours colour;
121 int32_t x;
122 int32_t y;
123};
124
126 SpriteID image;
127 PaletteID pal;
128 const SubSprite *sub;
129 int32_t x;
130 int32_t y;
131};
132
134 SpriteID image;
135 PaletteID pal;
136 const SubSprite *sub;
137 int32_t x;
138 int32_t y;
139 bool relative;
140 int next;
141};
142
144enum FoundationPart : uint8_t {
148 FOUNDATION_PART_END
149};
150
160
161typedef std::vector<TileSpriteToDraw> TileSpriteToDrawVector;
162typedef std::vector<StringSpriteToDraw> StringSpriteToDrawVector;
163typedef std::vector<ParentSpriteToDraw> ParentSpriteToDrawVector;
164typedef std::vector<ChildScreenSpriteToDraw> ChildScreenSpriteToDrawVector;
165
166constexpr int LAST_CHILD_NONE = -1;
167constexpr int LAST_CHILD_PARENT = -2;
168
171 DrawPixelInfo dpi;
172
173 StringSpriteToDrawVector string_sprites_to_draw;
174 TileSpriteToDrawVector tile_sprites_to_draw;
175 ParentSpriteToDrawVector parent_sprites_to_draw;
176 ParentSpriteToSortVector parent_sprites_to_sort;
177 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw;
178
179 int last_child;
180
182
183 int foundation[FOUNDATION_PART_END];
185 int last_foundation_child[FOUNDATION_PART_END];
186 Point foundation_offset[FOUNDATION_PART_END];
187};
188
189static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom);
190
191static ViewportDrawer _vd;
192
194static TileInfo _cur_ti;
195bool _draw_bounding_boxes = false;
196bool _draw_dirty_blocks = false;
197uint _dirty_block_colour = 0;
198static VpSpriteSorter _vp_sprite_sorter = nullptr;
199
200static Point MapXYZToViewport(const Viewport &vp, int x, int y, int z)
201{
202 Point p = RemapCoords(x, y, z);
203 p.x -= vp.virtual_width / 2;
204 p.y -= vp.virtual_height / 2;
205 return p;
206}
207
218void InitializeWindowViewport(Window *w, int x, int y,
219 int width, int height, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
220{
221 assert(w->viewport == nullptr);
222
223 auto vp = std::make_unique<ViewportData>();
224
225 vp->left = x + w->left;
226 vp->top = y + w->top;
227 vp->width = width;
228 vp->height = height;
229
231
232 vp->virtual_width = ScaleByZoom(width, zoom);
233 vp->virtual_height = ScaleByZoom(height, zoom);
234
235 Point pt;
236
237 if (std::holds_alternative<VehicleID>(focus)) {
238 const Vehicle *veh;
239
240 vp->follow_vehicle = std::get<VehicleID>(focus);
241 veh = Vehicle::Get(vp->follow_vehicle);
242 pt = MapXYZToViewport(*vp, veh->x_pos, veh->y_pos, veh->z_pos);
243 } else {
244 TileIndex tile = std::get<TileIndex>(focus);
245 if (tile == INVALID_TILE) {
246 /* No tile? Use center of main viewport. */
247 const Window *mw = GetMainWindow();
248
249 /* center on same place as main window (zoom is maximum, no adjustment needed) */
250 pt.x = mw->viewport->scrollpos_x + mw->viewport->virtual_width / 2;
251 pt.x -= vp->virtual_width / 2;
252 pt.y = mw->viewport->scrollpos_y + mw->viewport->virtual_height / 2;
253 pt.y -= vp->virtual_height / 2;
254 } else {
255 x = TileX(tile) * TILE_SIZE;
256 y = TileY(tile) * TILE_SIZE;
257 pt = MapXYZToViewport(*vp, x, y, GetSlopePixelZ(x, y));
258 }
259 vp->follow_vehicle = VehicleID::Invalid();
260 }
261
262 vp->scrollpos_x = pt.x;
263 vp->scrollpos_y = pt.y;
264 vp->dest_scrollpos_x = pt.x;
265 vp->dest_scrollpos_y = pt.y;
266
267 vp->overlay = nullptr;
268
269 vp->virtual_left = 0;
270 vp->virtual_top = 0;
271
272 w->viewport = std::move(vp);
273}
274
275static Point _vp_move_offs;
276
277static void DoSetViewportPosition(Window::IteratorToFront it, int left, int top, int width, int height)
278{
279 for (; !it.IsEnd(); ++it) {
280 const Window *w = *it;
281 if (left + width > w->left &&
282 w->left + w->width > left &&
283 top + height > w->top &&
284 w->top + w->height > top) {
285
286 if (left < w->left) {
287 DoSetViewportPosition(it, left, top, w->left - left, height);
288 DoSetViewportPosition(it, left + (w->left - left), top, width - (w->left - left), height);
289 return;
290 }
291
292 if (left + width > w->left + w->width) {
293 DoSetViewportPosition(it, left, top, (w->left + w->width - left), height);
294 DoSetViewportPosition(it, left + (w->left + w->width - left), top, width - (w->left + w->width - left), height);
295 return;
296 }
297
298 if (top < w->top) {
299 DoSetViewportPosition(it, left, top, width, (w->top - top));
300 DoSetViewportPosition(it, left, top + (w->top - top), width, height - (w->top - top));
301 return;
302 }
303
304 if (top + height > w->top + w->height) {
305 DoSetViewportPosition(it, left, top, width, (w->top + w->height - top));
306 DoSetViewportPosition(it, left, top + (w->top + w->height - top), width, height - (w->top + w->height - top));
307 return;
308 }
309
310 return;
311 }
312 }
313
314 {
315 int xo = _vp_move_offs.x;
316 int yo = _vp_move_offs.y;
317
318 if (abs(xo) >= width || abs(yo) >= height) {
319 /* fully_outside */
320 RedrawScreenRect(left, top, left + width, top + height);
321 return;
322 }
323
324 GfxScroll(left, top, width, height, xo, yo);
325
326 if (xo > 0) {
327 RedrawScreenRect(left, top, xo + left, top + height);
328 left += xo;
329 width -= xo;
330 } else if (xo < 0) {
331 RedrawScreenRect(left + width + xo, top, left + width, top + height);
332 width += xo;
333 }
334
335 if (yo > 0) {
336 RedrawScreenRect(left, top, width + left, top + yo);
337 } else if (yo < 0) {
338 RedrawScreenRect(left, top + height + yo, width + left, top + height);
339 }
340 }
341}
342
343static void SetViewportPosition(Window *w, int x, int y)
344{
345 Viewport &vp = *w->viewport;
346 int old_left = vp.virtual_left;
347 int old_top = vp.virtual_top;
348 int i;
349 int left, top, width, height;
350
351 vp.virtual_left = x;
352 vp.virtual_top = y;
353
354 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
355 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
356 */
357 old_left = UnScaleByZoomLower(old_left, vp.zoom);
358 old_top = UnScaleByZoomLower(old_top, vp.zoom);
359 x = UnScaleByZoomLower(x, vp.zoom);
360 y = UnScaleByZoomLower(y, vp.zoom);
361
362 old_left -= x;
363 old_top -= y;
364
365 if (old_top == 0 && old_left == 0) return;
366
367 _vp_move_offs.x = old_left;
368 _vp_move_offs.y = old_top;
369
370 left = vp.left;
371 top = vp.top;
372 width = vp.width;
373 height = vp.height;
374
375 if (left < 0) {
376 width += left;
377 left = 0;
378 }
379
380 i = left + width - _screen.width;
381 if (i >= 0) width -= i;
382
383 if (width > 0) {
384 if (top < 0) {
385 height += top;
386 top = 0;
387 }
388
389 i = top + height - _screen.height;
390 if (i >= 0) height -= i;
391
392 if (height > 0) {
394 ++it;
395 DoSetViewportPosition(it, left, top, width, height);
396 }
397 }
398}
399
408Viewport *IsPtInWindowViewport(const Window *w, int x, int y)
409{
410 if (w->viewport == nullptr) return nullptr;
411
412 const Viewport &vp = *w->viewport;
413 if (IsInsideMM(x, vp.left, vp.left + vp.width) && IsInsideMM(y, vp.top, vp.top + vp.height)) return w->viewport.get();
414
415 return nullptr;
416}
417
430Point TranslateXYToTileCoord(const Viewport &vp, int x, int y, bool clamp_to_map)
431{
432 if (!IsInsideBS(x, vp.left, vp.width) || !IsInsideBS(y, vp.top, vp.height)) {
433 Point pt = { -1, -1 };
434 return pt;
435 }
436
437 return InverseRemapCoords2(
438 ScaleByZoom(x - vp.left, vp.zoom) + vp.virtual_left,
439 ScaleByZoom(y - vp.top, vp.zoom) + vp.virtual_top, clamp_to_map);
440}
441
442/* When used for zooming, check area below current coordinates (x,y)
443 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
444 * when you just want the tile, make x = zoom_x and y = zoom_y */
445static Point GetTileFromScreenXY(int x, int y, int zoom_x, int zoom_y)
446{
447 if (Window *w = FindWindowFromPt(x, y); w != nullptr) {
448 if (Viewport *vp = IsPtInWindowViewport(w, x, y); vp != nullptr) {
449 return TranslateXYToTileCoord(*vp, zoom_x, zoom_y);
450 }
451 }
452
453 return {-1, -1};
454}
455
456Point GetTileBelowCursor()
457{
458 return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, _cursor.pos.x, _cursor.pos.y);
459}
460
461
462Point GetTileZoomCenterWindow(bool in, Window * w)
463{
464 int x, y;
465 const Viewport &vp = *w->viewport;
466
467 if (in) {
468 x = ((_cursor.pos.x - vp.left) >> 1) + (vp.width >> 2);
469 y = ((_cursor.pos.y - vp.top) >> 1) + (vp.height >> 2);
470 } else {
471 x = vp.width - (_cursor.pos.x - vp.left);
472 y = vp.height - (_cursor.pos.y - vp.top);
473 }
474 /* Get the tile below the cursor and center on the zoomed-out center */
475 return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, x + vp.left, y + vp.top);
476}
477
486void HandleZoomMessage(Window *w, const Viewport &vp, WidgetID widget_zoom_in, WidgetID widget_zoom_out)
487{
488 w->SetWidgetDisabledState(widget_zoom_in, vp.zoom <= _settings_client.gui.zoom_min);
489 w->SetWidgetDirty(widget_zoom_in);
490
491 w->SetWidgetDisabledState(widget_zoom_out, vp.zoom >= _settings_client.gui.zoom_max);
492 w->SetWidgetDirty(widget_zoom_out);
493}
494
507static void AddTileSpriteToDraw(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0)
508{
509 assert((image & SPRITE_MASK) < MAX_SPRITES);
510
511 TileSpriteToDraw &ts = _vd.tile_sprites_to_draw.emplace_back();
512 ts.image = image;
513 ts.pal = pal;
514 ts.sub = sub;
515 Point pt = RemapCoords(x, y, z);
516 ts.x = pt.x + extra_offs_x;
517 ts.y = pt.y + extra_offs_y;
518}
519
532static void AddChildSpriteToFoundation(SpriteID image, PaletteID pal, const SubSprite *sub, FoundationPart foundation_part, int extra_offs_x, int extra_offs_y)
533{
534 assert(IsInsideMM(foundation_part, 0, FOUNDATION_PART_END));
535 assert(_vd.foundation[foundation_part] != -1);
536 Point offs = _vd.foundation_offset[foundation_part];
537
538 /* Change the active ChildSprite list to the one of the foundation */
539 AutoRestoreBackup backup(_vd.last_child, _vd.last_foundation_child[foundation_part]);
540 AddChildSpriteScreen(image, pal, offs.x + extra_offs_x, offs.y + extra_offs_y, false, sub, false, false);
541}
542
556void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
557{
558 /* Switch to first foundation part, if no foundation was drawn */
560
561 if (_vd.foundation[_vd.foundation_part] != -1) {
562 Point pt = RemapCoords(x, y, z);
563 AddChildSpriteToFoundation(image, pal, sub, _vd.foundation_part, pt.x + extra_offs_x * ZOOM_BASE, pt.y + extra_offs_y * ZOOM_BASE);
564 } else {
565 AddTileSpriteToDraw(image, pal, _cur_ti.x + x, _cur_ti.y + y, _cur_ti.z + z, sub, extra_offs_x * ZOOM_BASE, extra_offs_y * ZOOM_BASE);
566 }
567}
568
579void DrawGroundSprite(SpriteID image, PaletteID pal, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
580{
581 DrawGroundSpriteAt(image, pal, 0, 0, 0, sub, extra_offs_x, extra_offs_y);
582}
583
591void OffsetGroundSprite(int x, int y)
592{
593 /* Switch to next foundation part */
594 switch (_vd.foundation_part) {
597 break;
600 break;
601 default: NOT_REACHED();
602 }
603
604 /* _vd.last_child is LAST_CHILD_NONE if foundation sprite was clipped by the viewport bounds */
605 if (_vd.last_child != LAST_CHILD_NONE) _vd.foundation[_vd.foundation_part] = static_cast<uint>(_vd.parent_sprites_to_draw.size()) - 1;
606
607 _vd.foundation_offset[_vd.foundation_part].x = x * ZOOM_BASE;
608 _vd.foundation_offset[_vd.foundation_part].y = y * ZOOM_BASE;
609 _vd.last_foundation_child[_vd.foundation_part] = _vd.last_child;
610}
611
623static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z, const SubSprite *sub)
624{
625 Point pt = RemapCoords(x, y, z);
626 const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
627
628 if (pt.x + spr->x_offs >= _vd.dpi.left + _vd.dpi.width ||
629 pt.x + spr->x_offs + spr->width <= _vd.dpi.left ||
630 pt.y + spr->y_offs >= _vd.dpi.top + _vd.dpi.height ||
631 pt.y + spr->y_offs + spr->height <= _vd.dpi.top)
632 return;
633
634 const ParentSpriteToDraw &pstd = _vd.parent_sprites_to_draw.back();
635 AddChildSpriteScreen(image, pal, pt.x - pstd.left, pt.y - pstd.top, false, sub, false);
636}
637
663void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, int h, int dz, int z, bool transparent, int bb_offset_x, int bb_offset_y, int bb_offset_z, const SubSprite *sub)
664{
665 int32_t left, right, top, bottom;
666
667 assert((image & SPRITE_MASK) < MAX_SPRITES);
668
669 /* make the sprites transparent with the right palette */
670 if (transparent) {
673 }
674
676 AddCombinedSprite(image, pal, x, y, z, sub);
677 return;
678 }
679
680 _vd.last_child = LAST_CHILD_NONE;
681
682 Point pt = RemapCoords(x, y, z);
683 int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
684
685 /* Compute screen extents of sprite */
686 if (image == SPR_EMPTY_BOUNDING_BOX) {
687 left = tmp_left = RemapCoords(x + w , y + bb_offset_y, z + bb_offset_z).x;
688 right = RemapCoords(x + bb_offset_x, y + h , z + bb_offset_z).x + 1;
689 top = tmp_top = RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz ).y;
690 bottom = RemapCoords(x + w , y + h , z + bb_offset_z).y + 1;
691 } else {
692 const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
693 left = tmp_left = (pt.x += spr->x_offs);
694 right = (pt.x + spr->width );
695 top = tmp_top = (pt.y += spr->y_offs);
696 bottom = (pt.y + spr->height);
697 }
698
699 if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
700 /* Compute maximal extents of sprite and its bounding box */
701 left = std::min(left , RemapCoords(x + w , y + bb_offset_y, z + bb_offset_z).x);
702 right = std::max(right , RemapCoords(x + bb_offset_x, y + h , z + bb_offset_z).x + 1);
703 top = std::min(top , RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz ).y);
704 bottom = std::max(bottom, RemapCoords(x + w , y + h , z + bb_offset_z).y + 1);
705 }
706
707 /* Do not add the sprite to the viewport, if it is outside */
708 if (left >= _vd.dpi.left + _vd.dpi.width ||
709 right <= _vd.dpi.left ||
710 top >= _vd.dpi.top + _vd.dpi.height ||
711 bottom <= _vd.dpi.top) {
712 return;
713 }
714
715 ParentSpriteToDraw &ps = _vd.parent_sprites_to_draw.emplace_back();
716 ps.x = tmp_x;
717 ps.y = tmp_y;
718
719 ps.left = tmp_left;
720 ps.top = tmp_top;
721
722 ps.image = image;
723 ps.pal = pal;
724 ps.sub = sub;
725 ps.xmin = x + bb_offset_x;
726 ps.xmax = x + std::max(bb_offset_x, w) - 1;
727
728 ps.ymin = y + bb_offset_y;
729 ps.ymax = y + std::max(bb_offset_y, h) - 1;
730
731 ps.zmin = z + bb_offset_z;
732 ps.zmax = z + std::max(bb_offset_z, dz) - 1;
733
735
736 _vd.last_child = LAST_CHILD_PARENT;
737
739}
740
764
774
784static bool IsInRangeInclusive(int begin, int end, int check)
785{
786 if (begin > end) std::swap(begin, end);
787 return begin <= check && check <= end;
788}
789
796bool IsInsideRotatedRectangle(int x, int y)
797{
798 int dist_a = (_thd.size.x + _thd.size.y); // Rotated coordinate system for selected rectangle.
799 int dist_b = (_thd.size.x - _thd.size.y); // We don't have to divide by 2. It's all relative!
800 int a = ((x - _thd.pos.x) + (y - _thd.pos.y)); // Rotated coordinate system for the point under scrutiny.
801 int b = ((x - _thd.pos.x) - (y - _thd.pos.y));
802
803 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
804 return IsInRangeInclusive(dist_a, 0, a) && IsInRangeInclusive(dist_b, 0, b);
805}
806
819void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent, const SubSprite *sub, bool scale, bool relative)
820{
821 assert((image & SPRITE_MASK) < MAX_SPRITES);
822
823 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
824 if (_vd.last_child == LAST_CHILD_NONE) return;
825
826 /* make the sprites transparent with the right palette */
827 if (transparent) {
830 }
831
832 int32_t child_id = static_cast<int32_t>(_vd.child_screen_sprites_to_draw.size());
833 if (_vd.last_child != LAST_CHILD_PARENT) {
834 _vd.child_screen_sprites_to_draw[_vd.last_child].next = child_id;
835 } else {
836 _vd.parent_sprites_to_draw.back().first_child = child_id;
837 }
838
839 ChildScreenSpriteToDraw &cs = _vd.child_screen_sprites_to_draw.emplace_back();
840 cs.image = image;
841 cs.pal = pal;
842 cs.sub = sub;
843 cs.x = scale ? x * ZOOM_BASE : x;
844 cs.y = scale ? y * ZOOM_BASE : y;
845 cs.relative = relative;
847
848 /* Append the sprite to the active ChildSprite list.
849 * If the active ParentSprite is a foundation, update last_foundation_child as well.
850 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
851 if (_vd.last_foundation_child[0] == _vd.last_child) _vd.last_foundation_child[0] = child_id;
852 if (_vd.last_foundation_child[1] == _vd.last_child) _vd.last_foundation_child[1] = child_id;
853 _vd.last_child = child_id;
854}
855
865static std::string &AddStringToDraw(int x, int y, Colours colour, ViewportStringFlags flags, uint16_t width)
866{
867 assert(width != 0);
868 StringSpriteToDraw &ss = _vd.string_sprites_to_draw.emplace_back();
869 ss.colour = colour;
870 ss.flags = flags;
871 ss.x = x;
872 ss.y = y;
873 ss.width = width;
874
875 return ss.string;
876}
877
878
892static void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part, int extra_offs_x = 0, int extra_offs_y = 0)
893{
894 if (_vd.foundation[foundation_part] == -1) {
895 /* draw on real ground */
896 AddTileSpriteToDraw(image, pal, ti->x, ti->y, ti->z + z_offset, nullptr, extra_offs_x, extra_offs_y);
897 } else {
898 /* draw on top of foundation */
899 AddChildSpriteToFoundation(image, pal, nullptr, foundation_part, extra_offs_x, extra_offs_y - z_offset * ZOOM_BASE);
900 }
901}
902
909static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal)
910{
911 if (!IsValidTile(ti->tile)) return;
912
913 SpriteID sel;
914 if (IsHalftileSlope(ti->tileh)) {
915 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
916 SpriteID sel2 = SPR_HALFTILE_SELECTION_FLAT + halftile_corner;
918
919 Corner opposite_corner = OppositeCorner(halftile_corner);
920 if (IsSteepSlope(ti->tileh)) {
921 sel = SPR_HALFTILE_SELECTION_DOWN;
922 } else {
923 sel = ((ti->tileh & SlopeWithOneCornerRaised(opposite_corner)) != 0 ? SPR_HALFTILE_SELECTION_UP : SPR_HALFTILE_SELECTION_FLAT);
924 }
925 sel += opposite_corner;
926 } else {
927 sel = SPR_SELECT_TILE + SlopeToSpriteOffset(ti->tileh);
928 }
930}
931
932static bool IsPartOfAutoLine(int px, int py)
933{
934 px -= _thd.selstart.x;
935 py -= _thd.selstart.y;
936
937 if ((_thd.drawstyle & HT_DRAG_MASK) != HT_LINE) return false;
938
939 switch (_thd.drawstyle & HT_DIR_MASK) {
940 case HT_DIR_X: return py == 0; // x direction
941 case HT_DIR_Y: return px == 0; // y direction
942 case HT_DIR_HU: return px == -py || px == -py - 16; // horizontal upper
943 case HT_DIR_HL: return px == -py || px == -py + 16; // horizontal lower
944 case HT_DIR_VL: return px == py || px == py + 16; // vertical left
945 case HT_DIR_VR: return px == py || px == py - 16; // vertical right
946 default:
947 NOT_REACHED();
948 }
949}
950
951/* [direction][side] */
952static const HighLightStyle _autorail_type[6][2] = {
953 { HT_DIR_X, HT_DIR_X },
954 { HT_DIR_Y, HT_DIR_Y },
955 { HT_DIR_HU, HT_DIR_HL },
956 { HT_DIR_HL, HT_DIR_HU },
957 { HT_DIR_VL, HT_DIR_VR },
959};
960
961#include "table/autorail.h"
962
969static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
970{
971 SpriteID image;
972 PaletteID pal;
973 int offset;
974
975 FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
976 Slope autorail_tileh = RemoveHalftileSlope(ti->tileh);
977 if (IsHalftileSlope(ti->tileh)) {
978 static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U };
979 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
980 if (autorail_type != _lower_rail[halftile_corner]) {
981 foundation_part = FOUNDATION_PART_HALFTILE;
982 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
983 autorail_tileh = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
984 }
985 }
986
987 offset = _AutorailTilehSprite[autorail_tileh][autorail_type];
988 if (offset >= 0) {
989 image = SPR_AUTORAIL_BASE + offset;
990 pal = PAL_NONE;
991 } else {
992 image = SPR_AUTORAIL_BASE - offset;
994 }
995
996 DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
997}
998
999enum TileHighlightType : uint8_t {
1000 THT_NONE,
1001 THT_WHITE,
1002 THT_BLUE,
1003 THT_RED,
1004};
1005
1009
1015static TileHighlightType GetTileHighlightType(TileIndex t)
1016{
1017 if (_viewport_highlight_station != nullptr) {
1018 if (IsTileType(t, MP_STATION) && GetStationIndex(t) == _viewport_highlight_station->index) return THT_WHITE;
1019 if (_viewport_highlight_station->TileIsInCatchment(t)) return THT_BLUE;
1020 }
1021 if (_viewport_highlight_waypoint != nullptr) {
1022 if (IsTileType(t, MP_STATION) && GetStationIndex(t) == _viewport_highlight_waypoint->index) return THT_BLUE;
1023 }
1024
1025 if (_viewport_highlight_town != nullptr) {
1026 if (IsTileType(t, MP_HOUSE)) {
1028 TileHighlightType type = THT_RED;
1030 if (st->owner != _current_company) continue;
1031 if (st->TileIsInCatchment(t)) return THT_BLUE;
1032 }
1033 return type;
1034 }
1035 } else if (IsTileType(t, MP_STATION)) {
1037 if (st->owner != _current_company) continue;
1038 if (GetStationIndex(t) == st->index) return THT_WHITE;
1039 }
1040 }
1041 }
1042
1043 return THT_NONE;
1044}
1045
1051static void DrawTileHighlightType(const TileInfo *ti, TileHighlightType tht)
1052{
1053 switch (tht) {
1054 default:
1055 case THT_NONE: break;
1056 case THT_WHITE: DrawTileSelectionRect(ti, PAL_NONE); break;
1057 case THT_BLUE: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE); break;
1058 case THT_RED: DrawTileSelectionRect(ti, PALETTE_SEL_TILE_RED); break;
1059 }
1060}
1061
1067{
1068 /* Going through cases in order of computational time. */
1069
1070 if (_town_local_authority_kdtree.Count() == 0) return;
1071
1072 /* Tile belongs to town regardless of distance from town. */
1073 if (GetTileType(ti->tile) == MP_HOUSE) {
1074 if (!Town::GetByTile(ti->tile)->show_zone) return;
1075
1077 return;
1078 }
1079
1080 /* If the closest town in the highlighted list is far, we can stop searching. */
1081 TownID tid = _town_local_authority_kdtree.FindNearest(TileX(ti->tile), TileY(ti->tile));
1082 Town *closest_highlighted_town = Town::Get(tid);
1083
1084 if (DistanceManhattan(ti->tile, closest_highlighted_town->xy) >= _settings_game.economy.dist_local_authority) return;
1085
1086 /* Tile is inside of the local autrhority distance of a highlighted town,
1087 but it is possible that a non-highlighted town is even closer. */
1089
1090 if (closest_town->show_zone) {
1092 }
1093
1094}
1095
1100static void DrawTileSelection(const TileInfo *ti)
1101{
1102 /* Highlight tiles insede local authority of selected towns. */
1104
1105 /* Draw a red error square? */
1106 bool is_redsq = _thd.redsq == ti->tile;
1108
1109 TileHighlightType tht = GetTileHighlightType(ti->tile);
1110 DrawTileHighlightType(ti, tht);
1111
1112 /* No tile selection active? */
1113 if ((_thd.drawstyle & HT_DRAG_MASK) == HT_NONE) return;
1114
1115 if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle
1116 if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner;
1117 return;
1118 }
1119
1120 /* Inside the inner area? */
1121 if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) &&
1122 IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) {
1123draw_inner:
1124 if (_thd.drawstyle & HT_RECT) {
1125 if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE);
1126 } else if (_thd.drawstyle & HT_POINT) {
1127 /* Figure out the Z coordinate for the single dot. */
1128 int z = 0;
1129 FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
1130 if (ti->tileh & SLOPE_N) {
1131 z += TILE_HEIGHT;
1133 }
1134 if (IsHalftileSlope(ti->tileh)) {
1135 Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
1136 if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT;
1137 if (halftile_corner != CORNER_S) {
1138 foundation_part = FOUNDATION_PART_HALFTILE;
1139 if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT;
1140 }
1141 }
1142 DrawSelectionSprite(SPR_DOT, PAL_NONE, ti, z, foundation_part);
1143 } else if (_thd.drawstyle & HT_RAIL) {
1144 /* autorail highlight piece under cursor */
1145 HighLightStyle type = _thd.drawstyle & HT_DIR_MASK;
1146 assert(type < HT_DIR_END);
1147 DrawAutorailSelection(ti, _autorail_type[type][0]);
1148 } else if (IsPartOfAutoLine(ti->x, ti->y)) {
1149 /* autorail highlighting long line */
1151 uint side;
1152
1153 if (dir == HT_DIR_X || dir == HT_DIR_Y) {
1154 side = 0;
1155 } else {
1156 TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
1157 side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile)));
1158 }
1159
1160 DrawAutorailSelection(ti, _autorail_type[dir][side]);
1161 }
1162 return;
1163 }
1164
1165 /* Check if it's inside the outer area? */
1166 if (!is_redsq && (tht == THT_NONE || tht == THT_RED) && _thd.outersize.x > 0 &&
1167 IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) &&
1168 IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) {
1169 /* Draw a blue rect. */
1171 return;
1172 }
1173}
1174
1181static int GetViewportY(Point tile)
1182{
1183 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1184 return (tile.y * (int)(TILE_PIXELS / 2) + tile.x * (int)(TILE_PIXELS / 2) - TilePixelHeightOutsideMap(tile.x, tile.y)) << ZOOM_BASE_SHIFT;
1185}
1186
1191{
1192 assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
1193 assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
1194
1195 Point upper_left = InverseRemapCoords(_vd.dpi.left, _vd.dpi.top);
1196 Point upper_right = InverseRemapCoords(_vd.dpi.left + _vd.dpi.width, _vd.dpi.top);
1197
1198 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1199 * column = y - x
1200 * row = x + y
1201 * x = (row - column) / 2
1202 * y = (row + column) / 2
1203 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1204 */
1205
1206 /* Columns overlap with neighbouring columns by a half tile.
1207 * - Left column is column of upper_left (rounded down) and one column to the left.
1208 * - Right column is column of upper_right (rounded up) and one column to the right.
1209 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1210 */
1211 int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
1212 int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
1213
1214 int potential_bridge_height = ZOOM_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height;
1215
1216 /* Rows overlap with neighbouring rows by a half tile.
1217 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1218 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1219 */
1220 int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
1221 bool last_row = false;
1222 for (; !last_row; row++) {
1223 last_row = true;
1224 for (int column = left_column; column <= right_column; column++) {
1225 /* Valid row/column? */
1226 if ((row + column) % 2 != 0) continue;
1227
1228 Point tilecoord;
1229 tilecoord.x = (row - column) / 2;
1230 tilecoord.y = (row + column) / 2;
1231 assert(column == tilecoord.y - tilecoord.x);
1232 assert(row == tilecoord.y + tilecoord.x);
1233
1234 TileType tile_type;
1235 _cur_ti.x = tilecoord.x * TILE_SIZE;
1236 _cur_ti.y = tilecoord.y * TILE_SIZE;
1237
1238 if (IsInsideBS(tilecoord.x, 0, Map::SizeX()) && IsInsideBS(tilecoord.y, 0, Map::SizeY())) {
1239 /* This includes the south border at Map::MaxX / Map::MaxY. When terraforming we still draw tile selections there. */
1240 _cur_ti.tile = TileXY(tilecoord.x, tilecoord.y);
1241 tile_type = GetTileType(_cur_ti.tile);
1242 } else {
1243 _cur_ti.tile = INVALID_TILE;
1244 tile_type = MP_VOID;
1245 }
1246
1247 if (tile_type != MP_VOID) {
1248 /* We are inside the map => paint landscape. */
1249 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlope(_cur_ti.tile);
1250 } else {
1251 /* We are outside the map => paint black. */
1252 std::tie(_cur_ti.tileh, _cur_ti.z) = GetTilePixelSlopeOutsideMap(tilecoord.x, tilecoord.y);
1253 }
1254
1255 int viewport_y = GetViewportY(tilecoord);
1256
1257 if (viewport_y + MAX_TILE_EXTENT_BOTTOM < _vd.dpi.top) {
1258 /* The tile in this column is not visible yet.
1259 * Tiles in other columns may be visible, but we need more rows in any case. */
1260 last_row = false;
1261 continue;
1262 }
1263
1264 int min_visible_height = viewport_y - (_vd.dpi.top + _vd.dpi.height);
1265 bool tile_visible = min_visible_height <= 0;
1266
1267 if (tile_type != MP_VOID) {
1268 /* Is tile with buildings visible? */
1269 if (min_visible_height < MAX_TILE_EXTENT_TOP) tile_visible = true;
1270
1271 if (IsBridgeAbove(_cur_ti.tile)) {
1272 /* Is the bridge visible? */
1273 TileIndex bridge_tile = GetNorthernBridgeEnd(_cur_ti.tile);
1274 int bridge_height = ZOOM_BASE * (GetBridgePixelHeight(bridge_tile) - TilePixelHeight(_cur_ti.tile));
1275 if (min_visible_height < bridge_height + MAX_TILE_EXTENT_TOP) tile_visible = true;
1276 }
1277
1278 /* Would a higher bridge on a more southern tile be visible?
1279 * If yes, we need to loop over more rows to possibly find one. */
1280 if (min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1281 } else {
1282 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1283 * so we need to loop over more rows to possibly find one. */
1284 if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1285 }
1286
1287 if (tile_visible) {
1288 last_row = false;
1290 _vd.foundation[0] = -1;
1291 _vd.foundation[1] = -1;
1294
1295 _tile_type_procs[tile_type]->draw_tile_proc(&_cur_ti);
1296 if (_cur_ti.tile != INVALID_TILE) DrawTileSelection(&_cur_ti);
1297 }
1298 }
1299 }
1300}
1301
1310std::string *ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour)
1311{
1312 int left = dpi->left;
1313 int top = dpi->top;
1314 int right = left + dpi->width;
1315 int bottom = top + dpi->height;
1316
1317 bool small = flags.Test(ViewportStringFlag::Small);
1318 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FS_SMALL : FS_NORMAL) + WidgetDimensions::scaled.fullbevel.bottom, dpi->zoom);
1319 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, dpi->zoom);
1320
1321 if (bottom < sign->top ||
1322 top > sign->top + sign_height ||
1323 right < sign->center - sign_half_width ||
1324 left > sign->center + sign_half_width) {
1325 return nullptr;
1326 }
1327
1328 return &AddStringToDraw(sign->center - sign_half_width, sign->top, colour, flags, small ? sign->width_small : sign->width_normal);
1329}
1330
1331static Rect ExpandRectWithViewportSignMargins(Rect r, ZoomLevel zoom)
1332{
1333 const int fh = std::max(GetCharacterHeight(FS_NORMAL), GetCharacterHeight(FS_SMALL));
1334 const int max_tw = _viewport_sign_maxwidth / 2 + 1;
1335 const int expand_y = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + fh + WidgetDimensions::scaled.fullbevel.bottom, zoom);
1336 const int expand_x = ScaleByZoom(WidgetDimensions::scaled.fullbevel.left + max_tw + WidgetDimensions::scaled.fullbevel.right, zoom);
1337
1338 r.left -= expand_x;
1339 r.right += expand_x;
1340 r.top -= expand_y;
1341 r.bottom += expand_y;
1342
1343 return r;
1344}
1345
1352static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector<const Town *> &towns, bool small)
1353{
1354 ViewportStringFlags flags{};
1356
1357 StringID stringid = !small && _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_TOWN_NAME;
1358 for (const Town *t : towns) {
1359 std::string *str = ViewportAddString(dpi, &t->cache.sign, flags, INVALID_COLOUR);
1360 if (str == nullptr) continue;
1361
1362 *str = GetString(stringid, t->index, t->cache.population);
1363 }
1364}
1365
1372static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector<const Sign *> &signs, bool small)
1373{
1374 ViewportStringFlags flags{};
1375 if (small) flags.Set(ViewportStringFlag::Small);
1376
1377 /* Signs placed by a game script don't have a frame. */
1378 ViewportStringFlags deity_flags{flags};
1380
1381 for (const Sign *si : signs) {
1382 std::string *str = ViewportAddString(dpi, &si->sign, (si->owner == OWNER_DEITY) ? deity_flags : flags,
1383 (si->owner == OWNER_NONE) ? COLOUR_GREY : (si->owner == OWNER_DEITY ? INVALID_COLOUR : _company_colours[si->owner]));
1384 if (str == nullptr) continue;
1385
1386 *str = GetString(STR_SIGN_NAME, si->index);
1387 }
1388}
1389
1396static void ViewportAddStationStrings(DrawPixelInfo *dpi, const std::vector<const BaseStation *> &stations, bool small)
1397{
1398 /* Transparent station signs have colour text instead of a colour panel. */
1400 if (small) flags.Set(ViewportStringFlag::Small);
1401
1402 for (const BaseStation *st : stations) {
1403 std::string *str = ViewportAddString(dpi, &st->sign, flags, (st->owner == OWNER_NONE || !st->IsInUse()) ? COLOUR_GREY : _company_colours[st->owner]);
1404 if (str == nullptr) continue;
1405
1406 if (Station::IsExpected(st)) { /* Station */
1407 *str = GetString(small ? STR_STATION_NAME : STR_VIEWPORT_STATION, st->index, st->facilities);
1408 } else { /* Waypoint */
1409 *str = GetString(STR_WAYPOINT_NAME, st->index);
1410 }
1411 }
1412}
1413
1414static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi)
1415{
1416 Rect search_rect{ dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height };
1417 search_rect = ExpandRectWithViewportSignMargins(search_rect, dpi->zoom);
1418
1419 bool show_stations = HasBit(_display_opt, DO_SHOW_STATION_NAMES) && _game_mode != GM_MENU;
1420 bool show_waypoints = HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES) && _game_mode != GM_MENU;
1421 bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES) && _game_mode != GM_MENU;
1423 bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
1424
1425 /* Collect all the items first and draw afterwards, to ensure layering */
1426 std::vector<const BaseStation *> stations;
1427 std::vector<const Town *> towns;
1428 std::vector<const Sign *> signs;
1429
1430 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
1431 switch (item.type) {
1432 case ViewportSignKdtreeItem::VKI_STATION: {
1433 if (!show_stations) break;
1434 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1435
1436 /* If no facilities are present the station is a ghost station. */
1437 StationFacilities facilities = st->facilities;
1438 if (facilities == StationFacilities{}) facilities = STATION_FACILITY_GHOST;
1439
1440 if (!facilities.Any(_facility_display_opt)) break;
1441
1442 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1443 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1444
1445 stations.push_back(st);
1446 break;
1447 }
1448
1449 case ViewportSignKdtreeItem::VKI_WAYPOINT: {
1450 if (!show_waypoints) break;
1451 const BaseStation *st = BaseStation::Get(std::get<StationID>(item.id));
1452
1453 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1454 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1455
1456 stations.push_back(st);
1457 break;
1458 }
1459
1460 case ViewportSignKdtreeItem::VKI_TOWN:
1461 if (!show_towns) break;
1462 towns.push_back(Town::Get(std::get<TownID>(item.id)));
1463 break;
1464
1465 case ViewportSignKdtreeItem::VKI_SIGN: {
1466 if (!show_signs) break;
1467 const Sign *si = Sign::Get(std::get<SignID>(item.id));
1468
1469 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1470 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1471 * companies can leave OWNER_NONE signs after them. */
1472 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
1473
1474 signs.push_back(si);
1475 break;
1476 }
1477
1478 default:
1479 NOT_REACHED();
1480 }
1481 });
1482
1483 /* Small versions of signs are used zoom level 4X and higher. */
1484 bool small = dpi->zoom >= ZoomLevel::Out4x;
1485
1486 /* Layering order (bottom to top): Town names, signs, stations */
1487 ViewportAddTownStrings(dpi, towns, small);
1488
1489 /* Do not draw signs nor station names if they are set invisible */
1490 if (IsInvisibilitySet(TO_SIGNS)) return;
1491
1492 ViewportAddSignStrings(dpi, signs, small);
1493 ViewportAddStationStrings(dpi, stations, small);
1494}
1495
1496
1504void ViewportSign::UpdatePosition(int center, int top, std::string_view str, std::string_view str_small)
1505{
1506 if (this->width_normal != 0) this->MarkDirty();
1507
1508 this->top = top;
1509
1511 this->center = center;
1512
1513 /* zoomed out version */
1514 if (str_small.empty()) str_small = str;
1516
1517 this->MarkDirty();
1518}
1519
1527{
1528 Rect zoomlevels[to_underlying(ZoomLevel::End)];
1529
1530 /* We don't know which size will be drawn, so mark the largest area dirty. */
1531 const uint half_width = std::max(this->width_normal, this->width_small) / 2 + 1;
1533
1534 for (ZoomLevel zoom = ZoomLevel::Begin; zoom != ZoomLevel::End; zoom++) {
1535 zoomlevels[to_underlying(zoom)].left = this->center - ScaleByZoom(half_width, zoom);
1536 zoomlevels[to_underlying(zoom)].top = this->top - ScaleByZoom(1, zoom);
1537 zoomlevels[to_underlying(zoom)].right = this->center + ScaleByZoom(half_width, zoom);
1538 zoomlevels[to_underlying(zoom)].bottom = this->top + ScaleByZoom(height, zoom);
1539 }
1540
1541 for (const Window *w : Window::Iterate()) {
1542 if (w->viewport == nullptr) continue;
1543
1544 Viewport &vp = *w->viewport;
1545 if (vp.zoom <= maxzoom) {
1546 assert(vp.width != 0);
1547 Rect &zl = zoomlevels[to_underlying(vp.zoom)];
1548 MarkViewportDirty(vp, zl.left, zl.top, zl.right, zl.bottom);
1549 }
1550 }
1551}
1552
1553static void ViewportDrawTileSprites(const TileSpriteToDrawVector *tstdv)
1554{
1555 for (const TileSpriteToDraw &ts : *tstdv) {
1556 DrawSpriteViewport(ts.image, ts.pal, ts.x, ts.y, ts.sub);
1557 }
1558}
1559
1562{
1563 return true;
1564}
1565
1567static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
1568{
1569 if (psdv->size() < 2) return;
1570
1571 /* We rely on sprites being, for the most part, already ordered.
1572 * So we don't need to move many of them and can keep track of their
1573 * order efficiently by using stack. We always move sprites to the front
1574 * of the current position, i.e. to the top of the stack.
1575 * Also use special constants to indicate sorting state without
1576 * adding extra fields to ParentSpriteToDraw structure.
1577 */
1578 const uint32_t ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it
1579 const uint32_t ORDER_RETURNED = UINT32_MAX - 1; // Makr sorted sprite in case there are other occurrences of it in the stack
1580 std::stack<ParentSpriteToDraw *> sprite_order;
1581 uint32_t next_order = 0;
1582
1583 std::forward_list<std::pair<int64_t, ParentSpriteToDraw *>> sprite_list; // We store sprites in a list sorted by xmin+ymin
1584
1585 /* Initialize sprite list and order. */
1586 for (auto p = psdv->rbegin(); p != psdv->rend(); p++) {
1587 sprite_list.emplace_front((*p)->xmin + (*p)->ymin, *p);
1588 sprite_order.push(*p);
1589 (*p)->order = next_order++;
1590 }
1591
1592 sprite_list.sort();
1593
1594 std::vector<ParentSpriteToDraw *> preceding; // Temporarily stores sprites that precede current and their position in the list
1595 auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preceding sprite
1596 auto out = psdv->begin(); // Iterator to output sorted sprites
1597
1598 while (!sprite_order.empty()) {
1599
1600 auto s = sprite_order.top();
1601 sprite_order.pop();
1602
1603 /* Sprite is already sorted, ignore it. */
1604 if (s->order == ORDER_RETURNED) continue;
1605
1606 /* Sprite was already compared, just need to output it. */
1607 if (s->order == ORDER_COMPARED) {
1608 *(out++) = s;
1609 s->order = ORDER_RETURNED;
1610 continue;
1611 }
1612
1613 preceding.clear();
1614
1615 /* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax
1616 * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax
1617 * we get all we need and some more that we filter out later.
1618 * We don't include zmin into the sum as there are usually more neighbours on x and y than z
1619 * so including it will actually increase the amount of false positives.
1620 * Also min coordinates can be > max so using max(xmin, xmax) + max(ymin, ymax)
1621 * to ensure that we iterate the current sprite as we need to remove it from the list.
1622 */
1623 auto ssum = std::max(s->xmax, s->xmin) + std::max(s->ymax, s->ymin);
1624 auto prev = sprite_list.before_begin();
1625 auto x = sprite_list.begin();
1626 while (x != sprite_list.end() && ((*x).first <= ssum)) {
1627 auto p = (*x).second;
1628 if (p == s) {
1629 /* We found the current sprite, remove it and move on. */
1630 x = sprite_list.erase_after(prev);
1631 continue;
1632 }
1633
1634 auto p_prev = prev;
1635 prev = x++;
1636
1637 if (s->xmax < p->xmin || s->ymax < p->ymin || s->zmax < p->zmin) continue;
1638 if (s->xmin <= p->xmax && // overlap in X?
1639 s->ymin <= p->ymax && // overlap in Y?
1640 s->zmin <= p->zmax) { // overlap in Z?
1641 if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <=
1642 p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) {
1643 continue;
1644 }
1645 }
1646 preceding.push_back(p);
1647 preceding_prev = p_prev;
1648 }
1649
1650 if (preceding.empty()) {
1651 /* No preceding sprites, add current one to the output */
1652 *(out++) = s;
1653 s->order = ORDER_RETURNED;
1654 continue;
1655 }
1656
1657 /* Optimization for the case when we only have 1 sprite to move. */
1658 if (preceding.size() == 1) {
1659 auto p = preceding[0];
1660 /* We can only output the preceding sprite if there can't be any other sprites preceding it. */
1661 if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) {
1662 p->order = ORDER_RETURNED;
1663 s->order = ORDER_RETURNED;
1664 sprite_list.erase_after(preceding_prev);
1665 *(out++) = p;
1666 *(out++) = s;
1667 continue;
1668 }
1669 }
1670
1671 /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */
1672 std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) {
1673 return a->order > b->order;
1674 });
1675
1676 s->order = ORDER_COMPARED;
1677 sprite_order.push(s); // Still need to output so push it back for now
1678
1679 for (auto p: preceding) {
1680 p->order = next_order++;
1681 sprite_order.push(p);
1682 }
1683 }
1684}
1685
1686
1687static void ViewportDrawParentSprites(const ParentSpriteToSortVector *psd, const ChildScreenSpriteToDrawVector *csstdv)
1688{
1689 for (const ParentSpriteToDraw *ps : *psd) {
1690 if (ps->image != SPR_EMPTY_BOUNDING_BOX) DrawSpriteViewport(ps->image, ps->pal, ps->x, ps->y, ps->sub);
1691
1692 int child_idx = ps->first_child;
1693 while (child_idx >= 0) {
1694 const ChildScreenSpriteToDraw *cs = &(*csstdv)[child_idx];
1695 child_idx = cs->next;
1696 if (cs->relative) {
1697 DrawSpriteViewport(cs->image, cs->pal, ps->left + cs->x, ps->top + cs->y, cs->sub);
1698 } else {
1699 DrawSpriteViewport(cs->image, cs->pal, ps->x + cs->x, ps->y + cs->y, cs->sub);
1700 }
1701 }
1702 }
1703}
1704
1709static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
1710{
1711 for (const ParentSpriteToDraw *ps : *psd) {
1712 Point pt1 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmax + 1); // top front corner
1713 Point pt2 = RemapCoords(ps->xmin , ps->ymax + 1, ps->zmax + 1); // top left corner
1714 Point pt3 = RemapCoords(ps->xmax + 1, ps->ymin , ps->zmax + 1); // top right corner
1715 Point pt4 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmin ); // bottom front corner
1716
1717 DrawBox( pt1.x, pt1.y,
1718 pt2.x - pt1.x, pt2.y - pt1.y,
1719 pt3.x - pt1.x, pt3.y - pt1.y,
1720 pt4.x - pt1.x, pt4.y - pt1.y);
1721 }
1722}
1723
1728{
1730 const DrawPixelInfo *dpi = _cur_dpi;
1731 void *dst;
1732 int right = UnScaleByZoom(dpi->width, dpi->zoom);
1733 int bottom = UnScaleByZoom(dpi->height, dpi->zoom);
1734
1735 int colour = _string_colourmap[_dirty_block_colour & 0xF];
1736
1737 dst = dpi->dst_ptr;
1738
1739 uint8_t bo = UnScaleByZoom(dpi->left + dpi->top, dpi->zoom) & 1;
1740 do {
1741 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8_t)colour);
1742 dst = blitter->MoveTo(dst, 0, 1);
1743 } while (--bottom > 0);
1744}
1745
1746static void ViewportDrawStrings(ZoomLevel zoom, const StringSpriteToDrawVector *sstdv)
1747{
1748 for (const StringSpriteToDraw &ss : *sstdv) {
1749 bool small = ss.flags.Test(ViewportStringFlag::Small);
1750 int w = ss.width;
1751 int x = UnScaleByZoom(ss.x, zoom);
1752 int y = UnScaleByZoom(ss.y, zoom);
1753 int h = WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FS_SMALL : FS_NORMAL) + WidgetDimensions::scaled.fullbevel.bottom;
1754
1755 TextColour colour = TC_WHITE;
1756 if (ss.flags.Test(ViewportStringFlag::ColourRect)) {
1757 if (ss.colour != INVALID_COLOUR) DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, {});
1758 colour = TC_BLACK;
1759 } else if (ss.flags.Test(ViewportStringFlag::TransparentRect)) {
1760 DrawFrameRect(x, y, x + w - 1, y + h - 1, ss.colour, FrameFlag::Transparent);
1761 }
1762
1763 if (ss.flags.Test(ViewportStringFlag::TextColour)) {
1764 if (ss.colour != INVALID_COLOUR) colour = static_cast<TextColour>(GetColourGradient(ss.colour, SHADE_LIGHTER) | TC_IS_PALETTE_COLOUR);
1765 }
1766
1767 int left = x + WidgetDimensions::scaled.fullbevel.left;
1768 int right = x + w - 1 - WidgetDimensions::scaled.fullbevel.right;
1769 int top = y + WidgetDimensions::scaled.fullbevel.top;
1770
1771 int shadow_offset = 0;
1772 if (small && ss.flags.Test(ViewportStringFlag::Shadow)) {
1773 /* Shadow needs to be shifted 1 pixel. */
1774 shadow_offset = WidgetDimensions::scaled.fullbevel.top;
1775 DrawString(left + shadow_offset, right + shadow_offset, top, ss.string, TC_BLACK, SA_HOR_CENTER, false, FS_SMALL);
1776 }
1777
1778 DrawString(left, right, top - shadow_offset, ss.string, colour, SA_HOR_CENTER, false, small ? FS_SMALL : FS_NORMAL);
1779 }
1780}
1781
1782void ViewportDoDraw(const Viewport &vp, int left, int top, int right, int bottom)
1783{
1784 _vd.dpi.zoom = vp.zoom;
1785 int mask = ScaleByZoom(-1, vp.zoom);
1786
1788
1789 _vd.dpi.width = (right - left) & mask;
1790 _vd.dpi.height = (bottom - top) & mask;
1791 _vd.dpi.left = left & mask;
1792 _vd.dpi.top = top & mask;
1793 _vd.dpi.pitch = _cur_dpi->pitch;
1794 _vd.last_child = LAST_CHILD_NONE;
1795
1796 int x = UnScaleByZoom(_vd.dpi.left - (vp.virtual_left & mask), vp.zoom) + vp.left;
1797 int y = UnScaleByZoom(_vd.dpi.top - (vp.virtual_top & mask), vp.zoom) + vp.top;
1798
1799 _vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_cur_dpi->dst_ptr, x - _cur_dpi->left, y - _cur_dpi->top);
1800 AutoRestoreBackup dpi_backup(_cur_dpi, &_vd.dpi);
1801
1803 ViewportAddVehicles(&_vd.dpi);
1804
1805 ViewportAddKdtreeSigns(&_vd.dpi);
1806
1807 DrawTextEffects(&_vd.dpi);
1808
1809 if (!_vd.tile_sprites_to_draw.empty()) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw);
1810
1811 for (auto &psd : _vd.parent_sprites_to_draw) {
1812 _vd.parent_sprites_to_sort.push_back(&psd);
1813 }
1814
1815 _vp_sprite_sorter(&_vd.parent_sprites_to_sort);
1816 ViewportDrawParentSprites(&_vd.parent_sprites_to_sort, &_vd.child_screen_sprites_to_draw);
1817
1818 if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(&_vd.parent_sprites_to_sort);
1819 if (_draw_dirty_blocks) ViewportDrawDirtyBlocks();
1820
1821 DrawPixelInfo dp = _vd.dpi;
1822 ZoomLevel zoom = _vd.dpi.zoom;
1823 dp.zoom = ZoomLevel::Min;
1824 dp.width = UnScaleByZoom(dp.width, zoom);
1825 dp.height = UnScaleByZoom(dp.height, zoom);
1826 AutoRestoreBackup cur_dpi(_cur_dpi, &dp);
1827
1828 if (vp.overlay != nullptr && vp.overlay->GetCargoMask() != 0 && vp.overlay->GetCompanyMask().Any()) {
1829 /* translate to window coordinates */
1830 dp.left = x;
1831 dp.top = y;
1832 vp.overlay->Draw(&dp);
1833 }
1834
1835 if (!_vd.string_sprites_to_draw.empty()) {
1836 /* translate to world coordinates */
1837 dp.left = UnScaleByZoom(_vd.dpi.left, zoom);
1838 dp.top = UnScaleByZoom(_vd.dpi.top, zoom);
1839 ViewportDrawStrings(zoom, &_vd.string_sprites_to_draw);
1840 }
1841
1842 _vd.string_sprites_to_draw.clear();
1843 _vd.tile_sprites_to_draw.clear();
1844 _vd.parent_sprites_to_draw.clear();
1845 _vd.parent_sprites_to_sort.clear();
1846 _vd.child_screen_sprites_to_draw.clear();
1847}
1848
1849static inline void ViewportDraw(const Viewport &vp, int left, int top, int right, int bottom)
1850{
1851 if (right <= vp.left || bottom <= vp.top) return;
1852
1853 if (left >= vp.left + vp.width) return;
1854
1855 if (left < vp.left) left = vp.left;
1856 if (right > vp.left + vp.width) right = vp.left + vp.width;
1857
1858 if (top >= vp.top + vp.height) return;
1859
1860 if (top < vp.top) top = vp.top;
1861 if (bottom > vp.top + vp.height) bottom = vp.top + vp.height;
1862
1863 ViewportDoDraw(vp,
1864 ScaleByZoom(left - vp.left, vp.zoom) + vp.virtual_left,
1865 ScaleByZoom(top - vp.top, vp.zoom) + vp.virtual_top,
1866 ScaleByZoom(right - vp.left, vp.zoom) + vp.virtual_left,
1867 ScaleByZoom(bottom - vp.top, vp.zoom) + vp.virtual_top
1868 );
1869}
1870
1875{
1877
1878 DrawPixelInfo *dpi = _cur_dpi;
1879
1880 dpi->left += this->left;
1881 dpi->top += this->top;
1882
1883 ViewportDraw(*this->viewport, dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height);
1884
1885 dpi->left -= this->left;
1886 dpi->top -= this->top;
1887}
1888
1899static inline void ClampViewportToMap(const Viewport &vp, int *scroll_x, int *scroll_y)
1900{
1901 /* Centre of the viewport is hot spot. */
1902 Point pt = {
1903 *scroll_x + vp.virtual_width / 2,
1904 *scroll_y + vp.virtual_height / 2
1905 };
1906
1907 /* Find nearest tile that is within borders of the map. */
1908 bool clamped;
1909 pt = InverseRemapCoords2(pt.x, pt.y, true, &clamped);
1910
1911 if (clamped) {
1912 /* Convert back to viewport coordinates and remove centering. */
1913 pt = RemapCoords2(pt.x, pt.y);
1914 *scroll_x = pt.x - vp.virtual_width / 2;
1915 *scroll_y = pt.y - vp.virtual_height / 2;
1916 }
1917}
1918
1931static void ClampSmoothScroll(uint32_t delta_ms, int64_t delta_hi, int64_t delta_lo, int &delta_hi_clamped, int &delta_lo_clamped)
1932{
1934 constexpr int PIXELS_PER_TILE = TILE_PIXELS * 2 * ZOOM_BASE;
1935
1936 assert(delta_hi != 0);
1937
1938 /* Move at most 75% of the distance every 30ms, for a smooth experience */
1939 int64_t delta_left = delta_hi * std::pow(0.75, delta_ms / 30.0);
1940 /* Move never more than 16 tiles per 30ms. */
1941 int max_scroll = Map::ScaleBySize1D(16 * PIXELS_PER_TILE * delta_ms / 30);
1942
1943 /* We never go over the max_scroll speed. */
1944 delta_hi_clamped = Clamp(delta_hi - delta_left, -max_scroll, max_scroll);
1945 /* The lower delta is in ratio of the higher delta, so we keep going straight at the destination. */
1946 delta_lo_clamped = delta_lo * delta_hi_clamped / delta_hi;
1947
1948 /* Ensure we always move (delta_hi can't be zero). */
1949 if (delta_hi_clamped == 0) {
1950 delta_hi_clamped = delta_hi > 0 ? 1 : -1;
1951 }
1952}
1953
1959void UpdateViewportPosition(Window *w, uint32_t delta_ms)
1960{
1961 ViewportData &vp = *w->viewport;
1962
1963 if (vp.follow_vehicle != VehicleID::Invalid()) {
1964 const Vehicle *veh = Vehicle::Get(vp.follow_vehicle);
1965 Point pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
1966
1967 vp.scrollpos_x = pt.x;
1968 vp.scrollpos_y = pt.y;
1969 SetViewportPosition(w, pt.x, pt.y);
1970 } else {
1971 /* Ensure the destination location is within the map */
1973
1974 int delta_x = vp.dest_scrollpos_x - vp.scrollpos_x;
1975 int delta_y = vp.dest_scrollpos_y - vp.scrollpos_y;
1976
1977 int current_x = vp.scrollpos_x;
1978 int current_y = vp.scrollpos_y;
1979
1980 bool update_overlay = false;
1981 if (delta_x != 0 || delta_y != 0) {
1983 int delta_x_clamped;
1984 int delta_y_clamped;
1985
1986 if (abs(delta_x) > abs(delta_y)) {
1987 ClampSmoothScroll(delta_ms, delta_x, delta_y, delta_x_clamped, delta_y_clamped);
1988 } else {
1989 ClampSmoothScroll(delta_ms, delta_y, delta_x, delta_y_clamped, delta_x_clamped);
1990 }
1991
1992 vp.scrollpos_x += delta_x_clamped;
1993 vp.scrollpos_y += delta_y_clamped;
1994 } else {
1997 }
1998 update_overlay = (vp.scrollpos_x == vp.dest_scrollpos_x &&
1999 vp.scrollpos_y == vp.dest_scrollpos_y);
2000 }
2001
2003
2004 /* When moving small amounts around the border we can get stuck, and
2005 * not actually move. In those cases, teleport to the destination. */
2006 if ((delta_x != 0 || delta_y != 0) && current_x == vp.scrollpos_x && current_y == vp.scrollpos_y) {
2009 }
2010
2011 SetViewportPosition(w, vp.scrollpos_x, vp.scrollpos_y);
2012 if (update_overlay) RebuildViewportOverlay(w);
2013 }
2014}
2015
2026static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom)
2027{
2028 /* Rounding wrt. zoom-out level */
2029 right += (1 << to_underlying(vp.zoom)) - 1;
2030 bottom += (1 << to_underlying(vp.zoom)) - 1;
2031
2032 right -= vp.virtual_left;
2033 if (right <= 0) return false;
2034
2035 bottom -= vp.virtual_top;
2036 if (bottom <= 0) return false;
2037
2038 left = std::max(0, left - vp.virtual_left);
2039
2040 if (left >= vp.virtual_width) return false;
2041
2042 top = std::max(0, top - vp.virtual_top);
2043
2044 if (top >= vp.virtual_height) return false;
2045
2047 UnScaleByZoomLower(left, vp.zoom) + vp.left,
2048 UnScaleByZoomLower(top, vp.zoom) + vp.top,
2049 UnScaleByZoom(right, vp.zoom) + vp.left + 1,
2050 UnScaleByZoom(bottom, vp.zoom) + vp.top + 1
2051 );
2052
2053 return true;
2054}
2055
2065bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
2066{
2067 bool dirty = false;
2068
2069 for (const Window *w : Window::Iterate()) {
2070 if (w->viewport != nullptr) {
2071 assert(w->viewport->width != 0);
2072 if (MarkViewportDirty(*w->viewport, left, top, right, bottom)) dirty = true;
2073 }
2074 }
2075
2076 return dirty;
2077}
2078
2079void ConstrainAllViewportsZoom()
2080{
2081 for (Window *w : Window::Iterate()) {
2082 if (w->viewport == nullptr) continue;
2083
2085 if (zoom != w->viewport->zoom) {
2086 while (w->viewport->zoom < zoom) DoZoomInOutWindow(ZOOM_OUT, w);
2087 while (w->viewport->zoom > zoom) DoZoomInOutWindow(ZOOM_IN, w);
2088 }
2089 }
2090}
2091
2099void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
2100{
2101 Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, tile_height_override * TILE_HEIGHT);
2103 pt.x - MAX_TILE_EXTENT_LEFT,
2104 pt.y - MAX_TILE_EXTENT_TOP - ZOOM_BASE * TILE_HEIGHT * bridge_level_offset,
2105 pt.x + MAX_TILE_EXTENT_RIGHT,
2106 pt.y + MAX_TILE_EXTENT_BOTTOM);
2107}
2108
2117{
2118 int x_size = _thd.size.x;
2119 int y_size = _thd.size.y;
2120
2121 if (!_thd.diagonal) { // Selecting in a straight rectangle (or a single square)
2122 int x_start = _thd.pos.x;
2123 int y_start = _thd.pos.y;
2124
2125 if (_thd.outersize.x != 0) {
2126 x_size += _thd.outersize.x;
2127 x_start += _thd.offs.x;
2128 y_size += _thd.outersize.y;
2129 y_start += _thd.offs.y;
2130 }
2131
2132 x_size -= TILE_SIZE;
2133 y_size -= TILE_SIZE;
2134
2135 assert(x_size >= 0);
2136 assert(y_size >= 0);
2137
2138 int x_end = Clamp(x_start + x_size, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2139 int y_end = Clamp(y_start + y_size, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2140
2141 x_start = Clamp(x_start, 0, Map::SizeX() * TILE_SIZE - TILE_SIZE);
2142 y_start = Clamp(y_start, 0, Map::SizeY() * TILE_SIZE - TILE_SIZE);
2143
2144 /* make sure everything is multiple of TILE_SIZE */
2145 assert((x_end | y_end | x_start | y_start) % TILE_SIZE == 0);
2146
2147 /* How it works:
2148 * Suppose we have to mark dirty rectangle of 3x4 tiles:
2149 * x
2150 * xxx
2151 * xxxxx
2152 * xxxxx
2153 * xxx
2154 * x
2155 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
2156 * 1) x 2) x
2157 * xxx Oxx
2158 * Oxxxx xOxxx
2159 * xxxxx Oxxxx
2160 * xxx xxx
2161 * x x
2162 * And so forth...
2163 */
2164
2165 int top_x = x_end; // coordinates of top dirty tile
2166 int top_y = y_start;
2167 int bot_x = top_x; // coordinates of bottom dirty tile
2168 int bot_y = top_y;
2169
2170 do {
2171 /* topmost dirty point */
2172 TileIndex top_tile = TileVirtXY(top_x, top_y);
2173 Point top = RemapCoords(top_x, top_y, GetTileMaxPixelZ(top_tile));
2174
2175 /* bottommost point */
2176 TileIndex bottom_tile = TileVirtXY(bot_x, bot_y);
2177 Point bot = RemapCoords(bot_x + TILE_SIZE, bot_y + TILE_SIZE, GetTilePixelZ(bottom_tile)); // bottommost point
2178
2179 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
2180 * tile height/slope affects only the 'y' on-screen coordinate! */
2181
2182 int l = top.x - TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of left side of the dirty rectangle
2183 int t = top.y; // 'y' coordinate of top side of the dirty rectangle
2184 int r = top.x + TILE_PIXELS * ZOOM_BASE; // 'x' coordinate of right side of the dirty rectangle
2185 int b = bot.y; // 'y' coordinate of bottom side of the dirty rectangle
2186
2187 static const int OVERLAY_WIDTH = 4 * ZOOM_BASE; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
2188
2189 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
2190 MarkAllViewportsDirty(l - OVERLAY_WIDTH, t - OVERLAY_WIDTH - TILE_HEIGHT * ZOOM_BASE, r + OVERLAY_WIDTH, b + OVERLAY_WIDTH);
2191
2192 /* haven't we reached the topmost tile yet? */
2193 if (top_x != x_start) {
2194 top_x -= TILE_SIZE;
2195 } else {
2196 top_y += TILE_SIZE;
2197 }
2198
2199 /* the way the bottom tile changes is different when we reach the bottommost tile */
2200 if (bot_y != y_end) {
2201 bot_y += TILE_SIZE;
2202 } else {
2203 bot_x -= TILE_SIZE;
2204 }
2205 } while (bot_x >= top_x);
2206 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
2207 /* a_size, b_size describe a rectangle with rotated coordinates */
2208 int a_size = x_size + y_size, b_size = x_size - y_size;
2209
2210 int interval_a = a_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2211 int interval_b = b_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2212
2213 for (int a = -interval_a; a != a_size + interval_a; a += interval_a) {
2214 for (int b = -interval_b; b != b_size + interval_b; b += interval_b) {
2215 uint x = (_thd.pos.x + (a + b) / 2) / TILE_SIZE;
2216 uint y = (_thd.pos.y + (a - b) / 2) / TILE_SIZE;
2217
2218 if (x < Map::MaxX() && y < Map::MaxY()) {
2220 }
2221 }
2222 }
2223 }
2224}
2225
2226
2227void SetSelectionRed(bool b)
2228{
2229 _thd.make_square_red = b;
2231}
2232
2241static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y, const ViewportSign *sign)
2242{
2243 bool small = (vp.zoom >= ZoomLevel::Out4x);
2244 int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, vp.zoom);
2245 int sign_height = ScaleByZoom(WidgetDimensions::scaled.fullbevel.top + GetCharacterHeight(small ? FS_SMALL : FS_NORMAL) + WidgetDimensions::scaled.fullbevel.bottom, vp.zoom);
2246
2247 return y >= sign->top && y < sign->top + sign_height &&
2248 x >= sign->center - sign_half_width && x < sign->center + sign_half_width;
2249}
2250
2251
2259static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y)
2260{
2261 if (_game_mode == GM_MENU) return false;
2262
2263 x = ScaleByZoom(x - vp.left, vp.zoom) + vp.virtual_left;
2264 y = ScaleByZoom(y - vp.top, vp.zoom) + vp.virtual_top;
2265
2266 Rect search_rect{ x - 1, y - 1, x + 1, y + 1 };
2267 search_rect = ExpandRectWithViewportSignMargins(search_rect, vp.zoom);
2268
2271 bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES);
2273 bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
2274
2275 /* Topmost of each type that was hit */
2276 BaseStation *st = nullptr, *last_st = nullptr;
2277 Town *t = nullptr, *last_t = nullptr;
2278 Sign *si = nullptr, *last_si = nullptr;
2279
2280 /* See ViewportAddKdtreeSigns() for details on the search logic */
2281 _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
2282 switch (item.type) {
2283 case ViewportSignKdtreeItem::VKI_STATION:
2284 if (!show_stations) break;
2285 st = BaseStation::Get(std::get<StationID>(item.id));
2286 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2287 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2288 break;
2289
2290 case ViewportSignKdtreeItem::VKI_WAYPOINT:
2291 if (!show_waypoints) break;
2292 st = BaseStation::Get(std::get<StationID>(item.id));
2293 if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2294 if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2295 break;
2296
2297 case ViewportSignKdtreeItem::VKI_TOWN:
2298 if (!show_towns) break;
2299 t = Town::Get(std::get<TownID>(item.id));
2300 if (CheckClickOnViewportSign(vp, x, y, &t->cache.sign)) last_t = t;
2301 break;
2302
2303 case ViewportSignKdtreeItem::VKI_SIGN:
2304 if (!show_signs) break;
2305 si = Sign::Get(std::get<SignID>(item.id));
2306 if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
2307 if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si;
2308 break;
2309
2310 default:
2311 NOT_REACHED();
2312 }
2313 });
2314
2315 /* Select which hit to handle based on priority */
2316 if (last_st != nullptr) {
2317 if (Station::IsExpected(last_st)) {
2318 ShowStationViewWindow(last_st->index);
2319 } else {
2321 }
2322 return true;
2323 } else if (last_t != nullptr) {
2324 ShowTownViewWindow(last_t->index);
2325 return true;
2326 } else if (last_si != nullptr) {
2327 HandleClickOnSign(last_si);
2328 return true;
2329 } else {
2330 return false;
2331 }
2332}
2333
2334
2335ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id)
2336{
2338 item.type = VKI_STATION;
2339 item.id = id;
2340
2341 const Station *st = Station::Get(id);
2342 assert(st->sign.kdtree_valid);
2343 item.center = st->sign.center;
2344 item.top = st->sign.top;
2345
2346 /* Assume the sign can be a candidate for drawing, so measure its width */
2347 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2348
2349 return item;
2350}
2351
2352ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id)
2353{
2355 item.type = VKI_WAYPOINT;
2356 item.id = id;
2357
2358 const Waypoint *st = Waypoint::Get(id);
2359 assert(st->sign.kdtree_valid);
2360 item.center = st->sign.center;
2361 item.top = st->sign.top;
2362
2363 /* Assume the sign can be a candidate for drawing, so measure its width */
2364 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, st->sign.width_normal, st->sign.width_small});
2365
2366 return item;
2367}
2368
2369ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeTown(TownID id)
2370{
2372 item.type = VKI_TOWN;
2373 item.id = id;
2374
2375 const Town *town = Town::Get(id);
2376 assert(town->cache.sign.kdtree_valid);
2377 item.center = town->cache.sign.center;
2378 item.top = town->cache.sign.top;
2379
2380 /* Assume the sign can be a candidate for drawing, so measure its width */
2381 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, town->cache.sign.width_normal, town->cache.sign.width_small});
2382
2383 return item;
2384}
2385
2386ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeSign(SignID id)
2387{
2389 item.type = VKI_SIGN;
2390 item.id = id;
2391
2392 const Sign *sign = Sign::Get(id);
2393 assert(sign->sign.kdtree_valid);
2394 item.center = sign->sign.center;
2395 item.top = sign->sign.top;
2396
2397 /* Assume the sign can be a candidate for drawing, so measure its width */
2398 _viewport_sign_maxwidth = std::max<int>({_viewport_sign_maxwidth, sign->sign.width_normal, sign->sign.width_small});
2399
2400 return item;
2401}
2402
2403void RebuildViewportKdtree()
2404{
2405 /* Reset biggest size sign seen */
2406 _viewport_sign_maxwidth = 0;
2407
2408 std::vector<ViewportSignKdtreeItem> items;
2410
2411 for (const Station *st : Station::Iterate()) {
2412 if (st->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeStation(st->index));
2413 }
2414
2415 for (const Waypoint *wp : Waypoint::Iterate()) {
2416 if (wp->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeWaypoint(wp->index));
2417 }
2418
2419 for (const Town *town : Town::Iterate()) {
2420 if (town->cache.sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeTown(town->index));
2421 }
2422
2423 for (const Sign *sign : Sign::Iterate()) {
2424 if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index));
2425 }
2426
2427 _viewport_sign_kdtree.Build(items.begin(), items.end());
2428}
2429
2430
2431static bool CheckClickOnLandscape(const Viewport &vp, int x, int y)
2432{
2433 Point pt = TranslateXYToTileCoord(vp, x, y);
2434
2435 if (pt.x != -1) return ClickTile(TileVirtXY(pt.x, pt.y));
2436 return true;
2437}
2438
2439static void PlaceObject()
2440{
2441 Point pt;
2442 Window *w;
2443
2444 pt = GetTileBelowCursor();
2445 if (pt.x == -1) return;
2446
2447 if ((_thd.place_mode & HT_DRAG_MASK) == HT_POINT) {
2448 pt.x += TILE_SIZE / 2;
2449 pt.y += TILE_SIZE / 2;
2450 }
2451
2452 _tile_fract_coords.x = pt.x & TILE_UNIT_MASK;
2453 _tile_fract_coords.y = pt.y & TILE_UNIT_MASK;
2454
2455 w = _thd.GetCallbackWnd();
2456 if (w != nullptr) w->OnPlaceObject(pt, TileVirtXY(pt.x, pt.y));
2457}
2458
2459
2460bool HandleViewportClicked(const Viewport &vp, int x, int y)
2461{
2462 const Vehicle *v = CheckClickOnVehicle(vp, x, y);
2463
2464 if (_thd.place_mode & HT_VEHICLE) {
2465 if (v != nullptr && VehicleClicked(v)) return true;
2466 }
2467
2468 /* Vehicle placement mode already handled above. */
2469 if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2470 PlaceObject();
2471 return true;
2472 }
2473
2474 if (CheckClickOnViewportSign(vp, x, y)) return true;
2475 bool result = CheckClickOnLandscape(vp, x, y);
2476
2477 if (v != nullptr) {
2478 Debug(misc, 2, "Vehicle {} (index {}) at {}", v->unitnumber, v->index, fmt::ptr(v));
2480 v = v->First();
2481 if (_ctrl_pressed && v->owner == _local_company) {
2482 StartStopVehicle(v, true);
2483 } else {
2485 }
2486 }
2487 return true;
2488 }
2489 return result;
2490}
2491
2492void RebuildViewportOverlay(Window *w)
2493{
2494 if (w->viewport->overlay != nullptr &&
2495 w->viewport->overlay->GetCompanyMask().Any() &&
2496 w->viewport->overlay->GetCargoMask() != 0) {
2497 w->viewport->overlay->SetDirty();
2498 w->SetDirty();
2499 }
2500}
2501
2511bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
2512{
2513 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2514 if (z == -1) {
2515 if ( x >= 0 && x <= (int)Map::SizeX() * (int)TILE_SIZE - 1
2516 && y >= 0 && y <= (int)Map::SizeY() * (int)TILE_SIZE - 1) {
2517 z = GetSlopePixelZ(x, y);
2518 } else {
2519 z = TileHeightOutsideMap(x / (int)TILE_SIZE, y / (int)TILE_SIZE);
2520 }
2521 }
2522
2523 Point pt = MapXYZToViewport(*w->viewport, x, y, z);
2524 w->viewport->CancelFollow(*w);
2525
2526 if (w->viewport->dest_scrollpos_x == pt.x && w->viewport->dest_scrollpos_y == pt.y) return false;
2527
2528 if (instant) {
2529 w->viewport->scrollpos_x = pt.x;
2530 w->viewport->scrollpos_y = pt.y;
2531 RebuildViewportOverlay(w);
2532 }
2533
2534 w->viewport->dest_scrollpos_x = pt.x;
2535 w->viewport->dest_scrollpos_y = pt.y;
2536 return true;
2537}
2538
2546bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
2547{
2548 return ScrollWindowTo(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, -1, w, instant);
2549}
2550
2557bool ScrollMainWindowToTile(TileIndex tile, bool instant)
2558{
2559 return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, -1, instant);
2560}
2561
2567{
2568 TileIndex old;
2569
2570 old = _thd.redsq;
2571 _thd.redsq = tile;
2572
2573 if (tile != old) {
2574 if (tile != INVALID_TILE) MarkTileDirtyByTile(tile);
2575 if (old != INVALID_TILE) MarkTileDirtyByTile(old);
2576 }
2577}
2578
2584void SetTileSelectSize(int w, int h)
2585{
2586 _thd.new_size.x = w * TILE_SIZE;
2587 _thd.new_size.y = h * TILE_SIZE;
2588 _thd.new_outersize.x = 0;
2589 _thd.new_outersize.y = 0;
2590}
2591
2592void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
2593{
2594 _thd.offs.x = ox * TILE_SIZE;
2595 _thd.offs.y = oy * TILE_SIZE;
2596 _thd.new_outersize.x = sx * TILE_SIZE;
2597 _thd.new_outersize.y = sy * TILE_SIZE;
2598}
2599
2601static HighLightStyle GetAutorailHT(int x, int y)
2602{
2603 return HT_RAIL | _autorail_piece[x & TILE_UNIT_MASK][y & TILE_UNIT_MASK];
2604}
2605
2610{
2611 this->pos.x = 0;
2612 this->pos.y = 0;
2613 this->new_pos.x = 0;
2614 this->new_pos.y = 0;
2615}
2616
2625
2634
2635
2636
2645{
2646 int x1;
2647 int y1;
2648
2649 if (_thd.freeze) return;
2650
2651 HighLightStyle new_drawstyle = HT_NONE;
2652 bool new_diagonal = false;
2653
2654 if ((_thd.place_mode & HT_DRAG_MASK) == HT_SPECIAL) {
2655 x1 = _thd.selend.x;
2656 y1 = _thd.selend.y;
2657 if (x1 != -1) {
2658 int x2 = _thd.selstart.x & ~TILE_UNIT_MASK;
2659 int y2 = _thd.selstart.y & ~TILE_UNIT_MASK;
2660 x1 &= ~TILE_UNIT_MASK;
2661 y1 &= ~TILE_UNIT_MASK;
2662
2663 if (_thd.IsDraggingDiagonal()) {
2664 new_diagonal = true;
2665 } else {
2666 if (x1 >= x2) std::swap(x1, x2);
2667 if (y1 >= y2) std::swap(y1, y2);
2668 }
2669 _thd.new_pos.x = x1;
2670 _thd.new_pos.y = y1;
2671 _thd.new_size.x = x2 - x1;
2672 _thd.new_size.y = y2 - y1;
2673 if (!new_diagonal) {
2674 _thd.new_size.x += TILE_SIZE;
2675 _thd.new_size.y += TILE_SIZE;
2676 }
2677 new_drawstyle = _thd.next_drawstyle;
2678 }
2679 } else if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2680 Point pt = GetTileBelowCursor();
2681 x1 = pt.x;
2682 y1 = pt.y;
2683 if (x1 != -1) {
2684 switch (_thd.place_mode & HT_DRAG_MASK) {
2685 case HT_RECT:
2686 new_drawstyle = HT_RECT;
2687 break;
2688 case HT_POINT:
2689 new_drawstyle = HT_POINT;
2690 x1 += TILE_SIZE / 2;
2691 y1 += TILE_SIZE / 2;
2692 break;
2693 case HT_RAIL:
2694 /* Draw one highlighted tile in any direction */
2695 new_drawstyle = GetAutorailHT(pt.x, pt.y);
2696 break;
2697 case HT_LINE:
2698 switch (_thd.place_mode & HT_DIR_MASK) {
2699 case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
2700 case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
2701
2702 case HT_DIR_HU:
2703 case HT_DIR_HL:
2704 new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
2705 break;
2706
2707 case HT_DIR_VL:
2708 case HT_DIR_VR:
2709 new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2710 break;
2711
2712 default: NOT_REACHED();
2713 }
2714 _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
2715 _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
2716 break;
2717 default:
2718 NOT_REACHED();
2719 }
2720 _thd.new_pos.x = x1 & ~TILE_UNIT_MASK;
2721 _thd.new_pos.y = y1 & ~TILE_UNIT_MASK;
2722 }
2723 }
2724
2725 /* redraw selection */
2726 if (_thd.drawstyle != new_drawstyle ||
2727 _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
2728 _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
2729 _thd.outersize.x != _thd.new_outersize.x ||
2730 _thd.outersize.y != _thd.new_outersize.y ||
2731 _thd.diagonal != new_diagonal) {
2732 /* Clear the old tile selection? */
2734
2735 _thd.drawstyle = new_drawstyle;
2736 _thd.pos = _thd.new_pos;
2737 _thd.size = _thd.new_size;
2738 _thd.outersize = _thd.new_outersize;
2739 _thd.diagonal = new_diagonal;
2740 _thd.dirty = 0xff;
2741
2742 /* Draw the new tile selection? */
2743 if ((new_drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2744 }
2745}
2746
2751static inline void ShowMeasurementTooltips(EncodedString &&text)
2752{
2754 GuiShowTooltips(_thd.GetCallbackWnd(), std::move(text), TCC_EXIT_VIEWPORT);
2755}
2756
2757static void HideMeasurementTooltips()
2758{
2760}
2761
2764{
2765 _thd.select_method = method;
2766 _thd.select_proc = process;
2767 _thd.selend.x = TileX(tile) * TILE_SIZE;
2768 _thd.selstart.x = TileX(tile) * TILE_SIZE;
2769 _thd.selend.y = TileY(tile) * TILE_SIZE;
2770 _thd.selstart.y = TileY(tile) * TILE_SIZE;
2771
2772 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2773 * In effect, placement starts from the centre of a tile
2774 */
2775 if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
2776 _thd.selend.x += TILE_SIZE / 2;
2777 _thd.selend.y += TILE_SIZE / 2;
2778 _thd.selstart.x += TILE_SIZE / 2;
2779 _thd.selstart.y += TILE_SIZE / 2;
2780 }
2781
2783 if ((_thd.place_mode & HT_DRAG_MASK) == HT_RECT) {
2784 _thd.place_mode = HT_SPECIAL | others;
2785 _thd.next_drawstyle = HT_RECT | others;
2786 } else if (_thd.place_mode & (HT_RAIL | HT_LINE)) {
2787 _thd.place_mode = HT_SPECIAL | others;
2788 _thd.next_drawstyle = _thd.drawstyle | others;
2789 } else {
2790 _thd.place_mode = HT_SPECIAL | others;
2791 _thd.next_drawstyle = HT_POINT | others;
2792 }
2794}
2795
2798{
2800 _thd.select_proc = process;
2801 _thd.selstart.x = 0;
2802 _thd.selstart.y = 0;
2803 _thd.next_drawstyle = HT_RECT;
2804
2806}
2807
2808void VpSetPlaceSizingLimit(int limit)
2809{
2810 _thd.sizelimit = limit;
2811}
2812
2819{
2820 uint64_t distance = DistanceManhattan(from, to) + 1;
2821
2822 _thd.selend.x = TileX(to) * TILE_SIZE;
2823 _thd.selend.y = TileY(to) * TILE_SIZE;
2824 _thd.selstart.x = TileX(from) * TILE_SIZE;
2825 _thd.selstart.y = TileY(from) * TILE_SIZE;
2826 _thd.next_drawstyle = HT_RECT;
2827
2828 /* show measurement only if there is any length to speak of */
2829 if (distance > 1) {
2830 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
2831 } else {
2832 HideMeasurementTooltips();
2833 }
2834}
2835
2836static void VpStartPreSizing()
2837{
2838 _thd.selend.x = -1;
2840}
2841
2847{
2848 int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
2849 int sxpy = (_thd.selend.x & TILE_UNIT_MASK) + (_thd.selend.y & TILE_UNIT_MASK);
2850 int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
2851 int sxmy = (_thd.selend.x & TILE_UNIT_MASK) - (_thd.selend.y & TILE_UNIT_MASK);
2852
2853 switch (mode) {
2854 default: NOT_REACHED();
2855 case 0: // end piece is lower right
2856 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2857 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2858 return HT_DIR_Y;
2859
2860 case 1:
2861 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2862 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2863 return HT_DIR_Y;
2864
2865 case 2:
2866 if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2867 if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2868 return HT_DIR_X;
2869
2870 case 3:
2871 if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2872 if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2873 return HT_DIR_X;
2874 }
2875}
2876
2890static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
2891{
2892 uint start_x = TileX(start_tile);
2893 uint start_y = TileY(start_tile);
2894 uint end_x = TileX(end_tile);
2895 uint end_y = TileY(end_tile);
2896
2897 switch (style & HT_DRAG_MASK) {
2898 case HT_RAIL:
2899 case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
2900
2901 case HT_RECT:
2902 case HT_POINT: return (end_x != start_x && end_y < start_y);
2903 default: NOT_REACHED();
2904 }
2905
2906 return false;
2907}
2908
2924static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
2925{
2926 bool swap = SwapDirection(style, start_tile, end_tile);
2927 uint h0, h1; // Start height and end height.
2928
2929 if (start_tile == end_tile) return 0;
2930 if (swap) std::swap(start_tile, end_tile);
2931
2932 switch (style & HT_DRAG_MASK) {
2933 case HT_RECT:
2934 /* In the case of an area we can determine whether we were dragging south or
2935 * east by checking the X-coordinates of the tiles */
2936 if (TileX(end_tile) > TileX(start_tile)) {
2937 /* Dragging south does not need to change the start tile. */
2938 end_tile = TileAddByDir(end_tile, DIR_S);
2939 } else {
2940 /* Dragging east. */
2941 start_tile = TileAddByDir(start_tile, DIR_SW);
2942 end_tile = TileAddByDir(end_tile, DIR_SE);
2943 }
2944 [[fallthrough]];
2945
2946 case HT_POINT:
2947 h0 = TileHeight(start_tile);
2948 h1 = TileHeight(end_tile);
2949 break;
2950 default: { // All other types, this is mostly only line/autorail
2951 static const HighLightStyle flip_style_direction[] = {
2953 };
2954 static const std::pair<TileIndexDiffC, TileIndexDiffC> start_heightdiff_line_by_dir[] = {
2955 { {1, 0}, {1, 1} }, // HT_DIR_X
2956 { {0, 1}, {1, 1} }, // HT_DIR_Y
2957 { {1, 0}, {0, 0} }, // HT_DIR_HU
2958 { {1, 0}, {1, 1} }, // HT_DIR_HL
2959 { {1, 0}, {1, 1} }, // HT_DIR_VL
2960 { {0, 1}, {1, 1} }, // HT_DIR_VR
2961 };
2962 static const std::pair<TileIndexDiffC, TileIndexDiffC> end_heightdiff_line_by_dir[] = {
2963 { {0, 1}, {0, 0} }, // HT_DIR_X
2964 { {1, 0}, {0, 0} }, // HT_DIR_Y
2965 { {0, 1}, {0, 0} }, // HT_DIR_HU
2966 { {1, 1}, {0, 1} }, // HT_DIR_HL
2967 { {1, 0}, {0, 0} }, // HT_DIR_VL
2968 { {0, 0}, {0, 1} }, // HT_DIR_VR
2969 };
2970 static_assert(std::size(start_heightdiff_line_by_dir) == HT_DIR_END);
2971 static_assert(std::size(end_heightdiff_line_by_dir) == HT_DIR_END);
2972
2973 distance %= 2; // we're only interested if the distance is even or uneven
2974 style &= HT_DIR_MASK;
2975 assert(style < HT_DIR_END);
2976
2977 /* To handle autorail, we do some magic to be able to use a lookup table.
2978 * Firstly if we drag the other way around, we switch start&end, and if needed
2979 * also flip the drag-position. Eg if it was on the left, and the distance is even
2980 * that means the end, which is now the start is on the right */
2981 if (swap && distance == 0) style = flip_style_direction[style];
2982
2983 /* Lambda to help calculating the height at one side of the line. */
2984 auto get_height = [](auto &tile, auto &heightdiffs) {
2985 return std::max(
2986 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.first))),
2987 TileHeight(TileAdd(tile, ToTileIndexDiff(heightdiffs.second))));
2988 };
2989
2990 /* Use lookup table for start-tile based on HighLightStyle direction */
2991 h0 = get_height(start_tile, start_heightdiff_line_by_dir[style]);
2992
2993 /* Use lookup table for end-tile based on HighLightStyle direction
2994 * flip around side (lower/upper, left/right) based on distance */
2995 if (distance == 0) style = flip_style_direction[style];
2996 h1 = get_height(end_tile, end_heightdiff_line_by_dir[style]);
2997 break;
2998 }
2999 }
3000
3001 if (swap) std::swap(h0, h1);
3002 return (int)(h1 - h0) * TILE_HEIGHT_STEP;
3003}
3004
3011static void CheckUnderflow(int &test, int &other, int mult)
3012{
3013 if (test >= 0) return;
3014
3015 other += mult * test;
3016 test = 0;
3017}
3018
3026static void CheckOverflow(int &test, int &other, int max, int mult)
3027{
3028 if (test <= max) return;
3029
3030 other += mult * (test - max);
3031 test = max;
3032}
3033
3035static void CalcRaildirsDrawstyle(int x, int y, int method)
3036{
3038
3039 int dx = _thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK);
3040 int dy = _thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK);
3041 uint w = abs(dx) + TILE_SIZE;
3042 uint h = abs(dy) + TILE_SIZE;
3043
3044 if (method & ~(VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3045 /* We 'force' a selection direction; first four rail buttons. */
3046 method &= ~(VPM_RAILDIRS | VPM_SIGNALDIRS);
3047 int raw_dx = _thd.selstart.x - _thd.selend.x;
3048 int raw_dy = _thd.selstart.y - _thd.selend.y;
3049 switch (method) {
3050 case VPM_FIX_X:
3051 b = HT_LINE | HT_DIR_Y;
3052 x = _thd.selstart.x;
3053 break;
3054
3055 case VPM_FIX_Y:
3056 b = HT_LINE | HT_DIR_X;
3057 y = _thd.selstart.y;
3058 break;
3059
3060 case VPM_FIX_HORIZONTAL:
3061 if (dx == -dy) {
3062 /* We are on a straight horizontal line. Determine the 'rail'
3063 * to build based the sub tile location. */
3065 } else {
3066 /* We are not on a straight line. Determine the rail to build
3067 * based on whether we are above or below it. */
3068 b = dx + dy >= (int)TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
3069
3070 /* Calculate where a horizontal line through the start point and
3071 * a vertical line from the selected end point intersect and
3072 * use that point as the end point. */
3073 int offset = (raw_dx - raw_dy) / 2;
3074 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3075 y = _thd.selstart.y + (offset & ~TILE_UNIT_MASK);
3076
3077 /* 'Build' the last half rail tile if needed */
3078 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3079 if (dx + dy >= (int)TILE_SIZE) {
3080 x -= (int)TILE_SIZE;
3081 } else {
3082 y += (int)TILE_SIZE;
3083 }
3084 }
3085
3086 /* Make sure we do not overflow the map! */
3087 CheckUnderflow(x, y, 1);
3088 CheckUnderflow(y, x, 1);
3089 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, 1);
3090 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, 1);
3091 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3092 }
3093 break;
3094
3095 case VPM_FIX_VERTICAL:
3096 if (dx == dy) {
3097 /* We are on a straight vertical line. Determine the 'rail'
3098 * to build based the sub tile location. */
3100 } else {
3101 /* We are not on a straight line. Determine the rail to build
3102 * based on whether we are left or right from it. */
3103 b = dx < dy ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
3104
3105 /* Calculate where a vertical line through the start point and
3106 * a horizontal line from the selected end point intersect and
3107 * use that point as the end point. */
3108 int offset = (raw_dx + raw_dy + (int)TILE_SIZE) / 2;
3109 x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
3110 y = _thd.selstart.y - (offset & ~TILE_UNIT_MASK);
3111
3112 /* 'Build' the last half rail tile if needed */
3113 if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
3114 if (dx < dy) {
3115 y -= (int)TILE_SIZE;
3116 } else {
3117 x -= (int)TILE_SIZE;
3118 }
3119 }
3120
3121 /* Make sure we do not overflow the map! */
3122 CheckUnderflow(x, y, -1);
3123 CheckUnderflow(y, x, -1);
3124 CheckOverflow(x, y, (Map::MaxX() - 1) * TILE_SIZE, -1);
3125 CheckOverflow(y, x, (Map::MaxY() - 1) * TILE_SIZE, -1);
3126 assert(x >= 0 && y >= 0 && x <= (int)(Map::MaxX() * TILE_SIZE) && y <= (int)(Map::MaxY() * TILE_SIZE));
3127 }
3128 break;
3129
3130 default:
3131 NOT_REACHED();
3132 }
3133 } else if (TileVirtXY(_thd.selstart.x, _thd.selstart.y) == TileVirtXY(x, y)) { // check if we're only within one tile
3134 if (method & VPM_RAILDIRS) {
3135 b = GetAutorailHT(x, y);
3136 } else { // rect for autosignals on one tile
3137 b = HT_RECT;
3138 }
3139 } else if (h == TILE_SIZE) { // Is this in X direction?
3140 if (dx == (int)TILE_SIZE) { // 2x1 special handling
3141 b = (Check2x1AutoRail(3)) | HT_LINE;
3142 } else if (dx == -(int)TILE_SIZE) {
3143 b = (Check2x1AutoRail(2)) | HT_LINE;
3144 } else {
3145 b = HT_LINE | HT_DIR_X;
3146 }
3147 y = _thd.selstart.y;
3148 } else if (w == TILE_SIZE) { // Or Y direction?
3149 if (dy == (int)TILE_SIZE) { // 2x1 special handling
3150 b = (Check2x1AutoRail(1)) | HT_LINE;
3151 } else if (dy == -(int)TILE_SIZE) { // 2x1 other direction
3152 b = (Check2x1AutoRail(0)) | HT_LINE;
3153 } else {
3154 b = HT_LINE | HT_DIR_Y;
3155 }
3156 x = _thd.selstart.x;
3157 } else if (w > h * 2) { // still count as x dir?
3158 b = HT_LINE | HT_DIR_X;
3159 y = _thd.selstart.y;
3160 } else if (h > w * 2) { // still count as y dir?
3161 b = HT_LINE | HT_DIR_Y;
3162 x = _thd.selstart.x;
3163 } else { // complicated direction
3164 int d = w - h;
3165 _thd.selend.x = _thd.selend.x & ~TILE_UNIT_MASK;
3166 _thd.selend.y = _thd.selend.y & ~TILE_UNIT_MASK;
3167
3168 /* four cases. */
3169 if (x > _thd.selstart.x) {
3170 if (y > _thd.selstart.y) {
3171 /* south */
3172 if (d == 0) {
3174 } else if (d >= 0) {
3175 x = _thd.selstart.x + h;
3176 b = HT_LINE | HT_DIR_VL;
3177 } else {
3178 y = _thd.selstart.y + w;
3179 b = HT_LINE | HT_DIR_VR;
3180 }
3181 } else {
3182 /* west */
3183 if (d == 0) {
3185 } else if (d >= 0) {
3186 x = _thd.selstart.x + h;
3187 b = HT_LINE | HT_DIR_HL;
3188 } else {
3189 y = _thd.selstart.y - w;
3190 b = HT_LINE | HT_DIR_HU;
3191 }
3192 }
3193 } else {
3194 if (y > _thd.selstart.y) {
3195 /* east */
3196 if (d == 0) {
3198 } else if (d >= 0) {
3199 x = _thd.selstart.x - h;
3200 b = HT_LINE | HT_DIR_HU;
3201 } else {
3202 y = _thd.selstart.y + w;
3203 b = HT_LINE | HT_DIR_HL;
3204 }
3205 } else {
3206 /* north */
3207 if (d == 0) {
3209 } else if (d >= 0) {
3210 x = _thd.selstart.x - h;
3211 b = HT_LINE | HT_DIR_VR;
3212 } else {
3213 y = _thd.selstart.y - w;
3214 b = HT_LINE | HT_DIR_VL;
3215 }
3216 }
3217 }
3218 }
3219
3221 TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
3222 TileIndex t1 = TileVirtXY(x, y);
3223 uint distance = DistanceManhattan(t0, t1) + 1;
3224
3225 if (distance == 1) {
3226 HideMeasurementTooltips();
3227 } else {
3228 int heightdiff = CalcHeightdiff(b, distance, t0, t1);
3229 /* If we are showing a tooltip for horizontal or vertical drags,
3230 * 2 tiles have a length of 1. To bias towards the ceiling we add
3231 * one before division. It feels more natural to count 3 lengths as 2 */
3232 if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
3233 distance = CeilDiv(distance, 2);
3234 }
3235
3236 if (heightdiff == 0) {
3237 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3238 } else {
3239 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3240 }
3241 }
3242 }
3243
3244 _thd.selend.x = x;
3245 _thd.selend.y = y;
3246 _thd.next_drawstyle = b;
3247}
3248
3257{
3258 int sx, sy;
3259 HighLightStyle style;
3260
3261 if (x == -1) {
3262 _thd.selend.x = -1;
3263 return;
3264 }
3265
3266 /* Special handling of drag in any (8-way) direction */
3267 if (method & (VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3268 _thd.selend.x = x;
3269 _thd.selend.y = y;
3270 CalcRaildirsDrawstyle(x, y, method);
3271 return;
3272 }
3273
3274 /* Needed so level-land is placed correctly */
3275 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_POINT) {
3276 x += TILE_SIZE / 2;
3277 y += TILE_SIZE / 2;
3278 }
3279
3280 sx = _thd.selstart.x;
3281 sy = _thd.selstart.y;
3282
3283 int limit = 0;
3284
3285 switch (method) {
3286 case VPM_X_OR_Y: // drag in X or Y direction
3287 if (abs(sy - y) < abs(sx - x)) {
3288 y = sy;
3289 style = HT_DIR_X;
3290 } else {
3291 x = sx;
3292 style = HT_DIR_Y;
3293 }
3294 goto calc_heightdiff_single_direction;
3295
3296 case VPM_X_LIMITED: // Drag in X direction (limited size).
3297 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3298 [[fallthrough]];
3299
3300 case VPM_FIX_X: // drag in Y direction
3301 x = sx;
3302 style = HT_DIR_Y;
3303 goto calc_heightdiff_single_direction;
3304
3305 case VPM_Y_LIMITED: // Drag in Y direction (limited size).
3306 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3307 [[fallthrough]];
3308
3309 case VPM_FIX_Y: // drag in X direction
3310 y = sy;
3311 style = HT_DIR_X;
3312
3313calc_heightdiff_single_direction:;
3314 if (limit > 0) {
3315 x = sx + Clamp(x - sx, -limit, limit);
3316 y = sy + Clamp(y - sy, -limit, limit);
3317 }
3319 TileIndex t0 = TileVirtXY(sx, sy);
3320 TileIndex t1 = TileVirtXY(x, y);
3321 uint distance = DistanceManhattan(t0, t1) + 1;
3322
3323 if (distance == 1) {
3324 HideMeasurementTooltips();
3325 } else {
3326 /* With current code passing a HT_LINE style to calculate the height
3327 * difference is enough. However if/when a point-tool is created
3328 * with this method, function should be called with new_style (below)
3329 * instead of HT_LINE | style case HT_POINT is handled specially
3330 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
3331 int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
3332
3333 if (heightdiff == 0) {
3334 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH, distance));
3335 } else {
3336 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_LENGTH_HEIGHTDIFF, distance, heightdiff));
3337 }
3338 }
3339 }
3340 break;
3341
3342 case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
3343 limit = (_thd.sizelimit - 1) * TILE_SIZE;
3344 x = sx + Clamp(x - sx, -limit, limit);
3345 y = sy + Clamp(y - sy, -limit, limit);
3346 [[fallthrough]];
3347
3348 case VPM_X_AND_Y: // drag an X by Y area
3350 TileIndex t0 = TileVirtXY(sx, sy);
3351 TileIndex t1 = TileVirtXY(x, y);
3352 uint dx = Delta(TileX(t0), TileX(t1)) + 1;
3353 uint dy = Delta(TileY(t0), TileY(t1)) + 1;
3354
3355 /* If dragging an area (eg dynamite tool) and it is actually a single
3356 * row/column, change the type to 'line' to get proper calculation for height */
3357 style = (HighLightStyle)_thd.next_drawstyle;
3358 if (_thd.IsDraggingDiagonal()) {
3359 /* Determine the "area" of the diagonal dragged selection.
3360 * We assume the area is the number of tiles along the X
3361 * edge and the number of tiles along the Y edge. However,
3362 * multiplying these two numbers does not give the exact
3363 * number of tiles; basically we are counting the black
3364 * squares on a chess board and ignore the white ones to
3365 * make the tile counts at the edges match up. There is no
3366 * other way to make a proper count though.
3367 *
3368 * First convert to the rotated coordinate system. */
3369 int dist_x = TileX(t0) - TileX(t1);
3370 int dist_y = TileY(t0) - TileY(t1);
3371 int a_max = dist_x + dist_y;
3372 int b_max = dist_y - dist_x;
3373
3374 /* Now determine the size along the edge, but due to the
3375 * chess board principle this counts double. */
3376 a_max = abs(a_max + (a_max > 0 ? 2 : -2)) / 2;
3377 b_max = abs(b_max + (b_max > 0 ? 2 : -2)) / 2;
3378
3379 /* We get a 1x1 on normal 2x1 rectangles, due to it being
3380 * a seen as two sides. As the result for actual building
3381 * will be the same as non-diagonal dragging revert to that
3382 * behaviour to give it a more normally looking size. */
3383 if (a_max != 1 || b_max != 1) {
3384 dx = a_max;
3385 dy = b_max;
3386 }
3387 } else if (style & HT_RECT) {
3388 if (dx == 1) {
3389 style = HT_LINE | HT_DIR_Y;
3390 } else if (dy == 1) {
3391 style = HT_LINE | HT_DIR_X;
3392 }
3393 }
3394
3395 if (dx != 1 || dy != 1) {
3396 int heightdiff = CalcHeightdiff(style, 0, t0, t1);
3397
3398 dx -= (style & HT_POINT ? 1 : 0);
3399 dy -= (style & HT_POINT ? 1 : 0);
3400
3401 if (heightdiff == 0) {
3402 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA, dx, dy));
3403 } else {
3404 ShowMeasurementTooltips(GetEncodedString(STR_MEASURE_AREA_HEIGHTDIFF, dx, dy, heightdiff));
3405 }
3406 }
3407 }
3408 break;
3409
3410 default: NOT_REACHED();
3411 }
3412
3413 _thd.selend.x = x;
3414 _thd.selend.y = y;
3415}
3416
3422{
3424
3425 /* stop drag mode if the window has been closed */
3426 Window *w = _thd.GetCallbackWnd();
3427 if (w == nullptr) {
3429 return ES_HANDLED;
3430 }
3431
3432 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
3433 if (_left_button_down) {
3435 /* Only register a drag event when the mouse moved. */
3436 if (_thd.new_pos.x == _thd.selstart.x && _thd.new_pos.y == _thd.selstart.y) return ES_HANDLED;
3437 _thd.selstart.x = _thd.new_pos.x;
3438 _thd.selstart.y = _thd.new_pos.y;
3439 }
3440
3441 w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor());
3442 return ES_HANDLED;
3443 }
3444
3445 /* Mouse button released. */
3448
3449 /* Keep the selected tool, but reset it to the original mode. */
3451 if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_RECT) {
3452 _thd.place_mode = HT_RECT | others;
3453 } else if (_thd.select_method & VPM_SIGNALDIRS) {
3454 _thd.place_mode = HT_RECT | others;
3455 } else if (_thd.select_method & VPM_RAILDIRS) {
3456 _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
3457 } else {
3458 _thd.place_mode = HT_POINT | others;
3459 }
3460 SetTileSelectSize(1, 1);
3461
3462 HideMeasurementTooltips();
3463 w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
3464
3465 return ES_HANDLED;
3466}
3467
3476{
3477 SetObjectToPlace(icon, pal, mode, w->window_class, w->window_number);
3478}
3479
3480#include "table/animcursors.h"
3481
3490void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
3491{
3492 if (_thd.window_class != WC_INVALID) {
3493 /* Undo clicking on button and drag & drop */
3494 Window *w = _thd.GetCallbackWnd();
3495 /* Call the abort function, but set the window class to something
3496 * that will never be used to avoid infinite loops. Setting it to
3497 * the 'next' window class must not be done because recursion into
3498 * this function might in some cases reset the newly set object to
3499 * place or not properly reset the original selection. */
3500 _thd.window_class = WC_INVALID;
3501 if (w != nullptr) {
3502 w->OnPlaceObjectAbort();
3503 HideMeasurementTooltips();
3504 }
3505 }
3506
3507 /* Mark the old selection dirty, in case the selection shape or colour changes */
3509
3510 SetTileSelectSize(1, 1);
3511
3512 _thd.make_square_red = false;
3513
3514 if (mode == HT_DRAG) { // HT_DRAG is for dragdropping trains in the depot window
3515 mode = HT_NONE;
3517 } else {
3519 }
3520
3521 _thd.place_mode = mode;
3522 _thd.window_class = window_class;
3523 _thd.window_number = window_num;
3524
3525 if ((mode & HT_DRAG_MASK) == HT_SPECIAL) { // special tools, like tunnels or docks start with presizing mode
3526 VpStartPreSizing();
3527 }
3528
3529 if ((icon & ANIMCURSOR_FLAG) != 0) {
3531 } else {
3532 SetMouseCursor(icon, pal);
3533 }
3534
3535}
3536
3542
3543Point GetViewportStationMiddle(const Viewport &vp, const Station *st)
3544{
3545 int x = TileX(st->xy) * TILE_SIZE;
3546 int y = TileY(st->xy) * TILE_SIZE;
3547 int z = GetSlopePixelZ(Clamp(x, 0, Map::SizeX() * TILE_SIZE - 1), Clamp(y, 0, Map::SizeY() * TILE_SIZE - 1));
3548
3549 Point p = RemapCoords(x, y, z);
3550 p.x = UnScaleByZoom(p.x - vp.virtual_left, vp.zoom) + vp.left;
3551 p.y = UnScaleByZoom(p.y - vp.virtual_top, vp.zoom) + vp.top;
3552 return p;
3553}
3554
3560
3563#ifdef WITH_SSE
3564 { &ViewportSortParentSpritesSSE41Checker, &ViewportSortParentSpritesSSE41 },
3565#endif
3567};
3568
3571{
3572 for (const auto &sprite_sorter : _vp_sprite_sorters) {
3573 if (sprite_sorter.fct_checker()) {
3574 _vp_sprite_sorter = sprite_sorter.fct_sorter;
3575 break;
3576 }
3577 }
3578 assert(_vp_sprite_sorter != nullptr);
3579}
3580
3590{
3591 if (_current_company != OWNER_DEITY) return CMD_ERROR;
3592 switch (target) {
3593 case VST_EVERYONE:
3594 break;
3595 case VST_COMPANY:
3596 if (_local_company != (CompanyID)ref) return CommandCost();
3597 break;
3598 case VST_CLIENT:
3599 if (_network_own_client_id != (ClientID)ref) return CommandCost();
3600 break;
3601 default:
3602 return CMD_ERROR;
3603 }
3604
3605 if (flags.Test(DoCommandFlag::Execute)) {
3608 }
3609 return CommandCost();
3610}
3611
3612void MarkCatchmentTilesDirty()
3613{
3614 if (_viewport_highlight_town != nullptr) {
3616 return;
3617 }
3618 if (_viewport_highlight_station != nullptr) {
3622 } else {
3624 for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) {
3625 MarkTileDirtyByTile(tile);
3626 }
3627 }
3628 }
3629 if (_viewport_highlight_waypoint != nullptr) {
3632 }
3634 }
3635}
3636
3637static void SetWindowDirtyForViewportCatchment()
3638{
3642}
3643
3644static void ClearViewportCatchment()
3645{
3646 MarkCatchmentTilesDirty();
3649 _viewport_highlight_town = nullptr;
3650}
3651
3658void SetViewportCatchmentStation(const Station *st, bool sel)
3659{
3660 SetWindowDirtyForViewportCatchment();
3661 if (sel && _viewport_highlight_station != st) {
3662 ClearViewportCatchment();
3664 MarkCatchmentTilesDirty();
3665 } else if (!sel && _viewport_highlight_station == st) {
3666 MarkCatchmentTilesDirty();
3668 }
3670}
3671
3679{
3680 SetWindowDirtyForViewportCatchment();
3681 if (sel && _viewport_highlight_waypoint != wp) {
3682 ClearViewportCatchment();
3684 MarkCatchmentTilesDirty();
3685 } else if (!sel && _viewport_highlight_waypoint == wp) {
3686 MarkCatchmentTilesDirty();
3688 }
3690}
3691
3698void SetViewportCatchmentTown(const Town *t, bool sel)
3699{
3700 SetWindowDirtyForViewportCatchment();
3701 if (sel && _viewport_highlight_town != t) {
3702 ClearViewportCatchment();
3705 } else if (!sel && _viewport_highlight_town == t) {
3706 _viewport_highlight_town = nullptr;
3708 }
3710}
3711
3716void ViewportData::CancelFollow(const Window &viewport_window)
3717{
3718 if (this->follow_vehicle == VehicleID::Invalid()) return;
3719
3720 if (viewport_window.window_class == WC_MAIN_WINDOW) {
3721 /* We're cancelling follow in the main viewport, so we need to check for a vehicle view window
3722 * to raise the location follow widget. */
3723 Window *vehicle_window = FindWindowById(WC_VEHICLE_VIEW, this->follow_vehicle);
3724 if (vehicle_window != nullptr) vehicle_window->RaiseWidgetWhenLowered(WID_VV_LOCATION);
3725 }
3726
3727 this->follow_vehicle = VehicleID::Invalid();
3728}
This file defines all the the animated cursors.
static const AnimCursor *const _animcursors[]
This is an array of pointers to all the animated cursor definitions we have above.
Definition animcursors.h:85
Highlight/sprite information for autorail.
Class for backupping variables and making sure they are restored later.
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.
TileIndex GetNorthernBridgeEnd(TileIndex t)
Finds the northern end of a bridge starting at a middle tile.
Map accessor functions for bridges.
int GetBridgePixelHeight(TileIndex tile)
Get the height ('z') of a bridge in pixels.
Definition bridge_map.h:84
bool IsBridgeAbove(Tile t)
checks if a bridge is set above the ground of this tile
Definition bridge_map.h:45
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Timpl & Set()
Set all bits.
Iterator to iterate over all tiles belonging to a bitmaptilearea.
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
Definition factory.hpp:136
How all blitters should look like.
Definition base.hpp:29
virtual void * MoveTo(void *video, int x, int y)=0
Move the destination pointer the requested amount x and y, keeping in mind any pitch and bpp of the r...
virtual void SetPixel(void *video, int x, int y, uint8_t colour)=0
Draw a pixel with a given colour on the video-buffer.
Common return value for all commands.
Container for an encoded string, created by GetEncodedString.
K-dimensional tree, specialised for 2-dimensional space.
Definition kdtree.hpp:33
void Build(It begin, It end)
Clear and rebuild the tree from a new sequence of elements,.
Definition kdtree.hpp:360
size_t Count() const
Get number of elements stored in tree.
Definition kdtree.hpp:428
void FindContained(CoordT x1, CoordT y1, CoordT x2, CoordT y2, const Outputter &outputter) const
Find all items contained within the given rectangle.
Definition kdtree.hpp:457
T FindNearest(CoordT x, CoordT y) const
Find the element closest to given coordinate, in Manhattan distance.
Definition kdtree.hpp:439
RAII class for measuring multi-step elements of performance.
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
RectPadding fullbevel
Always-scaled bevel thickness.
Definition window_gui.h:39
Functions related to commands.
static const CommandCost CMD_ERROR
Define a default return value for a failed command.
@ Execute
execute the given command
Definition of stuff that is very close to a company, like the company struct itself.
ReferenceThroughBaseContainer< std::array< Colours, MAX_COMPANIES > > _company_colours
NOSAVE: can be determined from company structs.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
CompanyID _current_company
Company currently doing an action.
Functions related to companies.
static constexpr Owner OWNER_DEITY
The object is owned by a superuser / goal script.
static constexpr Owner OWNER_NONE
The tile has no ownership.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
@ DIR_SW
Southwest.
@ DIR_SE
Southeast.
@ DIR_S
South.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23)
Definition enum_type.hpp:17
Factory to 'query' all available blitters.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:77
Types for recording game performance data.
@ PFE_DRAWWORLD
Time spent drawing world viewports in GUI.
void SetMouseCursor(CursorID sprite, PaletteID pal)
Assign a single non-animated sprite to the cursor.
Definition gfx.cpp:1692
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:41
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
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:38
void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
Draw a sprite in a viewport.
Definition gfx.cpp:996
void SetAnimatedMouseCursor(const AnimCursor *table)
Assign an animation to the cursor.
Definition gfx.cpp:1705
void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
Draws the projection of a parallelepiped.
Definition gfx.cpp:419
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
@ Normal
The most basic (normal) sprite.
@ 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
uint32_t CursorID
The number of the cursor (sprite)
Definition gfx_type.h:19
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:384
uint32_t PaletteID
The number of the palette.
Definition gfx_type.h:18
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:302
@ TC_IS_PALETTE_COLOUR
Colour value is already a real palette colour index, not an index of a StringColour.
Definition gfx_type.h:325
bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
Mark all viewports that display an area as dirty (in need of repaint).
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:955
void AddDirtyBlock(int left, int top, int right, int bottom)
Extend the internal _invalid_rect rectangle to contain the rectangle defined by the given parameters.
Definition gfx.cpp:1502
void MarkDirty(ZoomLevel maxzoom=ZoomLevel::Max) const
Mark the sign dirty in all viewports.
static bool MarkViewportDirty(const Viewport &vp, int left, int top, int right, int bottom)
Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition gfx.cpp:1535
void RedrawScreenRect(int left, int top, int right, int bottom)
Repaints a specific rectangle of the screen.
Definition gfx.cpp:1412
void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
Mark a tile given by its index dirty for repaint.
static void SetSelectionTilesDirty()
Marks the selected tiles as dirty.
const TileTypeProcs *const _tile_type_procs[16]
Tile callback functions for each type of tile.
Definition landscape.cpp:66
Point InverseRemapCoords2(int x, int y, bool clamp_to_map, bool *clamped)
Map 2D viewport or smallmap coordinate to 3D world or tile coordinate.
int GetSlopePixelZ(int x, int y, bool ground_vehicle)
Return world Z coordinate of a given point of a tile.
Functions related to OTTD's landscape.
Point RemapCoords(int x, int y, int z)
Map 3D world or tile coordinate to equivalent 2D coordinate as used in the viewports and smallmap.
Definition landscape.h:79
Point RemapCoords2(int x, int y)
Map 3D world or tile coordinate to equivalent 2D coordinate as used in the viewports and smallmap.
Definition landscape.h:95
Point InverseRemapCoords(int x, int y)
Map 2D viewport or smallmap coordinate to 3D world or tile coordinate.
Definition landscape.h:109
Declaration of linkgraph overlay GUI.
bool DoZoomInOutWindow(ZoomStateChange how, Window *w)
Zooms a viewport in a window in or out.
Definition main_gui.cpp:93
uint DistanceManhattan(TileIndex t0, TileIndex t1)
Gets the Manhattan distance between the two given tiles.
Definition map.cpp:142
TileIndex TileAddByDir(TileIndex tile, Direction dir)
Adds a Direction to a tile.
Definition map_func.h:598
static debug_inline TileIndex TileXY(uint x, uint y)
Returns the TileIndex of a coordinate.
Definition map_func.h:372
TileIndexDiff ToTileIndexDiff(TileIndexDiffC tidc)
Return the offset between two tiles from a TileIndexDiffC struct.
Definition map_func.h:439
constexpr TileIndex TileAdd(TileIndex tile, TileIndexDiff offset)
Adds a given offset to a tile.
Definition map_func.h:456
static debug_inline uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition map_func.h:424
static debug_inline uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:414
static debug_inline TileIndex TileVirtXY(uint x, uint y)
Get a tile from the virtual XY-coordinate.
Definition map_func.h:403
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
constexpr T abs(const T a)
Returns the absolute value of (scalar) variable.
Definition math_func.hpp:23
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition math_func.hpp:37
constexpr uint CeilDiv(uint a, uint b)
Computes ceil(a / b) for non-negative a and b.
constexpr T Delta(const T a, const T b)
Returns the (absolute) difference between two (scalar) variables.
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
void GuiShowTooltips(Window *parent, EncodedString &&text, TooltipCloseCondition close_tooltip)
Shows a tooltip.
Definition misc_gui.cpp:688
ClientID _network_own_client_id
Our client identifier.
Definition network.cpp:72
Network functions used by other parts of OpenTTD.
ClientID
'Unique' identifier to be given to clients
@ DO_SHOW_TOWN_NAMES
Display town names.
Definition openttd.h:46
@ DO_SHOW_COMPETITOR_SIGNS
Display signs, station names and waypoint names of opponent companies. Buoys and oilrig-stations are ...
Definition openttd.h:52
@ DO_SHOW_SIGNS
Display signs.
Definition openttd.h:48
@ DO_SHOW_WAYPOINT_NAMES
Display waypoint names.
Definition openttd.h:51
@ DO_SHOW_STATION_NAMES
Display station names.
Definition openttd.h:47
uint8_t 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
Base class for signs.
Functions related to signs.
void HandleClickOnSign(const Sign *si)
Handle clicking on a sign.
Slope SlopeWithThreeCornersRaised(Corner corner)
Returns the slope with all except one corner raised.
Definition slope_func.h:206
Corner OppositeCorner(Corner corner)
Returns the opposite corner.
Definition slope_func.h:184
static constexpr Corner GetHalftileSlopeCorner(Slope s)
Returns the leveled halftile of a halftile slope.
Definition slope_func.h:148
static constexpr Slope RemoveHalftileSlope(Slope s)
Removes a halftile slope from a slope.
Definition slope_func.h:60
uint SlopeToSpriteOffset(Slope s)
Returns the Sprite offset for a given Slope.
Definition slope_func.h:415
static constexpr bool IsSteepSlope(Slope s)
Checks if a slope is steep.
Definition slope_func.h:36
static constexpr bool IsHalftileSlope(Slope s)
Checks for non-continuous slope on halftile foundations.
Definition slope_func.h:47
Slope SlopeWithOneCornerRaised(Corner corner)
Returns the slope with a specific corner raised.
Definition slope_func.h:99
Corner
Enumeration of tile corners.
Definition slope_type.h:22
Slope
Enumeration for the slope-type.
Definition slope_type.h:48
@ SLOPE_N
the north corner of the tile is raised
Definition slope_type.h:53
@ SLOPE_STEEP_N
a steep slope falling to south (from north)
Definition slope_type.h:69
bool ScrollMainWindowTo(int x, int y, int z, bool instant)
Scrolls the main window to given coordinates.
static constexpr uint32_t MAX_SPRITES
Masks needed for sprite operations.
Definition sprites.h:1561
static const PaletteID PALETTE_TILE_RED_PULSATING
pulsating red tile drawn if you try to build a wrong tunnel or raise/lower land where it is not possi...
Definition sprites.h:1575
static const CursorID ANIMCURSOR_FLAG
Flag for saying a cursor sprite is an animated cursor.
Definition sprites.h:1513
static const PaletteID PALETTE_SEL_TILE_RED
makes a square red. is used when removing rails or other stuff
Definition sprites.h:1576
static constexpr uint32_t SPRITE_MASK
The mask to for the main sprite.
Definition sprites.h:1562
static constexpr uint8_t PALETTE_MODIFIER_TRANSPARENT
when a sprite is to be displayed transparently, this bit needs to be set.
Definition sprites.h:1553
static const PaletteID PALETTE_CRASH
Recolour sprite greying of crashed vehicles.
Definition sprites.h:1611
static const CursorID SPR_CURSOR_MOUSE
Cursor sprite numbers.
Definition sprites.h:1396
static const PaletteID PALETTE_SEL_TILE_BLUE
This draws a blueish square (catchment areas for example)
Definition sprites.h:1577
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1608
Base classes/functions for stations.
void ShowStationViewWindow(StationID station)
Opens StationViewWindow for given station.
StationID GetStationIndex(Tile t)
Get StationID from a tile.
Definition station_map.h:28
Definition of base types and functions in a cross-platform compatible way.
The colour translation of GRF's strings.
static const uint8_t _string_colourmap[17]
Colour mapping for TextColour.
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:91
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:415
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
Base class for all station-ish types.
TileIndex xy
Base tile of the station.
bool IsInUse() const
Check whether the base station currently is in use; in use means that it is not scheduled for deletio...
TrackedViewportSign sign
NOSAVE: Dimensions of sign.
int next
next child to draw (-1 at the end)
Definition viewport.cpp:140
const SubSprite * sub
only draw a rectangular part of the sprite
Definition viewport.cpp:136
GUISettings gui
settings related to the GUI
uint8_t max_bridge_height
maximum height of bridges
Point pos
logical mouse position
Definition gfx_type.h:126
Data about how and where to blit pixels.
Definition gfx_type.h:158
uint8_t dist_local_authority
distance for town local authority, default 20
bool population_in_label
show the population of a town in its label?
bool measure_tooltip
show a permanent tooltip when dragging tools
ZoomLevel zoom_min
minimum zoom out level
bool smooth_scroll
smooth scroll viewports
ZoomLevel zoom_max
maximum zoom out level
EconomySettings economy
settings to change the economy
ConstructionSettings construction
construction of things in-game
static uint SizeY()
Get the size of the map along the Y.
Definition map_func.h:278
static debug_inline uint SizeX()
Get the size of the map along the X.
Definition map_func.h:269
static uint ScaleBySize1D(uint n)
Scales the given value by the maps circumference, where the given value is for a 256 by 256 map.
Definition map_func.h:340
static uint MaxY()
Gets the maximum Y coordinate within the map, including MP_VOID.
Definition map_func.h:305
static debug_inline uint MaxX()
Gets the maximum X coordinate within the map, including MP_VOID.
Definition map_func.h:296
TileIndex tile
The base tile of the area.
Parent sprite that should be drawn.
const SubSprite * sub
only draw a rectangular part of the sprite
int32_t zmin
minimal world Z coordinate of bounding box
SpriteID image
sprite to draw
int32_t xmax
maximal world X coordinate of bounding box
int32_t ymax
maximal world Y coordinate of bounding box
int32_t x
screen X coordinate of sprite
int32_t y
screen Y coordinate of sprite
int32_t first_child
the first child to draw.
int32_t zmax
maximal world Z coordinate of bounding box
int32_t xmin
minimal world X coordinate of bounding box
int32_t ymin
minimal world Y coordinate of bounding box
PaletteID pal
palette to use
int32_t left
minimal screen X coordinate of sprite (= x + sprite->x_offs), reference point for child sprites
int32_t top
minimal screen Y coordinate of sprite (= y + sprite->y_offs), reference point for child sprites
Coordinates of a point in 2D.
static Titem * Get(auto index)
Returns Titem with given index.
static size_t GetNumItems()
Returns number of valid items in the pool.
Tindex index
Index of this pool item.
Specification of a rectangle with absolute coordinates of all edges.
static bool IsExpected(const BaseStation *st)
Helper for checking whether the given station is of this type.
static Station * Get(auto index)
Gets station with given index.
static Waypoint * From(BaseStation *st)
Converts a BaseStation to SpecializedStation with type checking.
Data structure describing a sprite.
Definition spritecache.h:17
uint16_t width
Width of the sprite.
Definition spritecache.h:19
uint16_t height
Height of the sprite.
Definition spritecache.h:18
int16_t y_offs
Number of pixels to shift the sprite downwards.
Definition spritecache.h:21
int16_t x_offs
Number of pixels to shift the sprite to the right.
Definition spritecache.h:20
Station data structure.
BitmapTileArea catchment_tiles
NOSAVE: Set of individual tiles covered by catchment area.
Used to only draw a part of the sprite.
Definition gfx_type.h:273
Metadata about the current highlighting.
TileIndex redsq
The tile that has to get a red selection.
Point new_pos
New value for pos; used to determine whether to redraw the selection.
Window * GetCallbackWnd()
Get the window that started the current highlighting.
bool make_square_red
Whether to give a tile a red selection.
bool IsDraggingDiagonal()
Is the user dragging a 'diagonal rectangle'?
ViewportDragDropSelectionProcess select_proc
The procedure that has to be called when the selection is done.
Point selstart
The location where the dragging started.
Point new_outersize
New value for outersize; used to determine whether to redraw the selection.
HighLightStyle drawstyle
Lower bits 0-3 are reserved for detailed highlight information.
HighLightStyle place_mode
Method which is used to place the selection.
WindowClass window_class
The WindowClass of the window that is responsible for the selection mode.
uint8_t dirty
Whether the build station window needs to redraw due to the changed selection.
Point offs
Offset, in tile "units", for the blue coverage area from the selected area's northern tile.
Point size
Size, in tile "units", of the white/red selection area.
uint8_t sizelimit
Whether the selection is limited in length, and what the maximum length is.
Point pos
Location, in tile "units", of the northern tile of the selected area.
Point outersize
Size, in tile "units", of the blue coverage area excluding the side of the selected area.
bool diagonal
Whether the dragged area is a 45 degrees rotated rectangle.
void Reset()
Reset tile highlighting.
Point selend
The location where the drag currently ends.
Point new_size
New value for size; used to determine whether to redraw the selection.
bool freeze
Freeze highlight in place.
HighLightStyle next_drawstyle
Queued, but not yet drawn style.
ViewportPlaceMethod select_method
The method which governs how tiles are selected.
WindowNumber window_number
The WindowNumber of the window that is responsible for the selection mode.
Tile information, used while rendering the tile.
Definition tile_cmd.h:29
int z
Height.
Definition tile_cmd.h:34
int x
X position of the tile in unit coordinates.
Definition tile_cmd.h:30
Slope tileh
Slope of the tile.
Definition tile_cmd.h:32
TileIndex tile
Tile index.
Definition tile_cmd.h:33
int y
Y position of the tile in unit coordinates.
Definition tile_cmd.h:31
int32_t y
screen Y coordinate of sprite
Definition viewport.cpp:130
int32_t x
screen X coordinate of sprite
Definition viewport.cpp:129
const SubSprite * sub
only draw a rectangular part of the sprite
Definition viewport.cpp:128
DrawTileProc * draw_tile_proc
Called to render the tile and its contents to the screen.
Definition tile_cmd.h:145
TrackedViewportSign sign
Location of name sign, UpdateVirtCoord updates this.
Definition town.h:43
Town data structure.
Definition town.h:52
TileIndex xy
town center tile
Definition town.h:53
TownCache cache
Container for all cacheable data.
Definition town.h:55
bool show_zone
NOSAVE: mark town to show the local authority zone in the viewports.
Definition town.h:102
StationList stations_near
NOSAVE: List of nearby stations.
Definition town.h:89
bool kdtree_valid
Are the sign data valid for use with the _viewport_sign_kdtree?
Vehicle data structure.
int32_t z_pos
z coordinate.
Vehicle * First() const
Get the first vehicle of this vehicle chain.
int32_t y_pos
y coordinate.
int32_t x_pos
x coordinate.
Owner owner
Which company owns the vehicle?
UnitID unitnumber
unit number, for display purposes only
Data structure for a window viewport.
Definition window_gui.h:250
void CancelFollow(const Window &viewport_window)
Cancel viewport vehicle following, and raise follow location widget if needed.
int32_t dest_scrollpos_y
Current destination y coordinate to display (virtual screen coordinate of topleft corner of the viewp...
Definition window_gui.h:255
int32_t scrollpos_y
Currently shown y coordinate (virtual screen coordinate of topleft corner of the viewport).
Definition window_gui.h:253
int32_t dest_scrollpos_x
Current destination x coordinate to display (virtual screen coordinate of topleft corner of the viewp...
Definition window_gui.h:254
VehicleID follow_vehicle
VehicleID to follow if following a vehicle, VehicleID::Invalid() otherwise.
Definition window_gui.h:251
int32_t scrollpos_x
Currently shown x coordinate (virtual screen coordinate of topleft corner of the viewport).
Definition window_gui.h:252
Data structure storing rendering information.
Definition viewport.cpp:170
int foundation[FOUNDATION_PART_END]
Foundation sprites (index into parent_sprites_to_draw).
Definition viewport.cpp:183
int last_foundation_child[FOUNDATION_PART_END]
Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
Definition viewport.cpp:185
ParentSpriteToSortVector parent_sprites_to_sort
Parent sprite pointer array used for sorting.
Definition viewport.cpp:176
Point foundation_offset[FOUNDATION_PART_END]
Pixel offset for ground sprites on the foundations.
Definition viewport.cpp:186
SpriteCombineMode combine_sprites
Current mode of "sprite combining".
Definition viewport.cpp:181
FoundationPart foundation_part
Currently active foundation for ground sprite drawing.
Definition viewport.cpp:184
Helper class for getting the best sprite sorter.
VpSorterChecker fct_checker
The check function.
VpSpriteSorter fct_sorter
The sorting function.
Location information about a sign as seen on the viewport.
int32_t center
The center position of the sign.
uint16_t width_small
The width when zoomed out (small font)
uint16_t width_normal
The width when not zoomed out (normal font)
void UpdatePosition(int center, int top, std::string_view str, std::string_view str_small={})
Update the position of the viewport sign.
int32_t top
The top of the sign.
Data structure for viewport, display of a part of the world.
int top
Screen coordinate top edge of the viewport.
int width
Screen width of the viewport.
ZoomLevel zoom
The zoom level of the viewport.
int virtual_top
Virtual top coordinate.
int virtual_left
Virtual left coordinate.
int virtual_width
width << zoom
int left
Screen coordinate left edge of the viewport.
int height
Screen height of the viewport.
int virtual_height
height << zoom
Representation of a waypoint.
Number to differentiate different windows of the same class.
Iterator to iterate all valid Windows.
Definition window_gui.h:874
Data structure for an opened window.
Definition window_gui.h:273
void SetWidgetDirty(WidgetID widget_index) const
Invalidate a widget, i.e.
Definition window.cpp:555
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:318
void RaiseWidgetWhenLowered(WidgetID widget_index)
Marks a widget as raised and dirty (redraw), when it is marked as lowered.
Definition window_gui.h:478
virtual void OnPlaceObjectAbort()
The user cancelled a tile highlight mode that has been set.
Definition window_gui.h:821
WindowClass window_class
Window class.
Definition window_gui.h:301
void DrawViewport() const
Draw the viewport of this window.
virtual void OnPlaceObject(Point pt, TileIndex tile)
The user clicked some place on the map when a tile highlight mode has been set.
Definition window_gui.h:800
int left
x position of left edge of the window
Definition window_gui.h:309
int top
y position of top edge of the window
Definition window_gui.h:310
virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
The user has dragged over the map when the tile highlight mode has been set.
Definition window_gui.h:842
virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
The user is dragging over the map when the tile highlight mode has been set.
Definition window_gui.h:831
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
Definition window_gui.h:381
AllWindows< false > Iterate
Iterate all windows in whatever order is easiest.
Definition window_gui.h:932
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
WindowNumber window_number
Window number within the window class.
Definition window_gui.h:302
std::tuple< Slope, int > GetTilePixelSlopeOutsideMap(int x, int y)
Return the slope of a given tile, also for tiles outside the map (virtual "black" tiles).
Definition tile_map.cpp:78
int GetTilePixelZ(TileIndex tile)
Get bottom height of the tile.
Definition tile_map.h:302
int GetTileMaxPixelZ(TileIndex tile)
Get top height of the tile.
Definition tile_map.h:312
static debug_inline TileType GetTileType(Tile tile)
Get the tiletype of a given tile.
Definition tile_map.h:96
std::tuple< Slope, int > GetTilePixelSlope(TileIndex tile)
Return the slope of a given tile.
Definition tile_map.h:289
bool IsValidTile(Tile tile)
Checks if a tile is valid.
Definition tile_map.h:161
uint TilePixelHeight(Tile tile)
Returns the height of a tile in pixels.
Definition tile_map.h:72
uint TilePixelHeightOutsideMap(int x, int y)
Returns the height of a tile in pixels, also for tiles outside the map (virtual "black" tiles).
Definition tile_map.h:84
static debug_inline bool IsTileType(Tile tile, TileType type)
Checks if a tile is a given tiletype.
Definition tile_map.h:150
static debug_inline uint TileHeight(Tile tile)
Returns the height of a tile.
Definition tile_map.h:29
uint TileHeightOutsideMap(int x, int y)
Returns the height of a tile, also for tiles outside the map (virtual "black" tiles).
Definition tile_map.h:42
static const uint TILE_PIXELS
Pixel distance between tile columns/rows in ZOOM_BASE.
Definition tile_type.h:17
static const uint MAX_BUILDING_PIXELS
Maximum height of a building in pixels in ZOOM_BASE. (Also applies to "bridge buildings" on the bridg...
Definition tile_type.h:20
static const uint TILE_HEIGHT
Height of a height level in world coordinate AND in pixels in ZOOM_BASE.
Definition tile_type.h:18
static const uint TILE_SIZE
Tile size in world coordinates.
Definition tile_type.h:15
static const uint TILE_UNIT_MASK
For masking in/out the inner-tile world coordinate units.
Definition tile_type.h:16
constexpr TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition tile_type.h:95
TileType
The different types of tiles.
Definition tile_type.h:47
@ MP_STATION
A tile of a station.
Definition tile_type.h:53
@ MP_HOUSE
A house by a town.
Definition tile_type.h:51
@ MP_VOID
Invisible tiles at the SW and SE border.
Definition tile_type.h:55
Functions related to tile highlights.
HighLightStyle
Highlighting draw styles.
@ HT_LINE
used for autorail highlighting (longer stretches), lower bits: direction
@ HT_DIR_HL
horizontal lower
@ HT_DIR_HU
horizontal upper
@ HT_DRAG
dragging items in the depot windows
@ HT_NONE
default
@ HT_DIR_X
X direction.
@ HT_DIAGONAL
Also allow 'diagonal rectangles'. Only usable in combination with HT_RECT or HT_POINT.
@ HT_POINT
point (lower land, raise land, level land, ...)
@ HT_RECT
rectangle (stations, depots, ...)
@ HT_DIR_MASK
masks the drag-direction
@ HT_RAIL
autorail (one piece), lower bits: direction
@ HT_DIR_END
end marker
@ HT_DRAG_MASK
Mask for the tile drag-type modes.
@ HT_DIR_VL
vertical left
@ HT_VEHICLE
vehicle is accepted as target as well (bitmask)
@ HT_DIR_VR
vertical right
@ HT_SPECIAL
special mode used for highlighting while dragging (and for tunnels/docks)
@ HT_DIR_Y
Y direction.
Base of the town class.
Town * ClosestTownFromTile(TileIndex tile, uint threshold)
Return the town closest (in distance or ownership) to a given tile, within a given threshold.
Declarations for accessing the k-d tree of towns.
TownID GetTownIndex(Tile t)
Get the index of which town this house/street is attached to.
Definition town_map.h:23
bool IsTransparencySet(TransparencyOption to)
Check if the transparency option bit is set and if we aren't in the game menu (there's never transpar...
uint8_t _display_opt
What do we want to draw/do?
bool IsInvisibilitySet(TransparencyOption to)
Check if the invisibility option bit is set and if we aren't in the game menu (there's never transpar...
@ TO_SIGNS
signs
void ViewportAddVehicles(DrawPixelInfo *dpi)
Add the vehicle sprites that should be drawn at a part of the screen.
Definition vehicle.cpp:1122
Vehicle * CheckClickOnVehicle(const Viewport &vp, int x, int y)
Find the vehicle close to the clicked coordinates.
Definition vehicle.cpp:1216
Base class for all vehicles.
Functions related to vehicles.
bool IsCompanyBuildableVehicleType(VehicleType type)
Is the given vehicle type buildable by a company?
void ShowVehicleViewWindow(const Vehicle *v)
Shows the vehicle view window of the given vehicle.
bool VehicleClicked(const Vehicle *v)
Dispatch a "vehicle selected" event if any window waits for it.
void StartStopVehicle(const Vehicle *v, bool texteffect)
Executes CMD_START_STOP_VEHICLE for given vehicle.
Functions related to the vehicle's GUIs.
Types related to the vehicle widgets.
@ WID_VV_LOCATION
Center the main view on this vehicle.
static void HighlightTownLocalAuthorityTiles(const TileInfo *ti)
Highlights tiles insede local authority of selected towns.
std::string * ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour)
Add a string to draw in the current viewport.
static void CheckOverflow(int &test, int &other, int max, int mult)
Check for overflowing the map.
bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
Scrolls the viewport in a window to a given location.
FoundationPart
Enumeration of multi-part foundations.
Definition viewport.cpp:144
@ FOUNDATION_PART_NONE
Neither foundation nor groundsprite drawn yet.
Definition viewport.cpp:145
@ FOUNDATION_PART_HALFTILE
Second part (halftile foundation)
Definition viewport.cpp:147
@ FOUNDATION_PART_NORMAL
First part (normal foundation or no foundation)
Definition viewport.cpp:146
void SetTileSelectSize(int w, int h)
Highlight w by h tiles at the cursor.
CommandCost CmdScrollViewport(DoCommandFlags flags, TileIndex tile, ViewportScrollTarget target, uint32_t ref)
Scroll players main viewport.
static void ViewportAddStationStrings(DrawPixelInfo *dpi, const std::vector< const BaseStation * > &stations, bool small)
Add station strings to a viewport.
void OffsetGroundSprite(int x, int y)
Called when a foundation has been drawn for the current tile.
Definition viewport.cpp:591
constexpr int LAST_CHILD_NONE
There is no last_child to fill.
Definition viewport.cpp:166
void ResetObjectToPlace()
Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows).
bool ScrollMainWindowToTile(TileIndex tile, bool instant)
Scrolls the viewport of the main window to a given location.
static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector< const Sign * > &signs, bool small)
Add sign strings to a viewport.
void SetObjectToPlaceWnd(CursorID icon, PaletteID pal, HighLightStyle mode, Window *w)
Change the cursor and mouse click/drag handling to a mode for performing special operations like tile...
constexpr int LAST_CHILD_PARENT
Fill last_child of the most recent parent sprite.
Definition viewport.cpp:167
static const int MAX_TILE_EXTENT_TOP
Maximum top extent of tile relative to north corner (not considering bridges).
Definition viewport.cpp:113
static void ShowMeasurementTooltips(EncodedString &&text)
Displays the measurement tooltips when selecting multiple tiles.
static const int MAX_TILE_EXTENT_LEFT
Maximum left extent of tile relative to north corner.
Definition viewport.cpp:111
bool IsInsideRotatedRectangle(int x, int y)
Checks whether a point is inside the selected a diagonal rectangle given by _thd.size and _thd....
Definition viewport.cpp:796
static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
Calculates height difference between one tile and another.
static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal)
Draws a selection rectangle on a tile.
Definition viewport.cpp:909
void StartSpriteCombine()
Starts a block of sprites, which are "combined" into a single bounding box.
Definition viewport.cpp:759
void InitializeSpriteSorter()
Choose the "best" sprite sorter and set _vp_sprite_sorter.
static const ViewportSSCSS _vp_sprite_sorters[]
List of sorters ordered from best to worst.
static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z, const SubSprite *sub)
Adds a child sprite to a parent sprite.
Definition viewport.cpp:623
static int GetViewportY(Point tile)
Returns the y coordinate in the viewport coordinate system where the given tile is painted.
static void AddChildSpriteToFoundation(SpriteID image, PaletteID pal, const SubSprite *sub, FoundationPart foundation_part, int extra_offs_x, int extra_offs_y)
Adds a child sprite to the active foundation.
Definition viewport.cpp:532
static HighLightStyle GetAutorailHT(int x, int y)
returns the best autorail highlight type from map coordinates
static void ClampSmoothScroll(uint32_t delta_ms, int64_t delta_hi, int64_t delta_lo, int &delta_hi_clamped, int &delta_lo_clamped)
Clamp the smooth scroll to a maxmimum speed and distance based on time elapsed.
static void DrawTileSelection(const TileInfo *ti)
Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
static TileHighlightType GetTileHighlightType(TileIndex t)
Get tile highlight type of coverage area for a given tile.
static bool IsInRangeInclusive(int begin, int end, int check)
Check if the parameter "check" is inside the interval between begin and end, including both begin and...
Definition viewport.cpp:784
Point TranslateXYToTileCoord(const Viewport &vp, int x, int y, bool clamp_to_map)
Translate screen coordinate in a viewport to underlying tile coordinate.
Definition viewport.cpp:430
static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
Draws autorail highlights.
Definition viewport.cpp:969
SpriteCombineMode
Mode of "sprite combining".
Definition viewport.cpp:155
@ SPRITE_COMBINE_PENDING
Sprite combining will start with the next unclipped sprite.
Definition viewport.cpp:157
@ SPRITE_COMBINE_ACTIVE
Sprite combining is active. AddSortableSpriteToDraw outputs child sprites.
Definition viewport.cpp:158
@ SPRITE_COMBINE_NONE
Every AddSortableSpriteToDraw start its own bounding box.
Definition viewport.cpp:156
static void DrawTileHighlightType(const TileInfo *ti, TileHighlightType tht)
Draw tile highlight for coverage area highlight.
static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
Draws the bounding boxes of all ParentSprites.
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, int h, int dz, int z, bool transparent, int bb_offset_x, int bb_offset_y, int bb_offset_z, const SubSprite *sub)
Draw a (transparent) sprite at given coordinates with a given bounding box.
Definition viewport.cpp:663
static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector< const Town * > &towns, bool small)
Add town strings to a viewport.
void SetViewportCatchmentWaypoint(const Waypoint *wp, bool sel)
Select or deselect waypoint for coverage area highlight.
void SetRedErrorSquare(TileIndex tile)
Set a tile to display a red error square.
static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
Sort parent sprites pointer array replicating the way original sorter did it.
void VpSetPresizeRange(TileIndex from, TileIndex to)
Highlights all tiles between a set of two tiles.
void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method)
Selects tiles while dragging.
EventState VpHandlePlaceSizingDrag()
Handle the mouse while dragging for placement/resizing.
Viewport * IsPtInWindowViewport(const Window *w, int x, int y)
Is a xy position inside the viewport of the window?
Definition viewport.cpp:408
void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
Change the cursor and mouse click/drag handling to a mode for performing special operations like tile...
void EndSpriteCombine()
Terminates a block of sprites started by StartSpriteCombine.
Definition viewport.cpp:769
static const int MAX_TILE_EXTENT_RIGHT
Maximum right extent of tile relative to north corner.
Definition viewport.cpp:112
void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent, const SubSprite *sub, bool scale, bool relative)
Add a child sprite to a parent sprite.
Definition viewport.cpp:819
static void CheckUnderflow(int &test, int &other, int mult)
Check for underflowing the map.
static bool ViewportSortParentSpritesChecker()
This fallback sprite checker always exists.
void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
Draws a ground sprite at a specific world-coordinate relative to the current tile.
Definition viewport.cpp:556
void VpStartDragging(ViewportDragDropSelectionProcess process)
Drag over the map while holding the left mouse down.
void UpdateTileSelection()
Updates tile highlighting for all cases.
static void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part, int extra_offs_x=0, int extra_offs_y=0)
Draws sprites between ground sprite and everything above.
Definition viewport.cpp:892
static const int MAX_TILE_EXTENT_BOTTOM
Maximum bottom extent of tile relative to north corner (worst case: SLOPE_STEEP_N).
Definition viewport.cpp:114
static bool CheckClickOnViewportSign(const Viewport &vp, int x, int y, const ViewportSign *sign)
Test whether a sign is below the mouse.
void SetViewportCatchmentStation(const Station *st, bool sel)
Select or deselect station for coverage area highlight.
void SetViewportCatchmentTown(const Town *t, bool sel)
Select or deselect town for coverage area highlight.
static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
Check if the direction of start and end tile should be swapped based on the dragging-style.
void InitializeWindowViewport(Window *w, int x, int y, int width, int height, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize viewport of the window for use.
Definition viewport.cpp:218
void DrawGroundSprite(SpriteID image, PaletteID pal, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
Draws a ground sprite for the current tile.
Definition viewport.cpp:579
static void CalcRaildirsDrawstyle(int x, int y, int method)
while dragging
static HighLightStyle Check2x1AutoRail(int mode)
returns information about the 2x1 piece to be build.
static void ViewportDrawDirtyBlocks()
Draw/colour the blocks that have been redrawn.
void HandleZoomMessage(Window *w, const Viewport &vp, WidgetID widget_zoom_in, WidgetID widget_zoom_out)
Update the status of the zoom-buttons according to the zoom-level of the viewport.
Definition viewport.cpp:486
void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, ViewportDragDropSelectionProcess process)
highlighting tiles while only going over them with the mouse
static void ClampViewportToMap(const Viewport &vp, int *scroll_x, int *scroll_y)
Ensure that a given viewport has a valid scroll position.
static void AddTileSpriteToDraw(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub=nullptr, int extra_offs_x=0, int extra_offs_y=0)
Schedules a tile sprite for drawing.
Definition viewport.cpp:507
static void ViewportAddLandscape()
Add the landscape to the viewport, i.e.
static std::string & AddStringToDraw(int x, int y, Colours colour, ViewportStringFlags flags, uint16_t width)
Add a string to draw to a viewport.
Definition viewport.cpp:865
const Station * _viewport_highlight_station
Currently selected station for coverage area highlight.
const Town * _viewport_highlight_town
Currently selected town for coverage area highlight.
void UpdateViewportPosition(Window *w, uint32_t delta_ms)
Update the viewport position being displayed.
const Waypoint * _viewport_highlight_waypoint
Currently selected waypoint for coverage area highlight.
bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
Scrolls the viewport in a window to a given location.
Command definitions related to viewports.
Functions related to (drawing on) viewports.
static const int TILE_HEIGHT_STEP
One Z unit tile height difference is displayed as 50m.
Types related to sprite sorting.
bool(* VpSorterChecker)()
Type for method for checking whether a viewport sprite sorter exists.
void(* VpSpriteSorter)(ParentSpriteToSortVector *psd)
Type for the actual viewport sprite sorter.
ViewportScrollTarget
Target of the viewport scrolling GS method.
@ VST_EVERYONE
All players.
@ VST_COMPANY
All players in specific company.
@ VST_CLIENT
Single player.
ViewportPlaceMethod
Viewport place method (type of highlighted area and placed objects)
@ VPM_FIX_Y
drag only in Y axis
@ VPM_Y_LIMITED
Drag only in Y axis with limited size.
@ VPM_X_AND_Y_LIMITED
area of land of limited size
@ VPM_FIX_VERTICAL
drag only in vertical direction
@ VPM_X_LIMITED
Drag only in X axis with limited size.
@ VPM_X_AND_Y
area of land in X and Y directions
@ VPM_FIX_HORIZONTAL
drag only in horizontal direction
@ VPM_FIX_X
drag only in X axis
@ VPM_SIGNALDIRS
similar to VMP_RAILDIRS, but with different cursor
@ VPM_X_OR_Y
drag in X or Y direction
@ VPM_RAILDIRS
all rail directions
@ ZOOM_IN
Zoom in (get more detailed view).
@ ZOOM_OUT
Zoom out (get helicopter view).
ViewportDragDropSelectionProcess
Drag and drop selection process, or, what to do with an area of land when you've selected it.
@ ColourRect
Draw a colour rect around the sign.
@ Small
Draw using the small font.
@ Shadow
Draw an extra text shadow. Should only be used with ViewportStringFlag::Small, as normal font already...
@ TransparentRect
Draw a transparent rect around the sign.
@ TextColour
Draw text in colour.
Base of waypoints.
Functions related to waypoints.
void ShowWaypointWindow(const Waypoint *wp)
Show the window for the given waypoint.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:298
void CloseWindowById(WindowClass cls, WindowNumber number, bool force, int data)
Close a window by its class and window number (if it is open).
Definition window.cpp:1182
Window * GetMainWindow()
Get the main window, i.e.
Definition window.cpp:1169
Window * FindWindowFromPt(int x, int y)
Do a search for a window at specific coordinates.
Definition window.cpp:1813
SpecialMouseMode _special_mouse_mode
Mode of the mouse.
Definition window.cpp:95
Window * FindWindowById(WindowClass cls, WindowNumber number)
Find a window by its class and window number.
Definition window.cpp:1140
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.
@ Transparent
Makes the background transparent if set.
@ WSM_DRAGDROP
Drag&drop an object.
@ WSM_DRAGGING
Dragging mode (trees).
@ WSM_PRESIZE
Presizing mode (docks, tunnels).
@ WSM_NONE
No special mouse mode.
@ WSM_SIZING
Sizing mode.
int WidgetID
Widget ID.
Definition window_type.h:20
EventState
State of handling an event.
@ ES_HANDLED
The passed event is handled.
@ ES_NOT_HANDLED
The passed event is not handled.
WindowClass
Window classes.
Definition window_type.h:46
@ WC_INVALID
Invalid window.
@ WC_WAYPOINT_VIEW
Waypoint view; Window numbers:
@ WC_STATION_VIEW
Station view; Window numbers:
@ WC_MAIN_WINDOW
Main window; Window numbers:
Definition window_type.h:53
@ WC_TOWN_VIEW
Town view; Window numbers:
@ WC_TOOLTIPS
Tooltip window; Window numbers:
@ WC_VEHICLE_VIEW
Vehicle view; Window numbers:
Functions related to zooming.
int ScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift left (when zoom > ZoomLevel::Min) When shifting right,...
Definition zoom_func.h:22
int UnScaleByZoomLower(int value, ZoomLevel zoom)
Scale by zoom level, usually shift right (when zoom > ZoomLevel::Min)
Definition zoom_func.h:67
int UnScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift right (when zoom > ZoomLevel::Min) When shifting right,...
Definition zoom_func.h:34
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:16
@ Begin
Begin for iteration.
@ Min
Minimum zoom level.
@ End
End for iteration.
@ Out4x
Zoomed 4 times out.