diff --git a/cmake/dependencies/interval_tree.cmake b/cmake/dependencies/interval_tree.cmake index 36a9d184..343dd8b9 100644 --- a/cmake/dependencies/interval_tree.cmake +++ b/cmake/dependencies/interval_tree.cmake @@ -1,13 +1,13 @@ option(NUI_FETCH_INTERVAL_TREE "Fetch interval tree" ON) set(NUI_INTERVAL_TREE_GIT_REPOSITORY "https://github.com/5cript/interval-tree.git" CACHE STRING "interval tree git repository") -set(NUI_INTERVAL_TREE_GIT_TAG "309b9c725191d4bb1d134f28a8a32ad2f68a8ffa" CACHE STRING "interval tree git tag") +set(NUI_INTERVAL_TREE_GIT_TAG "v2.2.4" CACHE STRING "interval tree git tag") if(NUI_FETCH_INTERVAL_TREE) include(FetchContent) FetchContent_Declare( interval-tree GIT_REPOSITORY ${NUI_INTERVAL_TREE_GIT_REPOSITORY} - GIT_TAG ${NUI_INTERVAL_TREE_GIT_TAG} + GIT_TAG ${NUI_INTERVAL_TREE_GIT_TAG} ) FetchContent_MakeAvailable(interval-tree) diff --git a/nui/include/nui/backend/filesystem/special_paths.hpp b/nui/include/nui/backend/filesystem/special_paths.hpp index 7ea7f08a..459a48c9 100644 --- a/nui/include/nui/backend/filesystem/special_paths.hpp +++ b/nui/include/nui/backend/filesystem/special_paths.hpp @@ -13,6 +13,15 @@ namespace Nui * %appdata% Linux: home. Windows: CSIDL_APPDATA * %localappdata% Linux: home. Windows: CSIDL_APPDATA_LOCAL * %temp% Linux: /tmp. Windows: ${USER}\AppData\Local\Temp + * %config_home% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_APPDATA + * %config_home2% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_MYDOCUMENTS + * %config_home3% Linux: $XDG_CONFIG_HOME. Windows: home + * %state_home% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_APPDATA + * %state_home2% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_MYDOCUMENTS + * %state_home3% Linux: $XDG_CONFIG_HOME. Windows: home + * %data_home% Linux: $XDG_DATA_HOME. Windows: CSIDL_APPDATA + * %data_home2% Linux: $XDG_DATA_HOME. Windows: CSIDL_MYDOCUMENTS + * %data_home3% Linux: $XDG_DATA_HOME. Windows: home * * * @param path diff --git a/nui/include/nui/event_system/event_context.hpp b/nui/include/nui/event_system/event_context.hpp index 8275c0d7..04895a3e 100644 --- a/nui/include/nui/event_system/event_context.hpp +++ b/nui/include/nui/event_system/event_context.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include @@ -38,6 +38,7 @@ namespace Nui { public: using EventIdType = EventRegistry::EventIdType; + constexpr static auto invalidEventId = EventRegistry::invalidEventId; EventContext() : impl_{std::make_shared()} @@ -56,6 +57,10 @@ namespace Nui { return impl_->eventRegistry().activateEvent(id); } + auto activateAfterEffect(EventIdType id) + { + return impl_->eventRegistry().activateAfterEffect(id); + } void executeActiveEventsImmediately() { impl_->eventRegistry().executeActiveEvents(); @@ -72,6 +77,10 @@ namespace Nui { impl_->eventRegistry().cleanInvalidEvents(); } + void removeAfterEffect(EventIdType id) + { + impl_->eventRegistry().removeAfterEffect(id); + } void reset() { impl_->eventRegistry().clear(); diff --git a/nui/include/nui/event_system/event_registry.hpp b/nui/include/nui/event_system/event_registry.hpp index 45430fcd..2e36752f 100644 --- a/nui/include/nui/event_system/event_registry.hpp +++ b/nui/include/nui/event_system/event_registry.hpp @@ -42,6 +42,24 @@ namespace Nui return registry_.select(id); } + /** + * @brief Activate an after effect. + * + * @param id + * @return RegistryType::SelectionResult + */ + RegistryType::SelectionResult activateAfterEffect(EventIdType id) + { + return afterEffects_.select(id); + } + + /** + * @brief After effects are used to cause something to happen after all other events have been processed. + * After effects are executed in indeterminate order. + * + * @param event + * @return EventIdType + */ EventIdType registerAfterEffect(Event event) { return afterEffects_.append(std::move(event)); @@ -85,6 +103,11 @@ namespace Nui registry_.erase(id); } + void removeAfterEffect(EventIdType id) + { + afterEffects_.erase(id); + } + void clear() { registry_.clear(); diff --git a/nui/include/nui/event_system/observed_value.hpp b/nui/include/nui/event_system/observed_value.hpp index 19063d39..faa1244e 100644 --- a/nui/include/nui/event_system/observed_value.hpp +++ b/nui/include/nui/event_system/observed_value.hpp @@ -2,10 +2,11 @@ #include #include -#include -#include +#include +#include #include #include +#include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include namespace Nui { @@ -298,6 +300,12 @@ namespace Nui return ModificationProxy{*this, true}; } + explicit operator bool() const + requires std::convertible_to + { + return static_cast(contained_); + } + ContainedT& value() { return contained_; @@ -347,55 +355,99 @@ namespace Nui ReferenceWrapper(ObservedContainer* owner, std::size_t pos, T& ref) : owner_{owner} , pos_{pos} - , ref_{ref} + , ref_{&ref} {} + ReferenceWrapper(ReferenceWrapper const&) = default; + ReferenceWrapper(ReferenceWrapper&& other) noexcept + : owner_{other.owner_} + , pos_{other.pos_} + , ref_{other.ref_} + { + other.owner_ = nullptr; + other.pos_ = 0; + other.ref_ = nullptr; + } + ReferenceWrapper& operator=(ReferenceWrapper const&) = default; + ReferenceWrapper& operator=(ReferenceWrapper&& other) noexcept + { + if (this != &other) + { + owner_ = other.owner_; + pos_ = other.pos_; + ref_ = other.ref_; + other.owner_ = nullptr; + other.pos_ = 0; + other.ref_ = nullptr; + } + return *this; + } operator T&() { - return ref_; + return *ref_; } T& operator*() { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return ref_; + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); + return *ref_; } T const& operator*() const { - return ref_; + return *ref_; } T* operator->() { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return &ref_; + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); + return ref_; } T const* operator->() const { - return &ref_; + return ref_; } T& get() { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return ref_; + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); + return *ref_; } T const& getReadonly() { - return ref_; + return *ref_; } void operator=(T&& val) { - ref_ = std::move(val); - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + *ref_ = std::move(val); + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); } void operator=(T const& val) { - ref_ = val; - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + *ref_ = val; + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); } protected: ObservedContainer* owner_; std::size_t pos_; - T& ref_; + T* ref_; }; + + template + auto& unwrapReferenceWrapper(ReferenceWrapper& wrapper) + { + return wrapper.get(); + } + template + auto const& unwrapReferenceWrapper(ReferenceWrapper const& wrapper) + { + return wrapper.get(); + } + auto& unwrapReferenceWrapper(auto& ref) + { + return ref; + } + auto const& unwrapReferenceWrapper(auto const& ref) + { + return ref; + } + template class PointerWrapper { @@ -411,7 +463,7 @@ namespace Nui } T& operator*() { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); return *ptr_; } T const& operator*() const @@ -420,7 +472,7 @@ namespace Nui } T* operator->() { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); return ptr_; } T const* operator->() const @@ -429,7 +481,7 @@ namespace Nui } T& get() { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); return *ptr_; } T const& getReadonly() @@ -439,7 +491,7 @@ namespace Nui void operator=(T* ptr) { ptr_ = ptr; - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + owner_->insertRangeChecked(pos_, pos_, RangeOperationType::Modify); } protected: @@ -463,6 +515,10 @@ namespace Nui : owner_{owner} , it_{std::move(it)} {} + IteratorWrapper(IteratorWrapper const&) = default; + IteratorWrapper(IteratorWrapper&&) = default; + IteratorWrapper& operator=(IteratorWrapper const&) = default; + IteratorWrapper& operator=(IteratorWrapper&&) = default; IteratorWrapper& operator+=(difference_type n) { it_ += n; @@ -507,7 +563,10 @@ namespace Nui { if constexpr (std::is_same_v) return ReferenceWrapper{ - owner_, static_cast(it_ - owner_->contained_.rbegin()), *it_}; + owner_, + owner_->contained_.size() - static_cast(1) - + static_cast(it_ - owner_->contained_.rbegin()), + *it_}; else return ReferenceWrapper{ owner_, static_cast(it_ - owner_->contained_.begin()), *it_}; @@ -520,7 +579,10 @@ namespace Nui { if constexpr (std::is_same_v) return PointerWrapper{ - owner_, static_cast(it_ - owner_->contained_.rbegin()), &*it_}; + owner_, + owner_->contained_.size() - static_cast(1) - + static_cast(it_ - owner_->contained_.rbegin()), + &*it_}; else return PointerWrapper{ owner_, static_cast(it_ - owner_->contained_.begin()), &*it_}; @@ -587,51 +649,50 @@ namespace Nui using const_reverse_iterator = typename ContainerT::const_reverse_iterator; using ModifiableObserved::contained_; - using ModifiableObserved::update; public: explicit ObservedContainer(CustomEventContextFlag_t, EventContext* ctx) : ModifiableObserved{CustomEventContextFlag, ctx} - , rangeContext_{0} + , rangeContext_{std::make_shared()} , afterEffectId_{registerAfterEffect()} {} explicit ObservedContainer() : ModifiableObserved{} - , rangeContext_{0} + , rangeContext_{std::make_shared()} , afterEffectId_{registerAfterEffect()} {} template explicit ObservedContainer(CustomEventContextFlag_t, EventContext* ctx, T&& t) : ModifiableObserved{CustomEventContextFlag, ctx, std::forward(t)} - , rangeContext_{static_cast(contained_.size())} + , rangeContext_{std::make_shared()} , afterEffectId_{registerAfterEffect()} {} template explicit ObservedContainer(T&& t) : ModifiableObserved{std::forward(t)} - , rangeContext_{static_cast(contained_.size())} + , rangeContext_{std::make_shared()} , afterEffectId_{registerAfterEffect()} {} explicit ObservedContainer(RangeEventContext&& rangeContext) : ModifiableObserved{} - , rangeContext_{std::move(rangeContext)} + , rangeContext_{std::make_shared(std::move(rangeContext))} , afterEffectId_{registerAfterEffect()} {} explicit ObservedContainer(CustomEventContextFlag_t, EventContext* ctx, RangeEventContext&& rangeContext) : ModifiableObserved{CustomEventContextFlag, ctx} - , rangeContext_{std::move(rangeContext)} + , rangeContext_{std::make_shared(std::move(rangeContext))} , afterEffectId_{registerAfterEffect()} {} template ObservedContainer(T&& t, RangeEventContext&& rangeContext) : ModifiableObserved{std::forward(t)} - , rangeContext_{std::move(rangeContext)} + , rangeContext_{std::make_shared(std::move(rangeContext))} , afterEffectId_{registerAfterEffect()} {} template ObservedContainer(CustomEventContextFlag_t, EventContext* ctx, T&& t, RangeEventContext&& rangeContext) : ModifiableObserved{CustomEventContextFlag, ctx, std::forward(t)} - , rangeContext_{std::move(rangeContext)} + , rangeContext_{std::make_shared(std::move(rangeContext))} , afterEffectId_{registerAfterEffect()} {} @@ -639,7 +700,11 @@ namespace Nui ObservedContainer(ObservedContainer&&) = default; ObservedContainer& operator=(const ObservedContainer&) = delete; ObservedContainer& operator=(ObservedContainer&&) = default; - ~ObservedContainer() = default; + ~ObservedContainer() + { + if (!moveDetector_.wasMoved()) + ObservedBase::eventContext_->removeAfterEffect(afterEffectId_); + } constexpr auto map(auto&& function) const; constexpr auto map(auto&& function); @@ -648,27 +713,27 @@ namespace Nui ObservedContainer& operator=(T&& t) { contained_ = std::forward(t); - rangeContext_.reset(static_cast(contained_.size()), true); + rangeContext_->reset(true); update(); return *this; } void assign(size_type count, const value_type& value) { contained_.assign(count, value); - rangeContext_.reset(static_cast(contained_.size()), true); + rangeContext_->reset(true); update(); } template void assign(Iterator first, Iterator last) { contained_.assign(first, last); - rangeContext_.reset(static_cast(contained_.size()), true); + rangeContext_->reset(true); update(); } void assign(std::initializer_list ilist) { contained_.assign(ilist); - rangeContext_.reset(static_cast(contained_.size()), true); + rangeContext_->reset(true); update(); } @@ -799,7 +864,7 @@ namespace Nui void clear() { contained_.clear(); - rangeContext_.reset(0, true); + rangeContext_->reset(true); update(); } template @@ -811,7 +876,7 @@ namespace Nui NUI_ASSERT(ObservedBase::eventContext_ != nullptr, "Event context must never be null."); const auto result = contained_.insert(value); - rangeContext_.performFullRangeUpdate(); + rangeContext_->performFullRangeUpdate(); update(); ObservedBase::eventContext_->executeActiveEventsImmediately(); return result; @@ -825,7 +890,7 @@ namespace Nui NUI_ASSERT(ObservedBase::eventContext_ != nullptr, "Event context must never be null."); const auto result = contained_.insert(std::move(value)); - rangeContext_.performFullRangeUpdate(); + rangeContext_->performFullRangeUpdate(); update(); ObservedBase::eventContext_->executeActiveEventsImmediately(); return result; @@ -838,7 +903,7 @@ namespace Nui { const auto distance = pos - cbegin(); auto it = contained_.insert(pos, value); - insertRangeChecked(distance, distance, RangeStateType::Insert); + insertRangeChecked(distance, distance, RangeOperationType::Insert); return iterator{this, it}; } iterator insert(iterator pos, value_type&& value) @@ -849,7 +914,7 @@ namespace Nui { const auto distance = pos - cbegin(); auto it = contained_.insert(pos, std::move(value)); - insertRangeChecked(distance, distance, RangeStateType::Insert); + insertRangeChecked(distance, distance, RangeOperationType::Insert); return iterator{this, it}; } iterator insert(iterator pos, size_type count, const value_type& value) @@ -860,7 +925,7 @@ namespace Nui { const auto distance = pos - cbegin(); auto it = contained_.insert(pos, count, value); - insertRangeChecked(distance, distance + count, RangeStateType::Insert); + insertRangeChecked(distance, distance + count - 1, RangeOperationType::Insert); return iterator{this, it}; } template @@ -873,7 +938,7 @@ namespace Nui { const auto distance = pos - cbegin(); auto it = contained_.insert(pos, first, last); - insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Insert); + insertRangeChecked(distance, distance + std::distance(first, last) - 1, RangeOperationType::Insert); return iterator{this, it}; } iterator insert(iterator pos, std::initializer_list ilist) @@ -884,7 +949,7 @@ namespace Nui { const auto distance = pos - cbegin(); auto it = contained_.insert(pos, ilist); - insertRangeChecked(distance, distance + ilist.size(), RangeStateType::Insert); + insertRangeChecked(distance, distance + ilist.size() - 1, RangeOperationType::Insert); return iterator{this, it}; } template @@ -892,36 +957,41 @@ namespace Nui { const auto distance = pos - cbegin(); auto it = contained_.emplace(pos, std::forward(args)...); - insertRangeChecked(distance, distance + sizeof...(Args), RangeStateType::Insert); + insertRangeChecked(distance, distance, RangeOperationType::Insert); return iterator{this, it}; } iterator erase(iterator pos) { - // TODO: move item to delete to end and then pop back? or is vector erase clever enough? const auto distance = pos - begin(); + eraseNotify(distance, distance); auto it = contained_.erase(pos.getWrapped()); - insertRangeChecked(distance, distance, RangeStateType::Erase); + insertRangeChecked(distance, distance, RangeOperationType::Erase); return iterator{this, it}; } iterator erase(const_iterator pos) { const auto distance = pos - cbegin(); + eraseNotify(distance, distance); auto it = contained_.erase(pos); - insertRangeChecked(distance, distance, RangeStateType::Erase); + insertRangeChecked(distance, distance, RangeOperationType::Erase); return iterator{this, it}; } iterator erase(iterator first, iterator last) { const auto distance = first - begin(); + const auto distance2 = std::distance(first, last); + eraseNotify(distance, distance + distance2 - 1); auto it = contained_.erase(first.getWrapped(), last.getWrapped()); - insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Erase); + insertRangeChecked(distance, distance + distance2 - 1, RangeOperationType::Erase); return iterator{this, it}; } iterator erase(const_iterator first, const_iterator last) { const auto distance = first - cbegin(); + const auto distance2 = std::distance(first, last); + eraseNotify(distance, distance + distance2 - 1); auto it = contained_.erase(first, last); - insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Erase); + insertRangeChecked(distance, distance + distance2 - 1, RangeOperationType::Erase); return iterator{this, it}; } template @@ -929,62 +999,76 @@ namespace Nui push_back(const value_type& value) { contained_.push_back(value); - insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); + insertRangeChecked(size() - 1, size() - 1, RangeOperationType::Insert); } template Detail::PickFirst_t().push_back(std::declval()))> push_back(value_type&& value) { contained_.push_back(std::move(value)); - insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); + insertRangeChecked(size() - 1, size() - 1, RangeOperationType::Insert); } template Detail::PickFirst_t().push_front(std::declval()))> push_front(const value_type& value) { contained_.push_front(value); - insertRangeChecked(0, 0, RangeStateType::Insert); + insertRangeChecked(0, 0, RangeOperationType::Insert); } template Detail::PickFirst_t().push_front(std::declval()))> push_front(value_type&& value) { contained_.push_front(std::move(value)); - insertRangeChecked(0, 0, RangeStateType::Insert); + insertRangeChecked(0, 0, RangeOperationType::Insert); } template void emplace_back(Args&&... args) { contained_.emplace_back(std::forward(args)...); - insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); + insertRangeChecked(size() - 1, size() - 1, RangeOperationType::Insert); } template Detail::PickFirst_t().emplace_front())> emplace_front(Args&&... args) { contained_.emplace_front(std::forward(args)...); - insertRangeChecked(0, 0, RangeStateType::Insert); + insertRangeChecked(0, 0, RangeOperationType::Insert); } void pop_back() { + if (contained_.empty()) + return; + eraseNotify(size() - 1, size() - 1); contained_.pop_back(); - insertRangeChecked(size(), size(), RangeStateType::Erase); + insertRangeChecked(size(), size(), RangeOperationType::Erase); } template Detail::PickFirst_t().pop_front())> pop_front() { + if (contained_.empty()) + return; + eraseNotify(0, 0); contained_.pop_front(); - insertRangeChecked(0, 0, RangeStateType::Erase); + insertRangeChecked(0, 0, RangeOperationType::Erase); } template Detail::PickFirst_t().resize(std::declval()))> resize(size_type count) { const auto sizeBefore = contained_.size(); + if (sizeBefore < count && sizeBefore != 0) + { + eraseNotify(sizeBefore, count - 1); + } contained_.resize(count); if (sizeBefore < count) - insertRangeChecked(sizeBefore, count, RangeStateType::Insert); - else - insertRangeChecked(count, sizeBefore, RangeStateType::Erase); + { + insertRangeChecked(sizeBefore, count - 1, RangeOperationType::Insert); + } + else if (sizeBefore != 0) + { + insertRangeChecked(count, sizeBefore - 1, RangeOperationType::Erase); + } } template Detail::PickFirst_t< @@ -993,16 +1077,21 @@ namespace Nui resize(size_type count, value_type const& fillValue) { const auto sizeBefore = contained_.size(); + eraseNotify(sizeBefore, count - 1); contained_.resize(count, fillValue); if (sizeBefore < count) - insertRangeChecked(sizeBefore, count, RangeStateType::Insert); - else - insertRangeChecked(count, sizeBefore, RangeStateType::Erase); + { + insertRangeChecked(sizeBefore, count - 1, RangeOperationType::Insert); + } + else if (sizeBefore != 0) + { + insertRangeChecked(count, sizeBefore - 1, RangeOperationType::Erase); + } } void swap(ContainerT& other) { contained_.swap(other); - rangeContext_.reset(contained_.size(), true); + rangeContext_->reset(true); update(); } @@ -1017,48 +1106,62 @@ namespace Nui } RangeEventContext& rangeContext() { - return rangeContext_; + return *rangeContext_; } RangeEventContext const& rangeContext() const { - return rangeContext_; + return *rangeContext_; } protected: void update(bool force = false) const override { if (force) - rangeContext_.reset(static_cast(contained_.size()), true); + rangeContext_->reset(true); + ObservedBase::eventContext_->activateAfterEffect(afterEffectId_); ObservedBase::update(force); } protected: - void insertRangeChecked(std::size_t low, std::size_t high, RangeStateType type) + void insertRangeChecked(std::size_t low, std::size_t high, RangeOperationType type) { std::function doInsert; doInsert = [&](int retries) { NUI_ASSERT(ObservedBase::eventContext_ != nullptr, "Event context must never be null."); - const auto result = rangeContext_.insertModificationRange(contained_.size(), low, high, type); - if (result == RangeEventContext::InsertResult::Final) + const auto result = rangeContext_->insertModificationRange(low, high, type); + if (result == RangeEventContext::InsertResult::Perform) { update(); ObservedBase::eventContext_->executeActiveEventsImmediately(); } - else if (result == RangeEventContext::InsertResult::Retry) + else if (result == RangeEventContext::InsertResult::PerformAndRetry) { + update(); + ObservedBase::eventContext_->executeActiveEventsImmediately(); + if (retries < 3) doInsert(retries + 1); else { - rangeContext_.reset(static_cast(contained_.size()), true); + rangeContext_->reset(true); update(); ObservedBase::eventContext_->executeActiveEventsImmediately(); return; } } + else if (result == RangeEventContext::InsertResult::Accepted) + { + update(); + } else + { + // Rejected! (why?) + rangeContext_->reset(true); update(); + ObservedBase::eventContext_->executeActiveEventsImmediately(); + return; + } }; doInsert(0); @@ -1067,14 +1170,30 @@ namespace Nui auto registerAfterEffect() { NUI_ASSERT(ObservedBase::eventContext_ != nullptr, "Event context must never be null."); - return ObservedBase::eventContext_->registerAfterEffect(Event{[this](EventContext::EventIdType) { - rangeContext_.reset(static_cast(contained_.size()), true); - return true; - }}); + return ObservedBase::eventContext_->registerAfterEffect( + Event{[weak = std::weak_ptr{rangeContext_}](EventContext::EventIdType) { + if (auto shared = weak.lock(); shared) + { + shared->reset(); + return true; + } + return false; + }}); } - private: - mutable RangeEventContext rangeContext_; + void eraseNotify(std::size_t index, std::size_t high) + { + const bool fixupPerformed = rangeContext_->eraseNotify(index, high); + if (fixupPerformed) // FORCE update: + { + update(); + ObservedBase::eventContext_->executeActiveEventsImmediately(); + } + } + + protected: + MoveDetector moveDetector_; + mutable std::shared_ptr rangeContext_; mutable EventContext::EventIdType afterEffectId_; }; @@ -1159,9 +1278,13 @@ namespace Nui Observed>& erase(std::size_t index = 0, std::size_t count = std::string::npos) { + if (count == std::size_t{0}) + return *this; const auto sizeBefore = this->contained_.size(); + const auto high = count == std::string::npos ? sizeBefore - 1 : count - 1; + this->eraseNotify(index, high); this->contained_.erase(index, count); - this->insertRangeChecked(index, sizeBefore, RangeStateType::Erase); + this->insertRangeChecked(index, high, RangeOperationType::Erase); return *this; } }; @@ -1176,13 +1299,11 @@ namespace Nui public: Observed() - : ObservedContainer>{RangeEventContext{0, true}} + : ObservedContainer>{RangeEventContext{true}} {} template > explicit Observed(T&& t) - : ObservedContainer>{ - std::forward(t), - RangeEventContext{static_cast(t.size()), true}} + : ObservedContainer>{std::forward(t), RangeEventContext{true}} {} Observed>& operator=(std::set const& contained) @@ -1207,13 +1328,11 @@ namespace Nui public: Observed() - : ObservedContainer>{RangeEventContext{0, true}} + : ObservedContainer>{RangeEventContext{true}} {} template > explicit Observed(T&& t) - : ObservedContainer>{ - std::forward(t), - RangeEventContext{static_cast(t.size()), true}} + : ObservedContainer>{std::forward(t), RangeEventContext{true}} {} Observed>& operator=(std::list const& contained) @@ -1259,6 +1378,37 @@ namespace Nui { static constexpr bool value = true; }; + + template + struct IsWeakObserved + { + static constexpr bool value = false; + }; + + template + struct IsWeakObserved>> + { + static constexpr bool value = true; + }; + + template + struct IsSharedObserved + { + static constexpr bool value = false; + }; + + template + struct IsSharedObserved>> + { + static constexpr bool value = true; + }; + + template + struct IsObservedLike + { + static constexpr bool value = + IsObserved::value || IsWeakObserved::value || IsSharedObserved::value; + }; } template @@ -1299,6 +1449,12 @@ namespace Nui template concept IsObserved = Detail::IsObserved>::value; + template + concept IsSharedObserved = Detail::IsSharedObserved>::value; + template + concept IsWeakObserved = Detail::IsWeakObserved>::value; + template + concept IsObservedLike = Detail::IsObservedLike>::value; namespace Detail { @@ -1328,5 +1484,101 @@ namespace Nui private: Observed const* observed_; }; + + template + struct ObservedAddReference + { + using type = T const&; + }; + template <> + struct ObservedAddReference + { + using type = void; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + + template + struct ObservedAddMutableReference + { + using type = T&; + using raw = T; + }; + template <> + struct ObservedAddMutableReference + { + using type = void; + using raw = void; + }; + template + struct ObservedAddMutableReference>> + { + using type = std::weak_ptr>; + using raw = Observed; + }; + template + struct ObservedAddMutableReference>> + { + using type = std::weak_ptr>; + using raw = Observed; + }; + + template + using ObservedAddReference_t = typename ObservedAddReference>::type; + template + using ObservedAddMutableReference_t = typename ObservedAddMutableReference>::type; + template + using ObservedAddMutableReference_raw = typename ObservedAddMutableReference>::raw; } + + template + struct UnpackObserved + { + using type = T; + }; + template + struct UnpackObserved> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + using UnpackObserved_t = typename UnpackObserved::type; } \ No newline at end of file diff --git a/nui/include/nui/event_system/observed_value_combinator.hpp b/nui/include/nui/event_system/observed_value_combinator.hpp index 1205e7cb..de621515 100644 --- a/nui/include/nui/event_system/observed_value_combinator.hpp +++ b/nui/include/nui/event_system/observed_value_combinator.hpp @@ -1,7 +1,9 @@ #pragma once -#include +#include +#include #include +#include #include #include @@ -12,51 +14,92 @@ namespace Nui class ObservedValueCombinatorBase { public: - explicit constexpr ObservedValueCombinatorBase(ObservedValues const&... observedValues) - : observedValues_{observedValues...} + explicit constexpr ObservedValueCombinatorBase( + Detail::ObservedAddReference_t&&... observedValues) + : observedValues_{std::forward>(observedValues)...} {} - explicit constexpr ObservedValueCombinatorBase(std::tuple observedValues) + explicit constexpr ObservedValueCombinatorBase( + std::tuple...> observedValues) : observedValues_{std::move(observedValues)} {} constexpr void attachEvent(auto eventId) const { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.attachEvent(eventId); - }); + tupleForEach( + observedValues_, + Nui::overloaded{ + [eventId](IsObserved auto const& observed) { + observed.attachEvent(eventId); + }, + [eventId](IsWeakObserved auto const& observed) { + if (auto shared = observed.lock(); shared) + shared->attachEvent(eventId); + }, + }); } constexpr void attachOneshotEvent(auto eventId) const { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.attachOneshotEvent(eventId); - }); + tupleForEach( + observedValues_, + Nui::overloaded{ + [eventId](IsObserved auto const& observed) { + observed.attachOneshotEvent(eventId); + }, + [eventId](IsWeakObserved auto const& observed) { + if (auto shared = observed.lock(); shared) + shared->attachOneshotEvent(eventId); + }, + }); } constexpr void detachEvent(auto eventId) const { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.detachEvent(eventId); - }); + tupleForEach( + observedValues_, + Nui::overloaded{ + [eventId](IsObserved auto const& observed) { + observed.detachEvent(eventId); + }, + [eventId](IsWeakObserved auto const& observed) { + if (auto shared = observed.lock(); shared) + shared->detachEvent(eventId); + }, + }); } - std::tuple const& observedValues() & + std::tuple...> const& observedValues() & { return observedValues_; } - std::tuple&& observedValues() && + std::tuple...>&& observedValues() && { - return std::move(const_cast&>(observedValues_)); + return std::move( + const_cast...>&>(observedValues_)); + } + + bool isAnyExpired() const + { + const auto isExpired = Nui::overloaded{ + [](IsObserved auto const&) { + return false; + }, + [](IsWeakObserved auto const& observed) { + return observed.expired(); + }, + }; + + return std::apply( + [isExpired](auto const&... observed) { + return (isExpired(observed) || ...); + }, + observedValues_); } protected: - const std::tuple observedValues_; + const std::tuple...> observedValues_; }; - template - ObservedValueCombinatorBase(std::tuple) -> ObservedValueCombinatorBase; - template - ObservedValueCombinatorBase(ObservedValues const&...) -> ObservedValueCombinatorBase; template class ObservedValueCombinator; @@ -66,7 +109,7 @@ namespace Nui { public: constexpr ObservedValueCombinatorWithGenerator( - std::tuple observedValues, + std::tuple...> observedValues, RendererType generator) : ObservedValueCombinatorBase{std::move(observedValues)} , generator_{std::move(generator)} @@ -138,12 +181,18 @@ namespace Nui } }; template - ObservedValueCombinator(ObservedValues const&...) -> ObservedValueCombinator; + ObservedValueCombinator(ObservedValues&&...) + -> ObservedValueCombinator>...>; + template + ObservedValueCombinator(std::tuple...>) + -> ObservedValueCombinator>...>; template - requires(Detail::IsObserved::value && ...) - ObservedValueCombinator observe(ObservedValues const&... observedValues) + requires(IsObservedLike && ...) + ObservedValueCombinator>...> + observe(ObservedValues&&... observedValues) { - return ObservedValueCombinator(observedValues...); + return ObservedValueCombinator>...>{ + std::forward>(observedValues)...}; } } \ No newline at end of file diff --git a/nui/include/nui/event_system/range.hpp b/nui/include/nui/event_system/range.hpp index 4759225c..2e7a44c2 100644 --- a/nui/include/nui/event_system/range.hpp +++ b/nui/include/nui/event_system/range.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -21,22 +21,24 @@ namespace Nui static constexpr bool isRandomAccess = ObservedType::isRandomAccess; - explicit constexpr ObservedRange(ObservedValue& observedValues) - : observedValue_{observedValues} + template + requires std::is_same_v, std::decay_t> + explicit constexpr ObservedRange(ObservedValueT&& observedValues) + : observedValue_{std::forward>(observedValues)} {} - ObservedValue const& underlying() const + Detail::ObservedAddReference_t underlying() const { return observedValue_; } - ObservedValue& underlying() + Detail::ObservedAddMutableReference_t underlying() requires(!std::is_const_v) { return observedValue_; } private: - ObservedValue& observedValue_; + Detail::ObservedAddMutableReference_t observedValue_; }; template @@ -91,40 +93,25 @@ namespace Nui } template - UnoptimizedRange, Observed...> - range(ContainerT const& container, Observed const&... observed) + UnoptimizedRange, std::decay_t>...> + range(ContainerT const& container, Observed&&... observed) { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, + return UnoptimizedRange< + IteratorAccessor, + std::decay_t>...>{ + ObservedValueCombinator{std::forward>(observed)...}, IteratorAccessor{container}, }; } template - UnoptimizedRange, Observed...> - range(ContainerT& container, Observed const&... observed) + UnoptimizedRange, std::decay_t>...> + range(ContainerT& container, Observed&&... observed) { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, - IteratorAccessor{container}, - }; - } - - template - UnoptimizedRange, Observed...> - range(ContainerT const& container, Observed&... observed) - { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, - IteratorAccessor{container}, - }; - } - - template - UnoptimizedRange, Observed...> range(ContainerT& container, Observed&... observed) - { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, + return UnoptimizedRange< + IteratorAccessor, + std::decay_t>...>{ + ObservedValueCombinator{std::forward>(observed)...}, IteratorAccessor{container}, }; } @@ -147,11 +134,15 @@ namespace Nui #ifdef NUI_HAS_STD_RANGES template - UnoptimizedRange>, Observed...> - range(T const& container, Observed const&... observed) + UnoptimizedRange< + std::ranges::subrange>, + std::decay_t>...> + range(T const& container, Observed&&... observed) { - return UnoptimizedRange>, Observed...>{ - ObservedValueCombinator{observed...}, + return UnoptimizedRange< + std::ranges::subrange>, + std::decay_t>...>{ + ObservedValueCombinator{std::forward>(observed)...}, std::ranges::subrange>{ std::ranges::begin(container), std::ranges::end(container)}, }; diff --git a/nui/include/nui/event_system/range_event_context.hpp b/nui/include/nui/event_system/range_event_context.hpp index 76cdca5f..f1b1c745 100644 --- a/nui/include/nui/event_system/range_event_context.hpp +++ b/nui/include/nui/event_system/range_event_context.hpp @@ -1,6 +1,9 @@ #pragma once +#include + #include +#include #include #include @@ -10,13 +13,12 @@ namespace Nui { - enum RangeStateType + enum RangeOperationType { Keep = 0b0001, Modify = 0b0010, Insert = 0b0100, - Erase = 0b1000, - Extended = 0b0001'0000 + Erase = 0b1000 }; namespace Detail @@ -24,11 +26,6 @@ namespace Nui template class RangeStateInterval; - template - std::vector> cutIntervals( - RangeStateInterval const& k, - RangeStateInterval const& m); - template class RangeStateInterval { @@ -37,16 +34,14 @@ namespace Nui using interval_kind = IntervalKind; public: - RangeStateInterval(value_type low, value_type high, RangeStateType type) + RangeStateInterval(value_type low, value_type high) : low_{low} , high_{high} - , type_{type} {} - void reset(value_type low, value_type high, RangeStateType type) + void reset(value_type low, value_type high) { low_ = low; high_ = high; - type_ = type; } friend bool operator==(RangeStateInterval const& lhs, RangeStateInterval const& rhs) { @@ -64,17 +59,32 @@ namespace Nui { return high_; } + void low(value_type value) + { + low_ = value; + } + void high(value_type value) + { + high_ = value; + } bool overlaps(value_type l, value_type h) const { - return low_ <= h && l <= high_; + // return low_ <= h && l <= high_; + return overlapsOrIsAdjacent(l, h); } + // looks inclusive, but inclusive now means adjacent: bool overlaps_exclusive(value_type l, value_type h) const { - return low_ < h && l < high_; + return low_ <= h && l <= high_; } bool overlaps(RangeStateInterval const& other) const { - return overlaps(other.low_, other.high_); + // return overlaps(other.low_, other.high_); + return overlapsOrIsAdjacent(other); + } + bool overlapsOrIsAdjacent(value_type l, value_type h) const + { + return low_ <= h + 1 && l - 1 <= high_; } bool overlapsOrIsAdjacent(RangeStateInterval const& other) const { @@ -92,6 +102,20 @@ namespace Nui { return low_ <= other.low_ && high_ >= other.high_; } + void shiftRight(value_type offset) + { + low_ += offset; + high_ += offset; + } + void shiftLeft(value_type offset) + { + low_ -= offset; + high_ -= offset; + } + bool isLeftOf(RangeStateInterval const& other) const + { + return high_ < other.low_; + } value_type operator-(RangeStateInterval const& other) const { if (overlaps(other)) @@ -105,40 +129,49 @@ namespace Nui { return high_ - low_; } - std::vector join(RangeStateInterval const& other) const + // undefined if they do not overlap + RangeStateInterval expand(RangeStateInterval const& other) const { - auto typeWithoutExtension = static_cast(other.type_ & ~RangeStateType::Extended); - long extensionFix = other.type_ & RangeStateType::Extended ? 1 : 0; - switch (typeWithoutExtension | type_) - { - case (RangeStateType::Modify | RangeStateType::Modify): - { - return { - {std::min(low_, other.low_ + extensionFix), - std::max(high_, other.high_ - extensionFix), - RangeStateType::Modify}}; - } - case (RangeStateType::Keep | RangeStateType::Modify): - { - // Modifications cut out of the keep part. - return cutIntervals( - *this, {other.low_ + extensionFix, other.high_ - extensionFix, typeWithoutExtension}); - } - default: - { - throw std::runtime_error("Invalid insertion case"); - } - } + const auto low = std::min(low_, other.low_); + // +1, because if they overlap they share a side, so its not double counted + // [0, 1] and [1, 2] -> [0, 2] + // [8, 8] and [8, 8] -> [8, 9] + const auto high = low + size() + other.size() + 1; + return {low, high}; } - RangeStateType type() const noexcept + RangeStateInterval join(RangeStateInterval const& other) const { - return type_; + return RangeStateInterval{std::min(low_, other.low_), std::max(high_, other.high_)}; } private: value_type low_; value_type high_; - RangeStateType type_; + }; + + struct CustomIntervalTreeNode + : public lib_interval_tree::node, CustomIntervalTreeNode> + { + using lib_interval_tree::node, CustomIntervalTreeNode>::node; + + void shiftRight(long offset) + { + this->interval_.low(this->interval_.low() + offset); + this->interval_.high(this->interval_.high() + offset); + this->max_ = this->max_ + offset; + } + + void shiftLeft(long offset) + { + this->interval_.low(this->interval_.low() - offset); + this->interval_.high(this->interval_.high() - offset); + this->max_ = this->max_ - offset; + } + }; + + struct IntervalTreeHook : public lib_interval_tree::hooks::regular + { + using node_type = CustomIntervalTreeNode; }; // stream iterator for intervals @@ -146,149 +179,338 @@ namespace Nui std::ostream& operator<<(std::ostream& os, RangeStateInterval const& interval) { os << "[" << interval.low() << ", " << interval.high() << "]"; - switch (interval.type()) - { - case RangeStateType::Keep: - os << "k"; - break; - case RangeStateType::Modify: - os << "m"; - break; - case RangeStateType::Insert: - os << "i"; - break; - default: - os << "?"; - break; - } return os; } - - template - std::vector> cutIntervals( - RangeStateInterval const& k, - RangeStateInterval const& m) - { - std::vector> result; - if (k.low() <= m.low() - 1) - result.emplace_back(k.low(), m.low() - 1, RangeStateType::Keep); - result.emplace_back(m.low(), m.high(), RangeStateType::Modify); - if (k.high() >= m.high() + 1) - result.emplace_back(m.high() + 1, k.high(), RangeStateType::Keep); - return result; - } } class RangeEventContext { public: - explicit RangeEventContext(long dataSize) - : modificationRanges_{} - , fullRangeUpdate_{true} - , disableOptimizations_{false} - { - reset(dataSize, true); - } - RangeEventContext(long dataSize, bool disableOptimizations) - : modificationRanges_{} - , fullRangeUpdate_{true} + explicit RangeEventContext() + : RangeEventContext(false) + {} + RangeEventContext(bool disableOptimizations) + : trackedRanges_{} + , operationType_{RangeOperationType::Keep} + , nextEraseOverride_{std::nullopt} + , fullRangeUpdate_{false} , disableOptimizations_{disableOptimizations} - { - reset(dataSize, true); - } + {} enum class InsertResult { - Final, // Final, cannot accept further updates, must update immediately - Accepted, // Accepted, update can be deferred. - Retry // Must update immediately and reissue this. + Perform, + PerformAndRetry, + Accepted, + Rejected }; void performFullRangeUpdate() { fullRangeUpdate_ = true; } - InsertResult insertModificationRange(long elementCount, long low, long high, RangeStateType type) + /// @brief Done before the erasure is performed: + /// @return true if a fixup was performed + bool eraseNotify(long low, long high) + { + if (operationType_ == RangeOperationType::Keep || operationType_ == RangeOperationType::Erase || + fullRangeUpdate_ || disableOptimizations_ || trackedRanges_.empty()) + return false; + if (operationType_ == RangeOperationType::Modify) + return eraseModificationFixup(low, high); + else + return eraseInsertionFixup(low, high); + } + bool eraseNotify(std::size_t low, std::size_t high) + { + return eraseNotify(static_cast(low), static_cast(high)); + } + InsertResult insertModificationRange(long low, long high, RangeOperationType type) { if (disableOptimizations_) { fullRangeUpdate_ = true; - return InsertResult::Final; + return InsertResult::Perform; } - if (type == RangeStateType::Erase) + + if (operationType_ == RangeOperationType::Keep) { - // TODO: optimize erase like insert. - reset(elementCount, true); - return InsertResult::Final; + operationType_ = type; } - else if (type == RangeStateType::Insert) + else if (operationType_ != type) + return InsertResult::PerformAndRetry; + + switch (type) { - if (modificationRanges_.size() > 1) - return InsertResult::Retry; - if (!insertInterval_) + default: + return InsertResult::Rejected; + case RangeOperationType::Modify: { - insertInterval_ = {low, high, type}; - return InsertResult::Accepted; + // Insert and merge interval only: + trackedRanges_.insert_overlap({low, high}); + break; } - else + case RangeOperationType::Insert: + { + insertInsertRange(low, high); + break; + } + case RangeOperationType::Erase: { - if (insertInterval_->overlapsOrIsAdjacent({low, high, type})) + if (nextEraseOverride_) { - auto lowmin = std::min(low, insertInterval_->low()); - insertInterval_->reset(lowmin, lowmin + insertInterval_->size() + (high - low + 1), type); - return InsertResult::Accepted; + insertEraseRange(nextEraseOverride_->low(), nextEraseOverride_->high()); + nextEraseOverride_ = std::nullopt; } else - { - if (high < insertInterval_->low()) - { - const auto size = high - low + 1; - insertInterval_->reset( - insertInterval_->low() + size, insertInterval_->high() + size, insertInterval_->type()); - } - return InsertResult::Retry; - } + insertEraseRange(low, high); + break; } } - if (insertInterval_) - return InsertResult::Retry; - auto iter = modificationRanges_.insert_overlap( - {low - 1, high + 1, static_cast(type | RangeStateType::Extended)}, false, true); return InsertResult::Accepted; } - InsertResult - insertModificationRange(std::size_t elementCount, std::size_t low, std::size_t high, RangeStateType type) + InsertResult insertModificationRange(std::size_t low, std::size_t high, RangeOperationType type) { - return insertModificationRange( - static_cast(elementCount), static_cast(low), static_cast(high), type); + return insertModificationRange(static_cast(low), static_cast(high), type); } - void reset(long dataSize, bool requireFullRangeUpdate) + void reset(bool requireFullRangeUpdate = false) { - modificationRanges_.clear(); - if (dataSize > 0) - modificationRanges_.insert({0l, dataSize - 1, RangeStateType::Keep}); - insertInterval_ = std::nullopt; + trackedRanges_.clear(); fullRangeUpdate_ = requireFullRangeUpdate; + operationType_ = RangeOperationType::Keep; } - bool isFullRangeUpdate() const noexcept + bool isInDefaultState() const { - return fullRangeUpdate_; + return trackedRanges_.empty() && operationType_ == RangeOperationType::Keep; } - std::optional> const& insertInterval() const + bool isFullRangeUpdate() const noexcept { - return insertInterval_; + return fullRangeUpdate_; } auto begin() const { - return modificationRanges_.begin(); + return trackedRanges_.begin(); } auto end() const { - return modificationRanges_.end(); + return trackedRanges_.end(); + } + auto rbegin() const + { + return trackedRanges_.rbegin(); + } + auto rend() const + { + return trackedRanges_.rend(); + } + RangeOperationType operationType() const noexcept + { + return operationType_; + } + + private: + void insertInsertRange(long low, long high) + { + // TODO: perform optimization using interval tree specialization for shifting subtrees. + auto newRange = Detail::RangeStateInterval{low, high}; + + // find insert at same position first to move previous insert at that position over: + auto it = trackedRanges_.find(newRange, [](auto const& a, auto const& b) { + return a.low() == b.low(); + }); + + if (it != trackedRanges_.end()) + { + newRange = it->expand(newRange); + trackedRanges_.erase(it); + it = trackedRanges_.insert(newRange); + } + else + { + it = trackedRanges_.insert(newRange); + } + if (it != std::end(trackedRanges_)) + ++it; + + // move all subsequent intervals to the right: + // TODO: This is the expensive part and should be optimized. + for (; it != trackedRanges_.end(); ++it) + { + it.node()->shiftRight(high - low + 1); + } + } + void insertEraseRange(long low, long high) + { + auto newRange = Detail::RangeStateInterval{low, high}; + const bool processed = [&newRange, this]() { + for (auto it = trackedRanges_.begin(); it != trackedRanges_.end(); ++it) + { + // Shift erasure over when previous erasures shrank the range, + // since erasures are replayed from the end. + if (it->low() < newRange.low()) + { + // +1 because the range includes its end + newRange.shiftRight(it->size() + 1); + } + else if (it->overlapsOrIsAdjacent(newRange)) + { + do + { + newRange = it->expand(newRange); + + // Remove the old range and insert the new one + it = trackedRanges_.erase(it); + } while (it != trackedRanges_.end() && it->overlapsOrIsAdjacent(newRange)); + trackedRanges_.insert(newRange); + return true; + } + else + { + return false; + } + } + return false; + }(); + if (!processed) + { + trackedRanges_.insert_overlap(newRange); + } + } + + /** + * @brief This is necessary to remove previous inserts that are now erased. + * + * @param low + * @param high + */ + bool eraseInsertionFixup(long low, long high) + { + const auto lastInterval = *trackedRanges_.rbegin(); + + // If the erase interval is left of the last insert interval, we must apply the changes and + // retry. The following optimization would make the insert positions invalid. + // TODO: Moving them might be possible, but expensive. + if (high < lastInterval.high()) + return true; + + auto eraseRange = Detail::RangeStateInterval{low, high}; + auto eraseRangeOrig = eraseRange; + + auto iter = trackedRanges_.overlap_find(eraseRange, true); + // if erase is completely at the end of a previous insert, we can cut the inserted elements out. + for (; iter != trackedRanges_.end(); iter = trackedRanges_.overlap_find(eraseRangeOrig, true)) + { + if (iter == trackedRanges_.end()) + return true; + const auto insertInterval = *iter; + + // erase overlaps insert to the end or over: + if (eraseRangeOrig.high() >= insertInterval.high()) + { + trackedRanges_.erase(iter); + // if beginning of insert is left of erase, we have to insert the left part of the insert + if (insertInterval.low() < eraseRangeOrig.low()) + { + trackedRanges_.insert({insertInterval.low(), eraseRangeOrig.low() - 1}); + eraseRangeOrig.high(-insertInterval.high() + eraseRangeOrig.low() + eraseRangeOrig.high() - 1); + } + + eraseRange.high(eraseRange.high() - insertInterval.size() - 1); + } + else + { + break; // bail out + } + } + + if (eraseRange.high() != high) + nextEraseOverride_ = eraseRange; + + // Other tracking cases are too complicated. + // The reason is that cutting the intervals is not enough, we also have to modify where these insertions are + // taken from. + return true; + } + + /** + * @brief This is necessary to remove previous modifies that are now erased. + * + * @param low + * @param high + */ + bool eraseModificationFixup(long low, long high) + { + auto range = Detail::RangeStateInterval{low, high}; + + auto iter = trackedRanges_.overlap_find(range, false); + + if (iter == trackedRanges_.end()) + { + // If we dont have an overlap, there might still be a case where modifications exists + // after the erase. In that case we have to apply these changes immediately and retry the erase. + // (Shifting the previous modifications would work too, but is more expensive) + const auto lastInterval = *trackedRanges_.rbegin(); + + // If the erase interval is left of the last modification interval, we must apply the changes and + // retry. An overlap would have been found otherwise. + if (high < lastInterval.low()) + return true; + + return false; + } + + // find all overlapping modifications and cut them + for (; iter != trackedRanges_.end(); iter = trackedRanges_.overlap_find(range, false)) + { + const auto modInterval = *iter; + trackedRanges_.erase(iter); + + // cut erasure from found modification interval: + // 1. erase is within modification interval: + if (modInterval.within(range)) + { + if (modInterval.low() < range.low()) + trackedRanges_.insert({modInterval.low(), range.low() - 1}); + if (range.high() < modInterval.high()) + trackedRanges_.insert({range.high() + 1, modInterval.high()}); + return true; // cannot overlap any further + } + else if (modInterval.low() < range.low()) + { + // 2. erase starts within modification interval: + trackedRanges_.insert({modInterval.low(), range.low() - 1}); + + // reduce range to right part: + range.low(range.low() + 1); + if (range.low() > range.high()) + return true; + } + else if (range.high() < modInterval.high()) + { + // 3. erase ends within modification interval: + trackedRanges_.insert({range.high() + 1, modInterval.high()}); + + // reduce range to left part: + range.high(range.high() - 1); + if (range.low() > range.high()) + return true; + } + else if (range.within(modInterval)) + { + // 4. erase encompasses modification interval, only deletion is necessary: + continue; + } + else + { + NUI_ASSERT(false, "Overlap without overlapping?"); + } + } + return true; } private: - lib_interval_tree::interval_tree> modificationRanges_; - std::optional> insertInterval_; + lib_interval_tree::interval_tree, Detail::IntervalTreeHook> trackedRanges_; + RangeOperationType operationType_; + std::optional> nextEraseOverride_; bool fullRangeUpdate_; bool disableOptimizations_; }; diff --git a/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp b/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp index db98ce06..472e3f5e 100644 --- a/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp +++ b/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp @@ -70,9 +70,12 @@ namespace Nui::Attributes : name_{name} {} - // Dont use this class like a value - PropertyFactory(PropertyFactory const&) = delete; - PropertyFactory(PropertyFactory&&) = delete; + explicit constexpr PropertyFactory(PropertyFactory const& other) + : name_{other.name_} + {} + explicit constexpr PropertyFactory(PropertyFactory&& other) + : name_{other.name_} + {} PropertyFactory& operator=(PropertyFactory const&) = delete; PropertyFactory& operator=(PropertyFactory&&) = delete; @@ -83,7 +86,7 @@ namespace Nui::Attributes template requires( - !IsObserved> && !std::invocable && !std::invocable && + !IsObservedLike> && !std::invocable && !std::invocable && !Nui::Detail::IsProperty>) Attribute operator=(U val) const { @@ -94,6 +97,60 @@ namespace Nui::Attributes }; } + template + requires(IsSharedObserved>) + Attribute operator=(U const& shared) const + { + return Attribute{ + [name = name(), weak = std::weak_ptr{shared}](Dom::ChildlessElement& element) { + if (auto shared = weak.lock(); shared) + element.setProperty(name, shared->value()); + }, + [name = name(), weak = std::weak_ptr{shared}](std::weak_ptr&& element) { + auto shared = weak.lock(); + if (!shared) + return EventContext::invalidEventId; + + const auto eventId = globalEventContext.registerEvent(Event{ + [name, element, obsWeak = std::weak_ptr{shared}](auto eventId) { + auto obsShared = obsWeak.lock(); + if (!obsShared) + { + return false; + } + if (auto shared = element.lock(); shared) + { + shared->setProperty(name, obsShared->value()); + return true; + } + obsShared->detachEvent(eventId); + return false; + }, + [element, obsWeak = std::weak_ptr{shared}]() { + return !element.expired() && !obsWeak.expired(); + }, + }); + shared->attachEvent(eventId); + return eventId; + }, + [weak = std::weak_ptr{shared}](EventContext::EventIdType id) { + if (auto shared = weak.lock(); shared) + shared->detachEvent(id); + }, + }; + } + + template + requires(IsWeakObserved>) + Attribute operator=(U&& val) const + { + auto shared = val.lock(); + if (!shared) + return Attribute{}; + + return operator=(shared); + } + template requires(IsObserved>) Attribute operator=(U& val) const @@ -106,7 +163,7 @@ namespace Nui::Attributes return Detail::defaultPropertyEvent( std::move(element), Nui::Detail::CopyableObservedWrap{val}, name); }, - [&val](EventContext::EventIdType const& id) { + [&val](EventContext::EventIdType id) { val.detachEvent(id); }, }; @@ -123,7 +180,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultPropertyEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; @@ -140,7 +197,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultPropertyEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; @@ -181,9 +238,12 @@ namespace Nui::Attributes : name_{name} {} - // Dont use this class like a value - AttributeFactory(AttributeFactory const&) = delete; - AttributeFactory(AttributeFactory&&) = delete; + explicit constexpr AttributeFactory(AttributeFactory const& other) + : name_{other.name_} + {} + explicit constexpr AttributeFactory(AttributeFactory&& other) + : name_{other.name_} + {} AttributeFactory& operator=(AttributeFactory const&) = delete; AttributeFactory& operator=(AttributeFactory&&) = delete; @@ -194,7 +254,7 @@ namespace Nui::Attributes template requires( - !IsObserved> && !std::invocable && !std::invocable && + !IsObservedLike> && !std::invocable && !std::invocable && !Nui::Detail::IsProperty>) Attribute operator=(U val) const { @@ -205,6 +265,60 @@ namespace Nui::Attributes }; } + template + requires(IsSharedObserved>) + Attribute operator=(U const& shared) const + { + return Attribute{ + [name = name(), weak = std::weak_ptr{shared}](Dom::ChildlessElement& element) { + if (auto shared = weak.lock(); shared) + element.setAttribute(name, shared->value()); + }, + [name = name(), weak = std::weak_ptr{shared}](std::weak_ptr&& element) { + auto shared = weak.lock(); + if (!shared) + return EventContext::invalidEventId; + + const auto eventId = globalEventContext.registerEvent(Event{ + [name, element, obsWeak = std::weak_ptr{shared}](auto eventId) { + auto obsShared = obsWeak.lock(); + if (!obsShared) + { + return false; + } + if (auto shared = element.lock(); shared) + { + shared->setAttribute(name, obsShared->value()); + return true; + } + obsShared->detachEvent(eventId); + return false; + }, + [element, obsWeak = std::weak_ptr{shared}]() { + return !element.expired() && !obsWeak.expired(); + }, + }); + shared->attachEvent(eventId); + return eventId; + }, + [weak = std::weak_ptr{shared}](EventContext::EventIdType id) { + if (auto shared = weak.lock(); shared) + shared->detachEvent(id); + }, + }; + } + + template + requires(IsWeakObserved>) + Attribute operator=(U&& val) const + { + auto shared = val.lock(); + if (!shared) + return Attribute{}; + + return operator=(shared); + } + template requires(IsObserved>) Attribute operator=(U& val) const @@ -217,7 +331,7 @@ namespace Nui::Attributes return Detail::defaultAttributeEvent( std::move(element), Nui::Detail::CopyableObservedWrap{val}, name); }, - [&val](EventContext::EventIdType const& id) { + [&val](EventContext::EventIdType id) { val.detachEvent(id); }, }; @@ -235,14 +349,61 @@ namespace Nui::Attributes return Detail::defaultPropertyEvent( std::move(element), Nui::Detail::CopyableObservedWrap{*p}, name); }, - [p = prop.prop](EventContext::EventIdType const& id) { + [p = prop.prop](EventContext::EventIdType id) { p->detachEvent(id); }, }; } template - requires(!IsObserved> && !std::invocable && !std::invocable) + requires(IsWeakObserved>) + Attribute operator=(Nui::Detail::Property const& prop) const + { + auto shared = prop.prop.lock(); + if (!shared) + return Attribute{}; + + return Attribute{ + [name = name(), weak = std::weak_ptr{shared}](Dom::ChildlessElement& element) { + if (auto shared = weak.lock(); shared) + element.setProperty(name, shared->value()); + }, + [name = name(), weak = std::weak_ptr{shared}](std::weak_ptr&& element) { + auto shared = weak.lock(); + if (!shared) + return EventContext::invalidEventId; + + const auto eventId = globalEventContext.registerEvent(Event{ + [name, element, obsWeak = std::weak_ptr{shared}](auto eventId) { + auto obsShared = obsWeak.lock(); + if (!obsShared) + { + return false; + } + if (auto shared = element.lock(); shared) + { + shared->setProperty(name, obsShared->value()); + return true; + } + obsShared->detachEvent(eventId); + return false; + }, + [element, obsWeak = std::weak_ptr{shared}]() { + return !element.expired() && !obsWeak.expired(); + }, + }); + shared->attachEvent(eventId); + return eventId; + }, + [weak = std::weak_ptr{shared}](EventContext::EventIdType id) { + if (auto shared = weak.lock(); shared) + shared->detachEvent(id); + }, + }; + } + + template + requires(!IsObservedLike> && !std::invocable && !std::invocable) Attribute operator=(Nui::Detail::Property const& prop) const { return Attribute{[name = name(), p = std::move(prop.prop)](Dom::ChildlessElement& element) { @@ -261,7 +422,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultPropertyEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; @@ -278,7 +439,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultAttributeEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; @@ -343,9 +504,12 @@ namespace Nui::Attributes : name_{name} {} - // Dont use this class like a value - EventFactory(EventFactory const&) = delete; - EventFactory(EventFactory&&) = delete; + explicit constexpr EventFactory(EventFactory const& other) + : name_{other.name_} + {} + explicit constexpr EventFactory(EventFactory&& other) + : name_{other.name_} + {} EventFactory& operator=(EventFactory const&) = delete; EventFactory& operator=(EventFactory&&) = delete; @@ -397,6 +561,31 @@ namespace Nui::Attributes return EventFactory{name}; } } + + namespace Detail + { + template + struct DeferWrap + { + T factory; + + template + Attribute operator=(Args&&... args) const + { + auto attr = factory.operator=(std::forward(args)...); + attr.defer(true); + return attr; + }; + }; + } + + template + requires( + std::is_same_v || std::is_same_v || std::is_same_v) + Detail::DeferWrap operator!(T const& factory) + { + return Detail::DeferWrap{.factory = T{std::move(factory)}}; + } } #define MAKE_HTML_VALUE_ATTRIBUTE_RENAME(NAME, HTML_NAME) \ diff --git a/nui/include/nui/frontend/attributes/style.hpp b/nui/include/nui/frontend/attributes/style.hpp index a229186b..8611e636 100644 --- a/nui/include/nui/frontend/attributes/style.hpp +++ b/nui/include/nui/frontend/attributes/style.hpp @@ -26,7 +26,7 @@ namespace Nui::Attributes template struct StylePropertyEbo { - std::tuple const&...> observed; + std::tuple<::Nui::Detail::ObservedAddReference_t...> observed; }; template <> struct StylePropertyEbo @@ -35,12 +35,12 @@ namespace Nui::Attributes template struct StylePropertyImpl : public Detail::StylePropertyEbo { - using value_type = std::tuple const&...>; + using value_type = std::tuple<::Nui::Detail::ObservedAddReference_t...>; FunctionT generator; constexpr static bool isStatic() { - return sizeof...(T) == 1 && std::is_same_v, void>; + return sizeof...(T) == 1 && std::is_same_v>, void>; } constexpr auto operator()() const { @@ -52,6 +52,16 @@ namespace Nui::Attributes : Detail::StylePropertyEbo{std::forward_as_tuple(observed)} , generator{std::move(generator)} {} + template + constexpr StylePropertyImpl(FunctionT generator, std::weak_ptr> observed) + : Detail::StylePropertyEbo{std::make_tuple(std::move(observed))} + , generator{std::move(generator)} + {} + template + constexpr StylePropertyImpl(FunctionT generator, std::shared_ptr> observed) + : Detail::StylePropertyEbo{std::make_tuple(std::weak_ptr{observed})} + , generator{std::move(generator)} + {} constexpr StylePropertyImpl(FunctionT generator, std::nullptr_t) : Detail::StylePropertyEbo{} , generator{std::move(generator)} @@ -59,7 +69,7 @@ namespace Nui::Attributes template constexpr StylePropertyImpl( FunctionT generator, - ObservedValueCombinatorWithGenerator...>&& observed) + ObservedValueCombinatorWithGenerator&& observed) : Detail::StylePropertyEbo{std::move(observed).observedValues()} , generator{std::move(generator)} {} @@ -67,10 +77,18 @@ namespace Nui::Attributes template StylePropertyImpl(FunctionT generator, std::nullptr_t) -> StylePropertyImpl; template - StylePropertyImpl(FunctionT generator, Observed&) -> StylePropertyImpl; + StylePropertyImpl(FunctionT generator, Observed&) -> StylePropertyImpl>; + template + StylePropertyImpl(FunctionT generator, Observed const&) -> StylePropertyImpl>; + template + StylePropertyImpl(FunctionT generator, std::shared_ptr>) + -> StylePropertyImpl>>; + template + StylePropertyImpl(FunctionT generator, std::weak_ptr>&&) + -> StylePropertyImpl>>; template StylePropertyImpl(FunctionT generator, ObservedValueCombinatorWithGenerator&&) - -> StylePropertyImpl; + -> StylePropertyImpl; namespace Detail { @@ -169,7 +187,7 @@ namespace Nui::Attributes }, nullptr}; } - auto operator=(Observed& observedValue) + auto operator=(Observed const& observedValue) { return StylePropertyImpl{ [name_ = std::string{name}, &observedValue]() { @@ -177,6 +195,26 @@ namespace Nui::Attributes }, observedValue}; } + auto operator=(std::weak_ptr>&& observedValue) + { + return StylePropertyImpl{ + [name_ = std::string{name}, observedValue = std::weak_ptr{observedValue.lock()}]() { + if (auto shared = observedValue.lock(); shared) + return name_ + ":" + shared->value(); + return std::string{}; + }, + std::move(observedValue)}; + } + auto operator=(std::shared_ptr> observedValue) + { + return StylePropertyImpl{ + [name_ = std::string{name}, observedValue = std::weak_ptr{observedValue}]() { + if (auto shared = observedValue.lock(); shared) + return name_ + ":" + shared->value(); + return std::string{}; + }, + std::move(observedValue)}; + } template auto operator=(ObservedValueCombinatorWithGenerator&& combinator) { @@ -206,8 +244,9 @@ namespace Nui::Attributes std::stringstream sstr; [&sstr](auto const& head, auto const&... tail) { using expander = int[]; - sstr << head(); - (void)expander{0, (sstr << ";" << tail(), void(), 0)...}; + const auto headStr = head(); + sstr << headStr; + (void)expander{0, (sstr << (headStr.empty() ? "" : ";") << tail(), void(), 0)...}; }(props...); return sstr.str(); }; @@ -288,9 +327,9 @@ namespace Nui::Attributes else { return std::apply( - [&style](ObservedValueTypes&... obs) { + [&style](ObservedValueTypes&&... obs) { return AttributeFactory{"style"}.operator=( - ObservedValueCombinator{obs...}.generate( + ObservedValueCombinator{std::forward(obs)...}.generate( std::move(style).ejectGenerator())); }, std::move(style).ejectObservedValues()); diff --git a/nui/include/nui/frontend/dom/basic_element.hpp b/nui/include/nui/frontend/dom/basic_element.hpp index 0a66e6b5..4a11aca2 100644 --- a/nui/include/nui/frontend/dom/basic_element.hpp +++ b/nui/include/nui/frontend/dom/basic_element.hpp @@ -65,6 +65,10 @@ namespace Nui::Dom } protected: + explicit BasicElement() + : element_{Nui::val::undefined()} + {} + Nui::val element_; }; } \ No newline at end of file diff --git a/nui/include/nui/frontend/dom/childless_element.hpp b/nui/include/nui/frontend/dom/childless_element.hpp index 989344cd..7a98d163 100644 --- a/nui/include/nui/frontend/dom/childless_element.hpp +++ b/nui/include/nui/frontend/dom/childless_element.hpp @@ -159,6 +159,10 @@ namespace Nui::Dom } protected: + explicit ChildlessElement() + : BasicElement{} + {} + static Nui::val createElement(HtmlElement const& element) { return element.bridge()->createElement(element); diff --git a/nui/include/nui/frontend/dom/element.hpp b/nui/include/nui/frontend/dom/element.hpp index 932c6f3f..15a572e5 100644 --- a/nui/include/nui/frontend/dom/element.hpp +++ b/nui/include/nui/frontend/dom/element.hpp @@ -53,6 +53,12 @@ namespace Nui::Dom , deferredSetup_{} {} + /** + * @brief This constructor takes ownership of a val. + * This val must not be managed by any other element. + * + * @param val + */ explicit Element(Nui::val val) : ChildlessElement{std::move(val)} , children_{} @@ -60,6 +66,13 @@ namespace Nui::Dom , deferredSetup_{} {} + explicit Element() + : ChildlessElement{} + , children_{} + , unsetup_{} + , deferredSetup_{} + {} + Element(Element const&) = delete; Element(Element&&) = delete; Element& operator=(Element const&) = delete; @@ -128,6 +141,22 @@ namespace Nui::Dom replaceElementImpl(element); return shared_from_base(); } + auto emplaceElement(HtmlElement const& element) + { + if (!element_.isUndefined()) + throw std::runtime_error("Element is not empty, cannot emplace"); + + element_ = createElement(element); + setup(element); + if (deferredSetup_) + deferredSetup_(element); + return shared_from_base(); + } + auto emplaceElement(std::invocable auto&& fn) + { + fn(*this, Renderer{.type = RendererType::Emplace}); + return shared_from_base(); + } void setTextContent(std::string const& text) { @@ -184,10 +213,13 @@ namespace Nui::Dom auto clear = attribute.getEventClear(); if (clear) { - eventClearers.push_back( - [clear = std::move(clear), id = attribute.createEvent(weak_from_base())]() { + const auto id = attribute.createEvent(weak_from_base()); + if (id != EventContext::invalidEventId) + { + eventClearers.push_back([clear = std::move(clear), id]() { clear(id); }); + } } } if (!eventClearers.empty()) @@ -219,10 +251,13 @@ namespace Nui::Dom auto clear = attribute.getEventClear(); if (clear) { - eventClearers.push_back( - [clear = std::move(clear), id = attribute.createEvent(weak_from_base())]() { + const auto id = attribute.createEvent(weak_from_base()); + if (id != EventContext::invalidEventId) + { + eventClearers.push_back([clear = std::move(clear), id]() { clear(id); }); + } } } if (!eventClearers.empty()) @@ -259,6 +294,10 @@ namespace Nui::Dom { return children_.erase(where); } + auto erase(iterator first, iterator last) + { + return children_.erase(first, last); + } void clearChildren() { @@ -275,6 +314,11 @@ namespace Nui::Dom return children_.size(); } + std::string tagName() const + { + return element_["tagName"].as(); + } + private: void replaceElementImpl(HtmlElement const& element) { @@ -283,6 +327,11 @@ namespace Nui::Dom unsetup_(); unsetup_ = {}; +#ifndef NDEBUG + if (element_.isUndefined()) + throw std::runtime_error("Element is undefined"); +#endif + auto replacement = createElement(element); element_.call("replaceWith", replacement); element_ = std::move(replacement); @@ -298,6 +347,13 @@ namespace Nui::Dom std::function unsetup_; std::function deferredSetup_; }; + + inline std::shared_ptr makeStandaloneElement(std::invocable auto&& fn) + { + auto elem = std::make_shared(); + fn(*elem, Renderer{.type = RendererType::Emplace}); + return elem; + } } #include diff --git a/nui/include/nui/frontend/elements/impl/html_element.hpp b/nui/include/nui/frontend/elements/impl/html_element.hpp index 52023f30..bd4a5ec1 100644 --- a/nui/include/nui/frontend/elements/impl/html_element.hpp +++ b/nui/include/nui/frontend/elements/impl/html_element.hpp @@ -111,7 +111,7 @@ namespace Nui fragmentContext.clear(); auto parent = createdSelfWeak.lock(); - if (!parent) + if (!parent || observedValues.isAnyExpired()) { childrenRefabricator.reset(); return; @@ -133,7 +133,7 @@ namespace Nui createdSelfWeak = std::weak_ptr(createdSelf), childrenRefabricator]() mutable { auto parent = createdSelfWeak.lock(); - if (!parent) + if (!parent || observedValues.isAnyExpired()) { childrenRefabricator.reset(); return; @@ -161,10 +161,10 @@ namespace Nui auto rangeRender(RangeType&& valueRange, GeneratorT&& elementRenderer) && { return [self = this->clone(), - rangeRenderer = - std::make_shared>( - std::move(valueRange).underlying(), std::forward(elementRenderer))]( - auto& parentElement, Renderer const& gen) { + rangeRenderer = std::make_shared< + Detail::RangeRenderer, GeneratorT, RangeType::isRandomAccess>>( + std::move(valueRange).underlying(), std::forward(elementRenderer))]( + auto& parentElement, Renderer const& gen) mutable { if (gen.type == RendererType::Inplace) throw std::runtime_error("fragments are not supported for range generators"); diff --git a/nui/include/nui/frontend/elements/impl/materialize.hpp b/nui/include/nui/frontend/elements/impl/materialize.hpp index b78b710c..8fd19fc9 100644 --- a/nui/include/nui/frontend/elements/impl/materialize.hpp +++ b/nui/include/nui/frontend/elements/impl/materialize.hpp @@ -27,6 +27,11 @@ namespace Nui { return element.replaceElement(htmlElement); } + /// Replaces the given element with the new one. + inline auto emplaceMaterialize(auto& element, auto const& htmlElement) + { + return element.emplaceElement(htmlElement); + } /// Used for elements that dont have a direct parent. inline auto inplaceMaterialize(auto& element, auto const&) { @@ -40,7 +45,8 @@ namespace Nui Fragment, Insert, Replace, - Inplace + Inplace, + Emplace }; struct Renderer { @@ -61,6 +67,8 @@ namespace Nui return Materializers::replaceMaterialize(element, htmlElement); case RendererType::Inplace: return Materializers::inplaceMaterialize(element, htmlElement); + case RendererType::Emplace: + return Materializers::emplaceMaterialize(element, htmlElement); } }; } \ No newline at end of file diff --git a/nui/include/nui/frontend/elements/impl/range_renderer.hpp b/nui/include/nui/frontend/elements/impl/range_renderer.hpp index a0c395e9..017f1f88 100644 --- a/nui/include/nui/frontend/elements/impl/range_renderer.hpp +++ b/nui/include/nui/frontend/elements/impl/range_renderer.hpp @@ -1,10 +1,14 @@ #pragma once -#include -#include +#include +#include #include +#include +#include +#include #include +#include namespace Nui::Detail { diff --git a/nui/include/nui/frontend/elements/impl/range_renderer.tpp b/nui/include/nui/frontend/elements/impl/range_renderer.tpp index 158c6aae..c9f1c1fa 100644 --- a/nui/include/nui/frontend/elements/impl/range_renderer.tpp +++ b/nui/include/nui/frontend/elements/impl/range_renderer.tpp @@ -6,16 +6,33 @@ namespace Nui::Detail KeepRange = true, }; + template + struct HoldToken + { + // FIXME: Remove this, when getValueRange overload is not instantiated for non weak observed types + std::shared_ptr locked; + }; + + template + struct HoldToken> + { + std::shared_ptr locked; + }; + + template + using CommonHoldToken = ::Nui::Detail::HoldToken>; + template class BasicObservedRenderer { public: using ObservedType = typename RangeT::ObservedType; + using HoldToken = CommonHoldToken; template - BasicObservedRenderer(ObservedType& observed, Generator&& elementRenderer) - : valueRange_{observed} - , elementRenderer_{std::forward(elementRenderer)} + BasicObservedRenderer(ObservedAddMutableReference_t&& observed, Generator&& elementRenderer) + : elementRenderer_{std::forward(elementRenderer)} + , valueRange_{std::forward>(observed)} {} virtual ~BasicObservedRenderer() = default; BasicObservedRenderer(BasicObservedRenderer const&) = delete; @@ -23,25 +40,47 @@ namespace Nui::Detail BasicObservedRenderer& operator=(BasicObservedRenderer const&) = delete; BasicObservedRenderer& operator=(BasicObservedRenderer&&) = delete; - bool fullRangeUpdate(auto& parent) const + auto* getValueRange(HoldToken& holder) { - if (valueRange_.rangeContext().isFullRangeUpdate()) + return Nui::overloaded{ + [](::Nui::IsObserved auto& valueRange) { + return &valueRange; + }, + [&holder](::Nui::IsWeakObserved auto& valueRange) { + if (holder.locked = valueRange.lock(); holder.locked) + return holder.locked.get(); + return nullptr; + }, + }(valueRange_); + } + + bool fullRangeUpdate(auto& parent, bool force = false) + { + auto valueRangeHolder = HoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange == nullptr) + return false; + + if (valueRange->rangeContext().isFullRangeUpdate() || force) { parent->clearChildren(); long long counter = 0; - for (auto& element : valueRange_.value()) + for (auto& element : valueRange->value()) elementRenderer_(counter++, element)(*parent, Renderer{.type = RendererType::Append}); + valueRange->rangeContext().reset(); return true; } return false; } - virtual bool updateChildren() const = 0; + virtual bool updateChildren(bool initial) = 0; protected: - ObservedType& valueRange_; GeneratorT elementRenderer_; std::weak_ptr weakMaterialized_; + + private: + ObservedAddMutableReference_t valueRange_; }; template @@ -52,59 +91,87 @@ namespace Nui::Detail public: using BasicObservedRenderer::BasicObservedRenderer; using BasicObservedRenderer::weakMaterialized_; - using BasicObservedRenderer::valueRange_; using BasicObservedRenderer::elementRenderer_; using BasicObservedRenderer::fullRangeUpdate; + using BasicObservedRenderer::getValueRange; - void insertions(auto& parent) const + void insertions(auto& parent) { - if (const auto insertInterval = valueRange_.rangeContext().insertInterval(); insertInterval) + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange == nullptr) + return; + + for (auto i = valueRange->rangeContext().begin(), end = valueRange->rangeContext().end(); i != end; ++i) { - for (auto i = insertInterval->low(); i <= insertInterval->high(); ++i) + for (auto r = i->low(), high = i->high(); r <= high; ++r) { - elementRenderer_(i, valueRange_.value()[static_cast(i)])( - *parent, Renderer{.type = RendererType::Insert, .metadata = static_cast(i)}); + elementRenderer_(r, valueRange->value()[static_cast(r)])( + *parent, Renderer{.type = RendererType::Insert, .metadata = static_cast(r)}); } } } - void updates(auto& parent) const + void modifications(auto& parent) { - for (auto const& range : valueRange_.rangeContext()) + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange == nullptr) + return; + + for (auto const& range : valueRange->rangeContext()) { - switch (range.type()) + for (auto r = range.low(), high = range.high(); r <= high; ++r) { - case RangeStateType::Keep: - { - continue; - } - case RangeStateType::Modify: - { - for (auto i = range.low(), high = range.high(); i <= high; ++i) - { - elementRenderer_(i, valueRange_.value()[static_cast(i)])( - *(*parent)[static_cast(i)], Renderer{.type = RendererType::Replace}); - } - break; - } - default: - break; + elementRenderer_(r, valueRange->value()[static_cast(r)])( + *(*parent)[static_cast(r)], Renderer{.type = RendererType::Replace}); } } } - bool updateChildren() const override + void erasures(auto& parent) + { + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange == nullptr) + return; + + for (auto const& eraseRange : reverse_view{valueRange->rangeContext()}) + { + parent->erase(begin(*parent) + eraseRange.low(), begin(*parent) + eraseRange.high() + 1); + } + } + + bool updateChildren(bool initial) override { auto parent = weakMaterialized_.lock(); if (!parent) return InvalidateRange; + auto valueRangeHolder = + ::Nui::Detail::HoldToken>>{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (!valueRange) + return InvalidateRange; + // Regenerate all elements if necessary: - if (fullRangeUpdate(parent)) + if (fullRangeUpdate(parent, initial)) return KeepRange; - insertions(parent); - updates(parent); + switch (valueRange->rangeContext().operationType()) + { + case RangeOperationType::Insert: + insertions(parent); + break; + case RangeOperationType::Modify: + modifications(parent); + break; + case RangeOperationType::Erase: + erasures(parent); + break; + default: + break; + } return KeepRange; } @@ -112,15 +179,21 @@ namespace Nui::Detail void operator()(auto& materialized) { weakMaterialized_ = materialized; - valueRange_.attachEvent(Nui::globalEventContext.registerEvent(Event{ - [self = this->shared_from_this()](int) -> bool { - return self->updateChildren(); - }, - [this /* fine because other function holds this */]() { - return !weakMaterialized_.expired(); - }, - })); - updateChildren(); + + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange) + { + valueRange->attachEvent(Nui::globalEventContext.registerEvent(Event{ + [self = this->shared_from_this()](int) -> bool { + return self->updateChildren(false); + }, + [this /* fine because other function holds this */]() { + return !weakMaterialized_.expired(); + }, + })); + updateChildren(true); + } } }; @@ -132,32 +205,37 @@ namespace Nui::Detail public: using BasicObservedRenderer::BasicObservedRenderer; using BasicObservedRenderer::weakMaterialized_; - using BasicObservedRenderer::valueRange_; using BasicObservedRenderer::elementRenderer_; using BasicObservedRenderer::fullRangeUpdate; + using BasicObservedRenderer::getValueRange; - bool updateChildren() const override + bool updateChildren(bool) override { auto parent = weakMaterialized_.lock(); if (!parent) return InvalidateRange; - fullRangeUpdate(parent); + fullRangeUpdate(parent, true /* force always */); return KeepRange; } void operator()(auto& materialized) { weakMaterialized_ = materialized; - valueRange_.attachEvent(Nui::globalEventContext.registerEvent(Event{ - [self = this->shared_from_this()](int) -> bool { - return self->updateChildren(); - }, - [this /* fine because other function holds this */]() { - return !weakMaterialized_.expired(); - }, - })); - updateChildren(); + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange) + { + valueRange->attachEvent(Nui::globalEventContext.registerEvent(Event{ + [self = this->shared_from_this()](int) -> bool { + return self->updateChildren(true); + }, + [this /* fine because other function holds this */]() { + return !weakMaterialized_.expired(); + }, + })); + updateChildren(true); + } } }; @@ -174,7 +252,7 @@ namespace Nui::Detail , elementRenderer_{std::forward(elementRenderer)} {} - bool updateChildren() const + bool updateChildren(bool) { auto materialized = weakMaterialized_.lock(); if (!materialized) @@ -183,9 +261,10 @@ namespace Nui::Detail materialized->clearChildren(); long long counter = 0; - for (auto& element : unoptimizedRange_) + for (auto&& element : unoptimizedRange_) { - elementRenderer_(counter++, element)(*materialized, Renderer{.type = RendererType::Append}); + elementRenderer_(counter++, ContainerWrapUtility::unwrapReferenceWrapper(element))( + *materialized, Renderer{.type = RendererType::Append}); } return KeepRange; @@ -196,13 +275,13 @@ namespace Nui::Detail weakMaterialized_ = materialized; unoptimizedRange_.underlying().attachEvent(Nui::globalEventContext.registerEvent(Event{ [self = this->shared_from_this()](int) -> bool { - return self->updateChildren(); + return self->updateChildren(true); }, [this]() { return !weakMaterialized_.expired(); }, })); - updateChildren(); + updateChildren(true); } protected: diff --git a/nui/include/nui/frontend/property.hpp b/nui/include/nui/frontend/property.hpp index 75cc479d..0c5fc3df 100644 --- a/nui/include/nui/frontend/property.hpp +++ b/nui/include/nui/frontend/property.hpp @@ -20,6 +20,12 @@ namespace Nui Observed const* prop; }; + template + struct Property>> + { + std::weak_ptr> prop; + }; + template struct IsPropertyImpl { @@ -43,6 +49,20 @@ namespace Nui return Detail::Property>{.prop = &val}; } + template + requires(IsWeakObserved>) + Detail::Property> property(U&& val) + { + return Detail::Property>{.prop = std::forward(val)}; + } + + template + requires(IsSharedObserved>) + Detail::Property::weak_type> property(U const& val) + { + return Detail::Property::weak_type>{.prop = std::weak_ptr{val}}; + } + template requires(std::invocable) Detail::Property> property(U val) @@ -58,7 +78,7 @@ namespace Nui } template - requires(!IsObserved> && !std::invocable && !std::invocable) + requires(!IsObservedLike> && !std::invocable && !std::invocable) Detail::Property> property(U val) { return Detail::Property>{.prop = std::move(val)}; diff --git a/nui/include/nui/utility/assert.hpp b/nui/include/nui/utility/assert.hpp index b1ad1202..a95a8304 100644 --- a/nui/include/nui/utility/assert.hpp +++ b/nui/include/nui/utility/assert.hpp @@ -1,6 +1,10 @@ #pragma once -#include +#if !defined(__cpp_exceptions) && !defined(__EMSCRIPTEN__) +# include +#else +# include +#endif #include @@ -12,8 +16,11 @@ namespace Nui { #ifdef __cpp_exceptions throw std::runtime_error(message); -#else +#elif defined(__EMSCRIPTEN__) Nui::val::global("console").call("error", message); +#else + std::cerr << message << std::endl; + std::terminate(); #endif } } diff --git a/nui/include/nui/utility/iterator_accessor.hpp b/nui/include/nui/utility/iterator_accessor.hpp index 36161b2a..49df1849 100644 --- a/nui/include/nui/utility/iterator_accessor.hpp +++ b/nui/include/nui/utility/iterator_accessor.hpp @@ -1,11 +1,11 @@ #pragma once #include - +#include namespace Nui { template - struct IteratorAccessor + class IteratorAccessor { public: using IteratorType = typename ContainerT::iterator; @@ -48,6 +48,94 @@ namespace Nui ContainerT* container_; }; + template + class IteratorAccessor> + { + public: + using IteratorType = typename ContainerT::iterator; + using ConstIteratorType = typename ContainerT::const_iterator; + + explicit IteratorAccessor(std::weak_ptr container) + : container_{std::move(container)} + {} + ~IteratorAccessor() = default; + IteratorAccessor(IteratorAccessor const&) = default; + IteratorAccessor(IteratorAccessor&&) = default; + IteratorAccessor& operator=(IteratorAccessor const&) = default; + IteratorAccessor& operator=(IteratorAccessor&&) = default; + + ConstIteratorType begin() const + requires(std::is_const_v) + { + return container_.lock()->begin(); + } + + ConstIteratorType end() const + requires(std::is_const_v) + { + return container_.lock()->end(); + } + + IteratorType begin() const + requires(!std::is_const_v) + { + return container_.lock()->begin(); + } + + IteratorType end() const + requires(!std::is_const_v) + { + return container_.lock()->end(); + } + + private: + std::weak_ptr container_; + }; + + template + class IteratorAccessor> + { + public: + using IteratorType = typename ContainerT::iterator; + using ConstIteratorType = typename ContainerT::const_iterator; + + explicit IteratorAccessor(std::weak_ptr container) + : container_{std::move(container)} + {} + ~IteratorAccessor() = default; + IteratorAccessor(IteratorAccessor const&) = default; + IteratorAccessor(IteratorAccessor&&) = default; + IteratorAccessor& operator=(IteratorAccessor const&) = default; + IteratorAccessor& operator=(IteratorAccessor&&) = default; + + ConstIteratorType begin() const + requires(std::is_const_v) + { + return container_->begin(); + } + + ConstIteratorType end() const + requires(std::is_const_v) + { + return container_->end(); + } + + IteratorType begin() const + requires(!std::is_const_v) + { + return container_->begin(); + } + + IteratorType end() const + requires(!std::is_const_v) + { + return container_->end(); + } + + private: + std::shared_ptr container_; + }; + // Deduction guide for const ContainerT template IteratorAccessor(ContainerT const&) -> IteratorAccessor; diff --git a/nui/include/nui/utility/reverse_view.hpp b/nui/include/nui/utility/reverse_view.hpp new file mode 100644 index 00000000..cd7f17cf --- /dev/null +++ b/nui/include/nui/utility/reverse_view.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace Nui +{ + template + class reverse_view + { + public: + reverse_view(T& container) + : container_{container} + {} + + auto begin() const + { + return std::rbegin(container_); + } + + auto end() const + { + return std::rend(container_); + } + + private: + T& container_; + }; +} \ No newline at end of file diff --git a/nui/src/nui/backend/filesystem/special_paths.cpp b/nui/src/nui/backend/filesystem/special_paths.cpp index d10591f5..d5851f02 100644 --- a/nui/src/nui/backend/filesystem/special_paths.cpp +++ b/nui/src/nui/backend/filesystem/special_paths.cpp @@ -31,6 +31,10 @@ namespace Nui { return getSpecialPath(CSIDL_APPDATA); } + std::filesystem::path getDocuments() + { + return getSpecialPath(CSIDL_MYDOCUMENTS); + } std::filesystem::path getLocalAppData() { return getSpecialPath(CSIDL_LOCAL_APPDATA); @@ -67,6 +71,27 @@ namespace Nui { return "/tmp"; } + std::filesystem::path getXdgConfigHome() + { + auto const* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + if (xdgConfigHome != nullptr) + return {xdgConfigHome}; + return getHome() / ".config"; + } + std::filesystem::path getXdgStateHome() + { + auto const* xdgStateHome = getenv("XDG_STATE_HOME"); + if (xdgStateHome != nullptr) + return {xdgStateHome}; + return getHome() / ".local/state"; + } + std::filesystem::path getXdgDataHome() + { + auto const* xdgDataHome = getenv("XDG_DATA_HOME"); + if (xdgDataHome != nullptr) + return {xdgDataHome}; + return getHome() / ".local/share"; + } #endif } @@ -106,6 +131,45 @@ namespace Nui return path; if (tryAndMap("%temp%", getTemp)) return path; +#ifdef _WIN32 + if (tryAndMap("%config_home%", getAppData)) + return path; + if (tryAndMap("%config_home2%", getDocuments)) + return path; + if (tryAndMap("%config_home3%", getHome)) + return path; + if (tryAndMap("%state_home%", getAppData)) + return path; + if (tryAndMap("%state_home2%", getDocuments)) + return path; + if (tryAndMap("%state_home3%", getHome)) + return path; + if (tryAndMap("%data_home%", getAppData)) + return path; + if (tryAndMap("%data_home2%", getDocuments)) + return path; + if (tryAndMap("%data_home3%", getHome)) + return path; +#else + if (tryAndMap("%config_home%", getXdgConfigHome)) + return path; + if (tryAndMap("%config_home2%", getXdgConfigHome)) + return path; + if (tryAndMap("%config_home3%", getXdgConfigHome)) + return path; + if (tryAndMap("%state_home%", getXdgStateHome)) + return path; + if (tryAndMap("%state_home2%", getXdgStateHome)) + return path; + if (tryAndMap("%state_home3%", getXdgStateHome)) + return path; + if (tryAndMap("%data_home%", getXdgDataHome)) + return path; + if (tryAndMap("%data_home2%", getXdgDataHome)) + return path; + if (tryAndMap("%data_home3%", getXdgDataHome)) + return path; +#endif return path; } diff --git a/nui/src/nui/frontend/attributes/impl/attribute.cpp b/nui/src/nui/frontend/attributes/impl/attribute.cpp index 99a9d1c2..b8594c57 100644 --- a/nui/src/nui/frontend/attributes/impl/attribute.cpp +++ b/nui/src/nui/frontend/attributes/impl/attribute.cpp @@ -15,7 +15,7 @@ namespace Nui { if (createEvent_) return createEvent_(std::move(element)); - return EventContext::EventIdType{}; + return EventContext::invalidEventId; } //--------------------------------------------------------------------------------------------------------------------- std::function Attribute::getEventClear() const diff --git a/nui/test/nui/CMakeLists.txt b/nui/test/nui/CMakeLists.txt index 4e10955e..74221d20 100644 --- a/nui/test/nui/CMakeLists.txt +++ b/nui/test/nui/CMakeLists.txt @@ -31,7 +31,13 @@ add_executable(nui-tests target_link_libraries(nui-tests PRIVATE nui-frontend-mocked gtest + gmock ) + +if (INT_TREE_DRAW_EXAMPLES) + target_link_libraries(nui-tests PRIVATE cairo cairo-wrap) +endif() + gtest_discover_tests(nui-tests) set_target_properties(nui-tests diff --git a/nui/test/nui/common_test_fixture.hpp b/nui/test/nui/common_test_fixture.hpp index 031c798c..7887bfc7 100644 --- a/nui/test/nui/common_test_fixture.hpp +++ b/nui/test/nui/common_test_fixture.hpp @@ -7,6 +7,10 @@ #include +#include +#include +#include + namespace Nui::Tests { class CommonTestFixture : public ::testing::Test @@ -39,6 +43,54 @@ namespace Nui::Tests }; }; + template