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