diff --git a/src/openrct2-ui/windows/Changelog.cpp b/src/openrct2-ui/windows/Changelog.cpp index 7e5cbeb216..a600a26b06 100644 --- a/src/openrct2-ui/windows/Changelog.cpp +++ b/src/openrct2-ui/windows/Changelog.cpp @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2014-2020 OpenRCT2 developers + * Copyright (c) 2014-2021 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 @@ -41,7 +41,7 @@ static constexpr const rct_string_id WINDOW_TITLE = STR_CHANGELOG_TITLE; constexpr int32_t MIN_WW = 300; constexpr int32_t MIN_WH = 250; -static rct_widget window_changelog_widgets[] = { +static rct_widget _windowChangelogWidgets[] = { WINDOW_SHIM(WINDOW_TITLE, WW, WH), MakeWidget({0, 14}, {500, 382}, WindowWidgetType::Resize, WindowColour::Secondary ), // content panel MakeWidget({3, 16}, {495, 366}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_BOTH ), // scroll area @@ -49,265 +49,285 @@ static rct_widget window_changelog_widgets[] = { { WIDGETS_END }, }; -static void window_changelog_close(rct_window *w); -static void window_changelog_mouseup(rct_window *w, rct_widgetindex widgetIndex); -static void window_changelog_resize(rct_window *w); -static void window_changelog_scrollgetsize(rct_window *w, int32_t scrollIndex, int32_t *width, int32_t *height); -static void window_changelog_invalidate(rct_window *w); -static void window_changelog_paint(rct_window *w, rct_drawpixelinfo *dpi); -static void window_changelog_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int32_t scrollIndex); - -static rct_window_event_list window_changelog_events([](auto& events) -{ - events.close = &window_changelog_close; - events.mouse_up = &window_changelog_mouseup; - events.resize = &window_changelog_resize; - events.get_scroll_size = &window_changelog_scrollgetsize; - events.invalidate = &window_changelog_invalidate; - events.paint = &window_changelog_paint; - events.scroll_paint = &window_changelog_scrollpaint; -}); // clang-format on -static void window_new_version_process_info(); -static void window_changelog_dispose_data(); -static bool window_changelog_read_file(); +class ChangelogWindow final : public Window +{ + const NewVersionInfo* _newVersionInfo; + std::vector _changelogLines; + int32_t _changelogLongestLineWidth = 0; + int _personality = 0; -static const NewVersionInfo* _newVersionInfo; -static std::vector _changelogLines; -static int32_t _changelogLongestLineWidth = 0; -static int _persnality = 0; +public: + /** + * @brief Retrieves the changelog contents. + */ + const std::string GetChangelogText() + { + auto path = GetChangelogPath(); +#if defined(_WIN32) && !defined(__MINGW32__) + auto pathW = String::ToWideChar(path); + auto fs = std::ifstream(pathW, std::ios::in); +#else + auto fs = std::ifstream(path, std::ios::in); +#endif + if (!fs.is_open()) + { + throw std::runtime_error("Unable to open " + path); + } + return std::string((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); + } + + /** + * @brief Set the Changelog Window's Personality, should be called just after creation. Returns true on success + * + * @param personality + */ + bool SetPersonality(int personality) + { + switch (personality) + { + case WV_NEW_VERSION_INFO: + if (!GetContext()->HasNewVersionInfo()) + { + return false; + } + _personality = WV_NEW_VERSION_INFO; + NewVersionProcessInfo(); + enabled_widgets = (1 << WIDX_CLOSE) | (1 << WIDX_OPEN_URL); + widgets[WIDX_OPEN_URL].type = WindowWidgetType::Button; + return true; + + case WV_CHANGELOG: + if (!ReadChangelogFile()) + { + return false; + } + enabled_widgets = (1 << WIDX_CLOSE); + _personality = WV_CHANGELOG; + return true; + + default: + log_error("Invalid personality for changelog window: %d", personality); + return false; + } + } + + void OnOpen() override + { + widgets = _windowChangelogWidgets; + + WindowInitScrollWidgets(this); + min_width = MIN_WW; + min_height = MIN_WH; + max_width = MIN_WW; + max_height = MIN_WH; + } + + void OnResize() override + { + int32_t screenWidth = context_get_width(); + int32_t screenHeight = context_get_height(); + + max_width = (screenWidth * 4) / 5; + max_height = (screenHeight * 4) / 5; + + min_width = MIN_WW; + min_height = MIN_WH; + + auto download_button_width = widgets[WIDX_OPEN_URL].width(); + widgets[WIDX_OPEN_URL].left = (width - download_button_width) / 2; + widgets[WIDX_OPEN_URL].right = widgets[WIDX_OPEN_URL].left + download_button_width; + + if (width < min_width) + { + Invalidate(); + width = min_width; + } + if (height < min_height) + { + Invalidate(); + height = min_height; + } + } + + void OnPrepareDraw() override + { + widgets[WIDX_BACKGROUND].right = width - 1; + widgets[WIDX_BACKGROUND].bottom = height - 1; + widgets[WIDX_TITLE].right = width - 2; + widgets[WIDX_CLOSE].left = width - 13; + widgets[WIDX_CLOSE].right = width - 3; + widgets[WIDX_CONTENT_PANEL].right = width - 1; + widgets[WIDX_CONTENT_PANEL].bottom = height - 1; + widgets[WIDX_SCROLL].right = width - 3; + widgets[WIDX_SCROLL].bottom = height - 22; + widgets[WIDX_OPEN_URL].bottom = height - 5; + widgets[WIDX_OPEN_URL].top = height - 19; + } + + void OnMouseUp(rct_widgetindex widgetIndex) override + { + switch (widgetIndex) + { + case WIDX_CLOSE: + Close(); + break; + case WIDX_OPEN_URL: + if (_newVersionInfo) + { + GetContext()->GetUiContext()->OpenURL(_newVersionInfo->url); + } + else + { + log_error("Cannot open URL: NewVersionInfo for ChangelogWindow is undefined!"); + } + break; + } + } + + void OnScrollDraw(int32_t scrollIndex, rct_drawpixelinfo& dpi) override + { + const int32_t lineHeight = font_get_line_height(FontSpriteBase::MEDIUM); + + ScreenCoordsXY screenCoords(3, 3 - lineHeight); + for (const auto& line : _changelogLines) + { + screenCoords.y += lineHeight; + if (screenCoords.y + lineHeight < dpi.y || screenCoords.y >= dpi.y + dpi.height) + continue; + + gfx_draw_string(&dpi, screenCoords, line.c_str(), { colours[0] }); + } + } + + ScreenSize OnScrollGetSize(int32_t scrollIndex) override + { + return ScreenSize( + _changelogLongestLineWidth + 4, + static_cast(_changelogLines.size()) * font_get_line_height(FontSpriteBase::MEDIUM)); + } + + // TODO: This probably should be a utility function defined elsewhere for reusability + /** + * @brief Reimplementation of Window's GetCentrePositionForNewWindow for ChangelogWindow. + * + * @return ScreenCoordsXY + */ + static ScreenCoordsXY GetCentrePositionForNewWindow(int32_t width, int32_t height) + { + auto uiContext = GetContext()->GetUiContext(); + auto screenWidth = uiContext->GetWidth(); + auto screenHeight = uiContext->GetHeight(); + return ScreenCoordsXY{ (screenWidth - width) / 2, std::max(TOP_TOOLBAR_HEIGHT + 1, (screenHeight - height) / 2) }; + } + +private: + /** + * @brief Converts NewVersionInfo into changelog lines + * + */ + void NewVersionProcessInfo() + { + _newVersionInfo = GetContext()->GetNewVersionInfo(); + if (_newVersionInfo != nullptr) + { + char version_info[256]; + + const char* version_info_ptr = _newVersionInfo->name.c_str(); + format_string(version_info, 256, STR_NEW_RELEASE_VERSION_INFO, &version_info_ptr); + + _changelogLines.emplace_back(version_info); + _changelogLines.emplace_back(""); + + ProcessChangelogText(_newVersionInfo->changelog); + } + else + { + log_error("ChangelogWindow: Could not process NewVersionInfo, result was undefined"); + } + } + + /** + * @brief Get the absolute path for the changelog file + * + * @return std::string + */ + std::string GetChangelogPath() + { + auto env = GetContext()->GetPlatformEnvironment(); + return env->GetFilePath(PATHID::CHANGELOG); + } + + /** + * @brief Attempts to read the changelog file, returns true on success + * + */ + bool ReadChangelogFile() + { + std::string _changelogText; + try + { + _changelogText = GetChangelogText(); + } + catch (const std::bad_alloc&) + { + log_error("Unable to allocate memory for changelog.txt"); + return false; + } + catch (const std::exception&) + { + log_error("Unable to read changelog.txt"); + return false; + } + + ProcessChangelogText(_changelogText); + return true; + } + + /** + * @brief Ingests a string of text and splits it into lines for the changelog and updates the longest line width for + * scrolling purposes + * + * @param text + */ + void ProcessChangelogText(const std::string& text) + { + std::string::size_type pos = 0; + std::string::size_type prev = 0; + while ((pos = text.find("\n", prev)) != std::string::npos) + { + _changelogLines.push_back(text.substr(prev, pos - prev)); + prev = pos + 1; + } + + // To get the last substring (or only, if delimiter is not found) + _changelogLines.push_back(text.substr(prev)); + + _changelogLongestLineWidth = 0; + for (const auto& line : _changelogLines) + { + int32_t linewidth = gfx_get_string_width(line.c_str(), FontSpriteBase::MEDIUM); + _changelogLongestLineWidth = std::max(linewidth, _changelogLongestLineWidth); + } + } +}; rct_window* window_changelog_open(int personality) { - rct_window* window; - - window = window_bring_to_front_by_class(WC_CHANGELOG); - if (window != nullptr) + auto* window = window_bring_to_front_by_class(WC_CHANGELOG); + if (window == nullptr) { - return window; + // Create a new centred window + int32_t screenWidth = context_get_width(); + int32_t screenHeight = context_get_height(); + int32_t width = (screenWidth * 4) / 5; + int32_t height = (screenHeight * 4) / 5; + + auto pos = ChangelogWindow::GetCentrePositionForNewWindow(width, height); + auto* newWindow = WindowCreate(WC_CHANGELOG, pos, width, height, WF_RESIZABLE); + newWindow->SetPersonality(personality); + return newWindow; } - - uint64_t enabled_widgets{}; - - window_changelog_widgets[WIDX_OPEN_URL].type = WindowWidgetType::Placeholder; - switch (personality) - { - case WV_NEW_VERSION_INFO: - if (!GetContext()->HasNewVersionInfo()) - { - return nullptr; - } - - _persnality = WV_NEW_VERSION_INFO; - window_new_version_process_info(); - enabled_widgets = (1 << WIDX_CLOSE) | (1 << WIDX_OPEN_URL); - window_changelog_widgets[WIDX_OPEN_URL].type = WindowWidgetType::Button; - break; - - case WV_CHANGELOG: - if (!window_changelog_read_file()) - { - return nullptr; - } - - _persnality = WV_CHANGELOG; - enabled_widgets = (1 << WIDX_CLOSE); - break; - - default: - log_error("Invalid personality for changelog window: %d", personality); - return nullptr; - } - - int32_t screenWidth = context_get_width(); - int32_t screenHeight = context_get_height(); - - window = WindowCreateCentred( - screenWidth * 4 / 5, screenHeight * 4 / 5, &window_changelog_events, WC_CHANGELOG, WF_RESIZABLE); - window->widgets = window_changelog_widgets; - window->enabled_widgets = enabled_widgets; - - WindowInitScrollWidgets(window); - window->min_width = MIN_WW; - window->min_height = MIN_WH; - window->max_width = MIN_WW; - window->max_height = MIN_WH; - return window; } - -static void window_changelog_close([[maybe_unused]] rct_window* w) -{ - window_changelog_dispose_data(); -} - -static void window_changelog_mouseup(rct_window* w, rct_widgetindex widgetIndex) -{ - switch (widgetIndex) - { - case WIDX_CLOSE: - window_close(w); - break; - case WIDX_OPEN_URL: - GetContext()->GetUiContext()->OpenURL(_newVersionInfo->url); - break; - } -} - -static void window_changelog_resize(rct_window* w) -{ - int32_t screenWidth = context_get_width(); - int32_t screenHeight = context_get_height(); - - w->max_width = (screenWidth * 4) / 5; - w->max_height = (screenHeight * 4) / 5; - - w->min_width = MIN_WW; - w->min_height = MIN_WH; - - auto download_button_width = window_changelog_widgets[WIDX_OPEN_URL].width(); - window_changelog_widgets[WIDX_OPEN_URL].left = (w->width - download_button_width) / 2; - window_changelog_widgets[WIDX_OPEN_URL].right = window_changelog_widgets[WIDX_OPEN_URL].left + download_button_width; - - if (w->width < w->min_width) - { - w->Invalidate(); - w->width = w->min_width; - } - if (w->height < w->min_height) - { - w->Invalidate(); - w->height = w->min_height; - } -} - -static void window_changelog_scrollgetsize( - [[maybe_unused]] rct_window* w, [[maybe_unused]] int32_t scrollIndex, int32_t* width, int32_t* height) -{ - *width = _changelogLongestLineWidth + 4; - - const int32_t lineHeight = font_get_line_height(FontSpriteBase::MEDIUM); - *height = static_cast(_changelogLines.size() * lineHeight); -} - -static void window_changelog_invalidate(rct_window* w) -{ - window_changelog_widgets[WIDX_BACKGROUND].right = w->width - 1; - window_changelog_widgets[WIDX_BACKGROUND].bottom = w->height - 1; - window_changelog_widgets[WIDX_TITLE].right = w->width - 2; - window_changelog_widgets[WIDX_CLOSE].left = w->width - 13; - window_changelog_widgets[WIDX_CLOSE].right = w->width - 3; - window_changelog_widgets[WIDX_CONTENT_PANEL].right = w->width - 1; - window_changelog_widgets[WIDX_CONTENT_PANEL].bottom = w->height - 1; - window_changelog_widgets[WIDX_SCROLL].right = w->width - 3; - window_changelog_widgets[WIDX_SCROLL].bottom = w->height - 22; - window_changelog_widgets[WIDX_OPEN_URL].bottom = w->height - 5; - window_changelog_widgets[WIDX_OPEN_URL].top = w->height - 19; -} - -static void window_changelog_paint(rct_window* w, rct_drawpixelinfo* dpi) -{ - WindowDrawWidgets(w, dpi); -} - -static void window_changelog_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, [[maybe_unused]] int32_t scrollIndex) -{ - const int32_t lineHeight = font_get_line_height(FontSpriteBase::MEDIUM); - - ScreenCoordsXY screenCoords(3, 3 - lineHeight); - for (const auto& line : _changelogLines) - { - screenCoords.y += lineHeight; - if (screenCoords.y + lineHeight < dpi->y || screenCoords.y >= dpi->y + dpi->height) - continue; - - gfx_draw_string(dpi, screenCoords, line.c_str(), { w->colours[0] }); - } -} - -static void window_changelog_process_changelog_text(const std::string& text) -{ - std::string::size_type pos = 0; - std::string::size_type prev = 0; - while ((pos = text.find("\n", prev)) != std::string::npos) - { - _changelogLines.push_back(text.substr(prev, pos - prev)); - prev = pos + 1; - } - - // To get the last substring (or only, if delimiter is not found) - _changelogLines.push_back(text.substr(prev)); - - _changelogLongestLineWidth = 0; - for (const auto& line : _changelogLines) - { - auto width = gfx_get_string_width(line.c_str(), FontSpriteBase::MEDIUM); - _changelogLongestLineWidth = std::max(width, _changelogLongestLineWidth); - } -} - -static void window_new_version_process_info() -{ - _newVersionInfo = GetContext()->GetNewVersionInfo(); - - char version_info[256]; - - const char* version_info_ptr = _newVersionInfo->name.c_str(); - format_string(version_info, 256, STR_NEW_RELEASE_VERSION_INFO, &version_info_ptr); - - _changelogLines.emplace_back(version_info); - _changelogLines.emplace_back(""); - - window_changelog_process_changelog_text(_newVersionInfo->changelog); -} - -static void window_changelog_dispose_data() -{ - _changelogLines.clear(); - _changelogLines.shrink_to_fit(); -} - -static std::string GetChangelogPath() -{ - auto env = GetContext()->GetPlatformEnvironment(); - return env->GetFilePath(PATHID::CHANGELOG); -} - -static std::string GetChangelogText() -{ - auto path = GetChangelogPath(); -#if defined(_WIN32) && !defined(__MINGW32__) - auto pathW = String::ToWideChar(path); - auto fs = std::ifstream(pathW, std::ios::in); -#else - auto fs = std::ifstream(path, std::ios::in); -#endif - if (!fs.is_open()) - { - throw std::runtime_error("Unable to open " + path); - } - return std::string((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); -} - -static bool window_changelog_read_file() -{ - std::string _changelogText; - try - { - _changelogText = GetChangelogText(); - } - catch (const std::bad_alloc&) - { - log_error("Unable to allocate memory for changelog.txt"); - return false; - } - catch (const std::exception&) - { - log_error("Unable to read changelog.txt"); - return false; - } - - window_changelog_process_changelog_text(_changelogText); - return true; -}