/***************************************************************************** * Copyright (c) 2014-2019 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 #include #include #include #include #include #include #include #include #include using namespace OpenRCT2; static constexpr const rct_string_id WINDOW_TITLE = STR_STRING; static constexpr const int32_t WH = 124; static constexpr const int32_t WW = 200; constexpr int16_t TRACK_MINI_PREVIEW_WIDTH = 168; constexpr int16_t TRACK_MINI_PREVIEW_HEIGHT = 78; constexpr uint16_t TRACK_MINI_PREVIEW_SIZE = TRACK_MINI_PREVIEW_WIDTH * TRACK_MINI_PREVIEW_HEIGHT; struct rct_track_td6; static constexpr uint8_t _PaletteIndexColourEntrance = PALETTE_INDEX_20; // White static constexpr uint8_t _PaletteIndexColourExit = PALETTE_INDEX_10; // Black static constexpr uint8_t _PaletteIndexColourTrack = PALETTE_INDEX_248; // Grey (dark) static constexpr uint8_t _PaletteIndexColourStation = PALETTE_INDEX_252; // Grey (light) // clang-format off enum { WIDX_BACKGROUND, WIDX_TITLE, WIDX_CLOSE, WIDX_ROTATE, WIDX_MIRROR, WIDX_SELECT_DIFFERENT_DESIGN, WIDX_PRICE }; validate_global_widx(WC_TRACK_DESIGN_PLACE, WIDX_ROTATE); static rct_widget window_track_place_widgets[] = { WINDOW_SHIM(WINDOW_TITLE, WW, WH), { WWT_FLATBTN, 0, 173, 196, 83, 106, SPR_ROTATE_ARROW, STR_ROTATE_90_TIP }, { WWT_FLATBTN, 0, 173, 196, 59, 82, SPR_MIRROR_ARROW, STR_MIRROR_IMAGE_TIP }, { WWT_BUTTON, 0, 4, 195, 109, 120, STR_SELECT_A_DIFFERENT_DESIGN, STR_GO_BACK_TO_DESIGN_SELECTION_WINDOW_TIP }, { WWT_EMPTY, 0, 0, 0, 0, 0, 0xFFFFFFFF, STR_NONE }, { WIDGETS_END }, }; static void window_track_place_close(rct_window *w); static void window_track_place_mouseup(rct_window *w, rct_widgetindex widgetIndex); static void window_track_place_update(rct_window *w); static void window_track_place_toolupdate(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords); static void window_track_place_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords); static void window_track_place_toolabort(rct_window *w, rct_widgetindex widgetIndex); static void window_track_place_unknown14(rct_window *w); static void window_track_place_invalidate(rct_window *w); static void window_track_place_paint(rct_window *w, rct_drawpixelinfo *dpi); static rct_window_event_list window_track_place_events = { window_track_place_close, window_track_place_mouseup, nullptr, nullptr, nullptr, nullptr, window_track_place_update, nullptr, nullptr, window_track_place_toolupdate, window_track_place_tooldown, nullptr, nullptr, window_track_place_toolabort, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, window_track_place_unknown14, nullptr, nullptr, nullptr, nullptr, window_track_place_invalidate, window_track_place_paint, nullptr }; // clang-format on static std::vector _window_track_place_mini_preview; static int16_t _window_track_place_last_x; static int16_t _window_track_place_last_y; static uint8_t _window_track_place_ride_index; static bool _window_track_place_last_was_valid; static int16_t _window_track_place_last_valid_x; static int16_t _window_track_place_last_valid_y; static int16_t _window_track_place_last_valid_z; static money32 _window_track_place_last_cost; static std::unique_ptr _trackDesign; static void window_track_place_clear_provisional(); static int32_t window_track_place_get_base_z(const CoordsXY& loc); static void window_track_place_clear_mini_preview(); static void window_track_place_draw_mini_preview(TrackDesign* td6); static void window_track_place_draw_mini_preview_track( TrackDesign* td6, int32_t pass, CoordsXY origin, CoordsXY min, CoordsXY max); static void window_track_place_draw_mini_preview_maze( TrackDesign* td6, int32_t pass, const CoordsXY& origin, CoordsXY min, CoordsXY max); static ScreenCoordsXY draw_mini_preview_get_pixel_position(const CoordsXY& location); static bool draw_mini_preview_is_pixel_in_bounds(const ScreenCoordsXY& pixel); static uint8_t* draw_mini_preview_get_pixel_ptr(const ScreenCoordsXY& pixel); /** * * rct2: 0x006D182E */ static void window_track_place_clear_mini_preview() { // Fill with transparent colour. std::fill(_window_track_place_mini_preview.begin(), _window_track_place_mini_preview.end(), PALETTE_INDEX_0); } /** * * rct2: 0x006CFCA0 */ rct_window* window_track_place_open(const track_design_file_ref* tdFileRef) { _trackDesign = track_design_open(tdFileRef->path); if (_trackDesign == nullptr) { return nullptr; } window_close_construction_windows(); _window_track_place_mini_preview.resize(TRACK_MINI_PREVIEW_SIZE); rct_window* w = window_create(ScreenCoordsXY(0, 29), 200, 124, &window_track_place_events, WC_TRACK_DESIGN_PLACE, 0); w->widgets = window_track_place_widgets; w->enabled_widgets = 1 << WIDX_CLOSE | 1 << WIDX_ROTATE | 1 << WIDX_MIRROR | 1 << WIDX_SELECT_DIFFERENT_DESIGN; window_init_scroll_widgets(w); tool_set(w, WIDX_PRICE, TOOL_CROSSHAIR); input_set_flag(INPUT_FLAG_6, true); window_push_others_right(w); show_gridlines(); _window_track_place_last_cost = MONEY32_UNDEFINED; _window_track_place_last_x = -1; _currentTrackPieceDirection = (2 - get_current_rotation()) & 3; window_track_place_clear_mini_preview(); window_track_place_draw_mini_preview(_trackDesign.get()); return w; } /** * * rct2: 0x006D0119 */ static void window_track_place_close(rct_window* w) { window_track_place_clear_provisional(); viewport_set_visibility(0); map_invalidate_map_selection_tiles(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; hide_gridlines(); _window_track_place_mini_preview.clear(); _window_track_place_mini_preview.shrink_to_fit(); _trackDesign = nullptr; } /** * * rct2: 0x006CFEAC */ static void window_track_place_mouseup(rct_window* w, rct_widgetindex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(w); break; case WIDX_ROTATE: window_track_place_clear_provisional(); _currentTrackPieceDirection = (_currentTrackPieceDirection + 1) & 3; w->Invalidate(); _window_track_place_last_x = -1; window_track_place_draw_mini_preview(_trackDesign.get()); break; case WIDX_MIRROR: track_design_mirror(_trackDesign.get()); _currentTrackPieceDirection = (0 - _currentTrackPieceDirection) & 3; w->Invalidate(); _window_track_place_last_x = -1; window_track_place_draw_mini_preview(_trackDesign.get()); break; case WIDX_SELECT_DIFFERENT_DESIGN: window_close(w); auto intent = Intent(WC_TRACK_DESIGN_LIST); intent.putExtra(INTENT_EXTRA_RIDE_TYPE, _window_track_list_item.Type); intent.putExtra(INTENT_EXTRA_RIDE_ENTRY_INDEX, _window_track_list_item.EntryIndex); context_open_intent(&intent); break; } } /** * * rct2: 0x006CFCA0 */ static void window_track_place_update(rct_window* w) { if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))) if (gCurrentToolWidget.window_classification != WC_TRACK_DESIGN_PLACE) window_close(w); } static GameActionResult::Ptr FindValidTrackDesignPlaceHeight(CoordsXYZ& loc, uint32_t flags) { GameActionResult::Ptr res; for (int32_t i = 0; i < 7; i++, loc.z += 8) { auto tdAction = TrackDesignAction(CoordsXYZD{ loc.x, loc.y, loc.z, _currentTrackPieceDirection }, *_trackDesign); tdAction.SetFlags(flags); res = GameActions::Query(&tdAction); // If successful dont keep trying. // If failure due to no money then increasing height only makes problem worse if (res->Error == GA_ERROR::OK || res->Error == GA_ERROR::INSUFFICIENT_FUNDS) { return res; } } return res; } /** * * rct2: 0x006CFF2D */ static void window_track_place_toolupdate(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) { int16_t mapZ; map_invalidate_map_selection_tiles(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; // Get the tool map position CoordsXY mapCoords = sub_68A15E(screenCoords); if (mapCoords.isNull()) { window_track_place_clear_provisional(); return; } // Check if tool map position has changed since last update if (mapCoords.x == _window_track_place_last_x && mapCoords.y == _window_track_place_last_y) { place_virtual_track( _trackDesign.get(), PTD_OPERATION_DRAW_OUTLINES, true, GetOrAllocateRide(0), mapCoords.x, mapCoords.y, 0); return; } money32 cost = MONEY32_UNDEFINED; // Get base Z position mapZ = window_track_place_get_base_z(mapCoords); CoordsXYZ trackLoc = { mapCoords, mapZ }; if (game_is_not_paused() || gCheatsBuildInPauseMode) { window_track_place_clear_provisional(); auto res = FindValidTrackDesignPlaceHeight(trackLoc, GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST); if (res->Error == GA_ERROR::OK) { // Valid location found. Place the ghost at the location. auto tdAction = TrackDesignAction({ trackLoc, _currentTrackPieceDirection }, *_trackDesign); tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST); tdAction.SetCallback([trackLoc](const GameAction*, const TrackDesignActionResult* result) { if (result->Error == GA_ERROR::OK) { _window_track_place_ride_index = result->rideIndex; _window_track_place_last_valid_x = trackLoc.x; _window_track_place_last_valid_y = trackLoc.y; _window_track_place_last_valid_z = trackLoc.z; _window_track_place_last_was_valid = true; } }); res = GameActions::Execute(&tdAction); cost = res->Error == GA_ERROR::OK ? res->Cost : MONEY32_UNDEFINED; } } _window_track_place_last_x = trackLoc.x; _window_track_place_last_y = trackLoc.y; if (cost != _window_track_place_last_cost) { _window_track_place_last_cost = cost; widget_invalidate(w, WIDX_PRICE); } place_virtual_track( _trackDesign.get(), PTD_OPERATION_DRAW_OUTLINES, true, GetOrAllocateRide(0), trackLoc.x, trackLoc.y, trackLoc.z); } /** * * rct2: 0x006CFF34 */ static void window_track_place_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) { window_track_place_clear_provisional(); map_invalidate_map_selection_tiles(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; const CoordsXY mapCoords = sub_68A15E(screenCoords); if (mapCoords.isNull()) return; // Try increasing Z until a feasible placement is found int16_t mapZ = window_track_place_get_base_z(mapCoords); CoordsXYZ trackLoc = { mapCoords, mapZ }; auto res = FindValidTrackDesignPlaceHeight(trackLoc, 0); if (res->Error == GA_ERROR::OK) { auto tdAction = TrackDesignAction({ trackLoc, _currentTrackPieceDirection }, *_trackDesign); tdAction.SetCallback([trackLoc](const GameAction*, const TrackDesignActionResult* result) { if (result->Error == GA_ERROR::OK) { auto ride = get_ride(result->rideIndex); if (ride != nullptr) { window_close_by_class(WC_ERROR); audio_play_sound_at_location(SoundId::PlaceItem, trackLoc); _currentRideIndex = result->rideIndex; if (track_design_are_entrance_and_exit_placed()) { auto intent = Intent(WC_RIDE); intent.putExtra(INTENT_EXTRA_RIDE_ID, result->rideIndex); context_open_intent(&intent); auto wnd = window_find_by_class(WC_TRACK_DESIGN_PLACE); window_close(wnd); } else { ride_initialise_construction_window(ride); auto wnd = window_find_by_class(WC_RIDE_CONSTRUCTION); window_event_mouse_up_call(wnd, WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE); } } } else { audio_play_sound_at_location(SoundId::Error, result->Position); } }); GameActions::Execute(&tdAction); return; } // Unable to build track audio_play_sound_at_location(SoundId::Error, trackLoc); auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); windowManager->ShowError(res->GetErrorTitle(), res->GetErrorMessage()); } /** * * rct2: 0x006D015C */ static void window_track_place_toolabort(rct_window* w, rct_widgetindex widgetIndex) { window_track_place_clear_provisional(); } /** * * rct2: 0x006CFF01 */ static void window_track_place_unknown14(rct_window* w) { window_track_place_draw_mini_preview(_trackDesign.get()); } static void window_track_place_invalidate(rct_window* w) { window_track_place_draw_mini_preview(_trackDesign.get()); } /** * * rct2: 0x006D017F */ static void window_track_place_clear_provisional() { if (_window_track_place_last_was_valid) { auto ride = get_ride(_window_track_place_ride_index); if (ride != nullptr) { place_virtual_track( _trackDesign.get(), PTD_OPERATION_REMOVE_GHOST, true, ride, _window_track_place_last_valid_x, _window_track_place_last_valid_y, _window_track_place_last_valid_z); _window_track_place_last_was_valid = false; } } } void TrackPlaceClearProvisionalTemporarily() { if (_window_track_place_last_was_valid) { auto ride = get_ride(_window_track_place_ride_index); if (ride != nullptr) { place_virtual_track( _trackDesign.get(), PTD_OPERATION_REMOVE_GHOST, true, ride, _window_track_place_last_valid_x, _window_track_place_last_valid_y, _window_track_place_last_valid_z); } } } void TrackPlaceRestoreProvisional() { if (_window_track_place_last_was_valid) { auto tdAction = TrackDesignAction( { _window_track_place_last_valid_x, _window_track_place_last_valid_y, _window_track_place_last_valid_z, _currentTrackPieceDirection }, *_trackDesign); tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST); auto res = GameActions::Execute(&tdAction); if (res->Error != GA_ERROR::OK) { _window_track_place_last_was_valid = false; } } } /** * * rct2: 0x006D17C6 */ static int32_t window_track_place_get_base_z(const CoordsXY& loc) { auto surfaceElement = map_get_surface_element_at(loc); if (surfaceElement == nullptr) return 0; auto z = surfaceElement->GetBaseZ(); // Increase Z above slope if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) { z += 16; // Increase Z above double slope if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) z += 16; } // Increase Z above water if (surfaceElement->GetWaterHeight() > 0) z = std::max(z, surfaceElement->GetWaterHeight()); return z + place_virtual_track(_trackDesign.get(), PTD_OPERATION_GET_PLACE_Z, true, GetOrAllocateRide(0), loc.x, loc.y, z); } /** * * rct2: 0x006CFD9D */ static void window_track_place_paint(rct_window* w, rct_drawpixelinfo* dpi) { set_format_arg(0, char*, _trackDesign->name.c_str()); window_draw_widgets(w, dpi); // Draw mini tile preview rct_drawpixelinfo clippedDpi; if (clip_drawpixelinfo(&clippedDpi, dpi, w->windowPos.x + 4, w->windowPos.y + 18, 168, 78)) { rct_g1_element g1temp = {}; g1temp.offset = _window_track_place_mini_preview.data(); g1temp.width = TRACK_MINI_PREVIEW_WIDTH; g1temp.height = TRACK_MINI_PREVIEW_HEIGHT; gfx_set_g1_element(SPR_TEMP, &g1temp); gfx_draw_sprite(&clippedDpi, SPR_TEMP | SPRITE_ID_PALETTE_COLOUR_1(NOT_TRANSLUCENT(w->colours[0])), 0, 0, 0); } // Price if (_window_track_place_last_cost != MONEY32_UNDEFINED && !(gParkFlags & PARK_FLAGS_NO_MONEY)) { gfx_draw_string_centred( dpi, STR_COST_LABEL, w->windowPos.x + 88, w->windowPos.y + 94, COLOUR_BLACK, &_window_track_place_last_cost); } } /** * * rct2: 0x006D1845 */ static void window_track_place_draw_mini_preview(TrackDesign* td6) { window_track_place_clear_mini_preview(); // First pass is used to determine the width and height of the image so it can centre it CoordsXY min = { 0, 0 }; CoordsXY max = { 0, 0 }; for (int32_t pass = 0; pass < 2; pass++) { CoordsXY origin = { 0, 0 }; if (pass == 1) { origin.x -= ((max.x + min.x) >> 6) * COORDS_XY_STEP; origin.y -= ((max.y + min.y) >> 6) * COORDS_XY_STEP; } if (td6->type == RIDE_TYPE_MAZE) { window_track_place_draw_mini_preview_maze(td6, pass, origin, min, max); } else { window_track_place_draw_mini_preview_track(td6, pass, origin, min, max); } } } static void window_track_place_draw_mini_preview_track( TrackDesign* td6, int32_t pass, CoordsXY origin, CoordsXY min, CoordsXY max) { uint8_t rotation = (_currentTrackPieceDirection + get_current_rotation()) & 3; const rct_preview_track** trackBlockArray = (ride_type_has_flag(td6->type, RIDE_TYPE_FLAG_HAS_TRACK)) ? TrackBlocks : FlatRideTrackBlocks; for (const auto& trackElement : td6->track_elements) { int32_t trackType = trackElement.type; if (trackType == TRACK_ELEM_INVERTED_90_DEG_UP_TO_FLAT_QUARTER_LOOP_ALIAS) { trackType = TRACK_ELEM_MULTIDIM_INVERTED_90_DEG_UP_TO_FLAT_QUARTER_LOOP; } // Follow a single track piece shape const rct_preview_track* trackBlock = trackBlockArray[trackType]; while (trackBlock->index != 255) { auto rotatedAndOffsetTrackBlock = origin + CoordsXY{ trackBlock->x, trackBlock->y }.Rotate(rotation); if (pass == 0) { min.x = std::min(min.x, rotatedAndOffsetTrackBlock.x); max.x = std::max(max.x, rotatedAndOffsetTrackBlock.x); min.y = std::min(min.y, rotatedAndOffsetTrackBlock.y); max.y = std::max(max.y, rotatedAndOffsetTrackBlock.y); } else { auto pixelPosition = draw_mini_preview_get_pixel_position(rotatedAndOffsetTrackBlock); if (draw_mini_preview_is_pixel_in_bounds(pixelPosition)) { uint8_t* pixel = draw_mini_preview_get_pixel_ptr(pixelPosition); auto bits = trackBlock->var_08.Rotate(rotation & 3).GetBaseQuarterOccupied(); // Station track is a lighter colour uint8_t colour = (TrackSequenceProperties[trackType][0] & TRACK_SEQUENCE_FLAG_ORIGIN) ? _PaletteIndexColourStation : _PaletteIndexColourTrack; for (int32_t i = 0; i < 4; i++) { if (bits & 1) pixel[338 + i] = colour; // x + 2, y + 2 if (bits & 2) pixel[168 + i] = colour; // y + 1 if (bits & 4) pixel[2 + i] = colour; // x + 2 if (bits & 8) pixel[172 + i] = colour; // x + 4, y + 1 } } } trackBlock++; } // Change rotation and next position based on track curvature rotation &= 3; const rct_track_coordinates* track_coordinate = &TrackCoordinates[trackType]; trackType *= 10; auto rotatedAndOfffsetTrack = origin + CoordsXY{ track_coordinate->x, track_coordinate->y }.Rotate(rotation); rotation += track_coordinate->rotation_end - track_coordinate->rotation_begin; rotation &= 3; if (track_coordinate->rotation_end & 4) { rotation |= 4; } if (!(rotation & 4)) { origin = rotatedAndOfffsetTrack + CoordsDirectionDelta[rotation]; } } // Draw entrance and exit preview. for (const auto& entrance : td6->entrance_elements) { auto rotatedAndOffsetEntrance = origin + CoordsXY{ entrance.x, entrance.y }.Rotate(rotation); if (pass == 0) { min.x = std::min(min.x, rotatedAndOffsetEntrance.x); max.x = std::max(max.x, rotatedAndOffsetEntrance.x); min.y = std::min(min.y, rotatedAndOffsetEntrance.y); max.y = std::max(max.y, rotatedAndOffsetEntrance.y); } else { auto pixelPosition = draw_mini_preview_get_pixel_position(rotatedAndOffsetEntrance); if (draw_mini_preview_is_pixel_in_bounds(pixelPosition)) { uint8_t* pixel = draw_mini_preview_get_pixel_ptr(pixelPosition); uint8_t colour = entrance.isExit ? _PaletteIndexColourExit : _PaletteIndexColourEntrance; for (int32_t i = 0; i < 4; i++) { pixel[338 + i] = colour; // x + 2, y + 2 pixel[168 + i] = colour; // y + 1 pixel[2 + i] = colour; // x + 2 pixel[172 + i] = colour; // x + 4, y + 1 } } } } } static void window_track_place_draw_mini_preview_maze( TrackDesign* td6, int32_t pass, const CoordsXY& origin, CoordsXY min, CoordsXY max) { uint8_t rotation = (_currentTrackPieceDirection + get_current_rotation()) & 3; for (const auto& mazeElement : td6->maze_elements) { auto rotatedMazeCoords = origin + TileCoordsXY{ mazeElement.x, mazeElement.y }.ToCoordsXY().Rotate(rotation); if (pass == 0) { min.x = std::min(min.x, rotatedMazeCoords.x); max.x = std::max(max.x, rotatedMazeCoords.x); min.y = std::min(min.y, rotatedMazeCoords.y); max.y = std::max(max.y, rotatedMazeCoords.y); } else { auto pixelPosition = draw_mini_preview_get_pixel_position(rotatedMazeCoords); if (draw_mini_preview_is_pixel_in_bounds(pixelPosition)) { uint8_t* pixel = draw_mini_preview_get_pixel_ptr(pixelPosition); uint8_t colour = _PaletteIndexColourTrack; // Draw entrance and exit with different colours. if (mazeElement.type == MAZE_ELEMENT_TYPE_ENTRANCE) colour = _PaletteIndexColourEntrance; else if (mazeElement.type == MAZE_ELEMENT_TYPE_EXIT) colour = _PaletteIndexColourExit; for (int32_t i = 0; i < 4; i++) { pixel[338 + i] = colour; // x + 2, y + 2 pixel[168 + i] = colour; // y + 1 pixel[2 + i] = colour; // x + 2 pixel[172 + i] = colour; // x + 4, y + 1 } } } } } static ScreenCoordsXY draw_mini_preview_get_pixel_position(const CoordsXY& location) { auto tilePos = TileCoordsXY(location); return { (80 + (tilePos.y - tilePos.x) * 4), (38 + (tilePos.y + tilePos.x) * 2) }; } static bool draw_mini_preview_is_pixel_in_bounds(const ScreenCoordsXY& pixel) { return pixel.x >= 0 && pixel.y >= 0 && pixel.x <= 160 && pixel.y <= 75; } static uint8_t* draw_mini_preview_get_pixel_ptr(const ScreenCoordsXY& pixel) { return &_window_track_place_mini_preview[pixel.y * TRACK_MINI_PREVIEW_WIDTH + pixel.x]; }