Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
binding_engine.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "aria/abi/export.hpp"
5#include "aria/command.hpp"
6#include "aria/concepts.hpp"
8#include "aria/property.hpp"
10#include "aria/subscription.hpp"
13
14#include <exception>
15#include <functional>
16#include <memory>
17#include <optional>
18#include <string>
19#include <string_view>
20#include <type_traits>
21#include <unordered_map>
22#include <utility>
23#include <vector>
24
25namespace aria::binding {
26
99#ifdef _MSC_VER
100#pragma warning(push)
101#pragma warning(disable: 4251)
102#endif
104public:
106 enum class DispatchPolicy {
107 Direct,
108 SmartMarshal,
109 AlwaysPost,
110 };
111
113 explicit BindingEngine(std::shared_ptr<IViewAdapter> adapter);
114
117 BindingEngine(std::shared_ptr<IViewAdapter> adapter,
118 std::shared_ptr<runtime::IDispatcher> ui_dispatcher,
120
124
125 BindingEngine(const BindingEngine&) = delete;
129
130 [[nodiscard]] IViewAdapter& adapter() noexcept { return *adapter_; }
131
132 [[nodiscard]] DispatchPolicy dispatch_policy() const noexcept { return policy_; }
133 [[nodiscard]] bool has_dispatcher() const noexcept { return static_cast<bool>(dispatcher_); }
134
135 // ══════════════════════════════════════════════════════════════════
136 // One-way (VM→View) bindings accept any read-only reactive source
137 //
138 // Every `bind_*_oneway` / `bind_visible` / `bind_enabled` comes in
139 // two flavours:
140 //
141 // * a non-template `Property<T>&` overload — the original,
142 // exported-from-the-library entry point, unchanged;
143 // * a template overload constrained on `ReadOnlyReactiveOf<S, T>`,
144 // which additionally accepts `Computed<T>`.
145 //
146 // Both do exactly the same thing (they share one private
147 // implementation). The split exists so the shipped symbols keep
148 // their ABI while `Computed` becomes bindable: passing a
149 // `Property<T>` still selects the non-template overload, because a
150 // non-template beats a template when the conversion sequences tie.
151 //
152 // Two-way binders stay `Property<T>&`-only on purpose. A derived
153 // value has no write-back path, so `bind_text(some_computed, view)`
154 // must remain a **compile error** rather than a silently dropped
155 // edit.
156 // ══════════════════════════════════════════════════════════════════
157
158 // ══════════════════════════════════════════════════════════════════
159 // Text
160 // ══════════════════════════════════════════════════════════════════
162
163 template<ReadOnlyReactiveOf<std::string> Src>
164 void bind_text_oneway(Src& src, IView& view) {
165 bind_scalar_oneway_<std::string>(src, view, &IViewAdapter::set_text);
166 }
167
169
170 // ══════════════════════════════════════════════════════════════════
171 // Bool (checkbox / switch)
172 // ══════════════════════════════════════════════════════════════════
174
175 template<ReadOnlyReactiveOf<bool> Src>
176 void bind_bool_oneway(Src& src, IView& view) {
177 bind_scalar_oneway_<bool>(src, view, &IViewAdapter::set_bool);
178 }
179
180 void bind_bool(Property<bool>& prop, IView& view);
181
182 // ══════════════════════════════════════════════════════════════════
183 // Int (slider / spinbox / progress bar)
184 // ══════════════════════════════════════════════════════════════════
186
187 template<ReadOnlyReactiveOf<int> Src>
188 void bind_int_oneway(Src& src, IView& view) {
189 bind_scalar_oneway_<int>(src, view, &IViewAdapter::set_int);
190 }
191
192 void bind_int(Property<int>& prop, IView& view);
193
194 // ══════════════════════════════════════════════════════════════════
195 // Int64 (timestamps, IDs, 64-bit counters)
196 // ══════════════════════════════════════════════════════════════════
198
199 template<ReadOnlyReactiveOf<std::int64_t> Src>
200 void bind_int64_oneway(Src& src, IView& view) {
201 bind_scalar_oneway_<std::int64_t>(src, view, &IViewAdapter::set_int64);
202 }
203
205
206 // ══════════════════════════════════════════════════════════════════
207 // UInt64 (raw handles, non-negative counters)
208 // ══════════════════════════════════════════════════════════════════
210
211 template<ReadOnlyReactiveOf<std::uint64_t> Src>
212 void bind_uint64_oneway(Src& src, IView& view) {
213 bind_scalar_oneway_<std::uint64_t>(src, view, &IViewAdapter::set_uint64);
214 }
215
217
218 // ══════════════════════════════════════════════════════════════════
219 // Float (UISlider, CALayer opacity)
220 // ══════════════════════════════════════════════════════════════════
222
223 template<ReadOnlyReactiveOf<float> Src>
224 void bind_float_oneway(Src& src, IView& view) {
225 bind_scalar_oneway_<float>(src, view, &IViewAdapter::set_float);
226 }
227
228 void bind_float(Property<float>& prop, IView& view);
229
230 // ══════════════════════════════════════════════════════════════════
231 // Double (QDoubleSpinBox)
232 // ══════════════════════════════════════════════════════════════════
234
235 template<ReadOnlyReactiveOf<double> Src>
236 void bind_double_oneway(Src& src, IView& view) {
237 bind_scalar_oneway_<double>(src, view, &IViewAdapter::set_double);
238 }
239
241
242 // ══════════════════════════════════════════════════════════════════
243 // Visible / Enabled — inherently one-way
244 // ══════════════════════════════════════════════════════════════════
246
247 template<ReadOnlyReactiveOf<bool> Src>
248 void bind_visible(Src& src, IView& view) {
249 bind_scalar_oneway_<bool>(src, view, &IViewAdapter::set_visible);
250 }
251
253
254 template<ReadOnlyReactiveOf<bool> Src>
255 void bind_enabled(Src& src, IView& view) {
256 bind_scalar_oneway_<bool>(src, view, &IViewAdapter::set_enabled);
257 }
258
259 // ══════════════════════════════════════════════════════════════════
260 // Converter-based bindings (non-string model types → text view)
261 //
262 // Use when your ViewModel exposes e.g. `Property<int>` but the View is
263 // a QLineEdit / QLabel. Provide a Converter<T, std::string>.
264 //
265 // The one-way form accepts any `ReadOnlyReactive` source, so a
266 // `Computed<T>` can feed a converted label. The two-way form stays
267 // `Property<T>&`-only — it writes back.
268 // ══════════════════════════════════════════════════════════════════
269 template<ReadOnlyReactive Src>
271 IView& view,
272 Converter<typename Src::value_type,
273 std::string> conv) {
274 bind_projected_(src, view, std::move(conv.to_view), &IViewAdapter::set_text);
275 }
276
277 template<typename T>
280 bind_converted_(prop, view, std::move(conv), &IViewAdapter::set_text,
282 }
283
288 template<typename T>
290 bind_converted_(prop, view, std::move(conv), &IViewAdapter::set_int,
292 }
293
294 // ══════════════════════════════════════════════════════════════════
295 // Projected one-way text bindings (read-only labels)
296 //
297 // A read-only label rarely wants the full bidirectional `Converter`
298 // machinery of `bind_text_converted` — it only ever renders VM→View
299 // and never parses text back. These two helpers take a plain
300 // projection functor `T -> std::string` and wire the one-way path,
301 // collapsing the hand-written `prop.on_changed([lbl]{ ... })` +
302 // initial-sync boilerplate that otherwise piles up in every view.
303 //
304 // They accept any read-only reactive source (`Property<T>` or
305 // `Computed<T>`) and are completely async-agnostic: the same call
306 // binds an `AsyncCommand`'s `last_error_message` / `last_result`
307 // projections, a `Computed`'s formatted output, or any other
308 // model-owned value — without this engine ever naming an
309 // `aria-async` type (see the `bind_view_lifetime` note on that
310 // deliberate API-level decoupling).
311 // ══════════════════════════════════════════════════════════════════
312
322 template<ReadOnlyReactive Src, typename Project>
323 void bind_text_projected(Src& src, IView& view, Project project) {
324 bind_projected_(src, view, std::move(project), &IViewAdapter::set_text);
325 }
326
336 template<ReadOnlyReactiveOptional Src, typename Project>
337 void bind_optional_text(Src& src,
338 IView& view,
339 Project project,
340 std::string empty_text = std::string{}) {
341 using Opt = typename Src::value_type; // std::optional<T>
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;
349 };
350 bind_projected_(src, view, std::move(render), &IViewAdapter::set_text);
351 }
352
353 // ═════════════════════════════════════════════════════════════════
354 // Command
355 // ═════════════════════════════════════════════════════════════════
356 template<typename... Args>
357 void bind_command(Command<Args...>& cmd, IView& view, const Args&... args) {
358 auto guard_alive = ensure_alive_token_(view);
359 auto command_lifetime = cmd.lifetime_token_();
360 auto adapter = adapter_;
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...);
368 });
369 });
370 if (!is_alive_(guard_alive) || command_lifetime.expired()) return;
371 add_view_sub_(view, std::move(click));
372 // The signal carries whatever truth value the publisher chose
373 // (e.g. `notify_can_execute_changed(other_args...)`). For bound
374 // buttons we want the enabled state to track *these specific
375 // args* — recompute via `cmd.can_execute(args...)` on every
376 // notification and ignore the wire payload. This restores the
377 // contract that `bind_command(cmd, view, args)` keeps the view
378 // in sync with `cmd.can_execute(args...)`.
379 add_view_sub_(view,
380 cmd.observe_can_execute(
381 [&cmd, adapter, &view, guard_alive, command_lifetime,
382 dispatcher, policy, args...](bool /*payload*/) {
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);
389 });
390 }));
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);
394 }
395
396 // ═════════════════════════════════════════════════════════════════
397 // View lifetime hook (async cancellation, resource teardown, ...)
398 // ═════════════════════════════════════════════════════════════════
399 //
400 // Register a callback that fires exactly once when `view` is destroyed
401 // (its `IView::on_destroy` fans out and the engine clears the view's
402 // subscription bucket) OR when the engine itself is destroyed / cleared
403 // — whichever comes first. The callback runs on whatever thread tears
404 // the view down (the UI thread, by the IView contract).
405 //
406 // This is the async-agnostic primitive behind "view-destroy
407 // cancellation": `BindingEngine` deliberately never names an
408 // `AsyncCommand` type (it takes a plain `std::function<void()>`), so
409 // instead of teaching BindingEngine about `AsyncCommand`, callers wire
410 // the two together themselves — even though the `binding` module as a
411 // whole does link `aria-async` for `ViewModelScope` / `Navigation` —
412 //
413 // AsyncCommand<void> load{ui, [](CancellationToken t) -> Task<void>{
414 // co_await fetch(t); // cooperative cancel point
415 // }};
416 // engine.bind_command(load.trigger(), view); // click → execute
417 // engine.bind_view_lifetime(view, [&load]{
418 // load.cancel_all_in_flight(); // view gone → cancel request
419 // });
420 //
421 // Now navigating away mid-request (destroying the sub-view) fires the
422 // in-flight invocation's CancellationToken, so the coroutine unwinds at
423 // its next probe instead of resuming against a dead view. This closes
424 // the third lifetime axis (view-destroy) alongside the existing
425 // VM-scope and Navigator-entry cancellation. See ROADMAP P1-H.
426 //
427 // Failures report through the callback boundary; teardown still completes.
428 void bind_view_lifetime(IView& view, std::function<void()> on_view_destroyed) {
429 if (!on_view_destroyed) return;
430 // A Subscription whose deleter runs the callback. Stored in the
431 // per-view bucket so it fires on view-destroy; also pinned by the
432 // engine, so engine destruction / clear() fires it too.
433 (void)ensure_alive_token_(view); // make sure the bucket+destroy wiring exists
434 add_view_sub_(view, Subscription{[callback = std::move(on_view_destroyed)] {
435 try { callback(); }
436 catch (...) { ::aria::report_callback_failure("binding.view_lifetime", std::current_exception()); }
437 }});
438 }
439
457 void adopt(IView& view, Subscription s) {
458 if (!s) return;
459 (void)ensure_alive_token_(view); // make sure bucket + destroy wiring exists
460 add_view_sub_(view, std::move(s));
461 }
462
464 void clear() noexcept;
465
466private:
467 template<class Src>
468 static reactive::detail::NodeHandle source_handle_(Src& source) noexcept {
469 if constexpr (std::derived_from<Src, reactive::Node>) return reactive::detail::NodeHandle{&source};
470 else return {};
471 }
472
473 template<class Src>
474 static bool source_alive_(const reactive::detail::NodeHandle& handle) noexcept {
475 if constexpr (std::derived_from<Src, reactive::Node>) return bool(handle);
476 else return true; // Custom ReadOnlyReactive sources own their lifetime contract.
477 }
478
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 // Copy before invoking user code; a projection can destroy its source.
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]
496 (const T& value) {
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);
501 });
502 });
503 if (is_alive_(alive)) add_view_sub_(view, std::move(sub));
504 }
505
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);
509 }
510
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;
518 // The graph thread owns the intrusive handle. Worker callbacks only
519 // copy its weak_ptr; they never link/unlink or retain a NodeHandle.
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;
528 {
529 GuardFlag g{*guard};
530 (adapter.get()->*setter)(view, rendered);
531 }
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;
539 GuardFlag g{*guard};
540 auto converted = converter->to_view(value);
541 if (!is_alive_(alive) || !model_alive_(model)) return;
542 (adapter.get()->*setter)(view, converted);
543 });
544 });
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;
553 try {
554 std::optional<T> parsed = converter->try_to_model
555 ? converter->try_to_model(value)
556 : std::optional<T>{converter->to_model(value)};
557 // Converters may synchronously clear, rebind or destroy
558 // the property/view. Neither weak token pins the target.
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");
563 } catch (...) {
564 ::aria::report_callback_failure("binding.converter", std::current_exception());
565 }
566 });
567 });
568 if (is_alive_(alive)) add_view_sub_(view, std::move(view_sub));
569 }
570
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) {
574 // Scalars use the same lifetime/dispatch/echo path as explicit converters.
575 bind_converted_(prop, view,
576 Converter<T, T>{[](const T& value) { return value; },
577 [to_model](const T& value) { return to_model(value); }, {}},
578 setter, subscriber);
579 }
580
581 // A view bucket owns every connection and its own destroy listener.
582 // It is retired before any callback can reenter clear/bind.
583 struct GuardFlag {
584 bool& slot;
585 bool previous;
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;
590 };
591
592 struct ViewBucket {
593 bool active = true; // Accessed only on the graph thread.
594 std::vector<Subscription> subscriptions;
595 Subscription destroy_listener;
596 };
597 using AliveToken = std::weak_ptr<ViewBucket>;
598
599 static bool is_alive_(const AliveToken& token) noexcept {
600 const auto state = token.lock();
601 return state && state->active;
602 }
603 static bool model_alive_(const std::weak_ptr<reactive::detail::NodeHandle>& token) noexcept {
604 const auto handle = token.lock(); // Only called after dispatch to the graph thread.
605 return handle && bool(*handle);
606 }
607
609 AliveToken ensure_alive_token_(IView& view);
610
611 // Inbound adapter callbacks can outlive their subscription (an HTTP
612 // worker may already have copied one). Capture all routing state by
613 // value and check the weak token on the graph thread immediately
614 // before touching Property / Command.
615 template<class Fn>
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();
622
623 // An AlwaysPost setter can synchronously echo while its guard is
624 // active. Drop that echo now: checking only after dequeue would
625 // observe a reset guard and feed formatted text back into the VM.
626 // Worker callbacks must never read this graph-thread-only flag.
627 if (on_graph_thread && guard && *guard) return;
628
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;
632 try { fn(); }
633 catch (...) { ::aria::report_callback_failure("binding.callback", std::current_exception()); }
634 };
635 if (direct || (policy == DispatchPolicy::SmartMarshal && on_graph_thread)) {
636 invoke();
637 } else {
638 dispatcher->post(std::move(invoke));
639 }
640 }
641
646 // The call sites gate payload construction. Protect construction as
647 // well as publication: diagnostic allocation failure must not interrupt
648 // binding delivery or teardown.
649 static void trace_binding_(std::string_view platform, std::string_view operation) noexcept {
650 try {
652 ::aria::trace::Binding{std::string{platform}, std::string{}, std::string{operation}});
653 } catch (...) {
654 ::aria::report_callback_failure("binding.trace", std::current_exception());
655 }
656 }
657
658 template <class Fn>
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)) {
665 if (::aria::has_trace_sink()) trace_binding_(adapter->platform_name(), "view_destroyed_drop");
666 return;
667 }
668 if (::aria::has_trace_sink()) trace_binding_(adapter->platform_name(), "vm_to_view");
669 // Trace sinks are user callbacks and can destroy/clear the binding.
670 if (is_alive_(alive)) fn();
671 };
672 if (!dispatcher || policy == DispatchPolicy::Direct ||
673 (policy == DispatchPolicy::SmartMarshal && dispatcher->is_main_thread())) {
674 invoke();
675 } else {
676 dispatcher->post(std::move(invoke));
677 }
678 }
679
680 void add_view_sub_(IView& view, Subscription sub);
681
682 std::shared_ptr<ViewBucket> bucket_for_(IView& view);
683
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;
689
690};
691#ifdef _MSC_VER
692#pragma warning(pop)
693#endif
694
695} // namespace aria::binding
Encapsulated user action with an optional CanExecute predicate.
Definition command.hpp:58
RAII handle to a single subscription.
Definition subscription.hpp:44
void bind_float_oneway(Property< float > &prop, IView &view)
DispatchPolicy
Binding dispatch policy — see the class header for semantics.
Definition binding_engine.hpp:106
@ SmartMarshal
inline iff dispatcher.is_main_thread()
Definition binding_engine.hpp:108
void bind_int_oneway(Src &src, IView &view)
Definition binding_engine.hpp:188
void bind_uint64(Property< std::uint64_t > &prop, IView &view)
void bind_optional_text(Src &src, IView &view, Project project, std::string empty_text=std::string{})
Bind a read-only text view to a reactive std::optional<T> source.
Definition binding_engine.hpp:337
void bind_double_oneway(Property< double > &prop, IView &view)
BindingEngine(std::shared_ptr< IViewAdapter > adapter)
Convenience constructor: no dispatcher, Direct policy.
void bind_int(Property< int > &prop, IView &view)
void bind_view_lifetime(IView &view, std::function< void()> on_view_destroyed)
Definition binding_engine.hpp:428
void bind_int_oneway(Property< int > &prop, IView &view)
void bind_bool(Property< bool > &prop, IView &view)
void bind_command(Command< Args... > &cmd, IView &view, const Args &... args)
Definition binding_engine.hpp:357
bool has_dispatcher() const noexcept
Definition binding_engine.hpp:133
void clear() noexcept
Drop every active binding.
BindingEngine & operator=(BindingEngine &&)=delete
void bind_double_oneway(Src &src, IView &view)
Definition binding_engine.hpp:236
void bind_text_projected(Src &src, IView &view, Project project)
Bind a read-only text view to src, rendered through project (T -> std::string).
Definition binding_engine.hpp:323
BindingEngine(const BindingEngine &)=delete
void bind_int64(Property< std::int64_t > &prop, IView &view)
void adopt(IView &view, Subscription s)
Adopt an arbitrary Subscription into view's per-view bucket.
Definition binding_engine.hpp:457
void bind_text(Property< std::string > &prop, IView &view)
void bind_float_oneway(Src &src, IView &view)
Definition binding_engine.hpp:224
DispatchPolicy dispatch_policy() const noexcept
Definition binding_engine.hpp:132
BindingEngine(std::shared_ptr< IViewAdapter > adapter, std::shared_ptr< runtime::IDispatcher > ui_dispatcher, DispatchPolicy policy=DispatchPolicy::SmartMarshal)
Constructor that opts into a dispatch policy.
void bind_uint64_oneway(Src &src, IView &view)
Definition binding_engine.hpp:212
void bind_bool_oneway(Src &src, IView &view)
Definition binding_engine.hpp:176
BindingEngine & operator=(const BindingEngine &)=delete
void bind_int64_oneway(Src &src, IView &view)
Definition binding_engine.hpp:200
void bind_enabled(Property< bool > &prop, IView &view)
void bind_bool_oneway(Property< bool > &prop, IView &view)
void bind_visible(Property< bool > &prop, IView &view)
void bind_float(Property< float > &prop, IView &view)
IViewAdapter & adapter() noexcept
Definition binding_engine.hpp:130
void bind_text_oneway(Src &src, IView &view)
Definition binding_engine.hpp:164
void bind_uint64_oneway(Property< std::uint64_t > &prop, IView &view)
void bind_int64_oneway(Property< std::int64_t > &prop, IView &view)
void bind_visible(Src &src, IView &view)
Definition binding_engine.hpp:248
void bind_text_converted(Property< T > &prop, IView &view, Converter< T, std::string > conv)
Definition binding_engine.hpp:278
void bind_int_converted(Property< T > &prop, IView &view, Converter< T, int > conv)
Bind a model value to an integer-valued control.
Definition binding_engine.hpp:289
void bind_text_oneway(Property< std::string > &prop, IView &view)
~BindingEngine()
Retires all lifetime gates before releasing the bindings.
void bind_text_converted_oneway(Src &src, IView &view, Converter< typename Src::value_type, std::string > conv)
Definition binding_engine.hpp:270
void bind_enabled(Src &src, IView &view)
Definition binding_engine.hpp:255
BindingEngine(BindingEngine &&)=delete
void bind_double(Property< double > &prop, IView &view)
Abstract platform adapter — knows how to read/write/observe widgets.
Definition view_adapter.hpp:80
virtual Subscription on_int_changed(IView &v, std::function< void(int)> cb)=0
virtual void set_visible(IView &v, bool visible)=0
virtual void set_uint64(IView &v, std::uint64_t value)=0
virtual void set_float(IView &v, float value)=0
virtual void set_double(IView &v, double value)=0
virtual void set_bool(IView &v, bool value)=0
virtual void set_int(IView &v, int value)=0
virtual void set_text(IView &v, std::string_view text)=0
virtual void set_enabled(IView &v, bool enabled)=0
virtual Subscription on_text_changed(IView &v, std::function< void(std::string_view)> cb)=0
virtual void set_int64(IView &v, std::int64_t value)=0
Abstract platform widget reference.
Definition view_adapter.hpp:28
Definition property.hpp:103
#define ARIA_BINDING_API
Definition export.hpp:39
Definition binding_engine.hpp:25
Definition computed.hpp:60
void publish_trace_unchecked(const TraceEvent &event) noexcept
Publish an already-built event using one owning sink snapshot.
Definition diagnostics.hpp:293
bool has_trace_sink() noexcept
True iff a sink is currently installed.
Definition diagnostics.hpp:286
@ Binding
BindingEngine VM<->View dispatch.
Definition diagnostics.hpp:69
void report_callback_failure(std::string_view category, std::exception_ptr exception, std::string_view message={}) noexcept
Report a callback failure.
Bidirectional converter between Model type T and View type U.
Definition converter.hpp:44
Binding events.
Definition diagnostics.hpp:142