Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(message): support multiple message #47

Merged
merged 7 commits into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 98 additions & 24 deletions src/Controller/Core/MessageManager.cc
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#include <Controller/AsyncExecutor.hh>
#include <Controller/Core/MessageManager.h>
#include <Controller/Core/UiBase.h>
#include <Controller/Core/UiUtility.h>
#include <chrono>
#include <slint.h>
#include <memory>
#include <spdlog/spdlog.h>
#include <string_view>

EVENTO_UI_START

Expand All @@ -13,43 +13,117 @@
, bridge(bridge) {
auto& self = *this;

toastList = std::make_shared<slint::VectorModel<ToastData>>();

self->on_show_message([this](slint::SharedString content, MessageType type) {
showMessage(std::string(content), type);
return showMessage(std::string(content), type);
});

self->on_get_message([this](int id) -> MessageData { return getMessage(id); });

self->on_hide_message([this](int id) { hideMessage(id); });

self->set_toast_list(toastList);
}

void MessageManager::showMessage(std::string content,
MessageType type,
std::chrono::steady_clock::duration timeout) {
int MessageManager::showMessage(std::string content,
MessageType type,
std::chrono::steady_clock::duration timeout) {
auto& self = *this;
auto id = nextId++;

if (isMessageShow) {
hideMessage();
evento::executor()->asyncExecute(
doNothing,
[=, this] { showMessage(content, type, timeout); },
animationLength,
AsyncExecutor::Once | AsyncExecutor::Delay);
return;
}
UiUtility::StylishLog::general(logOrigin,
std::format("new message [{}] content = \"{}\"", id, content));
newToast(id, {.content = slint::SharedString(content), .type = type});

UiUtility::StylishLog::newMessageShowed(logOrigin, content);
isMessageShow = true;
self->set_type(type);
self->set_content(std::string_view(content));
self->set_visible(true);
// set auto hide
evento::executor()->asyncExecute(
doNothing,
[this] { hideMessage(); },
[this, id] { hideMessage(id); },
timeout + animationLength,
AsyncExecutor::Once | AsyncExecutor::Delay);

return id;
}

void MessageManager::hideMessage(int id) {
if (auto index = getIndex(id); index != -1 && !toastList->row_data(getIndex(id))->removed) {
hideToast(id);
} else {
UiUtility::StylishLog::messageOperation(
logOrigin, id, "scheduled hide cancelled: already hidden or deleted");
}
}

int MessageManager::getIndex(int id) {

Check warning on line 58 in src/Controller/Core/MessageManager.cc

View workflow job for this annotation

GitHub Actions / review

method 'getIndex' can be made static [readability-convert-member-functions-to-static]
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < toastList->row_count(); i++) {
if (toastList->row_data(i)->id == id) {
return i;
}
}
return -1;
}

void MessageManager::newToast(int id, MessageData data) {
// prepare data then instantiate a toast
messageData.insert({id, data});
toastList->push_back({id, 0});
UiUtility::StylishLog::messageOperation(logOrigin, id, "instantiate toast, data added");

// make animation available
slint::invoke_from_event_loop([this, id] { showToast(id); });
}

void MessageManager::showToast(int id) {
operateToastData(id, increaseElevation);
UiUtility::StylishLog::messageOperation(logOrigin, id, "show");

// correct existing toast elevation
for (int i = 0; i < toastList->row_count(); i++) {
if (i != getIndex(id)) {
operateToastData(toastList->row_data(i)->id, increaseElevation);
}
}
}

void MessageManager::hideToast(int id) {
operateToastData(id, markRemoved);
UiUtility::StylishLog::messageOperation(logOrigin, id, "hide");

// wait animation finished
evento::executor()->asyncExecute(
doNothing,
[this, id] { deleteToast(id); },
animationLength,
AsyncExecutor::Once | AsyncExecutor::Delay);
}

