353 : _options{std::move(options)},
355 _dispatcher{&dispatcher},
358 InterpreterOptions{.host = static_cast<InterpreterHost*>(this), .diagnostics = _options.diagnostics}} {
359 if (!_options.documents || _options.backend ==
nullptr) {
360 throw std::invalid_argument(
"DocumentScreenHost: a document lookup and a backend are required");
368 [[nodiscard]] std::expected<std::unique_ptr<ScreenHandle>,
MountError>
mount(std::string_view screenId,
370 return mountAt(screenId, params,
nullptr);
381 return mountAt(screenId, params, &parent);
389 [[nodiscard]] std::optional<std::string>
identityOf(std::string_view screenId,
ParamMap const& params)
override {
390 std::shared_ptr<Document const>
const document = _options.documents(screenId);
391 if (!document || !document->identity) {
413 std::shared_ptr<Document const>
const document = _options.documents(screenId);
417 for (std::string
const& name : document->prefetch) {
418 auto const query = std::ranges::find(document->scope.queries, name, &
QueryDecl::name);
419 if (query == document->scope.queries.end() || query->evaluate) {
422 auto const model = std::ranges::find(document->scope.models, query->model, &
ModelDecl::name);
423 if (model == document->scope.models.end()) {
426 std::optional<Binding>
const binding = bindingOf(*model, params);
435 std::shared_ptr<ModelChannel>
const channel =
436 _dispatcher->bind(model->type, binding->sharing, binding->instanceKey);
437 _session->prefetch(channel, binding->token, model->type, query->action, body);
445 [[nodiscard]] std::expected<std::string, std::string>
routeOf(std::string_view screenId,
447 std::shared_ptr<Document const>
const document = _options.documents(screenId);
449 return std::unexpected(
"unknown screen `" + std::string{screenId} +
"`");
451 return ::morph::ui::routeOf(*document, params);
457 [[nodiscard]] std::expected<Route, std::string>
parseRoute(std::string_view route)
const {
458 return ::morph::ui::parseRoute(route, _options.documents);
465 ValueRecord const*
const params = request.params.asRecord();
487 std::shared_ptr<Document const> document) {
488 auto*
const own =
dynamic_cast<Handle*
>(&handle);
489 if (own ==
nullptr || !document) {
490 return std::unexpected(
MountError{.screenId = document ? document->id : std::string{},
491 .reason =
"not a document screen of this host"});
493 return own->remount(_interpreter, std::move(document), *_options.backend);
503 auto const*
const own =
dynamic_cast<Handle const*
>(&handle);
504 Screen*
const screen = own ==
nullptr ? nullptr : own->screen();
505 if (screen ==
nullptr) {
515 auto const*
const own =
dynamic_cast<Handle const*
>(&handle);
516 return own ==
nullptr ? nullptr : own->root();
523 bridge::BindSharing sharing = bridge::BindSharing::Private;
524 std::string instanceKey;
528 [[nodiscard]] std::optional<Binding> bindingOf(ModelDecl
const& model, ParamMap
const& params)
const {
529 InstanceMode mode = model.instance;
530 if (mode == InstanceMode::Default) {
532 std::optional<ModelInfo>
const info = catalog.model ? catalog.model(model.type) : std::nullopt;
533 mode = info && info->stateless ? InstanceMode::Shared : InstanceMode::Private;
535 if (mode == InstanceMode::Shared) {
536 return Binding{.token =
"shared", .sharing = bridge::BindSharing::Private, .instanceKey = {}};
538 if (mode != InstanceMode::Keyed || !model.key) {
541 detail::host::ParamsEnv
const env{params};
542 std::string
const key = detail::instanceKeyText(
evaluate(*model.key, env));
543 if (
env.unknown() || key.empty()) {
546 return Binding{.token =
"shared:" + key, .sharing = bridge::BindSharing::Shared, .instanceKey = key};
550 std::expected<std::unique_ptr<ScreenHandle>, MountError> mountAt(std::string_view screenId, ParamMap
const& params,
551 ContainerWidget* parent) {
552 std::shared_ptr<Document const> document = _options.documents(screenId);
554 return std::unexpected(MountError{.screenId = std::string{screenId}, .reason =
"unknown screen"});
557 _interpreter.mount(std::move(document), params, *_options.backend, MountOptions{.parent = parent});
559 return std::unexpected(mountErrorOf(screenId,
screen.error()));
561 auto handle = std::make_unique<Handle>(std::move(*screen), _options.guard, _options.saveFailed);
562 handle->placed(params, parent);
563 adoptCall(*handle, screenId, params);
568 [[nodiscard]]
static MountError mountErrorOf(std::string_view screenId, Refusal
const& refusal) {
569 std::string reason = refusal.reason;
570 if (!refusal.path.empty()) {
571 reason = refusal.path +
": " + reason;
573 return MountError{.screenId = std::string{screenId}, .reason = std::move(reason)};
581 std::function<void(Value)> onResult;
582 std::function<void()> onCancel;
583 bool answered =
false;
585 void answer(std::optional<Value> result) {
592 onResult(std::move(*result));
594 }
else if (onCancel) {
602 class Handle final :
public ScreenHandle,
public ScreenHead {
604 Handle(std::unique_ptr<Screen> screen, CloseGuard guard, std::function<
void(ScreenHandle&)> saveFailed)
605 : _state{std::make_shared<State>(std::move(screen), std::move(guard), std::move(saveFailed))} {
610 _state->self =
nullptr;
612 _state->finish(
true);
617 Handle(Handle
const&) =
delete;
618 Handle& operator=(Handle
const&) =
delete;
619 Handle(Handle&&) =
delete;
620 Handle& operator=(Handle&&) =
delete;
622 [[nodiscard]] reactive::Computed<std::string>
const&
title()
const override {
return _state->title; }
623 [[nodiscard]] reactive::Computed<bool>
const&
dirty()
const override {
return _state->dirty; }
624 [[nodiscard]] reactive::Computed<Value>
const&
identity()
const override {
return _state->identity; }
626 [[nodiscard]] ScreenHead*
head() noexcept
override {
return this; }
627 [[nodiscard]] reactive::Computed<std::string>
const&
subtitle()
const override {
return _state->subtitle; }
628 [[nodiscard]] reactive::Computed<std::vector<HeadBadge>>
const&
status()
const override {
629 return _state->status;
631 [[nodiscard]] reactive::Computed<std::vector<HeadAction>>
const&
actions()
const override {
632 return _state->actions;
634 bool run(std::string_view actionId)
override {
635 return _state->screen !=
nullptr && _state->screen->runAction(actionId);
638 void requestClose(std::function<
void(
bool closed)> done)
override { State::request(_state, std::move(done)); }
640 void forceClose()
override { _state->finish(
true); }
642 [[nodiscard]]
bool canSave()
const override {
return _state->canSave(); }
644 void save(std::function<
void(
bool clean)> done)
override { State::save(_state, std::move(done)); }
646 [[nodiscard]] Widget* root()
const {
return _state->screen ? &_state->screen->root() :
nullptr; }
649 [[nodiscard]] Screen* screen()
const {
return _state->screen.get(); }
652 void placed(ParamMap params, ContainerWidget* parent) {
653 _state->params = std::move(params);
654 _state->parent = parent;
659 std::expected<void, MountError> remount(Interpreter& interpreter, std::shared_ptr<Document const> document,
660 IViewBackend& backend) {
661 State& state = *_state;
662 std::string
const screenId = document->id;
664 return std::unexpected(MountError{.screenId = screenId, .reason =
"the screen is closed"});
666 if (state.closing || state.saved.effect) {
667 return std::unexpected(MountError{.screenId = screenId, .reason =
"the screen is closing or saving"});
669 auto next = interpreter.mount(std::move(document), state.params, backend,
670 MountOptions{.parent = state.parent, .keepStateOf = state.screen.get()});
672 return std::unexpected(mountErrorOf(screenId, next.error()));
676 std::unique_ptr<Screen>
const replaced = std::exchange(state.screen, std::move(*next));
677 state.runtime->batch([&state] { state.generation.set(state.generation.peek() + 1); });
682 void answers(std::shared_ptr<Call> call) { _state->call = std::move(call); }
686 void closeItself(std::function<
void(ScreenHandle&)> closed) {
687 std::weak_ptr<State>
const weak = _state;
688 _state->runtime->owner().post([weak, closed = std::move(closed)] {
689 auto const held = weak.lock();
690 if (!held || !held->screen) {
694 if (held->self !=
nullptr && closed) {
701 [[nodiscard]]
bool answering(std::shared_ptr<Call>
const& call)
const {
702 return _state->screen && _state->call == call;
707 State(std::unique_ptr<Screen> mounted, CloseGuard closeGuard,
708 std::function<
void(ScreenHandle&)> onSaveFailed)
709 : screen{std::move(mounted)},
710 runtime{&screen->runtime()},
711 guard{std::move(closeGuard)},
712 saveFailed{std::move(onSaveFailed)},
713 closed{*runtime, false},
714 generation{*runtime, 0},
717 if (!closed.get() && generation.get() >= 0) {
718 lastTitle = detail::host::titleText(screen->title());
722 dirty{*runtime, [
this] {
return !closed.get() && generation.get() >= 0 && screen->dirty(); }},
725 if (!closed.get() && generation.get() >= 0) {
726 lastIdentity = screen->identity();
732 if (!closed.get() && generation.get() >= 0) {
733 lastSubtitle = detail::host::titleText(screen->subtitle());
739 return !closed.get() && generation.get() >= 0 ? screen->status()
740 : std::vector<HeadBadge>{};
742 actions{*runtime, [
this] {
743 return !closed.get() && generation.get() >= 0 ? screen->actions()
744 : std::vector<HeadAction>{};
747 State(State
const&) =
delete;
748 State& operator=(State
const&) =
delete;
749 State(State&&) =
delete;
750 State& operator=(State&&) =
delete;
754 static void request(std::shared_ptr<State>
const& state, std::function<
void(
bool)> done) {
755 if (!state->screen) {
756 state->post({std::move(done)},
true);
759 state->waiting.push_back(std::move(done));
760 if (state->closing) {
763 state->closing =
true;
764 std::shared_ptr<bool const>
const cancelled = state->screen->beginClose();
765 if (cancelled ==
nullptr) {
766 bool const isDirty = state->screen->dirty();
769 }
else if (state->guard && state->self !=
nullptr) {
770 std::weak_ptr<State>
const weak = state;
771 state->guard(*state->self, [weak](CloseChoice choice) {
772 if (auto const held = weak.lock()) {
773 choose(held, choice);
777 state->decide(
false);
782 whenSettled(state, &State::settled,
783 [cancelled](State& settledState) { settledState.decide(!*cancelled); });
789 std::unique_ptr<reactive::Effect>
effect;
791 std::uint64_t
id = 0;
797 static void whenSettled(std::shared_ptr<State>
const& state,
Watch State::*
slot,
798 std::function<
void(State&)> then) {
799 std::weak_ptr<State>
const weak = state;
800 std::uint64_t
const watchId = ++state->watches;
804 std::make_unique<reactive::Effect>(*state->runtime, [weak,
slot, watchId, then = std::move(then)] {
805 auto const held = weak.lock();
806 if (!held || !held->screen || held->screen->pending()) {
809 held->runtime->owner().post([weak,
slot, watchId, then] {
810 auto const again = weak.lock();
811 if (!again || !again->screen || (again.get()->*slot).id != watchId) {
821 [[nodiscard]]
bool canSave()
const {
return screen && screen->declares(
"onSave"); }
824 static void save(std::shared_ptr<State>
const& state, std::function<
void(
bool)> done) {
825 if (!state->canSave()) {
831 if (state->saved.effect) {
832 state->saving.push_back(std::move(done));
835 state->screen->runLifecycle(
"onSave");
836 state->saving.push_back(std::move(done));
839 whenSettled(state, &State::saved, [](State& savedState) {
840 bool const clean = !savedState.screen->dirty();
841 for (
auto const& answer : std::exchange(savedState.saving, {})) {
842 if (!savedState.screen) {
853 static void choose(std::shared_ptr<State>
const& state, CloseChoice choice) {
854 if (!state->closing) {
857 if (choice != CloseChoice::Save || !state->canSave()) {
858 state->decide(choice == CloseChoice::Discard);
861 std::weak_ptr<State>
const weak = state;
862 save(state, [weak](
bool clean) {
863 auto const held = weak.lock();
867 if (!clean && held->saveFailed && held->self !=
nullptr) {
868 held->saveFailed(*held->self);
875 void decide(
bool close) {
884 post(std::exchange(waiting, {}),
false);
888 void finish(
bool answer) {
894 call->answer(std::nullopt);
898 lastTitle = detail::host::titleText(
screen->title());
899 lastIdentity =
screen->identity();
900 lastSubtitle = detail::host::titleText(
screen->subtitle());
901 runtime->batch([
this] { closed.set(
true); });
904 post(std::exchange(waiting, {}), answer);
908 void post(std::vector<std::function<
void(
bool)>> answers,
bool answer)
const {
909 if (answers.empty()) {
912 runtime->owner().post([answers = std::move(answers), answer] {
913 for (
auto const& done : answers) {
921 std::unique_ptr<Screen>
screen;
922 reactive::Runtime* runtime;
924 Handle*
self =
nullptr;
925 std::shared_ptr<Call> call;
926 bool closing =
false;
927 std::vector<std::function<void(
bool)>> waiting;
928 std::function<void(ScreenHandle&)> saveFailed;
930 std::vector<std::function<void(
bool)>> saving;
932 std::uint64_t watches = 0;
933 std::string lastTitle;
935 std::string lastSubtitle;
938 ContainerWidget* parent =
nullptr;
939 reactive::Signal<bool> closed;
941 reactive::Signal<int> generation;
942 reactive::Computed<std::string> title;
943 reactive::Computed<bool> dirty;
944 reactive::Computed<Value> identity;
945 reactive::Computed<std::string> subtitle;
946 reactive::Computed<std::vector<HeadBadge>> status;
947 reactive::Computed<std::vector<HeadAction>> actions;
950 std::shared_ptr<State> _state;
955 bool navigate(NavigateRequest
const& request, std::function<
void(Value)> onResult,
956 std::function<
void()> onCancel)
override {
958 return _options.application !=
nullptr &&
959 _options.application->navigate(request, std::move(onResult), std::move(onCancel));
961 if (onResult || onCancel) {
963 _unclaimed.push_back(std::make_shared<Call>(Call{.screen =
request.screen,
964 .params = params ==
nullptr ?
ParamMap{} : *params,
965 .onResult = std::move(onResult),
966 .onCancel = std::move(onCancel),
967 .answered =
false}));
973 bool closeSelf(std::string
const& screen, std::optional<Value> result)
override {
975 for (OpenCall
const& open : std::views::reverse(_open)) {
976 std::shared_ptr<Call>
const call =
open.call.lock();
977 if (call ==
nullptr || call->screen != screen || call->answered ||
open.handle ==
nullptr ||
978 !
open.handle->answering(call)) {
981 call->answer(std::move(result));
982 open.handle->closeItself([
this](ScreenHandle& closed) { onScreenClosed(closed); });
985 return _options.application !=
nullptr && _options.application->closeSelf(screen, std::move(result));
989 void adoptCall(Handle& handle, std::string_view screenId, ParamMap
const& params) {
990 std::erase_if(_open, [](OpenCall
const& open) {
return open.call.expired(); });
991 auto const found = std::ranges::find_if(_unclaimed, [&](std::shared_ptr<Call>
const& call) {
992 return call->screen == screenId && call->params == params;
994 if (found == _unclaimed.end()) {
997 std::shared_ptr<Call>
const call = std::move(*found);
998 _unclaimed.erase(found);
999 handle.answers(call);
1000 _open.push_back(OpenCall{.call = call, .handle = &handle});
1002 bool hostCommand(std::string
const& name, Value
const& args)
override {
1003 return _options.application !=
nullptr && _options.application->hostCommand(name, args);
1005 bool switchBackend(std::string
const& name, std::function<
void(std::expected<void, std::string>)> done)
override {
1006 return _options.application !=
nullptr && _options.application->switchBackend(name, std::move(done));
1008 void resetPrefs(std::string
const& screen, std::string
const& point)
override {
1009 if (_options.application !=
nullptr) {
1010 _options.application->resetPrefs(screen, point);
1013 void setLocale(std::string
const& locale)
override {
1014 if (_options.application !=
nullptr) {
1015 _options.application->setLocale(locale);
1018 bool notify(std::string
const& text, std::string
const& role)
override {
1019 return _options.application !=
nullptr && _options.application->notify(text, role);
1021 bool signIn(Value
const& reply)
override {
1022 return _options.application !=
nullptr && _options.application->signIn(reply);
1025 if (_options.application !=
nullptr) {
1026 _options.application->signOut();
1029 bool setAppValue(std::string
const& name, Value
const& value)
override {
1030 return _options.application !=
nullptr && _options.application->setAppValue(name, value);
1032 bool uploadFor(UploadRequest
const& request, std::function<
void(Value)> progress,
1033 std::function<
void(std::expected<Value, std::string>)> done)
override {
1034 return _options.application !=
nullptr &&
1035 _options.application->uploadFor(request, std::move(progress), std::move(done));
1037 bool download(Value
const& file, std::string
const& option, Value
const& name,
1038 std::function<
void(std::expected<Value, std::string>)> done)
override {
1039 return _options.application !=
nullptr && _options.application->download(file, option, name, std::move(done));
1041 bool readFile(Value
const& file, std::size_t maxBytes,
1042 std::function<
void(std::expected<InlineFile, std::string>)> done)
override {
1043 return _options.application !=
nullptr && _options.application->readFile(file, maxBytes, std::move(done));
1045 bool saveFile(InlineFile
const& file, std::string
const& option,
1046 std::function<
void(std::expected<Value, std::string>)> done)
override {
1047 return _options.application !=
nullptr && _options.application->saveFile(file, option, std::move(done));
1049 void preferenceChanged(std::string
const& screen, std::string
const& point, Value
const& value)
override {
1050 if (_options.application !=
nullptr) {
1051 _options.application->preferenceChanged(screen, point, value);
1054 void permissionsStale()
override {
1055 if (_options.application !=
nullptr) {
1056 _options.application->permissionsStale();
1062 std::weak_ptr<Call> call;
1063 Handle* handle =
nullptr;
1066 DocumentScreenHostOptions _options;
1068 Dispatcher* _dispatcher;
1069 std::vector<std::shared_ptr<Call>> _unclaimed;
1070 std::vector<OpenCall> _open;
1071 Interpreter _interpreter;