mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-11-04 09:20:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			410 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			410 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "animation_dock_widget.hpp"
 | 
						|
#include "ui_animation_dock_widget.h"
 | 
						|
 | 
						|
#include <scwx/qt/manager/hotkey_manager.hpp>
 | 
						|
#include <scwx/qt/settings/general_settings.hpp>
 | 
						|
#include <scwx/qt/util/time.hpp>
 | 
						|
#include <scwx/util/logger.hpp>
 | 
						|
 | 
						|
#include <QTimer>
 | 
						|
 | 
						|
namespace scwx
 | 
						|
{
 | 
						|
namespace qt
 | 
						|
{
 | 
						|
namespace ui
 | 
						|
{
 | 
						|
 | 
						|
static const std::string logPrefix_ = "scwx::qt::ui::animation_dock_widget";
 | 
						|
static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 | 
						|
 | 
						|
#if (__cpp_lib_chrono >= 201907L)
 | 
						|
using local_days  = std::chrono::local_days;
 | 
						|
using zoned_time_ = std::chrono::zoned_time<std::chrono::seconds>;
 | 
						|
#else
 | 
						|
using local_days  = date::local_days;
 | 
						|
using zoned_time_ = date::zoned_time<std::chrono::seconds>;
 | 
						|
#endif
 | 
						|
 | 
						|
class AnimationDockWidgetImpl
 | 
						|
{
 | 
						|
public:
 | 
						|
   explicit AnimationDockWidgetImpl(AnimationDockWidget* self) : self_ {self}
 | 
						|
   {
 | 
						|
      static const QString prefix   = QObject::tr("Auto Update");
 | 
						|
      static const QString disabled = QObject::tr("Disabled");
 | 
						|
      static const QString enabled  = QObject::tr("Enabled");
 | 
						|
 | 
						|
      enabledString_  = QString("%1: %2").arg(prefix).arg(enabled);
 | 
						|
      disabledString_ = QString("%1: %2").arg(prefix).arg(disabled);
 | 
						|
   }
 | 
						|
   ~AnimationDockWidgetImpl() = default;
 | 
						|
 | 
						|
   const QIcon kPauseIcon_ {":/res/icons/font-awesome-6/pause-solid.svg"};
 | 
						|
   const QIcon kPlayIcon_ {":/res/icons/font-awesome-6/play-solid.svg"};
 | 
						|
 | 
						|
   QString enabledString_;
 | 
						|
   QString disabledString_;
 | 
						|
 | 
						|
   AnimationDockWidget* self_;
 | 
						|
 | 
						|
   std::shared_ptr<manager::HotkeyManager> hotkeyManager_ {
 | 
						|
      manager::HotkeyManager::Instance()};
 | 
						|
 | 
						|
   types::AnimationState animationState_ {types::AnimationState::Pause};
 | 
						|
   types::MapTime        viewType_ {types::MapTime::Live};
 | 
						|
   bool                  isLive_ {true};
 | 
						|
 | 
						|
   local_days           selectedDate_ {};
 | 
						|
   std::chrono::seconds selectedTime_ {};
 | 
						|
 | 
						|
   const scwx::util::time_zone* timeZone_ {nullptr};
 | 
						|
 | 
						|
   void UpdateTimeZoneLabel(const zoned_time_ zonedTime);
 | 
						|
   std::chrono::system_clock::time_point GetTimePoint();
 | 
						|
   void SetTimePoint(std::chrono::system_clock::time_point time);
 | 
						|
 | 
						|
   void ConnectSignals();
 | 
						|
   void UpdateAutoUpdateLabel();
 | 
						|
};
 | 
						|
 | 
						|
AnimationDockWidget::AnimationDockWidget(QWidget* parent) :
 | 
						|
    QFrame(parent),
 | 
						|
    p {std::make_unique<AnimationDockWidgetImpl>(this)},
 | 
						|
    ui(new Ui::AnimationDockWidget)
 | 
						|
{
 | 
						|
   ui->setupUi(this);
 | 
						|
 | 
						|
#if (__cpp_lib_chrono >= 201907L)
 | 
						|
   p->timeZone_ = std::chrono::get_tzdb().locate_zone("UTC");
 | 
						|
#else
 | 
						|
   p->timeZone_ = date::get_tzdb().locate_zone("UTC");
 | 
						|
#endif
 | 
						|
   const std::chrono::sys_seconds currentTimePoint =
 | 
						|
      std::chrono::floor<std::chrono::minutes>(
 | 
						|
         std::chrono::system_clock::now());
 | 
						|
   p->SetTimePoint(currentTimePoint);
 | 
						|
 | 
						|
   // Update maximum date on a timer
 | 
						|
   // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) Qt Owns this memory
 | 
						|
   auto* maxDateTimer = new QTimer(this);
 | 
						|
   connect(maxDateTimer,
 | 
						|
           &QTimer::timeout,
 | 
						|
           this,
 | 
						|
           [this]()
 | 
						|
           {
 | 
						|
              // Update maximum date to today
 | 
						|
              QDate currentDate = QDateTime::currentDateTimeUtc().date();
 | 
						|
              if (ui->dateEdit->maximumDate() != currentDate)
 | 
						|
              {
 | 
						|
                 ui->dateEdit->setMaximumDate(currentDate);
 | 
						|
              }
 | 
						|
           });
 | 
						|
 | 
						|
   // Evaluate every 15 seconds, every second is unnecessary
 | 
						|
   maxDateTimer->start(15000);
 | 
						|
 | 
						|
   // Set loop defaults
 | 
						|
   auto& generalSettings = settings::GeneralSettings::Instance();
 | 
						|
   ui->loopTimeSpinBox->setValue(generalSettings.loop_time().GetValue());
 | 
						|
   ui->loopSpeedSpinBox->setValue(generalSettings.loop_speed().GetValue());
 | 
						|
   ui->loopDelaySpinBox->setValue(generalSettings.loop_delay().GetValue() *
 | 
						|
                                  0.001);
 | 
						|
 | 
						|
   // Connect widget signals
 | 
						|
   p->ConnectSignals();
 | 
						|
}
 | 
						|
 | 
						|
AnimationDockWidget::~AnimationDockWidget()
 | 
						|
{
 | 
						|
   delete ui;
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidgetImpl::UpdateTimeZoneLabel(const zoned_time_ zonedTime)
 | 
						|
{
 | 
						|
#if (__cpp_lib_chrono >= 201907L)
 | 
						|
   namespace df                                            = std;
 | 
						|
   static constexpr std::string_view kFormatStringTimezone = "{:%Z}";
 | 
						|
#else
 | 
						|
   namespace df                                   = date;
 | 
						|
   static const std::string kFormatStringTimezone = "%Z";
 | 
						|
#endif
 | 
						|
   const std::string timeZoneStr = df::format(kFormatStringTimezone, zonedTime);
 | 
						|
   self_->ui->timeZoneLabel->setText(timeZoneStr.c_str());
 | 
						|
}
 | 
						|
 | 
						|
std::chrono::system_clock::time_point AnimationDockWidgetImpl::GetTimePoint()
 | 
						|
{
 | 
						|
#if (__cpp_lib_chrono >= 201907L)
 | 
						|
   using namespace std::chrono;
 | 
						|
#else
 | 
						|
   using namespace date;
 | 
						|
#endif
 | 
						|
 | 
						|
   // Convert the local time, to a zoned time, to a system time
 | 
						|
   const local_time<std::chrono::seconds> localTime =
 | 
						|
      selectedDate_ + selectedTime_;
 | 
						|
   const auto zonedTime =
 | 
						|
      zoned_time<std::chrono::seconds>(timeZone_, localTime);
 | 
						|
   const std::chrono::sys_seconds systemTime = zonedTime.get_sys_time();
 | 
						|
 | 
						|
   // This is done to update it when the date changes
 | 
						|
   UpdateTimeZoneLabel(zonedTime);
 | 
						|
 | 
						|
   return systemTime;
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidgetImpl::SetTimePoint(
 | 
						|
   std::chrono::system_clock::time_point systemTime)
 | 
						|
{
 | 
						|
#if (__cpp_lib_chrono >= 201907L)
 | 
						|
   using namespace std::chrono;
 | 
						|
#else
 | 
						|
   using namespace date;
 | 
						|
#endif
 | 
						|
   // Convert the time to a local time
 | 
						|
   auto systemTimeSeconds = time_point_cast<std::chrono::seconds>(systemTime);
 | 
						|
   auto zonedTime =
 | 
						|
      zoned_time<std::chrono::seconds>(timeZone_, systemTimeSeconds);
 | 
						|
   const local_seconds localTime = zonedTime.get_local_time();
 | 
						|
 | 
						|
   // Get the date and time as seperate fields
 | 
						|
   selectedDate_ = floor<days>(localTime);
 | 
						|
   selectedTime_ = localTime - selectedDate_;
 | 
						|
 | 
						|
   // Pull out the local date and time as qt times (with c++20 this could be
 | 
						|
   // simplified)
 | 
						|
   auto time         = QTime::fromMSecsSinceStartOfDay(static_cast<int>(
 | 
						|
      duration_cast<std::chrono::milliseconds>(selectedTime_).count()));
 | 
						|
   auto yearMonthDay = year_month_day(selectedDate_);
 | 
						|
   auto date         = QDate(int(yearMonthDay.year()),
 | 
						|
                     // These are always in a small range, so cast is safe
 | 
						|
                     static_cast<int>(unsigned(yearMonthDay.month())),
 | 
						|
                     static_cast<int>(unsigned(yearMonthDay.day())));
 | 
						|
 | 
						|
   // Update labels
 | 
						|
   self_->ui->timeEdit->setTime(time);
 | 
						|
   self_->ui->dateEdit->setDate(date);
 | 
						|
 | 
						|
   // Time zone almost certainly just changed, so update it
 | 
						|
   UpdateTimeZoneLabel(zonedTime);
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidgetImpl::ConnectSignals()
 | 
						|
{
 | 
						|
   // View type
 | 
						|
   QObject::connect(self_->ui->liveViewRadioButton,
 | 
						|
                    &QRadioButton::toggled,
 | 
						|
                    self_,
 | 
						|
                    [this](bool checked)
 | 
						|
                    {
 | 
						|
                       if (checked)
 | 
						|
                       {
 | 
						|
                          Q_EMIT self_->ViewTypeChanged(types::MapTime::Live);
 | 
						|
                       }
 | 
						|
                    });
 | 
						|
   QObject::connect(self_->ui->archiveViewRadioButton,
 | 
						|
                    &QRadioButton::toggled,
 | 
						|
                    self_,
 | 
						|
                    [this](bool checked)
 | 
						|
                    {
 | 
						|
                       if (checked)
 | 
						|
                       {
 | 
						|
                          Q_EMIT self_->ViewTypeChanged(
 | 
						|
                             types::MapTime::Archive);
 | 
						|
                       }
 | 
						|
                    });
 | 
						|
 | 
						|
   // Date/time controls
 | 
						|
   QObject::connect( //
 | 
						|
      self_->ui->dateEdit,
 | 
						|
      &QDateTimeEdit::dateChanged,
 | 
						|
      self_,
 | 
						|
      [this](QDate date)
 | 
						|
      {
 | 
						|
         if (date.isValid())
 | 
						|
         {
 | 
						|
            selectedDate_ = util::LocalDays(date);
 | 
						|
            Q_EMIT self_->DateTimeChanged(GetTimePoint());
 | 
						|
         }
 | 
						|
      });
 | 
						|
   QObject::connect(
 | 
						|
      self_->ui->timeEdit,
 | 
						|
      &QDateTimeEdit::timeChanged,
 | 
						|
      self_,
 | 
						|
      [this](QTime time)
 | 
						|
      {
 | 
						|
         if (time.isValid())
 | 
						|
         {
 | 
						|
            selectedTime_ = std::chrono::duration_cast<std::chrono::seconds>(
 | 
						|
               std::chrono::milliseconds(time.msecsSinceStartOfDay()));
 | 
						|
            Q_EMIT self_->DateTimeChanged(GetTimePoint());
 | 
						|
         }
 | 
						|
      });
 | 
						|
 | 
						|
   // Loop controls
 | 
						|
   QObject::connect(
 | 
						|
      self_->ui->loopTimeSpinBox,
 | 
						|
      &QSpinBox::valueChanged,
 | 
						|
      self_,
 | 
						|
      [this](int i)
 | 
						|
      {
 | 
						|
         settings::GeneralSettings::Instance().loop_time().StageValue(i);
 | 
						|
         Q_EMIT self_->LoopTimeChanged(std::chrono::minutes(i));
 | 
						|
      });
 | 
						|
   QObject::connect(
 | 
						|
      self_->ui->loopSpeedSpinBox,
 | 
						|
      &QDoubleSpinBox::valueChanged,
 | 
						|
      self_,
 | 
						|
      [this](double d)
 | 
						|
      {
 | 
						|
         settings::GeneralSettings::Instance().loop_speed().StageValue(d);
 | 
						|
         Q_EMIT self_->LoopSpeedChanged(d);
 | 
						|
      });
 | 
						|
   QObject::connect(
 | 
						|
      self_->ui->loopDelaySpinBox,
 | 
						|
      &QDoubleSpinBox::valueChanged,
 | 
						|
      self_,
 | 
						|
      [this](double d)
 | 
						|
      {
 | 
						|
         settings::GeneralSettings::Instance().loop_delay().StageValue(
 | 
						|
            static_cast<std::int64_t>(d * 1000.0));
 | 
						|
         Q_EMIT self_->LoopDelayChanged(std::chrono::milliseconds(
 | 
						|
            static_cast<typename std::chrono::milliseconds::rep>(d * 1000.0)));
 | 
						|
      });
 | 
						|
 | 
						|
   // Animation controls
 | 
						|
   QObject::connect(self_->ui->beginButton,
 | 
						|
                    &QAbstractButton::clicked,
 | 
						|
                    self_,
 | 
						|
                    [this]() { Q_EMIT self_->AnimationStepBeginSelected(); });
 | 
						|
   QObject::connect(self_->ui->stepBackButton,
 | 
						|
                    &QAbstractButton::clicked,
 | 
						|
                    self_,
 | 
						|
                    [this]() { Q_EMIT self_->AnimationStepBackSelected(); });
 | 
						|
   QObject::connect(self_->ui->playButton,
 | 
						|
                    &QAbstractButton::clicked,
 | 
						|
                    self_,
 | 
						|
                    [this]() { Q_EMIT self_->AnimationPlaySelected(); });
 | 
						|
   QObject::connect(self_->ui->stepNextButton,
 | 
						|
                    &QAbstractButton::clicked,
 | 
						|
                    self_,
 | 
						|
                    [this]() { Q_EMIT self_->AnimationStepNextSelected(); });
 | 
						|
   QObject::connect(self_->ui->endButton,
 | 
						|
                    &QAbstractButton::clicked,
 | 
						|
                    self_,
 | 
						|
                    [this]() { Q_EMIT self_->AnimationStepEndSelected(); });
 | 
						|
 | 
						|
   // Shortcuts
 | 
						|
   QObject::connect(hotkeyManager_.get(),
 | 
						|
                    &manager::HotkeyManager::HotkeyPressed,
 | 
						|
                    self_,
 | 
						|
                    [this](types::Hotkey hotkey, bool /* isAutoRepeat */)
 | 
						|
                    {
 | 
						|
                       switch (hotkey)
 | 
						|
                       {
 | 
						|
                       case types::Hotkey::TimelineStepBegin:
 | 
						|
                          Q_EMIT self_->AnimationStepBeginSelected();
 | 
						|
                          break;
 | 
						|
 | 
						|
                       case types::Hotkey::TimelineStepBack:
 | 
						|
                          Q_EMIT self_->AnimationStepBackSelected();
 | 
						|
                          break;
 | 
						|
 | 
						|
                       case types::Hotkey::TimelinePlay:
 | 
						|
                          Q_EMIT self_->AnimationPlaySelected();
 | 
						|
                          break;
 | 
						|
 | 
						|
                       case types::Hotkey::TimelineStepNext:
 | 
						|
                          Q_EMIT self_->AnimationStepNextSelected();
 | 
						|
                          break;
 | 
						|
 | 
						|
                       case types::Hotkey::TimelineStepEnd:
 | 
						|
                          Q_EMIT self_->AnimationStepEndSelected();
 | 
						|
                          break;
 | 
						|
 | 
						|
                       default:
 | 
						|
                          break;
 | 
						|
                       }
 | 
						|
                    });
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidget::UpdateAnimationState(types::AnimationState state)
 | 
						|
{
 | 
						|
   if (p->animationState_ != state)
 | 
						|
   {
 | 
						|
      // Update icon to opposite of state
 | 
						|
      switch (state)
 | 
						|
      {
 | 
						|
      case types::AnimationState::Pause:
 | 
						|
         ui->playButton->setIcon(p->kPlayIcon_);
 | 
						|
         break;
 | 
						|
 | 
						|
      case types::AnimationState::Play:
 | 
						|
         ui->playButton->setIcon(p->kPauseIcon_);
 | 
						|
         break;
 | 
						|
      }
 | 
						|
 | 
						|
      p->animationState_ = state;
 | 
						|
      p->UpdateAutoUpdateLabel();
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidget::UpdateLiveState(bool isLive)
 | 
						|
{
 | 
						|
   if (p->isLive_ != isLive)
 | 
						|
   {
 | 
						|
      p->isLive_ = isLive;
 | 
						|
      p->UpdateAutoUpdateLabel();
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidget::UpdateViewType(types::MapTime viewType)
 | 
						|
{
 | 
						|
   if (p->viewType_ != viewType)
 | 
						|
   {
 | 
						|
      p->viewType_ = viewType;
 | 
						|
      p->UpdateAutoUpdateLabel();
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidgetImpl::UpdateAutoUpdateLabel()
 | 
						|
{
 | 
						|
   // Display "Auto Update: Enabled" if:
 | 
						|
   // - The map is live, and auto-updating (map widget update)
 | 
						|
   // - "Live" is selected, and the map is playing (timeline manager update)
 | 
						|
   if (isLive_ || (viewType_ == types::MapTime::Live &&
 | 
						|
                   animationState_ == types::AnimationState::Play))
 | 
						|
   {
 | 
						|
      self_->ui->autoUpdateLabel->setText(enabledString_);
 | 
						|
   }
 | 
						|
   else
 | 
						|
   {
 | 
						|
      self_->ui->autoUpdateLabel->setText(disabledString_);
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void AnimationDockWidget::UpdateTimeZone(const scwx::util::time_zone* timeZone)
 | 
						|
{
 | 
						|
   // null timezone is really UTC. This simplifies other code.
 | 
						|
   if (timeZone == nullptr)
 | 
						|
   {
 | 
						|
#if (__cpp_lib_chrono >= 201907L)
 | 
						|
      timeZone = std::chrono::get_tzdb().locate_zone("UTC");
 | 
						|
#else
 | 
						|
      timeZone = date::get_tzdb().locate_zone("UTC");
 | 
						|
#endif
 | 
						|
   }
 | 
						|
 | 
						|
   // Get the (UTC relative) time that is selected. We want to preserve this
 | 
						|
   // across timezone changes.
 | 
						|
   auto currentTime = p->GetTimePoint();
 | 
						|
   p->timeZone_     = timeZone;
 | 
						|
   // Set the (UTC relative) time that was already selected. This ensures that
 | 
						|
   // the actual time does not change, only the time zone.
 | 
						|
   p->SetTimePoint(currentTime);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace ui
 | 
						|
} // namespace qt
 | 
						|
} // namespace scwx
 |