diff --git a/res/rml/mod_browser.rcss b/res/rml/mod_browser.rcss index 8dd934d6ed..8964d7bfc1 100644 --- a/res/rml/mod_browser.rcss +++ b/res/rml/mod_browser.rcss @@ -1,10 +1,6 @@ window.mod-browser, window.mod-browser-detail, window.screenshot-viewer { - --color-official-badge: #46edd5; - --color-catalog-warning-background-rgb: 180, 107, 42; - --color-catalog-warning-text: #e8cba7; - --color-catalog-missing: #e8b177; background-color: rgba(var(--color-surface-rgb), 96%); } @@ -156,7 +152,7 @@ catalog-official-badge { padding: var(--space-2xs) 5dp; border-radius: var(--radius-small); background-color: rgba(var(--color-accent-rgb), 28%); - color: var(--color-official-badge); + color: var(--color-info); font-style: normal; font-size: 9dp; } @@ -191,7 +187,7 @@ catalog-card-icon { border-radius: var(--radius-panel); overflow: hidden; background-color: rgba(var(--color-border-rgb), 18%); - box-shadow: rgba(0, 0, 0, 60%) 0 6dp 16dp; + box-shadow: rgba(var(--color-black-rgb), 60%) 0 6dp 16dp; } catalog-card-icon-image { @@ -437,29 +433,29 @@ button.catalog-install-action.paused progress fill { button.catalog-install-action.retrying { color: var(--color-warning); - box-shadow: rgba(255, 168, 38, 60%) 0 0 0 2dp; + box-shadow: rgba(var(--color-warning-rgb), 60%) 0 0 0 2dp; } button.catalog-install-action.retrying progress fill { - background-color: rgba(255, 168, 38, 60%); + background-color: rgba(var(--color-warning-rgb), 60%); } button.catalog-install-action.failed { color: var(--color-white); - --button-background: rgba(133, 34, 33, 20%); - --button-background-hover: rgba(133, 34, 33, 35%); - --button-background-selected: rgba(133, 34, 33, 35%); - --button-background-active: rgba(133, 34, 33, 35%); - box-shadow: #B3261E 0 0 0 2dp; + --button-background: rgba(var(--color-error-rgb), 20%); + --button-background-hover: rgba(var(--color-error-rgb), 35%); + --button-background-selected: rgba(var(--color-error-rgb), 35%); + --button-background-active: rgba(var(--color-error-rgb), 35%); + box-shadow: var(--color-error) 0 0 0 2dp; } button.catalog-install-action.failed progress fill { - background-color: rgba(179, 38, 30, 70%); + background-color: rgba(var(--color-error-rgb), 70%); } button.catalog-install-action.installed { color: var(--color-success); - box-shadow: rgba(68, 204, 85, 50%) 0 0 0 2dp; + box-shadow: rgba(var(--color-success-rgb), 50%) 0 0 0 2dp; } button.catalog-install-action.installed:disabled { @@ -467,7 +463,7 @@ button.catalog-install-action.installed:disabled { } button.catalog-install-action.installing progress fill { - background-color: #2255BB; + background-color: rgba(var(--color-info-rgb), 80%); } catalog-install-control { @@ -490,7 +486,7 @@ catalog-install-caption { } catalog-install-caption.failed { - color: #B3261E; + color: var(--color-error); } catalog-detail-identity { @@ -509,7 +505,7 @@ catalog-detail-icon { border-radius: var(--radius-panel); overflow: hidden; background-color: rgba(var(--color-border-rgb), 25%); - box-shadow: rgba(0, 0, 0, 65%) 0 8dp 20dp; + box-shadow: rgba(var(--color-black-rgb), 65%) 0 8dp 20dp; } catalog-detail-icon-image { @@ -588,39 +584,6 @@ catalog-stat-value { font-weight: bold; } -/*catalog-native-warning { - display: flex; - align-items: center; - gap: 10dp; - margin: 18dp var(--space-xl) 0 var(--space-xl); - padding: 11dp 14dp; - border-radius: var(--radius-panel); - background-color: rgba(var(--color-catalog-warning-background-rgb), 18%); - box-shadow: rgba(206, 137, 67, 45%) 0 0 0 1dp; - color: var(--color-catalog-warning-text); -} - -catalog-native-warning icon { - font-size: var(--font-size-4xl); -} - -catalog-warning-copy { - display: flex; - flex-flow: column; - font-size: var(--font-size-2xs); -} - -catalog-warning-title { - display: block; - font-size: var(--font-size-xs); - font-weight: bold; -} - -catalog-warning-message { - display: block; - font-size: var(--font-size-3xs); -}*/ - catalog-detail-body { display: flex; flex-flow: row; @@ -647,7 +610,7 @@ catalog-detail-body section.catalog-scroll-anchor { catalog-detail-body section.catalog-scroll-anchor:focus-visible { border-radius: var(--radius-control); - box-shadow: rgba(194, 164, 45, 45%) 0 0 0 1dp; + box-shadow: rgba(var(--color-accent-rgb), 45%) 0 0 0 1dp; } catalog-detail-body h2, @@ -774,7 +737,7 @@ catalog-dependency-status { } catalog-dependency.missing { - color: var(--color-catalog-missing); + color: var(--color-warning); } catalog-detail-body aside { @@ -786,7 +749,7 @@ catalog-detail-body aside { gap: var(--space-md); border-radius: 9dp; background-color: rgba(var(--color-control-rgb), 42%); - box-shadow: rgba(146, 135, 91, 24%) 0 0 0 1dp; + box-shadow: rgba(var(--color-border-rgb), 24%) 0 0 0 1dp; } catalog-detail-body dl { diff --git a/res/rml/theme.rcss b/res/rml/theme.rcss index 29ae6a5e4d..fd01d88522 100644 --- a/res/rml/theme.rcss +++ b/res/rml/theme.rcss @@ -55,11 +55,14 @@ body { --color-black-rgb: 0, 0, 0; --color-black: rgb(var(--color-black-rgb)); - --color-success: #44cc55; + --color-success-rgb: 68, 204, 85; + --color-success: rgb(var(--color-success-rgb)); --color-info-rgb: 111, 183, 239; --color-info: rgb(var(--color-info-rgb)); - --color-warning: #ffa826; - --color-error: #cc4444; + --color-warning-rgb: 255, 168, 38; + --color-warning: rgb(var(--color-warning-rgb)); + --color-error-rgb: 204, 68, 68; + --color-error: rgb(var(--color-error-rgb)); --button-color: var(--color-text); --button-background: rgba(var(--color-control-rgb), 20%); diff --git a/src/dusk/mods/queue.cpp b/src/dusk/mods/queue.cpp index 12005fbf7f..69a3bb50ec 100644 --- a/src/dusk/mods/queue.cpp +++ b/src/dusk/mods/queue.cpp @@ -43,7 +43,6 @@ struct QueueItem { Request request; State state = State::Queued; std::filesystem::path partialPath; - std::filesystem::path stagedPath; uint64_t completed = 0; uint64_t total = 0; std::string message; @@ -51,6 +50,7 @@ struct QueueItem { clock::time_point retryAt{}; borealis::Task task; borealis::Task verification; + ModOperationHandle operation; PendingIntent pendingIntent = PendingIntent::None; }; @@ -247,9 +247,6 @@ void remove_partial(const QueueItem& item) { metadataPath += ".borealis-resume.json"; std::filesystem::remove(metadataPath, ec); } - if (!item.stagedPath.empty()) { - std::filesystem::remove(item.stagedPath, ec); - } } void fail(QueueItem& item, std::string message, bool discardPartial) { @@ -420,12 +417,12 @@ void finish_download(QueueItem& item) { }); } -bool finish_verification(QueueItem& item) { +void finish_verification(QueueItem& item) { VerifyResult result; try { auto completed = item.verification.try_take(); if (!completed) { - return false; + return; } result = std::move(*completed); } catch (const std::exception& exception) { @@ -443,31 +440,30 @@ bool finish_verification(QueueItem& item) { remove_partial(item); item.state = State::Canceled; item.completed = 0; - return false; + return; } if (!result.error.empty()) { fail(item, std::move(result.error), true); - return false; + return; } if (const auto duplicate = find_queue_item_by_mod_id(result.metadata.id); duplicate != nullptr && duplicate != &item && !is_terminal(duplicate->state)) { fail(item, "This mod already has an active install", true); - return false; + return; } if (local_source(item) != nullptr && !item.request.id.empty() && (item.request.id != result.metadata.id || item.request.version != result.metadata.version)) { fail(item, "The local package changed after confirmation", true); - return false; + return; } item.request.id = result.metadata.id; item.request.name = result.metadata.name; item.request.version = result.metadata.version; - item.stagedPath = std::move(result.stagedPath); item.completed = item.total; - ModLoader::instance().request_install(std::exchange(item.stagedPath, std::filesystem::path{})); - return true; + item.state = State::Handoff; + item.operation = ModLoader::instance().request_install(std::move(result.stagedPath)); } Item snapshot(const QueueItem& item) { @@ -528,9 +524,9 @@ bool enqueue(Request request, std::string* keyOut) { existing->completed = 0; existing->total = total; existing->partialPath.clear(); - existing->stagedPath.clear(); existing->message.clear(); existing->retryCount = 0; + existing->operation.reset(); existing->pendingIntent = PendingIntent::None; if (keyOut != nullptr) { *keyOut = existing->key; @@ -551,8 +547,11 @@ void update() { if (item->task && item->task.ready()) { finish_download(*item); } - if (item->state == State::Verifying && item->verification && item->verification.ready() && - finish_verification(*item)) + if (item->state == State::Verifying && item->verification && item->verification.ready()) { + finish_verification(*item); + } + if (item->state == State::Handoff && item->operation && + item->operation->state != ModOperation::State::Pending) { item = queueItems.erase(item); } else { @@ -561,7 +560,7 @@ void update() { } for (auto& item : queueItems) { - if (item.task || item.verification) { + if (item.task || item.verification || item.operation) { return; } if (is_terminal(item.state) || item.state == State::Paused) { @@ -691,7 +690,7 @@ void retry(std::string_view id) { void cancel(std::string_view id) { auto* item = find_queue_item(id); - if (item == nullptr) { + if (item == nullptr || item->state == State::Handoff) { return; } if (item->task) { diff --git a/src/dusk/mods/queue.hpp b/src/dusk/mods/queue.hpp index 8394b9f71a..d3d0533682 100644 --- a/src/dusk/mods/queue.hpp +++ b/src/dusk/mods/queue.hpp @@ -17,6 +17,7 @@ enum class State { Paused, Retrying, Verifying, + Handoff, Failed, Canceled, }; @@ -61,7 +62,6 @@ struct Item { /** Adds an install, replacing failed or canceled work for the same package ID. */ bool enqueue(Request request, std::string* key = nullptr); -/** Polls transfer and verification work. Call once per UI frame on the main thread. */ void update(); void shutdown() noexcept; diff --git a/src/dusk/ui/mod_browser.cpp b/src/dusk/ui/mod_browser.cpp index 2146b924dd..fadb841289 100644 --- a/src/dusk/ui/mod_browser.cpp +++ b/src/dusk/ui/mod_browser.cpp @@ -496,6 +496,13 @@ public: label = "Verifying…"; caption = "Checking package integrity"; break; + case Handoff: + glyph = "\uE8B5"; + label = "Installing…"; + caption = "Applying package"; + progress = 1.0f; + disabled = true; + break; case Failed: glyph = "\uE5D5"; label = queued->local ? "Retry package" : "Retry download"; @@ -679,7 +686,6 @@ DetailContent::DetailContent( auto* description = append(main, "section"); description->SetClass("catalog-scroll-anchor", true); add_existing_item(description); - // append_text_element(description, "h2", "About this mod"); auto* descriptionFragment = append(description, "catalog-fragment"); if (detail.descriptionHtml.empty()) { append_text_element(descriptionFragment, "p", "No description provided."); diff --git a/src/dusk/ui/package_row.cpp b/src/dusk/ui/package_row.cpp index e02c286481..6077150130 100644 --- a/src/dusk/ui/package_row.cpp +++ b/src/dusk/ui/package_row.cpp @@ -23,6 +23,8 @@ const char* queue_state_class(mods::queue::State state) { return "retrying"; case Verifying: return "installing"; + case Handoff: + return "installing"; case Failed: return "failed"; case Canceled: @@ -46,6 +48,8 @@ std::string state_label(const mods::queue::Item& item) { return fmt::format("Retrying in {}s", item.retrySeconds); case Verifying: return "Verifying"; + case Handoff: + return "Installing"; case Failed: return item.local ? "Package failed" : "Download failed"; case Canceled: diff --git a/src/dusk/ui/queue_window.cpp b/src/dusk/ui/queue_window.cpp index 65150b5c88..ebee779408 100644 --- a/src/dusk/ui/queue_window.cpp +++ b/src/dusk/ui/queue_window.cpp @@ -100,7 +100,8 @@ public: mPause->set_text("Pause"); } - mCancel->root()->SetProperty("display", "block"); + mCancel->root()->SetProperty( + "display", item->state == mods::queue::State::Handoff ? "none" : "block"); mCancel->set_text(mods::queue::is_terminal(item->state) ? "Clear" : "Cancel"); Component::update(); }