From 4710715102df3806479985679bd8048631ccaab5 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 18 Sep 2018 01:38:02 +0800 Subject: I think I can't sleep well after this commit. Still a lot of bugs!!! --- CruUI/CruUI.vcxproj | 3 + CruUI/CruUI.vcxproj.filters | 9 ++ CruUI/application.cpp | 6 +- CruUI/application.h | 11 +++ CruUI/builder.h | 42 ++++++++++ CruUI/timer.cpp | 163 ++++++++++++++++-------------------- CruUI/timer.h | 26 +++--- CruUI/ui/animations/animation.cpp | 103 +++++++++++++++++++++++ CruUI/ui/animations/animation.h | 131 +++++++++++++++++++++++++++++ CruUI/ui/controls/toggle_button.cpp | 32 ++++++- CruUI/ui/controls/toggle_button.h | 2 + 11 files changed, 418 insertions(+), 110 deletions(-) create mode 100644 CruUI/builder.h create mode 100644 CruUI/ui/animations/animation.cpp create mode 100644 CruUI/ui/animations/animation.h diff --git a/CruUI/CruUI.vcxproj b/CruUI/CruUI.vcxproj index 4d2538d8..adc035f1 100644 --- a/CruUI/CruUI.vcxproj +++ b/CruUI/CruUI.vcxproj @@ -161,6 +161,8 @@ + + @@ -174,6 +176,7 @@ + diff --git a/CruUI/CruUI.vcxproj.filters b/CruUI/CruUI.vcxproj.filters index 64cb4299..af6853a9 100644 --- a/CruUI/CruUI.vcxproj.filters +++ b/CruUI/CruUI.vcxproj.filters @@ -66,6 +66,12 @@ Header Files + + Header Files + + + Header Files + @@ -107,5 +113,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/CruUI/application.cpp b/CruUI/application.cpp index 04b18ff0..f1d57153 100644 --- a/CruUI/application.cpp +++ b/CruUI/application.cpp @@ -5,6 +5,7 @@ #include "timer.h" #include "ui/window.h" #include "graph/graph.h" +#include "ui/animations/animation.h" namespace cru { constexpr auto god_window_class_name = L"GodWindowClass"; @@ -63,9 +64,9 @@ namespace cru { case WM_TIMER: { const auto action = application_->GetTimerManager()->GetAction(static_cast(w_param)); - if (action.has_value()) + if (action) { - action.value()(); + (*action)(); return 0; } break; @@ -95,6 +96,7 @@ namespace cru { window_manager_ = std::make_unique(); graph_manager_ = std::make_unique(); timer_manager_ = std::make_unique(); + animation_manager_ = std::make_unique(); god_window_ = std::make_unique(this); } diff --git a/CruUI/application.h b/CruUI/application.h index fe0d431c..a0229bfa 100644 --- a/CruUI/application.h +++ b/CruUI/application.h @@ -14,6 +14,11 @@ namespace cru { class WindowClass; class WindowManager; + + namespace animations + { + class AnimationManager; + } } namespace graph @@ -81,6 +86,11 @@ namespace cru return timer_manager_.get(); } + ui::animations::AnimationManager* GetAnimationManager() const + { + return animation_manager_.get(); + } + HINSTANCE GetInstanceHandle() const { return h_instance_; @@ -97,6 +107,7 @@ namespace cru std::unique_ptr window_manager_; std::unique_ptr graph_manager_; std::unique_ptr timer_manager_; + std::unique_ptr animation_manager_; std::unique_ptr god_window_; }; diff --git a/CruUI/builder.h b/CruUI/builder.h new file mode 100644 index 00000000..3ae8724e --- /dev/null +++ b/CruUI/builder.h @@ -0,0 +1,42 @@ +#pragma once + +#include "base.h" + +namespace cru +{ + template + class OneTimeBuilder : public Object + { + protected: + OneTimeBuilder() = default; + + public: + OneTimeBuilder(const OneTimeBuilder& other) = delete; + OneTimeBuilder(OneTimeBuilder&& other) = delete; + OneTimeBuilder& operator=(const OneTimeBuilder& other) = delete; + OneTimeBuilder& operator=(OneTimeBuilder&& other) = delete; + virtual ~OneTimeBuilder() = default; + + T* Create() + { + if (is_valid_) + { + is_valid_ = false; + return OnCreate(); + } + else + throw std::runtime_error("OneTimeBuilder is invalid."); + } + + bool IsValid() const + { + return is_valid_; + } + + protected: + virtual T* OnCreate() = 0; + + private: + bool is_valid_ = true; + }; +} diff --git a/CruUI/timer.cpp b/CruUI/timer.cpp index eabc7865..7435adde 100644 --- a/CruUI/timer.cpp +++ b/CruUI/timer.cpp @@ -2,96 +2,79 @@ namespace cru { - TimerManager* TimerManager::instance_ = nullptr; - - TimerManager* TimerManager::GetInstance() - { - return instance_; - } - - TimerManager::TimerManager() - { - instance_ = this; - } - - TimerManager::~TimerManager() - { - instance_ = nullptr; - } - - UINT_PTR TimerManager::CreateTimer(const UINT microseconds, const bool loop, const TimerAction & action) - { - auto id = ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), 0, microseconds, nullptr); - if (loop) - map_[id] = action; - else - map_[id] = [this, action, id]() { - action(); - this->KillTimer(id); - }; - - return id; - } - - void TimerManager::KillTimer(const UINT_PTR id) - { - const auto find_result = map_.find(id); - if (find_result != map_.cend()) - { - ::KillTimer(nullptr, id); - map_.erase(find_result); - } - } - - std::optional TimerManager::GetAction(const UINT_PTR id) - { - auto find_result = map_.find(id); - if (find_result == map_.cend()) - return std::nullopt; - return find_result->second; - } - - class TimerTaskImpl : public ITimerTask - { - public: - explicit TimerTaskImpl(UINT_PTR id); - TimerTaskImpl(const TimerTaskImpl& other) = delete; - TimerTaskImpl(TimerTaskImpl&& other) = delete; - TimerTaskImpl& operator=(const TimerTaskImpl& other) = delete; - TimerTaskImpl& operator=(TimerTaskImpl&& other) = delete; - ~TimerTaskImpl() override = default; - - void Cancel() override; - - private: - UINT_PTR id_; - }; - - TimerTaskImpl::TimerTaskImpl(const UINT_PTR id) - : id_(id) - { - - } + UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, std::shared_ptr> action) + { + const auto id = current_count_++; + ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), 0, milliseconds, nullptr); + if (loop) + map_[id] = std::move(action); + else + map_[id] = std::make_shared>([this, action, id]() mutable { + (*action)(); + this->KillTimer(id); + }); + return id; + } + + void TimerManager::KillTimer(const UINT_PTR id) + { + const auto find_result = map_.find(id); + if (find_result != map_.cend()) + { + ::KillTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id); + map_.erase(find_result); + } + } + + std::shared_ptr> TimerManager::GetAction(const UINT_PTR id) + { + const auto find_result = map_.find(id); + if (find_result == map_.cend()) + return nullptr; + return find_result->second; + } + + class TimerTaskImpl : public ITimerTask + { + public: + explicit TimerTaskImpl(UINT_PTR id); + TimerTaskImpl(const TimerTaskImpl& other) = delete; + TimerTaskImpl(TimerTaskImpl&& other) = delete; + TimerTaskImpl& operator=(const TimerTaskImpl& other) = delete; + TimerTaskImpl& operator=(TimerTaskImpl&& other) = delete; + ~TimerTaskImpl() override = default; + + void Cancel() override; + + private: + UINT_PTR id_; + }; + + TimerTaskImpl::TimerTaskImpl(const UINT_PTR id) + : id_(id) + { + + } void TimerTaskImpl::Cancel() - { - TimerManager::GetInstance()->KillTimer(id_); - } - - inline UINT SecondToMicroSecond(const double seconds) - { - return static_cast(seconds * 1000); - } - - std::shared_ptr SetTimeout(const double seconds, const TimerAction & action) - { - auto id = TimerManager::GetInstance()->CreateTimer(SecondToMicroSecond(seconds), false, action); - return std::make_shared(id); - } - - std::shared_ptr SetInterval(const double seconds, const TimerAction & action) - { - auto id = TimerManager::GetInstance()->CreateTimer(SecondToMicroSecond(seconds), true, action); - return std::make_shared(id); - } + { + TimerManager::GetInstance()->KillTimer(id_); + } + + inline UINT SecondToMillisecond(const double seconds) + { + return static_cast(seconds * 1000); + } + + std::shared_ptr SetTimeout(double seconds, std::shared_ptr> action) + { + auto id = TimerManager::GetInstance()->CreateTimer(SecondToMillisecond(seconds), false, std::move(action)); + return std::make_shared(id); + } + + std::shared_ptr SetInterval(double seconds, std::shared_ptr> action) + { + auto id = TimerManager::GetInstance()->CreateTimer(SecondToMillisecond(seconds), true, std::move(action)); + return std::make_shared(id); + } } diff --git a/CruUI/timer.h b/CruUI/timer.h index 1a512a44..70913775 100644 --- a/CruUI/timer.h +++ b/CruUI/timer.h @@ -12,31 +12,29 @@ namespace cru { - using TimerAction = std::function; - class TimerManager : public Object { - friend class cru::Application; - private: - static TimerManager* instance_; - public: - static TimerManager* GetInstance(); + static TimerManager* GetInstance() + { + return Application::GetInstance()->GetTimerManager(); + } public: - TimerManager(); + TimerManager() = default; TimerManager(const TimerManager& other) = delete; TimerManager(TimerManager&& other) = delete; TimerManager& operator=(const TimerManager& other) = delete; TimerManager& operator=(TimerManager&& other) = delete; - ~TimerManager() override; + ~TimerManager() override = default; - UINT_PTR CreateTimer(UINT microseconds, bool loop, const TimerAction& action); + UINT_PTR CreateTimer(UINT milliseconds, bool loop, std::shared_ptr> action); void KillTimer(UINT_PTR id); - std::optional GetAction(UINT_PTR id); + std::shared_ptr> GetAction(UINT_PTR id); private: - std::map map_{}; + std::map>> map_{}; + UINT_PTR current_count_ = 0; }; struct ITimerTask : virtual Interface @@ -44,6 +42,6 @@ namespace cru virtual void Cancel() = 0; }; - std::shared_ptr SetTimeout(double seconds, const TimerAction& action); - std::shared_ptr SetInterval(double seconds, const TimerAction& action); + std::shared_ptr SetTimeout(double seconds, std::shared_ptr> action); + std::shared_ptr SetInterval(double seconds, std::shared_ptr> action); } diff --git a/CruUI/ui/animations/animation.cpp b/CruUI/ui/animations/animation.cpp new file mode 100644 index 00000000..26b7d5fc --- /dev/null +++ b/CruUI/ui/animations/animation.cpp @@ -0,0 +1,103 @@ +#include "animation.h" + +#include +#include + +namespace cru::ui::animations +{ + constexpr int frame_rate = 60; + constexpr double frame_step_time = 1.0 / frame_rate; + + AnimationManager::AnimationManager() + : timer_action_(new Action<>([this]() + { + for (auto& animation : animations_) + { + if (animation.second->Step(frame_step_time)) + InvokeLater([=] + { + RemoveAnimation(animation.second); //TODO!!! + }); + } + })) + { + + } + + AnimationManager::~AnimationManager() + { + for (auto& animation : animations_) + delete animation.second; + + if (timer_) + timer_->Cancel(); + } + + void AnimationManager::AddAnimation(Animation* animation) + { + if (animations_.empty()) + timer_ = SetInterval(frame_step_time, timer_action_); + + const auto find_result = animations_.find(animation->GetTag()); + if (find_result != animations_.cend()) + find_result->second->Cancel(); + animations_.insert_or_assign(animation->GetTag(), animation); + } + + void AnimationManager::RemoveAnimation(Animation* animation) + { + const auto find_result = animations_.find(animation->GetTag()); + if (find_result != animations_.cend()) + { + delete find_result->second; + animations_.erase(find_result); + } + + if (animations_.empty()) + { + assert(timer_); + timer_->Cancel(); + timer_ = nullptr; + } + } + + Animation::Animation(String tag, const double duration, + const Vector>>& step_handlers, + const Vector>>& start_handlers, + const Vector>>& finish_handlers, + const Vector>>& cancel_handlers + ) : tag_(std::move(tag)), duration_(duration), step_handlers_(step_handlers), + start_handlers_(start_handlers), finish_handlers_(finish_handlers), cancel_handlers_(cancel_handlers) + { + AnimationManager::GetInstance()->AddAnimation(this); + } + + bool Animation::Step(const double time) + { + current_time_ += time; + if (current_time_ > duration_) + { + for (auto& handler : step_handlers_) + (*handler)(this, 1); + for (auto& handler : finish_handlers_) + (*handler)(this); + return true; + } + else + { + for (auto& handler : step_handlers_) + (*handler)(this, current_time_ / duration_); + return false; + } + } + + void Animation::Cancel() + { + for (auto& handler : cancel_handlers_) + (*handler)(this); + InvokeLater([this] + { + AnimationManager::GetInstance()->RemoveAnimation(this); //TODO!!! + }); + } +} diff --git a/CruUI/ui/animations/animation.h b/CruUI/ui/animations/animation.h new file mode 100644 index 00000000..fb6ba93e --- /dev/null +++ b/CruUI/ui/animations/animation.h @@ -0,0 +1,131 @@ +#pragma once + +#include + +#include "base.h" +#include "application.h" +#include "timer.h" +#include "builder.h" + +namespace cru::ui::animations +{ + class Animation; + + + class AnimationManager : public Object + { + public: + static AnimationManager* GetInstance() + { + return Application::GetInstance()->GetAnimationManager(); + } + + public: + AnimationManager(); + AnimationManager(const AnimationManager& other) = delete; + AnimationManager(AnimationManager&& other) = delete; + AnimationManager& operator=(const AnimationManager& other) = delete; + AnimationManager& operator=(AnimationManager&& other) = delete; + ~AnimationManager() override; + + void AddAnimation(Animation* animation); + void RemoveAnimation(Animation* animation); + + private: + std::map animations_; + std::shared_ptr timer_; + std::shared_ptr> timer_action_; + }; + + class Animation : public Object + { + friend class AnimationManager; + protected: + Animation( + String tag, + double duration, + const Vector>>& step_handlers, + const Vector>>& start_handlers, + const Vector>>& finish_handlers, + const Vector>>& cancel_handlers + ); + + public: + Animation(const Animation& other) = delete; + Animation(Animation&& other) = delete; + Animation& operator=(const Animation& other) = delete; + Animation& operator=(Animation&& other) = delete; + ~Animation() override = default; // The animation will never destroy by users. + + bool Step(double time); + void Cancel(); + String GetTag() const + { + return tag_; + } + + private: + const String tag_; + const double duration_; + Vector>> step_handlers_; + Vector>> start_handlers_; + Vector>> finish_handlers_; + Vector>> cancel_handlers_; + + double current_time_ = 0; + + public: + class Builder : public OneTimeBuilder + { + public: + Builder(String tag, const double duration) + : tag(std::move(tag)), duration(duration) + { + + } + + String tag; + double duration; + + Builder& AddStepHandler(Action&& handler) + { + if (IsValid()) + step_handlers_.push_back(std::make_shared>(std::move(handler))); + return *this; + } + + Builder& AddStartHandler(Action&& handler) + { + if (IsValid()) + start_handlers_.push_back(std::make_shared>(std::move(handler))); + return *this; + } + + Builder& AddFinishHandler(Action&& handler) + { + if (IsValid()) + finish_handlers_.push_back(std::make_shared>(std::move(handler))); + return *this; + } + + Builder& AddCancelHandler(Action&& handler) + { + if (IsValid()) + cancel_handlers_.push_back(std::make_shared>(std::move(handler))); + return *this; + } + + protected: + Animation* OnCreate() override + { + return new Animation(std::move(tag), duration, step_handlers_, start_handlers_, finish_handlers_, cancel_handlers_); + } + + private: + Vector>> step_handlers_; + Vector>> start_handlers_; + Vector>> finish_handlers_; + Vector>> cancel_handlers_; + }; + }; +} diff --git a/CruUI/ui/controls/toggle_button.cpp b/CruUI/ui/controls/toggle_button.cpp index ea2329ea..a22cfda8 100644 --- a/CruUI/ui/controls/toggle_button.cpp +++ b/CruUI/ui/controls/toggle_button.cpp @@ -1,10 +1,14 @@ #include "toggle_button.h" +#include + #include "graph/graph.h" +#include "ui/animations/animation.h" namespace cru::ui::controls { using graph::CreateSolidBrush; + using animations::Animation; // ui length parameters of toggle button. constexpr float half_height = 15; @@ -13,11 +17,11 @@ namespace cru::ui::controls constexpr float inner_circle_radius = half_height - stroke_width; constexpr float inner_circle_x = half_width - half_height; - ToggleButton::ToggleButton() + ToggleButton::ToggleButton() : current_circle_position_(-inner_circle_x) { graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(D2D1::RoundedRect(D2D1::RectF(-half_width, -half_height, half_width, half_height), half_height, half_height), &frame_path_); - on_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightBlue)); + on_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::DeepSkyBlue)); off_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightGray)); } @@ -42,6 +46,26 @@ namespace cru::ui::controls if (state != state_) { state_ = state; + float destination_x; + + if (state) + destination_x = inner_circle_x; + else + destination_x = -inner_circle_x; + + const auto previous_position = current_circle_position_; + const auto delta = destination_x - current_circle_position_; + + constexpr double total_time = 0.5; + + const auto time = total_time * std::abs(delta) / (inner_circle_x * 2); + + Animation::Builder(fmt::format(L"ToggleButton {}", reinterpret_cast(this)), time).AddStepHandler([=](Animation*, const float percentage) + { + current_circle_position_ = previous_position + delta * percentage; + Repaint(); + }).Create(); + OnToggleInternal(state); Repaint(); } @@ -65,12 +89,12 @@ namespace cru::ui::controls if (state_) { device_context->DrawGeometry(frame_path_.Get(), on_brush_.Get(), stroke_width); - device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(inner_circle_x, 0), inner_circle_radius, inner_circle_radius), on_brush_.Get()); + device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), on_brush_.Get()); } else { device_context->DrawGeometry(frame_path_.Get(), off_brush_.Get(), stroke_width); - device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(-inner_circle_x, 0), inner_circle_radius, inner_circle_radius), off_brush_.Get()); + device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), off_brush_.Get()); } }); } diff --git a/CruUI/ui/controls/toggle_button.h b/CruUI/ui/controls/toggle_button.h index d2e49473..8668b2c9 100644 --- a/CruUI/ui/controls/toggle_button.h +++ b/CruUI/ui/controls/toggle_button.h @@ -52,6 +52,8 @@ namespace cru::ui::controls private: bool state_ = false; + float current_circle_position_; + Microsoft::WRL::ComPtr frame_path_; Microsoft::WRL::ComPtr on_brush_; Microsoft::WRL::ComPtr off_brush_; -- cgit v1.2.3