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