Add entry with basic text editing and validation for numbers

This commit is contained in:
dec05eba
2024-08-03 05:21:36 +02:00
parent 2869ef7cec
commit c080342fcd
7 changed files with 191 additions and 19 deletions

2
TODO
View File

@@ -13,3 +13,5 @@ DISPLAY gamescope-0
Colorscheme should follow graphics card in use. On nvidia use nvidia green, on intel use intel blue and on amd use amd red. Colorscheme should follow graphics card in use. On nvidia use nvidia green, on intel use intel blue and on amd use amd red.
Optimize list/page when having a few items in it (dont use vector<unique_ptr<Widget>>). Optimize list/page when having a few items in it (dont use vector<unique_ptr<Widget>>).
Only redraw ui if changed (dirty state, propagate upward. Set dirty when adding widget or changing any visible properly on a widget or when event updates how the widget should be displayed).

36
include/gui/Entry.hpp Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include "Widget.hpp"
#include <functional>
#include <mglpp/graphics/Color.hpp>
#include <mglpp/graphics/Text.hpp>
namespace gsr {
using EntryValidateHandler = std::function<bool(std::string &str)>;
class Entry : public Widget {
public:
Entry(mgl::Font *font, const char *text, float max_width);
Entry(const Entry&) = delete;
Entry& operator=(const Entry&) = 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_string(std::string str);
// 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;
private:
mgl::Text text;
float max_width;
bool selected = false;
float caret_offset_x = 0.0f;
};
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max);
}

View File

@@ -13,12 +13,7 @@ namespace gsr {
bool Button::on_event(mgl::Event &event, mgl::Window&, mgl::vec2f offset) { bool Button::on_event(mgl::Event &event, mgl::Window&, mgl::vec2f offset) {
if(event.type == mgl::Event::MouseMoved) { if(event.type == mgl::Event::MouseMoved) {
const bool inside = mgl::FloatRect(position + offset, size).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y }); mouse_inside = mgl::FloatRect(position + offset, size).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y });
if(mouse_inside && !inside) {
mouse_inside = false;
} else if(!mouse_inside && inside) {
mouse_inside = true;
}
} else if(event.type == mgl::Event::MouseButtonPressed) { } else if(event.type == mgl::Event::MouseButtonPressed) {
const bool clicked_inside = mouse_inside; const bool clicked_inside = mouse_inside;
if(clicked_inside && on_click) if(clicked_inside && on_click)

View File

@@ -29,12 +29,7 @@ namespace gsr {
if(event.type == mgl::Event::MouseMoved) { if(event.type == mgl::Event::MouseMoved) {
const mgl::vec2f draw_pos = position + offset; const mgl::vec2f draw_pos = position + offset;
const mgl::vec2f collision_margin(1.0f, 1.0f); // Makes sure that multiple buttons that are next to each other wont activate at the same time when the cursor is right between them const mgl::vec2f collision_margin(1.0f, 1.0f); // Makes sure that multiple buttons that are next to each other wont activate at the same time when the cursor is right between them
const bool inside = mgl::FloatRect(draw_pos + collision_margin, size - collision_margin).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y }); mouse_inside = mgl::FloatRect(draw_pos + collision_margin, size - collision_margin).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y });
if(mouse_inside && !inside) {
mouse_inside = false;
} else if(!mouse_inside && inside) {
mouse_inside = true;
}
} else if(event.type == mgl::Event::MouseButtonPressed) { } else if(event.type == mgl::Event::MouseButtonPressed) {
const bool clicked_inside = mouse_inside; const bool clicked_inside = mouse_inside;

121
src/gui/Entry.cpp Normal file
View File

@@ -0,0 +1,121 @@
#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>
#include <mglpp/system/Utf8.hpp>
#include <optional>
namespace gsr {
static const float padding_top = 10.0f;
static const float padding_bottom = 10.0f;
static const float padding_left = 10.0f;
static const float padding_right = 10.0f;
Entry::Entry(mgl::Font *font, const char *text, float max_width) : text("", *font), max_width(max_width) {
this->text.set_color(get_theme().text_color);
set_string(text);
}
bool Entry::on_event(mgl::Event &event, mgl::Window&, mgl::vec2f offset) {
if(event.type == mgl::Event::MouseButtonPressed) {
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_string(std::move(str));
}
} 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_string(std::move(str));
}
return true;
}
void Entry::draw(mgl::Window &window, mgl::vec2f offset) {
const mgl::vec2f draw_pos = position + offset;
mgl::Rectangle background(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);
if(selected) {
const int border_size = 3;
draw_rectangle_outline(window, draw_pos.floor(), get_size().floor(), get_theme().tint_color, border_size);
const int caret_width = 2;
mgl::Rectangle caret({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);
}
text.set_position((draw_pos + mgl::vec2f(padding_left, get_size().y * 0.5f - text.get_bounds().size.y * 0.5f)).floor());
window.draw(text);
}
mgl::vec2f Entry::get_size() {
return { max_width, text.get_bounds().size.y + padding_top + padding_bottom };
}
void Entry::set_string(std::string str) {
if(!validate_handler || validate_handler(str)) {
text.set_string(std::move(str));
caret_offset_x = text.find_character_pos(99999).x - this->text.get_position().x;
fprintf(stderr, "caret offset: %f\n", caret_offset_x);
}
}
static bool is_number(uint8_t c) {
return c >= '0' && c <= '9';
}
static std::optional<int> to_integer(const std::string &str) {
if(str.empty())
return std::nullopt;
size_t i = 0;
const bool negative = str[0] == '-';
if(negative)
i = 1;
int number = 0;
for(; i < str.size(); ++i) {
if(!is_number(str[i]))
return false;
const int new_number = number * 10 + (str[i] - '0');
if(new_number < number)
return std::nullopt; // Overflow
number = new_number;
}
if(negative)
number = -number;
return number;
}
EntryValidateHandler create_entry_validator_integer_in_range(int min, int max) {
return [min, max](std::string &str) {
if(str.empty())
return true;
std::optional<int> number = to_integer(str);
if(!number)
return false;
if(number.value() < min)
str = std::to_string(min);
else if(number.value() > max)
str = std::to_string(max);
return true;
};
}
}

