#pragma once #include "fl/namespace.h" #include "fl/memory.h" #include "fl/json.h" #include "fl/str.h" #include "fl/int.h" #include "fl/audio.h" #include "fl/engine_events.h" #include "fl/function_list.h" #include "fl/math_macros.h" #include "fl/type_traits.h" #include "fl/ui_impl.h" #include "fl/unused.h" #include "platforms/ui_defs.h" #include "sensors/button.h" #include "fl/virtual_if_not_avr.h" #include "fl/int.h" #define FL_NO_COPY(CLASS) \ CLASS(const CLASS &) = delete; \ CLASS &operator=(const CLASS &) = delete; namespace fl { // Base class for UI elements that provides string-based group functionality class UIElement { public: UIElement() {} VIRTUAL_IF_NOT_AVR ~UIElement() {} virtual void setGroup(const fl::string& groupName) { mGroupName = groupName; } fl::string getGroup() const { return mGroupName; } bool hasGroup() const { return !mGroupName.empty(); } private: fl::string mGroupName; }; // If the platform is missing ui components, provide stubs. class UISlider : public UIElement { public: FL_NO_COPY(UISlider) // If step is -1, it will be calculated as (max - min) / 100 UISlider(const char *name, float value = 128.0f, float min = 1, float max = 255, float step = -1.f) : mImpl(name, value, min, max, step), mListener(this) {} float value() const { return mImpl.value(); } float value_normalized() const { float min = mImpl.getMin(); float max = mImpl.getMax(); if (ALMOST_EQUAL(max, min, 0.0001f)) { return 0; } return (value() - min) / (max - min); } float getMax() const { return mImpl.getMax(); } float getMin() const { return mImpl.getMin(); } void setValue(float value); operator float() const { return mImpl.value(); } operator u8() const { return static_cast(mImpl.value()); } operator fl::u16() const { return static_cast(mImpl.value()); } operator int() const { return static_cast(mImpl.value()); } template T as() const { return static_cast(mImpl.value()); } int as_int() const { return static_cast(mImpl.value()); } UISlider &operator=(float value) { mImpl.setValue(value); return *this; } UISlider &operator=(int value) { mImpl.setValue(static_cast(value)); return *this; } // Override setGroup to also update the implementation void setGroup(const fl::string& groupName) override { UIElement::setGroup(groupName); // Update the implementation's group if it has the method (WASM platforms) mImpl.setGroup(groupName); } int onChanged(function callback) { int out = mCallbacks.add(callback); mListener.addToEngineEventsOnce(); return out; } void clearCallbacks() { mCallbacks.clear(); } protected: UISliderImpl mImpl; struct Listener : public EngineEvents::Listener { Listener(UISlider *owner) : mOwner(owner) { EngineEvents::addListener(this); } ~Listener() { if (added) { EngineEvents::removeListener(this); } } void addToEngineEventsOnce() { if (added) { return; } EngineEvents::addListener(this); added = true; } void onBeginFrame() override; private: UISlider *mOwner; bool added = false; }; private: FunctionList mCallbacks; float mLastFrameValue = 0; bool mLastFramevalueValid = false; Listener mListener; }; // template operator for >= against a jsSliderImpl class UIButton : public UIElement { public: FL_NO_COPY(UIButton) UIButton(const char *name) : mImpl(name), mListener(this) {} ~UIButton() {} bool isPressed() const { if (mImpl.isPressed()) { return true; } // If we have a real button, check if it's pressed if (mRealButton) { return mRealButton->isPressed(); } // Otherwise, return the default state return false; } bool clicked() const { if (mImpl.clicked()) { return true; } if (mRealButton) { // If we have a real button, check if it was clicked return mRealButton->isPressed(); } return false; } int clickedCount() const { return mImpl.clickedCount(); } operator bool() const { return clicked(); } bool value() const { return clicked(); } void addRealButton(fl::shared_ptr