cs-midi/Selectors/Selector.hpp

160 lines
4.6 KiB
C++

#pragma once
#include "Selectable.hpp"
#include <AH/Containers/Updatable.hpp>
#include <AH/Debug/Debug.hpp>
#include <Def/Def.hpp>
BEGIN_CS_NAMESPACE
/**
* @brief An enumeration to set the behavior of selectors that are incremented
* (decremented) beyond their maximum (minimum) setting.
*/
enum class Wrap : bool {
Clamp = false, ///< When the maximum (minimum) setting is reached,
///< clamp to the maximum (minimum) setting.
Wrap = true, ///< When the maximum (minimum) setting is reached,
///< wrap around to the minimum (maximum) setting.
NoWrap = false,
};
/// A callback for the GenericSelector class that does nothing.
struct EmptySelectorCallback {
/// Initialize.
void begin() {}
/// Refresh, called periodically.
void update() {}
/// Called when the setting changes.
void update(setting_t oldSetting, setting_t newSetting) {
(void)oldSetting, (void)newSetting;
}
};
/// Base class for all Selectors exposing the `get` method, so it can be used
/// by display elements etc, without having to provide the full generic type.
///
/// A `set` method is not provided, because that would require either more
/// virtual functions, or a rather large refactoring.
class SelectorBase {
protected:
/// Constructor.
SelectorBase() = default;
public:
/// Get the current selection/setting.
setting_t get() const { return setting; }
protected:
/// The selection of the selector. It is saved in the selector as well as
/// the selectable, because you need it in order to implement
/// increment/decrement methods.
setting_t setting = 0;
};
template <setting_t N, class Callback = EmptySelectorCallback>
class GenericSelector : public SelectorBase, public AH::Updatable<> {
public:
/**
* @brief Constructor.
*
* @param selectable
* The selectable object to manage. When the value of the selector
* changes, it changes the selection of this selectable.
* @param callback
* The callback to call when the value changes. Used for (visual)
* feedback from the selector (e.g. LEDs or some different kind of
* display).
*/
GenericSelector(Selectable<N> &selectable, const Callback &callback)
: selectable(selectable), callback(callback) {}
void begin() override {
callback.begin();
reset();
}
void update() override { callback.update(); }
/// Reset the selection to the initial selection.
void reset() {
setting_t initialSelection = selectable.getInitialSelection();
selectable.select(initialSelection);
callback.update(initialSelection, initialSelection);
this->setting = initialSelection;
}
/**
* @brief Select the given selection
*
* @param newSetting
* The new setting to select [0, N-1].
*/
void set(setting_t newSetting) {
newSetting = selectable.validateSetting(newSetting);
selectable.select(newSetting);
if (get() != newSetting) {
callback.update(get(), newSetting);
this->setting = newSetting;
}
}
/**
* @brief Add one to the setting, wrap around or clamp, depending on the
* parameter, if the new setting would be out of range.
*
* @param wrap
* Wrap or clamp if the new setting would be out of range.
*/
void increment(Wrap wrap) {
setting_t setting = this->get();
setting++;
if (setting == N) {
if (wrap == Wrap::Wrap)
setting = 0;
else
return;
}
this->set(setting);
}
/**
* @brief Subtract one from the setting, wrap around or clamp, depending
* on the parameter, if the new setting would be out of range.
*
* @param wrap
* Wrap or clamp if the new setting would be out of range.
*/
void decrement(Wrap wrap) {
setting_t setting = this->get();
if (setting == 0) {
if (wrap == Wrap::Wrap)
setting = N;
else
return;
}
setting--;
this->set(setting);
}
private:
Selectable<N> &selectable;
public:
Callback callback;
};
/**
* @brief A Selector with an empty callback.
*
* @tparam N
* The number of possible settings.
*/
template <setting_t N>
class Selector : public GenericSelector<N> {
public:
/// Constructor
Selector(Selectable<N> &selectable) : GenericSelector<N> {selectable, {}} {}
};
END_CS_NAMESPACE