~aleteoryx/muditaos

9edd5665305502e2b18317d99c62b62152065d1e — Mateusz Piesta 4 years ago e7776f6
[BH-678] Add TimeSetFmtSpinner widget

Added time set spinner widget with dynamic switching
between time formats(24/12H).
M module-apps/application-bell-settings/models/TimeUnitsModel.cpp => module-apps/application-bell-settings/models/TimeUnitsModel.cpp +5 -5
@@ 3,7 3,7 @@

#include "TimeUnitsModel.hpp"

#include <apps-common/widgets/TimeSetSpinner.hpp>
#include <apps-common/widgets/TimeSetFmtSpinner.hpp>
#include <gui/widgets/ListViewEngine.hpp>
#include <gui/widgets/Style.hpp>
#include <gui/widgets/Text.hpp>


@@ 57,8 57,8 @@ namespace app::bell_settings
    {
        std::time_t now         = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        struct std::tm *newTime = std::localtime(&now);
        newTime->tm_hour        = timeSetWidget->timeSetSpinner->getHour();
        newTime->tm_min         = timeSetWidget->timeSetSpinner->getMinute();
        newTime->tm_hour        = timeSetWidget->timeSetFmtSpinner->getHour();
        newTime->tm_min         = timeSetWidget->timeSetFmtSpinner->getMinute();
        LOG_INFO("Setting new time: %d:%d", newTime->tm_hour, newTime->tm_min);
        sendRtcUpdateTimeMessage(std::mktime(newTime));
    }


@@ 66,8 66,8 @@ namespace app::bell_settings
    void TimeUnitsModel::loadData()
    {
        std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        timeSetWidget->timeSetSpinner->setHour(std::localtime(&now)->tm_hour);
        timeSetWidget->timeSetSpinner->setMinute(std::localtime(&now)->tm_min);
        timeSetWidget->timeSetFmtSpinner->setHour(std::localtime(&now)->tm_hour);
        timeSetWidget->timeSetFmtSpinner->setMinute(std::localtime(&now)->tm_min);
    }

    auto TimeUnitsModel::requestRecords(uint32_t offset, uint32_t limit) -> void

M module-apps/application-bell-settings/widgets/TimeSetSpinnerListItem.cpp => module-apps/application-bell-settings/widgets/TimeSetSpinnerListItem.cpp +13 -7
@@ 4,7 4,8 @@
#include "BellSettingsStyle.hpp"
#include "TimeSetSpinnerListItem.hpp"

#include <widgets/TimeSetSpinner.hpp>
#include <gui/input/InputEvent.hpp>
#include <widgets/TimeSetFmtSpinner.hpp>

namespace gui
{


@@ 13,16 14,21 @@ namespace gui
        : SideListItem(std::move(description))
    {
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);

        timeSetSpinner = new TimeSetSpinner(body, 0, 0, 0, 0);
        timeSetSpinner->setMinimumSize(gui::bell_settings_style::time_set_spinner_list_item::w,
                                       gui::bell_settings_style::time_set_spinner_list_item::h);
        body->setFocusItem(timeSetSpinner);
        timeSetFmtSpinner = new TimeSetFmtSpinner(body);
        timeSetFmtSpinner->setMinimumSize(gui::bell_settings_style::time_set_spinner_list_item::w,
                                          gui::bell_settings_style::time_set_spinner_list_item::h);
        setFocusItem(body);

        dimensionChangedCallback = [&](gui::Item &, const BoundingBox &newDim) -> bool {
            body->setArea({0, 0, newDim.w, newDim.h});
            return true;
        };
        inputCallback = [&](gui::Item &item, const gui::InputEvent &event) { return body->onInput(event); };

        focusChangedCallback = [&](Item &item) {
            setFocusItem(focus ? body : nullptr);
            return true;
        };

        inputCallback = [&](Item &, const InputEvent &inputEvent) -> bool { return body->onInput(inputEvent); };
    }
} /* namespace gui */

M module-apps/application-bell-settings/widgets/TimeSetSpinnerListItem.hpp => module-apps/application-bell-settings/widgets/TimeSetSpinnerListItem.hpp +2 -2
@@ 9,12 9,12 @@

namespace gui
{
    class TimeSetSpinner;
    class TimeSetFmtSpinner;

    class TimeSetSpinnerListItem : public SideListItem
    {
      public:
        TimeSetSpinner *timeSetSpinner = nullptr;
        TimeSetFmtSpinner *timeSetFmtSpinner = nullptr;

