1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include <algorithm>
11 #include <iterator>
12 #include <openrct2-ui/interface/Dropdown.h>
13 #include <openrct2-ui/interface/Widget.h>
14 #include <openrct2-ui/windows/Window.h>
15 #include <openrct2/Game.h>
16 #include <openrct2/Input.h>
17 #include <openrct2/actions/TileModifyAction.h>
18 #include <openrct2/common.h>
19 #include <openrct2/core/Guard.hpp>
20 #include <openrct2/localisation/Localisation.h>
21 #include <openrct2/localisation/StringIds.h>
22 #include <openrct2/object/TerrainEdgeObject.h>
23 #include <openrct2/object/TerrainSurfaceObject.h>
24 #include <openrct2/ride/RideData.h>
25 #include <openrct2/ride/Track.h>
26 #include <openrct2/sprites.h>
27 #include <openrct2/windows/tile_inspector.h>
28 #include <openrct2/world/Banner.h>
29 #include <openrct2/world/Footpath.h>
30 #include <openrct2/world/LargeScenery.h>
31 #include <openrct2/world/Park.h>
32 #include <openrct2/world/Scenery.h>
33 #include <openrct2/world/SmallScenery.h>
34 #include <openrct2/world/Surface.h>
35 #include <openrct2/world/TileInspector.h>
36 
37 static constexpr const rct_string_id EntranceTypeStringIds[] = {
38     STR_TILE_INSPECTOR_ENTRANCE_TYPE_RIDE_ENTRANCE,
39     STR_TILE_INSPECTOR_ENTRANCE_TYPE_RIDE_EXIT,
40     STR_TILE_INSPECTOR_ENTRANCE_TYPE_PARK_ENTRANC,
41 };
42 
43 static constexpr const rct_string_id ParkEntrancePartStringIds[] = {
44     STR_TILE_INSPECTOR_ENTRANCE_MIDDLE,
45     STR_TILE_INSPECTOR_ENTRANCE_LEFT,
46     STR_TILE_INSPECTOR_ENTRANCE_RIGHT,
47 };
48 
49 static constexpr const rct_string_id WallSlopeStringIds[] = {
50     STR_TILE_INSPECTOR_WALL_FLAT,
51     STR_TILE_INSPECTOR_WALL_SLOPED_LEFT,
52     STR_TILE_INSPECTOR_WALL_SLOPED_RIGHT,
53     STR_TILE_INSPECTOR_WALL_ANIMATION_FRAME,
54 };
55 
56 enum WINDOW_TILE_INSPECTOR_WIDGET_IDX
57 {
58     WIDX_BACKGROUND,
59     WIDX_TITLE,
60     WIDX_CLOSE,
61     WIDX_LIST,
62     WIDX_SPINNER_X,
63     WIDX_SPINNER_X_INCREASE,
64     WIDX_SPINNER_X_DECREASE,
65     WIDX_SPINNER_Y,
66     WIDX_SPINNER_Y_INCREASE,
67     WIDX_SPINNER_Y_DECREASE,
68     WIDX_BUTTON_CORRUPT,
69     WIDX_BUTTON_REMOVE,
70     WIDX_BUTTON_MOVE_UP,
71     WIDX_BUTTON_MOVE_DOWN,
72     WIDX_BUTTON_ROTATE,
73     WIDX_BUTTON_SORT,
74     WIDX_BUTTON_PASTE,
75     WIDX_BUTTON_COPY,
76     WIDX_COLUMN_TYPE,
77     WIDX_COLUMN_BASEHEIGHT,
78     WIDX_COLUMN_CLEARANCEHEIGHT,
79     WIDX_COLUMN_DIRECTION,
80     WIDX_COLUMN_GHOSTFLAG,
81     WIDX_COLUMN_LASTFLAG,
82     WIDX_GROUPBOX_DETAILS,
83     WIDX_GROUPBOX_PROPERTIES,
84 
85     PAGE_WIDGETS,
86 
87     // Surface
88     WIDX_SURFACE_SPINNER_HEIGHT = PAGE_WIDGETS,
89     WIDX_SURFACE_SPINNER_HEIGHT_INCREASE,
90     WIDX_SURFACE_SPINNER_HEIGHT_DECREASE,
91     WIDX_SURFACE_BUTTON_REMOVE_FENCES,
92     WIDX_SURFACE_BUTTON_RESTORE_FENCES,
93     WIDX_SURFACE_CHECK_CORNER_N,
94     WIDX_SURFACE_CHECK_CORNER_E,
95     WIDX_SURFACE_CHECK_CORNER_S,
96     WIDX_SURFACE_CHECK_CORNER_W,
97     WIDX_SURFACE_CHECK_DIAGONAL,
98 
99     // Path
100     WIDX_PATH_SPINNER_HEIGHT = PAGE_WIDGETS,
101     WIDX_PATH_SPINNER_HEIGHT_INCREASE,
102     WIDX_PATH_SPINNER_HEIGHT_DECREASE,
103     WIDX_PATH_CHECK_BROKEN,
104     WIDX_PATH_CHECK_SLOPED,
105     WIDX_PATH_CHECK_EDGE_NE, // Note: This is NOT named after the world orientation, but after the way
106     WIDX_PATH_CHECK_EDGE_E,  // it looks in the window (top corner is north). Their order is important,
107     WIDX_PATH_CHECK_EDGE_SE, // as this is the same order paths use for their corners / edges.
108     WIDX_PATH_CHECK_EDGE_S,  //           N
109     WIDX_PATH_CHECK_EDGE_SW, //      NW-------NE
110     WIDX_PATH_CHECK_EDGE_W,  //   W ------------- E
111     WIDX_PATH_CHECK_EDGE_NW, //      SW-------SE
112     WIDX_PATH_CHECK_EDGE_N,  //           S
113 
114     // Track
115     WIDX_TRACK_CHECK_APPLY_TO_ALL = PAGE_WIDGETS,
116     WIDX_TRACK_SPINNER_HEIGHT,
117     WIDX_TRACK_SPINNER_HEIGHT_INCREASE,
118     WIDX_TRACK_SPINNER_HEIGHT_DECREASE,
119     WIDX_TRACK_CHECK_CHAIN_LIFT,
120     WIDX_TRACK_CHECK_BLOCK_BRAKE_CLOSED,
121     WIDX_TRACK_CHECK_IS_INDESTRUCTIBLE,
122 
123     // Scenery
124     WIDX_SCENERY_SPINNER_HEIGHT = PAGE_WIDGETS,
125     WIDX_SCENERY_SPINNER_HEIGHT_INCREASE,
126     WIDX_SCENERY_SPINNER_HEIGHT_DECREASE,
127     WIDX_SCENERY_CHECK_QUARTER_N,
128     WIDX_SCENERY_CHECK_QUARTER_E,
129     WIDX_SCENERY_CHECK_QUARTER_S,
130     WIDX_SCENERY_CHECK_QUARTER_W,
131     WIDX_SCENERY_CHECK_COLLISION_N,
132     WIDX_SCENERY_CHECK_COLLISION_E,
133     WIDX_SCENERY_CHECK_COLLISION_S,
134     WIDX_SCENERY_CHECK_COLLISION_W,
135 
136     // Entrance
137     WIDX_ENTRANCE_SPINNER_HEIGHT = PAGE_WIDGETS,
138     WIDX_ENTRANCE_SPINNER_HEIGHT_INCREASE,
139     WIDX_ENTRANCE_SPINNER_HEIGHT_DECREASE,
140     WIDX_ENTRANCE_BUTTON_MAKE_USABLE,
141 
142     // Wall
143     WIDX_WALL_SPINNER_HEIGHT = PAGE_WIDGETS,
144     WIDX_WALL_SPINNER_HEIGHT_INCREASE,
145     WIDX_WALL_SPINNER_HEIGHT_DECREASE,
146     WIDX_WALL_DROPDOWN_SLOPE,
147     WIDX_WALL_DROPDOWN_SLOPE_BUTTON,
148     WIDX_WALL_SPINNER_ANIMATION_FRAME,
149     WIDX_WALL_SPINNER_ANIMATION_FRAME_INCREASE,
150     WIDX_WALL_SPINNER_ANIMATION_FRAME_DECREASE,
151 
152     // Large
153     WIDX_LARGE_SCENERY_SPINNER_HEIGHT = PAGE_WIDGETS,
154     WIDX_LARGE_SCENERY_SPINNER_HEIGHT_INCREASE,
155     WIDX_LARGE_SCENERY_SPINNER_HEIGHT_DECREASE,
156 
157     // Banner
158     WIDX_BANNER_SPINNER_HEIGHT = PAGE_WIDGETS,
159     WIDX_BANNER_SPINNER_HEIGHT_INCREASE,
160     WIDX_BANNER_SPINNER_HEIGHT_DECREASE,
161     WIDX_BANNER_CHECK_BLOCK_NE,
162     WIDX_BANNER_CHECK_BLOCK_SE,
163     WIDX_BANNER_CHECK_BLOCK_SW,
164     WIDX_BANNER_CHECK_BLOCK_NW,
165 
166     // Corrupt
167     WIDX_CORRUPT_SPINNER_HEIGHT = PAGE_WIDGETS,
168     WIDX_CORRUPT_SPINNER_HEIGHT_INCREASE,
169     WIDX_CORRUPT_SPINNER_HEIGHT_DECREASE,
170     WIDX_CORRUPT_BUTTON_CLAMP,
171 };
172 
173 static constexpr const rct_string_id WINDOW_TITLE = STR_TILE_INSPECTOR_TITLE;
174 
175 // Window sizes
176 static constexpr const int32_t WW = 400;
177 static constexpr const int32_t WH = 170;
178 constexpr int32_t MIN_WW = WW;
179 constexpr int32_t MAX_WW = WW;
180 constexpr int32_t MIN_WH = 130;
181 constexpr int32_t MAX_WH = 800;
182 
183 // Button space for top buttons
184 constexpr auto ToolbarButtonAnchor = ScreenCoordsXY{ WW - 27, 17 };
185 constexpr auto ToolbarButtonSize = ScreenSize{ 24, 24 };
186 constexpr auto ToolbarButtonHalfSize = ScreenSize{ 24, 12 };
187 constexpr auto ToolbarButtonOffsetX = ScreenSize{ -24, 0 };
188 
189 // List's column offsets
190 constexpr auto TypeColumnXY = ScreenCoordsXY{ 3, 42 };
191 constexpr auto TypeColumnSize = ScreenSize{ 272, 14 };
192 constexpr auto BaseHeightColumnXY = TypeColumnXY + ScreenSize{ TypeColumnSize.width, 0 };
193 constexpr auto BaseHeightColumnSize = ScreenSize{ 30, 14 };
194 constexpr auto ClearanceHeightColumnXY = BaseHeightColumnXY + ScreenCoordsXY{ BaseHeightColumnSize.width, 0 };
195 constexpr auto ClearanceHeightColumnSize = ScreenSize{ 30, 14 };
196 constexpr auto DirectionColumnXY = ClearanceHeightColumnXY + ScreenCoordsXY{ ClearanceHeightColumnSize.width, 0 };
197 constexpr auto DirectionColumnSize = ScreenSize{ 15, 14 };
198 constexpr auto GhostFlagColumnXY = DirectionColumnXY + ScreenCoordsXY{ DirectionColumnSize.width, 0 };
199 constexpr auto GhostFlagColumnSize = ScreenSize{ 15, 14 };
200 constexpr auto LastFlagColumnXY = GhostFlagColumnXY + ScreenCoordsXY{ GhostFlagColumnSize.width, 0 };
201 constexpr auto LastFlagColumnSize = ScreenSize{ 32, 14 };
202 
203 constexpr int32_t PADDING_BOTTOM = 15;
204 constexpr int32_t GROUPBOX_PADDING = 6;
205 constexpr int32_t HORIZONTAL_GROUPBOX_PADDING = 5;
206 constexpr int32_t VERTICAL_GROUPBOX_PADDING = 4;
207 constexpr auto PropertyButtonSize = ScreenSize{ 130, 18 };
208 constexpr auto PropertyFullWidth = ScreenSize{ 370, 18 };
209 
PropertyRowCol(ScreenCoordsXY anchor,int32_t row,int32_t column)210 constexpr ScreenCoordsXY PropertyRowCol(ScreenCoordsXY anchor, int32_t row, int32_t column)
211 {
212     return anchor
213         + ScreenCoordsXY{ column * (PropertyButtonSize.width + HORIZONTAL_GROUPBOX_PADDING),
214                           row * (PropertyButtonSize.height + VERTICAL_GROUPBOX_PADDING) };
215 }
216 
CheckboxGroupOffset(ScreenCoordsXY anchorPoint,int16_t horizontalMultiplier,int16_t verticalMultiplier)217 constexpr ScreenCoordsXY CheckboxGroupOffset(
218     ScreenCoordsXY anchorPoint, int16_t horizontalMultiplier, int16_t verticalMultiplier)
219 {
220     return anchorPoint + ScreenCoordsXY{ 14 * horizontalMultiplier, 7 * verticalMultiplier };
221 }
222 
223 // clang-format off
224 // Macros for easily obtaining the top and bottom of a widget inside a properties group box
225 #define GBBT(GROUPTOP, row)     ((GROUPTOP) + 14 + row * (PropertyButtonSize.height + VERTICAL_GROUPBOX_PADDING))
226 #define GBBB(GROUPTOP, row)     (GBBT((GROUPTOP), row) + PropertyButtonSize.height)
227 
228 #define MAIN_TILE_INSPECTOR_WIDGETS \
229     WINDOW_SHIM(WINDOW_TITLE, WW, WH), \
230     MakeWidget({3, 57}, {WW - 6, WH - PADDING_BOTTOM - 58}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL), /* Element list */ \
231     /* X and Y spinners */ \
232     MakeSpinnerWidgets({20, 23}, {51, 12}, WindowWidgetType::Spinner, WindowColour::Secondary), /* Spinner X (3 widgets) */ \
233     MakeSpinnerWidgets({90, 23}, {51, 12}, WindowWidgetType::Spinner, WindowColour::Secondary), /* Spinner Y (3 widgets) */ \
234     /* Top buttons */ \
235     MakeWidget(ToolbarButtonAnchor,                                                ToolbarButtonSize,     WindowWidgetType::FlatBtn ,    WindowColour::Secondary, SPR_MAP,          STR_INSERT_CORRUPT_TIP),              /* Insert corrupt button */ \
236     MakeWidget(ToolbarButtonAnchor + ToolbarButtonOffsetX * 1,                     ToolbarButtonSize,     WindowWidgetType::FlatBtn,     WindowColour::Secondary, SPR_DEMOLISH,     STR_REMOVE_SELECTED_ELEMENT_TIP ),    /* Remove button */         \
237     MakeWidget(ToolbarButtonAnchor + ToolbarButtonOffsetX * 2,                     ToolbarButtonHalfSize, WindowWidgetType::Button,      WindowColour::Secondary, STR_UP,           STR_MOVE_SELECTED_ELEMENT_UP_TIP),    /* Move up */               \
238     MakeWidget(ToolbarButtonAnchor + ToolbarButtonOffsetX * 2 + ScreenSize{0, 12}, ToolbarButtonHalfSize, WindowWidgetType::Button,      WindowColour::Secondary, STR_DOWN,         STR_MOVE_SELECTED_ELEMENT_DOWN_TIP),  /* Move down */             \
239     MakeWidget(ToolbarButtonAnchor + ToolbarButtonOffsetX * 3,                     ToolbarButtonSize,     WindowWidgetType::FlatBtn,     WindowColour::Secondary, SPR_ROTATE_ARROW, STR_ROTATE_SELECTED_ELEMENT_TIP),     /* Rotate button */         \
240     MakeWidget(ToolbarButtonAnchor + ToolbarButtonOffsetX * 4,                     ToolbarButtonSize,     WindowWidgetType::FlatBtn,     WindowColour::Secondary, SPR_G2_SORT,      STR_TILE_INSPECTOR_SORT_TIP),         /* Sort button */           \
241     MakeWidget(ToolbarButtonAnchor + ToolbarButtonOffsetX * 5,                     ToolbarButtonSize,     WindowWidgetType::FlatBtn,     WindowColour::Secondary, SPR_G2_PASTE,     STR_TILE_INSPECTOR_PASTE_TIP),        /* Paste button */          \
242     MakeWidget(ToolbarButtonAnchor + ToolbarButtonOffsetX * 6,                     ToolbarButtonSize,     WindowWidgetType::FlatBtn,     WindowColour::Secondary, SPR_G2_COPY,      STR_TILE_INSPECTOR_COPY_TIP),         /* Copy button */           \
243     /* Column headers */ \
244     MakeWidget(TypeColumnXY,            TypeColumnSize,            WindowWidgetType::TableHeader, WindowColour::Secondary, STR_TILE_INSPECTOR_ELEMENT_TYPE),                                                /* Type */              \
245     MakeWidget(BaseHeightColumnXY,      BaseHeightColumnSize,      WindowWidgetType::TableHeader, WindowColour::Secondary, STR_TILE_INSPECTOR_BASE_HEIGHT_SHORT,      STR_TILE_INSPECTOR_BASE_HEIGHT),      /* Base height */       \
246     MakeWidget(ClearanceHeightColumnXY, ClearanceHeightColumnSize, WindowWidgetType::TableHeader, WindowColour::Secondary, STR_TILE_INSPECTOR_CLEARANGE_HEIGHT_SHORT, STR_TILE_INSPECTOR_CLEARANCE_HEIGHT), /* Clearance height */  \
247     MakeWidget(DirectionColumnXY,       DirectionColumnSize,       WindowWidgetType::TableHeader, WindowColour::Secondary, STR_TILE_INSPECTOR_DIRECTION_SHORT,        STR_TILE_INSPECTOR_DIRECTION),        /* Direction */         \
248     MakeWidget(GhostFlagColumnXY,       GhostFlagColumnSize,       WindowWidgetType::TableHeader, WindowColour::Secondary, STR_TILE_INSPECTOR_FLAG_GHOST_SHORT,       STR_TILE_INSPECTOR_FLAG_GHOST),       /* Ghost flag */        \
249     MakeWidget(LastFlagColumnXY,        LastFlagColumnSize,        WindowWidgetType::TableHeader, WindowColour::Secondary, STR_TILE_INSPECTOR_FLAG_LAST_SHORT,        STR_TILE_INSPECTOR_FLAG_LAST),        /* Last of tile flag */ \
250     /* Group boxes */ \
251     MakeWidget({6, 0},             {WW - 12, 0}, WindowWidgetType::Groupbox,    WindowColour::Secondary, STR_NONE,                               STR_NONE ), /* Details group box */     \
252     MakeWidget({6, 0},             {WW - 12, 0}, WindowWidgetType::Groupbox,    WindowColour::Secondary, STR_TILE_INSPECTOR_GROUPBOX_PROPERTIES, STR_NONE )  /* Properties group box */
253 
254 static rct_widget DefaultWidgets[] = {
255     MAIN_TILE_INSPECTOR_WIDGETS,
256     WIDGETS_END,
257 };
258 
259 constexpr int32_t NumSurfaceProperties = 4;
260 constexpr int32_t NumSurfaceDetails = 4;
261 constexpr int32_t SurfacePropertiesHeight = 16 + NumSurfaceProperties * 21;
262 constexpr int32_t SurfaceDetailsHeight = 20 + NumSurfaceDetails * 11;
263 static rct_widget SurfaceWidgets[] = {
264     MAIN_TILE_INSPECTOR_WIDGETS,
265     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_SURFACE_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
266     MakeWidget(PropertyRowCol({ 12, 0 }, 1, 0),         PropertyButtonSize, WindowWidgetType::Button,  WindowColour::Secondary, STR_TILE_INSPECTOR_SURFACE_REMOVE_FENCES), // WIDX_SURFACE_BUTTON_REMOVE_FENCES
267     MakeWidget(PropertyRowCol({ 12, 0 }, 1, 1),         PropertyButtonSize, WindowWidgetType::Button,  WindowColour::Secondary, STR_TILE_INSPECTOR_SURFACE_RESTORE_FENCES), // WIDX_SURFACE_BUTTON_RESTORE_FENCES
268     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 1, 0), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SURFACE_CHECK_CORNER_N
269     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 2, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SURFACE_CHECK_CORNER_E
270     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 1, 2), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SURFACE_CHECK_CORNER_S
271     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 0, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SURFACE_CHECK_CORNER_W
272     MakeWidget(PropertyRowCol({ 12, 0 }, 4, 0), PropertyFullWidth, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_TILE_INSPECTOR_SURFACE_DIAGONAL), // WIDX_SURFACE_CHECK_DIAGONAL
273     WIDGETS_END,
274 };
275 
276 constexpr int32_t NumPathProperties = 5;
277 constexpr int32_t NumPathDetails = 2;
278 constexpr int32_t PathPropertiesHeight = 16 + NumPathProperties * 21;
279 constexpr int32_t PathDetailsHeight = 20 + NumPathDetails * 11;
280 static rct_widget PathWidgets[] = {
281     MAIN_TILE_INSPECTOR_WIDGETS,
282     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_PATH_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
283     MakeWidget(PropertyRowCol({ 12, 0 }, 1, 0), PropertyFullWidth, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_TILE_INSPECTOR_PATH_BROKEN), // WIDX_PATH_CHECK_BROKEN
284     MakeWidget(PropertyRowCol({ 12, 0 }, 2, 0), PropertyFullWidth, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_TILE_INSPECTOR_PATH_SLOPED), // WIDX_PATH_CHECK_SLOPED
285     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 3, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_NE
286     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 4, 2), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_E
287     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 3, 3), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_SE
288     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 2, 4), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_S
289     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 1, 3), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_SW
290     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 0, 2), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_W
291     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 1, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_NW
292     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 3, 1), 2, 0), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_PATH_CHECK_EDGE_N
293     WIDGETS_END,
294 };
295 
296 constexpr int32_t NumTrackProperties = 5;
297 constexpr int32_t NumTrackDetails = 7;
298 constexpr int32_t TrackPropertiesHeight = 16 + NumTrackProperties * 21;
299 constexpr int32_t TrackDetailsHeight = 20 + NumTrackDetails * 11;
300 static rct_widget TrackWidgets[] = {
301     MAIN_TILE_INSPECTOR_WIDGETS,
302     MakeWidget(PropertyRowCol({ 12, 0}, 0, 0), PropertyFullWidth, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_TILE_INSPECTOR_TRACK_ENTIRE_TRACK_PIECE), // WIDX_TRACK_CHECK_APPLY_TO_ALL
303     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 1, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_TRACK_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
304     MakeWidget(PropertyRowCol({ 12, 0}, 2, 0), PropertyFullWidth, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_TILE_INSPECTOR_TRACK_CHAIN_LIFT), // WIDX_TRACK_CHECK_CHAIN_LIFT
305     MakeWidget(PropertyRowCol({ 12, 0}, 3, 0), PropertyFullWidth, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_TILE_INSPECTOR_TRACK_BLOCK_BRAKE), // WIDX_TRACK_CHECK_BLOCK_BRAKE_CLOSED
306     MakeWidget(PropertyRowCol({ 12, 0}, 4, 0), PropertyFullWidth, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_TILE_INSPECTOR_TRACK_IS_INDESTRUCTIBLE), // WIDX_TRACK_CHECK_IS_INDESTRUCTIBLE
307     WIDGETS_END,
308 };
309 
310 constexpr int32_t NumSceneryProperties = 4; // The checkbox groups both count for 2 rows
311 constexpr int32_t NumSceneryDetails = 4;
312 constexpr int32_t SceneryPropertiesHeight = 16 + NumSceneryProperties * 21;
313 constexpr int32_t SceneryDetailsHeight = 20 + NumSceneryDetails * 11;
314 static rct_widget SceneryWidgets[] = {
315     MAIN_TILE_INSPECTOR_WIDGETS,
316     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_SCENERY_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
317     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 1, 0), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_QUARTER_N
318     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 2, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_QUARTER_E
319     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 1, 2), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_QUARTER_S
320     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 0, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_QUARTER_W
321     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 2, 1), 1, 0), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_COLLISION_N
322     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 2, 1), 2, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_COLLISION_E
323     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 2, 1), 1, 2), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_COLLISION_S
324     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 2, 1), 0, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_SCENERY_CHECK_COLLISION_W
325     WIDGETS_END,
326 };
327 
328 constexpr int32_t NumEntranceProperties = 2;
329 constexpr int32_t NumEntranceDetails = 4;
330 constexpr int32_t EntrancePropertiesHeight = 16 + NumEntranceProperties * 21;
331 constexpr int32_t EntranceDetailsHeight = 20 + NumEntranceDetails * 11;
332 static rct_widget EntranceWidgets[] = {
333     MAIN_TILE_INSPECTOR_WIDGETS,
334     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_ENTRANCE_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
335     MakeWidget(PropertyRowCol({ 12, 0 }, 1, 0),         PropertyButtonSize, WindowWidgetType::Button,  WindowColour::Secondary, STR_TILE_INSPECTOR_ENTRANCE_MAKE_USABLE, STR_TILE_INSPECTOR_ENTRANCE_MAKE_USABLE_TIP), // WIDX_ENTRANCE_BUTTON_MAKE_USABLE
336     WIDGETS_END,
337 };
338 
339 constexpr int32_t NumWallProperties = 3;
340 constexpr int32_t NumWallDetails = 2;
341 constexpr int32_t WallPropertiesHeight = 16 + NumWallProperties * 21;
342 constexpr int32_t WallDetailsHeight = 20 + NumWallDetails * 11;
343 static rct_widget WallWidgets[] = {
344     MAIN_TILE_INSPECTOR_WIDGETS,
345     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1),                 PropertyButtonSize, WindowWidgetType::Spinner,      WindowColour::Secondary), // WIDX_WALL_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
346     MakeWidget(PropertyRowCol({ 12, 0 }, 1, 1),                         PropertyButtonSize, WindowWidgetType::DropdownMenu, WindowColour::Secondary), // WIDX_WALL_DROPDOWN_SLOPE
347     MakeWidget(PropertyRowCol({ 12 + PropertyButtonSize.width - 12, 0 }, 1, 1), { 11,  12}, WindowWidgetType::Button,       WindowColour::Secondary, STR_DROPDOWN_GLYPH), // WIDX_WALL_DROPDOWN_SLOPE_BUTTON
348     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 2, 1),                 PropertyButtonSize, WindowWidgetType::Spinner,      WindowColour::Secondary), // WIDX_WALL_SPINNER_ANIMATION_FRAME{,_INCREASE,_DECREASE}
349     WIDGETS_END,
350 };
351 
352 constexpr int32_t NumLargeSceneryProperties = 1;
353 constexpr int32_t NumLargeSceneryDetails = 3;
354 constexpr int32_t LargeSceneryPropertiesHeight = 16 + NumLargeSceneryProperties * 21;
355 constexpr int32_t LargeSceneryDetailsHeight = 20 + NumLargeSceneryDetails * 11;
356 static rct_widget LargeSceneryWidgets[] = {
357     MAIN_TILE_INSPECTOR_WIDGETS,
358     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_LARGE_SCENERY_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
359     WIDGETS_END,
360 };
361 
362 constexpr int32_t NumBannerProperties = 3;
363 constexpr int32_t NumBannerDetails = 1;
364 constexpr int32_t BannerPropertiesHeight = 16 + NumBannerProperties * 21;
365 constexpr int32_t BannerDetailsHeight = 20 + NumBannerDetails * 11;
366 static rct_widget BannerWidgets[] = {
367     MAIN_TILE_INSPECTOR_WIDGETS,
368     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_BANNER_SPINNER_HEIGHT{,_INCREASE,_DECREASE}
369     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 3, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_BANNER_CHECK_BLOCK_NE
370     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 3, 3), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_BANNER_CHECK_BLOCK_SE
371     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 1, 3), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_BANNER_CHECK_BLOCK_SW
372     MakeWidget(CheckboxGroupOffset(PropertyRowCol({ 12, 0 }, 1, 1), 1, 1), { 12, 12 }, WindowWidgetType::Checkbox, WindowColour::Secondary), // WIDX_BANNER_CHECK_BLOCK_NW
373 
374     WIDGETS_END,
375 };
376 
377 constexpr int32_t NumCorruptProperties = 2;
378 constexpr int32_t NumCorruptDetails = 0;
379 constexpr int32_t CorruptPropertiesHeight = 16 + NumCorruptProperties * 21;
380 constexpr int32_t CorruptDetailsHeight = 20 + NumCorruptDetails * 11;
381 static rct_widget CorruptWidgets[] = {
382     MAIN_TILE_INSPECTOR_WIDGETS,
383     MakeSpinnerWidgets(PropertyRowCol({ 12, 0 }, 0, 1), PropertyButtonSize, WindowWidgetType::Spinner, WindowColour::Secondary), // WIDX_CORRUPT_SPINNER_HEIGHT
384     MakeWidget(PropertyRowCol({ 12, 0 }, 1, 0), PropertyButtonSize, WindowWidgetType::Button, WindowColour::Secondary,STR_TILE_INSPECTOR_CLAMP_TO_NEXT, STR_TILE_INSPECTOR_CLAMP_TO_NEXT_TIP ), // WIDX_CORRUPT_BUTTON_CLAMP
385     WIDGETS_END,
386 };
387 
388 static rct_widget *PageWidgets[] = {
389     DefaultWidgets,
390     SurfaceWidgets,
391     PathWidgets,
392     TrackWidgets,
393     SceneryWidgets,
394     EntranceWidgets,
395     WallWidgets,
396     LargeSceneryWidgets,
397     BannerWidgets,
398     CorruptWidgets,
399 };
400 // clang-format on
401 
402 struct TileInspectorGroupboxSettings
403 {
404     // Offsets from the bottom of the window
405     int16_t details_top_offset, details_bottom_offset;
406     int16_t properties_top_offset, properties_bottom_offset;
407     // String to be displayed in the details groupbox
408     rct_string_id string_id;
409 };
410 
MakeGroupboxSettings(int16_t detailsHeight,int16_t propertiesHeight,rct_string_id stringId)411 static constexpr TileInspectorGroupboxSettings MakeGroupboxSettings(
412     int16_t detailsHeight, int16_t propertiesHeight, rct_string_id stringId)
413 {
414     TileInspectorGroupboxSettings settings{};
415     decltype(settings.properties_bottom_offset) offsetSum = 0;
416     settings.properties_bottom_offset = (offsetSum += PADDING_BOTTOM);
417     settings.properties_top_offset = (offsetSum += propertiesHeight);
418     settings.details_bottom_offset = (offsetSum += GROUPBOX_PADDING);
419     settings.details_top_offset = (offsetSum += detailsHeight);
420     settings.string_id = stringId;
421     return settings;
422 }
423 
424 static constexpr TileInspectorGroupboxSettings PageGroupBoxSettings[] = {
425     MakeGroupboxSettings(SurfaceDetailsHeight, SurfacePropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_SURFACE_INFO),
426     MakeGroupboxSettings(PathDetailsHeight, PathPropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_PATH_INFO),
427     MakeGroupboxSettings(TrackDetailsHeight, TrackPropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_TRACK_INFO),
428     MakeGroupboxSettings(SceneryDetailsHeight, SceneryPropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_SCENERY_INFO),
429     MakeGroupboxSettings(EntranceDetailsHeight, EntrancePropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_ENTRANCE_INFO),
430     MakeGroupboxSettings(WallDetailsHeight, WallPropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_WALL_INFO),
431     MakeGroupboxSettings(LargeSceneryDetailsHeight, LargeSceneryPropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_BANNER_INFO),
432     MakeGroupboxSettings(BannerDetailsHeight, BannerPropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_BANNER_INFO),
433     MakeGroupboxSettings(CorruptDetailsHeight, CorruptPropertiesHeight, STR_TILE_INSPECTOR_GROUPBOX_CORRUPT_INFO),
434 };
435 
436 static constexpr int32_t ViewportInteractionFlags = EnumsToFlags(
437     ViewportInteractionItem::Terrain, ViewportInteractionItem::Entity, ViewportInteractionItem::Ride,
438     ViewportInteractionItem::Scenery, ViewportInteractionItem::Footpath, ViewportInteractionItem::FootpathItem,
439     ViewportInteractionItem::ParkEntrance, ViewportInteractionItem::Wall, ViewportInteractionItem::LargeScenery,
440     ViewportInteractionItem::Banner);
441 
442 static int16_t windowTileInspectorHighlightedIndex = -1;
443 static bool windowTileInspectorTileSelected = false;
444 static int32_t windowTileInspectorToolMouseX = 0;
445 static int32_t windowTileInspectorToolMouseY = 0;
446 static bool windowTileInspectorToolCtrlDown = false;
447 static CoordsXY windowTileInspectorToolMap = {};
448 static bool windowTileInspectorApplyToAll = false;
449 static bool windowTileInspectorElementCopied = false;
450 static TileElement tileInspectorCopiedElement;
451 
452 static void window_tile_inspector_mouseup(rct_window* w, rct_widgetindex widgetIndex);
453 static void window_tile_inspector_resize(rct_window* w);
454 static void window_tile_inspector_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
455 static void window_tile_inspector_update(rct_window* w);
456 static void window_tile_inspector_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
457 static void window_tile_inspector_tool_update(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
458 static void window_tile_inspector_update_selected_tile(rct_window* w, const ScreenCoordsXY& screenCoords);
459 static void window_tile_inspector_tool_down(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
460 static void window_tile_inspector_tool_drag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
461 static void window_tile_inspector_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height);
462 static void window_tile_inspector_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
463 static void window_tile_inspector_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
464 static void window_tile_inspector_invalidate(rct_window* w);
465 static void window_tile_inspector_paint(rct_window* w, rct_drawpixelinfo* dpi);
466 static void window_tile_inspector_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex);
467 static void window_tile_inspector_set_page(rct_window* w, const TileInspectorPage page);
468 static void window_tile_inspector_close(rct_window* w);
469 
__anon2a52322c0102(auto& events) 470 static rct_window_event_list TileInspectorWindowEvents([](auto& events) {
471     events.mouse_up = &window_tile_inspector_mouseup;
472     events.resize = &window_tile_inspector_resize;
473     events.mouse_down = &window_tile_inspector_mousedown;
474     events.dropdown = &window_tile_inspector_dropdown;
475     events.update = &window_tile_inspector_update;
476     events.tool_update = &window_tile_inspector_tool_update;
477     events.tool_down = &window_tile_inspector_tool_down;
478     events.tool_drag = &window_tile_inspector_tool_drag;
479     events.get_scroll_size = &window_tile_inspector_scrollgetsize;
480     events.scroll_mousedown = &window_tile_inspector_scrollmousedown;
481     events.scroll_mouseover = &window_tile_inspector_scrollmouseover;
482     events.invalidate = &window_tile_inspector_invalidate;
483     events.paint = &window_tile_inspector_paint;
484     events.scroll_paint = &window_tile_inspector_scrollpaint;
485     events.close = &window_tile_inspector_close;
486 });
487 
488 // clang-format off
489 static uint64_t PageEnabledWidgets[] = {
490     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT),
491     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_SURFACE_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_SURFACE_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_SURFACE_BUTTON_REMOVE_FENCES) | (1ULL << WIDX_SURFACE_BUTTON_RESTORE_FENCES) | (1ULL << WIDX_SURFACE_CHECK_CORNER_N) | (1ULL << WIDX_SURFACE_CHECK_CORNER_E) | (1ULL << WIDX_SURFACE_CHECK_CORNER_S) | (1ULL << WIDX_SURFACE_CHECK_CORNER_W) | (1ULL << WIDX_SURFACE_CHECK_DIAGONAL),
492     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_PATH_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_PATH_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_PATH_CHECK_SLOPED) | (1ULL << WIDX_PATH_CHECK_BROKEN) | (1ULL << WIDX_PATH_CHECK_EDGE_N) | (1ULL << WIDX_PATH_CHECK_EDGE_NE) | (1ULL << WIDX_PATH_CHECK_EDGE_E) | (1ULL << WIDX_PATH_CHECK_EDGE_SE) | (1ULL << WIDX_PATH_CHECK_EDGE_S) | (1ULL << WIDX_PATH_CHECK_EDGE_SW) | (1ULL << WIDX_PATH_CHECK_EDGE_W) | (1ULL << WIDX_PATH_CHECK_EDGE_NW),
493     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_TRACK_CHECK_APPLY_TO_ALL) | (1ULL << WIDX_TRACK_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_TRACK_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_TRACK_CHECK_CHAIN_LIFT) | (1ULL << WIDX_TRACK_CHECK_BLOCK_BRAKE_CLOSED) | (1ULL << WIDX_TRACK_CHECK_IS_INDESTRUCTIBLE),
494     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_SCENERY_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_SCENERY_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_SCENERY_CHECK_QUARTER_N) | (1ULL << WIDX_SCENERY_CHECK_QUARTER_E) | (1ULL << WIDX_SCENERY_CHECK_QUARTER_S) | (1ULL << WIDX_SCENERY_CHECK_QUARTER_W) | (1ULL << WIDX_SCENERY_CHECK_COLLISION_N) | (1ULL << WIDX_SCENERY_CHECK_COLLISION_E) | (1ULL << WIDX_SCENERY_CHECK_COLLISION_S) | (1ULL << WIDX_SCENERY_CHECK_COLLISION_W),
495     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_ENTRANCE_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_ENTRANCE_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_ENTRANCE_BUTTON_MAKE_USABLE),
496     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_WALL_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_WALL_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_WALL_DROPDOWN_SLOPE) | (1ULL << WIDX_WALL_DROPDOWN_SLOPE_BUTTON) | (1ULL << WIDX_WALL_SPINNER_ANIMATION_FRAME_INCREASE) | (1ULL << WIDX_WALL_SPINNER_ANIMATION_FRAME_DECREASE),
497     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_LARGE_SCENERY_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_LARGE_SCENERY_SPINNER_HEIGHT_DECREASE),
498     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_BANNER_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_BANNER_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_BANNER_CHECK_BLOCK_NE) | (1ULL << WIDX_BANNER_CHECK_BLOCK_SE) | (1ULL << WIDX_BANNER_CHECK_BLOCK_SW) | (1ULL << WIDX_BANNER_CHECK_BLOCK_NW),
499     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_COPY) | (1ULL << WIDX_CORRUPT_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_CORRUPT_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_CORRUPT_BUTTON_CLAMP),
500 };
501 
502 static uint64_t PageHoldDownWidgets[] = {
503     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE),
504     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_SURFACE_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_SURFACE_SPINNER_HEIGHT_DECREASE),
505     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_PATH_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_PATH_SPINNER_HEIGHT_DECREASE),
506     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_TRACK_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_TRACK_SPINNER_HEIGHT_DECREASE),
507     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_SCENERY_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_SCENERY_SPINNER_HEIGHT_DECREASE),
508     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_ENTRANCE_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_ENTRANCE_SPINNER_HEIGHT_DECREASE),
509     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_WALL_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_WALL_SPINNER_HEIGHT_DECREASE) | (1ULL << WIDX_WALL_SPINNER_ANIMATION_FRAME_INCREASE) | (1ULL << WIDX_WALL_SPINNER_ANIMATION_FRAME_DECREASE),
510     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_LARGE_SCENERY_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_LARGE_SCENERY_SPINNER_HEIGHT_DECREASE),
511     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_BANNER_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_BANNER_SPINNER_HEIGHT_DECREASE),
512     (1ULL << WIDX_SPINNER_X_INCREASE) | (1ULL << WIDX_SPINNER_X_DECREASE) | (1ULL << WIDX_SPINNER_Y_INCREASE) | (1ULL << WIDX_SPINNER_Y_DECREASE) | (1ULL << WIDX_CORRUPT_SPINNER_HEIGHT_INCREASE) | (1ULL << WIDX_CORRUPT_SPINNER_HEIGHT_DECREASE),
513 };
514 
515 static uint64_t PageDisabledWidgets[] = {
516     (1ULL << WIDX_BUTTON_CORRUPT) | (1ULL << WIDX_BUTTON_MOVE_UP) | (1ULL << WIDX_BUTTON_MOVE_DOWN) | (1ULL << WIDX_BUTTON_REMOVE) | (1ULL << WIDX_BUTTON_ROTATE) | (1ULL << WIDX_BUTTON_COPY),
517     0,
518     0,
519     0,
520     0,
521     0,
522     0,
523     (1ULL << WIDX_BUTTON_ROTATE),
524     0,
525     (1ULL << WIDX_BUTTON_ROTATE),
526 };
527 // clang-format on
528 
window_tile_inspector_open()529 rct_window* window_tile_inspector_open()
530 {
531     rct_window* window;
532 
533     // Check if window is already open
534     window = window_bring_to_front_by_class(WC_TILE_INSPECTOR);
535     if (window != nullptr)
536         return window;
537 
538     window = WindowCreate(ScreenCoordsXY(0, 29), WW, WH, &TileInspectorWindowEvents, WC_TILE_INSPECTOR, WF_RESIZABLE);
539 
540     window_tile_inspector_set_page(window, TileInspectorPage::Default);
541     window->min_width = MIN_WW;
542     window->min_height = MIN_WH;
543     window->max_width = MAX_WW;
544     window->max_height = MAX_WH;
545     windowTileInspectorSelectedIndex = -1;
546     WindowInitScrollWidgets(window);
547 
548     windowTileInspectorTileSelected = false;
549 
550     tool_set(window, WIDX_BACKGROUND, Tool::Crosshair);
551 
552     return window;
553 }
554 
window_tile_inspector_clear_clipboard()555 void window_tile_inspector_clear_clipboard()
556 {
557     windowTileInspectorElementCopied = false;
558 }
559 
window_tile_inspector_get_selected_element(rct_window * w)560 static TileElement* window_tile_inspector_get_selected_element(rct_window* w)
561 {
562     openrct2_assert(
563         windowTileInspectorSelectedIndex >= 0 && windowTileInspectorSelectedIndex < windowTileInspectorElementCount,
564         "Selected list item out of range");
565     return map_get_first_element_at(windowTileInspectorToolMap) + windowTileInspectorSelectedIndex;
566 }
567 
window_tile_inspector_select_element_from_list(rct_window * w,int32_t index)568 static void window_tile_inspector_select_element_from_list(rct_window* w, int32_t index)
569 {
570     if (index < 0 || index >= windowTileInspectorElementCount)
571     {
572         windowTileInspectorSelectedIndex = -1;
573         OpenRCT2::TileInspector::SetSelectedElement(nullptr);
574     }
575     else
576     {
577         windowTileInspectorSelectedIndex = index;
578 
579         const TileElement* const tileElement = window_tile_inspector_get_selected_element(w);
580         OpenRCT2::TileInspector::SetSelectedElement(tileElement);
581     }
582 
583     w->Invalidate();
584 }
585 
window_tile_inspector_load_tile(rct_window * w,TileElement * elementToSelect)586 static void window_tile_inspector_load_tile(rct_window* w, TileElement* elementToSelect)
587 {
588     windowTileInspectorSelectedIndex = -1;
589     w->scrolls[0].v_top = 0;
590 
591     TileElement* element = map_get_first_element_at(windowTileInspectorToolMap);
592     int16_t numItems = 0;
593     do
594     {
595         if (element == nullptr)
596             break;
597         if (element == elementToSelect)
598         {
599             windowTileInspectorSelectedIndex = numItems;
600         }
601 
602         numItems++;
603     } while (!(element++)->IsLastForTile());
604 
605     windowTileInspectorElementCount = numItems;
606 
607     w->Invalidate();
608 }
609 
window_tile_inspector_insert_corrupt_element(int32_t elementIndex)610 static void window_tile_inspector_insert_corrupt_element(int32_t elementIndex)
611 {
612     openrct2_assert(elementIndex >= 0 && elementIndex < windowTileInspectorElementCount, "elementIndex out of range");
613     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::AnyInsertCorrupt, elementIndex);
614     GameActions::Execute(&modifyTile);
615 }
616 
window_tile_inspector_remove_element(int32_t elementIndex)617 static void window_tile_inspector_remove_element(int32_t elementIndex)
618 {
619     openrct2_assert(elementIndex >= 0 && elementIndex < windowTileInspectorElementCount, "elementIndex out of range");
620     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::AnyRemove, elementIndex);
621     GameActions::Execute(&modifyTile);
622 }
623 
window_tile_inspector_rotate_element(int32_t elementIndex)624 static void window_tile_inspector_rotate_element(int32_t elementIndex)
625 {
626     openrct2_assert(elementIndex >= 0 && elementIndex < windowTileInspectorElementCount, "elementIndex out of range");
627     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::AnyRotate, elementIndex);
628     GameActions::Execute(&modifyTile);
629 }
630 
631 // Swap element with its parent
window_tile_inspector_swap_elements(int16_t first,int16_t second)632 static void window_tile_inspector_swap_elements(int16_t first, int16_t second)
633 {
634     bool firstInRange = first >= 0 && first < windowTileInspectorElementCount;
635     bool secondInRange = second >= 0 && second < windowTileInspectorElementCount;
636     // This might happen if two people are modifying the same tile.
637     if (!firstInRange || !secondInRange)
638         return;
639 
640     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::AnySwap, first, second);
641     GameActions::Execute(&modifyTile);
642 }
643 
window_tile_inspector_sort_elements()644 static void window_tile_inspector_sort_elements()
645 {
646     openrct2_assert(windowTileInspectorTileSelected, "No tile selected");
647     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::AnySort);
648     GameActions::Execute(&modifyTile);
649 }
650 
window_tile_inspector_copy_element(rct_window * w)651 static void window_tile_inspector_copy_element(rct_window* w)
652 {
653     // Copy value, in case the element gets moved
654     tileInspectorCopiedElement = *window_tile_inspector_get_selected_element(w);
655     windowTileInspectorElementCopied = true;
656     w->Invalidate();
657 }
658 
window_tile_inspector_paste_element(rct_window * w)659 static void window_tile_inspector_paste_element(rct_window* w)
660 {
661     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::AnyPaste, 0, 0, tileInspectorCopiedElement);
662     GameActions::Execute(&modifyTile);
663 }
664 
window_tile_inspector_base_height_offset(int16_t elementIndex,int8_t heightOffset)665 static void window_tile_inspector_base_height_offset(int16_t elementIndex, int8_t heightOffset)
666 {
667     auto modifyTile = TileModifyAction(
668         windowTileInspectorToolMap, TileModifyType::AnyBaseHeightOffset, elementIndex, heightOffset);
669     GameActions::Execute(&modifyTile);
670 }
671 
window_tile_inspector_surface_show_park_fences(bool showFences)672 static void window_tile_inspector_surface_show_park_fences(bool showFences)
673 {
674     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::SurfaceShowParkFences, showFences);
675     GameActions::Execute(&modifyTile);
676 }
677 
window_tile_inspector_surface_toggle_corner(int32_t cornerIndex)678 static void window_tile_inspector_surface_toggle_corner(int32_t cornerIndex)
679 {
680     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::SurfaceToggleCorner, cornerIndex);
681     GameActions::Execute(&modifyTile);
682 }
683 
window_tile_inspector_surface_toggle_diagonal()684 static void window_tile_inspector_surface_toggle_diagonal()
685 {
686     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::SurfaceToggleDiagonal);
687     GameActions::Execute(&modifyTile);
688 }
689 
window_tile_inspector_path_set_sloped(int32_t elementIndex,bool sloped)690 static void window_tile_inspector_path_set_sloped(int32_t elementIndex, bool sloped)
691 {
692     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::PathSetSlope, elementIndex, sloped);
693     GameActions::Execute(&modifyTile);
694 }
695 
window_tile_inspector_path_set_broken(int32_t elementIndex,bool broken)696 static void window_tile_inspector_path_set_broken(int32_t elementIndex, bool broken)
697 {
698     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::PathSetBroken, elementIndex, broken);
699     GameActions::Execute(&modifyTile);
700 }
701 
window_tile_inspector_path_toggle_edge(int32_t elementIndex,int32_t cornerIndex)702 static void window_tile_inspector_path_toggle_edge(int32_t elementIndex, int32_t cornerIndex)
703 {
704     openrct2_assert(elementIndex >= 0 && elementIndex < windowTileInspectorElementCount, "elementIndex out of range");
705     openrct2_assert(cornerIndex >= 0 && cornerIndex < 8, "cornerIndex out of range");
706     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::PathToggleEdge, elementIndex, cornerIndex);
707     GameActions::Execute(&modifyTile);
708 }
709 
window_tile_inspector_entrance_make_usable(int32_t elementIndex)710 static void window_tile_inspector_entrance_make_usable(int32_t elementIndex)
711 {
712     Guard::ArgumentInRange(elementIndex, 0, windowTileInspectorElementCount - 1);
713     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::EntranceMakeUsable, elementIndex);
714     GameActions::Execute(&modifyTile);
715 }
716 
window_tile_inspector_wall_set_slope(int32_t elementIndex,int32_t slopeValue)717 static void window_tile_inspector_wall_set_slope(int32_t elementIndex, int32_t slopeValue)
718 {
719     // Make sure only the correct bits are set
720     openrct2_assert((slopeValue & 3) == slopeValue, "slopeValue doesn't match its mask");
721     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::WallSetSlope, elementIndex, slopeValue);
722     GameActions::Execute(&modifyTile);
723 }
724 
window_tile_inspector_wall_animation_frame_offset(int16_t elementIndex,int8_t animationFrameOffset)725 static void window_tile_inspector_wall_animation_frame_offset(int16_t elementIndex, int8_t animationFrameOffset)
726 {
727     auto modifyTile = TileModifyAction(
728         windowTileInspectorToolMap, TileModifyType::WallSetAnimationFrame, elementIndex, animationFrameOffset);
729     GameActions::Execute(&modifyTile);
730 }
731 
window_tile_inspector_track_block_height_offset(int32_t elementIndex,int8_t heightOffset)732 static void window_tile_inspector_track_block_height_offset(int32_t elementIndex, int8_t heightOffset)
733 {
734     auto modifyTile = TileModifyAction(
735         windowTileInspectorToolMap, TileModifyType::TrackBaseHeightOffset, elementIndex, heightOffset);
736     GameActions::Execute(&modifyTile);
737 }
738 
window_tile_inspector_track_block_set_lift(int32_t elementIndex,bool entireTrackBlock,bool chain)739 static void window_tile_inspector_track_block_set_lift(int32_t elementIndex, bool entireTrackBlock, bool chain)
740 {
741     auto modifyTile = TileModifyAction(
742         windowTileInspectorToolMap, entireTrackBlock ? TileModifyType::TrackSetChainBlock : TileModifyType::TrackSetChain,
743         elementIndex, chain);
744     GameActions::Execute(&modifyTile);
745 }
746 
window_tile_inspector_track_set_block_brake(int32_t elementIndex,bool blockBrake)747 static void window_tile_inspector_track_set_block_brake(int32_t elementIndex, bool blockBrake)
748 {
749     auto modifyTile = TileModifyAction(
750         windowTileInspectorToolMap, TileModifyType::TrackSetBlockBrake, elementIndex, blockBrake);
751     GameActions::Execute(&modifyTile);
752 }
753 
window_tile_inspector_track_set_indestructible(int32_t elementIndex,bool isIndestructible)754 static void window_tile_inspector_track_set_indestructible(int32_t elementIndex, bool isIndestructible)
755 {
756     auto modifyTile = TileModifyAction(
757         windowTileInspectorToolMap, TileModifyType::TrackSetIndestructible, elementIndex, isIndestructible);
758     GameActions::Execute(&modifyTile);
759 }
760 
window_tile_inspector_quarter_tile_set(int32_t elementIndex,const int32_t quarterIndex)761 static void window_tile_inspector_quarter_tile_set(int32_t elementIndex, const int32_t quarterIndex)
762 {
763     // quarterIndex is widget index relative to WIDX_SCENERY_CHECK_QUARTER_N, so a value from 0-3
764     openrct2_assert(quarterIndex >= 0 && quarterIndex < 4, "quarterIndex out of range");
765     auto modifyTile = TileModifyAction(
766         windowTileInspectorToolMap, TileModifyType::ScenerySetQuarterLocation, elementIndex,
767         (quarterIndex - get_current_rotation()) & 3);
768     GameActions::Execute(&modifyTile);
769 }
770 
window_tile_inspector_toggle_quadrant_collosion(int32_t elementIndex,const int32_t quadrantIndex)771 static void window_tile_inspector_toggle_quadrant_collosion(int32_t elementIndex, const int32_t quadrantIndex)
772 {
773     auto modifyTile = TileModifyAction(
774         windowTileInspectorToolMap, TileModifyType::ScenerySetQuarterCollision, elementIndex,
775         (quadrantIndex + 2 - get_current_rotation()) & 3);
776     GameActions::Execute(&modifyTile);
777 }
778 
window_tile_inspector_banner_toggle_block(int32_t elementIndex,int32_t edgeIndex)779 static void window_tile_inspector_banner_toggle_block(int32_t elementIndex, int32_t edgeIndex)
780 {
781     openrct2_assert(edgeIndex >= 0 && edgeIndex < 4, "edgeIndex out of range");
782 
783     // Make edgeIndex abstract
784     edgeIndex = (edgeIndex - get_current_rotation()) & 3;
785     auto modifyTile = TileModifyAction(
786         windowTileInspectorToolMap, TileModifyType::BannerToggleBlockingEdge, elementIndex, edgeIndex);
787     GameActions::Execute(&modifyTile);
788 }
789 
window_tile_inspector_clamp_corrupt(int32_t elementIndex)790 static void window_tile_inspector_clamp_corrupt(int32_t elementIndex)
791 {
792     auto modifyTile = TileModifyAction(windowTileInspectorToolMap, TileModifyType::CorruptClamp, elementIndex);
793     GameActions::Execute(&modifyTile);
794 }
795 
window_tile_inspector_close(rct_window * w)796 static void window_tile_inspector_close(rct_window* w)
797 {
798     OpenRCT2::TileInspector::SetSelectedElement(nullptr);
799 }
800 
window_tile_inspector_mouseup(rct_window * w,rct_widgetindex widgetIndex)801 static void window_tile_inspector_mouseup(rct_window* w, rct_widgetindex widgetIndex)
802 {
803     switch (widgetIndex)
804     {
805         case WIDX_CLOSE:
806             tool_cancel();
807             window_close(w);
808             return;
809         case WIDX_BUTTON_CORRUPT:
810             window_tile_inspector_insert_corrupt_element(windowTileInspectorSelectedIndex);
811             break;
812         case WIDX_BUTTON_REMOVE:
813         {
814             int32_t nextItemToSelect = windowTileInspectorSelectedIndex - 1;
815             window_tile_inspector_remove_element(windowTileInspectorSelectedIndex);
816             window_tile_inspector_select_element_from_list(w, nextItemToSelect);
817             break;
818         }
819         case WIDX_BUTTON_ROTATE:
820             window_tile_inspector_rotate_element(windowTileInspectorSelectedIndex);
821             break;
822         case WIDX_BUTTON_SORT:
823             window_tile_inspector_sort_elements();
824             break;
825         case WIDX_BUTTON_COPY:
826             window_tile_inspector_copy_element(w);
827             break;
828         case WIDX_BUTTON_PASTE:
829             window_tile_inspector_paste_element(w);
830             break;
831         case WIDX_BUTTON_MOVE_UP:
832             window_tile_inspector_swap_elements(windowTileInspectorSelectedIndex, windowTileInspectorSelectedIndex + 1);
833             break;
834         case WIDX_BUTTON_MOVE_DOWN:
835             window_tile_inspector_swap_elements(windowTileInspectorSelectedIndex - 1, windowTileInspectorSelectedIndex);
836             break;
837     }
838 
839     // Only element-specific widgets from now on
840     if (w->tileInspectorPage == TileInspectorPage::Default || windowTileInspectorSelectedIndex == -1)
841     {
842         return;
843     }
844 
845     TileElement* const tileElement = window_tile_inspector_get_selected_element(w);
846 
847     // Update selection, can be nullptr.
848     OpenRCT2::TileInspector::SetSelectedElement(tileElement);
849 
850     if (tileElement == nullptr)
851         return;
852 
853     // Page widgets
854     switch (tileElement->GetType())
855     {
856         case TILE_ELEMENT_TYPE_SURFACE:
857             switch (widgetIndex)
858             {
859                 case WIDX_SURFACE_BUTTON_REMOVE_FENCES:
860                     window_tile_inspector_surface_show_park_fences(false);
861                     break;
862                 case WIDX_SURFACE_BUTTON_RESTORE_FENCES:
863                     window_tile_inspector_surface_show_park_fences(true);
864                     break;
865                 case WIDX_SURFACE_CHECK_CORNER_N:
866                 case WIDX_SURFACE_CHECK_CORNER_E:
867                 case WIDX_SURFACE_CHECK_CORNER_S:
868                 case WIDX_SURFACE_CHECK_CORNER_W:
869                     window_tile_inspector_surface_toggle_corner(
870                         ((widgetIndex - WIDX_SURFACE_CHECK_CORNER_N) + 2 - get_current_rotation()) & 3);
871                     break;
872                 case WIDX_SURFACE_CHECK_DIAGONAL:
873                     window_tile_inspector_surface_toggle_diagonal();
874                     break;
875             } // switch widgetindex
876             break;
877 
878         case TILE_ELEMENT_TYPE_PATH:
879             switch (widgetIndex)
880             {
881                 case WIDX_PATH_CHECK_SLOPED:
882                     window_tile_inspector_path_set_sloped(windowTileInspectorSelectedIndex, !tileElement->AsPath()->IsSloped());
883                     break;
884                 case WIDX_PATH_CHECK_BROKEN:
885                     window_tile_inspector_path_set_broken(windowTileInspectorSelectedIndex, !tileElement->AsPath()->IsBroken());
886                     break;
887                 case WIDX_PATH_CHECK_EDGE_E:
888                 case WIDX_PATH_CHECK_EDGE_S:
889                 case WIDX_PATH_CHECK_EDGE_W:
890                 case WIDX_PATH_CHECK_EDGE_N:
891                 {
892                     // 0 = east/right, 1 = south/bottom, 2 = west/left, 3 = north/top
893                     const int32_t eswn = (widgetIndex - WIDX_PATH_CHECK_EDGE_E) / 2;
894                     // Transform to world orientation
895                     const int32_t index = (eswn - get_current_rotation()) & 3;
896                     window_tile_inspector_path_toggle_edge(
897                         windowTileInspectorSelectedIndex,
898                         index + 4); // The corners are stored in the 4 most significant bits, hence the + 4
899                     break;
900                 }
901                 case WIDX_PATH_CHECK_EDGE_NE:
902                 case WIDX_PATH_CHECK_EDGE_SE:
903                 case WIDX_PATH_CHECK_EDGE_SW:
904                 case WIDX_PATH_CHECK_EDGE_NW:
905                 {
906                     // 0 = NE, 1 = SE, 2 = SW, 3 = NW
907                     const int32_t neseswnw = (widgetIndex - WIDX_PATH_CHECK_EDGE_NE) / 2;
908                     // Transform to world orientation
909                     const int32_t index = (neseswnw - get_current_rotation()) & 3;
910                     window_tile_inspector_path_toggle_edge(windowTileInspectorSelectedIndex, index);
911                     break;
912                 }
913             } // switch widget index
914             break;
915 
916         case TILE_ELEMENT_TYPE_TRACK:
917             switch (widgetIndex)
918             {
919                 case WIDX_TRACK_CHECK_APPLY_TO_ALL:
920                     windowTileInspectorApplyToAll ^= 1;
921                     widget_invalidate(w, widgetIndex);
922                     break;
923                 case WIDX_TRACK_CHECK_CHAIN_LIFT:
924                 {
925                     bool entireTrackBlock = WidgetIsPressed(w, WIDX_TRACK_CHECK_APPLY_TO_ALL);
926                     bool newLift = !tileElement->AsTrack()->HasChain();
927                     window_tile_inspector_track_block_set_lift(windowTileInspectorSelectedIndex, entireTrackBlock, newLift);
928                     break;
929                 }
930                 case WIDX_TRACK_CHECK_BLOCK_BRAKE_CLOSED:
931                     window_tile_inspector_track_set_block_brake(
932                         windowTileInspectorSelectedIndex, !tileElement->AsTrack()->BlockBrakeClosed());
933                     break;
934                 case WIDX_TRACK_CHECK_IS_INDESTRUCTIBLE:
935                     window_tile_inspector_track_set_indestructible(
936                         windowTileInspectorSelectedIndex, !tileElement->AsTrack()->IsIndestructible());
937                     break;
938             } // switch widget index
939             break;
940 
941         case TILE_ELEMENT_TYPE_SMALL_SCENERY:
942             switch (widgetIndex)
943             {
944                 case WIDX_SCENERY_CHECK_QUARTER_N:
945                 case WIDX_SCENERY_CHECK_QUARTER_E:
946                 case WIDX_SCENERY_CHECK_QUARTER_S:
947                 case WIDX_SCENERY_CHECK_QUARTER_W:
948                     window_tile_inspector_quarter_tile_set(
949                         windowTileInspectorSelectedIndex, widgetIndex - WIDX_SCENERY_CHECK_QUARTER_N);
950                     break;
951                 case WIDX_SCENERY_CHECK_COLLISION_N:
952                 case WIDX_SCENERY_CHECK_COLLISION_E:
953                 case WIDX_SCENERY_CHECK_COLLISION_S:
954                 case WIDX_SCENERY_CHECK_COLLISION_W:
955                     window_tile_inspector_toggle_quadrant_collosion(
956                         windowTileInspectorSelectedIndex, widgetIndex - WIDX_SCENERY_CHECK_COLLISION_N);
957                     break;
958             } // switch widget index
959             break;
960 
961         case TILE_ELEMENT_TYPE_ENTRANCE:
962             switch (widgetIndex)
963             {
964                 case WIDX_ENTRANCE_BUTTON_MAKE_USABLE:
965                     window_tile_inspector_entrance_make_usable(windowTileInspectorSelectedIndex);
966                     break;
967             } // switch widget index
968             break;
969 
970         case TILE_ELEMENT_TYPE_BANNER:
971             switch (widgetIndex)
972             {
973                 case WIDX_BANNER_CHECK_BLOCK_NE:
974                 case WIDX_BANNER_CHECK_BLOCK_SE:
975                 case WIDX_BANNER_CHECK_BLOCK_SW:
976                 case WIDX_BANNER_CHECK_BLOCK_NW:
977                     window_tile_inspector_banner_toggle_block(
978                         windowTileInspectorSelectedIndex, widgetIndex - WIDX_BANNER_CHECK_BLOCK_NE);
979                     break;
980             } // switch widget index
981             break;
982 
983         case TILE_ELEMENT_TYPE_CORRUPT:
984             switch (widgetIndex)
985             {
986                 case WIDX_CORRUPT_BUTTON_CLAMP:
987                     window_tile_inspector_clamp_corrupt(windowTileInspectorSelectedIndex);
988                     break;
989             } // switch widget index
990             break;
991         case TILE_ELEMENT_TYPE_LARGE_SCENERY:
992         case TILE_ELEMENT_TYPE_WALL:
993         default:
994             break;
995     }
996 }
997 
window_tile_inspector_resize(rct_window * w)998 static void window_tile_inspector_resize(rct_window* w)
999 {
1000     if (w->width < w->min_width)
1001     {
1002         w->Invalidate();
1003         w->width = w->min_width;
1004     }
1005     if (w->height < w->min_height)
1006     {
1007         w->Invalidate();
1008         w->height = w->min_height;
1009     }
1010 }
1011 
window_tile_inspector_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)1012 static void window_tile_inspector_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
1013 {
1014     switch (widgetIndex)
1015     {
1016         case WIDX_SPINNER_X_INCREASE:
1017             windowTileInspectorTile.x = std::min<int32_t>(windowTileInspectorTile.x + 1, MAXIMUM_MAP_SIZE_TECHNICAL - 1);
1018             windowTileInspectorToolMap.x = std::min<int32_t>(windowTileInspectorToolMap.x + 32, MAXIMUM_TILE_START_XY);
1019             window_tile_inspector_load_tile(w, nullptr);
1020             break;
1021         case WIDX_SPINNER_X_DECREASE:
1022             windowTileInspectorTile.x = std::max<int32_t>(windowTileInspectorTile.x - 1, 0);
1023             windowTileInspectorToolMap.x = std::max<int32_t>(windowTileInspectorToolMap.x - 32, 0);
1024             window_tile_inspector_load_tile(w, nullptr);
1025             break;
1026         case WIDX_SPINNER_Y_INCREASE:
1027             windowTileInspectorTile.y = std::min<int32_t>(windowTileInspectorTile.y + 1, MAXIMUM_MAP_SIZE_TECHNICAL - 1);
1028             windowTileInspectorToolMap.y = std::min<int32_t>(windowTileInspectorToolMap.y + 32, MAXIMUM_TILE_START_XY);
1029             window_tile_inspector_load_tile(w, nullptr);
1030             break;
1031         case WIDX_SPINNER_Y_DECREASE:
1032             windowTileInspectorTile.y = std::max<int32_t>(windowTileInspectorTile.y - 1, 0);
1033             windowTileInspectorToolMap.y = std::max<int32_t>(windowTileInspectorToolMap.y - 32, 0);
1034             window_tile_inspector_load_tile(w, nullptr);
1035             break;
1036     } // switch widget index
1037 
1038     // Only element-specific widgets from now on
1039     if (w->tileInspectorPage == TileInspectorPage::Default || windowTileInspectorSelectedIndex == -1)
1040     {
1041         return;
1042     }
1043 
1044     const TileElement* tileElement = window_tile_inspector_get_selected_element(w);
1045     if (tileElement == nullptr)
1046         return;
1047 
1048     switch (tileElement->GetType())
1049     {
1050         case TILE_ELEMENT_TYPE_SURFACE:
1051             switch (widgetIndex)
1052             {
1053                 case WIDX_SURFACE_SPINNER_HEIGHT_INCREASE:
1054                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1055                     break;
1056                 case WIDX_SURFACE_SPINNER_HEIGHT_DECREASE:
1057                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1058                     break;
1059             } // switch widget index
1060             break;
1061 
1062         case TILE_ELEMENT_TYPE_PATH:
1063             switch (widgetIndex)
1064             {
1065                 case WIDX_PATH_SPINNER_HEIGHT_INCREASE:
1066                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1067                     break;
1068                 case WIDX_PATH_SPINNER_HEIGHT_DECREASE:
1069                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1070                     break;
1071             } // switch widget index
1072             break;
1073 
1074         case TILE_ELEMENT_TYPE_TRACK:
1075             switch (widgetIndex)
1076             {
1077                 case WIDX_TRACK_SPINNER_HEIGHT_INCREASE:
1078                     if (WidgetIsPressed(w, WIDX_TRACK_CHECK_APPLY_TO_ALL))
1079                     {
1080                         window_tile_inspector_track_block_height_offset(windowTileInspectorSelectedIndex, 1);
1081                     }
1082                     else
1083                     {
1084                         window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1085                     }
1086                     break;
1087                 case WIDX_TRACK_SPINNER_HEIGHT_DECREASE:
1088                     if (WidgetIsPressed(w, WIDX_TRACK_CHECK_APPLY_TO_ALL))
1089                     {
1090                         window_tile_inspector_track_block_height_offset(windowTileInspectorSelectedIndex, -1);
1091                     }
1092                     else
1093                     {
1094                         window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1095                     }
1096                     break;
1097             } // switch widget index
1098             break;
1099 
1100         case TILE_ELEMENT_TYPE_SMALL_SCENERY:
1101             switch (widgetIndex)
1102             {
1103                 case WIDX_SCENERY_SPINNER_HEIGHT_INCREASE:
1104                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1105                     break;
1106                 case WIDX_SCENERY_SPINNER_HEIGHT_DECREASE:
1107                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1108                     break;
1109             } // switch widget index
1110             break;
1111 
1112         case TILE_ELEMENT_TYPE_ENTRANCE:
1113             switch (widgetIndex)
1114             {
1115                 case WIDX_ENTRANCE_SPINNER_HEIGHT_INCREASE:
1116                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1117                     break;
1118                 case WIDX_ENTRANCE_SPINNER_HEIGHT_DECREASE:
1119                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1120                     break;
1121                 case WIDX_ENTRANCE_BUTTON_MAKE_USABLE:
1122                     window_tile_inspector_entrance_make_usable(windowTileInspectorSelectedIndex);
1123                     break;
1124             } // switch widget index
1125             break;
1126 
1127         case TILE_ELEMENT_TYPE_WALL:
1128             switch (widgetIndex)
1129             {
1130                 case WIDX_WALL_SPINNER_HEIGHT_INCREASE:
1131                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1132                     break;
1133                 case WIDX_WALL_SPINNER_HEIGHT_DECREASE:
1134                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1135                     break;
1136                 case WIDX_WALL_DROPDOWN_SLOPE_BUTTON:
1137                 {
1138                     // Use dropdown instead of dropdown button
1139                     widget--;
1140 
1141                     // Fill dropdown list
1142                     gDropdownItemsFormat[0] = STR_DROPDOWN_MENU_LABEL;
1143                     gDropdownItemsFormat[1] = STR_DROPDOWN_MENU_LABEL;
1144                     gDropdownItemsFormat[2] = STR_DROPDOWN_MENU_LABEL;
1145                     gDropdownItemsArgs[0] = STR_TILE_INSPECTOR_WALL_FLAT;
1146                     gDropdownItemsArgs[1] = STR_TILE_INSPECTOR_WALL_SLOPED_LEFT;
1147                     gDropdownItemsArgs[2] = STR_TILE_INSPECTOR_WALL_SLOPED_RIGHT;
1148                     WindowDropdownShowTextCustomWidth(
1149                         { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1, w->colours[1], 0,
1150                         Dropdown::Flag::StayOpen, 3, widget->width() - 3);
1151 
1152                     // Set current value as checked
1153                     Dropdown::SetChecked(tileElement->AsWall()->GetSlope(), true);
1154                     break;
1155                 }
1156                 case WIDX_WALL_SPINNER_ANIMATION_FRAME_INCREASE:
1157                     window_tile_inspector_wall_animation_frame_offset(windowTileInspectorSelectedIndex, 1);
1158                     break;
1159                 case WIDX_WALL_SPINNER_ANIMATION_FRAME_DECREASE:
1160                     window_tile_inspector_wall_animation_frame_offset(windowTileInspectorSelectedIndex, -1);
1161                     break;
1162             } // switch widget index
1163             break;
1164 
1165         case TILE_ELEMENT_TYPE_LARGE_SCENERY:
1166             switch (widgetIndex)
1167             {
1168                 case WIDX_LARGE_SCENERY_SPINNER_HEIGHT_INCREASE:
1169                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1170                     break;
1171                 case WIDX_LARGE_SCENERY_SPINNER_HEIGHT_DECREASE:
1172                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1173                     break;
1174             } // switch widget index
1175             break;
1176 
1177         case TILE_ELEMENT_TYPE_BANNER:
1178             switch (widgetIndex)
1179             {
1180                 case WIDX_BANNER_SPINNER_HEIGHT_INCREASE:
1181                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1182                     break;
1183                 case WIDX_BANNER_SPINNER_HEIGHT_DECREASE:
1184                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1185                     break;
1186             } // switch widget index
1187             break;
1188 
1189         case TILE_ELEMENT_TYPE_CORRUPT:
1190             switch (widgetIndex)
1191             {
1192                 case WIDX_CORRUPT_SPINNER_HEIGHT_INCREASE:
1193                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, 1);
1194                     break;
1195                 case WIDX_CORRUPT_SPINNER_HEIGHT_DECREASE:
1196                     window_tile_inspector_base_height_offset(windowTileInspectorSelectedIndex, -1);
1197                     break;
1198             } // switch widget index
1199         default:
1200             break;
1201     }
1202 }
1203 
window_tile_inspector_update(rct_window * w)1204 static void window_tile_inspector_update(rct_window* w)
1205 {
1206     // Check if the mouse is hovering over the list
1207     if (!WidgetIsHighlighted(w, WIDX_LIST))
1208     {
1209         windowTileInspectorHighlightedIndex = -1;
1210         widget_invalidate(w, WIDX_LIST);
1211     }
1212 
1213     if (gCurrentToolWidget.window_classification != WC_TILE_INSPECTOR)
1214         window_close(w);
1215 }
1216 
window_tile_inspector_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)1217 static void window_tile_inspector_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
1218 {
1219     if (dropdownIndex == -1)
1220     {
1221         return;
1222     }
1223 
1224     // Get selected element
1225     TileElement* const tileElement = window_tile_inspector_get_selected_element(w);
1226 
1227     if (w->tileInspectorPage == TileInspectorPage::Wall)
1228     {
1229         openrct2_assert(tileElement->GetType() == TILE_ELEMENT_TYPE_WALL, "Element is not a wall");
1230 
1231         if (widgetIndex == WIDX_WALL_DROPDOWN_SLOPE_BUTTON)
1232         {
1233             window_tile_inspector_wall_set_slope(windowTileInspectorSelectedIndex, dropdownIndex);
1234         }
1235     }
1236 }
1237 
window_tile_inspector_tool_update(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1238 static void window_tile_inspector_tool_update(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1239 {
1240     map_invalidate_selection_rect();
1241 
1242     gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE;
1243 
1244     CoordsXY mapCoords;
1245     TileElement* clickedElement = nullptr;
1246     bool mouseOnViewport = false;
1247     if (InputTestPlaceObjectModifier(PLACE_OBJECT_MODIFIER_COPY_Z))
1248     {
1249         auto info = get_map_coordinates_from_pos(screenCoords, ViewportInteractionFlags);
1250         clickedElement = info.Element;
1251         mapCoords = info.Loc;
1252     }
1253 
1254     // Even if Ctrl was pressed, fall back to normal selection when there was nothing under the cursor
1255     if (clickedElement == nullptr)
1256     {
1257         auto mouseCoords = screen_pos_to_map_pos(screenCoords, nullptr);
1258         if (mouseCoords.has_value())
1259         {
1260             mouseOnViewport = true;
1261             mapCoords = mouseCoords.value();
1262         }
1263     }
1264 
1265     if (mouseOnViewport)
1266     {
1267         gMapSelectPositionA = gMapSelectPositionB = mapCoords;
1268     }
1269     else if (windowTileInspectorTileSelected)
1270     {
1271         gMapSelectPositionA = gMapSelectPositionB = windowTileInspectorToolMap;
1272     }
1273     else
1274     {
1275         gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE;
1276     }
1277 
1278     gMapSelectType = MAP_SELECT_TYPE_FULL;
1279     map_invalidate_selection_rect();
1280 }
1281 
window_tile_inspector_update_selected_tile(rct_window * w,const ScreenCoordsXY & screenCoords)1282 static void window_tile_inspector_update_selected_tile(rct_window* w, const ScreenCoordsXY& screenCoords)
1283 {
1284     const bool ctrlIsHeldDown = InputTestPlaceObjectModifier(PLACE_OBJECT_MODIFIER_COPY_Z);
1285 
1286     // Mouse hasn't moved
1287     if (screenCoords.x == windowTileInspectorToolMouseX && screenCoords.y == windowTileInspectorToolMouseY
1288         && windowTileInspectorToolCtrlDown == ctrlIsHeldDown)
1289     {
1290         return;
1291     }
1292 
1293     windowTileInspectorToolMouseX = screenCoords.x;
1294     windowTileInspectorToolMouseY = screenCoords.y;
1295     windowTileInspectorToolCtrlDown = ctrlIsHeldDown;
1296 
1297     CoordsXY mapCoords{};
1298     TileElement* clickedElement = nullptr;
1299     if (ctrlIsHeldDown)
1300     {
1301         auto info = get_map_coordinates_from_pos(screenCoords, ViewportInteractionFlags);
1302         clickedElement = info.Element;
1303         mapCoords = info.Loc;
1304     }
1305 
1306     // Even if Ctrl was pressed, fall back to normal selection when there was nothing under the cursor
1307     if (clickedElement == nullptr)
1308     {
1309         auto mouseCoords = screen_pos_to_map_pos(screenCoords, nullptr);
1310 
1311         if (!mouseCoords.has_value())
1312         {
1313             return;
1314         }
1315 
1316         mapCoords = mouseCoords.value();
1317         // Tile is already selected
1318         if (windowTileInspectorTileSelected && mapCoords.x == windowTileInspectorToolMap.x
1319             && mapCoords.y == windowTileInspectorToolMap.y)
1320         {
1321             return;
1322         }
1323     }
1324 
1325     windowTileInspectorTileSelected = true;
1326     windowTileInspectorToolMap = mapCoords;
1327     windowTileInspectorTile = TileCoordsXY(mapCoords);
1328 
1329     OpenRCT2::TileInspector::SetSelectedElement(clickedElement);
1330 
1331     window_tile_inspector_load_tile(w, clickedElement);
1332 }
1333 
window_tile_inspector_tool_down(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1334 static void window_tile_inspector_tool_down(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1335 {
1336     window_tile_inspector_update_selected_tile(w, screenCoords);
1337 }
1338 
window_tile_inspector_tool_drag(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1339 static void window_tile_inspector_tool_drag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1340 {
1341     window_tile_inspector_update_selected_tile(w, screenCoords);
1342 }
1343 
window_tile_inspector_scrollgetsize(rct_window * w,int32_t scrollIndex,int32_t * width,int32_t * height)1344 static void window_tile_inspector_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
1345 {
1346     *width = WW - 30;
1347     *height = windowTileInspectorElementCount * SCROLLABLE_ROW_HEIGHT;
1348 }
1349 
window_tile_inspector_set_page(rct_window * w,const TileInspectorPage page)1350 static void window_tile_inspector_set_page(rct_window* w, const TileInspectorPage page)
1351 {
1352     // Invalidate the window already, because the size may change
1353     w->Invalidate();
1354 
1355     // subtract current page height, then add new page height
1356     if (w->tileInspectorPage != TileInspectorPage::Default)
1357     {
1358         auto index = EnumValue(w->tileInspectorPage) - 1;
1359         w->height -= PageGroupBoxSettings[index].details_top_offset - GROUPBOX_PADDING - 3;
1360         w->min_height -= PageGroupBoxSettings[index].details_top_offset - GROUPBOX_PADDING - 3;
1361     }
1362     if (page != TileInspectorPage::Default)
1363     {
1364         auto index = EnumValue(page) - 1;
1365         w->height += PageGroupBoxSettings[index].details_top_offset - GROUPBOX_PADDING - 3;
1366         w->min_height += PageGroupBoxSettings[index].details_top_offset - GROUPBOX_PADDING - 3;
1367     }
1368     w->tileInspectorPage = page;
1369     auto pageIndex = EnumValue(page);
1370     w->widgets = PageWidgets[pageIndex];
1371     w->enabled_widgets = PageEnabledWidgets[pageIndex];
1372     w->hold_down_widgets = PageHoldDownWidgets[pageIndex];
1373     w->disabled_widgets = PageDisabledWidgets[pageIndex];
1374     w->pressed_widgets = 0;
1375 }
1376 
window_tile_inspector_scrollmousedown(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)1377 static void window_tile_inspector_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
1378 {
1379     // Because the list items are displayed in reverse order, subtract the calculated index from the amount of elements
1380     const int16_t index = windowTileInspectorElementCount - (screenCoords.y - 1) / SCROLLABLE_ROW_HEIGHT - 1;
1381     window_tile_inspector_select_element_from_list(w, index);
1382 }
1383 
window_tile_inspector_scrollmouseover(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)1384 static void window_tile_inspector_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
1385 {
1386     int16_t index = windowTileInspectorElementCount - (screenCoords.y - 1) / SCROLLABLE_ROW_HEIGHT - 1;
1387     if (index < 0 || index >= windowTileInspectorElementCount)
1388         windowTileInspectorHighlightedIndex = -1;
1389     else
1390         windowTileInspectorHighlightedIndex = index;
1391 
1392     widget_invalidate(w, WIDX_LIST);
1393 }
1394 
window_tile_inspector_invalidate(rct_window * w)1395 static void window_tile_inspector_invalidate(rct_window* w)
1396 {
1397     // Set the correct page automatically
1398     TileInspectorPage page = TileInspectorPage::Default;
1399     if (windowTileInspectorSelectedIndex != -1)
1400     {
1401         const auto element = window_tile_inspector_get_selected_element(w);
1402         auto type = element->GetType();
1403         switch (type)
1404         {
1405             case TILE_ELEMENT_TYPE_SURFACE:
1406                 page = TileInspectorPage::Surface;
1407                 break;
1408             case TILE_ELEMENT_TYPE_PATH:
1409                 page = TileInspectorPage::Path;
1410                 break;
1411             case TILE_ELEMENT_TYPE_TRACK:
1412                 page = TileInspectorPage::Track;
1413                 break;
1414             case TILE_ELEMENT_TYPE_SMALL_SCENERY:
1415                 page = TileInspectorPage::Scenery;
1416                 break;
1417             case TILE_ELEMENT_TYPE_ENTRANCE:
1418                 page = TileInspectorPage::Entrance;
1419                 break;
1420             case TILE_ELEMENT_TYPE_WALL:
1421                 page = TileInspectorPage::Wall;
1422                 break;
1423             case TILE_ELEMENT_TYPE_LARGE_SCENERY:
1424                 page = TileInspectorPage::LargeScenery;
1425                 break;
1426             case TILE_ELEMENT_TYPE_BANNER:
1427                 page = TileInspectorPage::Banner;
1428                 break;
1429             case TILE_ELEMENT_TYPE_CORRUPT:
1430             default:
1431                 page = TileInspectorPage::Corrupt;
1432                 break;
1433         }
1434     }
1435 
1436     if (w->tileInspectorPage != page)
1437     {
1438         window_tile_inspector_set_page(w, page);
1439         w->Invalidate();
1440     }
1441 
1442     // X and Y spinners
1443     WidgetSetEnabled(
1444         w, WIDX_SPINNER_X_INCREASE,
1445         (windowTileInspectorTileSelected && ((windowTileInspectorToolMap.x / 32) < MAXIMUM_MAP_SIZE_TECHNICAL - 1)));
1446     WidgetSetEnabled(
1447         w, WIDX_SPINNER_X_DECREASE, (windowTileInspectorTileSelected && ((windowTileInspectorToolMap.x / 32) > 0)));
1448     WidgetSetEnabled(
1449         w, WIDX_SPINNER_Y_INCREASE,
1450         (windowTileInspectorTileSelected && ((windowTileInspectorToolMap.y / 32) < MAXIMUM_MAP_SIZE_TECHNICAL - 1)));
1451     WidgetSetEnabled(
1452         w, WIDX_SPINNER_Y_DECREASE, (windowTileInspectorTileSelected && ((windowTileInspectorToolMap.y / 32) > 0)));
1453 
1454     // Sort buttons
1455     WidgetSetEnabled(w, WIDX_BUTTON_SORT, (windowTileInspectorTileSelected && windowTileInspectorElementCount > 1));
1456 
1457     // Move Up button
1458     WidgetSetEnabled(
1459         w, WIDX_BUTTON_MOVE_UP,
1460         (windowTileInspectorSelectedIndex != -1 && windowTileInspectorSelectedIndex < windowTileInspectorElementCount - 1));
1461     widget_invalidate(w, WIDX_BUTTON_MOVE_UP);
1462 
1463     // Move Down button
1464     WidgetSetEnabled(w, WIDX_BUTTON_MOVE_DOWN, (windowTileInspectorSelectedIndex > 0));
1465     widget_invalidate(w, WIDX_BUTTON_MOVE_DOWN);
1466 
1467     // Copy button
1468     WidgetSetEnabled(w, WIDX_BUTTON_COPY, windowTileInspectorSelectedIndex >= 0);
1469     widget_invalidate(w, WIDX_BUTTON_COPY);
1470 
1471     // Paste button
1472     WidgetSetEnabled(w, WIDX_BUTTON_PASTE, windowTileInspectorTileSelected && windowTileInspectorElementCopied);
1473     widget_invalidate(w, WIDX_BUTTON_PASTE);
1474 
1475     w->widgets[WIDX_BACKGROUND].bottom = w->height - 1;
1476 
1477     if (w->tileInspectorPage == TileInspectorPage::Default)
1478     {
1479         w->widgets[WIDX_GROUPBOX_DETAILS].type = WindowWidgetType::Empty;
1480         w->widgets[WIDX_GROUPBOX_PROPERTIES].type = WindowWidgetType::Empty;
1481         w->widgets[WIDX_LIST].bottom = w->height - PADDING_BOTTOM;
1482     }
1483     else
1484     {
1485         w->widgets[WIDX_GROUPBOX_DETAILS].type = WindowWidgetType::Groupbox;
1486         w->widgets[WIDX_GROUPBOX_PROPERTIES].type = WindowWidgetType::Groupbox;
1487         auto pageIndex = EnumValue(w->tileInspectorPage) - 1;
1488         w->widgets[WIDX_GROUPBOX_DETAILS].text = PageGroupBoxSettings[pageIndex].string_id;
1489         w->widgets[WIDX_GROUPBOX_DETAILS].top = w->height - PageGroupBoxSettings[pageIndex].details_top_offset;
1490         w->widgets[WIDX_GROUPBOX_DETAILS].bottom = w->height - PageGroupBoxSettings[pageIndex].details_bottom_offset;
1491         w->widgets[WIDX_GROUPBOX_PROPERTIES].top = w->height - PageGroupBoxSettings[pageIndex].properties_top_offset;
1492         w->widgets[WIDX_GROUPBOX_PROPERTIES].bottom = w->height - PageGroupBoxSettings[pageIndex].properties_bottom_offset;
1493         w->widgets[WIDX_LIST].bottom = w->widgets[WIDX_GROUPBOX_DETAILS].top - GROUPBOX_PADDING;
1494     }
1495 
1496     // The default page doesn't need further invalidation
1497     if (w->tileInspectorPage == TileInspectorPage::Default)
1498     {
1499         return;
1500     }
1501 
1502     // Using a switch, because I don't think giving each page their own callbacks is
1503     // needed here, as only the mouseup and invalidate functions are different.
1504     const int32_t propertiesAnchor = w->widgets[WIDX_GROUPBOX_PROPERTIES].top;
1505     const TileElement* const tileElement = window_tile_inspector_get_selected_element(w);
1506     if (tileElement == nullptr)
1507         return;
1508 
1509     switch (tileElement->GetType())
1510     {
1511         case TILE_ELEMENT_TYPE_SURFACE:
1512             w->widgets[WIDX_SURFACE_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1513             w->widgets[WIDX_SURFACE_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1514             w->widgets[WIDX_SURFACE_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1515             w->widgets[WIDX_SURFACE_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1516             w->widgets[WIDX_SURFACE_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1517             w->widgets[WIDX_SURFACE_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1518             w->widgets[WIDX_SURFACE_BUTTON_REMOVE_FENCES].top = GBBT(propertiesAnchor, 1);
1519             w->widgets[WIDX_SURFACE_BUTTON_REMOVE_FENCES].bottom = GBBB(propertiesAnchor, 1);
1520             w->widgets[WIDX_SURFACE_BUTTON_RESTORE_FENCES].top = GBBT(propertiesAnchor, 1);
1521             w->widgets[WIDX_SURFACE_BUTTON_RESTORE_FENCES].bottom = GBBB(propertiesAnchor, 1);
1522             w->widgets[WIDX_SURFACE_CHECK_CORNER_N].top = GBBT(propertiesAnchor, 2) + 7 * 0;
1523             w->widgets[WIDX_SURFACE_CHECK_CORNER_N].bottom = w->widgets[WIDX_SURFACE_CHECK_CORNER_N].top + 13;
1524             w->widgets[WIDX_SURFACE_CHECK_CORNER_E].top = GBBT(propertiesAnchor, 2) + 7 * 1;
1525             w->widgets[WIDX_SURFACE_CHECK_CORNER_E].bottom = w->widgets[WIDX_SURFACE_CHECK_CORNER_E].top + 13;
1526             w->widgets[WIDX_SURFACE_CHECK_CORNER_S].top = GBBT(propertiesAnchor, 2) + 7 * 2;
1527             w->widgets[WIDX_SURFACE_CHECK_CORNER_S].bottom = w->widgets[WIDX_SURFACE_CHECK_CORNER_S].top + 13;
1528             w->widgets[WIDX_SURFACE_CHECK_CORNER_W].top = GBBT(propertiesAnchor, 2) + 7 * 1;
1529             w->widgets[WIDX_SURFACE_CHECK_CORNER_W].bottom = w->widgets[WIDX_SURFACE_CHECK_CORNER_W].top + 13;
1530             w->widgets[WIDX_SURFACE_CHECK_DIAGONAL].top = GBBT(propertiesAnchor, 3) + 7 * 1;
1531             w->widgets[WIDX_SURFACE_CHECK_DIAGONAL].bottom = w->widgets[WIDX_SURFACE_CHECK_DIAGONAL].top + 13;
1532             WidgetSetCheckboxValue(
1533                 w, WIDX_SURFACE_CHECK_CORNER_N,
1534                 tileElement->AsSurface()->GetSlope() & (1 << ((2 - get_current_rotation()) & 3)));
1535             WidgetSetCheckboxValue(
1536                 w, WIDX_SURFACE_CHECK_CORNER_E,
1537                 tileElement->AsSurface()->GetSlope() & (1 << ((3 - get_current_rotation()) & 3)));
1538             WidgetSetCheckboxValue(
1539                 w, WIDX_SURFACE_CHECK_CORNER_S,
1540                 tileElement->AsSurface()->GetSlope() & (1 << ((0 - get_current_rotation()) & 3)));
1541             WidgetSetCheckboxValue(
1542                 w, WIDX_SURFACE_CHECK_CORNER_W,
1543                 tileElement->AsSurface()->GetSlope() & (1 << ((1 - get_current_rotation()) & 3)));
1544             WidgetSetCheckboxValue(
1545                 w, WIDX_SURFACE_CHECK_DIAGONAL, tileElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT);
1546             break;
1547         case TILE_ELEMENT_TYPE_PATH:
1548             w->widgets[WIDX_PATH_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1549             w->widgets[WIDX_PATH_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1550             w->widgets[WIDX_PATH_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1551             w->widgets[WIDX_PATH_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1552             w->widgets[WIDX_PATH_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1553             w->widgets[WIDX_PATH_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1554             w->widgets[WIDX_PATH_CHECK_BROKEN].top = GBBT(propertiesAnchor, 1);
1555             w->widgets[WIDX_PATH_CHECK_BROKEN].bottom = GBBB(propertiesAnchor, 1);
1556             w->widgets[WIDX_PATH_CHECK_SLOPED].top = GBBT(propertiesAnchor, 2);
1557             w->widgets[WIDX_PATH_CHECK_SLOPED].bottom = GBBB(propertiesAnchor, 2);
1558             w->widgets[WIDX_PATH_CHECK_EDGE_N].top = GBBT(propertiesAnchor, 3) + 7 * 0;
1559             w->widgets[WIDX_PATH_CHECK_EDGE_N].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_N].top + 13;
1560             w->widgets[WIDX_PATH_CHECK_EDGE_NE].top = GBBT(propertiesAnchor, 3) + 7 * 1;
1561             w->widgets[WIDX_PATH_CHECK_EDGE_NE].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_NE].top + 13;
1562             w->widgets[WIDX_PATH_CHECK_EDGE_E].top = GBBT(propertiesAnchor, 3) + 7 * 2;
1563             w->widgets[WIDX_PATH_CHECK_EDGE_E].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_E].top + 13;
1564             w->widgets[WIDX_PATH_CHECK_EDGE_SE].top = GBBT(propertiesAnchor, 3) + 7 * 3;
1565             w->widgets[WIDX_PATH_CHECK_EDGE_SE].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_SE].top + 13;
1566             w->widgets[WIDX_PATH_CHECK_EDGE_S].top = GBBT(propertiesAnchor, 3) + 7 * 4;
1567             w->widgets[WIDX_PATH_CHECK_EDGE_S].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_S].top + 13;
1568             w->widgets[WIDX_PATH_CHECK_EDGE_SW].top = GBBT(propertiesAnchor, 3) + 7 * 3;
1569             w->widgets[WIDX_PATH_CHECK_EDGE_SW].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_SW].top + 13;
1570             w->widgets[WIDX_PATH_CHECK_EDGE_W].top = GBBT(propertiesAnchor, 3) + 7 * 2;
1571             w->widgets[WIDX_PATH_CHECK_EDGE_W].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_W].top + 13;
1572             w->widgets[WIDX_PATH_CHECK_EDGE_NW].top = GBBT(propertiesAnchor, 3) + 7 * 1;
1573             w->widgets[WIDX_PATH_CHECK_EDGE_NW].bottom = w->widgets[WIDX_PATH_CHECK_EDGE_NW].top + 13;
1574             WidgetSetCheckboxValue(w, WIDX_PATH_CHECK_SLOPED, tileElement->AsPath()->IsSloped());
1575             WidgetSetCheckboxValue(w, WIDX_PATH_CHECK_BROKEN, tileElement->AsPath()->IsBroken());
1576             WidgetSetCheckboxValue(
1577                 w, WIDX_PATH_CHECK_EDGE_NE, tileElement->AsPath()->GetEdges() & (1 << ((0 - get_current_rotation()) & 3)));
1578             WidgetSetCheckboxValue(
1579                 w, WIDX_PATH_CHECK_EDGE_SE, tileElement->AsPath()->GetEdges() & (1 << ((1 - get_current_rotation()) & 3)));
1580             WidgetSetCheckboxValue(
1581                 w, WIDX_PATH_CHECK_EDGE_SW, tileElement->AsPath()->GetEdges() & (1 << ((2 - get_current_rotation()) & 3)));
1582             WidgetSetCheckboxValue(
1583                 w, WIDX_PATH_CHECK_EDGE_NW, tileElement->AsPath()->GetEdges() & (1 << ((3 - get_current_rotation()) & 3)));
1584             WidgetSetCheckboxValue(
1585                 w, WIDX_PATH_CHECK_EDGE_E, tileElement->AsPath()->GetCorners() & (1 << ((0 - get_current_rotation()) & 3)));
1586             WidgetSetCheckboxValue(
1587                 w, WIDX_PATH_CHECK_EDGE_S, tileElement->AsPath()->GetCorners() & (1 << ((1 - get_current_rotation()) & 3)));
1588             WidgetSetCheckboxValue(
1589                 w, WIDX_PATH_CHECK_EDGE_W, tileElement->AsPath()->GetCorners() & (1 << ((2 - get_current_rotation()) & 3)));
1590             WidgetSetCheckboxValue(
1591                 w, WIDX_PATH_CHECK_EDGE_N, tileElement->AsPath()->GetCorners() & (1 << ((3 - get_current_rotation()) & 3)));
1592             break;
1593         case TILE_ELEMENT_TYPE_TRACK:
1594             w->widgets[WIDX_TRACK_CHECK_APPLY_TO_ALL].top = GBBT(propertiesAnchor, 0);
1595             w->widgets[WIDX_TRACK_CHECK_APPLY_TO_ALL].bottom = GBBB(propertiesAnchor, 0);
1596             w->widgets[WIDX_TRACK_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 1) + 3;
1597             w->widgets[WIDX_TRACK_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 1) - 3;
1598             w->widgets[WIDX_TRACK_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 1) + 4;
1599             w->widgets[WIDX_TRACK_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 1) - 4;
1600             w->widgets[WIDX_TRACK_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 1) + 4;
1601             w->widgets[WIDX_TRACK_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 1) - 4;
1602             w->widgets[WIDX_TRACK_CHECK_CHAIN_LIFT].top = GBBT(propertiesAnchor, 2);
1603             w->widgets[WIDX_TRACK_CHECK_CHAIN_LIFT].bottom = GBBB(propertiesAnchor, 2);
1604             w->widgets[WIDX_TRACK_CHECK_BLOCK_BRAKE_CLOSED].top = GBBT(propertiesAnchor, 3);
1605             w->widgets[WIDX_TRACK_CHECK_BLOCK_BRAKE_CLOSED].bottom = GBBB(propertiesAnchor, 3);
1606             w->widgets[WIDX_TRACK_CHECK_IS_INDESTRUCTIBLE].top = GBBT(propertiesAnchor, 4);
1607             w->widgets[WIDX_TRACK_CHECK_IS_INDESTRUCTIBLE].bottom = GBBB(propertiesAnchor, 4);
1608             WidgetSetCheckboxValue(w, WIDX_TRACK_CHECK_APPLY_TO_ALL, windowTileInspectorApplyToAll);
1609             WidgetSetCheckboxValue(w, WIDX_TRACK_CHECK_CHAIN_LIFT, tileElement->AsTrack()->HasChain());
1610             WidgetSetCheckboxValue(w, WIDX_TRACK_CHECK_BLOCK_BRAKE_CLOSED, tileElement->AsTrack()->BlockBrakeClosed());
1611             WidgetSetCheckboxValue(w, WIDX_TRACK_CHECK_IS_INDESTRUCTIBLE, tileElement->AsTrack()->IsIndestructible());
1612             break;
1613         case TILE_ELEMENT_TYPE_SMALL_SCENERY:
1614         {
1615             // Raise / Lower
1616             w->widgets[WIDX_SCENERY_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1617             w->widgets[WIDX_SCENERY_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1618             w->widgets[WIDX_SCENERY_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1619             w->widgets[WIDX_SCENERY_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1620             w->widgets[WIDX_SCENERY_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1621             w->widgets[WIDX_SCENERY_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1622 
1623             // Quadrant checkboxes
1624             w->widgets[WIDX_SCENERY_CHECK_QUARTER_N].top = GBBT(propertiesAnchor, 1) - 5 + 7 * 0;
1625             w->widgets[WIDX_SCENERY_CHECK_QUARTER_N].bottom = w->widgets[WIDX_SCENERY_CHECK_QUARTER_N].top + 13;
1626             w->widgets[WIDX_SCENERY_CHECK_QUARTER_E].top = GBBT(propertiesAnchor, 1) - 5 + 7 * 1;
1627             w->widgets[WIDX_SCENERY_CHECK_QUARTER_E].bottom = w->widgets[WIDX_SCENERY_CHECK_QUARTER_E].top + 13;
1628             w->widgets[WIDX_SCENERY_CHECK_QUARTER_S].top = GBBT(propertiesAnchor, 1) - 5 + 7 * 2;
1629             w->widgets[WIDX_SCENERY_CHECK_QUARTER_S].bottom = w->widgets[WIDX_SCENERY_CHECK_QUARTER_S].top + 13;
1630             w->widgets[WIDX_SCENERY_CHECK_QUARTER_W].top = GBBT(propertiesAnchor, 1) - 5 + 7 * 1;
1631             w->widgets[WIDX_SCENERY_CHECK_QUARTER_W].bottom = w->widgets[WIDX_SCENERY_CHECK_QUARTER_W].top + 13;
1632             // This gets the relative rotation, by subtracting the camera's rotation, and wrapping it between 0-3 inclusive
1633             bool N = tileElement->AsSmallScenery()->GetSceneryQuadrant() == ((0 - get_current_rotation()) & 3);
1634             bool E = tileElement->AsSmallScenery()->GetSceneryQuadrant() == ((1 - get_current_rotation()) & 3);
1635             bool S = tileElement->AsSmallScenery()->GetSceneryQuadrant() == ((2 - get_current_rotation()) & 3);
1636             bool W = tileElement->AsSmallScenery()->GetSceneryQuadrant() == ((3 - get_current_rotation()) & 3);
1637             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_QUARTER_N, N);
1638             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_QUARTER_E, E);
1639             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_QUARTER_S, S);
1640             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_QUARTER_W, W);
1641 
1642             // Collision checkboxes
1643             w->widgets[WIDX_SCENERY_CHECK_COLLISION_N].top = GBBT(propertiesAnchor, 2) + 5 + 7 * 0;
1644             w->widgets[WIDX_SCENERY_CHECK_COLLISION_N].bottom = w->widgets[WIDX_SCENERY_CHECK_COLLISION_N].top + 13;
1645             w->widgets[WIDX_SCENERY_CHECK_COLLISION_E].top = GBBT(propertiesAnchor, 2) + 5 + 7 * 1;
1646             w->widgets[WIDX_SCENERY_CHECK_COLLISION_E].bottom = w->widgets[WIDX_SCENERY_CHECK_COLLISION_E].top + 13;
1647             w->widgets[WIDX_SCENERY_CHECK_COLLISION_S].top = GBBT(propertiesAnchor, 2) + 5 + 7 * 2;
1648             w->widgets[WIDX_SCENERY_CHECK_COLLISION_S].bottom = w->widgets[WIDX_SCENERY_CHECK_COLLISION_S].top + 13;
1649             w->widgets[WIDX_SCENERY_CHECK_COLLISION_W].top = GBBT(propertiesAnchor, 2) + 5 + 7 * 1;
1650             w->widgets[WIDX_SCENERY_CHECK_COLLISION_W].bottom = w->widgets[WIDX_SCENERY_CHECK_COLLISION_W].top + 13;
1651             auto occupiedQuadrants = tileElement->GetOccupiedQuadrants();
1652             N = (occupiedQuadrants & (1 << ((2 - get_current_rotation()) & 3))) != 0;
1653             E = (occupiedQuadrants & (1 << ((3 - get_current_rotation()) & 3))) != 0;
1654             S = (occupiedQuadrants & (1 << ((0 - get_current_rotation()) & 3))) != 0;
1655             W = (occupiedQuadrants & (1 << ((1 - get_current_rotation()) & 3))) != 0;
1656             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_COLLISION_N, N);
1657             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_COLLISION_E, E);
1658             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_COLLISION_S, S);
1659             WidgetSetCheckboxValue(w, WIDX_SCENERY_CHECK_COLLISION_W, W);
1660             break;
1661         }
1662         case TILE_ELEMENT_TYPE_ENTRANCE:
1663             w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1664             w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1665             w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1666             w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1667             w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1668             w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1669             w->widgets[WIDX_ENTRANCE_BUTTON_MAKE_USABLE].top = GBBT(propertiesAnchor, 1);
1670             w->widgets[WIDX_ENTRANCE_BUTTON_MAKE_USABLE].bottom = GBBB(propertiesAnchor, 1);
1671             WidgetSetEnabled(
1672                 w, WIDX_ENTRANCE_BUTTON_MAKE_USABLE,
1673                 tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE);
1674             break;
1675         case TILE_ELEMENT_TYPE_WALL:
1676         {
1677             bool canBeSloped = false;
1678             bool hasAnimation = false;
1679             const auto wallEntry = tileElement->AsWall()->GetEntry();
1680             if (wallEntry != nullptr)
1681             {
1682                 canBeSloped = !(wallEntry->flags & WALL_SCENERY_CANT_BUILD_ON_SLOPE);
1683                 hasAnimation = wallEntry->flags & WALL_SCENERY_IS_DOOR;
1684             }
1685 
1686             w->widgets[WIDX_WALL_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1687             w->widgets[WIDX_WALL_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1688             w->widgets[WIDX_WALL_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1689             w->widgets[WIDX_WALL_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1690             w->widgets[WIDX_WALL_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1691             w->widgets[WIDX_WALL_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1692             w->widgets[WIDX_WALL_DROPDOWN_SLOPE].top = GBBT(propertiesAnchor, 1) + 3;
1693             w->widgets[WIDX_WALL_DROPDOWN_SLOPE].bottom = GBBB(propertiesAnchor, 1) - 3;
1694             w->widgets[WIDX_WALL_DROPDOWN_SLOPE].text = WallSlopeStringIds[tileElement->AsWall()->GetSlope()];
1695             w->widgets[WIDX_WALL_DROPDOWN_SLOPE_BUTTON].top = GBBT(propertiesAnchor, 1) + 4;
1696             w->widgets[WIDX_WALL_DROPDOWN_SLOPE_BUTTON].bottom = GBBB(propertiesAnchor, 1) - 4;
1697             w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME].top = GBBT(propertiesAnchor, 2) + 3;
1698             w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME].bottom = GBBB(propertiesAnchor, 2) - 3;
1699             w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME_INCREASE].top = GBBT(propertiesAnchor, 2) + 4;
1700             w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME_INCREASE].bottom = GBBB(propertiesAnchor, 2) - 4;
1701             w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME_DECREASE].top = GBBT(propertiesAnchor, 2) + 4;
1702             w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME_DECREASE].bottom = GBBB(propertiesAnchor, 2) - 4;
1703 
1704             // Wall slope dropdown
1705             WidgetSetEnabled(w, WIDX_WALL_DROPDOWN_SLOPE, canBeSloped);
1706             widget_invalidate(w, WIDX_WALL_DROPDOWN_SLOPE);
1707             WidgetSetEnabled(w, WIDX_WALL_DROPDOWN_SLOPE_BUTTON, canBeSloped);
1708             widget_invalidate(w, WIDX_WALL_DROPDOWN_SLOPE_BUTTON);
1709             // Wall animation frame spinner
1710             WidgetSetEnabled(w, WIDX_WALL_SPINNER_ANIMATION_FRAME, hasAnimation);
1711             WidgetSetEnabled(w, WIDX_WALL_SPINNER_ANIMATION_FRAME_INCREASE, hasAnimation);
1712             WidgetSetEnabled(w, WIDX_WALL_SPINNER_ANIMATION_FRAME_DECREASE, hasAnimation);
1713             break;
1714         }
1715         case TILE_ELEMENT_TYPE_LARGE_SCENERY:
1716             w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1717             w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1718             w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1719             w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1720             w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1721             w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1722             break;
1723         case TILE_ELEMENT_TYPE_BANNER:
1724             w->widgets[WIDX_BANNER_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1725             w->widgets[WIDX_BANNER_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1726             w->widgets[WIDX_BANNER_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1727             w->widgets[WIDX_BANNER_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1728             w->widgets[WIDX_BANNER_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1729             w->widgets[WIDX_BANNER_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1730             w->widgets[WIDX_BANNER_CHECK_BLOCK_NE].top = GBBT(propertiesAnchor, 1);
1731             w->widgets[WIDX_BANNER_CHECK_BLOCK_NE].bottom = GBBB(propertiesAnchor, 1);
1732             w->widgets[WIDX_BANNER_CHECK_BLOCK_SE].top = GBBT(propertiesAnchor, 2);
1733             w->widgets[WIDX_BANNER_CHECK_BLOCK_SE].bottom = GBBB(propertiesAnchor, 2);
1734             w->widgets[WIDX_BANNER_CHECK_BLOCK_SW].top = GBBT(propertiesAnchor, 2);
1735             w->widgets[WIDX_BANNER_CHECK_BLOCK_SW].bottom = GBBB(propertiesAnchor, 2);
1736             w->widgets[WIDX_BANNER_CHECK_BLOCK_NW].top = GBBT(propertiesAnchor, 1);
1737             w->widgets[WIDX_BANNER_CHECK_BLOCK_NW].bottom = GBBB(propertiesAnchor, 1);
1738             WidgetSetCheckboxValue(
1739                 w, WIDX_BANNER_CHECK_BLOCK_NE,
1740                 !(tileElement->AsBanner()->GetAllowedEdges() & (1 << ((0 - get_current_rotation()) & 3))));
1741             WidgetSetCheckboxValue(
1742                 w, WIDX_BANNER_CHECK_BLOCK_SE,
1743                 !(tileElement->AsBanner()->GetAllowedEdges() & (1 << ((1 - get_current_rotation()) & 3))));
1744             WidgetSetCheckboxValue(
1745                 w, WIDX_BANNER_CHECK_BLOCK_SW,
1746                 !(tileElement->AsBanner()->GetAllowedEdges() & (1 << ((2 - get_current_rotation()) & 3))));
1747             WidgetSetCheckboxValue(
1748                 w, WIDX_BANNER_CHECK_BLOCK_NW,
1749                 !(tileElement->AsBanner()->GetAllowedEdges() & (1 << ((3 - get_current_rotation()) & 3))));
1750             break;
1751         case TILE_ELEMENT_TYPE_CORRUPT:
1752             w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT].top = GBBT(propertiesAnchor, 0) + 3;
1753             w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT].bottom = GBBB(propertiesAnchor, 0) - 3;
1754             w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT_INCREASE].top = GBBT(propertiesAnchor, 0) + 4;
1755             w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT_INCREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1756             w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT_DECREASE].top = GBBT(propertiesAnchor, 0) + 4;
1757             w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT_DECREASE].bottom = GBBB(propertiesAnchor, 0) - 4;
1758             w->widgets[WIDX_CORRUPT_BUTTON_CLAMP].top = GBBT(propertiesAnchor, 1);
1759             w->widgets[WIDX_CORRUPT_BUTTON_CLAMP].bottom = GBBB(propertiesAnchor, 1);
1760             break;
1761         default:
1762             break; // Nothing.
1763     }
1764 }
1765 
window_tile_inspector_paint(rct_window * w,rct_drawpixelinfo * dpi)1766 static void window_tile_inspector_paint(rct_window* w, rct_drawpixelinfo* dpi)
1767 {
1768     WindowDrawWidgets(w, dpi);
1769 
1770     ScreenCoordsXY screenCoords(w->windowPos.x, w->windowPos.y);
1771 
1772     // Draw coordinates
1773     gfx_draw_string(dpi, screenCoords + ScreenCoordsXY(5, 24), "X:", { w->colours[1] });
1774     gfx_draw_string(dpi, screenCoords + ScreenCoordsXY(74, 24), "Y:", { w->colours[1] });
1775     if (windowTileInspectorTileSelected)
1776     {
1777         auto tileCoords = TileCoordsXY{ windowTileInspectorToolMap };
1778         auto ft = Formatter();
1779         ft.Add<int32_t>(tileCoords.x);
1780         DrawTextBasic(
1781             dpi, screenCoords + ScreenCoordsXY{ 43, 24 }, STR_FORMAT_INTEGER, ft, { w->colours[1], TextAlignment::RIGHT });
1782         ft = Formatter();
1783         ft.Add<int32_t>(tileCoords.y);
1784         DrawTextBasic(
1785             dpi, screenCoords + ScreenCoordsXY{ 113, 24 }, STR_FORMAT_INTEGER, ft, { w->colours[1], TextAlignment::RIGHT });
1786     }
1787     else
1788     {
1789         gfx_draw_string(dpi, screenCoords + ScreenCoordsXY(43 - 7, 24), "-", { w->colours[1] });
1790         gfx_draw_string(dpi, screenCoords + ScreenCoordsXY(113 - 7, 24), "-", { w->colours[1] });
1791     }
1792 
1793     if (windowTileInspectorSelectedIndex != -1)
1794     {
1795         // X and Y of first element in detail box
1796         screenCoords = w->windowPos
1797             + ScreenCoordsXY{ w->widgets[WIDX_GROUPBOX_DETAILS].left + 7, w->widgets[WIDX_GROUPBOX_DETAILS].top + 14 };
1798 
1799         // Get map element
1800         TileElement* const tileElement = window_tile_inspector_get_selected_element(w);
1801         if (tileElement == nullptr)
1802             return;
1803 
1804         switch (tileElement->GetType())
1805         {
1806             case TILE_ELEMENT_TYPE_SURFACE:
1807             {
1808                 // Details
1809                 // Terrain texture name
1810                 rct_string_id terrainNameId = STR_EMPTY;
1811                 auto surfaceStyle = tileElement->AsSurface()->GetSurfaceStyleObject();
1812                 if (surfaceStyle != nullptr)
1813                 {
1814                     terrainNameId = surfaceStyle->NameStringId;
1815                 }
1816                 auto ft = Formatter();
1817                 ft.Add<rct_string_id>(terrainNameId);
1818                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_SURFACE_TERAIN, ft, { w->colours[1] });
1819 
1820                 // Edge texture name
1821                 rct_string_id terrainEdgeNameId = STR_EMPTY;
1822                 auto edgeStyle = tileElement->AsSurface()->GetEdgeStyleObject();
1823                 if (edgeStyle != nullptr)
1824                 {
1825                     terrainEdgeNameId = edgeStyle->NameStringId;
1826                 }
1827                 ft = Formatter();
1828                 ft.Add<rct_string_id>(terrainEdgeNameId);
1829                 DrawTextBasic(
1830                     dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_SURFACE_EDGE, ft, { w->colours[1] });
1831 
1832                 // Land ownership
1833                 rct_string_id landOwnership;
1834                 if (tileElement->AsSurface()->GetOwnership() & OWNERSHIP_OWNED)
1835                     landOwnership = STR_LAND_OWNED;
1836                 else if (tileElement->AsSurface()->GetOwnership() & OWNERSHIP_AVAILABLE)
1837                     landOwnership = STR_LAND_SALE;
1838                 else if (tileElement->AsSurface()->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)
1839                     landOwnership = STR_CONSTRUCTION_RIGHTS_OWNED;
1840                 else if (tileElement->AsSurface()->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE)
1841                     landOwnership = STR_CONSTRUCTION_RIGHTS_SALE;
1842                 else
1843                     landOwnership = STR_TILE_INSPECTOR_LAND_NOT_OWNED_AND_NOT_AVAILABLE;
1844                 ft = Formatter();
1845                 ft.Add<rct_string_id>(landOwnership);
1846                 DrawTextBasic(
1847                     dpi, screenCoords + ScreenCoordsXY{ 0, 22 }, STR_TILE_INSPECTOR_SURFACE_OWNERSHIP, ft, { w->colours[1] });
1848 
1849                 // Water level
1850                 ft = Formatter();
1851                 ft.Add<uint32_t>(tileElement->AsSurface()->GetWaterHeight());
1852                 DrawTextBasic(
1853                     dpi, screenCoords + ScreenCoordsXY{ 0, 33 }, STR_TILE_INSPECTOR_SURFACE_WATER_LEVEL, ft, { w->colours[1] });
1854 
1855                 // Properties
1856                 // Raise / lower label
1857                 screenCoords = w->windowPos
1858                     + ScreenCoordsXY{ w->widgets[WIDX_GROUPBOX_DETAILS].left + 7, w->widgets[WIDX_SURFACE_SPINNER_HEIGHT].top };
1859                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
1860 
1861                 // Current base height
1862                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_SURFACE_SPINNER_HEIGHT].left + 3;
1863                 ft = Formatter();
1864                 ft.Add<int32_t>(tileElement->base_height);
1865                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
1866 
1867                 // Raised corners
1868                 screenCoords = w->windowPos
1869                     + ScreenCoordsXY{ w->widgets[WIDX_GROUPBOX_DETAILS].left + 7, w->widgets[WIDX_SURFACE_CHECK_CORNER_E].top };
1870                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_SURFACE_CORNERS, {}, { w->colours[1] });
1871                 break;
1872             }
1873 
1874             case TILE_ELEMENT_TYPE_PATH:
1875             {
1876                 // Details
1877                 // Path name
1878                 auto ft = Formatter();
1879                 ft.Add<rct_string_id>(tileElement->AsPath()->GetSurfaceDescriptor()->Name);
1880                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_PATH_NAME, ft, { w->colours[1] });
1881 
1882                 // Path addition
1883                 if (tileElement->AsPath()->HasAddition())
1884                 {
1885                     const auto pathAdditionType = tileElement->AsPath()->GetAdditionEntryIndex();
1886                     const auto* pathBitEntry = get_footpath_item_entry(pathAdditionType);
1887                     rct_string_id additionNameId = pathBitEntry != nullptr
1888                         ? pathBitEntry->name
1889                         : static_cast<rct_string_id>(STR_UNKNOWN_OBJECT_TYPE);
1890                     ft = Formatter();
1891                     ft.Add<rct_string_id>(additionNameId);
1892                     DrawTextBasic(
1893                         dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_PATH_ADDITIONS, ft, { w->colours[1] });
1894                 }
1895                 else
1896                     DrawTextBasic(
1897                         dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_PATH_ADDITIONS_NONE, {},
1898                         { w->colours[1] });
1899 
1900                 // Properties
1901                 // Raise / lower label
1902                 screenCoords = w->windowPos
1903                     + ScreenCoordsXY{ w->widgets[WIDX_GROUPBOX_DETAILS].left + 7, w->widgets[WIDX_PATH_SPINNER_HEIGHT].top };
1904                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
1905 
1906                 // Current base height
1907                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_PATH_SPINNER_HEIGHT].left + 3;
1908                 ft = Formatter();
1909                 ft.Add<int32_t>(tileElement->base_height);
1910                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
1911 
1912                 // Path connections
1913                 screenCoords = w->windowPos
1914                     + ScreenCoordsXY{ w->widgets[WIDX_GROUPBOX_DETAILS].left + 7, w->widgets[WIDX_PATH_CHECK_EDGE_W].top };
1915                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_PATH_CONNECTED_EDGES, {}, { w->colours[1] });
1916                 break;
1917             }
1918 
1919             case TILE_ELEMENT_TYPE_TRACK:
1920             {
1921                 auto trackElement = tileElement->AsTrack();
1922                 ride_id_t rideId = trackElement->GetRideIndex();
1923                 auto ride = get_ride(rideId);
1924 
1925                 // Ride ID
1926                 auto ft = Formatter();
1927                 ft.Add<int16_t>(rideId);
1928                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_TRACK_RIDE_ID, ft, { w->colours[1] });
1929 
1930                 // Ride name
1931                 if (ride != nullptr)
1932                 {
1933                     ft = Formatter();
1934                     ride->FormatNameTo(ft);
1935                     DrawTextBasic(
1936                         dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_TRACK_RIDE_NAME, ft, { w->colours[1] });
1937                 }
1938 
1939                 // Ride type. Individual pieces may be of a different ride type from the ride it belongs to.
1940                 const auto& rtd = GetRideTypeDescriptor(trackElement->GetRideType());
1941                 ft = Formatter();
1942                 ft.Add<rct_string_id>(rtd.Naming.Name);
1943                 DrawTextBasic(
1944                     dpi, screenCoords + ScreenCoordsXY{ 0, 22 }, STR_TILE_INSPECTOR_TRACK_RIDE_TYPE, ft, { w->colours[1] });
1945 
1946                 // Track
1947                 ft = Formatter();
1948                 ft.Add<track_type_t>(trackElement->GetTrackType());
1949                 DrawTextBasic(
1950                     dpi, screenCoords + ScreenCoordsXY{ 0, 33 }, STR_TILE_INSPECTOR_TRACK_PIECE_ID, ft, { w->colours[1] });
1951 
1952                 ft = Formatter();
1953                 ft.Add<track_type_t>(trackElement->GetSequenceIndex());
1954                 DrawTextBasic(
1955                     dpi, screenCoords + ScreenCoordsXY{ 0, 44 }, STR_TILE_INSPECTOR_TRACK_SEQUENCE, ft, { w->colours[1] });
1956                 if (trackElement->IsStation())
1957                 {
1958                     int16_t stationIndex = trackElement->GetStationIndex();
1959                     ft = Formatter();
1960                     ft.Add<rct_string_id>(STR_COMMA16);
1961                     ft.Add<int16_t>(stationIndex);
1962                     DrawTextBasic(
1963                         dpi, screenCoords + ScreenCoordsXY{ 0, 55 }, STR_TILE_INSPECTOR_STATION_INDEX, ft, { w->colours[1] });
1964                 }
1965                 else
1966                 {
1967                     const char* stationNone = "-";
1968                     ft = Formatter();
1969                     ft.Add<rct_string_id>(STR_STRING);
1970                     ft.Add<char*>(stationNone);
1971                     DrawTextBasic(
1972                         dpi, screenCoords + ScreenCoordsXY{ 0, 55 }, STR_TILE_INSPECTOR_STATION_INDEX, ft, { w->colours[1] });
1973                 }
1974 
1975                 ft = Formatter();
1976                 ft.Add<rct_string_id>(ColourSchemeNames[trackElement->GetColourScheme()]);
1977                 DrawTextBasic(
1978                     dpi, screenCoords + ScreenCoordsXY{ 0, 66 }, STR_TILE_INSPECTOR_COLOUR_SCHEME, ft, { w->colours[1] });
1979 
1980                 // Properties
1981                 // Raise / lower label
1982                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_TRACK_SPINNER_HEIGHT].top;
1983                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
1984 
1985                 // Current base height
1986                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_TRACK_SPINNER_HEIGHT].left + 3;
1987                 ft = Formatter();
1988                 ft.Add<int32_t>(tileElement->base_height);
1989                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
1990                 break;
1991             }
1992 
1993             case TILE_ELEMENT_TYPE_SMALL_SCENERY:
1994             {
1995                 // Details
1996                 // Age
1997                 auto ft = Formatter();
1998                 ft.Add<int16_t>(tileElement->AsSmallScenery()->GetAge());
1999                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_SCENERY_AGE, ft, { w->colours[1] });
2000 
2001                 // Quadrant value
2002                 const auto* sceneryEntry = tileElement->AsSmallScenery()->GetEntry();
2003                 if (sceneryEntry != nullptr && !(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE)))
2004                 {
2005                     int16_t quadrant = tileElement->AsSmallScenery()->GetSceneryQuadrant();
2006                     static constexpr rct_string_id quadrant_string_idx[] = {
2007                         STR_TILE_INSPECTOR_SCENERY_QUADRANT_SW,
2008                         STR_TILE_INSPECTOR_SCENERY_QUADRANT_NW,
2009                         STR_TILE_INSPECTOR_SCENERY_QUADRANT_NE,
2010                         STR_TILE_INSPECTOR_SCENERY_QUADRANT_SE,
2011                     };
2012                     ft = Formatter();
2013                     ft.Add<rct_string_id>(quadrant_string_idx[quadrant]);
2014                     DrawTextBasic(
2015                         dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_SCENERY_QUADRANT, ft,
2016                         { w->colours[1] });
2017                 }
2018 
2019                 // Scenery ID
2020                 ft = Formatter();
2021                 ft.Add<ObjectEntryIndex>(tileElement->AsSmallScenery()->GetEntryIndex());
2022                 DrawTextBasic(
2023                     dpi, screenCoords + ScreenCoordsXY{ 0, 22 }, STR_TILE_INSPECTOR_SCENERY_ENTRY_IDX, ft, { w->colours[1] });
2024 
2025                 // Properties
2026                 // Raise / Lower
2027                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_SCENERY_SPINNER_HEIGHT].top;
2028                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
2029 
2030                 // Current base height
2031                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_SCENERY_SPINNER_HEIGHT].left + 3;
2032                 ft = Formatter();
2033                 ft.Add<int32_t>(tileElement->base_height);
2034                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
2035 
2036                 // Quarter tile
2037                 screenCoords = w->windowPos
2038                     + ScreenCoordsXY{ w->widgets[WIDX_GROUPBOX_DETAILS].left + 7,
2039                                       w->widgets[WIDX_SCENERY_CHECK_QUARTER_E].top };
2040                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_SCENERY_QUADRANT_LABEL, {}, { w->colours[1] });
2041 
2042                 // Collision
2043                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_SCENERY_CHECK_COLLISION_E].top;
2044                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_COLLISSION, {}, { w->colours[1] });
2045                 break;
2046             }
2047 
2048             case TILE_ELEMENT_TYPE_ENTRANCE:
2049             {
2050                 // Details
2051                 // Entrance type
2052                 auto ft = Formatter();
2053                 ft.Add<rct_string_id>(EntranceTypeStringIds[tileElement->AsEntrance()->GetEntranceType()]);
2054                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_ENTRANCE_TYPE, ft, { w->colours[1] });
2055 
2056                 if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE)
2057                 {
2058                     // TODO: Make this work with Left/Right park entrance parts
2059                     ft = Formatter();
2060                     ft.Add<rct_string_id>(park_entrance_get_index({ windowTileInspectorToolMap, tileElement->GetBaseZ() }));
2061                     DrawTextBasic(
2062                         dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_ENTRANCE_ENTRANCE_ID, ft,
2063                         { w->colours[1] });
2064                 }
2065                 else
2066                 {
2067                     ft = Formatter();
2068                     ft.Add<int16_t>(tileElement->AsEntrance()->GetStationIndex());
2069                     if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE)
2070                     {
2071                         // Ride entrance ID
2072                         DrawTextBasic(
2073                             dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_ENTRANCE_ENTRANCE_ID, ft,
2074                             { w->colours[1] });
2075                     }
2076                     else
2077                     {
2078                         // Ride exit ID
2079                         DrawTextBasic(
2080                             dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_ENTRANCE_EXIT_ID, ft,
2081                             { w->colours[1] });
2082                     }
2083                 }
2084 
2085                 if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE)
2086                 {
2087                     // Entrance part
2088                     ft = Formatter();
2089                     ft.Add<rct_string_id>(ParkEntrancePartStringIds[tileElement->AsEntrance()->GetSequenceIndex()]);
2090                     DrawTextBasic(
2091                         dpi, screenCoords + ScreenCoordsXY{ 0, 22 }, STR_TILE_INSPECTOR_ENTRANCE_PART, ft, { w->colours[1] });
2092                 }
2093                 else
2094                 {
2095                     // Ride ID
2096                     ft = Formatter();
2097                     ft.Add<ride_id_t>(tileElement->AsEntrance()->GetRideIndex());
2098                     DrawTextBasic(
2099                         dpi, screenCoords + ScreenCoordsXY{ 0, 22 }, STR_TILE_INSPECTOR_ENTRANCE_RIDE_ID, ft,
2100                         { w->colours[1] });
2101                     // Station index
2102                     int16_t stationIndex = tileElement->AsEntrance()->GetStationIndex();
2103                     ft = Formatter();
2104                     ft.Add<rct_string_id>(STR_COMMA16);
2105                     ft.Add<int16_t>(stationIndex);
2106                     DrawTextBasic(
2107                         dpi, screenCoords + ScreenCoordsXY{ 0, 33 }, STR_TILE_INSPECTOR_STATION_INDEX, ft, { w->colours[1] });
2108                 }
2109 
2110                 // Properties
2111                 // Raise / Lower
2112                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT].top;
2113                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
2114 
2115                 // Current base height
2116                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_ENTRANCE_SPINNER_HEIGHT].left + 3;
2117                 ft = Formatter();
2118                 ft.Add<int32_t>(tileElement->base_height);
2119                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
2120                 break;
2121             }
2122 
2123             case TILE_ELEMENT_TYPE_WALL:
2124             {
2125                 // Details
2126                 // Type
2127                 auto ft = Formatter();
2128                 ft.Add<ObjectEntryIndex>(tileElement->AsWall()->GetEntryIndex());
2129                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_WALL_TYPE, ft, { w->colours[1] });
2130 
2131                 // Banner info
2132                 auto banner = tileElement->AsWall()->GetBanner();
2133                 if (banner != nullptr)
2134                 {
2135                     ft = Formatter();
2136                     banner->FormatTextTo(ft);
2137                     DrawTextBasic(
2138                         dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_ENTRY_BANNER_TEXT, ft,
2139                         { w->colours[1] });
2140                 }
2141                 else
2142                 {
2143                     DrawTextBasic(
2144                         dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_ENTRY_BANNER_NONE, {},
2145                         { w->colours[1] });
2146                 }
2147 
2148                 // Properties
2149                 // Raise / lower label
2150                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_WALL_SPINNER_HEIGHT].top;
2151                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
2152 
2153                 // Current base height
2154                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_WALL_SPINNER_HEIGHT].left + 3;
2155                 ft = Formatter();
2156                 ft.Add<int32_t>(tileElement->base_height);
2157                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
2158 
2159                 // Slope label
2160                 screenCoords = w->windowPos
2161                     + ScreenCoordsXY{ w->widgets[WIDX_GROUPBOX_DETAILS].left + 7, w->widgets[WIDX_WALL_DROPDOWN_SLOPE].top };
2162                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_WALL_SLOPE, {}, { w->colours[1] });
2163 
2164                 // Animation frame label
2165                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME].top;
2166                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_WALL_ANIMATION_FRAME, {}, { w->colours[1] });
2167 
2168                 // Current animation frame
2169                 colour_t colour = w->colours[1];
2170                 if (WidgetIsDisabled(w, WIDX_WALL_SPINNER_ANIMATION_FRAME))
2171                 {
2172                     colour = w->colours[0] | COLOUR_FLAG_INSET;
2173                 }
2174                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_WALL_SPINNER_ANIMATION_FRAME].left + 3;
2175                 ft = Formatter();
2176                 ft.Add<int32_t>(tileElement->AsWall()->GetAnimationFrame());
2177                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { colour });
2178                 break;
2179             }
2180 
2181             case TILE_ELEMENT_TYPE_LARGE_SCENERY:
2182             {
2183                 // Details
2184                 // Type
2185                 auto sceneryElement = tileElement->AsLargeScenery();
2186                 ObjectEntryIndex largeSceneryType = sceneryElement->GetEntryIndex();
2187                 auto ft = Formatter();
2188                 ft.Add<ObjectEntryIndex>(largeSceneryType);
2189                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_LARGE_SCENERY_TYPE, ft, { w->colours[1] });
2190 
2191                 // Part ID
2192                 ft = Formatter();
2193                 ft.Add<int16_t>(sceneryElement->GetSequenceIndex());
2194                 DrawTextBasic(
2195                     dpi, screenCoords + ScreenCoordsXY{ 0, 11 }, STR_TILE_INSPECTOR_LARGE_SCENERY_PIECE_ID, ft,
2196                     { w->colours[1] });
2197 
2198                 // Banner info
2199                 auto* largeSceneryEntry = get_large_scenery_entry(largeSceneryType);
2200                 if (largeSceneryEntry != nullptr && largeSceneryEntry->scrolling_mode != SCROLLING_MODE_NONE)
2201                 {
2202                     auto banner = sceneryElement->GetBanner();
2203                     if (banner != nullptr)
2204                     {
2205                         ft = Formatter();
2206                         banner->FormatTextTo(ft);
2207                         DrawTextBasic(
2208                             dpi, screenCoords + ScreenCoordsXY{ 0, 22 }, STR_TILE_INSPECTOR_ENTRY_BANNER_TEXT, ft,
2209                             { w->colours[1] });
2210                     }
2211                 }
2212                 else
2213                 {
2214                     DrawTextBasic(
2215                         dpi, screenCoords + ScreenCoordsXY{ 0, 22 }, STR_TILE_INSPECTOR_ENTRY_BANNER_NONE, {},
2216                         { w->colours[1] });
2217                 }
2218 
2219                 // Properties
2220                 // Raise / lower label
2221                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT].top;
2222                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
2223 
2224                 // Current base height
2225                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_LARGE_SCENERY_SPINNER_HEIGHT].left + 3;
2226                 ft = Formatter();
2227                 ft.Add<int32_t>(tileElement->base_height);
2228                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
2229                 break;
2230             }
2231 
2232             case TILE_ELEMENT_TYPE_BANNER:
2233             {
2234                 // Details
2235                 // Banner info
2236                 auto banner = tileElement->AsBanner()->GetBanner();
2237                 if (banner != nullptr)
2238                 {
2239                     Formatter ft;
2240                     banner->FormatTextTo(ft);
2241                     DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_ENTRY_BANNER_TEXT, ft, { w->colours[1] });
2242                 }
2243 
2244                 // Properties
2245                 // Raise / lower label
2246                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_BANNER_SPINNER_HEIGHT].top;
2247                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
2248 
2249                 // Current base height
2250                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_BANNER_SPINNER_HEIGHT].left + 3;
2251                 auto ft = Formatter();
2252                 ft.Add<int32_t>(tileElement->base_height);
2253                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
2254 
2255                 // Blocked paths
2256                 screenCoords.y += 28;
2257                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_GROUPBOX_DETAILS].left + 7;
2258                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BANNER_BLOCKED_PATHS, {}, { w->colours[1] });
2259                 break;
2260             }
2261 
2262             case TILE_ELEMENT_TYPE_CORRUPT:
2263             {
2264                 // Properties
2265                 // Raise / lower label
2266                 screenCoords.y = w->windowPos.y + w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT].top;
2267                 DrawTextBasic(dpi, screenCoords, STR_TILE_INSPECTOR_BASE_HEIGHT_FULL, {}, { w->colours[1] });
2268 
2269                 // Current base height
2270                 screenCoords.x = w->windowPos.x + w->widgets[WIDX_CORRUPT_SPINNER_HEIGHT].left + 3;
2271                 auto ft = Formatter();
2272                 ft.Add<int32_t>(tileElement->base_height);
2273                 DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { w->colours[1] });
2274                 break;
2275             }
2276 
2277             default:
2278             {
2279                 break;
2280             }
2281         }
2282     }
2283 }
2284 
window_tile_inspector_scrollpaint(rct_window * w,rct_drawpixelinfo * dpi,int32_t scrollIndex)2285 static void window_tile_inspector_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
2286 {
2287     const int32_t listWidth = w->widgets[WIDX_LIST].width();
2288     gfx_fill_rect(
2289         dpi, { { dpi->x, dpi->y }, { dpi->x + dpi->width - 1, dpi->y + dpi->height - 1 } },
2290         ColourMapA[w->colours[1]].mid_light);
2291 
2292     ScreenCoordsXY screenCoords{};
2293     screenCoords.y = SCROLLABLE_ROW_HEIGHT * (windowTileInspectorElementCount - 1);
2294     int32_t i = 0;
2295     char buffer[256];
2296 
2297     if (!windowTileInspectorTileSelected)
2298         return;
2299 
2300     const TileElement* tileElement = map_get_first_element_at(windowTileInspectorToolMap);
2301 
2302     do
2303     {
2304         if (tileElement == nullptr)
2305             break;
2306         const bool selectedRow = i == windowTileInspectorSelectedIndex;
2307         const bool hoveredRow = i == windowTileInspectorHighlightedIndex;
2308         int32_t type = tileElement->GetType();
2309         const char* typeName = "";
2310 
2311         auto fillRectangle = ScreenRect{ { 0, screenCoords.y }, { listWidth, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1 } };
2312         if (selectedRow)
2313         {
2314             gfx_fill_rect(dpi, fillRectangle, ColourMapA[w->colours[1]].mid_dark);
2315         }
2316         else if (hoveredRow)
2317         {
2318             gfx_fill_rect(dpi, fillRectangle, ColourMapA[w->colours[1]].mid_dark | 0x1000000);
2319         }
2320         else if (((windowTileInspectorElementCount - i) & 1) == 0)
2321         {
2322             // Zebra stripes
2323             gfx_fill_rect(dpi, fillRectangle, ColourMapA[w->colours[1]].light | 0x1000000);
2324         }
2325 
2326         switch (type)
2327         {
2328             case TILE_ELEMENT_TYPE_SURFACE:
2329                 typeName = language_get_string(STR_TILE_INSPECTOR_SURFACE);
2330                 break;
2331             case TILE_ELEMENT_TYPE_PATH:
2332                 typeName = tileElement->AsPath()->IsQueue() ? language_get_string(STR_QUEUE_LINE_MAP_TIP)
2333                                                             : language_get_string(STR_FOOTPATH_MAP_TIP);
2334                 break;
2335             case TILE_ELEMENT_TYPE_TRACK:
2336                 typeName = language_get_string(STR_RIDE_COMPONENT_TRACK_CAPITALISED);
2337                 break;
2338             case TILE_ELEMENT_TYPE_SMALL_SCENERY:
2339             {
2340                 const auto* sceneryEntry = tileElement->AsSmallScenery()->GetEntry();
2341                 snprintf(
2342                     buffer, sizeof(buffer), "%s (%s)", language_get_string(STR_OBJECT_SELECTION_SMALL_SCENERY),
2343                     sceneryEntry != nullptr ? language_get_string(sceneryEntry->name) : "");
2344                 typeName = buffer;
2345                 break;
2346             }
2347             case TILE_ELEMENT_TYPE_ENTRANCE:
2348                 typeName = language_get_string(STR_RIDE_CONSTRUCTION_ENTRANCE);
2349                 break;
2350             case TILE_ELEMENT_TYPE_WALL:
2351             {
2352                 const auto* entry = tileElement->AsWall()->GetEntry();
2353                 snprintf(
2354                     buffer, sizeof(buffer), "%s (%s)", language_get_string(STR_TILE_INSPECTOR_WALL),
2355                     entry != nullptr ? language_get_string(entry->name) : "");
2356                 typeName = buffer;
2357                 break;
2358             }
2359             case TILE_ELEMENT_TYPE_LARGE_SCENERY:
2360                 typeName = language_get_string(STR_OBJECT_SELECTION_LARGE_SCENERY);
2361                 break;
2362             case TILE_ELEMENT_TYPE_BANNER:
2363                 snprintf(
2364                     buffer, sizeof(buffer), "%s (%d)", language_get_string(STR_BANNER_WINDOW_TITLE),
2365                     tileElement->AsBanner()->GetIndex());
2366                 typeName = buffer;
2367                 break;
2368             case TILE_ELEMENT_TYPE_CORRUPT:
2369                 // fall-through
2370             default:
2371                 snprintf(buffer, sizeof(buffer), "%s (%d)", language_get_string(STR_UNKNOWN_OBJECT_TYPE), type);
2372                 typeName = buffer;
2373         }
2374 
2375         const int32_t clearanceHeight = tileElement->clearance_height;
2376         const bool ghost = tileElement->IsGhost();
2377         const bool last = tileElement->IsLastForTile();
2378 
2379         const rct_string_id stringFormat = (selectedRow || hoveredRow) ? STR_WHITE_STRING : STR_WINDOW_COLOUR_2_STRINGID;
2380 
2381         // Element name
2382         auto ft = Formatter();
2383         ft.Add<rct_string_id>(STR_STRING);
2384         ft.Add<char*>(typeName);
2385         DrawTextEllipsised(dpi, screenCoords + ScreenCoordsXY{ TypeColumnXY.x, 0 }, TypeColumnSize.width, stringFormat, ft);
2386 
2387         // Base height
2388         ft = Formatter();
2389         ft.Add<rct_string_id>(STR_FORMAT_INTEGER);
2390         ft.Add<int32_t>(tileElement->base_height);
2391         DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ BaseHeightColumnXY.x, 0 }, stringFormat, ft);
2392 
2393         // Clearance height
2394         ft = Formatter();
2395         ft.Add<rct_string_id>(STR_FORMAT_INTEGER);
2396         ft.Add<int32_t>(clearanceHeight);
2397         DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ ClearanceHeightColumnXY.x, 0 }, stringFormat, ft);
2398 
2399         // Direction
2400         ft = Formatter();
2401         ft.Add<rct_string_id>(STR_FORMAT_INTEGER);
2402         ft.Add<int32_t>(tileElement->GetDirection());
2403         DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ DirectionColumnXY.x, 0 }, stringFormat, ft);
2404 
2405         // Checkmarks for ghost and last for tile
2406         ft = Formatter();
2407         ft.Add<rct_string_id>(STR_STRING);
2408         ft.Add<char*>(CheckBoxMarkString);
2409         if (ghost)
2410         {
2411             DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ GhostFlagColumnXY.x, 0 }, stringFormat, ft);
2412         }
2413         if (last)
2414         {
2415             DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ LastFlagColumnXY.x, 0 }, stringFormat, ft);
2416         }
2417 
2418         screenCoords.y -= SCROLLABLE_ROW_HEIGHT;
2419         i++;
2420     } while (!(tileElement++)->IsLastForTile());
2421 }
2422