diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 364af8f..54044ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,8 @@ add_executable(MassBuilderSaveTool WIN32 Mass/Mass.cpp Maps/LastMissionId.h Maps/StoryProgress.h + ToastQueue/ToastQueue.h + ToastQueue/ToastQueue.cpp FontAwesome/IconsFontAwesome5.h FontAwesome/IconsFontAwesome5Brands.h resource.rc diff --git a/src/ToastQueue/ToastQueue.cpp b/src/ToastQueue/ToastQueue.cpp new file mode 100644 index 0000000..60e7351 --- /dev/null +++ b/src/ToastQueue/ToastQueue.cpp @@ -0,0 +1,160 @@ +// MassBuilderSaveTool +// Copyright (C) 2021 Guillaume Jacquemin +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include + +#include + +#include "../FontAwesome/IconsFontAwesome5.h" + +#include "ToastQueue.h" + +using namespace Corrade; + +constexpr UnsignedInt success_colour = 0xff67d23bu; +constexpr UnsignedInt info_colour = 0xffcc832fu; +constexpr UnsignedInt warning_colour = 0xff2fcfc7u; +constexpr UnsignedInt error_colour = 0xff3134cdu; + +constexpr UnsignedInt fade_time = 150; +constexpr Float base_opacity = 1.0f; +constexpr Vector2 padding{20.0f, 20.0f}; +constexpr Float toast_spacing = 10.0f; + +Toast::Toast(Type type, const std::string& message, std::chrono::milliseconds timeout): + _type{type}, _message{message}, _timeout{timeout}, _creationTime{std::chrono::steady_clock::now()} +{ + _phaseTrack = Animation::Track{{ + {0, Phase::FadeIn}, + {fade_time, Phase::Wait}, + {fade_time + timeout.count(), Phase::FadeOut}, + {(fade_time * 2) + timeout.count(), Phase::TimedOut} + }, Math::select, Animation::Extrapolation::Constant}; +} + +auto Toast::type() -> Type { + return _type; +} + +auto Toast::message() -> const std::string& { + return _message; +} + +auto Toast::timeout() -> std::chrono::milliseconds { + return _timeout; +} + +auto Toast::creationTime() -> std::chrono::steady_clock::time_point { + return _creationTime; +} + +auto Toast::elapsedTime() -> std::chrono::milliseconds { + return std::chrono::duration_cast(std::chrono::steady_clock::now() - _creationTime); +} + +auto Toast::phase() -> Phase { + return _phaseTrack.at(elapsedTime().count()); +} + +auto Toast::opacity() -> Float { + Phase phase = this->phase(); + Long elapsed_time = elapsedTime().count(); + + if(phase == Phase::FadeIn) { + return Float(elapsed_time) / Float(fade_time); + } + else if(phase == Phase::FadeOut) { + return 1.0f - ((Float(elapsed_time) - Float(fade_time) - Float(_timeout.count())) / Float(fade_time)); + } + + return 1.0f; +} + +void ToastQueue::addToast(Toast&& toast) { + _toasts.push_back(std::move(toast)); +} + +void ToastQueue::addToast(Toast::Type type, const std::string& message, std::chrono::milliseconds timeout) { + _toasts.emplace_back(type, message, timeout); +} + +void ToastQueue::draw(Vector2i viewport_size) { + Float height = 0.0f; + + for(UnsignedInt i = 0; i < _toasts.size(); i++) { + Toast* current = &_toasts[i]; + + if(current->phase() == Toast::Phase::TimedOut) { + removeToast(i); + continue; + } + + std::string win_id = Utility::formatString("##Toast{}", i); + + Float opacity = base_opacity * current->opacity(); + + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, opacity); + + ImGui::SetNextWindowPos({viewport_size.x() - padding.x(), viewport_size.y() - padding.y() - height}, ImGuiCond_Always, {1.0f, 1.0f}); + if(ImGui::Begin(win_id.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoDecoration| + ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoNav|ImGuiWindowFlags_NoFocusOnAppearing)) + { + ImColor colour = 0xffffffff; + + switch(current->type()) { + case Toast::Type::Default: + break; + case Toast::Type::Success: + colour = success_colour; + ImGui::TextColored(colour, ICON_FA_CHECK_CIRCLE); + break; + case Toast::Type::Info: + colour = info_colour; + ImGui::TextColored(colour, ICON_FA_INFO_CIRCLE); + break; + case Toast::Type::Warning: + colour = warning_colour; + ImGui::TextColored(colour, ICON_FA_EXCLAMATION_TRIANGLE); + break; + case Toast::Type::Error: + colour = error_colour; + ImGui::TextColored(colour, ICON_FA_TIMES_CIRCLE); + break; + } + + ImGui::SameLine(); + + if(current->message().length() > 127) { + ImGui::TextColored(colour, "%.*s...", 127, current->message().c_str()); + } + else { + ImGui::TextColored(colour, current->message().c_str()); + } + + height += ImGui::GetWindowHeight() + toast_spacing; + } + ImGui::End(); + + ImGui::PopStyleVar(); + } +} + +void ToastQueue::removeToast(Long index) { + _toasts.erase(_toasts.begin() + index); +} diff --git a/src/ToastQueue/ToastQueue.h b/src/ToastQueue/ToastQueue.h new file mode 100644 index 0000000..3c2d4ce --- /dev/null +++ b/src/ToastQueue/ToastQueue.h @@ -0,0 +1,82 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021 Guillaume Jacquemin +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include + +#include +#include + +using namespace Magnum; + +class Toast { + public: + enum class Type : UnsignedByte { + Default, Success, Info, Warning, Error + }; + + enum class Phase : UnsignedByte { + FadeIn, Wait, FadeOut, TimedOut + }; + + explicit Toast(Type type, const std::string& message, + std::chrono::milliseconds timeout = std::chrono::milliseconds{3000}); + + Toast(const Toast& other) = delete; + Toast& operator=(const Toast& other) = delete; + + Toast(Toast&& other) = default; + Toast& operator=(Toast&& other) = default; + + auto type() -> Type; + + auto message() -> std::string const&; + + auto timeout() -> std::chrono::milliseconds; + + auto creationTime() -> std::chrono::steady_clock::time_point; + + auto elapsedTime() -> std::chrono::milliseconds; + + auto phase() -> Phase; + + auto opacity() -> Float; + + private: + Type _type{Type::Default}; + std::string _message; + std::chrono::milliseconds _timeout; + std::chrono::steady_clock::time_point _creationTime; + Animation::Track _phaseTrack; +}; + +class ToastQueue { + public: + void addToast(Toast&& toast); + + void addToast(Toast::Type type, const std::string& message, + std::chrono::milliseconds timeout = std::chrono::milliseconds{3000}); + + void draw(Vector2i viewport_size); + + private: + void removeToast(Long index); + + std::vector _toasts; +};