stk-code_catmod/src/guiengine/widgets/dynamic_ribbon_widget.cpp

1113 lines
38 KiB
C++

// Supertuxkart - a fun racing game with go-kart
// Copyright (C) 2009-2013 Marianne Gagnon
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "guiengine/engine.hpp"
#include "guiengine/widgets/dynamic_ribbon_widget.hpp"
#include "io/file_manager.hpp"
#include "states_screens/state_manager.hpp"
#include "utils/vs.hpp"
#include <IGUIEnvironment.h>
#include <sstream>
#include <iostream>
#include <algorithm>
using namespace GUIEngine;
using namespace irr::core;
using namespace irr::gui;
DynamicRibbonWidget::DynamicRibbonWidget(const bool combo, const bool multi_row) : Widget(WTYPE_DYNAMIC_RIBBON)
{
m_scroll_offset = 0;
m_needed_cols = 0;
m_col_amount = 0;
m_previous_item_count = 0;
m_multi_row = multi_row;
m_combo = combo;
m_has_label = false;
m_left_widget = NULL;
m_right_widget = NULL;
m_check_inside_me = true;
m_supports_multiplayer = true;
m_scrolling_enabled = true;
// by default, set all players to have no selection in this ribbon
for (unsigned int n=0; n<MAX_PLAYER_COUNT; n++)
{
m_selected_item[n] = -1;
}
m_selected_item[0] = 0; // only player 0 has a selection by default
m_item_count_hint = 0;
}
// -----------------------------------------------------------------------------
DynamicRibbonWidget::~DynamicRibbonWidget()
{
if (m_animated_contents)
{
GUIEngine::needsUpdate.remove(this);
}
}
// -----------------------------------------------------------------------------
/** Function that estimates the area (in squared pixels) that ribbon icons
* would take given a number of rows (used to estimate the best number of
* rows)
* \param[out] visibleItems number of items that can be displayed in this
* configuration
* \param[out] takenArea how many square pixels are taken by the icons
* \param[out] itemHeight how high each item would be in this configuration
*/
void estimateIconAreaFor(const int rowCount, const int wantedIconWidth,
const int width, const int height,
const float iconAspectRatio, const int maxIcons,
int* visibleItems, int* takenArea, int* itemHeight)
{
assert(height > 0);
const int row_height = height / rowCount;
float icon_height = (float)row_height;
float icon_width = row_height * iconAspectRatio;
*itemHeight = int(icon_height);
const int icons_per_row = std::min(int(width / icon_width), int(width / wantedIconWidth));
*visibleItems = std::min(maxIcons, icons_per_row * rowCount);
*takenArea = int(*visibleItems * icon_width * icon_height);
}
void DynamicRibbonWidget::add()
{
//printf("****DynamicRibbonWidget::add()****\n");
m_has_label = (m_properties[PROP_LABELS_LOCATION] == "bottom");
assert( m_properties[PROP_LABELS_LOCATION] == "bottom" ||
m_properties[PROP_LABELS_LOCATION] == "each" ||
m_properties[PROP_LABELS_LOCATION] == "none" ||
m_properties[PROP_LABELS_LOCATION] == "");
if (m_has_label)
{
// m_label_height contains the height of ONE text line
m_label_height = GUIEngine::getFontHeight();
}
else
{
m_label_height = 0;
}
// ----- add dynamic label at bottom
if (m_has_label)
{
// leave room for many lines, just in case
rect<s32> label_size = rect<s32>(m_x,
m_y + m_h - m_label_height,
m_x + m_w,
m_y + m_h + m_label_height*5);
m_label = GUIEngine::getGUIEnv()->addStaticText(L" ", label_size, false, true /* word wrap */, NULL, -1);
m_label->setTextAlignment( EGUIA_CENTER, EGUIA_UPPERLEFT );
m_label->setWordWrap(true);
}
// ---- add arrow buttons on each side
if (m_left_widget != NULL)
{
m_left_widget->elementRemoved();
m_right_widget->elementRemoved();
delete m_left_widget;
delete m_right_widget;
}
m_left_widget = new IconButtonWidget(IconButtonWidget::SCALE_MODE_KEEP_TEXTURE_ASPECT_RATIO, false);
m_right_widget = new IconButtonWidget(IconButtonWidget::SCALE_MODE_KEEP_TEXTURE_ASPECT_RATIO, false);
const int average_y = m_y + (m_h - m_label_height)/2;
m_arrows_w = 40;
const int button_h = 50;
// right arrow
rect<s32> right_arrow_location = rect<s32>(m_x + m_w - m_arrows_w,
average_y - button_h/2,
m_x + m_w,
average_y + button_h/2);
m_right_widget->m_x = right_arrow_location.UpperLeftCorner.X;
m_right_widget->m_y = right_arrow_location.UpperLeftCorner.Y;
m_right_widget->m_w = right_arrow_location.getWidth();
m_right_widget->m_h = right_arrow_location.getHeight();
m_right_widget->m_event_handler = this;
m_right_widget->m_focusable = false;
m_right_widget->m_properties[PROP_ID] = "right";
m_right_widget->setImage(GUIEngine::getSkin()->getImage("right_arrow::neutral"));
m_right_widget->add();
m_right_widget->setHighlightedImage(GUIEngine::getSkin()->getImage("right_arrow::focus"));
m_children.push_back( m_right_widget );
// left arrow
rect<s32> left_arrow_location = rect<s32>(m_x,
average_y - button_h/2,
m_x + m_arrows_w,
average_y + button_h/2);
stringw lmessage = "<<";
m_left_widget->m_x = left_arrow_location.UpperLeftCorner.X;
m_left_widget->m_y = left_arrow_location.UpperLeftCorner.Y;
m_left_widget->m_w = left_arrow_location.getWidth();
m_left_widget->m_h = left_arrow_location.getHeight();
m_left_widget->m_event_handler = this;
m_left_widget->m_focusable = false;
m_left_widget->m_properties[PROP_ID] = "left";
m_left_widget->setImage( GUIEngine::getSkin()->getImage("left_arrow::neutral") );
m_left_widget->add();
m_left_widget->setHighlightedImage(GUIEngine::getSkin()->getImage("left_arrow::focus"));
m_children.push_back( m_left_widget );
assert( m_left_widget->ok() );
assert( m_right_widget->ok() );
m_left_widget->m_element->setVisible(true);
// ---- Determine number of rows
// Find children size (and ratio)
m_child_width = atoi(m_properties[PROP_CHILD_WIDTH].c_str());
m_child_height = atoi(m_properties[PROP_CHILD_HEIGHT].c_str());
if (m_child_width <= 0 || m_child_height <= 0)
{
Log::warn("DynamicRibbonWidget", "Ribbon grid widgets require 'child_width' and 'child_height' arguments");
m_child_width = 256;
m_child_height = 256;
}
if (m_multi_row)
{
// determine row amount
const float aspect_ratio = (float)m_child_width / (float)m_child_height;
// const int count = m_items.size();
m_row_amount = -1;
float max_score_so_far = -1;
if (m_h - m_label_height < 0)
{
Log::warn("DynamicRibbonWidget", "The widget is too small for anything to fit in it!!");
m_row_amount = 1;
}
else
{
for (int row_count = 1; row_count < 10; row_count++)
{
int visible_items;
int taken_area;
int item_height;
int item_count = m_item_count_hint;
if (item_count < 1)
{
item_count = m_items.size();
}
if (item_count < 1)
{
// No idea so make assumptions
item_count = 20;
}
estimateIconAreaFor(row_count, m_child_width, m_w, m_h - m_label_height,
aspect_ratio, item_count, &visible_items, &taken_area, &item_height);
// FIXME: this system to determine the best number of columns is really complicated!
// the score is computed from taken screen area AND visible item count.
// A number of rows that allows for the screen space to be used a lot will
// get a better score. A number of rows that allows showing very few items
// will be penalized. A configuration that makes items much smaller than
// requested in the XML file will also be penalized.
float ratio = (float)item_height / (float)m_child_height;
// huge icons not so good either
if (ratio > 1.0f)
{
ratio = 1.0f - ratio/5.0f;
if (ratio < 0.0f) ratio = 0.0f;
}
float total_area = (float)(m_w * m_h);
const float score = log(2.0f*visible_items) *
std::min(ratio, 1.0f) * std::min(taken_area/total_area, 1.0f);
//std::cout << " " << row_count << " rows : " << visible_items << " visible items; area = "
// << taken_area << "; size penalty = " << std::min((float)item_height / (float)m_child_height, 1.0f)
// << "; score = " << score << "\n";
if (score > max_score_so_far)
{
m_row_amount = row_count;
max_score_so_far = score;
}
}
assert(m_row_amount != -1);
}
if (m_properties[PROP_MAX_ROWS].size() > 0)
{
const int max_rows = atoi(m_properties[PROP_MAX_ROWS].c_str());
if (max_rows < 1)
{
std::cout << "/!\\ WARNING : the 'max_rows' property must be an integer greater than zero."
<< " Ingoring current value '" << m_properties[PROP_MAX_ROWS] << "'\n";
}
else
{
if (m_row_amount > max_rows) m_row_amount = max_rows;
}
}
}
else
{
m_row_amount = 1;
}
assert( m_left_widget->ok() );
assert( m_right_widget->ok() );
m_left_widget->m_element->setVisible(true);
// get and build a list of IDs (by now we may not yet know everything about items,
// but we need to get IDs *now* in order for tabbing to work.
m_ids.resize(m_row_amount);
for (int i=0; i<m_row_amount; i++)
{
m_ids[i] = getNewID();
//std::cout << "ribbon : getNewID returns " << m_ids[i] << std::endl;
}
buildInternalStructure();
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::buildInternalStructure()
{
//printf("****DynamicRibbonWidget::buildInternalStructure()****\n");
// ---- Clean-up what was previously there
for (unsigned int i=0; i<m_children.size(); i++)
{
IGUIElement* elem = m_children[i].m_element;
if (elem != NULL && m_children[i].m_type == WTYPE_RIBBON)
{
elem->remove();
m_children.erase(i);
i--;
}
}
m_rows.clearWithoutDeleting(); // rows already deleted above, don't double-delete
m_left_widget->m_element->setVisible(true);
assert( m_left_widget->ok() );
assert( m_right_widget->ok() );
// ---- determine column amount
const float row_height = (float)(m_h - m_label_height)/(float)m_row_amount;
float ratio_zoom = (float)row_height / (float)(m_child_height - m_label_height);
m_col_amount = (int)roundf( m_w / ( m_child_width*ratio_zoom ) );
// ajust column amount to not add more item slots than we actually need
const int item_count = m_items.size();
//std::cout << "item_count=" << item_count << ", row_amount*m_col_amount=" << m_row_amount*m_col_amount << std::endl;
if (m_row_amount*m_col_amount > item_count)
{
m_col_amount = (int)ceil((float)item_count/(float)m_row_amount);
//std::cout << "Adjusting m_col_amount to be " << m_col_amount << std::endl;
}
assert( m_left_widget->ok() );
assert( m_right_widget->ok() );
// Hide arrows when everything is visible
if (item_count <= m_row_amount*m_col_amount)
{
m_scrolling_enabled = false;
m_left_widget->m_element->setVisible(false);
m_right_widget->m_element->setVisible(false);
}
else
{
m_scrolling_enabled = true;
m_left_widget->m_element->setVisible(true);
m_right_widget->m_element->setVisible(true);
}
// ---- add rows
int added_item_count = 0;
for (int n=0; n<m_row_amount; n++)
{
RibbonWidget* ribbon;
if (m_combo)
{
ribbon = new RibbonWidget(RIBBON_COMBO);
}
else
{
ribbon = new RibbonWidget(RIBBON_TOOLBAR);
}
ribbon->setListener(this);
ribbon->m_reserved_id = m_ids[n];
ribbon->m_x = m_x + (m_scrolling_enabled ? m_arrows_w : 0);
ribbon->m_y = m_y + (int)(n*row_height);
ribbon->m_w = m_w - (m_scrolling_enabled ? m_arrows_w*2 : 0);
ribbon->m_h = (int)(row_height);
ribbon->m_type = WTYPE_RIBBON;
std::stringstream name;
name << this->m_properties[PROP_ID] << "_row" << n;
ribbon->m_properties[PROP_ID] = name.str();
ribbon->m_event_handler = this;
// add columns
for (int i=0; i<m_col_amount; i++)
{
// stretch the *texture* within the widget (and the widget has the right aspect ratio)
// (Yeah, that's complicated, but screenshots are saved compressed horizontally so it's hard to be clean)
IconButtonWidget* icon = new IconButtonWidget(IconButtonWidget::SCALE_MODE_STRETCH, false, true);
icon->m_properties[PROP_ICON]="textures/transparence.png";
// set size to get proper ratio (as most textures are saved scaled down to 256x256)
icon->m_properties[PROP_WIDTH] = m_properties[PROP_CHILD_WIDTH];
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());
// 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)
if (m_properties[PROP_LABELS_LOCATION] == "each") icon->m_text = " ";
// std::cout << "ribbon text = " << m_properties[PROP_TEXT].c_str() << std::endl;
ribbon->m_children.push_back( icon );
added_item_count++;
// stop adding columns when we have enough items
if (added_item_count == item_count)
{
assert(!m_scrolling_enabled); // we can see all items, so scrolling must be off
break;
}
else if (added_item_count > item_count)
{
assert(false);
break;
}
}
m_children.push_back( ribbon );
m_rows.push_back( ribbon );
ribbon->add();
// stop filling rows when we have enough items
if (added_item_count == item_count)
{
assert(!m_scrolling_enabled); // we can see all items, so scrolling must be off
break;
}
}
#ifdef DEBUG
if (!m_scrolling_enabled)
{
// debug checks
int childrenCount = 0;
for (unsigned int n=0; n<m_rows.size(); n++)
{
childrenCount += m_rows[n].m_children.size();
}
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_code_name = code_name;
desc.m_sshot_file = image_file;
desc.m_badges = badges;
desc.m_animated = false;
desc.m_image_path_type = image_path_type;
m_items.push_back(desc);
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::addAnimatedItem( const irr::core::stringw& user_name, const std::string& code_name,
const std::vector<std::string>& image_files, const float time_per_frame,
const unsigned int badges, IconButtonWidget::IconPathType image_path_type )
{
ItemDescription desc;
desc.m_user_name = user_name;
desc.m_code_name = code_name;
desc.m_all_images = image_files;
desc.m_badges = badges;
desc.m_animated = true;
desc.m_curr_time = 0.0f;
desc.m_time_per_frame = time_per_frame;
desc.m_image_path_type = image_path_type;
m_items.push_back(desc);
if (!m_animated_contents)
{
m_animated_contents = true;
/*
FIXME: remove this unclean thing, I think irrlicht provides this feature:
virtual void IGUIElement::OnPostRender (u32 timeMs)
\brief animate the element and its children.
*/
GUIEngine::needsUpdate.push_back(this);
}
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::clearItems()
{
m_items.clear();
m_animated_contents = false;
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::elementRemoved()
{
Widget::elementRemoved();
m_previous_item_count = 0;
m_rows.clearWithoutDeleting();
m_left_widget = NULL;
m_right_widget = NULL;
m_hover_listeners.clearAndDeleteAll();
}
#if 0
#pragma mark -
#pragma mark Getters
#endif
const std::string& DynamicRibbonWidget::getSelectionIDString(const int playerID)
{
RibbonWidget* row = (RibbonWidget*)(m_rows.size() == 1 ? m_rows.get(0) : getSelectedRibbon(playerID));
if (row != NULL) return row->getSelectionIDString(playerID);
static const std::string nothing = "";
return nothing;
}
// -----------------------------------------------------------------------------
irr::core::stringw DynamicRibbonWidget::getSelectionText(const int playerID)
{
RibbonWidget* row = (RibbonWidget*)(m_rows.size() == 1 ? m_rows.get(0) : getSelectedRibbon(playerID));
if (row != NULL) return row->getSelectionText(playerID);
static const irr::core::stringw nothing = "";
return nothing;
}
// -----------------------------------------------------------------------------
RibbonWidget* DynamicRibbonWidget::getRowContaining(Widget* w)
{
for_var_in (RibbonWidget*, row, m_rows)
{
if (row != NULL)
{
if (row->m_children.contains( w ) ) return row;
}
}
return NULL;
}
// -----------------------------------------------------------------------------
RibbonWidget* DynamicRibbonWidget::getSelectedRibbon(const int playerID)
{
for_var_in (RibbonWidget*, row, m_rows)
{
if (GUIEngine::isFocusedForPlayer(row, playerID))
{
return row;
}
}
return NULL;
}
#if 0
#pragma mark -
#pragma mark Event Handling
#endif
void DynamicRibbonWidget::registerHoverListener(DynamicRibbonHoverListener* listener)
{
m_hover_listeners.push_back(listener);
}
// -----------------------------------------------------------------------------
EventPropagation DynamicRibbonWidget::rightPressed(const int playerID)
{
if (m_deactivated) return EVENT_LET;
RibbonWidget* w = getSelectedRibbon(playerID);
if (w != NULL)
{
updateLabel();
propagateSelection();
const int listenerAmount = m_hover_listeners.size();
for (int n=0; n<listenerAmount; n++)
{
m_hover_listeners[n].onSelectionChanged(this, getSelectedRibbon(playerID)->getSelectionIDString(playerID),
getSelectedRibbon(playerID)->getSelectionText(playerID), playerID);
}
}
//std::cout << "rightpressed (dynamic ribbon) " << m_properties[PROP_ID] << "\n";
assert(m_rows.size() >= 1);
if (m_rows[0].m_ribbon_type == RIBBON_TOOLBAR) return EVENT_BLOCK;
//std::cout << " rightpressed returning EVENT_LET\n";
return EVENT_LET;
}
// -----------------------------------------------------------------------------
EventPropagation DynamicRibbonWidget::leftPressed(const int playerID)
{
if (m_deactivated) return EVENT_LET;
RibbonWidget* w = getSelectedRibbon(playerID);
if (w != NULL)
{
updateLabel();
propagateSelection();
for_var_in (DynamicRibbonHoverListener*, listener, m_hover_listeners)
{
listener->onSelectionChanged(this, w->getSelectionIDString(playerID),
w->getSelectionText(playerID), playerID);
}
}
assert(m_rows.size() >= 1);
if (m_rows[0].m_ribbon_type == RIBBON_TOOLBAR) return EVENT_BLOCK;
return EVENT_LET;
}
// -----------------------------------------------------------------------------
EventPropagation DynamicRibbonWidget::transmitEvent(Widget* w,
const std::string& originator,
const int playerID)
{
assert(m_magic_number == 0xCAFEC001);
if (m_deactivated) return EVENT_LET;
if (originator=="left")
{
scroll(-1);
return EVENT_BLOCK;
}
if (originator=="right")
{
scroll(1);
return EVENT_BLOCK;
}
// find selection in current ribbon
if (m_combo)
{
RibbonWidget* selected_ribbon = (RibbonWidget*)getSelectedRibbon(playerID);
if (selected_ribbon != NULL)
{
m_selected_item[playerID] = selected_ribbon->m_selection[playerID] + m_scroll_offset;
if (m_selected_item[playerID] >= (int)m_items.size()) m_selected_item[playerID] -= m_items.size();
}
}
return EVENT_LET;
}
// -----------------------------------------------------------------------------
EventPropagation DynamicRibbonWidget::mouseHovered(Widget* child, const int playerID)
{
if (m_deactivated) return EVENT_LET;
//std::cout << "DynamicRibbonWidget::mouseHovered " << playerID << std::endl;
updateLabel();
propagateSelection();
if (getSelectedRibbon(playerID) != NULL)
{
for_var_in (DynamicRibbonHoverListener*, listener, m_hover_listeners)
{
listener->onSelectionChanged(this, getSelectedRibbon(playerID)->getSelectionIDString(playerID),
getSelectedRibbon(playerID)->getSelectionText(playerID), playerID);
}
}
return EVENT_BLOCK;
}
// -----------------------------------------------------------------------------
EventPropagation DynamicRibbonWidget::focused(const int playerID)
{
Widget::focused(playerID);
updateLabel();
if (getSelectedRibbon(playerID)->getSelectionIDString(playerID) == "")
{
//fprintf(stderr, "[DynamicRibbonWidget] WARNING: Can't find selection for player %i, selecting first item\n", playerID);
getSelectedRibbon(playerID)->setSelection(0, playerID);
}
for_var_in (DynamicRibbonHoverListener*, listener, m_hover_listeners)
{
listener->onSelectionChanged(this, getSelectedRibbon(playerID)->getSelectionIDString(playerID),
getSelectedRibbon(playerID)->getSelectionText(playerID), playerID);
}
return EVENT_LET;
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::onRibbonWidgetScroll(const int delta_x)
{
scroll(delta_x);
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::onRibbonWidgetFocus(RibbonWidget* emitter, const int playerID)
{
if (m_deactivated) return;
if (emitter->m_selection[playerID] >= (int)emitter->m_children.size())
{
emitter->m_selection[playerID] = emitter->m_children.size()-1;
}
updateLabel(emitter);
if (emitter->getSelectionIDString(playerID) == "")
{
//fprintf(stderr, "[DynamicRibbonWidget] WARNING: Can't find selection for player %i, selecting first item\n", playerID);
emitter->setSelection(0, playerID);
}
for_var_in(DynamicRibbonHoverListener*, listener, m_hover_listeners)
{
listener->onSelectionChanged(this, emitter->getSelectionIDString(playerID),
emitter->getSelectionText(playerID), playerID);
}
}
#if 0
#pragma mark -
#pragma mark Setters / Actions
#endif
void DynamicRibbonWidget::scroll(const int x_delta)
{
if (m_deactivated) return;
// Refuse to scroll when everything is visible
if ((int)m_items.size() <= m_row_amount*m_col_amount) return;
m_scroll_offset += x_delta;
const int max_scroll = std::max(m_col_amount, m_needed_cols) - 1;
if (m_scroll_offset < 0) m_scroll_offset = max_scroll;
else if (m_scroll_offset > max_scroll) m_scroll_offset = 0;
updateItemDisplay();
// update selection markers in child ribbon
if (m_combo)
{
for (unsigned int n=0; n<MAX_PLAYER_COUNT; n++)
{
RibbonWidget* ribbon = m_rows.get(0); // there is a single row when we can select items
int id = m_selected_item[n] - m_scroll_offset;
if (id < 0) id += m_items.size();
ribbon->setSelection(id, n);
}
}
}
// -----------------------------------------------------------------------------
/** DynamicRibbonWidget is made of several ribbons; each of them thus has
its own selection independently of each other. To keep a grid feeling
(i.e. you remain in the same column when pressing up/down), this method is
used to ensure that all children ribbons always select the same column */
void DynamicRibbonWidget::propagateSelection()
{
for (unsigned int p=0; p<MAX_PLAYER_COUNT; p++)
{
// find selection in current ribbon
RibbonWidget* selected_ribbon = (RibbonWidget*)getSelectedRibbon(p);
if (selected_ribbon == NULL) continue;
const int relative_selection = selected_ribbon->m_selection[p];
float where = 0.0f;
if (selected_ribbon->m_children.size() > 1)
{
where = (float)relative_selection / (float)(selected_ribbon->m_children.size() - 1);
}
else
{
where = 0.0f;
}
if (where < 0.0f) where = 0.0f;
else if (where > 1.0f) where = 1.0f;
if (m_combo)
{
m_selected_item[p] = relative_selection + m_scroll_offset;
if (m_selected_item[p] >= (int)m_items.size()) m_selected_item[p] -= m_items.size();
}
// set same selection in all ribbons
for_var_in (RibbonWidget*, ribbon, m_rows)
{
if (ribbon != selected_ribbon)
{
ribbon->m_selection[p] = (int)roundf(where*(ribbon->m_children.size()-1));
ribbon->updateSelection();
}
}
}
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::updateLabel(RibbonWidget* from_this_ribbon)
{
if (!m_has_label) return;
// only the master player can update the label
const int playerID = PLAYER_ID_GAME_MASTER;
RibbonWidget* row = from_this_ribbon ? from_this_ribbon : (RibbonWidget*)getSelectedRibbon(playerID);
if (row == NULL) return;
std::string selection_id = row->getSelectionIDString(playerID);
const int amount = m_items.size();
for (int n=0; n<amount; n++)
{
if (m_items[n].m_code_name == selection_id)
{
m_label->setText( stringw(m_items[n].m_user_name.c_str()).c_str() );
return;
}
}
if (selection_id == RibbonWidget::NO_ITEM_ID) m_label->setText( L"" );
else m_label->setText( L"Unknown Item" );
}
// -----------------------------------------------------------------------------
/** Set to 1 if you wish information about item placement within the ribbon to be printed */
#define CHATTY_ABOUT_ITEM_PLACEMENT 0
void DynamicRibbonWidget::updateItemDisplay()
{
// ---- Check if we need to update the number of icons in the ribbon
if ((int)m_items.size() != m_previous_item_count)
{
buildInternalStructure();
m_previous_item_count = m_items.size();
}
// ---- some variables
int icon_id = 0;
const int row_amount = m_rows.size();
const int item_amount = m_items.size();
//FIXME: isn't this set by 'buildInternalStructure' already?
m_needed_cols = (int)ceil( (float)item_amount / (float)row_amount );
//const int max_scroll = std::max(m_col_amount, m_needed_cols) - 1;
// the number of items that fit perfectly the number of rows we have
// (this value will be useful to compute scrolling)
int fitting_item_amount = (m_scrolling_enabled ? m_needed_cols * row_amount : m_items.size());
// ---- to determine which items go in which cell of the dynamic ribbon now,
// we create a temporary 2D table and fill them with the ID of the item
// they need to display.
//int item_placement[row_amount][m_needed_cols];
std::vector<std::vector<int> > item_placement;
item_placement.resize(row_amount);
for(int i=0; i<row_amount; i++)
item_placement[i].resize(m_needed_cols);
int counter = 0;
#if CHATTY_ABOUT_ITEM_PLACEMENT
std::cout << m_items.size() << " items to be placed:\n{\n";
#endif
for (int c=0; c<m_needed_cols; c++)
{
for (int r=0; r<row_amount; r++)
{
#if CHATTY_ABOUT_ITEM_PLACEMENT
std::cout << " ";
#endif
const int items_in_row = m_rows[r].m_children.size();
if (c >= items_in_row)
{
item_placement[r][c] = -1;
#if CHATTY_ABOUT_ITEM_PLACEMENT
std::cout << item_placement[r][c] << " ";
#endif
continue;
}
int newVal = counter + m_scroll_offset*row_amount;
while (newVal >= fitting_item_amount) newVal -= fitting_item_amount;
item_placement[r][c] = newVal;
#if CHATTY_ABOUT_ITEM_PLACEMENT
std::cout << newVal << " ";
#endif
counter++;
}
#if CHATTY_ABOUT_ITEM_PLACEMENT
std::cout << "\n";
#endif
}
#if CHATTY_ABOUT_ITEM_PLACEMENT
std::cout << "}\n";
#endif
// ---- iterate through the rows, and set the items of each row to match those of the table
for (int n=0; n<row_amount; n++)
{
RibbonWidget& row = m_rows[n];
//std::cout << "Row " << n << "\n{\n";
const unsigned int items_in_row = row.m_children.size();
for (unsigned int i=0; i<items_in_row; i++)
{
IconButtonWidget* icon = dynamic_cast<IconButtonWidget*>(&row.m_children[i]);
assert(icon != NULL);
//FIXME : it is a bit hackish
if(i < item_placement[n].size())
{
icon_id = item_placement[n][i];
if (icon_id < item_amount && icon_id != -1)
{
std::string item_icon = (m_items[icon_id].m_animated ?
m_items[icon_id].m_all_images[0] :
m_items[icon_id].m_sshot_file);
icon->setImage( item_icon.c_str(), m_items[icon_id].m_image_path_type );
icon->m_properties[PROP_ID] = m_items[icon_id].m_code_name;
icon->setLabel(m_items[icon_id].m_user_name);
icon->m_text = m_items[icon_id].m_user_name;
icon->m_badges = m_items[icon_id].m_badges;
//std::cout << " item " << i << " is " << m_items[icon_id].m_code_name << "\n";
//std::wcout << L"Setting widget text '" << icon->m_text.c_str() << L"'\n";
// if the ribbon has no "ribbon-wide" label, call will do nothing
row.setLabel(i, m_items[icon_id].m_user_name);
}
else
{
icon->setImage( "textures/transparence.png", IconButtonWidget::ICON_PATH_TYPE_RELATIVE );
icon->resetAllBadges();
icon->m_properties[PROP_ID] = RibbonWidget::NO_ITEM_ID;
//std::cout << " item " << i << " is a FILLER\n";
}
}
} // next column
} // next row
}
// -----------------------------------------------------------------------------
void DynamicRibbonWidget::update(float dt)
{
const int row_amount = m_rows.size();
for (int n=0; n<row_amount; n++)
{
RibbonWidget& row = m_rows[n];
const int items_in_row = row.m_children.size();
for (int i=0; i<items_in_row; i++)
{
int col_scroll = i + m_scroll_offset;
int item_id = (col_scroll)*row_amount + n;
if (item_id >= (int)m_items.size()) item_id -= m_items.size();
assert(item_id >= 0);
assert(item_id < (int)m_items.size());
//m_items[icon_id].
if (m_items[item_id].m_animated)
{
const int frameBefore = (int)(m_items[item_id].m_curr_time / m_items[item_id].m_time_per_frame);
m_items[item_id].m_curr_time += dt;
int frameAfter = (int)(m_items[item_id].m_curr_time / m_items[item_id].m_time_per_frame);
if (frameAfter == frameBefore) continue; // no frame change yet
if (frameAfter >= (int)m_items[item_id].m_all_images.size())
{
m_items[item_id].m_curr_time = 0;
frameAfter = 0;
}
IconButtonWidget* icon = dynamic_cast<IconButtonWidget*>(&row.m_children[i]);
icon->setImage( m_items[item_id].m_all_images[frameAfter].c_str() );
}
}
}
}
// -----------------------------------------------------------------------------
bool DynamicRibbonWidget::findItemInRows(const char* name, int* p_row, int* p_id)
{
int row = -1;
int id = -1;
for (unsigned int r=0; r<m_rows.size(); r++)
{
id = m_rows[r].findItemNamed(name);
if (id > -1)
{
row = r;
break;
}
}
*p_row = row;
*p_id = id;
return (row != -1);
}
// -----------------------------------------------------------------------------
bool DynamicRibbonWidget::setSelection(int item_id, const int playerID,
const bool focusIt,
bool evenIfDeactivated)
{
if (m_deactivated && !evenIfDeactivated) return false;
//printf("****DynamicRibbonWidget::setSelection()****\n");
if ((unsigned int)item_id >= m_items.size()) return false;
m_selected_item[playerID] = item_id;
const std::string& name = m_items[item_id].m_code_name;
int row;
int id;
int iterations = 0; // a safeguard to avoid infinite loops (should not happen normally)
while (!findItemInRows(name.c_str(), &row, &id))
{
// if we get here it means the item is scrolled out. Try to find it.
scroll(1);
if (iterations > 50)
{
assert(false);
std::cerr << "DynamicRibbonWidget::setSelection cannot find item " << item_id << " (" << name.c_str() << ")\n";
return false;
}
iterations++;
}
//std::cout << "Player " << playerID << " has item " << item_id << " (" << name.c_str() << ") in row " << row << std::endl;
m_rows[row].setSelection(id, playerID);
if (focusIt)
{
m_rows[row].setFocusForPlayer(playerID);
}
else
{
// focusing the item is enough to trigger the selection listeners; however if we're setting selection
// without focusing they won't be noticed, which is why we notice them here
const int listenerAmount = m_hover_listeners.size();
for (int n=0; n<listenerAmount; n++)
{
m_hover_listeners[n].onSelectionChanged(this,
name,
m_rows[row].getSelectionText(playerID),
playerID);
}
}
propagateSelection();
return true;
}
// ----------------------------------------------------------------------------
bool DynamicRibbonWidget::setSelection(const std::string &item_codename,
const int playerID, const bool focusIt,
bool evenIfDeactivated)
{
if (m_deactivated && !evenIfDeactivated) return false;
const int item_count = m_items.size();
for (int n=0; n<item_count; n++)
{
if (m_items[n].m_code_name == item_codename)
{
return setSelection(n, playerID, focusIt, evenIfDeactivated);
}
}
return false;
}
// -----------------------------------------------------------------------------