View File

@@ -3,6 +3,7 @@
#include "../include/gui/ScrollablePage.hpp" #include "../include/gui/ScrollablePage.hpp"
#include "../include/gui/DropdownButton.hpp" #include "../include/gui/DropdownButton.hpp"
#include "../include/gui/Button.hpp" #include "../include/gui/Button.hpp"
#include "../include/gui/Entry.hpp"
#include "../include/gui/ComboBox.hpp" #include "../include/gui/ComboBox.hpp"
#include "../include/gui/Label.hpp" #include "../include/gui/Label.hpp"
#include "../include/gui/List.hpp" #include "../include/gui/List.hpp"
@@ -182,6 +183,28 @@ static std::string color_to_hex_str(mgl::Color color) {
return result; return result;
} }
/*
{
{
gsr::List::Orientation::VERTICAL,
"Record area:",
{
{"Window, "window"},
{"Focused window", "focused"},
},
},
{
gsr::List::Orientation::VERTICAL,
"Video quality:",
{
{"Medium, "medium"},
{"High", "high"},
{"Very high", "very_high"},
},
}
}
*/
static void add_widgets_to_settings_page(mgl::Font &title_font, mgl::vec2i window_size, mgl::vec2f settings_page_position, mgl::vec2f settings_page_size, gsr::Page *settings_page, gsr::Page *settings_content_page, const gsr::GsrInfo &gsr_info, const std::vector<gsr::AudioDevice> &audio_devices, std::function<void()> settings_back_button_callback) { static void add_widgets_to_settings_page(mgl::Font &title_font, mgl::vec2i window_size, mgl::vec2f settings_page_position, mgl::vec2f settings_page_size, gsr::Page *settings_page, gsr::Page *settings_content_page, const gsr::GsrInfo &gsr_info, const std::vector<gsr::AudioDevice> &audio_devices, std::function<void()> settings_back_button_callback) {
auto back_button = std::make_unique<gsr::Button>(&title_font, "Back", mgl::vec2f(window_size.x / 10, window_size.y / 15), gsr::get_theme().scrollable_page_bg_color); auto back_button = std::make_unique<gsr::Button>(&title_font, "Back", mgl::vec2f(window_size.x / 10, window_size.y / 15), gsr::get_theme().scrollable_page_bg_color);
back_button->set_position(settings_page_position + mgl::vec2f(settings_page_size.x + window_size.x / 50, 0.0f).floor()); back_button->set_position(settings_page_position + mgl::vec2f(settings_page_size.x + window_size.x / 50, 0.0f).floor());
@@ -232,7 +255,7 @@ auto back_button = std::make_unique<gsr::Button>(&title_font, "Back", mgl::vec2f
auto video_quality_box = std::make_unique<gsr::ComboBox>(&title_font); auto video_quality_box = std::make_unique<gsr::ComboBox>(&title_font);
video_quality_box->add_item("Medium", "medium"); video_quality_box->add_item("Medium", "medium");
video_quality_box->add_item("High (Recommended for live streaming)", "high"); video_quality_box->add_item("High (Recommended for live streaming)", "high");
video_quality_box->add_item("Very High (Recommended)", "very_high"); video_quality_box->add_item("Very high (Recommended)", "very_high");
video_quality_box->add_item("Ultra", "ultra"); video_quality_box->add_item("Ultra", "ultra");
video_quality_list->add_widget(std::move(video_quality_box)); video_quality_list->add_widget(std::move(video_quality_box));
} }
@@ -251,10 +274,10 @@ auto back_button = std::make_unique<gsr::Button>(&title_font, "Back", mgl::vec2f
auto framerate_list = std::make_unique<gsr::List>(gsr::List::Orientation::VERTICAL); auto framerate_list = std::make_unique<gsr::List>(gsr::List::Orientation::VERTICAL);
{ {
framerate_list->add_widget(std::make_unique<gsr::Label>(&title_font, "Frame rate:", gsr::get_theme().text_color)); framerate_list->add_widget(std::make_unique<gsr::Label>(&title_font, "Frame rate:", gsr::get_theme().text_color));
auto framerate_box = std::make_unique<gsr::ComboBox>(&title_font); //create_entry_validator_integer_in_range
framerate_box->add_item("60", "60"); auto framerate_entry = std::make_unique<gsr::Entry>(&title_font, "60", title_font.get_character_size() * 2);
framerate_box->add_item("30", "30"); framerate_entry->validate_handler = gsr::create_entry_validator_integer_in_range(1, 500);
framerate_list->add_widget(std::move(framerate_box)); framerate_list->add_widget(std::move(framerate_entry));
} }
quality_list->add_widget(std::move(framerate_list)); quality_list->add_widget(std::move(framerate_list));
} }