#pragma once #include #include #include #include #include "DiabloUI/ui_flags.hpp" #include "engine/clx_sprite.hpp" #include "engine/render/text_render.hpp" #include "utils/enum_traits.h" #include "utils/stubs.h" namespace devilution { enum class UiType : uint8_t { Text, ArtText, ArtTextButton, ImageClx, ImageAnimatedClx, Button, List, Scrollbar, Edit, }; class UiItemBase { public: virtual ~UiItemBase() = default; [[nodiscard]] UiType GetType() const { return type_; } [[nodiscard]] bool IsType(UiType testType) const { return type_ == testType; } [[nodiscard]] UiFlags GetFlags() const { return uiFlags_; } [[nodiscard]] bool IsHidden() const { return HasAnyOf(uiFlags_, UiFlags::ElementHidden); } [[nodiscard]] bool IsNotInteractive() const { return HasAnyOf(uiFlags_, UiFlags::ElementHidden | UiFlags::ElementDisabled); } void Hide() { uiFlags_ |= UiFlags::ElementHidden; } void Show() { uiFlags_ &= ~UiFlags::ElementHidden; } protected: UiItemBase(UiType type, SDL_Rect rect, UiFlags flags) : type_(type) , m_rect(rect) , uiFlags_(flags) { } void SetFlags(UiFlags flags) { uiFlags_ = flags; } private: UiType type_; public: SDL_Rect m_rect; private: UiFlags uiFlags_; }; //============================================================================= class UiImageClx : public UiItemBase { public: UiImageClx(ClxSprite sprite, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::ImageClx, rect, flags) , sprite_(sprite) { } [[nodiscard]] bool isCentered() const { return HasAnyOf(GetFlags(), UiFlags::AlignCenter); } [[nodiscard]] ClxSprite sprite() const { return sprite_; } void setSprite(ClxSprite sprite) { sprite_ = sprite; } private: ClxSprite sprite_; }; //============================================================================= class UiImageAnimatedClx : public UiItemBase { public: UiImageAnimatedClx(ClxSpriteList list, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::ImageAnimatedClx, rect, flags) , list_(list) { } [[nodiscard]] bool isCentered() const { return HasAnyOf(GetFlags(), UiFlags::AlignCenter); } [[nodiscard]] ClxSprite sprite(uint16_t frame) const { return list_[frame]; } [[nodiscard]] uint16_t numFrames() const { return list_.numSprites(); } private: ClxSpriteList list_; }; //============================================================================= class UiArtText : public UiItemBase { public: /** * @brief Constructs a UI element containing a (presumed to be) static line of text * @param text Pointer to the first character of a c-string * @param rect screen region defining the area to draw the text * @param flags UiFlags controlling color/alignment/size * @param spacing Spacing between characters * @param lineHeight Vertical distance between text lines */ UiArtText(const char *text, SDL_Rect rect, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1) : UiItemBase(UiType::ArtText, rect, flags) , text_(text) , spacing_(spacing) , lineHeight_(lineHeight) { } /** * @brief Constructs a UI element containing a line of text that may change between frames * @param ptext Pointer to a c-string (pointer to a pointer to the first character) * @param rect screen region defining the area to draw the text * @param flags UiFlags controlling color/alignment/size * @param spacing Spacing between characters * @param lineHeight Vertical distance between text lines */ UiArtText(const char **ptext, SDL_Rect rect, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1) : UiItemBase(UiType::ArtText, rect, flags) , textPointer_(ptext) , spacing_(spacing) , lineHeight_(lineHeight) { } [[nodiscard]] std::string_view GetText() const { if (text_ != nullptr) return text_; return *textPointer_; } [[nodiscard]] int GetSpacing() const { return spacing_; } [[nodiscard]] int GetLineHeight() const { return lineHeight_; } private: const char *text_ = nullptr; const char **textPointer_ = nullptr; int spacing_; int lineHeight_; }; //============================================================================= class UiScrollbar : public UiItemBase { public: UiScrollbar(ClxSprite bg, ClxSprite thumb, ClxSpriteList arrow, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Scrollbar, rect, flags) , m_bg(bg) , m_thumb(thumb) , m_arrow(arrow) { } // private: ClxSprite m_bg; ClxSprite m_thumb; ClxSpriteList m_arrow; }; //============================================================================= class UiArtTextButton : public UiItemBase { public: using Callback = void (*)(); UiArtTextButton(std::string_view text, Callback action, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::ArtTextButton, rect, flags) , text_(text) , action_(action) { } void SetFlags(UiFlags flags) { UiItemBase::SetFlags(flags); } [[nodiscard]] std::string_view GetText() const { return text_; } void Activate() const { action_(); } private: std::string_view text_; Callback action_; }; //============================================================================= class UiEdit : public UiItemBase { public: UiEdit(std::string_view hint, char *value, std::size_t maxLength, bool allowEmpty, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Edit, rect, flags) , m_hint(hint) , m_value(value) , m_max_length(maxLength) , m_allowEmpty(allowEmpty) { } // private: std::string_view m_hint; char *m_value; std::size_t m_max_length; bool m_allowEmpty; }; //============================================================================= // Plain text class UiText : public UiItemBase { public: UiText(std::string_view text, SDL_Rect rect, UiFlags flags = UiFlags::ColorDialogWhite) : UiItemBase(UiType::Text, rect, flags) , text_(text) { } [[nodiscard]] std::string_view GetText() const { return text_; } private: std::string_view text_; }; //============================================================================= // A button (uses Diablo sprites) class UiButton : public UiItemBase { public: using Callback = void (*)(); UiButton(std::string_view text, Callback action, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Button, rect, flags) , text_(text) , action_(action) , pressed_(false) { } [[nodiscard]] std::string_view GetText() const { return text_; } void Activate() const { action_(); } [[nodiscard]] bool IsPressed() const { return pressed_; } void Press() { pressed_ = true; } void Release() { pressed_ = false; } private: std::string_view text_; Callback action_; // State bool pressed_; }; //============================================================================= class UiListItem { public: UiListItem(std::string_view text = "", int value = 0, UiFlags uiFlags = UiFlags::None) : m_text(text) , m_value(value) , uiFlags(uiFlags) { } UiListItem(std::string_view text, std::vector &args, int value = 0, UiFlags uiFlags = UiFlags::None) : m_text(text) , args(args) , m_value(value) , uiFlags(uiFlags) { } // private: std::string_view m_text; std::vector args; int m_value; UiFlags uiFlags; }; class UiList : public UiItemBase { public: using vUiListItem = std::vector>; UiList(const vUiListItem &vItems, size_t viewportMaxSize, Sint16 x, Sint16 y, Uint16 item_width, Uint16 item_height, UiFlags flags = UiFlags::None, int spacing = 1) : UiList(PrivateConstructor {}, vItems, std::min(viewportMaxSize, vItems.size()), x, y, item_width, item_height, flags, spacing) { } [[nodiscard]] SDL_Rect itemRect(int i) const { SDL_Rect tmp; tmp.x = m_x; tmp.y = m_y + m_height * i; tmp.w = m_width; tmp.h = m_height; return tmp; } [[nodiscard]] size_t indexAt(Sint16 y) const { ASSERT(y >= m_rect.y); const size_t index = (y - m_rect.y) / m_height; ASSERT(index < m_vecItems.size()); return index; } [[nodiscard]] UiListItem *GetItem(std::size_t i) const { return m_vecItems[i]; } [[nodiscard]] int GetSpacing() const { return spacing_; } [[nodiscard]] bool IsPressed(size_t index) const { return pressed_item_index_ == index; } void Press(size_t index) { pressed_item_index_ = index; } void Release() { pressed_item_index_ = -1; } // private: size_t viewportSize; Sint16 m_x, m_y; Uint16 m_width, m_height; std::vector m_vecItems; private: struct PrivateConstructor final { }; UiList(PrivateConstructor tag, const vUiListItem &vItems, size_t viewportSize, Sint16 x, Sint16 y, Uint16 item_width, Uint16 item_height, UiFlags flags, int spacing) : UiItemBase(UiType::List, { x, y, item_width, static_cast(item_height * viewportSize) }, flags) , viewportSize(viewportSize) , m_x(x) , m_y(y) , m_width(item_width) , m_height(item_height) , spacing_(spacing) { for (const auto &item : vItems) m_vecItems.push_back(item.get()); pressed_item_index_ = -1; } int spacing_; // State size_t pressed_item_index_; }; } // namespace devilution