        TimeSetSpinnerListItem(gui::Length x, gui::Length y, gui::Length w, gui::Length h, std::string description);
    };

M module-apps/apps-common/CMakeLists.txt => module-apps/apps-common/CMakeLists.txt +3 -0
@@ 38,12 38,15 @@ target_sources(apps-common
        widgets/TextWithIconsWidget.cpp
        widgets/TimeSetSpinner.cpp
        widgets/AlarmSetSpinner.cpp
        widgets/TimeSetFmtSpinner.cpp
        widgets/TimeWidget.cpp
        widgets/WidgetsUtils.cpp
        windows/AppWindow.cpp
        windows/BrightnessWindow.cpp
        windows/Dialog.cpp
        windows/OptionWindow.cpp
    PUBLIC
        widgets/TimeSetFmtSpinner.hpp
)

add_subdirectory(popups)

A module-apps/apps-common/widgets/TimeSetFmtSpinner.cpp => module-apps/apps-common/widgets/TimeSetFmtSpinner.cpp +195 -0
@@ 0,0 1,195 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "TimeSetFmtSpinner.hpp"

#include "TimeSetSpinner.hpp"

#include <date/date.h>
#include <gui/core/FontManager.hpp>
#include <gui/core/RawFont.hpp>
#include <gui/widgets/Spinner.hpp>
#include <gui/widgets/TextSpinner.hpp>

namespace
{
    constexpr auto fmtSpinnerAMPos = 0U;
    constexpr auto fmtSpinnerPMPos = 1U;
} // namespace

namespace gui
{

    TimeSetFmtSpinner::TimeSetFmtSpinner(
        Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h, utils::time::Locale::TimeFormat timeFormat)
        : HBox{parent, x, y, w, h}
    {
        using namespace utils;

        setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        setEdges(RectangleEdge::None);

        timeSetSpinner = new TimeSetSpinner(this, 0, 0, 0, 0);
        timeSetSpinner->setFont(fontName);
        timeSetSpinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        timeSetSpinner->setMargins(Margins(0, 0, 0, 0));

        auto minSize   = getMinimumFmtSize();
        auto textRange = TextSpinner::TextRange{time::Locale::getAM(), time::Locale::getPM()};
        fmt            = new TextSpinner(textRange, Boundaries::Continuous);
        fmt->setMinimumSize(minSize.first, minSize.second);
        fmt->setFont(fontName);
        fmt->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        fmt->setEdges(RectangleEdge::None);
        fmt->setVisible(false);
        fmt->setPenFocusWidth(style::time_set_spinner::focus::size);
        addWidget(fmt);
        fmt->setEdges(RectangleEdge::Bottom);

        focusChangedCallback = [&](Item &) {
            setFocusItem(focus ? timeSetSpinner : nullptr);
            return true;
        };

        setTimeFormat(timeFormat);
    }

    auto TimeSetFmtSpinner::setTimeFormat(utils::time::Locale::TimeFormat newFormat) noexcept -> void
    {
        using namespace utils;
        timeFormat = newFormat;
        switch (timeFormat) {
        case utils::time::Locale::TimeFormat::FormatTime12H: {
            fmt->setVisible(true);
            timeSetSpinner->setHourMax(time::Locale::max_hour_12H_mode);
            timeSetSpinner->setHourMin(time::Locale::min_hour_12H_mode);

            auto hours = std::chrono::hours(timeSetSpinner->getHour());
            timeSetSpinner->setHour(date::make12(hours).count());
            if (date::is_pm(hours)) {
                fmt->setCurrentPosition(fmtSpinnerPMPos);
            }
            else {
                fmt->setCurrentPosition(fmtSpinnerAMPos);
            }
        } break;
        case utils::time::Locale::TimeFormat::FormatTime24H: {
            fmt->setVisible(false);
            timeSetSpinner->setHourMax(time::Locale::max_hour_24H_mode);
            timeSetSpinner->setHourMin(time::Locale::min_hour_24H_mode);

            auto hours = std::chrono::hours(timeSetSpinner->getHour());
            timeSetSpinner->setHour(date::make24(hours, isPm(fmt->getCurrentText())).count());
        } break;
        default:
            break;
        }

        resizeItems();

        // If we make 12->24 switch while focused on fmt then change focus to hour
        if (focusItem == fmt) {
            setFocusItem(timeSetSpinner);
        }
    }

