From 54c60d9a18d103011a12939c5029dd35a8e9e200 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 22 Aug 2024 21:44:06 +0200 Subject: Start on file chooser, page stack --- include/GsrInfo.hpp | 1 + include/SafeVector.hpp | 127 +++++++++++++++++++++++++++++++++++++++++ include/Theme.hpp | 5 +- include/Utils.hpp | 4 -- include/gui/Button.hpp | 2 + include/gui/ComboBox.hpp | 2 +- include/gui/FileChooser.hpp | 35 ++++++++++++ include/gui/GsrPage.hpp | 39 +++++++++++++ include/gui/List.hpp | 16 ++---- include/gui/Page.hpp | 2 +- include/gui/PageStack.hpp | 29 ++++++++++ include/gui/RadioButton.hpp | 2 +- include/gui/ScrollablePage.hpp | 17 +++--- include/gui/SettingsPage.hpp | 20 +++---- 14 files changed, 259 insertions(+), 42 deletions(-) create mode 100644 include/SafeVector.hpp create mode 100644 include/gui/FileChooser.hpp create mode 100644 include/gui/GsrPage.hpp create mode 100644 include/gui/PageStack.hpp (limited to 'include') 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 +#include +#include + +// A vector that can be modified while iterating +template +class SafeVector { +public: + using PointerType = typename std::pointer_traits::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 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 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 data; + std::vector 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 -#include #include #include #include @@ -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 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 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 +#include + +#include +#include + +// 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 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 + +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) override; + + void set_margins(float top, float bottom, float left, float right); + void set_on_back_button_click(std::function 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 +#include "../SafeVector.hpp" #include 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); - // 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>& get_child_widgets() const; + // Return true from |callback| to continue + void for_each_child_widget(std::function &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> widgets; - std::vector> add_queue; - std::vector remove_queue; + SafeVector> 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); + virtual void add_widget(std::unique_ptr widget); protected: std::vector> 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 + +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); + // 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> 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 +#include 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); 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> 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 - 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 &audio_devices, std::optional &config); + SettingsPage(Type type, const GsrInfo &gsr_info, const std::vector &audio_devices, std::optional &config, PageStack *page_stack); SettingsPage(const SettingsPage&) = delete; SettingsPage& operator=(const SettingsPage&) = delete; void save(); void on_navigate_away_from_page() override; - - std::function on_back_button_handler; private: - std::unique_ptr