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 <cmath>
12 #include <openrct2-ui/interface/Viewport.h>
13 #include <openrct2-ui/interface/Widget.h>
14 #include <openrct2-ui/windows/Window.h>
15 #include <openrct2/Input.h>
16 #include <openrct2/config/Config.h>
17 #include <openrct2/drawing/Drawing.h>
18 #include <openrct2/localisation/Localisation.h>
19 #include <openrct2/paint/Paint.h>
20 #include <openrct2/sprites.h>
21 #include <openrct2/world/Location.hpp>
22
23 // clang-format off
24 enum WINDOW_VIEW_CLIPPING_WIDGET_IDX {
25 WIDX_BACKGROUND,
26 WIDX_TITLE,
27 WIDX_CLOSE,
28 WIDX_CLIP_CHECKBOX_ENABLE,
29 WIDX_GROUPBOX_VERTICAL,
30 WIDX_CLIP_HEIGHT_VALUE,
31 WIDX_CLIP_HEIGHT_INCREASE,
32 WIDX_CLIP_HEIGHT_DECREASE,
33 WIDX_CLIP_HEIGHT_SLIDER,
34 WIDX_GROUPBOX_HORIZONTAL,
35 WIDX_CLIP_SELECTOR,
36 WIDX_CLIP_CLEAR,
37 };
38
39 enum class DISPLAY_TYPE {
40 DISPLAY_RAW,
41 DISPLAY_UNITS
42 };
43
44 #pragma region Widgets
45
46 static constexpr const rct_string_id WINDOW_TITLE = STR_VIEW_CLIPPING_TITLE;
47 static constexpr const int32_t WW = 180;
48 static constexpr const int32_t WH = 155;
49
50 static rct_widget window_view_clipping_widgets[] = {
51 WINDOW_SHIM(WINDOW_TITLE, WW, WH),
52 MakeWidget ({ 11, 19}, { 159, 11}, WindowWidgetType::Checkbox, WindowColour::Primary, STR_VIEW_CLIPPING_HEIGHT_ENABLE, STR_VIEW_CLIPPING_HEIGHT_ENABLE_TIP ), // clip enable/disable check box
53 MakeWidget ({ 5, 36}, {WW - 10, 48}, WindowWidgetType::Groupbox, WindowColour::Primary, STR_VIEW_CLIPPING_VERTICAL_CLIPPING ),
54 MakeSpinnerWidgets({ 90, 51}, { 79, 12}, WindowWidgetType::Spinner, WindowColour::Primary, STR_NONE, STR_VIEW_CLIPPING_HEIGHT_VALUE_TOGGLE), // clip height (3 widgets)
55 MakeWidget ({ 11, 66}, { 158, 13}, WindowWidgetType::Scroll, WindowColour::Primary, SCROLL_HORIZONTAL, STR_VIEW_CLIPPING_HEIGHT_SCROLL_TIP ), // clip height scrollbar
56 MakeWidget ({ 5, 90}, {WW - 10, 60}, WindowWidgetType::Groupbox, WindowColour::Primary, STR_VIEW_CLIPPING_HORIZONTAL_CLIPPING ),
57 MakeWidget ({ 11, 105}, { 158, 17}, WindowWidgetType::Button, WindowColour::Primary, STR_VIEW_CLIPPING_SELECT_AREA ), // selector
58 MakeWidget ({ 11, 126}, { 158, 18}, WindowWidgetType::Button, WindowColour::Primary, STR_VIEW_CLIPPING_CLEAR_SELECTION ), // clear
59
60 WIDGETS_END,
61 };
62
63 #pragma endregion
64
65 // clang-format on
66 class ViewClippingWindow final : public Window
67 {
68 private:
69 CoordsXY _selectionStart;
70 CoordsXY _previousClipSelectionA;
71 CoordsXY _previousClipSelectionB;
72 bool _toolActive{ false };
73 bool _dragging{ false };
74 static inline DISPLAY_TYPE _clipHeightDisplayType;
75
76 public:
OnCloseButton()77 void OnCloseButton()
78 {
79 OnClose();
80 }
81
OnMouseUp(rct_widgetindex widgetIndex)82 void OnMouseUp(rct_widgetindex widgetIndex) override
83 {
84 // mouseup appears to be used for buttons, checkboxes
85 switch (widgetIndex)
86 {
87 case WIDX_CLOSE:
88 window_close(this);
89 break;
90 case WIDX_CLIP_CHECKBOX_ENABLE:
91 {
92 // Toggle height clipping.
93 rct_window* mainWindow = window_get_main();
94 if (mainWindow != nullptr)
95 {
96 mainWindow->viewport->flags ^= VIEWPORT_FLAG_CLIP_VIEW;
97 mainWindow->Invalidate();
98 }
99 this->Invalidate();
100 break;
101 }
102 case WIDX_CLIP_HEIGHT_VALUE:
103 // Toggle display of the cut height value in RAW vs UNITS
104 if (_clipHeightDisplayType == DISPLAY_TYPE::DISPLAY_RAW)
105 {
106 _clipHeightDisplayType = DISPLAY_TYPE::DISPLAY_UNITS;
107 }
108 else
109 {
110 _clipHeightDisplayType = DISPLAY_TYPE::DISPLAY_RAW;
111 }
112 this->Invalidate();
113 break;
114 case WIDX_CLIP_SELECTOR:
115 // Activate the selection tool
116 tool_set(this, WIDX_BACKGROUND, Tool::Crosshair);
117 _toolActive = true;
118 _dragging = false;
119
120 // Reset clip selection to show all tiles
121 _previousClipSelectionA = gClipSelectionA;
122 _previousClipSelectionB = gClipSelectionB;
123 gClipSelectionA = { 0, 0 };
124 gClipSelectionB = { MAXIMUM_MAP_SIZE_BIG - 1, MAXIMUM_MAP_SIZE_BIG - 1 };
125 gfx_invalidate_screen();
126 break;
127 case WIDX_CLIP_CLEAR:
128 if (IsActive())
129 {
130 _toolActive = false;
131 tool_cancel();
132 }
133 gClipSelectionA = { 0, 0 };
134 gClipSelectionB = { MAXIMUM_MAP_SIZE_BIG - 1, MAXIMUM_MAP_SIZE_BIG - 1 };
135 gfx_invalidate_screen();
136 break;
137 }
138 }
139
OnMouseDown(rct_widgetindex widgetIndex)140 void OnMouseDown(rct_widgetindex widgetIndex) override
141 {
142 rct_window* mainWindow;
143
144 switch (widgetIndex)
145 {
146 case WIDX_CLIP_HEIGHT_INCREASE:
147 if (gClipHeight < 255)
148 SetClipHeight(gClipHeight + 1);
149 mainWindow = window_get_main();
150 if (mainWindow != nullptr)
151 mainWindow->Invalidate();
152 break;
153 case WIDX_CLIP_HEIGHT_DECREASE:
154 if (gClipHeight > 0)
155 SetClipHeight(gClipHeight - 1);
156 mainWindow = window_get_main();
157 if (mainWindow != nullptr)
158 mainWindow->Invalidate();
159 break;
160 }
161 }
162
OnUpdate()163 void OnUpdate() override
164 {
165 const auto& widget = widgets[WIDX_CLIP_HEIGHT_SLIDER];
166 const rct_scroll* const scroll = &this->scrolls[0];
167 const int16_t scroll_width = widget.width() - 1;
168 const uint8_t clip_height = static_cast<uint8_t>(
169 (static_cast<float>(scroll->h_left) / (scroll->h_right - scroll_width)) * 255);
170 if (clip_height != gClipHeight)
171 {
172 gClipHeight = clip_height;
173
174 // Update the main window accordingly.
175 rct_window* mainWindow = window_get_main();
176 if (mainWindow != nullptr)
177 {
178 mainWindow->Invalidate();
179 }
180 }
181
182 // Restore previous selection if the tool has been interrupted
183 if (_toolActive && !IsActive())
184 {
185 _toolActive = false;
186 gClipSelectionA = _previousClipSelectionA;
187 gClipSelectionB = _previousClipSelectionB;
188 }
189
190 widget_invalidate(this, WIDX_CLIP_HEIGHT_SLIDER);
191 }
192
OnToolUpdate(rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)193 void OnToolUpdate(rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) override
194 {
195 if (_dragging)
196 {
197 return;
198 }
199
200 int32_t direction;
201 auto mapCoords = screen_pos_to_map_pos(screenCoords, &direction);
202 if (mapCoords.has_value())
203 {
204 gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE;
205 map_invalidate_tile_full(gMapSelectPositionA);
206 gMapSelectPositionA = gMapSelectPositionB = mapCoords.value();
207 map_invalidate_tile_full(mapCoords.value());
208 gMapSelectType = MAP_SELECT_TYPE_FULL;
209 }
210 }
211
OnToolDown(rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)212 void OnToolDown(rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) override
213 {
214 int32_t direction;
215 auto mapCoords = screen_pos_to_map_pos(screenCoords, &direction);
216 if (mapCoords.has_value())
217 {
218 _dragging = true;
219 _selectionStart = mapCoords.value();
220 }
221 }
222
OnToolDrag(rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)223 void OnToolDrag(rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) override
224 {
225 if (!_dragging)
226 {
227 return;
228 }
229
230 int32_t direction;
231 auto mapCoords = screen_pos_to_map_pos(screenCoords, &direction);
232 if (mapCoords)
233 {
234 map_invalidate_selection_rect();
235 gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE;
236 gMapSelectPositionA.x = std::min(_selectionStart.x, mapCoords->x);
237 gMapSelectPositionB.x = std::max(_selectionStart.x, mapCoords->x);
238 gMapSelectPositionA.y = std::min(_selectionStart.y, mapCoords->y);
239 gMapSelectPositionB.y = std::max(_selectionStart.y, mapCoords->y);
240 gMapSelectType = MAP_SELECT_TYPE_FULL;
241 map_invalidate_selection_rect();
242 }
243 }
244
OnToolUp(rct_widgetindex,const ScreenCoordsXY &)245 void OnToolUp(rct_widgetindex, const ScreenCoordsXY&) override
246 {
247 gClipSelectionA = gMapSelectPositionA;
248 gClipSelectionB = gMapSelectPositionB;
249 _toolActive = false;
250 tool_cancel();
251 gfx_invalidate_screen();
252 }
253
OnPrepareDraw()254 void OnPrepareDraw() override
255 {
256 WidgetScrollUpdateThumbs(this, WIDX_CLIP_HEIGHT_SLIDER);
257
258 rct_window* mainWindow = window_get_main();
259 if (mainWindow != nullptr)
260 {
261 WidgetSetCheckboxValue(this, WIDX_CLIP_CHECKBOX_ENABLE, mainWindow->viewport->flags & VIEWPORT_FLAG_CLIP_VIEW);
262 }
263
264 if (IsActive())
265 {
266 this->pressed_widgets |= 1ULL << WIDX_CLIP_SELECTOR;
267 }
268 else
269 {
270 this->pressed_widgets &= ~(1ULL << WIDX_CLIP_SELECTOR);
271 }
272 }
273
OnDraw(rct_drawpixelinfo & dpi)274 void OnDraw(rct_drawpixelinfo& dpi) override
275 {
276 WindowDrawWidgets(this, &dpi);
277
278 // Clip height value
279 auto screenCoords = this->windowPos + ScreenCoordsXY{ 8, this->widgets[WIDX_CLIP_HEIGHT_VALUE].top };
280 DrawTextBasic(&dpi, screenCoords, STR_VIEW_CLIPPING_HEIGHT_VALUE, {}, { this->colours[0] });
281
282 screenCoords = this->windowPos
283 + ScreenCoordsXY{ this->widgets[WIDX_CLIP_HEIGHT_VALUE].left + 1, this->widgets[WIDX_CLIP_HEIGHT_VALUE].top };
284
285 switch (_clipHeightDisplayType)
286 {
287 case DISPLAY_TYPE::DISPLAY_RAW:
288 default:
289 {
290 auto ft = Formatter();
291 ft.Add<int32_t>(static_cast<int32_t>(gClipHeight));
292
293 // Printing the raw value.
294 DrawTextBasic(&dpi, screenCoords, STR_FORMAT_INTEGER, ft, { this->colours[0] });
295 break;
296 }
297 case DISPLAY_TYPE::DISPLAY_UNITS:
298 {
299 // Print the value in the configured height label type:
300 if (gConfigGeneral.show_height_as_units)
301 {
302 // Height label is Units.
303 auto ft = Formatter();
304 ft.Add<fixed16_1dp>(static_cast<fixed16_1dp>(FIXED_1DP(gClipHeight, 0) / 2 - FIXED_1DP(7, 0)));
305 DrawTextBasic(
306 &dpi, screenCoords, STR_UNIT1DP_NO_SUFFIX, ft,
307 { this->colours[0] }); // Printing the value in Height Units.
308 }
309 else
310 {
311 // Height label is Real Values.
312 // Print the value in the configured measurement units.
313 switch (gConfigGeneral.measurement_format)
314 {
315 case MeasurementFormat::Metric:
316 case MeasurementFormat::SI:
317 {
318 auto ft = Formatter();
319 ft.Add<fixed32_2dp>(
320 static_cast<fixed32_2dp>(FIXED_2DP(gClipHeight, 0) / 2 * 1.5f - FIXED_2DP(10, 50)));
321 DrawTextBasic(&dpi, screenCoords, STR_UNIT2DP_SUFFIX_METRES, ft, { this->colours[0] });
322 break;
323 }
324 case MeasurementFormat::Imperial:
325 {
326 auto ft = Formatter();
327 ft.Add<fixed16_1dp>(
328 static_cast<fixed16_1dp>(FIXED_1DP(gClipHeight, 0) / 2.0f * 5 - FIXED_1DP(35, 0)));
329 DrawTextBasic(&dpi, screenCoords, STR_UNIT1DP_SUFFIX_FEET, ft, { this->colours[0] });
330 break;
331 }
332 }
333 }
334 }
335 }
336 }
337
OnScrollGetSize(int32_t scrollIndex)338 ScreenSize OnScrollGetSize(int32_t scrollIndex) override
339 {
340 return { 1000, 0 };
341 }
342
OnOpen()343 void OnOpen() override
344 {
345 this->widgets = window_view_clipping_widgets;
346 this->enabled_widgets = (1ULL << WIDX_CLOSE) | (1ULL << WIDX_CLIP_CHECKBOX_ENABLE) | (1ULL << WIDX_CLIP_HEIGHT_VALUE)
347 | (1ULL << WIDX_CLIP_HEIGHT_INCREASE) | (1ULL << WIDX_CLIP_HEIGHT_DECREASE) | (1ULL << WIDX_CLIP_HEIGHT_SLIDER)
348 | (1ULL << WIDX_CLIP_SELECTOR) | (1ULL << WIDX_CLIP_CLEAR);
349 this->hold_down_widgets = (1ULL << WIDX_CLIP_HEIGHT_INCREASE) | (1UL << WIDX_CLIP_HEIGHT_DECREASE);
350 WindowInitScrollWidgets(this);
351
352 _clipHeightDisplayType = DISPLAY_TYPE::DISPLAY_UNITS;
353
354 // Initialise the clip height slider from the current clip height value.
355 this->SetClipHeight(gClipHeight);
356
357 window_push_others_below(this);
358
359 // Get the main viewport to set the view clipping flag.
360 rct_window* mainWindow = window_get_main();
361
362 // Turn on view clipping when the window is opened.
363 if (mainWindow != nullptr)
364 {
365 mainWindow->viewport->flags |= VIEWPORT_FLAG_CLIP_VIEW;
366 mainWindow->Invalidate();
367 }
368 }
369
370 private:
OnClose()371 void OnClose() override
372 {
373 // Turn off view clipping when the window is closed.
374 rct_window* mainWindow = window_get_main();
375 if (mainWindow != nullptr)
376 {
377 mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CLIP_VIEW;
378 mainWindow->Invalidate();
379 }
380 }
381
SetClipHeight(const uint8_t clipHeight)382 void SetClipHeight(const uint8_t clipHeight)
383 {
384 gClipHeight = clipHeight;
385 const auto& widget = widgets[WIDX_CLIP_HEIGHT_SLIDER];
386 const float clip_height_ratio = static_cast<float>(gClipHeight) / 255;
387 this->scrolls[0].h_left = static_cast<int16_t>(
388 std::ceil(clip_height_ratio * (this->scrolls[0].h_right - (widget.width() - 1))));
389 }
390
IsActive()391 bool IsActive()
392 {
393 if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE)))
394 return false;
395 if (gCurrentToolWidget.window_classification != WC_VIEW_CLIPPING)
396 return false;
397 return _toolActive;
398 }
399 };
400
window_view_clipping_open()401 rct_window* window_view_clipping_open()
402 {
403 auto* window = window_bring_to_front_by_class(WC_VIEW_CLIPPING);
404 if (window == nullptr)
405 {
406 window = WindowCreate<ViewClippingWindow>(WC_VIEW_CLIPPING, ScreenCoordsXY(32, 32), WW, WH);
407 }
408 return window;
409 }
410