    auto TimeSetFmtSpinner::setMinute(int value) noexcept -> void
    {
        timeSetSpinner->setMinute(value);
    }

    auto TimeSetFmtSpinner::setEditMode(EditMode newEditMode) noexcept -> void
    {
        editMode = newEditMode;
        if (editMode == EditMode::Edit) {
            setFocusItem(timeSetSpinner);
        }
        else {
            setFocusItem(nullptr);
        }
    }
    auto TimeSetFmtSpinner::getHour() const noexcept -> int
    {
        return timeSetSpinner->getHour();
    }
    auto TimeSetFmtSpinner::getMinute() const noexcept -> int
    {
        return timeSetSpinner->getMinute();
    }

    auto TimeSetFmtSpinner::getFontHeight() const noexcept -> uint16_t
    {
        const auto font = FontManager::getInstance().getFont(fontName);
        return font->info.line_height;
    }
    auto TimeSetFmtSpinner::getMinimumFmtSize() const noexcept -> std::pair<Length, Length>
    {
        constexpr auto spacer = 5U; // space between two chars
        const auto font       = FontManager::getInstance().getFont(fontName);
        return {font->getPixelWidth(utils::time::Locale::getAM()) + spacer, font->info.line_height};
    }
    auto TimeSetFmtSpinner::setHour(int value) noexcept -> void
    {
        timeSetSpinner->setHour(value);
    }
    auto TimeSetFmtSpinner::setFont(std::string newFontName) noexcept -> void
    {
        fontName        = std::move(newFontName);
        auto fontHeight = getFontHeight();
        auto minFmtSize = getMinimumFmtSize();

        timeSetSpinner->setFont(fontName);
        fmt->setFont(fontName);
        fmt->setMinimumSize(minFmtSize.first, minFmtSize.second);
        fmt->setText(fmt->getText());

        setMinimumSize(timeSetSpinner->getMinimumSize().first + minFmtSize.first, fontHeight);
        resizeItems();
    }

    auto TimeSetFmtSpinner::onInput(const InputEvent &inputEvent) -> bool
    {
        // Ignore input event when not in edit mode
        if (editMode != EditMode::Edit) {
            return false;
        }

        if (auto ret = this->focusItem->onInput(inputEvent)) {
            return ret;
        }

        if (inputEvent.isShortRelease()) {
            switch (inputEvent.getKeyCode()) {
            case KeyCode::KEY_ENTER:
                return handleEnterKey();
            case KeyCode::KEY_RF:
                return handleRightFunctionKey();

            default:
                break;
            }
        }
        return false;
    }
    auto TimeSetFmtSpinner::handleEnterKey() -> bool
    {
        if (focusItem == timeSetSpinner) {
            setFocusItem(fmt);
            return true;
        }
        return false;
    }
    auto TimeSetFmtSpinner::handleRightFunctionKey() -> bool
    {
        if (focusItem == fmt) {
            setFocusItem(timeSetSpinner);
            return true;
        }

        return false;
    }
    auto TimeSetFmtSpinner::isPm(const std::string_view str) const noexcept -> bool
    {
        return str == utils::time::Locale::getPM().c_str();
    }
} // namespace gui
\ No newline at end of file

A module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp => module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp +67 -0
@@ 0,0 1,67 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <gui/widgets/BoxLayout.hpp>
#include <gui/widgets/TextConstants.hpp>
#include <time/time_locale.hpp>

#include <string>

namespace gui
{
    class TextSpinner;
    class TimeSetSpinner;

    /// Time set spinner widget class with option for dynamic switching between 24/12-hour format
    /// Automatically recalculates hour upon switching format
    /// Can be used as a basic time displaying widget when @ref EditMode set to 'Browse'
    /// @ref KeyCode::KEY_ENTER switches hour -> minute -> time format
    /// @ref KeyCode::KEY_RF switches back
    /// Two time formats are supported:
    /// utils::time::Locale::TimeFormat::FormatTime12H
    /// utils::time::Locale::TimeFormat::FormatTime24H
    class TimeSetFmtSpinner : public HBox
    {
      public:
        explicit TimeSetFmtSpinner(
            Item *parent                               = nullptr,
            uint32_t x                                 = 0U,
            uint32_t y                                 = 0U,
            uint32_t w                                 = 0U,
            uint32_t h                                 = 0U,
            utils::time::Locale::TimeFormat timeFormat = utils::time::Locale::TimeFormat::FormatTime12H);

