diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/Button.cpp | 71 | ||||
-rw-r--r-- | src/gui/ComboBox.cpp | 34 | ||||
-rw-r--r-- | src/gui/CustomRendererWidget.cpp | 14 | ||||
-rw-r--r-- | src/gui/DropdownButton.cpp | 48 | ||||
-rw-r--r-- | src/gui/FileChooser.cpp | 3 | ||||
-rw-r--r-- | src/gui/GlobalSettingsPage.cpp | 632 | ||||
-rw-r--r-- | src/gui/GsrPage.cpp | 31 | ||||
-rw-r--r-- | src/gui/Image.cpp | 39 | ||||
-rw-r--r-- | src/gui/List.cpp | 30 | ||||
-rw-r--r-- | src/gui/Page.cpp | 15 | ||||
-rw-r--r-- | src/gui/RadioButton.cpp | 31 | ||||
-rw-r--r-- | src/gui/ScreenshotSettingsPage.cpp | 332 | ||||
-rw-r--r-- | src/gui/ScrollablePage.cpp | 18 | ||||
-rw-r--r-- | src/gui/SettingsPage.cpp | 650 | ||||
-rw-r--r-- | src/gui/StaticPage.cpp | 17 | ||||
-rw-r--r-- | src/gui/Subsection.cpp | 17 | ||||
-rw-r--r-- | src/gui/Utils.cpp | 7 | ||||
-rw-r--r-- | src/gui/Widget.cpp | 16 |
18 files changed, 1654 insertions, 351 deletions
diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index 54d1854..6e343c4 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -15,8 +15,8 @@ namespace gsr { // These are relative to the button size static const float padding_top_icon_scale = 0.25f; static const float padding_bottom_icon_scale = 0.25f; - static const float padding_left_icon_scale = 0.25f; - static const float padding_right_icon_scale = 0.25f; + //static const float padding_left_icon_scale = 0.25f; + static const float padding_right_icon_scale = 0.15f; Button::Button(mgl::Font *font, const char *text, mgl::vec2f size, mgl::Color bg_color) : size(size), bg_color(bg_color), bg_hover_color(bg_color), text(text, *font) @@ -53,13 +53,21 @@ namespace gsr { background.set_color(mouse_inside ? bg_hover_color : bg_color); window.draw(background); - text.set_position((draw_pos + item_size * 0.5f - text.get_bounds().size * 0.5f).floor()); - window.draw(text); - if(sprite.get_texture() && sprite.get_texture()->is_valid()) { scale_sprite_to_button_size(); - sprite.set_position((background.get_position() + background.get_size() * 0.5f - sprite.get_size() * 0.5f).floor()); + const int padding_left = padding_left_scale * get_theme().window_height; + if(text.get_string().empty()) // Center + sprite.set_position((background.get_position() + background.get_size() * 0.5f - sprite.get_size() * 0.5f).floor()); + else // Left + sprite.set_position((draw_pos + mgl::vec2f(padding_left, background.get_size().y * 0.5f - sprite.get_size().y * 0.5f)).floor()); window.draw(sprite); + + const int padding_icon_right = padding_right_icon_scale * get_button_height(); + text.set_position((sprite.get_position() + mgl::vec2f(sprite.get_size().x + padding_icon_right, sprite.get_size().y * 0.5f - text.get_bounds().size.y * 0.52f)).floor()); + window.draw(text); + } else { + text.set_position((draw_pos + item_size * 0.5f - text.get_bounds().size * 0.5f).floor()); + window.draw(text); } if(mouse_inside) { @@ -72,24 +80,35 @@ namespace gsr { if(!visible) return {0.0f, 0.0f}; - const int padding_top = padding_top_scale * get_theme().window_height; - const int padding_bottom = padding_bottom_scale * get_theme().window_height; const int padding_left = padding_left_scale * get_theme().window_height; const int padding_right = padding_right_scale * get_theme().window_height; const mgl::vec2f text_bounds = text.get_bounds().size; - mgl::vec2f s = size; - if(s.x < 0.0001f) - s.x = padding_left + text_bounds.x + padding_right; - if(s.y < 0.0001f) - s.y = padding_top + text_bounds.y + padding_bottom; - return s; + mgl::vec2f widget_size = size; + + if(widget_size.y < 0.0001f) + widget_size.y = get_button_height(); + + if(widget_size.x < 0.0001f) { + widget_size.x = padding_left + text_bounds.x + padding_right; + if(sprite.get_texture() && sprite.get_texture()->is_valid()) { + scale_sprite_to_button_size(); + const int padding_icon_right = text_bounds.x > 0.001f ? padding_right_icon_scale * widget_size.y : 0.0f; + widget_size.x += sprite.get_size().x + padding_icon_right; + } + } + + return widget_size; } void Button::set_border_scale(float scale) { border_scale = scale; } + void Button::set_icon_padding_scale(float scale) { + icon_padding_scale = scale; + } + void Button::set_bg_hover_color(mgl::Color color) { bg_hover_color = color; } @@ -110,13 +129,23 @@ namespace gsr { if(!sprite.get_texture() || !sprite.get_texture()->is_valid()) return; - const mgl::vec2f button_size = get_size(); - const int padding_icon_top = padding_top_icon_scale * button_size.y; - const int padding_icon_bottom = padding_bottom_icon_scale * button_size.y; - const int padding_icon_left = padding_left_icon_scale * button_size.y; - const int padding_icon_right = padding_right_icon_scale * button_size.y; + const float widget_height = get_button_height(); + + const int padding_icon_top = padding_top_icon_scale * icon_padding_scale * widget_height; + const int padding_icon_bottom = padding_bottom_icon_scale * icon_padding_scale * widget_height; + + const float desired_height = widget_height - (padding_icon_top + padding_icon_bottom); + sprite.set_height((int)desired_height); + } + + float Button::get_button_height() { + const int padding_top = padding_top_scale * get_theme().window_height; + const int padding_bottom = padding_bottom_scale * get_theme().window_height; + + float widget_height = size.y; + if(widget_height < 0.0001f) + widget_height = padding_top + text.get_bounds().size.y + padding_bottom; - const mgl::vec2f desired_size = button_size - mgl::vec2f(padding_icon_left + padding_icon_right, padding_icon_top + padding_icon_bottom); - sprite.set_size(scale_keep_aspect_ratio(sprite.get_texture()->get_size().to_vec2f(), desired_size).floor()); + return widget_height; } }
\ No newline at end of file diff --git a/src/gui/ComboBox.cpp b/src/gui/ComboBox.cpp index 62b2086..4287a53 100644 --- a/src/gui/ComboBox.cpp +++ b/src/gui/ComboBox.cpp @@ -26,16 +26,21 @@ namespace gsr { return true; if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { + const int padding_top = padding_top_scale * get_theme().window_height; + const int padding_bottom = padding_bottom_scale * get_theme().window_height; + const mgl::vec2f mouse_pos = { (float)event.mouse_button.x, (float)event.mouse_button.y }; - const mgl::vec2f item_size = get_size(); + mgl::vec2f item_size = get_size(); if(show_dropdown) { for(size_t i = 0; i < items.size(); ++i) { Item &item = items[i]; + item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom; if(mgl::FloatRect(item.position, item_size).contains(mouse_pos)) { const size_t prev_selected_item = selected_item; selected_item = i; show_dropdown = false; + dirty = true; remove_widget_as_selected_in_parent(); if(selected_item != prev_selected_item && on_selection_changed) @@ -47,6 +52,7 @@ namespace gsr { } const mgl::vec2f draw_pos = position + offset; + item_size = get_size(); if(mgl::FloatRect(draw_pos, item_size).contains(mouse_pos)) { show_dropdown = !show_dropdown; if(show_dropdown) @@ -66,9 +72,10 @@ namespace gsr { if(!visible) return; + //const mgl::Scissor scissor = window.get_scissor(); update_if_dirty(); - const mgl::vec2f draw_pos = (position + offset).floor(); + //max_size.x = std::min((scissor.position.x + scissor.size.x) - draw_pos.x, max_size.x); if(show_dropdown) draw_selected(window, draw_pos); @@ -78,6 +85,8 @@ namespace gsr { void ComboBox::add_item(const std::string &text, const std::string &id) { items.push_back({mgl::Text(text, *font), id, {0.0f, 0.0f}}); + items.back().text.set_max_width(font->get_character_size() * 20); // TODO: Make a proper solution + //items.back().text.set_max_rows(1); dirty = true; } @@ -87,6 +96,7 @@ namespace gsr { if(item.id == id) { const size_t prev_selected_item = selected_item; selected_item = i; + dirty = true; if(trigger_event && (trigger_event_even_if_selection_not_changed || selected_item != prev_selected_item) && on_selection_changed) on_selection_changed(item.text.get_string(), item.id); @@ -107,13 +117,13 @@ namespace gsr { void ComboBox::draw_selected(mgl::Window &window, mgl::vec2f draw_pos) { const int padding_top = padding_top_scale * get_theme().window_height; + const int padding_bottom = padding_bottom_scale * get_theme().window_height; const int padding_left = padding_left_scale * get_theme().window_height; - mgl_scissor scissor; - mgl_window_get_scissor(window.internal_window(), &scissor); + const mgl::Scissor scissor = window.get_scissor(); const bool bottom_is_outside_scissor = draw_pos.y + max_size.y > scissor.position.y + scissor.size.y; - const mgl::vec2f item_size = get_size(); + mgl::vec2f item_size = get_size(); mgl::vec2f items_draw_pos = draw_pos + mgl::vec2f(0.0f, item_size.y); mgl::Rectangle background(draw_pos, item_size.floor()); @@ -137,6 +147,9 @@ namespace gsr { const mgl::vec2f mouse_pos = window.get_mouse_position().to_vec2f(); for(size_t i = 0; i < items.size(); ++i) { + Item &item = items[i]; + item_size.y = padding_top + item.text.get_bounds().size.y + padding_bottom; + if(!cursor_inside) { cursor_inside = mgl::FloatRect(items_draw_pos, item_size).contains(mouse_pos); if(cursor_inside) { @@ -146,7 +159,6 @@ namespace gsr { } } - Item &item = items[i]; item.text.set_position((items_draw_pos + mgl::vec2f(padding_left, padding_top)).floor()); window.draw(item.text); @@ -160,7 +172,7 @@ namespace gsr { const int padding_left = padding_left_scale * get_theme().window_height; const int padding_right = padding_right_scale * get_theme().window_height; - const mgl::vec2f item_size = get_size(); + mgl::vec2f item_size = get_size(); mgl::Rectangle background(draw_pos.floor(), item_size.floor()); background.set_color(mgl::Color(0, 0, 0, 120)); window.draw(background); @@ -197,11 +209,12 @@ namespace gsr { const int padding_left = padding_left_scale * get_theme().window_height; const int padding_right = padding_right_scale * get_theme().window_height; - max_size = { 0.0f, font->get_character_size() + (float)padding_top + (float)padding_bottom }; + Item *selected_item_ptr = (selected_item < items.size()) ? &items[selected_item] : nullptr; + max_size = { 0.0f, padding_top + padding_bottom + (selected_item_ptr ? selected_item_ptr->text.get_bounds().size.y : font->get_character_size()) }; for(Item &item : items) { const mgl::vec2f bounds = item.text.get_bounds().size; max_size.x = std::max(max_size.x, bounds.x + padding_left + padding_right); - max_size.y += bounds.y + padding_top + padding_bottom; + max_size.y += padding_top + bounds.y + padding_bottom; } if(max_size.x <= 0.001f) @@ -219,7 +232,8 @@ namespace gsr { const int padding_top = padding_top_scale * get_theme().window_height; const int padding_bottom = padding_bottom_scale * get_theme().window_height; - return { max_size.x, font->get_character_size() + (float)padding_top + (float)padding_bottom }; + Item *selected_item_ptr = (selected_item < items.size()) ? &items[selected_item] : nullptr; + return { max_size.x, padding_top + padding_bottom + (selected_item_ptr ? selected_item_ptr->text.get_bounds().size.y : font->get_character_size()) }; } float ComboBox::get_dropdown_arrow_height() const { diff --git a/src/gui/CustomRendererWidget.cpp b/src/gui/CustomRendererWidget.cpp index 4cb7adf..5b6c809 100644 --- a/src/gui/CustomRendererWidget.cpp +++ b/src/gui/CustomRendererWidget.cpp @@ -17,19 +17,11 @@ namespace gsr { const mgl::vec2f draw_pos = position + offset; - mgl_scissor prev_scissor; - mgl_window_get_scissor(window.internal_window(), &prev_scissor); - - const mgl_scissor new_scissor = { - mgl_vec2i{(int)draw_pos.x, (int)draw_pos.y}, - mgl_vec2i{(int)size.x, (int)size.y} - }; - mgl_window_set_scissor(window.internal_window(), &new_scissor); - + const mgl::Scissor prev_scissor = window.get_scissor(); + window.set_scissor({draw_pos.to_vec2i(), size.to_vec2i()}); if(draw_handler) draw_handler(window, draw_pos, size); - - mgl_window_set_scissor(window.internal_window(), &prev_scissor); + window.set_scissor(prev_scissor); } mgl::vec2f CustomRendererWidget::get_size() { diff --git a/src/gui/DropdownButton.cpp b/src/gui/DropdownButton.cpp index 4a2ae3a..5d1cc38 100644 --- a/src/gui/DropdownButton.cpp +++ b/src/gui/DropdownButton.cpp @@ -20,7 +20,7 @@ namespace gsr { { if(icon_texture && icon_texture->is_valid()) { icon_sprite.set_texture(icon_texture); - icon_sprite.set_height((int)(size.y * 0.5f)); + icon_sprite.set_height((int)(size.y * 0.45f)); } this->description.set_color(mgl::Color(150, 150, 150)); } @@ -110,6 +110,14 @@ namespace gsr { window.draw(rect); } + if(activated) { + description.set_color(get_color_theme().tint_color); + icon_sprite.set_color(get_color_theme().tint_color); + } else { + description.set_color(mgl::Color(150, 150, 150)); + icon_sprite.set_color(mgl::Color(255, 255, 255)); + } + const int text_margin = size.y * 0.085; const auto title_bounds = title.get_bounds(); @@ -148,7 +156,7 @@ namespace gsr { window.draw(separator); } - if(mouse_inside_item == -1) { + if(mouse_inside_item == -1 && item.enabled) { const bool inside = mgl::FloatRect(item_position, item_size).contains({ (float)mouse_pos.x, (float)mouse_pos.y }); if(inside) { draw_rectangle_outline(window, item_position, item_size, get_color_theme().tint_color, border_size); @@ -161,16 +169,18 @@ namespace gsr { mgl::Sprite icon(item.icon_texture); icon.set_height((int)(item_size.y * 0.4f)); icon.set_position((item_position + mgl::vec2f(padding_left, item_size.y * 0.5f - icon.get_size().y * 0.5f)).floor()); + icon.set_color(item.enabled ? mgl::Color(255, 255, 255, 255) : mgl::Color(255, 255, 255, 80)); window.draw(icon); icon_offset = icon.get_size().x + icon_spacing; } item.text.set_position((item_position + mgl::vec2f(padding_left + icon_offset, item_size.y * 0.5f - text_bounds.size.y * 0.5f)).floor()); + item.text.set_color(item.enabled ? mgl::Color(255, 255, 255, 255) : mgl::Color(255, 255, 255, 80)); window.draw(item.text); const auto description_bounds = item.description_text.get_bounds(); item.description_text.set_position((item_position + mgl::vec2f(item_size.x - description_bounds.size.x - padding_right, item_size.y * 0.5f - description_bounds.size.y * 0.5f)).floor()); - item.description_text.set_color(mgl::Color(255, 255, 255, 120)); + item.description_text.set_color(item.enabled ? mgl::Color(255, 255, 255, 120) : mgl::Color(255, 255, 255, 40)); window.draw(item.description_text); item_position.y += item_size.y; @@ -179,6 +189,10 @@ namespace gsr { } void DropdownButton::add_item(const std::string &text, const std::string &id, const std::string &description) { + for(auto &item : items) { + if(item.id == id) + return; + } items.push_back({mgl::Text(text, *title_font), mgl::Text(description, *description_font), nullptr, id}); dirty = true; } @@ -201,6 +215,24 @@ namespace gsr { } } + void DropdownButton::set_item_description(const std::string &id, const std::string &new_description) { + for(auto &item : items) { + if(item.id == id) { + item.description_text.set_string(new_description); + return; + } + } + } + + void DropdownButton::set_item_enabled(const std::string &id, bool enabled) { + for(auto &item : items) { + if(item.id == id) { + item.enabled = enabled; + return; + } + } + } + void DropdownButton::set_description(std::string description_text) { description.set_string(std::move(description_text)); } @@ -210,14 +242,6 @@ namespace gsr { return; this->activated = activated; - - if(activated) { - description.set_color(get_color_theme().tint_color); - icon_sprite.set_color(get_color_theme().tint_color); - } else { - description.set_color(mgl::Color(150, 150, 150)); - icon_sprite.set_color(mgl::Color(255, 255, 255)); - } } void DropdownButton::update_if_dirty() { @@ -242,4 +266,4 @@ namespace gsr { update_if_dirty(); return size; } -}
\ No newline at end of file +} diff --git a/src/gui/FileChooser.cpp b/src/gui/FileChooser.cpp index 2612365..ceb8c94 100644 --- a/src/gui/FileChooser.cpp +++ b/src/gui/FileChooser.cpp @@ -65,8 +65,7 @@ namespace gsr { if(!visible) return; - mgl_scissor scissor; - mgl_window_get_scissor(window.internal_window(), &scissor); + const mgl::Scissor scissor = window.get_scissor(); const mgl::vec2f draw_pos = position + offset; const mgl::vec2f mouse_pos = window.get_mouse_position().to_vec2f(); diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp index ca4c4ea..6650c69 100644 --- a/src/gui/GlobalSettingsPage.cpp +++ b/src/gui/GlobalSettingsPage.cpp @@ -1,5 +1,6 @@ #include "../../include/gui/GlobalSettingsPage.hpp" +#include "../../include/Overlay.hpp" #include "../../include/Theme.hpp" #include "../../include/Process.hpp" #include "../../include/gui/GsrPage.hpp" @@ -8,26 +9,78 @@ #include "../../include/gui/Subsection.hpp" #include "../../include/gui/List.hpp" #include "../../include/gui/Label.hpp" +#include "../../include/gui/Image.hpp" #include "../../include/gui/RadioButton.hpp" +#include "../../include/gui/LineSeparator.hpp" +#include "../../include/gui/CustomRendererWidget.hpp" + +#include <assert.h> +#include <X11/Xlib.h> +extern "C" { +#include <mgl/mgl.h> +} +#include <mglpp/window/Window.hpp> +#include <mglpp/graphics/Rectangle.hpp> +#include <mglpp/graphics/Text.hpp> + +#ifndef GSR_UI_VERSION +#define GSR_UI_VERSION "Unknown" +#endif + +#ifndef GSR_FLATPAK_VERSION +#define GSR_FLATPAK_VERSION "Unknown" +#endif namespace gsr { static const char* gpu_vendor_to_color_name(GpuVendor vendor) { switch(vendor) { - case GpuVendor::UNKNOWN: return "amd"; - case GpuVendor::AMD: return "amd"; - case GpuVendor::INTEL: return "intel"; - case GpuVendor::NVIDIA: return "nvidia"; + case GpuVendor::UNKNOWN: return "amd"; + case GpuVendor::AMD: return "amd"; + case GpuVendor::INTEL: return "intel"; + case GpuVendor::NVIDIA: return "nvidia"; + case GpuVendor::BROADCOM: return "broadcom"; } return "amd"; } - GlobalSettingsPage::GlobalSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : + static const char* gpu_vendor_to_string(GpuVendor vendor) { + switch(vendor) { + case GpuVendor::UNKNOWN: return "Unknown"; + case GpuVendor::AMD: return "AMD"; + case GpuVendor::INTEL: return "Intel"; + case GpuVendor::NVIDIA: return "NVIDIA"; + case GpuVendor::BROADCOM: return "Broadcom"; + } + return "unknown"; + } + + static uint32_t mgl_modifier_to_hotkey_modifier(mgl::Keyboard::Key modifier_key) { + switch(modifier_key) { + case mgl::Keyboard::LControl: return HOTKEY_MOD_LCTRL; + case mgl::Keyboard::LShift: return HOTKEY_MOD_LSHIFT; + case mgl::Keyboard::LAlt: return HOTKEY_MOD_LALT; + case mgl::Keyboard::LSystem: return HOTKEY_MOD_LSUPER; + case mgl::Keyboard::RControl: return HOTKEY_MOD_RCTRL; + case mgl::Keyboard::RShift: return HOTKEY_MOD_RSHIFT; + case mgl::Keyboard::RAlt: return HOTKEY_MOD_RALT; + case mgl::Keyboard::RSystem: return HOTKEY_MOD_RSUPER; + default: return 0; + } + return 0; + } + + static bool key_is_alpha_numerical(mgl::Keyboard::Key key) { + return key >= mgl::Keyboard::A && key <= mgl::Keyboard::Num9; + } + + GlobalSettingsPage::GlobalSettingsPage(Overlay *overlay, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), + overlay(overlay), config(config), gsr_info(gsr_info), page_stack(page_stack) { - auto content_page = std::make_unique<GsrPage>(); + auto content_page = std::make_unique<GsrPage>("Global", "Settings"); content_page->add_button("Back", "back", get_color_theme().page_bg_color); content_page->on_click = [page_stack](const std::string &id) { if(id == "back") @@ -38,16 +91,64 @@ namespace gsr { add_widgets(); load(); + + auto hotkey_overlay = std::make_unique<CustomRendererWidget>(get_size()); + hotkey_overlay->draw_handler = [this](mgl::Window &window, mgl::vec2f, mgl::vec2f) { + Button *configure_hotkey_button = configure_hotkey_get_button_by_active_type(); + if(!configure_hotkey_button) + return; + + mgl::Text title_text("Press a key combination to use for the hotkey \"" + hotkey_configure_action_name + "\":", get_theme().title_font); + mgl::Text hotkey_text(configure_hotkey_button->get_text(), get_theme().top_bar_font); + mgl::Text description_text("Alpha-numerical keys can't be used alone in hotkeys, they have to be used one or more of these keys: Alt, Ctrl, Shift and Super.\nPress Esc to cancel or Backspace to remove the hotkey.", get_theme().body_font); + const float text_max_width = std::max(title_text.get_bounds().size.x, std::max(hotkey_text.get_bounds().size.x, description_text.get_bounds().size.x)); + + const float padding_horizontal = int(get_theme().window_height * 0.01f); + const float padding_vertical = int(get_theme().window_height * 0.01f); + + const mgl::vec2f bg_size = mgl::vec2f(text_max_width + padding_horizontal*2.0f, get_theme().window_height * 0.13f).floor(); + mgl::Rectangle bg_rect(mgl::vec2f(get_theme().window_width*0.5f - bg_size.x*0.5f, get_theme().window_height*0.5f - bg_size.y*0.5f).floor(), bg_size); + bg_rect.set_color(get_color_theme().page_bg_color); + window.draw(bg_rect); + + const mgl::vec2f tint_size = mgl::vec2f(bg_size.x, 0.004f * get_theme().window_height).floor(); + mgl::Rectangle tint_rect(bg_rect.get_position() - mgl::vec2f(0.0f, tint_size.y), tint_size); + tint_rect.set_color(get_color_theme().tint_color); + window.draw(tint_rect); + + title_text.set_position(mgl::vec2f(bg_rect.get_position() + mgl::vec2f(bg_rect.get_size().x*0.5f - title_text.get_bounds().size.x*0.5f, padding_vertical)).floor()); + description_text.set_position(mgl::vec2f(bg_rect.get_position() + mgl::vec2f(bg_rect.get_size().x*0.5f - description_text.get_bounds().size.x*0.5f, bg_rect.get_size().y - description_text.get_bounds().size.y - padding_vertical)).floor()); + + window.draw(title_text); + + const float title_text_bottom = title_text.get_position().y + title_text.get_bounds().size.y; + hotkey_text.set_position( + mgl::vec2f( + bg_rect.get_position().x + bg_rect.get_size().x*0.5f - hotkey_text.get_bounds().size.x*0.5f, + title_text_bottom + (description_text.get_position().y - title_text_bottom) * 0.5f - hotkey_text.get_bounds().size.y*0.5f + ).floor()); + window.draw(hotkey_text); + + const float caret_padding_x = int(0.001f * get_theme().window_height); + const mgl::vec2f caret_size = mgl::vec2f(std::max(2.0f, 0.002f * get_theme().window_height), hotkey_text.get_bounds().size.y).floor(); + mgl::Rectangle caret_rect(hotkey_text.get_position() + mgl::vec2f(hotkey_text.get_bounds().size.x + caret_padding_x, hotkey_text.get_bounds().size.y*0.5f - caret_size.y*0.5f).floor(), caret_size); + window.draw(caret_rect); + + window.draw(description_text); + }; + hotkey_overlay->set_visible(false); + hotkey_overlay_ptr = hotkey_overlay.get(); + add_widget(std::move(hotkey_overlay)); } std::unique_ptr<Subsection> GlobalSettingsPage::create_appearance_subsection(ScrollablePage *parent_page) { auto list = std::make_unique<List>(List::Orientation::VERTICAL); - list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Tint color", get_color_theme().text_color)); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Accent color", get_color_theme().text_color)); auto tint_color_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); tint_color_radio_button_ptr = tint_color_radio_button.get(); tint_color_radio_button->add_item("Red", "amd"); tint_color_radio_button->add_item("Green", "nvidia"); - tint_color_radio_button->add_item("blue", "intel"); + tint_color_radio_button->add_item("Blue", "intel"); tint_color_radio_button->on_selection_changed = [](const std::string&, const std::string &id) { if(id == "amd") get_color_theme().tint_color = mgl::Color(221, 0, 49); @@ -55,6 +156,7 @@ namespace gsr { get_color_theme().tint_color = mgl::Color(118, 185, 0); else if(id == "intel") get_color_theme().tint_color = mgl::Color(8, 109, 183); + return true; }; list->add_widget(std::move(tint_color_radio_button)); return std::make_unique<Subsection>("Appearance", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); @@ -62,10 +164,11 @@ namespace gsr { std::unique_ptr<Subsection> GlobalSettingsPage::create_startup_subsection(ScrollablePage *parent_page) { auto list = std::make_unique<List>(List::Orientation::VERTICAL); - auto startup_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start program on system startup?", get_color_theme().text_color)); + auto startup_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); startup_radio_button_ptr = startup_radio_button.get(); - startup_radio_button->add_item("Don't start this program on system startup", "dont_start_on_system_startup"); - startup_radio_button->add_item("Start this program on system startup", "start_on_system_startup"); + startup_radio_button->add_item("Yes", "start_on_system_startup"); + startup_radio_button->add_item("No", "dont_start_on_system_startup"); startup_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) { bool enable = false; if(id == "dont_start_on_system_startup") @@ -73,18 +176,256 @@ namespace gsr { else if(id == "start_on_system_startup") enable = true; else - return; + return false; const char *args[] = { "systemctl", enable ? "enable" : "disable", "--user", "gpu-screen-recorder-ui", nullptr }; std::string stdout_str; const int exit_status = exec_program_on_host_get_stdout(args, stdout_str); if(on_startup_changed) on_startup_changed(enable, exit_status); + return exit_status == 0; }; list->add_widget(std::move(startup_radio_button)); return std::make_unique<Subsection>("Startup", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); } + std::unique_ptr<RadioButton> GlobalSettingsPage::create_enable_keyboard_hotkeys_button() { + auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); + enable_keyboard_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get(); + enable_hotkeys_radio_button->add_item("Yes", "enable_hotkeys"); + enable_hotkeys_radio_button->add_item("No", "disable_hotkeys"); + enable_hotkeys_radio_button->add_item("Only grab virtual devices (supports input remapping software)", "enable_hotkeys_virtual_devices"); + enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) { + if(on_keyboard_hotkey_changed) + on_keyboard_hotkey_changed(id.c_str()); + return true; + }; + return enable_hotkeys_radio_button; + } + + std::unique_ptr<RadioButton> GlobalSettingsPage::create_enable_joystick_hotkeys_button() { + auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); + enable_joystick_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get(); + enable_hotkeys_radio_button->add_item("Yes", "enable_hotkeys"); + enable_hotkeys_radio_button->add_item("No", "disable_hotkeys"); + enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) { + if(on_joystick_hotkey_changed) + on_joystick_hotkey_changed(id.c_str()); + return true; + }; + return enable_hotkeys_radio_button; + } + + std::unique_ptr<List> GlobalSettingsPage::create_show_hide_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Show/hide UI:", get_color_theme().text_color)); + auto show_hide_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + show_hide_button_ptr = show_hide_button.get(); + list->add_widget(std::move(show_hide_button)); + + show_hide_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::SHOW_HIDE); + }; + + return list; + } + + std::unique_ptr<List> GlobalSettingsPage::create_replay_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Turn replay on/off:", get_color_theme().text_color)); + auto turn_replay_on_off_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + turn_replay_on_off_button_ptr = turn_replay_on_off_button.get(); + list->add_widget(std::move(turn_replay_on_off_button)); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save replay:", get_color_theme().text_color)); + auto save_replay_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_replay_button_ptr = save_replay_button.get(); + list->add_widget(std::move(save_replay_button)); + + turn_replay_on_off_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::REPLAY_START_STOP); + }; + + save_replay_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::REPLAY_SAVE); + }; + + return list; + } + + std::unique_ptr<List> GlobalSettingsPage::create_replay_partial_save_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save 1 minute replay:", get_color_theme().text_color)); + auto save_replay_1_min_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_replay_1_min_button_ptr = save_replay_1_min_button.get(); + list->add_widget(std::move(save_replay_1_min_button)); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save 10 minute replay:", get_color_theme().text_color)); + auto save_replay_10_min_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_replay_10_min_button_ptr = save_replay_10_min_button.get(); + list->add_widget(std::move(save_replay_10_min_button)); + + save_replay_1_min_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::REPLAY_SAVE_1_MIN); + }; + + save_replay_10_min_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::REPLAY_SAVE_10_MIN); + }; + + return list; + } + + std::unique_ptr<List> GlobalSettingsPage::create_record_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop recording:", get_color_theme().text_color)); + auto start_stop_recording_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + start_stop_recording_button_ptr = start_stop_recording_button.get(); + list->add_widget(std::move(start_stop_recording_button)); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Pause/unpause recording:", get_color_theme().text_color)); + auto pause_unpause_recording_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + pause_unpause_recording_button_ptr = pause_unpause_recording_button.get(); + list->add_widget(std::move(pause_unpause_recording_button)); + + start_stop_recording_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::RECORD_START_STOP); + }; + + pause_unpause_recording_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE); + }; + + return list; + } + + std::unique_ptr<List> GlobalSettingsPage::create_stream_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop streaming:", get_color_theme().text_color)); + auto start_stop_streaming_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + start_stop_streaming_button_ptr = start_stop_streaming_button.get(); + list->add_widget(std::move(start_stop_streaming_button)); + + start_stop_streaming_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::STREAM_START_STOP); + }; + + return list; + } + + std::unique_ptr<List> GlobalSettingsPage::create_screenshot_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Take a screenshot:", get_color_theme().text_color)); + auto take_screenshot_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + take_screenshot_button_ptr = take_screenshot_button.get(); + list->add_widget(std::move(take_screenshot_button)); + + take_screenshot_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::TAKE_SCREENSHOT); + }; + + return list; + } + + std::unique_ptr<List> GlobalSettingsPage::create_screenshot_region_hotkey_options() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Take a screenshot of a region:", get_color_theme().text_color)); + auto take_screenshot_region_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + take_screenshot_region_button_ptr = take_screenshot_region_button.get(); + list->add_widget(std::move(take_screenshot_region_button)); + + take_screenshot_region_button_ptr->on_click = [this] { + configure_hotkey_start(ConfigureHotkeyType::TAKE_SCREENSHOT_REGION); + }; + + return list; + } + + std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + + auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + clear_hotkeys_button->on_click = [this] { + config.streaming_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; + config.record_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; + config.record_config.pause_unpause_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.start_stop_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.save_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.save_1_min_hotkey = {mgl::Keyboard::Unknown, 0}; + config.replay_config.save_10_min_hotkey = {mgl::Keyboard::Unknown, 0}; + config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0}; + config.screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Unknown, 0}; + config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0}; + load_hotkeys(); + overlay->rebind_all_keyboard_hotkeys(); + }; + list->add_widget(std::move(clear_hotkeys_button)); + + auto reset_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Reset hotkeys to default", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + reset_hotkeys_button->on_click = [this] { + config.set_hotkeys_to_default(); + load_hotkeys(); + overlay->rebind_all_keyboard_hotkeys(); + }; + list->add_widget(std::move(reset_hotkeys_button)); + + return list; + } + + static std::unique_ptr<List> create_joystick_hotkey_text(mgl::Texture *image1, mgl::Texture *image2, float max_height, const char *suffix) { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Press", get_color_theme().text_color)); + list->add_widget(std::make_unique<Image>(image1, mgl::vec2f{max_height, 1000.0f}, Image::ScaleBehavior::SCALE)); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "and", get_color_theme().text_color)); + list->add_widget(std::make_unique<Image>(image2, mgl::vec2f{max_height, 1000.0f}, Image::ScaleBehavior::SCALE)); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, suffix, get_color_theme().text_color)); + return list; + } + + std::unique_ptr<Subsection> GlobalSettingsPage::create_keyboard_hotkey_subsection(ScrollablePage *parent_page) { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + List *list_ptr = list.get(); + auto subsection = std::make_unique<Subsection>("Keyboard hotkeys", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Enable keyboard hotkeys?", get_color_theme().text_color)); + list_ptr->add_widget(create_enable_keyboard_hotkeys_button()); + list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x)); + list_ptr->add_widget(create_show_hide_hotkey_options()); + list_ptr->add_widget(create_replay_hotkey_options()); + list_ptr->add_widget(create_replay_partial_save_hotkey_options()); + list_ptr->add_widget(create_record_hotkey_options()); + list_ptr->add_widget(create_stream_hotkey_options()); + list_ptr->add_widget(create_screenshot_hotkey_options()); + list_ptr->add_widget(create_screenshot_region_hotkey_options()); + list_ptr->add_widget(create_hotkey_control_buttons()); + return subsection; + } + + std::unique_ptr<Subsection> GlobalSettingsPage::create_controller_hotkey_subsection(ScrollablePage *parent_page) { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + List *list_ptr = list.get(); + auto subsection = std::make_unique<Subsection>("Controller hotkeys", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + + list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Enable controller hotkeys?", get_color_theme().text_color)); + list_ptr->add_widget(create_enable_joystick_hotkeys_button()); + list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x)); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_options_texture, get_theme().body_font.get_character_size(), "to show/hide the UI")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_up_texture, get_theme().body_font.get_character_size(), "to take a screenshot")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_down_texture, get_theme().body_font.get_character_size(), "to save a replay")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_left_texture, get_theme().body_font.get_character_size(), "to start/stop recording")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_right_texture, get_theme().body_font.get_character_size(), "to turn replay on/off")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_cross_texture, get_theme().body_font.get_character_size(), "to save a 1 minute replay")); + list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_triangle_texture, get_theme().body_font.get_character_size(), "to save a 10 minute replay")); + return subsection; + } + std::unique_ptr<Button> GlobalSettingsPage::create_exit_program_button() { auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, "Exit program", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); exit_program_button->on_click = [&]() { @@ -105,7 +446,6 @@ namespace gsr { std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) { const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; - auto list = std::make_unique<List>(List::Orientation::HORIZONTAL); list->add_widget(create_exit_program_button()); if(inside_flatpak) @@ -113,6 +453,29 @@ namespace gsr { return std::make_unique<Subsection>("Application options", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); } + std::unique_ptr<Subsection> GlobalSettingsPage::create_application_info_subsection(ScrollablePage *parent_page) { + const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + + char str[128]; + const std::string gsr_version = gsr_info->system_info.gsr_version.to_string(); + snprintf(str, sizeof(str), "GSR version: %s", gsr_version.c_str()); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color)); + + snprintf(str, sizeof(str), "GSR-UI version: %s", GSR_UI_VERSION); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color)); + + if(inside_flatpak) { + snprintf(str, sizeof(str), "Flatpak version: %s", GSR_FLATPAK_VERSION); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color)); + } + + snprintf(str, sizeof(str), "GPU vendor: %s", gpu_vendor_to_string(gsr_info->gpu_info.vendor)); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color)); + + return std::make_unique<Subsection>("Application info", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f)); + } + void GlobalSettingsPage::add_widgets() { auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size()); @@ -120,7 +483,10 @@ namespace gsr { settings_list->set_spacing(0.018f); settings_list->add_widget(create_appearance_subsection(scrollable_page.get())); settings_list->add_widget(create_startup_subsection(scrollable_page.get())); + settings_list->add_widget(create_keyboard_hotkey_subsection(scrollable_page.get())); + settings_list->add_widget(create_controller_hotkey_subsection(scrollable_page.get())); settings_list->add_widget(create_application_options_subsection(scrollable_page.get())); + settings_list->add_widget(create_application_info_subsection(scrollable_page.get())); scrollable_page->add_widget(std::move(settings_list)); content_page_ptr->add_widget(std::move(scrollable_page)); @@ -128,6 +494,8 @@ namespace gsr { void GlobalSettingsPage::on_navigate_away_from_page() { save(); + if(on_page_closed) + on_page_closed(); } void GlobalSettingsPage::load() { @@ -140,10 +508,246 @@ namespace gsr { std::string stdout_str; const int exit_status = exec_program_on_host_get_stdout(args, stdout_str); startup_radio_button_ptr->set_selected_item(exit_status == 0 ? "start_on_system_startup" : "dont_start_on_system_startup", false, false); + + enable_keyboard_hotkeys_radio_button_ptr->set_selected_item(config.main_config.hotkeys_enable_option, false, false); + enable_joystick_hotkeys_radio_button_ptr->set_selected_item(config.main_config.joystick_hotkeys_enable_option, false, false); + + load_hotkeys(); + } + + void GlobalSettingsPage::load_hotkeys() { + turn_replay_on_off_button_ptr->set_text(config.replay_config.start_stop_hotkey.to_string()); + save_replay_button_ptr->set_text(config.replay_config.save_hotkey.to_string()); + save_replay_1_min_button_ptr->set_text(config.replay_config.save_1_min_hotkey.to_string()); + save_replay_10_min_button_ptr->set_text(config.replay_config.save_10_min_hotkey.to_string()); + + start_stop_recording_button_ptr->set_text(config.record_config.start_stop_hotkey.to_string()); + pause_unpause_recording_button_ptr->set_text(config.record_config.pause_unpause_hotkey.to_string()); + + start_stop_streaming_button_ptr->set_text(config.streaming_config.start_stop_hotkey.to_string()); + + take_screenshot_button_ptr->set_text(config.screenshot_config.take_screenshot_hotkey.to_string()); + take_screenshot_region_button_ptr->set_text(config.screenshot_config.take_screenshot_region_hotkey.to_string()); + + show_hide_button_ptr->set_text(config.main_config.show_hide_hotkey.to_string()); } void GlobalSettingsPage::save() { + configure_hotkey_cancel(); config.main_config.tint_color = tint_color_radio_button_ptr->get_selected_id(); + config.main_config.hotkeys_enable_option = enable_keyboard_hotkeys_radio_button_ptr->get_selected_id(); + config.main_config.joystick_hotkeys_enable_option = enable_joystick_hotkeys_radio_button_ptr->get_selected_id(); save_config(config); } -}
\ No newline at end of file + + bool GlobalSettingsPage::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) { + if(!StaticPage::on_event(event, window, offset)) + return false; + + if(configure_hotkey_type == ConfigureHotkeyType::NONE) + return true; + + Button *configure_hotkey_button = configure_hotkey_get_button_by_active_type(); + if(!configure_hotkey_button) + return true; + + if(event.type == mgl::Event::KeyPressed) { + if(event.key.code == mgl::Keyboard::Escape) + return false; + + if(event.key.code == mgl::Keyboard::Backspace) { + configure_config_hotkey = {mgl::Keyboard::Unknown, 0}; + configure_hotkey_button->set_text(""); + configure_hotkey_stop_and_save(); + return false; + } + + if(mgl::Keyboard::key_is_modifier(event.key.code)) { + configure_config_hotkey.modifiers |= mgl_modifier_to_hotkey_modifier(event.key.code); + configure_hotkey_button->set_text(configure_config_hotkey.to_string()); + } else if(event.key.code != mgl::Keyboard::Unknown && (configure_config_hotkey.modifiers != 0 || !key_is_alpha_numerical(event.key.code))) { + configure_config_hotkey.key = event.key.code; + configure_hotkey_button->set_text(configure_config_hotkey.to_string()); + configure_hotkey_stop_and_save(); + } + + return false; + } else if(event.type == mgl::Event::KeyReleased) { + if(event.key.code == mgl::Keyboard::Escape) { + configure_hotkey_cancel(); + return false; + } + + if(mgl::Keyboard::key_is_modifier(event.key.code)) { + configure_config_hotkey.modifiers &= ~mgl_modifier_to_hotkey_modifier(event.key.code); + configure_hotkey_button->set_text(configure_config_hotkey.to_string()); + } + + return false; + } + + return true; + } + + Button* GlobalSettingsPage::configure_hotkey_get_button_by_active_type() { + switch(configure_hotkey_type) { + case ConfigureHotkeyType::NONE: + return nullptr; + case ConfigureHotkeyType::REPLAY_START_STOP: + return turn_replay_on_off_button_ptr; + case ConfigureHotkeyType::REPLAY_SAVE: + return save_replay_button_ptr; + case ConfigureHotkeyType::REPLAY_SAVE_1_MIN: + return save_replay_1_min_button_ptr; + case ConfigureHotkeyType::REPLAY_SAVE_10_MIN: + return save_replay_10_min_button_ptr; + case ConfigureHotkeyType::RECORD_START_STOP: + return start_stop_recording_button_ptr; + case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: + return pause_unpause_recording_button_ptr; + case ConfigureHotkeyType::STREAM_START_STOP: + return start_stop_streaming_button_ptr; + case ConfigureHotkeyType::TAKE_SCREENSHOT: + return take_screenshot_button_ptr; + case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION: + return take_screenshot_region_button_ptr; + case ConfigureHotkeyType::SHOW_HIDE: + return show_hide_button_ptr; + } + return nullptr; + } + + ConfigHotkey* GlobalSettingsPage::configure_hotkey_get_config_by_active_type() { + switch(configure_hotkey_type) { + case ConfigureHotkeyType::NONE: + return nullptr; + case ConfigureHotkeyType::REPLAY_START_STOP: + return &config.replay_config.start_stop_hotkey; + case ConfigureHotkeyType::REPLAY_SAVE: + return &config.replay_config.save_hotkey; + case ConfigureHotkeyType::REPLAY_SAVE_1_MIN: + return &config.replay_config.save_1_min_hotkey; + case ConfigureHotkeyType::REPLAY_SAVE_10_MIN: + return &config.replay_config.save_10_min_hotkey; + case ConfigureHotkeyType::RECORD_START_STOP: + return &config.record_config.start_stop_hotkey; + case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: + return &config.record_config.pause_unpause_hotkey; + case ConfigureHotkeyType::STREAM_START_STOP: + return &config.streaming_config.start_stop_hotkey; + case ConfigureHotkeyType::TAKE_SCREENSHOT: + return &config.screenshot_config.take_screenshot_hotkey; + case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION: + return &config.screenshot_config.take_screenshot_region_hotkey; + case ConfigureHotkeyType::SHOW_HIDE: + return &config.main_config.show_hide_hotkey; + } + return nullptr; + } + + void GlobalSettingsPage::for_each_config_hotkey(std::function<void(ConfigHotkey *config_hotkey)> callback) { + ConfigHotkey *config_hotkeys[] = { + &config.replay_config.start_stop_hotkey, + &config.replay_config.save_hotkey, + &config.record_config.start_stop_hotkey, + &config.record_config.pause_unpause_hotkey, + &config.streaming_config.start_stop_hotkey, + &config.screenshot_config.take_screenshot_hotkey, + &config.screenshot_config.take_screenshot_region_hotkey, + &config.main_config.show_hide_hotkey + }; + for(ConfigHotkey *config_hotkey : config_hotkeys) { + callback(config_hotkey); + } + } + + void GlobalSettingsPage::configure_hotkey_start(ConfigureHotkeyType hotkey_type) { + assert(hotkey_type != ConfigureHotkeyType::NONE); + configure_config_hotkey = {0, 0}; + configure_hotkey_type = hotkey_type; + + content_page_ptr->set_visible(false); + hotkey_overlay_ptr->set_visible(true); + overlay->unbind_all_keyboard_hotkeys(); + configure_hotkey_get_button_by_active_type()->set_text(""); + + switch(hotkey_type) { + case ConfigureHotkeyType::NONE: + hotkey_configure_action_name = ""; + break; + case ConfigureHotkeyType::REPLAY_START_STOP: + hotkey_configure_action_name = "Turn replay on/off"; + break; + case ConfigureHotkeyType::REPLAY_SAVE: + hotkey_configure_action_name = "Save replay"; + break; + case ConfigureHotkeyType::REPLAY_SAVE_1_MIN: + hotkey_configure_action_name = "Save 1 minute replay"; + break; + case ConfigureHotkeyType::REPLAY_SAVE_10_MIN: + hotkey_configure_action_name = "Save 10 minute replay"; + break; + case ConfigureHotkeyType::RECORD_START_STOP: + hotkey_configure_action_name = "Start/stop recording"; + break; + case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE: + hotkey_configure_action_name = "Pause/unpause recording"; + break; + case ConfigureHotkeyType::STREAM_START_STOP: + hotkey_configure_action_name = "Start/stop streaming"; + break; + case ConfigureHotkeyType::TAKE_SCREENSHOT: + hotkey_configure_action_name = "Take a screenshot"; + break; + case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION: + hotkey_configure_action_name = "Take a screenshot of a region"; + break; + case ConfigureHotkeyType::SHOW_HIDE: + hotkey_configure_action_name = "Show/hide UI"; + break; + } + } + + void GlobalSettingsPage::configure_hotkey_cancel() { + Button *config_hotkey_button = configure_hotkey_get_button_by_active_type(); + ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type(); + if(config_hotkey_button && config_hotkey) + config_hotkey_button->set_text(config_hotkey->to_string()); + + configure_config_hotkey = {0, 0}; + configure_hotkey_type = ConfigureHotkeyType::NONE; + content_page_ptr->set_visible(true); + hotkey_overlay_ptr->set_visible(false); + overlay->rebind_all_keyboard_hotkeys(); + } + + void GlobalSettingsPage::configure_hotkey_stop_and_save() { + Button *config_hotkey_button = configure_hotkey_get_button_by_active_type(); + ConfigHotkey *config_hotkey = configure_hotkey_get_config_by_active_type(); + if(config_hotkey_button && config_hotkey) { + bool hotkey_used_by_another_action = false; + if(configure_config_hotkey.key != mgl::Keyboard::Unknown) { + for_each_config_hotkey([&](ConfigHotkey *config_hotkey_item) { + if(config_hotkey_item != config_hotkey && *config_hotkey_item == configure_config_hotkey) + hotkey_used_by_another_action = true; + }); + } + + if(hotkey_used_by_another_action) { + const std::string error_msg = "The hotkey \"" + configure_config_hotkey.to_string() + " is already used for something else"; + overlay->show_notification(error_msg.c_str(), 3.0, mgl::Color(255, 0, 0, 255), mgl::Color(255, 0, 0, 255), NotificationType::NONE); + config_hotkey_button->set_text(config_hotkey->to_string()); + configure_config_hotkey = {0, 0}; + return; + } + + *config_hotkey = configure_config_hotkey; + } + + configure_config_hotkey = {0, 0}; + configure_hotkey_type = ConfigureHotkeyType::NONE; + content_page_ptr->set_visible(true); + hotkey_overlay_ptr->set_visible(false); + overlay->rebind_all_keyboard_hotkeys(); + } +} diff --git a/src/gui/GsrPage.cpp b/src/gui/GsrPage.cpp index 68ee292..b4005f5 100644 --- a/src/gui/GsrPage.cpp +++ b/src/gui/GsrPage.cpp @@ -8,8 +8,9 @@ namespace gsr { static const float button_spacing_scale = 0.015f; - GsrPage::GsrPage() : - label_text("Settings", get_theme().title_font) + GsrPage::GsrPage(const char *top_text, const char *bottom_text) : + top_text(top_text, get_theme().title_font), + bottom_text(bottom_text, get_theme().title_font) { const float margin = 0.02f; set_margins(margin, margin, margin, margin); @@ -38,8 +39,9 @@ namespace gsr { // Process widgets by visibility (backwards) return widgets.for_each_reverse([selected_widget, &window, &event, content_page_position](std::unique_ptr<Widget> &widget) { - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, content_page_position)) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, content_page_position)) return false; } return true; @@ -80,13 +82,17 @@ namespace gsr { window.draw(background); const int text_margin = background.get_size().y * 0.085; - label_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - label_text.get_bounds().size.x * 0.5f, text_margin)).floor()); - window.draw(label_text); + + top_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - top_text.get_bounds().size.x * 0.5f, text_margin)).floor()); + window.draw(top_text); mgl::Sprite icon(&get_theme().settings_texture); icon.set_height((int)(background.get_size().y * 0.5f)); icon.set_position((background.get_position() + background.get_size() * 0.5f - icon.get_size() * 0.5f).floor()); window.draw(icon); + + bottom_text.set_position((background.get_position() + mgl::vec2f(background.get_size().x * 0.5f - bottom_text.get_bounds().size.x * 0.5f, background.get_size().y - bottom_text.get_bounds().size.y - text_margin)).floor()); + window.draw(bottom_text); } void GsrPage::draw_buttons(mgl::Window &window, mgl::vec2f body_pos, mgl::vec2f body_size) { @@ -102,15 +108,8 @@ namespace gsr { void GsrPage::draw_children(mgl::Window &window, mgl::vec2f position) { Widget *selected_widget = selected_child_widget; - mgl_scissor prev_scissor; - mgl_window_get_scissor(window.internal_window(), &prev_scissor); - - const mgl::vec2f inner_size = get_inner_size(); - const mgl_scissor new_scissor = { - mgl_vec2i{(int)position.x, (int)position.y}, - mgl_vec2i{(int)inner_size.x, (int)inner_size.y} - }; - mgl_window_set_scissor(window.internal_window(), &new_scissor); + const mgl::Scissor prev_scissor = window.get_scissor(); + window.set_scissor({position.to_vec2i(), get_inner_size().to_vec2i()}); for(size_t i = 0; i < widgets.size(); ++i) { auto &widget = widgets[i]; @@ -121,7 +120,7 @@ namespace gsr { if(selected_widget) selected_widget->draw(window, position); - mgl_window_set_scissor(window.internal_window(), &prev_scissor); + window.set_scissor(prev_scissor); } mgl::vec2f GsrPage::get_size() { diff --git a/src/gui/Image.cpp b/src/gui/Image.cpp new file mode 100644 index 0000000..b6cec9a --- /dev/null +++ b/src/gui/Image.cpp @@ -0,0 +1,39 @@ +#include "../../include/gui/Image.hpp" +#include "../../include/gui/Utils.hpp" + +#include <mglpp/window/Window.hpp> +#include <mglpp/graphics/Texture.hpp> + +namespace gsr { + Image::Image(mgl::Texture *texture, mgl::vec2f size, ScaleBehavior scale_behavior) : + sprite(texture), size(size), scale_behavior(scale_behavior) + { + + } + + bool Image::on_event(mgl::Event&, mgl::Window&, mgl::vec2f) { + return true; + } + + void Image::draw(mgl::Window &window, mgl::vec2f offset) { + if(!visible) + return; + + sprite.set_size(get_size()); + sprite.set_position((position + offset).floor()); + window.draw(sprite); + } + + mgl::vec2f Image::get_size() { + if(!visible || !sprite.get_texture()) + return {0.0f, 0.0f}; + + const mgl::vec2f sprite_size = sprite.get_texture()->get_size().to_vec2f(); + if(size.x < 0.001f && size.y < 0.001f) + return sprite_size; + else if(scale_behavior == ScaleBehavior::SCALE) + return scale_keep_aspect_ratio(sprite_size, size); + else + return clamp_keep_aspect_ratio(sprite_size, size); + } +}
\ No newline at end of file diff --git a/src/gui/List.cpp b/src/gui/List.cpp index 5294e36..57a6045 100644 --- a/src/gui/List.cpp +++ b/src/gui/List.cpp @@ -24,14 +24,23 @@ namespace gsr { // Process widgets by visibility (backwards) return widgets.for_each_reverse([selected_widget, &event, &window](std::unique_ptr<Widget> &widget) { // Ignore offset because widgets are positioned with offset in ::draw, this solution is simpler - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) return false; } return true; }); } + List::~List() { + widgets.for_each([this](std::unique_ptr<Widget> &widget) { + if(widget->parent_widget == this) + widget->parent_widget = nullptr; + return true; + }, true); + } + void List::draw(mgl::Window &window, mgl::vec2f offset) { if(!visible) return; @@ -104,15 +113,6 @@ namespace gsr { selected_widget->draw(window, mgl::vec2f(0.0f, 0.0f)); } - // void List::remove_child_widget(Widget *widget) { - // for(auto it = widgets.begin(), end = widgets.end(); it != end; ++it) { - // if(it->get() == widget) { - // widgets.erase(it); - // return; - // } - // } - // } - void List::add_widget(std::unique_ptr<Widget> widget) { widget->parent_widget = this; widgets.push_back(std::move(widget)); @@ -122,6 +122,10 @@ namespace gsr { widgets.remove(widget); } + void List::replace_widget(Widget *widget, std::unique_ptr<Widget> new_widget) { + widgets.replace_item(widget, std::move(new_widget)); + } + void List::clear() { widgets.clear(); } @@ -137,6 +141,10 @@ namespace gsr { return nullptr; } + size_t List::get_num_children() const { + return widgets.size(); + } + void List::set_spacing(float spacing) { spacing_scale = spacing; } diff --git a/src/gui/Page.cpp b/src/gui/Page.cpp index ae13d82..5f21b71 100644 --- a/src/gui/Page.cpp +++ b/src/gui/Page.cpp @@ -1,14 +1,13 @@ #include "../../include/gui/Page.hpp" namespace gsr { - // void Page::remove_child_widget(Widget *widget) { - // for(auto it = widgets.begin(), end = widgets.end(); it != end; ++it) { - // if(it->get() == widget) { - // widgets.erase(it); - // return; - // } - // } - // } + Page::~Page() { + widgets.for_each([this](std::unique_ptr<Widget> &widget) { + if(widget->parent_widget == this) + widget->parent_widget = nullptr; + return true; + }, true); + } void Page::add_widget(std::unique_ptr<Widget> widget) { widget->parent_widget = this; diff --git a/src/gui/RadioButton.cpp b/src/gui/RadioButton.cpp index 061d811..bbb958a 100644 --- a/src/gui/RadioButton.cpp +++ b/src/gui/RadioButton.cpp @@ -35,12 +35,12 @@ namespace gsr { const bool mouse_inside = mgl::FloatRect(draw_pos, item_size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y)); if(mouse_inside) { - const size_t prev_selected_item = selected_item; - selected_item = i; - - if(selected_item != prev_selected_item && on_selection_changed) - on_selection_changed(item.text.get_string(), item.id); + if(selected_item != i && on_selection_changed) { + if(!on_selection_changed(item.text.get_string(), item.id)) + return false; + } + selected_item = i; return false; } @@ -158,18 +158,18 @@ namespace gsr { for(size_t i = 0; i < items.size(); ++i) { auto &item = items[i]; if(item.id == id) { - const size_t prev_selected_item = selected_item; - selected_item = i; - - if(trigger_event && (trigger_event_even_if_selection_not_changed || selected_item != prev_selected_item) && on_selection_changed) - on_selection_changed(item.text.get_string(), item.id); + if(trigger_event && (trigger_event_even_if_selection_not_changed || selected_item != i) && on_selection_changed) { + if(!on_selection_changed(item.text.get_string(), item.id)) + break; + } + selected_item = i; break; } } } - const std::string RadioButton::get_selected_id() const { + const std::string& RadioButton::get_selected_id() const { if(items.empty()) { static std::string dummy; return dummy; @@ -177,4 +177,13 @@ namespace gsr { return items[selected_item].id; } } + + const std::string& RadioButton::get_selected_text() const { + if(items.empty()) { + static std::string dummy; + return dummy; + } else { + return items[selected_item].text.get_string(); + } + } }
\ No newline at end of file diff --git a/src/gui/ScreenshotSettingsPage.cpp b/src/gui/ScreenshotSettingsPage.cpp new file mode 100644 index 0000000..27a94b0 --- /dev/null +++ b/src/gui/ScreenshotSettingsPage.cpp @@ -0,0 +1,332 @@ +#include "../../include/gui/ScreenshotSettingsPage.hpp" +#include "../../include/gui/GsrPage.hpp" +#include "../../include/gui/PageStack.hpp" +#include "../../include/Theme.hpp" +#include "../../include/GsrInfo.hpp" +#include "../../include/Utils.hpp" +#include "../../include/gui/List.hpp" +#include "../../include/gui/ScrollablePage.hpp" +#include "../../include/gui/Label.hpp" +#include "../../include/gui/Subsection.hpp" +#include "../../include/gui/FileChooser.hpp" + +namespace gsr { + ScreenshotSettingsPage::ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : + StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), + config(config), + gsr_info(gsr_info), + page_stack(page_stack) + { + capture_options = get_supported_capture_options(*gsr_info); + + auto content_page = std::make_unique<GsrPage>("Screenshot", "Settings"); + content_page->add_button("Back", "back", get_color_theme().page_bg_color); + content_page->on_click = [page_stack](const std::string &id) { + if(id == "back") + page_stack->pop(); + }; + content_page_ptr = content_page.get(); + add_widget(std::move(content_page)); + + add_widgets(); + load(); + } + + std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_record_area_box() { + auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font); + // TODO: Show options not supported but disable them + if(capture_options.window) + record_area_box->add_item("Window", "window"); + if(capture_options.region) + record_area_box->add_item("Region", "region"); + if(!capture_options.monitors.empty()) + record_area_box->add_item(gsr_info->system_info.display_server == DisplayServer::WAYLAND ? "Focused monitor (Experimental on Wayland)" : "Focused monitor", "focused_monitor"); + for(const auto &monitor : capture_options.monitors) { + char name[256]; + snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y); + record_area_box->add_item(name, monitor.name); + } + if(capture_options.portal) + record_area_box->add_item("Desktop portal", "portal"); + record_area_box_ptr = record_area_box.get(); + return record_area_box; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_area() { + auto record_area_list = std::make_unique<List>(List::Orientation::VERTICAL); + record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Capture target:", get_color_theme().text_color)); + record_area_list->add_widget(create_record_area_box()); + return record_area_list; + } + + std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_width_entry() { + auto image_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3); + image_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15); + image_width_entry_ptr = image_width_entry.get(); + return image_width_entry; + } + + std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_height_entry() { + auto image_height_entry = std::make_unique<Entry>(&get_theme().body_font, "1080", get_theme().body_font.get_character_size() * 3); + image_height_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15); + image_height_entry_ptr = image_height_entry.get(); + return image_height_entry; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_resolution() { + auto area_size_params_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + area_size_params_list->add_widget(create_image_width_entry()); + area_size_params_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "x", get_color_theme().text_color)); + area_size_params_list->add_widget(create_image_height_entry()); + return area_size_params_list; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_resolution_section() { + auto image_resolution_list = std::make_unique<List>(List::Orientation::VERTICAL); + image_resolution_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image resolution limit:", get_color_theme().text_color)); + image_resolution_list->add_widget(create_image_resolution()); + image_resolution_list_ptr = image_resolution_list.get(); + return image_resolution_list; + } + + std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_restore_portal_session_checkbox() { + auto restore_portal_session_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Restore portal session"); + restore_portal_session_checkbox->set_checked(true); + restore_portal_session_checkbox_ptr = restore_portal_session_checkbox.get(); + return restore_portal_session_checkbox; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_restore_portal_session_section() { + auto restore_portal_session_list = std::make_unique<List>(List::Orientation::VERTICAL); + restore_portal_session_list->add_widget(std::make_unique<Label>(&get_theme().body_font, " ", get_color_theme().text_color)); + restore_portal_session_list->add_widget(create_restore_portal_session_checkbox()); + restore_portal_session_list_ptr = restore_portal_session_list.get(); + return restore_portal_session_list; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_change_image_resolution_section() { + auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Change image resolution"); + change_image_resolution_checkbox_ptr = checkbox.get(); + return checkbox; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_capture_target_section() { + auto ll = std::make_unique<List>(List::Orientation::VERTICAL); + + auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + capture_target_list->add_widget(create_record_area()); + capture_target_list->add_widget(create_image_resolution_section()); + capture_target_list->add_widget(create_restore_portal_session_section()); + + ll->add_widget(std::move(capture_target_list)); + ll->add_widget(create_change_image_resolution_section()); + return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_quality_section() { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image quality:", get_color_theme().text_color)); + + auto image_quality_box = std::make_unique<ComboBox>(&get_theme().body_font); + image_quality_box->add_item("Medium", "medium"); + image_quality_box->add_item("High", "high"); + image_quality_box->add_item("Very high (Recommended)", "very_high"); + image_quality_box->add_item("Ultra", "ultra"); + image_quality_box->set_selected_item("very_high"); + + image_quality_box_ptr = image_quality_box.get(); + list->add_widget(std::move(image_quality_box)); + + return list; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_cursor_section() { + auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor"); + record_cursor_checkbox->set_checked(true); + record_cursor_checkbox_ptr = record_cursor_checkbox.get(); + return record_cursor_checkbox; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_image_section() { + auto image_section_list = std::make_unique<List>(List::Orientation::VERTICAL); + image_section_list->add_widget(create_image_quality_section()); + image_section_list->add_widget(create_record_cursor_section()); + return std::make_unique<Subsection>("Image", std::move(image_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_save_directory(const char *label) { + auto save_directory_list = std::make_unique<List>(List::Orientation::VERTICAL); + save_directory_list->add_widget(std::make_unique<Label>(&get_theme().body_font, label, get_color_theme().text_color)); + auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_pictures_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_directory_button_ptr = save_directory_button.get(); + save_directory_button->on_click = [this]() { + auto select_directory_page = std::make_unique<GsrPage>("File", "Settings"); + select_directory_page->add_button("Save", "save", get_color_theme().tint_color); + select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color); + + auto file_chooser = std::make_unique<FileChooser>(save_directory_button_ptr->get_text().c_str(), select_directory_page->get_inner_size()); + FileChooser *file_chooser_ptr = file_chooser.get(); + select_directory_page->add_widget(std::move(file_chooser)); + + select_directory_page->on_click = [this, file_chooser_ptr](const std::string &id) { + if(id == "save") { + save_directory_button_ptr->set_text(file_chooser_ptr->get_current_directory()); + page_stack->pop(); + } else if(id == "cancel") { + page_stack->pop(); + } + }; + + page_stack->push(std::move(select_directory_page)); + }; + save_directory_list->add_widget(std::move(save_directory_button)); + return save_directory_list; + } + + std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_image_format_box() { + auto box = std::make_unique<ComboBox>(&get_theme().body_font); + if(gsr_info->supported_image_formats.jpeg) + box->add_item("jpg", "jpg"); + if(gsr_info->supported_image_formats.png) + box->add_item("png", "png"); + image_format_box_ptr = box.get(); + return box; + } + + std::unique_ptr<List> ScreenshotSettingsPage::create_image_format_section() { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image format:", get_color_theme().text_color)); + list->add_widget(create_image_format_box()); + return list; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_file_info_section() { + auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL); + file_info_data_list->add_widget(create_save_directory("Directory to save the screenshot:")); + file_info_data_list->add_widget(create_image_format_section()); + return std::make_unique<Subsection>("File info", std::move(file_info_data_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_save_screenshot_in_game_folder() { + char text[256]; + snprintf(text, sizeof(text), "Save screenshot in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)"); + auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text); + save_screenshot_in_game_folder_checkbox_ptr = checkbox.get(); + return checkbox; + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_general_section() { + return std::make_unique<Subsection>("General", create_save_screenshot_in_game_folder(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_notifications_section() { + auto show_screenshot_saved_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show screenshot saved notification"); + show_screenshot_saved_notification_checkbox->set_checked(true); + show_screenshot_saved_notification_checkbox_ptr = show_screenshot_saved_notification_checkbox.get(); + return std::make_unique<Subsection>("Notifications", std::move(show_screenshot_saved_notification_checkbox), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<Widget> ScreenshotSettingsPage::create_settings() { + auto page_list = std::make_unique<List>(List::Orientation::VERTICAL); + page_list->set_spacing(0.018f); + auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size() - mgl::vec2f(0.0f, page_list->get_size().y + 0.018f * get_theme().window_height)); + settings_scrollable_page_ptr = scrollable_page.get(); + page_list->add_widget(std::move(scrollable_page)); + + auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL); + settings_list->set_spacing(0.018f); + settings_list->add_widget(create_capture_target_section()); + settings_list->add_widget(create_image_section()); + settings_list->add_widget(create_file_info_section()); + settings_list->add_widget(create_general_section()); + settings_list->add_widget(create_notifications_section()); + settings_scrollable_page_ptr->add_widget(std::move(settings_list)); + return page_list; + } + + void ScreenshotSettingsPage::add_widgets() { + content_page_ptr->add_widget(create_settings()); + + record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { + const bool portal_selected = id == "portal"; + image_resolution_list_ptr->set_visible(change_image_resolution_checkbox_ptr->is_checked()); + restore_portal_session_list_ptr->set_visible(portal_selected); + return true; + }; + + change_image_resolution_checkbox_ptr->on_changed = [this](bool checked) { + image_resolution_list_ptr->set_visible(checked); + }; + + if(!capture_options.monitors.empty()) + record_area_box_ptr->set_selected_item("focused_monitor"); + else if(capture_options.portal) + record_area_box_ptr->set_selected_item("portal"); + else if(capture_options.window) + record_area_box_ptr->set_selected_item("window"); + else + record_area_box_ptr->on_selection_changed("", ""); + } + + void ScreenshotSettingsPage::on_navigate_away_from_page() { + save(); + } + + void ScreenshotSettingsPage::load() { + record_area_box_ptr->set_selected_item(config.screenshot_config.record_area_option); + change_image_resolution_checkbox_ptr->set_checked(config.screenshot_config.change_image_resolution); + image_quality_box_ptr->set_selected_item(config.screenshot_config.image_quality); + image_format_box_ptr->set_selected_item(config.screenshot_config.image_format); + record_cursor_checkbox_ptr->set_checked(config.screenshot_config.record_cursor); + restore_portal_session_checkbox_ptr->set_checked(config.screenshot_config.restore_portal_session); + save_directory_button_ptr->set_text(config.screenshot_config.save_directory); + save_screenshot_in_game_folder_checkbox_ptr->set_checked(config.screenshot_config.save_screenshot_in_game_folder); + show_screenshot_saved_notification_checkbox_ptr->set_checked(config.screenshot_config.show_screenshot_saved_notifications); + + if(config.screenshot_config.image_width == 0) + config.screenshot_config.image_width = 1920; + + if(config.screenshot_config.image_height == 0) + config.screenshot_config.image_height = 1080; + + if(config.screenshot_config.image_width < 32) + config.screenshot_config.image_width = 32; + image_width_entry_ptr->set_text(std::to_string(config.screenshot_config.image_width)); + + if(config.screenshot_config.image_height < 32) + config.screenshot_config.image_height = 32; + image_height_entry_ptr->set_text(std::to_string(config.screenshot_config.image_height)); + } + + void ScreenshotSettingsPage::save() { + config.screenshot_config.record_area_option = record_area_box_ptr->get_selected_id(); + config.screenshot_config.image_width = atoi(image_width_entry_ptr->get_text().c_str()); + config.screenshot_config.image_height = atoi(image_height_entry_ptr->get_text().c_str()); + config.screenshot_config.change_image_resolution = change_image_resolution_checkbox_ptr->is_checked(); + config.screenshot_config.image_quality = image_quality_box_ptr->get_selected_id(); + config.screenshot_config.image_format = image_format_box_ptr->get_selected_id(); + config.screenshot_config.record_cursor = record_cursor_checkbox_ptr->is_checked(); + config.screenshot_config.restore_portal_session = restore_portal_session_checkbox_ptr->is_checked(); + config.screenshot_config.save_directory = save_directory_button_ptr->get_text(); + config.screenshot_config.save_screenshot_in_game_folder = save_screenshot_in_game_folder_checkbox_ptr->is_checked(); + config.screenshot_config.show_screenshot_saved_notifications = show_screenshot_saved_notification_checkbox_ptr->is_checked(); + + if(config.screenshot_config.image_width == 0) + config.screenshot_config.image_width = 1920; + + if(config.screenshot_config.image_height == 0) + config.screenshot_config.image_height = 1080; + + if(config.screenshot_config.image_width < 32) { + config.screenshot_config.image_width = 32; + image_width_entry_ptr->set_text("32"); + } + + if(config.screenshot_config.image_height < 32) { + config.screenshot_config.image_height = 32; + image_height_entry_ptr->set_text("32"); + } + + save_config(config); + } +}
\ No newline at end of file diff --git a/src/gui/ScrollablePage.cpp b/src/gui/ScrollablePage.cpp index 923a0c8..cec20d3 100644 --- a/src/gui/ScrollablePage.cpp +++ b/src/gui/ScrollablePage.cpp @@ -15,6 +15,14 @@ namespace gsr { ScrollablePage::ScrollablePage(mgl::vec2f size) : size(size) {} + ScrollablePage::~ScrollablePage() { + widgets.for_each([this](std::unique_ptr<Widget> &widget) { + if(widget->parent_widget == this) + widget->parent_widget = nullptr; + return true; + }, true); + } + bool ScrollablePage::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) { if(!visible) return true; @@ -57,8 +65,9 @@ namespace gsr { // Process widgets by visibility (backwards) const bool continue_events = widgets.for_each_reverse([selected_widget, &window, &event, offset](std::unique_ptr<Widget> &widget) { - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, offset)) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, offset)) return false; } return true; @@ -89,8 +98,7 @@ namespace gsr { offset = position + offset; - mgl_scissor prev_scissor; - mgl_window_get_scissor(window.internal_window(), &prev_scissor); + const mgl::Scissor prev_scissor = window.get_scissor(); const mgl::vec2f content_size = get_inner_size(); const mgl_scissor new_scissor = { @@ -150,7 +158,7 @@ namespace gsr { apply_animation(); limit_scroll(child_height); - mgl_window_set_scissor(window.internal_window(), &prev_scissor); + window.set_scissor(prev_scissor); double scrollbar_height = 1.0; if(child_height > 0.001) diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 2e6fa08..26e7335 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -8,20 +8,26 @@ #include "../../include/GsrInfo.hpp" #include "../../include/Utils.hpp" -#include <mglpp/graphics/Rectangle.hpp> -#include <mglpp/graphics/Sprite.hpp> -#include <mglpp/graphics/Text.hpp> -#include <mglpp/window/Window.hpp> - #include <string.h> namespace gsr { + static const char *custom_app_audio_tag = "[custom]"; + enum class AudioTrackType { DEVICE, APPLICATION, APPLICATION_CUSTOM }; + static const char* settings_page_type_to_title_text(SettingsPage::Type type) { + switch(type) { + case SettingsPage::Type::REPLAY: return "Instant Replay"; + case SettingsPage::Type::RECORD: return "Record"; + case SettingsPage::Type::STREAM: return "Livestream"; + } + return ""; + } + SettingsPage::SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) : StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), type(type), @@ -33,7 +39,7 @@ namespace gsr { application_audio = get_application_audio(); capture_options = get_supported_capture_options(*gsr_info); - auto content_page = std::make_unique<GsrPage>(); + auto content_page = std::make_unique<GsrPage>(settings_page_type_to_title_text(type), "Settings"); content_page->add_button("Back", "back", get_color_theme().page_bg_color); content_page->on_click = [page_stack](const std::string &id) { if(id == "back") @@ -59,11 +65,14 @@ namespace gsr { std::unique_ptr<ComboBox> SettingsPage::create_record_area_box() { auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font); // TODO: Show options not supported but disable them - // TODO: Enable this - //if(capture_options.window) - // record_area_box->add_item("Window", "window"); + if(capture_options.window) + record_area_box->add_item("Window", "window"); if(capture_options.focused) record_area_box->add_item("Follow focused window", "focused"); + if(capture_options.region) + record_area_box->add_item("Region", "region"); + if(!capture_options.monitors.empty()) + record_area_box->add_item(gsr_info->system_info.display_server == DisplayServer::WAYLAND ? "Focused monitor (Experimental on Wayland)" : "Focused monitor", "focused_monitor"); for(const auto &monitor : capture_options.monitors) { char name[256]; snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y); @@ -82,14 +91,6 @@ namespace gsr { return record_area_list; } - std::unique_ptr<List> SettingsPage::create_select_window() { - auto select_window_list = std::make_unique<List>(List::Orientation::VERTICAL); - select_window_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Select window:", get_color_theme().text_color)); - select_window_list->add_widget(std::make_unique<Button>(&get_theme().body_font, "Click here to select a window...", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120))); - select_window_list_ptr = select_window_list.get(); - return select_window_list; - } - std::unique_ptr<Entry> SettingsPage::create_area_width_entry() { auto area_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3); area_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15); @@ -171,12 +172,11 @@ namespace gsr { return checkbox; } - std::unique_ptr<Widget> SettingsPage::create_capture_target() { + std::unique_ptr<Widget> SettingsPage::create_capture_target_section() { auto ll = std::make_unique<List>(List::Orientation::VERTICAL); auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); capture_target_list->add_widget(create_record_area()); - capture_target_list->add_widget(create_select_window()); capture_target_list->add_widget(create_area_size_section()); capture_target_list->add_widget(create_video_resolution_section()); capture_target_list->add_widget(create_restore_portal_session_section()); @@ -186,128 +186,229 @@ namespace gsr { return std::make_unique<Subsection>("Record area", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); } - std::unique_ptr<ComboBox> SettingsPage::create_audio_device_selection_combobox() { + static bool audio_device_is_output(const std::string &audio_device_id) { + return audio_device_id == "default_output" || ends_with(audio_device_id, ".monitor"); + } + + std::unique_ptr<ComboBox> SettingsPage::create_audio_device_selection_combobox(AudioDeviceType device_type) { auto audio_device_box = std::make_unique<ComboBox>(&get_theme().body_font); for(const auto &audio_device : audio_devices) { - audio_device_box->add_item(audio_device.description, audio_device.name); + const bool device_is_output = audio_device_is_output(audio_device.name); + if((device_type == AudioDeviceType::OUTPUT && device_is_output) || (device_type == AudioDeviceType::INPUT && !device_is_output)) { + std::string description = audio_device.description; + if(starts_with(description, "Monitor of ")) + description.erase(0, 11); + audio_device_box->add_item(description, audio_device.name); + } } return audio_device_box; } - std::unique_ptr<Button> SettingsPage::create_remove_audio_device_button(List *audio_device_list_ptr) { - auto remove_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Remove", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - remove_audio_track_button->on_click = [this, audio_device_list_ptr]() { - audio_track_list_ptr->remove_widget(audio_device_list_ptr); + static void set_application_audio_options_visible(Subsection *audio_track_subsection, bool visible, const GsrInfo &gsr_info) { + if(!gsr_info.system_info.supports_app_audio) + visible = false; + + List *audio_track_items_list = dynamic_cast<List*>(audio_track_subsection->get_inner_widget()); + + List *buttons_list = dynamic_cast<List*>(audio_track_items_list->get_child_widget_by_index(1)); + Button *add_application_audio_button = dynamic_cast<Button*>(buttons_list->get_child_widget_by_index(2)); + add_application_audio_button->set_visible(visible); + + CheckBox *invert_app_audio_checkbox = dynamic_cast<CheckBox*>(audio_track_items_list->get_child_widget_by_index(3)); + invert_app_audio_checkbox->set_visible(visible); + } + + static void set_application_audio_options_visible(List *audio_track_section_list_ptr, bool visible, const GsrInfo &gsr_info) { + audio_track_section_list_ptr->for_each_child_widget([visible, &gsr_info](std::unique_ptr<Widget> &widget) { + Subsection *audio_track_subsection = dynamic_cast<Subsection*>(widget.get()); + set_application_audio_options_visible(audio_track_subsection, visible, gsr_info); + return true; + }); + } + + std::unique_ptr<Button> SettingsPage::create_remove_audio_device_button(List *audio_input_list_ptr, List *audio_device_list_ptr) { + auto remove_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 0)); + remove_audio_track_button->set_icon(&get_theme().trash_texture); + remove_audio_track_button->set_icon_padding_scale(0.75f); + remove_audio_track_button->on_click = [audio_input_list_ptr, audio_device_list_ptr]() { + audio_input_list_ptr->remove_widget(audio_device_list_ptr); }; return remove_audio_track_button; } - std::unique_ptr<List> SettingsPage::create_audio_device() { + std::unique_ptr<List> SettingsPage::create_audio_device(AudioDeviceType device_type, List *audio_input_list_ptr) { auto audio_device_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); audio_device_list->userdata = (void*)(uintptr_t)AudioTrackType::DEVICE; - audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Device:", get_color_theme().text_color)); - audio_device_list->add_widget(create_audio_device_selection_combobox()); - audio_device_list->add_widget(create_remove_audio_device_button(audio_device_list.get())); + audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, device_type == AudioDeviceType::OUTPUT ? "Output device:" : "Input device: ", get_color_theme().text_color)); + audio_device_list->add_widget(create_audio_device_selection_combobox(device_type)); + audio_device_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, audio_device_list.get())); return audio_device_list; } - std::unique_ptr<Button> SettingsPage::create_add_audio_device_button() { - auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add audio device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - add_audio_track_button->on_click = [this]() { + std::unique_ptr<Button> SettingsPage::create_add_audio_track_button() { + auto button = std::make_unique<Button>(&get_theme().body_font, "Add audio track", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + button->on_click = [this]() { + audio_track_section_list_ptr->add_widget(create_audio_track_section(audio_section_ptr)); + }; + button->set_visible(type != Type::STREAM); + return button; + } + + std::unique_ptr<Button> SettingsPage::create_add_audio_output_device_button(List *audio_input_list_ptr) { + auto button = std::make_unique<Button>(&get_theme().body_font, "Add output device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + button->on_click = [this, audio_input_list_ptr]() { audio_devices = get_audio_devices(); - audio_track_list_ptr->add_widget(create_audio_device()); + audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::OUTPUT, audio_input_list_ptr)); }; - return add_audio_track_button; + return button; + } + + std::unique_ptr<Button> SettingsPage::create_add_audio_input_device_button(List *audio_input_list_ptr) { + auto button = std::make_unique<Button>(&get_theme().body_font, "Add input device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + button->on_click = [this, audio_input_list_ptr]() { + audio_devices = get_audio_devices(); + audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::INPUT, audio_input_list_ptr)); + }; + return button; } - std::unique_ptr<ComboBox> SettingsPage::create_application_audio_selection_combobox() { + std::unique_ptr<ComboBox> SettingsPage::create_application_audio_selection_combobox(List *application_audio_row) { auto audio_device_box = std::make_unique<ComboBox>(&get_theme().body_font); + ComboBox *audio_device_box_ptr = audio_device_box.get(); for(const auto &app_audio : application_audio) { audio_device_box->add_item(app_audio, app_audio); } + audio_device_box->add_item("Custom...", custom_app_audio_tag); + + audio_device_box->on_selection_changed = [application_audio_row, audio_device_box_ptr](const std::string&, const std::string &id) { + if(id == custom_app_audio_tag) { + application_audio_row->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION_CUSTOM; + auto custom_app_audio_entry = std::make_unique<Entry>(&get_theme().body_font, "", (int)(get_theme().body_font.get_character_size() * 10.0f)); + application_audio_row->replace_widget(audio_device_box_ptr, std::move(custom_app_audio_entry)); + } + }; + return audio_device_box; } - std::unique_ptr<List> SettingsPage::create_application_audio() { + std::unique_ptr<List> SettingsPage::create_application_audio(List *audio_input_list_ptr) { auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION; - application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "App: ", get_color_theme().text_color)); - application_audio_list->add_widget(create_application_audio_selection_combobox()); - application_audio_list->add_widget(create_remove_audio_device_button(application_audio_list.get())); + application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color)); + application_audio_list->add_widget(create_application_audio_selection_combobox(application_audio_list.get())); + application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get())); return application_audio_list; } - std::unique_ptr<List> SettingsPage::create_custom_application_audio() { + std::unique_ptr<List> SettingsPage::create_custom_application_audio(List *audio_input_list_ptr) { auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION_CUSTOM; - application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "App: ", get_color_theme().text_color)); + application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color)); application_audio_list->add_widget(std::make_unique<Entry>(&get_theme().body_font, "", (int)(get_theme().body_font.get_character_size() * 10.0f))); - application_audio_list->add_widget(create_remove_audio_device_button(application_audio_list.get())); + application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get())); return application_audio_list; } - std::unique_ptr<Button> SettingsPage::create_add_application_audio_button() { + std::unique_ptr<Button> SettingsPage::create_add_application_audio_button(List *audio_input_list_ptr) { auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add application audio", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - add_application_audio_button_ptr = add_audio_track_button.get(); - add_audio_track_button->on_click = [this]() { + add_audio_track_button->on_click = [this, audio_input_list_ptr]() { application_audio = get_application_audio(); - audio_track_list_ptr->add_widget(create_application_audio()); - }; - return add_audio_track_button; - } - - std::unique_ptr<Button> SettingsPage::create_add_custom_application_audio_button() { - auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add custom application audio", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); - add_custom_application_audio_button_ptr = add_audio_track_button.get(); - add_audio_track_button->on_click = [this]() { - audio_track_list_ptr->add_widget(create_custom_application_audio()); + if(application_audio.empty()) + audio_input_list_ptr->add_widget(create_custom_application_audio(audio_input_list_ptr)); + else + audio_input_list_ptr->add_widget(create_application_audio(audio_input_list_ptr)); }; return add_audio_track_button; } - std::unique_ptr<List> SettingsPage::create_add_audio_buttons() { + std::unique_ptr<List> SettingsPage::create_add_audio_buttons(List *audio_input_list_ptr) { auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); - list->add_widget(create_add_audio_device_button()); - list->add_widget(create_add_application_audio_button()); - list->add_widget(create_add_custom_application_audio_button()); + list->add_widget(create_add_audio_output_device_button(audio_input_list_ptr)); + list->add_widget(create_add_audio_input_device_button(audio_input_list_ptr)); + list->add_widget(create_add_application_audio_button(audio_input_list_ptr)); return list; } - std::unique_ptr<List> SettingsPage::create_audio_track_track_section() { + std::unique_ptr<List> SettingsPage::create_audio_input_section() { auto list = std::make_unique<List>(List::Orientation::VERTICAL); - audio_track_list_ptr = list.get(); - audio_track_list_ptr->add_widget(create_audio_device()); // Add default_output by default + //list->add_widget(create_audio_device(list.get())); // Add default_output by default return list; } - std::unique_ptr<CheckBox> SettingsPage::create_merge_audio_tracks_checkbox() { - auto merge_audio_tracks_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Merge audio tracks"); - merge_audio_tracks_checkbox->set_checked(true); - merge_audio_tracks_checkbox_ptr = merge_audio_tracks_checkbox.get(); - return merge_audio_tracks_checkbox; - } - std::unique_ptr<CheckBox> SettingsPage::create_application_audio_invert_checkbox() { auto application_audio_invert_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record audio from all applications except the selected ones"); application_audio_invert_checkbox->set_checked(false); - application_audio_invert_checkbox_ptr = application_audio_invert_checkbox.get(); return application_audio_invert_checkbox; } - std::unique_ptr<Widget> SettingsPage::create_audio_track_section() { + static void update_audio_track_titles(List *audio_track_section_list_ptr) { + int index = 0; + audio_track_section_list_ptr->for_each_child_widget([&index](std::unique_ptr<Widget> &widget) { + char audio_track_name[32]; + snprintf(audio_track_name, sizeof(audio_track_name), "Audio track #%d", 1 + index); + ++index; + + Subsection *subsection = dynamic_cast<Subsection*>(widget.get()); + List *subesection_items = dynamic_cast<List*>(subsection->get_inner_widget()); + Label *audio_track_title = dynamic_cast<Label*>(dynamic_cast<List*>(subesection_items->get_child_widget_by_index(0))->get_child_widget_by_index(0)); + audio_track_title->set_text(audio_track_name); + return true; + }); + } + + std::unique_ptr<List> SettingsPage::create_audio_track_title_and_remove(Subsection *audio_track_subsection, const char *title) { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + list->add_widget(std::make_unique<Label>(&get_theme().title_font, title, get_color_theme().text_color)); + + auto remove_track_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 0)); + remove_track_button->set_icon(&get_theme().trash_texture); + remove_track_button->set_icon_padding_scale(0.75f); + remove_track_button->on_click = [this, audio_track_subsection]() { + audio_track_section_list_ptr->remove_widget(audio_track_subsection); + update_audio_track_titles(audio_track_section_list_ptr); + }; + list->add_widget(std::move(remove_track_button)); + list->set_visible(type != Type::STREAM); + return list; + } + + std::unique_ptr<Subsection> SettingsPage::create_audio_track_section(Widget *parent_widget) { + char audio_track_name[32]; + snprintf(audio_track_name, sizeof(audio_track_name), "Audio track #%d", 1 + (int)audio_track_section_list_ptr->get_num_children()); + + auto audio_input_section = create_audio_input_section(); + List *audio_input_section_ptr = audio_input_section.get(); + + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + List *list_ptr = list.get(); + auto subsection = std::make_unique<Subsection>("", std::move(std::move(list)), mgl::vec2f(parent_widget->get_inner_size().x, 0.0f)); + subsection->set_bg_color(mgl::Color(35, 40, 44)); + + list_ptr->add_widget(create_audio_track_title_and_remove(subsection.get(), audio_track_name)); + list_ptr->add_widget(create_add_audio_buttons(audio_input_section_ptr)); + list_ptr->add_widget(std::move(audio_input_section)); + list_ptr->add_widget(create_application_audio_invert_checkbox()); + + set_application_audio_options_visible(subsection.get(), view_radio_button_ptr->get_selected_id() == "advanced", *gsr_info); + return subsection; + } + + std::unique_ptr<List> SettingsPage::create_audio_track_section_list() { auto list = std::make_unique<List>(List::Orientation::VERTICAL); - list->add_widget(create_add_audio_buttons()); - list->add_widget(create_audio_track_track_section()); + audio_track_section_list_ptr = list.get(); return list; } std::unique_ptr<Widget> SettingsPage::create_audio_section() { auto audio_device_section_list = std::make_unique<List>(List::Orientation::VERTICAL); - audio_device_section_list->add_widget(create_audio_track_section()); - audio_device_section_list->add_widget(create_merge_audio_tracks_checkbox()); - audio_device_section_list->add_widget(create_application_audio_invert_checkbox()); - audio_device_section_list->add_widget(create_audio_codec()); - return std::make_unique<Subsection>("Audio", std::move(audio_device_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + List *audio_device_section_list_ptr = audio_device_section_list.get(); + + auto subsection = std::make_unique<Subsection>("Audio", std::move(audio_device_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); + audio_section_ptr = subsection.get(); + audio_device_section_list_ptr->add_widget(create_add_audio_track_button()); + audio_device_section_list_ptr->add_widget(create_audio_track_section_list()); + audio_device_section_list_ptr->add_widget(create_audio_codec()); + return subsection; } std::unique_ptr<List> SettingsPage::create_video_quality_box() { @@ -338,16 +439,32 @@ namespace gsr { return list; } - std::unique_ptr<Entry> SettingsPage::create_video_bitrate_entry() { - auto video_bitrate_entry = std::make_unique<Entry>(&get_theme().body_font, "15000", (int)(get_theme().body_font.get_character_size() * 4.0f)); + std::unique_ptr<List> SettingsPage::create_video_bitrate_entry() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + auto video_bitrate_entry = std::make_unique<Entry>(&get_theme().body_font, "8000", (int)(get_theme().body_font.get_character_size() * 4.0f)); video_bitrate_entry->validate_handler = create_entry_validator_integer_in_range(1, 500000); video_bitrate_entry_ptr = video_bitrate_entry.get(); - return video_bitrate_entry; + list->add_widget(std::move(video_bitrate_entry)); + + if(type == Type::STREAM) { + auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "", get_color_theme().text_color); + Label *size_mb_label_ptr = size_mb_label.get(); + list->add_widget(std::move(size_mb_label)); + + video_bitrate_entry_ptr->on_changed = [size_mb_label_ptr](const std::string &text) { + const double video_bitrate_mbits_per_seconds = (double)atoi(text.c_str()) / 1024.0; + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.2fMbps", video_bitrate_mbits_per_seconds); + size_mb_label_ptr->set_text(buffer); + }; + } + + return list; } std::unique_ptr<List> SettingsPage::create_video_bitrate() { auto video_bitrate_list = std::make_unique<List>(List::Orientation::VERTICAL); - video_bitrate_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video bitrate (kbps):", get_color_theme().text_color)); + video_bitrate_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video bitrate (Kbps):", get_color_theme().text_color)); video_bitrate_list->add_widget(create_video_bitrate_entry()); video_bitrate_list_ptr = video_bitrate_list.get(); return video_bitrate_list; @@ -387,20 +504,20 @@ namespace gsr { video_codec_box->add_item("H264", "h264"); if(gsr_info->supported_video_codecs.hevc) video_codec_box->add_item("HEVC", "hevc"); + if(gsr_info->supported_video_codecs.hevc_10bit) + video_codec_box->add_item("HEVC (10 bit, reduces banding)", "hevc_10bit"); + if(gsr_info->supported_video_codecs.hevc_hdr) + video_codec_box->add_item("HEVC (HDR)", "hevc_hdr"); if(gsr_info->supported_video_codecs.av1) video_codec_box->add_item("AV1", "av1"); + if(gsr_info->supported_video_codecs.av1_10bit) + video_codec_box->add_item("AV1 (10 bit, reduces banding)", "av1_10bit"); + if(gsr_info->supported_video_codecs.av1_hdr) + video_codec_box->add_item("AV1 (HDR)", "av1_hdr"); if(gsr_info->supported_video_codecs.vp8) video_codec_box->add_item("VP8", "vp8"); if(gsr_info->supported_video_codecs.vp9) video_codec_box->add_item("VP9", "vp9"); - if(gsr_info->supported_video_codecs.hevc_hdr) - video_codec_box->add_item("HEVC (HDR)", "hevc_hdr"); - if(gsr_info->supported_video_codecs.hevc_10bit) - video_codec_box->add_item("HEVC (10 bit, reduces banding)", "hevc_10bit"); - if(gsr_info->supported_video_codecs.av1_hdr) - video_codec_box->add_item("AV1 (HDR)", "av1_hdr"); - if(gsr_info->supported_video_codecs.av1_10bit) - video_codec_box->add_item("AV1 (10 bit, reduces banding)", "av1_10bit"); if(gsr_info->supported_video_codecs.h264_software) video_codec_box->add_item("H264 Software Encoder (Slow, not recommended)", "h264_software"); video_codec_box_ptr = video_codec_box.get(); @@ -495,7 +612,7 @@ namespace gsr { auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL); settings_list->set_spacing(0.018f); - settings_list->add_widget(create_capture_target()); + settings_list->add_widget(create_capture_target_section()); settings_list->add_widget(create_audio_section()); settings_list->add_widget(create_video_section()); settings_list_ptr = settings_list.get(); @@ -506,16 +623,14 @@ namespace gsr { void SettingsPage::add_widgets() { content_page_ptr->add_widget(create_settings()); - record_area_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { - (void)text; - const bool window_selected = id == "window"; + record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { const bool focused_selected = id == "focused"; const bool portal_selected = id == "portal"; - select_window_list_ptr->set_visible(window_selected); area_size_list_ptr->set_visible(focused_selected); video_resolution_list_ptr->set_visible(!focused_selected && change_video_resolution_checkbox_ptr->is_checked()); change_video_resolution_checkbox_ptr->set_visible(!focused_selected); restore_portal_session_list_ptr->set_visible(portal_selected); + return true; }; change_video_resolution_checkbox_ptr->on_changed = [this](bool checked) { @@ -523,30 +638,25 @@ namespace gsr { video_resolution_list_ptr->set_visible(!focused_selected && checked); }; - video_quality_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { - (void)text; + video_quality_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { const bool custom_selected = id == "custom"; video_bitrate_list_ptr->set_visible(custom_selected); if(estimated_file_size_ptr) estimated_file_size_ptr->set_visible(custom_selected); + + return true; }; video_quality_box_ptr->on_selection_changed("", video_quality_box_ptr->get_selected_id()); if(!capture_options.monitors.empty()) - record_area_box_ptr->set_selected_item(capture_options.monitors.front().name); + record_area_box_ptr->set_selected_item("focused_monitor"); else if(capture_options.portal) record_area_box_ptr->set_selected_item("portal"); else if(capture_options.window) record_area_box_ptr->set_selected_item("window"); else record_area_box_ptr->on_selection_changed("", ""); - - if(!gsr_info->system_info.supports_app_audio) { - add_application_audio_button_ptr->set_visible(false); - add_custom_application_audio_button_ptr->set_visible(false); - application_audio_invert_checkbox_ptr->set_visible(false); - } } void SettingsPage::add_page_specific_widgets() { @@ -569,7 +679,7 @@ namespace gsr { auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_videos_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); save_directory_button_ptr = save_directory_button.get(); save_directory_button->on_click = [this]() { - auto select_directory_page = std::make_unique<GsrPage>(); + auto select_directory_page = std::make_unique<GsrPage>("File", "Settings"); select_directory_page->add_button("Save", "save", get_color_theme().tint_color); select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color); @@ -609,20 +719,46 @@ namespace gsr { return container_list; } - std::unique_ptr<Entry> SettingsPage::create_replay_time_entry() { + std::unique_ptr<List> SettingsPage::create_replay_time_entry() { + auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); + auto replay_time_entry = std::make_unique<Entry>(&get_theme().body_font, "60", get_theme().body_font.get_character_size() * 3); - replay_time_entry->validate_handler = create_entry_validator_integer_in_range(1, 1200); + replay_time_entry->validate_handler = create_entry_validator_integer_in_range(1, 86400); replay_time_entry_ptr = replay_time_entry.get(); - return replay_time_entry; + list->add_widget(std::move(replay_time_entry)); + + auto replay_time_label = std::make_unique<Label>(&get_theme().body_font, "00h:00m:00s", get_color_theme().text_color); + replay_time_label_ptr = replay_time_label.get(); + list->add_widget(std::move(replay_time_label)); + + return list; } std::unique_ptr<List> SettingsPage::create_replay_time() { auto replay_time_list = std::make_unique<List>(List::Orientation::VERTICAL); - replay_time_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Replay time in seconds:", get_color_theme().text_color)); + replay_time_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Replay duration in seconds:", get_color_theme().text_color)); replay_time_list->add_widget(create_replay_time_entry()); return replay_time_list; } + std::unique_ptr<List> SettingsPage::create_replay_storage() { + auto list = std::make_unique<List>(List::Orientation::VERTICAL); + list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Where should temporary replay data be stored?", get_color_theme().text_color)); + auto replay_storage_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL); + replay_storage_button_ptr = replay_storage_button.get(); + replay_storage_button->add_item("RAM", "ram"); + replay_storage_button->add_item("Disk (Not recommended on SSDs)", "disk"); + + replay_storage_button->on_selection_changed = [this](const std::string&, const std::string &id) { + update_estimated_replay_file_size(id); + return true; + }; + + list->add_widget(std::move(replay_storage_button)); + list->set_visible(gsr_info->system_info.gsr_version >= GsrVersion{5, 5, 0}); + return list; + } + std::unique_ptr<RadioButton> SettingsPage::create_start_replay_automatically() { char fullscreen_text[256]; snprintf(fullscreen_text, sizeof(fullscreen_text), "Turn on replay when starting a fullscreen application%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)"); @@ -644,22 +780,52 @@ namespace gsr { return checkbox; } - std::unique_ptr<Label> SettingsPage::create_estimated_file_size() { - auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video max file size in RAM: 5.23MB", get_color_theme().text_color); + std::unique_ptr<CheckBox> SettingsPage::create_restart_replay_on_save() { + auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Restart replay on save"); + restart_replay_on_save = checkbox.get(); + return checkbox; + } + + std::unique_ptr<Label> SettingsPage::create_estimated_replay_file_size() { + auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video max file size in RAM: 57.60MB", get_color_theme().text_color); estimated_file_size_ptr = label.get(); return label; } - void SettingsPage::update_estimated_file_size() { + void SettingsPage::update_estimated_replay_file_size(const std::string &replay_storage_type) { const int64_t replay_time_seconds = atoi(replay_time_entry_ptr->get_text().c_str()); const int64_t video_bitrate_bps = atoi(video_bitrate_entry_ptr->get_text().c_str()) * 1000LL / 8LL; - const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1024.0 / 1024.0; + const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024; - char buffer[512]; - snprintf(buffer, sizeof(buffer), "Estimated video max file size in RAM: %.2fMB", video_filesize_mb); + char buffer[256]; + snprintf(buffer, sizeof(buffer), "Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.", replay_storage_type == "ram" ? "in RAM" : "on disk", video_filesize_mb); estimated_file_size_ptr->set_text(buffer); } + void SettingsPage::update_replay_time_text() { + int seconds = atoi(replay_time_entry_ptr->get_text().c_str()); + + const int hours = seconds / 60 / 60; + seconds -= (hours * 60 * 60); + + const int minutes = seconds / 60; + seconds -= (minutes * 60); + + char buffer[256]; + snprintf(buffer, sizeof(buffer), "%02dh:%02dm:%02ds", hours, minutes, seconds); + replay_time_label_ptr->set_text(buffer); + } + + void SettingsPage::view_changed(bool advanced_view, Subsection *notifications_subsection_ptr) { + color_range_list_ptr->set_visible(advanced_view); + audio_codec_ptr->set_visible(advanced_view); + video_codec_ptr->set_visible(advanced_view); + framerate_mode_list_ptr->set_visible(advanced_view); + notifications_subsection_ptr->set_visible(advanced_view); + set_application_audio_options_visible(audio_track_section_list_ptr, advanced_view, *gsr_info); + settings_scrollable_page_ptr->reset_scroll(); + } + void SettingsPage::add_replay_widgets() { auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL); auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL); @@ -667,14 +833,18 @@ namespace gsr { file_info_data_list->add_widget(create_container_section()); file_info_data_list->add_widget(create_replay_time()); file_info_list->add_widget(std::move(file_info_data_list)); - file_info_list->add_widget(create_estimated_file_size()); + file_info_list->add_widget(create_estimated_replay_file_size()); settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); auto general_list = std::make_unique<List>(List::Orientation::VERTICAL); - general_list->add_widget(create_start_replay_automatically()); + general_list->add_widget(create_replay_storage()); general_list->add_widget(create_save_replay_in_game_folder()); + if(gsr_info->system_info.gsr_version >= GsrVersion{5, 0, 3}) + general_list->add_widget(create_restart_replay_on_save()); settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); + settings_list_ptr->add_widget(std::make_unique<Subsection>("Autostart", create_start_replay_automatically(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); + auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); auto show_replay_started_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show replay started notification"); @@ -696,24 +866,19 @@ namespace gsr { Subsection *notifications_subsection_ptr = notifications_subsection.get(); settings_list_ptr->add_widget(std::move(notifications_subsection)); - view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { - (void)text; - const bool advanced_view = id == "advanced"; - color_range_list_ptr->set_visible(advanced_view); - audio_codec_ptr->set_visible(advanced_view); - video_codec_ptr->set_visible(advanced_view); - framerate_mode_list_ptr->set_visible(advanced_view); - notifications_subsection_ptr->set_visible(advanced_view); - settings_scrollable_page_ptr->reset_scroll(); + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string&, const std::string &id) { + view_changed(id == "advanced", notifications_subsection_ptr); + return true; }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); replay_time_entry_ptr->on_changed = [this](const std::string&) { - update_estimated_file_size(); + update_estimated_replay_file_size(replay_storage_button_ptr->get_selected_id()); + update_replay_time_text(); }; video_bitrate_entry_ptr->on_changed = [this](const std::string&) { - update_estimated_file_size(); + update_estimated_replay_file_size(replay_storage_button_ptr->get_selected_id()); }; } @@ -725,15 +890,31 @@ namespace gsr { return checkbox; } + std::unique_ptr<Label> SettingsPage::create_estimated_record_file_size() { + auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video file size per minute (excluding audio): 345.60MB", get_color_theme().text_color); + estimated_file_size_ptr = label.get(); + return label; + } + + void SettingsPage::update_estimated_record_file_size() { + const int64_t video_bitrate_bps = atoi(video_bitrate_entry_ptr->get_text().c_str()) * 1000LL / 8LL; + const double video_filesize_mb_per_minute = (60.0 * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024; + + char buffer[512]; + snprintf(buffer, sizeof(buffer), "Estimated video file size per minute (excluding audio): %.2fMB", video_filesize_mb_per_minute); + estimated_file_size_ptr->set_text(buffer); + } + void SettingsPage::add_record_widgets() { - auto file_list = std::make_unique<List>(List::Orientation::HORIZONTAL); - file_list->add_widget(create_save_directory("Directory to save the video:")); - file_list->add_widget(create_container_section()); - settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); + auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL); + auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL); + file_info_data_list->add_widget(create_save_directory("Directory to save the video:")); + file_info_data_list->add_widget(create_container_section()); + file_info_list->add_widget(std::move(file_info_data_list)); + file_info_list->add_widget(create_estimated_record_file_size()); + settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); - auto general_list = std::make_unique<List>(List::Orientation::VERTICAL); - general_list->add_widget(create_save_recording_in_game_folder()); - settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); + settings_list_ptr->add_widget(std::make_unique<Subsection>("General", create_save_recording_in_game_folder(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f))); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); @@ -747,27 +928,31 @@ namespace gsr { show_video_saved_notification_checkbox_ptr = show_video_saved_notification_checkbox.get(); checkboxes_list->add_widget(std::move(show_video_saved_notification_checkbox)); + auto show_video_paused_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show video paused/unpaused notification"); + show_video_paused_notification_checkbox->set_checked(true); + show_video_paused_notification_checkbox_ptr = show_video_paused_notification_checkbox.get(); + checkboxes_list->add_widget(std::move(show_video_paused_notification_checkbox)); + auto notifications_subsection = std::make_unique<Subsection>("Notifications", std::move(checkboxes_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)); Subsection *notifications_subsection_ptr = notifications_subsection.get(); settings_list_ptr->add_widget(std::move(notifications_subsection)); - view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { - (void)text; - const bool advanced_view = id == "advanced"; - color_range_list_ptr->set_visible(advanced_view); - audio_codec_ptr->set_visible(advanced_view); - video_codec_ptr->set_visible(advanced_view); - framerate_mode_list_ptr->set_visible(advanced_view); - notifications_subsection_ptr->set_visible(advanced_view); - settings_scrollable_page_ptr->reset_scroll(); + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string&, const std::string &id) { + view_changed(id == "advanced", notifications_subsection_ptr); + return true; }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); + + video_bitrate_entry_ptr->on_changed = [this](const std::string&) { + update_estimated_record_file_size(); + }; } std::unique_ptr<ComboBox> SettingsPage::create_streaming_service_box() { auto streaming_service_box = std::make_unique<ComboBox>(&get_theme().body_font); streaming_service_box->add_item("Twitch", "twitch"); streaming_service_box->add_item("YouTube", "youtube"); + streaming_service_box->add_item("Rumble", "rumble"); streaming_service_box->add_item("Custom", "custom"); streaming_service_box_ptr = streaming_service_box.get(); return streaming_service_box; @@ -792,6 +977,10 @@ namespace gsr { youtube_stream_key_entry_ptr = youtube_stream_key_entry.get(); stream_key_list->add_widget(std::move(youtube_stream_key_entry)); + auto rumble_stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20); + rumble_stream_key_entry_ptr = rumble_stream_key_entry.get(); + stream_key_list->add_widget(std::move(rumble_stream_key_entry)); + stream_key_list_ptr = stream_key_list.get(); return stream_key_list; } @@ -850,28 +1039,24 @@ namespace gsr { Subsection *notifications_subsection_ptr = notifications_subsection.get(); settings_list_ptr->add_widget(std::move(notifications_subsection)); - streaming_service_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { - (void)text; + streaming_service_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { const bool twitch_option = id == "twitch"; const bool youtube_option = id == "youtube"; + const bool rumble_option = id == "rumble"; const bool custom_option = id == "custom"; stream_key_list_ptr->set_visible(!custom_option); stream_url_list_ptr->set_visible(custom_option); container_list_ptr->set_visible(custom_option); twitch_stream_key_entry_ptr->set_visible(twitch_option); youtube_stream_key_entry_ptr->set_visible(youtube_option); + rumble_stream_key_entry_ptr->set_visible(rumble_option); + return true; }; streaming_service_box_ptr->on_selection_changed("Twitch", "twitch"); - view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { - (void)text; - const bool advanced_view = id == "advanced"; - color_range_list_ptr->set_visible(advanced_view); - audio_codec_ptr->set_visible(advanced_view); - video_codec_ptr->set_visible(advanced_view); - framerate_mode_list_ptr->set_visible(advanced_view); - notifications_subsection_ptr->set_visible(advanced_view); - settings_scrollable_page_ptr->reset_scroll(); + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string&, const std::string &id) { + view_changed(id == "advanced", notifications_subsection_ptr); + return true; }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); } @@ -921,49 +1106,64 @@ namespace gsr { return nullptr; } - static bool starts_with(std::string_view str, const char *substr) { - size_t len = strlen(substr); - return str.size() >= len && memcmp(str.data(), substr, len) == 0; - } - void SettingsPage::load_audio_tracks(const RecordOptions &record_options) { - audio_track_list_ptr->clear(); - for(const std::string &audio_track : record_options.audio_tracks) { - if(starts_with(audio_track, "app:")) { - if(!gsr_info->system_info.supports_app_audio) - continue; - - std::string audio_track_name = audio_track.substr(4); - const std::string *app_audio = get_application_audio_by_name_case_insensitive(application_audio, audio_track_name); - if(app_audio) { - std::unique_ptr<List> application_audio_widget = create_application_audio(); - ComboBox *application_audio_box = static_cast<ComboBox*>(application_audio_widget->get_child_widget_by_index(1)); - application_audio_box->set_selected_item(*app_audio); - audio_track_list_ptr->add_widget(std::move(application_audio_widget)); + audio_track_section_list_ptr->clear(); + for(const AudioTrack &audio_track : record_options.audio_tracks_list) { + auto audio_track_section = create_audio_track_section(audio_section_ptr); + List *audio_track_section_items_list_ptr = dynamic_cast<List*>(audio_track_section->get_inner_widget()); + List *audio_input_list_ptr = dynamic_cast<List*>(audio_track_section_items_list_ptr->get_child_widget_by_index(2)); + CheckBox *application_audio_invert_checkbox_ptr = dynamic_cast<CheckBox*>(audio_track_section_items_list_ptr->get_child_widget_by_index(3)); + application_audio_invert_checkbox_ptr->set_checked(audio_track.application_audio_invert); + + audio_input_list_ptr->clear(); + for(const std::string &audio_input : audio_track.audio_inputs) { + if(starts_with(audio_input, "app:")) { + if(!gsr_info->system_info.supports_app_audio) + continue; + + std::string audio_track_name = audio_input.substr(4); + const std::string *app_audio = get_application_audio_by_name_case_insensitive(application_audio, audio_track_name); + if(app_audio) { + std::unique_ptr<List> application_audio_widget = create_application_audio(audio_input_list_ptr); + ComboBox *application_audio_box = dynamic_cast<ComboBox*>(application_audio_widget->get_child_widget_by_index(1)); + application_audio_box->set_selected_item(*app_audio); + audio_input_list_ptr->add_widget(std::move(application_audio_widget)); + } else { + std::unique_ptr<List> application_audio_widget = create_custom_application_audio(audio_input_list_ptr); + Entry *application_audio_entry = dynamic_cast<Entry*>(application_audio_widget->get_child_widget_by_index(1)); + application_audio_entry->set_text(std::move(audio_track_name)); + audio_input_list_ptr->add_widget(std::move(application_audio_widget)); + } + } else if(starts_with(audio_input, "device:")) { + const std::string device_name = audio_input.substr(7); + const AudioDeviceType audio_device_type = audio_device_is_output(device_name) ? AudioDeviceType::OUTPUT : AudioDeviceType::INPUT; + std::unique_ptr<List> audio_track_widget = create_audio_device(audio_device_type, audio_input_list_ptr); + ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); + audio_device_box->set_selected_item(device_name); + audio_input_list_ptr->add_widget(std::move(audio_track_widget)); } else { - std::unique_ptr<List> application_audio_widget = create_custom_application_audio(); - Entry *application_audio_entry = static_cast<Entry*>(application_audio_widget->get_child_widget_by_index(1)); - application_audio_entry->set_text(std::move(audio_track_name)); - audio_track_list_ptr->add_widget(std::move(application_audio_widget)); + const AudioDeviceType audio_device_type = audio_device_is_output(audio_input) ? AudioDeviceType::OUTPUT : AudioDeviceType::INPUT; + std::unique_ptr<List> audio_track_widget = create_audio_device(audio_device_type, audio_input_list_ptr); + ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); + audio_device_box->set_selected_item(audio_input); + audio_input_list_ptr->add_widget(std::move(audio_track_widget)); } - } else if(starts_with(audio_track, "device:")) { - std::unique_ptr<List> audio_track_widget = create_audio_device(); - ComboBox *audio_device_box = static_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); - audio_device_box->set_selected_item(audio_track.substr(7)); - audio_track_list_ptr->add_widget(std::move(audio_track_widget)); - } else { - std::unique_ptr<List> audio_track_widget = create_audio_device(); - ComboBox *audio_device_box = static_cast<ComboBox*>(audio_track_widget->get_child_widget_by_index(1)); - audio_device_box->set_selected_item(audio_track); - audio_track_list_ptr->add_widget(std::move(audio_track_widget)); } + + audio_track_section_list_ptr->add_widget(std::move(audio_track_section)); + + if(type == Type::STREAM) + break; + } + + if(type == Type::STREAM && audio_track_section_list_ptr->get_num_children() == 0) { + auto audio_track_section = create_audio_track_section(audio_section_ptr); + audio_track_section_list_ptr->add_widget(std::move(audio_track_section)); } } void SettingsPage::load_common(RecordOptions &record_options) { record_area_box_ptr->set_selected_item(record_options.record_area_option); - merge_audio_tracks_checkbox_ptr->set_checked(record_options.merge_audio_tracks); - application_audio_invert_checkbox_ptr->set_checked(record_options.application_audio_invert); change_video_resolution_checkbox_ptr->set_checked(record_options.change_video_resolution); load_audio_tracks(record_options); color_range_box_ptr->set_selected_item(record_options.color_range); @@ -1016,16 +1216,21 @@ namespace gsr { void SettingsPage::load_replay() { load_common(config.replay_config.record_options); + replay_storage_button_ptr->set_selected_item(config.replay_config.replay_storage); turn_on_replay_automatically_mode_ptr->set_selected_item(config.replay_config.turn_on_replay_automatically_mode); save_replay_in_game_folder_ptr->set_checked(config.replay_config.save_video_in_game_folder); + if(restart_replay_on_save) + restart_replay_on_save->set_checked(config.replay_config.restart_replay_on_save); show_replay_started_notification_checkbox_ptr->set_checked(config.replay_config.show_replay_started_notifications); show_replay_stopped_notification_checkbox_ptr->set_checked(config.replay_config.show_replay_stopped_notifications); show_replay_saved_notification_checkbox_ptr->set_checked(config.replay_config.show_replay_saved_notifications); save_directory_button_ptr->set_text(config.replay_config.save_directory); container_box_ptr->set_selected_item(config.replay_config.container); - if(config.replay_config.replay_time < 5) - config.replay_config.replay_time = 5; + if(config.replay_config.replay_time < 2) + config.replay_config.replay_time = 2; + if(config.replay_config.replay_time > 86400) + config.replay_config.replay_time = 86400; replay_time_entry_ptr->set_text(std::to_string(config.replay_config.replay_time)); } @@ -1034,6 +1239,7 @@ namespace gsr { save_recording_in_game_folder_ptr->set_checked(config.record_config.save_video_in_game_folder); show_recording_started_notification_checkbox_ptr->set_checked(config.record_config.show_recording_started_notifications); show_video_saved_notification_checkbox_ptr->set_checked(config.record_config.show_video_saved_notifications); + show_video_paused_notification_checkbox_ptr->set_checked(config.record_config.show_video_paused_notifications); save_directory_button_ptr->set_text(config.record_config.save_directory); container_box_ptr->set_selected_item(config.record_config.container); } @@ -1045,32 +1251,43 @@ namespace gsr { streaming_service_box_ptr->set_selected_item(config.streaming_config.streaming_service); youtube_stream_key_entry_ptr->set_text(config.streaming_config.youtube.stream_key); twitch_stream_key_entry_ptr->set_text(config.streaming_config.twitch.stream_key); + rumble_stream_key_entry_ptr->set_text(config.streaming_config.rumble.stream_key); stream_url_entry_ptr->set_text(config.streaming_config.custom.url); container_box_ptr->set_selected_item(config.streaming_config.custom.container); } - static void save_audio_tracks(std::vector<std::string> &audio_devices, List *audio_devices_list_ptr) { - audio_devices.clear(); - audio_devices_list_ptr->for_each_child_widget([&audio_devices](std::unique_ptr<Widget> &child_widget) { - List *audio_track_line = static_cast<List*>(child_widget.get()); - const AudioTrackType audio_track_type = (AudioTrackType)(uintptr_t)audio_track_line->userdata; - switch(audio_track_type) { - case AudioTrackType::DEVICE: { - ComboBox *audio_device_box = static_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); - audio_devices.push_back("device:" + audio_device_box->get_selected_id()); - break; + static void save_audio_tracks(std::vector<AudioTrack> &audio_tracks, List *audio_track_section_list_ptr) { + audio_tracks.clear(); + audio_track_section_list_ptr->for_each_child_widget([&audio_tracks](std::unique_ptr<Widget> &child_widget) { + Subsection *audio_subsection = dynamic_cast<Subsection*>(child_widget.get()); + List *audio_track_section_items_list_ptr = dynamic_cast<List*>(audio_subsection->get_inner_widget()); + List *audio_input_list_ptr = dynamic_cast<List*>(audio_track_section_items_list_ptr->get_child_widget_by_index(2)); + CheckBox *application_audio_invert_checkbox_ptr = dynamic_cast<CheckBox*>(audio_track_section_items_list_ptr->get_child_widget_by_index(3)); + + audio_tracks.push_back({std::vector<std::string>{}, application_audio_invert_checkbox_ptr->is_checked()}); + audio_input_list_ptr->for_each_child_widget([&audio_tracks](std::unique_ptr<Widget> &child_widget){ + List *audio_track_line = dynamic_cast<List*>(child_widget.get()); + const AudioTrackType audio_track_type = (AudioTrackType)(uintptr_t)audio_track_line->userdata; + switch(audio_track_type) { + case AudioTrackType::DEVICE: { + ComboBox *audio_device_box = dynamic_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); + audio_tracks.back().audio_inputs.push_back("device:" + audio_device_box->get_selected_id()); + break; + } + case AudioTrackType::APPLICATION: { + ComboBox *application_audio_box = dynamic_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); + audio_tracks.back().audio_inputs.push_back("app:" + application_audio_box->get_selected_id()); + break; + } + case AudioTrackType::APPLICATION_CUSTOM: { + Entry *application_audio_entry = dynamic_cast<Entry*>(audio_track_line->get_child_widget_by_index(1)); + audio_tracks.back().audio_inputs.push_back("app:" + application_audio_entry->get_text()); + break; + } } - case AudioTrackType::APPLICATION: { - ComboBox *application_audio_box = static_cast<ComboBox*>(audio_track_line->get_child_widget_by_index(1)); - audio_devices.push_back("app:" + application_audio_box->get_selected_id()); - break; - } - case AudioTrackType::APPLICATION_CUSTOM: { - Entry *application_audio_entry = static_cast<Entry*>(audio_track_line->get_child_widget_by_index(1)); - audio_devices.push_back("app:" + application_audio_entry->get_text()); - break; - } - } + return true; + }); + return true; }); } @@ -1083,10 +1300,8 @@ namespace gsr { record_options.video_height = atoi(video_height_entry_ptr->get_text().c_str()); record_options.fps = atoi(framerate_entry_ptr->get_text().c_str()); record_options.video_bitrate = atoi(video_bitrate_entry_ptr->get_text().c_str()); - record_options.merge_audio_tracks = merge_audio_tracks_checkbox_ptr->is_checked(); - record_options.application_audio_invert = application_audio_invert_checkbox_ptr->is_checked(); record_options.change_video_resolution = change_video_resolution_checkbox_ptr->is_checked(); - save_audio_tracks(record_options.audio_tracks, audio_track_list_ptr); + save_audio_tracks(record_options.audio_tracks_list, audio_track_section_list_ptr); record_options.color_range = color_range_box_ptr->get_selected_id(); record_options.video_quality = video_quality_box_ptr->get_selected_id(); record_options.video_codec = video_codec_box_ptr->get_selected_id(); @@ -1145,12 +1360,15 @@ namespace gsr { save_common(config.replay_config.record_options); config.replay_config.turn_on_replay_automatically_mode = turn_on_replay_automatically_mode_ptr->get_selected_id(); config.replay_config.save_video_in_game_folder = save_replay_in_game_folder_ptr->is_checked(); + if(restart_replay_on_save) + config.replay_config.restart_replay_on_save = restart_replay_on_save->is_checked(); config.replay_config.show_replay_started_notifications = show_replay_started_notification_checkbox_ptr->is_checked(); config.replay_config.show_replay_stopped_notifications = show_replay_stopped_notification_checkbox_ptr->is_checked(); config.replay_config.show_replay_saved_notifications = show_replay_saved_notification_checkbox_ptr->is_checked(); config.replay_config.save_directory = save_directory_button_ptr->get_text(); config.replay_config.container = container_box_ptr->get_selected_id(); config.replay_config.replay_time = atoi(replay_time_entry_ptr->get_text().c_str()); + config.replay_config.replay_storage = replay_storage_button_ptr->get_selected_id(); if(config.replay_config.replay_time < 5) { config.replay_config.replay_time = 5; @@ -1163,6 +1381,7 @@ namespace gsr { config.record_config.save_video_in_game_folder = save_recording_in_game_folder_ptr->is_checked(); config.record_config.show_recording_started_notifications = show_recording_started_notification_checkbox_ptr->is_checked(); config.record_config.show_video_saved_notifications = show_video_saved_notification_checkbox_ptr->is_checked(); + config.record_config.show_video_paused_notifications = show_video_paused_notification_checkbox_ptr->is_checked(); config.record_config.save_directory = save_directory_button_ptr->get_text(); config.record_config.container = container_box_ptr->get_selected_id(); } @@ -1174,6 +1393,7 @@ namespace gsr { config.streaming_config.streaming_service = streaming_service_box_ptr->get_selected_id(); config.streaming_config.youtube.stream_key = youtube_stream_key_entry_ptr->get_text(); config.streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text(); + config.streaming_config.rumble.stream_key = rumble_stream_key_entry_ptr->get_text(); config.streaming_config.custom.url = stream_url_entry_ptr->get_text(); config.streaming_config.custom.container = container_box_ptr->get_selected_id(); } diff --git a/src/gui/StaticPage.cpp b/src/gui/StaticPage.cpp index c014f38..5147819 100644 --- a/src/gui/StaticPage.cpp +++ b/src/gui/StaticPage.cpp @@ -20,8 +20,9 @@ namespace gsr { // Process widgets by visibility (backwards) return widgets.for_each_reverse([selected_widget, &window, &event, offset](std::unique_ptr<Widget> &widget) { - if(widget.get() != selected_widget) { - if(!widget->on_event(event, window, offset)) + Widget *p = widget.get(); + if(p != selected_widget) { + if(!p->on_event(event, window, offset)) return false; } return true; @@ -36,14 +37,8 @@ namespace gsr { offset = draw_pos; Widget *selected_widget = selected_child_widget; - mgl_scissor prev_scissor; - mgl_window_get_scissor(window.internal_window(), &prev_scissor); - - const mgl_scissor new_scissor = { - mgl_vec2i{(int)draw_pos.x, (int)draw_pos.y}, - mgl_vec2i{(int)size.x, (int)size.y} - }; - mgl_window_set_scissor(window.internal_window(), &new_scissor); + const mgl::Scissor prev_scissor = window.get_scissor(); + window.set_scissor({draw_pos.to_vec2i(), size.to_vec2i()}); for(size_t i = 0; i < widgets.size(); ++i) { auto &widget = widgets[i]; @@ -54,7 +49,7 @@ namespace gsr { if(selected_widget) selected_widget->draw(window, offset); - mgl_window_set_scissor(window.internal_window(), &prev_scissor); + window.set_scissor(prev_scissor); } mgl::vec2f StaticPage::get_size() { diff --git a/src/gui/Subsection.cpp b/src/gui/Subsection.cpp index c97460e..bc75a9c 100644 --- a/src/gui/Subsection.cpp +++ b/src/gui/Subsection.cpp @@ -12,12 +12,17 @@ namespace gsr { static const float title_spacing_scale = 0.010f; Subsection::Subsection(const char *title, std::unique_ptr<Widget> inner_widget, mgl::vec2f size) : - label(&get_theme().title_font, title, get_color_theme().text_color), + label(&get_theme().title_font, title ? title : "", get_color_theme().text_color), inner_widget(std::move(inner_widget)), size(size) { this->inner_widget->parent_widget = this; } + + Subsection::~Subsection() { + if(inner_widget->parent_widget == this) + inner_widget->parent_widget = nullptr; + } bool Subsection::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f) { if(!visible) @@ -32,7 +37,7 @@ namespace gsr { mgl::vec2f draw_pos = position + offset; mgl::Rectangle background(draw_pos.floor(), get_size().floor()); - background.set_color(mgl::Color(25, 30, 34)); + background.set_color(bg_color); window.draw(background); draw_pos += mgl::vec2f(margin_left_scale, margin_top_scale) * mgl::vec2f(get_theme().window_height, get_theme().window_height); @@ -69,4 +74,12 @@ namespace gsr { const mgl::vec2f margin_size = mgl::vec2f(margin_left_scale + margin_right_scale, margin_top_scale + margin_bottom_scale) * mgl::vec2f(get_theme().window_height, get_theme().window_height); return get_size() - margin_size; } + + Widget* Subsection::get_inner_widget() { + return inner_widget.get(); + } + + void Subsection::set_bg_color(mgl::Color color) { + bg_color = color; + } }
\ No newline at end of file diff --git a/src/gui/Utils.cpp b/src/gui/Utils.cpp index d1643f2..8f77f17 100644 --- a/src/gui/Utils.cpp +++ b/src/gui/Utils.cpp @@ -67,4 +67,11 @@ namespace gsr { return from; } + + mgl::vec2f clamp_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to) { + if(from.x > to.x || from.y > to.y) + return scale_keep_aspect_ratio(from, to); + else + return from; + } }
\ No newline at end of file diff --git a/src/gui/Widget.cpp b/src/gui/Widget.cpp index 8732bd7..66cf193 100644 --- a/src/gui/Widget.cpp +++ b/src/gui/Widget.cpp @@ -1,14 +1,15 @@ #include "../../include/gui/Widget.hpp" +#include <vector> namespace gsr { + static std::vector<std::unique_ptr<Widget>> widgets_to_remove; + Widget::Widget() { } Widget::~Widget() { remove_widget_as_selected_in_parent(); - // if(parent_widget) - // parent_widget->remove_child_widget(this); } void Widget::set_position(mgl::vec2f position) { @@ -62,4 +63,15 @@ namespace gsr { void Widget::set_visible(bool visible) { this->visible = visible; } + + void add_widget_to_remove(std::unique_ptr<Widget> widget) { + widgets_to_remove.push_back(std::move(widget)); + } + + void remove_widgets_to_be_removed() { + for(size_t i = 0; i < widgets_to_remove.size(); ++i) { + widgets_to_remove[i].reset(); + } + widgets_to_remove.clear(); + } }
\ No newline at end of file |