diff options
32 files changed, 723 insertions, 259 deletions
@@ -20,4 +20,10 @@ Handle events in draw function because the render position of elements is availa Add nvidia overclock option. -Add support for window selection in capture.
\ No newline at end of file +Add support for window selection in capture. + +Add option to record the focused monitor. This works on wayland too when using kms capture since we can get cursor position without root and see which monitor (crtc) the cursor is on. + +Add option to select hevc_10bit and av1_10bit. + +For system startup dont allow default output/input audio, as that can change after startup. For example if default output is a bluetooth devices that gets connected after startup. diff --git a/depends/mglpp b/depends/mglpp -Subproject 66d126f824bc755bad042e1cb94c1d79a3203c9 +Subproject 17254ca13ce7985e87a413df1d8546779734f05 diff --git a/images/folder.png b/images/folder.png Binary files differnew file mode 100644 index 0000000..d24ae63 --- /dev/null +++ b/images/folder.png diff --git a/include/GsrInfo.hpp b/include/GsrInfo.hpp index 483b40f..d90d72f 100644 --- a/include/GsrInfo.hpp +++ b/include/GsrInfo.hpp @@ -58,6 +58,7 @@ namespace gsr { enum class GsrInfoExitStatus { OK, + BROKEN_DRIVERS, FAILED_TO_RUN_COMMAND, OPENGL_FAILED, NO_DRM_CARD diff --git a/include/SafeVector.hpp b/include/SafeVector.hpp new file mode 100644 index 0000000..cea9cb9 --- /dev/null +++ b/include/SafeVector.hpp @@ -0,0 +1,127 @@ +#pragma once + +#include <vector> +#include <memory> +#include <functional> + +// A vector that can be modified while iterating +template <typename T> +class SafeVector { +public: + using PointerType = typename std::pointer_traits<T>::element_type*; + + void push_back(T item) { + data.push_back(std::move(item)); + } + + // Safe to call when vector is empty + void pop_back() { + if(!data.empty()) + data.pop_back(); + } + + // Might not remove the data immediately if inside for_each loop. + // In that case the item is remove at the end of the loop. + void remove(PointerType item_to_remove) { + if(for_each_depth == 0) + remove_item(item_to_remove); + else + remove_queue.push_back(item_to_remove); + } + + // Safe to call when vector is empty, in which case it returns nullptr + T* back() { + if(data.empty()) + return nullptr; + else + return &data.back(); + } + + void clear() { + data.clear(); + remove_queue.clear(); + } + + // Return true from |callback| to continue. This function returns false if |callback| returned false + bool for_each(std::function<bool(T &t)> callback) { + bool result = true; + ++for_each_depth; + + for(size_t i = 0; i < data.size(); ++i) { + result = callback(data[i]); + if(!result) + break; + } + + --for_each_depth; + if(for_each_depth == 0) { + for(PointerType item_to_remove : remove_queue) { + remove_item(item_to_remove); + } + remove_queue.clear(); + } + + return result; + } + + // Return true from |callback| to continue. This function returns false if |callback| returned false + bool for_each_reverse(std::function<bool(T &t)> callback) { + bool result = true; + ++for_each_depth; + + int i = (int)data.size() - 1; + while(true) { + // pop_back can be called while iterating so handle that case + if(i >= (int)data.size()) + i = (int)data.size() - 1; + + if(i < 0) + break; + + result = callback(data[i]); + if(!result) + break; + + --i; + } + + --for_each_depth; + if(for_each_depth == 0) { + for(PointerType item_to_remove : remove_queue) { + remove_item(item_to_remove); + } + remove_queue.clear(); + } + + return result; + } + + T& operator[](size_t index) { + return data[index]; + } + + const T& operator[](size_t index) const { + return data[index]; + } + + size_t size() const { + return data.size(); + } + + bool empty() const { + return data.empty(); + } +private: + void remove_item(PointerType item_to_remove) { + for(auto it = data.begin(), end = data.end(); it != end; ++it) { + if(&*(*it) == item_to_remove) { + data.erase(it); + return; + } + } + } +private: + std::vector<T> data; + std::vector<PointerType> remove_queue; + int for_each_depth = 0; +};
\ No newline at end of file diff --git a/include/Theme.hpp b/include/Theme.hpp index cc321bd..5f228af 100644 --- a/include/Theme.hpp +++ b/include/Theme.hpp @@ -30,9 +30,12 @@ namespace gsr { mgl::Texture combobox_arrow_texture; mgl::Texture settings_texture; + mgl::Texture folder_texture; + + double double_click_timeout_seconds = 0.4; }; - bool init_theme(const gsr::GsrInfo &gsr_info, mgl::vec2i window_size, const std::string &resources_path); + bool init_theme(const GsrInfo &gsr_info, mgl::vec2i window_size, const std::string &resources_path); void deinit_theme(); Theme& get_theme(); }
\ No newline at end of file diff --git a/include/Utils.hpp b/include/Utils.hpp index f917834..6943e5e 100644 --- a/include/Utils.hpp +++ b/include/Utils.hpp @@ -1,7 +1,6 @@ #pragma once #include <functional> -#include <optional> #include <string_view> #include <map> #include <string> @@ -16,9 +15,6 @@ namespace gsr { void string_split_char(std::string_view str, char delimiter, StringSplitCallback callback_func); - // key value separated by one space - std::optional<KeyValue> parse_key_value(std::string_view line); - std::string get_home_dir(); std::string get_config_dir(); diff --git a/include/gui/Button.hpp b/include/gui/Button.hpp index f4c44ef..a4dcca1 100644 --- a/include/gui/Button.hpp +++ b/include/gui/Button.hpp @@ -21,6 +21,8 @@ namespace gsr { mgl::vec2f get_size() override; void set_border_scale(float scale); + const std::string& get_text() const; + std::function<void()> on_click; private: mgl::vec2f size; diff --git a/include/gui/ComboBox.hpp b/include/gui/ComboBox.hpp index e501132..96489e6 100644 --- a/include/gui/ComboBox.hpp +++ b/include/gui/ComboBox.hpp @@ -19,7 +19,7 @@ namespace gsr { void draw(mgl::Window &window, mgl::vec2f offset) override; void add_item(const std::string &text, const std::string &id); - void set_selected_item(const std::string &id, bool trigger_event = true); + void set_selected_item(const std::string &id, bool trigger_event = true, bool trigger_event_even_if_selection_not_changed = true); const std::string& get_selected_id() const; mgl::vec2f get_size() override; diff --git a/include/gui/FileChooser.hpp b/include/gui/FileChooser.hpp new file mode 100644 index 0000000..b8ea23e --- /dev/null +++ b/include/gui/FileChooser.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "Widget.hpp" +#include <functional> +#include <vector> + +#include <mglpp/graphics/Text.hpp> +#include <mglpp/system/Clock.hpp> + +// This currently only supports displaying folders +// TODO: Support files as well + +namespace gsr { + class FileChooser : public Widget { + public: + FileChooser(const char *start_directory, mgl::vec2f content_size); + FileChooser(const FileChooser&) = delete; + FileChooser& operator=(const FileChooser&) = delete; + + bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; + void draw(mgl::Window &window, mgl::vec2f offset) override; + + mgl::vec2f get_size() override; + + void set_current_directory(const char *directory); + private: + mgl::vec2f content_size; + mgl::Text current_directory_text; + int mouse_over_item = -1; + int selected_item = -1; + std::vector<mgl::Text> folders; + mgl::Clock double_click_timer; + int times_clicked_within_timer = 0; + }; +}
\ No newline at end of file diff --git a/include/gui/GsrPage.hpp b/include/gui/GsrPage.hpp new file mode 100644 index 0000000..9f38382 --- /dev/null +++ b/include/gui/GsrPage.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "Page.hpp" +#include "ScrollablePage.hpp" +#include "Button.hpp" + +#include <mglpp/graphics/Text.hpp> + +namespace gsr { + class GsrPage : public Page { + public: + GsrPage(); + GsrPage(const GsrPage&) = delete; + GsrPage& operator=(const GsrPage&) = delete; + + bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; + void draw(mgl::Window &window, mgl::vec2f offset) override; + + mgl::vec2f get_size() override; + mgl::vec2f get_inner_size() override; + + void add_widget(std::unique_ptr<Widget> widget) override; + + void set_margins(float top, float bottom, float left, float right); + void set_on_back_button_click(std::function<void()> on_click_handler); + private: + void draw_page_label(mgl::Window &window, mgl::vec2f body_pos); + float get_border_size() const; + float get_horizontal_spacing() const; + private: + float margin_top_scale = 0.0f; + float margin_bottom_scale = 0.0f; + float margin_left_scale = 0.0f; + float margin_right_scale = 0.0f; + ScrollablePage scrollable_body; + Button back_button; + mgl::Text label_text; + }; +}
\ No newline at end of file diff --git a/include/gui/List.hpp b/include/gui/List.hpp index 241a75c..55a5b84 100644 --- a/include/gui/List.hpp +++ b/include/gui/List.hpp @@ -1,7 +1,7 @@ #pragma once #include "Widget.hpp" -#include <vector> +#include "../SafeVector.hpp" #include <memory> namespace gsr { @@ -27,28 +27,20 @@ namespace gsr { //void remove_child_widget(Widget *widget) override; - // Might not take effect immediately but at the next draw iteration if inside an event loop void add_widget(std::unique_ptr<Widget> widget); - // Might not take effect immediately but at the next draw iteration if inside an event loop void remove_widget(Widget *widget); - // Excludes widgets from queue - const std::vector<std::unique_ptr<Widget>>& get_child_widgets() const; + // Return true from |callback| to continue + void for_each_child_widget(std::function<bool(std::unique_ptr<Widget> &widget)> callback); // Returns nullptr if index is invalid Widget* get_child_widget_by_index(size_t index) const; void set_spacing(float spacing); mgl::vec2f get_size() override; - private: - void update(); - void remove_widget_immediate(Widget *widget); protected: - std::vector<std::unique_ptr<Widget>> widgets; - std::vector<std::unique_ptr<Widget>> add_queue; - std::vector<Widget*> remove_queue; + SafeVector<std::unique_ptr<Widget>> widgets; Orientation orientation; Alignment content_alignment; - bool inside_event_handler = false; float spacing_scale = 0.009f; }; }
\ No newline at end of file diff --git a/include/gui/Page.hpp b/include/gui/Page.hpp index 4c63702..a8fa515 100644 --- a/include/gui/Page.hpp +++ b/include/gui/Page.hpp @@ -17,7 +17,7 @@ namespace gsr { //void remove_child_widget(Widget *widget) override; - void add_widget(std::unique_ptr<Widget> widget); + virtual void add_widget(std::unique_ptr<Widget> widget); protected: std::vector<std::unique_ptr<Widget>> widgets; }; diff --git a/include/gui/PageStack.hpp b/include/gui/PageStack.hpp new file mode 100644 index 0000000..cf42b7d --- /dev/null +++ b/include/gui/PageStack.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "Widget.hpp" +#include "Page.hpp" +#include "../SafeVector.hpp" +#include <memory> + +namespace gsr { + class PageStack : public Widget { + public: + PageStack(); + PageStack(const PageStack&) = delete; + PageStack& operator=(const PageStack&) = delete; + + bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; + void draw(mgl::Window &window, mgl::vec2f offset) override; + + mgl::vec2f get_size() override; + + void push(std::unique_ptr<Page> page); + // This can be used even if the stack is empty + void pop(); + // This can be used even if the stack is empty + Page* top(); + bool empty() const; + private: + SafeVector<std::unique_ptr<Page>> widget_stack; + }; +}
\ No newline at end of file diff --git a/include/gui/RadioButton.hpp b/include/gui/RadioButton.hpp index 7839c68..69339db 100644 --- a/include/gui/RadioButton.hpp +++ b/include/gui/RadioButton.hpp @@ -17,7 +17,7 @@ namespace gsr { void draw(mgl::Window &window, mgl::vec2f offset) override; void add_item(const std::string &text, const std::string &id); - void set_selected_item(const std::string &id, bool trigger_event = true); + void set_selected_item(const std::string &id, bool trigger_event = true, bool trigger_event_even_if_selection_not_changed = true); const std::string get_selected_id() const; mgl::vec2f get_size() override; diff --git a/include/gui/ScrollablePage.hpp b/include/gui/ScrollablePage.hpp index 60d719f..5048627 100644 --- a/include/gui/ScrollablePage.hpp +++ b/include/gui/ScrollablePage.hpp @@ -1,9 +1,11 @@ #pragma once -#include "Page.hpp" +#include "Widget.hpp" +#include <memory> +#include <vector> namespace gsr { - class ScrollablePage : public Page { + class ScrollablePage : public Widget { public: ScrollablePage(mgl::vec2f size); ScrollablePage(const ScrollablePage&) = delete; @@ -13,16 +15,11 @@ namespace gsr { void draw(mgl::Window &window, mgl::vec2f offset) override; mgl::vec2f get_size() override; - mgl::vec2f get_inner_size() override; + void set_size(mgl::vec2f size); - void set_margins(float top, float bottom, float left, float right); - private: - float get_border_size() const; + void add_widget(std::unique_ptr<Widget> widget); private: mgl::vec2f size; - float margin_top_scale = 0.0f; - float margin_bottom_scale = 0.0f; - float margin_left_scale = 0.0f; - float margin_right_scale = 0.0f; + std::vector<std::unique_ptr<Widget>> widgets; }; }
\ No newline at end of file diff --git a/include/gui/SettingsPage.hpp b/include/gui/SettingsPage.hpp index 1b301bf..1ad4c67 100644 --- a/include/gui/SettingsPage.hpp +++ b/include/gui/SettingsPage.hpp @@ -7,14 +7,12 @@ #include "RadioButton.hpp" #include "CheckBox.hpp" #include "Button.hpp" -#include "CustomRendererWidget.hpp" #include "../GsrInfo.hpp" #include "../Config.hpp" -#include <functional> - namespace gsr { - class ScrollablePage; + class GsrPage; + class PageStack; class SettingsPage : public StaticPage { public: @@ -24,17 +22,13 @@ namespace gsr { STREAM }; - SettingsPage(Type type, const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices, std::optional<Config> &config); + SettingsPage(Type type, const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices, std::optional<Config> &config, PageStack *page_stack); SettingsPage(const SettingsPage&) = delete; SettingsPage& operator=(const SettingsPage&) = delete; void save(); void on_navigate_away_from_page() override; - - std::function<void()> on_back_button_handler; private: - std::unique_ptr<Button> create_back_button(); - std::unique_ptr<CustomRendererWidget> create_settings_icon(); std::unique_ptr<RadioButton> create_view_radio_button(); std::unique_ptr<ComboBox> create_record_area_box(const GsrInfo &gsr_info); std::unique_ptr<List> create_record_area(const GsrInfo &gsr_info); @@ -69,7 +63,7 @@ namespace gsr { std::unique_ptr<List> create_framerate_mode(); std::unique_ptr<List> create_framerate_section(); std::unique_ptr<List> create_settings(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices); - void add_widgets(const gsr::GsrInfo &gsr_info, const std::vector<gsr::AudioDevice> &audio_devices); + void add_widgets(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices); void add_page_specific_widgets(); @@ -97,7 +91,7 @@ namespace gsr { Type type; std::optional<Config> &config; - ScrollablePage *content_page_ptr = nullptr; + GsrPage *content_page_ptr = nullptr; List *settings_list_ptr = nullptr; List *select_window_list_ptr = nullptr; List *area_size_list_ptr = nullptr; @@ -131,12 +125,14 @@ namespace gsr { CheckBox *show_video_saved_notification_checkbox_ptr = nullptr; CheckBox *show_streaming_started_notification_checkbox_ptr = nullptr; CheckBox *show_streaming_stopped_notification_checkbox_ptr = nullptr; - Entry *save_directory_entry_ptr = nullptr; + Button *save_directory_button_ptr = nullptr; Entry *twitch_stream_key_entry_ptr = nullptr; Entry *youtube_stream_key_entry_ptr = nullptr; Entry *stream_url_entry_ptr = nullptr; Entry *replay_time_entry_ptr = nullptr; + PageStack *page_stack = nullptr; + mgl::Text settings_title_text; }; }
\ No newline at end of file diff --git a/meson.build b/meson.build index f65d8eb..ddc08f5 100644 --- a/meson.build +++ b/meson.build @@ -8,6 +8,7 @@ endif src = [ 'src/Theme.cpp', + 'src/gui/Widget.cpp', 'src/gui/ScrollablePage.cpp', 'src/gui/Button.cpp', 'src/gui/RadioButton.cpp', @@ -16,13 +17,15 @@ src = [ 'src/gui/ComboBox.cpp', 'src/gui/Page.cpp', 'src/gui/StaticPage.cpp', - 'src/gui/Widget.cpp', + 'src/gui/PageStack.cpp', 'src/gui/List.cpp', 'src/gui/Utils.cpp', 'src/gui/DropdownButton.cpp', 'src/gui/Label.cpp', 'src/gui/CustomRendererWidget.cpp', + 'src/gui/FileChooser.cpp', 'src/gui/SettingsPage.cpp', + 'src/gui/GsrPage.cpp', 'src/Utils.cpp', 'src/Config.cpp', 'src/GsrInfo.cpp', diff --git a/src/Config.cpp b/src/Config.cpp index b4825a3..8f28c89 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -12,6 +12,13 @@ #define CONFIG_FILE_VERSION 1 namespace gsr { + static std::optional<KeyValue> parse_key_value(std::string_view line) { + const size_t space_index = line.find(' '); + if(space_index == std::string_view::npos) + return std::nullopt; + return KeyValue{line.substr(0, space_index), line.substr(space_index + 1)}; + } + using ConfigValue = std::variant<bool*, std::string*, int32_t*, ConfigHotkey*, std::vector<std::string>*>; static std::map<std::string_view, ConfigValue> get_config_options(Config &config) { diff --git a/src/GsrInfo.cpp b/src/GsrInfo.cpp index dfd18af..5d9773d 100644 --- a/src/GsrInfo.cpp +++ b/src/GsrInfo.cpp @@ -4,6 +4,13 @@ #include <string.h> namespace gsr { + static std::optional<KeyValue> parse_key_value(std::string_view line) { + const size_t space_index = line.find('|'); + if(space_index == std::string_view::npos) + return std::nullopt; + return KeyValue{line.substr(0, space_index), line.substr(space_index + 1)}; + } + static void parse_system_info_line(GsrInfo *gsr_info, std::string_view line) { const std::optional<KeyValue> key_value = parse_key_value(line); if(!key_value) @@ -156,6 +163,7 @@ namespace gsr { if(WIFEXITED(status)) { switch(WEXITSTATUS(status)) { case 0: return GsrInfoExitStatus::OK; + case 14: return GsrInfoExitStatus::BROKEN_DRIVERS; case 22: return GsrInfoExitStatus::OPENGL_FAILED; case 23: return GsrInfoExitStatus::NO_DRM_CARD; default: return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; @@ -180,14 +188,14 @@ namespace gsr { FILE *f = popen("gpu-screen-recorder --list-audio-devices", "r"); if(!f) { - fprintf(stderr, "error: 'gpu-screen-recorder --info' failed\n"); + fprintf(stderr, "error: 'gpu-screen-recorder --list-audio-devices' failed\n"); return audio_devices; } char output[16384]; ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f); if(bytes_read < 0 || ferror(f)) { - fprintf(stderr, "error: failed to read 'gpu-screen-recorder --info' output\n"); + fprintf(stderr, "error: failed to read 'gpu-screen-recorder --list-audio-devices' output\n"); pclose(f); return audio_devices; } @@ -202,4 +210,4 @@ namespace gsr { return audio_devices; } -}
\ No newline at end of file +} diff --git a/src/Theme.cpp b/src/Theme.cpp index 946b4b2..9fcf057 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -5,7 +5,7 @@ namespace gsr { static Theme *theme = nullptr; - bool init_theme(const gsr::GsrInfo &gsr_info, mgl::vec2i window_size, const std::string &resources_path) { + bool init_theme(const GsrInfo &gsr_info, mgl::vec2i window_size, const std::string &resources_path) { if(theme) return true; @@ -15,18 +15,18 @@ namespace gsr { theme->window_height = window_size.y; switch(gsr_info.gpu_info.vendor) { - case gsr::GpuVendor::UNKNOWN: { + case GpuVendor::UNKNOWN: { break; } - case gsr::GpuVendor::AMD: { + case GpuVendor::AMD: { theme->tint_color = mgl::Color(221, 0, 49); break; } - case gsr::GpuVendor::INTEL: { + case GpuVendor::INTEL: { theme->tint_color = mgl::Color(8, 109, 183); break; } - case gsr::GpuVendor::NVIDIA: { + case GpuVendor::NVIDIA: { theme->tint_color = mgl::Color(118, 185, 0); break; } @@ -53,6 +53,9 @@ namespace gsr { if(!theme->settings_texture.load_from_file((resources_path + "images/settings.png").c_str(), {false, false, false})) goto error; + if(!theme->folder_texture.load_from_file((resources_path + "images/folder.png").c_str(), {false, false, false})) + goto error; + return true; error: diff --git a/src/Utils.cpp b/src/Utils.cpp index 4252de8..55fc57e 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -22,13 +22,6 @@ namespace gsr { } } - std::optional<KeyValue> parse_key_value(std::string_view line) { - const size_t space_index = line.find(' '); - if(space_index == std::string_view::npos) - return std::nullopt; - return KeyValue{line.substr(0, space_index), line.substr(space_index + 1)}; - } - std::string get_home_dir() { const char *home_dir = getenv("HOME"); if(!home_dir) { diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index e36d07b..20a771e 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -70,4 +70,8 @@ namespace gsr { void Button::set_border_scale(float scale) { border_scale = scale; } + + const std::string& Button::get_text() const { + return text.get_string(); + } }
\ No newline at end of file diff --git a/src/gui/ComboBox.cpp b/src/gui/ComboBox.cpp index f23a323..b344c4e 100644 --- a/src/gui/ComboBox.cpp +++ b/src/gui/ComboBox.cpp @@ -146,14 +146,14 @@ namespace gsr { dirty = true; } - void ComboBox::set_selected_item(const std::string &id, bool trigger_event) { + void ComboBox::set_selected_item(const std::string &id, bool trigger_event, bool trigger_event_even_if_selection_not_changed) { 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 && selected_item != prev_selected_item && on_selection_changed) + 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); break; diff --git a/src/gui/FileChooser.cpp b/src/gui/FileChooser.cpp new file mode 100644 index 0000000..8cc5d74 --- /dev/null +++ b/src/gui/FileChooser.cpp @@ -0,0 +1,174 @@ +#include "../../include/gui/FileChooser.hpp" +#include "../../include/gui/Utils.hpp" +#include "../../include/Theme.hpp" +#include <mglpp/graphics/Rectangle.hpp> +#include <mglpp/graphics/Sprite.hpp> +#include <mglpp/window/Window.hpp> +#include <mglpp/window/Event.hpp> +#include <mglpp/system/FloatRect.hpp> +#include <dirent.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> + +namespace gsr { + static const float current_directory_padding_top_scale = 0.004629f; + static const float current_directory_padding_bottom_scale = 0.004629f; + static const float current_directory_padding_left_scale = 0.004629f; + static const float current_directory_padding_right_scale = 0.004629f; + static const float spacing_between_current_directory_and_content = 0.015f; + static const int num_columns = 6; + static const float content_padding_top_scale = 0.015f; + static const float content_padding_bottom_scale = 0.015f; + static const float content_padding_left_scale = 0.015f; + static const float content_padding_right_scale = 0.015f; + + FileChooser::FileChooser(const char *start_directory, mgl::vec2f content_size) : + content_size(content_size), current_directory_text(start_directory, get_theme().body_font) + { + set_current_directory(start_directory); + } + + bool FileChooser::on_event(mgl::Event &event, mgl::Window&, mgl::vec2f offset) { + if(!visible) + return true; + + if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { + if(double_click_timer.get_elapsed_time_seconds() <= get_theme().double_click_timeout_seconds) { + ++times_clicked_within_timer; + } else { + times_clicked_within_timer = 1; + } + double_click_timer.restart(); + + selected_item = mouse_over_item; + + if(selected_item != -1 && times_clicked_within_timer > 0 && times_clicked_within_timer % 2 == 0) { + char new_directory[PATH_MAX]; + snprintf(new_directory, sizeof(new_directory), "%s/%s", current_directory_text.get_string().c_str(), folders[selected_item].get_string().c_str()); + set_current_directory(new_directory); + } + } + return true; + } + + void FileChooser::draw(mgl::Window &window, mgl::vec2f offset) { + mouse_over_item = -1; + + if(!visible) + return; + + const mgl::vec2f draw_pos = position + offset; + const mgl::vec2f current_directory_padding( + current_directory_padding_left_scale * get_theme().window_height + current_directory_padding_right_scale * get_theme().window_height, + current_directory_padding_top_scale * get_theme().window_height + current_directory_padding_bottom_scale * get_theme().window_height + ); + + mgl::Rectangle current_directory_background(mgl::vec2f(content_size.x, current_directory_text.get_bounds().size.y + current_directory_padding.y).floor()); + current_directory_background.set_color(mgl::Color(0, 0, 0, 120)); + current_directory_background.set_position(draw_pos.floor()); + window.draw(current_directory_background); + + current_directory_text.set_color(get_theme().text_color); + current_directory_text.set_position((draw_pos + mgl::vec2f(current_directory_padding.x, current_directory_background.get_size().y * 0.5f - current_directory_text.get_bounds().size.y * 0.5f)).floor()); + window.draw(current_directory_text); + + mgl::Rectangle content_background(content_size.floor()); + content_background.set_color(mgl::Color(0, 0, 0, 120)); + content_background.set_position((draw_pos + mgl::vec2f(0.0f, current_directory_background.get_size().y + spacing_between_current_directory_and_content * get_theme().window_height)).floor()); + window.draw(content_background); + + const mgl::vec2f mouse_pos = window.get_mouse_position().to_vec2f(); + + const int content_padding_top = content_padding_top_scale * get_theme().window_height; + const int content_padding_bottom = content_padding_bottom_scale * get_theme().window_height; + const int content_padding_left = content_padding_left_scale * get_theme().window_height; + const int content_padding_right = content_padding_right_scale * get_theme().window_height; + + const float folder_width = (int)((content_size.x - (content_padding_left + content_padding_right) * num_columns) / num_columns); + const float width_per_item_after = content_padding_right + folder_width + content_padding_left; + mgl::vec2f folder_pos = content_background.get_position() + mgl::vec2f(content_padding_left, content_padding_top); + for(int i = 0; i < (int)folders.size(); ++i) { + auto &folder_text = folders[i]; + + mgl::Sprite folder_sprite(&get_theme().folder_texture); + folder_sprite.set_position(folder_pos.floor()); + folder_sprite.set_width((int)folder_width); + + const mgl::vec2f item_pos = folder_pos - mgl::vec2f(content_padding_left, content_padding_top); + const mgl::vec2f item_size = folder_sprite.get_size() + mgl::vec2f(content_padding_left + content_padding_right, content_padding_top + content_padding_bottom); + if(i == selected_item) { + mgl::Rectangle selected_item_background(item_size.floor()); + selected_item_background.set_position(item_pos.floor()); + selected_item_background.set_color(get_theme().tint_color); + window.draw(selected_item_background); + } + if(!has_parent_with_selected_child_widget() && mouse_over_item == -1 && mgl::FloatRect(item_pos, item_size).contains(mouse_pos)) { + // mgl::Rectangle selected_item_background(item_size.floor()); + // selected_item_background.set_position(item_pos.floor()); + // selected_item_background.set_color(mgl::Color(20, 20, 20, 150)); + // window.draw(selected_item_background); + const float border_scale = 0.0015f; + draw_rectangle_outline(window, item_pos.floor(), item_size.floor(), get_theme().tint_color, border_scale * get_theme().window_height); + mouse_over_item = i; + } + + window.draw(folder_sprite); + + // TODO: Dont allow text to go further left/right than item_pos (on the left side) and item_pos + item_size (on the right side). + folder_text.set_position(folder_sprite.get_position() + mgl::vec2f(folder_sprite.get_size().x * 0.5f - folder_text.get_bounds().size.x * 0.5f, folder_sprite.get_size().y)); + window.draw(folder_text); + + folder_pos.x += width_per_item_after; + if(folder_pos.x + folder_width > content_background.get_position().x + content_size.x) { + folder_pos.x = content_background.get_position().x + content_padding_left; + folder_pos.y += content_padding_bottom + folder_sprite.get_size().y + content_padding_top; + } + } + } + + mgl::vec2f FileChooser::get_size() { + if(!visible) + return {0.0f, 0.0f}; + + const mgl::vec2f current_directory_padding( + current_directory_padding_left_scale * get_theme().window_height + current_directory_padding_right_scale * get_theme().window_height, + current_directory_padding_top_scale * get_theme().window_height + current_directory_padding_bottom_scale * get_theme().window_height + ); + return {content_size.x, current_directory_text.get_bounds().size.y + current_directory_padding.y + spacing_between_current_directory_and_content * get_theme().window_height + content_size.y}; + } + + static bool is_dir(struct dirent *dir, const char *parent_directory) { + if(dir->d_type == DT_DIR) + return true; + + char filepath[PATH_MAX]; + snprintf(filepath, sizeof(filepath), "%s/%s", parent_directory, dir->d_name); + + struct stat st; + return stat(filepath, &st) != -1 && S_ISDIR(st.st_mode); + } + + void FileChooser::set_current_directory(const char *directory) { + folders.clear(); + selected_item = -1; + mouse_over_item = -1; + current_directory_text.set_string(directory); + + DIR *d = opendir(directory); + if(!d) { + fprintf(stderr, "gsr-overlay error: failed to open directory: %s, error: %s\n", directory, strerror(errno)); + return; + } + + struct dirent *dir = NULL; + while((dir = readdir(d)) != NULL) { + /* Ignore hidden files */ + if(dir->d_name[0] == '.' || !is_dir(dir, directory)) + continue; + folders.push_back(mgl::Text(dir->d_name, get_theme().body_font)); + } + + closedir(d); + } +}
\ No newline at end of file diff --git a/src/gui/GsrPage.cpp b/src/gui/GsrPage.cpp new file mode 100644 index 0000000..db49ced --- /dev/null +++ b/src/gui/GsrPage.cpp @@ -0,0 +1,126 @@ +#include "../../include/gui/GsrPage.hpp" +#include "../../include/Theme.hpp" + +#include <mglpp/graphics/Rectangle.hpp> +#include <mglpp/graphics/Sprite.hpp> +#include <mglpp/window/Window.hpp> + +namespace gsr { + GsrPage::GsrPage() : + scrollable_body(mgl::vec2f(0.0f, 0.0f)), + back_button(&get_theme().title_font, "Back", + mgl::vec2f(get_theme().window_width / 10, get_theme().window_height / 15).floor(), get_theme().scrollable_page_bg_color), + label_text("Settings", get_theme().title_font) + { + //set_position(content_page_position); + const float margin = 0.02f; + set_margins(margin, margin, margin, margin); + back_button.set_border_scale(0.003f); + } + + bool GsrPage::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f) { + if(!visible) + return true; + + if(!scrollable_body.on_event(event, window, mgl::vec2f(0.0f, 0.0f))) + return false; + + if(!back_button.on_event(event, window, mgl::vec2f(0.0f, 0.0f))) + return false; + + return true; + } + + void GsrPage::draw(mgl::Window &window, mgl::vec2f offset) { + if(!visible) + return; + + const int margin_top = margin_top_scale * get_theme().window_height; + const int margin_left = margin_left_scale * get_theme().window_height; + + const mgl::vec2f window_size = mgl::vec2f(get_theme().window_width, get_theme().window_height).floor(); + const mgl::vec2f content_page_size = get_size(); + const mgl::vec2f content_page_position = mgl::vec2f(window_size * 0.5f - content_page_size * 0.5f).floor(); + offset = content_page_position + mgl::vec2f(margin_left, get_border_size() + margin_top).floor(); + + mgl::Rectangle background(content_page_size); + background.set_position(content_page_position); + background.set_color(get_theme().scrollable_page_bg_color); + window.draw(background); + + mgl::Rectangle border(mgl::vec2f(content_page_size.x, get_border_size()).floor()); + border.set_position(content_page_position); + border.set_color(get_theme().tint_color); + window.draw(border); + + scrollable_body.set_position(offset); + scrollable_body.draw(window, mgl::vec2f(0.0f, 0.0f)); + + draw_page_label(window, content_page_position); + + back_button.set_position(content_page_position + mgl::vec2f(content_page_size.x + get_horizontal_spacing(), 0.0f).floor()); + back_button.draw(window, mgl::vec2f(0.0f, 0.0f)); + } + + void GsrPage::draw_page_label(mgl::Window &window, mgl::vec2f body_pos) { + const mgl::vec2f window_size = mgl::vec2f(get_theme().window_width, get_theme().window_height).floor(); + + mgl::Rectangle background(mgl::vec2f(window_size.x / 10, window_size.x / 10).floor()); + background.set_position(body_pos.floor() - mgl::vec2f(background.get_size().x + get_horizontal_spacing(), 0.0f).floor()); + background.set_color(mgl::Color(0, 0, 0, 255)); + 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); + + 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); + } + + mgl::vec2f GsrPage::get_size() { + if(!visible) + return {0.0f, 0.0f}; + + const mgl::vec2f window_size = mgl::vec2f(get_theme().window_width, get_theme().window_height).floor(); + const mgl::vec2f content_page_size = (window_size * mgl::vec2f(0.3333f, 0.7f)).floor(); + return content_page_size; + } + + mgl::vec2f GsrPage::get_inner_size() { + if(!visible) + return {0.0f, 0.0f}; + + const int margin_top = margin_top_scale * get_theme().window_height; + const int margin_bottom = margin_bottom_scale * get_theme().window_height; + const int margin_left = margin_left_scale * get_theme().window_height; + const int margin_right = margin_right_scale * get_theme().window_height; + return get_size() - mgl::vec2f(margin_left + margin_right, margin_top + margin_bottom + get_border_size()); + } + + void GsrPage::add_widget(std::unique_ptr<Widget> widget) { + scrollable_body.add_widget(std::move(widget)); + } + + void GsrPage::set_margins(float top, float bottom, float left, float right) { + margin_top_scale = top; + margin_bottom_scale = bottom; + margin_left_scale = left; + margin_right_scale = right; + scrollable_body.set_size(get_inner_size()); + } + + void GsrPage::set_on_back_button_click(std::function<void()> on_click_handler) { + back_button.on_click = std::move(on_click_handler); + } + + float GsrPage::get_border_size() const { + return 0.004f * get_theme().window_height; + } + + float GsrPage::get_horizontal_spacing() const { + return get_theme().window_width / 50; + } +}
\ No newline at end of file diff --git a/src/gui/List.cpp b/src/gui/List.cpp index acdb695..849329f 100644 --- a/src/gui/List.cpp +++ b/src/gui/List.cpp @@ -14,55 +14,25 @@ namespace gsr { if(!visible) return true; - inside_event_handler = true; // We want to store the selected child widget since it can change in the event loop below Widget *selected_widget = selected_child_widget; if(selected_widget) { - if(!selected_widget->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) { - inside_event_handler = false; + if(!selected_widget->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) return false; - } } // Process widgets by visibility (backwards) - for(auto it = widgets.rbegin(), end = widgets.rend(); it != end; ++it) { + 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(it->get() != selected_widget) { - if(!(*it)->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) { - inside_event_handler = false; + if(widget.get() != selected_widget) { + if(!widget->on_event(event, window, mgl::vec2f(0.0f, 0.0f))) return false; - } } - } - - inside_event_handler = false; - return true; - } - - // TODO: Maybe call this from main on all widgets instead of from draw - void List::update() { - //widgets.insert(widgets.back(), std::make_move_iterator(add_queue.begin()), std::make_move_iterator(add_queue.end())); - std::move(add_queue.begin(), add_queue.end(), std::back_inserter(widgets)); - add_queue.clear(); - - for(Widget *widget_to_remove : remove_queue) { - remove_widget_immediate(widget_to_remove); - } - remove_queue.clear(); - } - - void List::remove_widget_immediate(Widget *widget) { - for(auto it = widgets.begin(), end = widgets.end(); it != end; ++it) { - if(it->get() == widget) { - widgets.erase(it); - return; - } - } + return true; + }); } void List::draw(mgl::Window &window, mgl::vec2f offset) { - update(); - if(!visible) return; @@ -140,28 +110,15 @@ namespace gsr { void List::add_widget(std::unique_ptr<Widget> widget) { widget->parent_widget = this; - if(inside_event_handler) - add_queue.push_back(std::move(widget)); - else - widgets.push_back(std::move(widget)); + widgets.push_back(std::move(widget)); } void List::remove_widget(Widget *widget) { - for(auto it = add_queue.begin(), end = add_queue.end(); it != end; ++it) { - if(it->get() == widget) { - add_queue.erase(it); - return; - } - } - - if(inside_event_handler) - remove_queue.push_back(widget); - else - remove_widget_immediate(widget); + widgets.remove(widget); } - const std::vector<std::unique_ptr<Widget>>& List::get_child_widgets() const { - return widgets; + void List::for_each_child_widget(std::function<bool(std::unique_ptr<Widget> &widget)> callback) { + widgets.for_each(callback); } Widget* List::get_child_widget_by_index(size_t index) const { diff --git a/src/gui/PageStack.cpp b/src/gui/PageStack.cpp new file mode 100644 index 0000000..94196cd --- /dev/null +++ b/src/gui/PageStack.cpp @@ -0,0 +1,58 @@ +#include "../../include/gui/PageStack.hpp" + +namespace gsr { + PageStack::PageStack() {} + + bool PageStack::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) { + if(!visible) + return true; + + offset = position + offset; + std::unique_ptr<Page> *top_page = widget_stack.back(); + if(top_page) { + if(!(*top_page)->on_event(event, window, offset)) + return false; + } + + return true; + } + + void PageStack::draw(mgl::Window &window, mgl::vec2f offset) { + if(!visible) + return; + + offset = position + offset; + std::unique_ptr<Page> *top_page = widget_stack.back(); + if(top_page) + (*top_page)->draw(window, offset); + } + + mgl::vec2f PageStack::get_size() { + return {0.0f, 0.0f}; + } + + void PageStack::push(std::unique_ptr<Page> widget) { + widget->on_navigate_to_page(); + widget_stack.push_back(std::move(widget)); + } + + void PageStack::pop() { + std::unique_ptr<Page> *top_page = widget_stack.back(); + if(top_page) { + (*top_page)->on_navigate_away_from_page(); + widget_stack.pop_back(); + } + } + + Page* PageStack::top() { + std::unique_ptr<Page> *top_page = widget_stack.back(); + if(top_page) + return top_page->get(); + else + return nullptr; + } + + bool PageStack::empty() const { + return widget_stack.empty(); + } +}
\ No newline at end of file diff --git a/src/gui/RadioButton.cpp b/src/gui/RadioButton.cpp index 87428bf..66f3b69 100644 --- a/src/gui/RadioButton.cpp +++ b/src/gui/RadioButton.cpp @@ -121,14 +121,14 @@ namespace gsr { dirty = true; } - void RadioButton::set_selected_item(const std::string &id, bool trigger_event) { + void RadioButton::set_selected_item(const std::string &id, bool trigger_event, bool trigger_event_even_if_selection_not_changed) { 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 && selected_item != prev_selected_item && on_selection_changed) + 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); break; diff --git a/src/gui/ScrollablePage.cpp b/src/gui/ScrollablePage.cpp index a0977ce..a2b6c8f 100644 --- a/src/gui/ScrollablePage.cpp +++ b/src/gui/ScrollablePage.cpp @@ -1,7 +1,5 @@ #include "../../include/gui/ScrollablePage.hpp" -#include "../../include/Theme.hpp" -#include <mglpp/graphics/Rectangle.hpp> #include <mglpp/window/Window.hpp> namespace gsr { @@ -11,11 +9,8 @@ namespace gsr { if(!visible) return true; - const int margin_top = margin_top_scale * get_theme().window_height; - const int margin_left = margin_left_scale * get_theme().window_height; - const mgl::vec2f draw_pos = position + offset; - offset = draw_pos + mgl::vec2f(margin_left, get_border_size() + margin_top).floor(); + offset = draw_pos; Widget *selected_widget = selected_child_widget; if(selected_widget) { @@ -38,21 +33,8 @@ namespace gsr { if(!visible) return; - const int margin_top = margin_top_scale * get_theme().window_height; - const int margin_left = margin_left_scale * get_theme().window_height; - const mgl::vec2f draw_pos = position + offset; - offset = draw_pos + mgl::vec2f(margin_left, get_border_size() + margin_top).floor(); - - mgl::Rectangle background(size.floor()); - background.set_position(draw_pos); - background.set_color(get_theme().scrollable_page_bg_color); - window.draw(background); - - mgl::Rectangle border(mgl::vec2f(size.x, get_border_size()).floor()); - border.set_position(draw_pos); - border.set_color(get_theme().tint_color); - window.draw(border); + offset = draw_pos; mgl_scissor prev_scissor; mgl_window_get_scissor(window.internal_window(), &prev_scissor); @@ -84,25 +66,11 @@ namespace gsr { return size; } - mgl::vec2f ScrollablePage::get_inner_size() { - if(!visible) - return {0.0f, 0.0f}; - - const int margin_top = margin_top_scale * get_theme().window_height; - const int margin_bottom = margin_bottom_scale * get_theme().window_height; - const int margin_left = margin_left_scale * get_theme().window_height; - const int margin_right = margin_right_scale * get_theme().window_height; - return size - mgl::vec2f(margin_left + margin_right, margin_top + margin_bottom + get_border_size()); - } - - void ScrollablePage::set_margins(float top, float bottom, float left, float right) { - margin_top_scale = top; - margin_bottom_scale = bottom; - margin_left_scale = left; - margin_right_scale = right; + void ScrollablePage::set_size(mgl::vec2f size) { + this->size = size; } - float ScrollablePage::get_border_size() const { - return 0.004f * get_theme().window_height; + void ScrollablePage::add_widget(std::unique_ptr<Widget> widget) { + widgets.push_back(std::move(widget)); } }
\ No newline at end of file diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 9bf6a12..d6022ab 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -1,6 +1,8 @@ #include "../../include/gui/SettingsPage.hpp" -#include "../../include/gui/ScrollablePage.hpp" +#include "../../include/gui/GsrPage.hpp" #include "../../include/gui/Label.hpp" +#include "../../include/gui/PageStack.hpp" +#include "../../include/gui/FileChooser.hpp" #include "../../include/Theme.hpp" #include "../../include/GsrInfo.hpp" @@ -10,20 +12,17 @@ #include <mglpp/window/Window.hpp> namespace gsr { - SettingsPage::SettingsPage(Type type, const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices, std::optional<Config> &config) : + SettingsPage::SettingsPage(Type type, const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices, std::optional<Config> &config, PageStack *page_stack) : StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), type(type), config(config), + page_stack(page_stack), settings_title_text("Settings", get_theme().title_font) { - const mgl::vec2f window_size = mgl::vec2f(get_theme().window_width, get_theme().window_height).floor(); - const mgl::vec2f content_page_size = (window_size * mgl::vec2f(0.3333f, 0.7f)).floor(); - const mgl::vec2f content_page_position = mgl::vec2f(window_size * 0.5f - content_page_size * 0.5f).floor(); - const float settings_body_margin = 0.02f; - - auto content_page = std::make_unique<ScrollablePage>(content_page_size); - content_page->set_position(content_page_position); - content_page->set_margins(settings_body_margin, settings_body_margin, settings_body_margin, settings_body_margin); + auto content_page = std::make_unique<GsrPage>(); + content_page->set_on_back_button_click([page_stack]() { + page_stack->pop(); + }); content_page_ptr = content_page.get(); add_widget(std::move(content_page)); @@ -31,40 +30,6 @@ namespace gsr { add_page_specific_widgets(); } - std::unique_ptr<Button> SettingsPage::create_back_button() { - const mgl::vec2i window_size(get_theme().window_width, get_theme().window_height); - auto back_button = std::make_unique<Button>(&get_theme().title_font, "Back", mgl::vec2f(window_size.x / 10, window_size.y / 15).floor(), get_theme().scrollable_page_bg_color); - back_button->set_position(content_page_ptr->get_position().floor() + mgl::vec2f(content_page_ptr->get_size().x + window_size.x / 50, 0.0f).floor()); - back_button->set_border_scale(0.003f); - back_button->on_click = [this]() { - if(on_back_button_handler) - on_back_button_handler(); - }; - return back_button; - } - - std::unique_ptr<CustomRendererWidget> SettingsPage::create_settings_icon() { - const mgl::vec2i window_size(get_theme().window_width, get_theme().window_height); - auto settings_icon_widget = std::make_unique<CustomRendererWidget>(mgl::vec2f(window_size.x / 10, window_size.x / 10).floor()); - settings_icon_widget->set_position(content_page_ptr->get_position().floor() - mgl::vec2f(settings_icon_widget->get_size().x + window_size.x / 50, 0.0f).floor()); - settings_icon_widget->draw_handler = [&](mgl::Window &window, mgl::vec2f pos, mgl::vec2f size) { - mgl::Rectangle background(size); - background.set_position(pos); - background.set_color(mgl::Color(0, 0, 0, 255)); - window.draw(background); - - const int text_margin = size.y * 0.085; - settings_title_text.set_position((pos + mgl::vec2f(size.x * 0.5f - settings_title_text.get_bounds().size.x * 0.5f, text_margin)).floor()); - window.draw(settings_title_text); - - mgl::Sprite icon(&get_theme().settings_texture); - icon.set_height((int)(size.y * 0.5f)); - icon.set_position((pos + size * 0.5f - icon.get_size() * 0.5f).floor()); - window.draw(icon); - }; - return settings_icon_widget; - } - std::unique_ptr<RadioButton> SettingsPage::create_view_radio_button() { auto view_radio_button = std::make_unique<RadioButton>(&get_theme().body_font); view_radio_button->add_item("Simple view", "simple"); @@ -363,8 +328,6 @@ namespace gsr { } void SettingsPage::add_widgets(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices) { - add_widget(create_back_button()); - add_widget(create_settings_icon()); content_page_ptr->add_widget(create_settings(gsr_info, audio_devices)); record_area_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { @@ -400,9 +363,20 @@ namespace gsr { std::unique_ptr<List> SettingsPage::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_theme().text_color)); - auto save_directory_entry = std::make_unique<Entry>(&get_theme().body_font, "/home/dec05eba/Videos", get_theme().body_font.get_character_size() * 20); - save_directory_entry_ptr = save_directory_entry.get(); - save_directory_list->add_widget(std::move(save_directory_entry)); + auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, "/home/dec05eba/Videos", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + save_directory_button->on_click = [this]() { + auto select_directory_page = std::make_unique<GsrPage>(); + select_directory_page->set_on_back_button_click([this]() { + page_stack->pop(); + }); + + auto file_chooser = std::make_unique<gsr::FileChooser>("/home/dec05eba", select_directory_page->get_size()); + select_directory_page->add_widget(std::move(file_chooser)); + + page_stack->push(std::move(select_directory_page)); + }; + save_directory_button_ptr = save_directory_button.get(); + save_directory_list->add_widget(std::move(save_directory_button)); return save_directory_list; } @@ -658,12 +632,12 @@ namespace gsr { static void save_audio_tracks(std::vector<std::string> &audio_tracks, List *audio_devices_list_ptr) { audio_tracks.clear(); - const std::vector<std::unique_ptr<Widget>> &audio_devices_list = audio_devices_list_ptr->get_child_widgets(); - for(const std::unique_ptr<Widget> &child_widget : audio_devices_list) { + audio_devices_list_ptr->for_each_child_widget([&audio_tracks](std::unique_ptr<Widget> &child_widget) { List *audio_device_line = static_cast<List*>(child_widget.get()); ComboBox *audio_device_box = static_cast<ComboBox*>(audio_device_line->get_child_widget_by_index(0)); audio_tracks.push_back(audio_device_box->get_selected_id()); - } + return true; + }); } void SettingsPage::save_common(RecordOptions &record_options) { @@ -705,7 +679,7 @@ namespace gsr { 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_entry_ptr->get_text(); + 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()); @@ -719,7 +693,7 @@ namespace gsr { save_common(config->record_config.record_options); 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.save_directory = save_directory_entry_ptr->get_text(); + config->record_config.save_directory = save_directory_button_ptr->get_text(); config->record_config.container = container_box_ptr->get_selected_id(); } diff --git a/src/main.cpp b/src/main.cpp index be872d9..138cd3f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,24 +4,17 @@ #include "../include/gui/CustomRendererWidget.hpp" #include "../include/gui/SettingsPage.hpp" #include "../include/gui/Utils.hpp" +#include "../include/gui/PageStack.hpp" #include "../include/Process.hpp" #include "../include/Theme.hpp" #include "../include/GsrInfo.hpp" #include "../include/window_texture.h" #include "../include/Config.hpp" -#include <stdio.h> -#include <stdlib.h> #include <string.h> -#include <time.h> -#include <errno.h> -#include <libgen.h> #include <signal.h> #include <sys/wait.h> -#include <optional> -#include <signal.h> #include <assert.h> -#include <stack> #include <X11/Xlib.h> #include <X11/cursorfont.h> @@ -235,10 +228,10 @@ int main(int argc, char **argv) { signal(SIGINT, sigint_handler); gsr::GsrInfo gsr_info; - // TODO: + // TODO: Show the error in ui gsr::GsrInfoExitStatus gsr_info_exit_status = gsr::get_gpu_screen_recorder_info(&gsr_info); if(gsr_info_exit_status != gsr::GsrInfoExitStatus::OK) { - fprintf(stderr, "error: failed to get gpu-screen-recorder info\n"); + fprintf(stderr, "error: failed to get gpu-screen-recorder info, error: %d\n", (int)gsr_info_exit_status); exit(1); } @@ -331,8 +324,6 @@ int main(int argc, char **argv) { WindowTexture window_texture; bool window_texture_loaded = false; - mgl_texture window_texture_tex; - memset(&window_texture_tex, 0, sizeof(window_texture_tex)); mgl::Texture window_texture_texture; mgl::Sprite window_texture_sprite; @@ -343,18 +334,7 @@ int main(int argc, char **argv) { window_texture_loaded = window_texture_init(&window_texture, display, mgl_window_get_egl_display(window.internal_window()), window_at_cursor_position, egl_funcs) == 0; if(window_texture_loaded && window_texture.texture_id) { - DrawableGeometry geometry; - get_drawable_geometry(display, (Drawable)window_texture.pixmap, &geometry); - - window_texture_tex.id = window_texture.texture_id; - window_texture_tex.width = geometry.width; - window_texture_tex.height = geometry.height; - window_texture_tex.format = MGL_TEXTURE_FORMAT_RGB; - window_texture_tex.max_width = 1 << 15; - window_texture_tex.max_height = 1 << 15; - window_texture_tex.pixel_coordinates = false; - window_texture_tex.mipmap = false; - window_texture_texture = mgl::Texture::reference(window_texture_tex); + window_texture_texture = mgl::Texture(window_texture.texture_id, MGL_TEXTURE_FORMAT_RGB); window_texture_sprite.set_texture(&window_texture_texture); } else { XImage *img = XGetImage(display, DefaultRootWindow(display), window_pos.x, window_pos.y, window_size.x, window_size.y, AllPlanes, ZPixmap); @@ -376,33 +356,14 @@ int main(int argc, char **argv) { mgl::Rectangle bg_screenshot_overlay(window_size.to_vec2f()); bg_screenshot_overlay.set_color(bg_color); - gsr::StaticPage front_page(window_size.to_vec2f()); + auto front_page = std::make_unique<gsr::StaticPage>(window_size.to_vec2f()); + gsr::StaticPage *front_page_ptr = front_page.get(); - std::stack<gsr::Page*> page_stack; - page_stack.push(&front_page); - - const auto settings_back_button_callback = [&] { - page_stack.top()->on_navigate_away_from_page(); - page_stack.pop(); - }; + gsr::PageStack page_stack; + page_stack.push(std::move(front_page)); std::optional<gsr::Config> config = gsr::read_config(); - gsr::SettingsPage replay_settings_page(gsr::SettingsPage::Type::REPLAY, gsr_info, audio_devices, config); - replay_settings_page.on_back_button_handler = settings_back_button_callback; - - gsr::SettingsPage record_settings_page(gsr::SettingsPage::Type::RECORD, gsr_info, audio_devices, config); - record_settings_page.on_back_button_handler = settings_back_button_callback; - - gsr::SettingsPage stream_settings_page(gsr::SettingsPage::Type::STREAM, gsr_info, audio_devices, config); - stream_settings_page.on_back_button_handler = settings_back_button_callback; - - if(!config) { - replay_settings_page.save(); - record_settings_page.save(); - stream_settings_page.save(); - } - struct MainButton { gsr::DropdownButton* button; gsr::GsrMode mode; @@ -444,7 +405,7 @@ int main(int argc, char **argv) { button->add_item("Start", "start"); button->add_item("Settings", "settings"); gsr::DropdownButton *button_ptr = button.get(); - front_page.add_widget(std::move(button)); + front_page_ptr->add_widget(std::move(button)); MainButton main_button = { button_ptr, @@ -488,7 +449,8 @@ int main(int argc, char **argv) { // Replay main_buttons[0].button->on_click = [&](const std::string &id) { if(id == "settings") { - page_stack.push(&replay_settings_page); + auto replay_settings_page = std::make_unique<gsr::SettingsPage>(gsr::SettingsPage::Type::REPLAY, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(replay_settings_page)); return; } /* @@ -513,7 +475,8 @@ int main(int argc, char **argv) { // Record main_buttons[1].button->on_click = [&](const std::string &id) { if(id == "settings") { - page_stack.push(&record_settings_page); + auto record_settings_page = std::make_unique<gsr::SettingsPage>(gsr::SettingsPage::Type::RECORD, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(record_settings_page)); return; } @@ -598,7 +561,8 @@ int main(int argc, char **argv) { // Stream main_buttons[2].button->on_click = [&](const std::string &id) { if(id == "settings") { - page_stack.push(&stream_settings_page); + auto stream_settings_page = std::make_unique<gsr::SettingsPage>(gsr::SettingsPage::Type::STREAM, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(stream_settings_page)); return; } }; @@ -733,6 +697,8 @@ int main(int argc, char **argv) { quit: fprintf(stderr, "shutting down!\n"); + XUngrabKeyboard(display, CurrentTime); + XUngrabPointer(display, CurrentTime); if(window_texture_loaded) window_texture_deinit(&window_texture); gsr::deinit_theme(); |