19struct StateBindingOwner {
20 std::function<bool()> alive;
24 if (stopped)
return false;
25 if (alive && ! alive()) stopped =
true;
31 QPointer<QObject> owner;
32 std::type_index accessor;
33 std::shared_ptr<StateBindingOwner> lifetime;
34 StateProperty(QObject*
object, std::type_index type, std::shared_ptr<StateBindingOwner> guard)
35 : owner(object), accessor(type), lifetime(std::move(guard)) {}
36 virtual ~StateProperty() =
default;
37 virtual void apply(std::optional<qint64>) = 0;
38 virtual void abandon() = 0;
42struct StatePropertyOf : StateProperty {
43 std::function<QBindable<T>()> access;
44 std::map<qint64, QPropertyBinding<T>> overrides;
46 std::optional<QPropertyBinding<T>> base, saved;
47 T initial {}, savedValue {};
48 bool covered =
false, stopped =
false, baseDeclared =
false;
50 StatePropertyOf(QObject*
object, std::type_index type, std::shared_ptr<StateBindingOwner> guard,
51 std::function<QBindable<T>()> getter)
52 : StateProperty(object, type, std::move(guard)), access(std::move(getter)) {
53 if (! isActive())
return;
54 initial = access().value();
55 if (! isActive())
return;
56 const auto binding = access().binding();
57 if (! binding.isNull())
58 base.emplace(binding);
60 base.emplace(Qt::makePropertyBinding([value = initial] {
66 if (! stopped && owner && lifetime->isActive() && owner && ! stopped)
return true;
71 void install(QPropertyBinding<T> binding) {
72 if (! isActive())
return;
73 access().setBinding(binding);
77 if (! isActive())
return;
79 access().setValue(initial);
84 void apply(std::optional<qint64> state)
override {
85 if (! isActive())
return;
86 const auto next = state ? overrides.find(*state) : overrides.end();
87 if (next != overrides.end()) {
88 const auto binding = next->second;
90 const auto original = access().binding();
91 if (! isActive())
return;
92 const auto value = original.isNull() ? access().value() : T {};
93 if (! isActive())
return;
94 saved.emplace(original);
101 const auto original = std::move(*saved);
103 const auto value = savedValue;
104 if (! isActive())
return;
105 if (original.isNull())
106 access().setValue(value);
108 access().setBinding(original);
112 void abandon()
override {
120struct StateBindingData {
121 std::shared_ptr<StateBindingOwner> lifetime = std::make_shared<StateBindingOwner>();
122 std::vector<std::shared_ptr<StateProperty>> properties;
123 std::map<qint64, std::vector<std::shared_ptr<StateProperty>>> states;
124 std::optional<qint64> current;
125 bool frozen =
false, switching =
false, stopped =
false;
128 if (std::exchange(stopped,
true))
return;
129 lifetime->stopped =
true;
130 for (
const auto& property : properties)
property->abandon();
134 if (! stopped && lifetime->isActive() && ! stopped)
return true;
139 bool select(std::optional<qint64> next) {
140 if (! isActive())
return false;
142 qWarning(
"StateBindings: recursive state change");
146 if (current == next)
return true;
147 auto affected = current ? states[*current] : std::vector<std::shared_ptr<StateProperty>> {};
149 for (
const auto& property : states[*next]) {
150 if (std::find(affected.begin(), affected.end(), property) == affected.end())
151 affected.push_back(property);
157 const QScopedPropertyUpdateGroup group;
158 for (
const auto& property : affected) {
159 if (! isActive())
break;
160 property->apply(next);
168template<auto Accessor>
169struct AccessorTag {};
173struct BindableValue<QBindable<T>> {
181 friend class StateBindingSet;
182 friend class StateBindings;
183 std::weak_ptr<detail::StateBindingData> m_owner;
184 std::shared_ptr<detail::StatePropertyOf<T>> m_property;
187 bool isValid()
const {
188 const auto property = m_property;
189 return property &&
property->isActive();
192 const auto property = m_property;
193 if (property)
property->reset();
199 friend class StateBindingSet;
200 std::shared_ptr<detail::StateBindingData> m_data;
201 std::optional<qint64> m_state;
202 StateBindings(std::shared_ptr<detail::StateBindingData> data, std::optional<qint64> state)
203 : m_data(std::move(data)), m_state(state) {}
207 bool bind(
const PropertyKey<T>& key,
const QPropertyBinding<T>& binding)
const {
208 const auto data = m_data;
209 if (! data->isActive())
return false;
210 if (data->frozen || data->stopped || key.m_owner.lock() != data || ! key.isValid() ||
212 qWarning(
"StateBindings: invalid or frozen declaration");
215 const auto property = key.m_property;
217 if (! property->overrides.emplace(*m_state, binding).second) {
218 qWarning(
"StateBindings: duplicate property declaration");
221 data->states[*m_state].push_back(property);
223 if (property->baseDeclared) {
224 qWarning(
"StateBindings: duplicate base declaration");
227 property->base.emplace(binding);
228 property->baseDeclared =
true;
229 property->install(binding);
231 return data->isActive();
234 template<
typename T,
typename F>
235 requires std::is_invocable_r_v<T, F> &&
236 std::is_same_v<std::remove_cvref_t<std::invoke_result_t<F>>, T>
237 bool bind(
const PropertyKey<T>& key, F&& function)
const {
238 return bind(key, Qt::makePropertyBinding(std::forward<F>(function)));
242template<
typename StateId>
243class StateBindingSet {
244 static_assert(std::is_enum_v<StateId>);
245 std::shared_ptr<detail::StateBindingData> m_data = std::make_shared<detail::StateBindingData>();
249 explicit StateBindingSet(StateId base): m_base(base) {}
250 StateBindingSet(
const StateBindingSet&) =
delete;
251 StateBindingSet& operator=(
const StateBindingSet&) =
delete;
252 ~StateBindingSet() { abandon(); }
255 bool setOwnerAliveCheck(std::function<
bool()> check) {
256 const auto data = m_data;
257 if (data->frozen || data->stopped || ! data->properties.empty()) {
258 qWarning(
"StateBindings: owner check must precede property registration");
261 data->lifetime->alive = std::move(check);
265 template<auto Accessor,
typename Object>
266 auto property(Object*
object) {
268 typename detail::BindableValue<std::invoke_result_t<
decltype(Accessor), Object*>>::Type;
270 const auto data = m_data;
271 if (! data->isActive())
return key;
272 if (!
object || data->frozen || data->stopped) {
273 qWarning(
"StateBindings: invalid or frozen property registration");
276 const auto bindable = std::invoke(Accessor,
object);
277 if (! bindable.isValid() || ! bindable.isBindable() || bindable.isReadOnly()) {
278 qWarning(
"StateBindings: property must be writable and bindable");
281 const std::type_index type(
typeid(detail::AccessorTag<Accessor>));
283 for (
const auto& existing : data->properties) {
284 if (existing->owner ==
object && existing->accessor == type) {
285 key.m_property = std::static_pointer_cast<detail::StatePropertyOf<T>>(existing);
289 const auto property =
290 std::make_shared<detail::StatePropertyOf<T>>(object, type, data->lifetime, [object] {
291 return std::invoke(Accessor,
object);
293 if (! data->isActive() || ! property->isActive())
return key;
294 key.m_property = property;
295 data->properties.push_back(property);
299 StateBindings base()
const {
return { m_data, {} }; }
300 StateBindings state(StateId
id)
const {
301 return { m_data,
id == m_base ? std::nullopt : std::optional<qint64>(qint64(
id)) };
303 bool select(StateId
id) {
304 const auto data = m_data;
305 return data->select(
id == m_base ? std::nullopt : std::optional<qint64>(qint64(
id)));
308 const auto data = m_data;
309 const bool restored = data->select({});
314 const auto data = m_data;
318 std::shared_ptr<detail::StateBindingData> m_data;
319 friend class StateBindingSet;
320 explicit Lifetime(std::shared_ptr<detail::StateBindingData> data)
321 : m_data(std::move(data)) {}
324 Lifetime(
const Lifetime&) =
delete;
325 ~Lifetime() { m_data->abandon(); }
327 Lifetime lifetime()
const {
return Lifetime(m_data); }