You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

452 lines
9.2 KiB

#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "DiabloUI/art.h"
#include "DiabloUI/ui_flags.hpp"
#include "engine/render/text_render.hpp"
#include "utils/enum_traits.h"
#include "utils/stubs.h"
namespace devilution {
enum class UiType {
Text,
ArtText,
ArtTextButton,
Image,
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 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)
, art_(art)
, animated_(animated)
, frame_(frame)
{
}
[[nodiscard]] bool IsCentered() const
{
return HasAnyOf(GetFlags(), UiFlags::AlignCenter);
}
[[nodiscard]] Art *GetArt() const
{
return art_;
}
[[nodiscard]] bool IsAnimated() const
{
return animated_;
}
[[nodiscard]] int GetFrame() const
{
return frame_;
}
void SetFrame(int frame)
{
frame_ = frame;
}
private:
Art *art_;
bool animated_;
int 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)
, 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
*/
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]] 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(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:
using Callback = void (*)();
UiArtTextButton(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]] string_view GetText() const
{
return text_;
}
void Activate() const
{
action_();
}
private:
string_view text_;
Callback action_;
};
//=============================================================================
class UiEdit : public UiItemBase {
public:
UiEdit(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:
string_view m_hint;
char *m_value;
std::size_t m_max_length;
bool m_allowEmpty;
};
//=============================================================================
// Plain text
class UiText : public UiItemBase {
public:
UiText(string_view text, SDL_Rect rect, UiFlags flags = UiFlags::ColorDialogWhite)
: UiItemBase(UiType::Text, rect, flags)
, text_(text)
{
}
[[nodiscard]] string_view GetText() const
{
return text_;
}
private:
string_view text_;
};
//=============================================================================
// A button (uses Diablo sprites)
class UiButton : public UiItemBase {
public:
using Callback = void (*)();
UiButton(Art *art, string_view text, Callback action, SDL_Rect rect, UiFlags flags = UiFlags::None)
: UiItemBase(UiType::Button, rect, flags)
, art_(art)
, text_(text)
, action_(action)
, pressed_(false)
{
}
[[nodiscard]] int GetFrame() const
{
// Frame 1 is a held button sprite, frame 0 is the default
return IsPressed() ? 1 : 0;
}
[[nodiscard]] Art *GetArt() const
{
return art_;
}
[[nodiscard]] 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:
Art *art_;
string_view text_;
Callback action_;
// State
bool pressed_;
};
//=============================================================================
class UiListItem {
public:
UiListItem(string_view text = "", int value = 0, UiFlags uiFlags = UiFlags::None)
: m_text(text)
, m_value(value)
, uiFlags(uiFlags)
{
}
UiListItem(string_view text, std::vector<DrawStringFormatArg> &args, int value = 0, UiFlags uiFlags = UiFlags::None)
: m_text(text)
, args(args)
, m_value(value)
, uiFlags(uiFlags)
{
}
// private:
string_view m_text;
std::vector<DrawStringFormatArg> args;
int m_value;
UiFlags uiFlags;
};
class UiList : public UiItemBase {
public:
using vUiListItem = std::vector<std::unique_ptr<UiListItem>>;
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<size_t>(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<UiListItem *> 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<Uint16>(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