// SPDX-License-Identifier: MIT // // ES-DE Frontend // ComponentList.h // // Used to lay out and navigate lists in GUI menus. // #ifndef ES_CORE_COMPONENTS_COMPONENT_LIST_H #define ES_CORE_COMPONENTS_COMPONENT_LIST_H #include "IList.h" struct ComponentListElement { ComponentListElement(const std::shared_ptr& componentArg = nullptr, bool resizeWidthArg = true, bool invertWhenSelectedArg = true) : component {componentArg} , resizeWidth {resizeWidthArg} , invertWhenSelected {invertWhenSelectedArg} { } std::shared_ptr component; bool resizeWidth; bool invertWhenSelected; }; struct ComponentListRow { std::vector elements; // The input handler is called when the user enters any input while this row is // highlighted (including up/down navigation). // Return false to let the list try to use it or true if the input has been consumed. // If no input handler is supplied (inputHandler == nullptr), then the default behavior // is to forward the input to the rightmost element in the currently selected row. std::function inputHandler; void addElement(const std::shared_ptr& comp, bool resizeWidth, bool invertWhenSelected = true, glm::ivec2 autoCalcExtent = {0, 0}) { comp->setAutoCalcExtent(autoCalcExtent); elements.push_back(ComponentListElement(comp, resizeWidth, invertWhenSelected)); } // Utility function for making an input handler for an input event. void makeAcceptInputHandler(const std::function& func) { inputHandler = [func](InputConfig* config, Input input) -> bool { if (config->isMappedTo("a", input) && input.value != 0) { func(); return true; } return false; }; } }; class ComponentList : public IList { public: ComponentList(); enum ScrollIndicator { SCROLL_NONE, SCROLL_UP, SCROLL_UP_DOWN, SCROLL_DOWN }; void addRow(const ComponentListRow& row, bool setCursorHere = false); void textInput(const std::string& text, const bool pasting = false) override; bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; void render(const glm::mat4& parentTrans) override; std::vector getHelpPrompts() override; void onSizeChanged() override; void onFocusGained() override { mFocused = true; } void onFocusLost() override { mFocused = false; resetSelectedRow(); } bool moveCursor(int amount); int getCursorId() const { return mCursor; } const float getRowHeight() const { return mRowHeight; } void setRowHeight(float height) { mRowHeight = height; } const float getTotalRowHeight() const { return mRowHeight * mEntries.size(); } void resetSelectedRow() { if (mEntries.size() > static_cast(mCursor)) { for (auto& comp : mEntries.at(mCursor).data.elements) comp.component->resetComponent(); } } void setHorizontalScrolling(bool state) override { for (auto& entry : mEntries) { for (auto& element : entry.data.elements) element.component->setHorizontalScrolling(state); } } void resetScrollIndicatorStatus() { mScrollIndicatorStatus = SCROLL_NONE; if (mScrollIndicatorChangedCallback != nullptr) mScrollIndicatorChangedCallback(mScrollIndicatorStatus, false); } void setCursorChangedCallback(const std::function& callback) { mCursorChangedCallback = callback; } const std::function& getCursorChangedCallback() const { return mCursorChangedCallback; } void setScrollIndicatorChangedCallback( const std::function& callback) { mScrollIndicatorChangedCallback = callback; } protected: void onCursorChanged(const CursorState& state) override; private: Renderer* mRenderer; bool mFocused; bool mSetupCompleted; bool mBottomCameraOffset; bool mSingleRowScroll; void updateCameraOffset(); void updateElementPosition(const ComponentListRow& row); void updateElementSize(const ComponentListRow& row); float mRowHeight; float mHorizontalPadding; float mSelectorBarOffset; float mCameraOffset; std::function mCursorChangedCallback; std::function mScrollIndicatorChangedCallback; ScrollIndicator mScrollIndicatorStatus; }; #endif // ES_CORE_COMPONENTS_COMPONENT_LIST_H