diff --git a/src/gui/ProjectMGUI.cpp b/src/gui/ProjectMGUI.cpp index 415bd7a..11b4fcb 100644 --- a/src/gui/ProjectMGUI.cpp +++ b/src/gui/ProjectMGUI.cpp @@ -130,6 +130,11 @@ void ProjectMGUI::Draw() return; } + if (_userScalingFactor != GetClampedUserScalingFactor()) + { + UpdateFontSize(); + } + ImGui_ImplSDL2_NewFrame(); ImGui_ImplOpenGL3_NewFrame(); ImGui::NewFrame(); @@ -218,9 +223,16 @@ float ProjectMGUI::GetScalingFactor() SDL_GetWindowSize(_renderingWindow, &windowWidth, &windowHeight); SDL_GL_GetDrawableSize(_renderingWindow, &renderWidth, &renderHeight); + _userScalingFactor = GetClampedUserScalingFactor(); + // If the OS has a scaled UI, this will return the inverse factor. E.g. if the display is scaled to 200%, // the renderWidth (in actual pixels) will be twice as much as the "virtual" unscaled window width. - return ((static_cast(windowWidth) / static_cast(renderWidth)) + (static_cast(windowHeight) / static_cast(renderHeight))) * 0.5f; + return ((static_cast(windowWidth) / static_cast(renderWidth)) + (static_cast(windowHeight) / static_cast(renderHeight))) * 0.5f * _userScalingFactor; +} + +float ProjectMGUI::GetClampedUserScalingFactor() +{ + return std::min(3.0f, std::max(0.1f, static_cast(Poco::Util::Application::instance().config().getDouble("window.uiScale", 1.0)))); } void ProjectMGUI::DisplayToastNotificationHandler(const Poco::AutoPtr& notification) diff --git a/src/gui/ProjectMGUI.h b/src/gui/ProjectMGUI.h index 3083dc9..aca9d88 100644 --- a/src/gui/ProjectMGUI.h +++ b/src/gui/ProjectMGUI.h @@ -3,8 +3,8 @@ #include "AboutWindow.h" #include "HelpWindow.h" #include "MainMenu.h" -#include "ToastMessage.h" #include "SettingsWindow.h" +#include "ToastMessage.h" #include "notifications/DisplayToastNotification.h" @@ -107,6 +107,8 @@ class ProjectMGUI : public Poco::Util::Subsystem private: float GetScalingFactor(); + static float GetClampedUserScalingFactor(); + void DisplayToastNotificationHandler(const Poco::AutoPtr& notification); ProjectMWrapper* _projectMWrapper{nullptr}; @@ -122,6 +124,7 @@ class ProjectMGUI : public Poco::Util::Subsystem uint64_t _lastFrameTicks{0}; //!< Tick count of the last frame (see SDL_GetTicks64) + float _userScalingFactor{1.0f}; //!< The user-defined UI scaling factor. float _textScalingFactor{0.0f}; //!< The text scaling factor. MainMenu _mainMenu{*this}; diff --git a/src/gui/SettingsWindow.cpp b/src/gui/SettingsWindow.cpp index aec27a5..fa3844d 100644 --- a/src/gui/SettingsWindow.cpp +++ b/src/gui/SettingsWindow.cpp @@ -24,6 +24,7 @@ SettingsWindow::SettingsWindow(ProjectMGUI& gui) void SettingsWindow::Show() { + _userScale = static_cast(_userConfiguration->getDouble("window.uiScale", 1.0)); _visible = true; } @@ -67,7 +68,8 @@ void SettingsWindow::Draw() auto& selectedDirectory = _pathChooser.SelectedFiles(); if (!selectedDirectory.empty()) { - _userConfiguration->setString(_pathChooser.Context(), Poco::Path(selectedDirectory.at(0).path()).makeDirectory().toString()); + _userConfiguration->setString(_pathChooser.Context(), + Poco::Path(selectedDirectory.at(0).path()).makeDirectory().toString()); _changed = true; } } @@ -81,8 +83,8 @@ void SettingsWindow::DrawProjectMSettingsTab() { ImGui::TableSetupColumn("##desc", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableSetupColumn("##setting", ImGuiTableColumnFlags_WidthStretch, .0f); - ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 50.0f); - ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 50.0f); ImGui::TableSetupColumn("##override", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableNextRow(); @@ -94,7 +96,8 @@ void SettingsWindow::DrawProjectMSettingsTab() PathSetting("projectM.texturePath"); ImGui::TableNextRow(); - LabelWithTooltip("Display projectM Logo Preset", "If enabled, the projectM logo preset is shown on startup."); + LabelWithTooltip("Display projectM Logo Preset", + "If enabled, the projectM logo preset is shown on startup."); BooleanSetting("projectM.enableSplash", false); ImGui::TableNextRow(); @@ -106,39 +109,48 @@ void SettingsWindow::DrawProjectMSettingsTab() BooleanSetting("projectM.shuffleEnabled", true); ImGui::TableNextRow(); - LabelWithTooltip("Skip To Dropped Presets", "If enabled, will skip to the new presets when preset(s) are dropped onto the window and added to the playlist"); + LabelWithTooltip("Skip To Dropped Presets", + "If enabled, will skip to the new presets when preset(s) are dropped onto the window and added to the playlist"); BooleanSetting("projectM.skipToDropped", true); ImGui::TableNextRow(); - LabelWithTooltip("Dropped Folder Overrides Playlist", "When dropping a folder, clear the playlist and add all presets from the folder."); + LabelWithTooltip("Dropped Folder Overrides Playlist", + "When dropping a folder, clear the playlist and add all presets from the folder."); BooleanSetting("projectM.droppedFolderOverride", false); ImGui::TableNextRow(); - LabelWithTooltip("Preset Display Duration", "Time in seconds a preset will be displayed before it's switched."); + LabelWithTooltip("Preset Display Duration", + "Time in seconds a preset will be displayed before it's switched."); DoubleSetting("projectM.displayDuration", 30.0, 1.0, 240.0); ImGui::TableNextRow(); - LabelWithTooltip("Preset Transition Duration", "Time in seconds it takes to transition softly from one preset to another."); + LabelWithTooltip("Preset Transition Duration", + "Time in seconds it takes to transition softly from one preset to another."); DoubleSetting("projectM.transitionDuration", 3.0, .0, 10.0); ImGui::TableNextRow(); - LabelWithTooltip("Enable Hard Cuts", "Enables beat-driven, fast preset changes.\nSensitivity and earliest switch time can be configured separately."); + LabelWithTooltip("Enable Hard Cuts", + "Enables beat-driven, fast preset changes.\nSensitivity and earliest switch time can be configured separately."); BooleanSetting("projectM.hardCutsEnabled", false); ImGui::TableNextRow(); - LabelWithTooltip(" Hard Cut Duration", "Time in seconds before a preset will be switched at\nthe earliest on hard cuts. If larger than display duration,\nhard cuts won't happen at all."); + LabelWithTooltip(" Hard Cut Duration", + "Time in seconds before a preset will be switched at\nthe earliest on hard cuts. If larger than display duration,\nhard cuts won't happen at all."); DoubleSetting("projectM.hardCutDuration", 20.0, 1.0, 240.0); ImGui::TableNextRow(); - LabelWithTooltip(" Hard Cut Threshold", "Volume difference between measurements required to trigger a hard cut.\nHigher values mean fewer hard cuts."); + LabelWithTooltip(" Hard Cut Threshold", + "Volume difference between measurements required to trigger a hard cut.\nHigher values mean fewer hard cuts."); DoubleSetting("projectM.hardCutSensitivity", 1.0, 0.0, 10.0); ImGui::TableNextRow(); - LabelWithTooltip("Aspect Correction", "Enables aspect ration correction in presets.\nOnly affects presets using the aspect ratio variables."); + LabelWithTooltip("Aspect Correction", + "Enables aspect ration correction in presets.\nOnly affects presets using the aspect ratio variables."); BooleanSetting("projectM.aspectCorrectionEnabled", true); ImGui::TableNextRow(); - LabelWithTooltip("Per-Point Mesh Size X/Y", "Size of the per-point transformation grid.\nHigher values produce better quality, but require exponentially more CPU time to calculate.\nMilkdrop's default is 48x32."); + LabelWithTooltip("Per-Point Mesh Size X/Y", + "Size of the per-point transformation grid.\nHigher values produce better quality, but require exponentially more CPU time to calculate.\nMilkdrop's default is 48x32."); IntegerSettingVec("projectM.meshX", "projectM.meshY", 64, 48, 8, 300); ImGui::EndTable(); @@ -149,19 +161,19 @@ void SettingsWindow::DrawProjectMSettingsTab() void SettingsWindow::DrawWindowSettingsTab() { - if (ImGui::BeginTabItem("Window / Rendering")) { if (ImGui::BeginTable("projectM", 5, ImGuiTableFlags_None)) { ImGui::TableSetupColumn("##desc", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableSetupColumn("##setting", ImGuiTableColumnFlags_WidthStretch, .0f); - ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 50.0f); - ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 50.0f); ImGui::TableSetupColumn("##override", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableNextRow(); - LabelWithTooltip("Startup Window Placement", "Initial window placement options when starting projectM.\nClick the button to copy the current window size, position and display to the settings."); + LabelWithTooltip("Startup Window Placement", + "Initial window placement options when starting projectM.\nClick the button to copy the current window size, position and display to the settings."); ImGui::TableSetColumnIndex(1); if (ImGui::Button("Use Current Size and Position")) { @@ -182,19 +194,23 @@ void SettingsWindow::DrawWindowSettingsTab() } ImGui::TableNextRow(); - LabelWithTooltip(" Window Size", "Initial window size when starting projectM.\nThis might or might not include the window decoration, depending on the OS."); + LabelWithTooltip(" Window Size", + "Initial window size when starting projectM.\nThis might or might not include the window decoration, depending on the OS."); IntegerSettingVec("window.width", "window.height", 1920, 1080, 32, 8192); ImGui::TableNextRow(); - LabelWithTooltip(" Window Position", "Initial window position when starting projectM.\nThe coordinates are relative to the current monitor."); + LabelWithTooltip(" Window Position", + "Initial window position when starting projectM.\nThe coordinates are relative to the current monitor."); WindowPositionSetting(); ImGui::TableNextRow(); - LabelWithTooltip(" Monitor", "Use 0 to let the OS select the monitor, or any positive number to select a specific monitor.\nIf the number is larger than the number of connected monitors, the last available one will be used."); + LabelWithTooltip(" Monitor", + "Use 0 to let the OS select the monitor, or any positive number to select a specific monitor.\nIf the number is larger than the number of connected monitors, the last available one will be used."); IntegerSetting("window.monitor", 0, 0, 10); ImGui::TableNextRow(); - LabelWithTooltip("Borderless Window", "Don't display the window border and title bar if the OS supports it.\nCan make the window immovable."); + LabelWithTooltip("Borderless Window", + "Don't display the window border and title bar if the OS supports it.\nCan make the window immovable."); BooleanSetting("window.borderless", false); ImGui::TableNextRow(); @@ -202,33 +218,44 @@ void SettingsWindow::DrawWindowSettingsTab() BooleanSetting("window.fullscreen", false); ImGui::TableNextRow(); - LabelWithTooltip(" Exclusive Fullscreen Mode", "Use exclusive mode if fullscreen, e.g. not as a borderless window.\nThis can improve performance, but may switch the desktop resolution!"); + LabelWithTooltip(" Exclusive Fullscreen Mode", + "Use exclusive mode if fullscreen, e.g. not as a borderless window.\nThis can improve performance, but may switch the desktop resolution!"); BooleanSetting("window.fullscreen.exclusive", false); ImGui::TableNextRow(); - LabelWithTooltip(" Exclusive Mode Resolution", "Resolution to change to in exclusive fullscreen mode.\nNot all graphics driver support arbitrary resolution and will use the next-best supported one."); + LabelWithTooltip(" Exclusive Mode Resolution", + "Resolution to change to in exclusive fullscreen mode.\nNot all graphics driver support arbitrary resolution and will use the next-best supported one."); IntegerSettingVec("fullscreen.width", "fullscreen.height", 1920, 1080, 240, 8192); ImGui::TableNextRow(); - LabelWithTooltip("Target FPS", "Limit frames rendered per second to the given FPS value.\nNOTE: A value of 0 will NOT limit FPS and render at either VSync or unlimited pace, possibly using all CPU/GPU resources."); + LabelWithTooltip("Target FPS", + "Limit frames rendered per second to the given FPS value.\nNOTE: A value of 0 will NOT limit FPS and render at either VSync or unlimited pace, possibly using all CPU/GPU resources."); IntegerSetting("projectM.fps", 60, 0, 300); ImGui::TableNextRow(); - LabelWithTooltip("Wait for Vertical Sync", "Wait for vertical sync interval before displaying the next frame.\nThis will limit max FPS to the vertical sync frequency but prevents tearing."); + LabelWithTooltip("Wait for Vertical Sync", + "Wait for vertical sync interval before displaying the next frame.\nThis will limit max FPS to the vertical sync frequency but prevents tearing."); BooleanSetting("window.waitForVerticalSync", false); ImGui::TableNextRow(); - LabelWithTooltip(" Use Adaptive Sync", "Tries to use adaptive vertical sync if vertical sync is enabled.\nWhen using a monitor capable of adaptive sync, setting FPS to 0 gives the best results."); + LabelWithTooltip(" Use Adaptive Sync", + "Tries to use adaptive vertical sync if vertical sync is enabled.\nWhen using a monitor capable of adaptive sync, setting FPS to 0 gives the best results."); BooleanSetting("window.adaptiveVerticalSync", false); ImGui::TableNextRow(); - LabelWithTooltip("Preset Name in Title", "Controls displaying the current preset name after the application name in the window title."); + LabelWithTooltip("Preset Name in Title", + "Controls displaying the current preset name after the application name in the window title."); BooleanSetting("window.displayPresetNameInTitle", true); ImGui::TableNextRow(); - LabelWithTooltip("Display Toast Messages", "Controls displaying toast messages/notifications, e.g. when changing the audio device."); + LabelWithTooltip("Display Toast Messages", + "Controls displaying toast messages/notifications, e.g. when changing the audio device."); BooleanSetting("projectM.displayToasts", true); + ImGui::TableNextRow(); + LabelWithTooltip("UI Scaling Factor", "Multiplies the default UI/font size with the given factor."); + DoubleSettingWithApply("window.uiScale", 1.0, 0.1, 3.0, _userScale); + ImGui::EndTable(); } ImGui::EndTabItem(); @@ -274,16 +301,21 @@ void SettingsWindow::DrawHelpTab() const ImGui::Bullet(); ImGui::TextWrapped("%s", "Settings overridden by command-line parameters cannot be changed."); ImGui::Bullet(); - ImGui::TextWrapped("All values changed/set in this window are stored in the configuration file, projectMSDL.properties, in the user configuration directory \"%s\"", - userConfigurationDir.toString().c_str()); + ImGui::TextWrapped( + "All values changed/set in this window are stored in the configuration file, projectMSDL.properties, in the user configuration directory \"%s\"", + userConfigurationDir.toString().c_str()); ImGui::Separator(); ImGui::TextUnformatted("Changing values:"); ImGui::Bullet(); - ImGui::TextWrapped("%s", "Hold down control/command key when clicking on sliders to enter a custom value. The manually entered value can be outside the slider's value range."); + ImGui::TextWrapped( + "%s", + "Hold down control/command key when clicking on sliders to enter a custom value. The manually entered value can be outside the slider's value range."); ImGui::Bullet(); - ImGui::TextWrapped("%s", "Click on \"Reset\" to unset a value and use the default value from the application's factory configuration file."); + ImGui::TextWrapped( + "%s", + "Click on \"Reset\" to unset a value and use the default value from the application's factory configuration file."); ImGui::EndTabItem(); } } @@ -298,16 +330,19 @@ void SettingsWindow::SaveButton() if (!configFile.empty()) { _userConfiguration->save(configFile); - Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification("Settings saved!")); + Poco::NotificationCenter::defaultCenter().postNotification( + new DisplayToastNotification("Settings saved!")); } else { - Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification("Error saving settings")); + Poco::NotificationCenter::defaultCenter().postNotification( + new DisplayToastNotification("Error saving settings")); } } catch (...) { - Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification("Error saving settings")); + Poco::NotificationCenter::defaultCenter().postNotification( + new DisplayToastNotification("Error saving settings")); } _changed = false; @@ -336,7 +371,8 @@ void SettingsWindow::PathSetting(const std::string& property) strncpy(pathBuffer, path.c_str(), std::min(2047, path.size())); ImGui::SetNextItemWidth(-1); - if (ImGui::InputText(std::string("##path_" + property).c_str(), &pathBuffer[0], IM_ARRAYSIZE(pathBuffer), ImGuiInputTextFlags_EnterReturnsTrue)) + if (ImGui::InputText(std::string("##path_" + property).c_str(), &pathBuffer[0], IM_ARRAYSIZE(pathBuffer), + ImGuiInputTextFlags_EnterReturnsTrue)) { _userConfiguration->setString(property, std::string(pathBuffer)); _changed = true; @@ -403,7 +439,8 @@ void SettingsWindow::IntegerSetting(const std::string& property, int defaultValu } } -void SettingsWindow::IntegerSettingVec(const std::string& property1, const std::string& property2, int defaultValue1, int defaultValue2, int min, int max) +void SettingsWindow::IntegerSettingVec(const std::string& property1, const std::string& property2, int defaultValue1, + int defaultValue2, int min, int max) { ImGui::TableSetColumnIndex(1); @@ -432,7 +469,8 @@ void SettingsWindow::DoubleSetting(const std::string& property, double defaultVa auto value = static_cast(_userConfiguration->getDouble(property, defaultValue)); - if (ImGui::SliderFloat(std::string("##double_" + property).c_str(), &value, static_cast(min), static_cast(max))) + if (ImGui::SliderFloat(std::string("##double_" + property).c_str(), &value, static_cast(min), + static_cast(max))) { _userConfiguration->setDouble(property, value); _changed = true; @@ -446,6 +484,35 @@ void SettingsWindow::DoubleSetting(const std::string& property, double defaultVa } } +void SettingsWindow::DoubleSettingWithApply(const std::string& property, double defaultValue, double min, double max, + float& tempValue) +{ + ImGui::TableSetColumnIndex(1); + + ImGui::SliderFloat(std::string("##double_" + property).c_str(), &tempValue, static_cast(min), + static_cast(max)); + + ImGui::SameLine(); + + ImGui::PushID(std::string(property + "_ApplyButton").c_str()); + if (ImGui::Button("Apply")) + { + _userConfiguration->setDouble(property, tempValue); + _changed = true; + } + ImGui::PopID(); + + if (ResetButton(property)) + { + tempValue = static_cast(_userConfiguration->getDouble(property, defaultValue)); + } + + if (_commandLineConfiguration->has(property)) + { + OverriddenSettingMarker(); + } +} + void SettingsWindow::WindowPositionSetting() { ImGui::TableSetColumnIndex(1); @@ -526,15 +593,17 @@ void SettingsWindow::AudioDeviceSetting() } } -void SettingsWindow::ResetButton(const std::string& property1, const std::string& property2) +bool SettingsWindow::ResetButton(const std::string& property1, const std::string& property2) { if (!_userConfiguration->has(property1) && (property2.empty() || !_userConfiguration->has(property2))) { - return; + return false; } ImGui::TableSetColumnIndex(3); + bool pressed{false}; + ImGui::PushID(std::string(property1 + property2 + "_ResetButton").c_str()); if (ImGui::Button("Reset")) { @@ -544,8 +613,11 @@ void SettingsWindow::ResetButton(const std::string& property1, const std::string _userConfiguration->remove(property2); } _changed = true; + pressed = true; } ImGui::PopID(); + + return pressed; } void SettingsWindow::OverriddenSettingMarker() diff --git a/src/gui/SettingsWindow.h b/src/gui/SettingsWindow.h index be56d69..17f247b 100644 --- a/src/gui/SettingsWindow.h +++ b/src/gui/SettingsWindow.h @@ -104,6 +104,18 @@ class SettingsWindow */ void DoubleSetting(const std::string& property, double defaultValue, double min, double max); + /** + * @brief Displays a slider to select a double min/max value and an "Apply" button to set the value. + * Useful if the slider affects UI rendering (e.g. scaling). + * @param property The property name in the config. + * @param defaultValue Default value for the property if not set. + * @param min Minimum slider value. + * @param max Maximum slider value. + * @param tempValue The storage location for the displayed slider value. + */ + void DoubleSettingWithApply(const std::string& property, double defaultValue, double min, double max, + float& tempValue); + /** * @brief Displays a checkbox to override the window startup position, and if this is selected, displays two sliders. */ @@ -118,8 +130,9 @@ class SettingsWindow * @brief Displays a reset button and removes the property from the UI map if clicked. * @param property1 First property to reset. * @param property2 Optional second property to reset. + * @return true if the button was pressed, false otherwise. */ - void ResetButton(const std::string& property1, const std::string& property2 = ""); + bool ResetButton(const std::string& property1, const std::string& property2 = ""); /** * @brief Displays a red note and a tooltip when hovered explaining the setting can't be changed now. @@ -136,4 +149,6 @@ class SettingsWindow Poco::AutoPtr _commandLineConfiguration; FileChooser _pathChooser{FileChooser::Mode::Directory}; //!< The file chooser dialog to select preset and texture paths. + + float _userScale{1.0f}; //!< Temporary value for UI scale }; diff --git a/src/resources/projectMSDL.properties.in b/src/resources/projectMSDL.properties.in index b6a1dce..8dbc72b 100644 --- a/src/resources/projectMSDL.properties.in +++ b/src/resources/projectMSDL.properties.in @@ -48,6 +48,10 @@ window.adaptiveVerticalSync = true # If false, the window title is fixed to "projectM". window.displayPresetNameInTitle = true +# User-defined scaling factor for UI/font size. Values will be clamped to a range from 0.1 up to 3.0 (10% and 300%, respectively). +# Useful if the automatically determined UI size is either to small or too large. +window.uiScale = 1.0 + ### projectM settings # Default path where projectMSDL will search for presets and textures. The directory will be searched recursively.