1 //  SuperTuxKart - a fun racing game with go-kart
2 //  Copyright (C) 2009-2015 Marianne Gagnon
3 //
4 //  This program is free software; you can redistribute it and/or
5 //  modify it under the terms of the GNU General Public License
6 //  as published by the Free Software Foundation; either version 3
7 //  of the License, or (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 
18 
19 
20 #ifndef HEADER_RIBBONGRID_HPP
21 #define HEADER_RIBBONGRID_HPP
22 
23 #include <irrString.h>
24 
25 #include <algorithm>
26 
27 #include "guiengine/widget.hpp"
28 #include "guiengine/widgets/ribbon_widget.hpp"
29 #include "utils/leak_check.hpp"
30 #include "utils/ptr_vector.hpp"
31 
32 namespace GUIEngine
33 {
34     class IconButtonWidget;
35 
36     /**
37      * Even if you have a ribbon that only acts on click/enter, you may wish to know which
38      * item is currently highlighted. In this case, create a listener and pass it to the ribbon.
39      */
40     class DynamicRibbonHoverListener
41     {
42     public:
~DynamicRibbonHoverListener()43         virtual ~DynamicRibbonHoverListener() {}
44         virtual void onSelectionChanged(DynamicRibbonWidget* theWidget,
45                                        const std::string& selectionID,
46                                        const irr::core::stringw& selectionText,
47                                        const int playerID) = 0;
48     };
49 
50     /** The description of an item added to a DynamicRibbonWidget */
51     struct ItemDescription
52     {
53         irr::core::stringw m_user_name;
54         std::string m_code_name;
55         std::string m_sshot_file;
56         IconButtonWidget::IconPathType m_image_path_type;
57 
58         bool m_animated;
59         /** used instead of 'm_sshot_file' if m_animated is true */
60         std::vector<std::string> m_all_images;
61         float m_curr_time;
62         float m_time_per_frame;
63 
64         unsigned int m_badges;
65     };
66 
67     struct DynamicRibbonScrollCallback
68     {
69         void* data;
70         void (*callback)(void* data);
71     };
72 
73     /**
74       * \brief An extended version of RibbonWidget, with more capabilities.
75       * A dynamic ribbon builds upon RibbonWidget, adding dynamic contents creation and sizing,
76       * scrolling, multiple-row layouts.
77       * \ingroup widgetsgroup
78       * \note items you add to a list are kept after the the ribbon was in is removed
79       *       (i.e. you don't need to add items everytime the screen is shown, only upon loading)
80       */
81     class DynamicRibbonWidget : public Widget, public RibbonWidget::IRibbonListener
82     {
83         friend class RibbonWidget;
84 
85         /** A list of all listeners that registered to be notified on hover/selection */
86         PtrVector<DynamicRibbonHoverListener> m_hover_listeners;
87 
88         virtual ~DynamicRibbonWidget();
89 
90         /** Used for ribbon grids that have a label at the bottom */
91         bool m_has_label;
92         irr::gui::IGUIStaticText* m_label;
93 
94         /** Height of ONE label text line (if label is multiline only one line is measured here).
95           * If there is no label, will be 0.
96           */
97         int m_label_height;
98 
99         /** Whether this ribbon contains at least one animated item */
100         bool m_animated_contents;
101 
102         /** Whether there are more items than can fit in a single screen; arrows will then appear
103           * on each side of the ribbon to scroll the contents
104           */
105         bool m_scrolling_enabled;
106 
107         /** Used to keep track of item count changes */
108         int m_previous_item_count;
109 
110         /** List of items in the ribbon */
111         std::vector<ItemDescription> m_items;
112 
113         /** Width of the scrolling arrows on each side */
114         int m_arrows_w;
115 
116         /** Current scroll offset within items */
117         int m_scroll_offset;
118 
119         /** Width and height of children as declared in the GUI file */
120         int m_child_width, m_child_height;
121 
122         /** Number of rows and columns. Number of columns can dynamically change, number of row is
123             determined at creation */
124         int m_row_amount;
125         int m_col_amount;
126 
127         /** The total number of columns given item count and row count (even counting not visible with current scrolling) */
128         int m_needed_cols;
129 
130         /** Whether this ribbon can have multiple rows (i.e. ribbon grid) or is a single line */
131         bool m_multi_row;
132 
133         /** irrlicht relies on consecutive IDs to perform keyboard navigation between widgets. However, since this
134             widget is dynamic, irrlicht widgets are not created as early as all others, so by the time we're ready
135             to create the full contents of this widget, the ID generator is already incremented, thus messing up
136             keyboard navigation. To work around this, at the same time all other widgets are created, I gather a
137             number of IDs (the number of rows) and store them here. Then, when we're finally ready to create the
138             contents dynamically, we can re-use these IDs and get correct navigation order. */
139         std::vector<int> m_ids;
140 
141         /** Whether this is a "combo" style ribbon grid widget */
142         bool m_combo;
143 
144         /* reference pointers only, the actual instances are owned by m_children */
145         IconButtonWidget* m_left_widget;
146         IconButtonWidget* m_right_widget;
147 
148         /** Returns the currently selected row */
149         RibbonWidget* getSelectedRibbon(const int playerID);
150 
151         /** Returns the row */
152         RibbonWidget* getRowContaining(Widget* w);
153 
154         /** Updates the visible label to match the currently selected item */
155         void updateLabel(RibbonWidget* from_this_ribbon=NULL);
156 
157         /** Even though the ribbon grid widget looks like a grid, it is really a vertical stack of
158             independant ribbons. When moving selection horizontally, this method is used to notify
159             other rows above and below of which column is selected, so that moving vertically to
160             another row keeps the same selected column. */
161         void propagateSelection();
162 
163         /** Callback called widget is focused */
164         EventPropagation focused(const int playerID);
165 
166         /** Removes all previously added contents icons, and re-adds them (calculating the new amount) */
167         void buildInternalStructure();
168 
169         /** Call this to scroll within a scrollable ribbon */
170         void scroll(int x_delta, bool evenIfDeactivated = false);
171 
172         /** Used  for combo ribbons, to contain the ID of the currently selected item for each player */
173         int m_selected_item[MAX_PLAYER_COUNT];
174 
175         /** Callbacks */
176         virtual void add();
177         virtual EventPropagation mouseHovered(Widget* child, const int playerID);
178         virtual EventPropagation transmitEvent(Widget* w, const std::string& originator, const int playerID);
179 
180         bool findItemInRows(const char* name, int* p_row, int* p_id);
181 
182         int m_item_count_hint;
183 
184         float getFontScale(int icon_width) const;
185         void setLabelSize(const irr::core::stringw& text);
186         irr::core::stringw getUserName(const irr::core::stringw& user_name) const;
187 
188         /**
189          * Font used to write the labels, can be scaled down depending on the
190          * length of the text
191          */
192         irr::gui::ScalableFont* m_font;
193 
194         /** Max width of a label, in pixels */
195         int m_max_label_width;
196 
197         /** Max length of a label, in characters */
198         unsigned int m_max_label_length;
199 
200         DynamicRibbonScrollCallback m_scroll_callback;
201     public:
202 
203         LEAK_CHECK()
204 
205         /**
206           * \param combo     Whether this is a "combo" ribbon, i.e. whether there is always one selected item.
207           *                  If set to false, will behave more like a toolbar.
208           * \param multi_row Whether this ribbon can have more than one row
209           */
210         DynamicRibbonWidget(const bool combo, const bool multi_row);
211 
212         /** Reference pointers only, the actual instances are owned by m_children. Used to create multiple-row
213          ribbons (what appears to be a grid of icons is actually a vector of stacked basic ribbons) */
214         PtrVector<RibbonWidget, REF> m_rows;
215 
216         /** Dynamically add an item to the ribbon's list of items (will not be visible until you
217           * call 'updateItemDisplay' or 'add').
218           *
219           * \param user_name       The name that will shown to the user (may be translated)
220           * \param code_name       The non-translated internal name used to uniquely identify this item.
221           * \param image_file      A path to a texture that will the icon of this item (path relative to data dir, just like PROP_ICON)
222           * \param badge           Whether to add badges to this item (bitmask, see possible values in widget.hpp)
223           * \param image_path_type How to interpret the path to the image (absolute or relative)
224           */
225         void addItem( const irr::core::stringw& user_name, const std::string& code_name,
226                       const std::string& image_file, const unsigned int badge=0,
227                       IconButtonWidget::IconPathType image_path_type=IconButtonWidget::ICON_PATH_TYPE_RELATIVE);
228 
229         /** Dynamically add an animated item to the ribbon's list of items (will not be visible until you
230          * call 'updateItemDisplay' or 'add'). Animated means it has many images that will be shown in
231          * a slideshown fashion.
232          *
233          * \param user_name       The name that will shown to the user (may be translated)
234          * \param code_name       The non-translated internal name used to uniquely identify this item.
235          * \param image_files     A path to a texture that will the icon of this item (path relative to data dir, just like PROP_ICON)
236          * \param time_per_frame  Time (in seconds) to spend at each image.
237          * \param badge           Whether to add badges to this item (bitmask, see possible values in widget.hpp)
238          * \param image_path_type How to interpret the path to the image (absolute or relative)
239          */
240         void addAnimatedItem( const irr::core::stringw& user_name, const std::string& code_name,
241                              const std::vector<std::string>& image_files, const float time_per_frame,
242                              const unsigned int badge=0,
243                              IconButtonWidget::IconPathType image_path_type=IconButtonWidget::ICON_PATH_TYPE_RELATIVE);
244 
245         /** Clears all items added through 'addItem'. You can then add new items with 'addItem' and call
246             'updateItemDisplay' to update the display. */
247         void clearItems();
248         void setBadge(const std::string &name, BadgeType badge);
249 
250         /** Sort the list of items with a given comparator. */
251         template<typename Compare>
sortItems(Compare comp)252         void sortItems(Compare comp)
253         {
254             std::sort(m_items.begin(), m_items.end(), comp);
255         }
256 
257         /**
258           * \brief Register a listener to be notified of selection changes within the ribbon.
259           * \note  The ribbon takes ownership of this listener and will delete it.
260           * \note  The listener will be deleted upon leaving the screen, so you will likely
261           *        want to add a listener in the "init" callback of your screen.
262           */
263         void registerHoverListener(DynamicRibbonHoverListener* listener);
264 
265         /** Called when right key is pressed */
266         EventPropagation rightPressed(const int playerID);
267 
268         /** Called when left key is pressed */
269         EventPropagation leftPressed(const int playerID);
270 
271         /** Updates icons/labels given current items and scrolling offset, taking care of resizing
272             the dynamic ribbon if the number of items changed */
273         void updateItemDisplay();
274 
275         /** Get the internal name (ID) of the selected item */
276         const std::string& getSelectionIDString(const int playerID);
277 
278         /** Get the user-visible text of the selected item */
279         irr::core::stringw getSelectionText(const int playerID);
280 
281         /** Returns a read-only list of items added to this ribbon */
getItems() const282         const std::vector<ItemDescription>& getItems() const { return m_items; }
283 
284         /**
285           * \brief Select an item from its numerical ID. Only for [1-row] combo ribbons.
286           *
287           * \param  item_id In range [0 .. number of items added through 'addItem' - 1]
288           * \return Whether setting the selection was successful (whether the item exists)
289           */
290         bool setSelection(int item_id, const int playerID, const bool focusIt, bool evenIfDeactivated=false);
291 
292         /**
293           * \brief Select an item from its codename.
294           *
295           * \return Whether setting the selection was successful (whether the item exists)
296           */
297         bool setSelection(const std::string &item_codename,
298                           const int playerID, const bool focusIt,
299                           bool evenIfDeactivated=false);
300 
301         /** \brief Callback from parent class Widget. */
302         virtual void elementRemoved();
303 
304         /** \brief callback from IRibbonListener */
305         virtual void onRibbonWidgetScroll(const int delta_x);
306 
307         /** \brief callback from IRibbonListener */
308         virtual void onRibbonWidgetFocus(RibbonWidget* emitter, const int playerID);
309 
310         /** \brief callback from IRibbonListener */
onSelectionChange()311         virtual void onSelectionChange(){}
312 
313         virtual void setText(const irr::core::stringw& text);
314 
315         virtual void update(float delta);
316 
317         /** Set approximately how many items are expected to be in this ribbon; will help the layout
318           * algorithm next time add() is called */
setItemCountHint(int hint)319         void setItemCountHint(int hint) { m_item_count_hint = hint; }
320 
321         /** Set max length of displayed text. */
setMaxLabelLength(int length)322         void setMaxLabelLength(int length) { m_max_label_length = length; }
323 
registerScrollCallback(void (* callback)(void * data),void * data)324         void registerScrollCallback(void (*callback)(void* data), void* data)
325         {
326             m_scroll_callback.callback = callback;
327             m_scroll_callback.data = data;
328         }
329     };
330 
331 }
332 
333 #endif
334