#pragma once // More traditional UI framework than ui/ui.h. // Still very simple to use. // Works very similarly to Android, there's a Measure pass and a Layout pass which you don't // really need to care about if you just use the standard containers and widgets. #include #include #include #include #include #include #include #include "Common/Render/TextureAtlas.h" #include "Common/Render/Text/Font.h" #include "Common/Math/lin/matrix4x4.h" #include "Common/Math/math_util.h" #include "Common/Math/geom2d.h" #include "Common/Input/KeyCodes.h" #include "Common/Common.h" #include "Common/UI/Screen.h" // for DialogResult #undef small struct KeyInput; struct TouchInput; struct AxisInput; class DrawBuffer; class Texture; class UIContext; namespace Draw { class DrawContext; class Texture; } // I don't generally like namespaces but I think we do need one for UI, so many potentially-clashing names. namespace UI { class View; enum DrawableType { DRAW_NOTHING, DRAW_SOLID_COLOR, DRAW_4GRID, DRAW_STRETCH_IMAGE, }; enum ImageSizeMode { IS_DEFAULT, IS_FIXED, IS_KEEP_ASPECT, }; enum Visibility { V_VISIBLE, V_INVISIBLE, // Keeps position, not drawn or interacted with V_GONE, // Does not participate in layout }; struct Drawable { Drawable() : type(DRAW_NOTHING), image(ImageID::invalid()), color(0xFFFFFFFF) {} explicit Drawable(uint32_t col) : type(DRAW_SOLID_COLOR), image(ImageID::invalid()), color(col) {} Drawable(DrawableType t, ImageID img, uint32_t col = 0xFFFFFFFF) : type(t), image(img), color(col) {} DrawableType type; ImageID image; uint32_t color; }; struct Style { Style() : fgColor(0xFFFFFFFF), background(0xFF303030), image(ImageID::invalid()) {} uint32_t fgColor; Drawable background; ImageID image; // where applicable. }; // To use with an UI atlas. struct Theme { FontStyle uiFontTiny; FontStyle uiFontSmall; FontStyle uiFont; FontStyle uiFontBig; FontStyle uiFontCode; ImageID checkOn; ImageID checkOff; ImageID sliderKnob; ImageID whiteImage; ImageID dropShadow4Grid; Style itemStyle; Style itemDownStyle; Style itemFocusedStyle; Style itemDisabledStyle; Style headerStyle; Style infoStyle; Style collapsibleHeaderStyle; Style popupStyle; Style popupTitleStyle; Style tooltipStyle; uint32_t backgroundColor; uint32_t scrollbarColor; uint32_t popupSliderColor; uint32_t popupSliderFocusedColor; }; // The four cardinal directions should be enough, plus Prev/Next in "element order". enum FocusDirection { FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_NEXT, FOCUS_PREV, FOCUS_FIRST, FOCUS_LAST, FOCUS_PREV_PAGE, FOCUS_NEXT_PAGE, }; typedef float Size; // can also be WRAP_CONTENT or FILL_PARENT. static constexpr Size WRAP_CONTENT = -1.0f; static constexpr Size FILL_PARENT = -2.0f; // TODO: This needs to move to the theme. static constexpr Size ITEM_HEIGHT = 64.f; // Gravity enum class Gravity { G_LEFT = 0, G_RIGHT = 1, G_HCENTER = 2, G_HORIZMASK = 3, G_TOP = 0, G_BOTTOM = 4, G_VCENTER = 8, G_TOPLEFT = G_TOP | G_LEFT, G_TOPRIGHT = G_TOP | G_RIGHT, G_BOTTOMLEFT = G_BOTTOM | G_LEFT, G_BOTTOMRIGHT = G_BOTTOM | G_RIGHT, G_CENTER = G_HCENTER | G_VCENTER, G_VERTMASK = 3 << 2, }; ENUM_CLASS_BITOPS(Gravity); enum Borders { BORDER_NONE = 0, BORDER_TOP = 0x0001, BORDER_LEFT = 0x0002, BORDER_BOTTOM = 0x0004, BORDER_RIGHT = 0x0008, BORDER_HORIZ = BORDER_LEFT | BORDER_RIGHT, BORDER_VERT = BORDER_TOP | BORDER_BOTTOM, BORDER_ALL = BORDER_TOP | BORDER_LEFT | BORDER_BOTTOM | BORDER_RIGHT, }; enum class BorderStyle { HEADER_FG, ITEM_DOWN_BG, }; inline FocusDirection Opposite(FocusDirection d) { switch (d) { case FOCUS_UP: return FOCUS_DOWN; case FOCUS_DOWN: return FOCUS_UP; case FOCUS_LEFT: return FOCUS_RIGHT; case FOCUS_RIGHT: return FOCUS_LEFT; case FOCUS_PREV: return FOCUS_NEXT; case FOCUS_NEXT: return FOCUS_PREV; case FOCUS_FIRST: return FOCUS_LAST; case FOCUS_LAST: return FOCUS_FIRST; case FOCUS_PREV_PAGE: return FOCUS_NEXT_PAGE; case FOCUS_NEXT_PAGE: return FOCUS_PREV_PAGE; } return d; } enum MeasureSpecType { UNSPECIFIED, EXACTLY, AT_MOST, }; enum FocusFlags { FF_LOSTFOCUS = 1, FF_GOTFOCUS = 2 }; enum PersistStatus { PERSIST_SAVE, PERSIST_RESTORE, }; typedef std::vector PersistBuffer; typedef std::map PersistMap; class ViewGroup; struct MeasureSpec { MeasureSpec(MeasureSpecType t, float s = 0.0f) : type(t), size(s) {} MeasureSpec() : type(UNSPECIFIED), size(0) {} MeasureSpec operator -(float amount) { // TODO: Check type return MeasureSpec(type, size - amount); } MeasureSpecType type; float size; }; // Should cover all bases. struct EventParams { View *v = nullptr; uint32_t a = 0, b = 0, x = 0, y = 0; float f = 0.0f; std::string s; DialogResult bubbleResult = DialogResult::DR_NONE; }; typedef std::function EventCallback; class Event { public: Event() = default; ~Event(); // Call this from input thread or whatever, it doesn't matter void Trigger(EventParams &e); // Call this from UI thread void Dispatch(EventParams &e); // This is now the suggested default way to bind a handler - just used a lambda, and if necessary call other functions. void Add(EventCallback func); // The old way of doing things. template T *Handle(T *thiz, void (T::* theCallback)(EventParams &)) { Add(std::bind(theCallback, thiz, std::placeholders::_1)); return thiz; } private: std::function func_; DISALLOW_COPY_AND_ASSIGN(Event); }; struct Margins { Margins() : top(0), bottom(0), left(0), right(0) {} explicit Margins(int8_t all) : top(all), bottom(all), left(all), right(all) {} Margins(int8_t horiz, int8_t vert) : top(vert), bottom(vert), left(horiz), right(horiz) {} Margins(int8_t l, int8_t t, int8_t r, int8_t b) : top(t), bottom(b), left(l), right(r) {} int horiz() const { return left + right; } int vert() const { return top + bottom; } void SetAll(float f) { int8_t i = (int)f; top = i; bottom = i; left = i; right = i; } int8_t top; int8_t bottom; int8_t left; int8_t right; }; struct Padding { Padding() : top(0), bottom(0), left(0), right(0) {} explicit Padding(float all) : top(all), bottom(all), left(all), right(all) {} Padding(float horiz, float vert) : top(vert), bottom(vert), left(horiz), right(horiz) {} Padding(float l, float t, float r, float b) : top(t), bottom(b), left(l), right(r) {} float horiz() const { return left + right; } float vert() const { return top + bottom; } float top; float bottom; float left; float right; }; enum LayoutParamsType { LP_PLAIN = 0, LP_LINEAR = 1, LP_ANCHOR = 2, LP_GRID = 3, }; // Need a virtual destructor so vtables are created, otherwise RTTI can't work class LayoutParams { public: LayoutParams(LayoutParamsType type = LP_PLAIN) : width(WRAP_CONTENT), height(WRAP_CONTENT), type_(type) {} LayoutParams(Size w, Size h, LayoutParamsType type = LP_PLAIN) : width(w), height(h), type_(type) {} virtual ~LayoutParams() {} Size width; Size height; // Fake RTTI bool Is(LayoutParamsType type) const { return type_ == type; } template T *As() { if (Is(T::StaticType())) { return static_cast(this); } return nullptr; } template const T *As() const { if (Is(T::StaticType())) { return static_cast(this); } return nullptr; } static LayoutParamsType StaticType() { return LP_PLAIN; } private: LayoutParamsType type_; }; View *GetFocusedView(); class Tween; class CallbackColorTween; class View { public: View(LayoutParams *layoutParams = 0) : layoutParams_(layoutParams) { if (!layoutParams) layoutParams_.reset(new LayoutParams()); } virtual ~View(); // Please note that Touch is called ENTIRELY asynchronously from drawing! // Can even be called on a different thread! This is to really minimize latency, and decouple // touch response from the frame rate. Same with Key and Axis. virtual bool Key(const KeyInput &input) { return false; } virtual bool Touch(const TouchInput &input) { return true; } virtual void Axis(const AxisInput &input) {} virtual void Update(); virtual void DeviceLost() {} virtual void DeviceRestored(Draw::DrawContext *draw) {} // If this view covers these coordinates, it should add itself and its children to the list. virtual void Query(float x, float y, std::vector &list); virtual std::string DescribeLog() const; // Accessible/searchable description. virtual std::string DescribeText() const { return ""; } virtual void FocusChanged(int focusFlags) {} virtual void PersistData(PersistStatus status, std::string anonId, PersistMap &storage); void Move(Bounds bounds) { bounds_ = bounds; } // Views don't do anything here in Layout, only containers implement this. virtual void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert); virtual void Layout() {} virtual void Draw(UIContext &dc) {} virtual float GetMeasuredWidth() const { return measuredWidth_; } virtual float GetMeasuredHeight() const { return measuredHeight_; } // Override this for easy standard behaviour. No need to override Measure. virtual void GetContentDimensions(const UIContext &dc, float &w, float &h) const; virtual void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const; // Called when the layout is done. void SetBounds(Bounds bounds) { bounds_ = bounds; } virtual const LayoutParams *GetLayoutParams() const { return layoutParams_.get(); } virtual void ReplaceLayoutParams(LayoutParams *newLayoutParams) { layoutParams_.reset(newLayoutParams); } const Bounds &GetBounds() const { return bounds_; } virtual bool SetFocus(); virtual bool CanBeFocused() const { return true; } virtual bool SubviewFocused(View *view) { return false; } virtual bool CanMoveFocus(FocusDirection dir) const { return true; } void SetPopupStyle(bool popupStyle) { popupStyle_ = popupStyle; } bool HasFocus() const { return GetFocusedView() == this; } void SetEnabled(bool enabled) { enabledFunc_ = nullptr; enabledPtr_ = nullptr; enabled_ = enabled; enabledMeansDisabled_ = false; } bool IsEnabled() const { if (enabledFunc_) return enabledFunc_() != enabledMeansDisabled_; if (enabledPtr_) return *enabledPtr_ != enabledMeansDisabled_; return enabled_ != enabledMeansDisabled_; } void SetEnabledFunc(std::function func) { enabledFunc_ = func; enabledPtr_ = nullptr; enabledMeansDisabled_ = false; } void SetEnabledPtr(bool *enabled) { enabledFunc_ = nullptr; enabledPtr_ = enabled; enabledMeansDisabled_ = false; } void SetDisabledPtr(bool *disabled) { enabledFunc_ = nullptr; enabledPtr_ = disabled; enabledMeansDisabled_ = true; } virtual void SetVisibility(Visibility visibility) { visibility_ = visibility; } Visibility GetVisibility() const { return visibility_; } const std::string &Tag() const { return tag_; } void SetTag(std::string_view str) { tag_ = str; } // Fake RTTI virtual bool IsViewGroup() const { return false; } virtual bool ContainsSubview(const View *view) const { return false; } virtual Point2D GetFocusPosition(FocusDirection dir) const; template T *AddTween(T *t) { tweens_.push_back(t); return t; } virtual void Recurse(std::function func) {} virtual void SetAutoResult(DialogResult result) { hasAutoResult_ = true; autoResult_ = result; } protected: // Inputs to layout std::unique_ptr layoutParams_; std::string tag_; Visibility visibility_ = V_VISIBLE; // Results of measure pass. Set these in Measure. float measuredWidth_ = 0.0f; float measuredHeight_ = 0.0f; // Outputs of layout. X/Y are absolute screen coordinates, hierarchy is "gone" here. Bounds bounds_{}; std::vector tweens_; // Whether to use popup colors for styling. bool popupStyle_ = false; bool hasAutoResult_ = false; DialogResult autoResult_ = DR_OK; private: std::function enabledFunc_; bool *enabledPtr_ = nullptr; bool enabled_ = true; bool enabledMeansDisabled_ = false; DISALLOW_COPY_AND_ASSIGN(View); }; // These don't do anything when touched. class InertView : public View { public: InertView(LayoutParams *layoutParams) : View(layoutParams) {} bool Key(const KeyInput &input) override { return false; } bool Touch(const TouchInput &input) override { return false; } bool CanBeFocused() const override { return false; } }; // All these light up their background when touched, or have focus. class Clickable : public View { public: Clickable(LayoutParams *layoutParams); bool Key(const KeyInput &input) override; bool Touch(const TouchInput &input) override; void FocusChanged(int focusFlags) override; Event OnClick; protected: // Internal method that fires on a click. Default behaviour is to trigger // the event. // Use it for checking/unchecking checkboxes, etc. virtual void ClickInternal(); void DrawBG(UIContext &dc, const Style &style); CallbackColorTween *bgColor_ = nullptr; float bgColorLast_ = 0.0f; int downCountDown_ = 0; bool dragging_ = false; bool down_ = false; }; // TODO: Very similar to Choice, should probably merge them. // Right now more flexible image support though. class Button : public Clickable { public: Button(std::string_view text, LayoutParams *layoutParams = 0) : Clickable(layoutParams), text_(text), imageID_(ImageID::invalid()) {} Button(std::string_view text, ImageID imageID, LayoutParams *layoutParams = 0) : Clickable(layoutParams), text_(text), imageID_(imageID) {} void Draw(UIContext &dc) override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; std::string_view GetText() const { return text_; } std::string DescribeText() const override; void SetPadding(int w, int h) { paddingW_ = w; paddingH_ = h; } void SetImageID(ImageID imageID) { imageID_ = imageID; } void SetImageIDFunc(std::function func) { imageFunc_ = func; } void SetIgnoreText(bool ignore) { ignoreText_ = ignore; } // Needed an extra small button... void SetScale(float f) { scale_ = f; } private: void ClickInternal() override; Style style_; std::string text_; ImageID imageID_; std::function imageFunc_{}; int paddingW_ = 16; int paddingH_ = 8; float scale_ = 1.0f; bool ignoreText_ = false; }; class RadioButton : public Clickable { public: RadioButton(int *value, int thisButtonValue, std::string_view text, LayoutParams *layoutParams = 0) : Clickable(layoutParams), value_(value), thisButtonValue_(thisButtonValue), text_(text) {} void Draw(UIContext &dc) override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; std::string DescribeText() const override; private: void ClickInternal() override; int *value_; int thisButtonValue_; std::string text_; const float paddingW_ = 8; const float paddingH_ = 4; const float radioRadius_ = 16.0f; const float radioInnerRadius_ = 8.0f; }; class Slider : public Clickable { public: Slider(int *value, int minValue, int maxValue, int step = 1, LayoutParams *layoutParams = nullptr) : Clickable(layoutParams), value_(value), showPercent_(false), minValue_(minValue), maxValue_(maxValue), paddingLeft_(5), paddingRight_(70), repeat_(-1) { step_ = step <= 0 ? 1 : step; } void Draw(UIContext &dc) override; std::string DescribeText() const override; bool Key(const KeyInput &input) override; bool Touch(const TouchInput &input) override; void Update() override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; void SetShowPercent(bool s) { showPercent_ = s; } void RestrictChoices(const int *fixedChoices, size_t numFixedChoices) { fixedChoices_ = fixedChoices; numFixedChoices_ = numFixedChoices; } // OK to call this from the outside after having modified *value_ void Clamp(); Event OnChange; private: bool ApplyKey(InputKeyCode keyCode); void SnapToFixed(); int *value_; bool showPercent_; int minValue_; int maxValue_; float paddingLeft_; float paddingRight_; int step_; int repeat_ = 0; const int *fixedChoices_ = nullptr; size_t numFixedChoices_ = 0; InputKeyCode repeatCode_ = NKCODE_UNKNOWN; }; class SliderFloat : public Clickable { public: SliderFloat(float *value, float minValue, float maxValue, LayoutParams *layoutParams = 0) : Clickable(layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), paddingLeft_(5), paddingRight_(70), repeat_(-1) {} void Draw(UIContext &dc) override; std::string DescribeText() const override; bool Key(const KeyInput &input) override; bool Touch(const TouchInput &input) override; void Update() override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; // OK to call this from the outside after having modified *value_ void Clamp(); Event OnChange; private: bool ApplyKey(InputKeyCode keyCode); float *value_; float minValue_; float maxValue_; float paddingLeft_; float paddingRight_; int repeat_; InputKeyCode repeatCode_ = NKCODE_UNKNOWN; }; // Basic button that modifies a bitfield based on the pressed status. Supports multitouch. // Suitable for controller simulation (ABXY etc). class TriggerButton : public View { public: TriggerButton(uint32_t *bitField, uint32_t bit, ImageID imageBackground, ImageID imageForeground, LayoutParams *layoutParams) : View(layoutParams), down_(0.0), bitField_(bitField), bit_(bit), imageBackground_(imageBackground), imageForeground_(imageForeground) {} bool Touch(const TouchInput &input) override; void Draw(UIContext &dc) override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; private: int down_; // bitfield of pressed fingers, translates into bitField uint32_t *bitField_; uint32_t bit_; ImageID imageBackground_; ImageID imageForeground_; }; // The following classes are mostly suitable as items in ListView which // really is just a LinearLayout in a ScrollView, possibly with some special optimizations. class Item : public InertView { public: Item(LayoutParams *layoutParams); void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; }; class ClickableItem : public Clickable { public: ClickableItem(LayoutParams *layoutParams); void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; // Draws the item background. void Draw(UIContext &dc) override; }; // Use to trigger something or open a submenu screen. class Choice : public ClickableItem { public: Choice(std::string_view text, LayoutParams *layoutParams = nullptr) : ClickableItem(layoutParams), text_(text) { } Choice(std::string_view text, ImageID image, LayoutParams *layoutParams = nullptr) : ClickableItem(layoutParams), text_(text), image_(image) {} Choice(std::string_view text, std::string_view smallText, LayoutParams *layoutParams = nullptr) : ClickableItem(layoutParams), text_(text), smallText_(smallText), image_(ImageID::invalid()) {} Choice(ImageID image, LayoutParams *layoutParams = nullptr) : ClickableItem(layoutParams), image_(image), rightIconImage_(ImageID::invalid()) {} Choice(ImageID image, float imgScale, float imgRot, bool imgFlipH = false, LayoutParams *layoutParams = nullptr) : ClickableItem(layoutParams), image_(image), rightIconImage_(ImageID::invalid()), imgScale_(imgScale), imgRot_(imgRot), imgFlipH_(imgFlipH) {} void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void Draw(UIContext &dc) override; std::string DescribeText() const override; void SetCentered(bool c) { centered_ = c; } void SetDrawTextFlags(u32 flags) { drawTextFlags_ = flags; } void SetIconLeft(ImageID iconImage) { image_ = iconImage; } void SetIconRight(ImageID iconImage, float scale = 1.0f, float rot = 0.0f, bool flipH = false, bool keepColor = true) { rightIconKeepColor_ = keepColor; rightIconScale_ = scale; rightIconRot_ = rot; rightIconFlipH_ = flipH; rightIconImage_ = iconImage; } // Only useful for things that extend Choice. void SetHideTitle(bool hide) { hideTitle_ = hide; } void SetShine(bool shine) { shine_ = true; } void SetImageScale(float scale) { imgScale_ = scale; } void SetText(std::string_view text) { text_ = text; } void SetIconOnly(bool iconOnly) { iconOnly_ = iconOnly; } void SetSelectedIndicator(bool selected) { selected_ = selected; } protected: void ClickInternal() override; // hackery virtual bool IsSticky() const { return false; } std::string text_; std::string smallText_; ImageID image_; // Centered if no text, on the left if text. ImageID rightIconImage_ = ImageID::invalid(); // Shows in the right. float rightIconScale_ = 0.0f; float rightIconRot_ = 0.0f; bool rightIconFlipH_ = false; bool rightIconKeepColor_ = false; Padding textPadding_; bool centered_ = false; float imgScale_ = 1.0f; float imgRot_ = 0.0f; bool imgFlipH_ = false; u32 drawTextFlags_ = 0; bool hideTitle_ = false; float shine_ = false; bool iconOnly_ = false; private: bool selected_ = false; }; // Different key handling. class StickyChoice : public Choice { public: StickyChoice(std::string_view text, std::string_view smallText = "", LayoutParams *layoutParams = nullptr) : Choice(text, smallText, layoutParams) {} StickyChoice(ImageID buttonImage, LayoutParams *layoutParams = nullptr) : Choice(buttonImage, layoutParams) {} StickyChoice(std::string_view text, ImageID image, LayoutParams *layoutParams = nullptr) : Choice(text, image, layoutParams) {} bool Key(const KeyInput &key) override; bool Touch(const TouchInput &touch) override; void FocusChanged(int focusFlags) override; void Press() { down_ = true; dragging_ = false; } void Release() { down_ = false; dragging_ = false; } bool IsDown() { return down_; } protected: // hackery bool IsSticky() const override { return true; } }; class InfoItem : public Item { public: InfoItem(std::string_view text, std::string_view rightText, LayoutParams *layoutParams = nullptr); void Draw(UIContext &dc) override; std::string DescribeText() const override; // These are focusable so that long lists of them can be keyboard scrolled. bool CanBeFocused() const override { return true; } void SetText(std::string_view text) { text_ = text; } const std::string &GetText() const { return text_; } void SetRightText(std::string_view text) { rightText_ = text; } protected: void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; private: std::string text_; std::string rightText_; }; class AbstractChoiceWithValueDisplay : public Choice { public: AbstractChoiceWithValueDisplay(std::string_view text, LayoutParams *layoutParams = nullptr) : Choice(text, layoutParams) { } AbstractChoiceWithValueDisplay(std::string_view text, ImageID image, LayoutParams *layoutParams = nullptr) : Choice(text, image, layoutParams) { } void Draw(UIContext &dc) override; void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void SetPasswordDisplay() { passwordMasking_ = true; } protected: virtual std::string ValueText(bool *shadow) const = 0; virtual ImageID ValueImage() const { return ImageID::invalid(); } float CalculateValueScale(const UIContext &dc, std::string_view valueText, float availWidth) const; bool passwordMasking_ = false; }; class ChoiceWithCallbackValueDisplay : public AbstractChoiceWithValueDisplay { public: ChoiceWithCallbackValueDisplay(std::string_view text, std::function valueFunc, LayoutParams *layoutParams = nullptr) : AbstractChoiceWithValueDisplay(text, layoutParams), valueFunc_(valueFunc) {} protected: std::string ValueText(bool *shadow) const override { *shadow = false; return valueFunc_(); } std::function valueFunc_; }; class ItemHeader : public Item { public: ItemHeader(std::string_view text, LayoutParams *layoutParams = 0); void Draw(UIContext &dc) override; std::string DescribeText() const override; void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void SetLarge(bool large) { large_ = large; } private: std::string text_; bool large_ = false; }; class PopupHeader : public Item { public: PopupHeader(std::string_view text, LayoutParams *layoutParams = 0) : Item(layoutParams), text_(text) { layoutParams_->width = FILL_PARENT; layoutParams_->height = 64; } void Draw(UIContext &dc) override; std::string DescribeText() const override; private: std::string text_; }; class CheckBox : public ClickableItem { public: CheckBox(bool *toggle, std::string_view text, std::string_view smallText = "", LayoutParams *layoutParams = nullptr) : ClickableItem(layoutParams), toggle_(toggle), text_(text), smallText_(smallText) { } // Image-only "checkbox", lights up instead of showing a checkmark. CheckBox(bool *toggle, ImageID imageID, LayoutParams *layoutParams = nullptr) : ClickableItem(layoutParams), toggle_(toggle), imageID_(imageID) { } void Draw(UIContext &dc) override; std::string DescribeText() const override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; //allow external agents to toggle the checkbox virtual void Toggle(); virtual bool Toggled() const; // we don't allow these for checkboxes. void SetAutoResult(DialogResult result) override {} protected: void ClickInternal() override; bool *toggle_; std::string text_; std::string smallText_; ImageID imageID_; }; class CollapsibleHeader : public CheckBox { public: CollapsibleHeader(bool *open, std::string_view text, LayoutParams *layoutParams = nullptr); void Draw(UIContext &dc) override; void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; Point2D GetFocusPosition(FocusDirection dir) const override; void SetHasSubitems(bool hasSubItems) { hasSubItems_ = hasSubItems; } void SetOpenPtr(bool *open) { toggle_ = open; } void SetUnderline(bool underline) { underline_ = underline; } private: bool hasSubItems_ = true; bool underline_ = true; }; class BitCheckBox : public CheckBox { public: // bit is a bitmask (should only have a single bit set), not a bit index. BitCheckBox(uint32_t *bitfield, uint32_t bit, std::string_view text, std::string_view smallText = "", LayoutParams *layoutParams = nullptr); BitCheckBox(int *bitfield, int bit, std::string_view text, std::string_view smallText = "", LayoutParams *layoutParams = nullptr) : BitCheckBox((uint32_t *)bitfield, (uint32_t)bit, text, smallText, layoutParams) {} void Toggle() override; bool Toggled() const override; private: uint32_t *bitfield_; uint32_t bit_; }; // These are for generic use. class Spacer : public InertView { public: Spacer(LayoutParams *layoutParams = 0) : InertView(layoutParams), size_(0.0f) {} Spacer(float size, LayoutParams *layoutParams = 0) : InertView(layoutParams), size_(size) {} void GetContentDimensions(const UIContext &dc, float &w, float &h) const override { w = size_; h = size_; } void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override { if (horiz.type == AT_MOST || horiz.type == EXACTLY) w = horiz.size; else w = size_; if (vert.type == AT_MOST || vert.type == EXACTLY) h = vert.size; else h = size_; } void Draw(UIContext &dc) override; std::string DescribeText() const override { return ""; } void SetSeparator() { drawAsSeparator_ = true; } private: float size_ = 0.0f; bool drawAsSeparator_ = false; }; class BorderView : public InertView { public: BorderView(Borders borderFlags, BorderStyle style, float size = 2.0f, LayoutParams *layoutParams = nullptr) : InertView(layoutParams), borderFlags_(borderFlags), style_(style), size_(size) { } void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void Draw(UIContext &dc) override; std::string DescribeText() const override { return ""; } private: Borders borderFlags_; BorderStyle style_; float size_; }; // Single-line text only. class SimpleTextView : public InertView { public: SimpleTextView(std::string_view text, LayoutParams *layoutParams = 0) : InertView(layoutParams), text_(text) { } void SetSmall(bool small) { small_ = small; } void SetBig(bool big) { big_ = big; } void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; void Draw(UIContext &dc) override; private: const FontStyle *ComputeStyle(const UIContext &dc) const; std::string text_; bool small_ = false; bool big_ = false; }; enum class TextSize { Tiny, Small, Normal, Big, }; class TextView : public InertView { public: TextView(std::string_view text, LayoutParams *layoutParams = 0) : InertView(layoutParams), text_(text), textAlign_(0), textColor_(0xFFFFFFFF) {} TextView(std::string_view text, int textAlign, bool small, LayoutParams *layoutParams = 0) : InertView(layoutParams), text_(text), textAlign_(textAlign), textColor_(0xFFFFFFFF), textSize_(small ? TextSize::Small : TextSize::Normal) {} void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void Draw(UIContext &dc) override; void SetText(std::string_view text) { text_ = text; } const std::string &GetText() const { return text_; } std::string DescribeText() const override { return GetText(); } void SetSmall(bool small) { textSize_ = TextSize::Small; } void SetBig(bool big) { textSize_ = TextSize::Big; } TextView *SetTextSize(TextSize size) { textSize_ = size; return this; } void SetTextColor(uint32_t color) { textColor_ = color; hasTextColor_ = true; } void SetShadow(bool shadow) { shadow_ = shadow; } void SetFocusable(bool focusable) { focusable_ = focusable; } void SetClip(bool clip) { clip_ = clip; } void SetBullet(bool bullet) { bullet_ = bullet; } void SetPadding(Margins padding) { pad_ = padding; } void SetAlign(int align) { textAlign_ = align; } TextView *SetWordWrap(); bool CanBeFocused() const override { return focusable_; } private: std::string text_; int textAlign_; uint32_t textColor_; bool hasTextColor_ = false; TextSize textSize_ = TextSize::Normal; bool shadow_ = false; bool focusable_ = false; bool clip_ = true; bool bullet_ = false; Margins pad_{}; }; // Quick hack for clickable version number class ClickableTextView : public TextView { public: ClickableTextView(std::string_view text, LayoutParams *layoutParams = 0) : TextView(text, layoutParams) {} bool Touch(const TouchInput &input) override; bool Key(const KeyInput &input) override; Event OnClick; private: bool down_ = false; bool dragging_ = false; }; class TextEdit : public View { public: TextEdit(std::string_view text, std::string_view title, std::string_view placeholderText, LayoutParams *layoutParams = nullptr); void SetText(std::string_view text) { text_ = text; scrollPos_ = 0; caret_ = (int)text_.size(); } void SetTextColor(uint32_t color) { textColor_ = color; hasTextColor_ = true; } const std::string &GetText() const { return text_; } void SetMaxLen(size_t maxLen) { maxLen_ = maxLen; } void SetTextAlign(int align) { align_ = align; } // Only really useful for setting FLAG_DYNAMIC_ASCII void SetPasswordMasking(bool masking) { passwordMasking_ = masking; } void SetPadding(Margins padding) { padding_ = padding; } void FocusChanged(int focusFlags) override; bool CanMoveFocus(FocusDirection dir) const { return dir != FocusDirection::FOCUS_LEFT && dir != FocusDirection::FOCUS_RIGHT; } void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; void Draw(UIContext &dc) override; std::string DescribeText() const override; bool Key(const KeyInput &key) override; bool Touch(const TouchInput &touch) override; bool Backspace(); void MoveLeft(); void MoveRight(); void InsertAtCaret(const char *text); Event OnTextChange; Event OnEnter; private: Margins padding_; std::string text_; std::string title_; std::string undo_; std::string placeholderText_; uint32_t textColor_; bool hasTextColor_ = false; int caret_; int scrollPos_ = 0; size_t maxLen_; bool ctrlDown_ = false; // TODO: Make some global mechanism for this. bool passwordMasking_ = false; int align_ = 0; int selectAtX_ = -1; // on next draw, will select the character closest to this X coordinate. Used for touch selection. // TODO: Selections }; class ImageView : public InertView { public: ImageView(ImageID atlasImage, const std::string &text, LayoutParams *layoutParams = nullptr); ImageView(std::function func, LayoutParams *layoutParams = nullptr); void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; void Draw(UIContext &dc) override; std::string DescribeText() const override { return text_; } void SetScale(float s) { scale_ = s; } // Only used for measuring. void SetFunc(std::function func) { func_ = func; } private: std::string text_; ImageID atlasImage_; float scale_ = 1.0f; std::function func_; }; class ProgressBar : public InertView { public: ProgressBar(LayoutParams *layoutParams = 0) : InertView(layoutParams), progress_(0.0) {} void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; void Draw(UIContext &dc) override; std::string DescribeText() const override; void SetProgress(float progress) { if (progress > 1.0f) { progress_ = 1.0f; } else if (progress < 0.0f) { progress_ = 0.0f; } else { progress_ = progress; } } float GetProgress() const { return progress_; } private: float progress_; }; // If no images, we'll use a rotating arc. class Spinner : public InertView { public: Spinner(const ImageID *images, int numImages, LayoutParams *layoutParams = 0) : InertView(layoutParams), images_(images), numImages_(numImages) { } void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; void Draw(UIContext &dc) override; std::string DescribeText() const override { return ""; } void SetColor(uint32_t color) { color_ = color; } private: const ImageID *images_; int numImages_; uint32_t color_ = 0xFFFFFFFF; }; void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measured); void ApplyBoundBySpec(float &bound, MeasureSpec spec); void ApplyBoundsBySpec(Bounds &bounds, MeasureSpec horiz, MeasureSpec vert); bool IsDPadKey(const KeyInput &key); bool IsAcceptKey(const KeyInput &key); bool IsEscapeKey(const KeyInput &key); bool IsInfoKey(const KeyInput &key); bool IsTabLeftKey(const KeyInput &key); bool IsTabRightKey(const KeyInput &key); // TODO: Doesn't really belong here. void DrawIconShine(UIContext &dc, const Bounds &bounds, float shine, bool animated); const FontStyle *GetTextStyle(const UIContext &dc, TextSize size); } // namespace