[EGD-7792] Meditation timer interval widget replacement

Meditation timer interval widget replacement.
This commit is contained in:
Przemyslaw Brudny
2022-01-25 15:14:53 +01:00
committed by Przemysław Brudny
parent 53a51e866d
commit 810a8dc1c3
16 changed files with 183 additions and 227 deletions

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "NewEditAlarmModel.hpp"
@@ -30,7 +30,7 @@ namespace app::alarmClock
unsigned int NewEditAlarmModel::getMinimalItemSpaceRequired() const
{
return style::alarmClock::window::item::options::h;
return style::text_spinner_label::h;
}
void NewEditAlarmModel::requestRecords(uint32_t offset, uint32_t limit)

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -40,14 +40,6 @@ namespace style::alarmClock
inline constexpr auto marginBot = 20;
inline constexpr auto separator = 30;
} // namespace time
namespace options
{
inline constexpr auto spinner_h = 40;
inline constexpr auto label_h = 27;
inline constexpr auto h = spinner_h + label_h;
} // namespace options
} // namespace item
} // namespace window

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "AlarmOptionsItem.hpp"
@@ -11,27 +11,12 @@ namespace gui
{
AlarmOptionsItem::AlarmOptionsItem(const std::string &description, const std::vector<UTF8> &options)
{
setMinimumSize(style::listview::item_width_with_without_scroll, style::alarmClock::window::item::options::h);
setMinimumSize(style::listview::item_width_with_without_scroll, style::text_spinner_label::h);
setEdges(RectangleEdge::None);
setMargins(gui::Margins(style::widgets::leftMargin, style::margins::large, 0, 0));
vBox = new gui::VBox(this, 0, 0, 0, 0);
vBox->setEdges(gui::RectangleEdge::None);
vBox->activeItem = false;
descriptionLabel = new gui::Label(vBox, 0, 0, 0, 0);
descriptionLabel->setMinimumSize(style::window::default_body_width,
style::alarmClock::window::item::options::label_h);
descriptionLabel->setEdges(gui::RectangleEdge::None);
descriptionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Top));
descriptionLabel->setFont(style::window::font::small);
descriptionLabel->activeItem = false;
descriptionLabel->setText(description);
optionSpinner = new gui::TextSpinnerBox(vBox, options, Boundaries::Continuous);
optionSpinner->setMinimumSize(style::window::default_body_width,
style::alarmClock::window::item::options::spinner_h);
optionSpinner = new gui::TextSpinnerBoxWithLabel(this, description, options, Boundaries::Continuous);
focusChangedCallback = [&](gui::Item &item) {
setFocusItem(focus ? optionSpinner : nullptr);
@@ -41,7 +26,7 @@ namespace gui
inputCallback = [&](Item &item, const InputEvent &event) { return optionSpinner->onInput(event); };
dimensionChangedCallback = [&](gui::Item &, const BoundingBox &newDim) -> bool {
vBox->setArea({0, 0, newDim.w, newDim.h});
optionSpinner->setArea({0, 0, newDim.w, newDim.h});
return true;
};
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -7,7 +7,7 @@
#include <Label.hpp>
#include <BoxLayout.hpp>
#include <widgets/TextSpinnerBox.hpp>
#include <widgets/TextSpinnerBoxWithLabel.hpp>
namespace gui
{
@@ -17,11 +17,7 @@ namespace gui
explicit AlarmOptionsItem(const std::string &description, const std::vector<UTF8> &options = {});
protected:
gui::TextSpinnerBox *optionSpinner = nullptr;
private:
gui::VBox *vBox = nullptr;
gui::Label *descriptionLabel = nullptr;
gui::TextSpinnerBoxWithLabel *optionSpinner = nullptr;
};
} /* namespace gui */

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -43,7 +43,7 @@ namespace callAppStyle
inline constexpr auto x = 60U;
inline constexpr auto y = 254U;
inline constexpr auto w = style::window_width - 2 * x;
inline constexpr auto h = 20U;
inline constexpr auto h = 40U;
} // namespace numberDescriptionLabel
namespace icon

