diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index 2f6cb9ee60..c757fb185f 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -3785,3 +3785,5 @@ STR_6707 :(none selected) STR_6708 :Smooth Strength STR_6709 :Enter Smooth Strength between {COMMA16} and {COMMA16} STR_6710 :Stable sort +STR_6711 :Filename: +STR_6712 :Save diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 4d15863ac7..b689a9d60a 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -4,6 +4,7 @@ - Improved: [#23260] Add diagonal (block) brakes to LSM Launched Roller Coaster. - Improved: [#23350] Increased the maximum width of the ride graph window. - Improved: [#23404] Folders are now paired with an icon in the load/save window. +- Improved: [#23405] Filenames can now be input directly into the file browser (load/save) window. - Improved: [#23431] Opaque water and Corkscrew Roller Coaster boosters now show up if RCT1 isn’t linked. - Change: [#23413] The max number of park entrance objects has been raised to 255. - Fix: [#1122] Trains spawned on a cable lift hill will fall down and crash (original bug). diff --git a/src/openrct2-ui/UiStringIds.h b/src/openrct2-ui/UiStringIds.h index f0f5f0ae77..5b8eeffe9f 100644 --- a/src/openrct2-ui/UiStringIds.h +++ b/src/openrct2-ui/UiStringIds.h @@ -853,7 +853,9 @@ namespace OpenRCT2 STR_FILEBROWSER_FOLDER_NAME_PROMPT = 5986, STR_FILEBROWSER_OVERWRITE_PROMPT = 2708, STR_FILEBROWSER_OVERWRITE_TITLE = 2709, + STR_FILEBROWSER_SAVE_BUTTON = 6712, STR_FILEBROWSER_USE_SYSTEM_WINDOW = 2707, + STR_FILENAME = 6711, STR_FILE_DIALOG_TITLE_INSTALL_NEW_TRACK_DESIGN = 1039, STR_FILE_DIALOG_TITLE_LOAD_GAME = 1036, STR_FILE_DIALOG_TITLE_LOAD_HEIGHTMAP = 6042, diff --git a/src/openrct2-ui/input/InputManager.cpp b/src/openrct2-ui/input/InputManager.cpp index 9bfe6b471c..cf72683e55 100644 --- a/src/openrct2-ui/input/InputManager.cpp +++ b/src/openrct2-ui/input/InputManager.cpp @@ -209,6 +209,7 @@ void InputManager::Process(const InputEvent& e) if (e.DeviceKind == InputDeviceKind::Keyboard) { + // TODO: replace with event auto w = WindowFindByClass(WindowClass::Textinput); if (w != nullptr) { @@ -219,6 +220,28 @@ void InputManager::Process(const InputEvent& e) return; } + // TODO: replace with event + w = WindowFindByClass(WindowClass::LoadsaveOverwritePrompt); + if (w != nullptr) + { + if (e.State == InputEventState::Release) + { + OpenRCT2::Ui::Windows::WindowLoadSaveOverwritePromptInputKey(w, e.Button); + } + return; + } + + // TODO: replace with event + w = WindowFindByClass(WindowClass::Loadsave); + if (w != nullptr) + { + if (e.State == InputEventState::Release) + { + OpenRCT2::Ui::Windows::WindowLoadSaveInputKey(w, e.Button); + } + return; + } + if (OpenRCT2::Ui::Windows::IsUsingWidgetTextBox()) { return; diff --git a/src/openrct2-ui/interface/Widget.cpp b/src/openrct2-ui/interface/Widget.cpp index 2ed4442450..7d195a37e8 100644 --- a/src/openrct2-ui/interface/Widget.cpp +++ b/src/openrct2-ui/interface/Widget.cpp @@ -1183,7 +1183,7 @@ namespace OpenRCT2::Ui if (OpenRCT2::Ui::Windows::TextBoxCaretIsFlashed()) { auto colour = ColourMapA[w.colours[1].colour].mid_light; - auto y = topLeft.y + (widget.height() - 1); + auto y = topLeft.y + 1 + widget.height() - 4; GfxFillRect(dpi, { { curX, y }, { curX + width, y } }, colour + 5); } } diff --git a/src/openrct2-ui/windows/LoadSave.cpp b/src/openrct2-ui/windows/LoadSave.cpp index 2b429ed50a..5fab1fbb31 100644 --- a/src/openrct2-ui/windows/LoadSave.cpp +++ b/src/openrct2-ui/windows/LoadSave.cpp @@ -7,6 +7,7 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ +#include #include #include #include @@ -64,6 +65,8 @@ namespace OpenRCT2::Ui::Windows WIDX_SORT_NAME, WIDX_SORT_DATE, WIDX_SCROLL, + WIDX_FILENAME_TEXTBOX, + WIDX_SAVE, WIDX_BROWSE, }; @@ -71,15 +74,17 @@ namespace OpenRCT2::Ui::Windows static Widget window_loadsave_widgets[] = { WINDOW_SHIM(WINDOW_TITLE, WW, WH), - MakeWidget({ 0, WH - 1}, { WW, 1}, WindowWidgetType::Resize, WindowColour::Secondary ), // WIDX_RESIZE - MakeWidget({ 4, 36}, { 84, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_LOADSAVE_DEFAULT, STR_LOADSAVE_DEFAULT_TIP), // WIDX_DEFAULT - MakeWidget({ 88, 36}, { 84, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_ACTION_UP ), // WIDX_UP - MakeWidget({ 172, 36}, { 87, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_ACTION_NEW_FOLDER ), // WIDX_NEW_FOLDER - MakeWidget({ 259, 36}, { 87, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_ACTION_NEW_FILE ), // WIDX_NEW_FILE - MakeWidget({ 4, 55}, {170, 14}, WindowWidgetType::TableHeader, WindowColour::Primary ), // WIDX_SORT_NAME - MakeWidget({(WW - 5) / 2 + 1, 55}, {170, 14}, WindowWidgetType::TableHeader, WindowColour::Primary ), // WIDX_SORT_DATE - MakeWidget({ 4, 68}, {342, 303}, WindowWidgetType::Scroll, WindowColour::Primary, SCROLL_VERTICAL ), // WIDX_SCROLL - MakeWidget({ 4, WH - 24}, {197, 19}, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_USE_SYSTEM_WINDOW ), // WIDX_BROWSE + MakeWidget({ 0, WH - 1}, { WW, 1 }, WindowWidgetType::Resize, WindowColour::Secondary ), // WIDX_RESIZE + MakeWidget({ 4, 36}, { 84, 14 }, WindowWidgetType::Button, WindowColour::Primary, STR_LOADSAVE_DEFAULT, STR_LOADSAVE_DEFAULT_TIP), // WIDX_DEFAULT + MakeWidget({ 88, 36}, { 84, 14 }, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_ACTION_UP ), // WIDX_UP + MakeWidget({ 172, 36}, { 87, 14 }, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_ACTION_NEW_FOLDER ), // WIDX_NEW_FOLDER + MakeWidget({ 259, 36}, { 87, 14 }, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_ACTION_NEW_FILE ), // WIDX_NEW_FILE + MakeWidget({ 4, 55}, { 170, 14 }, WindowWidgetType::TableHeader, WindowColour::Primary ), // WIDX_SORT_NAME + MakeWidget({(WW - 5) / 2 + 1, 55}, { 170, 14 }, WindowWidgetType::TableHeader, WindowColour::Primary ), // WIDX_SORT_DATE + MakeWidget({ 4, 68}, { 342, 303 }, WindowWidgetType::Scroll, WindowColour::Primary, SCROLL_VERTICAL ), // WIDX_SCROLL + MakeWidget({ 64, WH - 50}, { WW - 133, 14 }, WindowWidgetType::TextBox, WindowColour::Secondary ), // WIDX_FILENAME_TEXTBOX + MakeWidget({ WW - 65, WH - 50}, { 60, 14 }, WindowWidgetType::Button, WindowColour::Secondary, STR_FILEBROWSER_SAVE_BUTTON ), // WIDX_SAVE + MakeWidget({ 4, WH - 24}, { 197, 19 }, WindowWidgetType::Button, WindowColour::Primary, STR_FILEBROWSER_USE_SYSTEM_WINDOW ), // WIDX_BROWSE kWidgetsEnd, }; // clang-format on @@ -111,6 +116,7 @@ namespace OpenRCT2::Ui::Windows static std::vector _listItems; static char _directory[MAX_PATH]; static char _parentDirectory[MAX_PATH]; + static char _currentFilename[MAX_PATH]; static u8string _extensionPattern; static u8string _defaultPath; static int32_t _type; @@ -500,7 +506,7 @@ namespace OpenRCT2::Ui::Windows int32_t type; public: - void PopulateList(int32_t includeNewItem, const u8string& directory, std::string_view extensionPattern) + void PopulateList(bool includeNewItem, const u8string& directory, std::string_view extensionPattern) { const auto absoluteDirectory = Path::GetAbsolute(directory); String::safeUtf8Copy(_directory, absoluteDirectory.c_str(), std::size(_directory)); @@ -691,11 +697,31 @@ namespace OpenRCT2::Ui::Windows Audio::StopAll(); } + if (isSave) + { + widgets[WIDX_FILENAME_TEXTBOX].type = WindowWidgetType::TextBox; + widgets[WIDX_FILENAME_TEXTBOX].string = _currentFilename; + widgets[WIDX_SAVE].type = WindowWidgetType::Button; + + // Set current filename + String::set(_currentFilename, sizeof(_currentFilename), _defaultPath.c_str()); + + // Focus textbox + WindowStartTextbox(*this, WIDX_FILENAME_TEXTBOX, _currentFilename, sizeof(_currentFilename)); + } + else + { + widgets[WIDX_FILENAME_TEXTBOX].type = WindowWidgetType::Empty; + widgets[WIDX_SAVE].type = WindowWidgetType::Empty; + } + + // Populate file list const char* pattern = GetFilterPatternByType(type, isSave); PopulateList(isSave, path, pattern); no_list_items = static_cast(_listItems.size()); selected_list_item = -1; + // Reset window dimensions InitScrollWidgets(); ComputeMaxDateWidth(); min_width = WW; @@ -731,6 +757,15 @@ namespace OpenRCT2::Ui::Windows } } + void OnUpdate() override + { + if (GetCurrentTextBox().window.classification == classification && GetCurrentTextBox().window.number == number) + { + WindowUpdateTextboxCaret(); + WidgetInvalidate(*this, WIDX_FILENAME_TEXTBOX); + } + } + void OnPrepareDraw() override { ResizeFrameWithPage(); @@ -746,6 +781,30 @@ namespace OpenRCT2::Ui::Windows window_loadsave_widgets[WIDX_SCROLL].right = width - 5; window_loadsave_widgets[WIDX_SCROLL].bottom = height - 30; + if (_type & LOADSAVETYPE_SAVE) + { + window_loadsave_widgets[WIDX_SCROLL].bottom -= 18; + + // Get 'Save' button string width + auto saveLabel = LanguageGetString(STR_FILEBROWSER_SAVE_BUTTON); + auto saveLabelWidth = GfxGetStringWidth(saveLabel, FontStyle::Medium) + 16; + + window_loadsave_widgets[WIDX_SAVE].top = height - 42; + window_loadsave_widgets[WIDX_SAVE].bottom = height - 30; + window_loadsave_widgets[WIDX_SAVE].left = width - saveLabelWidth - 5; + window_loadsave_widgets[WIDX_SAVE].right = width - 5; + + // Get 'Filename:' string width + auto filenameLabel = LanguageGetString(STR_FILENAME); + auto filenameLabelWidth = GfxGetStringWidth(filenameLabel, FontStyle::Medium); + + window_loadsave_widgets[WIDX_FILENAME_TEXTBOX].top = height - 42; + window_loadsave_widgets[WIDX_FILENAME_TEXTBOX].bottom = height - 30; + window_loadsave_widgets[WIDX_FILENAME_TEXTBOX].left = 4 + filenameLabelWidth + 6; + window_loadsave_widgets[WIDX_FILENAME_TEXTBOX].right = window_loadsave_widgets[WIDX_SAVE].left - 5; + } + + // 'Use system file browser' window_loadsave_widgets[WIDX_BROWSE].top = height - 24; window_loadsave_widgets[WIDX_BROWSE].bottom = height - 6; } @@ -776,7 +835,7 @@ namespace OpenRCT2::Ui::Windows id = STR_DOWN; // Draw name button indicator. - Widget sort_name_widget = window_loadsave_widgets[WIDX_SORT_NAME]; + auto& sort_name_widget = widgets[WIDX_SORT_NAME]; ft = Formatter(); ft.Add(id); DrawTextBasic( @@ -791,12 +850,19 @@ namespace OpenRCT2::Ui::Windows else id = STR_NONE; - Widget sort_date_widget = window_loadsave_widgets[WIDX_SORT_DATE]; + auto& sort_date_widget = widgets[WIDX_SORT_DATE]; ft = Formatter(); ft.Add(id); DrawTextBasic( dpi, windowPos + ScreenCoordsXY{ sort_date_widget.left + 5, sort_date_widget.top + 1 }, STR_DATE, ft, { COLOUR_GREY }); + + // 'Filename:' label + if (_type & LOADSAVETYPE_SAVE) + { + auto& widget = widgets[WIDX_FILENAME_TEXTBOX]; + DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ 5, widget.top + 2 }, STR_FILENAME, ft, { COLOUR_GREY }); + } } void OnMouseUp(WidgetIndex widgetIndex) override @@ -874,6 +940,21 @@ namespace OpenRCT2::Ui::Windows InitScrollWidgets(); no_list_items = static_cast(_listItems.size()); break; + + case WIDX_FILENAME_TEXTBOX: + WindowStartTextbox(*this, widgetIndex, _currentFilename, sizeof(_currentFilename)); + break; + + case WIDX_SAVE: + { + const u8string path = Path::WithExtension( + Path::Combine(_directory, _currentFilename), RemovePatternWildcard(_extensionPattern)); + + if (File::Exists(path)) + WindowOverwritePromptOpen(_currentFilename, path); + else + Select(path.c_str()); + } } } @@ -921,6 +1002,16 @@ namespace OpenRCT2::Ui::Windows Select(path.c_str()); break; } + + case WIDX_FILENAME_TEXTBOX: + { + std::string tempText = text.data(); + const char* cStr = tempText.c_str(); + if (strcmp(_currentFilename, cStr) == 0) + return; + + String::safeUtf8Copy(_currentFilename, cStr, sizeof(_currentFilename)); + } } } @@ -955,11 +1046,9 @@ namespace OpenRCT2::Ui::Windows if (_listItems[selectedItem].type == FileType::directory) { // The selected item is a folder - int32_t includeNewItem; - no_list_items = 0; selected_list_item = -1; - includeNewItem = (_type & 1) == LOADSAVETYPE_SAVE; + bool includeNewItem = (_type & 1) == LOADSAVETYPE_SAVE; char directory[MAX_PATH]; String::safeUtf8Copy(directory, _listItems[selectedItem].path.c_str(), sizeof(directory)); @@ -972,6 +1061,9 @@ namespace OpenRCT2::Ui::Windows else // FileType::file { // Load or overwrite + String::set(_currentFilename, std::size(_currentFilename), _listItems[selectedItem].name.c_str()); + WidgetInvalidate(*this, WIDX_FILENAME_TEXTBOX); + if ((_type & 0x01) == LOADSAVETYPE_SAVE) WindowOverwritePromptOpen(_listItems[selectedItem].name, _listItems[selectedItem].path); else @@ -1109,6 +1201,25 @@ namespace OpenRCT2::Ui::Windows return w; } + void WindowLoadSaveInputKey(WindowBase* w, uint32_t keycode) + { + if (w->classification != WindowClass::Loadsave) + { + return; + } + + auto loadSaveWindow = static_cast(w); + + if (keycode == SDLK_RETURN || keycode == SDLK_KP_ENTER) + { + loadSaveWindow->OnMouseUp(WIDX_SAVE); + } + else if (keycode == SDLK_ESCAPE) + { + loadSaveWindow->Close(); + } + } + #pragma region Overwrite prompt constexpr int32_t OVERWRITE_WW = 200; @@ -1191,5 +1302,24 @@ namespace OpenRCT2::Ui::Windows WF_TRANSPARENT | WF_STICK_TO_FRONT | WF_CENTRE_SCREEN, name, path); } + void WindowLoadSaveOverwritePromptInputKey(WindowBase* w, uint32_t keycode) + { + if (w->classification != WindowClass::LoadsaveOverwritePrompt) + { + return; + } + + auto promptWindow = static_cast(w); + + if (keycode == SDLK_RETURN || keycode == SDLK_KP_ENTER) + { + promptWindow->OnMouseUp(WIDX_OVERWRITE_OVERWRITE); + } + else if (keycode == SDLK_ESCAPE) + { + promptWindow->OnMouseUp(WIDX_OVERWRITE_CANCEL); + } + } + #pragma endregion } // namespace OpenRCT2::Ui::Windows diff --git a/src/openrct2-ui/windows/Window.h b/src/openrct2-ui/windows/Window.h index 75b39767ca..492324d9fe 100644 --- a/src/openrct2-ui/windows/Window.h +++ b/src/openrct2-ui/windows/Window.h @@ -127,6 +127,9 @@ namespace OpenRCT2::Ui::Windows WindowBase* LoadsaveOpen( int32_t type, std::string_view defaultPath, std::function callback, TrackDesign* trackDesign); + void WindowLoadSaveInputKey(WindowBase* w, uint32_t keycode); + void WindowLoadSaveOverwritePromptInputKey(WindowBase* w, uint32_t keycode); + WindowBase* TrackPlaceOpen(const struct TrackDesignFileRef* tdFileRef); WindowBase* TrackManageOpen(struct TrackDesignFileRef* tdFileRef);