        /// Switches currently displayed time format
        auto setTimeFormat(utils::time::Locale::TimeFormat fmt) noexcept -> void;
        auto setHour(int value) noexcept -> void;
        auto setMinute(int value) noexcept -> void;
        auto setEditMode(EditMode newEditMode) noexcept -> void;
        auto setFont(std::string newFontName) noexcept -> void;
        [[nodiscard]] auto getHour() const noexcept -> int;
        [[nodiscard]] auto getMinute() const noexcept -> int;

      private:
        enum class TraverseDir : bool
        {
            Left,
            Right
        };

        [[nodiscard]] auto getFontHeight() const noexcept -> uint16_t;
        [[nodiscard]] auto getMinimumFmtSize() const noexcept -> std::pair<Length, Length>;

        [[nodiscard]] auto isPm(std::string_view str) const noexcept -> bool;
        auto onInput(const InputEvent &inputEvent) -> bool override;
        auto handleEnterKey() -> bool;
        auto handleRightFunctionKey() -> bool;

        TimeSetSpinner *timeSetSpinner             = nullptr;
        TextSpinner *fmt                           = nullptr;
        EditMode editMode                          = EditMode::Edit;
        std::string fontName                       = style::window::font::supersizemelight;
        utils::time::Locale::TimeFormat timeFormat = utils::time::Locale::TimeFormat::FormatTime12H;
    };

} // namespace gui

M module-apps/apps-common/widgets/TimeSetSpinner.cpp => module-apps/apps-common/widgets/TimeSetSpinner.cpp +34 -5
@@ 33,6 33,7 @@ namespace gui
        hour->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        hour->setFixedFieldWidth(noOfDigits);
        hour->setEdges(RectangleEdge::None);
        hour->setPenFocusWidth(style::time_set_spinner::focus::size);
        hour->setCurrentValue(0);

        addWidget(hour);


@@ 48,6 49,7 @@ namespace gui
        minute = new Spinner(minuteMin, minuteMax, minuteStep, Boundaries::Continuous);
        minute->setMinimumSize(doubleCharWidth, fontHeight);
        minute->setFont(fontName);
        minute->setPenFocusWidth(style::time_set_spinner::focus::size);

        minute->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        minute->setFixedFieldWidth(noOfDigits);


@@ 57,8 59,15 @@ namespace gui

        resizeItems();

        focusChangedCallback = [&](Item &) {
            if (editMode != EditMode::Edit) {
                return false;
            }
            setFocusItem(focus ? lastFocus : nullptr);
            return true;
        };
        if (editMode == EditMode::Edit) {
            setFocusItem(hour);
            updateFocus(hour);
        }
    }