void MessageManager::deleteToast(int id) {

Check warning on line 101 in src/Controller/Core/MessageManager.cc

View workflow job for this annotation

GitHub Actions / review

method 'deleteToast' can be made static [readability-convert-member-functions-to-static]
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
cEvolve05 marked this conversation as resolved.
Show resolved Hide resolved
// correct existing toast elevation bigger than given id
int removedElevation = toastList->row_data(getIndex(id))->elevation;
for (int i = 0; i < toastList->row_count(); i++) {
auto toastData = toastList->row_data(i);
if (toastData->elevation > removedElevation) {
operateToastData(toastData->id, decreaseElevation);
}
}

// delete instance and data
toastList->erase(getIndex(id));
messageData.erase(id);
UiUtility::StylishLog::general(logOrigin, std::format("delete message [{}]", id));
}

void MessageManager::operateToastData(int id, std::function<ToastData(ToastData)> operation) {
auto index = getIndex(id);
auto data = toastList->row_data(index).value();
toastList->set_row_data(index, operation(data));
}

void MessageManager::hideMessage() {
MessageData MessageManager::getMessage(int id) {
auto& self = *this;

isMessageShow = false;
self->set_visible(false);
return messageData.at(id);
}

EVENTO_UI_END
54 changes: 47 additions & 7 deletions src/Controller/Core/MessageManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <Controller/Core/GlobalAgent.hh>
#include <Controller/Core/UiBase.h>
#include <chrono>
#include <functional>
#include <map>
#include <string>

EVENTO_UI_START
Expand All @@ -12,21 +14,59 @@ class MessageManager : private GlobalAgent<MessageManagerBridge> {
UiBridge& bridge;
std::string logOrigin = "MessageManager";

bool isMessageShow = false;
const static inline auto animationLength = std::chrono::milliseconds(200);

public:
MessageManager(slint::ComponentHandle<UiEntryName> uiEntry, UiBridge& bridge);
MessageManager(MessageManager&) = delete;

void showMessage(std::string content,
MessageType type = MessageType::Info,
std::chrono::steady_clock::duration timeout = std::chrono::milliseconds(3000));
// @return message id
int showMessage(std::string content,
MessageType type = MessageType::Info,
std::chrono::steady_clock::duration timeout = std::chrono::milliseconds(3000));

// unnecessary to invoke if set correct timeout
void hideMessage();
void hideMessage(int id);

private:
// id, int
int nextId = 0;

// sync with slint, every line stand for a toast instance
std::shared_ptr<slint::VectorModel<ToastData>> toastList;
// every toast instance will read data from here, mapping id to message data
std::map<int, MessageData> messageData;
MessageData getMessage(int id);
int getIndex(int id);

/**
* toast life cycle:
* - called newToast()
* - new instance, invisible (elevation=0)
* - called showToast()
* - visible
* - called hideToast()
* - invisible (removed) (exist in list)
* - called deleteToast()
*/
void newToast(int id, MessageData data);
void showToast(int id);
void hideToast(int id);
void deleteToast(int id);

void operateToastData(int id, std::function<ToastData(ToastData)> operation);
static inline auto increaseElevation = [](ToastData data) -> ToastData {
data.elevation += 1;
return data;
};
static inline auto decreaseElevation = [](ToastData data) -> ToastData {
data.elevation -= 1;
return data;
};
static inline auto markRemoved = [](ToastData data) -> ToastData {
data.removed = true;
return data;
};

const static inline auto animationLength = std::chrono::milliseconds(200);
static auto inline doNothing = []() -> Task<void> { co_return; };
};

Expand Down
15 changes: 0 additions & 15 deletions src/Controller/Core/UiUtility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,4 @@ bool UiUtility::isTransparent(ViewName target) {
return overlayList.find(target) != overlayList.end();
}

void UiUtility::StylishLog::viewActionTriggered(std::string origin,
std::string actionName,
std::string viewName) {
spdlog::debug("{}: {}: triggered {}", origin, viewName, actionName);
}

void UiUtility::StylishLog::viewVisibilityChanged(std::string origin,
std::string actionName,
std::string viewName) {
spdlog::debug("{}: {} visibility changed: {}", origin, viewName, actionName);
}
void UiUtility::StylishLog::newMessageShowed(std::string origin, std::string content) {
spdlog::debug("{}: new message: {}", origin, content);
}

EVENTO_UI_END
15 changes: 12 additions & 3 deletions src/Controller/Core/UiUtility.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@ class UiUtility {

class StylishLog {
public:
static void general(std::string origin, std::string content) {
spdlog::debug("{}: {}", origin, content);
}
static void viewActionTriggered(std::string origin,
std::string actionName,
std::string viewName = std::string("All-View"));
std::string viewName = std::string("All-View")) {
spdlog::debug("{}: {}: triggered {}", origin, viewName, actionName);
}
static void viewVisibilityChanged(std::string origin,
std::string actionName,
std::string viewName);
static void newMessageShowed(std::string origin, std::string content);
std::string viewName) {
spdlog::debug("{}: {} visibility changed: {}", origin, viewName, actionName);
}
static void messageOperation(std::string origin, int id, std::string content) {
spdlog::debug("{}: message [{}]: {}", origin, id, content);
}
};

