mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-05 11:16:28 +09:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf282bc225 | ||
|
|
c05a8290b7 | ||
|
|
a9e118ea8f | ||
|
|
8ed1fe4799 | ||
|
|
c1d76b5169 | ||
|
|
b1e650c7ec | ||
|
|
2e0dc48f3e | ||
|
|
d64e698eb1 |
5
TODO
5
TODO
@@ -87,6 +87,7 @@ Dont put widget position to int position when scrolling. This makes the UI jitte
|
||||
Show warning if another instance of gpu screen recorder is already running when starting recording?
|
||||
|
||||
Keyboard leds get turned off when stopping gsr-global-hotkeys (for example numlock). The numlock key has to be pressed twice again to make it look correct to match its state.
|
||||
Fix this by writing 0 or 1 to /sys/class/leds/input2::numlock/brightness.
|
||||
|
||||
Make gsr-ui flatpak systemd work nicely with non-flatpak gsr-ui. Maybe change ExecStart to do flatpak run ... || gsr-ui, but make it run as a shell command first with /bin/sh -c "".
|
||||
|
||||
@@ -206,4 +207,6 @@ Disable hotkeys if virtual keyboard is found (either at startup or after running
|
||||
|
||||
Support localization.
|
||||
|
||||
Add option to not capture cursor in screenshot when doing region/window capture.
|
||||
Add option to not capture cursor in screenshot when doing region/window capture.
|
||||
|
||||
Window selection doesn't work when a window is fullscreen on x11.
|
||||
|
||||
Submodule depends/mglpp updated: a784fdb62b...0a3fe76641
@@ -85,6 +85,7 @@ namespace gsr {
|
||||
|
||||
struct CustomStreamConfig {
|
||||
std::string url;
|
||||
std::string key;
|
||||
std::string container = "flv";
|
||||
};
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ namespace gsr {
|
||||
|
||||
void close_gpu_screen_recorder_output();
|
||||
|
||||
double get_time_passed_in_replay_buffer_seconds();
|
||||
void update_notification_process_status();
|
||||
void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type);
|
||||
void on_replay_saved(const char *replay_saved_filepath);
|
||||
@@ -220,6 +221,12 @@ namespace gsr {
|
||||
bool try_replay_startup = true;
|
||||
bool replay_recording = false;
|
||||
int replay_save_duration_min = 0;
|
||||
double replay_buffer_save_duration_sec = 0.0;
|
||||
mgl::Clock replay_duration_clock;
|
||||
double replay_saved_duration_sec = 0.0;
|
||||
bool replay_restart_on_save = false;
|
||||
|
||||
mgl::Clock recording_duration_clock;
|
||||
|
||||
AudioPlayer audio_player;
|
||||
|
||||
|
||||
@@ -5,9 +5,17 @@
|
||||
|
||||
#include <mglpp/graphics/Color.hpp>
|
||||
#include <mglpp/graphics/Text.hpp>
|
||||
#include <mglpp/graphics/Rectangle.hpp>
|
||||
|
||||
namespace gsr {
|
||||
using EntryValidateHandler = std::function<bool(std::string &str)>;
|
||||
class Entry;
|
||||
|
||||
enum class EntryValidateHandlerResult {
|
||||
DENY,
|
||||
ALLOW,
|
||||
REPLACED
|
||||
};
|
||||
using EntryValidateHandler = std::function<EntryValidateHandlerResult(Entry &entry, const std::string &str)>;
|
||||
|
||||
class Entry : public Widget {
|
||||
public:
|
||||
@@ -20,19 +28,39 @@ namespace gsr {
|
||||
|
||||
mgl::vec2f get_size() override;
|
||||
|
||||
void set_text(std::string str);
|
||||
EntryValidateHandlerResult set_text(std::string str);
|
||||
const std::string& get_text() const;
|
||||
|
||||
// Also updates the cursor position
|
||||
void replace_text(size_t index, size_t size, const std::string &replacement);
|
||||
|
||||
// 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.
|
||||
EntryValidateHandler validate_handler;
|
||||
|
||||
std::function<void(const std::string &text)> on_changed;
|
||||
private:
|
||||
EntryValidateHandlerResult set_text_internal(std::string 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);
|
||||
private:
|
||||
struct Caret {
|
||||
float offset_x = 0.0f;
|
||||
int utf8_index = 0;
|
||||
int byte_index = 0;
|
||||
};
|
||||
|
||||
mgl::Rectangle background;
|
||||
mgl::Text text;
|
||||
float max_width;
|
||||
bool selected = false;
|
||||
float caret_offset_x = 0.0f;
|
||||
bool selecting_text = false;
|
||||
bool selecting_with_keyboard = false;
|
||||
bool show_selection = false;
|
||||
Caret caret;
|
||||
Caret selection_start_caret;
|
||||
float text_overflow = 0.0f;
|
||||
};
|
||||
|
||||
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max);
|
||||
|
||||
@@ -118,7 +118,7 @@ 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_url_section();
|
||||
std::unique_ptr<List> create_stream_custom_section();
|
||||
std::unique_ptr<ComboBox> create_stream_container_box();
|
||||
std::unique_ptr<List> create_stream_container_section();
|
||||
void add_stream_widgets();
|
||||
@@ -192,6 +192,7 @@ namespace gsr {
|
||||
Entry *youtube_stream_key_entry_ptr = nullptr;
|
||||
Entry *rumble_stream_key_entry_ptr = nullptr;
|
||||
Entry *stream_url_entry_ptr = nullptr;
|
||||
Entry *stream_key_entry_ptr = nullptr;
|
||||
Entry *replay_time_entry_ptr = nullptr;
|
||||
RadioButton *replay_storage_button_ptr = nullptr;
|
||||
Label *replay_time_label_ptr = nullptr;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <mglpp/system/vec.hpp>
|
||||
#include <mglpp/graphics/Color.hpp>
|
||||
#include <mglpp/window/Window.hpp>
|
||||
|
||||
namespace mgl {
|
||||
class Window;
|
||||
@@ -14,4 +15,5 @@ namespace gsr {
|
||||
void set_frame_delta_seconds(double frame_delta);
|
||||
mgl::vec2f scale_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to);
|
||||
mgl::vec2f clamp_keep_aspect_ratio(mgl::vec2f from, mgl::vec2f to);
|
||||
mgl::Scissor scissor_get_sub_area(mgl::Scissor parent, mgl::Scissor child);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.7.0', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.7.2', 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.4"', language: ['c', 'cpp'])
|
||||
add_project_arguments('-DGSR_FLATPAK_VERSION="5.7.5"', language: ['c', 'cpp'])
|
||||
|
||||
executable(
|
||||
meson.project_name(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.7.0"
|
||||
version = "1.7.2"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
|
||||
@@ -204,6 +204,7 @@ namespace gsr {
|
||||
{"streaming.twitch.key", &config.streaming_config.twitch.stream_key},
|
||||
{"streaming.rumble.key", &config.streaming_config.rumble.stream_key},
|
||||
{"streaming.custom.url", &config.streaming_config.custom.url},
|
||||
{"streaming.custom.key", &config.streaming_config.custom.key},
|
||||
{"streaming.custom.container", &config.streaming_config.custom.container},
|
||||
{"streaming.start_stop_hotkey", &config.streaming_config.start_stop_hotkey},
|
||||
|
||||
|
||||
@@ -102,7 +102,8 @@ namespace gsr {
|
||||
close(event_fd);
|
||||
|
||||
for(int i = 0; i < num_poll_fd; ++i) {
|
||||
close(poll_fd[i].fd);
|
||||
if(poll_fd[i].fd > 0)
|
||||
close(poll_fd[i].fd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +222,9 @@ namespace gsr {
|
||||
if(i == event_index)
|
||||
goto done;
|
||||
|
||||
char dev_input_filepath[256];
|
||||
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/input/js%d", extra_data[i].dev_input_id);
|
||||
fprintf(stderr, "Info: removed joystick: %s\n", dev_input_filepath);
|
||||
if(remove_poll_fd(i))
|
||||
--i; // This item was removed so we want to repeat the same index to continue to the next item
|
||||
|
||||
@@ -234,18 +238,13 @@ namespace gsr {
|
||||
goto done;
|
||||
} else if(i == hotplug_poll_index) {
|
||||
hotplug.process_event_data(poll_fd[i].fd, [&](HotplugAction hotplug_action, const char *devname) {
|
||||
char dev_input_filepath[1024];
|
||||
snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/%s", devname);
|
||||
switch(hotplug_action) {
|
||||
case HotplugAction::ADD: {
|
||||
// Cant open the /dev/input device immediately or it fails.
|
||||
// TODO: Remove this hack when a better solution is found.
|
||||
usleep(50 * 1000);
|
||||
add_device(dev_input_filepath);
|
||||
add_device(devname);
|
||||
break;
|
||||
}
|
||||
case HotplugAction::REMOVE: {
|
||||
if(remove_device(dev_input_filepath))
|
||||
if(remove_device(devname))
|
||||
--i; // This item was removed so we want to repeat the same index to continue to the next item
|
||||
break;
|
||||
}
|
||||
@@ -373,7 +372,9 @@ namespace gsr {
|
||||
if(index < 0 || index >= num_poll_fd)
|
||||
return false;
|
||||
|
||||
close(poll_fd[index].fd);
|
||||
if(poll_fd[index].fd > 0)
|
||||
close(poll_fd[index].fd);
|
||||
|
||||
for(int i = index + 1; i < num_poll_fd; ++i) {
|
||||
poll_fd[i - 1] = poll_fd[i];
|
||||
extra_data[i - 1] = extra_data[i];
|
||||
|
||||
@@ -59,10 +59,9 @@ namespace gsr {
|
||||
|
||||
/* TODO: This assumes SUBSYSTEM= is output before DEVNAME=, is that always true? */
|
||||
void Hotplug::parse_netlink_data(const char *line, const HotplugEventCallback &callback) {
|
||||
const char *at_symbol = strchr(line, '@');
|
||||
if(at_symbol) {
|
||||
event_is_add = strncmp(line, "add@", 4) == 0;
|
||||
event_is_remove = strncmp(line, "remove@", 7) == 0;
|
||||
if(strncmp(line, "ACTION=", 7) == 0) {
|
||||
event_is_add = strncmp(line+7, "add", 3) == 0;
|
||||
event_is_remove = strncmp(line+7, "remove", 6) == 0;
|
||||
subsystem_is_input = false;
|
||||
} else if(event_is_add || event_is_remove) {
|
||||
if(strcmp(line, "SUBSYSTEM=input") == 0)
|
||||
|
||||
126
src/Overlay.cpp
126
src/Overlay.cpp
@@ -27,6 +27,7 @@
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
@@ -49,7 +50,7 @@ namespace gsr {
|
||||
static const double force_window_on_top_timeout_seconds = 1.0;
|
||||
static const double replay_status_update_check_timeout_seconds = 1.5;
|
||||
static const double replay_saving_notification_timeout_seconds = 0.5;
|
||||
static const double notification_timeout_seconds = 2.5;
|
||||
static const double notification_timeout_seconds = 3.0;
|
||||
static const double notification_error_timeout_seconds = 5.0;
|
||||
static const double cursor_tracker_update_timeout_sec = 0.1;
|
||||
|
||||
@@ -1573,7 +1574,7 @@ namespace gsr {
|
||||
return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
|
||||
}
|
||||
|
||||
static std::string capture_target_get_notification_name(const char *capture_target) {
|
||||
static std::string capture_target_get_notification_name(const char *capture_target, bool save) {
|
||||
std::string result;
|
||||
if(is_capture_target_monitor(capture_target)) {
|
||||
result = "this monitor";
|
||||
@@ -1585,9 +1586,11 @@ namespace gsr {
|
||||
sscanf(capture_target, "%" PRIi64, &window_id);
|
||||
|
||||
const std::optional<std::string> window_title = get_window_title(display, window_id);
|
||||
if(window_title) {
|
||||
if(save) {
|
||||
result = "window";
|
||||
} else if(window_title) {
|
||||
result = strip(window_title.value());
|
||||
truncate_string(result, 20);
|
||||
truncate_string(result, 30);
|
||||
result = "window \"" + result + "\"";
|
||||
} else {
|
||||
result = std::string("window ") + capture_target;
|
||||
@@ -1774,6 +1777,45 @@ namespace gsr {
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string to_duration_string(double duration_sec) {
|
||||
int seconds = ceil(duration_sec);
|
||||
|
||||
const int hours = seconds / 60 / 60;
|
||||
seconds -= (hours * 60 * 60);
|
||||
|
||||
const int minutes = seconds / 60;
|
||||
seconds -= (minutes * 60);
|
||||
|
||||
std::string result;
|
||||
if(hours > 0)
|
||||
result += std::to_string(hours) + " hour" + (hours == 1 ? "" : "s");
|
||||
|
||||
if(minutes > 0) {
|
||||
if(!result.empty())
|
||||
result += " ";
|
||||
result += std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
if(seconds > 0 || (hours == 0 && minutes == 0)) {
|
||||
if(!result.empty())
|
||||
result += " ";
|
||||
result += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
fprintf(stderr, "to duration string: %f, %d, %d, %d\n", duration_sec, seconds, minutes, hours);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double Overlay::get_time_passed_in_replay_buffer_seconds() {
|
||||
double replay_duration_sec = replay_saved_duration_sec;
|
||||
if(replay_duration_sec > replay_buffer_save_duration_sec)
|
||||
replay_duration_sec = replay_buffer_save_duration_sec;
|
||||
if(replay_save_duration_min > 0 && replay_duration_sec > replay_save_duration_min * 60)
|
||||
replay_duration_sec = replay_save_duration_min * 60;
|
||||
return replay_duration_sec;
|
||||
}
|
||||
|
||||
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
|
||||
mgl_context *context = mgl_get_context();
|
||||
Display *display = (Display*)context->connection;
|
||||
@@ -1794,7 +1836,7 @@ namespace gsr {
|
||||
const std::string new_video_filepath = video_directory + "/" + video_filename;
|
||||
rename(video_filepath, new_video_filepath.c_str());
|
||||
|
||||
truncate_string(focused_window_name, 20);
|
||||
truncate_string(focused_window_name, 40);
|
||||
const char *capture_target = nullptr;
|
||||
char msg[512];
|
||||
|
||||
@@ -1803,7 +1845,10 @@ namespace gsr {
|
||||
if(!config.record_config.show_video_saved_notifications)
|
||||
return;
|
||||
|
||||
snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
|
||||
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds());
|
||||
snprintf(msg, sizeof(msg), "Saved a %s recording of %s\nto \"%s\"",
|
||||
duration_str.c_str(),
|
||||
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
|
||||
capture_target = recording_capture_target.c_str();
|
||||
break;
|
||||
}
|
||||
@@ -1811,13 +1856,10 @@ namespace gsr {
|
||||
if(!config.replay_config.show_replay_saved_notifications)
|
||||
return;
|
||||
|
||||
char duration[32];
|
||||
if(replay_save_duration_min > 0)
|
||||
snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
|
||||
else
|
||||
snprintf(duration, sizeof(duration), " ");
|
||||
|
||||
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
|
||||
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
|
||||
snprintf(msg, sizeof(msg), "Saved a %s replay of %s\nto \"%s\"",
|
||||
duration_str.c_str(),
|
||||
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
|
||||
capture_target = recording_capture_target.c_str();
|
||||
break;
|
||||
}
|
||||
@@ -1825,7 +1867,8 @@ namespace gsr {
|
||||
if(!config.screenshot_config.show_screenshot_saved_notifications)
|
||||
return;
|
||||
|
||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str(), focused_window_name.c_str());
|
||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s\nto \"%s\"",
|
||||
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
|
||||
capture_target = screenshot_capture_target.c_str();
|
||||
break;
|
||||
}
|
||||
@@ -1851,14 +1894,12 @@ namespace gsr {
|
||||
if(config.replay_config.save_video_in_game_folder) {
|
||||
save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY);
|
||||
} else if(config.replay_config.show_replay_saved_notifications) {
|
||||
char duration[32];
|
||||
if(replay_save_duration_min > 0)
|
||||
snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
|
||||
else
|
||||
snprintf(duration, sizeof(duration), " ");
|
||||
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
||||
snprintf(msg, sizeof(msg), "Saved a %s replay of %s",
|
||||
duration_str.c_str(),
|
||||
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str());
|
||||
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
|
||||
}
|
||||
}
|
||||
@@ -2006,7 +2047,8 @@ namespace gsr {
|
||||
save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT);
|
||||
} else if(config.screenshot_config.show_screenshot_saved_notifications) {
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str());
|
||||
snprintf(msg, sizeof(msg), "Saved a screenshot of %s",
|
||||
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str());
|
||||
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str());
|
||||
}
|
||||
} else {
|
||||
@@ -2104,8 +2146,12 @@ namespace gsr {
|
||||
if(config.record_config.save_video_in_game_folder) {
|
||||
save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD);
|
||||
} else if(config.record_config.show_video_saved_notifications) {
|
||||
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds());
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Saved a recording of %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
||||
snprintf(msg, sizeof(msg), "Saved a %s recording of %s",
|
||||
duration_str.c_str(),
|
||||
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str());
|
||||
show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
|
||||
}
|
||||
} else {
|
||||
@@ -2394,6 +2440,9 @@ namespace gsr {
|
||||
replay_save_duration_min = 0;
|
||||
replay_save_show_notification = true;
|
||||
replay_save_clock.restart();
|
||||
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
|
||||
if(replay_restart_on_save)
|
||||
replay_duration_clock.restart();
|
||||
kill(gpu_screen_recorder_process, SIGUSR1);
|
||||
}
|
||||
|
||||
@@ -2404,6 +2453,7 @@ namespace gsr {
|
||||
replay_save_duration_min = 1;
|
||||
replay_save_show_notification = true;
|
||||
replay_save_clock.restart();
|
||||
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
|
||||
kill(gpu_screen_recorder_process, SIGRTMIN+3);
|
||||
}
|
||||
|
||||
@@ -2414,6 +2464,7 @@ namespace gsr {
|
||||
replay_save_duration_min = 10;
|
||||
replay_save_show_notification = true;
|
||||
replay_save_clock.restart();
|
||||
replay_saved_duration_sec = replay_duration_clock.get_elapsed_time_seconds();
|
||||
kill(gpu_screen_recorder_process, SIGRTMIN+5);
|
||||
}
|
||||
|
||||
@@ -2564,6 +2615,9 @@ namespace gsr {
|
||||
if(config.replay_config.restart_replay_on_save && gsr_info.system_info.gsr_version >= GsrVersion{5, 0, 3}) {
|
||||
args.push_back("-restart-replay-on-save");
|
||||
args.push_back("yes");
|
||||
replay_restart_on_save = true;
|
||||
} else {
|
||||
replay_restart_on_save = false;
|
||||
}
|
||||
|
||||
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) {
|
||||
@@ -2603,13 +2657,17 @@ namespace gsr {
|
||||
// to see when the program has exit.
|
||||
if(!disable_notification && config.replay_config.show_replay_started_notifications) {
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
||||
snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
|
||||
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
|
||||
}
|
||||
|
||||
if(config.replay_config.record_options.record_area_option == "portal")
|
||||
hide_ui = true;
|
||||
|
||||
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||
// selected what to capture and accepted it.
|
||||
replay_duration_clock.restart();
|
||||
replay_buffer_save_duration_sec = config.replay_config.replay_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2630,6 +2688,10 @@ namespace gsr {
|
||||
if(config.record_config.show_recording_started_notifications)
|
||||
show_notification("Started recording in the replay session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
|
||||
update_ui_recording_started();
|
||||
|
||||
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||
// selected what to capture and accepted it.
|
||||
recording_duration_clock.restart();
|
||||
}
|
||||
replay_recording = true;
|
||||
kill(gpu_screen_recorder_process, SIGRTMIN);
|
||||
@@ -2647,6 +2709,10 @@ namespace gsr {
|
||||
if(config.record_config.show_recording_started_notifications)
|
||||
show_notification("Started recording in the streaming session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
|
||||
update_ui_recording_started();
|
||||
|
||||
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||
// selected what to capture and accepted it.
|
||||
recording_duration_clock.restart();
|
||||
}
|
||||
replay_recording = true;
|
||||
kill(gpu_screen_recorder_process, SIGRTMIN);
|
||||
@@ -2766,12 +2832,16 @@ namespace gsr {
|
||||
// 1...
|
||||
if(config.record_config.show_recording_started_notifications) {
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
||||
snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
|
||||
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
|
||||
}
|
||||
|
||||
if(config.record_config.record_options.record_area_option == "portal")
|
||||
hide_ui = true;
|
||||
|
||||
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
|
||||
// selected what to capture and accepted it.
|
||||
recording_duration_clock.restart();
|
||||
}
|
||||
|
||||
static std::string streaming_get_url(const Config &config) {
|
||||
@@ -2805,6 +2875,11 @@ namespace gsr {
|
||||
{}
|
||||
else
|
||||
url = "rtmp://" + url;
|
||||
|
||||
if(!url.empty() && url.back() != '/' && url.back() != '=' && !config.streaming_config.custom.key.empty())
|
||||
url += "/";
|
||||
|
||||
url += config.streaming_config.custom.key;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
@@ -2943,7 +3018,7 @@ namespace gsr {
|
||||
// to see when the program has exit.
|
||||
if(config.streaming_config.show_streaming_started_notifications) {
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
|
||||
snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
|
||||
show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str());
|
||||
}
|
||||
|
||||
@@ -2987,7 +3062,6 @@ namespace gsr {
|
||||
if(record_area_option == "region" && !finished_selection) {
|
||||
start_region_capture = true;
|
||||
on_region_selected = [this, force_type]() {
|
||||
usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this
|
||||
on_press_take_screenshot(true, force_type);
|
||||
};
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "../../include/gui/Entry.hpp"
|
||||
#include "../../include/gui/Utils.hpp"
|
||||
#include "../../include/Theme.hpp"
|
||||
#include <mglpp/graphics/Rectangle.hpp>
|
||||
#include <mglpp/window/Window.hpp>
|
||||
#include <mglpp/window/Event.hpp>
|
||||
#include <mglpp/system/FloatRect.hpp>
|
||||
@@ -16,6 +15,13 @@ namespace gsr {
|
||||
static const float border_scale = 0.0015f;
|
||||
static const float caret_width_scale = 0.001f;
|
||||
|
||||
static void string_replace_all(std::string &str, char old_char, char new_char) {
|
||||
for(char &c : str) {
|
||||
if(c == old_char)
|
||||
c = new_char;
|
||||
}
|
||||
}
|
||||
|
||||
Entry::Entry(mgl::Font *font, const char *text, float max_width) : text("", *font), max_width(max_width) {
|
||||
this->text.set_color(get_color_theme().text_color);
|
||||
set_text(text);
|
||||
@@ -26,24 +32,149 @@ namespace gsr {
|
||||
return true;
|
||||
|
||||
if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) {
|
||||
selected = mgl::FloatRect(position + offset, get_size()).contains({ (float)event.mouse_button.x, (float)event.mouse_button.y });
|
||||
} else if(event.type == mgl::Event::KeyPressed && selected) {
|
||||
if(event.key.code == mgl::Keyboard::Backspace && !text.get_string().empty()) {
|
||||
std::string str = text.get_string();
|
||||
const size_t prev_index = mgl::utf8_get_start_of_codepoint((const unsigned char*)str.c_str(), str.size(), str.size());
|
||||
str.erase(prev_index, std::string::npos);
|
||||
set_text(std::move(str));
|
||||
} else if(event.key.code == mgl::Keyboard::V && event.key.control) {
|
||||
std::string clipboard_text = window.get_clipboard_string();
|
||||
std::string str = text.get_string();
|
||||
str += clipboard_text;
|
||||
set_text(std::move(str));
|
||||
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);
|
||||
if(selected) {
|
||||
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;
|
||||
selection_start_caret = caret;
|
||||
show_selection = true;
|
||||
} else {
|
||||
selecting_text = false;
|
||||
selecting_with_keyboard = false;
|
||||
show_selection = false;
|
||||
}
|
||||
} else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32) {
|
||||
std::string str = text.get_string();
|
||||
str.append(event.text.str, event.text.size);
|
||||
set_text(std::move(str));
|
||||
} 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)
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
} else if(event.type == mgl::Event::KeyPressed && selected) {
|
||||
int selection_start_byte = caret.byte_index;
|
||||
int selection_end_byte = caret.byte_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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, "");
|
||||
} 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;
|
||||
}
|
||||
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, "");
|
||||
} 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));
|
||||
} 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));
|
||||
} 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.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());
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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.offset_x = 0.0f;
|
||||
|
||||
if(!selecting_with_keyboard) {
|
||||
selection_start_caret = caret;
|
||||
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());
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
|
||||
if(!selecting_with_keyboard) {
|
||||
selection_start_caret = caret;
|
||||
show_selection = false;
|
||||
}
|
||||
} else if(event.key.code == mgl::Keyboard::LShift || event.key.code == mgl::Keyboard::RShift) {
|
||||
if(!show_selection)
|
||||
selection_start_caret = caret;
|
||||
selecting_with_keyboard = true;
|
||||
show_selection = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if(event.type == mgl::Event::KeyReleased && selected) {
|
||||
if(event.key.code == mgl::Keyboard::LShift || event.key.code == mgl::Keyboard::RShift) {
|
||||
selecting_with_keyboard = false;
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
replace_text(selection_start_byte, selection_end_byte - selection_start_byte, std::string(event.text.str, event.text.size));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,26 +185,84 @@ namespace gsr {
|
||||
const mgl::vec2f draw_pos = position + offset;
|
||||
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_bottom = padding_bottom_scale * get_theme().window_height;
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
const int padding_right = padding_right_scale * get_theme().window_height;
|
||||
|
||||
mgl::Rectangle background(get_size());
|
||||
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 float overflow_left = (caret.offset_x + padding_left) - (padding_left + text_overflow);
|
||||
if(overflow_left < 0.0f)
|
||||
text_overflow += overflow_left;
|
||||
|
||||
const float overflow_right = (caret.offset_x + padding_left) - (background.get_size().x - padding_right);
|
||||
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());
|
||||
|
||||
const auto text_bounds = 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));
|
||||
}
|
||||
} else {
|
||||
text.set_position(text.get_position() + mgl::vec2f(-text_overflow, 0.0f));
|
||||
text_overflow = 0.0f;
|
||||
}
|
||||
|
||||
if(selected) {
|
||||
const int border_size = std::max(1.0f, border_scale * get_theme().window_height);
|
||||
draw_rectangle_outline(window, draw_pos.floor(), get_size().floor(), get_color_theme().tint_color, border_size);
|
||||
|
||||
const int caret_width = std::max(1.0f, caret_width_scale * get_theme().window_height);
|
||||
mgl::Rectangle caret({(float)caret_width, text.get_bounds().size.y});
|
||||
caret.set_position((draw_pos + mgl::vec2f(padding_left + caret_offset_x, padding_top)).floor());
|
||||
caret.set_color(mgl::Color(255, 255, 255));
|
||||
window.draw(caret);
|
||||
draw_caret(window, draw_pos, caret_size);
|
||||
}
|
||||
|
||||
text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - text.get_bounds().size.y * 0.5f)).floor());
|
||||
const mgl::Scissor parent_scissor = window.get_scissor();
|
||||
const mgl::Scissor scissor = scissor_get_sub_area(parent_scissor,
|
||||
mgl::Scissor{
|
||||
(background.get_position() + mgl::vec2f(padding_left, padding_top)).to_vec2i(),
|
||||
(background.get_size() - mgl::vec2f(padding_left + padding_right, padding_top + padding_bottom)).to_vec2i()
|
||||
});
|
||||
window.set_scissor(scissor);
|
||||
|
||||
window.draw(text);
|
||||
|
||||
if(show_selection)
|
||||
draw_caret_selection(window, draw_pos, caret_size);
|
||||
|
||||
window.set_scissor(parent_scissor);
|
||||
}
|
||||
|
||||
void Entry::draw_caret(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size) {
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
|
||||
mgl::Rectangle caret_rect(caret_size);
|
||||
mgl::vec2f caret_draw_pos = draw_pos + mgl::vec2f(padding_left + caret.offset_x - text_overflow, padding_top);
|
||||
caret_rect.set_position(caret_draw_pos.floor());
|
||||
caret_rect.set_color(mgl::Color(255, 255, 255));
|
||||
window.draw(caret_rect);
|
||||
}
|
||||
|
||||
void Entry::draw_caret_selection(mgl::Window &window, mgl::vec2f draw_pos, mgl::vec2f caret_size) {
|
||||
const int padding_top = padding_top_scale * get_theme().window_height;
|
||||
const int padding_left = padding_left_scale * get_theme().window_height;
|
||||
|
||||
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::Color caret_select_color = get_color_theme().tint_color;
|
||||
caret_select_color.a = 100;
|
||||
caret_selection_rect.set_color(caret_select_color);
|
||||
window.draw(caret_selection_rect);
|
||||
}
|
||||
|
||||
mgl::vec2f Entry::get_size() {
|
||||
@@ -85,19 +274,107 @@ namespace gsr {
|
||||
return { max_width, text.get_bounds().size.y + padding_top + padding_bottom };
|
||||
}
|
||||
|
||||
void Entry::set_text(std::string str) {
|
||||
if(!validate_handler || validate_handler(str)) {
|
||||
EntryValidateHandlerResult Entry::set_text(std::string str) {
|
||||
EntryValidateHandlerResult validate_result = set_text_internal(std::move(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());
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
selection_start_caret = caret;
|
||||
|
||||
selecting_text = false;
|
||||
selecting_with_keyboard = false;
|
||||
show_selection = false;
|
||||
}
|
||||
return validate_result;
|
||||
}
|
||||
|
||||
EntryValidateHandlerResult Entry::set_text_internal(std::string 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));
|
||||
caret_offset_x = text.find_character_pos(99999).x - this->text.get_position().x;
|
||||
if(on_changed)
|
||||
on_changed(text.get_string());
|
||||
}
|
||||
|
||||
return validate_result;
|
||||
}
|
||||
|
||||
const std::string& Entry::get_text() const {
|
||||
return text.get_string();
|
||||
}
|
||||
|
||||
void Entry::replace_text(size_t index, size_t size, const std::string &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();
|
||||
}
|
||||
|
||||
std::string str = text.get_string();
|
||||
str.replace(index, size, replacement);
|
||||
const EntryValidateHandlerResult validate_result = set_text_internal(std::move(str));
|
||||
if(validate_result == EntryValidateHandlerResult::DENY) {
|
||||
caret = prev_caret;
|
||||
return;
|
||||
} else if(validate_result == EntryValidateHandlerResult::REPLACED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Optimize
|
||||
caret.offset_x = text.find_character_pos(caret.utf8_index).x - this->text.get_position().x;
|
||||
selection_start_caret = caret;
|
||||
|
||||
selecting_text = false;
|
||||
selecting_with_keyboard = false;
|
||||
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();
|
||||
|
||||
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;
|
||||
|
||||
float glyph_width = 0.0f;
|
||||
if(codepoint == '\t') {
|
||||
const auto glyph = font->get_glyph(' ');
|
||||
const int tab_width = 4;
|
||||
glyph_width = glyph.advance * tab_width;
|
||||
} else {
|
||||
const auto glyph = font->get_glyph(codepoint);
|
||||
glyph_width = glyph.advance;
|
||||
}
|
||||
|
||||
if(result.pos.x + glyph_width * 0.5f >= position.x)
|
||||
break;
|
||||
|
||||
result.pos.x += glyph_width;
|
||||
result.byte_index += clen;
|
||||
result.utf8_index += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool is_number(uint8_t c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
@@ -114,7 +391,7 @@ namespace gsr {
|
||||
int number = 0;
|
||||
for(; i < str.size(); ++i) {
|
||||
if(!is_number(str[i]))
|
||||
return false;
|
||||
return std::nullopt;
|
||||
|
||||
const int new_number = number * 10 + (str[i] - '0');
|
||||
if(new_number < number)
|
||||
@@ -129,19 +406,23 @@ namespace gsr {
|
||||
}
|
||||
|
||||
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max) {
|
||||
return [min, max](std::string &str) {
|
||||
return [min, max](Entry &entry, const std::string &str) {
|
||||
if(str.empty())
|
||||
return true;
|
||||
return EntryValidateHandlerResult::ALLOW;
|
||||
|
||||
std::optional<int> number = to_integer(str);
|
||||
const std::optional<int> number = to_integer(str);
|
||||
if(!number)
|
||||
return false;
|
||||
return EntryValidateHandlerResult::DENY;
|
||||
|
||||
if(number.value() < min)
|
||||
str = std::to_string(min);
|
||||
else if(number.value() > max)
|
||||
str = std::to_string(max);
|
||||
return true;
|
||||
if(number.value() < min) {
|
||||
entry.set_text(std::to_string(min));
|
||||
return EntryValidateHandlerResult::REPLACED;
|
||||
} else if(number.value() > max) {
|
||||
entry.set_text(std::to_string(max));
|
||||
return EntryValidateHandlerResult::REPLACED;
|
||||
}
|
||||
|
||||
return EntryValidateHandlerResult::ALLOW;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -985,14 +985,20 @@ namespace gsr {
|
||||
return stream_key_list;
|
||||
}
|
||||
|
||||
std::unique_ptr<List> SettingsPage::create_stream_url_section() {
|
||||
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, "URL:", get_color_theme().text_color));
|
||||
stream_url_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream URL:", get_color_theme().text_color));
|
||||
|
||||
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));
|
||||
|
||||
auto stream_key_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
|
||||
stream_key_entry_ptr = stream_key_entry.get();
|
||||
stream_url_list->add_widget(std::move(stream_key_entry));
|
||||
|
||||
stream_url_list_ptr = stream_url_list.get();
|
||||
return stream_url_list;
|
||||
}
|
||||
@@ -1019,7 +1025,7 @@ namespace gsr {
|
||||
auto streaming_info_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
|
||||
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_url_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)));
|
||||
|
||||
@@ -1253,6 +1259,7 @@ namespace gsr {
|
||||
twitch_stream_key_entry_ptr->set_text(config.streaming_config.twitch.stream_key);
|
||||
rumble_stream_key_entry_ptr->set_text(config.streaming_config.rumble.stream_key);
|
||||
stream_url_entry_ptr->set_text(config.streaming_config.custom.url);
|
||||
stream_key_entry_ptr->set_text(config.streaming_config.custom.key);
|
||||
container_box_ptr->set_selected_item(config.streaming_config.custom.container);
|
||||
}
|
||||
|
||||
@@ -1395,6 +1402,7 @@ namespace gsr {
|
||||
config.streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text();
|
||||
config.streaming_config.rumble.stream_key = rumble_stream_key_entry_ptr->get_text();
|
||||
config.streaming_config.custom.url = stream_url_entry_ptr->get_text();
|
||||
config.streaming_config.custom.key = stream_key_entry_ptr->get_text();
|
||||
config.streaming_config.custom.container = container_box_ptr->get_selected_id();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,18 @@
|
||||
namespace gsr {
|
||||
static double frame_delta_seconds = 1.0;
|
||||
|
||||
static mgl::vec2i min_vec2i(mgl::vec2i a, mgl::vec2i b) {
|
||||
return { std::min(a.x, b.x), std::min(a.y, b.y) };
|
||||
}
|
||||
|
||||
static mgl::vec2i max_vec2i(mgl::vec2i a, mgl::vec2i b) {
|
||||
return { std::max(a.x, b.x), std::max(a.y, b.y) };
|
||||
}
|
||||
|
||||
static mgl::vec2i clamp_vec2i(mgl::vec2i value, mgl::vec2i min, mgl::vec2i max) {
|
||||
return min_vec2i(max, max_vec2i(value, min));
|
||||
}
|
||||
|
||||
// TODO: Use vertices to make it one draw call
|
||||
void draw_rectangle_outline(mgl::Window &window, mgl::vec2f pos, mgl::vec2f size, mgl::Color color, float border_size) {
|
||||
pos = pos.floor();
|
||||
@@ -74,4 +86,12 @@ namespace gsr {
|
||||
else
|
||||
return from;
|
||||
}
|
||||
|
||||
mgl::Scissor scissor_get_sub_area(mgl::Scissor parent, mgl::Scissor child) {
|
||||
const mgl::vec2i pos = clamp_vec2i(child.position, parent.position, parent.position + parent.size);
|
||||
return mgl::Scissor{
|
||||
pos,
|
||||
min_vec2i(child.size, parent.position + parent.size - pos)
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user