| 1 | // Aseprite UI Library |
| 2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
| 3 | // Copyright (C) 2001-2018 David Capello |
| 4 | // |
| 5 | // This file is released under the terms of the MIT license. |
| 6 | // Read LICENSE.txt for more information. |
| 7 | |
| 8 | #ifndef UI_WIDGET_H_INCLUDED |
| 9 | #define UI_WIDGET_H_INCLUDED |
| 10 | #pragma once |
| 11 | |
| 12 | #include "gfx/border.h" |
| 13 | #include "gfx/color.h" |
| 14 | #include "gfx/point.h" |
| 15 | #include "gfx/rect.h" |
| 16 | #include "gfx/region.h" |
| 17 | #include "gfx/size.h" |
| 18 | #include "obs/signal.h" |
| 19 | #include "os/font.h" |
| 20 | #include "ui/base.h" |
| 21 | #include "ui/component.h" |
| 22 | #include "ui/graphics.h" |
| 23 | #include "ui/widget_type.h" |
| 24 | #include "ui/widgets_list.h" |
| 25 | |
| 26 | #include <string> |
| 27 | |
| 28 | #define ASSERT_VALID_WIDGET(widget) ASSERT((widget) != nullptr) |
| 29 | |
| 30 | namespace ui { |
| 31 | |
| 32 | class Display; |
| 33 | class InitThemeEvent; |
| 34 | class KeyMessage; |
| 35 | class LoadLayoutEvent; |
| 36 | class Manager; |
| 37 | class Message; |
| 38 | class MouseMessage; |
| 39 | class PaintEvent; |
| 40 | class ResizeEvent; |
| 41 | class SaveLayoutEvent; |
| 42 | class SizeHintEvent; |
| 43 | class Style; |
| 44 | class Theme; |
| 45 | class Window; |
| 46 | |
| 47 | class Widget : public Component { |
| 48 | public: |
| 49 | |
| 50 | // =============================================================== |
| 51 | // CTOR & DTOR |
| 52 | // =============================================================== |
| 53 | |
| 54 | Widget(WidgetType type = kGenericWidget); |
| 55 | virtual ~Widget(); |
| 56 | |
| 57 | // Safe way to delete a widget when it is not in the manager message |
| 58 | // queue anymore. |
| 59 | void deferDelete(); |
| 60 | |
| 61 | // Main properties. |
| 62 | |
| 63 | WidgetType type() const { return m_type; } |
| 64 | void setType(WidgetType type) { m_type = type; } // TODO remove this function |
| 65 | |
| 66 | const std::string& id() const { return m_id; } |
| 67 | void setId(const char* id) { m_id = id; } |
| 68 | |
| 69 | int flags() const { return m_flags; } |
| 70 | bool hasFlags(int flags) const { return ((m_flags & flags) == flags); } |
| 71 | void enableFlags(int flags) { m_flags |= flags; } |
| 72 | void disableFlags(int flags) { m_flags &= ~flags; } |
| 73 | |
| 74 | int align() const { return (m_flags & ALIGN_MASK); } |
| 75 | void setAlign(int align) { |
| 76 | m_flags = ((m_flags & PROPERTIES_MASK) | |
| 77 | (align & ALIGN_MASK)); |
| 78 | } |
| 79 | |
| 80 | // Text property. |
| 81 | |
| 82 | bool hasText() const { return hasFlags(HAS_TEXT); } |
| 83 | |
| 84 | const std::string& text() const { return m_text; } |
| 85 | int textInt() const; |
| 86 | double textDouble() const; |
| 87 | void setText(const std::string& text); |
| 88 | void setTextf(const char* text, ...); |
| 89 | void setTextQuiet(const std::string& text); |
| 90 | |
| 91 | int textWidth() const; |
| 92 | int textHeight() const; |
| 93 | |
| 94 | gfx::Size textSize() const { |
| 95 | return gfx::Size(textWidth(), textHeight()); |
| 96 | } |
| 97 | |
| 98 | // =============================================================== |
| 99 | // COMMON PROPERTIES |
| 100 | // =============================================================== |
| 101 | |
| 102 | // True if this widget and all its ancestors are visible. |
| 103 | bool isVisible() const; |
| 104 | void setVisible(bool state); |
| 105 | |
| 106 | // True if this widget can receive user input (is not disabled). |
| 107 | bool isEnabled() const; |
| 108 | void setEnabled(bool state); |
| 109 | |
| 110 | // True if this widget is selected (pushed in case of a button, or |
| 111 | // checked in the case of a check-box). |
| 112 | bool isSelected() const; |
| 113 | void setSelected(bool state); |
| 114 | |
| 115 | // True if this widget wants more space when it's inside a Box |
| 116 | // parent. |
| 117 | bool isExpansive() const; |
| 118 | void setExpansive(bool state); |
| 119 | |
| 120 | // True if this is a decorative widget created by the current |
| 121 | // theme. Decorative widgets are arranged by the theme instead that |
| 122 | // the parent's widget. |
| 123 | bool isDecorative() const; |
| 124 | void setDecorative(bool state); |
| 125 | |
| 126 | // True if this widget can receive the keyboard focus. |
| 127 | bool isFocusStop() const; |
| 128 | void setFocusStop(bool state); |
| 129 | |
| 130 | // True if this widget wants the focus by default when it's shown by |
| 131 | // first time (e.g. when its parent window is opened). |
| 132 | void setFocusMagnet(bool state); |
| 133 | bool isFocusMagnet() const; |
| 134 | |
| 135 | // =============================================================== |
| 136 | // LOOK & FEEL |
| 137 | // =============================================================== |
| 138 | |
| 139 | os::Font* font() const; |
| 140 | |
| 141 | // Gets the background color of the widget. |
| 142 | gfx::Color bgColor() const { |
| 143 | if (gfx::geta(m_bgColor) == 0 && m_parent) |
| 144 | return m_parent->bgColor(); |
| 145 | else |
| 146 | return m_bgColor; |
| 147 | } |
| 148 | |
| 149 | // Sets the background color of the widget |
| 150 | void setBgColor(gfx::Color color); |
| 151 | |
| 152 | Theme* theme() const { return m_theme; } |
| 153 | Style* style() const { return m_style; } |
| 154 | void setTheme(Theme* theme); |
| 155 | void setStyle(Style* style); |
| 156 | void initTheme(); |
| 157 | |
| 158 | // =============================================================== |
| 159 | // PARENTS & CHILDREN |
| 160 | // =============================================================== |
| 161 | |
| 162 | Window* window() const; |
| 163 | Widget* parent() const { return m_parent; } |
| 164 | int parentIndex() const { return m_parentIndex; } |
| 165 | Manager* manager() const; |
| 166 | Display* display() const; |
| 167 | |
| 168 | // Returns a list of children. |
| 169 | const WidgetsList& children() const { return m_children; } |
| 170 | bool hasChildren() const { return !m_children.empty(); } |
| 171 | |
| 172 | Widget* at(int index) { return m_children[index]; } |
| 173 | int getChildIndex(Widget* child); |
| 174 | |
| 175 | // Returns the first/last child or nullptr if it doesn't exist. |
| 176 | Widget* firstChild() { |
| 177 | return (hasChildren() ? m_children.front(): nullptr); |
| 178 | } |
| 179 | Widget* lastChild() { |
| 180 | return (hasChildren() ? m_children.back(): nullptr); |
| 181 | } |
| 182 | |
| 183 | // Returns the next or previous siblings. |
| 184 | Widget* nextSibling(); |
| 185 | Widget* previousSibling(); |
| 186 | |
| 187 | Widget* pick(const gfx::Point& pt, |
| 188 | const bool checkParentsVisibility = true) const; |
| 189 | virtual Widget* pickFromScreenPos(const gfx::Point& screenPos) const; |
| 190 | |
| 191 | bool hasChild(Widget* child); |
| 192 | bool hasAncestor(Widget* ancestor); |
| 193 | Widget* findChild(const char* id) const; |
| 194 | |
| 195 | // Returns a widget in the same window that is located "sibling". |
| 196 | Widget* findSibling(const char* id) const; |
| 197 | |
| 198 | // Finds a child with the specified ID and dynamic-casts it to type |
| 199 | // T. |
| 200 | template<class T> |
| 201 | T* findChildT(const char* id) const { |
| 202 | return dynamic_cast<T*>(findChild(id)); |
| 203 | } |
| 204 | |
| 205 | template<class T> |
| 206 | T* findFirstChildByType() const { |
| 207 | for (auto child : m_children) { |
| 208 | if (T* specificChild = dynamic_cast<T*>(child)) |
| 209 | return specificChild; |
| 210 | } |
| 211 | return nullptr; |
| 212 | } |
| 213 | |
| 214 | void addChild(Widget* child); |
| 215 | void removeChild(Widget* child); |
| 216 | void removeAllChildren(); |
| 217 | void replaceChild(Widget* oldChild, Widget* newChild); |
| 218 | void insertChild(int index, Widget* child); |
| 219 | void moveChildTo(Widget* thisChild, Widget* toThisPosition); |
| 220 | |
| 221 | // =============================================================== |
| 222 | // LAYOUT & CONSTRAINT |
| 223 | // =============================================================== |
| 224 | |
| 225 | void layout(); |
| 226 | void loadLayout(); |
| 227 | void saveLayout(); |
| 228 | |
| 229 | void setDecorativeWidgetBounds(); |
| 230 | |
| 231 | // =============================================================== |
| 232 | // POSITION & GEOMETRY |
| 233 | // =============================================================== |
| 234 | |
| 235 | gfx::Rect bounds() const { return m_bounds; } |
| 236 | gfx::Point origin() const { return m_bounds.origin(); } |
| 237 | gfx::Size size() const { return m_bounds.size(); } |
| 238 | |
| 239 | gfx::Rect clientBounds() const { |
| 240 | return gfx::Rect(0, 0, m_bounds.w, m_bounds.h); |
| 241 | } |
| 242 | |
| 243 | gfx::Rect childrenBounds() const; |
| 244 | gfx::Rect clientChildrenBounds() const; |
| 245 | |
| 246 | // Bounds of this widget or window on native screen/desktop coordinates. |
| 247 | gfx::Rect boundsOnScreen() const; |
| 248 | |
| 249 | // Sets the bounds of the widget generating a onResize() event. |
| 250 | void setBounds(const gfx::Rect& rc); |
| 251 | |
| 252 | // Sets the bounds of the widget without generating any kind of |
| 253 | // event. This member function must be used if you override |
| 254 | // onResize() and want to change the size of the widget without |
| 255 | // generating recursive onResize() events. |
| 256 | void setBoundsQuietly(const gfx::Rect& rc); |
| 257 | void offsetWidgets(int dx, int dy); |
| 258 | |
| 259 | const gfx::Size& minSize() const { return m_minSize; } |
| 260 | const gfx::Size& maxSize() const { return m_maxSize; } |
| 261 | void setMinSize(const gfx::Size& sz); |
| 262 | void setMaxSize(const gfx::Size& sz); |
| 263 | void resetMinSize(); |
| 264 | void resetMaxSize(); |
| 265 | |
| 266 | const gfx::Border& border() const { return m_border; } |
| 267 | void setBorder(const gfx::Border& border); |
| 268 | |
| 269 | int childSpacing() const { return m_childSpacing; } |
| 270 | void setChildSpacing(int childSpacing); |
| 271 | |
| 272 | void noBorderNoChildSpacing(); |
| 273 | |
| 274 | // Flags for getDrawableRegion() |
| 275 | enum DrawableRegionFlags { |
| 276 | kCutTopWindows = 1, // Cut areas where are windows on top. |
| 277 | kUseChildArea = 2, // Use areas where are children. |
| 278 | kCutTopWindowsAndUseChildArea = kCutTopWindows | kUseChildArea, |
| 279 | }; |
| 280 | |
| 281 | void getRegion(gfx::Region& region); |
| 282 | void getDrawableRegion(gfx::Region& region, DrawableRegionFlags flags); |
| 283 | |
| 284 | gfx::Point toClient(const gfx::Point& pt) const { |
| 285 | return pt - m_bounds.origin(); |
| 286 | } |
| 287 | gfx::Rect toClient(const gfx::Rect& rc) const { |
| 288 | return gfx::Rect(rc).offset(-m_bounds.x, -m_bounds.y); |
| 289 | } |
| 290 | |
| 291 | void getTextIconInfo( |
| 292 | gfx::Rect* box, |
| 293 | gfx::Rect* text = nullptr, |
| 294 | gfx::Rect* icon = nullptr, |
| 295 | int icon_align = 0, int icon_w = 0, int icon_h = 0); |
| 296 | |
| 297 | // =============================================================== |
| 298 | // REFRESH ISSUES |
| 299 | // =============================================================== |
| 300 | |
| 301 | bool isDoubleBuffered() const; |
| 302 | void setDoubleBuffered(bool doubleBuffered); |
| 303 | |
| 304 | bool isTransparent() const; |
| 305 | void setTransparent(bool transparent); |
| 306 | |
| 307 | void invalidate(); |
| 308 | void invalidateRect(const gfx::Rect& rect); |
| 309 | void invalidateRegion(const gfx::Region& region); |
| 310 | |
| 311 | // Returns the region to generate PaintMessages. It's cleared |
| 312 | // after flushRedraw() is called. |
| 313 | const gfx::Region& getUpdateRegion() const { |
| 314 | return m_updateRegion; |
| 315 | } |
| 316 | |
| 317 | // Generates paint messages for the current update region. |
| 318 | void flushRedraw(); |
| 319 | |
| 320 | GraphicsPtr getGraphics(const gfx::Rect& clip); |
| 321 | |
| 322 | // =============================================================== |
| 323 | // GUI MANAGER |
| 324 | // =============================================================== |
| 325 | |
| 326 | bool sendMessage(Message* msg); |
| 327 | void closeWindow(); |
| 328 | |
| 329 | void broadcastMouseMessage(const gfx::Point& screenPos, |
| 330 | WidgetsList& targets); |
| 331 | |
| 332 | // =============================================================== |
| 333 | // SIZE & POSITION |
| 334 | // =============================================================== |
| 335 | |
| 336 | gfx::Size sizeHint(); |
| 337 | gfx::Size sizeHint(const gfx::Size& fitIn); |
| 338 | void setSizeHint(const gfx::Size& fixedSize); |
| 339 | void setSizeHint(int fixedWidth, int fixedHeight); |
| 340 | void resetSizeHint(); |
| 341 | |
| 342 | // =============================================================== |
| 343 | // MOUSE, FOCUS & KEYBOARD |
| 344 | // =============================================================== |
| 345 | |
| 346 | void requestFocus(); |
| 347 | void releaseFocus(); |
| 348 | void captureMouse(); |
| 349 | void releaseMouse(); |
| 350 | |
| 351 | bool hasFocus() const { return hasFlags(HAS_FOCUS); } |
| 352 | bool hasMouse() const { return hasFlags(HAS_MOUSE); } |
| 353 | bool hasCapture() const { return hasFlags(HAS_CAPTURE); } |
| 354 | |
| 355 | // Checking if the mouse is currently above the widget. |
| 356 | bool hasMouseOver() const; |
| 357 | |
| 358 | // Returns the mouse position relative to the top-left corner of |
| 359 | // the ui::Display's client area/content rect. |
| 360 | gfx::Point mousePosInDisplay() const; |
| 361 | |
| 362 | // Returns the mouse position relative to the top-left cornder of |
| 363 | // the widget bounds. |
| 364 | gfx::Point mousePosInClientBounds() const { |
| 365 | return toClient(mousePosInDisplay()); |
| 366 | } |
| 367 | |
| 368 | // Offer the capture to widgets of the given type. Returns true if |
| 369 | // the capture was passed to other widget. |
| 370 | bool offerCapture(ui::MouseMessage* mouseMsg, int widget_type); |
| 371 | |
| 372 | // Returns lower-case letter that represet the mnemonic of the widget |
| 373 | // (the underscored character, i.e. the letter after & symbol). |
| 374 | int mnemonic() const { return m_mnemonic; } |
| 375 | void setMnemonic(int mnemonic); |
| 376 | |
| 377 | // Assigns mnemonic from the character preceded by the given |
| 378 | // escapeChar ('&' by default). |
| 379 | void processMnemonicFromText(int escapeChar = '&'); |
| 380 | |
| 381 | // Returns true if the mnemonic character is pressed. |
| 382 | bool isMnemonicPressed(const ui::KeyMessage* keyMsg) const; |
| 383 | |
| 384 | // Signals |
| 385 | obs::signal<void()> InitTheme; |
| 386 | |
| 387 | protected: |
| 388 | // =============================================================== |
| 389 | // MESSAGE PROCESSING |
| 390 | // =============================================================== |
| 391 | |
| 392 | virtual bool onProcessMessage(Message* msg); |
| 393 | |
| 394 | // =============================================================== |
| 395 | // EVENTS |
| 396 | // =============================================================== |
| 397 | |
| 398 | virtual void onInvalidateRegion(const gfx::Region& region); |
| 399 | virtual void onSizeHint(SizeHintEvent& ev); |
| 400 | virtual void onLoadLayout(LoadLayoutEvent& ev); |
| 401 | virtual void onSaveLayout(SaveLayoutEvent& ev); |
| 402 | virtual void onResize(ResizeEvent& ev); |
| 403 | virtual void onPaint(PaintEvent& ev); |
| 404 | virtual void onBroadcastMouseMessage(const gfx::Point& screenPos, |
| 405 | WidgetsList& targets); |
| 406 | virtual void onInitTheme(InitThemeEvent& ev); |
| 407 | virtual void onSetDecorativeWidgetBounds(); |
| 408 | virtual void onVisible(bool visible); |
| 409 | virtual void onEnable(bool enabled); |
| 410 | virtual void onSelect(bool selected); |
| 411 | virtual void onSetText(); |
| 412 | virtual void onSetBgColor(); |
| 413 | virtual int onGetTextInt() const; |
| 414 | virtual double onGetTextDouble() const; |
| 415 | |
| 416 | private: |
| 417 | void removeChild(const WidgetsList::iterator& it); |
| 418 | void paint(Graphics* graphics, |
| 419 | const gfx::Region& drawRegion, |
| 420 | const bool isBg); |
| 421 | bool paintEvent(Graphics* graphics, |
| 422 | const bool isBg); |
| 423 | void setDirtyFlag(); |
| 424 | |
| 425 | WidgetType m_type; // Widget's type |
| 426 | std::string m_id; // Widget's id |
| 427 | int m_flags; // Special boolean properties (see flags in ui/base.h) |
| 428 | Theme* m_theme; // Widget's theme |
| 429 | Style* m_style; |
| 430 | std::string m_text; // Widget text |
| 431 | mutable os::FontRef m_font; // Cached font returned by the theme |
| 432 | gfx::Color m_bgColor; // Background color |
| 433 | gfx::Rect m_bounds; |
| 434 | gfx::Region m_updateRegion; // Region to be redrawed. |
| 435 | WidgetsList m_children; // Sub-widgets |
| 436 | Widget* m_parent; // Who is the parent? |
| 437 | int m_parentIndex; // Location/index of this widget in the parent's Widget::m_children vector |
| 438 | gfx::Size* m_sizeHint; |
| 439 | int m_mnemonic; // Keyboard shortcut to access this widget like Alt+mnemonic |
| 440 | |
| 441 | // Widget size limits |
| 442 | gfx::Size m_minSize, m_maxSize; |
| 443 | |
| 444 | gfx::Border m_border; // Border separation with the parent |
| 445 | int m_childSpacing; // Separation between children |
| 446 | }; |
| 447 | |
| 448 | WidgetType register_widget_type(); |
| 449 | |
| 450 | } // namespace ui |
| 451 | |
| 452 | #endif |
| 453 | |