~aleteoryx/muditaos

ceccf471c77c9f60d405ad17d360e015f25af2c9 — Paweł Joński 3 years ago 58319eb
[BH-1522] Meditation - new circular progress layout

Rework meditation progress layout
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 &params);

        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;