52#include <unordered_map>
85 std::shared_ptr<ViewModel> vm;
88 std::string route_path;
93 std::function<void(std::optional<std::any>)> result_setter;
96 NavEntry(
const NavEntry&) =
delete;
97 NavEntry& operator=(
const NavEntry&) =
delete;
98 NavEntry(NavEntry&&) noexcept = default;
99 NavEntry& operator=(NavEntry&&) noexcept = default;
119 template<
typename VM,
typename... Args>
120 requires std::derived_from<VM, ViewModel>
121 std::shared_ptr<VM>
push(Args&&... args) {
122 auto vm = std::make_shared<VM>(std::forward<Args>(args)...);
127 template<
typename VM,
typename... Args>
128 requires std::derived_from<VM, ViewModel>
130 auto vm = std::make_shared<VM>(std::forward<Args>(args)...);
137 void push(std::shared_ptr<ViewModel> vm,
139 if (!vm)
throw std::invalid_argument(
"Navigator::push: vm is null");
142 e.vm = std::move(vm);
144 stack_.push_back(std::move(e));
145 stack_.back().vm->activate();
153 if (!vm)
throw std::invalid_argument(
"Navigator::replace: vm is null");
154 if (!stack_.empty()) {
155 tear_down_top_(std::nullopt);
158 e.vm = std::move(vm);
159 stack_.push_back(std::move(e));
160 stack_.back().vm->activate();
168 template<
typename R,
typename VM,
typename... Args>
169 requires std::derived_from<VM, ViewModel>
170 [[nodiscard]] std::shared_future<std::optional<R>>
172 auto vm = std::make_shared<VM>(std::forward<Args>(args)...);
177 [[nodiscard]] std::shared_future<std::optional<R>>
180 if (!vm)
throw std::invalid_argument(
181 "Navigator::push_for_result: vm is null");
183 auto promise = std::make_shared<std::promise<std::optional<R>>>();
184 std::shared_future<std::optional<R>> fut =
185 promise->get_future().share();
189 e.vm = std::move(vm);
197 e.result_setter = [p](std::optional<std::any> payload) {
199 if (!payload.has_value()) {
200 p->set_value(std::nullopt);
202 p->set_value(std::any_cast<R>(*payload));
204 }
catch (
const std::bad_any_cast&) {
209 try { p->set_value(std::nullopt); }
catch (...) {}
215 stack_.push_back(std::move(e));
216 stack_.back().vm->activate();
228 if (stack_.empty())
return false;
229 std::optional<std::any> payload{std::any{std::move(result)}};
230 tear_down_top_(std::move(payload));
231 if (!stack_.empty()) stack_.back().vm->activate();
239 if (stack_.empty())
return false;
240 tear_down_top_(std::nullopt);
241 if (!stack_.empty()) stack_.back().vm->activate();
258 while (stack_.size() > 1) {
259 tear_down_top_(std::nullopt);
261 if (!stack_.empty()) stack_.back().vm->activate();
266 while (!stack_.empty()) {
267 tear_down_top_(std::nullopt);
272 [[nodiscard]]
bool empty() const noexcept {
return stack_.empty(); }
273 [[nodiscard]] std::size_t
size() const noexcept {
return stack_.size(); }
275 [[nodiscard]] std::shared_ptr<ViewModel>
at(std::size_t i)
const {
276 return stack_.at(i).vm;
283 if (stack_.empty()) {
284 throw std::out_of_range(
"Navigator::top_token: stack is empty");
286 return stack_.back().cancel.token();
291 if (stack_.empty()) {
292 throw std::out_of_range(
293 "Navigator::top_presentation: stack is empty");
295 return stack_.back().kind;
309 throw std::invalid_argument(
310 "Navigator::register_route: factory is null");
312 routes_.emplace(std::move(pattern), std::move(factory));
319 for (
const auto& [pattern, factory] : routes_) {
321 if (match_route_(pattern, path, params)) {
322 auto vm = factory(params);
323 if (!vm)
return false;
324 if (opts.clear_stack) {
327 push(std::move(vm), opts.presentation);
328 if (!stack_.empty()) {
329 stack_.back().route_path = std::string(path);
339 current = stack_.empty() ? nullptr : stack_.back().vm;
340 depth = stack_.size();
343 void deactivate_top_() {
344 if (!stack_.empty()) {
345 auto vm = stack_.back().vm;
354 void tear_down_top_(std::optional<std::any> result_payload) {
355 if (stack_.empty())
return;
358 auto e = std::move(stack_.back());
360 try { e.vm->deactivate(); }
362 if (e.result_setter) {
363 try { e.result_setter(std::move(result_payload)); }
catch (...) {}
365 try { e.cancel.cancel(); }
catch (...) {}
371 static bool match_route_(std::string_view pattern,
372 std::string_view path,
374 auto split = [](std::string_view s) {
375 std::vector<std::string_view> segs;
376 std::size_t start = 0;
377 for (std::size_t i = 0; i <= s.size(); ++i) {
378 if (i == s.size() || s[i] ==
'/') {
379 if (i > start) segs.push_back(s.substr(start, i - start));
385 auto pat_segs = split(pattern);
386 auto path_segs = split(path);
387 if (pat_segs.size() != path_segs.size())
return false;
388 for (std::size_t i = 0; i < pat_segs.size(); ++i) {
389 const auto& ps = pat_segs[i];
390 if (ps.size() >= 2 && ps.front() ==
'{' && ps.back() ==
'}') {
391 if (path_segs[i].empty())
return false;
392 out.emplace(std::string(ps.substr(1, ps.size() - 2)),
393 std::string(path_segs[i]));
394 }
else if (ps != path_segs[i]) {
401 std::vector<detail::NavEntry> stack_;
402 std::unordered_map<std::string, RouteFactory> routes_;
Definition cancellation.hpp:218
Definition cancellation.hpp:182
std::shared_future< std::optional< R > > push_for_result(Args &&... args)
N-2: push a child entry that will eventually return a typed result R to the caller.
Definition navigation.hpp:171
void push(std::shared_ptr< ViewModel > vm, Presentation kind=Presentation::Push)
Push (Presentation::Push).
Definition navigation.hpp:137
std::size_t size() const noexcept
Definition navigation.hpp:273
Property< std::size_t > depth
Total stack depth (modals included).
Definition navigation.hpp:109
void clear()
Definition navigation.hpp:265
bool pop()
Pop the topmost entry.
Definition navigation.hpp:238
std::function< std::shared_ptr< ViewModel >(const RouteParams &)> RouteFactory
Definition navigation.hpp:302
bool dismiss_modal()
N-1: dismiss the topmost MODAL entry.
Definition navigation.hpp:248
bool dismiss_with(R result)
N-2: dismiss the topmost entry with a typed result.
Definition navigation.hpp:227
void pop_to_root()
Pop until only one Push entry remains.
Definition navigation.hpp:257
std::unordered_map< std::string, std::string > RouteParams
Param map captured from a route pattern (e.g. {id}).
Definition navigation.hpp:301
std::shared_ptr< ViewModel > at(std::size_t i) const
Definition navigation.hpp:275
Property< std::shared_ptr< ViewModel > > current
Topmost entry's VM, or nullptr if the stack is empty.
Definition navigation.hpp:107
std::shared_ptr< VM > replace(Args &&... args)
Definition navigation.hpp:129
void replace(std::shared_ptr< ViewModel > vm)
Replace the topmost entry (in place).
Definition navigation.hpp:152
Presentation top_presentation() const
Topmost entry's presentation kind.
Definition navigation.hpp:290
aria::async::CancellationToken top_token() const
Topmost entry's cancellation token.
Definition navigation.hpp:282
~Navigator()
Definition navigation.hpp:112
std::shared_ptr< VM > push(Args &&... args)
Definition navigation.hpp:121
Navigator & operator=(const Navigator &)=delete
void register_route(std::string pattern, RouteFactory factory)
Register a route pattern.
Definition navigation.hpp:307
Navigator(const Navigator &)=delete
bool empty() const noexcept
Definition navigation.hpp:272
bool route(std::string_view path, RouteOptions opts={})
Resolve path against registered patterns.
Definition navigation.hpp:318
std::shared_future< std::optional< R > > push_for_result(std::shared_ptr< ViewModel > vm, Presentation kind=Presentation::Push)
Definition navigation.hpp:178
Definition property.hpp:103
Definition binding_engine.hpp:25
Presentation
How a navigation entry is presented to the user.
Definition navigation.hpp:64
@ Push
Definition navigation.hpp:65
@ Modal
Definition navigation.hpp:66
void report_callback_failure(std::string_view category, std::exception_ptr exception, std::string_view message={}) noexcept
Report a callback failure.
Routing options for Navigator::route(...).
Definition navigation.hpp:70
Presentation presentation
Presentation kind for the deep-linked entry.
Definition navigation.hpp:75
bool clear_stack
If true, replace the entire stack with the deep-linked entry.
Definition navigation.hpp:73