#pragma once #include #include #include #include #include "DiabloUI/art.h" #include "utils/enum_traits.h" #include "utils/stubs.h" namespace devilution { enum class UiType { Text, ArtText, ArtTextButton, Image, Button, List, Scrollbar, Edit, }; enum class UiFlags { // clang-format off None = 0, FontSize12 = 1 << 0, FontSize24 = 1 << 1, FontSize30 = 1 << 2, FontSize42 = 1 << 3, FontSize46 = 1 << 4, FontSizeDialog = 1 << 5, ColorUiGold = 1 << 6, ColorUiSilver = 1 << 7, ColorUiGoldDark = 1 << 8, ColorUiSilverDark = 1 << 9, ColorDialogWhite = 1 << 10, ColorDialogYellow = 1 << 11, ColorGold = 1 << 12, ColorBlack = 1 << 13, ColorWhite = 1 << 14, ColorWhitegold = 1 << 15, ColorRed = 1 << 16, ColorBlue = 1 << 17, ColorButtonface = 1 << 18, ColorButtonpushed = 1 << 19, AlignCenter = 1 << 20, AlignRight = 1 << 21, VerticalCenter = 1 << 22, KerningFitSpacing = 1 << 23, ElementDisabled = 1 << 24, ElementHidden = 1 << 25, PentaCursor = 1 << 26, TextCursor = 1 << 27, // clang-format on }; use_enum_as_flags(UiFlags); enum class UiPanels { Main, Quest, Character, Spell, Inventory, }; class UiItemBase { public: UiItemBase(UiType type, SDL_Rect rect, UiFlags flags) : m_type(type) , m_rect(rect) , m_iFlags(flags) { } virtual ~UiItemBase() {}; bool has_flag(UiFlags flag) const { return (m_iFlags & flag) == flag; } bool has_any_flag(UiFlags flags) const { return HasAnyOf(m_iFlags, flags); } void add_flag(UiFlags flag) { m_iFlags |= flag; } void remove_flag(UiFlags flag) { m_iFlags &= ~flag; } //protected: UiType m_type; SDL_Rect m_rect; UiFlags m_iFlags; }; //============================================================================= class UiImage : public UiItemBase { public: UiImage(Art *art, SDL_Rect rect, UiFlags flags = UiFlags::None, bool animated = false, int frame = 0) : UiItemBase(UiType::Image, rect, flags) , m_art(art) , m_animated(animated) , m_frame(frame) { } ~UiImage() {}; //private: Art *m_art; bool m_animated; int m_frame; }; //============================================================================= 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 */ UiArtText(const char *text, SDL_Rect rect, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1) : UiItemBase(UiType::ArtText, rect, flags) , m_text(text) , m_spacing(spacing) , m_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 */ UiArtText(const char **ptext, SDL_Rect rect, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1) : UiItemBase(UiType::ArtText, rect, flags) , m_ptext(ptext) , m_spacing(spacing) , m_lineHeight(lineHeight) { } const char *text() const { if (m_text != nullptr) return m_text; return *m_ptext; } int spacing() const { return m_spacing; } int lineHeight() const { return m_lineHeight; } ~UiArtText() {}; private: const char *m_text = nullptr; const char **m_ptext = nullptr; int m_spacing = 1; int m_lineHeight = -1; }; //============================================================================= class UiScrollbar : public UiItemBase { public: UiScrollbar(Art *bg, Art *thumb, Art *arrow, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Scrollbar, rect, flags) , m_bg(bg) , m_thumb(thumb) , m_arrow(arrow) { } //private: Art *m_bg; Art *m_thumb; Art *m_arrow; }; //============================================================================= class UiArtTextButton : public UiItemBase { public: UiArtTextButton(const char *text, void (*action)(), SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::ArtTextButton, rect, flags) , m_text(text) , m_action(action) { } //private: const char *m_text; void (*m_action)(); }; //============================================================================= class UiEdit : public UiItemBase { public: UiEdit(const char *hint, char *value, std::size_t max_length, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Edit, rect, flags) , m_hint(hint) , m_value(value) , m_max_length(max_length) { } //private: const char *m_hint; char *m_value; std::size_t m_max_length; }; //============================================================================= // Plain text class UiText : public UiItemBase { public: UiText(const char *text, SDL_Rect rect, UiFlags flags = UiFlags::ColorDialogWhite) : UiItemBase(UiType::Text, rect, flags) , m_text(text) { } //private: const char *m_text; }; //============================================================================= // A button (uses Diablo sprites) class UiButton : public UiItemBase { public: UiButton(Art *art, const char *text, void (*action)(), SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Button, rect, flags) , m_art(art) , m_text(text) , m_action(action) , m_pressed(false) { } enum FrameKey : uint8_t { DEFAULT, PRESSED, }; //private: Art *m_art; const char *m_text; void (*m_action)(); // State bool m_pressed; }; //============================================================================= class UiListItem { public: UiListItem(const char *text = "", int value = 0) : m_text(text) , m_value(value) { } ~UiListItem() {}; //private: const char *m_text; int m_value; }; typedef std::vector> vUiListItem; class UiList : public UiItemBase { public: UiList(const vUiListItem &vItems, Sint16 x, Sint16 y, Uint16 item_width, Uint16 item_height, UiFlags flags = UiFlags::None, int spacing = 1) : UiItemBase(UiType::List, { x, y, item_width, static_cast(item_height * vItems.size()) }, flags) , m_x(x) , m_y(y) , m_width(item_width) , m_height(item_height) , m_spacing(spacing) { for (auto &item : vItems) m_vecItems.push_back(item.get()); } ~UiList() {}; 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; } UiListItem *GetItem(int i) const { return m_vecItems[i]; } int spacing() const { return m_spacing; } //private: Sint16 m_x, m_y; Uint16 m_width, m_height; std::vector m_vecItems; int m_spacing; }; } // namespace devilution