/***************************************************************************** * Copyright (c) 2014-2024 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include // clang-format off enum WindowViewClippingWidgetIdx { WIDX_BACKGROUND, WIDX_TITLE, WIDX_CLOSE, WIDX_CLIP_CHECKBOX_ENABLE, WIDX_GROUPBOX_VERTICAL, WIDX_CLIP_HEIGHT_VALUE, WIDX_CLIP_HEIGHT_INCREASE, WIDX_CLIP_HEIGHT_DECREASE, WIDX_CLIP_HEIGHT_SLIDER, WIDX_GROUPBOX_HORIZONTAL, WIDX_CLIP_SELECTOR, WIDX_CLIP_CLEAR, }; enum class DisplayType { DisplayRaw, DisplayUnits }; #pragma region Widgets static constexpr StringId WINDOW_TITLE = STR_VIEW_CLIPPING_TITLE; static constexpr int32_t WW = 180; static constexpr int32_t WH = 155; static Widget _viewClippingWidgets[] = { WINDOW_SHIM(WINDOW_TITLE, WW, WH), 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 MakeWidget ({ 5, 36}, {WW - 10, 48}, WindowWidgetType::Groupbox, WindowColour::Primary, STR_VIEW_CLIPPING_VERTICAL_CLIPPING ), MakeSpinnerWidgets({ 90, 51}, { 79, 12}, WindowWidgetType::Spinner, WindowColour::Primary, STR_NONE, STR_VIEW_CLIPPING_HEIGHT_VALUE_TOGGLE), // clip height (3 widgets) MakeWidget ({ 11, 66}, { 158, 13}, WindowWidgetType::Scroll, WindowColour::Primary, SCROLL_HORIZONTAL, STR_VIEW_CLIPPING_HEIGHT_SCROLL_TIP ), // clip height scrollbar MakeWidget ({ 5, 90}, {WW - 10, 60}, WindowWidgetType::Groupbox, WindowColour::Primary, STR_VIEW_CLIPPING_HORIZONTAL_CLIPPING ), MakeWidget ({ 11, 105}, { 158, 17}, WindowWidgetType::Button, WindowColour::Primary, STR_VIEW_CLIPPING_SELECT_AREA ), // selector MakeWidget ({ 11, 126}, { 158, 18}, WindowWidgetType::Button, WindowColour::Primary, STR_VIEW_CLIPPING_CLEAR_SELECTION ), // clear WIDGETS_END, }; #pragma endregion // clang-format on class ViewClippingWindow final : public Window { private: CoordsXY _selectionStart; CoordsXY _previousClipSelectionA; CoordsXY _previousClipSelectionB; bool _toolActive{ false }; bool _dragging{ false }; static inline DisplayType _clipHeightDisplayType; public: void OnCloseButton() { OnClose(); } void OnMouseUp(WidgetIndex widgetIndex) override { // mouseup appears to be used for buttons, checkboxes switch (widgetIndex) { case WIDX_CLOSE: WindowClose(*this); break; case WIDX_CLIP_CHECKBOX_ENABLE: { // Toggle height clipping. WindowBase* mainWindow = WindowGetMain(); if (mainWindow != nullptr) { mainWindow->viewport->flags ^= VIEWPORT_FLAG_CLIP_VIEW; mainWindow->Invalidate(); } this->Invalidate(); break; } case WIDX_CLIP_HEIGHT_VALUE: // Toggle display of the cut height value in RAW vs UNITS if (_clipHeightDisplayType == DisplayType::DisplayRaw) { _clipHeightDisplayType = DisplayType::DisplayUnits; } else { _clipHeightDisplayType = DisplayType::DisplayRaw; } this->Invalidate(); break; case WIDX_CLIP_SELECTOR: // Activate the selection tool ToolSet(*this, WIDX_BACKGROUND, Tool::Crosshair); _toolActive = true; _dragging = false; // Reset clip selection to show all tiles _previousClipSelectionA = gClipSelectionA; _previousClipSelectionB = gClipSelectionB; gClipSelectionA = { 0, 0 }; gClipSelectionB = { MAXIMUM_MAP_SIZE_BIG - 1, MAXIMUM_MAP_SIZE_BIG - 1 }; GfxInvalidateScreen(); break; case WIDX_CLIP_CLEAR: if (IsActive()) { _toolActive = false; ToolCancel(); } gClipSelectionA = { 0, 0 }; gClipSelectionB = { MAXIMUM_MAP_SIZE_BIG - 1, MAXIMUM_MAP_SIZE_BIG - 1 }; GfxInvalidateScreen(); break; } } void OnMouseDown(WidgetIndex widgetIndex) override { WindowBase* mainWindow; switch (widgetIndex) { case WIDX_CLIP_HEIGHT_INCREASE: if (gClipHeight < 255) SetClipHeight(gClipHeight + 1); mainWindow = WindowGetMain(); if (mainWindow != nullptr) mainWindow->Invalidate(); break; case WIDX_CLIP_HEIGHT_DECREASE: if (gClipHeight > 0) SetClipHeight(gClipHeight - 1); mainWindow = WindowGetMain(); if (mainWindow != nullptr) mainWindow->Invalidate(); break; } } void OnUpdate() override { const auto& widget = widgets[WIDX_CLIP_HEIGHT_SLIDER]; const ScrollBar* const scroll = &this->scrolls[0]; const int16_t scroll_width = widget.width() - 1; const uint8_t clip_height = static_cast( (static_cast(scroll->h_left) / (scroll->h_right - scroll_width)) * 255); if (clip_height != gClipHeight) { gClipHeight = clip_height; // Update the main window accordingly. WindowBase* mainWindow = WindowGetMain(); if (mainWindow != nullptr) { mainWindow->Invalidate(); } } // Restore previous selection if the tool has been interrupted if (_toolActive && !IsActive()) { _toolActive = false; gClipSelectionA = _previousClipSelectionA; gClipSelectionB = _previousClipSelectionB; } WidgetInvalidate(*this, WIDX_CLIP_HEIGHT_SLIDER); } void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { if (_dragging) { return; } int32_t direction; auto mapCoords = ScreenPosToMapPos(screenCoords, &direction); if (mapCoords.has_value()) { gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; MapInvalidateTileFull(gMapSelectPositionA); gMapSelectPositionA = gMapSelectPositionB = mapCoords.value(); MapInvalidateTileFull(mapCoords.value()); gMapSelectType = MAP_SELECT_TYPE_FULL; } } void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { int32_t direction; auto mapCoords = ScreenPosToMapPos(screenCoords, &direction); if (mapCoords.has_value()) { _dragging = true; _selectionStart = mapCoords.value(); } } void OnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { if (!_dragging) { return; } int32_t direction; auto mapCoords = ScreenPosToMapPos(screenCoords, &direction); if (mapCoords) { MapInvalidateSelectionRect(); gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; gMapSelectPositionA.x = std::min(_selectionStart.x, mapCoords->x); gMapSelectPositionB.x = std::max(_selectionStart.x, mapCoords->x); gMapSelectPositionA.y = std::min(_selectionStart.y, mapCoords->y); gMapSelectPositionB.y = std::max(_selectionStart.y, mapCoords->y); gMapSelectType = MAP_SELECT_TYPE_FULL; MapInvalidateSelectionRect(); } } void OnToolUp(WidgetIndex, const ScreenCoordsXY&) override { gClipSelectionA = gMapSelectPositionA; gClipSelectionB = gMapSelectPositionB; _toolActive = false; ToolCancel(); GfxInvalidateScreen(); } void OnPrepareDraw() override { WidgetScrollUpdateThumbs(*this, WIDX_CLIP_HEIGHT_SLIDER); WindowBase* mainWindow = WindowGetMain(); if (mainWindow != nullptr) { WidgetSetCheckboxValue(*this, WIDX_CLIP_CHECKBOX_ENABLE, mainWindow->viewport->flags & VIEWPORT_FLAG_CLIP_VIEW); } if (IsActive()) { this->pressed_widgets |= 1uLL << WIDX_CLIP_SELECTOR; } else { this->pressed_widgets &= ~(1uLL << WIDX_CLIP_SELECTOR); } } void OnDraw(DrawPixelInfo& dpi) override { WindowDrawWidgets(*this, dpi); // Clip height value auto screenCoords = this->windowPos + ScreenCoordsXY{ 8, this->widgets[WIDX_CLIP_HEIGHT_VALUE].top }; DrawTextBasic(dpi, screenCoords, STR_VIEW_CLIPPING_HEIGHT_VALUE, {}, { this->colours[0] }); screenCoords = this->windowPos + ScreenCoordsXY{ this->widgets[WIDX_CLIP_HEIGHT_VALUE].left + 1, this->widgets[WIDX_CLIP_HEIGHT_VALUE].top }; switch (_clipHeightDisplayType) { case DisplayType::DisplayRaw: default: { auto ft = Formatter(); ft.Add(static_cast(gClipHeight)); // Printing the raw value. DrawTextBasic(dpi, screenCoords, STR_FORMAT_INTEGER, ft, { this->colours[0] }); break; } case DisplayType::DisplayUnits: { // Print the value in the configured height label type: if (gConfigGeneral.ShowHeightAsUnits) { // Height label is Units. auto ft = Formatter(); ft.Add(static_cast(FIXED_1DP(gClipHeight, 0) / 2 - FIXED_1DP(7, 0))); DrawTextBasic( dpi, screenCoords, STR_UNIT1DP_NO_SUFFIX, ft, { this->colours[0] }); // Printing the value in Height Units. } else { // Height label is Real Values. // Print the value in the configured measurement units. switch (gConfigGeneral.MeasurementFormat) { case MeasurementFormat::Metric: case MeasurementFormat::SI: { auto ft = Formatter(); ft.Add( static_cast(FIXED_2DP(gClipHeight, 0) / 2 * 1.5f - FIXED_2DP(10, 50))); DrawTextBasic(dpi, screenCoords, STR_UNIT2DP_SUFFIX_METRES, ft, { this->colours[0] }); break; } case MeasurementFormat::Imperial: { auto ft = Formatter(); ft.Add( static_cast(FIXED_1DP(gClipHeight, 0) / 2.0f * 5 - FIXED_1DP(35, 0))); DrawTextBasic(dpi, screenCoords, STR_UNIT1DP_SUFFIX_FEET, ft, { this->colours[0] }); break; } } } } } } ScreenSize OnScrollGetSize(int32_t scrollIndex) override { return { 1000, 0 }; } void OnOpen() override { this->widgets = _viewClippingWidgets; this->hold_down_widgets = (1uLL << WIDX_CLIP_HEIGHT_INCREASE) | (1uL << WIDX_CLIP_HEIGHT_DECREASE); WindowInitScrollWidgets(*this); _clipHeightDisplayType = DisplayType::DisplayUnits; // Initialise the clip height slider from the current clip height value. this->SetClipHeight(gClipHeight); WindowPushOthersBelow(*this); // Get the main viewport to set the view clipping flag. WindowBase* mainWindow = WindowGetMain(); // Turn on view clipping when the window is opened. if (mainWindow != nullptr) { mainWindow->viewport->flags |= VIEWPORT_FLAG_CLIP_VIEW; mainWindow->Invalidate(); } } void OnResize() override { ResizeFrame(); } private: void OnClose() override { // Turn off view clipping when the window is closed. WindowBase* mainWindow = WindowGetMain(); if (mainWindow != nullptr) { mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CLIP_VIEW; mainWindow->Invalidate(); } } void SetClipHeight(const uint8_t clipHeight) { gClipHeight = clipHeight; const auto& widget = widgets[WIDX_CLIP_HEIGHT_SLIDER]; const float clip_height_ratio = static_cast(gClipHeight) / 255; this->scrolls[0].h_left = static_cast( std::ceil(clip_height_ratio * (this->scrolls[0].h_right - (widget.width() - 1)))); } bool IsActive() { if (!(InputTestFlag(INPUT_FLAG_TOOL_ACTIVE))) return false; if (gCurrentToolWidget.window_classification != WindowClass::ViewClipping) return false; return _toolActive; } }; WindowBase* WindowViewClippingOpen() { auto* window = WindowBringToFrontByClass(WindowClass::ViewClipping); if (window == nullptr) { window = WindowCreate(WindowClass::ViewClipping, ScreenCoordsXY(32, 32), WW, WH); } return window; }