@@ 113,7 122,7 @@ namespace gui
    auto TimeSetSpinner::handleEnterKey() -> bool
    {
        if (focusItem == hour) {
            setFocusItem(minute);
            updateFocus(minute);
            return true;
        }
        return false;


@@ 122,7 131,7 @@ namespace gui
    auto TimeSetSpinner::handleRightFunctionKey() -> bool
    {
        if (focusItem == minute) {
            setFocusItem(hour);
            updateFocus(hour);
            return true;
        }
        return false;


@@ 157,7 166,7 @@ namespace gui
        minute->setMinimumSize(doubleCharWidth, fontHeight);
        minute->setText(minute->getText());

        setMinimumSize(2 * doubleCharWidth + getColonWidth(), fontHeight);
        setMinimumSize(noOfDigits * doubleCharWidth + getColonWidth(), fontHeight);
        resizeItems();
    }



@@ 165,7 174,7 @@ namespace gui
    {
        this->editMode = editMode;
        if (editMode == EditMode::Edit) {
            setFocusItem(hour);
            updateFocus(hour);
        }
        else {
            setFocusItem(nullptr);


@@ 181,4 190,24 @@ namespace gui
    {
        return minute->getCurrentValue();
    }
    auto TimeSetSpinner::setHourMax(std::uint32_t newMax) noexcept -> void
    {
        hour->setMaxValue(newMax);
    }
    auto TimeSetSpinner::setHourMin(std::uint32_t newMin) noexcept -> void
    {
        hour->setMinValue(newMin);
    }
    void TimeSetSpinner::updateFocus(Item *newFocus)
    {
        setFocusItem(newFocus);
        lastFocus = newFocus;
    }
    auto TimeSetSpinner::getMinimumSize() const noexcept -> std::pair<std::uint32_t, std::uint32_t>
    {
        constexpr auto spacer = 1U;
        auto fontHeight       = getFontHeight();
        auto doubleCharWidth  = (getWidestDigitWidth() * noOfDigits) + (spacer * noOfDigits);
        return {noOfDigits * doubleCharWidth + getColonWidth(), fontHeight};
    }
} /* namespace gui */

M module-apps/apps-common/widgets/TimeSetSpinner.hpp => module-apps/apps-common/widgets/TimeSetSpinner.hpp +14 -0
@@ 10,6 10,14 @@

#include <string>

namespace style::time_set_spinner
{
    namespace focus
    {
        inline constexpr auto size = 3U;
    } // namespace focus
} // namespace style::time_set_spinner

namespace gui
{
    class TimeSetSpinner : public HBox


@@ 21,16 29,22 @@ namespace gui
        auto setMinute(int value) noexcept -> void;
        auto setFont(std::string newFontName) noexcept -> void;
        auto setEditMode(EditMode editMode) noexcept -> void;
        auto setHourMax(std::uint32_t newMax) noexcept -> void;
        auto setHourMin(std::uint32_t newMin) noexcept -> void;
        [[nodiscard]] auto getHour() const noexcept -> int;
        [[nodiscard]] auto getMinute() const noexcept -> int;

        auto getMinimumSize() const noexcept -> std::pair<std::uint32_t, std::uint32_t>;

      private:
        Spinner *hour        = nullptr;
        Label *colon         = nullptr;
        Spinner *minute      = nullptr;
        EditMode editMode    = EditMode::Edit;
        Item *lastFocus      = nullptr;
        std::string fontName = style::window::font::supersizemelight;

        void updateFocus(Item *newFocus);
        auto handleEnterKey() -> bool;
        auto handleRightFunctionKey() -> bool;
        auto onInput(const InputEvent &inputEvent) -> bool override;

M module-gui/gui/widgets/Spinner.cpp => module-gui/gui/widgets/Spinner.cpp +9 -1
@@ 87,7 87,7 @@ namespace gui
    bool Spinner::onFocus(bool state)
    {
        if (focus) {
            setEdges(RectangleEdge::Top | RectangleEdge::Bottom);
            setEdges(RectangleEdge::Bottom);
        }
        else {
            setEdges(RectangleEdge::None);


@@ 105,5 105,13 @@ namespace gui
        outStream << currentValue;
        setText(outStream.str());
    }
    void Spinner::setMinValue(int newMinValue)
    {
        minValue = newMinValue;
    }
    void Spinner::setMaxValue(int newMaxValue)
    {
        maxValue = newMaxValue;
    }

} // namespace gui

M module-gui/gui/widgets/Spinner.hpp => module-gui/gui/widgets/Spinner.hpp +3 -0
@@ 15,6 15,9 @@ namespace gui
        void setCurrentValue(int newCurrent);
        void setFixedFieldWidth(unsigned char newFixedFieldWidth);

        void setMinValue(int newMinValue);
        void setMaxValue(int newMaxValue);

        [[nodiscard]] int getCurrentValue() const noexcept;
        [[nodiscard]] unsigned char getFixedFieldWidth() const noexcept;


M module-gui/gui/widgets/TextSpinner.cpp => module-gui/gui/widgets/TextSpinner.cpp +1 -1
@@ 82,7 82,7 @@ namespace gui
    bool TextSpinner::onFocus(bool state)
    {
        if (focus) {
            setEdges(RectangleEdge::Top | RectangleEdge::Bottom);
            setEdges(RectangleEdge::Bottom);
        }
        else {
            setEdges(RectangleEdge::None);

M module-utils/time/time/time_locale.hpp => module-utils/time/time/time_locale.hpp +6 -3
@@ 65,9 65,12 @@ namespace utils
          public:
            static constexpr int max_hour_24H_mode = 23;
            static constexpr int max_hour_12H_mode = 12;
            static constexpr int max_minutes       = 59;
            static constexpr int max_years         = 2038;
            static constexpr int min_years         = 1970;
            static constexpr int min_hour_24H_mode = 0;
            static constexpr int min_hour_12H_mode = 1;

            static constexpr int max_minutes = 59;
            static constexpr int max_years   = 2038;
            static constexpr int min_years   = 1970;

            enum Day
            {