Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "toastprovider.h"
23 :
24 : #include "async/async.h"
25 :
26 : using namespace muse::toast;
27 :
28 0 : ToastProvider::~ToastProvider()
29 : {
30 0 : for (const auto& pair : m_progressTimers) {
31 0 : delete pair.second;
32 : }
33 0 : }
34 :
35 0 : muse::async::Promise<ToastResult> ToastProvider::show(ToastItem item)
36 : {
37 0 : return muse::async::make_promise<ToastResult>([this, item](auto resolve, auto) {
38 0 : int id = item.id();
39 0 : auto toast = std::make_shared<ToastItem>(item);
40 0 : m_toasts.emplace_back(toast);
41 0 : m_toastAdded.send(toast);
42 :
43 0 : m_resolvers[id] = std::move(resolve);
44 :
45 0 : checkProgress(id);
46 0 : checkTimer(id);
47 :
48 0 : return muse::async::Promise<ToastResult>::dummy_result();
49 0 : }, muse::async::PromiseType::AsyncByBody);
50 : }
51 :
52 0 : muse::async::Channel<std::shared_ptr<ToastItem> > ToastProvider::toastAdded() const
53 : {
54 0 : return m_toastAdded;
55 : }
56 :
57 0 : muse::async::Channel<int> ToastProvider::toastDismissed() const
58 : {
59 0 : return m_toastDismissed;
60 : }
61 :
62 0 : void ToastProvider::dismissToast(int id)
63 : {
64 0 : for (int i = 0; i < static_cast<int>(m_toasts.size()); ++i) {
65 0 : if (m_toasts.at(i)->id() == id) {
66 0 : m_toastDismissed.send(id);
67 0 : const auto& item = m_toasts.at(i);
68 0 : cleanup(item->id());
69 0 : m_toasts.erase(m_toasts.begin() + i);
70 0 : return;
71 : }
72 : }
73 : }
74 :
75 0 : void ToastProvider::executeAction(int id, int actionCode)
76 : {
77 0 : resolveToast(id, actionCode);
78 0 : dismissToast(id);
79 0 : }
80 :
81 0 : void ToastProvider::pauseToast(int id)
82 : {
83 0 : auto timerIt = m_progressTimers.find(id);
84 0 : if (timerIt != m_progressTimers.end()) {
85 0 : timerIt->second->stop();
86 : }
87 0 : }
88 :
89 0 : void ToastProvider::resumeToast(int id)
90 : {
91 0 : auto timerIt = m_progressTimers.find(id);
92 0 : if (timerIt != m_progressTimers.end() && !timerIt->second->isActive()) {
93 0 : timerIt->second->start();
94 : }
95 0 : }
96 :
97 0 : void ToastProvider::cleanup(int id)
98 : {
99 0 : auto timerIt = m_progressTimers.find(id);
100 0 : if (timerIt != m_progressTimers.end()) {
101 : // cleanup() may run from within the timer's own timeout callback (progress reaching 100%),
102 : // so defer the actual destruction instead of freeing it while its signal is still on the stack
103 0 : QTimer* timer = timerIt->second;
104 0 : timer->stop();
105 0 : timer->deleteLater();
106 0 : m_progressTimers.erase(timerIt);
107 : }
108 :
109 0 : const auto itemIt = std::find_if(m_toasts.begin(), m_toasts.end(), [id](const std::shared_ptr<ToastItem>& toast) {
110 0 : return toast->id() == id;
111 : });
112 0 : if (itemIt != m_toasts.end()) {
113 0 : if (auto progress = (*itemIt)->progress()) {
114 0 : progress->progressChanged().disconnect(this);
115 0 : progress->finished().disconnect(this);
116 0 : }
117 : }
118 :
119 0 : auto resolverIt = m_resolvers.find(id);
120 0 : if (resolverIt != m_resolvers.end()) {
121 0 : resolveToast(id, int(ToastActionCode::None));
122 : }
123 0 : }
124 :
125 0 : void ToastProvider::checkProgress(int id)
126 : {
127 0 : const auto itemIt = std::find_if(m_toasts.begin(), m_toasts.end(), [id](const std::shared_ptr<ToastItem>& toast) {
128 0 : return toast->id() == id;
129 : });
130 :
131 0 : if (itemIt == m_toasts.end()) {
132 0 : return;
133 : }
134 :
135 0 : auto& item = *itemIt;
136 0 : auto progress = item->progress();
137 0 : if (progress) {
138 0 : progress->progressChanged().onReceive(this, [this, item](int64_t current, int64_t total, const std::string&) {
139 0 : if (total > 0) {
140 0 : const double newProgress = static_cast<int>((static_cast<double>(current) / static_cast<double>(total)) * 100.0);
141 0 : item->setCurrentProgress(newProgress);
142 :
143 0 : if (newProgress >= 100.0) {
144 0 : async::Async::call(this, [this, item]() { dismissToast(item->id()); });
145 : }
146 : }
147 0 : });
148 :
149 0 : progress->finished().onReceive(this, [this, item](const muse::ProgressResult&) {
150 0 : async::Async::call(this, [this, item]() { dismissToast(item->id()); });
151 0 : });
152 : }
153 0 : }
154 :
155 0 : void ToastProvider::checkTimer(int id)
156 : {
157 0 : const auto itemIt = std::find_if(m_toasts.begin(), m_toasts.end(), [id](const std::shared_ptr<ToastItem>& toast) {
158 0 : return toast->id() == id;
159 : });
160 :
161 0 : if (itemIt == m_toasts.end()) {
162 0 : return;
163 : }
164 :
165 0 : auto& item = *itemIt;
166 0 : if (item->timeout().count() > 0) {
167 0 : const int timeoutMs = std::chrono::duration_cast<std::chrono::milliseconds>(item->timeout()).count();
168 :
169 0 : if (timeoutMs > 0) {
170 0 : int interval = static_cast<int>(timeoutMs / 100);
171 0 : QTimer* timer = new QTimer();
172 0 : timer->setInterval(interval);
173 0 : timer->setSingleShot(false);
174 0 : timer->start();
175 0 : timer->callOnTimeout([this, item, timeoutMs, interval]() {
176 0 : double currentProgress = item->currentProgress()
177 0 : + ((static_cast<double>(interval) / static_cast<double>(timeoutMs)) * 100.0);
178 0 : item->setCurrentProgress(currentProgress);
179 0 : if (currentProgress >= 100.0) {
180 0 : dismissToast(item->id());
181 : }
182 0 : });
183 :
184 0 : m_progressTimers[item->id()] = timer;
185 : }
186 : }
187 : }
188 :
189 0 : void ToastProvider::resolveToast(int id, int actionCode)
190 : {
191 0 : auto promiseIt = m_resolvers.find(id);
192 0 : if (promiseIt != m_resolvers.end()) {
193 0 : auto resolve = std::move(promiseIt->second);
194 0 : m_resolvers.erase(promiseIt);
195 0 : (void)resolve(ToastResult(actionCode));
196 0 : }
197 0 : }
|