Dynamically change font size in a DynamicRibbonWidget to make labels fit,
and also cut the text when it's just too long
This commit is contained in:
Marc Coll Carrillo 2014-09-28 10:18:47 +02:00
parent eaf6ee9556
commit 2651c082a4
4 changed files with 172 additions and 79 deletions

View File

@ -16,6 +16,7 @@
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "guiengine/engine.hpp"
#include "guiengine/scalable_font.hpp"
#include "guiengine/widgets/dynamic_ribbon_widget.hpp"
#include "io/file_manager.hpp"
#include "states_screens/state_manager.hpp"
@ -53,10 +54,14 @@ DynamicRibbonWidget::DynamicRibbonWidget(const bool combo, const bool multi_row)
m_selected_item[0] = 0; // only player 0 has a selection by default
m_item_count_hint = 0;
m_font = GUIEngine::getFont()->getHollowCopy();
m_max_label_width = 0;
}
// -----------------------------------------------------------------------------
DynamicRibbonWidget::~DynamicRibbonWidget()
{
m_font->drop();
if (m_animated_contents)
{
GUIEngine::needsUpdate.remove(this);
@ -379,6 +384,13 @@ void DynamicRibbonWidget::buildInternalStructure()
ribbon->m_properties[PROP_ID] = name.str();
ribbon->m_event_handler = this;
// calculate font size
if (m_col_amount > 0)
{
m_font->setScale(GUIEngine::getFont()->getScale() *
getFontScale((ribbon->m_w / m_col_amount) - 30));
}
// add columns
for (int i=0; i<m_col_amount; i++)
{
@ -392,6 +404,7 @@ void DynamicRibbonWidget::buildInternalStructure()
icon->m_properties[PROP_HEIGHT] = m_properties[PROP_CHILD_HEIGHT];
icon->m_w = atoi(icon->m_properties[PROP_WIDTH].c_str());
icon->m_h = atoi(icon->m_properties[PROP_HEIGHT].c_str());
icon->setLabelFont(m_font);
// If we want each icon to have its own label, we must make it non-empty, otherwise
// it will assume there is no label and none will be created (FIXME: that's ugly)
@ -438,14 +451,14 @@ void DynamicRibbonWidget::buildInternalStructure()
assert(childrenCount == (int)m_items.size());
}
#endif
}
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::addItem( const irr::core::stringw& user_name, const std::string& code_name,
const std::string& image_file, const unsigned int badges,
IconButtonWidget::IconPathType image_path_type)
{
ItemDescription desc;
desc.m_user_name = user_name;
desc.m_user_name = getUserName(user_name);
desc.m_code_name = code_name;
desc.m_sshot_file = image_file;
desc.m_badges = badges;
@ -453,6 +466,8 @@ void DynamicRibbonWidget::addItem( const irr::core::stringw& user_name, const st
desc.m_image_path_type = image_path_type;
m_items.push_back(desc);
setLabelSize(desc.m_user_name);
}
// -----------------------------------------------------------------------------
@ -462,7 +477,7 @@ void DynamicRibbonWidget::addAnimatedItem( const irr::core::stringw& user_name,
const unsigned int badges, IconButtonWidget::IconPathType image_path_type )
{
ItemDescription desc;
desc.m_user_name = user_name;
desc.m_user_name = getUserName(user_name);
desc.m_code_name = code_name;
desc.m_all_images = image_files;
desc.m_badges = badges;
@ -473,6 +488,8 @@ void DynamicRibbonWidget::addAnimatedItem( const irr::core::stringw& user_name,
m_items.push_back(desc);
setLabelSize(desc.m_user_name);
if (!m_animated_contents)
{
m_animated_contents = true;
@ -498,6 +515,7 @@ void DynamicRibbonWidget::clearItems()
m_items.clear();
m_animated_contents = false;
m_scroll_offset = 0;
m_max_label_width = 0;
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::elementRemoved()
@ -1097,7 +1115,9 @@ bool DynamicRibbonWidget::setSelection(int item_id, const int playerID,
propagateSelection();
return true;
}
// ----------------------------------------------------------------------------
bool DynamicRibbonWidget::setSelection(const std::string &item_codename,
const int playerID, const bool focusIt,
bool evenIfDeactivated)
@ -1117,4 +1137,29 @@ bool DynamicRibbonWidget::setSelection(const std::string &item_codename,
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::setLabelSize(const irr::core::stringw& text)
{
int w = GUIEngine::getFont()->getDimension(text.c_str()).Width;
if (w > m_max_label_width)
m_max_label_width = w;
}
// -----------------------------------------------------------------------------
float DynamicRibbonWidget::getFontScale(int icon_width) const
{
if (m_max_label_width <= icon_width || m_max_label_width == 0 || icon_width == 0)
return 1.0f;
else
return std::max (0.5f, ((float)icon_width / (float)m_max_label_width));
}
// -----------------------------------------------------------------------------
irr::core::stringw DynamicRibbonWidget::getUserName(const irr::core::stringw& user_name) const
{
if (user_name.size() < MAX_LABEL_LENGTH)
return user_name;
else
return (user_name.subString(0, MAX_LABEL_LENGTH - 3) + L"...");
}

View File

@ -30,7 +30,7 @@
namespace GUIEngine
{
class IconButtonWidget;
/**
* Even if you have a ribbon that only acts on click/enter, you may wish to know which
* item is currently highlighted. In this case, create a listener and pass it to the ribbon.
@ -44,7 +44,7 @@ namespace GUIEngine
const irr::core::stringw& selectionText,
const int playerID) = 0;
};
/** The description of an item added to a DynamicRibbonWidget */
struct ItemDescription
{
@ -52,16 +52,16 @@ namespace GUIEngine
std::string m_code_name;
std::string m_sshot_file;
IconButtonWidget::IconPathType m_image_path_type;
bool m_animated;
/** used instead of 'm_sshot_file' if m_animated is true */
std::vector<std::string> m_all_images;
float m_curr_time;
float m_time_per_frame;
unsigned int m_badges;
};
/**
* \brief An extended version of RibbonWidget, with more capabilities.
* A dynamic ribbon builds upon RibbonWidget, adding dynamic contents creation and sizing,
@ -73,55 +73,55 @@ namespace GUIEngine
class DynamicRibbonWidget : public Widget, public RibbonWidget::IRibbonListener
{
friend class RibbonWidget;
/** A list of all listeners that registered to be notified on hover/selection */
PtrVector<DynamicRibbonHoverListener> m_hover_listeners;
virtual ~DynamicRibbonWidget();
/** Used for ribbon grids that have a label at the bottom */
bool m_has_label;
irr::gui::IGUIStaticText* m_label;
/** Height of ONE label text line (if label is multiline only one line is measured here).
* If there is no label, will be 0.
*/
int m_label_height;
/** Whether this ribbon contains at least one animated item */
bool m_animated_contents;
/** Whether there are more items than can fit in a single screen; arrows will then appear
* on each side of the ribbon to scroll the contents
*/
bool m_scrolling_enabled;
/** Used to keep track of item count changes */
int m_previous_item_count;
/** List of items in the ribbon */
std::vector<ItemDescription> m_items;
/** Width of the scrolling arrows on each side */
int m_arrows_w;
/** Current scroll offset within items */
int m_scroll_offset;
/** Width and height of children as declared in the GUI file */
int m_child_width, m_child_height;
/** Number of rows and columns. Number of columns can dynamically change, number of row is
determined at creation */
int m_row_amount;
int m_col_amount;
/** The total number of columns given item count and row count (even counting not visible with current scrolling) */
int m_needed_cols;
/** Whether this ribbon can have multiple rows (i.e. ribbon grid) or is a single line */
bool m_multi_row;
/** irrlicht relies on consecutive IDs to perform keyboard navigation between widgets. However, since this
widget is dynamic, irrlicht widgets are not created as early as all others, so by the time we're ready
to create the full contents of this widget, the ID generator is already incremented, thus messing up
@ -129,65 +129,81 @@ namespace GUIEngine
number of IDs (the number of rows) and store them here. Then, when we're finally ready to create the
contents dynamically, we can re-use these IDs and get correct navigation order. */
std::vector<int> m_ids;
/** Whether this is a "combo" style ribbon grid widget */
bool m_combo;
/* reference pointers only, the actual instances are owned by m_children */
IconButtonWidget* m_left_widget;
IconButtonWidget* m_right_widget;
/** Returns the currently selected row */
RibbonWidget* getSelectedRibbon(const int playerID);
/** Returns the row */
RibbonWidget* getRowContaining(Widget* w);
/** Updates the visible label to match the currently selected item */
void updateLabel(RibbonWidget* from_this_ribbon=NULL);
/** Even though the ribbon grid widget looks like a grid, it is really a vertical stack of
independant ribbons. When moving selection horizontally, this method is used to notify
other rows above and below of which column is selected, so that moving vertically to
another row keeps the same selected column. */
void propagateSelection();
/** Callback called widget is focused */
EventPropagation focused(const int playerID);
/** Removes all previously added contents icons, and re-adds them (calculating the new amount) */
void buildInternalStructure();
/** Call this to scroll within a scrollable ribbon */
void scroll(const int x_delta);
/** Used for combo ribbons, to contain the ID of the currently selected item for each player */
int m_selected_item[MAX_PLAYER_COUNT];
/** Callbacks */
virtual void add();
virtual EventPropagation mouseHovered(Widget* child, const int playerID);
virtual EventPropagation transmitEvent(Widget* w, const std::string& originator, const int playerID);
bool findItemInRows(const char* name, int* p_row, int* p_id);
int m_item_count_hint;
float getFontScale(int icon_width) const;
void setLabelSize(const irr::core::stringw& text);
irr::core::stringw getUserName(const irr::core::stringw& user_name) const;
/**
* Font used to write the labels, can be scaled down depending on the
* length of the text
*/
irr::gui::ScalableFont* m_font;
/** Max width of a label, in pixels */
int m_max_label_width;
/** Max length of a label, in characters */
static const int MAX_LABEL_LENGTH = 30;
public:
LEAK_CHECK()
/**
* \param combo Whether this is a "combo" ribbon, i.e. whether there is always one selected item.
* If set to false, will behave more like a toolbar.
* \param multi_row Whether this ribbon can have more than one row
*/
DynamicRibbonWidget(const bool combo, const bool multi_row);
/** Reference pointers only, the actual instances are owned by m_children. Used to create mtultiple-row
ribbons (what appears to be a grid of icons is actually a vector of stacked basic ribbons) */
PtrVector<RibbonWidget, REF> m_rows;
/** Dynamically add an item to the ribbon's list of items (will not be visible until you
* call 'updateItemDisplay' or 'add').
*
@ -200,7 +216,7 @@ namespace GUIEngine
void addItem( const irr::core::stringw& user_name, const std::string& code_name,
const std::string& image_file, const unsigned int badge=0,
IconButtonWidget::IconPathType image_path_type=IconButtonWidget::ICON_PATH_TYPE_RELATIVE);
/** Dynamically add an animated item to the ribbon's list of items (will not be visible until you
* call 'updateItemDisplay' or 'add'). Animated means it has many images that will be shown in
* a slideshown fashion.
@ -220,7 +236,7 @@ namespace GUIEngine
/** Clears all items added through 'addItem'. You can then add new items with 'addItem' and call
'updateItemDisplay' to update the display. */
void clearItems();
/**
* \brief Register a listener to be notified of selection changes within the ribbon.
* \note The ribbon takes ownership of this listener and will delete it.
@ -228,26 +244,26 @@ namespace GUIEngine
* want to add a listener in the "init" callback of your screen.
*/
void registerHoverListener(DynamicRibbonHoverListener* listener);
/** Called when right key is pressed */
EventPropagation rightPressed(const int playerID);
/** Called when left key is pressed */
EventPropagation leftPressed(const int playerID);
/** Updates icons/labels given current items and scrolling offset, taking care of resizing
the dynamic ribbon if the number of items changed */
void updateItemDisplay();
/** Get the internal name (ID) of the selected item */
const std::string& getSelectionIDString(const int playerID);
/** Get the user-visible text of the selected item */
irr::core::stringw getSelectionText(const int playerID);
/** Returns a read-only list of items added to this ribbon */
const std::vector<ItemDescription>& getItems() const { return m_items; }
/**
* \brief Select an item from its numerical ID. Only for [1-row] combo ribbons.
*
@ -255,7 +271,7 @@ namespace GUIEngine
* \return Whether setting the selection was successful (whether the item exists)
*/
bool setSelection(int item_id, const int playerID, const bool focusIt, bool evenIfDeactivated=false);
/**
* \brief Select an item from its codename.
*
@ -264,26 +280,26 @@ namespace GUIEngine
bool setSelection(const std::string &item_codename,
const int playerID, const bool focusIt,
bool evenIfDeactivated=false);
/** \brief Callback from parent class Widget. */
virtual void elementRemoved();
/** \brief callback from IRibbonListener */
virtual void onRibbonWidgetScroll(const int delta_x);
/** \brief callback from IRibbonListener */
virtual void onRibbonWidgetFocus(RibbonWidget* emitter, const int playerID);
/** \brief callback from IRibbonListener */
virtual void onSelectionChange(){}
virtual void update(float delta);
/** Set approximately how many items are expected to be in this ribbon; will help the layout
* algorithm next time add() is called */
void setItemCountHint(int hint) { m_item_count_hint = hint; }
};
}
#endif

View File

@ -39,6 +39,7 @@ IconButtonWidget::IconButtonWidget(ScaleMode scale_mode, const bool tab_stop,
const bool focusable, IconPathType pathType) : Widget(WTYPE_ICON_BUTTON)
{
m_label = NULL;
m_font = NULL;
m_texture = NULL;
m_highlight_texture = NULL;
m_deactivated_texture = NULL;
@ -54,6 +55,12 @@ IconButtonWidget::IconButtonWidget(ScaleMode scale_mode, const bool tab_stop,
m_icon_path_type = pathType;
}
// -----------------------------------------------------------------------------
IconButtonWidget::~IconButtonWidget()
{
if (m_deactivated_texture != NULL)
m_deactivated_texture->drop();
}
// -----------------------------------------------------------------------------
void IconButtonWidget::add()
{
// ---- Icon
@ -176,13 +183,7 @@ void IconButtonWidget::add()
m_label->setVisible(false);
}
const int max_w = m_label->getAbsolutePosition().getWidth();
if (!word_wrap &&
(int)GUIEngine::getFont()->getDimension(message.c_str()).Width > max_w + 4) // arbitrarily allow for 4 pixels
{
m_label->setOverrideFont( GUIEngine::getSmallFont() );
}
setLabelFont();
#if IRRLICHT_VERSION_MAJOR > 1 || (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8)
m_label->setRightToLeft( translations->isRTLLanguage() );
@ -248,20 +249,12 @@ void IconButtonWidget::setLabel(const stringw& new_label)
if (m_label == NULL) return;
m_label->setText( new_label.c_str() );
const bool word_wrap = (m_properties[PROP_WORD_WRAP] == "true");
const int max_w = m_label->getAbsolutePosition().getWidth();
if (!word_wrap &&
(int)GUIEngine::getFont()->getDimension(new_label.c_str()).Width
> max_w + 4) // arbitrarily allow for 4 pixels
{
m_label->setOverrideFont( GUIEngine::getSmallFont() );
}
else
{
m_label->setOverrideFont( NULL );
}
setLabelFont();
}
// -----------------------------------------------------------------------------
void IconButtonWidget::setLabelFont(irr::gui::ScalableFont* font)
{
m_font = font;
}
// -----------------------------------------------------------------------------
EventPropagation IconButtonWidget::focused(const int playerID)
@ -333,3 +326,29 @@ void IconButtonWidget::setTexture(video::ITexture* texture)
m_texture_h = texture->getSize().Height;
}
}
// -----------------------------------------------------------------------------
void IconButtonWidget::setLabelFont()
{
if (m_font != NULL)
{
m_label->setOverrideFont( m_font );
}
else
{
const bool word_wrap = (m_properties[PROP_WORD_WRAP] == "true");
const int max_w = m_label->getAbsolutePosition().getWidth();
if (!word_wrap &&
(int)GUIEngine::getFont()->getDimension(m_label->getText()).Width
> max_w + 4) // arbitrarily allow for 4 pixels
{
m_label->setOverrideFont( GUIEngine::getSmallFont() );
}
else
{
m_label->setOverrideFont( NULL );
}
}
}

View File

@ -23,7 +23,11 @@
#include <irrString.h>
namespace irr
{
namespace gui { class IGUIStaticText; }
namespace gui
{
class IGUIStaticText;
class ScalableFont;
}
namespace video { class ITexture; }
}
@ -44,6 +48,7 @@ namespace GUIEngine
int m_texture_w, m_texture_h;
video::ITexture* getDeactivatedTexture(video::ITexture* texture);
void setLabelFont();
public:
enum ScaleMode
@ -69,6 +74,7 @@ namespace GUIEngine
friend class Skin;
irr::gui::IGUIStaticText* m_label;
irr::gui::ScalableFont* m_font;
ScaleMode m_scale_mode;
float m_custom_aspect_ratio;
@ -84,7 +90,7 @@ namespace GUIEngine
IconButtonWidget(ScaleMode scale_mode=SCALE_MODE_KEEP_TEXTURE_ASPECT_RATIO, const bool tab_stop=true,
const bool focusable=true, IconPathType pathType=ICON_PATH_TYPE_RELATIVE);
virtual ~IconButtonWidget() {}
virtual ~IconButtonWidget();
/** \brief Implement callback from base class Widget */
virtual void add();
@ -105,6 +111,13 @@ namespace GUIEngine
*/
void setLabel(const irr::core::stringw& new_label);
/**
* \brief Sets the font used to draw the label.
*
* \note Calling this method on a button without label will have no effect
*/
void setLabelFont(irr::gui::ScalableFont* font);
/**
* Change the texture used for this icon.
* \pre At the moment, the new texture must have the same aspct ratio