M image/assets/lang/English.json => image/assets/lang/English.json +2 -1
@@ 723,5 723,6 @@
"<text>Hejsan!<br />Have a wonderful day!</text>"
],
"app_bell_settings_factory_reset": "Factory reset",
- "app_bell_settings_display_factory_reset_confirmation": "<text>Reset to factory<br></br>settings ?</text>"
+ "app_bell_settings_display_factory_reset_confirmation": "<text>Reset to factory<br></br>settings ?</text>",
+ "app_meditation_summary": "<text>You have meditated for<br />"
}
M module-apps/apps-common/widgets/ProgressTimer.cpp => module-apps/apps-common/widgets/ProgressTimer.cpp +7 -2
@@ 54,7 54,7 @@ namespace app
auto ProgressTimer::onTimerTimeout(sys::Timer &task) -> bool
{
- ++elapsed;
+ elapsed += baseTickInterval;
update();
if (isStopped() || isFinished()) {
task.stop();
@@ 82,13 82,18 @@ namespace app
auto ProgressTimer::intervalReached() const noexcept -> bool
{
- return hasInterval && (elapsed.count() % interval.count()) == 0;
+ return hasInterval &&
+ (std::chrono::duration_cast<std::chrono::seconds>(elapsed).count() % interval.count()) == 0;
}
void ProgressTimer::stop()
{
isRunning = false;
}
+ std::chrono::milliseconds ProgressTimer::getElapsed()
+ {
+ return elapsed;
+ }
void ProgressTimer::registerOnFinishedCallback(std::function<void()> cb)
{
M module-apps/apps-common/widgets/ProgressTimer.hpp => module-apps/apps-common/widgets/ProgressTimer.hpp +5 -4
@@ 43,10 43,10 @@ namespace app
protected:
std::atomic_bool isRunning{false};
- std::chrono::seconds duration{std::chrono::seconds::zero()};
- std::chrono::seconds elapsed{std::chrono::seconds::zero()};
- std::chrono::seconds interval{std::chrono::seconds::zero()};
- std::chrono::milliseconds baseTickInterval{std::chrono::milliseconds::zero()};
+ std::chrono::seconds duration{};
+ std::chrono::milliseconds elapsed{};
+ std::chrono::seconds interval{};
+ std::chrono::milliseconds baseTickInterval{};
bool hasInterval = false;
sys::TimerHandle timerTask;
@@ 76,6 76,7 @@ namespace app
std::chrono::seconds _interval = std::chrono::seconds::zero()) override;
void start() override;
void stop() override;
+ std::chrono::milliseconds getElapsed() override;
void registerOnFinishedCallback(std::function<void()> cb) override;
void registerOnIntervalCallback(std::function<void()> cb) override;
[[nodiscard]] auto isStopped() const noexcept -> bool override;
M module-apps/apps-common/widgets/ProgressTimerWithBarGraphAndCounter.cpp => module-apps/apps-common/widgets/ProgressTimerWithBarGraphAndCounter.cpp +4 -2
@@ 27,7 27,7 @@ namespace app
if (text == nullptr) {
return;
}
- const auto secondsRemaining = duration - elapsed;
+ const auto secondsRemaining = duration - std::chrono::duration_cast<std::chrono::seconds>(elapsed);
const Duration remainingDuration{std::time_t{secondsRemaining.count()}};
UTF8 timerText;
if (countdownMode == ProgressCountdownMode::Increasing && secondsRemaining != std::chrono::seconds::zero()) {
@@ 40,7 40,9 @@ namespace app
void ProgressTimerWithBarGraphAndCounter::updateProgress()
{
if (progress != nullptr) {
- const auto percentage = static_cast<float>(elapsed.count()) / duration.count();
+ const auto percentage =
+ static_cast<float>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count()) /
+ duration.count();
const auto currentStep = percentage * progress->getMaximum();
progress->setValue(std::ceil(currentStep));
}
M module-apps/apps-common/widgets/TimerWithCallbacks.hpp => module-apps/apps-common/widgets/TimerWithCallbacks.hpp +1 -0
@@ 18,6 18,7 @@ namespace app
std::chrono::seconds interval = std::chrono::seconds::zero()) = 0;
virtual void start() = 0;
virtual void stop() = 0;
+ virtual std::chrono::milliseconds getElapsed() = 0;
virtual void registerOnFinishedCallback(std::function<void()> cb) = 0;
virtual void registerOnIntervalCallback(std::function<void()> cb) = 0;
};
M module-gui/gui/widgets/Arc.cpp => module-gui/gui/widgets/Arc.cpp +5 -0
@@ 89,6 89,11 @@ namespace gui
center = point;
}
+ void Arc::setStartAngle(trigonometry::Degrees angle) noexcept
+ {
+ start = angle;
+ }
+
void Arc::setSweepAngle(trigonometry::Degrees angle) noexcept
{
sweep = angle;
M module-gui/gui/widgets/Arc.hpp => module-gui/gui/widgets/Arc.hpp +1 -0
@@ 43,6 43,7 @@ namespace gui
Arc(Item *parent, const ShapeParams ¶ms);
void setCenter(Point point) noexcept;
+ void setStartAngle(trigonometry::Degrees angle) noexcept;
void setSweepAngle(trigonometry::Degrees angle) noexcept;
trigonometry::Degrees getSweepAngle() const noexcept;
trigonometry::Degrees getStartAngle() const noexcept;
M module-gui/gui/widgets/ProgressBar.cpp => module-gui/gui/widgets/ProgressBar.cpp +122 -0
@@ 149,4 149,126 @@ namespace gui
{
return true;
}
+
+ ArcProgressBar::ArcProgressBar(Item *parent, const Arc::ShapeParams &shape, ProgressDirection direction)
+ : Arc{parent, shape}, direction{direction}
+ {
+ if (direction == ProgressDirection::CounterClockwise) {
+ start -= sweep;
+ };
+ createWidgets();
+ updateDrawArea();
+ }
+
+ void ArcProgressBar::createWidgets()
+ {
+ // Arc progress indicator (stronger line) must be a bit wider than the base circle
+ // Those values were selected to match the design and look good enough on multiple
+ // radius and penWidth values
+ const auto progressArcRadius = radius + 3;
+ const auto progressArcWidth = penWidth + 7;
+ const auto progressIndicatorRadius = (progressArcWidth - 1) / 2;
+
+ Arc::ShapeParams arcParams;
+ arcParams.setCenterPoint(center)
+ .setRadius(progressArcRadius)
+ .setSweepAngle(0)
+ .setPenWidth(progressArcWidth)
+ .setBorderColor(ColorFullBlack);
+
+ if (direction == ProgressDirection::Clockwise) {
+ arcParams.setStartAngle(start);
+ }
+ else {
+ arcParams.setStartAngle(start + sweep);
+ }
+ progressArc = new Arc(this, arcParams);
+
+ Circle::ShapeParams indicatorStartParams;
+ indicatorStartParams.setCenterPoint(calculateStartIndicatorCenter())
+ .setRadius(progressIndicatorRadius)
+ .setPenWidth(2)
+ .setBorderColor(ColorFullBlack)
+ .setFillColor(ColorFullBlack);
+ progressStartIndicator = new Circle(this, indicatorStartParams);
+
+ Circle::ShapeParams indicatorEndParams;
+ indicatorEndParams.setCenterPoint(calculateEndIndicatorCenter())
+ .setRadius(progressIndicatorRadius)
+ .setPenWidth(2)
+ .setBorderColor(ColorFullBlack)
+ .setFillColor(ColorFullBlack);
+ progressEndIndicator = new Circle(this, indicatorEndParams);
+ }
+
+ Point ArcProgressBar::calculateStartIndicatorCenter() const
+ {
+ using namespace trigonometry;
+ const auto sweepAngleRadians = toRadians(progressArc->getSweepAngle() + progressArc->getStartAngle());
+ return Point(center.x + AdjacentSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2)),
+ center.y + OppositeSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2)));
+ }
+
+ Point ArcProgressBar::calculateEndIndicatorCenter() const
+ {
+ using namespace trigonometry;
+ const auto sweepAngleRadians = toRadians(progressArc->getStartAngle());
+ return Point(center.x + AdjacentSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2)),
+ center.y + OppositeSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2)));
+ }
+
+ void ArcProgressBar::setMaximum(unsigned int value) noexcept
+ {
+ maxValue = value;
+ if (currentValue > maxValue) {
+ currentValue = maxValue;
+ }
+ }
+
+ bool ArcProgressBar::setValue(unsigned int value) noexcept
+ {
+ currentValue = std::clamp(value, 0U, maxValue);
+ return value == currentValue;
+ }
+
+ void ArcProgressBar::setPercentageValue(unsigned int value) noexcept
+ {
+ const auto percent = static_cast<float>(value) / 100.0f;
+ const auto absoluteValue = std::lround(static_cast<float>(maxValue) * percent);
+ setValue(absoluteValue);
+ }
+ int ArcProgressBar::getMaximum() const noexcept
+ {
+ return maxValue;
+ }
+
+ float ArcProgressBar::getPercentageValue() const
+ {
+ if (maxValue == 0) {
+ return .0f;
+ }
+ return static_cast<float>(currentValue) / maxValue;
+ }
+
+ void ArcProgressBar::buildDrawListImplementation(std::list<Command> &commands)
+ {
+ if (direction == ProgressDirection::Clockwise) {
+ progressArc->setSweepAngle(std::ceil(getPercentageValue() * sweep));
+ }
+ else {
+ progressArc->setStartAngle(start + sweep -
+ std::ceil(getPercentageValue() * sweep)); // Start drawing the circle from top.
+ progressArc->setSweepAngle(std::ceil(getPercentageValue() * sweep));
+ }
+ progressStartIndicator->setCenter(calculateStartIndicatorCenter());
+ progressEndIndicator->setCenter(calculateEndIndicatorCenter());
+
+ Arc::buildDrawListImplementation(commands);
+ }
+
+ bool ArcProgressBar::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim)
+ {
+ return true;
+ }
+
} /* namespace gui */
M module-gui/gui/widgets/ProgressBar.hpp => module-gui/gui/widgets/ProgressBar.hpp +35 -0
@@ 67,4 67,39 @@ namespace gui
Arc *progressArc = nullptr;
Circle *progressIndicator = nullptr;
};
+
+ class ArcProgressBar : public Arc, public Progress
+ {
+ public:
+ enum class ProgressDirection
+ {
+ Clockwise,
+ CounterClockwise
+ };
+ ArcProgressBar(Item *parent,
+ const Arc::ShapeParams &shape,
+ ProgressDirection direction = ProgressDirection::Clockwise);
+
+ void setMaximum(unsigned int value) noexcept override;
+ auto setValue(unsigned int value) noexcept -> bool override;
+ void setPercentageValue(unsigned int value) noexcept override;
+ [[nodiscard]] int getMaximum() const noexcept override;
+
+ void buildDrawListImplementation(std::list<Command> &commands) override;
+ auto onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim) -> bool override;
+
+ private:
+ void createWidgets();
+
+ auto calculateStartIndicatorCenter() const -> Point;
+ auto calculateEndIndicatorCenter() const -> Point;
+ auto getPercentageValue() const -> float;
+
+ unsigned int maxValue = 0U;
+ unsigned int currentValue = 0U;
+ Arc *progressArc = nullptr;
+ Arc *progressStartIndicator = nullptr;
+ Arc *progressEndIndicator = nullptr;
+ ProgressDirection direction = ProgressDirection::Clockwise;
+ };
} // namespace gui
M products/BellHybrid/apps/application-bell-meditation-timer/data/MeditationStyle.hpp => products/BellHybrid/apps/application-bell-meditation-timer/data/MeditationStyle.hpp +14 -10
@@ 39,23 39,27 @@ namespace app::meditationStyle
namespace mrStyle
{
- namespace title
- {
- constexpr inline auto font = style::window::font::verybiglight;
- } // namespace title
-
namespace progress
{
- constexpr inline auto progressMarginTop = 40;
- constexpr inline auto progressMarginLeft = 60;
- constexpr inline auto boxesCount = 16;
+ constexpr inline auto radius = 192;
+ constexpr inline auto penWidth = 3;
+ constexpr inline auto verticalDeviationDegrees = 38;
} // namespace progress
namespace timer
{
- constexpr inline auto timerMarginBottom = 20;
- constexpr inline auto font = style::window::font::largelight;
+ constexpr inline auto marginTop = 39;
+ constexpr inline auto font = style::window::font::supersizeme;
+ constexpr inline auto maxSizeX = 340;
+ constexpr inline auto maxSizeY = 198;
} // namespace timer
+
+ namespace clock
+ {
+ constexpr inline auto marginTop = 17;
+ constexpr inline auto maxSizeX = 340;
+ constexpr inline auto maxSizeY = 84;
+ } // namespace clock
} // namespace mrStyle
namespace mtStyle
M products/BellHybrid/apps/application-bell-meditation-timer/presenter/MeditationProgressPresenter.cpp => products/BellHybrid/apps/application-bell-meditation-timer/presenter/MeditationProgressPresenter.cpp +6 -2
@@ 6,6 6,7 @@
#include "MeditationProgressPresenter.hpp"
#include "models/ChimeInterval.hpp"
+#include <common/LanguageUtils.hpp>
#include <common/models/TimeModel.hpp>
#include <common/windows/BellFinishedWindow.hpp>
#include <common/windows/SessionPausedWindow.hpp>
@@ 59,7 60,7 @@ namespace app::meditation
void MeditationProgressPresenter::stop()
{
- timer->stop();
+ finish();
}
void MeditationProgressPresenter::pause()
@@ 82,9 83,12 @@ namespace app::meditation
void MeditationProgressPresenter::finish()
{
timer->stop();
+ const auto elapsed = std::chrono::duration_cast<std::chrono::minutes>(timer->getElapsed());
+ const auto summaryText = utils::translate("app_meditation_summary") + std::to_string(elapsed.count()) + " " +
+ utils::language::getCorrectMinutesNumeralForm(elapsed.count());
app->switchWindow(
gui::window::bell_finished::defaultName,
- gui::BellFinishedWindowData::Factory::create("big_namaste_W_G", "", "", true, endWindowTimeout));
+ gui::BellFinishedWindowData::Factory::create("big_namaste_W_G", "", summaryText, true, endWindowTimeout));
}
void MeditationProgressPresenter::onProgressFinished()
M products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationRunningWindow.cpp => products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationRunningWindow.cpp +36 -63
@@ 16,47 16,6 @@ namespace
inline constexpr auto meditationProgressTimerName = "MeditationProgressTimer";
inline constexpr std::chrono::seconds baseTick{1};
inline constexpr auto meditationProgressMode = app::ProgressCountdownMode::Increasing;
-
- using namespace app::meditationStyle;
-
- void decorateProgressItem(gui::Rect *item, gui::Alignment::Vertical alignment)
- {
- item->setEdges(gui::RectangleEdge::None);
- item->activeItem = false;
- item->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, alignment));
- }
-
- gui::Label *createTitle(gui::VBox *parent)
- {
- auto title = new gui::Label(parent, 0, 0, parent->getWidth(), parent->getHeight() / 2);
- title->setText(utils::translate("app_bell_meditation_progress"));
- title->setFont(mrStyle::title::font);
- decorateProgressItem(title, gui::Alignment::Vertical::Top);
- return title;
- }
-
- gui::HBarGraph *createProgress(gui::VBox *parent)
- {
- auto progressBox = new gui::HBox(parent, 0, 0, parent->getWidth(), parent->getHeight() / 2);
- decorateProgressItem(progressBox, gui::Alignment::Vertical::Bottom);
- auto progressBar =
- new gui::HBarGraph(progressBox, 0, 0, mrStyle::progress::boxesCount, gui::BarGraphStyle::Heavy);
- decorateProgressItem(progressBar, gui::Alignment::Vertical::Center);
- return progressBar;
- }
-
- gui::Text *createTimer(gui::Item *parent)
- {
- auto timer = new gui::Text(parent,
- 0,
- 0,
- style::bell_base_layout::w,
- style::bell_base_layout::outer_layouts_h - mrStyle::timer::timerMarginBottom);
- timer->setFont(mrStyle::timer::font);
- timer->setMargins(gui::Margins(0, mrStyle::timer::timerMarginBottom, 0, 0));
- decorateProgressItem(timer, gui::Alignment::Vertical::Top);
- return timer;
- }
} // namespace
namespace gui
@@ 84,24 43,38 @@ namespace gui
void MeditationRunningWindow::buildLayout()
{
- auto body = new gui::BellBaseLayout(this, 0, 0, style::bell_base_layout::w, style::bell_base_layout::h, false);
- auto vBox =
- new VBox(body->getCenterBox(), 0, 0, style::bell_base_layout::w, style::bell_base_layout::center_layout_h);
-
- decorateProgressItem(vBox, gui::Alignment::Vertical::Top);
- createTitle(vBox);
- progress = createProgress(vBox);
- timer = createTimer(body->lastBox);
-
- time = new BellStatusClock(body->firstBox);
- time->setMaximumSize(body->firstBox->getWidth(), body->firstBox->getHeight());
- time->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
- body->firstBox->resizeItems();
-
- dimensionChangedCallback = [&](Item &, const BoundingBox &newDim) -> bool {
- body->setArea({0, 0, newDim.w, newDim.h});
- return true;
- };
+ using namespace app::meditationStyle;
+ const auto progressArcRadius = mrStyle::progress::radius;
+ const auto progressArcWidth = mrStyle::progress::penWidth;
+ const auto arcStartAngle = -90 - mrStyle::progress::verticalDeviationDegrees;
+ const auto arcSweepAngle = 360 - (2 * mrStyle::progress::verticalDeviationDegrees);
+ const auto arcProgressSteps = 1000;
+
+ Arc::ShapeParams arcParams;
+ arcParams.setCenterPoint(Point(getWidth() / 2, getHeight() / 2))
+ .setRadius(progressArcRadius)
+ .setStartAngle(arcStartAngle) // Start drawing the circle from top.
+ .setSweepAngle(arcSweepAngle)
+ .setPenWidth(progressArcWidth)
+ .setBorderColor(ColorFullBlack);
+
+ progress = new ArcProgressBar(this, arcParams, ArcProgressBar::ProgressDirection::CounterClockwise);
+ progress->setMaximum(arcProgressSteps);
+
+ mainVBox = new VBox(this, 0, 0, style::window_width, style::window_height);
+
+ clock = new BellStatusClock(mainVBox);
+ clock->setMaximumSize(mrStyle::clock::maxSizeX, mrStyle::clock::maxSizeY);
+ clock->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
+ clock->setMargins(gui::Margins(0, mrStyle::clock::marginTop, 0, 0));
+
+ timer = new gui::Text(mainVBox, 0, 0, 0, 0);
+ timer->setFont(style::window::font::supersizeme);
+ timer->setMinimumSize(mrStyle::timer::maxSizeX, mrStyle::timer::maxSizeY);
+ timer->setMargins(gui::Margins(0, mrStyle::timer::marginTop, 0, 0));
+ timer->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
+
+ mainVBox->resizeItems();
}
void MeditationRunningWindow::onBeforeShow(ShowMode mode, SwitchData *data)
@@ 127,7 100,7 @@ namespace gui
}
if (inputEvent.isShortRelease(gui::KeyCode::KEY_RF)) {
reinterpret_cast<app::Application *>(application)->resumeIdleTimer();
- presenter->abandon();
+ presenter->finish();
return true;
}
@@ 155,13 128,13 @@ namespace gui
void MeditationRunningWindow::setTime(std::time_t newTime)
{
- time->setTime(newTime);
- time->setTimeFormatSpinnerVisibility(true);
+ clock->setTime(newTime);
+ clock->setTimeFormatSpinnerVisibility(true);
}
void MeditationRunningWindow::setTimeFormat(utils::time::Locale::TimeFormat fmt)
{
- time->setTimeFormat(fmt);
+ clock->setTimeFormat(fmt);
}
RefreshModes MeditationRunningWindow::updateTime()
M products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationRunningWindow.hpp => products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationRunningWindow.hpp +4 -3
@@ 30,9 30,10 @@ namespace gui
private:
std::unique_ptr<app::meditation::MeditationProgressContract::Presenter> presenter;
- gui::HBarGraph *progress = nullptr;
- gui::Text *timer = nullptr;
- gui::BellStatusClock *time = nullptr;
+ gui::VBox *mainVBox = nullptr;
+ gui::ArcProgressBar *progress = nullptr;
+ gui::Text *timer = nullptr;
+ gui::BellStatusClock *clock = nullptr;
void setTime(std::time_t newTime) override;
void setTimeFormat(utils::time::Locale::TimeFormat fmt) override;