mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-07 20:08:07 +09:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d3abace0e | ||
|
|
47c02fc6c8 | ||
|
|
5f8c366b43 | ||
|
|
f4ed622510 | ||
|
|
f1ee19d014 | ||
|
|
67a8040e57 | ||
|
|
ff00be30df |
2
TODO
2
TODO
@@ -25,8 +25,6 @@ Have different modes. Overlay, window and side menu. Overlay can be used on x11,
|
||||
|
||||
Show navigation breadcrumbs for settings and deeper navigation (such as selecting a directory to save videos).
|
||||
|
||||
Add option to hide stream key like a password input.
|
||||
|
||||
Add global setting. In that setting there should be an option to enable/disable gsr-ui from system startup (the systemd service).
|
||||
|
||||
Add profiles and hotkey to switch between profiles (show notification when switching profile).
|
||||
|
||||
Submodule depends/mglpp updated: 0a3fe76641...b247af888c
BIN
images/masked.png
Normal file
BIN
images/masked.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 930 B |
BIN
images/unmasked.png
Normal file
BIN
images/unmasked.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -44,6 +44,8 @@ namespace gsr {
|
||||
mgl::Texture save_texture;
|
||||
mgl::Texture screenshot_texture;
|
||||
mgl::Texture trash_texture;
|
||||
mgl::Texture masked_texture;
|
||||
mgl::Texture unmasked_texture;
|
||||
|
||||
mgl::Texture ps4_home_texture;
|
||||
mgl::Texture ps4_options_texture;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <functional>
|
||||
|
||||
#include <mglpp/graphics/Color.hpp>
|
||||
#include <mglpp/graphics/Text.hpp>
|
||||
#include <mglpp/graphics/Text32.hpp>
|
||||
#include <mglpp/graphics/Rectangle.hpp>
|
||||
|
||||
namespace gsr {
|
||||
@@ -15,10 +15,20 @@ namespace gsr {
|
||||
ALLOW,
|
||||
REPLACED
|
||||
};
|
||||
using EntryValidateHandler = std::function<EntryValidateHandlerResult(Entry &entry, const std::string &str)>;
|
||||
using EntryValidateHandler = std::function<EntryValidateHandlerResult(Entry &entry, const std::u32string &str)>;
|
||||
|
||||
struct CaretIndexPos {
|
||||
int index;
|
||||
mgl::vec2f pos;
|
||||
};
|
||||
|
||||
class Entry : public Widget {
|
||||
public:
|
||||
enum class Direction {
|
||||
LEFT,
|
||||
RIGHT
|
||||
};
|
||||
|
||||
Entry(mgl::Font *font, const char *text, float max_width);
|
||||
Entry(const Entry&) = delete;
|
||||
Entry& operator=(const Entry&) = delete;
|
||||
@@ -28,11 +38,11 @@ namespace gsr {
|
||||
|
||||
mgl::vec2f get_size() override;
|
||||
|
||||
EntryValidateHandlerResult set_text(std::string str);
|
||||
const std::string& get_text() const;
|
||||
EntryValidateHandlerResult set_text(const std::string &str);
|
||||
std::string get_text() const;
|
||||
|
||||
// Also updates the cursor position
|
||||
void replace_text(size_t index, size_t size, const std::string &replacement);
|
||||
void set_masked(bool masked);
|
||||
bool is_masked() const;
|
||||
|
||||
// Return false to specify that the string should not be accepted. This reverts the string back to its previous value.
|
||||
// The input can be changed by changing the input parameter and returning true.
|
||||
@@ -40,24 +50,28 @@ namespace gsr {
|
||||
|
||||
std::function<void(const std::string &text)> on_changed;
|
||||
private:
|
||||
EntryValidateHandlerResult set_text_internal(std::string str);
|
||||
// Also updates the cursor position
|
||||
void replace_text(size_t index, size_t size, const std::u32string &replacement);
|
||||
void move_caret_word(Direction direction, size_t max_codepoints);
|
||||
EntryValidateHandlerResult set_text_internal(std::u32string str);
|
||||
void draw_caret(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size);
|
||||
void draw_caret_selection(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size);
|
||||
mgl_index_codepoint_pair find_closest_caret_index_by_position(mgl::vec2f position);
|
||||
CaretIndexPos find_closest_caret_index_by_position(mgl::vec2f position);
|
||||
private:
|
||||
struct Caret {
|
||||
float offset_x = 0.0f;
|
||||
int utf8_index = 0;
|
||||
int byte_index = 0;
|
||||
int index = 0;
|
||||
};
|
||||
|
||||
mgl::Rectangle background;
|
||||
mgl::Text text;
|
||||
mgl::Text32 text;
|
||||
mgl::Text32 masked_text;
|
||||
float max_width;
|
||||
bool selected = false;
|
||||
bool selecting_text = false;
|
||||
bool selecting_with_keyboard = false;
|
||||
bool show_selection = false;
|
||||
bool masked = false;
|
||||
Caret caret;
|
||||
Caret selection_start_caret;
|
||||
float text_overflow = 0.0f;
|
||||
|
||||
@@ -118,9 +118,11 @@ namespace gsr {
|
||||
std::unique_ptr<ComboBox> create_streaming_service_box();
|
||||
std::unique_ptr<List> create_streaming_service_section();
|
||||
std::unique_ptr<List> create_stream_key_section();
|
||||
std::unique_ptr<List> create_stream_custom_url();
|
||||
std::unique_ptr<List> create_stream_custom_key();
|
||||
std::unique_ptr<List> create_stream_custom_section();
|
||||
std::unique_ptr<ComboBox> create_stream_container_box();
|
||||
std::unique_ptr<List> create_stream_container_section();
|
||||
std::unique_ptr<List> create_stream_container();
|
||||
void add_stream_widgets();
|
||||
|
||||
void load_audio_tracks(const RecordOptions &record_options);
|
||||
@@ -173,8 +175,7 @@ namespace gsr {
|
||||
ComboBox *container_box_ptr = nullptr;
|
||||
ComboBox *streaming_service_box_ptr = nullptr;
|
||||
List *stream_key_list_ptr = nullptr;
|
||||
List *stream_url_list_ptr = nullptr;
|
||||
List *container_list_ptr = nullptr;
|
||||
List *custom_stream_list_ptr = nullptr;
|
||||
CheckBox *save_replay_in_game_folder_ptr = nullptr;
|
||||
CheckBox *restart_replay_on_save = nullptr;
|
||||
Label *estimated_file_size_ptr = nullptr;
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace gsr {
|
||||
|
||||
void set_visible(bool visible);
|
||||
|
||||
Widget* get_parent_widget();
|
||||
|
||||
void *userdata = nullptr;
|
||||
protected:
|
||||
void set_widget_as_selected_in_parent();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.7.2', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.7.3', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
|
||||
if get_option('buildtype') == 'debug'
|
||||
add_project_arguments('-g3', language : ['c', 'cpp'])
|
||||
@@ -62,7 +62,7 @@ datadir = get_option('datadir')
|
||||
gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
|
||||
|
||||
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
|
||||
add_project_arguments('-DGSR_FLATPAK_VERSION="5.7.5"', language: ['c', 'cpp'])
|
||||
add_project_arguments('-DGSR_FLATPAK_VERSION="5.7.6"', language: ['c', 'cpp'])
|
||||
|
||||
executable(
|
||||
meson.project_name(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.7.2"
|
||||
version = "1.7.3"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
|
||||
@@ -1951,7 +1951,15 @@ namespace gsr {
|
||||
void Overlay::on_gsr_process_error(int exit_code, NotificationType notification_type) {
|
||||
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
|
||||
if(exit_code == 50) {
|
||||
show_notification("Desktop portal capture failed.\nEither you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture\nor it's incorrectly setup on your system", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||
show_notification("Desktop portal capture failed.\nEither you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture\nor it's incorrectly setup on your system.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||
} else if(exit_code == 51) {
|
||||
show_notification("Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||
} else if(exit_code == 52) {
|
||||
show_notification("Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||
} else if(exit_code == 53) {
|
||||
show_notification("Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||
} else if(exit_code == 54) {
|
||||
show_notification("Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||
} else if(exit_code == 60) {
|
||||
show_notification("Stopped capture because the user canceled the desktop portal", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
|
||||
} else {
|
||||
@@ -2468,7 +2476,7 @@ namespace gsr {
|
||||
kill(gpu_screen_recorder_process, SIGRTMIN+5);
|
||||
}
|
||||
|
||||
static const char* switch_video_codec_to_usable_hardware_encoder(const GsrInfo &gsr_info) {
|
||||
static const char* get_first_usable_hardware_video_codec_name(const GsrInfo &gsr_info) {
|
||||
if(gsr_info.supported_video_codecs.h264)
|
||||
return "h264";
|
||||
else if(gsr_info.supported_video_codecs.hevc)
|
||||
@@ -2501,8 +2509,7 @@ namespace gsr {
|
||||
*video_codec = "h264";
|
||||
*encoder = "cpu";
|
||||
} else if(strcmp(*video_codec, "auto") == 0) {
|
||||
*video_codec = switch_video_codec_to_usable_hardware_encoder(gsr_info);
|
||||
if(!*video_codec) {
|
||||
if(!get_first_usable_hardware_video_codec_name(gsr_info)) {
|
||||
*video_codec = "h264";
|
||||
*encoder = "cpu";
|
||||
}
|
||||
|
||||
@@ -120,6 +120,12 @@ namespace gsr {
|
||||
if(!theme->trash_texture.load_from_file((resources_path + "images/trash.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||
goto error;
|
||||
|
||||
if(!theme->masked_texture.load_from_file((resources_path + "images/masked.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||
goto error;
|
||||
|
||||
if(!theme->unmasked_texture.load_from_file((resources_path + "images/unmasked.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||
goto error;
|
||||
|
||||
if(!theme->ps4_home_texture.load_from_file((resources_path + "images/ps4_home.png").c_str(), mgl::Texture::LoadOptions{false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP}))
|
||||
goto error;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mglpp/system/FloatRect.hpp>
|
||||
#include <mglpp/system/Utf8.hpp>
|
||||
#include <optional>
|
||||
#include <string.h>
|
||||
|
||||
namespace gsr {
|
||||
static const float padding_top_scale = 0.004629f;
|
||||
@@ -22,8 +23,13 @@ namespace gsr {
|
||||
}
|
||||
}
|
||||
|
||||
Entry::Entry(mgl::Font *font, const char *text, float max_width) : text("", *font), max_width(max_width) {
|
||||
Entry::Entry(mgl::Font *font, const char *text, float max_width) :
|
||||
text(std::u32string(), *font),
|
||||
masked_text(std::u32string(), *font),
|
||||
max_width(max_width)
|
||||
{
|
||||
this->text.set_color(get_color_theme().text_color);
|
||||
this->masked_text.set_color(get_color_theme().text_color);
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
@@ -31,6 +37,8 @@ namespace gsr {
|
||||
if(!visible)
|
||||
return true;
|
||||
|
||||
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||
|
||||
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
|
||||
const mgl::vec2f mouse_pos = { (float)event.mouse_button.x, (float)event.mouse_button.y };
|
||||
selected = mgl::FloatRect(position + offset, get_size()).contains(mouse_pos);
|
||||
@@ -38,9 +46,8 @@ namespace gsr {
|
||||
selecting_text = true;
|
||||
|
||||
const auto caret_index_mouse = find_closest_caret_index_by_position(mouse_pos);
|
||||
caret.byte_index = caret_index_mouse.byte_index;
|
||||
caret.utf8_index = caret_index_mouse.utf8_index;
|
||||
caret.offset_x = caret_index_mouse.pos.x - this->text.get_position().x;
|
||||
caret.index = caret_index_mouse.index;
|
||||
caret.offset_x = caret_index_mouse.pos.x - active_text.get_position().x;
|
||||
selection_start_caret = caret;
|
||||
show_selection = true;
|
||||
} else {
|
||||
@@ -50,89 +57,72 @@ namespace gsr {
|
||||
}
|
||||
} else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left) {
|
||||
selecting_text = false;
|
||||
if(caret.byte_index == selection_start_caret.byte_index)
|
||||
if(caret.index == selection_start_caret.index)
|
||||
show_selection = false;
|
||||
} else if(event.type == mgl::Event::MouseMoved && selected) {
|
||||
if(selecting_text) {
|
||||
const auto caret_index_mouse = find_closest_caret_index_by_position(mgl::vec2f(event.mouse_move.x, event.mouse_move.y));
|
||||
caret.byte_index = caret_index_mouse.byte_index;
|
||||
caret.utf8_index = caret_index_mouse.utf8_index;
|
||||
caret.offset_x = caret_index_mouse.pos.x - this->text.get_position().x;
|
||||
caret.index = caret_index_mouse.index;
|
||||
caret.offset_x = caret_index_mouse.pos.x - active_text.get_position().x;
|
||||
return false;
|
||||
}
|
||||
} else if(event.type == mgl::Event::KeyPressed && selected) {
|
||||
int selection_start_byte = caret.byte_index;
|
||||
int selection_end_byte = caret.byte_index;
|
||||
int selection_start_byte = caret.index;
|
||||
int selection_end_byte = caret.index;
|
||||
if(show_selection) {
|
||||
selection_start_byte = std::min(caret.byte_index, selection_start_caret.byte_index);
|
||||
selection_end_byte = std::max(caret.byte_index, selection_start_caret.byte_index);
|
||||
selection_start_byte = std::min(caret.index, selection_start_caret.index);
|
||||
selection_end_byte = std::max(caret.index, selection_start_caret.index);
|
||||
}
|
||||
|
||||
if(event.key.code == mgl::Keyboard::Backspace) {
|
||||
if(selection_start_byte == selection_end_byte && caret.byte_index > 0)
|
||||
selection_start_byte = mgl::utf8_get_start_of_codepoint((const unsigned char*)text.get_string().c_str(), text.get_string().size(), caret.byte_index - 1);
|
||||
if(selection_start_byte == selection_end_byte && caret.index > 0)
|
||||
selection_start_byte -= 1;
|
||||
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, "");
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::u32string());
|
||||
} else if(event.key.code == mgl::Keyboard::Delete) {
|
||||
if(selection_start_byte == selection_end_byte && caret.byte_index < (int)text.get_string().size()) {
|
||||
size_t codepoint_length = 1;
|
||||
mgl::utf8_get_codepoint_length(((const unsigned char*)text.get_string().c_str())[caret.byte_index], &codepoint_length);
|
||||
selection_end_byte = selection_start_byte + codepoint_length;
|
||||
}
|
||||
if(selection_start_byte == selection_end_byte && caret.index < (int)active_text.get_string().size())
|
||||
selection_end_byte += 1;
|
||||
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, "");
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::u32string());
|
||||
} else if(event.key.code == mgl::Keyboard::C && event.key.control) {
|
||||
const size_t selection_num_bytes = selection_end_byte - selection_start_byte;
|
||||
if(selection_num_bytes > 0)
|
||||
window.set_clipboard(text.get_string().substr(selection_start_byte, selection_num_bytes));
|
||||
window.set_clipboard(mgl::utf32_to_utf8(text.get_string().substr(selection_start_byte, selection_num_bytes)));
|
||||
} else if(event.key.code == mgl::Keyboard::V && event.key.control) {
|
||||
std::string clipboard_string = window.get_clipboard_string();
|
||||
string_replace_all(clipboard_string, '\n', ' ');
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::move(clipboard_string));
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, mgl::utf8_to_utf32(clipboard_string));
|
||||
} else if(event.key.code == mgl::Keyboard::A && event.key.control) {
|
||||
selection_start_caret.byte_index = 0;
|
||||
selection_start_caret.utf8_index = 0;
|
||||
selection_start_caret.index = 0;
|
||||
selection_start_caret.offset_x = 0.0f;
|
||||
|
||||
caret.byte_index = text.get_string().size();
|
||||
caret.utf8_index = mgl::utf8_get_character_count((const unsigned char*)text.get_string().data(), text.get_string().size());
|
||||
caret.index = active_text.get_string().size();
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||
|
||||
show_selection = true;
|
||||
} else if(event.key.code == mgl::Keyboard::Left && caret.byte_index > 0) {
|
||||
if(!selecting_with_keyboard && show_selection) {
|
||||
} else if(event.key.code == mgl::Keyboard::Left) {
|
||||
if(!selecting_with_keyboard && show_selection)
|
||||
show_selection = false;
|
||||
} else {
|
||||
caret.byte_index = mgl::utf8_get_start_of_codepoint((const unsigned char*)text.get_string().data(), text.get_string().size(), caret.byte_index - 1);
|
||||
caret.utf8_index -= 1;
|
||||
// TODO: Move left by one character instead of calculating every character to caret index
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
}
|
||||
else
|
||||
move_caret_word(Direction::LEFT, event.key.control ? 999999 : 1);
|
||||
|
||||
if(!selecting_with_keyboard) {
|
||||
selection_start_caret = caret;
|
||||
show_selection = false;
|
||||
}
|
||||
} else if(event.key.code == mgl::Keyboard::Right) {
|
||||
if(!selecting_with_keyboard && show_selection) {
|
||||
if(!selecting_with_keyboard && show_selection)
|
||||
show_selection = false;
|
||||
} else {
|
||||
const int caret_byte_index_before = caret.byte_index;
|
||||
caret.byte_index = mgl::utf8_index_to_byte_index((const unsigned char*)text.get_string().data(), text.get_string().size(), caret.utf8_index + 1);
|
||||
if(caret.byte_index != caret_byte_index_before)
|
||||
caret.utf8_index += 1;
|
||||
// TODO: Move right by one character instead of calculating every character to caret index
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
}
|
||||
else
|
||||
move_caret_word(Direction::RIGHT, event.key.control ? 999999 : 1);
|
||||
|
||||
if(!selecting_with_keyboard) {
|
||||
selection_start_caret = caret;
|
||||
show_selection = false;
|
||||
}
|
||||
} else if(event.key.code == mgl::Keyboard::Home) {
|
||||
caret.byte_index = 0;
|
||||
caret.utf8_index = 0;
|
||||
caret.index = 0;
|
||||
caret.offset_x = 0.0f;
|
||||
|
||||
if(!selecting_with_keyboard) {
|
||||
@@ -140,10 +130,9 @@ namespace gsr {
|
||||
show_selection = false;
|
||||
}
|
||||
} else if(event.key.code == mgl::Keyboard::End) {
|
||||
caret.byte_index = text.get_string().size();
|
||||
caret.utf8_index = mgl::utf8_get_character_count((const unsigned char*)text.get_string().data(), text.get_string().size());
|
||||
caret.index = active_text.get_string().size();
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||
|
||||
if(!selecting_with_keyboard) {
|
||||
selection_start_caret = caret;
|
||||
@@ -164,14 +153,14 @@ namespace gsr {
|
||||
|
||||
return false;
|
||||
} else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32 && event.text.codepoint != 127) {
|
||||
int selection_start_byte = caret.byte_index;
|
||||
int selection_end_byte = caret.byte_index;
|
||||
int selection_start_byte = caret.index;
|
||||
int selection_end_byte = caret.index;
|
||||
if(show_selection) {
|
||||
selection_start_byte = std::min(caret.byte_index, selection_start_caret.byte_index);
|
||||
selection_end_byte = std::max(caret.byte_index, selection_start_caret.byte_index);
|
||||
selection_start_byte = std::min(caret.index, selection_start_caret.index);
|
||||
selection_end_byte = std::max(caret.index, selection_start_caret.index);
|
||||
}
|
||||
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::string(event.text.str, event.text.size));
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, mgl::utf8_to_utf32((const unsigned char*)event.text.str, event.text.size));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -189,13 +178,15 @@ namespace gsr {
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
const int padding_right = padding_right_scale * get_theme().window_height;
|
||||
|
||||
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||
|
||||
background.set_size(get_size());
|
||||
background.set_position(draw_pos.floor());
|
||||
background.set_color(selected ? mgl::Color(0, 0, 0, 255) : mgl::Color(0, 0, 0, 120));
|
||||
window.draw(background);
|
||||
|
||||
const int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
|
||||
const mgl::vec2f caret_size = mgl::vec2f(caret_width, text.get_bounds().size.y).floor();
|
||||
const mgl::vec2f caret_size = mgl::vec2f(caret_width, active_text.get_bounds().size.y).floor();
|
||||
|
||||
const float overflow_left = (caret.offset_x + padding_left) - (padding_left + text_overflow);
|
||||
if(overflow_left < 0.0f)
|
||||
@@ -205,18 +196,18 @@ namespace gsr {
|
||||
if(overflow_right - text_overflow > 0.0f)
|
||||
text_overflow = overflow_right;
|
||||
|
||||
text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - text.get_bounds().size.y * 0.5f) - mgl::vec2f(text_overflow, 0.0f)).floor());
|
||||
active_text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - active_text.get_bounds().size.y * 0.5f) - mgl::vec2f(text_overflow, 0.0f)).floor());
|
||||
|
||||
const auto text_bounds = text.get_bounds();
|
||||
const auto text_bounds = active_text.get_bounds();
|
||||
const bool text_larger_than_background = text_bounds.size.x > (background.get_size().x - padding_left - padding_right);
|
||||
const float text_overflow_right = (text_bounds.position.x + text_bounds.size.x) - (background.get_position().x + background.get_size().x - padding_right);
|
||||
if(text_larger_than_background) {
|
||||
if(text_overflow_right < 0.0f) {
|
||||
text_overflow += text_overflow_right;
|
||||
text.set_position(text.get_position() + mgl::vec2f(-text_overflow_right, 0.0f));
|
||||
active_text.set_position(active_text.get_position() + mgl::vec2f(-text_overflow_right, 0.0f));
|
||||
}
|
||||
} else {
|
||||
text.set_position(text.get_position() + mgl::vec2f(-text_overflow, 0.0f));
|
||||
active_text.set_position(active_text.get_position() + mgl::vec2f(-text_overflow, 0.0f));
|
||||
text_overflow = 0.0f;
|
||||
}
|
||||
|
||||
@@ -234,7 +225,7 @@ namespace gsr {
|
||||
});
|
||||
window.set_scissor(scissor);
|
||||
|
||||
window.draw(text);
|
||||
window.draw(active_text);
|
||||
|
||||
if(show_selection)
|
||||
draw_caret_selection(window, draw_pos, caret_size);
|
||||
@@ -254,11 +245,16 @@ namespace gsr {
|
||||
}
|
||||
|
||||
void Entry::draw_caret_selection(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size) {
|
||||
if(selection_start_caret.index == caret.index)
|
||||
return;
|
||||
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
const int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
|
||||
const int offset = caret.index < selection_start_caret.index ? caret_width : 0;
|
||||
|
||||
mgl::Rectangle caret_selection_rect(mgl::vec2f(std::abs(selection_start_caret.offset_x - caret.offset_x), caret_size.y).floor());
|
||||
caret_selection_rect.set_position((draw_pos + mgl::vec2f(padding_left + std::min(caret.offset_x, selection_start_caret.offset_x) - text_overflow, padding_top)).floor());
|
||||
mgl::Rectangle caret_selection_rect(mgl::vec2f(std::abs(selection_start_caret.offset_x - caret.offset_x) - offset, caret_size.y).floor());
|
||||
caret_selection_rect.set_position((draw_pos + mgl::vec2f(padding_left + std::min(caret.offset_x, selection_start_caret.offset_x) - text_overflow + offset, padding_top)).floor());
|
||||
mgl::Color caret_select_color = get_color_theme().tint_color;
|
||||
caret_select_color.a = 100;
|
||||
caret_selection_rect.set_color(caret_select_color);
|
||||
@@ -274,13 +270,43 @@ namespace gsr {
|
||||
return { max_width, text.get_bounds().size.y + padding_top + padding_bottom };
|
||||
}
|
||||
|
||||
EntryValidateHandlerResult Entry::set_text(std::string str) {
|
||||
EntryValidateHandlerResult validate_result = set_text_internal(std::move(str));
|
||||
void Entry::move_caret_word(Direction direction, size_t max_codepoints) {
|
||||
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||
const int dir_step = direction == Direction::LEFT ? -1 : 1;
|
||||
const int num_delimiter_chars = 15;
|
||||
const char delimiter_chars[num_delimiter_chars + 1] = " \t\n/.,:;\\[](){}";
|
||||
const char32_t *text_str = active_text.get_string().data();
|
||||
|
||||
int num_non_delimiter_chars_found = 0;
|
||||
|
||||
for(size_t i = 0; i < max_codepoints; ++i) {
|
||||
const uint32_t codepoint = text_str[caret.index];
|
||||
|
||||
const bool is_delimiter_char = codepoint < 127 && !!memchr(delimiter_chars, codepoint, num_delimiter_chars);
|
||||
if(is_delimiter_char) {
|
||||
if(num_non_delimiter_chars_found > 0)
|
||||
break;
|
||||
} else {
|
||||
++num_non_delimiter_chars_found;
|
||||
}
|
||||
|
||||
if(caret.index + dir_step < 0 || caret.index + dir_step > (int)active_text.get_string().size())
|
||||
break;
|
||||
|
||||
caret.index += dir_step;
|
||||
}
|
||||
|
||||
// TODO: Move right by some characters instead of calculating every character to caret index
|
||||
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||
}
|
||||
|
||||
EntryValidateHandlerResult Entry::set_text(const std::string &str) {
|
||||
EntryValidateHandlerResult validate_result = set_text_internal(mgl::utf8_to_utf32(str));
|
||||
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
||||
caret.byte_index = text.get_string().size();
|
||||
caret.utf8_index = mgl::utf8_get_character_count((const unsigned char*)text.get_string().data(), text.get_string().size());
|
||||
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||
caret.index = active_text.get_string().size();
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||
selection_start_caret = caret;
|
||||
|
||||
selecting_text = false;
|
||||
@@ -290,40 +316,58 @@ namespace gsr {
|
||||
return validate_result;
|
||||
}
|
||||
|
||||
EntryValidateHandlerResult Entry::set_text_internal(std::string str) {
|
||||
EntryValidateHandlerResult Entry::set_text_internal(std::u32string str) {
|
||||
EntryValidateHandlerResult validate_result = EntryValidateHandlerResult::ALLOW;
|
||||
if(validate_handler)
|
||||
validate_result = validate_handler(*this, str);
|
||||
|
||||
if(validate_result == EntryValidateHandlerResult::ALLOW) {
|
||||
text.set_string(std::move(str));
|
||||
if(masked)
|
||||
masked_text.set_string(std::u32string(text.get_string().size(), '*'));
|
||||
// TODO: Call callback with utf32 instead?
|
||||
if(on_changed)
|
||||
on_changed(text.get_string());
|
||||
on_changed(mgl::utf32_to_utf8(text.get_string()));
|
||||
}
|
||||
|
||||
return validate_result;
|
||||
}
|
||||
|
||||
const std::string& Entry::get_text() const {
|
||||
return text.get_string();
|
||||
std::string Entry::get_text() const {
|
||||
return mgl::utf32_to_utf8(text.get_string());
|
||||
}
|
||||
|
||||
void Entry::replace_text(size_t index, size_t size, const std::string &replacement) {
|
||||
void Entry::set_masked(bool masked) {
|
||||
if(masked == this->masked)
|
||||
return;
|
||||
|
||||
this->masked = masked;
|
||||
|
||||
if(masked)
|
||||
masked_text.set_string(std::u32string(text.get_string().size(), '*'));
|
||||
else
|
||||
masked_text.set_string(std::u32string());
|
||||
|
||||
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||
}
|
||||
|
||||
bool Entry::is_masked() const {
|
||||
return masked;
|
||||
}
|
||||
|
||||
void Entry::replace_text(size_t index, size_t size, const std::u32string &replacement) {
|
||||
if(index + size > text.get_string().size())
|
||||
return;
|
||||
|
||||
const auto prev_caret = caret;
|
||||
|
||||
if((int)index >= caret.byte_index) {
|
||||
caret.utf8_index += mgl::utf8_get_character_count((const unsigned char*)replacement.c_str(), replacement.size());
|
||||
caret.byte_index += replacement.size();
|
||||
} else {
|
||||
caret.utf8_index -= mgl::utf8_get_character_count((const unsigned char*)(text.get_string().c_str() + caret.byte_index - size), size);
|
||||
caret.utf8_index += mgl::utf8_get_character_count((const unsigned char*)replacement.c_str(), replacement.size());
|
||||
caret.byte_index = caret.byte_index - size + replacement.size();
|
||||
}
|
||||
if((int)index >= caret.index)
|
||||
caret.index += replacement.size();
|
||||
else
|
||||
caret.index = caret.index - size + replacement.size();
|
||||
|
||||
std::string str = text.get_string();
|
||||
std::u32string str = text.get_string();
|
||||
str.replace(index, size, replacement);
|
||||
const EntryValidateHandlerResult validate_result = set_text_internal(std::move(str));
|
||||
if(validate_result == EntryValidateHandlerResult::DENY) {
|
||||
@@ -333,8 +377,9 @@ namespace gsr {
|
||||
return;
|
||||
}
|
||||
|
||||
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
caret.offset_x = active_text.find_character_pos(caret.index).x - active_text.get_position().x;
|
||||
selection_start_caret = caret;
|
||||
|
||||
selecting_text = false;
|
||||
@@ -342,17 +387,14 @@ namespace gsr {
|
||||
show_selection = false;
|
||||
}
|
||||
|
||||
mgl_index_codepoint_pair Entry::find_closest_caret_index_by_position(mgl::vec2f position) {
|
||||
const std::string &str = text.get_string();
|
||||
mgl::Font *font = text.get_font();
|
||||
CaretIndexPos Entry::find_closest_caret_index_by_position(mgl::vec2f position) {
|
||||
mgl::Text32 &active_text = masked ? masked_text : text;
|
||||
const std::u32string &str = active_text.get_string();
|
||||
mgl::Font *font = active_text.get_font();
|
||||
|
||||
mgl_index_codepoint_pair result = {0, 0, {text.get_position().x, text.get_position().y}};
|
||||
|
||||
for(; result.byte_index < str.size();) {
|
||||
uint32_t codepoint = ' ';
|
||||
size_t clen = 1;
|
||||
if(!mgl::utf8_decode((const unsigned char*)&str[result.byte_index], str.size() - result.byte_index, &codepoint, &clen))
|
||||
clen = 1;
|
||||
CaretIndexPos result = {0, {active_text.get_position().x, active_text.get_position().y}};
|
||||
for(result.index = 0; result.index < (int)str.size(); ++result.index) {
|
||||
const uint32_t codepoint = str[result.index];
|
||||
|
||||
float glyph_width = 0.0f;
|
||||
if(codepoint == '\t') {
|
||||
@@ -368,8 +410,6 @@ namespace gsr {
|
||||
break;
|
||||
|
||||
result.pos.x += glyph_width;
|
||||
result.byte_index += clen;
|
||||
result.utf8_index += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -379,7 +419,7 @@ namespace gsr {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
static std::optional<int> to_integer(const std::string &str) {
|
||||
static std::optional<int> to_integer(const std::u32string &str) {
|
||||
if(str.empty())
|
||||
return std::nullopt;
|
||||
|
||||
@@ -406,7 +446,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max) {
|
||||
return [min, max](Entry &entry, const std::string &str) {
|
||||
return [min, max](Entry &entry, const std::u32string &str) {
|
||||
if(str.empty())
|
||||
return EntryValidateHandlerResult::ALLOW;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "../../include/gui/PageStack.hpp"
|
||||
#include "../../include/gui/FileChooser.hpp"
|
||||
#include "../../include/gui/Subsection.hpp"
|
||||
#include "../../include/gui/Image.hpp"
|
||||
#include "../../include/Theme.hpp"
|
||||
#include "../../include/GsrInfo.hpp"
|
||||
#include "../../include/Utils.hpp"
|
||||
@@ -965,42 +966,75 @@ namespace gsr {
|
||||
return streaming_service_list;
|
||||
}
|
||||
|
||||
static std::unique_ptr<Button> create_mask_toggle_button(Entry *entry_to_toggle, mgl::vec2f size) {
|
||||
auto button = std::make_unique<Button>(&get_theme().body_font, "", size, mgl::Color(0, 0, 0, 0));
|
||||
Button *button_ptr = button.get();
|
||||
button->set_icon(&get_theme().masked_texture);
|
||||
button->on_click = [entry_to_toggle, button_ptr]() {
|
||||
const bool is_masked = entry_to_toggle->is_masked();
|
||||
button_ptr->set_icon(is_masked ? &get_theme().unmasked_texture : &get_theme().masked_texture);
|
||||
entry_to_toggle->set_masked(!is_masked);
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
static Entry* add_stream_key_entry_to_list(List *stream_key_list) {
|
||||
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
auto key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||
key_entry->set_masked(true);
|
||||
Entry *key_entry_ptr = key_entry.get();
|
||||
const float mask_icon_size = key_entry_ptr->get_size().y * 0.9f;
|
||||
list->add_widget(std::move(key_entry));
|
||||
list->add_widget(create_mask_toggle_button(key_entry_ptr, mgl::vec2f(mask_icon_size, mask_icon_size)));
|
||||
stream_key_list->add_widget(std::move(list));
|
||||
return key_entry_ptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_stream_key_section() {
|
||||
auto stream_key_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
stream_key_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
|
||||
|
||||
auto twitch_stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||
twitch_stream_key_entry_ptr = twitch_stream_key_entry.get();
|
||||
stream_key_list->add_widget(std::move(twitch_stream_key_entry));
|
||||
|
||||
auto youtube_stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||
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));
|
||||
twitch_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
|
||||
youtube_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
|
||||
rumble_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
|
||||
|
||||
stream_key_list_ptr = stream_key_list.get();
|
||||
return stream_key_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_stream_custom_section() {
|
||||
auto stream_url_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
stream_url_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream URL:", get_color_theme().text_color));
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_stream_custom_url() {
|
||||
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
auto stream_url_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||
stream_url_entry_ptr = stream_url_entry.get();
|
||||
stream_url_list->add_widget(std::move(stream_url_entry));
|
||||
|
||||
stream_url_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
|
||||
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream URL:", get_color_theme().text_color));
|
||||
list->add_widget(std::move(stream_url_entry));
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_stream_custom_key() {
|
||||
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
|
||||
auto stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||
stream_key_entry->set_masked(true);
|
||||
stream_key_entry_ptr = stream_key_entry.get();
|
||||
stream_url_list->add_widget(std::move(stream_key_entry));
|
||||
const float mask_icon_size = stream_key_entry_ptr->get_size().y * 0.9f;
|
||||
list->add_widget(std::move(stream_key_entry));
|
||||
list->add_widget(create_mask_toggle_button(stream_key_entry_ptr, mgl::vec2f(mask_icon_size, mask_icon_size)));
|
||||
return list;
|
||||
}
|
||||
|
||||
stream_url_list_ptr = stream_url_list.get();
|
||||
return stream_url_list;
|
||||
std::unique_ptr<List> SettingsPage::create_stream_custom_section() {
|
||||
auto custom_stream_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
|
||||
auto stream_url_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
||||
stream_url_list->add_widget(create_stream_custom_url());
|
||||
stream_url_list->add_widget(create_stream_container());
|
||||
|
||||
custom_stream_list->add_widget(std::move(stream_url_list));
|
||||
custom_stream_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
|
||||
custom_stream_list->add_widget(create_stream_custom_key());
|
||||
|
||||
custom_stream_list_ptr = custom_stream_list.get();
|
||||
return custom_stream_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<ComboBox> SettingsPage::create_stream_container_box() {
|
||||
@@ -1013,11 +1047,10 @@ namespace gsr {
|
||||
return container_box;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_stream_container_section() {
|
||||
std::unique_ptr<List> SettingsPage::create_stream_container() {
|
||||
auto container_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
container_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Container:", get_color_theme().text_color));
|
||||
container_list->add_widget(create_stream_container_box());
|
||||
container_list_ptr = container_list.get();
|
||||
return container_list;
|
||||
}
|
||||
|
||||
@@ -1026,7 +1059,6 @@ namespace gsr {
|
||||
streaming_info_list->add_widget(create_streaming_service_section());
|
||||
streaming_info_list->add_widget(create_stream_key_section());
|
||||
streaming_info_list->add_widget(create_stream_custom_section());
|
||||
streaming_info_list->add_widget(create_stream_container_section());
|
||||
settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming info", std::move(streaming_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
|
||||
|
||||
auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL);
|
||||
@@ -1051,11 +1083,10 @@ namespace gsr {
|
||||
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);
|
||||
custom_stream_list_ptr->set_visible(custom_option);
|
||||
twitch_stream_key_entry_ptr->get_parent_widget()->set_visible(twitch_option);
|
||||
youtube_stream_key_entry_ptr->get_parent_widget()->set_visible(youtube_option);
|
||||
rumble_stream_key_entry_ptr->get_parent_widget()->set_visible(rumble_option);
|
||||
return true;
|
||||
};
|
||||
streaming_service_box_ptr->on_selection_changed("Twitch", "twitch");
|
||||
|
||||
@@ -64,6 +64,10 @@ namespace gsr {
|
||||
this->visible = visible;
|
||||
}
|
||||
|
||||
Widget* Widget::get_parent_widget() {
|
||||
return parent_widget;
|
||||
}
|
||||
|
||||
void add_widget_to_remove(std::unique_ptr<Widget> widget) {
|
||||
widgets_to_remove.push_back(std::move(widget));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user