diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index b92add2344..38c40b2b71 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -4214,6 +4214,8 @@ STR_5902 :Show bounding boxes STR_5903 :Show paint debug window STR_5904 :Reset date STR_5905 :{SMALLFONT}{BLACK}A map generation tool that automatically creates a custom landscape +STR_5906 :Zoom to cursor position +STR_5907 :{SMALLFONT}{BLACK}When enabled, zooming in will centre around the cursor, as opposed to the screen centre. ############# # Scenarios # diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 22690aad66..0a4e9fff49 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -19,6 +19,7 @@ - Feature: Add console command to set scenario initial cash. - Feature: Objects are scanned from the user directory as well as the RCT2 directory. - Feature: Objects directory is scanned recursively. +- Feature: Optionally zoom in towards the cursor rather than the screen centre. - Improve: Performance and reliability of loading objects. - Improve: Screenshots are now saved with the name of the park and the current date and time. - Improve: More accurate frame rate calculation diff --git a/src/config.c b/src/config.c index d5ab291413..c85c76d76d 100644 --- a/src/config.c +++ b/src/config.c @@ -227,7 +227,7 @@ config_property_definition _generalDefinitions[] = { { offsetof(general_configuration, last_save_scenario_directory), "last_scenario_directory", CONFIG_VALUE_TYPE_STRING, { .value_string = NULL }, NULL }, { offsetof(general_configuration, last_save_track_directory), "last_track_directory", CONFIG_VALUE_TYPE_STRING, { .value_string = NULL }, NULL }, { offsetof(general_configuration, window_limit), "window_limit", CONFIG_VALUE_TYPE_UINT8, WINDOW_LIMIT_MAX, NULL }, - + { offsetof(general_configuration, zoom_to_cursor), "zoom_to_cursor", CONFIG_VALUE_TYPE_BOOLEAN, true, NULL }, }; config_property_definition _interfaceDefinitions[] = { diff --git a/src/config.h b/src/config.h index e211374c29..ffc28ba085 100644 --- a/src/config.h +++ b/src/config.h @@ -198,6 +198,7 @@ typedef struct general_configuration { utf8string last_save_scenario_directory; utf8string last_save_track_directory; uint8 window_limit; + uint8 zoom_to_cursor; } general_configuration; typedef struct interface_configuration { diff --git a/src/interface/window.c b/src/interface/window.c index 5e042798d8..2fc1a323f7 100644 --- a/src/interface/window.c +++ b/src/interface/window.c @@ -374,7 +374,7 @@ static void window_close_surplus(int cap, sint8 avoid_classification) /* * Changes the maximum amount of windows allowed */ -void window_set_window_limit(int value) +void window_set_window_limit(int value) { int prev = gConfigGeneral.window_limit; int val = clamp(value, WINDOW_LIMIT_MIN, WINDOW_LIMIT_MAX); @@ -1445,6 +1445,49 @@ void window_rotate_camera(rct_window *w, int direction) reset_all_sprite_quadrant_placements(); } +void window_viewport_get_map_coords_by_cursor(rct_window *w, sint16 *map_x, sint16 *map_y, sint16 *offset_x, sint16 *offset_y) +{ + // Get mouse position to offset against. + int mouse_x, mouse_y; + platform_get_cursor_position_scaled(&mouse_x, &mouse_y); + + // Compute map coordinate by mouse position. + get_map_coordinates_from_pos(mouse_x, mouse_y, VIEWPORT_INTERACTION_MASK_NONE, map_x, map_y, NULL, NULL, NULL); + + // Get viewport coordinates centring around the tile. + int base_height = map_element_height(*map_x, *map_y); + int dest_x, dest_y; + center_2d_coordinates(*map_x, *map_y, base_height, &dest_x, &dest_y, w->viewport); + + // Rebase mouse position onto centre of window, and compensate for zoom level. + int rebased_x = ((w->width >> 1) - mouse_x) << w->viewport->zoom, + rebased_y = ((w->height >> 1) - mouse_y) << w->viewport->zoom; + + // Compute cursor offset relative to tile. + *offset_x = (w->saved_view_x - (dest_x + rebased_x)) << w->viewport->zoom; + *offset_y = (w->saved_view_y - (dest_y + rebased_y)) << w->viewport->zoom; +} + +void window_viewport_centre_tile_around_cursor(rct_window *w, sint16 map_x, sint16 map_y, sint16 offset_x, sint16 offset_y) +{ + // Get viewport coordinates centring around the tile. + int dest_x, dest_y; + int base_height = map_element_height(map_x, map_y); + center_2d_coordinates(map_x, map_y, base_height, &dest_x, &dest_y, w->viewport); + + // Get mouse position to offset against. + int mouse_x, mouse_y; + platform_get_cursor_position_scaled(&mouse_x, &mouse_y); + + // Rebase mouse position onto centre of window, and compensate for zoom level. + int rebased_x = ((w->width >> 1) - mouse_x) << w->viewport->zoom, + rebased_y = ((w->height >> 1) - mouse_y) << w->viewport->zoom; + + // Apply offset to the viewport. + w->saved_view_x = dest_x + rebased_x + (offset_x >> w->viewport->zoom); + w->saved_view_y = dest_y + rebased_y + (offset_y >> w->viewport->zoom); +} + void window_zoom_set(rct_window *w, int zoomLevel) { rct_viewport* v = w->viewport; @@ -1453,12 +1496,18 @@ void window_zoom_set(rct_window *w, int zoomLevel) if (v->zoom == zoomLevel) return; + // Zooming to cursor? Remember where we're pointing at the moment. + sint16 saved_map_x, saved_map_y, offset_x, offset_y; + if (gConfigGeneral.zoom_to_cursor) { + window_viewport_get_map_coords_by_cursor(w, &saved_map_x, &saved_map_y, &offset_x, &offset_y); + } + // Zoom in while (v->zoom > zoomLevel) { v->zoom--; w->saved_view_x += v->view_width / 4; w->saved_view_y += v->view_height / 4; - v->view_width /= 2; + v->view_width /= 2; v->view_height /= 2; } @@ -1467,10 +1516,15 @@ void window_zoom_set(rct_window *w, int zoomLevel) v->zoom++; w->saved_view_x -= v->view_width / 2; w->saved_view_y -= v->view_height / 2; - v->view_width *= 2; + v->view_width *= 2; v->view_height *= 2; } + // Zooming to cursor? Centre around the tile we were hovering over just now. + if (gConfigGeneral.zoom_to_cursor) { + window_viewport_centre_tile_around_cursor(w, saved_map_x, saved_map_y, offset_x, offset_y); + } + // HACK: Prevents the redraw from failing when there is // a window on top of the viewport. window_bring_to_front(w); diff --git a/src/interface/window.h b/src/interface/window.h index fa7d8a75a8..1802060697 100644 --- a/src/interface/window.h +++ b/src/interface/window.h @@ -574,6 +574,8 @@ rct_window *window_get_main(); void window_scroll_to_viewport(rct_window *w); void window_scroll_to_location(rct_window *w, int x, int y, int z); void window_rotate_camera(rct_window *w, int direction); +void window_viewport_get_map_coords_by_cursor(rct_window *w, sint16 *map_x, sint16 *map_y, sint16 *offset_x, sint16 *offset_y); +void window_viewport_centre_tile_around_cursor(rct_window *w, sint16 map_x, sint16 map_y, sint16 offset_x, sint16 offset_y); void window_zoom_set(rct_window *w, int zoomLevel); void window_zoom_in(rct_window *w); void window_zoom_out(rct_window *w); diff --git a/src/localisation/string_ids.h b/src/localisation/string_ids.h index db725ab61a..d0613e4dfa 100644 --- a/src/localisation/string_ids.h +++ b/src/localisation/string_ids.h @@ -3336,8 +3336,9 @@ enum { STR_DEBUG_PAINT_SHOW_BOUND_BOXES = 5902, STR_DEBUG_DROPDOWN_DEBUG_PAINT = 5903, STR_CHEAT_RESET_DATE = 5904, - STR_MAP_GENERATOR_TIP = 5905, + STR_ZOOM_TO_CURSOR = 5906, + STR_ZOOM_TO_CURSOR_TIP = 5907, // Have to include resource strings (from scenarios and objects) for the time being now that language is partially working STR_COUNT = 32768 diff --git a/src/platform/platform.h b/src/platform/platform.h index c85e1ec33d..88ad8d3bf9 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -166,6 +166,7 @@ bool platform_file_delete(const utf8 *path); void platform_hide_cursor(); void platform_show_cursor(); void platform_get_cursor_position(int *x, int *y); +void platform_get_cursor_position_scaled(int *x, int *y); void platform_set_cursor_position(int x, int y); unsigned int platform_get_ticks(); void platform_resolve_user_data_path(); diff --git a/src/platform/shared.c b/src/platform/shared.c index 83f28719b4..50eccbf2e0 100644 --- a/src/platform/shared.c +++ b/src/platform/shared.c @@ -783,6 +783,15 @@ void platform_get_cursor_position(int *x, int *y) SDL_GetMouseState(x, y); } +void platform_get_cursor_position_scaled(int *x, int *y) +{ + platform_get_cursor_position(x, y); + + // Compensate for window scaling. + *x = (int) ceilf(*x / gConfigGeneral.window_scale); + *y = (int) ceilf(*y / gConfigGeneral.window_scale); +} + void platform_set_cursor_position(int x, int y) { SDL_WarpMouseInWindow(NULL, x, y); diff --git a/src/windows/options.c b/src/windows/options.c index 8d7692b71b..3a341fbb9d 100644 --- a/src/windows/options.c +++ b/src/windows/options.c @@ -129,6 +129,7 @@ enum WINDOW_OPTIONS_WIDGET_IDX { WIDX_SCREEN_EDGE_SCROLLING, WIDX_TRAP_CURSOR, WIDX_INVERT_DRAG, + WIDX_ZOOM_TO_CURSOR, WIDX_HOTKEY_DROPDOWN, WIDX_THEMES_GROUP, WIDX_THEMES, @@ -269,27 +270,28 @@ static rct_widget window_options_audio_widgets[] = { static rct_widget window_options_controls_and_interface_widgets[] = { MAIN_OPTIONS_WIDGETS, - { WWT_GROUPBOX, 1, 5, 304, 53, 129, STR_CONTROLS_GROUP, STR_NONE }, // Controls group + { WWT_GROUPBOX, 1, 5, 304, 53, 144, STR_CONTROLS_GROUP, STR_NONE }, // Controls group { WWT_CHECKBOX, 2, 10, 299, 68, 79, STR_SCREEN_EDGE_SCROLLING, STR_SCREEN_EDGE_SCROLLING_TIP }, // Edge scrolling { WWT_CHECKBOX, 2, 10, 299, 83, 94, STR_TRAP_MOUSE, STR_TRAP_MOUSE_TIP }, // Trap mouse { WWT_CHECKBOX, 2, 10, 299, 98, 109, STR_INVERT_RIGHT_MOUSE_DRAG, STR_INVERT_RIGHT_MOUSE_DRAG_TIP }, // Invert right mouse dragging - { WWT_DROPDOWN_BUTTON, 1, 26, 185, 113, 124, STR_HOTKEY, STR_HOTKEY_TIP }, // Set hotkeys buttons + { WWT_CHECKBOX, 2, 10, 299, 113, 124, STR_ZOOM_TO_CURSOR, STR_ZOOM_TO_CURSOR_TIP }, // Zoom to cursor + { WWT_DROPDOWN_BUTTON, 1, 26, 185, 128, 139, STR_HOTKEY, STR_HOTKEY_TIP }, // Set hotkeys buttons - { WWT_GROUPBOX, 1, 5, 304, 133, 179, STR_THEMES_GROUP, STR_NONE }, // Toolbar buttons group - { WWT_DROPDOWN, 1, 155, 299, 147, 158, STR_NONE, STR_NONE }, // Themes - { WWT_DROPDOWN_BUTTON, 1, 288, 298, 148, 157, STR_DROPDOWN_GLYPH, STR_CURRENT_THEME_TIP }, - { WWT_DROPDOWN_BUTTON, 1, 10, 145, 163, 174, STR_EDIT_THEMES_BUTTON, STR_EDIT_THEMES_BUTTON_TIP }, // Themes button + { WWT_GROUPBOX, 1, 5, 304, 148, 194, STR_THEMES_GROUP, STR_NONE }, // Toolbar buttons group + { WWT_DROPDOWN, 1, 155, 299, 162, 173, STR_NONE, STR_NONE }, // Themes + { WWT_DROPDOWN_BUTTON, 1, 288, 298, 163, 172, STR_DROPDOWN_GLYPH, STR_CURRENT_THEME_TIP }, + { WWT_DROPDOWN_BUTTON, 1, 10, 145, 178, 189, STR_EDIT_THEMES_BUTTON, STR_EDIT_THEMES_BUTTON_TIP }, // Themes button - { WWT_GROUPBOX, 1, 5, 304, 183, 245, STR_TOOLBAR_BUTTONS_GROUP, STR_NONE }, // Toolbar buttons group - { WWT_CHECKBOX, 2, 10, 145, 214, 225, STR_FINANCES_BUTTON_ON_TOOLBAR, STR_FINANCES_BUTTON_ON_TOOLBAR_TIP }, // Finances - { WWT_CHECKBOX, 2, 10, 145, 229, 240, STR_RESEARCH_BUTTON_ON_TOOLBAR, STR_RESEARCH_BUTTON_ON_TOOLBAR_TIP }, // Research - { WWT_CHECKBOX, 2, 155, 299, 214, 225, STR_CHEATS_BUTTON_ON_TOOLBAR, STR_CHEATS_BUTTON_ON_TOOLBAR_TIP }, // Cheats - { WWT_CHECKBOX, 2, 155, 299, 229, 240, STR_SHOW_RECENT_MESSAGES_ON_TOOLBAR, STR_SHOW_RECENT_MESSAGES_ON_TOOLBAR_TIP }, // Recent messages + { WWT_GROUPBOX, 1, 5, 304, 198, 260, STR_TOOLBAR_BUTTONS_GROUP, STR_NONE }, // Toolbar buttons group + { WWT_CHECKBOX, 2, 10, 145, 229, 240, STR_FINANCES_BUTTON_ON_TOOLBAR, STR_FINANCES_BUTTON_ON_TOOLBAR_TIP }, // Finances + { WWT_CHECKBOX, 2, 10, 145, 244, 255, STR_RESEARCH_BUTTON_ON_TOOLBAR, STR_RESEARCH_BUTTON_ON_TOOLBAR_TIP }, // Research + { WWT_CHECKBOX, 2, 155, 299, 229, 240, STR_CHEATS_BUTTON_ON_TOOLBAR, STR_CHEATS_BUTTON_ON_TOOLBAR_TIP }, // Cheats + { WWT_CHECKBOX, 2, 155, 299, 244, 255, STR_SHOW_RECENT_MESSAGES_ON_TOOLBAR, STR_SHOW_RECENT_MESSAGES_ON_TOOLBAR_TIP }, // Recent messages - { WWT_CHECKBOX, 2, 10, 299, 254, 265, STR_SELECT_BY_TRACK_TYPE, STR_SELECT_BY_TRACK_TYPE_TIP }, // Select by track type - { WWT_DROPDOWN, 2, 155, 299, 269, 280, STR_NONE, STR_NONE }, // Scenario select mode - { WWT_DROPDOWN_BUTTON, 2, 288, 298, 270, 279, STR_DROPDOWN_GLYPH, STR_SCENARIO_GROUPING_TIP }, - { WWT_CHECKBOX, 2, 18, 299, 284, 295, STR_OPTIONS_SCENARIO_UNLOCKING, STR_SCENARIO_UNLOCKING_TIP }, // Unlocking of scenarios + { WWT_CHECKBOX, 2, 10, 299, 269, 280, STR_SELECT_BY_TRACK_TYPE, STR_SELECT_BY_TRACK_TYPE_TIP }, // Select by track type + { WWT_DROPDOWN, 2, 155, 299, 284, 295, STR_NONE, STR_NONE }, // Scenario select mode + { WWT_DROPDOWN_BUTTON, 2, 288, 298, 285, 294, STR_DROPDOWN_GLYPH, STR_SCENARIO_GROUPING_TIP }, + { WWT_CHECKBOX, 2, 18, 299, 299, 310, STR_OPTIONS_SCENARIO_UNLOCKING, STR_SCENARIO_UNLOCKING_TIP }, // Unlocking of scenarios { WIDGETS_END }, }; @@ -491,6 +493,7 @@ static uint32 window_options_page_enabled_widgets[] = { (1 << WIDX_SCREEN_EDGE_SCROLLING) | (1 << WIDX_TRAP_CURSOR) | (1 << WIDX_INVERT_DRAG) | + (1 << WIDX_ZOOM_TO_CURSOR) | (1 << WIDX_HOTKEY_DROPDOWN) | (1 << WIDX_TOOLBAR_SHOW_FINANCES) | (1 << WIDX_TOOLBAR_SHOW_RESEARCH) | @@ -694,6 +697,11 @@ static void window_options_mouseup(rct_window *w, int widgetIndex) SDL_SetWindowGrab(gWindow, gConfigGeneral.trap_cursor ? SDL_TRUE : SDL_FALSE); window_invalidate(w); break; + case WIDX_ZOOM_TO_CURSOR: + gConfigGeneral.zoom_to_cursor ^= 1; + config_save_default(); + window_invalidate(w); + break; case WIDX_TOOLBAR_SHOW_FINANCES: gConfigInterface.toolbar_show_finances ^= 1; config_save_default(); @@ -1556,6 +1564,7 @@ static void window_options_invalidate(rct_window *w) widget_set_checkbox_value(w, WIDX_SCREEN_EDGE_SCROLLING, gConfigGeneral.edge_scrolling); widget_set_checkbox_value(w, WIDX_TRAP_CURSOR, gConfigGeneral.trap_cursor); widget_set_checkbox_value(w, WIDX_INVERT_DRAG, gConfigGeneral.invert_viewport_drag); + widget_set_checkbox_value(w, WIDX_ZOOM_TO_CURSOR, gConfigGeneral.zoom_to_cursor); widget_set_checkbox_value(w, WIDX_TOOLBAR_SHOW_FINANCES, gConfigInterface.toolbar_show_finances); widget_set_checkbox_value(w, WIDX_TOOLBAR_SHOW_RESEARCH, gConfigInterface.toolbar_show_research); widget_set_checkbox_value(w, WIDX_TOOLBAR_SHOW_CHEATS, gConfigInterface.toolbar_show_cheats);