// 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 #include #include namespace gui { // This spinner operates on container elements and transforms the current container into a string. // For the containers of types that are convertible to strings like std::string, integer, floating-point integers, // there is no need to provide the custom formatter. However, it is possible to pass a formatter if a user wants to // perform custom formatting. It is not possible to use a formatter when using container of std::string. template class StringOutputSpinner : public TextFixedSize { public: using range = typename Container::range; using value_type = typename Container::value_type; explicit StringOutputSpinner(Container &&container, Orientation orientation = Orientation::Vertical) : container{std::move(container)}, orientation{orientation} { init(); } explicit StringOutputSpinner(range &range, Boundaries boundaries = Boundaries::Fixed, Orientation orientation = Orientation::Vertical) : container{range, convert_boundaries(boundaries)}, orientation{orientation} { init(); } explicit StringOutputSpinner(range &&range, Boundaries boundaries = Boundaries::Fixed, Orientation orientation = Orientation::Vertical) : container{std::move(range), convert_boundaries(boundaries)}, orientation{orientation} { init(); } using OnValueChanged = std::function; void setFocusEdges(RectangleEdge edges); bool onInput(const InputEvent &inputEvent) override; bool onFocus(bool state) override; [[nodiscard]] size_t size() const; [[nodiscard]] bool is_min() const; [[nodiscard]] bool is_max() const; OnValueChanged onValueChanged; void set_range(const range &range); void set_value(const value_type &value); auto get_value() -> value_type; [[nodiscard]] auto value() const noexcept; private: inline typename Container::Boundaries convert_boundaries(gui::Boundaries boundaries) { switch (boundaries) { case gui::Boundaries::Fixed: return Container::Boundaries::Fixed; case gui::Boundaries::Continuous: return Container::Boundaries::Continuous; } return Container::Boundaries::Fixed; } void init(); [[nodiscard]] std::string value_as_str() const noexcept; void next(); void previous(); bool is_previous_event(const InputEvent &inputEvent); bool is_next_event(const InputEvent &inputEvent); void update(); void invoke(); static constexpr bool is_string = std::is_same_v or std::is_convertible_v; static constexpr bool is_fundamental = std::is_fundamental_v; static constexpr bool is_formatter_defined = not std::is_same_v; Container container; RectangleEdge focusEdges = RectangleEdge::Bottom; Orientation orientation = Orientation::Vertical; }; template auto StringOutputSpinner::value() const noexcept { return container.get(); } template std::string StringOutputSpinner::value_as_str() const noexcept { if constexpr (is_string) { return container.get(); } else if constexpr (is_fundamental and not is_formatter_defined) { return std::to_string(container.get()); } else { return Formatter{}(container.get()); } } template void StringOutputSpinner::setFocusEdges(RectangleEdge edges) { focusEdges = edges; } template bool StringOutputSpinner::onInput(const InputEvent &inputEvent) { if (inputEvent.isShortRelease()) { if (is_previous_event(inputEvent)) { previous(); return true; } else if (is_next_event(inputEvent)) { next(); return true; } } return false; } template bool StringOutputSpinner::onFocus(bool state) { if (focus) { setEdges(focusEdges); } else { setEdges(RectangleEdge::None); } showCursor(state); return true; } template size_t StringOutputSpinner::size() const { return container.size(); } template bool StringOutputSpinner::is_min() const { return container.is_min(); } template bool StringOutputSpinner::is_max() const { return container.is_max(); } template void StringOutputSpinner::set_value(const value_type &value) { container.set(value); update(); } template auto StringOutputSpinner::get_value() -> StringOutputSpinner::value_type { return container.get(); } template void StringOutputSpinner::set_range(const range &range) { container.set_range(range); update(); } template void StringOutputSpinner::next() { if (container.next()) { update(); invoke(); } } template void StringOutputSpinner::previous() { if (container.previous()) { update(); invoke(); } } template void StringOutputSpinner::update() { setText(value_as_str()); } template void StringOutputSpinner::invoke() { if (onValueChanged) { onValueChanged(value()); } } template bool StringOutputSpinner::is_previous_event(const InputEvent &inputEvent) { return (orientation == Orientation::Vertical && inputEvent.is(KeyCode::KEY_DOWN)) || (orientation == Orientation::Horizontal && inputEvent.is(KeyCode::KEY_LEFT)); } template bool StringOutputSpinner::is_next_event(const InputEvent &inputEvent) { return (orientation == Orientation::Vertical && inputEvent.is(KeyCode::KEY_UP)) || (orientation == Orientation::Horizontal && inputEvent.is(KeyCode::KEY_RIGHT)); } template void StringOutputSpinner::init() { setEditMode(EditMode::Browse); drawUnderline(false); update(); } } // namespace gui