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));
488 auto revision = std::make_shared<std::size_t>(0);
492 auto sub = src.on_changed(
493 [adapter, dispatcher, policy, &view, projection, alive, setter, revision]
496 dispatch_to_view_(adapter, dispatcher, policy, alive,
497 [adapter, &view, projection, alive, setter, value, revision] {
498 const auto before = *revision;
499 auto rendered_value = (*projection)(value);
500 if (is_alive_(alive) && before == *revision)
501 (adapter.get()->*setter)(view, rendered_value);
505 T initial = src.get();
506 const auto before = *revision;
507 auto rendered = (*projection)(initial);
508 if (!is_alive_(alive) || !source_alive_<Src>(source))
return;
509 if (before == *revision) (adapter.get()->*setter)(view, rendered);
510 if (is_alive_(alive) && source_alive_<Src>(source)) add_view_sub_(view, std::move(sub));
513 template<
typename T,
typename Src,
typename Setter>
514 void bind_scalar_oneway_(Src& src, IView& view, Setter setter) {
515 bind_projected_(src, view, [](
const T& value) {
return value; }, setter);
518 template<
typename T,
typename U,
class Setter,
class Subscriber>
519 void bind_converted_(Property<T>& prop, IView& view, Converter<T, U> conv,
520 Setter setter, Subscriber subscriber) {
521 auto guard = std::make_shared<bool>(
false);
522 auto alive = ensure_alive_token_(view);
523 auto handle = std::make_shared<reactive::detail::NodeHandle>(&prop);
524 std::weak_ptr<reactive::detail::NodeHandle> model = handle;
527 add_view_sub_(view, Subscription{std::move(handle)});
528 auto adapter = adapter_;
529 auto dispatcher = dispatcher_;
530 const auto policy = policy_;
531 auto converter = std::make_shared<Converter<T, U>>(std::move(conv));
532 auto revision = std::make_shared<std::size_t>(0);
533 auto property_sub = prop.on_changed(
534 [adapter, dispatcher, policy, &view, converter,
535 guard, alive, model, setter, revision](
const T& value) {
537 dispatch_to_view_(adapter, dispatcher, policy, alive,
538 [adapter, &view, converter, guard, alive, model, setter, value, revision] {
539 if (!model_alive_(model))
return;
541 const auto before = *revision;
542 auto converted = converter->to_view(value);
543 if (!is_alive_(alive) || !model_alive_(model) || before != *revision)
return;
544 (adapter.get()->*setter)(view, converted);
547 T initial = prop.get();
548 const auto before = *revision;
549 auto rendered = converter->to_view(initial);
550 if (!is_alive_(alive) || !model_alive_(model))
return;
551 if (before == *revision) {
553 (adapter.get()->*setter)(view, rendered);
555 if (!is_alive_(alive) || !model_alive_(model))
return;
556 add_view_sub_(view, std::move(property_sub));
557 auto view_sub = (adapter.get()->*subscriber)(view,
558 [&prop, converter, guard, alive, model,
559 dispatcher, policy](
auto native_value) {
560 dispatch_to_model_(dispatcher, policy, alive, guard,
561 [&prop, converter, alive, model, value = U{native_value}] {
562 if (!model_alive_(model))
return;
564 std::optional<T> parsed = converter->try_to_model
565 ? converter->try_to_model(value)
566 : std::optional<T>{converter->to_model(value)};
569 if (!is_alive_(alive) || !model_alive_(model))
return;
570 if (parsed) prop.set(std::move(*parsed));
571 else ::aria::report_callback_failure(
"binding.converter",
nullptr,
572 "converter.try_to_model rejected input");
578 if (is_alive_(alive)) add_view_sub_(view, std::move(view_sub));
581 template<
typename T,
typename Setter,
typename Subscriber,
typename ToModel>
582 void bind_scalar_two_way_(Property<T>& prop, IView& view,
583 Setter setter, Subscriber subscriber, ToModel to_model) {
585 bind_converted_(prop, view,
586 Converter<T, T>{[](
const T& value) {
return value; },
587 [to_model](
const T& value) {
return to_model(value); }, {}},
596 explicit GuardFlag(
bool& s) noexcept : slot(s), previous(s) { slot =
true; }
597 ~GuardFlag() { slot = previous; }
598 GuardFlag(
const GuardFlag&) =
delete;
599 GuardFlag& operator=(
const GuardFlag&) =
delete;
604 std::vector<Subscription> subscriptions;
605 Subscription destroy_listener;
607 using AliveToken = std::weak_ptr<ViewBucket>;
609 static bool is_alive_(
const AliveToken& token)
noexcept {
610 const auto state = token.lock();
611 return state && state->active;
613 static bool model_alive_(
const std::weak_ptr<reactive::detail::NodeHandle>& token)
noexcept {
614 const auto handle = token.lock();
615 return handle && bool(*handle);
619 AliveToken ensure_alive_token_(IView& view);
626 static void dispatch_to_model_(
627 const std::shared_ptr<runtime::IDispatcher>& dispatcher,
628 DispatchPolicy policy, AliveToken alive_token,
629 std::shared_ptr<bool> guard, Fn&& fn) {
630 const bool direct = !dispatcher || policy == DispatchPolicy::Direct;
631 const bool on_graph_thread = direct || dispatcher->is_main_thread();
637 if (on_graph_thread && guard && *guard)
return;
639 auto invoke = [alive_token, guard = std::move(guard),
640 fn = std::forward<Fn>(fn)]()
mutable {
641 if (!is_alive_(alive_token) || (guard && *guard))
return;
645 if (direct || (policy == DispatchPolicy::SmartMarshal && on_graph_thread)) {
648 dispatcher->post(std::move(invoke));
659 static void trace_binding_(std::string_view platform, std::string_view operation)
noexcept {
669 static void dispatch_to_view_(std::shared_ptr<IViewAdapter> adapter,
670 std::shared_ptr<runtime::IDispatcher> dispatcher,
671 DispatchPolicy policy, AliveToken alive, Fn&& fn) {
672 auto invoke = [adapter = std::move(adapter), alive,
673 fn = std::forward<Fn>(fn)]()
mutable {
674 if (!is_alive_(alive)) {
680 if (is_alive_(alive)) fn();
682 if (!dispatcher || policy == DispatchPolicy::Direct ||
683 (policy == DispatchPolicy::SmartMarshal && dispatcher->is_main_thread())) {
686 dispatcher->post(std::move(invoke));
690 void add_view_sub_(IView& view, Subscription sub);
692 std::shared_ptr<ViewBucket> bucket_for_(IView& view);
694 std::shared_ptr<IViewAdapter> adapter_;
695 std::shared_ptr<runtime::IDispatcher> dispatcher_;
696 DispatchPolicy policy_ = DispatchPolicy::Direct;
697 std::unordered_map<const IView*, std::shared_ptr<ViewBucket>> per_view_;
698 bool closing_ =
false;