private:
Expand Down
10 changes: 5 additions & 5 deletions src/Controller/Core/ViewManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ ViewManager::ViewManager(slint::ComponentHandle<UiEntryName> uiEntry, UiBridge&
, bridge(bridge) {
auto& self = *this;

self->on_navigate_to([this](ViewName newView) { return navigateTo(newView); });
self->on_clean_navigate_to([this](ViewName newView) { return cleanNavigateTo(newView); });
self->on_replace_navigate_to([this](ViewName newView) { return replaceNavigateTo(newView); });
self->on_prior_view([this]() { return priorView(); });
self->on_navigate_to([this](ViewName newView) { navigateTo(newView); });
self->on_clean_navigate_to([this](ViewName newView) { cleanNavigateTo(newView); });
self->on_replace_navigate_to([this](ViewName newView) { replaceNavigateTo(newView); });
self->on_prior_view([this]() { priorView(); });

self->on_is_show([&self](ViewName target) {
// used to trigger re-calculate
Expand All @@ -33,7 +33,7 @@ void ViewManager::initStack(ViewName newView, std::any data) {
static bool scheduleSync = false;
if (!scheduleSync) {
scheduleSync = true;
slint::invoke_from_event_loop([this] { return syncViewVisibility(); });
slint::invoke_from_event_loop([this] { syncViewVisibility(); });
}
}

Expand Down
52 changes: 36 additions & 16 deletions ui/app.slint
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
AccountManager,
MessageManager,
ViewName,
MessageType
MessageType,
MessageData,
ToastData
} from "./logic/index.slint";

import {
Expand Down Expand Up @@ -44,11 +46,10 @@ export {
ViewManagerBridge,
AccountManagerBridge,
MessageManagerBridge,
ViewManager,
AccountManager,
MessageManager,
ViewName,
MessageType,
MessageData,
ToastData,
LoginOverlayBridge,
MenuOverlayBridge,
DiscoveryPageBridge,
Expand Down Expand Up @@ -260,29 +261,48 @@ export component App inherits Window {
width: 100%;
}

Toast {
x: root.width - self.width - 16px;
y: root.height + 16px;
private property <length> toast-padding: 16px;
private property <length> toast-vertical-spacing: 8px;
private property <length> toast-height: 48px;
private property <Point> toast-origin: { x: root.width - toast-padding, y: root.height - toast-padding };
for item in MessageManager.toast-list: Toast {
toast-data: item;
x: root.width - toast-padding - self.width;
y: get-toast-y(self.elevation);
width: self.min-width > (parent.width * (1 - 0.618) / 2) ? self.preferred-width : parent.width * (1 - 0.618) / 2;
animate y {
duration: 200ms;
easing: ease-out-quart;
}
states [
visible when MessageManager.visible: {
y: root.height - self.height - 16px;
removed when self.removed: {
x: root.width - toast-padding - self.width / 2;
opacity: 0;
in {
animate y {
animate opacity {
duration: 200ms;
easing: ease-out-quart;
easing: linear;
}
}
out {
animate y {
animate x {
duration: 200ms;
easing: ease-in-quart;
}
}
}
invisible when !MessageManager.visible: {
y: root.height + 16px;
// self.elevation > 0
visible when self.elevation > 0: {
opacity: 1;
}
invisible when self.elevation == 0: {
opacity: 0;
}
]
}
function get-toast-y(elevation: int) -> length {
if (elevation > 0) {
return toast-origin.y - elevation * toast-height - (elevation - 1) * toast-vertical-spacing;
} else {
return root.height + toast-padding;
}
}
}
6 changes: 3 additions & 3 deletions ui/assets/image/icon/arrow-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading