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