118 std::shared_ptr<runtime::IDispatcher> ui_dispatcher,
133 [[nodiscard]]
bool has_dispatcher() const noexcept {
return static_cast<bool>(dispatcher_); }
163 template<ReadOnlyReactiveOf<std::
string> Src>
175 template<ReadOnlyReactiveOf<
bool> Src>
187 template<ReadOnlyReactiveOf<
int> Src>
199 template<ReadOnlyReactiveOf<std::
int64_t> Src>
211 template<ReadOnlyReactiveOf<std::u
int64_t> Src>
223 template<ReadOnlyReactiveOf<
float> Src>
235 template<ReadOnlyReactiveOf<
double> Src>
247 template<ReadOnlyReactiveOf<
bool> Src>
254 template<ReadOnlyReactiveOf<
bool> Src>
269 template<ReadOnlyReactive Src>
322 template<ReadOnlyReactive Src,
typename Project>
336 template<ReadOnlyReactiveOptional Src,
typename Project>
340 std::string empty_text = std::string{}) {
341 using Opt =
typename Src::value_type;
342 using T =
typename Opt::value_type;
343 static_assert(std::is_invocable_v<Project&, const T&>,
344 "bind_optional_text: `project` must be callable as "
345 "project(const T&) where the source holds std::optional<T>.");
346 auto render = [project = std::move(project), empty_text = std::move(empty_text)]
347 (
const Opt& opt)
mutable -> std::string {
348 return opt ? project(*opt) : empty_text;
350 bind_projected_(src, view, std::move(render), &IViewAdapter::set_text);
356 template<
typename... Args>
358 auto guard_alive = ensure_alive_token_(view);
359 auto command_lifetime = cmd.lifetime_token_();
361 auto dispatcher = dispatcher_;
362 const auto policy = policy_;
363 auto click =
adapter->on_click(view,
364 [&cmd, args..., guard_alive, command_lifetime, dispatcher, policy]() {
365 dispatch_to_model_(dispatcher, policy, guard_alive, {},
366 [&cmd, args..., command_lifetime] {
367 if (!command_lifetime.expired()) cmd.execute(args...);
370 if (!is_alive_(guard_alive) || command_lifetime.expired())
return;
371 add_view_sub_(view, std::move(click));
380 cmd.observe_can_execute(
381 [&cmd, adapter, &view, guard_alive, command_lifetime,
382 dispatcher, policy, args...](
bool ) {
383 if (!is_alive_(guard_alive) || command_lifetime.expired()) return;
384 const bool can = cmd.can_execute(args...);
385 if (!is_alive_(guard_alive) || command_lifetime.expired()) return;
386 dispatch_to_view_(adapter, dispatcher, policy, guard_alive,
387 [adapter, &view, can]() {
388 adapter->set_enabled(view, can);
391 if (!is_alive_(guard_alive) || command_lifetime.expired())
return;
392 const bool can = cmd.can_execute(args...);
393 if (is_alive_(guard_alive) && !command_lifetime.expired()) adapter->set_enabled(view, can);
429 if (!on_view_destroyed)
return;
433 (void)ensure_alive_token_(view);
434 add_view_sub_(view,
Subscription{[callback = std::move(on_view_destroyed)] {
459 (void)ensure_alive_token_(view);
460 add_view_sub_(view, std::move(s));
468 static
reactive::detail::NodeHandle source_handle_(Src& source) noexcept {
469 if constexpr (std::derived_from<Src, reactive::Node>)
return reactive::detail::NodeHandle{&source};
474 static bool source_alive_(
const reactive::detail::NodeHandle& handle)
noexcept {
475 if constexpr (std::derived_from<Src, reactive::Node>)
return bool(handle);
479 template<
class Src,
class Project,
class Setter>
480 void bind_projected_(Src& src, IView& view, Project project, Setter setter) {
481 using T =
typename Src::value_type;
482 auto alive = ensure_alive_token_(view);
483 auto source = source_handle_(src);
484 auto adapter = adapter_;
485 auto dispatcher = dispatcher_;
486 const auto policy = policy_;
487 auto projection = std::make_shared<Project>(std::move(project));
489 T initial = src.get();
490 auto rendered = (*projection)(initial);
491 if (!is_alive_(alive) || !source_alive_<Src>(source))
return;
492 (adapter.get()->*setter)(view, rendered);
493 if (!is_alive_(alive) || !source_alive_<Src>(source))
return;
494 auto sub = src.on_changed(
495 [adapter, dispatcher, policy, &view, projection, alive, setter]
497 dispatch_to_view_(adapter, dispatcher, policy, alive,
498 [adapter, &view, projection, alive, setter, value] {
499 auto rendered_value = (*projection)(value);
500 if (is_alive_(alive)) (adapter.get()->*setter)(view, rendered_value);
503 if (is_alive_(alive)) add_view_sub_(view, std::move(sub));
506 template<
typename T,
typename Src,
typename Setter>
507 void bind_scalar_oneway_(Src& src, IView& view, Setter setter) {
508 bind_projected_(src, view, [](
const T& value) {
return value; }, setter);
511 template<
typename T,
typename U,
class Setter,
class Subscriber>
512 void bind_converted_(Property<T>& prop, IView& view, Converter<T, U> conv,
513 Setter setter, Subscriber subscriber) {
514 auto guard = std::make_shared<bool>(
false);
515 auto alive = ensure_alive_token_(view);
516 auto handle = std::make_shared<reactive::detail::NodeHandle>(&prop);
517 std::weak_ptr<reactive::detail::NodeHandle> model = handle;
520 add_view_sub_(view, Subscription{std::move(handle)});
521 auto adapter = adapter_;
522 auto dispatcher = dispatcher_;
523 const auto policy = policy_;
524 auto converter = std::make_shared<Converter<T, U>>(std::move(conv));
525 T initial = prop.get();
526 auto rendered = converter->to_view(initial);
527 if (!is_alive_(alive) || !model_alive_(model))
return;
530 (adapter.get()->*setter)(view, rendered);
532 if (!is_alive_(alive) || !model_alive_(model))
return;
533 auto property_sub = prop.on_changed(
534 [adapter, dispatcher, policy, &view, converter,
535 guard, alive, model, setter](
const T& value) {
536 dispatch_to_view_(adapter, dispatcher, policy, alive,
537 [adapter, &view, converter, guard, alive, model, setter, value] {
538 if (!model_alive_(model))
return;
540 auto converted = converter->to_view(value);
541 if (!is_alive_(alive) || !model_alive_(model))
return;
542 (adapter.get()->*setter)(view, converted);
545 if (!is_alive_(alive))
return;
546 add_view_sub_(view, std::move(property_sub));
547 auto view_sub = (adapter.get()->*subscriber)(view,
548 [&prop, converter, guard, alive, model,
549 dispatcher, policy](
auto native_value) {
550 dispatch_to_model_(dispatcher, policy, alive, guard,
551 [&prop, converter, alive, model, value = U{native_value}] {
552 if (!model_alive_(model))
return;
554 std::optional<T> parsed = converter->try_to_model
555 ? converter->try_to_model(value)
556 : std::optional<T>{converter->to_model(value)};
559 if (!is_alive_(alive) || !model_alive_(model))
return;
560 if (parsed) prop.set(std::move(*parsed));
561 else ::aria::report_callback_failure(
"binding.converter",
nullptr,
562 "converter.try_to_model rejected input");
568 if (is_alive_(alive)) add_view_sub_(view, std::move(view_sub));
571 template<
typename T,
typename Setter,
typename Subscriber,
typename ToModel>
572 void bind_scalar_two_way_(Property<T>& prop, IView& view,
573 Setter setter, Subscriber subscriber, ToModel to_model) {
575 bind_converted_(prop, view,
576 Converter<T, T>{[](
const T& value) {
return value; },
577 [to_model](
const T& value) {
return to_model(value); }, {}},
586 explicit GuardFlag(
bool& s) noexcept : slot(s), previous(s) { slot =
true; }
587 ~GuardFlag() { slot = previous; }
588 GuardFlag(
const GuardFlag&) =
delete;
589 GuardFlag& operator=(
const GuardFlag&) =
delete;
594 std::vector<Subscription> subscriptions;
595 Subscription destroy_listener;
597 using AliveToken = std::weak_ptr<ViewBucket>;
599 static bool is_alive_(
const AliveToken& token)
noexcept {
600 const auto state = token.lock();
601 return state && state->active;
603 static bool model_alive_(
const std::weak_ptr<reactive::detail::NodeHandle>& token)
noexcept {
604 const auto handle = token.lock();
605 return handle && bool(*handle);
609 AliveToken ensure_alive_token_(IView& view);
616 static void dispatch_to_model_(
617 const std::shared_ptr<runtime::IDispatcher>& dispatcher,
618 DispatchPolicy policy, AliveToken alive_token,
619 std::shared_ptr<bool> guard, Fn&& fn) {
620 const bool direct = !dispatcher || policy == DispatchPolicy::Direct;
621 const bool on_graph_thread = direct || dispatcher->is_main_thread();
627 if (on_graph_thread && guard && *guard)
return;
629 auto invoke = [alive_token, guard = std::move(guard),
630 fn = std::forward<Fn>(fn)]()
mutable {
631 if (!is_alive_(alive_token) || (guard && *guard))
return;
635 if (direct || (policy == DispatchPolicy::SmartMarshal && on_graph_thread)) {
638 dispatcher->post(std::move(invoke));
649 static void trace_binding_(std::string_view platform, std::string_view operation)
noexcept {
659 static void dispatch_to_view_(std::shared_ptr<IViewAdapter> adapter,
660 std::shared_ptr<runtime::IDispatcher> dispatcher,
661 DispatchPolicy policy, AliveToken alive, Fn&& fn) {
662 auto invoke = [adapter = std::move(adapter), alive,
663 fn = std::forward<Fn>(fn)]()
mutable {
664 if (!is_alive_(alive)) {
670 if (is_alive_(alive)) fn();
672 if (!dispatcher || policy == DispatchPolicy::Direct ||
673 (policy == DispatchPolicy::SmartMarshal && dispatcher->is_main_thread())) {
676 dispatcher->post(std::move(invoke));
680 void add_view_sub_(IView& view, Subscription sub);
682 std::shared_ptr<ViewBucket> bucket_for_(IView& view);
684 std::shared_ptr<IViewAdapter> adapter_;
685 std::shared_ptr<runtime::IDispatcher> dispatcher_;
686 DispatchPolicy policy_ = DispatchPolicy::Direct;
687 std::unordered_map<const IView*, std::shared_ptr<ViewBucket>> per_view_;
688 bool closing_ =
false;