1 /** 2 * @file 3 * @brief Hierarchical layout system. 4 **/ 5 6 #pragma once 7 8 #include <functional> 9 #include <vector> 10 11 #include "format.h" 12 #include "KeymapContext.h" 13 #include "state.h" 14 #include "rltiles/tiledef-gui.h" 15 #include "tilefont.h" 16 #include "unwind.h" 17 #include "cio.h" 18 #ifdef USE_TILE_LOCAL 19 # include "tilebuf.h" 20 # include "tiledgnbuf.h" 21 # include "tiledoll.h" 22 # include "tilesdl.h" 23 #endif 24 #ifdef USE_TILE_WEB 25 # include "tileweb.h" 26 # include "json.h" 27 #endif 28 29 using std::vector; 30 31 struct wm_keyboard_event; 32 33 #define UI_SCROLLER_SHADE_SIZE 12 34 35 namespace ui { 36 37 struct SizeReq 38 { 39 int min, nat; 40 }; 41 42 class Margin 43 { 44 public: Margin()45 constexpr Margin() : top(0), right(0), bottom(0), left(0) {} 46 Margin(int v)47 explicit constexpr Margin(int v) : top(v), right(v), bottom(v), left(v) {} Margin(int v,int h)48 constexpr Margin(int v, int h) : top(v), right(h), bottom(v), left(h) {} Margin(int t,int lr,int b)49 constexpr Margin(int t, int lr, int b) 50 : top(t), right(lr), bottom(b), left(lr) {} Margin(int t,int r,int b,int l)51 constexpr Margin(int t, int r, int b, int l) 52 : top(t), right(r), bottom(b), left(l) {} 53 54 int top, right, bottom, left; 55 }; 56 57 class Region 58 { 59 public: Region()60 constexpr Region() : x(0), y(0), width(0), height(0) {} Region(int _x,int _y,int _width,int _height)61 constexpr Region(int _x, int _y, int _width, int _height) 62 : x(_x), y(_y), width(_width), height(_height) {} 63 64 constexpr bool operator == (const Region& other) const 65 { 66 return x == other.x && y == other.y 67 && width == other.width && height == other.height; 68 } 69 70 constexpr bool operator != (const Region& other) const 71 { 72 return !(*this == other); 73 } 74 empty()75 constexpr bool empty() const 76 { 77 return width == 0 || height == 0; 78 } 79 ex()80 constexpr int ex() const 81 { 82 return x + width; 83 } 84 ey()85 constexpr int ey() const 86 { 87 return y + height; 88 } 89 contains_point(int _x,int _y)90 constexpr bool contains_point(int _x, int _y) const 91 { 92 return _x >= x && _x < ex() && _y >= y && _y < ey(); 93 } 94 aabb_intersect(const Region & b)95 Region aabb_intersect(const Region &b) const 96 { 97 const Region& a = *this; 98 Region i = { max(a.x, b.x), max(a.y, b.y), min(a.ex(), b.ex()), min(a.ey(), b.ey()) }; 99 i.width -= i.x; i.height -= i.y; 100 return i; 101 } 102 aabb_union(const Region & b)103 Region aabb_union(const Region &b) const 104 { 105 const Region& a = *this; 106 Region i = { min(a.x, b.x), min(a.y, b.y), max(a.ex(), b.ex()), max(a.ey(), b.ey()) }; 107 i.width -= i.x; i.height -= i.y; 108 return i; 109 } 110 111 int x, y, width, height; 112 }; 113 114 inline ostream& operator << (ostream& ostr, Region const& value) 115 { 116 ostr << "Region(x=" << value.x << ", y=" << value.y << ", "; 117 ostr << "w=" << value.width << ", h=" << value.height << ")"; 118 return ostr; 119 } 120 121 class Size 122 { 123 public: Size()124 constexpr Size() : width(0), height(0) {} Size(int v)125 explicit constexpr Size(int v) : width(v), height(v) {} Size(int w,int h)126 constexpr Size(int w, int h) : width(w), height(h) {} 127 is_valid()128 constexpr bool is_valid() const { return width >= 0 && height >= 0; } 129 130 constexpr bool operator <= (const Size& other) const 131 { 132 return width <= other.width && height <= other.height; 133 } 134 135 constexpr bool operator == (const Size& other) const 136 { 137 return width == other.width && height == other.height; 138 } 139 140 int width, height; 141 }; 142 143 class Widget; 144 145 class Event 146 { 147 public: 148 enum Type { 149 KeyDown = 0, 150 KeyUp, 151 MouseMove, 152 MouseDown, 153 MouseUp, 154 MouseEnter, 155 MouseLeave, 156 MouseWheel, 157 FocusIn, 158 FocusOut, 159 Activate, 160 }; 161 162 explicit Event(Type type); 163 type()164 Type type() const { return m_type; } 165 target()166 shared_ptr<Widget>& target() { return m_target; } target()167 shared_ptr<Widget> target() const { return m_target; } set_target(shared_ptr<Widget> _target)168 void set_target(shared_ptr<Widget> _target) { m_target = move(_target); } 169 170 protected: 171 Type m_type; 172 shared_ptr<Widget> m_target; 173 }; 174 175 class KeyEvent final : public Event 176 { 177 public: 178 KeyEvent(Type type, const wm_keyboard_event& wm_ev); 179 key()180 int key() const { return m_key; } 181 182 protected: 183 int m_key; 184 }; 185 186 class MouseEvent final : public Event 187 { 188 public: 189 #ifdef USE_TILE_LOCAL 190 MouseEvent(Type type, const wm_mouse_event& wm_ev); 191 #endif 192 193 enum class Button 194 { 195 None = 0, 196 Left = 1, 197 Middle = 2, 198 Right = 4, 199 }; 200 button()201 Button button() const { return m_button; } x()202 int x() const { return m_x; } y()203 int y() const { return m_y; } wheel_dx()204 int wheel_dx() const { return m_wheel_dx; } wheel_dy()205 int wheel_dy() const { return m_wheel_dy; } 206 207 protected: 208 Button m_button; 209 int m_x, m_y; 210 int m_wheel_dx, m_wheel_dy; 211 }; 212 213 class FocusEvent final : public Event 214 { 215 public: 216 FocusEvent(Type type); 217 }; 218 219 class ActivateEvent final : public Event 220 { 221 public: 222 ActivateEvent(); 223 }; 224 225 template<typename, typename> class Slot; 226 227 template<class Target, class... Args> 228 class Slot<Target, bool(Args...)> 229 { 230 public: ~Slot()231 ~Slot() { alive = false; } 232 typedef function<bool (Args...)> HandlerSig; 233 typedef multimap<Target*, HandlerSig> HandlerMap; 234 template<typename P> emit_if(P pred,Args &...args)235 bool emit_if(P pred, Args&... args) 236 { 237 for (auto it : handlers) 238 if (pred(it.first)) 239 { 240 HandlerSig func = it.second; 241 if (func(forward<Args>(args)...)) 242 return true; 243 } 244 return false; 245 } emit(Target * target,Args &...args)246 bool emit(Target *target, Args&... args) 247 { 248 auto i = handlers.equal_range(target); 249 for (auto it = i.first; it != i.second; ++it) 250 { 251 HandlerSig func = it->second; 252 if (func(forward<Args>(args)...)) 253 return true; 254 } 255 return false; 256 } on(Target * target,HandlerSig handler)257 void on(Target *target, HandlerSig handler) 258 { 259 auto new_pair = pair<Target*, HandlerSig>(target, handler); 260 handlers.insert(new_pair); 261 } remove_by_target(Target * target)262 void remove_by_target(Target *target) 263 { 264 if (alive) 265 handlers.erase(target); 266 } 267 protected: 268 bool alive {true}; 269 HandlerMap handlers; 270 }; 271 272 class Widget : public enable_shared_from_this<Widget> 273 { 274 friend struct UIRoot; 275 276 public: 277 enum Align { 278 START = 0, 279 END, 280 CENTER, 281 STRETCH, 282 }; 283 284 enum Direction { 285 HORZ = 0, 286 VERT, 287 }; 288 289 virtual ~Widget(); 290 291 int flex_grow = 1; 292 bool expand_h = false, expand_v = false; 293 bool shrink_h = false, shrink_v = false; get_region()294 Region get_region() const { return m_region; } 295 296 // FIXME: convert to getter and setter min_size()297 Size& min_size() 298 { 299 _invalidate_sizereq(); 300 return m_min_size; 301 } 302 max_size()303 Size& max_size() 304 { 305 _invalidate_sizereq(); 306 return m_max_size; 307 } 308 309 virtual void _render() = 0; 310 virtual SizeReq _get_preferred_size(Direction dim, int prosp_width); 311 virtual void _allocate_region(); 312 313 void _set_parent(Widget* p); 314 _get_parent()315 Widget* _get_parent() const 316 { 317 return m_parent; 318 } 319 get_shared()320 shared_ptr<Widget> get_shared() 321 { 322 return shared_from_this(); 323 } 324 325 /** 326 * Mark this widget as possibly having changed size. 327 * 328 * _get_preferred_size() will be called before the next allocation/render. 329 */ 330 void _invalidate_sizereq(bool immediate = true); 331 332 /** 333 * Mark this widget as needing reallocation. 334 * 335 * _allocate_region() will be called before the next call to _render(), even 336 * if the widget has not resized or moved. This is useful if buffers need to 337 * be repacked due to widget state change. 338 */ 339 void _queue_allocation(bool immediate = true); 340 set_allocation_needed()341 void set_allocation_needed() 342 { 343 alloc_queued = true; 344 } 345 346 /** 347 * Mark this widget as needing redraw. render() will be called. 348 */ 349 virtual void _expose(); 350 351 /** 352 * Get/set visibility of this widget only, ignoring the visibility of its 353 * ancestors, if there are any, or whether it is in a layout at all. 354 */ is_visible()355 bool is_visible() const 356 { 357 return m_visible; 358 } 359 void set_visible(bool); 360 361 bool is_ancestor_of(const shared_ptr<Widget>& other); 362 for_each_child(function<void (shared_ptr<Widget> &)>)363 virtual void for_each_child(function<void(shared_ptr<Widget>&)>) 364 { 365 } 366 367 // Wrapper functions which handle common behaviour 368 // - margins 369 // - caching 370 void render(); 371 SizeReq get_preferred_size(Direction dim, int prosp_width); 372 virtual void allocate_region(Region region); 373 get_margin()374 Margin get_margin() const 375 { 376 return margin; 377 } 378 379 template<class... Args> set_margin_for_crt(Args &&...args)380 void set_margin_for_crt(Args&&... args) 381 { 382 #ifndef USE_TILE_LOCAL 383 margin = Margin(forward<Args>(args)...); 384 _invalidate_sizereq(); 385 #else 386 UNUSED(args...); 387 #endif 388 } 389 390 template<class... Args> set_margin_for_sdl(Args &&...args)391 void set_margin_for_sdl(Args&&... args) 392 { 393 #ifdef USE_TILE_LOCAL 394 margin = Margin(forward<Args>(args)...); 395 _invalidate_sizereq(); 396 #else 397 UNUSED(args...); 398 #endif 399 } 400 401 virtual bool on_event(const Event& event); 402 403 template<class F> on_any_event(F && cb)404 void on_any_event(F&& cb) 405 { 406 slots.event.on(this, forward<F>(cb)); 407 } 408 409 template<class F> on_hotkey_event(F && cb)410 void on_hotkey_event(F&& cb) 411 { 412 slots.hotkey.on(this, [cb](const Event& event){ 413 return cb(static_cast<const KeyEvent&>(event)); 414 }); 415 } 416 417 template<class F> on_layout_pop(F && cb)418 void on_layout_pop(F&& cb) 419 { 420 slots.layout_pop.on(this, [cb](){ 421 cb(); 422 return false; 423 }); 424 } 425 426 #define EVENT_HANDLER_HELPER(NAME, ENUM, CLASS) \ 427 template<class F> \ 428 void NAME(F&& cb) \ 429 { \ 430 slots.event.on(this, [cb](const Event& event){ \ 431 if (event.type() != Event::Type::ENUM) \ 432 return false; \ 433 return cb(static_cast<const CLASS&>(event)); \ 434 }); \ 435 } EVENT_HANDLER_HELPER(on_keydown_event,KeyDown,KeyEvent)436 EVENT_HANDLER_HELPER(on_keydown_event, KeyDown, KeyEvent) 437 EVENT_HANDLER_HELPER(on_keyup_event, KeyUp, KeyEvent) 438 EVENT_HANDLER_HELPER(on_mousemove_event, MouseMove, MouseEvent) 439 EVENT_HANDLER_HELPER(on_mousedown_event, MouseDown, MouseEvent) 440 EVENT_HANDLER_HELPER(on_mouseup_event, MouseUp, MouseEvent) 441 EVENT_HANDLER_HELPER(on_mouseenter_event, MouseEnter, MouseEvent) 442 EVENT_HANDLER_HELPER(on_mouseleave_event, MouseLeave, MouseEvent) 443 EVENT_HANDLER_HELPER(on_mousewheel_event, MouseWheel, MouseEvent) 444 EVENT_HANDLER_HELPER(on_focusin_event, FocusIn, FocusEvent) 445 EVENT_HANDLER_HELPER(on_focusout_event, FocusOut, FocusEvent) 446 EVENT_HANDLER_HELPER(on_activate_event, Activate, ActivateEvent) 447 #undef EVENT_HANDLER_HELPER 448 449 /** 450 * Container widget interface. Must return a pointer to the child widget at 451 * the given screen position, or nullptr. 452 */ 453 virtual shared_ptr<Widget> get_child_at_offset(int, int) 454 { 455 return nullptr; 456 } 457 sync_id()458 const string& sync_id() 459 { 460 return m_sync_id; 461 } 462 463 void set_sync_id(string id); 464 465 protected: 466 Region m_region; 467 Margin margin = Margin{0}; 468 469 void _unparent(shared_ptr<Widget>& child); 470 471 vector<shared_ptr<Widget>> m_internal_children; 472 void add_internal_child(shared_ptr<Widget> child); 473 474 template<class F> for_each_internal_child(F && cb)475 void for_each_internal_child(F&& cb) 476 { 477 for (auto& child : m_internal_children) 478 cb(child); 479 } 480 481 template<class F> for_each_child_including_internal(F && cb)482 void for_each_child_including_internal(F&& cb) 483 { 484 for_each_internal_child(forward<F>(cb)); 485 for_each_child(forward<F>(cb)); 486 } 487 488 /** 489 * Whether widgets of this class should be included in the set of focusable 490 * widgets. Should return a constant boolean. 491 */ can_take_focus()492 virtual bool can_take_focus() { return false; }; 493 494 /** 495 * Whether a widget is currently focusable. 496 */ focusable()497 bool focusable() 498 { 499 // FIXME: does not take into account hierarchical visibility 500 // e.g. widget in currently hidden child of switcher 501 return is_visible(); 502 } 503 504 #ifdef USE_TILE_WEB 505 virtual void sync_save_state(); 506 virtual void sync_load_state(const JsonNode *json); 507 void sync_state_changed(); 508 #endif 509 510 void _emit_layout_pop(); 511 512 private: 513 bool cached_sr_valid[2] = { false, false }; 514 SizeReq cached_sr[2]; 515 int cached_sr_pw; 516 bool alloc_queued = false; 517 bool m_visible = true; 518 Widget* m_parent = nullptr; 519 520 Size m_min_size = Size{0}; 521 Size m_max_size = Size{INT_MAX}; 522 523 string m_sync_id; 524 525 static struct slots { 526 Slot<Widget, bool(const Event&)> event; 527 Slot<Widget, bool(const Event&)> hotkey; 528 Slot<Widget, bool()> layout_pop; 529 } slots; 530 }; 531 532 /** 533 * An OverlayWidget is an "empty" widget that handles its own redrawing; it 534 * lives in the widget stack etc and uses the same API, but occupies 0 space 535 * and forces a render any time it is exposed. It is currently intended for 536 * using the widget API to render the regular screen, as in the widget-wrapped 537 * direction chooser, in order to simplify the amount of redrawing that 538 * happens. Not well-tested on local tiles (though in a quick test, it didn't 539 * cause any obvious issues). 540 */ 541 class OverlayWidget : public Widget 542 { 543 public: 544 void allocate_region(Region) override; 545 void _expose() override; 546 }; 547 548 class Container : public Widget 549 { 550 public: ~Container()551 virtual ~Container() {} 552 virtual void for_each_child(function<void(shared_ptr<Widget>&)> f) = 0; 553 }; 554 555 class Bin : public Container 556 { 557 public: ~Bin()558 virtual ~Bin() 559 { 560 if (m_child) 561 _unparent(m_child); 562 } 563 void set_child(shared_ptr<Widget> child); get_child()564 virtual shared_ptr<Widget> get_child() const 565 { 566 return m_child; 567 } 568 shared_ptr<Widget> get_child_at_offset(int x, int y) override; 569 570 protected: 571 class iterator 572 { 573 public: 574 typedef shared_ptr<Widget> value_type; 575 typedef ptrdiff_t distance; 576 typedef shared_ptr<Widget>* pointer; 577 typedef shared_ptr<Widget>& reference; 578 typedef input_iterator_tag iterator_category; 579 580 value_type c; 581 bool state; 582 iterator(value_type & _c,bool _state)583 iterator(value_type& _c, bool _state) : c(_c), state(_state) {} 584 void operator++ () { state = true; } 585 value_type& operator* () { return c; } 586 bool operator== (const iterator& other) { return c == other.c && state == other.state; } 587 bool operator!= (const iterator& other) { return !(*this == other); } 588 }; 589 590 public: begin()591 iterator begin() { return iterator(m_child, false); } end()592 iterator end() { return iterator(m_child, true); } for_each_child(function<void (shared_ptr<Widget> &)> f)593 void for_each_child(function<void(shared_ptr<Widget>&)> f) override 594 { 595 for (auto& child : *this) 596 f(child); 597 } 598 599 protected: 600 shared_ptr<Widget> m_child; 601 }; 602 603 class ContainerVec : public Container 604 { 605 public: ~ContainerVec()606 virtual ~ContainerVec() 607 { 608 for (auto& child : m_children) 609 if (child) 610 _unparent(child); 611 } 612 613 shared_ptr<Widget> get_child_at_offset(int x, int y) override; 614 num_children()615 size_t num_children() const 616 { 617 return m_children.size(); 618 } 619 620 template<typename T> get_child(T pos)621 shared_ptr<Widget>& get_child(T pos) 622 { 623 return m_children[pos]; 624 } 625 626 template<typename T> get_child(T pos)627 const shared_ptr<Widget>& get_child(T pos) const 628 { 629 return m_children[pos]; 630 } 631 632 template<typename T> 633 shared_ptr<Widget>& operator[](T pos) 634 { 635 return m_children[pos]; 636 } 637 638 template<typename T> 639 const shared_ptr<Widget>& operator[](T pos) const 640 { 641 return m_children[pos]; 642 } 643 644 protected: 645 class iterator 646 { 647 public: 648 typedef shared_ptr<Widget> value_type; 649 typedef ptrdiff_t distance; 650 typedef shared_ptr<Widget>* pointer; 651 typedef shared_ptr<Widget>& reference; 652 typedef input_iterator_tag iterator_category; 653 654 vector<value_type>& c; 655 vector<value_type>::iterator it; 656 iterator(vector<value_type> & _c,vector<value_type>::iterator _it)657 iterator(vector<value_type>& _c, vector<value_type>::iterator _it) : c(_c), it(_it) {} 658 void operator++ () { ++it; } 659 value_type& operator* () { return *it; } 660 bool operator== (const iterator& other) { return c == other.c && it == other.it; } 661 bool operator!= (const iterator& other) { return !(*this == other); } 662 }; 663 664 public: begin()665 iterator begin() 666 { 667 return iterator(m_children, m_children.begin()); 668 } end()669 iterator end() 670 { 671 return iterator(m_children, m_children.end()); 672 } 673 for_each_child(function<void (shared_ptr<Widget> &)> f)674 void for_each_child(function<void(shared_ptr<Widget>&)> f) override 675 { 676 for (auto& child : *this) 677 f(child); 678 } 679 680 protected: 681 vector<shared_ptr<Widget>> m_children; 682 }; 683 684 // Box widget: similar to the CSS flexbox (without wrapping) 685 // - Lays its children out in either a row or a column 686 // - Extra space is allocated according to each child's flex_grow property 687 688 class Box : public ContainerVec 689 { 690 public: 691 enum Expand { 692 NONE = 0x0, 693 EXPAND_H = 0x1, 694 EXPAND_V = 0x2, 695 SHRINK_H = 0x4, 696 SHRINK_V = 0x8, 697 }; 698 699 explicit Box(Direction dir, Expand expand_flags = NONE) 700 { 701 horz = dir == HORZ; 702 expand_h = expand_flags & EXPAND_H; 703 expand_v = expand_flags & EXPAND_V; 704 } 705 ~Box()706 virtual ~Box() {} 707 void add_child(shared_ptr<Widget> child); 708 main_alignment()709 Widget::Align main_alignment() const { return align_main; } cross_alignment()710 Widget::Align cross_alignment() const { return align_cross; } 711 set_main_alignment(Widget::Align align)712 void set_main_alignment(Widget::Align align) 713 { 714 align_main = align; 715 _invalidate_sizereq(); 716 }; set_cross_alignment(Widget::Align align)717 void set_cross_alignment(Widget::Align align) 718 { 719 align_cross = align; 720 _invalidate_sizereq(); 721 }; 722 723 void _render() override; 724 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 725 void _allocate_region() override; 726 727 protected: 728 bool horz; 729 Widget::Align align_main = START; 730 Widget::Align align_cross = START; 731 732 vector<int> layout_main_axis(vector<SizeReq>& ch_psz, int main_sz); 733 vector<int> layout_cross_axis(vector<SizeReq>& ch_psz, int cross_sz); 734 }; 735 736 class Text : public Widget 737 { 738 public: 739 Text(); ~Text()740 virtual ~Text() {} 741 742 template<class... Args> Text(Args &&...args)743 explicit Text(Args&&... args) : Text() 744 { 745 set_text(forward<Args>(args)...); 746 } 747 748 void set_text(const formatted_string &fs); set_text(const string & text)749 void set_text(const string& text) 750 { 751 set_text(formatted_string(text)); 752 } 753 get_text()754 const formatted_string& get_text() const 755 { 756 return m_text; 757 } 758 759 void set_font(FontWrapper *font); 760 void set_highlight_pattern(string pattern, bool hl_line = false); 761 762 void _render() override; 763 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 764 void _allocate_region() override; 765 766 #ifndef USE_TILE_LOCAL 767 void set_bg_colour(COLOURS colour); 768 #endif 769 set_wrap_text(bool _wrap_text)770 void set_wrap_text(bool _wrap_text) 771 { 772 if (wrap_text == _wrap_text) 773 return; 774 wrap_text = _wrap_text; 775 _invalidate_sizereq(); 776 } 777 set_ellipsize(bool _ellipsize)778 void set_ellipsize(bool _ellipsize) 779 { 780 if (ellipsize == _ellipsize) 781 return; 782 ellipsize = _ellipsize; 783 _invalidate_sizereq(); 784 } 785 786 protected: 787 void wrap_text_to_size(int width, int height); 788 789 bool wrap_text = false; 790 bool ellipsize = false; 791 792 formatted_string m_text; 793 #ifdef USE_TILE_LOCAL 794 struct brkpt { unsigned int op, line; }; 795 vector<brkpt> m_brkpts; 796 formatted_string m_text_wrapped; 797 ShapeBuffer m_hl_buf; 798 FontWrapper *m_font; 799 #else 800 vector<formatted_string> m_wrapped_lines; 801 COLOURS m_bg_colour = BLACK; 802 #endif 803 Size m_wrapped_size = Size{-1}; 804 Size m_wrapped_sizereq = Size{-1}; 805 string hl_pat; 806 bool hl_line; 807 }; 808 809 class Image : public Widget 810 { 811 public: Image()812 Image() {} Image(tile_def tile)813 explicit Image(tile_def tile) : Image() 814 { 815 set_tile(tile); 816 } ~Image()817 virtual ~Image() {} 818 void set_tile(tile_def tile); get_tile()819 tile_def get_tile() const 820 { 821 return m_tile; 822 } 823 824 void _render() override; 825 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 826 827 protected: 828 tile_def m_tile = {TILEG_ERROR}; 829 int m_tw {0}, m_th {0}; 830 831 #ifdef USE_TILE_LOCAL 832 GenericTexture m_img; 833 #endif 834 }; 835 836 class Stack : public ContainerVec 837 { 838 public: ~Stack()839 virtual ~Stack() {} 840 void add_child(shared_ptr<Widget> child); 841 void pop_child(); 842 843 shared_ptr<Widget> get_child_at_offset(int x, int y) override; 844 845 void _render() override; 846 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 847 void _allocate_region() override; 848 }; 849 850 class Switcher : public ContainerVec 851 { 852 public: ~Switcher()853 virtual ~Switcher() {} 854 void add_child(shared_ptr<Widget> child); 855 // FIXME: convert to getter and setter 856 int& current(); 857 shared_ptr<Widget> current_widget(); 858 859 Widget::Align align_x = START, align_y = START; 860 861 void _render() override; 862 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 863 void _allocate_region() override; 864 shared_ptr<Widget> get_child_at_offset(int x, int y) override; 865 866 protected: 867 int m_current; 868 }; 869 870 class Grid : public Container 871 { 872 public: ~Grid()873 virtual ~Grid() { 874 for (auto& child : m_child_info) 875 if (child.widget) 876 _unparent(child.widget); 877 } 878 879 void add_child(shared_ptr<Widget> child, int x, int y, int w = 1, int h = 1); 880 column_flex_grow(int x)881 int column_flex_grow(int x) const 882 { 883 return m_col_info.at(x).flex_grow; 884 } row_flex_grow(int y)885 int row_flex_grow(int y) const 886 { 887 return m_row_info.at(y).flex_grow; 888 } 889 890 // FIXME: convert to getter and setter column_flex_grow(int x)891 int& column_flex_grow(int x) 892 { 893 init_track_info(); 894 return m_col_info.at(x).flex_grow; 895 } row_flex_grow(int y)896 int& row_flex_grow(int y) 897 { 898 init_track_info(); 899 return m_row_info.at(y).flex_grow; 900 } 901 902 void _render() override; 903 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 904 void _allocate_region() override; 905 shared_ptr<Widget> get_child_at_offset(int x, int y) override; 906 907 bool stretch_h = false, stretch_v = false; 908 909 protected: get_tracks_region(int x,int y,int w,int h)910 Region get_tracks_region(int x, int y, int w, int h) const 911 { 912 Region track_region; 913 track_region.x = m_col_info[x].offset; 914 track_region.y = m_row_info[y].offset; 915 track_region.width = m_col_info[x+w-1].size + m_col_info[x+w-1].offset - m_col_info[x].offset; 916 track_region.height = m_row_info[y+h-1].size + m_row_info[y+h-1].offset - m_row_info[y].offset; 917 return track_region; 918 } 919 920 struct track_info { 921 int size; 922 int offset; 923 SizeReq sr; 924 int flex_grow = 1; 925 }; 926 vector<track_info> m_col_info; 927 vector<track_info> m_row_info; 928 929 struct child_info { 930 struct { 931 int x, y; 932 } pos; 933 Size span; 934 shared_ptr<Widget> widget; 935 inline bool operator==(const child_info& rhs) const { return widget == rhs.widget; } 936 }; 937 vector<child_info> m_child_info; 938 939 void layout_track(Direction dim, SizeReq sr, int size); 940 void set_track_offsets(vector<track_info>& tracks); 941 void compute_track_sizereqs(Direction dim); 942 void init_track_info(); 943 bool m_track_info_dirty = false; 944 945 protected: 946 class iterator 947 { 948 public: 949 typedef shared_ptr<Widget> value_type; 950 typedef ptrdiff_t distance; 951 typedef shared_ptr<Widget>* pointer; 952 typedef shared_ptr<Widget>& reference; 953 typedef input_iterator_tag iterator_category; 954 955 vector<child_info>& c; 956 vector<child_info>::iterator it; 957 iterator(vector<child_info> & _c,vector<child_info>::iterator _it)958 iterator(vector<child_info>& _c, vector<child_info>::iterator _it) : c(_c), it(_it) {} 959 void operator++ () { ++it; } 960 value_type& operator* () { return it->widget; } 961 bool operator== (const iterator& other) { return c == other.c && it == other.it; } 962 bool operator!= (const iterator& other) { return !(*this == other); } 963 }; 964 965 public: begin()966 iterator begin() { return iterator(m_child_info, m_child_info.begin()); } end()967 iterator end() { return iterator(m_child_info, m_child_info.end()); } for_each_child(function<void (shared_ptr<Widget> &)> f)968 void for_each_child(function<void(shared_ptr<Widget>&)> f) override 969 { 970 for (auto& child : *this) 971 f(child); 972 } 973 }; 974 975 class Scroller : public Bin 976 { 977 public: ~Scroller()978 virtual ~Scroller() {} 979 980 virtual void set_scroll(int y); 981 get_scroll()982 int get_scroll() const 983 { 984 return m_scroll; 985 } 986 set_scrollbar_visible(bool vis)987 void set_scrollbar_visible(bool vis) 988 { 989 m_scrolbar_visible = vis; 990 } 991 992 void _render() override; 993 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 994 void _allocate_region() override; 995 bool on_event(const Event& event) override; 996 997 protected: 998 int m_scroll = 0; 999 bool m_scrolbar_visible = true; 1000 #ifdef USE_TILE_LOCAL 1001 VertBuffer m_shade_buf = VertBuffer(false, true); 1002 ShapeBuffer m_scrollbar_buf; 1003 #endif 1004 }; 1005 1006 class Layout : public Bin 1007 { 1008 public: 1009 explicit Layout(shared_ptr<Widget> child); 1010 1011 void _render() override; 1012 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 1013 void _allocate_region() override; 1014 protected: 1015 #ifdef USE_TILE_LOCAL 1016 int m_depth; 1017 #endif 1018 }; 1019 1020 class Popup : public Layout 1021 { 1022 public: Popup(shared_ptr<Widget> child)1023 explicit Popup(shared_ptr<Widget> child) : Layout(move(child)) {} 1024 1025 void _render() override; 1026 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 1027 void _allocate_region() override; 1028 1029 Size get_max_child_size(); 1030 1031 protected: 1032 #ifdef USE_TILE_LOCAL 1033 ShapeBuffer m_buf; 1034 static constexpr int m_depth_indent = 20; 1035 static constexpr int m_padding = 23; 1036 1037 int base_margin(); 1038 #endif 1039 bool m_centred{!crawl_state.need_save}; 1040 }; 1041 1042 class Checkbox : public Bin 1043 { 1044 public: Checkbox()1045 Checkbox() {}; 1046 1047 virtual void _render() override; 1048 virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 1049 virtual void _allocate_region() override; 1050 1051 virtual bool on_event(const Event& event) override; 1052 checked()1053 bool checked() const 1054 { 1055 return m_checked; 1056 }; 1057 set_checked(bool _checked)1058 void set_checked(bool _checked) 1059 { 1060 if (m_checked == _checked) 1061 return; 1062 m_checked = _checked; 1063 #ifdef USE_TILE_WEB 1064 sync_state_changed(); 1065 #endif 1066 _expose(); 1067 }; 1068 1069 protected: can_take_focus()1070 bool can_take_focus() override { return true; }; 1071 1072 #ifdef USE_TILE_WEB 1073 void sync_save_state() override; 1074 void sync_load_state(const JsonNode *json) override; 1075 #endif 1076 1077 bool m_checked = false; 1078 #ifdef USE_TILE_LOCAL 1079 bool m_hovered = false; 1080 #endif 1081 1082 #ifdef USE_TILE_LOCAL 1083 static const int check_w = 30; 1084 static const int check_h = 20; 1085 #else 1086 static const int check_w = 4; 1087 static const int check_h = 1; 1088 #endif 1089 }; 1090 1091 class TextEntry : public Widget 1092 { 1093 public: 1094 TextEntry(); 1095 virtual void _render() override; 1096 virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 1097 virtual void _allocate_region() override; 1098 virtual bool on_event(const Event& event) override; 1099 1100 void set_font(FontWrapper *font); 1101 get_text()1102 string get_text() const { return m_text; }; set_text(string s)1103 void set_text(string s) { 1104 m_line_reader.set_text(s); 1105 m_text = m_line_reader.get_text(); 1106 m_cursor = m_line_reader.get_cursor_position(); 1107 #ifdef USE_TILE_WEB 1108 sync_state_changed(); 1109 #endif 1110 _expose(); 1111 }; 1112 1113 template<typename T> set_input_history(T && fn)1114 void set_input_history(T&& fn) { 1115 m_line_reader.set_input_history(forward<T>(fn)); 1116 } 1117 1118 template<typename T> set_keyproc(T && fn)1119 void set_keyproc(T&& fn) { 1120 m_line_reader.set_keyproc(forward<T>(fn)); 1121 } 1122 1123 protected: can_take_focus()1124 bool can_take_focus() override { return true; }; 1125 1126 #ifdef USE_TILE_WEB 1127 void sync_save_state() override; 1128 void sync_load_state(const JsonNode *json) override; 1129 #endif 1130 1131 #ifdef USE_TILE_LOCAL 1132 int padding_size(); 1133 #endif 1134 1135 string m_text; 1136 int m_cursor = 0; 1137 int m_hscroll = 0; 1138 1139 class LineReader 1140 { 1141 public: 1142 LineReader(char *buffer, size_t bufsz); 1143 virtual ~LineReader(); 1144 1145 typedef keyfun_action (*keyproc)(int &key); 1146 1147 string get_text() const; 1148 void set_text(string s); 1149 1150 void set_input_history(input_history *ih); 1151 void set_keyproc(keyproc fn); 1152 1153 void set_edit_mode(edit_mode m); 1154 edit_mode get_edit_mode(); 1155 1156 void set_prompt(string p); 1157 1158 void insert_char_at_cursor(int ch); 1159 void overwrite_char_at_cursor(int ch); 1160 #ifdef USE_TILE_WEB 1161 void set_tag(const string &tag); 1162 #endif 1163 get_cursor_position()1164 int get_cursor_position() { 1165 return cur - buffer; 1166 }; 1167 1168 int process_key_core(int ch); 1169 int process_key(int ch); 1170 1171 protected: 1172 void backspace(); 1173 void delete_char(); 1174 void killword(); 1175 void kill_to_begin(); 1176 void kill_to_end(); 1177 #ifdef USE_TILE_LOCAL 1178 void clipboard_paste(); 1179 #endif 1180 1181 bool is_wordchar(char32_t c); 1182 1183 protected: 1184 char *buffer; 1185 size_t bufsz; 1186 input_history *history; 1187 keyproc keyfn; 1188 edit_mode mode; 1189 string prompt; // currently only used for webtiles input dialogs 1190 1191 #ifdef USE_TILE_WEB 1192 string tag; // For identification on the Webtiles client side 1193 #endif 1194 1195 // These are subject to change during editing. 1196 char *cur; 1197 int length; 1198 }; 1199 1200 char m_buffer[1024]; 1201 LineReader m_line_reader; 1202 1203 #ifdef USE_TILE_LOCAL 1204 ShapeBuffer m_buf; 1205 FontWrapper *m_font; 1206 #endif 1207 }; 1208 1209 #ifdef USE_TILE_LOCAL 1210 class Dungeon : public Widget 1211 { 1212 public: Dungeon()1213 Dungeon() : m_buf((ImageManager*)tiles.get_image_manager()) {} ~Dungeon()1214 virtual ~Dungeon() {} 1215 1216 void _render() override; 1217 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 1218 1219 unsigned width = 0; 1220 unsigned height = 0; 1221 1222 // FIXME: convert to getter and setter buf()1223 DungeonCellBuffer& buf() 1224 { 1225 m_dirty = true; 1226 return m_buf; 1227 } 1228 1229 protected: 1230 DungeonCellBuffer m_buf; 1231 bool m_dirty = true; 1232 }; 1233 1234 class PlayerDoll : public Widget 1235 { 1236 public: 1237 explicit PlayerDoll(dolls_data doll); 1238 virtual ~PlayerDoll(); 1239 1240 void _render() override; 1241 SizeReq _get_preferred_size(Direction dim, int prosp_width) override; 1242 void _allocate_region() override; 1243 1244 protected: 1245 void _pack_doll(); 1246 1247 dolls_data m_save_doll; 1248 vector<tile_def> m_tiles; 1249 FixedVector<TileBuffer, TEX_MAX> m_tile_buf; 1250 }; 1251 #endif 1252 1253 #ifdef USE_TILE 1254 void push_cutoff(); 1255 void pop_cutoff(); 1256 1257 class cutoff_point 1258 { 1259 public: cutoff_point()1260 cutoff_point() { push_cutoff(); } ~cutoff_point()1261 ~cutoff_point() { pop_cutoff(); } 1262 }; 1263 #endif 1264 1265 void push_layout(shared_ptr<Widget> root, KeymapContext km = KMC_DEFAULT); 1266 void pop_layout(); 1267 shared_ptr<Widget> top_layout(); 1268 void pump_events(int wait_event_timeout = INT_MAX); 1269 void run_layout(shared_ptr<Widget> root, const bool& done, 1270 shared_ptr<Widget> initial_focus = nullptr); 1271 bool has_layout(); 1272 NORETURN void restart_layout(); 1273 int getch(KeymapContext km = KMC_DEFAULT); 1274 void force_render(); 1275 void render(); 1276 void delay(unsigned int ms); 1277 1278 void set_focused_widget(Widget* w); 1279 Widget* get_focused_widget(); 1280 bool raise_event(Event& event); 1281 1282 #ifdef USE_TILE_WEB 1283 void recv_ui_state_change(const JsonNode *json); 1284 void sync_ui_state(); 1285 int layout_generation_id(); 1286 #endif 1287 1288 // XXX: this is a hack used to ensure that when switching to a 1289 // layout-based UI, the starting window size is correct. This is necessary 1290 // because there's no way to query TilesFramework for the current screen size 1291 void resize(int w, int h); 1292 1293 bool is_available(); 1294 1295 void show_cursor_at(coord_def pos); 1296 void show_cursor_at(int x, int y); 1297 1298 class progress_popup 1299 { 1300 public: 1301 progress_popup(string title, int width); 1302 ~progress_popup(); 1303 void set_status_text(string status); 1304 void advance_progress(); 1305 void force_redraw(); 1306 private: 1307 shared_ptr<Popup> contents; 1308 shared_ptr<Text> progress_bar; 1309 shared_ptr<Text> status_text; 1310 unsigned int position; 1311 unsigned int bar_width; 1312 formatted_string get_progress_string(unsigned int len); 1313 unwind_bool no_more; 1314 }; 1315 1316 #ifdef USE_TILE_LOCAL 1317 wm_mouse_event to_wm_event(const MouseEvent &); 1318 #endif 1319 1320 #ifdef USE_TILE_LOCAL 1321 extern bool should_render_current_regions; 1322 #endif 1323 } 1324