diff --git a/cmake/properties.cmake b/cmake/properties.cmake index fc837c00..2a528f88 100644 --- a/cmake/properties.cmake +++ b/cmake/properties.cmake @@ -1,4 +1,4 @@ -set(EKG_VERSION 2.2.0) +set(EKG_VERSION 2.3.0) set(EKG_INCLUDE_DIRS "${CMAKE_INSTALL_PREFIX}/include") if(CMAKE_TOOLCHAIN_FILE) diff --git a/include/ekg/core/pools.hpp b/include/ekg/core/pools.hpp index 10792bd9..2351e891 100644 --- a/include/ekg/core/pools.hpp +++ b/include/ekg/core/pools.hpp @@ -41,6 +41,9 @@ #include "ekg/ui/label/label.hpp" #include "ekg/ui/label/widget.hpp" +#include "ekg/ui/scrollbar/scrollbar.hpp" +#include "ekg/ui/scrollbar/widget.hpp" + namespace ekg::core { void registry(ekg::property_t &property); } @@ -62,6 +65,7 @@ namespace ekg::core { ekg_core_declare_widget_case_todo(ekg::frame_t, widget_descriptor_at, ekg_core_widget_todo); \ ekg_core_declare_widget_case_todo(ekg::button_t, widget_descriptor_at, ekg_core_widget_todo); \ ekg_core_declare_widget_case_todo(ekg::label_t, widget_descriptor_at, ekg_core_widget_todo); \ + ekg_core_declare_widget_case_todo(ekg::scrollbar_t, widget_descriptor_at, ekg_core_widget_todo); \ } #define ekg_registry_widget(widget_descriptor_t, register_widget_pool, register_property_pool, is_container, register_settings) \ @@ -123,6 +127,9 @@ namespace ekg { ekg::pool label_property {}; ekg::pool label {}; + + ekg::pool scrollbar_property {}; + ekg::pool scrollbar {}; } pools; template @@ -156,6 +163,10 @@ namespace ekg { return ekg::io::any_static_cast( &ekg::pools.label_property.query(at) ); + case ekg::type::scrollbar: + return ekg::io::any_static_cast( + &ekg::pools.scrollbar_property.query(at) + ); } case ekg::type::button: return ekg::io::any_static_cast( @@ -169,6 +180,10 @@ namespace ekg { return ekg::io::any_static_cast( &ekg::pools.label.query(at) ); + case ekg::type::scrollbar: + return ekg::io::any_static_cast( + &ekg::pools.scrollbar.query(at) + ); } return t::not_found; @@ -186,8 +201,8 @@ namespace ekg { ekg::pools.frame_property, true, { - property.widget.is_childnizate = true; - property.widget.is_children_docknizable = true; + property.is_childnizate = true; + property.is_children_docknizable = true; widget.color_scheme = global_theme.frame_color_scheme; } ); @@ -200,8 +215,8 @@ namespace ekg { ekg::pools.button_property, false, { - property.widget.is_childnizate = false; - property.widget.is_children_docknizable = false; + property.is_childnizate = false; + property.is_children_docknizable = false; widget.color_scheme = global_theme.button_color_scheme; } ); @@ -212,14 +227,29 @@ namespace ekg { ekg::label_t, ekg::pools.label, ekg::pools.label_property, - true, + false, { - property.widget.is_childnizate = false; - property.widget.is_children_docknizable = false; + property.is_childnizate = false; + property.is_children_docknizable = false; widget.color_scheme = global_theme.label_color_scheme; } ); } + + case ekg::type::scrollbar: { + ekg_registry_widget( + ekg::scrollbar_t, + ekg::pools.scrollbar, + ekg::pools.scrollbar_property, + false, + { + property.is_childnizate = false; + property.is_children_docknizable = false; + widget.color_scheme = global_theme.scrollbar_color_scheme; + } + ); + } + case ekg::type::stack: { ekg::stack_t &stack { ekg::pools.stack.push_back( @@ -240,7 +270,7 @@ namespace ekg { ) ); case ekg::type::sampler: - return ekg::io::any_static_cast( + return ekg::io::any_static_cast( &ekg::pools.sampler.push_back( ekg::io::any_static_cast(&descriptor) ) diff --git a/include/ekg/draw/allocator.hpp b/include/ekg/draw/allocator.hpp index 7cc20239..c916b527 100644 --- a/include/ekg/draw/allocator.hpp +++ b/include/ekg/draw/allocator.hpp @@ -31,19 +31,28 @@ #include "ekg/gpu/sampler.hpp" /** - * This macro prevent from dispatching any GPU-data under a render section - * if the content is not visible to the parent rect_scissor. + * Each rendering section must begin with this, so we can do smart-caching + * for CPU-side. **/ -#define ekg_draw_allocator_assert_scissor(rect_scissor, rect_child, rect_parent, is_parented) \ - if (!ekg::p_core->draw_allocator.sync_scissor(rect_scissor, rect_child, rect_parent, is_parented)) return; - #define ekg_draw_allocator_bind_local(p_geometry_buffer, p_gpu_data_buffer) \ ekg::p_core->draw_allocator.bind_local(p_geometry_buffer, p_gpu_data_buffer); +/** + * Each rendering section must end with this, for send buffers to GPU. + **/ #define ekg_draw_allocator_pass() \ ekg::p_core->draw_allocator.pass(); \ return; +/** + * This macro prevent from dispatching any GPU-data under a render section + * if the content is not visible to the parent rect_scissor. + **/ +#define ekg_draw_allocator_assert_scissor(rect_scissor, rect_child, rect_parent, is_parented) \ + if (!ekg::p_core->draw_allocator.sync_scissor(rect_scissor, rect_child, rect_parent, is_parented)) { \ + ekg_draw_allocator_pass(); \ + }; + namespace ekg::draw { class allocator { public: diff --git a/include/ekg/handler/input.hpp b/include/ekg/handler/input.hpp index c52ae1f1..80606034 100644 --- a/include/ekg/handler/input.hpp +++ b/include/ekg/handler/input.hpp @@ -60,7 +60,7 @@ namespace ekg { struct input_info_t { public: - float scroll_speed {0.4f}; + float scroll_speed {1.0f}; ekg::timing_t ui_timeout_timing {}; ekg::timing_t ui_scrolling_timing {}; ekg::timing_t timing_last_interact {}; diff --git a/include/ekg/handler/theme.hpp b/include/ekg/handler/theme.hpp index 01ee08e6..8bd49f3f 100644 --- a/include/ekg/handler/theme.hpp +++ b/include/ekg/handler/theme.hpp @@ -27,6 +27,7 @@ #include "ekg/ui/button/button.hpp" #include "ekg/ui/frame/frame.hpp" #include "ekg/ui/label/label.hpp" +#include "ekg/ui/scrollbar/scrollbar.hpp" namespace ekg { struct theme_t { @@ -40,10 +41,11 @@ namespace ekg { ekg::button_color_scheme_t button_color_scheme {}; ekg::frame_color_scheme_t frame_color_scheme {}; ekg::label_color_scheme_t label_color_scheme {}; + ekg::scrollbar_color_scheme_t scrollbar_color_scheme {}; }; - ekg::theme_t &theme(std::string_view tag = ""); - ekg::theme_t &set_current_theme(std::string_view tag); + ekg::theme_t &theme(std::string tag = ""); + ekg::theme_t &set_current_theme(const std::string &tag); } #endif diff --git a/include/ekg/handler/theme/handler.hpp b/include/ekg/handler/theme/handler.hpp index bab6c401..d895c457 100644 --- a/include/ekg/handler/theme/handler.hpp +++ b/include/ekg/handler/theme/handler.hpp @@ -32,15 +32,15 @@ namespace ekg::handler { class theme { protected: - std::string_view current_theme_tag {"dark-theme"}; - std::map themes {}; + std::string current_theme_tag {"dark-theme"}; + std::map themes {}; public: void init(); void quit(); - ekg::theme_t ®istry(const std::string_view &tag); + ekg::theme_t ®istry(const std::string &tag); ekg::theme_t &get_current_theme(); - ekg::theme_t &set_current_theme(const std::string_view &tag); + ekg::theme_t &set_current_theme(const std::string &tag); }; } diff --git a/include/ekg/io/event.hpp b/include/ekg/io/event.hpp index 7d84f477..5c75d3d5 100644 --- a/include/ekg/io/event.hpp +++ b/include/ekg/io/event.hpp @@ -51,9 +51,11 @@ namespace ekg { hover, active, press, - release + release, + drag, + resize }; - constexpr size_t enum_action_size {static_cast(ekg::action::release)+1}; + constexpr size_t enum_action_size {static_cast(ekg::action::resize)+1}; template struct at_array_t { diff --git a/include/ekg/math/geometry.hpp b/include/ekg/math/geometry.hpp index f38a3cb0..d0d0b358 100644 --- a/include/ekg/math/geometry.hpp +++ b/include/ekg/math/geometry.hpp @@ -284,7 +284,7 @@ namespace ekg { } template - ekg::rect_t operator + (ekg::vec4_t vec) { + ekg::rect_t operator + (const ekg::vec4_t &vec) { return ekg::rect_t { this->x + static_cast(vec.x), this->y + static_cast(vec.y), diff --git a/include/ekg/ui/button/button.hpp b/include/ekg/ui/button/button.hpp index bfed6f55..d0193e05 100644 --- a/include/ekg/ui/button/button.hpp +++ b/include/ekg/ui/button/button.hpp @@ -28,6 +28,7 @@ #include "ekg/io/font.hpp" #include "ekg/math/geometry.hpp" #include "ekg/io/event.hpp" +#include "ekg/ui/property.hpp" namespace ekg { struct button_color_scheme_t { @@ -51,8 +52,6 @@ namespace ekg { public: struct widget_t { public: - bool is_highlight {}; - bool is_active {}; ekg::rect_t rect_text {}; ekg::rect_t rect_box {}; }; @@ -63,6 +62,7 @@ namespace ekg { ekg::flags_t box {ekg::dock::none}; ekg::flags_t dock {ekg::dock::left}; ekg::button_t::check_t::widget_t widget {}; + ekg::property_t::states_t states {}; ekg::at_array_t layers {}; ekg::at_array_t actions {}; }; diff --git a/include/ekg/ui/label/label.hpp b/include/ekg/ui/label/label.hpp index de04e55e..f20e8369 100644 --- a/include/ekg/ui/label/label.hpp +++ b/include/ekg/ui/label/label.hpp @@ -50,7 +50,7 @@ namespace ekg { ekg::at_t property_at {}; public: std::string tag {}; - ekg::rect_t rect {}; + ekg::rect_t rect {0.0f, 0.0f, 75.0f, 0.0f}; ekg::value text {}; ekg::flags_t dock {}; ekg::flags_t dock_text {}; diff --git a/include/ekg/ui/property.hpp b/include/ekg/ui/property.hpp index 95d7d920..f7c0f3e3 100644 --- a/include/ekg/ui/property.hpp +++ b/include/ekg/ui/property.hpp @@ -31,6 +31,18 @@ namespace ekg { struct property_t { public: + struct states_t { + public: + bool is_absolute {}; + bool is_active {}; + bool is_hovering {}; + bool is_visible {true}; + bool is_enabled {true}; + bool is_highlight {}; + bool is_focused {}; + bool is_warning {}; + }; + struct operation_t { public: bool should_reload {}; @@ -53,18 +65,8 @@ namespace ekg { ekg::rect_t rect_scissor {}; ekg::vec2_t min_size {}; ekg::rect_t rect {}; - bool is_childnizate {}; - bool is_children_docknizable {}; - bool is_targeting_absolute_parent {}; - bool is_absolute {}; - bool is_active {}; - bool is_hovering {}; - bool is_visible {true}; - bool is_enabled {true}; - bool is_highlight {}; - bool is_focused {}; - bool is_warning {}; bool is_high_frequency {}; + bool is_targeting_absolute_parent {}; bool should_refresh_size {true}; bool should_buffering {true}; }; @@ -75,8 +77,13 @@ namespace ekg { ekg::at_t parent_at {ekg::at_t::not_found}; ekg::at_t abs_parent_at {ekg::at_t::not_found}; ekg::at_t descriptor_at {ekg::at_t::not_found}; + ekg::at_t nearest_scrollbar_at {ekg::at_t::not_found}; + + bool is_childnizate {}; + bool is_children_docknizable {}; std::vector children {}; public: + ekg::property_t::states_t states {}; ekg::property_t::widget_t widget {}; ekg::property_t::scroll_t scroll {}; ekg::property_t::operation_t operation {}; diff --git a/include/ekg/ui/scrollbar/scrollbar.hpp b/include/ekg/ui/scrollbar/scrollbar.hpp new file mode 100644 index 00000000..76e29416 --- /dev/null +++ b/include/ekg/ui/scrollbar/scrollbar.hpp @@ -0,0 +1,74 @@ +/** + * 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_SCROLLBAR_HPP +#define EKG_UI_SCROLLBAR_HPP + +#include "ekg/ui/property.hpp" +#include "ekg/io/event.hpp" + +namespace ekg { + struct scrollbar_color_scheme_t { + public: + ekg::rgba_t background {}; + ekg::rgba_t outline {}; + + ekg::rgba_t bar_background {}; + ekg::rgba_t bar_highlight {}; + ekg::rgba_t bar_active {}; + ekg::rgba_t bar_outline {}; + + ekg::pixel_thickness_t bar_thickness {8}; + ekg::pixel_t bar_size_limit {20}; + }; + + struct scrollbar_t { + public: + struct widget_t { + public: + ekg::property_t::states_t states_horizontal_bar {}; + ekg::property_t::states_t states_vertical_bar {}; + ekg::rect_t rect_horizontal {}; + ekg::rect_t rect_vertical {}; + ekg::rect_t rect_delta {}; + }; + public: + static constexpr ekg::type type {ekg::type::scrollbar}; + static ekg::scrollbar_t not_found; + public: + ekg::at_t property_at {}; + public: + std::string tag {}; + ekg::rect_t rect {}; + ekg::vec2_t acceleration {}; + ekg::scrollbar_color_scheme_t color_scheme {}; + ekg::at_array_t layers {}; + ekg::at_array_t actions {}; + ekg::scrollbar_t::widget_t widget {}; + ekg::flags_t dock {}; // useless + public: + ekg_descriptor(ekg::scrollbar_t); + }; +} + +#endif diff --git a/include/ekg/ui/scrollbar/widget.hpp b/include/ekg/ui/scrollbar/widget.hpp new file mode 100644 index 00000000..9a806748 --- /dev/null +++ b/include/ekg/ui/scrollbar/widget.hpp @@ -0,0 +1,120 @@ +/** + * 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_SCROLLBAR_WIDGET_HPP +#define EKG_UI_SCROLLBAR_WIDGET_HPP + +#include "scrollbar.hpp" +#include "ekg/ui/property.hpp" + +namespace ekg::ui { + float get_horizontal_scrollbar_normalized( + ekg::property_t &property, + ekg::rect_t &rect_parent + ); + + float get_vertical_scrollbar_normalized( + ekg::property_t &property, + ekg::rect_t &rect_parent + ); + + void check_scrollbar( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent + ); + + void reset_scrollbar( + ekg::property_t &property + ); + + void clamp_scrollbar( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent + ); + + bool is_scrollbar_scrolling( + ekg::property_t &property, + bool state + ); + + void reload( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent, + std::vector &children, + bool should_childnizate_metrics + ); + + void reload( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar + ); + + void event( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + const ekg::io::stage &stage, + ekg::rect_t &rect_parent + ); + + void event( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + const ekg::io::stage &stage + ); + + void high_frequency( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent + ); + + void high_frequency( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar + ); + + void pass( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar + ); + + void buffering( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent + ); + + void buffering( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar + ); + + void unmap( + ekg::scrollbar_t &scrollbar + ); +} + +#endif diff --git a/src/core/runtime.cpp b/src/core/runtime.cpp index eb9451c2..01218791 100644 --- a/src/core/runtime.cpp +++ b/src/core/runtime.cpp @@ -48,7 +48,7 @@ void ekg::core::swap_collector( ekg::p_core->collector.push_back(property_at); - if (parent_property.widget.is_childnizate && parent_property.widget.is_children_docknizable) { + if (parent_property.is_childnizate && parent_property.is_children_docknizable) { for (ekg::at_t &at : parent_property.children) { ekg::property_t &property {ekg::query(at)}; if (property == ekg::property_t::not_found) { @@ -121,7 +121,7 @@ void ekg::core::reload(ekg::info_t &info) { for (ekg::at_t &at : ekg::p_core->reload) { ekg::property_t &property {ekg::query(at)}; - if (property == ekg::property_t::not_found) { + if (property == ekg::property_t::not_found || !property.operation.should_reload) { continue; } @@ -207,6 +207,27 @@ void ekg::core::scalenize(ekg::info_t &info) { ) ); } + + for (ekg::at_t &at : ekg::p_core->stack) { + ekg::property_t &property {ekg::query(at)}; + if (property == ekg::property_t::not_found) { + continue; + } + + ekg_core_abstract_todo( + property.descriptor_at.flags, + property.descriptor_at, + + ekg::ui::reload(property, descriptor); + + if (!property.is_children_docknizable) { + continue; + } + ); + + property.widget.should_refresh_size = true; + ekg::io::dispatch(ekg::io::operation::docknize, at); + } } void ekg::core::poll_event() { @@ -230,7 +251,7 @@ void ekg::core::poll_event() { if ( abs_widget != ekg::property_t::not_found && - (abs_widget.widget.is_absolute || is_on_scrolling_timeout) + (abs_widget.states.is_absolute || is_on_scrolling_timeout) ) { ekg_core_abstract_todo( @@ -288,11 +309,11 @@ void ekg::core::poll_event() { ekg::p_core->p_platform_base->event.type == ekg::io::event_type::text_input ) && - property.widget.is_hovering + property.states.is_hovering && - property.widget.is_visible + property.states.is_visible && - property.widget.is_enabled + property.states.is_enabled ); if (hovered) { @@ -301,19 +322,19 @@ void ekg::core::poll_event() { }; focused_property != ekg::property_t::not_found - && (focused_property.widget.is_hovering = false); + && (focused_property.states.is_hovering = false); focused_at = at; first_absolute = false; } - if (property.widget.is_absolute && !first_absolute) { + if (property.states.is_absolute && !first_absolute) { focused_at = at; first_absolute = true; } ekg::ui::event(property, descriptor, ekg::io::stage::post); - if (!hovered && !property.widget.is_absolute) { + if (!hovered && !property.states.is_absolute) { ekg::ui::event(property, descriptor, ekg::io::stage::process); } ); @@ -333,7 +354,7 @@ void ekg::core::poll_event() { ekg::gui.ui.hovered_type = static_cast(focused_at.flags); ekg::gui.ui.hovered_at = focused_at; - if (focused_property.widget.is_absolute) { + if (focused_property.states.is_absolute) { ekg::gui.ui.abs_widget_at = focused_at; } diff --git a/src/draw/allocator.cpp b/src/draw/allocator.cpp index 0d3a78bb..06dc01e5 100644 --- a/src/draw/allocator.cpp +++ b/src/draw/allocator.cpp @@ -83,7 +83,7 @@ void ekg::draw::allocator::revoke() { this->last_geometry_buffer_size = this->geometry_instance; this->was_hash_changed = false; - ekg::metrics.gpu_data_count = this->data_instance; + ekg::metrics.gpu_data_count = this->gpu_data_buffer.size(); } void ekg::draw::allocator::to_gpu() { diff --git a/src/ekg.cpp b/src/ekg.cpp index 391575e0..3a8300df 100644 --- a/src/ekg.cpp +++ b/src/ekg.cpp @@ -26,6 +26,7 @@ #include "ekg/core/context.hpp" #include "ekg/core/pools.hpp" +#include "ekg/ui/abstract.hpp" #include "ekg/ui/button/widget.hpp" ekg::runtime_t *ekg::p_core {nullptr}; @@ -95,8 +96,6 @@ void ekg::update() { return; } - ekg::p_core->handler_input.update(); - size_t size {ekg::p_core->high_frequency.size()}; for (size_t it {}; it < size; it++) { ekg::property_t &property { @@ -123,6 +122,7 @@ void ekg::update() { } } + ekg::p_core->handler_input.update(); ekg::p_core->handler_callback.update(); ekg::p_core->p_platform_base->update(); ekg::p_core->p_platform_base->event.type = ekg::io::event_type::none; @@ -151,6 +151,22 @@ void ekg::render() { property.descriptor_at.flags, property.descriptor_at, + /** + * @TODO: Fix this stupid glitch where scrolling is jittering because of something + * + * 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, + * 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. + * + * - @mrsrina / Rina Wilk + * 12:45am 6/17/2025 + * + **/ + ekg::ui::event(property, descriptor, ekg::io::stage::post); + /* */ + ekg::ui::pass(property, descriptor); if (!property.widget.should_buffering) { continue; diff --git a/src/handler/theme.cpp b/src/handler/theme.cpp index b474841d..55bb9958 100644 --- a/src/handler/theme.cpp +++ b/src/handler/theme.cpp @@ -25,7 +25,7 @@ #include "ekg/core/runtime.hpp" ekg::theme_t &ekg::theme( - std::string_view tag + std::string tag ) { if (tag.empty()) { return ekg::p_core->handler_theme.get_current_theme(); @@ -35,7 +35,7 @@ ekg::theme_t &ekg::theme( } ekg::theme_t &ekg::set_current_theme( - std::string_view tag + const std::string &tag ) { ekg::p_core->handler_theme.set_current_theme(tag); return ekg::theme(tag); diff --git a/src/handler/theme/handler.cpp b/src/handler/theme/handler.cpp index 35ab0a68..4f0d3355 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 = "Pasted light-theme... moow", + .description = "dim again, dim again, not even a light can undim my life, wasted love, wasted love...", }; light_pinky_theme.layout_offset = 2.0f; @@ -59,18 +59,65 @@ void ekg::handler::theme::init() { light_pinky_theme.label_color_scheme.outline = {202, 207, 222, 0}; light_pinky_theme.label_color_scheme.text_foreground = {141, 141, 141, 255}; + light_pinky_theme.scrollbar_color_scheme.background = {204, 204, 204, 30}; + light_pinky_theme.scrollbar_color_scheme.outline = {204, 204, 204, 0}; + light_pinky_theme.scrollbar_color_scheme.bar_background = {245, 169, 184, 100}; + light_pinky_theme.scrollbar_color_scheme.bar_highlight = {245, 169, 184, 50}; + light_pinky_theme.scrollbar_color_scheme.bar_active = {245, 169, 184, 100}; + this->registry(light_pinky_theme.tag) = light_pinky_theme; this->set_current_theme(light_pinky_theme.tag); + + ekg::theme_t black_light_pinky_theme { + .tag = "black-light-pinky", + .author = "Rina Wilk", + .description = "yea i loved you alot, but you broken my heart, I can not do nothing, this is obscure, I miss you 2666, but I do not know how to live anymore", + }; + + black_light_pinky_theme.layout_offset = 2.0f; + black_light_pinky_theme.layout_margin_thickness = 2; + black_light_pinky_theme.frame_color_scheme.background = {40, 40, 40, 255}; + black_light_pinky_theme.frame_color_scheme.highlight = {242, 242, 242, 0}; + black_light_pinky_theme.frame_color_scheme.outline = {190, 190, 190, 0}; + black_light_pinky_theme.frame_color_scheme.active = {242, 242, 242, 0}; + black_light_pinky_theme.frame_color_scheme.focused_background = {242, 242, 242, 0}; + black_light_pinky_theme.frame_color_scheme.focused_outline = {242, 242, 242, 0}; + black_light_pinky_theme.frame_color_scheme.warning_outline = {242, 242, 0, 100}; + black_light_pinky_theme.frame_color_scheme.actions_margin_pixel_thickness = 18; + + black_light_pinky_theme.button_color_scheme.text_foreground = {141, 141, 141, 255}; + black_light_pinky_theme.button_color_scheme.background = {204, 204, 204, 50}; + black_light_pinky_theme.button_color_scheme.active = {245, 169, 184, 100}; + black_light_pinky_theme.button_color_scheme.outline = {202, 207, 222, 0}; + black_light_pinky_theme.button_color_scheme.highlight = {245, 169, 184, 50}; + black_light_pinky_theme.button_color_scheme.text_foreground = {141, 141, 141, 255}; + black_light_pinky_theme.button_color_scheme.box_outline = light_pinky_theme.button_color_scheme.outline; + black_light_pinky_theme.button_color_scheme.box_active = {245, 169, 184, 200}; + black_light_pinky_theme.button_color_scheme.box_highlight = {245, 169, 184, 50}; + black_light_pinky_theme.button_color_scheme.box_background = {202, 207, 222, 100}; + + black_light_pinky_theme.label_color_scheme.background = {204, 204, 204, 0}; + black_light_pinky_theme.label_color_scheme.outline = {202, 207, 222, 0}; + black_light_pinky_theme.label_color_scheme.text_foreground = {141, 141, 141, 255}; + + black_light_pinky_theme.scrollbar_color_scheme.background = {204, 204, 204, 30}; + black_light_pinky_theme.scrollbar_color_scheme.outline = {204, 204, 204, 0}; + black_light_pinky_theme.scrollbar_color_scheme.bar_background = {245, 169, 184, 100}; + black_light_pinky_theme.scrollbar_color_scheme.bar_highlight = {245, 169, 184, 50}; + black_light_pinky_theme.scrollbar_color_scheme.bar_active = {245, 169, 184, 100}; + + this->registry(black_light_pinky_theme.tag) = black_light_pinky_theme; + //this->set_current_theme(black_light_pinky_theme.tag); } void ekg::handler::theme::quit() { } -ekg::theme_t &ekg::handler::theme::registry(const std::string_view &tag) { +ekg::theme_t &ekg::handler::theme::registry(const std::string &tag) { return this->themes[tag]; } -ekg::theme_t &ekg::handler::theme::set_current_theme(const std::string_view &tag) { +ekg::theme_t &ekg::handler::theme::set_current_theme(const std::string &tag) { this->current_theme_tag = tag; return this->themes[tag]; } diff --git a/src/layout/docknize.cpp b/src/layout/docknize.cpp index 4fa58dd3..b923c135 100644 --- a/src/layout/docknize.cpp +++ b/src/layout/docknize.cpp @@ -259,7 +259,7 @@ void ekg::layout::docknize_widget( if ( parent_property == ekg::property_t::not_found || - !parent_property.widget.is_children_docknizable + !parent_property.is_children_docknizable ) { return; } @@ -304,17 +304,35 @@ void ekg::layout::docknize_widget( current_global_theme.layout_margin_thickness ); + if (parent_property.nearest_scrollbar_at != ekg::at_t::not_found) { + ekg::scrollbar_t &scrollbar { + ekg::query(parent_property.nearest_scrollbar_at) + }; + + ekg::property_t &scrollbar_property { + ekg::query(scrollbar.property_at) + }; + + if ( + scrollbar_property != ekg::property_t::not_found + && + scrollbar != ekg::scrollbar_t::not_found + ) { + ekg::ui::reload(scrollbar_property, scrollbar); + } + } + if ( - parent_property.scroll.is_scrolling.x + parent_property.scroll.is_enabled.x || - parent_property.scroll.is_scrolling.y + parent_property.scroll.is_enabled.y ) { float nearest_scroll_bar_thickness { static_cast(parent_property.scroll.nearest_scroll_bar_thickness) }; - container_rect.w -= nearest_scroll_bar_thickness * static_cast(parent_property.scroll.is_scrolling.x); - container_rect.h -= nearest_scroll_bar_thickness * static_cast(parent_property.scroll.is_scrolling.y); + container_rect.w -= nearest_scroll_bar_thickness * static_cast(parent_property.scroll.is_enabled.y); + container_rect.h -= nearest_scroll_bar_thickness * static_cast(parent_property.scroll.is_enabled.x); } ekg::flags_t flags {}; @@ -371,7 +389,6 @@ void ekg::layout::docknize_widget( float imperfect_pixel_max_bounding {pixel_perfect_projection.x + ekg::one_pixel * 5.0f}; float projected_pixel_perfect_width {pixel_perfect_projection.x + pixel_perfect_projection.w}; float unsolved_pixel_position {}; - ekg::rect_t rect {}; for (ekg::at_t &at : parent_property.children) { @@ -380,7 +397,7 @@ void ekg::layout::docknize_widget( continue; } - if (property.widget.should_refresh_size) { + if (parent_property.widget.should_refresh_size) { property.widget.should_refresh_size = true; } @@ -566,7 +583,7 @@ void ekg::layout::docknize_widget( ); h_extent_backup = ekg::layout::extent_t::h_widget; - if (property.widget.is_children_docknizable && !property.children.empty()) { + if (property.is_children_docknizable && !property.children.empty()) { ekg::layout::docknize_widget(property); } @@ -575,8 +592,6 @@ void ekg::layout::docknize_widget( } parent_property.widget.should_refresh_size = false; - - // TODO: may is necessary to re-docknize the parent widget if previous scroll is disabled but now enabled } float ekg::layout::get_widget_height_by_children( @@ -585,7 +600,7 @@ float ekg::layout::get_widget_height_by_children( if ( parent_property == ekg::property_t::not_found || - !parent_property.widget.is_children_docknizable + !parent_property.is_children_docknizable ) { return 0.0f; } @@ -613,7 +628,7 @@ float ekg::layout::get_widget_height_by_children( height = property.widget.rect.h; if ( - property.widget.is_children_docknizable + property.is_children_docknizable && !property.children.empty() ) { diff --git a/src/ui/abstract.cpp b/src/ui/abstract.cpp index 1e5ee9a7..01b7309a 100644 --- a/src/ui/abstract.cpp +++ b/src/ui/abstract.cpp @@ -24,6 +24,7 @@ #include "ekg/ui/abstract.hpp" #include "ekg/core/pools.hpp" #include "ekg/core/runtime.hpp" +#include "ekg/io/descriptor.hpp" ekg::rect_t &ekg::ui::get_abs_rect( ekg::property_t &property, @@ -54,7 +55,7 @@ void ekg::ui::pre_event( ekg::rect_t &abs {ekg::ui::get_abs_rect(property, descriptor_rect)}; ekg::vec2_t interact {static_cast>(input.interact)}; - property.widget.is_hovering = ( + property.states.is_hovering = ( ekg::rect_collide_vec2(abs, interact) && ( @@ -71,13 +72,13 @@ void ekg::ui::pre_event( void ekg::ui::post_event( ekg::property_t &property ) { - property.widget.is_hovering = false; + property.states.is_hovering = false; #if defined(__ANDROID__) - property.widget.is_highlight = ( - !(!property.widget.is_hovering && ekg::p_core->handler_input.input.was_released) + property.states.is_highlight = ( + !(!property.states.is_hovering && ekg::p_core->handler_input.input.was_released) && - property.widget.is_highlight + property.states.is_highlight ); #endif } diff --git a/src/ui/button/widget.cpp b/src/ui/button/widget.cpp index 415fd922..ceee91a5 100644 --- a/src/ui/button/widget.cpp +++ b/src/ui/button/widget.cpp @@ -163,15 +163,15 @@ void ekg::ui::event( ekg_set( property.widget.should_buffering, - check.widget.is_highlight, - (property.widget.is_hovering && ekg::rect_collide_vec2(rect, interact)) + check.states.is_highlight, + (property.states.is_hovering && ekg::rect_collide_vec2(rect, interact)) ); ekg_action( check.actions, ekg::action::hover, ( - check.widget.is_highlight + check.states.is_highlight ) && (is_hovering_any = true) @@ -182,7 +182,7 @@ void ekg::ui::event( if ( input.was_pressed && - check.widget.is_highlight + check.states.is_highlight && ekg::fire("button-active") ) { @@ -192,7 +192,7 @@ void ekg::ui::event( ( ekg_set( property.widget.should_buffering, - check.widget.is_active, + check.states.is_active, true ) ) @@ -208,11 +208,11 @@ void ekg::ui::event( if ( input.was_released && - check.widget.is_active + check.states.is_active ) { ekg_set( property.widget.should_buffering, - check.widget.is_active, + check.states.is_active, false ); @@ -326,7 +326,7 @@ void ekg::ui::buffering( ekg_draw_allocator_assert_scissor( property.widget.rect_scissor, rect_abs, - ekg::query(property.parent_at).widget.rect, + ekg::query(property.parent_at).widget.rect_scissor, true ); @@ -369,7 +369,7 @@ void ekg::ui::buffering( ); } - if (check.widget.is_highlight) { + if (check.states.is_highlight) { ekg::draw::rect( rect, button.color_scheme.box_highlight, @@ -410,7 +410,7 @@ void ekg::ui::buffering( ); } - if (check.widget.is_highlight) { + if (check.states.is_highlight) { ekg::draw::rect( rect, button.color_scheme.highlight, diff --git a/src/ui/frame/widget.cpp b/src/ui/frame/widget.cpp index ee8a5a3a..31cd7683 100644 --- a/src/ui/frame/widget.cpp +++ b/src/ui/frame/widget.cpp @@ -70,7 +70,7 @@ void ekg::ui::event( ( input.has_motion && - property.widget.is_hovering + property.states.is_hovering && (ekg::timing_t::second > ekg::tweaks.task_latency) ), @@ -127,13 +127,13 @@ void ekg::ui::event( frame.widget.rect_delta.h = rect.h; frame.widget.rect_cache = rect; - property.widget.is_active = ( + property.states.is_active = ( frame.widget.target_dock_drag != ekg::dock::none || frame.widget.target_dock_resize != ekg::dock::none ); - property.widget.is_absolute = property.widget.is_active; + property.states.is_absolute = property.states.is_active; /*ekg::io::trigger( true, @@ -141,7 +141,7 @@ void ekg::ui::event( frame.actions, this->properties );*/ - } else if (input.has_motion && property.widget.is_active) { + } else if (input.has_motion && property.states.is_active) { ekg::rect_t new_rect {rect}; ekg::vec2_t interact {static_cast>(input.interact)}; @@ -218,11 +218,11 @@ void ekg::ui::event( ekg::gui.ui.redraw = true; } } else if ( - property.widget.is_hovering + property.states.is_hovering && frame.resize != ekg::dock::none && - !property.widget.is_active + !property.states.is_active ) { ekg::vec2_t margin { static_cast(frame.color_scheme.actions_margin_pixel_thickness), @@ -261,11 +261,11 @@ void ekg::ui::event( } if (input.was_released) { - if (property.widget.is_active) { - property.widget.is_absolute = false; + if (property.states.is_active) { + property.states.is_absolute = false; /*ekg::io::trigger( - property.widget.is_hovering, + property.states.is_hovering, ekg::action::release, frame.actions, this->properties @@ -274,7 +274,7 @@ void ekg::ui::event( frame.widget.target_dock_resize = ekg::dock::none; frame.widget.target_dock_drag = ekg::dock::none; - property.widget.is_active = false; + property.states.is_active = false; } break; @@ -311,18 +311,18 @@ void ekg::ui::buffering( ekg_draw_allocator_assert_scissor( property.widget.rect_scissor, rect, - ekg::query(property.parent_at).widget.rect, + ekg::query(property.parent_at).widget.rect_scissor, property.parent_at != ekg::at_t::not_found ); ekg::draw::rect( rect, - property.widget.is_focused ? frame.color_scheme.focused_background : frame.color_scheme.background, + property.states.is_focused ? frame.color_scheme.focused_background : frame.color_scheme.background, ekg::draw::mode::fill, ekg::at_t::not_found ); - if (property.widget.is_active) { + if (property.states.is_active) { ekg::draw::rect( rect, frame.color_scheme.highlight, @@ -331,7 +331,7 @@ void ekg::ui::buffering( ); } - if (property.widget.is_highlight) { + if (property.states.is_highlight) { ekg::draw::rect( rect, frame.color_scheme.highlight, @@ -342,12 +342,12 @@ void ekg::ui::buffering( ekg::draw::rect( rect, - property.widget.is_focused ? frame.color_scheme.focused_outline : frame.color_scheme.outline, + property.states.is_focused ? frame.color_scheme.focused_outline : frame.color_scheme.outline, ekg::draw::mode::outline, ekg::at_t::not_found ); - if (property.widget.is_warning) { + if (property.states.is_warning) { ekg::draw::rect( rect, frame.color_scheme.warning_outline, diff --git a/src/ui/label/widget.cpp b/src/ui/label/widget.cpp index d7791ce8..88ed144b 100644 --- a/src/ui/label/widget.cpp +++ b/src/ui/label/widget.cpp @@ -142,7 +142,7 @@ void ekg::ui::buffering( ekg_draw_allocator_assert_scissor( property.widget.rect_scissor, rect_abs, - ekg::query(property.parent_at).widget.rect, + ekg::query(property.parent_at).widget.rect_scissor, true ); diff --git a/src/ui/scrollbar/scrollbar.cpp b/src/ui/scrollbar/scrollbar.cpp new file mode 100644 index 00000000..38bc3fab --- /dev/null +++ b/src/ui/scrollbar/scrollbar.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/scrollbar/scrollbar.hpp" + +ekg::scrollbar_t ekg::scrollbar_t::not_found { + .at = ekg::at_t::not_found +}; diff --git a/src/ui/scrollbar/widget.cpp b/src/ui/scrollbar/widget.cpp new file mode 100644 index 00000000..08f39d31 --- /dev/null +++ b/src/ui/scrollbar/widget.cpp @@ -0,0 +1,855 @@ +/** + * 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/scrollbar/widget.hpp" +#include "ekg/draw/allocator.hpp" +#include "ekg/draw/shape/shape.hpp" +#include "ekg/draw/typography/font.hpp" +#include "ekg/core/runtime.hpp" +#include "ekg/core/pools.hpp" +#include "ekg/math/floating_point.hpp" +#include "ekg/core/context.hpp" +#include "ekg/ui/abstract.hpp" + +float ekg::ui::get_horizontal_scrollbar_normalized( + ekg::property_t &property, + ekg::rect_t &rect_parent +) { + return (property.scroll.position.x / rect_parent.w); +} + +float ekg::ui::get_vertical_scrollbar_normalized( + ekg::property_t &property, + ekg::rect_t &rect_parent +) { + return (property.scroll.position.y / rect_parent.h); +} + +void ekg::ui::check_scrollbar( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent +) { + property.scroll.is_enabled.x = scrollbar.rect.w > rect_parent.w; + property.scroll.is_enabled.y = scrollbar.rect.h > rect_parent.h; +} + +void ekg::ui::reset_scrollbar( + ekg::property_t &property +) { + property.scroll.position.x = property.scroll.position.z; + property.scroll.position.y = property.scroll.position.w; +} + +void ekg::ui::clamp_scrollbar( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent +) { + ekg::vec2_t h_limit {0.0f, rect_parent.w - scrollbar.rect.w}; + if (scrollbar.rect.w < rect_parent.w) { + property.scroll.position.x = h_limit.x; + property.scroll.position.z = h_limit.x; + } else if (property.scroll.position.x < h_limit.y) { + property.scroll.position.x = h_limit.y; + property.scroll.position.z = h_limit.y; + } else if (property.scroll.position.x > h_limit.x) { + property.scroll.position.x = h_limit.x; + property.scroll.position.z = h_limit.x; + } + + ekg::vec2_t v_limit {0.0f, rect_parent.h - scrollbar.rect.h}; + if (scrollbar.rect.h < rect_parent.h) { + property.scroll.position.y = v_limit.x; + property.scroll.position.w = v_limit.x; + } else if (property.scroll.position.y < v_limit.y) { + property.scroll.position.y = v_limit.y; + property.scroll.position.w = v_limit.y; + } else if (property.scroll.position.y > v_limit.x) { + property.scroll.position.y = v_limit.x; + property.scroll.position.w = v_limit.x; + } +} + +bool ekg::ui::is_scrollbar_scrolling( + ekg::property_t &property, + bool state +) { + if ( + !( + state = ( + state + || + ( + ( + static_cast(roundf(property.scroll.position.x)) + != + static_cast(roundf(property.scroll.position.z)) + ) + || + ( + static_cast(roundf(property.scroll.position.y)) + != + static_cast(roundf(property.scroll.position.w)) + ) + ) + ) + ) + ) { + property.scroll.position.x = property.scroll.position.z; + property.scroll.position.y = property.scroll.position.w; + } + + return state; +} + +void ekg::ui::reload( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent, + std::vector &children, + bool should_childnizate_metrics +) { + ekg::ui::check_scrollbar(property, scrollbar, rect_parent); + + if (!should_childnizate_metrics) { + return; + } + + scrollbar.rect = {}; + scrollbar.acceleration.x = ekg::draw::get_font_renderer(ekg::font::medium).get_text_height(); + + float place {}; + for (ekg::at_t &at : children) { + ekg_core_abstract_todo( + at.flags, + at, + + scrollbar.rect.w = ( + ekg::max(scrollbar.rect.w, descriptor.rect.x + descriptor.rect.w) + ); + + scrollbar.rect.h = ( + ekg::max(scrollbar.rect.h, descriptor.rect.y + descriptor.rect.h) + ); + + scrollbar.acceleration.y = ekg::min(scrollbar.acceleration.y, descriptor.rect.h); + ); + } + + scrollbar.acceleration.y += scrollbar.acceleration.x + (scrollbar.acceleration.x * 0.5f); + ekg::ui::check_scrollbar(property, scrollbar, rect_parent); + ekg::ui::clamp_scrollbar(property, scrollbar, rect_parent); +} + +void ekg::ui::reload( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar +) { + ekg::property_t &parent {ekg::query(property.parent_at)}; + if (parent == ekg::property_t::not_found) { + return; + } + + bool should_childnizate_metrics {true}; + ekg::ui::reload( + property, + scrollbar, + parent.widget.rect, + parent.children, + should_childnizate_metrics + ); + + property.nearest_scrollbar_at = scrollbar.at; + parent.scroll.is_enabled = property.scroll.is_enabled; + parent.scroll.is_scrolling = property.scroll.is_scrolling; + parent.scroll.nearest_scroll_bar_thickness = scrollbar.color_scheme.bar_thickness; +} + + +void ekg::ui::event( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + const ekg::io::stage &stage, + ekg::rect_t &rect_parent +) { + switch (stage) { + case ekg::io::stage::pre: { + ekg::input_info_t &input {ekg::p_core->handler_input.input}; + ekg::vec2_t interact {static_cast>(input.interact)}; + + if ( + input.was_pressed + || + input.was_released + || + input.has_motion + || + input.was_wheel + ) { + bool is_visible { + ekg::rect_collide_vec2(property.widget.rect_scissor, interact) + }; + + property.states.is_active = ( + is_visible + && + (property.scroll.is_enabled.x || property.scroll.is_enabled.y) + && + (ekg::fire("scrollbar-scroll") || ekg::fire("scrollbar-scroll-horizontal")) + ); + + ekg::rect_t h_bar {scrollbar.widget.rect_horizontal}; + h_bar.x += rect_parent.x; + + scrollbar.widget.states_horizontal_bar.is_hovering = ( + is_visible + && + ekg::rect_collide_vec2(h_bar, interact) + ); + + ekg::rect_t v_bar {scrollbar.widget.rect_vertical}; + v_bar.y += rect_parent.y; + + scrollbar.widget.states_vertical_bar.is_hovering = ( + is_visible + && + ekg::rect_precise_collide_vec2(v_bar, interact) + ); + + property.states.is_hovering = ( + property.states.is_active + || + scrollbar.widget.states_horizontal_bar.is_hovering + || + scrollbar.widget.states_vertical_bar.is_hovering + ); + } + + property.states.is_absolute = ( + (property.scroll.is_scrolling.x || property.scroll.is_scrolling.y) + || + property.states.is_active + ); + + break; + } + + case ekg::io::stage::post: { + property.states.is_hovering = false; + scrollbar.widget.states_horizontal_bar.is_hovering = false; + scrollbar.widget.states_vertical_bar.is_hovering = false; + break; + } + + case ekg::io::stage::process: { + ekg::ui::check_scrollbar( + property, + scrollbar, + rect_parent + ); + + ekg::input_info_t &input {ekg::p_core->handler_input.input}; + bool is_scroll_fired {ekg::fire("scrollbar-scroll")}; + + property.scroll.is_scrolling.x = false; + property.scroll.is_scrolling.y = false; + + #if defined(ANDROID) + if (property.states.is_hovering && property.states.is_enabled.x && is_scroll_fired) { + property.scroll.is_scrolling.x = true; + property.scroll.position.z = ekg::clamp( + property.scroll.position.x + (-input.interact.z * scrollbar.acceleration.y), + rect_parent.w - scrollbar.rect.w, + 0.0f + ); + } + + if (property.states.is_hovering && property.states.is_enabled.y && is_scroll_fired) { + property.scroll.is_scrolling.y = true; + property.scroll.position.w = ekg::clamp( + property.scroll.position.y + (input.interact.w * scrollbar.acceleration.y), + rect_parent.h - scrollbar.rect.h, + 0.0f + ); + } + #else + bool is_scroll_horizontal_fired { + ekg::fire("scrollbar-scroll-horizontal") + }; + + if ( + is_scroll_horizontal_fired + && + property.states.is_hovering + && + property.scroll.is_enabled.x + ) { + property.scroll.is_scrolling.x = true; + property.scroll.position.z = ekg::clamp( + property.scroll.position.x + (input.interact.w * scrollbar.acceleration.x), + rect_parent.w - scrollbar.rect.w, + 0.0f + ); + } + + if ( + is_scroll_fired + && + !is_scroll_horizontal_fired + && + property.states.is_hovering + && + property.scroll.is_enabled.y + ) { + property.scroll.is_scrolling.y = true; + property.scroll.position.w = ekg::clamp( + property.scroll.position.y + (input.interact.w * scrollbar.acceleration.y), + rect_parent.h - scrollbar.rect.h, + 0.0f + ); + } + #endif + + if (input.has_motion) { + ekg_set( + property.widget.should_buffering, + scrollbar.widget.states_horizontal_bar.is_highlight, + scrollbar.widget.states_horizontal_bar.is_hovering + ); + + ekg_set( + property.widget.should_buffering, + scrollbar.widget.states_vertical_bar.is_highlight, + scrollbar.widget.states_vertical_bar.is_hovering + ); + } + + if ( + property.states.is_hovering + && + input.was_pressed + && + ekg::fire("scrollbar-drag") + ) { + ekg::rect_t h_bar {scrollbar.widget.rect_horizontal}; + h_bar.x += rect_parent.x; + scrollbar.widget.rect_delta.x = input.interact.x - h_bar.x; + + ekg_set( + property.widget.should_buffering, + scrollbar.widget.states_horizontal_bar.is_active, + scrollbar.widget.states_horizontal_bar.is_hovering + ); + + ekg::rect_t v_bar {scrollbar.widget.rect_vertical}; + v_bar.y += rect_parent.y; + scrollbar.widget.rect_delta.y = input.interact.y - v_bar.y; + + ekg_set( + property.widget.should_buffering, + scrollbar.widget.states_vertical_bar.is_active, + scrollbar.widget.states_vertical_bar.is_hovering + ); + + ekg_action( + scrollbar.actions, + ekg::action::press, + ( + scrollbar.widget.states_horizontal_bar.is_active + || + scrollbar.widget.states_vertical_bar.is_active + ) + ); + } + + if ( + ( + scrollbar.widget.states_horizontal_bar.is_focused = ( + input.has_motion + && + scrollbar.widget.states_horizontal_bar.is_active + && + !is_scroll_fired + ) + ) + ) { + ekg::rect_t h_bar {scrollbar.widget.rect_horizontal}; + h_bar.x = (input.interact.x - scrollbar.widget.rect_delta.x) - rect_parent.x; + + property.scroll.position.z = ( + -ekg::clamp( + h_bar.x / (rect_parent.w - scrollbar.widget.rect_horizontal.w), + 0.0f, + 1.0f + ) + * + (scrollbar.rect.w - rect_parent.w) + ); + + property.scroll.is_scrolling.x = true; + property.scroll.position.x = property.scroll.position.z; + property.widget.should_buffering = true; + ekg::gui.ui.redraw = true; + } + + if ( + ( + scrollbar.widget.states_vertical_bar.is_focused = ( + scrollbar.widget.states_vertical_bar.is_active + && + input.has_motion + && + !is_scroll_fired + ) + ) + ) { + ekg::rect_t v_bar {scrollbar.widget.rect_vertical}; + v_bar.y = (input.interact.y - scrollbar.widget.rect_delta.y) - rect_parent.y; + + + property.scroll.position.w = ( + -ekg::clamp( + v_bar.y / (rect_parent.h - v_bar.h), + 0.0f, + 1.0f + ) + * + (scrollbar.rect.h - rect_parent.h) + ); + + property.scroll.is_scrolling.y = true; + property.scroll.position.y = property.scroll.position.w; + property.widget.should_buffering = true; + ekg::gui.ui.redraw = true; + } + + if (input.was_released) { + ekg_action( + scrollbar.actions, + ekg::action::release, + ( + input.was_pressed + && + ( + scrollbar.widget.states_horizontal_bar.is_active + || + scrollbar.widget.states_vertical_bar.is_active + ) + ) + ); + + ekg_set( + property.widget.should_buffering, + scrollbar.widget.states_horizontal_bar.is_active, + (property.scroll.is_scrolling.x = false) + ); + + ekg_set( + property.widget.should_buffering, + scrollbar.widget.states_vertical_bar.is_active, + (property.scroll.is_scrolling.y = false) + ); + } + + if (scrollbar.widget.states_horizontal_bar.is_hovering || scrollbar.widget.states_vertical_bar.is_hovering) { + ekg::p_core->p_platform_base->system_cursor = ekg::system_cursor::arrow; + } + + ekg_action( + scrollbar.actions, + ekg::action::drag, + ( + input.has_motion + && + ( + scrollbar.widget.states_horizontal_bar.is_focused + || + scrollbar.widget.states_vertical_bar.is_focused + ) + && + (ekg::timing_t::second > ekg::gui.ui.frequency) + ) + ); + + ekg_action( + scrollbar.actions, + ekg::action::hover, + ( + input.has_motion + && + ( + scrollbar.widget.states_horizontal_bar.is_hovering + || + scrollbar.widget.states_vertical_bar.is_hovering + ) + && + (ekg::timing_t::second > ekg::gui.ui.frequency) + ) + ); + + if (property.scroll.is_scrolling.x || property.scroll.is_scrolling.y) { + ekg::gui.ui.redraw = true; + property.widget.should_buffering = true; + } + + break; + } + } +} + +void ekg::ui::event( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + const ekg::io::stage &stage +) { + ekg::property_t &parent {ekg::query(property.parent_at)}; + if (parent == ekg::property_t::not_found) { + return; + } + + ekg::ui::event( + property, + scrollbar, + stage, + parent.widget.rect + ); + + if ( + ( + property.scroll.is_scrolling.x + || + property.scroll.is_scrolling.y + || + property.states.is_hovering + ) + && + !property.widget.is_high_frequency + ) { + ekg::io::dispatch( + ekg::io::operation::high_frequency, + property.at + ); + } + + parent.scroll.is_enabled = property.scroll.is_enabled; + parent.scroll.is_scrolling = property.scroll.is_scrolling; + parent.scroll.nearest_scroll_bar_thickness = scrollbar.color_scheme.bar_thickness; + parent.scroll.position = property.scroll.position; +} + +void ekg::ui::high_frequency( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent +) { + float speed { + ekg::p_core->handler_input.input.scroll_speed + }; + + property.scroll.position.x = ekg::lerp( + property.scroll.position.x, + property.scroll.position.z, + ekg::gui.ui.dt * scrollbar.acceleration.x * speed + ); + + property.scroll.position.y = ekg::lerp( + property.scroll.position.y, + property.scroll.position.w, + ekg::gui.ui.dt * scrollbar.acceleration.y * speed + ); + + ekg::ui::clamp_scrollbar( + property, + scrollbar, + rect_parent + ); + + if ( + ( + property.widget.is_high_frequency = ekg::ui::is_scrollbar_scrolling( + property, + property.states.is_hovering || property.states.is_active + ) + ) + ) { + property.widget.should_buffering = true; + ekg::gui.ui.redraw = true; + } +} + +void ekg::ui::high_frequency( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar +) { + ekg::property_t &parent {ekg::query(property.parent_at)}; + if (parent == ekg::property_t::not_found) { + property.widget.is_high_frequency = false; + return; + } + + ekg::ui::high_frequency( + property, + scrollbar, + parent.widget.rect + ); +} + +void ekg::ui::pass( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar +) { + ekg_draw_allocator_bind_local( + &property.widget.geometry_buffer, + &property.widget.gpu_data_buffer + ); + + if (property.widget.should_buffering) { + return; + } + + ekg_draw_allocator_pass(); +} + +void ekg::ui::buffering( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar, + ekg::rect_t &rect_parent +) { + ekg_draw_allocator_assert_scissor( + property.widget.rect_scissor, + rect_parent, + rect_parent, + true + ); + + scrollbar.widget.rect_horizontal.w = 0.0f; + scrollbar.widget.rect_vertical.h = 0.0f; + + property.scroll.is_enabled.x = scrollbar.rect.w > rect_parent.w; + property.scroll.is_enabled.y = scrollbar.rect.h > rect_parent.h; + + if ( + !property.scroll.is_enabled.x + && + !property.scroll.is_enabled.y + ) { + ekg_draw_allocator_pass(); + } + + ekg::rect_t bar {}; + + /* start of horizontal bar */ + + scrollbar.widget.rect_horizontal.h = static_cast( + scrollbar.color_scheme.bar_thickness * property.scroll.is_enabled.x + ); + + scrollbar.widget.rect_horizontal.y = ( + rect_parent.y + + + rect_parent.h + - + scrollbar.widget.rect_horizontal.h + ); + + float out_of_parent_rect_width {scrollbar.rect.w - rect_parent.w}; + float x_factor {abs(property.scroll.position.x) / out_of_parent_rect_width}; + + scrollbar.widget.rect_horizontal.w = ( + rect_parent.w + - + ( + static_cast(out_of_parent_rect_width) < 0 + ? + rect_parent.w + : + ekg::clamp_max( + out_of_parent_rect_width, + rect_parent.w - static_cast(scrollbar.color_scheme.bar_size_limit) + ) + ) + ); + + scrollbar.widget.rect_horizontal.x = x_factor * (rect_parent.w - scrollbar.widget.rect_horizontal.w); + + ekg::draw::rect( + rect_parent.x, + scrollbar.widget.rect_horizontal.y, + rect_parent.w, + scrollbar.widget.rect_horizontal.h, + scrollbar.color_scheme.background, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::bg] + ); + + ekg::draw::rect( + rect_parent.x, + scrollbar.widget.rect_horizontal.y, + rect_parent.w, + scrollbar.widget.rect_horizontal.h, + scrollbar.color_scheme.outline, + ekg::draw::mode::outline, + scrollbar.layers[ekg::layer::outline] + ); + + bar.x = rect_parent.x + scrollbar.widget.rect_horizontal.x; + bar.y = scrollbar.widget.rect_horizontal.y; + bar.w = scrollbar.widget.rect_horizontal.w; + bar.h = scrollbar.widget.rect_horizontal.h; + + ekg::draw::rect( + bar, + scrollbar.color_scheme.bar_background, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::bg] + ); + + if (scrollbar.widget.states_horizontal_bar.is_highlight) { + ekg::draw::rect( + bar, + scrollbar.color_scheme.bar_highlight, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::highlight_bg] + ); + } + + if (scrollbar.widget.states_horizontal_bar.is_active) { + ekg::draw::rect( + bar, + scrollbar.color_scheme.bar_active, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::active_bg] + ); + } + + /* start of vertical bar draw */ + + scrollbar.widget.rect_vertical.w = static_cast( + scrollbar.color_scheme.bar_thickness * property.scroll.is_enabled.y + ); + + scrollbar.widget.rect_vertical.x = ( + rect_parent.x + + + rect_parent.w + - + scrollbar.widget.rect_vertical.w + ); + + float out_of_parent_rect_height {scrollbar.rect.h - rect_parent.h}; + float y_factor {abs(property.scroll.position.y) / out_of_parent_rect_height}; + + scrollbar.widget.rect_vertical.h = ( + rect_parent.h + - + ( + static_cast(out_of_parent_rect_height) < 0 + ? + rect_parent.h + : + ekg::clamp_max( + out_of_parent_rect_height, + rect_parent.h - static_cast(scrollbar.color_scheme.bar_size_limit) + ) + ) + ); + + scrollbar.widget.rect_vertical.y = y_factor * (rect_parent.h - scrollbar.widget.rect_vertical.h); + + ekg::draw::rect( + scrollbar.widget.rect_vertical.x, + rect_parent.y, + scrollbar.widget.rect_vertical.w, + rect_parent.h, + scrollbar.color_scheme.background, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::bg] + ); + + ekg::draw::rect( + scrollbar.widget.rect_vertical.x, + rect_parent.y, + scrollbar.widget.rect_vertical.w, + rect_parent.h, + scrollbar.color_scheme.outline, + ekg::draw::mode::outline, + scrollbar.layers[ekg::layer::outline] + ); + + bar.x = scrollbar.widget.rect_vertical.x; + bar.y = rect_parent.y + scrollbar.widget.rect_vertical.y; + bar.w = scrollbar.widget.rect_vertical.w; + bar.h = scrollbar.widget.rect_vertical.h; + + ekg::draw::rect( + bar, + scrollbar.color_scheme.bar_background, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::bg] + ); + + if (scrollbar.widget.states_vertical_bar.is_highlight) { + ekg::draw::rect( + bar, + scrollbar.color_scheme.bar_highlight, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::highlight_bg] + ); + } + + if (scrollbar.widget.states_vertical_bar.is_active) { + ekg::draw::rect( + bar, + scrollbar.color_scheme.bar_active, + ekg::draw::mode::fill, + scrollbar.layers[ekg::layer::active_bg] + ); + } + + ekg::draw::rect( + bar, + scrollbar.color_scheme.bar_outline, + ekg::draw::mode::outline, + scrollbar.layers[ekg::layer::outline] + ); + + ekg_draw_allocator_pass(); +} + +void ekg::ui::buffering( + ekg::property_t &property, + ekg::scrollbar_t &scrollbar +) { + ekg::ui::buffering( + property, + scrollbar, + ekg::query(property.parent_at).widget.rect_scissor + ); +} + +void ekg::ui::unmap( + ekg::scrollbar_t &scrollbar +) { + +}