Skip to content

Commit

Permalink
🕝 Simple date/time visual added (#34)
Browse files Browse the repository at this point in the history
A local date and time readout has been added to the top left corner of
the FlashCom UI, potentially to be made configurable in future releases.

A setting is added to control whether 12 or 24-hour time is shown.
  • Loading branch information
haydenmc authored Oct 5, 2024
1 parent 6e953f4 commit 64f9a53
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The following fields can be set in the `settings.json` root object:
| Name | Type | Description |
| --------------------------- | ------------------------ | ------------------------------------------ |
| `"showStartupNotification"` | `true` / `false` | Determines whether the "FlashCom is running" toast notification is shown on startup. |
| `"use24HourClock"` | `true` / `false` | Determines whether a 12-hour or 24-hour clock is shown. |
| `"commands"` | Array of Command Objects | The set of commands shown in FlashCom. |

## Commands
Expand Down
1 change: 1 addition & 0 deletions src/FlashCom/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace FlashCom
{
auto result{ m_settingsManager.LoadSettings() };
m_dataModel->ShowStartupNotification = m_settingsManager.GetShowStartupNotification();
m_dataModel->UseTwentyFourHourClock = m_settingsManager.UseTwentyFourHourClock();
m_dataModel->RootNode = m_settingsManager.GetCommandTreeRoot();
m_dataModel->CurrentNode = m_dataModel->RootNode.get();
if (!result.has_value())
Expand Down
1 change: 1 addition & 0 deletions src/FlashCom/Models/DataModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace FlashCom::Models
{
std::string LoadErrorMessage;
bool ShowStartupNotification;
bool UseTwentyFourHourClock;
std::shared_ptr<TreeNode> RootNode;
TreeNode* CurrentNode{ nullptr };
};
Expand Down
20 changes: 20 additions & 0 deletions src/FlashCom/Settings/SettingsManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace
constexpr std::string_view c_settingsFileName{ "settings.json" };
// JSON property names
constexpr std::string_view c_showStartupNotificationProperty{ "showStartupNotification" };
constexpr std::string_view c_useTwentyFourHourClockProperty{ "use24HourClock" };
constexpr std::string_view c_commandsJsonProperty{ "commands" };
constexpr std::string_view c_commandNameJsonProperty{ "name" };
constexpr std::string_view c_commandKeyJsonProperty{ "key" };
Expand All @@ -31,6 +32,7 @@ namespace
// Default settings.json contents
constexpr std::string_view c_defaultSettingsFileContents{ R"({
"showStartupNotification": true,
"use24HourClock": false,
"commands": [
{
"name": "README",
Expand Down Expand Up @@ -369,6 +371,11 @@ namespace FlashCom::Settings
return m_showStartupNotification;
}

bool SettingsManager::UseTwentyFourHourClock()
{
return m_useTwentyFourHourClock;
}

std::shared_ptr<Models::TreeNode> SettingsManager::GetCommandTreeRoot()
{
std::shared_lock settingsLock{ m_settingsAccessMutex };
Expand All @@ -393,6 +400,19 @@ namespace FlashCom::Settings
"Defaulting to '{}'.", c_showStartupNotificationProperty,
m_showStartupNotification);
}

if (settingsJson.contains(c_useTwentyFourHourClockProperty) &&
settingsJson.at(c_useTwentyFourHourClockProperty).is_boolean())
{
m_useTwentyFourHourClock =
settingsJson.at(c_useTwentyFourHourClockProperty).get<bool>();
}
else
{
SPDLOG_INFO("SettingsManager::PopulateSettingsValues - No '{}' bool property found. "
"Defaulting to '{}'.", c_useTwentyFourHourClockProperty,
m_useTwentyFourHourClock);
}
}

std::expected<void, std::string> SettingsManager::PopulateCommandTree(
Expand Down
2 changes: 2 additions & 0 deletions src/FlashCom/Settings/SettingsManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ namespace FlashCom::Settings
std::expected<void, std::string> LoadSettings();
std::filesystem::path GetSettingsFilePath();
bool GetShowStartupNotification();
bool UseTwentyFourHourClock();
std::shared_ptr<Models::TreeNode> GetCommandTreeRoot();

private:
std::filesystem::path const m_settingsFilePath;
std::shared_mutex m_settingsAccessMutex;
bool m_showStartupNotification{ true };
bool m_useTwentyFourHourClock{ false };
std::shared_ptr<Models::TreeNode> m_commandTreeRoot;

void PopulateSettingsValues(
Expand Down
103 changes: 103 additions & 0 deletions src/FlashCom/View/Ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ namespace
return root;
}

winrt::WUIC::ContainerVisual CreateClockContainer(winrt::WUIC::Compositor compositor)
{
auto clockContainer{ compositor.CreateContainerVisual() };
clockContainer.RelativeSizeAdjustment({ 1.0f, 1.0f });
clockContainer.Offset({ 0, 0, 0 });
return clockContainer;
}

winrt::WUIC::SpriteVisual CreateTextVisual(
FlashCom::View::CompositionManager& compositionManager,
winrt::MGCT::CanvasTextFormat textFormat, std::string_view content)
Expand Down Expand Up @@ -113,6 +121,17 @@ namespace

return spriteVisual;
}

std::chrono::milliseconds TimeToNextMinute()
{
auto const time = std::chrono::current_zone()
->to_local(std::chrono::system_clock::now());
auto minutes = std::chrono::floor<std::chrono::minutes>(time);
auto ms = std::chrono::floor<std::chrono::milliseconds>(time);
auto untilNextMinute = std::chrono::floor<std::chrono::milliseconds>(
std::chrono::minutes{ 1 } - (ms - minutes));
return untilNextMinute;
}
}

namespace FlashCom::View
Expand All @@ -127,6 +146,7 @@ namespace FlashCom::View
m_contentsVisual{ CreateContentsVisual(m_compositionManager.GetCompositor()) },
m_rootVisual{ CreateRootVisual(m_compositionManager.GetCompositor(), m_backgroundVisual,
m_contentsVisual) },
m_clockVisual{ CreateClockContainer(m_compositionManager.GetCompositor()) },
m_backgroundFadeInAnimation{
CreateBackgroundFadeInAnimation(m_compositionManager.GetCompositor()) },
m_contentScaleInAnimation{ CreateContentScaleInAnimation(
Expand All @@ -150,6 +170,12 @@ namespace FlashCom::View
void Ui::Hide()
{
m_hostWindow.Hide();
if (m_clockUpdateTimer)
{
SPDLOG_INFO("Ui::Hide - Canceling clock timer");
m_clockUpdateTimer.Cancel();
m_clockUpdateTimer = nullptr;
}
}

void Ui::Update(UpdateReasonKind reason)
Expand Down Expand Up @@ -204,13 +230,90 @@ namespace FlashCom::View
m_contentsVisual.Children().InsertAtTop(textVisual);
}

// Clock visual
UpdateClockVisual();
m_contentsVisual.Children().InsertAtTop(m_clockVisual);

// If we're showing, prepare to animate in
if (reason == UpdateReasonKind::Showing)
{
m_backgroundVisual.StartAnimation(L"opacity", m_backgroundFadeInAnimation);
m_contentsVisual.StartAnimation(L"opacity", m_backgroundFadeInAnimation);
m_contentsVisual.StartAnimation(L"scale", m_contentScaleInAnimation);

if (m_clockUpdateTimer)
{
m_clockUpdateTimer.Cancel();
m_clockUpdateTimer = nullptr;
}
auto nextUpdate{ TimeToNextMinute() };
SPDLOG_INFO("Ui::Update - setting clock timer for {}ms", nextUpdate.count());
m_clockUpdateTimer = winrt::WST::ThreadPoolTimer::CreateTimer(
{ this, &Ui::OnClockUpdateTimerElapsed }, nextUpdate);
}
}

void Ui::UpdateClockVisual()
{
static auto const locale{ std::locale("") };
auto const time = std::chrono::current_zone()
->to_local(std::chrono::system_clock::now());
auto days = std::chrono::floor<std::chrono::days>(time);
std::chrono::hh_mm_ss hhmmss{ std::chrono::floor<std::chrono::milliseconds>(time - days) };
std::string timeText;

if (m_dataModel->UseTwentyFourHourClock)
{
timeText = std::format(locale, "{:0>2}:{:0>2}", hhmmss.hours().count(),
hhmmss.minutes().count());
}
else
{
timeText = std::format(locale, "{}:{:0>2}", hhmmss.hours().count() % 12,
hhmmss.minutes().count());
}

winrt::MGCT::CanvasTextFormat timeTextFormat;
timeTextFormat.FontFamily(L"Segoe UI");
timeTextFormat.FontSize(128);
timeTextFormat.FontWeight(winrt::WUIT::FontWeights::Bold());
timeTextFormat.HorizontalAlignment(winrt::MGCT::CanvasHorizontalAlignment::Left);
auto timeVisual{ CreateTextVisual(m_compositionManager, timeTextFormat, timeText) };
timeVisual.AnchorPoint({ 0.0f, 0.0f });
timeVisual.RelativeOffsetAdjustment({ 0.0f, 0.0f, 0.0f });
timeVisual.Offset({ 28.0f, 0.0f, 0.0f });

auto dateText{ std::format(locale, "{:%x}", time) };
winrt::MGCT::CanvasTextFormat dateTextFormat;
dateTextFormat.FontFamily(L"Segoe UI");
dateTextFormat.FontSize(48);
dateTextFormat.FontWeight(winrt::WUIT::FontWeights::Light());
dateTextFormat.HorizontalAlignment(winrt::MGCT::CanvasHorizontalAlignment::Left);
auto dateVisual{ CreateTextVisual(m_compositionManager, dateTextFormat, dateText) };
dateVisual.AnchorPoint({ 0.0f, 0.0f });
dateVisual.RelativeOffsetAdjustment({ 0.0f, 0.0f, 0.0f });
dateVisual.Offset({ 32.0f, (timeVisual.Size().y - 24.0f), 0.0f });

m_clockVisual.Children().RemoveAll();
m_clockVisual.Children().InsertAtTop(timeVisual);
m_clockVisual.Children().InsertAtTop(dateVisual);

}

void Ui::OnClockUpdateTimerElapsed(winrt::WST::ThreadPoolTimer /*timer*/)
{
auto nextUpdate{ TimeToNextMinute() };
SPDLOG_INFO(
"Ui::OnClockUpdateTimerElapsed - Updating clock visual and resetting timer to {}ms",
nextUpdate.count());
UpdateClockVisual();
if (m_clockUpdateTimer)
{
m_clockUpdateTimer.Cancel();
m_clockUpdateTimer = nullptr;
}
m_clockUpdateTimer = winrt::WST::ThreadPoolTimer::CreateTimer(
{ this, &Ui::OnClockUpdateTimerElapsed }, nextUpdate);
}
#pragma endregion Public
}
8 changes: 8 additions & 0 deletions src/FlashCom/View/Ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,25 @@ namespace FlashCom::View
void Update(UpdateReasonKind reason);

private:
void UpdateClockVisual();
void OnClockUpdateTimerElapsed(winrt::Windows::System::Threading::ThreadPoolTimer timer);

HostWindow& m_hostWindow;
Models::DataModel const * const m_dataModel;
CompositionManager m_compositionManager;
std::pair<uint32_t, uint32_t> m_uiBounds;
// Container visuals
winrt::Windows::UI::Composition::ContainerVisual m_backgroundVisual{ nullptr };
winrt::Windows::UI::Composition::ContainerVisual m_contentsVisual{ nullptr };
winrt::Windows::UI::Composition::ContainerVisual m_rootVisual{ nullptr };
// Element visuals
winrt::Windows::UI::Composition::ContainerVisual m_clockVisual{ nullptr };
// Animations
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation
m_backgroundFadeInAnimation{ nullptr };
winrt::Windows::UI::Composition::Vector3KeyFrameAnimation
m_contentScaleInAnimation{ nullptr };
// Misc
winrt::Windows::System::Threading::ThreadPoolTimer m_clockUpdateTimer{ nullptr };
};
}

0 comments on commit 64f9a53

Please sign in to comment.