View File

@@ -68,7 +68,7 @@ namespace gui
numberDescriptionLabel::h);
numberDescriptionLabel->setFont(style::window::font::mediumlight);
numberDescriptionLabel->setAlignment(
gui::Alignment{gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center});
gui::Alignment{gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Top});
numberDescriptionLabel->setEdges(RectangleEdge::None);
}

View File

@@ -50,33 +50,6 @@ namespace style::meditation
constexpr auto X = style::window::default_left_margin;
constexpr auto Y = style::meditation::timer::Y + 2 * style::meditation::timer::Radius + VerticalWidgetGap;
constexpr auto Width = style::window::default_body_width;
constexpr auto Height = 60;
namespace topLabel
{
constexpr auto X = 0;
constexpr auto Y = 0;
constexpr auto Width = intervalBox::Width;
constexpr auto Height = intervalBox::Height / 2;
} // namespace topLabel
namespace bottomLabel
{
constexpr auto X = 0;
constexpr auto Y = topLabel::Y + topLabel::Height;
constexpr auto Width = intervalBox::Width;
constexpr auto Height = intervalBox::Height / 2;
} // namespace bottomLabel
namespace arrow
{
constexpr auto Y = 10;
constexpr auto Width = 15;
constexpr auto Height = intervalBox::Height / 2;
constexpr auto LeftX = 0;
constexpr auto RightX = intervalBox::Width - arrow::Width;
} // namespace arrow
} // namespace intervalBox
namespace itemList

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "IntervalBox.hpp"
@@ -20,7 +20,7 @@ namespace
} // namespace
IntervalBox::IntervalBox(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h, TimerProperty *timerSetter)
: BoxLayout(parent, x, y, w, h), timerSetter(timerSetter)
: HBox(parent, x, y, w, h), timerSetter(timerSetter)
{
assert(timerSetter);
build();
@@ -28,136 +28,74 @@ IntervalBox::IntervalBox(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint3
void IntervalBox::build()
{
namespace boxStyle = style::meditation::intervalBox;
chimeSpinner = new gui::TextSpinnerBoxWithLabel(
this, utils::translate("app_meditation_interval_chime"), {}, Boundaries::Continuous);
chimeSpinner->setMinimumSize(widgetArea.w, widgetArea.h);
updateIntervals();
auto topLabel = new Label(this,
boxStyle::topLabel::X,
boxStyle::topLabel::Y,
boxStyle::topLabel::Width,
boxStyle::topLabel::Height,
utils::translate("app_meditation_interval_chime"));
topLabel->setAlignment(Alignment(Alignment::Horizontal::Left, Alignment::Vertical::Bottom));
topLabel->setFont(style::window::font::small);
topLabel->setEdges(RectangleEdge::None);
resizeItems();
bottomLabel = new Label(this,
boxStyle::bottomLabel::X,
boxStyle::bottomLabel::Y,
boxStyle::bottomLabel::Width,
boxStyle::bottomLabel::Height);
bottomLabel->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
bottomLabel->setFont(style::window::font::small);
bottomLabel->setEdges(RectangleEdge::Bottom);
bottomLabel->setPenWidth(style::window::default_border_rect_no_focus);
leftSwitchArrow = new gui::Image(bottomLabel,
boxStyle::arrow::LeftX,
boxStyle::arrow::Y,
boxStyle::arrow::Width,
boxStyle::arrow::Height,
"arrow_left_20px_W_M");
rightSwitchArrow = new gui::Image(bottomLabel,
boxStyle::arrow::RightX,
boxStyle::arrow::Y,
boxStyle::arrow::Width,
boxStyle::arrow::Height,
"arrow_right_20px_W_M");
updateIntervals(ChimeIntervalList::Direction::Next);
leftSwitchArrow->setVisible(false);
rightSwitchArrow->setVisible(false);
}
bool IntervalBox::onFocus(bool state)
{
if (state) {
rescaleIntervals();
bottomLabel->setPenWidth(style::window::default_border_focus_w);
bottomLabel->setFont(style::window::font::smallbold);
}
else {
bottomLabel->setPenWidth(style::window::default_border_rect_no_focus);
bottomLabel->setFont(style::window::font::small);
}
auto currentMeditationTime = timerSetter->getTime();
leftSwitchArrow->setVisible(state &&
chimeIntervals.hasNext(ChimeIntervalList::Direction::Previous, currentMeditationTime));
rightSwitchArrow->setVisible(state &&
chimeIntervals.hasNext(ChimeIntervalList::Direction::Next, currentMeditationTime));
return true;
}
bool IntervalBox::onInput(const InputEvent &inputEvent)
{
if (inputEvent.isShortRelease()) {
if (inputEvent.is(KeyCode::KEY_LEFT)) {
updateIntervals(ChimeIntervalList::Direction::Previous);
return true;
focusChangedCallback = [&](gui::Item &item) {
if (focus) {
updateIntervals();
}
else if (inputEvent.is(KeyCode::KEY_RIGHT)) {
updateIntervals(ChimeIntervalList::Direction::Next);
return true;
setFocusItem(focus ? chimeSpinner : nullptr);
return true;
};
}
void IntervalBox::updateIntervals()
{
auto setTime = timerSetter->getTime();
std::vector<UTF8> chimeOptions;
for (auto chime : chimeIntervals) {
if (chime < setTime) {
chimeOptions.push_back(intervalToString(chime));
}
else {
break;
}
}
return false;
}
void IntervalBox::updateIntervals(ChimeIntervalList::Direction direction)
{
auto currentMeditationTime = timerSetter->getTime();
if (!chimeIntervals.moveToNext(direction, currentMeditationTime)) {
return;
}
intervalValue = chimeIntervals.getCurrent();
bottomLabel->setText(ChimeIntervalList::toPrintableInterval(intervalValue));
auto currentInterval = chimeSpinner->getCurrentValue();
chimeSpinner->setData(chimeOptions);
leftSwitchArrow->setVisible(chimeIntervals.hasNext(ChimeIntervalList::Direction::Previous, currentMeditationTime));
rightSwitchArrow->setVisible(chimeIntervals.hasNext(ChimeIntervalList::Direction::Next, currentMeditationTime));
}
void IntervalBox::rescaleIntervals()
{
while (intervalValue >= timerSetter->getTime() && intervalValue != minutes{0}) {
updateIntervals(ChimeIntervalList::Direction::Previous);
if (stringToInterval(currentInterval) < setTime) {
chimeSpinner->setCurrentValue(currentInterval);
}
}
IntervalBox::ChimeIntervalList::ChimeIntervalList() : intervals(::chimeIntervals), current(intervals.begin())
{}
bool IntervalBox::ChimeIntervalList::moveToNext(Direction direction, std::chrono::minutes meditationTime) noexcept
std::chrono::seconds IntervalBox::getIntervalValue() noexcept
{
if (!hasNext(direction, meditationTime)) {
return false;
}
if (direction == Direction::Next) {
current++;
if (stringToInterval(chimeSpinner->getCurrentValue()) < timerSetter->getTime()) {
return std::chrono::seconds{stringToInterval(chimeSpinner->getCurrentValue())};
}
else {
current--;
return std::chrono::seconds{0};
}
return true;
}
bool IntervalBox::ChimeIntervalList::hasNext(Direction direction, std::chrono::minutes meditationTime) const noexcept
std::string IntervalBox::intervalToString(std::chrono::minutes time)
{
if (direction == Direction::Previous) {
return current != intervals.begin();
}
auto result = std::next(current) != intervals.end();
if (result) {
result = *std::next(current) < meditationTime;
}
return result;
}
std::string IntervalBox::ChimeIntervalList::toPrintableInterval(std::chrono::minutes value)
{
if (value.count() == 0) {
if (time.count() == 0) {
return utils::translate("app_meditation_interval_none");
}
const std::string toReplace = "%0";
std::string temp = utils::translate("app_meditation_interval_every_x_minutes");
temp.replace(temp.find(toReplace), toReplace.size(), std::to_string(static_cast<int>(value.count())));
return temp;
std::string description = utils::translate("app_meditation_interval_every_x_minutes");
description.replace(description.find(toReplace), toReplace.size(), std::to_string(static_cast<int>(time.count())));
return description;
}
std::chrono::minutes IntervalBox::stringToInterval(const std::string &description)
{
if (description == utils::translate("app_meditation_interval_none")) {
return std::chrono::minutes(0);
}
auto number = description;
number.erase(std::remove_if(number.begin(), number.end(), [](char c) { return !isdigit(c, std::locale()); }),
number.end());
return std::chrono::minutes(std::atoi(number.c_str()));
}

View File

@@ -6,6 +6,7 @@
#include <gui/widgets/BoxLayout.hpp>
#include <gui/widgets/Image.hpp>
#include <gui/widgets/text/Label.hpp>
#include <apps-common/widgets/TextSpinnerBoxWithLabel.hpp>
#include <list>
#include <chrono>
@@ -14,52 +15,21 @@ namespace gui
{
class TimerProperty;
class IntervalBox : public BoxLayout
class IntervalBox : public HBox
{
class ChimeIntervalList
{
std::vector<std::chrono::minutes> intervals;
std::vector<std::chrono::minutes>::const_iterator current;
public:
enum class Direction
{
Next,
Previous
};
ChimeIntervalList();
[[nodiscard]] std::chrono::minutes getCurrent() const noexcept
{
return *current;
}
bool moveToNext(Direction dir, std::chrono::minutes meditationTime) noexcept;
[[nodiscard]] bool hasNext(Direction dir, std::chrono::minutes meditationTime) const noexcept;
[[nodiscard]] static std::string toPrintableInterval(std::chrono::minutes time);
} chimeIntervals;
TimerProperty *timerSetter = nullptr;
Label *bottomLabel = nullptr;
Image *leftSwitchArrow = nullptr;
Image *rightSwitchArrow = nullptr;
std::chrono::minutes intervalValue{0};
TimerProperty *timerSetter = nullptr;
TextSpinnerBoxWithLabel *chimeSpinner = nullptr;
void build();
void updateIntervals(ChimeIntervalList::Direction dir);
void updateIntervals();
[[nodiscard]] std::string intervalToString(std::chrono::minutes time);
[[nodiscard]] std::chrono::minutes stringToInterval(const std::string &description);
void rescaleIntervals();
public:
IntervalBox(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h, TimerProperty *timerSetter);
bool onFocus(bool isFocused) final;
bool onInput(const InputEvent &inputEvent) final;
[[nodiscard]] std::chrono::seconds getIntervalValue() const noexcept
{
return std::chrono::seconds{intervalValue};
}
[[nodiscard]] std::chrono::seconds getIntervalValue() noexcept;
};
} // namespace gui

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ApplicationMeditation.hpp"
@@ -51,7 +51,7 @@ namespace gui
style::meditation::intervalBox::X,
style::meditation::intervalBox::Y,
style::meditation::intervalBox::Width,
style::meditation::intervalBox::Height,
style::text_spinner_label::h,
timeSetter);
intervalBox->setEdges(RectangleEdge::None);

View File

@@ -51,6 +51,7 @@ target_sources(apps-common
widgets/ModesBox.cpp
widgets/SpinBox.cpp
widgets/TextSpinnerBox.cpp
widgets/TextSpinnerBoxWithLabel.cpp
widgets/TextWithIconsWidget.cpp
widgets/TimeSetSpinner.cpp
widgets/AlarmSetSpinner.cpp

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "TextSpinnerBox.hpp"
@@ -27,12 +27,18 @@ namespace gui
inputCallback = [&](Item &item, const InputEvent &event) { return spinner->onInput(event); };
focusChangedCallback = [&](gui::Item &item) {
if (focus) {
if (focus && !spinner->isSingle()) {
leftArrow->setVisible(true);
rightArrow->setVisible(true);
spinner->setFont(style::window::font::mediumbold);
setPenWidth(style::window::default_border_focus_w);
}
else if (focus && spinner->isSingle()) {
leftArrow->setVisible(false);
rightArrow->setVisible(false);
spinner->setFont(style::window::font::mediumbold);
setPenWidth(style::window::default_border_focus_w);
}
else {
leftArrow->setVisible(false);
rightArrow->setVisible(false);

View File

@@ -0,0 +1,46 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "TextSpinnerBoxWithLabel.hpp"
namespace gui
{
TextSpinnerBoxWithLabel::TextSpinnerBoxWithLabel(Item *parent,
const std::string &description,
const std::vector<UTF8> &data,
Boundaries boundaries)
: VBox(parent)
{
setEdges(RectangleEdge::None);
descriptionLabel = new gui::Label(this, 0, 0, 0, 0);
descriptionLabel->setMinimumSize(style::window::default_body_width, style::text_spinner_label::label_h);
descriptionLabel->setEdges(gui::RectangleEdge::None);
descriptionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Top));
descriptionLabel->setFont(style::window::font::small);
descriptionLabel->activeItem = false;
descriptionLabel->setText(description);
optionSpinner = new gui::TextSpinnerBox(this, data, boundaries);
optionSpinner->setMinimumSize(style::window::default_body_width, style::text_spinner_label::spinner_h);
focusChangedCallback = [&](gui::Item &item) {
setFocusItem(focus ? optionSpinner : nullptr);
return true;
};
}
void TextSpinnerBoxWithLabel::setData(const std::vector<UTF8> &data)
{
optionSpinner->setData(data);
}
UTF8 TextSpinnerBoxWithLabel::getCurrentValue() const noexcept
{
return optionSpinner->getCurrentValue();
}
void TextSpinnerBoxWithLabel::setCurrentValue(UTF8 val)
{
optionSpinner->setCurrentValue(val);
}
} // namespace gui

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
#include <Label.hpp>
#include <BoxLayout.hpp>
#include <widgets/TextSpinnerBox.hpp>
namespace style::text_spinner_label
{
inline constexpr auto spinner_h = 40;
inline constexpr auto label_h = 27;
inline constexpr auto h = spinner_h + label_h;
} // namespace style::text_spinner_label
namespace gui
{
class TextSpinnerBoxWithLabel : public VBox
{
public:
TextSpinnerBoxWithLabel(Item *parent,
const std::string &description,
const std::vector<UTF8> &data,
Boundaries boundaries);
void setData(const std::vector<UTF8> &data);
[[nodiscard]] UTF8 getCurrentValue() const noexcept;
void setCurrentValue(UTF8 val);
protected:
gui::TextSpinnerBox *optionSpinner = nullptr;
private:
gui::Label *descriptionLabel = nullptr;
};
} // namespace gui

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -27,6 +27,7 @@ namespace gui
void setFocusEdges(RectangleEdge edges);
bool onInput(const InputEvent &inputEvent) override;
bool onFocus(bool state) override;
[[nodiscard]] bool isSingle() const;
[[nodiscard]] bool isAtMin() const;
[[nodiscard]] bool isAtMax() const;
@@ -144,6 +145,10 @@ namespace gui
onValueChanged(getCurrentValue());
}
}
template <typename Policy> bool GenericSpinner<Policy>::isSingle() const
{
return policy.isSingle();
}
template <typename Policy> bool GenericSpinner<Policy>::isAtMin() const
{
return policy.isAtMin();

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -85,6 +85,10 @@ namespace gui
}
}
[[nodiscard]] bool isSingle() const
{
return range.size() == 1;
}
[[nodiscard]] bool isAtMin() const
{
return pos == 0;
@@ -291,7 +295,10 @@ namespace gui
pos = 0;
}
}
[[nodiscard]] bool isSingle() const
{
return range.size() == 1;
}
[[nodiscard]] bool isAtMin() const
{
return pos == 0;