From 242d26cd8a0794d62636e3abd290a42686d9a98b Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 20 Jul 2025 04:07:26 -0300 Subject: [PATCH 01/61] [feature] text-chunk based: --- include/ekg/core/pools.hpp | 29 +++++++++++ include/ekg/handler/theme.hpp | 2 + include/ekg/io/descriptor.hpp | 1 + include/ekg/io/utf.hpp | 82 ++++++++++++++++++++++++++++++ include/ekg/ui/textbox/textbox.hpp | 63 +++++++++++++++++++++++ include/ekg/ui/textbox/widget.hpp | 62 ++++++++++++++++++++++ src/io/utf.cpp | 2 + src/ui/textbox/textbox.cpp | 28 ++++++++++ src/ui/textbox/widget.cpp | 66 ++++++++++++++++++++++++ version/todo.txt | 35 +++---------- 10 files changed, 341 insertions(+), 29 deletions(-) create mode 100644 include/ekg/ui/textbox/textbox.hpp create mode 100644 include/ekg/ui/textbox/widget.hpp create mode 100644 src/ui/textbox/textbox.cpp create mode 100644 src/ui/textbox/widget.cpp diff --git a/include/ekg/core/pools.hpp b/include/ekg/core/pools.hpp index 84a2c7c7..a6e17110 100644 --- a/include/ekg/core/pools.hpp +++ b/include/ekg/core/pools.hpp @@ -50,6 +50,9 @@ #include "ekg/ui/popup/popup.hpp" #include "ekg/ui/popup/widget.hpp" +#include "ekg/ui/textbox/textbox.hpp" +#include "ekg/ui/textbox/widget.hpp" + namespace ekg::core { void registry(ekg::property_t &property); } @@ -80,6 +83,7 @@ namespace ekg::core { ekg_core_widget_call_impl(ekg::scrollbar_t, widget_descriptor_at, todo); \ ekg_core_widget_call_impl(ekg::slider_t, widget_descriptor_at, todo); \ ekg_core_widget_call_impl(ekg::popup_t, widget_descriptor_at, todo); \ + ekg_core_widget_call_impl(ekg::textbox_t, widget_descriptor_at, todo); \ } #define ekg_core_unique_widget_call(descriptor_t, widget_descriptor_type, widget_descriptor_at, todo) \ @@ -155,6 +159,9 @@ namespace ekg { ekg::pool popup_property {}; ekg::pool popup {}; + + ekg::pool textbox_property {}; + ekg::pool textbox {}; } pools; template @@ -200,6 +207,10 @@ namespace ekg { return ekg::io::any_static_cast( &ekg::pools.popup_property.query(at) ); + case ekg::type::textbox: + return ekg::io::any_static_cast( + &ekg::pools.textbox_property.query(at) + ); } case ekg::type::button: return ekg::io::any_static_cast( @@ -225,6 +236,10 @@ namespace ekg { return ekg::io::any_static_cast( &ekg::pools.popup.query(at) ); + case ekg::type::textbox: + return ekg::io::any_static_cast( + &ekg::pools.textbox.query(at) + ); } return t::not_found; @@ -322,6 +337,20 @@ namespace ekg { ); } + case ekg::type::textbox: { + ekg_registry_widget_impl( + ekg::textbox_t, + ekg::pools.textbox, + ekg::pools.textbox_property, + true, + { + property.is_childnizate = false; + property.is_children_docknizable = false; + widget.color_scheme = global_theme.textbox_color_scheme; + } + ); + } + case ekg::type::stack: { ekg::stack_t &stack { ekg::pools.stack.push_back( diff --git a/include/ekg/handler/theme.hpp b/include/ekg/handler/theme.hpp index 8756af0a..59923dc0 100644 --- a/include/ekg/handler/theme.hpp +++ b/include/ekg/handler/theme.hpp @@ -30,6 +30,7 @@ #include "ekg/ui/scrollbar/scrollbar.hpp" #include "ekg/ui/slider/slider.hpp" #include "ekg/ui/popup/popup.hpp" +#include "ekg/ui/textbox/textbox.hpp" namespace ekg { struct theme_t { @@ -46,6 +47,7 @@ namespace ekg { ekg::scrollbar_color_scheme_t scrollbar_color_scheme {}; ekg::slider_color_scheme_t slider_color_scheme {}; ekg::popup_color_scheme_t popup_color_scheme {}; + ekg::textbox_color_scheme_t textbox_color_scheme {}; }; ekg::theme_t &theme(std::string tag = ""); diff --git a/include/ekg/io/descriptor.hpp b/include/ekg/io/descriptor.hpp index 32fbe07d..03e07b85 100644 --- a/include/ekg/io/descriptor.hpp +++ b/include/ekg/io/descriptor.hpp @@ -39,6 +39,7 @@ namespace ekg { slider = 8, label = 9, popup = 10, + textbox = 11 }; } diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 4ceb9353..87c5b8d6 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -117,4 +117,86 @@ namespace ekg { } } +namespace ekg::io { + typedef std::vector chunk_t; +} + +namespace ekg { + class text { + protected: + std::vector chunks {}; + size_t chunk_limit {10}; + protected: + std::string &at_by_index(size_t index, bool swizzle = false) { + size_t total_lines {}; + size_t chunks_size {this->chunks.size()}; + for (size_t it {}; it < chunks_size; it++) { + chunk_t &chunk {this->chunks.at(it)}; + total_lines += chunk.size(); + + if (swizzle && total_lines == index) { + this + break; + } + } + } + + ekg::io::chunk_t &chunk_at(size_t chunk_index) { + return this- + } + public: + static std::string not_found; + public: + std::string &at(size_t index) { + size_t chunks_size {this->chunks.size()}; + size_t total_lines {}; + size_t previous_total_lines {}; + size_t index_of {}; + size_t chunk_size {}; + + for (size_t it {}; it < chunks_size; it++) { + ekg::io::chunk_t &chunk {this->chunks.at(it)}; + + previous_total_lines = total_lines; + total_lines += (chunk_size = chunk.size()); + index_of = index - previous_total_lines; + + if (total_lines < index && index_of < chunk_size) { + return chunk.at(index - previous_total_lines); + } + } + } + + void push_back(const std::string &line) { + if (this->chunks.empty()) { + this->chunks.emplace_back().push_back(line); + return; + } + + size_t chunks_size {this->chunks.size()}; + ekg::io::chunk_t &chunk {this->chunks.at(chunks_size - (chunks_size != 0))}; + if (chunk.size() >= this->chunk_limit) { + this->chunks.emplace_back().push_back(line); + return; + } + + this->chunks.emplace_back().push_back(line); + } + + std::string &emplace_back() { + if (this->chunks.empty()) { + return this->chunks.emplace_back().emplace_back(); + } + + size_t chunks_size {this->chunks.size()}; + ekg::io::chunk_t &chunk {this->chunks.at(chunks_size - (chunks_size != 0))}; + if (chunk.size() >= this->chunk_limit) { + return this->chunks.emplace_back().emplace_back(); + } + + return chunk.emplace_back(); + } + }; +} + #endif diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp new file mode 100644 index 00000000..c8f4b746 --- /dev/null +++ b/include/ekg/ui/textbox/textbox.hpp @@ -0,0 +1,63 @@ +/** + * MIT License + * + * Copyright (c) 2022-2025 Rina Wilk / vokegpu@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef EKG_UI_TEXTBOX_HPP +#define EKG_UI_TEXTBOX_HPP + +#include "ekg/ui/scrollbar/scrollbar.hpp" +#include "ekg/io/descriptor.hpp" +#include "ekg/io/utf.hpp" + +namespace ekg { + struct textbox_color_scheme_t { + public: + ekg::rgba_t background {}; + ekg::rgba_t outline {}; + ekg::rgba_t text_foreground {}; + ekg::rgba_t text_select_foreground {}; + ekg::rgba_t text_cursor_foreground {}; + bool caret_cursor {}; + }; + + struct textbox_t { + public: + struct widget_t { + public: + ekg::scrollbar_t scrollbar {}; + }; + + static constexpr ekg::type type {ekg::type::textbox}; + static ekg::textbox_t not_found; + public: + ekg::at_t property_at {}; + public: + std::string tag {}; + ekg::flags_t dock {}; + ekg::rect_t rect {}; + ekg::textbox_color_scheme_t color_scheme {}; + public: + ekg_descriptor(ekg::textbox_t); + }; +} + +#endif diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp new file mode 100644 index 00000000..20ad7b07 --- /dev/null +++ b/include/ekg/ui/textbox/widget.hpp @@ -0,0 +1,62 @@ +/** + * MIT License + * + * Copyright (c) 2022-2025 Rina Wilk / vokegpu@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef EKG_UI_TEXTBOX_WIDGET_HPP +#define EKG_UI_TEXTBOX_WIDGET_HPP + +#include "ekg/ui/property.hpp" +#include "textbox.hpp" + +namespace ekg::ui {` + void reload( + ekg::property_t &property, + ekg::textbox_t &textbox + ); + + void event( + ekg::property_t &property, + ekg::textbox_t &textbox, + const ekg::io::stage &stage + ); + + void high_frequency( + ekg::property_t &property, + ekg::textbox_t &textbox + ); + + void pass( + ekg::property_t &property, + ekg::textbox_t &textbox + ); + + void buffering( + ekg::property_t &property, + ekg::textbox_t &textbox + ); + + void unmap( + ekg::textbox_t &textbox + ); +} + +#endif diff --git a/src/io/utf.cpp b/src/io/utf.cpp index ad51b8eb..a922d2b7 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -242,3 +242,5 @@ bool ekg::utf8_split( return found_flag; } + +std::string ekg::io::text::str_out_of_bouding {'\e\k\g'}; diff --git a/src/ui/textbox/textbox.cpp b/src/ui/textbox/textbox.cpp new file mode 100644 index 00000000..ee6d9961 --- /dev/null +++ b/src/ui/textbox/textbox.cpp @@ -0,0 +1,28 @@ +/** + * MIT License + * + * Copyright (c) 2022-2025 Rina Wilk / vokegpu@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "ekg/ui/textbox/textbox.hpp" + +ekg::textbox_t ekg::textbox_t::not_found { + .at = ekg::at_t::not_found +}; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp new file mode 100644 index 00000000..11616afc --- /dev/null +++ b/src/ui/textbox/widget.cpp @@ -0,0 +1,66 @@ +/** + * MIT License + * + * Copyright (c) 2022-2025 Rina Wilk / vokegpu@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "ekg/ui/textbox/widget.hpp" + +void ekg::ui::reload( + ekg::property_t &property, + ekg::textbox_t &textbox +) { + +} + +void ekg::ui::event( + ekg::property_t &property, + ekg::textbox_t &textbox, + const ekg::io::stage &stage +) { + +} + +void ekg::ui::high_frequency( + ekg::property_t &property, + ekg::textbox_t &textbox +) { + +} + +void ekg::ui::pass( + ekg::property_t &property, + ekg::textbox_t &textbox +) { + +} + +void ekg::ui::buffering( + ekg::property_t &property, + ekg::textbox_t &textbox +) { + +} + +void ekg::ui::unmap( + ekg::textbox_t &textbox +) { + +} diff --git a/version/todo.txt b/version/todo.txt index 76282c29..c2d58f13 100644 --- a/version/todo.txt +++ b/version/todo.txt @@ -1,29 +1,6 @@ -/* DPI, UI, and Scale */ - -The current stupid system of EKG for scalling is actually useless; the scaled height factors at each widget, -is not really scaled by the layout, so, I want to use scaled height factor but scale with the DPI/UI scale. - -So 800x600 will be the same as 1920x1080 indifferent of font-size. -How? idk, i will use the mother/parent frame as base/factor and the height font too as base. -So the layout service will get the perfect sized DPI and scale. -Some day I do this. - -/* Slider must be rewrite the part of docking text value! */ - -Slider contains a glitch were you can drag with no maximum limit. - -/* Textbox is not updating newer text! */ - -stupid ticking glitch. - -/* stupid scorlling glitch */ - -rewrite slider -fix listbox issue when scrolling and dragging? -reduce ekg::slider verbose number setting. - -// Add checkbox - -✔ - -// add `set_width` and `set_height` to all widgets setters dimension methods >< meowg \ No newline at end of file +text chunk-based for textbox: +1- chunk is opportunish, any inserted text will occup any chunk if is not limited +2- if chunk is full a new one is created +3- copypasting should be calculated to fit or generate news chunks if necessary +4- if textbox is unfocused, once time should re-index and fix incorrect text align --- this may affect perf but idc for now +5- the user-programmer will use the `ekg::text` class for handling texts --- but should be like an equals one From f87ce95493420961887d34c4ef2436e9c219c5f7 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Wed, 23 Jul 2025 23:38:46 -0300 Subject: [PATCH 02/61] [feature] text-chunk based complete --- include/ekg/io/utf.hpp | 98 ++++--------- include/ekg/ui/textbox/widget.hpp | 2 +- src/io/utf.cpp | 230 +++++++++++++++++++++++++++++- 3 files changed, 258 insertions(+), 72 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 87c5b8d6..ff89a7fb 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -124,78 +124,36 @@ namespace ekg::io { namespace ekg { class text { protected: - std::vector chunks {}; - size_t chunk_limit {10}; - protected: - std::string &at_by_index(size_t index, bool swizzle = false) { - size_t total_lines {}; - size_t chunks_size {this->chunks.size()}; - for (size_t it {}; it < chunks_size; it++) { - chunk_t &chunk {this->chunks.at(it)}; - total_lines += chunk.size(); - - if (swizzle && total_lines == index) { - this - break; - } - } - } - - ekg::io::chunk_t &chunk_at(size_t chunk_index) { - return this- - } + std::vector loaded_chunks {}; + size_t lines_per_chunk_limit {10}; + size_t total_lines {}; public: - static std::string not_found; + static std::string line_not_found; public: - std::string &at(size_t index) { - size_t chunks_size {this->chunks.size()}; - size_t total_lines {}; - size_t previous_total_lines {}; - size_t index_of {}; - size_t chunk_size {}; - - for (size_t it {}; it < chunks_size; it++) { - ekg::io::chunk_t &chunk {this->chunks.at(it)}; - - previous_total_lines = total_lines; - total_lines += (chunk_size = chunk.size()); - index_of = index - previous_total_lines; - - if (total_lines < index && index_of < chunk_size) { - return chunk.at(index - previous_total_lines); - } - } - } - - void push_back(const std::string &line) { - if (this->chunks.empty()) { - this->chunks.emplace_back().push_back(line); - return; - } - - size_t chunks_size {this->chunks.size()}; - ekg::io::chunk_t &chunk {this->chunks.at(chunks_size - (chunks_size != 0))}; - if (chunk.size() >= this->chunk_limit) { - this->chunks.emplace_back().push_back(line); - return; - } - - this->chunks.emplace_back().push_back(line); - } - - std::string &emplace_back() { - if (this->chunks.empty()) { - return this->chunks.emplace_back().emplace_back(); - } - - size_t chunks_size {this->chunks.size()}; - ekg::io::chunk_t &chunk {this->chunks.at(chunks_size - (chunks_size != 0))}; - if (chunk.size() >= this->chunk_limit) { - return this->chunks.emplace_back().emplace_back(); - } - - return chunk.emplace_back(); - } + std::string &at( + size_t index + ); + + void insert( + size_t index, + ekg::io::chunk_t &to_insert_chunk + ); + + void insert( + size_t index, + const std::string &line + ); + + void erase( + size_t begin, + size_t end + ); + + void push_back(const std::string &line); + std::string &emplace_back(); + + size_t lines(); + size_t chunks(); }; } diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 20ad7b07..6296c986 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -27,7 +27,7 @@ #include "ekg/ui/property.hpp" #include "textbox.hpp" -namespace ekg::ui {` +namespace ekg::ui { void reload( ekg::property_t &property, ekg::textbox_t &textbox diff --git a/src/io/utf.cpp b/src/io/utf.cpp index a922d2b7..492aef05 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -243,4 +243,232 @@ bool ekg::utf8_split( return found_flag; } -std::string ekg::io::text::str_out_of_bouding {'\e\k\g'}; +std::string ekg::text::line_not_found {'\x1B'}; + +std::string &ekg::text::at(size_t index) { + size_t chunks_size {this->loaded_chunks.size()}; + size_t total_lines {}; + size_t previous_total_lines {}; + size_t chunk_size {}; + + for (size_t it {}; it < chunks_size; it++) { + ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + + previous_total_lines = total_lines; + total_lines += (chunk_size = chunk.size()); + + if (index < total_lines && (index - previous_total_lines) < chunk_size) { + return chunk.at(index - previous_total_lines); + } + } + + return ekg::text::line_not_found; +} + +void ekg::text::insert( + size_t index, + ekg::io::chunk_t &to_insert_chunk +) { + if (this->loaded_chunks.empty()) { + if (index == 0) { + this->loaded_chunks.emplace_back(); + this->insert(index, to_insert_chunk); + } + return; + } + + bool ok {}; + + size_t previous_total_lines {}; + size_t chunk_size {}; + size_t current_total_lines {}; + + ekg::io::chunk_t to_swizzle_chunk {}; + + this->total_lines = 0; + for (size_t it {}; it < this->loaded_chunks.size(); it++) { + ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + + previous_total_lines = this->total_lines; + current_total_lines += (chunk_size = chunk.size()); + + if ( + !ok + && + index < current_total_lines + && + (index - previous_total_lines) < chunk_size + ) { + chunk.insert( + chunk.begin() + (index - previous_total_lines), + to_insert_chunk.begin(), + to_insert_chunk.end() + ); + + if (chunk.size() < this->lines_per_chunk_limit) { + ok = true; + continue; + } + + to_swizzle_chunk.insert( + to_swizzle_chunk.begin(), + chunk.begin() + this->lines_per_chunk_limit, + chunk.end() + ); + + chunk.erase( + chunk.begin() + this->lines_per_chunk_limit, + chunk.end() + ); + + size_t to_swizzle_chunk_size {to_swizzle_chunk.size()}; + size_t newly_chunks { + to_swizzle_chunk_size / this->lines_per_chunk_limit + }; + + for (size_t jt {}; jt < newly_chunks; jt++) { + this->loaded_chunks.insert( + this->loaded_chunks.begin() + it + jt + 1, + ekg::io::chunk_t { + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 0)), + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 1)) + } + ); + } + + size_t rest {to_swizzle_chunk_size - (this->lines_per_chunk_limit * newly_chunks)}; + if (rest > 0) { + this->loaded_chunks.insert( + this->loaded_chunks.begin() + it + newly_chunks + 1, + ekg::io::chunk_t { + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (newly_chunks + 0)), + to_swizzle_chunk.end() + } + ); + } + + ok = true; + } + + this->total_lines += this->loaded_chunks.at(it).size(); + } +} + +void ekg::text::insert( + size_t index, + const std::string &line +) { + ekg::io::chunk_t chunk {}; + chunk.push_back(line); + this->insert(index, chunk); +} + +void ekg::text::erase( + size_t begin, + size_t end +) { + if (this->loaded_chunks.empty()) { + throw std::out_of_range("ekg::text::erase -> text empty"); + return; + } + + if (begin >= this->total_lines || end >= this->total_lines || end <= begin) { + throw std::out_of_range("ekg::text::erase -> begin or end out of index"); + return; + } + + this->total_lines -= end - begin; + + size_t previous_total_lines {}; + size_t chunk_size {}; + size_t total_lines {}; + size_t remains_lines {}; + + bool empty_chunk {}; + bool goto_next_chunk {}; + + for (size_t it {}; it < this->loaded_chunks.size(); it++) { + ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + + previous_total_lines = total_lines; + total_lines += (chunk_size = chunk.size()); + + if (begin < total_lines) { + remains_lines = end - begin; + begin = begin - previous_total_lines; + while (remains_lines != 0) { + ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + + chunk_size = chunk.size(); + goto_next_chunk = begin + remains_lines > chunk.size(); + + chunk.erase( + chunk.begin() + begin, + chunk.begin() + begin + (!goto_next_chunk * remains_lines) + ); + + ekg_log_low_level(begin << " x ") + + empty_chunk = chunk.empty(); + if (empty_chunk) { + this->loaded_chunks.erase( + this->loaded_chunks.begin() + it + ); + } + + if (goto_next_chunk) { + it += !empty_chunk; + remains_lines -= chunk_size - begin; + begin = 0; + continue; + } + + remains_lines = 0; + } + + break; + } + } +} + +void ekg::text::push_back(const std::string &line) { + if (this->loaded_chunks.empty()) { + this->loaded_chunks.emplace_back().push_back(line); + return; + } + + this->total_lines++; + + size_t chunks_size {this->loaded_chunks.size()}; + ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunks_size - (chunks_size != 0))}; + if (chunk.size() >= this->lines_per_chunk_limit) { + this->loaded_chunks.emplace_back().push_back(line); + return; + } + + this->loaded_chunks.emplace_back().push_back(line); +} + +std::string &ekg::text::emplace_back() { + if (this->loaded_chunks.empty()) { + return this->loaded_chunks.emplace_back().emplace_back(); + } + + this->total_lines++; + + size_t chunks_size {this->loaded_chunks.size()}; + ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunks_size - (chunks_size != 0))}; + if (chunk.size() >= this->lines_per_chunk_limit) { + return this->loaded_chunks.emplace_back().emplace_back(); + } + + return chunk.emplace_back(); +} + +size_t ekg::text::lines() { + return this->total_lines; +} + +size_t ekg::text::chunks() { + return this->loaded_chunks.size(); +} From c0de01f36df0913a78cb67c5b0dcc6f878f3a486 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 27 Jul 2025 02:02:55 -0300 Subject: [PATCH 03/61] [update] chunk data access; utf8 sequence reloaded --- include/ekg/core/pools.hpp | 2 +- include/ekg/io/utf.hpp | 38 +++++++-- include/ekg/ui/textbox/textbox.hpp | 8 +- src/draw/typography/font.cpp | 4 +- src/handler/theme/handler.cpp | 6 ++ src/io/utf.cpp | 46 ++++++++++- src/ui/textbox/widget.cpp | 124 ++++++++++++++++++++++++++++- 7 files changed, 215 insertions(+), 13 deletions(-) diff --git a/include/ekg/core/pools.hpp b/include/ekg/core/pools.hpp index a6e17110..af9461b4 100644 --- a/include/ekg/core/pools.hpp +++ b/include/ekg/core/pools.hpp @@ -342,7 +342,7 @@ namespace ekg { ekg::textbox_t, ekg::pools.textbox, ekg::pools.textbox_property, - true, + false, { property.is_childnizate = false; property.is_children_docknizable = false; diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index ff89a7fb..2774a21c 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -31,6 +31,32 @@ #include namespace ekg { + /** + * @brief: + * check utf8 sequence and add to `it` index. + * + * @param `uc8` the current char to be checked. + * @param `c32` the post utf32 char converted. + * @param `utf8_str` the utf8 encoded text. + * @param `it` the current `utf8_str` index. + * + * @description: + * this function check the first byte of a char utf8 encoded: + * if first char fit less than 127; 1 byte utf8 char. + * if first char fit equals 192~224; 2 byte utf8 char. + * if first char fit equals 224~240; 3 byte utf8 char. + * if first char fit equals 240~248; 4 byte utf8 char. + * + * by the amount of bytes per-char it is added to `it` e.g: + * if 224~240; then `it + 2` (`it` already counts as 1 byte) + **/ + void utf8_sequence( + uint8_t &uc8, + char32_t &c32, + std::string_view utf8_str, + size_t &it + ); + /** * Returns a UTF string by `char32` converting * the UTF-32 unique char into a sequence of UTF-8 @@ -51,8 +77,7 @@ namespace ekg { uint64_t size ); - /** - * Returns the `string` length considering UTF chars. + /** * Returns the `string` length considering UTF chars. */ uint64_t utf8_length( std::string_view string @@ -127,12 +152,12 @@ namespace ekg { std::vector loaded_chunks {}; size_t lines_per_chunk_limit {10}; size_t total_lines {}; + bool was_audited {}; public: static std::string line_not_found; public: - std::string &at( - size_t index - ); + std::string read(size_t index); + std::string &write(size_t index); void insert( size_t index, @@ -152,8 +177,11 @@ namespace ekg { void push_back(const std::string &line); std::string &emplace_back(); + std::vector &data(); + size_t lines(); size_t chunks(); + bool audited(); }; } diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index c8f4b746..86e3cad7 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -27,6 +27,7 @@ #include "ekg/ui/scrollbar/scrollbar.hpp" #include "ekg/io/descriptor.hpp" #include "ekg/io/utf.hpp" +#include "ekg/io/font.hpp" namespace ekg { struct textbox_color_scheme_t { @@ -43,6 +44,7 @@ namespace ekg { public: struct widget_t { public: + ekg::rect_t rect_text_size {}; ekg::scrollbar_t scrollbar {}; }; @@ -52,9 +54,13 @@ namespace ekg { ekg::at_t property_at {}; public: std::string tag {}; + ekg::text text {}; + ekg::font font_size {ekg::font::medium}; ekg::flags_t dock {}; - ekg::rect_t rect {}; + ekg::rect_t rect {.w = 100.0f}; ekg::textbox_color_scheme_t color_scheme {}; + ekg::textbox_t::widget_t widget {}; + ekg::at_array_t layers {}; public: ekg_descriptor(ekg::textbox_t); }; diff --git a/src/draw/typography/font.cpp b/src/draw/typography/font.cpp index fc8ee67d..71d0d058 100644 --- a/src/draw/typography/font.cpp +++ b/src/draw/typography/font.cpp @@ -435,7 +435,9 @@ void ekg::draw::font::blit( x = static_cast(static_cast(x)); y = static_cast(static_cast(y - this->offset_text_height)); - ekg::gpu::data_t &data {ekg::p_core->draw_allocator.bind_current_data()}; + ekg::gpu::data_t &data { + ekg::p_core->draw_allocator.bind_current_data() + }; data.buffer[0] = x; data.buffer[1] = y; diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index 26c6deeb..07d87330 100644 --- a/src/handler/theme/handler.cpp +++ b/src/handler/theme/handler.cpp @@ -76,6 +76,9 @@ void ekg::handler::theme::init() { light_pinky_theme.popup_color_scheme.outline = {50, 50, 50, 100}; light_pinky_theme.popup_color_scheme.popup_mode = true; + light_pinky_theme.textbox_color_scheme.background = {242, 242, 242, 255}; + light_pinky_theme.textbox_color_scheme.outline = {190, 190, 190, 100}; + this->registry(light_pinky_theme.tag) = light_pinky_theme; this->set_current_theme(light_pinky_theme.tag); @@ -127,6 +130,9 @@ void ekg::handler::theme::init() { black_light_pinky_theme.popup_color_scheme = black_light_pinky_theme.frame_color_scheme; black_light_pinky_theme.popup_color_scheme.popup_mode = true; + light_pinky_theme.textbox_color_scheme.background = {204, 204, 204, 50}; + light_pinky_theme.textbox_color_scheme.outline = {190, 190, 190, 0}; + this->registry(black_light_pinky_theme.tag) = black_light_pinky_theme; //this->set_current_theme(black_light_pinky_theme.tag); } diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 492aef05..edb903c3 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -27,6 +27,29 @@ #include #include +void ekg::utf8_sequence( + uint8_t &uc8, + char32_t &c32, + std::string_view utf8_str, + size_t &it +) { + if (uc8 <= 0x7F) { + c32 = static_cast(uc8); + } else if ((uc8 & 0xE0) == 0xC0) { + c32 = uc8 & 0x1F; + c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); + } else if ((uc8 & 0xF0) == 0xE0) { + c32 = uc8 & 0x0F; + c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); + c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); + } else if ((uc8 & 0xF8) == 0xF0) { + c32 = uc8 & 0x07; + c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); + c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); + c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); + } +} + uint64_t ekg::utf8_check_sequence( uint8_t &char8, char32_t &char32, @@ -245,12 +268,14 @@ bool ekg::utf8_split( std::string ekg::text::line_not_found {'\x1B'}; -std::string &ekg::text::at(size_t index) { +std::string &ekg::text::write(size_t index) { size_t chunks_size {this->loaded_chunks.size()}; size_t total_lines {}; size_t previous_total_lines {}; size_t chunk_size {}; + this->was_audited = true; + for (size_t it {}; it < chunks_size; it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; @@ -265,6 +290,13 @@ std::string &ekg::text::at(size_t index) { return ekg::text::line_not_found; } +std::string ekg::text::read(size_t index) { + bool was_audited_before {this->was_audited}; + std::string &line {this->write(index)}; + this->was_audited = was_audited_before; + return line; +} + void ekg::text::insert( size_t index, ekg::io::chunk_t &to_insert_chunk @@ -407,8 +439,6 @@ void ekg::text::erase( chunk.begin() + begin + (!goto_next_chunk * remains_lines) ); - ekg_log_low_level(begin << " x ") - empty_chunk = chunk.empty(); if (empty_chunk) { this->loaded_chunks.erase( @@ -472,3 +502,13 @@ size_t ekg::text::lines() { size_t ekg::text::chunks() { return this->loaded_chunks.size(); } + +bool ekg::text::audited() { + bool should {this->was_audited}; + this->was_audited = false; + return should; +} + +std::vector &ekg::text::data() { + return this->loaded_chunks; +} diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 11616afc..f9949754 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -22,12 +22,42 @@ * SOFTWARE. */ #include "ekg/ui/textbox/widget.hpp" +#include "ekg/layout/docknize.hpp" +#include "ekg/io/log.hpp" +#include "ekg/ui/abstract.hpp" +#include "ekg/draw/shape/shape.hpp" +#include "ekg/draw/typography/font.hpp" +#include "ekg/core/context.hpp" +#include "ekg/core/runtime.hpp" +#include "ekg/core/pools.hpp" void ekg::ui::reload( ekg::property_t &property, ekg::textbox_t &textbox ) { - + ekg::axis pick_axis { + ekg::axis::horizontal + }; + + ekg::draw::font &draw_font { + ekg::draw::get_font_renderer(textbox.font_size) + }; + + textbox.widget.rect_text_size.h = draw_font.get_text_height(); + textbox.widget.rect_text_size.w = textbox.widget.rect_text_size.h; + + ekg::aligned_t aligned_dimension {}; + ekg::align_rect_dimension( + pick_axis, + textbox.widget.rect_text_size, + ekg::dpi.min_sizes, + aligned_dimension + ); + + textbox.rect.scaled_height = ekg::max(1, textbox.rect.scaled_height); + textbox.rect.h = aligned_dimension.h * textbox.rect.scaled_height; + + } void ekg::ui::event( @@ -35,7 +65,17 @@ void ekg::ui::event( ekg::textbox_t &textbox, const ekg::io::stage &stage ) { - + switch (stage) { + default: { + break; + } + case ekg::io::stage::pre: + ekg::ui::pre_event(property, textbox.rect, false); + break; + case ekg::io::stage::post: + ekg::ui::post_event(property); + break; + } } void ekg::ui::high_frequency( @@ -49,14 +89,94 @@ void ekg::ui::pass( ekg::property_t &property, ekg::textbox_t &textbox ) { + ekg_draw_allocator_bind_local( + &property.widget.geometry_buffer, + &property.widget.gpu_data_buffer + ); + + if (property.widget.should_buffering || !textbox.text.audited()) { + property.widget.should_buffering = true; + return; + } + ekg_draw_allocator_pass(); } void ekg::ui::buffering( ekg::property_t &property, ekg::textbox_t &textbox ) { + ekg::rect_t &rect_abs { + ekg::ui::get_abs_rect(property, textbox.rect) + }; + + ekg_draw_allocator_assert_scissor( + property.widget.rect_scissor, + rect_abs, + ekg::query(property.parent_at).widget.rect_scissor, + ekg::always_parented + ); + + /* start of background */ + + ekg::draw::rect( + rect_abs, + textbox.color_scheme.background, + ekg::draw::mode::fill, + textbox.layers[ekg::layer::bg] + ); + + /* start of text */ + + ekg::gpu::data_t &data { + ekg::p_core->draw_allocator.bind_current_data() + }; + + ekg::draw::font &draw_font { + ekg::draw::get_font_renderer(textbox.font_size) + }; + + ekg::vec2_t pos {}; + + data.buffer[0] = rect_abs.x; + data.buffer[1] = rect_abs.y; + data.buffer[2] = static_cast(-draw_font.non_swizzlable_range); + + data.buffer[4] = static_cast(textbox.color_scheme.text_foreground.x / 255); + data.buffer[5] = static_cast(textbox.color_scheme.text_foreground.y / 255); + data.buffer[6] = static_cast(textbox.color_scheme.text_foreground.z / 255); + data.buffer[7] = static_cast(textbox.color_scheme.text_foreground.w / 255); + + size_t text_len {}; + uint8_t uc8 {}; + char32_t c32 {}; + std::string utf8_str {}; + + for (ekg::io::chunk_t &chunk : textbox.text.data()) { + text_len = chunk.size(); + for (size_t it {}; it < text_len; it++) { + std::string &line_text {chunk.at(it)}; + uc8 = static_cast(uc8); + + ekg::utf8_sequence( + uc8, + c32, + line_text, + it + ); + } + } + + /* start of outline */ + + ekg::draw::rect( + rect_abs, + textbox.color_scheme.outline, + ekg::draw::mode::outline, + ekg::at_t::not_found + ); + ekg_draw_allocator_pass(); } void ekg::ui::unmap( From d27eb126f16662bd870686fd13218ae872c2f796 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Mon, 28 Jul 2025 00:45:34 -0300 Subject: [PATCH 04/61] [update] cursors layers --- include/ekg/draw/shape/shape.hpp | 4 +- include/ekg/ui/textbox/textbox.hpp | 41 +++++ include/ekg/ui/textbox/widget.hpp | 6 + src/ekg.cpp | 3 + src/handler/theme/handler.cpp | 4 + src/ui/textbox/widget.cpp | 244 ++++++++++++++++++++++++++--- 6 files changed, 281 insertions(+), 21 deletions(-) diff --git a/include/ekg/draw/shape/shape.hpp b/include/ekg/draw/shape/shape.hpp index 9a1aad6d..2cbc60ac 100644 --- a/include/ekg/draw/shape/shape.hpp +++ b/include/ekg/draw/shape/shape.hpp @@ -36,14 +36,14 @@ namespace ekg::draw { void rect( const ekg::rect_t &rect, const ekg::rgba_t &color, - ekg::pixel_thickness_t line_thicnkess, + ekg::pixel_thickness_t line_thickness, ekg::at_t &sampler_at ); void rect( float x, float y, float w, float h, const ekg::rgba_t &color, - ekg::pixel_thickness_t line_thicnkess, + ekg::pixel_thickness_t line_thickness, ekg::at_t &sampler_at ); diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 86e3cad7..98a2f874 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -37,15 +37,56 @@ namespace ekg { ekg::rgba_t text_foreground {}; ekg::rgba_t text_select_foreground {}; ekg::rgba_t text_cursor_foreground {}; + ekg::pixel_thickness_t cursor_thickness {2}; bool caret_cursor {}; }; struct textbox_t { public: + struct select_draw_layer_t { + public: + bool is_ab_equals {}; + ekg::rect_t rect {}; + }; + + struct cursor_t { + public: + size_t index_a {}; + size_t index_b {}; + ekg::rect_t rect {}; + public: + bool operator == (size_t index) { + return this->index_a == index && this->index_b == index; + } + + bool operator > (size_t index) { + return this->index_a > index; + } + + bool operator >= (size_t index) { + return this->index_a >= index; + } + + bool operator < (size_t index) { + return this->index_b < index; + } + + bool operator <= (size_t index) { + return this->index_b <= index; + } + + bool operator == (const ekg::textbox_t::cursor_t &cursor) { + return this->index_a == cursor.index_a && this->index_b == cursor.index_b; + } + }; + struct widget_t { public: ekg::rect_t rect_text_size {}; ekg::scrollbar_t scrollbar {}; + std::vector cursors {}; + std::vector layers_select {}; + size_t last_layers_select_size {}; }; static constexpr ekg::type type {ekg::type::textbox}; diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 6296c986..1833ad5b 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -28,6 +28,12 @@ #include "textbox.hpp" namespace ekg::ui { + bool find_cursor( + ekg::textbox_t &textbox, + size_t len, + ekg::textbox_t::cursor_t &cursor_out + ); + void reload( ekg::property_t &property, ekg::textbox_t &textbox diff --git a/src/ekg.cpp b/src/ekg.cpp index 03b9ea72..de7d59bf 100644 --- a/src/ekg.cpp +++ b/src/ekg.cpp @@ -138,12 +138,14 @@ void ekg::render() { ekg::gui.ui.redraw = false; ekg::p_core->draw_allocator.invoke(); + size_t a {}; for (ekg::at_t &at : ekg::p_core->stack) { ekg::property_t &property { ekg::query(at) }; + if (property == ekg::property_t::not_found) { continue; } @@ -183,6 +185,7 @@ void ekg::render() { } ekg::ui::buffering(property, descriptor); + //property.widget.should_buffering = false; ); } diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index 07d87330..3b01a0cf 100644 --- a/src/handler/theme/handler.cpp +++ b/src/handler/theme/handler.cpp @@ -78,6 +78,9 @@ void ekg::handler::theme::init() { light_pinky_theme.textbox_color_scheme.background = {242, 242, 242, 255}; light_pinky_theme.textbox_color_scheme.outline = {190, 190, 190, 100}; + light_pinky_theme.textbox_color_scheme.text_foreground = {141, 141, 141, 255}; + light_pinky_theme.textbox_color_scheme.text_cursor_foreground = {141, 141, 141, 255}; + light_pinky_theme.textbox_color_scheme.text_select_foreground = {245, 169, 184, 50}; this->registry(light_pinky_theme.tag) = light_pinky_theme; this->set_current_theme(light_pinky_theme.tag); @@ -132,6 +135,7 @@ void ekg::handler::theme::init() { light_pinky_theme.textbox_color_scheme.background = {204, 204, 204, 50}; light_pinky_theme.textbox_color_scheme.outline = {190, 190, 190, 0}; + light_pinky_theme.textbox_color_scheme.text_foreground = {141, 141, 141, 255}; this->registry(black_light_pinky_theme.tag) = black_light_pinky_theme; //this->set_current_theme(black_light_pinky_theme.tag); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index f9949754..5f0ef707 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -31,6 +31,21 @@ #include "ekg/core/runtime.hpp" #include "ekg/core/pools.hpp" +bool ekg::ui::find_cursor( + ekg::textbox_t &textbox, + size_t len, + ekg::textbox_t::cursor_t &cursor_out +) { + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor >= len && cursor <= len) { + cursor_out = cursor; + return true; + } + } + + return false; +} + void ekg::ui::reload( ekg::property_t &property, ekg::textbox_t &textbox @@ -56,8 +71,6 @@ void ekg::ui::reload( textbox.rect.scaled_height = ekg::max(1, textbox.rect.scaled_height); textbox.rect.h = aligned_dimension.h * textbox.rect.scaled_height; - - } void ekg::ui::event( @@ -126,17 +139,30 @@ void ekg::ui::buffering( textbox.layers[ekg::layer::bg] ); - /* start of text */ + /* start of select */ - ekg::gpu::data_t &data { - ekg::p_core->draw_allocator.bind_current_data() - }; + for (ekg::textbox_t::select_draw_layer_t &layer : textbox.widget.layers_select) { + if (layer.is_ab_equals) { + ekg::draw::rect( + layer.rect + rect_abs, + textbox.color_scheme.text_cursor_foreground, + ekg::draw::mode::fill, + ekg::at_t::not_found + ); + + continue; + } + } + + /* start of text */ ekg::draw::font &draw_font { ekg::draw::get_font_renderer(textbox.font_size) }; - ekg::vec2_t pos {}; + ekg::gpu::data_t &data { + ekg::p_core->draw_allocator.bind_current_data() + }; data.buffer[0] = rect_abs.x; data.buffer[1] = rect_abs.y; @@ -147,26 +173,191 @@ void ekg::ui::buffering( data.buffer[6] = static_cast(textbox.color_scheme.text_foreground.z / 255); data.buffer[7] = static_cast(textbox.color_scheme.text_foreground.w / 255); + ekg::hash_t hash {}; + ekg::vec2_t pos {}; + ekg::rect_t vertex {}; + ekg::rect_t uv {}; + + size_t len {}; size_t text_len {}; uint8_t uc8 {}; char32_t c32 {}; - std::string utf8_str {}; + + bool break_text {}; + bool r_n_break_text {}; + + FT_Face ft_face {}; + FT_Vector ft_vector_previous_char {}; + char32_t ft_uint_previous {}; + + ekg::io::font_face_t &text_font_face {draw_font.faces[ekg::io::font_face_type::text]}; + ekg::io::font_face_t &emojis_font_face {draw_font.faces[ekg::io::font_face_type::emojis]}; + ekg::io::font_face_t &kanjis_font_face {draw_font.faces[ekg::io::font_face_type::kanjis]}; + + textbox.widget.cursors = { + { + 2, 2 + } + }; + + bool is_select_cursor {}; + bool is_caret_cursor {}; + bool cursors_going_on {!textbox.widget.cursors.empty()}; + ekg::textbox_t::cursor_t cursor {}; + + textbox.widget.layers_select.clear(); for (ekg::io::chunk_t &chunk : textbox.text.data()) { - text_len = chunk.size(); - for (size_t it {}; it < text_len; it++) { - std::string &line_text {chunk.at(it)}; - uc8 = static_cast(uc8); - - ekg::utf8_sequence( - uc8, - c32, - line_text, - it - ); + for (std::string &line : chunk) { + text_len = line.size(); + for (size_t it {}; it < text_len; it++) { + uc8 = static_cast(line.at(it)); + + ekg::utf8_sequence( + uc8, + c32, + line, + it + ); + + break_text = uc8 == '\n'; + if ( + break_text + || + ( + r_n_break_text = ( + uc8 == '\r' && it < text_len && line.at(it + 1) == '\n' + ) + ) + ) { + + ekg::io::glyph_t &glyph {draw_font.mapped_glyph[c32]}; + + it += static_cast(r_n_break_text); + hash += ekg_generate_hash(pos.y, c32, glyph.x); + + pos.y += draw_font.text_height; + pos.x = 0.0f; + len++; + + continue; + } + + if (draw_font.ft_bool_kerning && ft_uint_previous) { + switch (c32 < 256 || !emojis_font_face.was_loaded) { + case true: { + ft_face = text_font_face.ft_face; + break; + } + + default: { + ft_face = emojis_font_face.ft_face; + break; + } + } + + FT_Get_Kerning(ft_face, ft_uint_previous, c32, 0, &ft_vector_previous_char); + pos.x += static_cast(ft_vector_previous_char.x >> 6); + } + + ekg::io::glyph_t &glyph {draw_font.mapped_glyph[c32]}; + + if ( + cursors_going_on + && + ekg::ui::find_cursor(textbox, len, cursor) + ) { + is_caret_cursor = cursor == len; + is_select_cursor = cursor >= len && cursor <= len && !is_caret_cursor; + + if (is_caret_cursor) { + cursor.rect.x = pos.x; + cursor.rect.y = pos.y; + cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph.wsize : textbox.color_scheme.cursor_thickness; + cursor.rect.h = draw_font.text_height; + textbox.widget.layers_select.push_back({true, cursor.rect}); + } + + if (is_select_cursor) { + + } + } + + if (!glyph.was_sampled) { + draw_font.new_glyphs_to_atlas.emplace_back(c32); + glyph.was_sampled = true; + } + + vertex.x = pos.x + glyph.left; + vertex.y = pos.y + draw_font.atlas_rect.h - glyph.top; + + vertex.w = glyph.w; + vertex.h = glyph.h; + + uv.x = glyph.x; + uv.w = vertex.w / draw_font.atlas_rect.w; + uv.h = vertex.h / draw_font.atlas_rect.h; + + ekg::p_core->draw_allocator.push_back_geometry( + vertex.x, + vertex.y, + uv.x, + uv.y + ); + + ekg::p_core->draw_allocator.push_back_geometry( + vertex.x, + vertex.y + vertex.h, + uv.x, + uv.y + uv.h + ); + + ekg::p_core->draw_allocator.push_back_geometry( + vertex.x + vertex.w, + vertex.y + vertex.h, + uv.x + uv.w, + uv.y + uv.h + ); + + ekg::p_core->draw_allocator.push_back_geometry( + vertex.x + vertex.w, + vertex.y + vertex.h, + uv.x + uv.w, + uv.y + uv.h + ); + + ekg::p_core->draw_allocator.push_back_geometry( + vertex.x + vertex.w, + vertex.y, + uv.x + uv.w, + uv.y + ); + + ekg::p_core->draw_allocator.push_back_geometry( + vertex.x, + vertex.y, + uv.x, + uv.y + ); + + pos.x += glyph.wsize; + ft_uint_previous = c32; + + /** + * Peek `ekg/io/memory.hpp` for better hash definition and purpose. + **/ + hash += ekg_generate_hash(pos.x, c32, glyph.x); + len++; + } } } + data.hash = hash; + + ekg::draw::allocator::is_simple_shape = false; + ekg::p_core->draw_allocator.bind_texture(draw_font.atlas_texture_sampler_at); + ekg::p_core->draw_allocator.dispatch(); + /* start of outline */ ekg::draw::rect( @@ -176,6 +367,21 @@ void ekg::ui::buffering( ekg::at_t::not_found ); + /** + * Cursors should be rendered backward from text-render. + * Textbox first try-render render the cursors, then at end of renderer-segment + * check if latest size is diff, then re-call buffering once for render + * the cursos (backward). + * + * The EKG renderer engine does not allow doing strip on gpu resources (not yet). + * May soon strip-resources should be implemented and this code part become deprecated. + **/ + size_t layers_select_size {textbox.widget.layers_select.size()}; + if (textbox.widget.last_layers_select_size != layers_select_size) { + property.widget.should_buffering = true; + textbox.widget.last_layers_select_size = layers_select_size; + } + ekg_draw_allocator_pass(); } From 230c2ee769078bb0fa7fa59ab7a4ff940d5964bc Mon Sep 17 00:00:00 2001 From: mrsrina Date: Tue, 29 Jul 2025 00:52:37 -0300 Subject: [PATCH 05/61] [deprecated] stupid brain --- include/ekg/io/utf.hpp | 7 +- include/ekg/ui/textbox/textbox.hpp | 17 ++-- src/ekg.cpp | 2 +- src/io/utf.cpp | 92 ++++++++++++++-------- src/ui/textbox/widget.cpp | 120 ++++++++++++++++++++++++----- 5 files changed, 180 insertions(+), 58 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 2774a21c..5b60c262 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -151,8 +151,10 @@ namespace ekg { protected: std::vector loaded_chunks {}; size_t lines_per_chunk_limit {10}; - size_t total_lines {}; + size_t total_chars {}; + size_t prev_total_chars {}; bool was_audited {}; + bool should_count {}; public: static std::string line_not_found; public: @@ -179,8 +181,11 @@ namespace ekg { std::vector &data(); + size_t size(); size_t lines(); size_t chunks(); + + void unset_audited(); bool audited(); }; } diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 98a2f874..05850666 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -56,23 +56,23 @@ namespace ekg { ekg::rect_t rect {}; public: bool operator == (size_t index) { - return this->index_a == index && this->index_b == index; + return index == this->index_a && index == this->index_b; } bool operator > (size_t index) { - return this->index_a > index; + return index > this->index_a; } bool operator >= (size_t index) { - return this->index_a >= index; + return index >= this->index_a; } bool operator < (size_t index) { - return this->index_b < index; + return index < this->index_b; } bool operator <= (size_t index) { - return this->index_b <= index; + return index <= this->index_b; } bool operator == (const ekg::textbox_t::cursor_t &cursor) { @@ -80,12 +80,19 @@ namespace ekg { } }; + struct untracked_line_ending_t { + public: + ekg::vec2_t indices {}; + std::string text {}; + }; + struct widget_t { public: ekg::rect_t rect_text_size {}; ekg::scrollbar_t scrollbar {}; std::vector cursors {}; std::vector layers_select {}; + std::vector line_ending_untracked {}; size_t last_layers_select_size {}; }; diff --git a/src/ekg.cpp b/src/ekg.cpp index de7d59bf..b677c30d 100644 --- a/src/ekg.cpp +++ b/src/ekg.cpp @@ -167,7 +167,7 @@ void ekg::render() { * * I do not know why this is hapenning, likely wtf, if I remove this * scrolling is horrible, I am not sure why this is hapning, I tried make the make redraw always, - * rendering everything, no allocators asserts. Nothing. I tried do a stupid `meow` where t receives a descriptor, + * rendering everything, no allocators asscerts. Nothing. I tried do a stupid `meow` where t receives a descriptor, * but nothing too. I do not know why this happens but it is very insanely weird. Post does not affect the performance * a lot, may we consider to let for some long time. While no solution was found. * diff --git a/src/io/utf.cpp b/src/io/utf.cpp index edb903c3..5e5c02fe 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -270,8 +270,8 @@ std::string ekg::text::line_not_found {'\x1B'}; std::string &ekg::text::write(size_t index) { size_t chunks_size {this->loaded_chunks.size()}; - size_t total_lines {}; - size_t previous_total_lines {}; + size_t total_chars {}; + size_t previous_total_chars {}; size_t chunk_size {}; this->was_audited = true; @@ -279,11 +279,11 @@ std::string &ekg::text::write(size_t index) { for (size_t it {}; it < chunks_size; it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; - previous_total_lines = total_lines; - total_lines += (chunk_size = chunk.size()); + previous_total_chars = total_chars; + total_chars += (chunk_size = chunk.size()); - if (index < total_lines && (index - previous_total_lines) < chunk_size) { - return chunk.at(index - previous_total_lines); + if (index < total_chars && (index - previous_total_chars) < chunk_size) { + return chunk.at(index - previous_total_chars); } } @@ -308,31 +308,31 @@ void ekg::text::insert( } return; } + this->was_audited = true; bool ok {}; - size_t previous_total_lines {}; + size_t previous_total_chars {}; size_t chunk_size {}; - size_t current_total_lines {}; + size_t current_total_chars {}; ekg::io::chunk_t to_swizzle_chunk {}; - this->total_lines = 0; for (size_t it {}; it < this->loaded_chunks.size(); it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; - previous_total_lines = this->total_lines; - current_total_lines += (chunk_size = chunk.size()); + previous_total_chars = this->total_chars; + current_total_chars += (chunk_size = chunk.size()); if ( !ok && - index < current_total_lines + index < current_total_chars && - (index - previous_total_lines) < chunk_size + (index - previous_total_chars) < chunk_size ) { chunk.insert( - chunk.begin() + (index - previous_total_lines), + chunk.begin() + (index - previous_total_chars), to_insert_chunk.begin(), to_insert_chunk.end() ); @@ -381,8 +381,6 @@ void ekg::text::insert( ok = true; } - - this->total_lines += this->loaded_chunks.at(it).size(); } } @@ -404,16 +402,22 @@ void ekg::text::erase( return; } - if (begin >= this->total_lines || end >= this->total_lines || end <= begin) { - throw std::out_of_range("ekg::text::erase -> begin or end out of index"); + if (begin > this->total_chars) { + throw std::out_of_range("ekg::text::erase: " + std::to_string(begin) + ", " + std::to_string(this->total_chars)); return; } - this->total_lines -= end - begin; + end = ekg::min(begin + end, this->total_chars); + + this->should_count = true; + this->size(); + + this->total_chars -= end - begin; + this->was_audited = true; - size_t previous_total_lines {}; + size_t previous_total_chars {}; size_t chunk_size {}; - size_t total_lines {}; + size_t total_chars {}; size_t remains_lines {}; bool empty_chunk {}; @@ -422,12 +426,12 @@ void ekg::text::erase( for (size_t it {}; it < this->loaded_chunks.size(); it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; - previous_total_lines = total_lines; - total_lines += (chunk_size = chunk.size()); + previous_total_chars = total_chars; + total_chars += (chunk_size = chunk.size()); - if (begin < total_lines) { + if (begin < total_chars) { remains_lines = end - begin; - begin = begin - previous_total_lines; + begin = begin - previous_total_chars; while (remains_lines != 0) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; @@ -459,6 +463,9 @@ void ekg::text::erase( break; } } + + this->should_count = true; + this->size(); } void ekg::text::push_back(const std::string &line) { @@ -466,8 +473,9 @@ void ekg::text::push_back(const std::string &line) { this->loaded_chunks.emplace_back().push_back(line); return; } - - this->total_lines++; + + this->should_count = true; + this->size(); size_t chunks_size {this->loaded_chunks.size()}; ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunks_size - (chunks_size != 0))}; @@ -484,7 +492,8 @@ std::string &ekg::text::emplace_back() { return this->loaded_chunks.emplace_back().emplace_back(); } - this->total_lines++; + this->should_count = true; + this->size(); size_t chunks_size {this->loaded_chunks.size()}; ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunks_size - (chunks_size != 0))}; @@ -496,17 +505,36 @@ std::string &ekg::text::emplace_back() { } size_t ekg::text::lines() { - return this->total_lines; + return 0; +} + +size_t ekg::text::size() { + if (this->should_count) { + this->total_chars = 0; + for (ekg::io::chunk_t &chunk : this->loaded_chunks) { + for (std::string &lines : chunk) { + this->total_chars += lines.size(); + } + } + + this->should_count = false; + } + + return this->total_chars; } size_t ekg::text::chunks() { return this->loaded_chunks.size(); } -bool ekg::text::audited() { - bool should {this->was_audited}; +void ekg::text::unset_audited() { this->was_audited = false; - return should; +} + +bool ekg::text::audited() { + this->was_audited = this->was_audited || (this->prev_total_chars != this->total_chars); + this->prev_total_chars = this->total_chars; + return this->was_audited; } std::vector &ekg::text::data() { diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 5f0ef707..286dd8db 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -119,6 +119,8 @@ void ekg::ui::buffering( ekg::property_t &property, ekg::textbox_t &textbox ) { + textbox.text.unset_audited(); + ekg::rect_t &rect_abs { ekg::ui::get_abs_rect(property, textbox.rect) }; @@ -142,16 +144,12 @@ void ekg::ui::buffering( /* start of select */ for (ekg::textbox_t::select_draw_layer_t &layer : textbox.widget.layers_select) { - if (layer.is_ab_equals) { - ekg::draw::rect( - layer.rect + rect_abs, - textbox.color_scheme.text_cursor_foreground, - ekg::draw::mode::fill, - ekg::at_t::not_found - ); - - continue; - } + ekg::draw::rect( + layer.rect + rect_abs, + layer.is_ab_equals ? textbox.color_scheme.text_cursor_foreground : textbox.color_scheme.text_select_foreground, + ekg::draw::mode::fill, + ekg::at_t::not_found + ); } /* start of text */ @@ -164,8 +162,8 @@ void ekg::ui::buffering( ekg::p_core->draw_allocator.bind_current_data() }; - data.buffer[0] = rect_abs.x; - data.buffer[1] = rect_abs.y; + data.buffer[0] = static_cast(static_cast(rect_abs.x)); + data.buffer[1] = static_cast(static_cast(rect_abs.y - draw_font.offset_text_height * 2.0f)); data.buffer[2] = static_cast(-draw_font.non_swizzlable_range); data.buffer[4] = static_cast(textbox.color_scheme.text_foreground.x / 255); @@ -196,13 +194,22 @@ void ekg::ui::buffering( textbox.widget.cursors = { { - 2, 2 + 0, 13 } }; - bool is_select_cursor {}; - bool is_caret_cursor {}; + bool is_inline_selected {}; + bool is_complete_line_selected {}; + bool is_ab_equals_selected {}; + bool was_complete_line_selected {}; bool cursors_going_on {!textbox.widget.cursors.empty()}; + + size_t current_line_for_cursor_complete {}; + size_t difference {}; + size_t lines {}; + size_t text_total_chars {textbox.text.size()}; + + ekg::pixel_t line_wsize {}; ekg::textbox_t::cursor_t cursor {}; textbox.widget.layers_select.clear(); @@ -238,7 +245,17 @@ void ekg::ui::buffering( pos.y += draw_font.text_height; pos.x = 0.0f; + + + textbox.widget.line_ending_untracked.push_back( + { + .indices = {len - it, len}, + .text = line.substr(len - it, len - (len - it)) + } + ); + len++; + lines++; continue; } @@ -267,10 +284,27 @@ void ekg::ui::buffering( && ekg::ui::find_cursor(textbox, len, cursor) ) { - is_caret_cursor = cursor == len; - is_select_cursor = cursor >= len && cursor <= len && !is_caret_cursor; - if (is_caret_cursor) { + is_ab_equals_selected = cursor == len; + is_inline_selected = cursor >= len && cursor <= len && !is_ab_equals_selected; + + is_complete_line_selected = false; + + if (is_inline_selected) { + difference = len - it; + if (difference >= cursor.index_a) { + is_complete_line_selected = true; + } + + difference = len + (text_len - it); + if (difference <= cursor.index_b) { + is_complete_line_selected = is_complete_line_selected; + } + + is_inline_selected = !is_complete_line_selected; + } + + if (is_ab_equals_selected) { cursor.rect.x = pos.x; cursor.rect.y = pos.y; cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph.wsize : textbox.color_scheme.cursor_thickness; @@ -278,8 +312,37 @@ void ekg::ui::buffering( textbox.widget.layers_select.push_back({true, cursor.rect}); } - if (is_select_cursor) { + if (is_inline_selected) { + cursor.rect.x = pos.x; + cursor.rect.y = pos.y; + cursor.rect.w = glyph.wsize; + cursor.rect.h = draw_font.text_height; + textbox.widget.layers_select.push_back({false, cursor.rect}); + } + + if (is_complete_line_selected && current_line_for_cursor_complete != lines) { + line_wsize = 0.0f; + current_line_for_cursor_complete = lines; + was_complete_line_selected = true; + } + if (is_complete_line_selected) { + line_wsize += glyph.wsize; + } + + if ( + was_complete_line_selected + && + (current_line_for_cursor_complete != lines || lines + 1 == text_total_chars) + && + !is_complete_line_selected + ) { + cursor.rect.x = 0.0f; + cursor.rect.y = pos.y; + cursor.rect.w = line_wsize; + cursor.rect.h = draw_font.text_height; + textbox.widget.layers_select.push_back({false, cursor.rect}); + was_complete_line_selected = false; } } @@ -380,6 +443,25 @@ void ekg::ui::buffering( if (textbox.widget.last_layers_select_size != layers_select_size) { property.widget.should_buffering = true; textbox.widget.last_layers_select_size = layers_select_size; + ekg::gui.ui.redraw = true; + } + + if (!textbox.widget.line_ending_untracked.empty()) { + for (ekg::textbox_t::untracked_line_ending_t &untracked_line_ending : textbox.widget.line_ending_untracked) { + textbox.text.erase( + untracked_line_ending.indices.x, + untracked_line_ending.indices.y + ); + + textbox.text.insert( + untracked_line_ending.indices.x, + untracked_line_ending.text + ); + } + + property.widget.should_buffering = true; + textbox.widget.line_ending_untracked.clear(); + ekg::gui.ui.redraw = true; } ekg_draw_allocator_pass(); From 538e060b06cbec0cc5c4cd4d2ae0931c9204ee50 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 3 Aug 2025 15:52:27 -0300 Subject: [PATCH 06/61] [fix] box cursor rendering and selection --- include/ekg/io/utf.hpp | 54 ++--- include/ekg/ui/textbox/textbox.hpp | 7 - src/handler/input/handler.cpp | 2 +- src/io/utf.cpp | 357 ++++++++++++++--------------- src/ui/textbox/widget.cpp | 141 +++++------- 5 files changed, 256 insertions(+), 305 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 5b60c262..eafca65e 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -103,24 +103,9 @@ namespace ekg { std::string_view string ); - /** - * Fast splitter specialized in `\n` or `\r\n` (non OS unix-based). - * UTF to sinalize the string unicode-like suggested by EKG. - */ - void utf8_split_new_line( - std::string_view string, - std::vector &utf8_split_new_lined - ); - - /** - * Return true if `string` contains `find_char`, - * then it must allocate and insert elements to - * `p_string_split_list` ptr. - */ - bool utf8_split( - std::vector &string_split_list, - const std::string &string, - char find_char + size_t utf8_split_endings( + std::string_view line, + std::vector &splitted ); template @@ -151,24 +136,32 @@ namespace ekg { protected: std::vector loaded_chunks {}; size_t lines_per_chunk_limit {10}; + size_t total_lines {}; size_t total_chars {}; + size_t prev_lines {}; size_t prev_total_chars {}; + bool was_audited {}; bool should_count {}; + protected: + void swizzle( + size_t chunk_index, + size_t line_index, + std::vector &to_swizzle, + bool skip_first_line + ); public: - static std::string line_not_found; - public: - std::string read(size_t index); - std::string &write(size_t index); + void push_back(std::string_view line); + void set(size_t index, std::string_view line); void insert( size_t index, ekg::io::chunk_t &to_insert_chunk ); - + void insert( size_t index, - const std::string &line + std::string_view line ); void erase( @@ -176,16 +169,13 @@ namespace ekg { size_t end ); - void push_back(const std::string &line); - std::string &emplace_back(); - - std::vector &data(); + std::vector &chunks_data(); + size_t length_of_chunks(); - size_t size(); - size_t lines(); - size_t chunks(); + std::string at(size_t index); + size_t length_of_lines(); + size_t length_of_chars(); - void unset_audited(); bool audited(); }; } diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 05850666..88d7c1b4 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -80,19 +80,12 @@ namespace ekg { } }; - struct untracked_line_ending_t { - public: - ekg::vec2_t indices {}; - std::string text {}; - }; - struct widget_t { public: ekg::rect_t rect_text_size {}; ekg::scrollbar_t scrollbar {}; std::vector cursors {}; std::vector layers_select {}; - std::vector line_ending_untracked {}; size_t last_layers_select_size {}; }; diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index 4b102a3f..2f6e80d2 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -521,7 +521,7 @@ void ekg::handler::input::update() { #endif ekg::reset_if_reach(this->input.ui_timing, 1000); - ekg::timing_t::second = this->input.ui_timing.elapsed_ticks; + ekg::timing_t::second = this->input.ui_timing.current_ticks; } void ekg::handler::input::insert_input_bind( diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 5e5c02fe..ae902f68 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -214,87 +214,146 @@ std::string ekg::utf8_substr(std::string_view string, uint64_t offset, uint64_t return ""; } -void ekg::utf8_split_new_line(std::string_view string, std::vector &utf8_read) { - if (string.empty()) { - return; +size_t ekg::utf8_split_endings( + std::string_view line, + std::vector &splitted +) { + if (line.empty()) { + return 0; } - /* if the first element is empty, then the decode must start from it */ - if (utf8_read.size() == 1 && utf8_read.at(0).empty()) { - utf8_read.clear(); + size_t new_lines_count {}; + size_t str_size {line.size()}; + size_t start_pos {}; + size_t pos {}; + + while ((pos = line.find('\n', start_pos)) != std::string::npos) { + std::string &split {splitted.emplace_back()}; + split = line.substr(start_pos, pos - start_pos); + if (split.back() == '\r') split.pop_back(); + start_pos = pos + 1; + new_lines_count++; } - - uint64_t index {}; - uint64_t start_index {}; - - while ((index = string.find('\n', start_index)) != std::string_view::npos) { - utf8_read.emplace_back( - string.substr( - start_index, - (index - start_index) - (index > 0 && string.at(index - 1) == '\r') - ) - ); - - start_index = index + 1; + + if (start_pos < str_size) { + splitted.emplace_back() = line.substr(start_pos, str_size); + new_lines_count++; } - if (!string.empty() && - !( - string = string.substr(start_index, string.find('\0', start_index)) - ).empty() - ) { - // `meow\n\0`, so the decode wont emplace a empty string. - utf8_read.emplace_back(string); - } + return new_lines_count; } -bool ekg::utf8_split( - std::vector &splitted, - const std::string &string, - char find_char +void ekg::text::swizzle( + size_t chunk_index, + size_t line_index, + std::vector &to_swizzle, + bool skip_first_line ) { - std::stringstream ss(string); - std::string find_string {}; + ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunk_index)}; + if (skip_first_line) { + chunk.at(line_index) = to_swizzle.at(0); + } - bool found_flag {}; + chunk.insert( + chunk.begin() + line_index + 1, + to_swizzle.begin() + skip_first_line, + to_swizzle.end() + ); - while (std::getline(ss, find_string, find_char)) { - splitted.push_back(find_string); - found_flag = true; + if (chunk.size() < this->lines_per_chunk_limit) { + return; } - return found_flag; -} + std::vector to_swizzle_chunk { + chunk.begin() + this->lines_per_chunk_limit, + chunk.end() + }; + + chunk.erase( + chunk.begin() + this->lines_per_chunk_limit, + chunk.end() + ); + + size_t to_swizzle_chunk_size {to_swizzle_chunk.size()}; + size_t newly_chunks { + to_swizzle_chunk_size / this->lines_per_chunk_limit + }; + + for (size_t jt {}; jt < newly_chunks; jt++) { + this->loaded_chunks.insert( + this->loaded_chunks.begin() + chunk_index + jt + 1, + ekg::io::chunk_t { + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 0)), + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 1)) + } + ); + } -std::string ekg::text::line_not_found {'\x1B'}; + size_t rest {to_swizzle_chunk_size - (this->lines_per_chunk_limit * newly_chunks)}; + if (rest > 0) { + this->loaded_chunks.insert( + this->loaded_chunks.begin() + chunk_index + newly_chunks + 1, + ekg::io::chunk_t { + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (newly_chunks + 0)), + to_swizzle_chunk.end() + } + ); + } +} -std::string &ekg::text::write(size_t index) { - size_t chunks_size {this->loaded_chunks.size()}; - size_t total_chars {}; - size_t previous_total_chars {}; +void ekg::text::set(size_t index, std::string_view line) { + size_t current_lines {}; + size_t previous_lines {}; size_t chunk_size {}; - this->was_audited = true; + std::vector ending_splitted {}; + ekg::utf8_split_endings(line, ending_splitted); - for (size_t it {}; it < chunks_size; it++) { + bool ok {}; + for (size_t it {}; it < this->loaded_chunks.size(); it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; - previous_total_chars = total_chars; - total_chars += (chunk_size = chunk.size()); + previous_lines = current_lines; + current_lines += (chunk_size = chunk.size()); - if (index < total_chars && (index - previous_total_chars) < chunk_size) { - return chunk.at(index - previous_total_chars); + if ( + !ok + && + index < current_lines + && + (index - previous_lines) < chunk_size + ) { + this->swizzle(it, (index - previous_lines), ending_splitted, true); + this->was_audited = true; + ok = true; } } - return ekg::text::line_not_found; + if (!ok) throw std::out_of_range("ekg::text::set -> lines length: " + std::to_string(current_lines)); + this->total_lines = current_lines; } -std::string ekg::text::read(size_t index) { - bool was_audited_before {this->was_audited}; - std::string &line {this->write(index)}; - this->was_audited = was_audited_before; - return line; +std::string ekg::text::at(size_t index) { + size_t chunks_size {this->loaded_chunks.size()}; + size_t current_lines {}; + size_t previous_lines {}; + size_t chunk_size {}; + + for (size_t it {}; it < chunks_size; it++) { + ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + previous_lines = current_lines; + current_lines += (chunk_size = chunk.size()); + + if ( + index < current_lines + && + (index - previous_lines) < chunk_size + ) { + return chunk.at(index - previous_lines); + } + } + + throw std::out_of_range("ekg::text::insert -> lines length: " + std::to_string(current_lines)); } void ekg::text::insert( @@ -308,88 +367,46 @@ void ekg::text::insert( } return; } - this->was_audited = true; - - bool ok {}; - size_t previous_total_chars {}; + size_t previous_lines {}; size_t chunk_size {}; - size_t current_total_chars {}; + size_t current_lines {}; + size_t to_insert_chunk_size {to_insert_chunk.size()}; - ekg::io::chunk_t to_swizzle_chunk {}; + ekg::io::chunk_t splitted {}; + for (size_t it {}; it < to_insert_chunk_size; it++) { + std::string &lines {to_insert_chunk.at(it)}; + ekg::utf8_split_endings(lines, splitted); + } for (size_t it {}; it < this->loaded_chunks.size(); it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; - previous_total_chars = this->total_chars; - current_total_chars += (chunk_size = chunk.size()); + previous_lines = current_lines; + current_lines += (chunk_size = chunk.size()); if ( - !ok - && - index < current_total_chars + index < current_lines && - (index - previous_total_chars) < chunk_size + (index - previous_lines) < chunk_size ) { - chunk.insert( - chunk.begin() + (index - previous_total_chars), - to_insert_chunk.begin(), - to_insert_chunk.end() - ); - - if (chunk.size() < this->lines_per_chunk_limit) { - ok = true; - continue; - } - - to_swizzle_chunk.insert( - to_swizzle_chunk.begin(), - chunk.begin() + this->lines_per_chunk_limit, - chunk.end() - ); - - chunk.erase( - chunk.begin() + this->lines_per_chunk_limit, - chunk.end() - ); - - size_t to_swizzle_chunk_size {to_swizzle_chunk.size()}; - size_t newly_chunks { - to_swizzle_chunk_size / this->lines_per_chunk_limit - }; - - for (size_t jt {}; jt < newly_chunks; jt++) { - this->loaded_chunks.insert( - this->loaded_chunks.begin() + it + jt + 1, - ekg::io::chunk_t { - to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 0)), - to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 1)) - } - ); - } - - size_t rest {to_swizzle_chunk_size - (this->lines_per_chunk_limit * newly_chunks)}; - if (rest > 0) { - this->loaded_chunks.insert( - this->loaded_chunks.begin() + it + newly_chunks + 1, - ekg::io::chunk_t { - to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (newly_chunks + 0)), - to_swizzle_chunk.end() - } - ); - } - - ok = true; + this->swizzle(it, (index - previous_lines), splitted, false); + this->was_audited = true; + this->should_count = true; + this->total_lines += splitted.size(); + return; } } + + throw std::out_of_range("ekg::text::insert -> lines length: " + std::to_string(current_lines)); } void ekg::text::insert( size_t index, - const std::string &line + std::string_view line ) { ekg::io::chunk_t chunk {}; - chunk.push_back(line); + chunk.push_back(std::string(line)); this->insert(index, chunk); } @@ -402,22 +419,20 @@ void ekg::text::erase( return; } - if (begin > this->total_chars) { - throw std::out_of_range("ekg::text::erase: " + std::to_string(begin) + ", " + std::to_string(this->total_chars)); + if (begin > this->total_lines) { + throw std::out_of_range("ekg::text::erase: " + std::to_string(begin) + ", " + std::to_string(this->total_lines)); return; } - end = ekg::min(begin + end, this->total_chars); - - this->should_count = true; - this->size(); + end = ekg::min(begin + end, this->total_lines); + this->total_lines -= end - begin; - this->total_chars -= end - begin; this->was_audited = true; + this->should_count = true; - size_t previous_total_chars {}; + size_t previous_lines {}; size_t chunk_size {}; - size_t total_chars {}; + size_t lines {}; size_t remains_lines {}; bool empty_chunk {}; @@ -426,12 +441,12 @@ void ekg::text::erase( for (size_t it {}; it < this->loaded_chunks.size(); it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; - previous_total_chars = total_chars; - total_chars += (chunk_size = chunk.size()); + previous_lines = lines; + lines += (chunk_size = chunk.size()); - if (begin < total_chars) { + if (begin < lines) { remains_lines = end - begin; - begin = begin - previous_total_chars; + begin = begin - previous_lines; while (remains_lines != 0) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; @@ -463,57 +478,51 @@ void ekg::text::erase( break; } } +} + +void ekg::text::push_back(std::string_view line) { + ekg::io::chunk_t splitted {}; + ekg::utf8_split_endings(line, splitted); this->should_count = true; - this->size(); -} + this->total_lines += splitted.size(); -void ekg::text::push_back(const std::string &line) { if (this->loaded_chunks.empty()) { - this->loaded_chunks.emplace_back().push_back(line); + this->loaded_chunks.emplace_back().emplace_back(); + this->swizzle(0, 0, splitted, true); return; } - - this->should_count = true; - this->size(); - size_t chunks_size {this->loaded_chunks.size()}; - ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunks_size - (chunks_size != 0))}; - if (chunk.size() >= this->lines_per_chunk_limit) { - this->loaded_chunks.emplace_back().push_back(line); - return; - } + ekg::io::chunk_t &last_chunk { + this->loaded_chunks.back() + }; - this->loaded_chunks.emplace_back().push_back(line); + this->swizzle( + this->loaded_chunks.size() - 1, + last_chunk.size() - !last_chunk.empty(), + splitted, + true + ); } -std::string &ekg::text::emplace_back() { - if (this->loaded_chunks.empty()) { - return this->loaded_chunks.emplace_back().emplace_back(); - } - - this->should_count = true; - this->size(); - - size_t chunks_size {this->loaded_chunks.size()}; - ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunks_size - (chunks_size != 0))}; - if (chunk.size() >= this->lines_per_chunk_limit) { - return this->loaded_chunks.emplace_back().emplace_back(); - } +std::vector &ekg::text::chunks_data() { + return this->loaded_chunks; +} - return chunk.emplace_back(); +size_t ekg::text::length_of_chunks() { + return this->loaded_chunks.size(); } -size_t ekg::text::lines() { - return 0; +size_t ekg::text::length_of_lines() { + return this->total_lines; } -size_t ekg::text::size() { +size_t ekg::text::length_of_chars() { if (this->should_count) { this->total_chars = 0; for (ekg::io::chunk_t &chunk : this->loaded_chunks) { for (std::string &lines : chunk) { - this->total_chars += lines.size(); + this->total_chars += ekg::utf8_length(lines); } } @@ -523,20 +532,10 @@ size_t ekg::text::size() { return this->total_chars; } -size_t ekg::text::chunks() { - return this->loaded_chunks.size(); -} - -void ekg::text::unset_audited() { - this->was_audited = false; -} - bool ekg::text::audited() { - this->was_audited = this->was_audited || (this->prev_total_chars != this->total_chars); - this->prev_total_chars = this->total_chars; - return this->was_audited; -} - -std::vector &ekg::text::data() { - return this->loaded_chunks; + this->was_audited = this->was_audited || (this->prev_lines != this->total_lines); + this->prev_lines = this->total_lines; + bool was_audited {this->was_audited}; + this->was_audited = false; + return was_audited; } diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 286dd8db..57f3761f 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -95,7 +95,8 @@ void ekg::ui::high_frequency( ekg::property_t &property, ekg::textbox_t &textbox ) { - + ekg::gui.ui.redraw = true; + property.widget.should_buffering = true; } void ekg::ui::pass( @@ -119,8 +120,6 @@ void ekg::ui::buffering( ekg::property_t &property, ekg::textbox_t &textbox ) { - textbox.text.unset_audited(); - ekg::rect_t &rect_abs { ekg::ui::get_abs_rect(property, textbox.rect) }; @@ -144,6 +143,10 @@ void ekg::ui::buffering( /* start of select */ for (ekg::textbox_t::select_draw_layer_t &layer : textbox.widget.layers_select) { + if (layer.is_ab_equals && ekg::timing_t::second > 500) { + continue; + } + ekg::draw::rect( layer.rect + rect_abs, layer.is_ab_equals ? textbox.color_scheme.text_cursor_foreground : textbox.color_scheme.text_select_foreground, @@ -178,6 +181,7 @@ void ekg::ui::buffering( size_t len {}; size_t text_len {}; + size_t utf8_text_len {}; uint8_t uc8 {}; char32_t c32 {}; @@ -194,7 +198,10 @@ void ekg::ui::buffering( textbox.widget.cursors = { { - 0, 13 + 5,5 + }, + { + 6,6 } }; @@ -203,18 +210,21 @@ void ekg::ui::buffering( bool is_ab_equals_selected {}; bool was_complete_line_selected {}; bool cursors_going_on {!textbox.widget.cursors.empty()}; + bool is_end_of_line_and_text {}; - size_t current_line_for_cursor_complete {}; - size_t difference {}; size_t lines {}; - size_t text_total_chars {textbox.text.size()}; + size_t current_line_for_cursor_complete {UINT64_MAX}; + size_t text_total_chars {textbox.text.length_of_chars()}; + size_t text_total_lines {textbox.text.length_of_lines()}; ekg::pixel_t line_wsize {}; ekg::textbox_t::cursor_t cursor {}; + std::vector chunks {textbox.text.chunks_data()}; - textbox.widget.layers_select.clear(); + ekg::io::dispatch(ekg::io::operation::high_frequency, property.at); - for (ekg::io::chunk_t &chunk : textbox.text.data()) { + textbox.widget.layers_select.clear(); + for (ekg::io::chunk_t &chunk : chunks) { for (std::string &line : chunk) { text_len = line.size(); for (size_t it {}; it < text_len; it++) { @@ -227,39 +237,6 @@ void ekg::ui::buffering( it ); - break_text = uc8 == '\n'; - if ( - break_text - || - ( - r_n_break_text = ( - uc8 == '\r' && it < text_len && line.at(it + 1) == '\n' - ) - ) - ) { - - ekg::io::glyph_t &glyph {draw_font.mapped_glyph[c32]}; - - it += static_cast(r_n_break_text); - hash += ekg_generate_hash(pos.y, c32, glyph.x); - - pos.y += draw_font.text_height; - pos.x = 0.0f; - - - textbox.widget.line_ending_untracked.push_back( - { - .indices = {len - it, len}, - .text = line.substr(len - it, len - (len - it)) - } - ); - - len++; - lines++; - - continue; - } - if (draw_font.ft_bool_kerning && ft_uint_previous) { switch (c32 < 256 || !emojis_font_face.was_loaded) { case true: { @@ -284,24 +261,24 @@ void ekg::ui::buffering( && ekg::ui::find_cursor(textbox, len, cursor) ) { - is_ab_equals_selected = cursor == len; - is_inline_selected = cursor >= len && cursor <= len && !is_ab_equals_selected; - + is_inline_selected = cursor >= len && cursor < len && !is_ab_equals_selected; is_complete_line_selected = false; if (is_inline_selected) { - difference = len - it; - if (difference >= cursor.index_a) { - is_complete_line_selected = true; - } - - difference = len + (text_len - it); - if (difference <= cursor.index_b) { - is_complete_line_selected = is_complete_line_selected; + if ( + ( + (len - it) >= cursor.index_a + ) + && + ( + (len + (text_len - it)) <= cursor.index_b + ) + ) { + is_inline_selected = false; } - is_inline_selected = !is_complete_line_selected; + is_complete_line_selected = !is_inline_selected; } if (is_ab_equals_selected) { @@ -320,30 +297,32 @@ void ekg::ui::buffering( textbox.widget.layers_select.push_back({false, cursor.rect}); } - if (is_complete_line_selected && current_line_for_cursor_complete != lines) { - line_wsize = 0.0f; - current_line_for_cursor_complete = lines; - was_complete_line_selected = true; - } - - if (is_complete_line_selected) { - line_wsize += glyph.wsize; - } - if ( was_complete_line_selected && - (current_line_for_cursor_complete != lines || lines + 1 == text_total_chars) - && - !is_complete_line_selected + ( + (current_line_for_cursor_complete != lines) + || + (is_end_of_line_and_text = lines + 1 == text_total_lines && it + 1 == text_len) + ) ) { cursor.rect.x = 0.0f; - cursor.rect.y = pos.y; - cursor.rect.w = line_wsize; + cursor.rect.y = pos.y - (draw_font.text_height * !is_end_of_line_and_text); + cursor.rect.w = line_wsize + (glyph.wsize * is_end_of_line_and_text); cursor.rect.h = draw_font.text_height; textbox.widget.layers_select.push_back({false, cursor.rect}); was_complete_line_selected = false; } + + if (is_complete_line_selected && current_line_for_cursor_complete != lines) { + line_wsize = 0.0f; + current_line_for_cursor_complete = lines; + was_complete_line_selected = true; + } + + if (is_complete_line_selected) { + line_wsize += glyph.wsize; + } } if (!glyph.was_sampled) { @@ -412,6 +391,10 @@ void ekg::ui::buffering( hash += ekg_generate_hash(pos.x, c32, glyph.x); len++; } + + pos.x = 0.0f; + pos.y += draw_font.text_height; + lines++; } } @@ -430,6 +413,8 @@ void ekg::ui::buffering( ekg::at_t::not_found ); + /* start of final tweaks */ + /** * Cursors should be rendered backward from text-render. * Textbox first try-render render the cursors, then at end of renderer-segment @@ -446,23 +431,7 @@ void ekg::ui::buffering( ekg::gui.ui.redraw = true; } - if (!textbox.widget.line_ending_untracked.empty()) { - for (ekg::textbox_t::untracked_line_ending_t &untracked_line_ending : textbox.widget.line_ending_untracked) { - textbox.text.erase( - untracked_line_ending.indices.x, - untracked_line_ending.indices.y - ); - - textbox.text.insert( - untracked_line_ending.indices.x, - untracked_line_ending.text - ); - } - - property.widget.should_buffering = true; - textbox.widget.line_ending_untracked.clear(); - ekg::gui.ui.redraw = true; - } + /* end of pass */ ekg_draw_allocator_pass(); } From 206c393ea9c22ae0896da6d6aa5eeda5a6a0a828 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 3 Aug 2025 16:28:40 -0300 Subject: [PATCH 07/61] [fix] scrollbar embedded impl was conflicting other-render passes --- include/ekg/io/utf.hpp | 2 +- include/ekg/ui/textbox/widget.hpp | 7 +++ src/io/utf.cpp | 6 +- src/ui/scrollbar/widget.cpp | 20 +++---- src/ui/textbox/widget.cpp | 99 +++++++++++++++++++++++++++---- 5 files changed, 108 insertions(+), 26 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index eafca65e..7dc0e5b9 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -156,7 +156,7 @@ namespace ekg { void insert( size_t index, - ekg::io::chunk_t &to_insert_chunk + const ekg::io::chunk_t &to_insert_chunk ); void insert( diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 1833ad5b..c45e2343 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -34,6 +34,13 @@ namespace ekg::ui { ekg::textbox_t::cursor_t &cursor_out ); + bool find_index_by_interact( + ekg::property_t &property, + ekg::textbox_t &textbox, + ekg::vec2_t &interact, + size_t &index + ); + void reload( ekg::property_t &property, ekg::textbox_t &textbox diff --git a/src/io/utf.cpp b/src/io/utf.cpp index ae902f68..53ff73b9 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -353,12 +353,12 @@ std::string ekg::text::at(size_t index) { } } - throw std::out_of_range("ekg::text::insert -> lines length: " + std::to_string(current_lines)); + throw std::out_of_range("ekg::text::at -> lines length: " + std::to_string(current_lines)); } void ekg::text::insert( size_t index, - ekg::io::chunk_t &to_insert_chunk + const ekg::io::chunk_t &to_insert_chunk ) { if (this->loaded_chunks.empty()) { if (index == 0) { @@ -375,7 +375,7 @@ void ekg::text::insert( ekg::io::chunk_t splitted {}; for (size_t it {}; it < to_insert_chunk_size; it++) { - std::string &lines {to_insert_chunk.at(it)}; + const std::string &lines {to_insert_chunk.at(it)}; ekg::utf8_split_endings(lines, splitted); } diff --git a/src/ui/scrollbar/widget.cpp b/src/ui/scrollbar/widget.cpp index 0c261932..f70e1f67 100644 --- a/src/ui/scrollbar/widget.cpp +++ b/src/ui/scrollbar/widget.cpp @@ -638,13 +638,6 @@ void ekg::ui::buffering( ekg::rect_t &rect_parent, ekg::rect_t &rect_parent_scissor ) { - ekg_draw_allocator_assert_scissor( - property.widget.rect_scissor, - rect_parent, - rect_parent_scissor, - ekg::always_parented - ); - scrollbar.widget.rect_horizontal.w = 0.0f; scrollbar.widget.rect_vertical.h = 0.0f; @@ -656,7 +649,7 @@ void ekg::ui::buffering( && !property.scroll.is_enabled.y ) { - ekg_draw_allocator_pass(); + return; } ekg::rect_t bar {}; @@ -835,8 +828,6 @@ void ekg::ui::buffering( ekg::draw::mode::outline, scrollbar.layers[ekg::layer::outline] ); - - ekg_draw_allocator_pass(); } void ekg::ui::buffering( @@ -847,12 +838,21 @@ void ekg::ui::buffering( ekg::query(property.parent_at) }; + ekg_draw_allocator_assert_scissor( + property.widget.rect_scissor, + property_parent.widget.rect, + property_parent.widget.rect_scissor, + ekg::always_parented + ); + ekg::ui::buffering( property, scrollbar, property_parent.widget.rect, property_parent.widget.rect_scissor ); + + ekg_draw_allocator_pass(); } void ekg::ui::unmap( diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 57f3761f..c48f8b20 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#include "ekg/ui/scrollbar/widget.hpp" #include "ekg/ui/textbox/widget.hpp" #include "ekg/layout/docknize.hpp" #include "ekg/io/log.hpp" @@ -46,6 +47,17 @@ bool ekg::ui::find_cursor( return false; } +bool ekg::ui::find_index_by_interact( + ekg::property_t &property, + ekg::textbox_t &textbox, + ekg::vec2_t &interact, + size_t &index +) { + ekg::rect_t &rect_abs { + ekg::ui::get_abs_rect(property, textbox.rect) + }; +} + void ekg::ui::reload( ekg::property_t &property, ekg::textbox_t &textbox @@ -80,6 +92,56 @@ void ekg::ui::event( ) { switch (stage) { default: { + ekg::input_info_t &input {ekg::p_core->handler_input.input}; + ekg::vec2_t interact {static_cast>(input.interact)}; + + bool should_enable_high_frequency { + property.states.is_hovering + || + property.states.is_focused + }; + + if ( + !property.states.is_focused + && + property.states.is_hovering + && + ekg::fire("textbox-focus") + ) { + should_enable_high_frequency = true; + property.states.is_focused = true; + + if (textbox.widget.cursors.empty()) { + textbox.widget.cursors = { + { + 2, 2 + } + }; + } + } + + if ( + property.states.is_focused + && + !property.states.is_hovering + && + input.was_pressed + && + !input.was_typed + ) { + property.states.is_focused = false; + should_enable_high_frequency = false; + } + + if ( + should_enable_high_frequency + ) { + ekg::io::dispatch( + ekg::io::operation::high_frequency, + property.at + ); + } + break; } case ekg::io::stage::pre: @@ -95,8 +157,23 @@ void ekg::ui::high_frequency( ekg::property_t &property, ekg::textbox_t &textbox ) { + ekg::rect_t &rect_abs { + ekg::ui::get_abs_rect(property, textbox.rect) + }; + + ekg::ui::high_frequency( + property, + textbox.widget.scrollbar, + rect_abs + ); + ekg::gui.ui.redraw = true; property.widget.should_buffering = true; + property.widget.is_high_frequency = ( + property.states.is_focused + || + property.widget.is_high_frequency + ); } void ekg::ui::pass( @@ -196,15 +273,6 @@ void ekg::ui::buffering( ekg::io::font_face_t &emojis_font_face {draw_font.faces[ekg::io::font_face_type::emojis]}; ekg::io::font_face_t &kanjis_font_face {draw_font.faces[ekg::io::font_face_type::kanjis]}; - textbox.widget.cursors = { - { - 5,5 - }, - { - 6,6 - } - }; - bool is_inline_selected {}; bool is_complete_line_selected {}; bool is_ab_equals_selected {}; @@ -221,8 +289,6 @@ void ekg::ui::buffering( ekg::textbox_t::cursor_t cursor {}; std::vector chunks {textbox.text.chunks_data()}; - ekg::io::dispatch(ekg::io::operation::high_frequency, property.at); - textbox.widget.layers_select.clear(); for (ekg::io::chunk_t &chunk : chunks) { for (std::string &line : chunk) { @@ -261,7 +327,7 @@ void ekg::ui::buffering( && ekg::ui::find_cursor(textbox, len, cursor) ) { - is_ab_equals_selected = cursor == len; + is_ab_equals_selected = cursor == len && property.states.is_focused; is_inline_selected = cursor >= len && cursor < len && !is_ab_equals_selected; is_complete_line_selected = false; @@ -404,6 +470,15 @@ void ekg::ui::buffering( ekg::p_core->draw_allocator.bind_texture(draw_font.atlas_texture_sampler_at); ekg::p_core->draw_allocator.dispatch(); + /* start of scrollbar */ + + ekg::ui::buffering( + property, + textbox.widget.scrollbar, + rect_abs, + ekg::query(property.parent_at).widget.rect_scissor + ); + /* start of outline */ ekg::draw::rect( From e08c101e461dfb487df193a0a0da497758f0521d Mon Sep 17 00:00:00 2001 From: mrsrina Date: Thu, 7 Aug 2025 01:29:26 -0300 Subject: [PATCH 08/61] [fix] performance overhead --- include/ekg/io/memory.hpp | 3 +- include/ekg/io/utf.hpp | 3 +- include/ekg/ui/textbox/textbox.hpp | 29 +++-- include/ekg/ui/textbox/widget.hpp | 2 +- src/io/utf.cpp | 8 +- src/ui/scrollbar/widget.cpp | 3 +- src/ui/textbox/widget.cpp | 195 ++++++++++++++++++++++++----- 7 files changed, 191 insertions(+), 52 deletions(-) diff --git a/include/ekg/io/memory.hpp b/include/ekg/io/memory.hpp index 4fff11c6..171a4082 100644 --- a/include/ekg/io/memory.hpp +++ b/include/ekg/io/memory.hpp @@ -130,7 +130,7 @@ namespace ekg { descriptor.at.unique_id = this->highest_unique_id++; descriptor.at.flags = t::type; - descriptor.at.index = index; + descriptor.at.index = index; return descriptor; } @@ -149,6 +149,7 @@ namespace ekg { for (size_t it {}; it < size; it++) { t &descriptor {this->loaded.at(it)}; descriptor.at.index = it; + if (descriptor.at.unique_id == at.unique_id) { at.index = it; return descriptor; diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 7dc0e5b9..96580ab3 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -135,7 +135,7 @@ namespace ekg { class text { protected: std::vector loaded_chunks {}; - size_t lines_per_chunk_limit {10}; + size_t lines_per_chunk_limit {100000}; size_t total_lines {}; size_t total_chars {}; size_t prev_lines {}; @@ -177,6 +177,7 @@ namespace ekg { size_t length_of_chars(); bool audited(); + void unset_audited(); }; } diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 88d7c1b4..e9d4d01b 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -28,6 +28,7 @@ #include "ekg/io/descriptor.hpp" #include "ekg/io/utf.hpp" #include "ekg/io/font.hpp" +#include "ekg/ui/property.hpp" namespace ekg { struct textbox_color_scheme_t { @@ -39,6 +40,7 @@ namespace ekg { ekg::rgba_t text_cursor_foreground {}; ekg::pixel_thickness_t cursor_thickness {2}; bool caret_cursor {}; + ekg::pixel_thickness_t gutter_margin {2}; }; struct textbox_t { @@ -51,38 +53,39 @@ namespace ekg { struct cursor_t { public: - size_t index_a {}; - size_t index_b {}; + ekg::vec2_t a {}; + ekg::vec2_t b {}; ekg::rect_t rect {}; public: - bool operator == (size_t index) { - return index == this->index_a && index == this->index_b; + bool operator == (const ekg::vec2_t &index) { + return index.x == this->a.x && index.y == this->a.y && index.x == this->b.x && index.y == this->b.y; } - bool operator > (size_t index) { - return index > this->index_a; + bool operator > (const ekg::vec2_t &index) { + return (index.x > this->a.x && index.y == this->a.y) || (index.y > this->a.y); } - bool operator >= (size_t index) { - return index >= this->index_a; + bool operator >= (const ekg::vec2_t &index) { + return (index.x >= this->a.x && index.y == this->a.y) || (index.y > this->a.y); } - bool operator < (size_t index) { - return index < this->index_b; + bool operator < (const ekg::vec2_t &index) { + return (index.x < this->b.x && index.y == this->b.y) || (index.y < this->b.y); } - bool operator <= (size_t index) { - return index <= this->index_b; + bool operator <= (const ekg::vec2_t &index) { + return (index.x <= this->b.x && index.y == this->b.y) || (index.y < this->b.y); } bool operator == (const ekg::textbox_t::cursor_t &cursor) { - return this->index_a == cursor.index_a && this->index_b == cursor.index_b; + return *this == cursor.a && *this == cursor.b; } }; struct widget_t { public: ekg::rect_t rect_text_size {}; + ekg::property_t scrollbar_property {}; ekg::scrollbar_t scrollbar {}; std::vector cursors {}; std::vector layers_select {}; diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index c45e2343..7f4f7f1f 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -30,7 +30,7 @@ namespace ekg::ui { bool find_cursor( ekg::textbox_t &textbox, - size_t len, + ekg::vec2_t &index, ekg::textbox_t::cursor_t &cursor_out ); diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 53ff73b9..0f0c6292 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -501,7 +501,7 @@ void ekg::text::push_back(std::string_view line) { this->loaded_chunks.size() - 1, last_chunk.size() - !last_chunk.empty(), splitted, - true + false ); } @@ -535,7 +535,9 @@ size_t ekg::text::length_of_chars() { bool ekg::text::audited() { this->was_audited = this->was_audited || (this->prev_lines != this->total_lines); this->prev_lines = this->total_lines; - bool was_audited {this->was_audited}; + return this->was_audited; +} + +void ekg::text::unset_audited() { this->was_audited = false; - return was_audited; } diff --git a/src/ui/scrollbar/widget.cpp b/src/ui/scrollbar/widget.cpp index f70e1f67..9f31df0d 100644 --- a/src/ui/scrollbar/widget.cpp +++ b/src/ui/scrollbar/widget.cpp @@ -187,7 +187,6 @@ void ekg::ui::reload( parent.scroll.nearest_scroll_bar_thickness = scrollbar.color_scheme.bar_thickness; } - void ekg::ui::event( ekg::property_t &property, ekg::scrollbar_t &scrollbar, @@ -207,7 +206,7 @@ void ekg::ui::event( input.has_motion || input.was_wheel - ) { + ) { bool is_visible { ekg::rect_collide_vec2(property.widget.rect_scissor, interact) }; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index c48f8b20..58a0246a 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -31,14 +31,15 @@ #include "ekg/core/context.hpp" #include "ekg/core/runtime.hpp" #include "ekg/core/pools.hpp" +#include "ekg/math/floating_point.hpp" bool ekg::ui::find_cursor( ekg::textbox_t &textbox, - size_t len, + ekg::vec2_t &index, ekg::textbox_t::cursor_t &cursor_out ) { for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor >= len && cursor <= len) { + if (cursor >= index && cursor <= index) { cursor_out = cursor; return true; } @@ -83,6 +84,38 @@ void ekg::ui::reload( textbox.rect.scaled_height = ekg::max(1, textbox.rect.scaled_height); textbox.rect.h = aligned_dimension.h * textbox.rect.scaled_height; + + size_t highest_size {}; + std::vector &chunks {textbox.text.chunks_data()}; + + if (textbox.text.audited()) { + for (ekg::io::chunk_t &chunk : chunks) { + for (std::string &line : chunk) { + highest_size = ekg::max(line.size(), highest_size); + } + }; + + property.widget.should_buffering = true; + } + + ekg::rect_t &rect_abs { + ekg::ui::get_abs_rect(property, textbox.rect) + }; + + textbox.widget.scrollbar.rect.x = rect_abs.x; + textbox.widget.scrollbar.rect.y = rect_abs.y; + textbox.widget.scrollbar.rect.w = highest_size * draw_font.text_height; + textbox.widget.scrollbar.acceleration = {draw_font.text_height, draw_font.text_height}; + + textbox.widget.scrollbar.color_scheme = ekg::p_core->handler_theme.get_current_theme().scrollbar_color_scheme; + + ekg::ui::reload( + textbox.widget.scrollbar_property, + textbox.widget.scrollbar, + rect_abs, + property.children, + false + ); } void ekg::ui::event( @@ -90,6 +123,23 @@ void ekg::ui::event( ekg::textbox_t &textbox, const ekg::io::stage &stage ) { + ekg::property_t &scrollbar_property { + textbox.widget.scrollbar_property + }; + + scrollbar_property.widget.rect_scissor = property.widget.rect_scissor; + + ekg::ui::event( + scrollbar_property, + textbox.widget.scrollbar, + stage, + ekg::ui::get_abs_rect(property, textbox.rect) + ); + + property.widget.should_buffering = scrollbar_property.widget.should_buffering; + property.states.is_absolute = scrollbar_property.states.is_absolute; + property.scroll = scrollbar_property.scroll; + switch (stage) { default: { ekg::input_info_t &input {ekg::p_core->handler_input.input}; @@ -99,6 +149,8 @@ void ekg::ui::event( property.states.is_hovering || property.states.is_focused + || + scrollbar_property.widget.is_high_frequency }; if ( @@ -112,11 +164,7 @@ void ekg::ui::event( property.states.is_focused = true; if (textbox.widget.cursors.empty()) { - textbox.widget.cursors = { - { - 2, 2 - } - }; + } } @@ -162,7 +210,7 @@ void ekg::ui::high_frequency( }; ekg::ui::high_frequency( - property, + textbox.widget.scrollbar_property, textbox.widget.scrollbar, rect_abs ); @@ -172,7 +220,7 @@ void ekg::ui::high_frequency( property.widget.is_high_frequency = ( property.states.is_focused || - property.widget.is_high_frequency + textbox.widget.scrollbar_property.widget.is_high_frequency ); } @@ -208,6 +256,8 @@ void ekg::ui::buffering( ekg::always_parented ); + textbox.text.unset_audited(); + /* start of background */ ekg::draw::rect( @@ -219,13 +269,15 @@ void ekg::ui::buffering( /* start of select */ + ekg::vec2_t scrollable_pos {}; + for (ekg::textbox_t::select_draw_layer_t &layer : textbox.widget.layers_select) { if (layer.is_ab_equals && ekg::timing_t::second > 500) { continue; } ekg::draw::rect( - layer.rect + rect_abs, + layer.rect + rect_abs + textbox.widget.scrollbar_property.scroll.position, layer.is_ab_equals ? textbox.color_scheme.text_cursor_foreground : textbox.color_scheme.text_select_foreground, ekg::draw::mode::fill, ekg::at_t::not_found @@ -242,8 +294,13 @@ void ekg::ui::buffering( ekg::p_core->draw_allocator.bind_current_data() }; - data.buffer[0] = static_cast(static_cast(rect_abs.x)); - data.buffer[1] = static_cast(static_cast(rect_abs.y - draw_font.offset_text_height * 2.0f)); + ekg::vec2_t pos { + static_cast(static_cast(rect_abs.x + textbox.color_scheme.gutter_margin)) + textbox.widget.scrollbar_property.scroll.position.x, + floorf(static_cast(static_cast(rect_abs.y - draw_font.offset_text_height * 2))) + }; + + data.buffer[0] = pos.x; + data.buffer[1] = pos.y; data.buffer[2] = static_cast(-draw_font.non_swizzlable_range); data.buffer[4] = static_cast(textbox.color_scheme.text_foreground.x / 255); @@ -252,11 +309,10 @@ void ekg::ui::buffering( data.buffer[7] = static_cast(textbox.color_scheme.text_foreground.w / 255); ekg::hash_t hash {}; - ekg::vec2_t pos {}; ekg::rect_t vertex {}; ekg::rect_t uv {}; + ekg::rect_t rendered_size {}; - size_t len {}; size_t text_len {}; size_t utf8_text_len {}; uint8_t uc8 {}; @@ -279,19 +335,83 @@ void ekg::ui::buffering( bool was_complete_line_selected {}; bool cursors_going_on {!textbox.widget.cursors.empty()}; bool is_end_of_line_and_text {}; + bool oka_found_visual_index {}; + bool get_out {}; - size_t lines {}; size_t current_line_for_cursor_complete {UINT64_MAX}; size_t text_total_chars {textbox.text.length_of_chars()}; size_t text_total_lines {textbox.text.length_of_lines()}; + size_t ij {}; + size_t addition_chunk_index {}; + size_t chunk_size {}; + ekg::vec2_t index {}; ekg::pixel_t line_wsize {}; ekg::textbox_t::cursor_t cursor {}; - std::vector chunks {textbox.text.chunks_data()}; + std::vector &chunks {textbox.text.chunks_data()}; + + textbox.widget.scrollbar.rect.h = text_total_lines * draw_font.text_height; + + size_t visual_target_line_index { + static_cast( + static_cast( + -( + textbox.widget.scrollbar_property.scroll.position.y + / + textbox.widget.scrollbar.rect.h + ) + ) + * + text_total_lines + ) + }; + + /** + * This technique prevent from floating-point precision loss inside GPU. + * + * Think that you are scrolling a 1milion text, if you subtract the scroll-position (1 milion) + * with the text-content, for do scrolling effect, this will send 1000000.0f to the GPU. + * Some GPUs can not handle high floating numbers, because matrix projection calculate + * is too heavy to a large float32. + * + * This method send to the GPU: from 0.0 to textbox-height. The scrolling effect happens but + * without sending 1 milion of f32 to the GPU. The effect is emulated by scrolling only the first + * line: from 0.0 to negative text-height; so the effect occurs without the needs of stupid + * scrolling everything. + * + * This technique works because the line height is fixed, ultimately, soon, should be re-worked + * to support differents text-heights at same time, also, for widgets scrolling (I do not think + * someone can write a GUI context with +5000000 heights from widgets without pages). + * + * - Rina - 11:39; 08/06/2025 + **/ + float visible_text_height {static_cast(visual_target_line_index * draw_font.text_height)}; + pos.y = textbox.widget.scrollbar_property.scroll.position.y + visible_text_height; + pos.y = static_cast(static_cast(floorf(pos.y))); + pos.x = 0.0f; textbox.widget.layers_select.clear(); + for (ekg::io::chunk_t &chunk : chunks) { - for (std::string &line : chunk) { + chunk_size = chunk.size(); + if (addition_chunk_index + chunk_size < visual_target_line_index) { + pos.x = 0.0f; + index.y += chunk_size; + addition_chunk_index += chunk_size; + continue; + } + + ij = 0; + if (!oka_found_visual_index) { + ij = visual_target_line_index - addition_chunk_index; + index.y += ij; + oka_found_visual_index = true; + } + + addition_chunk_index += chunk_size; + for (;ij < chunk_size; ij++) { + std::string &line {chunk.at(ij)}; + index.x = 0; text_len = line.size(); for (size_t it {}; it < text_len; it++) { uc8 = static_cast(line.at(it)); @@ -325,25 +445,24 @@ void ekg::ui::buffering( if ( cursors_going_on && - ekg::ui::find_cursor(textbox, len, cursor) + ekg::ui::find_cursor(textbox, index, cursor) ) { - is_ab_equals_selected = cursor == len && property.states.is_focused; - is_inline_selected = cursor >= len && cursor < len && !is_ab_equals_selected; + is_ab_equals_selected = property.states.is_focused && cursor == index; + is_inline_selected = cursor >= index && cursor < index && !is_ab_equals_selected; is_complete_line_selected = false; if (is_inline_selected) { if ( ( - (len - it) >= cursor.index_a + cursor >= ekg::vec2_t(0, index.y) ) && ( - (len + (text_len - it)) <= cursor.index_b + cursor <= ekg::vec2_t(text_len, index.y) ) ) { is_inline_selected = false; } - is_complete_line_selected = !is_inline_selected; } @@ -367,9 +486,9 @@ void ekg::ui::buffering( was_complete_line_selected && ( - (current_line_for_cursor_complete != lines) + (current_line_for_cursor_complete != index.y) || - (is_end_of_line_and_text = lines + 1 == text_total_lines && it + 1 == text_len) + (is_end_of_line_and_text = index.y + 1 == text_total_lines && it + 1 == text_len) ) ) { cursor.rect.x = 0.0f; @@ -377,12 +496,12 @@ void ekg::ui::buffering( cursor.rect.w = line_wsize + (glyph.wsize * is_end_of_line_and_text); cursor.rect.h = draw_font.text_height; textbox.widget.layers_select.push_back({false, cursor.rect}); - was_complete_line_selected = false; + was_complete_line_selected = false; } - if (is_complete_line_selected && current_line_for_cursor_complete != lines) { + if (is_complete_line_selected && current_line_for_cursor_complete != index.y) { line_wsize = 0.0f; - current_line_for_cursor_complete = lines; + current_line_for_cursor_complete = index.y; was_complete_line_selected = true; } @@ -455,12 +574,23 @@ void ekg::ui::buffering( * Peek `ekg/io/memory.hpp` for better hash definition and purpose. **/ hash += ekg_generate_hash(pos.x, c32, glyph.x); - len++; + index.x++; } pos.x = 0.0f; pos.y += draw_font.text_height; - lines++; + rendered_size.y += draw_font.text_height; + index.y++; + hash += pos.y * 32; + + if (rendered_size.y > rect_abs.h) { + get_out = true; + break; + } + } + + if (get_out) { + break; } } @@ -472,8 +602,11 @@ void ekg::ui::buffering( /* start of scrollbar */ + textbox.widget.scrollbar.rect.x = rect_abs.x; + textbox.widget.scrollbar.rect.y = rect_abs.y; + ekg::ui::buffering( - property, + textbox.widget.scrollbar_property, textbox.widget.scrollbar, rect_abs, ekg::query(property.parent_at).widget.rect_scissor From 4fea3081763027f5b99a53d45b05bb3ab4282f84 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Fri, 8 Aug 2025 20:06:43 -0300 Subject: [PATCH 09/61] [fix] events not orderning absolute priority --- src/core/runtime.cpp | 18 +++++++++++++----- src/ui/scrollbar/widget.cpp | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/core/runtime.cpp b/src/core/runtime.cpp index 6917921c..02c870b3 100644 --- a/src/core/runtime.cpp +++ b/src/core/runtime.cpp @@ -321,11 +321,19 @@ void ekg::core::poll_event() { ekg::query(focused_at) }; - focused_property != ekg::property_t::not_found - && (focused_property.states.is_hovering = false); - - focused_at = at; - first_absolute = false; + if (first_absolute) { + first_absolute = ( + focused_property != ekg::property_t::not_found + && property.abs_parent_at == focused_property.abs_parent_at + ); + } + + if (!first_absolute) { + focused_property != ekg::property_t::not_found + && (focused_property.states.is_hovering = false); + + focused_at = at; + } } if (property.states.is_absolute && !first_absolute) { diff --git a/src/ui/scrollbar/widget.cpp b/src/ui/scrollbar/widget.cpp index 9f31df0d..a8e05f9f 100644 --- a/src/ui/scrollbar/widget.cpp +++ b/src/ui/scrollbar/widget.cpp @@ -237,6 +237,7 @@ void ekg::ui::event( ekg::rect_precise_collide_vec2(v_bar, interact) ); + property.states.is_focused = is_visible; property.states.is_hovering = ( property.states.is_active || @@ -535,6 +536,22 @@ void ekg::ui::event( parent.widget.rect ); + if (stage == ekg::io::stage::pre) { + /* + property.states.is_hovering = ( + !property.states.is_active + && + ( + property.states.is_focused + || + scrollbar.widget.states_horizontal_bar.is_hovering + || + scrollbar.widget.states_vertical_bar.is_hovering + ) + ); + */ + } + if ( ( property.scroll.is_scrolling.x From 5e0bcb052dfa25185e46735b8ca6b76d050f62f1 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sat, 9 Aug 2025 21:06:25 -0300 Subject: [PATCH 10/61] [fix] interact by mouse --- include/ekg/handler/input.hpp | 2 +- include/ekg/math/geometry.hpp | 8 + include/ekg/ui/textbox/textbox.hpp | 17 ++ include/ekg/ui/textbox/widget.hpp | 2 +- src/handler/input.cpp | 2 +- src/ui/button/widget.cpp | 2 +- src/ui/frame/widget.cpp | 4 +- src/ui/scrollbar/widget.cpp | 8 +- src/ui/textbox/widget.cpp | 287 ++++++++++++++++++++++++----- 9 files changed, 281 insertions(+), 51 deletions(-) diff --git a/include/ekg/handler/input.hpp b/include/ekg/handler/input.hpp index 80606034..a3b53692 100644 --- a/include/ekg/handler/input.hpp +++ b/include/ekg/handler/input.hpp @@ -89,7 +89,7 @@ namespace ekg { namespace ekg { ekg::input_info_t &input(); - bool fire(std::string_view tag); + bool fired(std::string_view tag); bool input(std::string_view input); void bind(std::string_view tag, std::string_view input); void bind(std::string_view tag, std::vector inputs); diff --git a/include/ekg/math/geometry.hpp b/include/ekg/math/geometry.hpp index d0d0b358..3f832419 100644 --- a/include/ekg/math/geometry.hpp +++ b/include/ekg/math/geometry.hpp @@ -621,6 +621,14 @@ namespace ekg { template using rgba_t =ekg::vec4_t; + template + constexpr t arithmetic_normalize( + t x, + t len + ) { + return x / len; + } + void ortho( float *p_mat4x4, float left, diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index e9d4d01b..5bb7bd66 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -61,6 +61,10 @@ namespace ekg { return index.x == this->a.x && index.y == this->a.y && index.x == this->b.x && index.y == this->b.y; } + bool operator != (const ekg::vec2_t &index) { + return !(*this == index); + } + bool operator > (const ekg::vec2_t &index) { return (index.x > this->a.x && index.y == this->a.y) || (index.y > this->a.y); } @@ -89,7 +93,20 @@ namespace ekg { ekg::scrollbar_t scrollbar {}; std::vector cursors {}; std::vector layers_select {}; + size_t last_layers_select_size {}; + size_t view_line_index {UINT64_MAX}; + size_t view_chunk_index {UINT64_MAX}; + size_t view_chunk_line_index {UINT64_MAX}; + + ekg::vec2_t picked_left {UINT64_MAX, UINT64_MAX}; + ekg::vec2_t picked_right {UINT64_MAX, UINT64_MAX}; + ekg::vec2_t first_pick_pos {}; + size_t current_cursor_index {UINT64_MAX}; + + ekg::timing_t cursor_timing {}; + bool set_cursor_static {}; + bool unset_cursor_static {}; }; static constexpr ekg::type type {ekg::type::textbox}; diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 7f4f7f1f..45390c84 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -38,7 +38,7 @@ namespace ekg::ui { ekg::property_t &property, ekg::textbox_t &textbox, ekg::vec2_t &interact, - size_t &index + ekg::vec2_t &index ); void reload( diff --git a/src/handler/input.cpp b/src/handler/input.cpp index 69b02f25..de162cfc 100644 --- a/src/handler/input.cpp +++ b/src/handler/input.cpp @@ -5,7 +5,7 @@ ekg::input_info_t &ekg::input() { return ekg::p_core->handler_input.input; } -bool ekg::fire(std::string_view tag) { +bool ekg::fired(std::string_view tag) { return ekg::p_core->handler_input.get_input_bind_state(tag); } diff --git a/src/ui/button/widget.cpp b/src/ui/button/widget.cpp index 298c624f..2503d923 100644 --- a/src/ui/button/widget.cpp +++ b/src/ui/button/widget.cpp @@ -184,7 +184,7 @@ void ekg::ui::event( && check.states.is_highlight && - ekg::fire("button-active") + ekg::fired("button-active") ) { ekg_action( check.actions, diff --git a/src/ui/frame/widget.cpp b/src/ui/frame/widget.cpp index 741b8b99..d963b3df 100644 --- a/src/ui/frame/widget.cpp +++ b/src/ui/frame/widget.cpp @@ -96,9 +96,9 @@ void ekg::ui::event( input.was_pressed && ( - (frame.drag != ekg::dock::none && ekg::fire("frame-drag")) + (frame.drag != ekg::dock::none && ekg::fired("frame-drag")) || - (frame.resize != ekg::dock::none && ekg::fire("frame-resize")) + (frame.resize != ekg::dock::none && ekg::fired("frame-resize")) ) ) { diff --git a/src/ui/scrollbar/widget.cpp b/src/ui/scrollbar/widget.cpp index a8e05f9f..d5ae4dae 100644 --- a/src/ui/scrollbar/widget.cpp +++ b/src/ui/scrollbar/widget.cpp @@ -216,7 +216,7 @@ void ekg::ui::event( && (property.scroll.is_enabled.x || property.scroll.is_enabled.y) && - (ekg::fire("scrollbar-scroll") || ekg::fire("scrollbar-scroll-horizontal")) + (ekg::fired("scrollbar-scroll") || ekg::fired("scrollbar-scroll-horizontal")) ); ekg::rect_t h_bar {scrollbar.widget.rect_horizontal}; @@ -271,7 +271,7 @@ void ekg::ui::event( ); ekg::input_info_t &input {ekg::p_core->handler_input.input}; - bool is_scroll_fired {ekg::fire("scrollbar-scroll")}; + bool is_scroll_fired {ekg::fired("scrollbar-scroll")}; property.scroll.is_scrolling.x = false; property.scroll.is_scrolling.y = false; @@ -296,7 +296,7 @@ void ekg::ui::event( } #else bool is_scroll_horizontal_fired { - ekg::fire("scrollbar-scroll-horizontal") + ekg::fired("scrollbar-scroll-horizontal") }; if ( @@ -351,7 +351,7 @@ void ekg::ui::event( && input.was_pressed && - ekg::fire("scrollbar-drag") + ekg::fired("scrollbar-drag") ) { ekg::rect_t h_bar {scrollbar.widget.rect_horizontal}; h_bar.x += rect_parent.x; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 58a0246a..a45113ce 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -38,6 +38,10 @@ bool ekg::ui::find_cursor( ekg::vec2_t &index, ekg::textbox_t::cursor_t &cursor_out ) { + if (cursor_out != ekg::vec2_t(0, 0) && cursor_out >= index && cursor_out <= index) { + return true; + } + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { if (cursor >= index && cursor <= index) { cursor_out = cursor; @@ -52,11 +56,124 @@ bool ekg::ui::find_index_by_interact( ekg::property_t &property, ekg::textbox_t &textbox, ekg::vec2_t &interact, - size_t &index + ekg::vec2_t &index ) { ekg::rect_t &rect_abs { ekg::ui::get_abs_rect(property, textbox.rect) }; + + ekg::draw::font &draw_font { + ekg::draw::get_font_renderer(textbox.font_size) + }; + + ekg::io::font_face_t &text_font_face {draw_font.faces[ekg::io::font_face_type::text]}; + ekg::io::font_face_t &emojis_font_face {draw_font.faces[ekg::io::font_face_type::emojis]}; + ekg::io::font_face_t &kanjis_font_face {draw_font.faces[ekg::io::font_face_type::kanjis]}; + + FT_Face ft_face {}; + FT_Vector ft_vector_previous_char {}; + char32_t ft_uint_previous {}; + + size_t chunk_size {}; + size_t il {}; + size_t addition_chunk_index {}; + size_t text_total_lines {textbox.text.length_of_lines()}; + + ekg::vec2_t pos {}; + ekg::vec2_t rendered {}; + ekg::rect_t rect {}; + + size_t text_len {}; + size_t utf8_text_len {}; + uint8_t uc8 {}; + char32_t c32 {}; + + std::vector &chunks {textbox.text.chunks_data()}; + size_t chunks_size {chunks.size()}; + + index.y += textbox.widget.view_line_index; + textbox.widget.scrollbar.rect.h = text_total_lines * draw_font.text_height; + + for (size_t ic {textbox.widget.view_chunk_index}; ic < chunks_size; ic++) { + ekg::io::chunk_t &chunk {chunks.at(ic)}; + + il = 0; + if (ic == textbox.widget.view_chunk_index) { + il = textbox.widget.view_chunk_line_index; + } + + chunk_size = chunk.size(); + for (;il < chunk_size; il++) { + std::string &line {chunk.at(il)}; + text_len = line.size(); + for (size_t it {}; it < text_len; it++) { + uc8 = static_cast(line.at(it)); + + ekg::utf8_sequence( + uc8, + c32, + line, + it + ); + + if (draw_font.ft_bool_kerning && ft_uint_previous) { + switch (c32 < 256 || !emojis_font_face.was_loaded) { + case true: { + ft_face = text_font_face.ft_face; + break; + } + + default: { + ft_face = emojis_font_face.ft_face; + break; + } + } + + FT_Get_Kerning(ft_face, ft_uint_previous, c32, 0, &ft_vector_previous_char); + pos.x += static_cast(ft_vector_previous_char.x >> 6); + } + + ekg::io::glyph_t &glyph {draw_font.mapped_glyph[c32]}; + + rect.x = pos.x + rect_abs.x; + rect.y = pos.y + rect_abs.y; + rect.w = glyph.wsize / 2; + rect.h = draw_font.text_height; + + if (ekg::rect_collide_vec2(rect, interact)) { + return true; + } + + rect.w = glyph.wsize; + if (ekg::rect_collide_vec2(rect, interact)) { + index.x++; + return true; + } + + index.x++; + pos.x += rect.w; + + rect.w = rect_abs.w; + if (index.x == text_len && ekg::rect_collide_vec2(rect, interact)) { + index.x = text_len; + return true; + } + } + + index.x = 0; + pos.x = 0.0f; + + index.y++; + pos.y += draw_font.text_height; + + rendered.y += draw_font.text_height; + if (rendered.y >= rect_abs.h) { + return false; + } + } + } + + return false; } void ekg::ui::reload( @@ -145,6 +262,8 @@ void ekg::ui::event( ekg::input_info_t &input {ekg::p_core->handler_input.input}; ekg::vec2_t interact {static_cast>(input.interact)}; + /* focus part */ + bool should_enable_high_frequency { property.states.is_hovering || @@ -153,19 +272,21 @@ void ekg::ui::event( scrollbar_property.widget.is_high_frequency }; + bool is_focus_action_fired { + property.states.is_hovering + && + input.was_pressed + && + ekg::fired("textbox-focus") + }; + if ( !property.states.is_focused && - property.states.is_hovering - && - ekg::fire("textbox-focus") + is_focus_action_fired ) { should_enable_high_frequency = true; property.states.is_focused = true; - - if (textbox.widget.cursors.empty()) { - - } } if ( @@ -181,6 +302,85 @@ void ekg::ui::event( should_enable_high_frequency = false; } + if (!property.states.is_focused) { + return; + } + + ekg::vec2_t pick_index {}; + bool should_pick_index {}; + bool picked_index {}; + bool should_create_a_cursor {}; + + if ( + property.states.is_focused + && + property.states.is_hovering + && + input.was_pressed + ) { + should_pick_index = true; + } + + /* where input logic and optmized parts are placed */ + + bool is_action_modifier_fired { + ekg::fired("textbox-action-modifier") + }; + + if (should_pick_index) { + picked_index = + ekg::ui::find_index_by_interact( + pick_index + ); + } + + if (textbox.widget.set_cursor_static) { + textbox.widget.unset_cursor_static = input.was_released; + } + + if (picked_index) { + if (input.was_pressed && is_focus_action_fired) { + textbox.widget.picked_left = pick_index; + textbox.widget.picked_right = pick_index; + textbox.widget.first_pick_pos = pick_index; + textbox.widget.set_cursor_static = true; + should_create_a_cursor = true; + } + + if (input.was_released && !input.was_typed) { + textbox.widget.current_cursor_index = UINT64_MAX; + } + + if (textbox.widget.current_cursor_index != UINT64_MAX) { + if (ekg::textbox_t::cursor_t {.a = pick_index, .b = pick_index} < textbox.widget.first_pick_pos) { + textbox.widget.picked_left = pick_index; + textbox.widget.picked_right = textbox.widget.first_pick_pos; + } else { + textbox.widget.picked_left = textbox.widget.first_pick_pos; + textbox.widget.picked_right = pick_index; + } + } + + if (should_create_a_cursor && is_action_modifier_fired) { + textbox.widget.current_cursor_index = textbox.widget.cursors.size(); + textbox.widget.cursors.push_back( + { + .a = textbox.widget.picked_left, + .b = textbox.widget.picked_right + } + ); + } else if (should_create_a_cursor) { + textbox.widget.current_cursor_index = 0; + textbox.widget.cursors.clear(); + textbox.widget.cursors.push_back( + { + .a = textbox.widget.picked_left, + .b = textbox.widget.picked_right + } + ); + } + } + if ( should_enable_high_frequency ) { @@ -205,6 +405,14 @@ void ekg::ui::high_frequency( ekg::property_t &property, ekg::textbox_t &textbox ) { + if (textbox.widget.set_cursor_static) { + ekg::reset(textbox.widget.cursor_timing); + textbox.widget.set_cursor_static = !textbox.widget.unset_cursor_static; + } + + textbox.widget.unset_cursor_static = false; + ekg::reset_if_reach(textbox.widget.cursor_timing, 1000); + ekg::rect_t &rect_abs { ekg::ui::get_abs_rect(property, textbox.rect) }; @@ -269,10 +477,12 @@ void ekg::ui::buffering( /* start of select */ - ekg::vec2_t scrollable_pos {}; + bool elapsed_mid_second { + textbox.widget.cursor_timing.current_ticks > 500 + }; for (ekg::textbox_t::select_draw_layer_t &layer : textbox.widget.layers_select) { - if (layer.is_ab_equals && ekg::timing_t::second > 500) { + if (layer.is_ab_equals && elapsed_mid_second) { continue; } @@ -299,6 +509,10 @@ void ekg::ui::buffering( floorf(static_cast(static_cast(rect_abs.y - draw_font.offset_text_height * 2))) }; + ekg::io::font_face_t &text_font_face {draw_font.faces[ekg::io::font_face_type::text]}; + ekg::io::font_face_t &emojis_font_face {draw_font.faces[ekg::io::font_face_type::emojis]}; + ekg::io::font_face_t &kanjis_font_face {draw_font.faces[ekg::io::font_face_type::kanjis]}; + data.buffer[0] = pos.x; data.buffer[1] = pos.y; data.buffer[2] = static_cast(-draw_font.non_swizzlable_range); @@ -311,24 +525,17 @@ void ekg::ui::buffering( ekg::hash_t hash {}; ekg::rect_t vertex {}; ekg::rect_t uv {}; - ekg::rect_t rendered_size {}; + ekg::rect_t rendered {}; size_t text_len {}; size_t utf8_text_len {}; uint8_t uc8 {}; char32_t c32 {}; - bool break_text {}; - bool r_n_break_text {}; - FT_Face ft_face {}; FT_Vector ft_vector_previous_char {}; char32_t ft_uint_previous {}; - ekg::io::font_face_t &text_font_face {draw_font.faces[ekg::io::font_face_type::text]}; - ekg::io::font_face_t &emojis_font_face {draw_font.faces[ekg::io::font_face_type::emojis]}; - ekg::io::font_face_t &kanjis_font_face {draw_font.faces[ekg::io::font_face_type::kanjis]}; - bool is_inline_selected {}; bool is_complete_line_selected {}; bool is_ab_equals_selected {}; @@ -341,7 +548,7 @@ void ekg::ui::buffering( size_t current_line_for_cursor_complete {UINT64_MAX}; size_t text_total_chars {textbox.text.length_of_chars()}; size_t text_total_lines {textbox.text.length_of_lines()}; - size_t ij {}; + size_t il {}; size_t addition_chunk_index {}; size_t chunk_size {}; ekg::vec2_t index {}; @@ -349,22 +556,18 @@ void ekg::ui::buffering( ekg::pixel_t line_wsize {}; ekg::textbox_t::cursor_t cursor {}; std::vector &chunks {textbox.text.chunks_data()}; + size_t chunks_size {chunks.size()}; textbox.widget.scrollbar.rect.h = text_total_lines * draw_font.text_height; - - size_t visual_target_line_index { + textbox.widget.view_line_index = static_cast( - static_cast( - -( - textbox.widget.scrollbar_property.scroll.position.y - / - textbox.widget.scrollbar.rect.h - ) + -ekg::arithmetic_normalize( + textbox.widget.scrollbar_property.scroll.position.y, + textbox.widget.scrollbar.rect.h ) * text_total_lines - ) - }; + ); /** * This technique prevent from floating-point precision loss inside GPU. @@ -385,32 +588,34 @@ void ekg::ui::buffering( * * - Rina - 11:39; 08/06/2025 **/ - float visible_text_height {static_cast(visual_target_line_index * draw_font.text_height)}; + float visible_text_height {static_cast(textbox.widget.view_line_index * draw_font.text_height)}; pos.y = textbox.widget.scrollbar_property.scroll.position.y + visible_text_height; pos.y = static_cast(static_cast(floorf(pos.y))); pos.x = 0.0f; textbox.widget.layers_select.clear(); - - for (ekg::io::chunk_t &chunk : chunks) { + for (size_t ic {}; ic < chunks_size; ic++) { + ekg::io::chunk_t &chunk {chunks.at(ic)}; chunk_size = chunk.size(); - if (addition_chunk_index + chunk_size < visual_target_line_index) { + if (addition_chunk_index + chunk_size < textbox.widget.view_line_index) { pos.x = 0.0f; index.y += chunk_size; addition_chunk_index += chunk_size; continue; } - ij = 0; + il = 0; if (!oka_found_visual_index) { - ij = visual_target_line_index - addition_chunk_index; - index.y += ij; + textbox.widget.view_chunk_index = ic; + il = textbox.widget.view_line_index - addition_chunk_index; + textbox.widget.view_chunk_line_index = il; + index.y += il; oka_found_visual_index = true; } addition_chunk_index += chunk_size; - for (;ij < chunk_size; ij++) { - std::string &line {chunk.at(ij)}; + for (;il < chunk_size; il++) { + std::string &line {chunk.at(il)}; index.x = 0; text_len = line.size(); for (size_t it {}; it < text_len; it++) { @@ -487,7 +692,7 @@ void ekg::ui::buffering( && ( (current_line_for_cursor_complete != index.y) - || + || (is_end_of_line_and_text = index.y + 1 == text_total_lines && it + 1 == text_len) ) ) { @@ -579,11 +784,11 @@ void ekg::ui::buffering( pos.x = 0.0f; pos.y += draw_font.text_height; - rendered_size.y += draw_font.text_height; + rendered.y += draw_font.text_height; index.y++; hash += pos.y * 32; - if (rendered_size.y > rect_abs.h) { + if (rendered.y > rect_abs.h) { get_out = true; break; } From 1dda68a608c688d025d022b38807d6d743485ea4 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 10 Aug 2025 23:53:55 -0300 Subject: [PATCH 11/61] [fix] textbox text-height standarized --- include/ekg/handler/input/handler.hpp | 4 + src/core/runtime.cpp | 14 +- src/handler/input/handler.cpp | 176 +++++++++++++++----------- src/ui/textbox/widget.cpp | 126 ++++++++++++------ 4 files changed, 201 insertions(+), 119 deletions(-) diff --git a/include/ekg/handler/input/handler.hpp b/include/ekg/handler/input/handler.hpp index d7e6adef..1a734792 100644 --- a/include/ekg/handler/input/handler.hpp +++ b/include/ekg/handler/input/handler.hpp @@ -29,6 +29,10 @@ namespace ekg::handler { ekg::timing_t double_interact {}; ekg::timing_t last_time_wheel_was_fired {}; + + // prevent too many unoptmized use of string allocations + std::string key_name {}; + std::string string_builder {}; public: ekg::input_info_t input {}; protected: diff --git a/src/core/runtime.cpp b/src/core/runtime.cpp index 02c870b3..9a7399d7 100644 --- a/src/core/runtime.cpp +++ b/src/core/runtime.cpp @@ -302,11 +302,12 @@ void ekg::core::poll_event() { hovered = ( !( - ekg::p_core->p_platform_base->event.type == ekg::io::event_type::key_down - || - ekg::p_core->p_platform_base->event.type == ekg::io::event_type::key_up - || - ekg::p_core->p_platform_base->event.type == ekg::io::event_type::text_input + false + //ekg::p_core->p_platform_base->event.type == ekg::io::event_type::key_down + //|| + //ekg::p_core->p_platform_base->event.type == ekg::io::event_type::key_up + //|| + //ekg::p_core->p_platform_base->event.type == ekg::io::event_type::text_input ) && property.states.is_hovering @@ -330,8 +331,7 @@ void ekg::core::poll_event() { if (!first_absolute) { focused_property != ekg::property_t::not_found - && (focused_property.states.is_hovering = false); - + && (focused_property.states.is_hovering = false); focused_at = at; } } diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index 2f6e80d2..bfd72662 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -148,8 +148,10 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-action-break-line", "lshift+return"); this->insert_input_bind("textbox-action-break-line", "rshift+return"); this->insert_input_bind("textbox-action-tab", "tab"); - this->insert_input_bind("textbox-action-modifier", "lctrl"); - this->insert_input_bind("textbox-action-modifier", "rctrl"); + + this->insert_input_bind("textbox-action-cursor", "abs-mouse-1"); + this->insert_input_bind("textbox-action-multicursor", "lctrl+mouse-1"); + this->insert_input_bind("textbox-action-multicursor", "rctrl+mouse-1"); this->insert_input_bind("textbox-action-up", "abs-up"); this->insert_input_bind("textbox-action-down", "abs-down"); @@ -263,38 +265,42 @@ void ekg::handler::input::poll_event() { this->input.was_pressed = true; this->input.was_typed = true; - std::string key_name {}; - std::string string_builder {}; + this->string_builder.clear(); ekg::p_core->p_platform_base->get_key_name( platform_event.key, - key_name + this->key_name ); ekg::special_key special_key {ekg::special_key::unknown}; ekg::p_core->p_platform_base->get_special_key(platform_event.key, special_key); if (special_key != ekg::special_key::unknown) { - this->special_keys[static_cast(special_key)][0] = key_name[0]; - string_builder += key_name; + this->special_keys[static_cast(special_key)][0] = this->key_name[0]; + + this->string_builder += "abs-"; + this->string_builder += this->key_name; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); - this->set_input_state(string_builder, true); + this->string_builder.clear(); + this->string_builder += this->key_name; + this->set_input_state(this->string_builder, true); this->is_special_keys_released = true; } else { - std::transform(key_name.begin(), key_name.end(), key_name.begin(), ::tolower); - string_builder += "abs-"; - string_builder += key_name; - - this->set_input_state(string_builder, true); - this->input_released_list.push_back(string_builder); - - string_builder.clear(); - this->complete_with_units(string_builder, key_name); - this->set_input_state(string_builder, true); - this->input_released_list.push_back(string_builder); - - if (string_builder != key_name && !this->contains_unit(string_builder)) { - this->special_keys_unit_pressed.push_back(string_builder); + std::transform(this->key_name.begin(), this->key_name.end(), this->key_name.begin(), ::tolower); + this->string_builder += "abs-"; + this->string_builder += this->key_name; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); + + this->string_builder.clear(); + this->complete_with_units(this->string_builder, this->key_name); + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); + + if (this->string_builder != this->key_name && !this->contains_unit(this->string_builder)) { + this->special_keys_unit_pressed.push_back(this->string_builder); } } @@ -303,12 +309,10 @@ void ekg::handler::input::poll_event() { case ekg::io::event_type::key_up: { this->input.was_released = true; - std::string key_name {}; - std::string string_builder {}; ekg::p_core->p_platform_base->get_key_name( platform_event.key, - key_name + this->key_name ); ekg::special_key special_key {ekg::special_key::unknown}; @@ -316,55 +320,72 @@ void ekg::handler::input::poll_event() { if (special_key != ekg::special_key::unknown) { this->special_keys[static_cast(special_key)][0] = '\0'; - string_builder += key_name; - this->set_input_state(string_builder, false); + this->string_builder = "abs-"; + this->string_builder += this->key_name; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); + + this->string_builder = this->key_name; + this->set_input_state(this->string_builder, false); this->is_special_keys_released = true; - string_builder += "-up"; - this->set_input_state(string_builder, true); + this->string_builder += "-up"; + this->set_input_state(this->string_builder, true); } else { - std::transform(key_name.begin(), key_name.end(), key_name.begin(), ::tolower); - string_builder += "abs-"; - string_builder += key_name; - string_builder += "-up"; - - this->set_input_state(string_builder, true); - this->input_released_list.push_back(string_builder); - - string_builder.clear(); - this->complete_with_units(string_builder, key_name); - string_builder += "-up"; - - this->set_input_state(string_builder, true); - this->input_released_list.push_back(string_builder); + std::transform(this->key_name.begin(), this->key_name.end(), this->key_name.begin(), ::tolower); + this->string_builder = "abs-"; + this->string_builder += this->key_name; + this->string_builder += "-up"; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); + + this->string_builder.clear(); + this->complete_with_units(this->string_builder, this->key_name); + this->string_builder += "-up"; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); } break; } case ekg::io::event_type::mouse_button_down: { - std::string string_builder {}; - std::string key_name {"mouse"}; + this->input.was_pressed = true; + this->key_name = "mouse"; - this->set_input_state(key_name, true); - this->input_released_list.push_back(key_name); + this->string_builder = "abs-"; + this->string_builder += this->key_name; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); - key_name = "mouse-"; - key_name += std::to_string(platform_event.mouse_button); + this->set_input_state(this->key_name, true); + this->input_released_list.push_back(this->key_name); - this->input.was_pressed = true; - this->complete_with_units(string_builder, key_name); - this->set_input_state(string_builder, true); - this->input_released_list.push_back(string_builder); + this->key_name = "mouse-"; + this->key_name += std::to_string(platform_event.mouse_button); + + this->string_builder = "abs-"; + this->string_builder += this->key_name; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); + + this->string_builder.clear(); + this->complete_with_units(this->string_builder, this->key_name); + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); bool double_click_factor {ekg::reach(this->double_interact, 500)}; if (!double_click_factor) { - string_builder += "-double"; - this->set_input_state(string_builder, true); + this->string_builder += "-double"; + this->set_input_state(this->string_builder, true); + + this->string_builder = "abs-" + this->string_builder; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); - this->double_click_mouse_buttons_pressed.push_back(string_builder); - this->input_released_list.push_back(string_builder); + this->double_click_mouse_buttons_pressed.push_back(this->string_builder); + this->input_released_list.push_back(this->string_builder); } if (double_click_factor) { @@ -375,21 +396,32 @@ void ekg::handler::input::poll_event() { } case ekg::io::event_type::mouse_button_up: { - std::string string_builder {}; - std::string key_name {"mouse-up"}; - this->input.was_released = true; - this->set_input_state(key_name, true); - this->input_released_list.push_back(key_name); + this->key_name = "mouse-up"; + + this->string_builder = "abs-"; + this->string_builder += this->key_name; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); + + this->set_input_state(this->key_name, true); + this->input_released_list.push_back(this->key_name); + + this->key_name = "mouse-"; + this->key_name += std::to_string(platform_event.mouse_button); - key_name = "mouse-"; - key_name += std::to_string(platform_event.mouse_button); + this->string_builder = "abs-"; + this->string_builder += this->key_name; + this->string_builder += "-up"; + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); - this->complete_with_units(string_builder, key_name); - string_builder += "-up"; + this->string_builder.clear(); + this->complete_with_units(this->string_builder, this->key_name); + this->string_builder += "-up"; - this->set_input_state(string_builder, true); - this->input_released_list.push_back(string_builder); + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); break; } @@ -401,10 +433,10 @@ void ekg::handler::input::poll_event() { } case ekg::io::event_type::mouse_wheel: { - std::string string_builder {}; - this->complete_with_units(string_builder, "mouse-wheel"); - this->set_input_state(string_builder, true); - this->input_released_list.push_back(string_builder); + this->string_builder.clear(); + this->complete_with_units(this->string_builder, "mouse-wheel"); + this->set_input_state(this->string_builder, true); + this->input_released_list.push_back(this->string_builder); this->input.was_wheel = true; this->set_input_state("mouse-wheel-up", platform_event.mouse_wheel_y > 0); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index a45113ce..29e89733 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -79,7 +79,11 @@ bool ekg::ui::find_index_by_interact( size_t addition_chunk_index {}; size_t text_total_lines {textbox.text.length_of_lines()}; - ekg::vec2_t pos {}; + ekg::vec2_t pos { + static_cast(static_cast(textbox.color_scheme.gutter_margin)) + textbox.widget.scrollbar_property.scroll.position.x, + floorf(static_cast(static_cast(-draw_font.offset_text_height))) + }; + ekg::vec2_t rendered {}; ekg::rect_t rect {}; @@ -92,7 +96,12 @@ bool ekg::ui::find_index_by_interact( size_t chunks_size {chunks.size()}; index.y += textbox.widget.view_line_index; - textbox.widget.scrollbar.rect.h = text_total_lines * draw_font.text_height; + textbox.widget.scrollbar.rect.h = text_total_lines * textbox.widget.rect_text_size.h; + + float visible_text_height {static_cast(textbox.widget.view_line_index * textbox.widget.rect_text_size.h)}; + pos.y = textbox.widget.scrollbar_property.scroll.position.y + visible_text_height; + pos.y = static_cast(static_cast(floorf(pos.y))); + pos.x = textbox.color_scheme.gutter_margin; for (size_t ic {textbox.widget.view_chunk_index}; ic < chunks_size; ic++) { ekg::io::chunk_t &chunk {chunks.at(ic)}; @@ -138,7 +147,7 @@ bool ekg::ui::find_index_by_interact( rect.x = pos.x + rect_abs.x; rect.y = pos.y + rect_abs.y; rect.w = glyph.wsize / 2; - rect.h = draw_font.text_height; + rect.h = textbox.widget.rect_text_size.h; if (ekg::rect_collide_vec2(rect, interact)) { return true; @@ -161,13 +170,13 @@ bool ekg::ui::find_index_by_interact( } index.x = 0; - pos.x = 0.0f; + pos.x = textbox.color_scheme.gutter_margin; index.y++; - pos.y += draw_font.text_height; + pos.y += textbox.widget.rect_text_size.h; - rendered.y += draw_font.text_height; - if (rendered.y >= rect_abs.h) { + rendered.y += textbox.widget.rect_text_size.h; + if (rendered.y >= rect_abs.h + textbox.widget.rect_text_size.h) { return false; } } @@ -188,7 +197,7 @@ void ekg::ui::reload( ekg::draw::get_font_renderer(textbox.font_size) }; - textbox.widget.rect_text_size.h = draw_font.get_text_height(); + textbox.widget.rect_text_size.h = draw_font.text_height + draw_font.offset_text_height; textbox.widget.rect_text_size.w = textbox.widget.rect_text_size.h; ekg::aligned_t aligned_dimension {}; @@ -221,8 +230,8 @@ void ekg::ui::reload( textbox.widget.scrollbar.rect.x = rect_abs.x; textbox.widget.scrollbar.rect.y = rect_abs.y; - textbox.widget.scrollbar.rect.w = highest_size * draw_font.text_height; - textbox.widget.scrollbar.acceleration = {draw_font.text_height, draw_font.text_height}; + textbox.widget.scrollbar.rect.w = highest_size * textbox.widget.rect_text_size.h; + textbox.widget.scrollbar.acceleration = {textbox.widget.rect_text_size.h, textbox.widget.rect_text_size.h}; textbox.widget.scrollbar.color_scheme = ekg::p_core->handler_theme.get_current_theme().scrollbar_color_scheme; @@ -317,19 +326,24 @@ void ekg::ui::event( property.states.is_hovering && input.was_pressed + && + !textbox.widget.scrollbar_property.states.is_hovering ) { should_pick_index = true; } /* where input logic and optmized parts are placed */ - bool is_action_modifier_fired { - ekg::fired("textbox-action-modifier") - }; + if (textbox.widget.current_cursor_index != UINT64_MAX) { + should_pick_index = true; + } if (should_pick_index) { picked_index = - ekg::ui::find_index_by_interact( + ekg::ui::find_index_by_interact( + property, + textbox, + interact, pick_index ); } @@ -339,7 +353,7 @@ void ekg::ui::event( } if (picked_index) { - if (input.was_pressed && is_focus_action_fired) { + if (input.was_pressed && ekg::fired("textbox-action-cursor")) { textbox.widget.picked_left = pick_index; textbox.widget.picked_right = pick_index; textbox.widget.first_pick_pos = pick_index; @@ -353,15 +367,30 @@ void ekg::ui::event( if (textbox.widget.current_cursor_index != UINT64_MAX) { if (ekg::textbox_t::cursor_t {.a = pick_index, .b = pick_index} < textbox.widget.first_pick_pos) { - textbox.widget.picked_left = pick_index; - textbox.widget.picked_right = textbox.widget.first_pick_pos; - } else { textbox.widget.picked_left = textbox.widget.first_pick_pos; textbox.widget.picked_right = pick_index; + } else { + textbox.widget.picked_left = pick_index; + textbox.widget.picked_right = textbox.widget.first_pick_pos; + } + + if (textbox.widget.current_cursor_index < textbox.widget.cursors.size()) { + ekg::textbox_t::cursor_t &cursor { + textbox.widget.cursors[textbox.widget.current_cursor_index] + }; + + cursor.a = textbox.widget.picked_left; + cursor.b = textbox.widget.picked_right; + + ekg_log_low_level("a: " << cursor.a.x << ", " << cursor.a.y << " b: " << cursor.b.x << " , " << cursor.b.y) + + ekg::gui.ui.redraw = true; + property.widget.should_buffering = true; } } - if (should_create_a_cursor && is_action_modifier_fired) { + ekg::textbox_t::cursor_t cursor {}; + if (should_create_a_cursor && !ekg::ui::find_cursor(textbox, pick_index, cursor) && ekg::fired("textbox-action-multicursor")) { textbox.widget.current_cursor_index = textbox.widget.cursors.size(); textbox.widget.cursors.push_back( { @@ -369,7 +398,10 @@ void ekg::ui::event( .b = textbox.widget.picked_right } ); - } else if (should_create_a_cursor) { + should_create_a_cursor = false; + } + + if (should_create_a_cursor) { textbox.widget.current_cursor_index = 0; textbox.widget.cursors.clear(); textbox.widget.cursors.push_back( @@ -481,13 +513,17 @@ void ekg::ui::buffering( textbox.widget.cursor_timing.current_ticks > 500 }; + ekg::rect_t rect_select {}; for (ekg::textbox_t::select_draw_layer_t &layer : textbox.widget.layers_select) { if (layer.is_ab_equals && elapsed_mid_second) { continue; } + rect_select = layer.rect + rect_abs + textbox.widget.scrollbar_property.scroll.position; + rect_select.y = layer.rect.y + rect_abs.y; + ekg::draw::rect( - layer.rect + rect_abs + textbox.widget.scrollbar_property.scroll.position, + rect_select, layer.is_ab_equals ? textbox.color_scheme.text_cursor_foreground : textbox.color_scheme.text_select_foreground, ekg::draw::mode::fill, ekg::at_t::not_found @@ -506,7 +542,7 @@ void ekg::ui::buffering( ekg::vec2_t pos { static_cast(static_cast(rect_abs.x + textbox.color_scheme.gutter_margin)) + textbox.widget.scrollbar_property.scroll.position.x, - floorf(static_cast(static_cast(rect_abs.y - draw_font.offset_text_height * 2))) + floorf(static_cast(static_cast(rect_abs.y - draw_font.offset_text_height))) }; ekg::io::font_face_t &text_font_face {draw_font.faces[ekg::io::font_face_type::text]}; @@ -539,12 +575,16 @@ void ekg::ui::buffering( bool is_inline_selected {}; bool is_complete_line_selected {}; bool is_ab_equals_selected {}; + bool is_cursor_at_end_of_line {}; bool was_complete_line_selected {}; bool cursors_going_on {!textbox.widget.cursors.empty()}; bool is_end_of_line_and_text {}; bool oka_found_visual_index {}; bool get_out {}; + float extra_final_char_cursor {}; + float extra_rect_height {rect_abs.h + textbox.widget.rect_text_size.h}; + size_t current_line_for_cursor_complete {UINT64_MAX}; size_t text_total_chars {textbox.text.length_of_chars()}; size_t text_total_lines {textbox.text.length_of_lines()}; @@ -558,7 +598,7 @@ void ekg::ui::buffering( std::vector &chunks {textbox.text.chunks_data()}; size_t chunks_size {chunks.size()}; - textbox.widget.scrollbar.rect.h = text_total_lines * draw_font.text_height; + textbox.widget.scrollbar.rect.h = text_total_lines * textbox.widget.rect_text_size.h; textbox.widget.view_line_index = static_cast( -ekg::arithmetic_normalize( @@ -588,10 +628,10 @@ void ekg::ui::buffering( * * - Rina - 11:39; 08/06/2025 **/ - float visible_text_height {static_cast(textbox.widget.view_line_index * draw_font.text_height)}; + float visible_text_height {static_cast(textbox.widget.view_line_index * textbox.widget.rect_text_size.h)}; pos.y = textbox.widget.scrollbar_property.scroll.position.y + visible_text_height; pos.y = static_cast(static_cast(floorf(pos.y))); - pos.x = 0.0f; + pos.x = textbox.color_scheme.gutter_margin; textbox.widget.layers_select.clear(); for (size_t ic {}; ic < chunks_size; ic++) { @@ -650,11 +690,16 @@ void ekg::ui::buffering( if ( cursors_going_on && - ekg::ui::find_cursor(textbox, index, cursor) + ( + ekg::ui::find_cursor(textbox, index, cursor) + || + (is_cursor_at_end_of_line = it+1 == text_len && ++index.x && ekg::ui::find_cursor(textbox, index, cursor)) + ) ) { is_ab_equals_selected = property.states.is_focused && cursor == index; is_inline_selected = cursor >= index && cursor < index && !is_ab_equals_selected; is_complete_line_selected = false; + extra_final_char_cursor = (is_cursor_at_end_of_line * glyph.wsize); if (is_inline_selected) { if ( @@ -672,22 +717,22 @@ void ekg::ui::buffering( } if (is_ab_equals_selected) { - cursor.rect.x = pos.x; + cursor.rect.x = pos.x + extra_final_char_cursor; cursor.rect.y = pos.y; cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph.wsize : textbox.color_scheme.cursor_thickness; - cursor.rect.h = draw_font.text_height; + cursor.rect.h = textbox.widget.rect_text_size.h; textbox.widget.layers_select.push_back({true, cursor.rect}); } if (is_inline_selected) { - cursor.rect.x = pos.x; + cursor.rect.x = pos.x + extra_final_char_cursor; cursor.rect.y = pos.y; cursor.rect.w = glyph.wsize; - cursor.rect.h = draw_font.text_height; + cursor.rect.h = textbox.widget.rect_text_size.h; textbox.widget.layers_select.push_back({false, cursor.rect}); } - if ( + if ( was_complete_line_selected && ( @@ -696,16 +741,16 @@ void ekg::ui::buffering( (is_end_of_line_and_text = index.y + 1 == text_total_lines && it + 1 == text_len) ) ) { - cursor.rect.x = 0.0f; - cursor.rect.y = pos.y - (draw_font.text_height * !is_end_of_line_and_text); - cursor.rect.w = line_wsize + (glyph.wsize * is_end_of_line_and_text); - cursor.rect.h = draw_font.text_height; + cursor.rect.x = textbox.color_scheme.gutter_margin; + cursor.rect.y = pos.y - (textbox.widget.rect_text_size.h * !is_end_of_line_and_text); + cursor.rect.w = line_wsize + (is_end_of_line_and_text * glyph.wsize) + extra_final_char_cursor; + cursor.rect.h = textbox.widget.rect_text_size.h; textbox.widget.layers_select.push_back({false, cursor.rect}); was_complete_line_selected = false; } if (is_complete_line_selected && current_line_for_cursor_complete != index.y) { - line_wsize = 0.0f; + line_wsize = textbox.color_scheme.gutter_margin; current_line_for_cursor_complete = index.y; was_complete_line_selected = true; } @@ -779,16 +824,17 @@ void ekg::ui::buffering( * Peek `ekg/io/memory.hpp` for better hash definition and purpose. **/ hash += ekg_generate_hash(pos.x, c32, glyph.x); + index.x++; } - pos.x = 0.0f; - pos.y += draw_font.text_height; - rendered.y += draw_font.text_height; + pos.x = textbox.color_scheme.gutter_margin; + pos.y += textbox.widget.rect_text_size.h; + rendered.y += textbox.widget.rect_text_size.h; index.y++; hash += pos.y * 32; - if (rendered.y > rect_abs.h) { + if (rendered.y > extra_rect_height) { get_out = true; break; } From cc38114889753aa288bc7357cc4ac0c717671438 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Thu, 14 Aug 2025 00:41:01 -0300 Subject: [PATCH 12/61] [fix] textbox unvisible selected-complete line --- include/ekg/ui/textbox/textbox.hpp | 1 + src/handler/theme/handler.cpp | 1 + src/ui/textbox/widget.cpp | 41 ++++++++++++++++++++---------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 5bb7bd66..62beabee 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -38,6 +38,7 @@ namespace ekg { ekg::rgba_t text_foreground {}; ekg::rgba_t text_select_foreground {}; ekg::rgba_t text_cursor_foreground {}; + ekg::rgba_t text_select_outline {}; ekg::pixel_thickness_t cursor_thickness {2}; bool caret_cursor {}; ekg::pixel_thickness_t gutter_margin {2}; diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index 3b01a0cf..0144fec2 100644 --- a/src/handler/theme/handler.cpp +++ b/src/handler/theme/handler.cpp @@ -81,6 +81,7 @@ void ekg::handler::theme::init() { light_pinky_theme.textbox_color_scheme.text_foreground = {141, 141, 141, 255}; light_pinky_theme.textbox_color_scheme.text_cursor_foreground = {141, 141, 141, 255}; light_pinky_theme.textbox_color_scheme.text_select_foreground = {245, 169, 184, 50}; + light_pinky_theme.textbox_color_scheme.text_select_outline = {245, 169, 184, 100}; this->registry(light_pinky_theme.tag) = light_pinky_theme; this->set_current_theme(light_pinky_theme.tag); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 29e89733..aa897751 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -364,9 +364,9 @@ void ekg::ui::event( if (input.was_released && !input.was_typed) { textbox.widget.current_cursor_index = UINT64_MAX; } - + if (textbox.widget.current_cursor_index != UINT64_MAX) { - if (ekg::textbox_t::cursor_t {.a = pick_index, .b = pick_index} < textbox.widget.first_pick_pos) { + if (ekg::textbox_t::cursor_t {.a = pick_index, .b = pick_index} <= textbox.widget.first_pick_pos) { textbox.widget.picked_left = textbox.widget.first_pick_pos; textbox.widget.picked_right = pick_index; } else { @@ -382,8 +382,6 @@ void ekg::ui::event( cursor.a = textbox.widget.picked_left; cursor.b = textbox.widget.picked_right; - ekg_log_low_level("a: " << cursor.a.x << ", " << cursor.a.y << " b: " << cursor.b.x << " , " << cursor.b.y) - ekg::gui.ui.redraw = true; property.widget.should_buffering = true; } @@ -581,9 +579,11 @@ void ekg::ui::buffering( bool is_end_of_line_and_text {}; bool oka_found_visual_index {}; bool get_out {}; + bool is_last_char_from_line {}; float extra_final_char_cursor {}; float extra_rect_height {rect_abs.h + textbox.widget.rect_text_size.h}; + float glyph_wsize {}; size_t current_line_for_cursor_complete {UINT64_MAX}; size_t text_total_chars {textbox.text.length_of_chars()}; @@ -687,19 +687,21 @@ void ekg::ui::buffering( ekg::io::glyph_t &glyph {draw_font.mapped_glyph[c32]}; + is_last_char_from_line = it+1 == text_len; if ( cursors_going_on && ( ekg::ui::find_cursor(textbox, index, cursor) || - (is_cursor_at_end_of_line = it+1 == text_len && ++index.x && ekg::ui::find_cursor(textbox, index, cursor)) + (is_cursor_at_end_of_line = is_last_char_from_line && ++index.x && ekg::ui::find_cursor(textbox, index, cursor)) ) ) { + glyph_wsize = glyph.wsize; is_ab_equals_selected = property.states.is_focused && cursor == index; is_inline_selected = cursor >= index && cursor < index && !is_ab_equals_selected; is_complete_line_selected = false; - extra_final_char_cursor = (is_cursor_at_end_of_line * glyph.wsize); + extra_final_char_cursor = (is_cursor_at_end_of_line * glyph_wsize); if (is_inline_selected) { if ( @@ -719,7 +721,7 @@ void ekg::ui::buffering( if (is_ab_equals_selected) { cursor.rect.x = pos.x + extra_final_char_cursor; cursor.rect.y = pos.y; - cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph.wsize : textbox.color_scheme.cursor_thickness; + cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph_wsize : textbox.color_scheme.cursor_thickness; cursor.rect.h = textbox.widget.rect_text_size.h; textbox.widget.layers_select.push_back({true, cursor.rect}); } @@ -727,26 +729,31 @@ void ekg::ui::buffering( if (is_inline_selected) { cursor.rect.x = pos.x + extra_final_char_cursor; cursor.rect.y = pos.y; - cursor.rect.w = glyph.wsize; + cursor.rect.w = glyph_wsize + (glyph_wsize * (is_last_char_from_line && !is_cursor_at_end_of_line && cursor.a.y != cursor.b.y)); cursor.rect.h = textbox.widget.rect_text_size.h; - textbox.widget.layers_select.push_back({false, cursor.rect}); + textbox.widget.layers_select.push_back({false, cursor.rect}); } + rect_select.x = textbox.color_scheme.gutter_margin; + rect_select.y = pos.y - (textbox.widget.rect_text_size.h * !is_end_of_line_and_text); + rect_select.w = line_wsize + (is_end_of_line_and_text * glyph_wsize) + glyph_wsize; + rect_select.h = textbox.widget.rect_text_size.h; + if ( was_complete_line_selected && ( (current_line_for_cursor_complete != index.y) || - (is_end_of_line_and_text = index.y + 1 == text_total_lines && it + 1 == text_len) + (is_end_of_line_and_text = index.y + 1 == text_total_lines && is_last_char_from_line) ) ) { cursor.rect.x = textbox.color_scheme.gutter_margin; cursor.rect.y = pos.y - (textbox.widget.rect_text_size.h * !is_end_of_line_and_text); - cursor.rect.w = line_wsize + (is_end_of_line_and_text * glyph.wsize) + extra_final_char_cursor; + cursor.rect.w = line_wsize + (is_end_of_line_and_text * glyph_wsize) + glyph_wsize; cursor.rect.h = textbox.widget.rect_text_size.h; textbox.widget.layers_select.push_back({false, cursor.rect}); - was_complete_line_selected = false; + was_complete_line_selected = false; } if (is_complete_line_selected && current_line_for_cursor_complete != index.y) { @@ -756,8 +763,10 @@ void ekg::ui::buffering( } if (is_complete_line_selected) { - line_wsize += glyph.wsize; + line_wsize += glyph_wsize; } + + is_cursor_at_end_of_line = false; } if (!glyph.was_sampled) { @@ -845,6 +854,12 @@ void ekg::ui::buffering( } } + if (was_complete_line_selected) { + rect_select.y += textbox.widget.rect_text_size.h; + textbox.widget.layers_select.push_back({false, rect_select}); + was_complete_line_selected = false; + } + data.hash = hash; ekg::draw::allocator::is_simple_shape = false; From bcbfaa6d5ed4186231ba2157443bdbdb695de7b8 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Fri, 15 Aug 2025 00:13:00 -0300 Subject: [PATCH 13/61] [update] added basic arrow movement for entry --- include/ekg/io/utf.hpp | 2 +- include/ekg/math/geometry.hpp | 8 + include/ekg/ui/textbox/textbox.hpp | 11 +- include/ekg/ui/textbox/widget.hpp | 12 ++ src/ui/textbox/widget.cpp | 244 ++++++++++++++++++++++------- 5 files changed, 216 insertions(+), 61 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 96580ab3..212805a9 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -177,7 +177,7 @@ namespace ekg { size_t length_of_chars(); bool audited(); - void unset_audited(); + b }; } diff --git a/include/ekg/math/geometry.hpp b/include/ekg/math/geometry.hpp index 3f832419..07ee8fb3 100644 --- a/include/ekg/math/geometry.hpp +++ b/include/ekg/math/geometry.hpp @@ -94,6 +94,14 @@ namespace ekg { this->y / div_by }; } + + bool operator == (const ekg::vec2_t &vec) { + return this->x == vec.x && this->y == vec.y; + } + + bool operator != (const ekg::vec2_t &vec) { + return !(*this == vec); + } }; template diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 62beabee..b786df1a 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -54,8 +54,10 @@ namespace ekg { struct cursor_t { public: + size_t highest_char_index {}; ekg::vec2_t a {}; ekg::vec2_t b {}; + ekg::vec2_t delta {}; ekg::rect_t rect {}; public: bool operator == (const ekg::vec2_t &index) { @@ -102,7 +104,6 @@ namespace ekg { ekg::vec2_t picked_left {UINT64_MAX, UINT64_MAX}; ekg::vec2_t picked_right {UINT64_MAX, UINT64_MAX}; - ekg::vec2_t first_pick_pos {}; size_t current_cursor_index {UINT64_MAX}; ekg::timing_t cursor_timing {}; @@ -110,6 +111,14 @@ namespace ekg { bool unset_cursor_static {}; }; + enum class text_input_mode : uint8_t { + none, + text, + ending, + backspace, + del + }; + static constexpr ekg::type type {ekg::type::textbox}; static ekg::textbox_t not_found; public: diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 45390c84..f63779c3 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -41,6 +41,18 @@ namespace ekg::ui { ekg::vec2_t &index ); + void handle_cursor_interact( + ekg::property_t &property, + ekg::textbox_t &textbox, + bool picked_index, + ekg::vec2_t &pick_index, + ekg::input_info_t &input + ); + + void handle_cursor_movement( + ekg::textbox_t &textbox + ); + void reload( ekg::property_t &property, ekg::textbox_t &textbox diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index aa897751..db492f4b 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -185,6 +185,189 @@ bool ekg::ui::find_index_by_interact( return false; } +void ekg::ui::handle_cursor_interact( + ekg::property_t &property, + ekg::textbox_t &textbox, + bool picked_index, + ekg::vec2_t &pick_index, + ekg::input_info_t &input +) { + if (input.was_released && !input.was_typed) { + textbox.widget.current_cursor_index = UINT64_MAX; + } + + if (!picked_index) { + return; + } + + bool should_create_a_cursor {}; + ekg::vec2_t first_pick_pos {}; + + if (input.was_pressed && ekg::fired("textbox-action-cursor")) { + textbox.widget.picked_left = pick_index; + textbox.widget.picked_right = pick_index; + first_pick_pos = pick_index; + textbox.widget.set_cursor_static = true; + should_create_a_cursor = true; + } + + if ( + textbox.widget.current_cursor_index != UINT64_MAX && + textbox.widget.current_cursor_index >= textbox.widget.cursors.size() + ) { + textbox.widget.current_cursor_index = UINT64_MAX; + } + + if (textbox.widget.current_cursor_index != UINT64_MAX) { + ekg::textbox_t::cursor_t &cursor { + textbox.widget.cursors[textbox.widget.current_cursor_index] + }; + + if (ekg::textbox_t::cursor_t {.a = pick_index, .b = pick_index} <= cursor.delta) { + textbox.widget.picked_left = cursor.delta; + textbox.widget.picked_right = pick_index; + } else { + textbox.widget.picked_left = pick_index; + textbox.widget.picked_right = cursor.delta; + } + + cursor.a = textbox.widget.picked_left; + cursor.b = textbox.widget.picked_right; + + ekg::gui.ui.redraw = true; + property.widget.should_buffering = true; + } + + ekg::textbox_t::cursor_t cursor {}; + if ( + should_create_a_cursor + && + !ekg::ui::find_cursor(textbox, pick_index, cursor) + && + ekg::fired("textbox-action-multicursor") + ) { + textbox.widget.current_cursor_index = textbox.widget.cursors.size(); + textbox.widget.cursors.push_back( + { + .highest_char_index = textbox.widget.picked_left.x, + .a = textbox.widget.picked_left, + .b = textbox.widget.picked_right, + .delta = first_pick_pos + } + ); + should_create_a_cursor = false; + } + + if (should_create_a_cursor) { + textbox.widget.current_cursor_index = 0; + textbox.widget.cursors.clear(); + textbox.widget.cursors.push_back( + { + .highest_char_index = textbox.widget.picked_left.x, + .a = textbox.widget.picked_left, + .b = textbox.widget.picked_right, + .delta = first_pick_pos + } + ); + } +} + +void ekg::ui::handle_cursor_movement( + ekg::textbox_t &textbox +) { + bool is_left_fired {ekg::fired("textbox-action-left")}; + bool is_right_fired {ekg::fired("textbox-action-right")}; + bool is_up_fired {ekg::fired("textbox-action-up")}; + bool is_down_fired {ekg::fired("textbox-action-down")}; + + if (!is_left_fired && !is_right_fired && !is_up_fired && !is_down_fired) { + return; + } + + textbox.widget.set_cursor_static = true; + + bool is_ab_equals {}; + bool is_ab_delta_equals {}; + + size_t text_total_lines {textbox.text.length_of_lines()}; + + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + is_ab_equals = cursor.a == cursor.b; + is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; + + if (is_left_fired) { + if (is_ab_equals) { + if (cursor.a.x > 0) { + cursor.a.x -= 1; + } else if (cursor.a.x == 0 && cursor.a.y > 0) { + cursor.a.y -= 1; + cursor.a.x = ekg::utf8_length(textbox.text.at(cursor.a.y)); + } + } + + cursor.highest_char_index = cursor.a.x; + cursor.b = cursor.a; + cursor.delta = cursor.b; + + continue; + } + + if (is_right_fired) { + if (is_ab_equals) { + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; + if (cursor.b.x < line_text_length) { + cursor.b.x += 1; + } else if (cursor.a.y < text_total_lines-1) { + cursor.b.y += 1; + cursor.b.x = 0; + } + } + + cursor.highest_char_index = cursor.b.x; + cursor.a = cursor.b; + cursor.delta = cursor.a; + + continue; + } + + if (is_up_fired) { + if (cursor.a.y > 0) { + cursor.a.y -= 1; + } + + if (is_ab_equals) cursor.a.x = cursor.highest_char_index; + + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; + if (cursor.a.x > line_text_length) { + cursor.a.x = line_text_length; + } + + if (!is_ab_equals) cursor.highest_char_index = cursor.a.x; + + cursor.b = cursor.a; + cursor.delta = cursor.b; + } + + if (is_down_fired) { + if (cursor.b.y < text_total_lines-1) { + cursor.b.y += 1; + } + + if (is_ab_equals) cursor.b.x = cursor.highest_char_index; + + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.b.y))}; + if (cursor.b.x > line_text_length) { + cursor.b.x = line_text_length; + } + + if (!is_ab_equals) cursor.highest_char_index = cursor.b.x; + + cursor.a = cursor.b; + cursor.delta = cursor.a; + } + } +} + void ekg::ui::reload( ekg::property_t &property, ekg::textbox_t &textbox @@ -318,7 +501,6 @@ void ekg::ui::event( ekg::vec2_t pick_index {}; bool should_pick_index {}; bool picked_index {}; - bool should_create_a_cursor {}; if ( property.states.is_focused @@ -352,64 +534,8 @@ void ekg::ui::event( textbox.widget.unset_cursor_static = input.was_released; } - if (picked_index) { - if (input.was_pressed && ekg::fired("textbox-action-cursor")) { - textbox.widget.picked_left = pick_index; - textbox.widget.picked_right = pick_index; - textbox.widget.first_pick_pos = pick_index; - textbox.widget.set_cursor_static = true; - should_create_a_cursor = true; - } - - if (input.was_released && !input.was_typed) { - textbox.widget.current_cursor_index = UINT64_MAX; - } - - if (textbox.widget.current_cursor_index != UINT64_MAX) { - if (ekg::textbox_t::cursor_t {.a = pick_index, .b = pick_index} <= textbox.widget.first_pick_pos) { - textbox.widget.picked_left = textbox.widget.first_pick_pos; - textbox.widget.picked_right = pick_index; - } else { - textbox.widget.picked_left = pick_index; - textbox.widget.picked_right = textbox.widget.first_pick_pos; - } - - if (textbox.widget.current_cursor_index < textbox.widget.cursors.size()) { - ekg::textbox_t::cursor_t &cursor { - textbox.widget.cursors[textbox.widget.current_cursor_index] - }; - - cursor.a = textbox.widget.picked_left; - cursor.b = textbox.widget.picked_right; - - ekg::gui.ui.redraw = true; - property.widget.should_buffering = true; - } - } - - ekg::textbox_t::cursor_t cursor {}; - if (should_create_a_cursor && !ekg::ui::find_cursor(textbox, pick_index, cursor) && ekg::fired("textbox-action-multicursor")) { - textbox.widget.current_cursor_index = textbox.widget.cursors.size(); - textbox.widget.cursors.push_back( - { - .a = textbox.widget.picked_left, - .b = textbox.widget.picked_right - } - ); - should_create_a_cursor = false; - } - - if (should_create_a_cursor) { - textbox.widget.current_cursor_index = 0; - textbox.widget.cursors.clear(); - textbox.widget.cursors.push_back( - { - .a = textbox.widget.picked_left, - .b = textbox.widget.picked_right - } - ); - } - } + ekg::ui::handle_cursor_interact(property, textbox, picked_index, pick_index, input); + ekg::ui::handle_cursor_movement(textbox); if ( should_enable_high_frequency From 41fefef8d30651371534311e89142b4e58808a85 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Fri, 15 Aug 2025 00:23:17 -0300 Subject: [PATCH 14/61] [ref] fixed stupid keyboard --- include/ekg/io/utf.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 212805a9..96580ab3 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -177,7 +177,7 @@ namespace ekg { size_t length_of_chars(); bool audited(); - b + void unset_audited(); }; } From d73fd026b52ec355ff3dc06bb4afdac5039f1ecf Mon Sep 17 00:00:00 2001 From: mrsrina Date: Wed, 20 Aug 2025 01:00:47 -0300 Subject: [PATCH 15/61] [update] added vertical scrolling on cursor movement --- include/ekg/math/floating_point.hpp | 2 -- include/ekg/math/geometry.hpp | 5 +-- include/ekg/ui/textbox/widget.hpp | 1 + src/ui/textbox/widget.cpp | 50 ++++++++++++++++++++++------- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/include/ekg/math/floating_point.hpp b/include/ekg/math/floating_point.hpp index fddebb48..86bbe81c 100644 --- a/include/ekg/math/floating_point.hpp +++ b/include/ekg/math/floating_point.hpp @@ -28,8 +28,6 @@ #include namespace ekg { - constexpr float pi {3.1415927f}; - constexpr bool fequalsf(float compare, float compared) { return ( fabsf(compare - compared) diff --git a/include/ekg/math/geometry.hpp b/include/ekg/math/geometry.hpp index 07ee8fb3..dcaaad95 100644 --- a/include/ekg/math/geometry.hpp +++ b/include/ekg/math/geometry.hpp @@ -31,7 +31,8 @@ namespace ekg { typedef int32_t pixel_thickness_t; typedef float pixel_t; - + + constexpr float pi {3.1415927f}; constexpr float one_pixel {1.0000000f}; constexpr float half_pixel {0.5000000f}; @@ -627,7 +628,7 @@ namespace ekg { } template - using rgba_t =ekg::vec4_t; + using rgba_t = ekg::vec4_t; template constexpr t arithmetic_normalize( diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index f63779c3..d1116a9f 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -50,6 +50,7 @@ namespace ekg::ui { ); void handle_cursor_movement( + ekg::property_t &property, ekg::textbox_t &textbox ); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index db492f4b..b19b7b24 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -81,7 +81,7 @@ bool ekg::ui::find_index_by_interact( ekg::vec2_t pos { static_cast(static_cast(textbox.color_scheme.gutter_margin)) + textbox.widget.scrollbar_property.scroll.position.x, - floorf(static_cast(static_cast(-draw_font.offset_text_height))) + 0.0f }; ekg::vec2_t rendered {}; @@ -273,6 +273,7 @@ void ekg::ui::handle_cursor_interact( } void ekg::ui::handle_cursor_movement( + ekg::property_t &property, ekg::textbox_t &textbox ) { bool is_left_fired {ekg::fired("textbox-action-left")}; @@ -288,9 +289,13 @@ void ekg::ui::handle_cursor_movement( bool is_ab_equals {}; bool is_ab_delta_equals {}; + bool is_bounding {}; size_t text_total_lines {textbox.text.length_of_lines()}; + ekg::rect_t &rect_abs {ekg::ui::get_abs_rect(property, textbox.rect)}; + ekg::vec2_t cursor_pos {}; + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; @@ -308,8 +313,6 @@ void ekg::ui::handle_cursor_movement( cursor.highest_char_index = cursor.a.x; cursor.b = cursor.a; cursor.delta = cursor.b; - - continue; } if (is_right_fired) { @@ -326,45 +329,68 @@ void ekg::ui::handle_cursor_movement( cursor.highest_char_index = cursor.b.x; cursor.a = cursor.b; cursor.delta = cursor.a; - - continue; } if (is_up_fired) { + if (is_ab_equals) cursor.a.x = cursor.highest_char_index; + + is_bounding = true; if (cursor.a.y > 0) { cursor.a.y -= 1; + is_bounding = false; } - if (is_ab_equals) cursor.a.x = cursor.highest_char_index; - size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; if (cursor.a.x > line_text_length) { cursor.a.x = line_text_length; } - if (!is_ab_equals) cursor.highest_char_index = cursor.a.x; + if (is_bounding) { + cursor.highest_char_index = cursor.a.x; + cursor.a.x = 0; + } + + if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.a.x; cursor.b = cursor.a; cursor.delta = cursor.b; } if (is_down_fired) { + if (is_ab_equals) cursor.b.x = cursor.highest_char_index; + + is_bounding = true; if (cursor.b.y < text_total_lines-1) { cursor.b.y += 1; + is_bounding = false; } - if (is_ab_equals) cursor.b.x = cursor.highest_char_index; - size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.b.y))}; if (cursor.b.x > line_text_length) { cursor.b.x = line_text_length; } - if (!is_ab_equals) cursor.highest_char_index = cursor.b.x; + if (is_bounding) { + cursor.highest_char_index = cursor.b.x; + cursor.b.x = line_text_length; + } + + if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.b.x; cursor.a = cursor.b; cursor.delta = cursor.a; } + + cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); + cursor_pos.y = static_cast(static_cast(floorf(cursor_pos.y))); + + if (cursor_pos.y + textbox.widget.rect_text_size.h > rect_abs.h) { + textbox.widget.scrollbar_property.scroll.position.w -= + ekg::min(cursor_pos.y + textbox.widget.rect_text_size.h - rect_abs.h, textbox.widget.rect_text_size.h); + } else if (cursor_pos.y <= 0.0f) { + textbox.widget.scrollbar_property.scroll.position.w += + ekg::min(-cursor_pos.y, textbox.widget.rect_text_size.h); + } } } @@ -535,7 +561,7 @@ void ekg::ui::event( } ekg::ui::handle_cursor_interact(property, textbox, picked_index, pick_index, input); - ekg::ui::handle_cursor_movement(textbox); + ekg::ui::handle_cursor_movement(property, textbox); if ( should_enable_high_frequency From b49bed6fb3d53ca54a1bab4cb527b7b6d8f327ec Mon Sep 17 00:00:00 2001 From: mrsrina Date: Wed, 20 Aug 2025 01:12:43 -0300 Subject: [PATCH 16/61] [fix] clamp rect by square but stupid --- include/ekg/math/geometry.hpp | 8 ++++---- src/handler/theme/handler.cpp | 11 +++++++---- src/ui/frame/widget.cpp | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/include/ekg/math/geometry.hpp b/include/ekg/math/geometry.hpp index dcaaad95..9d147c0c 100644 --- a/include/ekg/math/geometry.hpp +++ b/include/ekg/math/geometry.hpp @@ -31,7 +31,7 @@ namespace ekg { typedef int32_t pixel_thickness_t; typedef float pixel_t; - + constexpr float pi {3.1415927f}; constexpr float one_pixel {1.0000000f}; constexpr float half_pixel {0.5000000f}; @@ -420,12 +420,12 @@ namespace ekg { ekg::rect_t rect, float square ) { - const t zero {}; + const t zero {}; return ekg::rect_t { ekg::clamp_min(rect.x, zero), ekg::clamp_min(rect.y, zero), - ekg::clamp_max(rect.w, square), - ekg::clamp_max(rect.h, square) + ekg::clamp_min(rect.w, square), + ekg::clamp_min(rect.h, square) }; } diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index 0144fec2..5f74dc3e 100644 --- a/src/handler/theme/handler.cpp +++ b/src/handler/theme/handler.cpp @@ -134,12 +134,15 @@ void ekg::handler::theme::init() { black_light_pinky_theme.popup_color_scheme = black_light_pinky_theme.frame_color_scheme; black_light_pinky_theme.popup_color_scheme.popup_mode = true; - light_pinky_theme.textbox_color_scheme.background = {204, 204, 204, 50}; - light_pinky_theme.textbox_color_scheme.outline = {190, 190, 190, 0}; - light_pinky_theme.textbox_color_scheme.text_foreground = {141, 141, 141, 255}; + black_light_pinky_theme.textbox_color_scheme.background = {242, 242, 242, 255}; + black_light_pinky_theme.textbox_color_scheme.outline = {190, 190, 190, 100}; + black_light_pinky_theme.textbox_color_scheme.text_foreground = {141, 141, 141, 255}; + black_light_pinky_theme.textbox_color_scheme.text_cursor_foreground = {141, 141, 141, 255}; + black_light_pinky_theme.textbox_color_scheme.text_select_foreground = {245, 169, 184, 50}; + black_light_pinky_theme.textbox_color_scheme.text_select_outline = {245, 169, 184, 100}; this->registry(black_light_pinky_theme.tag) = black_light_pinky_theme; - //this->set_current_theme(black_light_pinky_theme.tag); + this->set_current_theme(black_light_pinky_theme.tag); } void ekg::handler::theme::quit() { diff --git a/src/ui/frame/widget.cpp b/src/ui/frame/widget.cpp index d963b3df..cb49928d 100644 --- a/src/ui/frame/widget.cpp +++ b/src/ui/frame/widget.cpp @@ -199,7 +199,7 @@ void ekg::ui::event( } } - ekg::clamp_rect_by_square( + new_rect = ekg::clamp_rect_by_square( new_rect, ekg::dpi.min_sizes ); From c6c0add47b47b8ce7e4e63602a667f501d31a03b Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sat, 23 Aug 2025 18:04:29 -0300 Subject: [PATCH 17/61] [update] only-one loop for cursors --- include/ekg/ui/textbox/widget.hpp | 5 - src/ui/textbox/widget.cpp | 242 +++++++++++++++--------------- 2 files changed, 125 insertions(+), 122 deletions(-) diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index d1116a9f..7edcffa7 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -49,11 +49,6 @@ namespace ekg::ui { ekg::input_info_t &input ); - void handle_cursor_movement( - ekg::property_t &property, - ekg::textbox_t &textbox - ); - void reload( ekg::property_t &property, ekg::textbox_t &textbox diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index b19b7b24..975b4955 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -276,122 +276,7 @@ void ekg::ui::handle_cursor_movement( ekg::property_t &property, ekg::textbox_t &textbox ) { - bool is_left_fired {ekg::fired("textbox-action-left")}; - bool is_right_fired {ekg::fired("textbox-action-right")}; - bool is_up_fired {ekg::fired("textbox-action-up")}; - bool is_down_fired {ekg::fired("textbox-action-down")}; - if (!is_left_fired && !is_right_fired && !is_up_fired && !is_down_fired) { - return; - } - - textbox.widget.set_cursor_static = true; - - bool is_ab_equals {}; - bool is_ab_delta_equals {}; - bool is_bounding {}; - - size_t text_total_lines {textbox.text.length_of_lines()}; - - ekg::rect_t &rect_abs {ekg::ui::get_abs_rect(property, textbox.rect)}; - ekg::vec2_t cursor_pos {}; - - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - is_ab_equals = cursor.a == cursor.b; - is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; - - if (is_left_fired) { - if (is_ab_equals) { - if (cursor.a.x > 0) { - cursor.a.x -= 1; - } else if (cursor.a.x == 0 && cursor.a.y > 0) { - cursor.a.y -= 1; - cursor.a.x = ekg::utf8_length(textbox.text.at(cursor.a.y)); - } - } - - cursor.highest_char_index = cursor.a.x; - cursor.b = cursor.a; - cursor.delta = cursor.b; - } - - if (is_right_fired) { - if (is_ab_equals) { - size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; - if (cursor.b.x < line_text_length) { - cursor.b.x += 1; - } else if (cursor.a.y < text_total_lines-1) { - cursor.b.y += 1; - cursor.b.x = 0; - } - } - - cursor.highest_char_index = cursor.b.x; - cursor.a = cursor.b; - cursor.delta = cursor.a; - } - - if (is_up_fired) { - if (is_ab_equals) cursor.a.x = cursor.highest_char_index; - - is_bounding = true; - if (cursor.a.y > 0) { - cursor.a.y -= 1; - is_bounding = false; - } - - size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; - if (cursor.a.x > line_text_length) { - cursor.a.x = line_text_length; - } - - if (is_bounding) { - cursor.highest_char_index = cursor.a.x; - cursor.a.x = 0; - } - - if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.a.x; - - cursor.b = cursor.a; - cursor.delta = cursor.b; - } - - if (is_down_fired) { - if (is_ab_equals) cursor.b.x = cursor.highest_char_index; - - is_bounding = true; - if (cursor.b.y < text_total_lines-1) { - cursor.b.y += 1; - is_bounding = false; - } - - size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.b.y))}; - if (cursor.b.x > line_text_length) { - cursor.b.x = line_text_length; - } - - if (is_bounding) { - cursor.highest_char_index = cursor.b.x; - cursor.b.x = line_text_length; - } - - if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.b.x; - - cursor.a = cursor.b; - cursor.delta = cursor.a; - } - - cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); - cursor_pos.y = static_cast(static_cast(floorf(cursor_pos.y))); - - if (cursor_pos.y + textbox.widget.rect_text_size.h > rect_abs.h) { - textbox.widget.scrollbar_property.scroll.position.w -= - ekg::min(cursor_pos.y + textbox.widget.rect_text_size.h - rect_abs.h, textbox.widget.rect_text_size.h); - } else if (cursor_pos.y <= 0.0f) { - textbox.widget.scrollbar_property.scroll.position.w += - ekg::min(-cursor_pos.y, textbox.widget.rect_text_size.h); - } - } } void ekg::ui::reload( @@ -560,8 +445,13 @@ void ekg::ui::event( textbox.widget.unset_cursor_static = input.was_released; } - ekg::ui::handle_cursor_interact(property, textbox, picked_index, pick_index, input); - ekg::ui::handle_cursor_movement(property, textbox); + ekg::ui::handle_cursor_interact( + property, + textbox, + picked_index, + pick_index, + input + ); if ( should_enable_high_frequency @@ -572,6 +462,124 @@ void ekg::ui::event( ); } + /* logic of cursors, for handling lot of curosrs we will use only one loop for improve performance */ + + bool is_left_fired {ekg::fired("textbox-action-left")}; + bool is_right_fired {ekg::fired("textbox-action-right")}; + bool is_up_fired {ekg::fired("textbox-action-up")}; + bool is_down_fired {ekg::fired("textbox-action-down")}; + + if (!is_left_fired && !is_right_fired && !is_up_fired && !is_down_fired) { + textbox.widget.set_cursor_static = true; + } + + bool is_ab_equals {}; + bool is_ab_delta_equals {}; + bool is_bounding {}; + + size_t text_total_lines {textbox.text.length_of_lines()}; + + ekg::rect_t &rect_abs {ekg::ui::get_abs_rect(property, textbox.rect)}; + ekg::vec2_t cursor_pos {}; + + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + is_ab_equals = cursor.a == cursor.b; + is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; + + if (is_left_fired) { + if (is_ab_equals) { + if (cursor.a.x > 0) { + cursor.a.x -= 1; + } else if (cursor.a.x == 0 && cursor.a.y > 0) { + cursor.a.y -= 1; + cursor.a.x = ekg::utf8_length(textbox.text.at(cursor.a.y)); + } + } + + cursor.highest_char_index = cursor.a.x; + cursor.b = cursor.a; + cursor.delta = cursor.b; + } + + + if (is_right_fired) { + if (is_ab_equals) { + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; + if (cursor.b.x < line_text_length) { + cursor.b.x += 1; + } else if (cursor.a.y < text_total_lines-1) { + cursor.b.y += 1; + cursor.b.x = 0; + } + } + + cursor.highest_char_index = cursor.b.x; + cursor.a = cursor.b; + cursor.delta = cursor.a; + } + + if (is_up_fired) { + if (is_ab_equals) cursor.a.x = cursor.highest_char_index; + + is_bounding = true; + if (cursor.a.y > 0) { + cursor.a.y -= 1; + is_bounding = false; + } + + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; + if (cursor.a.x > line_text_length) { + cursor.a.x = line_text_length; + } + + if (is_bounding) { + cursor.highest_char_index = cursor.a.x; + cursor.a.x = 0; + } + + if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.a.x; + + cursor.b = cursor.a; + cursor.delta = cursor.b; + } + + if (is_down_fired) { + if (is_ab_equals) cursor.b.x = cursor.highest_char_index; + + is_bounding = true; + if (cursor.b.y < text_total_lines-1) { + cursor.b.y += 1; + is_bounding = false; + } + + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.b.y))}; + if (cursor.b.x > line_text_length) { + cursor.b.x = line_text_length; + } + + if (is_bounding) { + cursor.highest_char_index = cursor.b.x; + cursor.b.x = line_text_length; + } + + if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.b.x; + + cursor.a = cursor.b; + cursor.delta = cursor.a; + } + + cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); + cursor_pos.y = static_cast(static_cast(floorf(cursor_pos.y))); + + if (cursor_pos.y + textbox.widget.rect_text_size.h > rect_abs.h) { + textbox.widget.scrollbar_property.scroll.position.w -= + ekg::min(cursor_pos.y + textbox.widget.rect_text_size.h - rect_abs.h, textbox.widget.rect_text_size.h); + } else if (cursor_pos.y <= 0.0f) { + textbox.widget.scrollbar_property.scroll.position.w += + ekg::min(-cursor_pos.y, textbox.widget.rect_text_size.h); + } + } + break; } case ekg::io::stage::pre: From ef7fc479cdafddd9756aaa7c6650a307a244a2d8 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 24 Aug 2025 01:14:56 -0300 Subject: [PATCH 18/61] [update] regex options for op(s) --- include/ekg/ui/textbox/textbox.hpp | 12 +++++++++++ src/handler/input/handler.cpp | 34 +++++++++++++++++++++++++++--- src/ui/textbox/widget.cpp | 19 ++++++++++------- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index b786df1a..f1c53d9f 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -29,6 +29,7 @@ #include "ekg/io/utf.hpp" #include "ekg/io/font.hpp" #include "ekg/ui/property.hpp" +#include namespace ekg { struct textbox_color_scheme_t { @@ -46,6 +47,12 @@ namespace ekg { struct textbox_t { public: + enum operation : size_t { + modifier_left, + modifier_right + }; + static constexpr size_t operation_enum_size {ekg::textbox_t::operation::modifier_right+1}; + struct select_draw_layer_t { public: bool is_ab_equals {}; @@ -132,6 +139,11 @@ namespace ekg { ekg::textbox_color_scheme_t color_scheme {}; ekg::textbox_t::widget_t widget {}; ekg::at_array_t layers {}; + + std::array regex_operations { + std::regex(" +|:+|&+|\\(\\)|\\(|\\)|;+"), + std::regex(" +|:+|&+|\\(\\)|\\(|\\)|;+") + }; public: ekg_descriptor(ekg::textbox_t); }; diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index bfd72662..c821a892 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -135,7 +135,8 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-action-select-all", "rctrl+a"); this->insert_input_bind("textbox-action-select-all-inline", "mouse-1"); - this->insert_input_bind("textbox-action-select", "lshift"); + + this->insert_input_bind("textbox-action-select", "rshift"); this->insert_input_bind("textbox-action-select", "rshift"); this->insert_input_bind("textbox-action-select-word", "mouse-1-double"); @@ -153,10 +154,37 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-action-multicursor", "lctrl+mouse-1"); this->insert_input_bind("textbox-action-multicursor", "rctrl+mouse-1"); - this->insert_input_bind("textbox-action-up", "abs-up"); this->insert_input_bind("textbox-action-down", "abs-down"); - this->insert_input_bind("textbox-action-right", "abs-right"); + this->insert_input_bind("textbox-action-select", "lshift+down"); + this->insert_input_bind("textbox-action-select", "rshift+down"); + this->insert_input_bind("textbox-action-modifier-down", "lctrl+down"); + this->insert_input_bind("textbox-action-modifier-down", "rctrl+down"); + + this->insert_input_bind("textbox-action-down", "abs-down"); + this->insert_input_bind("textbox-action-select", "lshift+down"); + this->insert_input_bind("textbox-action-select", "rshift+down"); + this->insert_input_bind("textbox-action-modifier-down", "lctrl+down"); + this->insert_input_bind("textbox-action-modifier-down", "rctrl+down"); + + this->insert_input_bind("textbox-action-right", "abs-left"); + this->insert_input_bind("textbox-action-select", "lctrl+lshift+right"); + this->insert_input_bind("textbox-action-select", "lctrl+rshift+right"); + this->insert_input_bind("textbox-action-select", "rctrl+lshift+right"); + this->insert_input_bind("textbox-action-select", "rctrl+rshift+right"); + this->insert_input_bind("textbox-action-select", "lshift+right"); + this->insert_input_bind("textbox-action-select", "rshift+right"); + this->insert_input_bind("textbox-action-modifier-right", "lctrl+right"); + this->insert_input_bind("textbox-action-modifier-right", "rctrl+right"); + this->insert_input_bind("textbox-action-left", "abs-left"); + this->insert_input_bind("textbox-action-select", "lctrl+lshift+left"); + this->insert_input_bind("textbox-action-select", "lctrl+rshift+left"); + this->insert_input_bind("textbox-action-select", "rctrl+lshift+left"); + this->insert_input_bind("textbox-action-select", "rctrl+rshift+left"); + this->insert_input_bind("textbox-action-select", "lshift+left"); + this->insert_input_bind("textbox-action-select", "rshift+left"); + this->insert_input_bind("textbox-action-modifier-left", "lctrl+left"); + this->insert_input_bind("textbox-action-modifier-left", "rctrl+left"); this->insert_input_bind("clipboard-copy", "lctrl+c"); this->insert_input_bind("clipboard-copy", "rctrl+c"); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 975b4955..cb286463 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -272,13 +272,6 @@ void ekg::ui::handle_cursor_interact( } } -void ekg::ui::handle_cursor_movement( - ekg::property_t &property, - ekg::textbox_t &textbox -) { - -} - void ekg::ui::reload( ekg::property_t &property, ekg::textbox_t &textbox @@ -465,9 +458,14 @@ void ekg::ui::event( /* logic of cursors, for handling lot of curosrs we will use only one loop for improve performance */ bool is_left_fired {ekg::fired("textbox-action-left")}; + bool is_modifier_left_fired {is_left_fired && ekg::fired("textbox-action-modifier-left")}; bool is_right_fired {ekg::fired("textbox-action-right")}; + bool is_modifier_right_fired {is_right_fired && ekg::fired("textbox-action-modifier-right")}; bool is_up_fired {ekg::fired("textbox-action-up")}; + bool is_modifier_up_fired {is_up_fired && ekg::fired("textbox-action-modifier-up")}; bool is_down_fired {ekg::fired("textbox-action-down")}; + bool is_modifier_down_fired {ekg::fired("textbox-action-modifier-down")}; + bool is_action_selected_fired {is_modifier_down_fired && ekg::fired("textbox-action-select")}; if (!is_left_fired && !is_right_fired && !is_up_fired && !is_down_fired) { textbox.widget.set_cursor_static = true; @@ -496,12 +494,17 @@ void ekg::ui::event( } } + if (is_modifier_left_fired) { + std::string line {textbox.text.at(cursor.a.y)}; + std::regex ®ex {textbox.regex_operations[ekg::textbox_t::operation::modifier_left]}; + + } + cursor.highest_char_index = cursor.a.x; cursor.b = cursor.a; cursor.delta = cursor.b; } - if (is_right_fired) { if (is_ab_equals) { size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; From 6a9799dcb17007c0673de4235632f9ac1a46b327 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 24 Aug 2025 23:28:32 -0300 Subject: [PATCH 19/61] [fix] regex --- src/ui/textbox/widget.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index cb286463..af7d11f5 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -479,6 +479,7 @@ void ekg::ui::event( ekg::rect_t &rect_abs {ekg::ui::get_abs_rect(property, textbox.rect)}; ekg::vec2_t cursor_pos {}; + std::sregex_iterator end {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; @@ -496,8 +497,13 @@ void ekg::ui::event( if (is_modifier_left_fired) { std::string line {textbox.text.at(cursor.a.y)}; - std::regex ®ex {textbox.regex_operations[ekg::textbox_t::operation::modifier_left]}; - + std::sregex_iterator it( + line.begin(), + line.end(), + textbox.regex_operations[ekg::textbox_t::operation::modifier_left] + ); + + std::smatch j = *it; } cursor.highest_char_index = cursor.a.x; From 602052ff86e096e1d92674ab9e1387a45cff3d60 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Thu, 28 Aug 2025 01:17:18 -0300 Subject: [PATCH 20/61] [fix] regex search --- include/ekg/io/utf.hpp | 40 +++++++++++++ include/ekg/ui/textbox/textbox.hpp | 4 +- src/handler/input/handler.cpp | 12 ++-- src/io/utf.cpp | 64 ++++++++++++++++++++ src/ui/textbox/widget.cpp | 94 +++++++++++++++++++++++++++--- 5 files changed, 199 insertions(+), 15 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 96580ab3..422c09ce 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -57,6 +57,46 @@ namespace ekg { size_t &it ); + /** + * @brief + * find for a aligned utf position by byte position + * + * @param `string` string for find utf position + * @param `byte_pos` byte pos used for find utf position + * @param `utf_pos` utf position relative to byte position + * + * @description + * if no utf position was found then `utf_pos` keeps unchanged. + * if a invalid byte position is passed, e.g a position inside a + * utf sequence, it return atuomatically `false`. + * + * @return `true` if a valid utf position was found else `false` if was not found + **/ + bool utf8_find_utf_pos_by_byte_pos( + std::string &string, + size_t byte_pos, + size_t &utf_pos + ); + + /** + * @brief + * find for a byte pos from an aligned utf position + * + * @param `string` string for find utf position + * @param `utf_pos` utf pos used for find byte pos + * @param `byte_pos` byte pos relative to utf position + * + * @description + * if no byte position was found then `utf_byte` keeps unchanged. + * + * @return `true` if a valid byte position was found else `false` if was not found + **/ + bool utf8_find_byte_pos_by_utf_pos( + std::string &string, + size_t utf_pos, + size_t &byte_pos + ); + /** * Returns a UTF string by `char32` converting * the UTF-32 unique char into a sequence of UTF-8 diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index f1c53d9f..fe91caba 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -141,8 +141,8 @@ namespace ekg { ekg::at_array_t layers {}; std::array regex_operations { - std::regex(" +|:+|&+|\\(\\)|\\(|\\)|;+"), - std::regex(" +|:+|&+|\\(\\)|\\(|\\)|;+") + std::regex("^| +|:+|&+|\\(\\)|\\(|\\)|;+"), + std::regex("$| +|:+|&+|\\(\\)|\\(|\\)|;+") }; public: ekg_descriptor(ekg::textbox_t); diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index c821a892..f1ef6f88 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -154,11 +154,11 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-action-multicursor", "lctrl+mouse-1"); this->insert_input_bind("textbox-action-multicursor", "rctrl+mouse-1"); - this->insert_input_bind("textbox-action-down", "abs-down"); - this->insert_input_bind("textbox-action-select", "lshift+down"); - this->insert_input_bind("textbox-action-select", "rshift+down"); - this->insert_input_bind("textbox-action-modifier-down", "lctrl+down"); - this->insert_input_bind("textbox-action-modifier-down", "rctrl+down"); + this->insert_input_bind("textbox-action-up", "abs-up"); + this->insert_input_bind("textbox-action-select", "lshift+up"); + this->insert_input_bind("textbox-action-select", "rshift+up"); + this->insert_input_bind("textbox-action-modifier-up", "lctrl+up"); + this->insert_input_bind("textbox-action-modifier-up", "rctrl+up"); this->insert_input_bind("textbox-action-down", "abs-down"); this->insert_input_bind("textbox-action-select", "lshift+down"); @@ -166,7 +166,7 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-action-modifier-down", "lctrl+down"); this->insert_input_bind("textbox-action-modifier-down", "rctrl+down"); - this->insert_input_bind("textbox-action-right", "abs-left"); + this->insert_input_bind("textbox-action-right", "abs-right"); this->insert_input_bind("textbox-action-select", "lctrl+lshift+right"); this->insert_input_bind("textbox-action-select", "lctrl+rshift+right"); this->insert_input_bind("textbox-action-select", "rctrl+lshift+right"); diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 0f0c6292..ef71ac22 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -50,6 +50,70 @@ void ekg::utf8_sequence( } } +bool ekg::utf8_find_utf_pos_by_byte_pos( + std::string &string, + size_t byte_pos, + size_t &utf_pos +) { + size_t utf_sequence_size {}; + size_t string_size {string.size()}; + size_t utf_pos_count {}; + for (size_t it {}; it < string_size; it++) { + if (it == byte_pos) { + utf_pos = utf_pos_count; + return true; + } + + char &char8 {string.at(it)}; + utf_sequence_size = 0; + utf_sequence_size += ((char8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); + + if (it > byte_pos && it + utf_sequence_size < byte_pos) { + return false; + } + + it += utf_sequence_size; + utf_pos_count++; + + if (it == byte_pos) { + utf_pos = utf_pos_count; + return true; + } + } + + return false; +} + +bool ekg::utf8_find_byte_pos_by_utf_pos( + std::string &string, + size_t utf_pos, + size_t &byte_pos +) { + size_t utf_sequence_size {}; + size_t string_size {string.size()}; + size_t utf_pos_count {}; + + for (size_t it {}; it < string_size; it++) { + if (utf_pos_count == utf_pos) { + byte_pos = it; + return true; + } + + char &char8 {string.at(it)}; + utf_sequence_size = 0; + utf_sequence_size += ((char8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); + + it += utf_sequence_size; + utf_pos_count++; + } + + return false; +} + uint64_t ekg::utf8_check_sequence( uint8_t &char8, char32_t &char32, diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index af7d11f5..dd514563 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -464,10 +464,10 @@ void ekg::ui::event( bool is_up_fired {ekg::fired("textbox-action-up")}; bool is_modifier_up_fired {is_up_fired && ekg::fired("textbox-action-modifier-up")}; bool is_down_fired {ekg::fired("textbox-action-down")}; - bool is_modifier_down_fired {ekg::fired("textbox-action-modifier-down")}; + bool is_modifier_down_fired {is_down_fired && ekg::fired("textbox-action-modifier-down")}; bool is_action_selected_fired {is_modifier_down_fired && ekg::fired("textbox-action-select")}; - if (!is_left_fired && !is_right_fired && !is_up_fired && !is_down_fired) { + if (is_left_fired || is_right_fired || is_up_fired || is_down_fired) { textbox.widget.set_cursor_static = true; } @@ -475,10 +475,17 @@ void ekg::ui::event( bool is_ab_delta_equals {}; bool is_bounding {}; + size_t utf_position_count {}; + size_t utf_sequence_size {}; + size_t new_cursor_byte_pos {}; + size_t cursor_byte_pos {}; size_t text_total_lines {textbox.text.length_of_lines()}; + size_t byte_pos {}; ekg::rect_t &rect_abs {ekg::ui::get_abs_rect(property, textbox.rect)}; ekg::vec2_t cursor_pos {}; + + std::string line {}; std::sregex_iterator end {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { @@ -495,15 +502,45 @@ void ekg::ui::event( } } - if (is_modifier_left_fired) { - std::string line {textbox.text.at(cursor.a.y)}; - std::sregex_iterator it( + if ( + is_modifier_left_fired + && + ekg::utf8_find_byte_pos_by_utf_pos( + (line = textbox.text.at(cursor.a.y)), + cursor.a.x, + cursor_byte_pos + ) + ) { + std::sregex_iterator iterator( line.begin(), - line.end(), + line.begin() + cursor_byte_pos, textbox.regex_operations[ekg::textbox_t::operation::modifier_left] ); - std::smatch j = *it; + new_cursor_byte_pos = UINT64_MAX; + std::sregex_iterator end {}; + for (auto it = iterator; it != end; it++) { + std::smatch j = *it; + new_cursor_byte_pos = j.position(); + ekg_log_low_level(new_cursor_byte_pos << " " << j.prefix().str()) + } + + if (new_cursor_byte_pos != UINT64_MAX) { + byte_pos = new_cursor_byte_pos; + utf_position_count = 0; + while (byte_pos < cursor_byte_pos) { + char &char8 {line.at(byte_pos)}; + utf_sequence_size = 1; + utf_sequence_size += ((char8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); + byte_pos += utf_sequence_size; + utf_position_count++; + } + + ekg_log_low_level(utf_position_count) + cursor.a.x -= utf_position_count; + } } cursor.highest_char_index = cursor.a.x; @@ -522,6 +559,49 @@ void ekg::ui::event( } } + if ( + is_modifier_right_fired + && + ekg::utf8_find_byte_pos_by_utf_pos( + (line = textbox.text.at(cursor.b.y)), + cursor.b.x, + cursor_byte_pos + ) + ) { + std::sregex_iterator iterator( + line.begin() + cursor_byte_pos, + line.end(), + textbox.regex_operations[ekg::textbox_t::operation::modifier_right] + ); + + new_cursor_byte_pos = UINT64_MAX; + std::sregex_iterator end {}; + for (auto it = iterator; it != end; it++) { + std::smatch j = *it; + new_cursor_byte_pos = cursor_byte_pos + j.position(); + ekg_log_low_level(new_cursor_byte_pos << " v " << j.prefix().str()) + break; + } + + if (new_cursor_byte_pos != UINT64_MAX) { + byte_pos = cursor_byte_pos; + utf_position_count = 0; + ekg_log_low_level(byte_pos << " x " << new_cursor_byte_pos) + while (byte_pos < new_cursor_byte_pos) { + char &char8 {line.at(byte_pos)}; + utf_sequence_size = 1; + utf_sequence_size += ((char8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); + byte_pos += utf_sequence_size; + utf_position_count++; + } + + ekg_log_low_level(utf_position_count) + cursor.b.x += utf_position_count; + } + } + cursor.highest_char_index = cursor.b.x; cursor.a = cursor.b; cursor.delta = cursor.a; From 7a98c09c5f0580ad64c9fbcb3c980adc967a590a Mon Sep 17 00:00:00 2001 From: mrsrina Date: Fri, 29 Aug 2025 00:59:55 -0300 Subject: [PATCH 21/61] [update] reg --- include/ekg/ui/textbox/textbox.hpp | 4 ++-- src/ui/textbox/widget.cpp | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index fe91caba..6fd9ef5e 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -141,8 +141,8 @@ namespace ekg { ekg::at_array_t layers {}; std::array regex_operations { - std::regex("^| +|:+|&+|\\(\\)|\\(|\\)|;+"), - std::regex("$| +|:+|&+|\\(\\)|\\(|\\)|;+") + std::regex("^[ \t:;]|[^ \t:;]+|:+|&+|\\(\\)|\\(|\\)|;+"), + std::regex("$|[ \t:;]+|:+|&+|\\(\\)|\\(|\\)|;+") }; public: ekg_descriptor(ekg::textbox_t); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index dd514563..fc9d628b 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -402,6 +402,10 @@ void ekg::ui::event( return; } + if (property.states.is_hovering) { + ekg::p_core->p_platform_base->system_cursor = ekg::system_cursor::ibeam; + } + ekg::vec2_t pick_index {}; bool should_pick_index {}; bool picked_index {}; From 9d91e4f80fd6aabf9326e5506e95190d6a586b68 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Mon, 1 Sep 2025 00:25:51 -0300 Subject: [PATCH 22/61] [update] added regex movement --- include/ekg/io/utf.hpp | 47 +++++++++ include/ekg/ui/textbox/textbox.hpp | 4 +- src/io/utf.cpp | 162 ++++++++++++++++++++++------- src/ui/textbox/widget.cpp | 112 +++++++------------- 4 files changed, 214 insertions(+), 111 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 422c09ce..c432152b 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace ekg { /** @@ -97,6 +98,52 @@ namespace ekg { size_t &byte_pos ); + /** + * @brief + * align utf position and byute-position by the next byte-position + * + * @param `string` string for be used in alignment + * @param `unaligned_byte_pos` byte pos to be aligned + * @param `unaligned_utf_pos` utf pos to be aligned + * @param `next_byte_pos` next byte pos to align byte-pos and utf-pos + * + * @description + * if next byte pos is greater than unaligned byte pos, then move to left + * if not move to right + * + * may output be corrupted if current unaligned byte-pos is an invalid utf position + * + * @return true if could align, else false if could not + **/ + bool utf8_align_utf_pos_by_byte_pos( + std::string &string, + size_t &unaligned_byte_pos, + size_t &unaligned_utf_pos, + size_t next_byte_pos + ); + + /** + * @brief + * get the first nearest group matched byte-position + * + * @param `begin` const string iterator to begin regex matching + * @param `end` const string iterator to end regex matching + * @param `nearest_byte_pos` first group matched bidirectional byte-position + * + * @description + * for left dock the first matched group position is assigned, + * for right dock the last matched group position is assigned + * + * @return true if matched else false if not matched + **/ + bool utf8_nearest_regex_group_matched_position( + const std::string::const_iterator &begin, + const std::string::const_iterator &end, + size_t &nearest_byte_pos, + std::regex &pattern, + const ekg::dock &dock + ); + /** * Returns a UTF string by `char32` converting * the UTF-32 unique char into a sequence of UTF-8 diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 6fd9ef5e..a67125ce 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -141,8 +141,8 @@ namespace ekg { ekg::at_array_t layers {}; std::array regex_operations { - std::regex("^[ \t:;]|[^ \t:;]+|:+|&+|\\(\\)|\\(|\\)|;+"), - std::regex("$|[ \t:;]+|:+|&+|\\(\\)|\\(|\\)|;+") + std::regex("(^)|([^\\s:&\\(\\);]+)|(:+)|(&+)|(\\(\\))|(\\()|(\\))|(;+)"), + std::regex("[^\\s:&\\(\\);)]([\\s:])|(:+)|(&+)|(\\(\\))|(\\()|(\\))|(;+)|($)") }; public: ekg_descriptor(ekg::textbox_t); diff --git a/src/io/utf.cpp b/src/io/utf.cpp index ef71ac22..a87b4d44 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -27,6 +27,98 @@ #include #include +bool ekg::utf8_align_utf_pos_by_byte_pos( + std::string &string, + size_t &unaligned_byte_pos, + size_t &unaligned_utf_pos, + size_t next_byte_pos +) { + if (string.empty() || next_byte_pos == unaligned_byte_pos) { + return false; + } + + ekg::dock axis { + next_byte_pos > unaligned_byte_pos + ? ekg::dock::left : ekg::dock::right + }; + + size_t byte_pos {}; + size_t utf_pos_count {}; + size_t utf_sequence_size {}; + + switch (axis) { + case ekg::dock::left: + byte_pos = unaligned_byte_pos; + while (byte_pos < next_byte_pos) { + char &c8 {string.at(byte_pos)}; + utf_sequence_size = 1; + utf_sequence_size += ((c8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((c8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((c8 & 0xF8) == 0xF0); + byte_pos += utf_sequence_size; + unaligned_utf_pos++; + } + + unaligned_byte_pos = next_byte_pos; + + break; + case ekg::dock::right: + byte_pos = next_byte_pos; + while (byte_pos < unaligned_byte_pos) { + char &c8 {string.at(byte_pos)}; + utf_sequence_size = 1; + utf_sequence_size += ((c8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((c8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((c8 & 0xF8) == 0xF0); + byte_pos += utf_sequence_size; + unaligned_utf_pos--; + } + + unaligned_byte_pos = next_byte_pos; + + break; + default: + break; + } + + return true; +} + +bool ekg::utf8_nearest_regex_group_matched_position( + const std::string::const_iterator &begin, + const std::string::const_iterator &end, + size_t &nearest_byte_pos, + std::regex &pattern, + const ekg::dock &dock +) { + std::sregex_iterator sregex_it(begin, end, pattern); + std::sregex_iterator sregex_it_end {}; + + if (sregex_it == sregex_it_end) { + return false; + } + + std::smatch match {}; + size_t groups_size {}; + + bool any_matched {}; + bool is_right {dock == ekg::dock::right}; + + for (; sregex_it != sregex_it_end; sregex_it++) { + match = *sregex_it; + groups_size = match.size(); + for (size_t i {1}; i < groups_size; i++) { + if (match[i].matched) { + any_matched = true; + nearest_byte_pos = match.position(i); + if (is_right) return true; + } + } + } + + return any_matched; +} + void ekg::utf8_sequence( uint8_t &uc8, char32_t &c32, @@ -64,11 +156,11 @@ bool ekg::utf8_find_utf_pos_by_byte_pos( return true; } - char &char8 {string.at(it)}; + char &c8 {string.at(it)}; utf_sequence_size = 0; - utf_sequence_size += ((char8 & 0xE0) == 0xC0); - utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); - utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); + utf_sequence_size += ((c8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((c8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((c8 & 0xF8) == 0xF0); if (it > byte_pos && it + utf_sequence_size < byte_pos) { return false; @@ -101,11 +193,11 @@ bool ekg::utf8_find_byte_pos_by_utf_pos( return true; } - char &char8 {string.at(it)}; + char &c8 {string.at(it)}; utf_sequence_size = 0; - utf_sequence_size += ((char8 & 0xE0) == 0xC0); - utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); - utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); + utf_sequence_size += ((c8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((c8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((c8 & 0xF8) == 0xF0); it += utf_sequence_size; utf_pos_count++; @@ -115,25 +207,25 @@ bool ekg::utf8_find_byte_pos_by_utf_pos( } uint64_t ekg::utf8_check_sequence( - uint8_t &char8, + uint8_t &c8, char32_t &char32, std::string &utf_string, std::string_view string, uint64_t index ) { - if (char8 <= 0x7F) { - utf_string = char8; - char32 = static_cast(char8); + if (c8 <= 0x7F) { + utf_string = c8; + char32 = static_cast(c8); return 0; - } else if ((char8 & 0xE0) == 0xC0) { + } else if ((c8 & 0xE0) == 0xC0) { utf_string = string.substr(index, 2); char32 = ekg::utf8_to_utf32(utf_string); return 1; - } else if ((char8 & 0xF0) == 0xE0) { + } else if ((c8 & 0xF0) == 0xE0) { utf_string = string.substr(index, 3); char32 = ekg::utf8_to_utf32(utf_string); return 2; - } else if ((char8 & 0xF8) == 0xF0) { + } else if ((c8 & 0xF8) == 0xF0) { utf_string = string.substr(index, 4); char32 = ekg::utf8_to_utf32(utf_string); return 3; @@ -172,19 +264,19 @@ char32_t ekg::utf8_to_utf32(std::string_view string) { char32_t char32 {}; uint64_t it {}; - uint8_t char8 {static_cast(string.at(0))}; + uint8_t c8 {static_cast(string.at(0))}; - if (char8 <= 0x7F) { - char32 = char8; - } else if (char8 <= 0xDF) { - char32 = char8 & 0x1F; + if (c8 <= 0x7F) { + char32 = c8; + } else if (c8 <= 0xDF) { + char32 = c8 & 0x1F; char32 = (char32 << 6) | (string.at(++it) & 0x3F); - } else if (char8 <= 0xEF) { - char32 = char8 & 0x0F; + } else if (c8 <= 0xEF) { + char32 = c8 & 0x0F; char32 = (char32 << 6) | (string.at(++it) & 0x3F); char32 = (char32 << 6) | (string.at(++it) & 0x3F); } else { - char32 = char8 & 0x07; + char32 = c8 & 0x07; char32 = (char32 << 6) | (string.at(++it) & 0x3F); char32 = (char32 << 6) | (string.at(++it) & 0x3F); char32 = (char32 << 6) | (string.at(++it) & 0x3F); @@ -199,21 +291,21 @@ uint64_t ekg::utf8_length(std::string_view utf_string) { } uint64_t string_size {}; - uint8_t char8 {}; + uint8_t c8 {}; for (uint64_t it {}; it < utf_string.size(); it++) { - char8 = static_cast(utf_string.at(it)); - if (char8 == '\n' || char8 == '\r') { + c8 = static_cast(utf_string.at(it)); + if (c8 == '\n' || c8 == '\r') { continue; - } else if (char8 <= 0x7F) { + } else if (c8 <= 0x7F) { string_size++; - } else if ((char8 & 0xE0) == 0xC0) { + } else if ((c8 & 0xE0) == 0xC0) { string_size++; it++; - } else if ((char8 & 0xF0) == 0xE0) { + } else if ((c8 & 0xF0) == 0xE0) { string_size++; it += 2; - } else if ((char8 & 0xF8) == 0xF0) { + } else if ((c8 & 0xF8) == 0xF0) { string_size++; it += 3; } @@ -236,7 +328,7 @@ std::string ekg::utf8_substr(std::string_view string, uint64_t offset, uint64_t uint64_t index {}; uint64_t utf_sequence_size {}; uint64_t begin {UINT64_MAX}; - uint8_t char8 {}; + uint8_t c8 {}; bool at_last_index {}; /* @@ -245,12 +337,12 @@ std::string ekg::utf8_substr(std::string_view string, uint64_t offset, uint64_t */ while (index < string_size) { - char8 = static_cast(string.at(index)); + c8 = static_cast(string.at(index)); utf_sequence_size = 1; - utf_sequence_size += ((char8 & 0xE0) == 0xC0); - utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); - utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); + utf_sequence_size += ((c8 & 0xE0) == 0xC0); + utf_sequence_size += 2 * ((c8 & 0xF0) == 0xE0); + utf_sequence_size += 3 * ((c8 & 0xF8) == 0xF0); at_last_index = index + utf_sequence_size == string_size; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index fc9d628b..0e8599ee 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -479,18 +479,14 @@ void ekg::ui::event( bool is_ab_delta_equals {}; bool is_bounding {}; - size_t utf_position_count {}; - size_t utf_sequence_size {}; - size_t new_cursor_byte_pos {}; - size_t cursor_byte_pos {}; size_t text_total_lines {textbox.text.length_of_lines()}; size_t byte_pos {}; + size_t nearest_byte_pos {}; + size_t cursor_byte_pos {}; ekg::rect_t &rect_abs {ekg::ui::get_abs_rect(property, textbox.rect)}; ekg::vec2_t cursor_pos {}; - std::string line {}; - std::sregex_iterator end {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; @@ -503,10 +499,11 @@ void ekg::ui::event( } else if (cursor.a.x == 0 && cursor.a.y > 0) { cursor.a.y -= 1; cursor.a.x = ekg::utf8_length(textbox.text.at(cursor.a.y)); + is_modifier_left_fired = false; } } - if ( + ( is_modifier_left_fired && ekg::utf8_find_byte_pos_by_utf_pos( @@ -514,38 +511,22 @@ void ekg::ui::event( cursor.a.x, cursor_byte_pos ) - ) { - std::sregex_iterator iterator( - line.begin(), - line.begin() + cursor_byte_pos, - textbox.regex_operations[ekg::textbox_t::operation::modifier_left] - ); - - new_cursor_byte_pos = UINT64_MAX; - std::sregex_iterator end {}; - for (auto it = iterator; it != end; it++) { - std::smatch j = *it; - new_cursor_byte_pos = j.position(); - ekg_log_low_level(new_cursor_byte_pos << " " << j.prefix().str()) - } - - if (new_cursor_byte_pos != UINT64_MAX) { - byte_pos = new_cursor_byte_pos; - utf_position_count = 0; - while (byte_pos < cursor_byte_pos) { - char &char8 {line.at(byte_pos)}; - utf_sequence_size = 1; - utf_sequence_size += ((char8 & 0xE0) == 0xC0); - utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); - utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); - byte_pos += utf_sequence_size; - utf_position_count++; - } - - ekg_log_low_level(utf_position_count) - cursor.a.x -= utf_position_count; - } - } + && + ekg::utf8_nearest_regex_group_matched_position( + line.cbegin(), + line.cbegin() + cursor_byte_pos, + nearest_byte_pos, + textbox.regex_operations[ekg::textbox_t::operation::modifier_left], + ekg::dock::left + ) + && + ekg::utf8_align_utf_pos_by_byte_pos( + line, + cursor.a.x, + cursor_byte_pos, + nearest_byte_pos + ) + ); cursor.highest_char_index = cursor.a.x; cursor.b = cursor.a; @@ -560,10 +541,11 @@ void ekg::ui::event( } else if (cursor.a.y < text_total_lines-1) { cursor.b.y += 1; cursor.b.x = 0; + is_modifier_right_fired = false; } } - if ( + ( is_modifier_right_fired && ekg::utf8_find_byte_pos_by_utf_pos( @@ -571,40 +553,22 @@ void ekg::ui::event( cursor.b.x, cursor_byte_pos ) - ) { - std::sregex_iterator iterator( - line.begin() + cursor_byte_pos, - line.end(), - textbox.regex_operations[ekg::textbox_t::operation::modifier_right] - ); - - new_cursor_byte_pos = UINT64_MAX; - std::sregex_iterator end {}; - for (auto it = iterator; it != end; it++) { - std::smatch j = *it; - new_cursor_byte_pos = cursor_byte_pos + j.position(); - ekg_log_low_level(new_cursor_byte_pos << " v " << j.prefix().str()) - break; - } - - if (new_cursor_byte_pos != UINT64_MAX) { - byte_pos = cursor_byte_pos; - utf_position_count = 0; - ekg_log_low_level(byte_pos << " x " << new_cursor_byte_pos) - while (byte_pos < new_cursor_byte_pos) { - char &char8 {line.at(byte_pos)}; - utf_sequence_size = 1; - utf_sequence_size += ((char8 & 0xE0) == 0xC0); - utf_sequence_size += 2 * ((char8 & 0xF0) == 0xE0); - utf_sequence_size += 3 * ((char8 & 0xF8) == 0xF0); - byte_pos += utf_sequence_size; - utf_position_count++; - } - - ekg_log_low_level(utf_position_count) - cursor.b.x += utf_position_count; - } - } + && + ekg::utf8_nearest_regex_group_matched_position( + line.cbegin() + cursor_byte_pos, + line.cend(), + nearest_byte_pos, + textbox.regex_operations[ekg::textbox_t::operation::modifier_right], + ekg::dock::right + ) + && + ekg::utf8_align_utf_pos_by_byte_pos( + line, + cursor.b.x, + cursor_byte_pos, + cursor_byte_pos + nearest_byte_pos + ) + ); cursor.highest_char_index = cursor.b.x; cursor.a = cursor.b; From ab5c7b44afec9f7224c05ca34b8fa8a0b90dfd73 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 7 Sep 2025 00:32:10 -0300 Subject: [PATCH 23/61] [fix] select and modifier; new binds --- include/ekg/draw/typography/font.hpp | 1 + include/ekg/ui/textbox/textbox.hpp | 1 + src/draw/typography/font.cpp | 14 ++- src/handler/input/handler.cpp | 31 +---- src/ui/textbox/widget.cpp | 173 ++++++++++++++++++--------- 5 files changed, 134 insertions(+), 86 deletions(-) diff --git a/include/ekg/draw/typography/font.hpp b/include/ekg/draw/typography/font.hpp index 4d0a6cfb..6d717919 100644 --- a/include/ekg/draw/typography/font.hpp +++ b/include/ekg/draw/typography/font.hpp @@ -50,6 +50,7 @@ namespace ekg::draw { uint32_t font_size {}; float text_height {}; float non_swizzlable_range {}; + float space_wsize {}; FT_Bool ft_bool_kerning {}; bool font_size_changed {}; diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index a67125ce..f555c85d 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -61,6 +61,7 @@ namespace ekg { struct cursor_t { public: + ekg::dock direction {}; size_t highest_char_index {}; ekg::vec2_t a {}; ekg::vec2_t b {}; diff --git a/src/draw/typography/font.cpp b/src/draw/typography/font.cpp index 71d0d058..5952a01f 100644 --- a/src/draw/typography/font.cpp +++ b/src/draw/typography/font.cpp @@ -401,6 +401,10 @@ void ekg::draw::font::reload() { ); } + FT_Vector space_char_metrics {}; + FT_Get_Kerning(text_font_face.ft_face, 32, 32, 0, &space_char_metrics); + this->space_wsize = static_cast(space_char_metrics.x >> 6); + this->text_height = static_cast(this->font_size); this->offset_text_height = this->text_height / 6; @@ -423,11 +427,11 @@ void ekg::draw::font::blit( const ekg::rgba_t &color ) { if ( - !this->is_any_functional_font_face_loaded - || - color.w < 0.1f - || - text.empty() + !this->is_any_functional_font_face_loaded + || + color.w < 0.1f + || + text.empty() ) { return; } diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index f1ef6f88..0560e307 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -153,38 +153,15 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-action-cursor", "abs-mouse-1"); this->insert_input_bind("textbox-action-multicursor", "lctrl+mouse-1"); this->insert_input_bind("textbox-action-multicursor", "rctrl+mouse-1"); + this->insert_input_bind("textbox-action-select", "lshift"); + this->insert_input_bind("textbox-action-select", "rshift"); + this->insert_input_bind("textbox-action-modifier", "lctrl"); + this->insert_input_bind("textbox-action-modifier", "rctrl"); this->insert_input_bind("textbox-action-up", "abs-up"); - this->insert_input_bind("textbox-action-select", "lshift+up"); - this->insert_input_bind("textbox-action-select", "rshift+up"); - this->insert_input_bind("textbox-action-modifier-up", "lctrl+up"); - this->insert_input_bind("textbox-action-modifier-up", "rctrl+up"); - this->insert_input_bind("textbox-action-down", "abs-down"); - this->insert_input_bind("textbox-action-select", "lshift+down"); - this->insert_input_bind("textbox-action-select", "rshift+down"); - this->insert_input_bind("textbox-action-modifier-down", "lctrl+down"); - this->insert_input_bind("textbox-action-modifier-down", "rctrl+down"); - this->insert_input_bind("textbox-action-right", "abs-right"); - this->insert_input_bind("textbox-action-select", "lctrl+lshift+right"); - this->insert_input_bind("textbox-action-select", "lctrl+rshift+right"); - this->insert_input_bind("textbox-action-select", "rctrl+lshift+right"); - this->insert_input_bind("textbox-action-select", "rctrl+rshift+right"); - this->insert_input_bind("textbox-action-select", "lshift+right"); - this->insert_input_bind("textbox-action-select", "rshift+right"); - this->insert_input_bind("textbox-action-modifier-right", "lctrl+right"); - this->insert_input_bind("textbox-action-modifier-right", "rctrl+right"); - this->insert_input_bind("textbox-action-left", "abs-left"); - this->insert_input_bind("textbox-action-select", "lctrl+lshift+left"); - this->insert_input_bind("textbox-action-select", "lctrl+rshift+left"); - this->insert_input_bind("textbox-action-select", "rctrl+lshift+left"); - this->insert_input_bind("textbox-action-select", "rctrl+rshift+left"); - this->insert_input_bind("textbox-action-select", "lshift+left"); - this->insert_input_bind("textbox-action-select", "rshift+left"); - this->insert_input_bind("textbox-action-modifier-left", "lctrl+left"); - this->insert_input_bind("textbox-action-modifier-left", "rctrl+left"); this->insert_input_bind("clipboard-copy", "lctrl+c"); this->insert_input_bind("clipboard-copy", "rctrl+c"); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 0e8599ee..49c482b3 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -224,9 +224,11 @@ void ekg::ui::handle_cursor_interact( }; if (ekg::textbox_t::cursor_t {.a = pick_index, .b = pick_index} <= cursor.delta) { + cursor.direction = ekg::dock::left; textbox.widget.picked_left = cursor.delta; textbox.widget.picked_right = pick_index; } else { + cursor.direction = ekg::dock::right; textbox.widget.picked_left = pick_index; textbox.widget.picked_right = cursor.delta; } @@ -459,17 +461,22 @@ void ekg::ui::event( ); } + if (!input.was_typed) { + return; + } + /* logic of cursors, for handling lot of curosrs we will use only one loop for improve performance */ + bool is_modifier_fired {ekg::fired("textbox-action-modifier")}; bool is_left_fired {ekg::fired("textbox-action-left")}; - bool is_modifier_left_fired {is_left_fired && ekg::fired("textbox-action-modifier-left")}; + bool is_modifier_left_fired {is_left_fired && is_modifier_fired}; bool is_right_fired {ekg::fired("textbox-action-right")}; - bool is_modifier_right_fired {is_right_fired && ekg::fired("textbox-action-modifier-right")}; + bool is_modifier_right_fired {is_right_fired && is_modifier_fired}; bool is_up_fired {ekg::fired("textbox-action-up")}; - bool is_modifier_up_fired {is_up_fired && ekg::fired("textbox-action-modifier-up")}; + bool is_modifier_up_fired {is_up_fired && is_modifier_fired}; bool is_down_fired {ekg::fired("textbox-action-down")}; - bool is_modifier_down_fired {is_down_fired && ekg::fired("textbox-action-modifier-down")}; - bool is_action_selected_fired {is_modifier_down_fired && ekg::fired("textbox-action-select")}; + bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; + bool is_action_selected_fired {ekg::fired("textbox-action-select")}; if (is_left_fired || is_right_fired || is_up_fired || is_down_fired) { textbox.widget.set_cursor_static = true; @@ -483,15 +490,41 @@ void ekg::ui::event( size_t byte_pos {}; size_t nearest_byte_pos {}; size_t cursor_byte_pos {}; + size_t highest {}; ekg::rect_t &rect_abs {ekg::ui::get_abs_rect(property, textbox.rect)}; ekg::vec2_t cursor_pos {}; std::string line {}; + ekg::textbox_t::cursor_t cursor {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; + /** + * Before move the cursor, we need make sure we are working with + * right directions. This is necessary for make possible select. + **/ + if ( + is_action_selected_fired + ) { + if (cursor == cursor.delta && is_left_fired) { + cursor.b = cursor.a; + cursor.direction = ekg::dock::left; + } else if (cursor == cursor.delta && is_right_fired) { + cursor.a = cursor.b; + cursor.direction = ekg::dock::right; + } else if (cursor < cursor.delta) { + cursor.a = cursor.b; + cursor.direction = ekg::dock::right; + } else if (cursor > cursor.delta) { + cursor.b = cursor.a; + cursor.direction = ekg::dock::left; + } + + is_ab_equals = true; + } + if (is_left_fired) { if (is_ab_equals) { if (cursor.a.x > 0) { @@ -530,7 +563,6 @@ void ekg::ui::event( cursor.highest_char_index = cursor.a.x; cursor.b = cursor.a; - cursor.delta = cursor.b; } if (is_right_fired) { @@ -572,7 +604,6 @@ void ekg::ui::event( cursor.highest_char_index = cursor.b.x; cursor.a = cursor.b; - cursor.delta = cursor.a; } if (is_up_fired) { @@ -597,7 +628,6 @@ void ekg::ui::event( if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.a.x; cursor.b = cursor.a; - cursor.delta = cursor.b; } if (is_down_fired) { @@ -622,9 +652,7 @@ void ekg::ui::event( if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.b.x; cursor.a = cursor.b; - cursor.delta = cursor.a; } - cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); cursor_pos.y = static_cast(static_cast(floorf(cursor_pos.y))); @@ -635,6 +663,46 @@ void ekg::ui::event( textbox.widget.scrollbar_property.scroll.position.w += ekg::min(-cursor_pos.y, textbox.widget.rect_text_size.h); } + + /** + * While firstly we move cursor and check the right direction. + * We need check again the right direction after moved. + * This will make possible modifier works with select from any direction. + **/ + if (is_action_selected_fired) { + if (cursor == cursor.delta && is_left_fired) { + cursor.b = cursor.a; + cursor.direction = ekg::dock::left; + } else if (cursor == cursor.delta && is_right_fired) { + cursor.a = cursor.b; + cursor.direction = ekg::dock::right; + } else if (cursor < cursor.delta) { + cursor.a = cursor.b; + cursor.direction = ekg::dock::right; + } else if (cursor > cursor.delta) { + cursor.b = cursor.a; + cursor.direction = ekg::dock::left; + } + + highest = cursor.highest_char_index; + if (cursor.direction == ekg::dock::left) { + cursor.a = cursor.a; + cursor.b = cursor.delta; + highest = cursor.a.x; + } else if (cursor.direction == ekg::dock::right) { + cursor.a = cursor.delta; + cursor.b = cursor.b; + highest = cursor.a.x; + } + + if (is_left_fired) { + cursor.highest_char_index = highest; + } else if (is_right_fired) { + cursor.highest_char_index = highest; + } + } else { + cursor.delta = cursor.a; + } } break; @@ -791,14 +859,12 @@ void ekg::ui::buffering( bool is_complete_line_selected {}; bool is_ab_equals_selected {}; bool is_cursor_at_end_of_line {}; - bool was_complete_line_selected {}; bool cursors_going_on {!textbox.widget.cursors.empty()}; - bool is_end_of_line_and_text {}; bool oka_found_visual_index {}; bool get_out {}; bool is_last_char_from_line {}; - float extra_final_char_cursor {}; + float end_cursor_position {}; float extra_rect_height {rect_abs.h + textbox.widget.rect_text_size.h}; float glyph_wsize {}; @@ -814,6 +880,7 @@ void ekg::ui::buffering( ekg::textbox_t::cursor_t cursor {}; std::vector &chunks {textbox.text.chunks_data()}; size_t chunks_size {chunks.size()}; + bool is_renderable {}; textbox.widget.scrollbar.rect.h = text_total_lines * textbox.widget.rect_text_size.h; textbox.widget.view_line_index = @@ -850,6 +917,10 @@ void ekg::ui::buffering( pos.y = static_cast(static_cast(floorf(pos.y))); pos.x = textbox.color_scheme.gutter_margin; + std::string empty {"\n"}; + bool is_empty {}; + bool was_empty_before {}; + textbox.widget.layers_select.clear(); for (size_t ic {}; ic < chunks_size; ic++) { ekg::io::chunk_t &chunk {chunks.at(ic)}; @@ -872,7 +943,9 @@ void ekg::ui::buffering( addition_chunk_index += chunk_size; for (;il < chunk_size; il++) { - std::string &line {chunk.at(il)}; + std::string &chunk_line {chunk.at(il)}; + std::string &line {(is_empty = chunk_line.empty()) ? empty : chunk_line}; + index.x = 0; text_len = line.size(); for (size_t it {}; it < text_len; it++) { @@ -911,14 +984,19 @@ void ekg::ui::buffering( ( ekg::ui::find_cursor(textbox, index, cursor) || - (is_cursor_at_end_of_line = is_last_char_from_line && ++index.x && ekg::ui::find_cursor(textbox, index, cursor)) + ( + is_cursor_at_end_of_line = + is_last_char_from_line + && + ++index.x && ekg::ui::find_cursor(textbox, index, cursor) + ) ) ) { glyph_wsize = glyph.wsize; + end_cursor_position = glyph_wsize * is_cursor_at_end_of_line; is_ab_equals_selected = property.states.is_focused && cursor == index; is_inline_selected = cursor >= index && cursor < index && !is_ab_equals_selected; is_complete_line_selected = false; - extra_final_char_cursor = (is_cursor_at_end_of_line * glyph_wsize); if (is_inline_selected) { if ( @@ -936,7 +1014,7 @@ void ekg::ui::buffering( } if (is_ab_equals_selected) { - cursor.rect.x = pos.x + extra_final_char_cursor; + cursor.rect.x = pos.x + end_cursor_position; cursor.rect.y = pos.y; cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph_wsize : textbox.color_scheme.cursor_thickness; cursor.rect.h = textbox.widget.rect_text_size.h; @@ -944,46 +1022,28 @@ void ekg::ui::buffering( } if (is_inline_selected) { - cursor.rect.x = pos.x + extra_final_char_cursor; + cursor.rect.x = pos.x + end_cursor_position; cursor.rect.y = pos.y; - cursor.rect.w = glyph_wsize + (glyph_wsize * (is_last_char_from_line && !is_cursor_at_end_of_line && cursor.a.y != cursor.b.y)); + cursor.rect.w = glyph_wsize; cursor.rect.h = textbox.widget.rect_text_size.h; - textbox.widget.layers_select.push_back({false, cursor.rect}); + textbox.widget.layers_select.push_back({false, cursor.rect}); } - rect_select.x = textbox.color_scheme.gutter_margin; - rect_select.y = pos.y - (textbox.widget.rect_text_size.h * !is_end_of_line_and_text); - rect_select.w = line_wsize + (is_end_of_line_and_text * glyph_wsize) + glyph_wsize; - rect_select.h = textbox.widget.rect_text_size.h; - - if ( - was_complete_line_selected + if ( + is_complete_line_selected && - ( - (current_line_for_cursor_complete != index.y) - || - (is_end_of_line_and_text = index.y + 1 == text_total_lines && is_last_char_from_line) - ) + it == 0 ) { - cursor.rect.x = textbox.color_scheme.gutter_margin; - cursor.rect.y = pos.y - (textbox.widget.rect_text_size.h * !is_end_of_line_and_text); - cursor.rect.w = line_wsize + (is_end_of_line_and_text * glyph_wsize) + glyph_wsize; - cursor.rect.h = textbox.widget.rect_text_size.h; - textbox.widget.layers_select.push_back({false, cursor.rect}); - was_complete_line_selected = false; + line_wsize = 0; } - if (is_complete_line_selected && current_line_for_cursor_complete != index.y) { - line_wsize = textbox.color_scheme.gutter_margin; - current_line_for_cursor_complete = index.y; - was_complete_line_selected = true; - } - - if (is_complete_line_selected) { + if (is_complete_line_selected && !is_empty) { line_wsize += glyph_wsize; } + } - is_cursor_at_end_of_line = false; + if (is_empty) { + continue; } if (!glyph.was_sampled) { @@ -1046,19 +1106,30 @@ void ekg::ui::buffering( pos.x += glyph.wsize; ft_uint_previous = c32; - /** + /** * Peek `ekg/io/memory.hpp` for better hash definition and purpose. **/ hash += ekg_generate_hash(pos.x, c32, glyph.x); - index.x++; } + if ( + is_complete_line_selected + ) { + cursor.rect.x = textbox.color_scheme.gutter_margin; + cursor.rect.y = pos.y; + cursor.rect.h = textbox.widget.rect_text_size.h; + cursor.rect.w = line_wsize + glyph_wsize; + textbox.widget.layers_select.push_back({false, cursor.rect}); + is_complete_line_selected = false; + } + pos.x = textbox.color_scheme.gutter_margin; pos.y += textbox.widget.rect_text_size.h; rendered.y += textbox.widget.rect_text_size.h; index.y++; hash += pos.y * 32; + index.x += is_empty; if (rendered.y > extra_rect_height) { get_out = true; @@ -1071,12 +1142,6 @@ void ekg::ui::buffering( } } - if (was_complete_line_selected) { - rect_select.y += textbox.widget.rect_text_size.h; - textbox.widget.layers_select.push_back({false, rect_select}); - was_complete_line_selected = false; - } - data.hash = hash; ekg::draw::allocator::is_simple_shape = false; From 81a959ba8e3bcbd491565e246e0448b02292d699 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 7 Sep 2025 01:03:19 -0300 Subject: [PATCH 24/61] [fix] movements for up/down --- src/ui/textbox/widget.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 49c482b3..1825da26 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -615,18 +615,18 @@ void ekg::ui::event( is_bounding = false; } + if (!is_ab_equals) cursor.highest_char_index = cursor.a.x; + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.a.y))}; if (cursor.a.x > line_text_length) { cursor.a.x = line_text_length; } - + if (is_bounding) { cursor.highest_char_index = cursor.a.x; cursor.a.x = 0; } - if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.a.x; - cursor.b = cursor.a; } @@ -639,6 +639,8 @@ void ekg::ui::event( is_bounding = false; } + if (!is_ab_equals) cursor.highest_char_index = cursor.b.x; + size_t line_text_length {ekg::utf8_length(textbox.text.at(cursor.b.y))}; if (cursor.b.x > line_text_length) { cursor.b.x = line_text_length; @@ -649,8 +651,6 @@ void ekg::ui::event( cursor.b.x = line_text_length; } - if (!is_ab_equals && !is_bounding) cursor.highest_char_index = cursor.b.x; - cursor.a = cursor.b; } cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); From 6648dc51f1c23943937753290a0ec761faa4fd16 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Tue, 9 Sep 2025 00:08:55 -0300 Subject: [PATCH 25/61] [update] erase --- include/ekg/ui/textbox/widget.hpp | 7 +++++++ src/handler/input/handler.cpp | 6 ++---- src/ui/textbox/widget.cpp | 34 +++++++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 7edcffa7..f7918a26 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -49,6 +49,13 @@ namespace ekg::ui { ekg::input_info_t &input ); + void erase( + ekg::textbox_t &textbox, + const ekg::vec2_t &a, + const ekg::vec2_t &b, + ekg::textbox_t::cursor_t &cursor + ); + void reload( ekg::property_t &property, ekg::textbox_t &textbox diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index 0560e307..f4a5f537 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -128,8 +128,6 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-focus", "mouse-1"); this->insert_input_bind("textbox-focus", "finger-click"); - this->insert_input_bind("textbox-action-break", "return"); - this->insert_input_bind("textbox-action-break", "keypad enter"); this->insert_input_bind("textbox-action-select-all", "lctrl+a"); this->insert_input_bind("textbox-action-select-all", "rctrl+a"); @@ -142,8 +140,8 @@ void ekg::handler::input::init() { this->insert_input_bind("textbox-action-select-word", "mouse-1-double"); this->insert_input_bind("textbox-action-select-word", "finger-hold"); - this->insert_input_bind("textbox-action-delete-left", "abs-backspace"); - this->insert_input_bind("textbox-action-delete-right", "abs-delete"); + this->insert_input_bind("textbox-action-erase-left", "abs-backspace"); + this->insert_input_bind("textbox-action-erase-right", "abs-delete"); this->insert_input_bind("textbox-action-break-line", "return"); this->insert_input_bind("textbox-action-break-line", "keypad enter"); this->insert_input_bind("textbox-action-break-line", "lshift+return"); diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 1825da26..422bc987 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -103,6 +103,9 @@ bool ekg::ui::find_index_by_interact( pos.y = static_cast(static_cast(floorf(pos.y))); pos.x = textbox.color_scheme.gutter_margin; + std::string empty {"\n"}; + bool is_empty {}; + for (size_t ic {textbox.widget.view_chunk_index}; ic < chunks_size; ic++) { ekg::io::chunk_t &chunk {chunks.at(ic)}; @@ -113,7 +116,9 @@ bool ekg::ui::find_index_by_interact( chunk_size = chunk.size(); for (;il < chunk_size; il++) { - std::string &line {chunk.at(il)}; + std::string &chunk_line {chunk.at(il)}; + std::string &line {(is_empty = chunk_line.empty()) ? empty : chunk_line}; + text_len = line.size(); for (size_t it {}; it < text_len; it++) { uc8 = static_cast(line.at(it)); @@ -154,7 +159,7 @@ bool ekg::ui::find_index_by_interact( } rect.w = glyph.wsize; - if (ekg::rect_collide_vec2(rect, interact)) { + if (!is_empty && ekg::rect_collide_vec2(rect, interact)) { index.x++; return true; } @@ -164,7 +169,7 @@ bool ekg::ui::find_index_by_interact( rect.w = rect_abs.w; if (index.x == text_len && ekg::rect_collide_vec2(rect, interact)) { - index.x = text_len; + index.x = is_empty ? 0 : text_len; return true; } } @@ -274,6 +279,18 @@ void ekg::ui::handle_cursor_interact( } } +void ekg::ui::erase( + ekg::textbox_t &textbox, + const ekg::vec2_t &a, + const ekg::vec2_t &b, + ekg::textbox_t::cursor_t &cursor +) { + if (a.y == b.y) { + ekg::utf8_substr(textbox.text.at(a.y), a.x, b.x - a.x); + return; + } +} + void ekg::ui::reload( ekg::property_t &property, ekg::textbox_t &textbox @@ -477,6 +494,8 @@ void ekg::ui::event( bool is_down_fired {ekg::fired("textbox-action-down")}; bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; bool is_action_selected_fired {ekg::fired("textbox-action-select")}; + bool is_action_erase_left_fired {ekg::fired("textbox-action-erase-left")}; + bool is_action_erase_right_fired {ekg::fired("textbox-action-erase-right")}; if (is_left_fired || is_right_fired || is_up_fired || is_down_fired) { textbox.widget.set_cursor_static = true; @@ -496,11 +515,13 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; - ekg::textbox_t::cursor_t cursor {}; + ekg::textbox_t::cursor_t io_cursor {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; + io_cursor = cursor; + /** * Before move the cursor, we need make sure we are working with * right directions. This is necessary for make possible select. @@ -653,6 +674,7 @@ void ekg::ui::event( cursor.a = cursor.b; } + cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); cursor_pos.y = static_cast(static_cast(floorf(cursor_pos.y))); @@ -703,6 +725,10 @@ void ekg::ui::event( } else { cursor.delta = cursor.a; } + + if (is_action_erase_left_fired || is_action_erase_right_fired) + ekg::ui::erase(textbox, cursor.a, cursor.b, cursor); + io_cursor = cursor; } break; From 27eae685410a9eace343eb170f7c946c588ee0f0 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Thu, 11 Sep 2025 01:25:52 -0300 Subject: [PATCH 26/61] [fix] text swizzle not considering empty-cases; per-line edit --- src/io/utf.cpp | 10 +++++-- src/ui/textbox/widget.cpp | 56 ++++++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/io/utf.cpp b/src/io/utf.cpp index a87b4d44..66b45e29 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -356,7 +356,7 @@ std::string ekg::utf8_substr(std::string_view string, uint64_t offset, uint64_t if ( /** * OBS: - * If the `offset` parameter is equals to the last UTF-8 string size, + * If the `offset` paramete is equals to the last UTF-8 string size, * then it continue without substring process. **/ (at_last_index && begin != UINT64_MAX && offset != utf_text_size) || @@ -405,9 +405,15 @@ void ekg::text::swizzle( std::vector &to_swizzle, bool skip_first_line ) { + bool is_empty {to_swizzle.empty()}; ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunk_index)}; + if (skip_first_line) { - chunk.at(line_index) = to_swizzle.at(0); + chunk.at(line_index) = is_empty ? "" : to_swizzle.at(0); + } + + if (to_swizzle.empty()) { + return; } chunk.insert( diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 422bc987..e809447a 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -286,9 +286,40 @@ void ekg::ui::erase( ekg::textbox_t::cursor_t &cursor ) { if (a.y == b.y) { - ekg::utf8_substr(textbox.text.at(a.y), a.x, b.x - a.x); + std::string line {textbox.text.at(a.y)}; + + std::string la { + ekg::utf8_substr( + line, + 0, a.x + ) + }; + + std::string lb { + ekg::utf8_substr( + line, + b.x, line.size() + ) + }; + + line.clear(); + if (!lb.empty() || !la.empty()) { + line = la + lb; + } + + textbox.text.set( + a.y, + line + ); + + cursor.b = cursor.a; + cursor.delta = cursor.a; + return; } + + + } void ekg::ui::reload( @@ -484,18 +515,20 @@ void ekg::ui::event( /* logic of cursors, for handling lot of curosrs we will use only one loop for improve performance */ + bool is_action_erase_right_fired {ekg::fired("textbox-action-erase-right")}; + bool is_action_erase_left_fired {ekg::fired("textbox-action-erase-left")}; + bool is_action_erase_fired {is_action_erase_left_fired || is_action_erase_right_fired}; + bool is_action_selected_fired {is_action_erase_fired || ekg::fired("textbox-action-select")}; + bool is_modifier_fired {ekg::fired("textbox-action-modifier")}; - bool is_left_fired {ekg::fired("textbox-action-left")}; + bool is_left_fired {is_action_erase_left_fired || ekg::fired("textbox-action-left")}; bool is_modifier_left_fired {is_left_fired && is_modifier_fired}; - bool is_right_fired {ekg::fired("textbox-action-right")}; + bool is_right_fired {is_action_erase_right_fired || ekg::fired("textbox-action-right")}; bool is_modifier_right_fired {is_right_fired && is_modifier_fired}; bool is_up_fired {ekg::fired("textbox-action-up")}; bool is_modifier_up_fired {is_up_fired && is_modifier_fired}; bool is_down_fired {ekg::fired("textbox-action-down")}; bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; - bool is_action_selected_fired {ekg::fired("textbox-action-select")}; - bool is_action_erase_left_fired {ekg::fired("textbox-action-erase-left")}; - bool is_action_erase_right_fired {ekg::fired("textbox-action-erase-right")}; if (is_left_fired || is_right_fired || is_up_fired || is_down_fired) { textbox.widget.set_cursor_static = true; @@ -515,13 +548,10 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; - ekg::textbox_t::cursor_t io_cursor {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; - io_cursor = cursor; - /** * Before move the cursor, we need make sure we are working with * right directions. This is necessary for make possible select. @@ -726,9 +756,9 @@ void ekg::ui::event( cursor.delta = cursor.a; } - if (is_action_erase_left_fired || is_action_erase_right_fired) - ekg::ui::erase(textbox, cursor.a, cursor.b, cursor); - io_cursor = cursor; + if (is_action_erase_fired) { + ekg::ui::erase(textbox, cursor.a, cursor.b, cursor); + } } break; @@ -935,7 +965,7 @@ void ekg::ui::buffering( * This technique works because the line height is fixed, ultimately, soon, should be re-worked * to support differents text-heights at same time, also, for widgets scrolling (I do not think * someone can write a GUI context with +5000000 heights from widgets without pages). - * + * [] * - Rina - 11:39; 08/06/2025 **/ float visible_text_height {static_cast(textbox.widget.view_line_index * textbox.widget.rect_text_size.h)}; From 7471e5584e361809275ffc31f6240449c3a022a9 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Fri, 12 Sep 2025 22:40:52 -0300 Subject: [PATCH 27/61] [fix] erase --- include/ekg/io/utf.hpp | 6 +++ include/ekg/ui/textbox/widget.hpp | 4 +- src/io/utf.cpp | 27 +++++++++- src/ui/textbox/widget.cpp | 82 +++++++++++++++++-------------- 4 files changed, 78 insertions(+), 41 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index c432152b..1992e9ab 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -212,6 +212,12 @@ namespace ekg { ) ); } + + void utf8_concat( + std::string &string, + const ekg::vec4_t &stride, + std::string &concated + ); } namespace ekg::io { diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index f7918a26..c59bb54f 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -49,10 +49,8 @@ namespace ekg::ui { ekg::input_info_t &input ); - void erase( + void handle_erase( ekg::textbox_t &textbox, - const ekg::vec2_t &a, - const ekg::vec2_t &b, ekg::textbox_t::cursor_t &cursor ); diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 66b45e29..e138079d 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -399,6 +399,25 @@ size_t ekg::utf8_split_endings( return new_lines_count; } +void ekg::utf8_concat( + std::string &string, + const ekg::vec4_t &stride, + std::string &concated +) { + std::string a { + ekg::utf8_substr(string, stride.x, stride.y) + }; + + std::string b { + ekg::utf8_substr(string, stride.z, stride.w) + }; + + concated.clear(); + if (!a.empty() || !b.empty()) { + concated = a + b; + } +} + void ekg::text::swizzle( size_t chunk_index, size_t line_index, @@ -586,7 +605,11 @@ void ekg::text::erase( return; } - end = ekg::min(begin + end, this->total_lines); + if (end < begin) { + throw std::out_of_range("ekg::text::erase: " + std::to_string(end) + " end is > than " + std::to_string(begin) + " begin"); + return; + } + this->total_lines -= end - begin; this->was_audited = true; @@ -606,7 +629,7 @@ void ekg::text::erase( previous_lines = lines; lines += (chunk_size = chunk.size()); - if (begin < lines) { + if (begin <= lines) { remains_lines = end - begin; begin = begin - previous_lines; while (remains_lines != 0) { diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index e809447a..c4dd8fec 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -279,37 +279,23 @@ void ekg::ui::handle_cursor_interact( } } -void ekg::ui::erase( +void ekg::ui::handle_erase( ekg::textbox_t &textbox, - const ekg::vec2_t &a, - const ekg::vec2_t &b, ekg::textbox_t::cursor_t &cursor ) { - if (a.y == b.y) { - std::string line {textbox.text.at(a.y)}; - - std::string la { - ekg::utf8_substr( - line, - 0, a.x - ) - }; - - std::string lb { - ekg::utf8_substr( - line, - b.x, line.size() - ) - }; - - line.clear(); - if (!lb.empty() || !la.empty()) { - line = la + lb; - } + if (cursor.a.y == cursor.b.y) { + std::string line {textbox.text.at(cursor.a.y)}; + std::string concated {}; + + ekg::utf8_concat( + line, + {0, cursor.a.x, cursor.b.x, line.size()}, + concated + ); textbox.text.set( - a.y, - line + cursor.a.y, + concated ); cursor.b = cursor.a; @@ -318,8 +304,26 @@ void ekg::ui::erase( return; } + textbox.text.set( + cursor.a.y, + ekg::utf8_substr( + textbox.text.at(cursor.a.y), + 0, cursor.a.x + ) + + + ekg::utf8_substr( + textbox.text.at(cursor.b.y), + cursor.b.x, UINT32_MAX + ) + ); + + textbox.text.erase( + cursor.a.y + 1, + cursor.b.y + 1 + ); - + cursor.b = cursor.a; + cursor.delta = cursor.a; } void ekg::ui::reload( @@ -518,24 +522,23 @@ void ekg::ui::event( bool is_action_erase_right_fired {ekg::fired("textbox-action-erase-right")}; bool is_action_erase_left_fired {ekg::fired("textbox-action-erase-left")}; bool is_action_erase_fired {is_action_erase_left_fired || is_action_erase_right_fired}; - bool is_action_selected_fired {is_action_erase_fired || ekg::fired("textbox-action-select")}; + bool is_action_selected_fired {ekg::fired("textbox-action-select")}; bool is_modifier_fired {ekg::fired("textbox-action-modifier")}; - bool is_left_fired {is_action_erase_left_fired || ekg::fired("textbox-action-left")}; - bool is_modifier_left_fired {is_left_fired && is_modifier_fired}; - bool is_right_fired {is_action_erase_right_fired || ekg::fired("textbox-action-right")}; - bool is_modifier_right_fired {is_right_fired && is_modifier_fired}; + bool is_left_fired {ekg::fired("textbox-action-left")}; + bool is_modifier_left_fired {}; + bool is_right_fired {ekg::fired("textbox-action-right")}; + bool is_modifier_right_fired {}; bool is_up_fired {ekg::fired("textbox-action-up")}; bool is_modifier_up_fired {is_up_fired && is_modifier_fired}; bool is_down_fired {ekg::fired("textbox-action-down")}; bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; - if (is_left_fired || is_right_fired || is_up_fired || is_down_fired) { + if (is_left_fired || is_right_fired || is_up_fired || is_down_fired || is_action_erase_fired) { textbox.widget.set_cursor_static = true; } bool is_ab_equals {}; - bool is_ab_delta_equals {}; bool is_bounding {}; size_t text_total_lines {textbox.text.length_of_lines()}; @@ -548,9 +551,16 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; - is_ab_delta_equals = is_ab_equals && cursor.delta == cursor.a; + + is_action_selected_fired = is_action_selected_fired || (is_action_erase_fired && is_ab_equals); + is_left_fired = is_left_fired || (is_action_erase_left_fired && is_ab_equals); + is_right_fired = is_right_fired || (is_action_erase_right_fired && is_ab_equals); + + is_modifier_left_fired = is_left_fired && is_modifier_fired; + is_modifier_right_fired = is_right_fired && is_modifier_fired; /** * Before move the cursor, we need make sure we are working with @@ -757,7 +767,7 @@ void ekg::ui::event( } if (is_action_erase_fired) { - ekg::ui::erase(textbox, cursor.a, cursor.b, cursor); + ekg::ui::handle_erase(textbox, cursor); } } From 1d2d9b87b33a35fa02cac5577d1b39ef2691aa33 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sat, 13 Sep 2025 01:08:10 -0300 Subject: [PATCH 28/61] [update] insert --- include/ekg/handler/input.hpp | 1 + include/ekg/ui/textbox/widget.hpp | 6 ++++++ src/handler/input/handler.cpp | 2 +- src/handler/theme/handler.cpp | 2 +- src/ui/textbox/widget.cpp | 31 +++++++++++++++++++++++++++++-- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/include/ekg/handler/input.hpp b/include/ekg/handler/input.hpp index a3b53692..3d38041d 100644 --- a/include/ekg/handler/input.hpp +++ b/include/ekg/handler/input.hpp @@ -71,6 +71,7 @@ namespace ekg { bool has_motion {}; bool was_wheel {}; bool was_typed {}; + std::string_view typed {}; }; struct input_key_t { diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index c59bb54f..c1e8b8db 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -54,6 +54,12 @@ namespace ekg::ui { ekg::textbox_t::cursor_t &cursor ); + void handle_insert( + ekg::textbox_t &textbox, + ekg::textbox_t::cursor_t &cursor, + std::string_view typed + ); + void reload( ekg::property_t &property, ekg::textbox_t &textbox diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index f4a5f537..c8d5ca38 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -261,12 +261,12 @@ void ekg::handler::input::poll_event() { case ekg::io::event_type::text_input: { this->input.was_pressed = true; this->input.was_typed = true; + this->input.typed = platform_event.text_input; break; } case ekg::io::event_type::key_down: { this->input.was_pressed = true; - this->input.was_typed = true; this->string_builder.clear(); diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index 5f74dc3e..b5bc3f79 100644 --- a/src/handler/theme/handler.cpp +++ b/src/handler/theme/handler.cpp @@ -142,7 +142,7 @@ void ekg::handler::theme::init() { black_light_pinky_theme.textbox_color_scheme.text_select_outline = {245, 169, 184, 100}; this->registry(black_light_pinky_theme.tag) = black_light_pinky_theme; - this->set_current_theme(black_light_pinky_theme.tag); + //this->set_current_theme(black_light_pinky_theme.tag); } void ekg::handler::theme::quit() { diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index c4dd8fec..a3fd8504 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -326,6 +326,28 @@ void ekg::ui::handle_erase( cursor.delta = cursor.a; } +void ekg::ui::handle_insert( + ekg::textbox_t &textbox, + ekg::textbox_t::cursor_t &cursor, + std::string_view typed +) { + if (cursor.a != cursor.b) { + ekg::ui::handle_erase(textbox, cursor); + } + + std::string line {textbox.text.at(cursor.a.y)}; + + line.insert( + line.begin() + cursor.a.x, + typed.begin(), + typed.end() + ); + + textbox.text.set(cursor.a.y, line); + cursor.a.x += ekg::utf8_length(typed); + cursor.b = cursor.a; +} + void ekg::ui::reload( ekg::property_t &property, ekg::textbox_t &textbox @@ -513,7 +535,7 @@ void ekg::ui::event( ); } - if (!input.was_typed) { + if (!input.was_typed && !input.was_pressed) { return; } @@ -534,7 +556,7 @@ void ekg::ui::event( bool is_down_fired {ekg::fired("textbox-action-down")}; bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; - if (is_left_fired || is_right_fired || is_up_fired || is_down_fired || is_action_erase_fired) { + if (is_left_fired || is_right_fired || is_up_fired || is_down_fired || is_action_erase_fired || input.was_typed) { textbox.widget.set_cursor_static = true; } @@ -551,6 +573,7 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; + ekg_log_low_level(input.was_typed); for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; @@ -769,6 +792,10 @@ void ekg::ui::event( if (is_action_erase_fired) { ekg::ui::handle_erase(textbox, cursor); } + + if (input.was_typed) { + ekg::ui::handle_insert(textbox, cursor, input.typed); + } } break; From 98aa846a2d5db382e34c18ff3982f9afb55e484b Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 14 Sep 2025 00:10:10 -0300 Subject: [PATCH 29/61] [fix] glitches on typing/erase --- cmake/properties.cmake | 14 +++++++++----- src/io/utf.cpp | 2 +- src/ui/textbox/widget.cpp | 39 +++++++++++++++++++++++++++++++-------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/cmake/properties.cmake b/cmake/properties.cmake index c228367e..46a26c66 100644 --- a/cmake/properties.cmake +++ b/cmake/properties.cmake @@ -37,15 +37,19 @@ endif() if(WIN32 OR EKG_FORCE_WINDOWS) set(LIBRARY_OUTPUT_PATH "../lib/windows/") - set(PLATFORM "windows") + set(PLATFORM "windows") + add_compile_definitions(EKG_EOF_SYSTEM="\r\n") elseif(ANDROID OR EKG_FORCE_ANDROID) set(LIBRARY_OUTPUT_PATH "${ANDROID_ABI}/") - set(PLATFORM "${ANDROID_ABI}") + set(PLATFORM "${ANDROID_ABI}") + add_compile_definitions(EKG_EOF_SYSTEM="\n") elseif(EKG_EMSCRIPTEN_BUILD_TYPE) set(LIBRARY_OUTPUT_PATH "../lib/linux-wasm/") - set(PLATFORM "linux-wasm") + set(PLATFORM "linux-wasm") + add_compile_definitions(EKG_EOF_SYSTEM="\n") elseif(LINUX OR EKG_FORCE_LINUX) - # WSL is not detected as Linux-based OS, same you use a Linux kernel-based distribution. + # WSL is not detected as Linux-kernel based OS; force is necessary >< set(LIBRARY_OUTPUT_PATH "../lib/linux/") - set(PLATFORM "linux") + set(PLATFORM "linux") + add_compile_definitions(EKG_EOF_SYSTEM="\n") endif() diff --git a/src/io/utf.cpp b/src/io/utf.cpp index e138079d..d4fee966 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -399,7 +399,7 @@ size_t ekg::utf8_split_endings( return new_lines_count; } -void ekg::utf8_concat( +void ekg:: utf8_concat( std::string &string, const ekg::vec4_t &stride, std::string &concated diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index a3fd8504..a28d7343 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -335,15 +335,31 @@ void ekg::ui::handle_insert( ekg::ui::handle_erase(textbox, cursor); } + if (typed == EKG_EOF_SYSTEM) { + std::string line {textbox.text.at(cursor.a.y)}; + + textbox.text.set(cursor.a.y, ekg::utf8_substr(line, 0, cursor.a.x)); + textbox.text.insert(cursor.a.y, ekg::utf8_substr(line, cursor.a.x, UINT32_MAX)); + + cursor.a.y++; + cursor.a.x = 0; + cursor.b = cursor.a; + + return; + } + + size_t byte_pos {}; std::string line {textbox.text.at(cursor.a.y)}; + ekg::utf8_find_byte_pos_by_utf_pos(line, cursor.a.x, byte_pos); line.insert( - line.begin() + cursor.a.x, + line.begin() + byte_pos, typed.begin(), typed.end() ); textbox.text.set(cursor.a.y, line); + cursor.a.x += ekg::utf8_length(typed); cursor.b = cursor.a; } @@ -544,7 +560,8 @@ void ekg::ui::event( bool is_action_erase_right_fired {ekg::fired("textbox-action-erase-right")}; bool is_action_erase_left_fired {ekg::fired("textbox-action-erase-left")}; bool is_action_erase_fired {is_action_erase_left_fired || is_action_erase_right_fired}; - bool is_action_selected_fired {ekg::fired("textbox-action-select")}; + bool is_action_selected_keybind_fired {ekg::fired("textbox-action-select")}; + bool is_action_selected_fired {}; bool is_modifier_fired {ekg::fired("textbox-action-modifier")}; bool is_left_fired {ekg::fired("textbox-action-left")}; @@ -555,8 +572,11 @@ void ekg::ui::event( bool is_modifier_up_fired {is_up_fired && is_modifier_fired}; bool is_down_fired {ekg::fired("textbox-action-down")}; bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; + bool is_arrows_fired {is_left_fired || is_right_fired || is_up_fired || is_down_fired}; + + bool is_textbox_action_break_line_fired {ekg::fired("textbox-action-break-line")}; - if (is_left_fired || is_right_fired || is_up_fired || is_down_fired || is_action_erase_fired || input.was_typed) { + if (is_arrows_fired || is_action_erase_fired || input.was_typed || is_textbox_action_break_line_fired) { textbox.widget.set_cursor_static = true; } @@ -573,12 +593,10 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; - ekg_log_low_level(input.was_typed); - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; - is_action_selected_fired = is_action_selected_fired || (is_action_erase_fired && is_ab_equals); + is_action_selected_fired = (is_arrows_fired && is_action_selected_keybind_fired) || (is_action_erase_fired && is_ab_equals); is_left_fired = is_left_fired || (is_action_erase_left_fired && is_ab_equals); is_right_fired = is_right_fired || (is_action_erase_right_fired && is_ab_equals); @@ -793,8 +811,13 @@ void ekg::ui::event( ekg::ui::handle_erase(textbox, cursor); } - if (input.was_typed) { - ekg::ui::handle_insert(textbox, cursor, input.typed); + if (input.was_typed || is_textbox_action_break_line_fired) { + ekg::ui::handle_insert( + textbox, + cursor, + is_textbox_action_break_line_fired + ? EKG_EOF_SYSTEM : input.typed + ); } } From 195e38a94e36d5591fa119ee57dede73131e71af Mon Sep 17 00:00:00 2001 From: mrsrina Date: Thu, 18 Sep 2025 22:52:02 -0300 Subject: [PATCH 30/61] [fix] tpyiong at end --- src/io/utf.cpp | 12 ++++++++++-- src/ui/textbox/widget.cpp | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/io/utf.cpp b/src/io/utf.cpp index d4fee966..b768778d 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -187,7 +187,8 @@ bool ekg::utf8_find_byte_pos_by_utf_pos( size_t string_size {string.size()}; size_t utf_pos_count {}; - for (size_t it {}; it < string_size; it++) { + size_t it {}; + for (; it < string_size; it++) { if (utf_pos_count == utf_pos) { byte_pos = it; return true; @@ -203,6 +204,11 @@ bool ekg::utf8_find_byte_pos_by_utf_pos( utf_pos_count++; } + if (utf_pos_count == utf_pos) { + byte_pos = it; + return true; + } + return false; } @@ -399,7 +405,7 @@ size_t ekg::utf8_split_endings( return new_lines_count; } -void ekg:: utf8_concat( +void ekg::utf8_concat( std::string &string, const ekg::vec4_t &stride, std::string &concated @@ -415,6 +421,7 @@ void ekg:: utf8_concat( concated.clear(); if (!a.empty() || !b.empty()) { concated = a + b; + ekg_log_low_level(a << " x " << b) } } @@ -632,6 +639,7 @@ void ekg::text::erase( if (begin <= lines) { remains_lines = end - begin; begin = begin - previous_lines; + while (remains_lines != 0) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index a28d7343..39ac835c 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -287,6 +287,8 @@ void ekg::ui::handle_erase( std::string line {textbox.text.at(cursor.a.y)}; std::string concated {}; + ekg_log_low_level(cursor.a.x << " | " << cursor.b.x); + ekg::utf8_concat( line, {0, cursor.a.x, cursor.b.x, line.size()}, @@ -593,6 +595,8 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; + ekg_log_low_level("xup << " << textbox.widget.cursors.size()); + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; @@ -609,24 +613,32 @@ void ekg::ui::event( **/ if ( is_action_selected_fired + && + !(is_action_erase_left_fired || is_action_erase_left_fired) ) { if (cursor == cursor.delta && is_left_fired) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; + ekg_log_low_level("aa"); } else if (cursor == cursor.delta && is_right_fired) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; + ekg_log_low_level("bb"); } else if (cursor < cursor.delta) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; + ekg_log_low_level("cc"); } else if (cursor > cursor.delta) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; + ekg_log_low_level("dd"); } is_ab_equals = true; } + ekg_log_low_level("merow"); + if (is_left_fired) { if (is_ab_equals) { if (cursor.a.x > 0) { @@ -776,15 +788,19 @@ void ekg::ui::event( if (cursor == cursor.delta && is_left_fired) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; + ekg_log_low_level("a"); } else if (cursor == cursor.delta && is_right_fired) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; + ekg_log_low_level("b"); } else if (cursor < cursor.delta) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; + ekg_log_low_level("c"); } else if (cursor > cursor.delta) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; + ekg_log_low_level("d"); } highest = cursor.highest_char_index; @@ -805,6 +821,7 @@ void ekg::ui::event( } } else { cursor.delta = cursor.a; + ekg_log_low_level(cursor.delta.x << " xu " ); } if (is_action_erase_fired) { From 64384579f152d5f58fddfc200e95cd61af33651c Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sat, 20 Sep 2025 17:55:35 -0300 Subject: [PATCH 31/61] [fix] cursor insert spaces, horizontal-scroll, picking index --- include/ekg/ui/textbox/widget.hpp | 4 ++ src/io/utf.cpp | 5 +- src/ui/textbox/widget.cpp | 93 ++++++++++++++++--------------- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index c1e8b8db..497f2c20 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -54,6 +54,10 @@ namespace ekg::ui { ekg::textbox_t::cursor_t &cursor ); + void refresh_scroll_sizes( + ekg::textbox_t &textbox + ); + void handle_insert( ekg::textbox_t &textbox, ekg::textbox_t::cursor_t &cursor, diff --git a/src/io/utf.cpp b/src/io/utf.cpp index b768778d..92ffeabe 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -421,7 +421,6 @@ void ekg::utf8_concat( concated.clear(); if (!a.empty() || !b.empty()) { concated = a + b; - ekg_log_low_level(a << " x " << b) } } @@ -431,6 +430,8 @@ void ekg::text::swizzle( std::vector &to_swizzle, bool skip_first_line ) { + this->was_audited = true; + bool is_empty {to_swizzle.empty()}; ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunk_index)}; @@ -439,7 +440,7 @@ void ekg::text::swizzle( } if (to_swizzle.empty()) { - return; + to_swizzle.emplace_back(); } chunk.insert( diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 39ac835c..799ec1fb 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -33,6 +33,20 @@ #include "ekg/core/pools.hpp" #include "ekg/math/floating_point.hpp" +void ekg::ui::refresh_scroll_sizes( + ekg::textbox_t &textbox +) { + size_t highest_size {}; + for (ekg::io::chunk_t &chunk : textbox.text.chunks_data()) { + for (std::string &line : chunk) { + highest_size = ekg::max(line.size(), highest_size); + } + }; + + textbox.widget.scrollbar.rect.w = highest_size * (textbox.widget.rect_text_size.h * 0.6f); + textbox.widget.scrollbar.rect.h = textbox.text.length_of_lines() * textbox.widget.rect_text_size.h; +} + bool ekg::ui::find_cursor( ekg::textbox_t &textbox, ekg::vec2_t &index, @@ -79,11 +93,6 @@ bool ekg::ui::find_index_by_interact( size_t addition_chunk_index {}; size_t text_total_lines {textbox.text.length_of_lines()}; - ekg::vec2_t pos { - static_cast(static_cast(textbox.color_scheme.gutter_margin)) + textbox.widget.scrollbar_property.scroll.position.x, - 0.0f - }; - ekg::vec2_t rendered {}; ekg::rect_t rect {}; @@ -98,10 +107,14 @@ bool ekg::ui::find_index_by_interact( index.y += textbox.widget.view_line_index; textbox.widget.scrollbar.rect.h = text_total_lines * textbox.widget.rect_text_size.h; + ekg::vec2_t pos { + 0.0f, + 0.0f + }; + float visible_text_height {static_cast(textbox.widget.view_line_index * textbox.widget.rect_text_size.h)}; pos.y = textbox.widget.scrollbar_property.scroll.position.y + visible_text_height; pos.y = static_cast(static_cast(floorf(pos.y))); - pos.x = textbox.color_scheme.gutter_margin; std::string empty {"\n"}; bool is_empty {}; @@ -149,7 +162,7 @@ bool ekg::ui::find_index_by_interact( ekg::io::glyph_t &glyph {draw_font.mapped_glyph[c32]}; - rect.x = pos.x + rect_abs.x; + rect.x = pos.x + textbox.widget.scrollbar_property.scroll.position.x + rect_abs.x; rect.y = pos.y + rect_abs.y; rect.w = glyph.wsize / 2; rect.h = textbox.widget.rect_text_size.h; @@ -287,7 +300,6 @@ void ekg::ui::handle_erase( std::string line {textbox.text.at(cursor.a.y)}; std::string concated {}; - ekg_log_low_level(cursor.a.x << " | " << cursor.b.x); ekg::utf8_concat( line, @@ -346,7 +358,6 @@ void ekg::ui::handle_insert( cursor.a.y++; cursor.a.x = 0; cursor.b = cursor.a; - return; } @@ -395,23 +406,12 @@ void ekg::ui::reload( size_t highest_size {}; std::vector &chunks {textbox.text.chunks_data()}; - if (textbox.text.audited()) { - for (ekg::io::chunk_t &chunk : chunks) { - for (std::string &line : chunk) { - highest_size = ekg::max(line.size(), highest_size); - } - }; - - property.widget.should_buffering = true; - } - ekg::rect_t &rect_abs { ekg::ui::get_abs_rect(property, textbox.rect) }; textbox.widget.scrollbar.rect.x = rect_abs.x; textbox.widget.scrollbar.rect.y = rect_abs.y; - textbox.widget.scrollbar.rect.w = highest_size * textbox.widget.rect_text_size.h; textbox.widget.scrollbar.acceleration = {textbox.widget.rect_text_size.h, textbox.widget.rect_text_size.h}; textbox.widget.scrollbar.color_scheme = ekg::p_core->handler_theme.get_current_theme().scrollbar_color_scheme; @@ -423,6 +423,8 @@ void ekg::ui::reload( property.children, false ); + + ekg::ui::refresh_scroll_sizes(textbox); } void ekg::ui::event( @@ -500,6 +502,14 @@ void ekg::ui::event( ekg::p_core->p_platform_base->system_cursor = ekg::system_cursor::ibeam; } + if ( + textbox.widget.scrollbar.widget.states_horizontal_bar.is_hovering + || + textbox.widget.scrollbar.widget.states_vertical_bar.is_hovering + ) { + ekg::p_core->p_platform_base->system_cursor = ekg::system_cursor::arrow; + } + ekg::vec2_t pick_index {}; bool should_pick_index {}; bool picked_index {}; @@ -595,7 +605,6 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; - ekg_log_low_level("xup << " << textbox.widget.cursors.size()); for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { is_ab_equals = cursor.a == cursor.b; @@ -619,25 +628,20 @@ void ekg::ui::event( if (cursor == cursor.delta && is_left_fired) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; - ekg_log_low_level("aa"); } else if (cursor == cursor.delta && is_right_fired) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; - ekg_log_low_level("bb"); } else if (cursor < cursor.delta) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; - ekg_log_low_level("cc"); } else if (cursor > cursor.delta) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; - ekg_log_low_level("dd"); } is_ab_equals = true; } - ekg_log_low_level("merow"); if (is_left_fired) { if (is_ab_equals) { @@ -768,17 +772,6 @@ void ekg::ui::event( cursor.a = cursor.b; } - cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); - cursor_pos.y = static_cast(static_cast(floorf(cursor_pos.y))); - - if (cursor_pos.y + textbox.widget.rect_text_size.h > rect_abs.h) { - textbox.widget.scrollbar_property.scroll.position.w -= - ekg::min(cursor_pos.y + textbox.widget.rect_text_size.h - rect_abs.h, textbox.widget.rect_text_size.h); - } else if (cursor_pos.y <= 0.0f) { - textbox.widget.scrollbar_property.scroll.position.w += - ekg::min(-cursor_pos.y, textbox.widget.rect_text_size.h); - } - /** * While firstly we move cursor and check the right direction. * We need check again the right direction after moved. @@ -788,19 +781,15 @@ void ekg::ui::event( if (cursor == cursor.delta && is_left_fired) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; - ekg_log_low_level("a"); } else if (cursor == cursor.delta && is_right_fired) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; - ekg_log_low_level("b"); } else if (cursor < cursor.delta) { cursor.a = cursor.b; cursor.direction = ekg::dock::right; - ekg_log_low_level("c"); } else if (cursor > cursor.delta) { cursor.b = cursor.a; cursor.direction = ekg::dock::left; - ekg_log_low_level("d"); } highest = cursor.highest_char_index; @@ -819,9 +808,6 @@ void ekg::ui::event( } else if (is_right_fired) { cursor.highest_char_index = highest; } - } else { - cursor.delta = cursor.a; - ekg_log_low_level(cursor.delta.x << " xu " ); } if (is_action_erase_fired) { @@ -836,6 +822,25 @@ void ekg::ui::event( ? EKG_EOF_SYSTEM : input.typed ); } + + if (!is_action_selected_fired) { + cursor.delta = cursor.a; + } + + cursor_pos.y = textbox.widget.scrollbar_property.scroll.position.y + (cursor.a.y * textbox.widget.rect_text_size.h); + cursor_pos.y = static_cast(static_cast(floorf(cursor_pos.y))); + + if (cursor_pos.y + textbox.widget.rect_text_size.h > rect_abs.h) { + textbox.widget.scrollbar_property.scroll.position.w -= + ekg::min(cursor_pos.y + textbox.widget.rect_text_size.h - rect_abs.h, textbox.widget.rect_text_size.h); + } else if (cursor_pos.y <= 0.0f) { + textbox.widget.scrollbar_property.scroll.position.w += + ekg::min(-cursor_pos.y, textbox.widget.rect_text_size.h); + } + } + + if (is_action_erase_fired || input.was_typed || is_textbox_action_break_line_fired) { + ekg::ui::refresh_scroll_sizes(textbox); } break; From 8b3abed33400e81b59d75cb71aa689a32bb9b805 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 21 Sep 2025 18:58:09 -0300 Subject: [PATCH 32/61] [feature] added clipboard functions --- CMakeLists.txt | 1 + cmake/properties.cmake | 8 ++-- include/ekg/io/utf.hpp | 5 +++ src/io/utf.cpp | 75 ++++++++++++++++++++++++++++++++- src/ui/textbox/widget.cpp | 89 +++++++++++++++++++++++++++++++++++---- 5 files changed, 165 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d267196..ee06479c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,7 @@ target_compile_options( target_compile_definitions( ekg PRIVATE EKG_VERSION="${EKG_VERSION}" + EKG_EOF_SYSTEM="${PLATFORM_EOF_SYSTEM}" ) set_target_properties( diff --git a/cmake/properties.cmake b/cmake/properties.cmake index 46a26c66..e668ee68 100644 --- a/cmake/properties.cmake +++ b/cmake/properties.cmake @@ -38,18 +38,18 @@ endif() if(WIN32 OR EKG_FORCE_WINDOWS) set(LIBRARY_OUTPUT_PATH "../lib/windows/") set(PLATFORM "windows") - add_compile_definitions(EKG_EOF_SYSTEM="\r\n") + set(PLATFORM_EOF_SYSTEM "\\r\\n") elseif(ANDROID OR EKG_FORCE_ANDROID) set(LIBRARY_OUTPUT_PATH "${ANDROID_ABI}/") set(PLATFORM "${ANDROID_ABI}") - add_compile_definitions(EKG_EOF_SYSTEM="\n") + set(PLATFORM_EOF_SYSTEM "\\n") elseif(EKG_EMSCRIPTEN_BUILD_TYPE) set(LIBRARY_OUTPUT_PATH "../lib/linux-wasm/") set(PLATFORM "linux-wasm") - add_compile_definitions(EKG_EOF_SYSTEM="\n") + set(PLATFORM_EOF_SYSTEM "\\n") elseif(LINUX OR EKG_FORCE_LINUX) # WSL is not detected as Linux-kernel based OS; force is necessary >< set(LIBRARY_OUTPUT_PATH "../lib/linux/") set(PLATFORM "linux") - add_compile_definitions(EKG_EOF_SYSTEM="\n") + set(PLATFORM_EOF_SYSTEM "\\n") endif() diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 1992e9ab..a546cff7 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -247,6 +247,11 @@ namespace ekg { void push_back(std::string_view line); void set(size_t index, std::string_view line); + std::string read( + ekg::vec2_t &begin, + ekg::vec2_t &end + ); + void insert( size_t index, const ekg::io::chunk_t &to_insert_chunk diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 92ffeabe..414a9cb0 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -568,7 +568,8 @@ void ekg::text::insert( ekg::utf8_split_endings(lines, splitted); } - for (size_t it {}; it < this->loaded_chunks.size(); it++) { + size_t total_of_chunks {this->loaded_chunks.size()}; + for (size_t it {}; it < total_of_chunks; it++) { ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; previous_lines = current_lines; @@ -599,6 +600,78 @@ void ekg::text::insert( this->insert(index, chunk); } +std::string ekg::text::read( + ekg::vec2_t &begin, + ekg::vec2_t &end +) { + size_t previous_lines {}; + size_t chunk_size {}; + size_t current_lines {}; + size_t total_of_chunks {this->loaded_chunks.size()}; + size_t line_index {}; + size_t cut_length {end.y - begin.y}; + size_t cut_count {}; + size_t lines {}; + + size_t i {}; + bool oka_begin {}; + bool oka_end {}; + + std::string builder {}; + for (size_t it {}; it < total_of_chunks; it++) { + ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + + previous_lines = current_lines; + current_lines += (chunk_size = chunk.size()); + + if ( + begin.y > current_lines + ) { + continue; + } + + i = 0; + if (!oka_begin) { + i = begin.y - previous_lines; + oka_begin = true; + } + + if (end.y < previous_lines) { + return builder; + } + + previous_lines += i; + + for (; i < chunk_size; i++) { + std::string &line {chunk.at(i)}; + + if ( + previous_lines == begin.y + && + previous_lines == end.y + ) { + builder += ekg::utf8_substr(line, begin.x, end.x - begin.x); + return builder; + } + + if ( + previous_lines == begin.y + ) { + builder += ekg::utf8_substr(line, begin.x, UINT32_MAX) + EKG_EOF_SYSTEM; + } else if ( + previous_lines == end.y + ) { + builder += ekg::utf8_substr(line, 0, end.x); + return builder; + } else { + builder += line + EKG_EOF_SYSTEM; + } + + previous_lines++; + } + } +} + void ekg::text::erase( size_t begin, size_t end diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 799ec1fb..e1d92a3e 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -296,6 +296,10 @@ void ekg::ui::handle_erase( ekg::textbox_t &textbox, ekg::textbox_t::cursor_t &cursor ) { + if (cursor.a == cursor.b) { + return; + } + if (cursor.a.y == cursor.b.y) { std::string line {textbox.text.at(cursor.a.y)}; std::string concated {}; @@ -314,6 +318,7 @@ void ekg::ui::handle_erase( cursor.b = cursor.a; cursor.delta = cursor.a; + cursor.highest_char_index = cursor.a.x; return; } @@ -338,6 +343,7 @@ void ekg::ui::handle_erase( cursor.b = cursor.a; cursor.delta = cursor.a; + cursor.highest_char_index = cursor.a.x; } void ekg::ui::handle_insert( @@ -358,6 +364,7 @@ void ekg::ui::handle_insert( cursor.a.y++; cursor.a.x = 0; cursor.b = cursor.a; + cursor.highest_char_index = cursor.a.x; return; } @@ -375,6 +382,7 @@ void ekg::ui::handle_insert( cursor.a.x += ekg::utf8_length(typed); cursor.b = cursor.a; + cursor.highest_char_index = cursor.a.x; } void ekg::ui::reload( @@ -451,7 +459,7 @@ void ekg::ui::event( switch (stage) { default: { - ekg::input_info_t &input {ekg::p_core->handler_input.input}; + ekg::input_info_t input {ekg::p_core->handler_input.input}; ekg::vec2_t interact {static_cast>(input.interact)}; /* focus part */ @@ -567,13 +575,40 @@ void ekg::ui::event( return; } - /* logic of cursors, for handling lot of curosrs we will use only one loop for improve performance */ + /** + * logic of cursors: + * for handling lot of cursors we will + * use only one loop for improve performance + **/ + + bool is_action_selected_all_fired {ekg::fired("textbox-action-select-all")}; + if (is_action_selected_all_fired) { + size_t lines {textbox.text.length_of_lines()}; + if (lines > 0) { + lines -= 1; + std::string last_line {textbox.text.at(lines)}; + textbox.widget.cursors.clear(); + textbox.widget.cursors.push_back( + { + .a = {0, 0}, + .b = {ekg::utf8_length(last_line), lines} + } + ); + } + + return; + } + + bool is_action_copy {ekg::fired("clipboard-copy")}; + bool is_action_paste {ekg::fired("clipboard-paste")}; + bool is_action_cut {ekg::fired("clipboard-cut")}; bool is_action_erase_right_fired {ekg::fired("textbox-action-erase-right")}; bool is_action_erase_left_fired {ekg::fired("textbox-action-erase-left")}; bool is_action_erase_fired {is_action_erase_left_fired || is_action_erase_right_fired}; bool is_action_selected_keybind_fired {ekg::fired("textbox-action-select")}; bool is_action_selected_fired {}; + bool is_action_break_line_fired {ekg::fired("textbox-action-break-line")}; bool is_modifier_fired {ekg::fired("textbox-action-modifier")}; bool is_left_fired {ekg::fired("textbox-action-left")}; @@ -586,9 +621,31 @@ void ekg::ui::event( bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; bool is_arrows_fired {is_left_fired || is_right_fired || is_up_fired || is_down_fired}; - bool is_textbox_action_break_line_fired {ekg::fired("textbox-action-break-line")}; + if (is_action_paste) { + input.was_typed = true; + input.typed = ekg::p_core->p_platform_base->get_clipboard_text(); + } + + std::string clipboard_builder {}; + if (is_action_cut) { + input.was_typed = false; + } - if (is_arrows_fired || is_action_erase_fired || input.was_typed || is_textbox_action_break_line_fired) { + if ( + is_arrows_fired + || + is_action_erase_fired + || + input.was_typed + || + is_action_break_line_fired + || + is_action_copy + || + is_action_cut + || + is_action_paste + ) { textbox.widget.set_cursor_static = true; } @@ -605,8 +662,11 @@ void ekg::ui::event( ekg::vec2_t cursor_pos {}; std::string line {}; + size_t cursors_size {textbox.widget.cursors.size()}; + size_t cursor_count {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + cursor_count++; is_ab_equals = cursor.a == cursor.b; is_action_selected_fired = (is_arrows_fired && is_action_selected_keybind_fired) || (is_action_erase_fired && is_ab_equals); @@ -810,15 +870,24 @@ void ekg::ui::event( } } - if (is_action_erase_fired) { + if (is_action_cut || is_action_copy) { + clipboard_builder += textbox.text.read( + cursor.a, + cursor.b + ) + + + (((cursors_size > 1) && !is_ab_equals && cursor_count != cursors_size) ? EKG_EOF_SYSTEM : ""); + } + + if (is_action_erase_fired || is_action_cut) { ekg::ui::handle_erase(textbox, cursor); } - if (input.was_typed || is_textbox_action_break_line_fired) { + if (input.was_typed || is_action_break_line_fired) { ekg::ui::handle_insert( textbox, cursor, - is_textbox_action_break_line_fired + is_action_break_line_fired ? EKG_EOF_SYSTEM : input.typed ); } @@ -839,7 +908,11 @@ void ekg::ui::event( } } - if (is_action_erase_fired || input.was_typed || is_textbox_action_break_line_fired) { + if (is_action_cut || is_action_copy) { + ekg::p_core->p_platform_base->set_clipboard_text(clipboard_builder.c_str()); + } + + if (is_action_erase_fired || input.was_typed || is_action_break_line_fired) { ekg::ui::refresh_scroll_sizes(textbox); } From c4935667ae4b18f7a5ba24f68eb63c9379fb961d Mon Sep 17 00:00:00 2001 From: mrsrina Date: Wed, 24 Sep 2025 00:08:50 -0300 Subject: [PATCH 33/61] [fix] text glitches --- include/ekg/handler/input/handler.hpp | 1 + include/ekg/io/utf.hpp | 2 +- include/ekg/ui/textbox/textbox.hpp | 4 ++-- src/draw/typography/font.cpp | 2 +- src/handler/input/handler.cpp | 34 ++++++++++++++++++++++++--- src/io/utf.cpp | 9 ++++++- src/ui/textbox/widget.cpp | 22 +++++++++++++---- 7 files changed, 62 insertions(+), 12 deletions(-) diff --git a/include/ekg/handler/input/handler.hpp b/include/ekg/handler/input/handler.hpp index 1a734792..c3c708fb 100644 --- a/include/ekg/handler/input/handler.hpp +++ b/include/ekg/handler/input/handler.hpp @@ -29,6 +29,7 @@ namespace ekg::handler { ekg::timing_t double_interact {}; ekg::timing_t last_time_wheel_was_fired {}; + ekg::timing_t cooldown_wheel {}; // prevent too many unoptmized use of string allocations std::string key_name {}; diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index a546cff7..18349b69 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -271,7 +271,7 @@ namespace ekg { size_t length_of_chunks(); std::string at(size_t index); - size_t length_of_lines(); + size_t length_of_lines(bool force = false); size_t length_of_chars(); bool audited(); diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index f555c85d..50120ed5 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -142,8 +142,8 @@ namespace ekg { ekg::at_array_t layers {}; std::array regex_operations { - std::regex("(^)|([^\\s:&\\(\\);]+)|(:+)|(&+)|(\\(\\))|(\\()|(\\))|(;+)"), - std::regex("[^\\s:&\\(\\);)]([\\s:])|(:+)|(&+)|(\\(\\))|(\\()|(\\))|(;+)|($)") + std::regex("(^)|([^\\s:&\\(\\);]+)|(:+)|(&+)|(\\(\\))|(\\()|(\\))|(;+)|([.]+)|(,+)"), + std::regex("[^\\s:&\\(\\);)]([\\s:])|(:+)|(&+)|(\\(\\))|(\\()|(\\))|(;+)|(\\.+)|(,+)|($)") }; public: ekg_descriptor(ekg::textbox_t); diff --git a/src/draw/typography/font.cpp b/src/draw/typography/font.cpp index 5952a01f..7607e45b 100644 --- a/src/draw/typography/font.cpp +++ b/src/draw/typography/font.cpp @@ -403,7 +403,7 @@ void ekg::draw::font::reload() { FT_Vector space_char_metrics {}; FT_Get_Kerning(text_font_face.ft_face, 32, 32, 0, &space_char_metrics); - this->space_wsize = static_cast(space_char_metrics.x >> 6); + this->space_wsize = static_cast(space_char_metrics.x >> 6); this->text_height = static_cast(this->font_size); this->offset_text_height = this->text_height / 6; diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index c8d5ca38..6f399624 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -463,9 +463,37 @@ void ekg::handler::input::poll_event() { wheel_precise_interval = wheel_precise_interval + (static_cast(wheel_precise_interval > 0.99) * 0.5f); wheel_precise_interval = ekg::clamp_min(wheel_precise_interval, 0.2f); - this->input.interact.z = platform_event.mouse_wheel_precise_x * wheel_precise_interval; - this->input.interact.w = platform_event.mouse_wheel_precise_y * wheel_precise_interval; - + bool cooldown {ekg::reach(this->cooldown_wheel, 350)}; + ekg::reset(this->cooldown_wheel); + + if ( + !cooldown + && + ( + (platform_event.mouse_wheel_precise_x < 0 && this->input.interact.z < 0) + || + (platform_event.mouse_wheel_precise_x > 0 && this->input.interact.z > 0) + ) + ) { + this->input.interact.z += platform_event.mouse_wheel_precise_x * wheel_precise_interval * 0.5f; + } else { + this->input.interact.z = platform_event.mouse_wheel_precise_x * wheel_precise_interval; + } + + if ( + !cooldown + && + ( + (platform_event.mouse_wheel_precise_y < 0 && this->input.interact.w < 0) + || + (platform_event.mouse_wheel_precise_y > 0 && this->input.interact.w > 0) + ) + ) { + this->input.interact.w += platform_event.mouse_wheel_precise_y * wheel_precise_interval * 0.5f; + } else { + this->input.interact.w = platform_event.mouse_wheel_precise_y * wheel_precise_interval; + } + ekg::reset(this->last_time_wheel_was_fired); break; } diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 414a9cb0..0976c65e 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -780,7 +780,14 @@ size_t ekg::text::length_of_chunks() { return this->loaded_chunks.size(); } -size_t ekg::text::length_of_lines() { +size_t ekg::text::length_of_lines(bool force) { + if (force) { + this->total_lines = 0; + for (ekg::io::chunk_t &chunk : this->loaded_chunks) { + this->total_lines += chunk.size(); + } + } + return this->total_lines; } diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index e1d92a3e..a6d4b178 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -583,7 +583,7 @@ void ekg::ui::event( bool is_action_selected_all_fired {ekg::fired("textbox-action-select-all")}; if (is_action_selected_all_fired) { - size_t lines {textbox.text.length_of_lines()}; + size_t lines {textbox.text.length_of_lines(true)}; if (lines > 0) { lines -= 1; std::string last_line {textbox.text.at(lines)}; @@ -876,7 +876,16 @@ void ekg::ui::event( cursor.b ) + - (((cursors_size > 1) && !is_ab_equals && cursor_count != cursors_size) ? EKG_EOF_SYSTEM : ""); + ( + ( + (cursors_size > 1) + && + !is_ab_equals + && + cursor_count != cursors_size + ) + ? EKG_EOF_SYSTEM : "" + ); } if (is_action_erase_fired || is_action_cut) { @@ -890,6 +899,10 @@ void ekg::ui::event( is_action_break_line_fired ? EKG_EOF_SYSTEM : input.typed ); + + if (is_action_break_line_fired || is_action_paste) { + text_total_lines = textbox.text.length_of_lines(true); + } } if (!is_action_selected_fired) { @@ -901,10 +914,10 @@ void ekg::ui::event( if (cursor_pos.y + textbox.widget.rect_text_size.h > rect_abs.h) { textbox.widget.scrollbar_property.scroll.position.w -= - ekg::min(cursor_pos.y + textbox.widget.rect_text_size.h - rect_abs.h, textbox.widget.rect_text_size.h); + ekg::max(cursor_pos.y + textbox.widget.rect_text_size.h - rect_abs.h, textbox.widget.rect_text_size.h); } else if (cursor_pos.y <= 0.0f) { textbox.widget.scrollbar_property.scroll.position.w += - ekg::min(-cursor_pos.y, textbox.widget.rect_text_size.h); + ekg::max(-cursor_pos.y, textbox.widget.rect_text_size.h); } } @@ -1353,6 +1366,7 @@ void ekg::ui::buffering( } } + draw_font.flush(); data.hash = hash; ekg::draw::allocator::is_simple_shape = false; From 150496a1f6e4c1f858fc9c6374bace5c644ae4c5 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Thu, 25 Sep 2025 21:35:48 -0300 Subject: [PATCH 34/61] [fix] hash for line ending --- include/ekg/io/utf.hpp | 2 +- src/io/utf.cpp | 3 ++- src/ui/textbox/widget.cpp | 19 +++++++++++++------ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 18349b69..89505be8 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -245,7 +245,7 @@ namespace ekg { ); public: void push_back(std::string_view line); - void set(size_t index, std::string_view line); + size_t set(size_t index, std::string_view line); std::string read( ekg::vec2_t &begin, diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 0976c65e..f31540d0 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -490,7 +490,7 @@ void ekg::text::swizzle( } } -void ekg::text::set(size_t index, std::string_view line) { +size_t ekg::text::set(size_t index, std::string_view line) { size_t current_lines {}; size_t previous_lines {}; size_t chunk_size {}; @@ -520,6 +520,7 @@ void ekg::text::set(size_t index, std::string_view line) { if (!ok) throw std::out_of_range("ekg::text::set -> lines length: " + std::to_string(current_lines)); this->total_lines = current_lines; + return ending_splitted.size(); } std::string ekg::text::at(size_t index) { diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index a6d4b178..ba38032b 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -201,7 +201,7 @@ bool ekg::ui::find_index_by_interact( } return false; -} +} void ekg::ui::handle_cursor_interact( ekg::property_t &property, @@ -304,7 +304,6 @@ void ekg::ui::handle_erase( std::string line {textbox.text.at(cursor.a.y)}; std::string concated {}; - ekg::utf8_concat( line, {0, cursor.a.x, cursor.b.x, line.size()}, @@ -378,9 +377,14 @@ void ekg::ui::handle_insert( typed.end() ); - textbox.text.set(cursor.a.y, line); - - cursor.a.x += ekg::utf8_length(typed); + size_t added {textbox.text.set(cursor.a.y, line)}; + if (added == 1) { + cursor.a.x += ekg::utf8_length(typed); + } else { + cursor.a.y += added-1; + cursor.a.x = ekg::utf8_length(textbox.text.at(cursor.a.y)); + } + cursor.b = cursor.a; cursor.highest_char_index = cursor.a.x; } @@ -901,7 +905,7 @@ void ekg::ui::event( ); if (is_action_break_line_fired || is_action_paste) { - text_total_lines = textbox.text.length_of_lines(true); + text_total_lines = textbox.text.length_of_lines(true); } } @@ -929,6 +933,8 @@ void ekg::ui::event( ekg::ui::refresh_scroll_sizes(textbox); } + ekg::gui.ui.redraw = true; + break; } case ekg::io::stage::pre: @@ -1267,6 +1273,7 @@ void ekg::ui::buffering( } if (is_empty) { + hash += ekg_generate_hash(pos.x, c32, glyph.x++); continue; } From 5dcbbd7222cd10b8d90b5762326e70910d99d477 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sat, 11 Oct 2025 02:38:21 -0300 Subject: [PATCH 35/61] [fix] multi-cursor erasing complex glitchy --- include/ekg/ui/textbox/textbox.hpp | 7 +- include/ekg/ui/textbox/widget.hpp | 11 ++- src/ui/textbox/textbox.cpp | 2 +- src/ui/textbox/widget.cpp | 131 ++++++++++++++++++++++++++++- 4 files changed, 144 insertions(+), 7 deletions(-) diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 50120ed5..9d293330 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -67,6 +67,7 @@ namespace ekg { ekg::vec2_t b {}; ekg::vec2_t delta {}; ekg::rect_t rect {}; + bool is_ignored {}; public: bool operator == (const ekg::vec2_t &index) { return index.x == this->a.x && index.y == this->a.y && index.x == this->b.x && index.y == this->b.y; @@ -93,7 +94,11 @@ namespace ekg { } bool operator == (const ekg::textbox_t::cursor_t &cursor) { - return *this == cursor.a && *this == cursor.b; + return (this->a.x == cursor.a.x && this->a.y == cursor.a.y) && (this->b.x == cursor.b.x && this->b.y == cursor.b.y); + } + + bool operator != (const ekg::textbox_t::cursor_t &cursor) { + return !(*this == cursor); } }; diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 497f2c20..64cbbdbc 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -28,6 +28,15 @@ #include "textbox.hpp" namespace ekg::ui { + void refresh_cursors_pos( + ekg::textbox_t &textbox, + ekg::textbox_t::cursor_t &origin, + const ekg::vec2_t &displacement_a, + const ekg::vec2_t &displacement_b, + const ekg::vec2_t &displacement_delta, + ekg::flags_t direction + ); + bool find_cursor( ekg::textbox_t &textbox, ekg::vec2_t &index, @@ -54,7 +63,7 @@ namespace ekg::ui { ekg::textbox_t::cursor_t &cursor ); - void refresh_scroll_sizes( + void refresh_scroll_positions( ekg::textbox_t &textbox ); diff --git a/src/ui/textbox/textbox.cpp b/src/ui/textbox/textbox.cpp index ee6d9961..5c55d706 100644 --- a/src/ui/textbox/textbox.cpp +++ b/src/ui/textbox/textbox.cpp @@ -10,7 +10,7 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all + * The above copyright `notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index ba38032b..2ca00369 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -33,7 +33,74 @@ #include "ekg/core/pools.hpp" #include "ekg/math/floating_point.hpp" -void ekg::ui::refresh_scroll_sizes( +void ekg::ui::refresh_cursors_pos( + ekg::textbox_t &textbox, + ekg::textbox_t::cursor_t &origin, + const ekg::vec2_t &displacement_a, + const ekg::vec2_t &displacement_b, + const ekg::vec2_t &displacement_delta, + ekg::flags_t direction +) { + bool is_resize {ekg::has(direction, ekg::dock::resize)}; + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_ignored) continue; + if (cursor.a.y < origin.a.y) continue; + + if (!is_resize) { + if ( + cursor.a.y == origin.a.y + && + cursor.a.x > origin.a.x + ) { + cursor.a.x = ekg::has(direction, ekg::dock::left) + ? (cursor.a.x - displacement_a.x) : (cursor.a.x + displacement_a.x); + } + + if ( + cursor.b.y == origin.b.y + && + cursor.b.x > origin.b.x + ) { + cursor.b.x = ekg::has(direction, ekg::dock::left) + ? (cursor.b.x - displacement_b.x) : (cursor.b.x + displacement_b.x); + } + + if (cursor.a.y >= origin.b.y) { + cursor.a.y -= displacement_a.y; + cursor.b.y -= displacement_b.y; + } + } else { + if ( + cursor.a.y == origin.b.y + && + cursor.a.x > origin.b.x + ) { + cursor.a.x = (origin.a.x < origin.b.x) ? + cursor.a.x - (origin.b.x - origin.a.x) + : + cursor.a.x + (origin.a.x - origin.b.x); + } + + if ( + cursor.b.y == origin.b.y + && + cursor.b.x > origin.b.x + ) { + cursor.b.x = (origin.a.x < origin.b.x) ? + cursor.b.x- (origin.b.x - origin.a.x) + : + cursor.b.x + (origin.a.x - origin.b.x); + } + + if (cursor.a.y >= origin.b.y) { + cursor.a.y -= displacement_a.y; + cursor.b.y -= displacement_b.y; + } + } + } +} + +void ekg::ui::refresh_scroll_positions( ekg::textbox_t &textbox ) { size_t highest_size {}; @@ -211,6 +278,33 @@ void ekg::ui::handle_cursor_interact( ekg::input_info_t &input ) { if (input.was_released && !input.was_typed) { + if ( + textbox.widget.current_cursor_index != UINT64_MAX + && + textbox.widget.current_cursor_index < textbox.widget.cursors.size() + ) { + ekg::textbox_t::cursor_t cursor { + textbox.widget.cursors[textbox.widget.current_cursor_index] + }; + + std::vector new_cursors {}; + for (size_t i {}; i < textbox.widget.cursors.size(); i++) { + ekg::textbox_t::cursor_t cur {textbox.widget.cursors[i]}; + if (cursor == cur) { + new_cursors.push_back(cur); + continue; + } + + if (cursor >= cur.a && cursor <= cur.b) { + continue; + } + + new_cursors.push_back(cur); + } + + textbox.widget.cursors = new_cursors; + } + textbox.widget.current_cursor_index = UINT64_MAX; } @@ -304,6 +398,21 @@ void ekg::ui::handle_erase( std::string line {textbox.text.at(cursor.a.y)}; std::string concated {}; + ekg::textbox_t::cursor_t origin {cursor}; + origin.a = origin.delta; + origin.b = origin.delta; + + cursor.is_ignored = true; + ekg::ui::refresh_cursors_pos( + textbox, + origin, + {cursor.b.x - cursor.a.x, 0}, + {cursor.b.x - cursor.a.x, 0}, + {cursor.b.x - cursor.a.x, 0}, + ekg::dock::left + ); + cursor.is_ignored = false; + ekg::utf8_concat( line, {0, cursor.a.x, cursor.b.x, line.size()}, @@ -322,6 +431,19 @@ void ekg::ui::handle_erase( return; } + ekg::textbox_t::cursor_t origin {cursor}; + + cursor.is_ignored = true; + ekg::ui::refresh_cursors_pos( + textbox, + origin, + {cursor.b.x, cursor.b.y - cursor.a.y}, + {cursor.b.x, cursor.b.y - cursor.a.y}, + {cursor.b.x, cursor.b.y - cursor.a.y}, + ekg::dock::left | ekg::dock::resize + ); + + cursor.is_ignored = false; textbox.text.set( cursor.a.y, ekg::utf8_substr( @@ -425,7 +547,6 @@ void ekg::ui::reload( textbox.widget.scrollbar.rect.x = rect_abs.x; textbox.widget.scrollbar.rect.y = rect_abs.y; textbox.widget.scrollbar.acceleration = {textbox.widget.rect_text_size.h, textbox.widget.rect_text_size.h}; - textbox.widget.scrollbar.color_scheme = ekg::p_core->handler_theme.get_current_theme().scrollbar_color_scheme; ekg::ui::reload( @@ -436,7 +557,7 @@ void ekg::ui::reload( false ); - ekg::ui::refresh_scroll_sizes(textbox); + ekg::ui::refresh_scroll_positions(textbox); } void ekg::ui::event( @@ -704,6 +825,8 @@ void ekg::ui::event( } is_ab_equals = true; + } else { + cursor.delta = cursor.a; } @@ -930,7 +1053,7 @@ void ekg::ui::event( } if (is_action_erase_fired || input.was_typed || is_action_break_line_fired) { - ekg::ui::refresh_scroll_sizes(textbox); + ekg::ui::refresh_scroll_positions(textbox); } ekg::gui.ui.redraw = true; From 23bd31fc8623f2fc9666796b4d4fec27cf1d01b5 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sat, 11 Oct 2025 18:39:15 -0300 Subject: [PATCH 36/61] [fix] erase multi-cursors movement --- src/ui/textbox/widget.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 2ca00369..9e88eaca 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -43,8 +43,7 @@ void ekg::ui::refresh_cursors_pos( ) { bool is_resize {ekg::has(direction, ekg::dock::resize)}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored) continue; - if (cursor.a.y < origin.a.y) continue; + if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; if (!is_resize) { if ( @@ -479,6 +478,21 @@ void ekg::ui::handle_insert( if (typed == EKG_EOF_SYSTEM) { std::string line {textbox.text.at(cursor.a.y)}; + ekg::textbox_t::cursor_t origin {cursor}; + origin.a = origin.delta; + origin.b = origin.delta; + + cursor.is_ignored = true; + ekg::ui::refresh_cursors_pos( + textbox, + origin, + {cursor.b.x - cursor.a.x, 0}, + {cursor.b.x - cursor.a.x, 0}, + {cursor.b.x - cursor.a.x, 0}, + ekg::dock::left + ); + cursor.is_ignored = false; + textbox.text.set(cursor.a.y, ekg::utf8_substr(line, 0, cursor.a.x)); textbox.text.insert(cursor.a.y, ekg::utf8_substr(line, cursor.a.x, UINT32_MAX)); @@ -734,17 +748,20 @@ void ekg::ui::event( bool is_action_selected_keybind_fired {ekg::fired("textbox-action-select")}; bool is_action_selected_fired {}; bool is_action_break_line_fired {ekg::fired("textbox-action-break-line")}; + bool block_if_ab_diff_when_erased_is_fired {}; bool is_modifier_fired {ekg::fired("textbox-action-modifier")}; bool is_left_fired {ekg::fired("textbox-action-left")}; + bool is_bind_left_fired {is_left_fired}; bool is_modifier_left_fired {}; bool is_right_fired {ekg::fired("textbox-action-right")}; + bool is_bind_right_fired {is_right_fired}; bool is_modifier_right_fired {}; bool is_up_fired {ekg::fired("textbox-action-up")}; bool is_modifier_up_fired {is_up_fired && is_modifier_fired}; bool is_down_fired {ekg::fired("textbox-action-down")}; bool is_modifier_down_fired {is_down_fired && is_modifier_fired}; - bool is_arrows_fired {is_left_fired || is_right_fired || is_up_fired || is_down_fired}; + bool is_arrows_fired {is_left_fired || is_right_fired || is_up_fired || is_down_fired}; if (is_action_paste) { input.was_typed = true; @@ -795,8 +812,8 @@ void ekg::ui::event( is_ab_equals = cursor.a == cursor.b; is_action_selected_fired = (is_arrows_fired && is_action_selected_keybind_fired) || (is_action_erase_fired && is_ab_equals); - is_left_fired = is_left_fired || (is_action_erase_left_fired && is_ab_equals); - is_right_fired = is_right_fired || (is_action_erase_right_fired && is_ab_equals); + is_left_fired = is_bind_left_fired || (is_action_erase_left_fired && is_ab_equals); + is_right_fired = is_bind_right_fired || (is_action_erase_right_fired && is_ab_equals); is_modifier_left_fired = is_left_fired && is_modifier_fired; is_modifier_right_fired = is_right_fired && is_modifier_fired; @@ -829,7 +846,6 @@ void ekg::ui::event( cursor.delta = cursor.a; } - if (is_left_fired) { if (is_ab_equals) { if (cursor.a.x > 0) { @@ -960,7 +976,7 @@ void ekg::ui::event( } /** - * While firstly we move cursor and check the right direction. + * First we move cursor and check the right direction (first structure like this). Then. * We need check again the right direction after moved. * This will make possible modifier works with select from any direction. **/ From ad26104d7fc61e3029d650fa9824dd73ee65879f Mon Sep 17 00:00:00 2001 From: mrsrina Date: Sun, 19 Oct 2025 20:24:24 -0300 Subject: [PATCH 37/61] [fix] refresh cursors inline multiline --- include/ekg/io/utf.hpp | 1 + include/ekg/ui/textbox/widget.hpp | 11 +- src/io/utf.cpp | 12 ++- src/ui/textbox/widget.cpp | 162 +++++++++++++++++++++++------- 4 files changed, 142 insertions(+), 44 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 89505be8..03f08cc7 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -245,6 +245,7 @@ namespace ekg { ); public: void push_back(std::string_view line); + size_t set(size_t index, std::string_view line, ekg::io::chunk_t &split_endings); size_t set(size_t index, std::string_view line); std::string read( diff --git a/include/ekg/ui/textbox/widget.hpp b/include/ekg/ui/textbox/widget.hpp index 64cbbdbc..6033f975 100644 --- a/include/ekg/ui/textbox/widget.hpp +++ b/include/ekg/ui/textbox/widget.hpp @@ -28,13 +28,20 @@ #include "textbox.hpp" namespace ekg::ui { + enum class textbox_operation { + insert_inline, + insert_multiline, + insert_line, + erase_inline, + erase_multiline + }; + void refresh_cursors_pos( ekg::textbox_t &textbox, ekg::textbox_t::cursor_t &origin, const ekg::vec2_t &displacement_a, const ekg::vec2_t &displacement_b, - const ekg::vec2_t &displacement_delta, - ekg::flags_t direction + const ekg::ui::textbox_operation &operation ); bool find_cursor( diff --git a/src/io/utf.cpp b/src/io/utf.cpp index f31540d0..53a73df6 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -491,12 +491,16 @@ void ekg::text::swizzle( } size_t ekg::text::set(size_t index, std::string_view line) { + ekg::io::chunk_t split_endings {}; + return this->set(index, line, split_endings); +} + +size_t ekg::text::set(size_t index, std::string_view line, ekg::io::chunk_t &split_endings) { size_t current_lines {}; size_t previous_lines {}; size_t chunk_size {}; - std::vector ending_splitted {}; - ekg::utf8_split_endings(line, ending_splitted); + ekg::utf8_split_endings(line, split_endings); bool ok {}; for (size_t it {}; it < this->loaded_chunks.size(); it++) { @@ -512,7 +516,7 @@ size_t ekg::text::set(size_t index, std::string_view line) { && (index - previous_lines) < chunk_size ) { - this->swizzle(it, (index - previous_lines), ending_splitted, true); + this->swizzle(it, (index - previous_lines), split_endings, true); this->was_audited = true; ok = true; } @@ -520,7 +524,7 @@ size_t ekg::text::set(size_t index, std::string_view line) { if (!ok) throw std::out_of_range("ekg::text::set -> lines length: " + std::to_string(current_lines)); this->total_lines = current_lines; - return ending_splitted.size(); + return split_endings.size(); } std::string ekg::text::at(size_t index) { diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 9e88eaca..5fe1b630 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -38,37 +38,89 @@ void ekg::ui::refresh_cursors_pos( ekg::textbox_t::cursor_t &origin, const ekg::vec2_t &displacement_a, const ekg::vec2_t &displacement_b, - const ekg::vec2_t &displacement_delta, - ekg::flags_t direction + const ekg::ui::textbox_operation &operation ) { - bool is_resize {ekg::has(direction, ekg::dock::resize)}; - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + switch (operation) { + case ekg::ui::textbox_operation::insert_line: + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; - if (!is_resize) { - if ( - cursor.a.y == origin.a.y - && - cursor.a.x > origin.a.x - ) { - cursor.a.x = ekg::has(direction, ekg::dock::left) - ? (cursor.a.x - displacement_a.x) : (cursor.a.x + displacement_a.x); + if (cursor.a.y == origin.b.y && cursor.a.x > origin.b.x) { + cursor.a.x += displacement_a.x; } - if ( - cursor.b.y == origin.b.y - && - cursor.b.x > origin.b.x - ) { - cursor.b.x = ekg::has(direction, ekg::dock::left) - ? (cursor.b.x - displacement_b.x) : (cursor.b.x + displacement_b.x); + if (cursor.b.y == origin.b.y && cursor.b.x > origin.b.x) { + cursor.b.x += displacement_b.x; } if (cursor.a.y >= origin.b.y) { - cursor.a.y -= displacement_a.y; - cursor.b.y -= displacement_b.y; + cursor.a.y += displacement_a.y; + cursor.b.y += displacement_b.y; } - } else { + } + break; + case ekg::ui::textbox_operation::insert_inline: + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + + if (cursor.a.y == origin.b.y && cursor.a.x > origin.b.x) { + cursor.a.x += displacement_a.x; + } + + if (cursor.b.y == origin.b.y && cursor.b.x > origin.b.x) { + cursor.b.x += displacement_b.x; + } + } + break; + case ekg::ui::textbox_operation::insert_multiline: + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + + if (cursor.a.y == origin.b.y && cursor.a.x > origin.b.x) { + cursor.a.x += displacement_a.x; + cursor.a.y += displacement_a.y; + } + + if (cursor.b.y == origin.b.y && cursor.b.x > origin.b.x) { + cursor.b.x += displacement_b.x; + cursor.b.y += displacement_b.y; + } + + if (cursor.a.y >= origin.b.y) { + cursor.a.y += displacement_a.y; + cursor.b.y += displacement_b.y; + } + } + break; + case ekg::ui::textbox_operation::erase_inline: + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + if ( + cursor.a.y == origin.a.y + && + cursor.a.x > origin.a.x + ) { + cursor.a.x = cursor.a.x - displacement_a.x; + } + + if ( + cursor.b.y == origin.b.y + && + cursor.b.x > origin.b.x + ) { + cursor.b.x = cursor.b.x - displacement_b.x; + } + + if (cursor.a.y >= origin.b.y) { + cursor.a.y -= displacement_a.y; + cursor.b.y -= displacement_b.y; + } + } + break; + case ekg::ui::textbox_operation::erase_multiline: + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + if ( cursor.a.y == origin.b.y && @@ -96,6 +148,7 @@ void ekg::ui::refresh_cursors_pos( cursor.b.y -= displacement_b.y; } } + break; } } @@ -407,8 +460,7 @@ void ekg::ui::handle_erase( origin, {cursor.b.x - cursor.a.x, 0}, {cursor.b.x - cursor.a.x, 0}, - {cursor.b.x - cursor.a.x, 0}, - ekg::dock::left + ekg::ui::textbox_operation::erase_inline ); cursor.is_ignored = false; @@ -438,8 +490,7 @@ void ekg::ui::handle_erase( origin, {cursor.b.x, cursor.b.y - cursor.a.y}, {cursor.b.x, cursor.b.y - cursor.a.y}, - {cursor.b.x, cursor.b.y - cursor.a.y}, - ekg::dock::left | ekg::dock::resize + ekg::ui::textbox_operation::erase_multiline ); cursor.is_ignored = false; @@ -477,24 +528,24 @@ void ekg::ui::handle_insert( if (typed == EKG_EOF_SYSTEM) { std::string line {textbox.text.at(cursor.a.y)}; + std::string right_cut {ekg::utf8_substr(line, cursor.a.x, UINT32_MAX)}; + size_t right_cut_size {ekg::utf8_length(right_cut)}; ekg::textbox_t::cursor_t origin {cursor}; - origin.a = origin.delta; - origin.b = origin.delta; + origin.b = origin.a; cursor.is_ignored = true; ekg::ui::refresh_cursors_pos( textbox, origin, - {cursor.b.x - cursor.a.x, 0}, - {cursor.b.x - cursor.a.x, 0}, - {cursor.b.x - cursor.a.x, 0}, - ekg::dock::left + {right_cut_size, 1}, + {right_cut_size, 1}, + ekg::ui::textbox_operation::insert_line ); cursor.is_ignored = false; textbox.text.set(cursor.a.y, ekg::utf8_substr(line, 0, cursor.a.x)); - textbox.text.insert(cursor.a.y, ekg::utf8_substr(line, cursor.a.x, UINT32_MAX)); + textbox.text.insert(cursor.a.y, right_cut); cursor.a.y++; cursor.a.x = 0; @@ -513,14 +564,49 @@ void ekg::ui::handle_insert( typed.end() ); - size_t added {textbox.text.set(cursor.a.y, line)}; + ekg::textbox_t::cursor_t origin {cursor}; + origin.b = origin.a; + + ekg::vec2_t displacement_a {}; + ekg::vec2_t displacement_b {}; + + ekg::io::chunk_t split_endings {}; + size_t added {textbox.text.set(cursor.a.y, line, split_endings)}; + if (added == 1) { - cursor.a.x += ekg::utf8_length(typed); + displacement_a = {ekg::utf8_length(typed), 0}; + displacement_b = displacement_a; + cursor.a.x += displacement_a.x; } else { - cursor.a.y += added-1; - cursor.a.x = ekg::utf8_length(textbox.text.at(cursor.a.y)); + displacement_a.y = added-1; + cursor.a.y += displacement_a.y; + + size_t ending {typed.rfind('\n')}; + if (ending != std::string::npos) { + displacement_a.x = ekg::utf8_length(typed.substr(ending, UINT32_MAX)); + } else { + displacement_a.x = ekg::utf8_length(typed); + } + + displacement_b = displacement_a; + cursor.a.x = displacement_a.x; } + cursor.is_ignored = true; + ekg::ui::refresh_cursors_pos( + textbox, + origin, + displacement_a, + displacement_b, + ( + added == 1 ? + ekg::ui::textbox_operation::insert_inline + : + ekg::ui::textbox_operation::insert_multiline + ) + ); + cursor.is_ignored = false; + cursor.b = cursor.a; cursor.highest_char_index = cursor.a.x; } From fc197bb8b73b65c1edb1fba8c9cf5e08e9e78201 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Tue, 28 Oct 2025 22:45:58 -0300 Subject: [PATCH 38/61] [fix] multiline insert --- src/handler/theme/handler.cpp | 63 +++++++++++++++++++++++++++++++++-- src/ui/textbox/widget.cpp | 61 +++++++++++++++++++-------------- 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index b5bc3f79..142403bf 100644 --- a/src/handler/theme/handler.cpp +++ b/src/handler/theme/handler.cpp @@ -30,7 +30,7 @@ void ekg::handler::theme::init() { ekg::theme_t light_pinky_theme { .tag = "light-pinky", .author = "Rina Wilk", - .description = "wasted love of my life, no love no more love no more" + .description = "amo vc amo vc" }; light_pinky_theme.layout_offset = 2.0f; @@ -89,7 +89,7 @@ void ekg::handler::theme::init() { ekg::theme_t black_light_pinky_theme { .tag = "black-light-pinky", .author = "Rina Wilk", - .description = "loved a shitty person God save me" + .description = "oiiiiiiiii eu amo vc serpa, lindo maravilhoso" }; black_light_pinky_theme.layout_offset = 2.0f; @@ -143,6 +143,65 @@ void ekg::handler::theme::init() { this->registry(black_light_pinky_theme.tag) = black_light_pinky_theme; //this->set_current_theme(black_light_pinky_theme.tag); + + ekg::theme_t black_pinky { + .tag = "black-pinky", + .author = "serpa", + .description = "colors for show colors" + }; + + black_pinky.layout_offset = 2.0f; + black_pinky.layout_margin_thickness = 2; + black_pinky.frame_color_scheme.background = {242, 242, 242, 255}; + black_pinky.frame_color_scheme.highlight = {242, 242, 242, 0}; + black_pinky.frame_color_scheme.outline = {190, 190, 190, 0}; + black_pinky.frame_color_scheme.active = {242, 242, 242, 0}; + black_pinky.frame_color_scheme.focused_background = {242, 242, 242, 0}; + black_pinky.frame_color_scheme.focused_outline = {242, 242, 242, 0}; + black_pinky.frame_color_scheme.warning_outline = {242, 242, 0, 100}; + black_pinky.frame_color_scheme.actions_margin_pixel_thickness = 18; + + black_pinky.button_color_scheme.text_foreground = {141, 141, 141, 255}; + black_pinky.button_color_scheme.background = {204, 204, 204, 50}; + black_pinky.button_color_scheme.active = {245, 169, 184, 100}; + black_pinky.button_color_scheme.outline = {202, 207, 222, 0}; + black_pinky.button_color_scheme.highlight = {245, 169, 184, 50}; + black_pinky.button_color_scheme.text_foreground = {141, 141, 141, 255}; + black_pinky.button_color_scheme.box_outline = black_pinky.button_color_scheme.outline; + black_pinky.button_color_scheme.box_active = {245, 169, 184, 200}; + black_pinky.button_color_scheme.box_highlight = {245, 169, 184, 50}; + black_pinky.button_color_scheme.box_background = {202, 207, 222, 100}; + + black_pinky.label_color_scheme.background = {204, 204, 204, 0}; + black_pinky.label_color_scheme.outline = {202, 207, 222, 0}; + black_pinky.label_color_scheme.text_foreground = {141, 141, 141, 255}; + + black_pinky.scrollbar_color_scheme.background = {204, 204, 204, 30}; + black_pinky.scrollbar_color_scheme.outline = {204, 204, 204, 0}; + black_pinky.scrollbar_color_scheme.bar_background = {245, 169, 184, 100}; + black_pinky.scrollbar_color_scheme.bar_highlight = {245, 169, 184, 50}; + black_pinky.scrollbar_color_scheme.bar_active = {245, 169, 184, 100}; + + black_pinky.slider_color_scheme.outline = {204, 204, 204, 0}; + black_pinky.slider_color_scheme.background = {204, 204, 204, 30}; + black_pinky.slider_color_scheme.bar_background = {204, 204, 204, 50}; + black_pinky.slider_color_scheme.bar_highlight = {245, 169, 184, 50}; + black_pinky.slider_color_scheme.bar_active = {245, 169, 184, 100}; + black_pinky.slider_color_scheme.text_foreground = {141, 141, 141, 255}; + + black_pinky.popup_color_scheme = black_pinky.frame_color_scheme; + black_pinky.popup_color_scheme.outline = {50, 50, 50, 100}; + black_pinky.popup_color_scheme.popup_mode = true; + + black_pinky.textbox_color_scheme.background = {20, 34, 21, 255}; + black_pinky.textbox_color_scheme.outline = {190, 190, 190, 100}; + black_pinky.textbox_color_scheme.text_foreground = {242, 242, 242, 255}; + black_pinky.textbox_color_scheme.text_cursor_foreground = {141, 141, 141, 255}; + black_pinky.textbox_color_scheme.text_select_foreground = {245, 169, 184, 50}; + black_pinky.textbox_color_scheme.text_select_outline = {245, 169, 184, 100}; + + this->registry(black_pinky.tag) = black_pinky; + this->set_current_theme(black_pinky.tag); } void ekg::handler::theme::quit() { diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 5fe1b630..41d5f4d4 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -45,17 +45,19 @@ void ekg::ui::refresh_cursors_pos( for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + if (cursor.a.y > origin.b.y) { + cursor.a.y += displacement_a.y; + cursor.b.y += displacement_b.y; + } + if (cursor.a.y == origin.b.y && cursor.a.x > origin.b.x) { cursor.a.x += displacement_a.x; + cursor.a.y += displacement_a.y; } if (cursor.b.y == origin.b.y && cursor.b.x > origin.b.x) { cursor.b.x += displacement_b.x; - } - - if (cursor.a.y >= origin.b.y) { - cursor.a.y += displacement_a.y; - cursor.b.y += displacement_b.y; + cursor.b.y += displacement_b.y; } } break; @@ -63,11 +65,11 @@ void ekg::ui::refresh_cursors_pos( for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; - if (cursor.a.y == origin.b.y && cursor.a.x > origin.b.x) { + if (cursor.a.y == origin.a.y && cursor.a.x > origin.a.x) { cursor.a.x += displacement_a.x; } - if (cursor.b.y == origin.b.y && cursor.b.x > origin.b.x) { + if (cursor.b.y == origin.a.y && cursor.b.x > origin.a.x) { cursor.b.x += displacement_b.x; } } @@ -76,31 +78,42 @@ void ekg::ui::refresh_cursors_pos( for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; - if (cursor.a.y == origin.b.y && cursor.a.x > origin.b.x) { - cursor.a.x += displacement_a.x; + if (cursor.a.y > origin.a.y) { cursor.a.y += displacement_a.y; + cursor.b.y += displacement_b.y; } - if (cursor.b.y == origin.b.y && cursor.b.x > origin.b.x) { - cursor.b.x += displacement_b.x; - cursor.b.y += displacement_b.y; + if (cursor.a.y == origin.a.y && cursor.a.x > origin.a.x) { + cursor.a.x = (origin.b.x > origin.a.x) + ? cursor.a.x + (origin.b.x - origin.a.x) + : cursor.a.x - (origin.a.x - origin.b.x); + cursor.a.y += displacement_a.y; } - if (cursor.a.y >= origin.b.y) { - cursor.a.y += displacement_a.y; - cursor.b.y += displacement_b.y; + if (cursor.b.y == origin.a.y && cursor.b.x > origin.a.x) { + cursor.b.x = (origin.b.x > origin.a.x) + ? cursor.b.x + (origin.b.x - origin.a.x) + : cursor.b.x - (origin.a.x - origin.b.x); + cursor.b.y += displacement_b.y; } } break; case ekg::ui::textbox_operation::erase_inline: for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + + if (cursor.a.y > origin.b.y) { + cursor.a.y -= displacement_a.y; + cursor.b.y -= displacement_b.y; + } + if ( cursor.a.y == origin.a.y && cursor.a.x > origin.a.x ) { cursor.a.x = cursor.a.x - displacement_a.x; + cursor.a.y -= displacement_a.y; } if ( @@ -109,10 +122,6 @@ void ekg::ui::refresh_cursors_pos( cursor.b.x > origin.b.x ) { cursor.b.x = cursor.b.x - displacement_b.x; - } - - if (cursor.a.y >= origin.b.y) { - cursor.a.y -= displacement_a.y; cursor.b.y -= displacement_b.y; } } @@ -121,6 +130,11 @@ void ekg::ui::refresh_cursors_pos( for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + if (cursor.a.y > origin.b.y) { + cursor.a.y -= displacement_a.y; + cursor.b.y -= displacement_b.y; + } + if ( cursor.a.y == origin.b.y && @@ -130,6 +144,7 @@ void ekg::ui::refresh_cursors_pos( cursor.a.x - (origin.b.x - origin.a.x) : cursor.a.x + (origin.a.x - origin.b.x); + cursor.a.y -= displacement_a.y; } if ( @@ -141,10 +156,6 @@ void ekg::ui::refresh_cursors_pos( cursor.b.x- (origin.b.x - origin.a.x) : cursor.b.x + (origin.a.x - origin.b.x); - } - - if (cursor.a.y >= origin.b.y) { - cursor.a.y -= displacement_a.y; cursor.b.y -= displacement_b.y; } } @@ -565,8 +576,6 @@ void ekg::ui::handle_insert( ); ekg::textbox_t::cursor_t origin {cursor}; - origin.b = origin.a; - ekg::vec2_t displacement_a {}; ekg::vec2_t displacement_b {}; @@ -592,6 +601,8 @@ void ekg::ui::handle_insert( cursor.a.x = displacement_a.x; } + origin.b = cursor.a; + cursor.is_ignored = true; ekg::ui::refresh_cursors_pos( textbox, From 6fe9c74349233f77fd4ad7d1a953713b7e6ede27 Mon Sep 17 00:00:00 2001 From: mrsrina Date: Wed, 29 Oct 2025 22:14:57 -0300 Subject: [PATCH 39/61] [fix] equals repeated cursors --- include/ekg/ui/textbox/textbox.hpp | 1 + src/ui/textbox/widget.cpp | 110 ++++++++++++++++------------- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index 9d293330..eabcb85c 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -68,6 +68,7 @@ namespace ekg { ekg::vec2_t delta {}; ekg::rect_t rect {}; bool is_ignored {}; + bool is_deleted {}; public: bool operator == (const ekg::vec2_t &index) { return index.x == this->a.x && index.y == this->a.y && index.x == this->b.x && index.y == this->b.y; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 41d5f4d4..5ab71cb5 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -40,11 +40,11 @@ void ekg::ui::refresh_cursors_pos( const ekg::vec2_t &displacement_b, const ekg::ui::textbox_operation &operation ) { - switch (operation) { - case ekg::ui::textbox_operation::insert_line: - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + switch (operation) { + case ekg::ui::textbox_operation::insert_line: if (cursor.a.y > origin.b.y) { cursor.a.y += displacement_a.y; cursor.b.y += displacement_b.y; @@ -59,12 +59,8 @@ void ekg::ui::refresh_cursors_pos( cursor.b.x += displacement_b.x; cursor.b.y += displacement_b.y; } - } - break; - case ekg::ui::textbox_operation::insert_inline: - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; - + break; + case ekg::ui::textbox_operation::insert_inline: if (cursor.a.y == origin.a.y && cursor.a.x > origin.a.x) { cursor.a.x += displacement_a.x; } @@ -72,15 +68,11 @@ void ekg::ui::refresh_cursors_pos( if (cursor.b.y == origin.a.y && cursor.b.x > origin.a.x) { cursor.b.x += displacement_b.x; } - } - break; - case ekg::ui::textbox_operation::insert_multiline: - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; - + break; + case ekg::ui::textbox_operation::insert_multiline: if (cursor.a.y > origin.a.y) { cursor.a.y += displacement_a.y; - cursor.b.y += displacement_b.y; + cursor.b.y += displacement_b.y; } if (cursor.a.y == origin.a.y && cursor.a.x > origin.a.x) { @@ -96,40 +88,32 @@ void ekg::ui::refresh_cursors_pos( : cursor.b.x - (origin.a.x - origin.b.x); cursor.b.y += displacement_b.y; } - } - break; - case ekg::ui::textbox_operation::erase_inline: - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; - - if (cursor.a.y > origin.b.y) { - cursor.a.y -= displacement_a.y; - cursor.b.y -= displacement_b.y; - } + break; + case ekg::ui::textbox_operation::erase_inline: + if (cursor.a.y > origin.b.y) { + cursor.a.y -= displacement_a.y; + cursor.b.y -= displacement_b.y; + } if ( - cursor.a.y == origin.a.y - && - cursor.a.x > origin.a.x - ) { - cursor.a.x = cursor.a.x - displacement_a.x; - cursor.a.y -= displacement_a.y; - } - - if ( - cursor.b.y == origin.b.y - && - cursor.b.x > origin.b.x - ) { - cursor.b.x = cursor.b.x - displacement_b.x; - cursor.b.y -= displacement_b.y; - } - } - break; - case ekg::ui::textbox_operation::erase_multiline: - for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + cursor.a.y == origin.a.y + && + cursor.a.x > origin.a.x + ) { + cursor.a.x = cursor.a.x - displacement_a.x; + cursor.a.y -= displacement_a.y; + } + if ( + cursor.b.y == origin.b.y + && + cursor.b.x > origin.b.x + ) { + cursor.b.x = cursor.b.x - displacement_b.x; + cursor.b.y -= displacement_b.y; + } + break; + case ekg::ui::textbox_operation::erase_multiline: if (cursor.a.y > origin.b.y) { cursor.a.y -= displacement_a.y; cursor.b.y -= displacement_b.y; @@ -147,7 +131,7 @@ void ekg::ui::refresh_cursors_pos( cursor.a.y -= displacement_a.y; } - if ( + if ( cursor.b.y == origin.b.y && cursor.b.x > origin.b.x @@ -158,8 +142,8 @@ void ekg::ui::refresh_cursors_pos( cursor.b.x + (origin.a.x - origin.b.x); cursor.b.y -= displacement_b.y; } + break; } - break; } } @@ -904,6 +888,8 @@ void ekg::ui::event( size_t cursors_size {textbox.widget.cursors.size()}; size_t cursor_count {}; + bool should_clear_equals_repeated_cursors {}; + for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { cursor_count++; is_ab_equals = cursor.a == cursor.b; @@ -1159,6 +1145,30 @@ void ekg::ui::event( textbox.widget.scrollbar_property.scroll.position.w += ekg::max(-cursor_pos.y, textbox.widget.rect_text_size.h); } + + if (!cursor.is_deleted) { + cursor.is_ignored = true; + for (ekg::textbox_t::cursor_t &nearest_cursor : textbox.widget.cursors) { + if ( + nearest_cursor.is_ignored + || + nearest_cursor.is_deleted + || + cursor != nearest_cursor + ) continue; + should_clear_equals_repeated_cursors = true; + nearest_cursor.is_deleted = true; + } + cursor.is_ignored = false; + } + } + + if (should_clear_equals_repeated_cursors) { + for (size_t it {}; it < textbox.widget.cursors.size(); it++) { + ekg::textbox_t::cursor_t &cursor {textbox.widget.cursors.at(it)}; + if (!cursor.is_deleted) continue; + textbox.widget.cursors.erase(textbox.widget.cursors.begin() + it); + } } if (is_action_cut || is_action_copy) { From 78c561bc719f42f9e9805386f07eea17160ee1ea Mon Sep 17 00:00:00 2001 From: MrsRina Date: Tue, 23 Dec 2025 14:11:46 -0300 Subject: [PATCH 40/61] [update] monospaced flag; fixed textbox visual selection --- CMakeLists.txt | 2 +- include/ekg/draw/typography/font.hpp | 1 + include/ekg/ui/textbox/textbox.hpp | 1 + src/draw/typography/font.cpp | 4 +- src/ui/textbox/widget.cpp | 65 ++++++++++++++++++++++------ 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee06479c..38085a13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# 1 +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/include/ekg/draw/typography/font.hpp b/include/ekg/draw/typography/font.hpp index 6d717919..7be937f4 100644 --- a/include/ekg/draw/typography/font.hpp +++ b/include/ekg/draw/typography/font.hpp @@ -56,6 +56,7 @@ namespace ekg::draw { bool font_size_changed {}; bool was_initialized {}; bool is_any_functional_font_face_loaded {}; + bool is_monospaced {}; public: void init(); void quit(); diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index eabcb85c..d4b0cd33 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -56,6 +56,7 @@ namespace ekg { struct select_draw_layer_t { public: bool is_ab_equals {}; + bool is_always_static {}; ekg::rect_t rect {}; }; diff --git a/src/draw/typography/font.cpp b/src/draw/typography/font.cpp index 7607e45b..b93111a1 100644 --- a/src/draw/typography/font.cpp +++ b/src/draw/typography/font.cpp @@ -401,9 +401,11 @@ void ekg::draw::font::reload() { ); } + this->is_monospaced = FT_IS_FIXED_WIDTH(text_font_face.ft_face); + this->space_wsize = this->text_height / 3.0f; + FT_Vector space_char_metrics {}; FT_Get_Kerning(text_font_face.ft_face, 32, 32, 0, &space_char_metrics); - this->space_wsize = static_cast(space_char_metrics.x >> 6); this->text_height = static_cast(this->font_size); this->offset_text_height = this->text_height / 6; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 5ab71cb5..0007f0b0 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -41,7 +41,7 @@ void ekg::ui::refresh_cursors_pos( const ekg::ui::textbox_operation &operation ) { for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { - if (cursor.is_ignored || cursor.a.y < origin.a.y) continue; + if (cursor.is_ignored || cursor.is_deleted || cursor.a.y < origin.a.y) continue; switch (operation) { case ekg::ui::textbox_operation::insert_line: @@ -891,6 +891,8 @@ void ekg::ui::event( bool should_clear_equals_repeated_cursors {}; for (ekg::textbox_t::cursor_t &cursor : textbox.widget.cursors) { + if (cursor.is_deleted) continue; + cursor_count++; is_ab_equals = cursor.a == cursor.b; @@ -1274,7 +1276,7 @@ void ekg::ui::buffering( ekg::rect_t rect_select {}; for (ekg::textbox_t::select_draw_layer_t &layer : textbox.widget.layers_select) { - if (layer.is_ab_equals && elapsed_mid_second) { + if (!layer.is_always_static && layer.is_ab_equals && elapsed_mid_second) { continue; } @@ -1385,7 +1387,7 @@ void ekg::ui::buffering( * This technique works because the line height is fixed, ultimately, soon, should be re-worked * to support differents text-heights at same time, also, for widgets scrolling (I do not think * someone can write a GUI context with +5000000 heights from widgets without pages). - * [] + * * - Rina - 11:39; 08/06/2025 **/ float visible_text_height {static_cast(textbox.widget.view_line_index * textbox.widget.rect_text_size.h)}; @@ -1468,33 +1470,68 @@ void ekg::ui::buffering( ) ) ) { - glyph_wsize = glyph.wsize; + glyph_wsize = glyph.wsize; end_cursor_position = glyph_wsize * is_cursor_at_end_of_line; is_ab_equals_selected = property.states.is_focused && cursor == index; + is_inline_selected = cursor >= index && cursor < index && !is_ab_equals_selected; is_complete_line_selected = false; if (is_inline_selected) { if ( ( - cursor >= ekg::vec2_t(0, index.y) - ) - && - ( - cursor <= ekg::vec2_t(text_len, index.y) + ( + cursor >= ekg::vec2_t(0, index.y) + ) + && + ( + cursor <= ekg::vec2_t(text_len, index.y) + ) ) ) { is_inline_selected = false; } + is_complete_line_selected = !is_inline_selected; } + ekg::textbox_t::cursor_t nearest_cursor {}; + ekg::vec2_t next_line_index(0, index.y + 1); + bool is_next_line_selected_in_some_way {}; + + if ( + !is_complete_line_selected + && + is_last_char_from_line + && + (is_next_line_selected_in_some_way = ekg::ui::find_cursor(textbox, next_line_index, nearest_cursor)) + ) { + glyph_wsize += draw_font.space_wsize; + } + + ekg::vec2_t next_char_index(next_line_index.x + 1, next_line_index.y); + if ( + !textbox.color_scheme.caret_cursor + && + is_next_line_selected_in_some_way + && + is_inline_selected + && + !ekg::ui::find_cursor(textbox, next_char_index, nearest_cursor) + ) { + cursor.rect.x = textbox.color_scheme.gutter_margin; + cursor.rect.y = pos.y + textbox.widget.rect_text_size.h; + cursor.rect.w = textbox.color_scheme.cursor_thickness; + cursor.rect.h = textbox.widget.rect_text_size.h; + textbox.widget.layers_select.push_back({.is_ab_equals = true, .is_always_static = true, .rect = cursor.rect}); + } + if (is_ab_equals_selected) { cursor.rect.x = pos.x + end_cursor_position; cursor.rect.y = pos.y; cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph_wsize : textbox.color_scheme.cursor_thickness; cursor.rect.h = textbox.widget.rect_text_size.h; - textbox.widget.layers_select.push_back({true, cursor.rect}); + textbox.widget.layers_select.push_back({.is_ab_equals = true, .rect = cursor.rect}); } if (is_inline_selected) { @@ -1502,7 +1539,7 @@ void ekg::ui::buffering( cursor.rect.y = pos.y; cursor.rect.w = glyph_wsize; cursor.rect.h = textbox.widget.rect_text_size.h; - textbox.widget.layers_select.push_back({false, cursor.rect}); + textbox.widget.layers_select.push_back({.rect = cursor.rect}); } if ( @@ -1516,6 +1553,8 @@ void ekg::ui::buffering( if (is_complete_line_selected && !is_empty) { line_wsize += glyph_wsize; } + + is_cursor_at_end_of_line = false; } if (is_empty) { @@ -1596,8 +1635,8 @@ void ekg::ui::buffering( cursor.rect.x = textbox.color_scheme.gutter_margin; cursor.rect.y = pos.y; cursor.rect.h = textbox.widget.rect_text_size.h; - cursor.rect.w = line_wsize + glyph_wsize; - textbox.widget.layers_select.push_back({false, cursor.rect}); + cursor.rect.w = line_wsize + draw_font.space_wsize; + textbox.widget.layers_select.push_back({.rect = cursor.rect}); is_complete_line_selected = false; } From f75fdd242e2740fc71256a1e409569b67179a142 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Tue, 23 Dec 2025 14:22:35 -0300 Subject: [PATCH 41/61] [fix] inline ab equals cursor fixed --- src/ui/textbox/widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 0007f0b0..920209ce 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -1527,7 +1527,7 @@ void ekg::ui::buffering( } if (is_ab_equals_selected) { - cursor.rect.x = pos.x + end_cursor_position; + cursor.rect.x = pos.x + glyph.left + end_cursor_position; cursor.rect.y = pos.y; cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph_wsize : textbox.color_scheme.cursor_thickness; cursor.rect.h = textbox.widget.rect_text_size.h; From ed8798be874d711550fc35bffbc40f2d65bfd3f6 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Mon, 5 Jan 2026 00:10:44 -0300 Subject: [PATCH 42/61] [update] new utf8 utils; fixed last utf8 char issue selection --- include/ekg/io/utf.hpp | 88 +++++++++++++++++++++++---------------- src/io/utf.cpp | 56 +++++++++++++++++++++++++ src/ui/textbox/widget.cpp | 10 +++-- 3 files changed, 113 insertions(+), 41 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 03f08cc7..4f26b8bd 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -33,23 +33,21 @@ namespace ekg { /** - * @brief: - * check utf8 sequence and add to `it` index. + * Check utf8 sequence and add to `it` index; checking for the first byte of a char utf8 encoded: + * + * * - If first char fit less than 127; 1 byte utf8 char. + * * - If first char fit equals 192~224; 2 byte utf8 char. + * * - If first char fit equals 224~240; 3 byte utf8 char. + * * - If first char fit equals 240~248; 4 byte utf8 char. * + * By the amount of bytes per-char it is added to `it` e.g: + * * - If 224~240; then `it + 2` (`it` already counts as 1 byte) + * * @param `uc8` the current char to be checked. * @param `c32` the post utf32 char converted. * @param `utf8_str` the utf8 encoded text. * @param `it` the current `utf8_str` index. * - * @description: - * this function check the first byte of a char utf8 encoded: - * if first char fit less than 127; 1 byte utf8 char. - * if first char fit equals 192~224; 2 byte utf8 char. - * if first char fit equals 224~240; 3 byte utf8 char. - * if first char fit equals 240~248; 4 byte utf8 char. - * - * by the amount of bytes per-char it is added to `it` e.g: - * if 224~240; then `it + 2` (`it` already counts as 1 byte) **/ void utf8_sequence( uint8_t &uc8, @@ -59,18 +57,16 @@ namespace ekg { ); /** - * @brief - * find for a aligned utf position by byte position + * Find for an aligned utf position by byte position: + * + * * - If no utf position was found then `utf_pos` keeps unchanged. + * * - If an invalid byte position is passed, e.g a position inside a + * utf sequence, it return atuomatically `false`. * * @param `string` string for find utf position * @param `byte_pos` byte pos used for find utf position * @param `utf_pos` utf position relative to byte position * - * @description - * if no utf position was found then `utf_pos` keeps unchanged. - * if a invalid byte position is passed, e.g a position inside a - * utf sequence, it return atuomatically `false`. - * * @return `true` if a valid utf position was found else `false` if was not found **/ bool utf8_find_utf_pos_by_byte_pos( @@ -80,16 +76,13 @@ namespace ekg { ); /** - * @brief - * find for a byte pos from an aligned utf position + * Find for a byte pos from an aligned utf position: + * * - If no byte position was found then `utf_byte` keeps unchanged. * * @param `string` string for find utf position * @param `utf_pos` utf pos used for find byte pos * @param `byte_pos` byte pos relative to utf position * - * @description - * if no byte position was found then `utf_byte` keeps unchanged. - * * @return `true` if a valid byte position was found else `false` if was not found **/ bool utf8_find_byte_pos_by_utf_pos( @@ -99,20 +92,16 @@ namespace ekg { ); /** - * @brief - * align utf position and byute-position by the next byte-position + * Align utf position and byte-position by the next byte-position: + * * - If next byte pos is greater than unaligned byte pos, then move to left; if not move to right. + * + * May output be corrupted if current unaligned byte-pos is an invalid utf position. * * @param `string` string for be used in alignment * @param `unaligned_byte_pos` byte pos to be aligned * @param `unaligned_utf_pos` utf pos to be aligned * @param `next_byte_pos` next byte pos to align byte-pos and utf-pos * - * @description - * if next byte pos is greater than unaligned byte pos, then move to left - * if not move to right - * - * may output be corrupted if current unaligned byte-pos is an invalid utf position - * * @return true if could align, else false if could not **/ bool utf8_align_utf_pos_by_byte_pos( @@ -123,16 +112,13 @@ namespace ekg { ); /** - * @brief - * get the first nearest group matched byte-position + * Get the first nearest group matched byte-position: + * * - For left dock the first matched group position is assigned; + * * - For right dock the last matched group position is assigned. * * @param `begin` const string iterator to begin regex matching * @param `end` const string iterator to end regex matching * @param `nearest_byte_pos` first group matched bidirectional byte-position - * - * @description - * for left dock the first matched group position is assigned, - * for right dock the last matched group position is assigned * * @return true if matched else false if not matched **/ @@ -144,6 +130,34 @@ namespace ekg { const ekg::dock &dock ); + /** + * Check if current index is last index by the last utf char size. + * + * @param `byte_index` the current index in byte to be checked + * @param `last_char_bytes` the utf size (1 | 2 | 3 | 4 bytes per utf char) + * @param `string_bytes_size` the string size in bytes + * + * @return true if is last index else false if not last index + **/ + bool utf8_is_last_index( + size_t byte_index, + size_t last_char_bytes, + size_t string_bytes_size + ); + + /** + * Check for last char utf8 bytes sequence. + * + * @param `utf8_str` the utf8 valid string to find + * @param `bytes` the last char utf8 bytes output + * + * @return true if found-valid else false if not found + **/ + bool utf8_check_last_char_byte_sequence( + std::string &utf8_str, + size_t &bytes + ); + /** * Returns a UTF string by `char32` converting * the UTF-32 unique char into a sequence of UTF-8 diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 53a73df6..2e915b05 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -27,6 +27,14 @@ #include #include +bool ekg::utf8_is_last_index( + size_t byte_index, + size_t last_char_bytes, + size_t string_bytes_size +) { + return byte_index == (string_bytes_size - last_char_bytes); +} + bool ekg::utf8_align_utf_pos_by_byte_pos( std::string &string, size_t &unaligned_byte_pos, @@ -142,6 +150,54 @@ void ekg::utf8_sequence( } } +bool ekg::utf8_check_last_char_byte_sequence( + std::string &utf8_str, + size_t &bytes +) { + if (utf8_str.empty()) { + return false; + } + + size_t byte {utf8_str.size()}; + + /** + * Reversing find by byte pos is efficient, + * since we want to reduce the index utf8 check. + * + * It cost many CPU iterations instructions for calculating + * the string size by utf8 sequence. + **/ + + return ( + byte + && + ( + ( + byte >= (bytes = 4) + && + (static_cast(utf8_str.at(byte - 4)) & 0xF8) == 0xF0 + ) + || + ( + byte >= (bytes = 3) + && + (static_cast(utf8_str.at(byte - 3)) & 0xF0) == 0xE0 + ) + || + ( + byte >= (bytes = 2) + && + (static_cast(utf8_str.at(byte - 2)) & 0xE0) == 0xC0) + || + ( + byte >= (bytes = 1) + && + (static_cast(utf8_str.at(byte - 1))) <= 0xE0 + ) + ) + ); +} + bool ekg::utf8_find_utf_pos_by_byte_pos( std::string &string, size_t byte_pos, diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 920209ce..dafa19c1 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -295,8 +295,8 @@ bool ekg::ui::find_index_by_interact( pos.x += rect.w; rect.w = rect_abs.w; - if (index.x == text_len && ekg::rect_collide_vec2(rect, interact)) { - index.x = is_empty ? 0 : text_len; + if (it+1 == text_len && ekg::rect_collide_vec2(rect, interact)) { + index.x *= !is_empty; // if line is empty then last utf8 index is 0 return true; } } @@ -1399,6 +1399,8 @@ void ekg::ui::buffering( bool is_empty {}; bool was_empty_before {}; + //ekg_log_low_level("-----------------------"); + textbox.widget.layers_select.clear(); for (size_t ic {}; ic < chunks_size; ic++) { ekg::io::chunk_t &chunk {chunks.at(ic)}; @@ -1543,8 +1545,6 @@ void ekg::ui::buffering( } if ( - is_complete_line_selected - && it == 0 ) { line_wsize = 0; @@ -1552,6 +1552,8 @@ void ekg::ui::buffering( if (is_complete_line_selected && !is_empty) { line_wsize += glyph_wsize; + //ekg_log_low_level(index.x << " ; " << index.y); + //ekg_log_low_level(glyph_wsize << " | " << line_wsize); } is_cursor_at_end_of_line = false; From 5ce2cb1eb9da32a9110f6147b0a048c6df043549 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Mon, 5 Jan 2026 00:15:49 -0300 Subject: [PATCH 43/61] [fix] fixed selection line wsize not returning to zero size --- src/ui/textbox/widget.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index dafa19c1..d08ce977 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -1399,8 +1399,6 @@ void ekg::ui::buffering( bool is_empty {}; bool was_empty_before {}; - //ekg_log_low_level("-----------------------"); - textbox.widget.layers_select.clear(); for (size_t ic {}; ic < chunks_size; ic++) { ekg::io::chunk_t &chunk {chunks.at(ic)}; @@ -1552,8 +1550,6 @@ void ekg::ui::buffering( if (is_complete_line_selected && !is_empty) { line_wsize += glyph_wsize; - //ekg_log_low_level(index.x << " ; " << index.y); - //ekg_log_low_level(glyph_wsize << " | " << line_wsize); } is_cursor_at_end_of_line = false; @@ -1640,6 +1636,7 @@ void ekg::ui::buffering( cursor.rect.w = line_wsize + draw_font.space_wsize; textbox.widget.layers_select.push_back({.rect = cursor.rect}); is_complete_line_selected = false; + line_wsize = 0; } pos.x = textbox.color_scheme.gutter_margin; From c5b1b95c0ba056809e9d11796b409801748e4f59 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Mon, 5 Jan 2026 00:44:15 -0300 Subject: [PATCH 44/61] [fix] fixed crash when invalid utf8 is pasted --- include/ekg/platform/base.hpp | 5 +++-- include/ekg/platform/sdl/sdl2.hpp | 4 ++-- src/io/utf.cpp | 10 ++++++---- src/platform/sdl/sdl2.cpp | 6 +++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/include/ekg/platform/base.hpp b/include/ekg/platform/base.hpp index e72518e8..17d9adf8 100644 --- a/include/ekg/platform/base.hpp +++ b/include/ekg/platform/base.hpp @@ -25,6 +25,7 @@ #define EKG_PLATFORM_HPP #include "ekg/io/event.hpp" +#include namespace ekg::platform { class base { @@ -40,8 +41,8 @@ namespace ekg::platform { virtual void update() {}; virtual void get_key_name(ekg::input_key_t &key, std::string &name) {}; virtual void get_special_key(ekg::input_key_t &key, ekg::special_key &espcial_key) {}; - virtual const char *get_clipboard_text() { return nullptr; }; - virtual void set_clipboard_text(const char *p_text) {}; + virtual std::string_view get_clipboard_text() { return ""; }; + virtual void set_clipboard_text(std::string_view text) {}; virtual bool has_clipboard_text() { return false; }; }; } diff --git a/include/ekg/platform/sdl/sdl2.hpp b/include/ekg/platform/sdl/sdl2.hpp index bc63ad34..5e72891b 100644 --- a/include/ekg/platform/sdl/sdl2.hpp +++ b/include/ekg/platform/sdl/sdl2.hpp @@ -50,8 +50,8 @@ namespace ekg { void update() override; void get_key_name(ekg::input_key_t &key, std::string &name) override; void get_special_key(ekg::input_key_t &key, ekg::special_key &special_key) override; - const char *get_clipboard_text() override; - void set_clipboard_text(const char *p_text) override; + std::string_view get_clipboard_text() override; + void set_clipboard_text(std::string_view text) override; bool has_clipboard_text() override; }; diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 2e915b05..d3dddbab 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -133,16 +133,18 @@ void ekg::utf8_sequence( std::string_view utf8_str, size_t &it ) { - if (uc8 <= 0x7F) { + size_t size {utf8_str.size()}; + + if (size >= 1 && uc8 <= 0x7F) { c32 = static_cast(uc8); - } else if ((uc8 & 0xE0) == 0xC0) { + } else if ((size >= 2 && it + 1 < size) && (uc8 & 0xE0) == 0xC0) { c32 = uc8 & 0x1F; c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); - } else if ((uc8 & 0xF0) == 0xE0) { + } else if ((size >= 3 && it + 2 < size) && (uc8 & 0xF0) == 0xE0) { c32 = uc8 & 0x0F; c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); - } else if ((uc8 & 0xF8) == 0xF0) { + } else if ((size >= 4 && it + 3 < size) && (uc8 & 0xF8) == 0xF0) { c32 = uc8 & 0x07; c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); c32 = (c32 << 6) | (utf8_str.at(++it) & 0x3F); diff --git a/src/platform/sdl/sdl2.cpp b/src/platform/sdl/sdl2.cpp index be4ad831..2b46ade3 100644 --- a/src/platform/sdl/sdl2.cpp +++ b/src/platform/sdl/sdl2.cpp @@ -46,15 +46,15 @@ ekg::sdl2::sdl2( SDL_SetWindowSize(this->p_sdl_win, w++, h++); } -void ekg::sdl2::set_clipboard_text(const char *p_text) { - SDL_SetClipboardText(p_text); +void ekg::sdl2::set_clipboard_text(std::string_view text) { + SDL_SetClipboardText(text.data()); } bool ekg::sdl2::has_clipboard_text() { return SDL_HasClipboardText(); } -const char *ekg::sdl2::get_clipboard_text() { +std::string_view ekg::sdl2::get_clipboard_text() { return SDL_GetClipboardText(); } From a8d549647aac9d55306d150361e2e1e694dadb1d Mon Sep 17 00:00:00 2001 From: MrsRina Date: Sat, 24 Jan 2026 18:37:21 -0300 Subject: [PATCH 45/61] [fix] draw font wsize next-line visually --- src/handler/theme/handler.cpp | 6 +++--- src/ui/textbox/widget.cpp | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index 142403bf..b458724b 100644 --- a/src/handler/theme/handler.cpp +++ b/src/handler/theme/handler.cpp @@ -89,7 +89,7 @@ void ekg::handler::theme::init() { ekg::theme_t black_light_pinky_theme { .tag = "black-light-pinky", .author = "Rina Wilk", - .description = "oiiiiiiiii eu amo vc serpa, lindo maravilhoso" + .description = "misses" }; black_light_pinky_theme.layout_offset = 2.0f; @@ -146,7 +146,7 @@ void ekg::handler::theme::init() { ekg::theme_t black_pinky { .tag = "black-pinky", - .author = "serpa", + .author = "weasted", .description = "colors for show colors" }; @@ -193,7 +193,7 @@ void ekg::handler::theme::init() { black_pinky.popup_color_scheme.outline = {50, 50, 50, 100}; black_pinky.popup_color_scheme.popup_mode = true; - black_pinky.textbox_color_scheme.background = {20, 34, 21, 255}; + black_pinky.textbox_color_scheme.background = {0, 0, 0, 255}; black_pinky.textbox_color_scheme.outline = {190, 190, 190, 100}; black_pinky.textbox_color_scheme.text_foreground = {242, 242, 242, 255}; black_pinky.textbox_color_scheme.text_cursor_foreground = {141, 141, 141, 255}; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index d08ce977..d62621f5 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -88,6 +88,7 @@ void ekg::ui::refresh_cursors_pos( : cursor.b.x - (origin.a.x - origin.b.x); cursor.b.y += displacement_b.y; } + break; case ekg::ui::textbox_operation::erase_inline: if (cursor.a.y > origin.b.y) { @@ -1500,11 +1501,11 @@ void ekg::ui::buffering( bool is_next_line_selected_in_some_way {}; if ( - !is_complete_line_selected - && is_last_char_from_line && (is_next_line_selected_in_some_way = ekg::ui::find_cursor(textbox, next_line_index, nearest_cursor)) + && + !is_complete_line_selected ) { glyph_wsize += draw_font.space_wsize; } @@ -1515,8 +1516,6 @@ void ekg::ui::buffering( && is_next_line_selected_in_some_way && - is_inline_selected - && !ekg::ui::find_cursor(textbox, next_char_index, nearest_cursor) ) { cursor.rect.x = textbox.color_scheme.gutter_margin; @@ -1535,7 +1534,7 @@ void ekg::ui::buffering( } if (is_inline_selected) { - cursor.rect.x = pos.x + end_cursor_position; + cursor.rect.x = pos.x; cursor.rect.y = pos.y; cursor.rect.w = glyph_wsize; cursor.rect.h = textbox.widget.rect_text_size.h; From 8cb8991f23ab17f6b2cdaaaa0bc8abc83571067f Mon Sep 17 00:00:00 2001 From: MrsRina Date: Sun, 25 Jan 2026 18:53:25 -0300 Subject: [PATCH 46/61] [fix] rendering selection visuals --- src/ui/textbox/widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index d62621f5..905b7808 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -1507,7 +1507,7 @@ void ekg::ui::buffering( && !is_complete_line_selected ) { - glyph_wsize += draw_font.space_wsize; + glyph_wsize = (glyph_wsize * !is_cursor_at_end_of_line) + draw_font.space_wsize; } ekg::vec2_t next_char_index(next_line_index.x + 1, next_line_index.y); @@ -1534,7 +1534,7 @@ void ekg::ui::buffering( } if (is_inline_selected) { - cursor.rect.x = pos.x; + cursor.rect.x = pos.x + end_cursor_position; cursor.rect.y = pos.y; cursor.rect.w = glyph_wsize; cursor.rect.h = textbox.widget.rect_text_size.h; From 80f591aad15fc1eeb414205cfea147c345a8c289 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Wed, 28 Jan 2026 22:31:38 -0300 Subject: [PATCH 47/61] [fix] fixed align position when left/right modifier action --- src/draw/typography/font.cpp | 2 -- src/io/utf.cpp | 4 ++-- src/ui/textbox/widget.cpp | 15 ++++++++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/draw/typography/font.cpp b/src/draw/typography/font.cpp index b93111a1..ec04cb48 100644 --- a/src/draw/typography/font.cpp +++ b/src/draw/typography/font.cpp @@ -1,5 +1,3 @@ -#include "ekg/draw/typography/font.hpp" - /** * MIT License * diff --git a/src/io/utf.cpp b/src/io/utf.cpp index d3dddbab..35c666f4 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -60,7 +60,7 @@ bool ekg::utf8_align_utf_pos_by_byte_pos( while (byte_pos < next_byte_pos) { char &c8 {string.at(byte_pos)}; utf_sequence_size = 1; - utf_sequence_size += ((c8 & 0xE0) == 0xC0); + utf_sequence_size += 1 * ((c8 & 0xE0) == 0xC0); utf_sequence_size += 2 * ((c8 & 0xF0) == 0xE0); utf_sequence_size += 3 * ((c8 & 0xF8) == 0xF0); byte_pos += utf_sequence_size; @@ -75,7 +75,7 @@ bool ekg::utf8_align_utf_pos_by_byte_pos( while (byte_pos < unaligned_byte_pos) { char &c8 {string.at(byte_pos)}; utf_sequence_size = 1; - utf_sequence_size += ((c8 & 0xE0) == 0xC0); + utf_sequence_size += 1 * ((c8 & 0xE0) == 0xC0); utf_sequence_size += 2 * ((c8 & 0xF0) == 0xE0); utf_sequence_size += 3 * ((c8 & 0xF8) == 0xF0); byte_pos += utf_sequence_size; diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index 905b7808..b130bdf2 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -962,8 +962,8 @@ void ekg::ui::event( && ekg::utf8_align_utf_pos_by_byte_pos( line, - cursor.a.x, cursor_byte_pos, + cursor.a.x, nearest_byte_pos ) ); @@ -984,7 +984,7 @@ void ekg::ui::event( } } - ( + if ( is_modifier_right_fired && ekg::utf8_find_byte_pos_by_utf_pos( @@ -1003,8 +1003,8 @@ void ekg::ui::event( && ekg::utf8_align_utf_pos_by_byte_pos( line, - cursor.b.x, cursor_byte_pos, + cursor.b.x, cursor_byte_pos + nearest_byte_pos ) ); @@ -1346,6 +1346,8 @@ void ekg::ui::buffering( float end_cursor_position {}; float extra_rect_height {rect_abs.h + textbox.widget.rect_text_size.h}; float glyph_wsize {}; + float previous_fitting_glyph {}; + float previous_kerning {}; size_t current_line_for_cursor_complete {UINT64_MAX}; size_t text_total_chars {textbox.text.length_of_chars()}; @@ -1451,10 +1453,11 @@ void ekg::ui::buffering( } FT_Get_Kerning(ft_face, ft_uint_previous, c32, 0, &ft_vector_previous_char); - pos.x += static_cast(ft_vector_previous_char.x >> 6); + pos.x += (previous_kerning = static_cast(ft_vector_previous_char.x >> 6)); } ekg::io::glyph_t &glyph {draw_font.mapped_glyph[c32]}; + glyph.kerning = previous_kerning; is_last_char_from_line = it+1 == text_len; if ( @@ -1471,7 +1474,7 @@ void ekg::ui::buffering( ) ) ) { - glyph_wsize = glyph.wsize; + glyph_wsize = glyph.wsize; end_cursor_position = glyph_wsize * is_cursor_at_end_of_line; is_ab_equals_selected = property.states.is_focused && cursor == index; @@ -1618,6 +1621,7 @@ void ekg::ui::buffering( pos.x += glyph.wsize; ft_uint_previous = c32; + previous_fitting_glyph = glyph.wsize - (glyph.left + glyph.w); /** * Peek `ekg/io/memory.hpp` for better hash definition and purpose. @@ -1644,6 +1648,7 @@ void ekg::ui::buffering( index.y++; hash += pos.y * 32; index.x += is_empty; + previous_fitting_glyph = 0.0f; if (rendered.y > extra_rect_height) { get_out = true; From 4b788158fe896f889f9460056817e98013a58059 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Wed, 28 Jan 2026 22:35:22 -0300 Subject: [PATCH 48/61] [fix] cursor ab equals vibe --- src/ui/textbox/widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index b130bdf2..a6468772 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -1529,7 +1529,7 @@ void ekg::ui::buffering( } if (is_ab_equals_selected) { - cursor.rect.x = pos.x + glyph.left + end_cursor_position; + cursor.rect.x = pos.x + end_cursor_position; cursor.rect.y = pos.y; cursor.rect.w = textbox.color_scheme.caret_cursor ? glyph_wsize : textbox.color_scheme.cursor_thickness; cursor.rect.h = textbox.widget.rect_text_size.h; From dd9e8d4d51a341bd15c28a553904772dca6a7122 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Sun, 1 Feb 2026 00:17:18 -0300 Subject: [PATCH 49/61] [feature] linked list to textbox --- include/ekg/io/utf.hpp | 8 ++++-- include/ekg/ui/textbox/textbox.hpp | 2 +- src/io/utf.cpp | 46 ++++++++++++++++-------------- src/ui/textbox/widget.cpp | 35 +++++++++++------------ 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 4f26b8bd..fae2a537 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace ekg { /** @@ -241,7 +242,7 @@ namespace ekg::io { namespace ekg { class text { protected: - std::vector loaded_chunks {}; + std::list loaded_chunks {}; size_t lines_per_chunk_limit {100000}; size_t total_lines {}; size_t total_chars {}; @@ -252,7 +253,7 @@ namespace ekg { bool should_count {}; protected: void swizzle( - size_t chunk_index, + std::list::iterator chunk_it, size_t line_index, std::vector &to_swizzle, bool skip_first_line @@ -282,7 +283,7 @@ namespace ekg { size_t end ); - std::vector &chunks_data(); + std::list &chunks_data(); size_t length_of_chunks(); std::string at(size_t index); @@ -291,6 +292,7 @@ namespace ekg { bool audited(); void unset_audited(); + void gc(); }; } diff --git a/include/ekg/ui/textbox/textbox.hpp b/include/ekg/ui/textbox/textbox.hpp index d4b0cd33..d737559f 100644 --- a/include/ekg/ui/textbox/textbox.hpp +++ b/include/ekg/ui/textbox/textbox.hpp @@ -114,7 +114,7 @@ namespace ekg { size_t last_layers_select_size {}; size_t view_line_index {UINT64_MAX}; - size_t view_chunk_index {UINT64_MAX}; + std::list::iterator view_chunk_it {}; size_t view_chunk_line_index {UINT64_MAX}; ekg::vec2_t picked_left {UINT64_MAX, UINT64_MAX}; diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 35c666f4..34a7a1ba 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -483,16 +483,15 @@ void ekg::utf8_concat( } void ekg::text::swizzle( - size_t chunk_index, + std::list::iterator chunk_it, size_t line_index, std::vector &to_swizzle, bool skip_first_line ) { this->was_audited = true; - bool is_empty {to_swizzle.empty()}; - ekg::io::chunk_t &chunk {this->loaded_chunks.at(chunk_index)}; + ekg::io::chunk_t &chunk {*chunk_it}; if (skip_first_line) { chunk.at(line_index) = is_empty ? "" : to_swizzle.at(0); } @@ -528,18 +527,20 @@ void ekg::text::swizzle( for (size_t jt {}; jt < newly_chunks; jt++) { this->loaded_chunks.insert( - this->loaded_chunks.begin() + chunk_index + jt + 1, + chunk_it, ekg::io::chunk_t { to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 0)), to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 1)) } ); + + chunk_it++; } size_t rest {to_swizzle_chunk_size - (this->lines_per_chunk_limit * newly_chunks)}; if (rest > 0) { this->loaded_chunks.insert( - this->loaded_chunks.begin() + chunk_index + newly_chunks + 1, + chunk_it, ekg::io::chunk_t { to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (newly_chunks + 0)), to_swizzle_chunk.end() @@ -561,8 +562,8 @@ size_t ekg::text::set(size_t index, std::string_view line, ekg::io::chunk_t &spl ekg::utf8_split_endings(line, split_endings); bool ok {}; - for (size_t it {}; it < this->loaded_chunks.size(); it++) { - ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + for (std::list::iterator it {this->loaded_chunks.begin()}; it != this->loaded_chunks.end(); it++) { + ekg::io::chunk_t &chunk {*it}; previous_lines = current_lines; current_lines += (chunk_size = chunk.size()); @@ -591,8 +592,8 @@ std::string ekg::text::at(size_t index) { size_t previous_lines {}; size_t chunk_size {}; - for (size_t it {}; it < chunks_size; it++) { - ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + for (std::list::iterator it {this->loaded_chunks.begin()}; it != this->loaded_chunks.end(); it++) { + ekg::io::chunk_t &chunk {*it}; previous_lines = current_lines; current_lines += (chunk_size = chunk.size()); @@ -632,8 +633,8 @@ void ekg::text::insert( } size_t total_of_chunks {this->loaded_chunks.size()}; - for (size_t it {}; it < total_of_chunks; it++) { - ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + for (std::list::iterator it {this->loaded_chunks.begin()}; it != this->loaded_chunks.end(); it++) { + ekg::io::chunk_t &chunk {*it}; previous_lines = current_lines; current_lines += (chunk_size = chunk.size()); @@ -681,8 +682,8 @@ std::string ekg::text::read( bool oka_end {}; std::string builder {}; - for (size_t it {}; it < total_of_chunks; it++) { - ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + for (std::list::iterator it {this->loaded_chunks.begin()}; it != this->loaded_chunks.end(); it++) { + ekg::io::chunk_t &chunk {*it}; previous_lines = current_lines; current_lines += (chunk_size = chunk.size()); @@ -767,8 +768,8 @@ void ekg::text::erase( bool empty_chunk {}; bool goto_next_chunk {}; - for (size_t it {}; it < this->loaded_chunks.size(); it++) { - ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + for (std::list::iterator it {this->loaded_chunks.begin()}; it != this->loaded_chunks.end(); it++) { + ekg::io::chunk_t &chunk {*it}; previous_lines = lines; lines += (chunk_size = chunk.size()); @@ -778,7 +779,7 @@ void ekg::text::erase( begin = begin - previous_lines; while (remains_lines != 0) { - ekg::io::chunk_t &chunk {this->loaded_chunks.at(it)}; + ekg::io::chunk_t &chunk {*it}; chunk_size = chunk.size(); goto_next_chunk = begin + remains_lines > chunk.size(); @@ -791,12 +792,12 @@ void ekg::text::erase( empty_chunk = chunk.empty(); if (empty_chunk) { this->loaded_chunks.erase( - this->loaded_chunks.begin() + it + it ); } if (goto_next_chunk) { - it += !empty_chunk; + if (!empty_chunk) it++; remains_lines -= chunk_size - begin; begin = 0; continue; @@ -819,23 +820,24 @@ void ekg::text::push_back(std::string_view line) { if (this->loaded_chunks.empty()) { this->loaded_chunks.emplace_back().emplace_back(); - this->swizzle(0, 0, splitted, true); + this->swizzle(this->loaded_chunks.begin(), 0, splitted, true); return; } + std::list::iterator last_it {--this->loaded_chunks.end()}; ekg::io::chunk_t &last_chunk { - this->loaded_chunks.back() + *last_it }; this->swizzle( - this->loaded_chunks.size() - 1, + last_it, last_chunk.size() - !last_chunk.empty(), splitted, false ); } -std::vector &ekg::text::chunks_data() { +std::list &ekg::text::chunks_data() { return this->loaded_chunks; } diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index a6468772..ce81e9b0 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -205,7 +205,7 @@ bool ekg::ui::find_index_by_interact( size_t chunk_size {}; size_t il {}; - size_t addition_chunk_index {}; + size_t sum_chunk_sizes {}; size_t text_total_lines {textbox.text.length_of_lines()}; ekg::vec2_t rendered {}; @@ -216,7 +216,7 @@ bool ekg::ui::find_index_by_interact( uint8_t uc8 {}; char32_t c32 {}; - std::vector &chunks {textbox.text.chunks_data()}; + std::list &chunks {textbox.text.chunks_data()}; size_t chunks_size {chunks.size()}; index.y += textbox.widget.view_line_index; @@ -234,13 +234,9 @@ bool ekg::ui::find_index_by_interact( std::string empty {"\n"}; bool is_empty {}; - for (size_t ic {textbox.widget.view_chunk_index}; ic < chunks_size; ic++) { - ekg::io::chunk_t &chunk {chunks.at(ic)}; - - il = 0; - if (ic == textbox.widget.view_chunk_index) { - il = textbox.widget.view_chunk_line_index; - } + il = textbox.widget.view_chunk_line_index; + for (std::list::iterator ic {textbox.widget.view_chunk_it}; ic != chunks.end(); ic++) { + ekg::io::chunk_t &chunk {*ic}; chunk_size = chunk.size(); for (;il < chunk_size; il++) { @@ -302,6 +298,7 @@ bool ekg::ui::find_index_by_interact( } } + il = 0; index.x = 0; pos.x = textbox.color_scheme.gutter_margin; @@ -634,7 +631,7 @@ void ekg::ui::reload( textbox.rect.h = aligned_dimension.h * textbox.rect.scaled_height; size_t highest_size {}; - std::vector &chunks {textbox.text.chunks_data()}; + std::list &chunks {textbox.text.chunks_data()}; ekg::rect_t &rect_abs { ekg::ui::get_abs_rect(property, textbox.rect) @@ -1353,13 +1350,13 @@ void ekg::ui::buffering( size_t text_total_chars {textbox.text.length_of_chars()}; size_t text_total_lines {textbox.text.length_of_lines()}; size_t il {}; - size_t addition_chunk_index {}; + size_t sum_chunk_sizes {}; size_t chunk_size {}; ekg::vec2_t index {}; ekg::pixel_t line_wsize {}; ekg::textbox_t::cursor_t cursor {}; - std::vector &chunks {textbox.text.chunks_data()}; + std::list &chunks {textbox.text.chunks_data()}; size_t chunks_size {chunks.size()}; bool is_renderable {}; @@ -1403,26 +1400,26 @@ void ekg::ui::buffering( bool was_empty_before {}; textbox.widget.layers_select.clear(); - for (size_t ic {}; ic < chunks_size; ic++) { - ekg::io::chunk_t &chunk {chunks.at(ic)}; + for (std::list::iterator ic {chunks.begin()}; ic != chunks.end(); ic++) { + ekg::io::chunk_t &chunk {*ic}; chunk_size = chunk.size(); - if (addition_chunk_index + chunk_size < textbox.widget.view_line_index) { + if (sum_chunk_sizes + chunk_size < textbox.widget.view_line_index) { pos.x = 0.0f; index.y += chunk_size; - addition_chunk_index += chunk_size; + sum_chunk_sizes += chunk_size; continue; } il = 0; if (!oka_found_visual_index) { - textbox.widget.view_chunk_index = ic; - il = textbox.widget.view_line_index - addition_chunk_index; + textbox.widget.view_chunk_it = ic; + il = textbox.widget.view_line_index - sum_chunk_sizes; textbox.widget.view_chunk_line_index = il; index.y += il; oka_found_visual_index = true; } - addition_chunk_index += chunk_size; + sum_chunk_sizes += chunk_size; for (;il < chunk_size; il++) { std::string &chunk_line {chunk.at(il)}; std::string &line {(is_empty = chunk_line.empty()) ? empty : chunk_line}; From 5145c57231a4aa59b3134bb29ac0fbadba8ea3fd Mon Sep 17 00:00:00 2001 From: MrsRina Date: Thu, 5 Feb 2026 00:50:28 -0300 Subject: [PATCH 50/61] [fix] text chunks wrong insert order when swizzle --- include/ekg/io/utf.hpp | 3 +++ src/io/utf.cpp | 18 ++++++++++++++---- src/ui/textbox/widget.cpp | 19 +++++++++++-------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index fae2a537..1bcb0014 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -293,6 +293,9 @@ namespace ekg { bool audited(); void unset_audited(); void gc(); + + void set_lines_per_chunk_limit(size_t limit); + size_t get_lines_per_chunk_limit(); }; } diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 34a7a1ba..71fc408e 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -527,20 +527,18 @@ void ekg::text::swizzle( for (size_t jt {}; jt < newly_chunks; jt++) { this->loaded_chunks.insert( - chunk_it, + ++chunk_it, ekg::io::chunk_t { to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 0)), to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 1)) } ); - - chunk_it++; } size_t rest {to_swizzle_chunk_size - (this->lines_per_chunk_limit * newly_chunks)}; if (rest > 0) { this->loaded_chunks.insert( - chunk_it, + ++chunk_it, ekg::io::chunk_t { to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (newly_chunks + 0)), to_swizzle_chunk.end() @@ -632,6 +630,8 @@ void ekg::text::insert( ekg::utf8_split_endings(lines, splitted); } + size_t b {}; + size_t total_of_chunks {this->loaded_chunks.size()}; for (std::list::iterator it {this->loaded_chunks.begin()}; it != this->loaded_chunks.end(); it++) { ekg::io::chunk_t &chunk {*it}; @@ -650,6 +650,8 @@ void ekg::text::insert( this->total_lines += splitted.size(); return; } + + b++; } throw std::out_of_range("ekg::text::insert -> lines length: " + std::to_string(current_lines)); @@ -880,3 +882,11 @@ bool ekg::text::audited() { void ekg::text::unset_audited() { this->was_audited = false; } + +void ekg::text::set_lines_per_chunk_limit(size_t limit) { + this->lines_per_chunk_limit = limit; +} + +size_t ekg::text::get_lines_per_chunk_limit() { + return this->lines_per_chunk_limit; +} diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index ce81e9b0..dabde21f 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -138,7 +138,7 @@ void ekg::ui::refresh_cursors_pos( cursor.b.x > origin.b.x ) { cursor.b.x = (origin.a.x < origin.b.x) ? - cursor.b.x- (origin.b.x - origin.a.x) + cursor.b.x - (origin.b.x - origin.a.x) : cursor.b.x + (origin.a.x - origin.b.x); cursor.b.y -= displacement_b.y; @@ -287,7 +287,7 @@ bool ekg::ui::find_index_by_interact( index.x++; return true; } - + index.x++; pos.x += rect.w; @@ -298,7 +298,6 @@ bool ekg::ui::find_index_by_interact( } } - il = 0; index.x = 0; pos.x = textbox.color_scheme.gutter_margin; @@ -310,6 +309,8 @@ bool ekg::ui::find_index_by_interact( return false; } } + + il = 0; } return false; @@ -1181,6 +1182,8 @@ void ekg::ui::event( ekg::gui.ui.redraw = true; + ekg_log_low_level(textbox.text.length_of_chunks()) + break; } case ekg::io::stage::pre: @@ -1336,7 +1339,7 @@ void ekg::ui::buffering( bool is_ab_equals_selected {}; bool is_cursor_at_end_of_line {}; bool cursors_going_on {!textbox.widget.cursors.empty()}; - bool oka_found_visual_index {}; + bool reached_renderable_state {}; bool get_out {}; bool is_last_char_from_line {}; @@ -1411,12 +1414,12 @@ void ekg::ui::buffering( } il = 0; - if (!oka_found_visual_index) { - textbox.widget.view_chunk_it = ic; + if (!reached_renderable_state) { il = textbox.widget.view_line_index - sum_chunk_sizes; - textbox.widget.view_chunk_line_index = il; index.y += il; - oka_found_visual_index = true; + textbox.widget.view_chunk_line_index = il; + textbox.widget.view_chunk_it = ic; + reached_renderable_state = true; } sum_chunk_sizes += chunk_size; From 7fb8d8c270f16124e023a00ad63ccf852eeb8e83 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Thu, 26 Feb 2026 23:22:13 -0300 Subject: [PATCH 51/61] [fix] meow --- include/ekg/io/utf.hpp | 1 + src/io/utf.cpp | 128 ++++++++++++++++++++++++++------------ src/ui/textbox/widget.cpp | 8 ++- 3 files changed, 96 insertions(+), 41 deletions(-) diff --git a/include/ekg/io/utf.hpp b/include/ekg/io/utf.hpp index 1bcb0014..a6b6c4a7 100644 --- a/include/ekg/io/utf.hpp +++ b/include/ekg/io/utf.hpp @@ -293,6 +293,7 @@ namespace ekg { bool audited(); void unset_audited(); void gc(); + void sanitize(); void set_lines_per_chunk_limit(size_t limit); size_t get_lines_per_chunk_limit(); diff --git a/src/io/utf.cpp b/src/io/utf.cpp index 71fc408e..44fc7910 100644 --- a/src/io/utf.cpp +++ b/src/io/utf.cpp @@ -488,15 +488,18 @@ void ekg::text::swizzle( std::vector &to_swizzle, bool skip_first_line ) { + ekg_log_low_level("swizzle...") + + ekg::io::chunk_t &chunk {*chunk_it}; + this->was_audited = true; bool is_empty {to_swizzle.empty()}; - ekg::io::chunk_t &chunk {*chunk_it}; if (skip_first_line) { chunk.at(line_index) = is_empty ? "" : to_swizzle.at(0); } - if (to_swizzle.empty()) { + if (is_empty) { to_swizzle.emplace_back(); } @@ -506,7 +509,7 @@ void ekg::text::swizzle( to_swizzle.end() ); - if (chunk.size() < this->lines_per_chunk_limit) { + if (chunk.size() <= this->lines_per_chunk_limit) { return; } @@ -521,30 +524,61 @@ void ekg::text::swizzle( ); size_t to_swizzle_chunk_size {to_swizzle_chunk.size()}; - size_t newly_chunks { - to_swizzle_chunk_size / this->lines_per_chunk_limit - }; - for (size_t jt {}; jt < newly_chunks; jt++) { - this->loaded_chunks.insert( - ++chunk_it, - ekg::io::chunk_t { - to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 0)), - to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 1)) - } - ); + if (++chunk_it == this->loaded_chunks.end()) { + size_t newly_chunks { + to_swizzle_chunk_size / this->lines_per_chunk_limit + }; + + ekg_log_low_level("insert at end if necessary") + + for (size_t jt {}; jt < newly_chunks; jt++) { + chunk_it = this->loaded_chunks.insert( + chunk_it, + ekg::io::chunk_t { + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 0)), + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (jt + 1)) + } + ); + } + + size_t rest {to_swizzle_chunk_size - (this->lines_per_chunk_limit * newly_chunks)}; + if (rest > 0) { + chunk_it = this->loaded_chunks.insert( + chunk_it, + ekg::io::chunk_t { + to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (newly_chunks + 0)), + to_swizzle_chunk.end() + } + ); + } + + return; } - size_t rest {to_swizzle_chunk_size - (this->lines_per_chunk_limit * newly_chunks)}; - if (rest > 0) { - this->loaded_chunks.insert( - ++chunk_it, - ekg::io::chunk_t { - to_swizzle_chunk.begin() + (this->lines_per_chunk_limit * (newly_chunks + 0)), - to_swizzle_chunk.end() - } + ekg::io::chunk_t &next_chunk { + *chunk_it + }; + + if (to_swizzle_chunk_size + next_chunk.size() > this->lines_per_chunk_limit) { + chunk_it = this->loaded_chunks.insert(chunk_it, {}); + ekg_log_low_level("insert if next chunk too large for this") + this->swizzle( + chunk_it, + 0, + to_swizzle_chunk, + false ); + return; } + + next_chunk.insert( + next_chunk.begin(), + to_swizzle_chunk.begin(), + to_swizzle_chunk.end() + ); + + ekg_log_low_level("insert if next chunk allows") } size_t ekg::text::set(size_t index, std::string_view line) { @@ -776,36 +810,38 @@ void ekg::text::erase( previous_lines = lines; lines += (chunk_size = chunk.size()); - if (begin <= lines) { + if (begin < lines) { remains_lines = end - begin; begin = begin - previous_lines; - while (remains_lines != 0) { - ekg::io::chunk_t &chunk {*it}; + ekg_log_low_level(remains_lines << " x " << begin) + while (remains_lines != 0 && it != this->loaded_chunks.end()) { + ekg::io::chunk_t &chunk {*it}; chunk_size = chunk.size(); - goto_next_chunk = begin + remains_lines > chunk.size(); - chunk.erase( - chunk.begin() + begin, - chunk.begin() + begin + (!goto_next_chunk * remains_lines) - ); + if (chunk_size != 0) { + lines = (remains_lines + begin) >= (chunk_size) ? (chunk_size - begin) : (remains_lines); + + ekg_log_low_level(remains_lines << " " << begin << " | " << lines) - empty_chunk = chunk.empty(); - if (empty_chunk) { - this->loaded_chunks.erase( - it + chunk.erase( + chunk.begin() + begin, // 5 + chunk.begin() + begin + remains_lines // 5 + 1 = 6 ); - } - if (goto_next_chunk) { - if (!empty_chunk) it++; - remains_lines -= chunk_size - begin; + ekg_log_low_level(remains_lines << " vv " << lines) + remains_lines *= lines != 0; + remains_lines -= lines; + begin = 0; - continue; } - remains_lines = 0; + if (chunk.empty()) { + it = this->loaded_chunks.erase(it); + } + + it++; } break; @@ -890,3 +926,15 @@ void ekg::text::set_lines_per_chunk_limit(size_t limit) { size_t ekg::text::get_lines_per_chunk_limit() { return this->lines_per_chunk_limit; } + +void ekg::text::sanitize() { + if (this->loaded_chunks.empty()) { + this->loaded_chunks.emplace_back(); + } + + ekg::io::chunk_t &chunk {*this->loaded_chunks.begin()}; + + if (chunk.empty()) { + chunk.emplace_back(); + } +} diff --git a/src/ui/textbox/widget.cpp b/src/ui/textbox/widget.cpp index dabde21f..0c248296 100644 --- a/src/ui/textbox/widget.cpp +++ b/src/ui/textbox/widget.cpp @@ -440,6 +440,8 @@ void ekg::ui::handle_erase( return; } + ekg_log_low_level("begin") + if (cursor.a.y == cursor.b.y) { std::string line {textbox.text.at(cursor.a.y)}; std::string concated {}; @@ -506,9 +508,13 @@ void ekg::ui::handle_erase( cursor.b.y + 1 ); + textbox.text.sanitize(); + cursor.b = cursor.a; cursor.delta = cursor.a; cursor.highest_char_index = cursor.a.x; + + ekg_log_low_level("ended") } void ekg::ui::handle_insert( @@ -845,7 +851,7 @@ void ekg::ui::event( if (is_action_paste) { input.was_typed = true; - input.typed = ekg::p_core->p_platform_base->get_clipboard_text(); + input.typed = ekg::p_core->p_platform_base->get_clipboard_text(); } std::string clipboard_builder {}; From b609b8d3da3c465db486a9e9392ca445c4262035 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Thu, 26 Feb 2026 23:32:12 -0300 Subject: [PATCH 52/61] [build] hacky for including --- CMakeLists.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38085a13..f6628b4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,11 +43,27 @@ file(GLOB_RECURSE SOURCE_FILES "src/*.cpp") # exclude_files_by_regex(SOURCE_FILES /* regex */) +### +## +## This is a "hacky" for git clone fetching from +## other CMakeLists that use `FetchContent_MakeAvailable`. +## +### + +### here + +include_directories(include) +include_directories(${FREETYPE_INCLUDE_DIRS}) + +### + add_library( ekg STATIC ${SOURCE_FILES} ) +### here + target_include_directories( ekg PUBLIC $ @@ -55,6 +71,8 @@ target_include_directories( ${FREETYPE_INCLUDE_DIRS} ) +### + target_compile_options( ekg PRIVATE ${COMPILE_OPTIMIZATION_NUMBER} From ff60ad8a5fe5542c78eefbf53bde34ca88d708a6 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Thu, 26 Feb 2026 23:45:59 -0300 Subject: [PATCH 53/61] [build] hacky for including --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6628b4a..c98748bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ file(GLOB_RECURSE SOURCE_FILES "src/*.cpp") ### here -include_directories(include) +include_directories($) include_directories(${FREETYPE_INCLUDE_DIRS}) ### From 4c8d41aac713982d58b20988ee329b35c6a0b891 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Thu, 26 Feb 2026 23:47:28 -0300 Subject: [PATCH 54/61] [build] hacky for including --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c98748bd..910c8ee2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,12 +64,6 @@ add_library( ### here -target_include_directories( - ekg PUBLIC - $ - $ - ${FREETYPE_INCLUDE_DIRS} -) ### From 02afbdd1b75be4ed6e3c6361908262ec4fce1d3a Mon Sep 17 00:00:00 2001 From: MrsRina Date: Thu, 26 Feb 2026 23:48:35 -0300 Subject: [PATCH 55/61] [build] hacky for including --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 910c8ee2..50e0277b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,6 @@ file(GLOB_RECURSE SOURCE_FILES "src/*.cpp") ### here -include_directories($) -include_directories(${FREETYPE_INCLUDE_DIRS}) - ### add_library( From 8721d817080e52b0e590eddd18a2f7ee56c578bb Mon Sep 17 00:00:00 2001 From: MrsRina Date: Thu, 26 Feb 2026 23:58:25 -0300 Subject: [PATCH 56/61] [build] hacky for including --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e0277b..fa4e44bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,9 @@ file(GLOB_RECURSE SOURCE_FILES "src/*.cpp") ### here +include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${FREETYPE_INCLUDE_DIRS}) + ### add_library( @@ -61,6 +64,12 @@ add_library( ### here +target_include_directories( + ekg PUBLIC + $ + $ + ${FREETYPE_INCLUDE_DIRS} +) ### From 30752f3a2accd625d59794a26c40d0c00843d211 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Fri, 27 Feb 2026 00:04:17 -0300 Subject: [PATCH 57/61] [build] hacky for including --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa4e44bc..6544b3eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,6 @@ file(GLOB_RECURSE SOURCE_FILES "src/*.cpp") ### here -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${FREETYPE_INCLUDE_DIRS}) - ### add_library( From 122351fce8a3a311740c4702fe9bc622c954fd79 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Fri, 27 Feb 2026 00:07:24 -0300 Subject: [PATCH 58/61] [build] undid hacky --- CMakeLists.txt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6544b3eb..38085a13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,24 +43,11 @@ file(GLOB_RECURSE SOURCE_FILES "src/*.cpp") # exclude_files_by_regex(SOURCE_FILES /* regex */) -### -## -## This is a "hacky" for git clone fetching from -## other CMakeLists that use `FetchContent_MakeAvailable`. -## -### - -### here - -### - add_library( ekg STATIC ${SOURCE_FILES} ) -### here - target_include_directories( ekg PUBLIC $ @@ -68,8 +55,6 @@ target_include_directories( ${FREETYPE_INCLUDE_DIRS} ) -### - target_compile_options( ekg PRIVATE ${COMPILE_OPTIMIZATION_NUMBER} From a6c1b7eb2f430a173dd3f66483f1790aeb1fce60 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Fri, 27 Feb 2026 00:28:26 -0300 Subject: [PATCH 59/61] [build] removed trash code --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38085a13..794ea2fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,6 @@ find_package(Freetype REQUIRED) file(GLOB_RECURSE HEADER_FILES "include/*.hpp") file(GLOB_RECURSE SOURCE_FILES "src/*.cpp") -# exclude_files_by_regex(SOURCE_FILES /* regex */) - add_library( ekg STATIC ${SOURCE_FILES} From 6da9be66f0c7c7664250724608b302330ce59905 Mon Sep 17 00:00:00 2001 From: MrsRina Date: Tue, 3 Mar 2026 20:34:42 -0300 Subject: [PATCH 60/61] [update] listener for input binding --- include/ekg/handler/input.hpp | 2 ++ include/ekg/handler/input/handler.hpp | 6 ++++++ include/ekg/io/memory.hpp | 3 +-- src/handler/input.cpp | 23 +++++++++++++++++++++++ src/handler/input/handler.cpp | 10 ++++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/include/ekg/handler/input.hpp b/include/ekg/handler/input.hpp index 3d38041d..adbfbe64 100644 --- a/include/ekg/handler/input.hpp +++ b/include/ekg/handler/input.hpp @@ -74,6 +74,8 @@ namespace ekg { std::string_view typed {}; }; + using input_bind_function_t = ekg::result (*)(std::string_view, bool); + struct input_key_t { public: int32_t key {}; diff --git a/include/ekg/handler/input/handler.hpp b/include/ekg/handler/input/handler.hpp index c3c708fb..818bdde9 100644 --- a/include/ekg/handler/input/handler.hpp +++ b/include/ekg/handler/input/handler.hpp @@ -34,6 +34,8 @@ namespace ekg::handler { // prevent too many unoptmized use of string allocations std::string key_name {}; std::string string_builder {}; + + ekg::input_bind_function_t input_bind_listener_function {}; public: ekg::input_info_t input {}; protected: @@ -86,6 +88,10 @@ namespace ekg::handler { bool get_input_state( std::string_view tag ); + + void set_input_bind_listener( + ekg::input_bind_function_t input_bind_listener_function + ); }; } diff --git a/include/ekg/io/memory.hpp b/include/ekg/io/memory.hpp index 171a4082..d548b9c3 100644 --- a/include/ekg/io/memory.hpp +++ b/include/ekg/io/memory.hpp @@ -84,9 +84,8 @@ namespace ekg { **/ namespace ekg { /** - * Broken heart hash.......... **/ - constexpr ekg::id_t not_found {2942656639}; + constexpr ekg::id_t not_found {283233071866}; struct at_t { public: diff --git a/src/handler/input.cpp b/src/handler/input.cpp index de162cfc..e8da052d 100644 --- a/src/handler/input.cpp +++ b/src/handler/input.cpp @@ -1,3 +1,26 @@ +/** + * MIT License + * + * Copyright (c) 2022-2025 Rina Wilk / vokegpu@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ #include "ekg/handler/input.hpp" #include "ekg/core/runtime.hpp" diff --git a/src/handler/input/handler.cpp b/src/handler/input/handler.cpp index 6f399624..adbdc97e 100644 --- a/src/handler/input/handler.cpp +++ b/src/handler/input/handler.cpp @@ -670,6 +670,10 @@ void ekg::handler::input::set_input_state( ) { this->input_map[key.data()] = state; + if (!this->input_bind_listener_function) { + this->input_bind_listener_function(key, state); + } + for (bool *p_address : this->input_bindings_map[key.data()]) { if (!p_address) { continue; @@ -732,3 +736,9 @@ bool ekg::handler::input::get_input_bind_state( ) { return this->input_bind_map[tag.data()].state; } + +void ekg::handler::input::set_input_bind_listener( + ekg::input_bind_function_t input_bind_listener_function +) { + this->input_bind_listener_function = input_bind_listener_function; +} From 4e09fd7ff1f08d5c2ee72e599fdf0e4d75dab4bf Mon Sep 17 00:00:00 2001 From: MrsRina Date: Tue, 3 Mar 2026 21:14:21 -0300 Subject: [PATCH 61/61] [update] listener for input binding --- include/ekg/handler/input.hpp | 1 + src/handler/input.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/ekg/handler/input.hpp b/include/ekg/handler/input.hpp index adbfbe64..f7d8579c 100644 --- a/include/ekg/handler/input.hpp +++ b/include/ekg/handler/input.hpp @@ -96,6 +96,7 @@ namespace ekg { bool input(std::string_view input); void bind(std::string_view tag, std::string_view input); void bind(std::string_view tag, std::vector inputs); + void listener(ekg::input_bind_function_t input_bind_listener_function); } #endif diff --git a/src/handler/input.cpp b/src/handler/input.cpp index e8da052d..fbc5f310 100644 --- a/src/handler/input.cpp +++ b/src/handler/input.cpp @@ -45,3 +45,7 @@ void ekg::bind(std::string_view tag, std::vector inputs) { ekg::bind(tag, inputs); } } + +void ekg::listener(ekg::input_bind_function_t input_bind_listener_function) { + ekg::p_core->handler_input.set_input_bind_listener(input_bind_listener_function); +}