Improved the pointer's(mouse) widget selection method, so that instead of using a bounding box to know if the pointer is on top of a widget, it draws the widget's rect into OpenGL'sselection buffer; this way, the widgets can be selected with precision regardless of the rotation or if it has rounded corners or not.

git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/trunk/supertuxkart@1612 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
cosmosninja 2008-03-01 21:13:57 +00:00
parent 32d4e3f117
commit 573694016c
3 changed files with 250 additions and 175 deletions

View File

@ -103,15 +103,9 @@ void Widget::update(const float DELTA)
*/
glClear( GL_STENCIL_BUFFER_BIT );
glTranslatef ( (GLfloat)(m_x + m_width * 0.5f), (GLfloat)(m_y + m_height * 0.5f), 0);
m_rotation_angle += m_rotation_speed * DELTA;
if( m_enable_rotation )
{
glRotatef( (GLfloat)m_rotation_angle, 0.0f, 0.0f, (GLfloat)1.0f );
}
if( m_enable_rotation ) m_rotation_angle += m_rotation_speed * DELTA;
applyTransformations();
/*Handle delta time dependant features*/
if(m_text_scale > MIN_TEXT_SCALE)
@ -370,6 +364,145 @@ void Widget::update(const float DELTA)
glPopMatrix();
}
//-----------------------------------------------------------------------------
void Widget::resizeToText()
{
if( !m_text.empty() )
{
float left, right, bottom, top;
m_font->getBBox(m_text.c_str(), m_text_size, false, &left, &right, &bottom, &top);
const int TEXT_WIDTH = (int)(right - left);
const int TEXT_HEIGHT = (int)(top - bottom);
if( TEXT_WIDTH > m_width ) m_width = TEXT_WIDTH;
if( TEXT_HEIGHT > m_height ) m_height = TEXT_HEIGHT;
}
}
//-----------------------------------------------------------------------------
/* Please note that this function only lightens 'non-light' colors */
void Widget::lightenColor()
{
if(m_rect_color == WGT_GRAY)
{
m_rect_color = WGT_LIGHT_GRAY;
}
if(m_rect_color == WGT_BLACK)
{
m_rect_color = WGT_LIGHT_BLACK;
}
else if (m_rect_color == WGT_YELLOW)
{
m_rect_color = WGT_LIGHT_YELLOW;
}
else if (m_rect_color == WGT_RED)
{
m_rect_color = WGT_LIGHT_RED;
}
else if (m_rect_color == WGT_GREEN)
{
m_rect_color = WGT_LIGHT_GREEN;
}
else if (m_rect_color == WGT_BLUE)
{
m_rect_color = WGT_LIGHT_BLUE;
}
else if (m_rect_color == WGT_TRANS_GRAY)
{
m_rect_color = WGT_LIGHT_TRANS_GRAY;
}
else if (m_rect_color == WGT_TRANS_BLACK)
{
m_rect_color = WGT_LIGHT_TRANS_BLACK;
}
else if (m_rect_color == WGT_TRANS_YELLOW)
{
m_rect_color = WGT_LIGHT_TRANS_YELLOW;
}
else if (m_rect_color == WGT_TRANS_RED)
{
m_rect_color = WGT_LIGHT_TRANS_RED;
}
else if (m_rect_color == WGT_TRANS_GREEN)
{
m_rect_color = WGT_LIGHT_TRANS_GREEN;
}
else if (m_rect_color == WGT_TRANS_BLUE)
{
m_rect_color = WGT_LIGHT_TRANS_BLUE;
}
}
//-----------------------------------------------------------------------------
/* Please note that this function only darkens 'light' colors. */
void Widget::darkenColor()
{
if(m_rect_color == WGT_LIGHT_GRAY)
{
m_rect_color = WGT_GRAY;
}
if(m_rect_color == WGT_LIGHT_BLACK)
{
m_rect_color = WGT_BLACK;
}
else if (m_rect_color == WGT_LIGHT_YELLOW)
{
m_rect_color = WGT_YELLOW;
}
else if (m_rect_color == WGT_LIGHT_RED)
{
m_rect_color = WGT_RED;
}
else if (m_rect_color == WGT_LIGHT_GREEN)
{
m_rect_color = WGT_GREEN;
}
else if (m_rect_color == WGT_LIGHT_BLUE)
{
m_rect_color = WGT_BLUE;
}
else if (m_rect_color == WGT_LIGHT_TRANS_GRAY)
{
m_rect_color = WGT_TRANS_GRAY;
}
else if (m_rect_color == WGT_LIGHT_TRANS_BLACK)
{
m_rect_color = WGT_TRANS_BLACK;
}
else if (m_rect_color == WGT_LIGHT_TRANS_YELLOW)
{
m_rect_color = WGT_TRANS_YELLOW;
}
else if (m_rect_color == WGT_LIGHT_TRANS_RED)
{
m_rect_color = WGT_TRANS_RED;
}
else if (m_rect_color == WGT_LIGHT_TRANS_GREEN)
{
m_rect_color = WGT_TRANS_GREEN;
}
else if (m_rect_color == WGT_LIGHT_TRANS_BLUE)
{
m_rect_color = WGT_TRANS_BLUE;
}
}
//-----------------------------------------------------------------------------
void Widget::setFont( const WidgetFont FONT )
{
switch( FONT )
{
case WGT_FONT_GUI:
m_font = font_gui;
break;
case WGT_FONT_RACE:
m_font = font_race;
break;
};
}
/** Initialize a display list containing a rectangle that can have rounded
* corners, with texture coordinates to properly apply a texture
* map to the rectangle as though the corners were not rounded . Returns
@ -541,140 +674,13 @@ bool Widget::createRect(int radius)
}
//-----------------------------------------------------------------------------
void Widget::resizeToText()
void Widget::applyTransformations()
{
if( !m_text.empty() )
glTranslatef ( (GLfloat)(m_x + m_width * 0.5f), (GLfloat)(m_y + m_height * 0.5f), 0);
if( m_enable_rotation )
{
float left, right, bottom, top;
m_font->getBBox(m_text.c_str(), m_text_size, false, &left, &right, &bottom, &top);
const int TEXT_WIDTH = (int)(right - left);
const int TEXT_HEIGHT = (int)(top - bottom);
if( TEXT_WIDTH > m_width ) m_width = TEXT_WIDTH;
if( TEXT_HEIGHT > m_height ) m_height = TEXT_HEIGHT;
glRotatef( (GLfloat)m_rotation_angle, 0.0f, 0.0f, (GLfloat)1.0f );
}
}
//-----------------------------------------------------------------------------
/* Please note that this function only lightens 'non-light' colors */
void Widget::lightenColor()
{
if(m_rect_color == WGT_GRAY)
{
m_rect_color = WGT_LIGHT_GRAY;
}
if(m_rect_color == WGT_BLACK)
{
m_rect_color = WGT_LIGHT_BLACK;
}
else if (m_rect_color == WGT_YELLOW)
{
m_rect_color = WGT_LIGHT_YELLOW;
}
else if (m_rect_color == WGT_RED)
{
m_rect_color = WGT_LIGHT_RED;
}
else if (m_rect_color == WGT_GREEN)
{
m_rect_color = WGT_LIGHT_GREEN;
}
else if (m_rect_color == WGT_BLUE)
{
m_rect_color = WGT_LIGHT_BLUE;
}
else if (m_rect_color == WGT_TRANS_GRAY)
{
m_rect_color = WGT_LIGHT_TRANS_GRAY;
}
else if (m_rect_color == WGT_TRANS_BLACK)
{
m_rect_color = WGT_LIGHT_TRANS_BLACK;
}
else if (m_rect_color == WGT_TRANS_YELLOW)
{
m_rect_color = WGT_LIGHT_TRANS_YELLOW;
}
else if (m_rect_color == WGT_TRANS_RED)
{
m_rect_color = WGT_LIGHT_TRANS_RED;
}
else if (m_rect_color == WGT_TRANS_GREEN)
{
m_rect_color = WGT_LIGHT_TRANS_GREEN;
}
else if (m_rect_color == WGT_TRANS_BLUE)
{
m_rect_color = WGT_LIGHT_TRANS_BLUE;
}
}
//-----------------------------------------------------------------------------
/* Please note that this function only darkens 'light' colors. */
void Widget::darkenColor()
{
if(m_rect_color == WGT_LIGHT_GRAY)
{
m_rect_color = WGT_GRAY;
}
if(m_rect_color == WGT_LIGHT_BLACK)
{
m_rect_color = WGT_BLACK;
}
else if (m_rect_color == WGT_LIGHT_YELLOW)
{
m_rect_color = WGT_YELLOW;
}
else if (m_rect_color == WGT_LIGHT_RED)
{
m_rect_color = WGT_RED;
}
else if (m_rect_color == WGT_LIGHT_GREEN)
{
m_rect_color = WGT_GREEN;
}
else if (m_rect_color == WGT_LIGHT_BLUE)
{
m_rect_color = WGT_BLUE;
}
else if (m_rect_color == WGT_LIGHT_TRANS_GRAY)
{
m_rect_color = WGT_TRANS_GRAY;
}
else if (m_rect_color == WGT_LIGHT_TRANS_BLACK)
{
m_rect_color = WGT_TRANS_BLACK;
}
else if (m_rect_color == WGT_LIGHT_TRANS_YELLOW)
{
m_rect_color = WGT_TRANS_YELLOW;
}
else if (m_rect_color == WGT_LIGHT_TRANS_RED)
{
m_rect_color = WGT_TRANS_RED;
}
else if (m_rect_color == WGT_LIGHT_TRANS_GREEN)
{
m_rect_color = WGT_TRANS_GREEN;
}
else if (m_rect_color == WGT_LIGHT_TRANS_BLUE)
{
m_rect_color = WGT_TRANS_BLUE;
}
}
//-----------------------------------------------------------------------------
void Widget::setFont( const WidgetFont FONT )
{
switch( FONT )
{
case WGT_FONT_GUI:
m_font = font_gui;
break;
case WGT_FONT_RACE:
m_font = font_race;
break;
};
}

View File

@ -183,19 +183,22 @@ class Widget
void update(const float DELTA);
void resizeToText(); //This checks if the widget is smaller than the
//text, and if so, changes the width and height.
bool createRect(int radius);
/* Time limited features' functions. */
void pulse() {m_text_scale = MAX_TEXT_SCALE;}
/* Convenience functions. */
void resizeToText(); //This checks if the widget is smaller than the
//text, and if so, changes the width and height.
void lightenColor();
void darkenColor();
void setFont( const WidgetFont FONT);
/* Functions created simply to organize the code */
bool createRect(int radius);
void applyTransformations();
};
#endif

View File

@ -20,6 +20,12 @@
#include "user_config.hpp"
#ifdef __APPLE__
# include <OpenGL/glu.h>
#else
# include <GL/glu.h>
#endif
#include <iostream>
#include <cstdlib>
#include <iterator>
@ -1247,40 +1253,100 @@ void WidgetManager::darkenWgtColor(const int TOKEN)
*/
int WidgetManager::handlePointer(const int X, const int Y )
{
//Search if the given x and y positions are on top of any widget. Please
//note that the bounding box for each widget is used instead of the
//real widget shape.
const int NUM_WGTS = m_widgets.size();
if( NUM_WGTS < 1 ) return WGT_NONE;
//The search starts with the current selected widget(since it's most
//probable that the mouse is on top of it )
const int NUM_WIDGETS = (int)m_widgets.size();
//OpenGL provides a mechanism to select objects; simply 'draw' named
//objects, and for each non-culled visible object a 'hit' will be saved
//into a selection buffer. Objects are named by using OpenGL's name
//stack. The information in each hit is the number of names in the name
//stack, two hard-to-explain depth values that aren't used in this
//function, and the contents of the name stack at the time of the hit.
if( m_selected_wgt_token != WGT_NONE )
{
const int SELECTED_WGT_ID = findId(m_selected_wgt_token);
//This function loads 1 name into the stack (because if you pop an empty
//stack you get an error), then uses glLoadName (which is a shortcut for
//popping then pushing) to change the name. That means that each time a
//hit is recorded, only the last drawn widget will be on the name stack.
if(( X > m_widgets[SELECTED_WGT_ID].widget->m_x ) &&
( X < m_widgets[SELECTED_WGT_ID].widget->m_x + m_widgets[SELECTED_WGT_ID].widget->m_width ) &&
( Y > m_widgets[SELECTED_WGT_ID].widget->m_y ) &&
( Y < m_widgets[SELECTED_WGT_ID].widget->m_y + m_widgets[SELECTED_WGT_ID].widget->m_height ))
{
return WGT_NONE;
}
}
const int HIT_SIZE = 4; //1 Gluint for the number of names, 2 depth
//values, and 1 for the token of the widget.
const int BUFFER_SIZE = HIT_SIZE * NUM_WGTS;
for( int i = 0; i < NUM_WIDGETS; ++i )
GLuint select_buffer[BUFFER_SIZE];
glSelectBuffer(BUFFER_SIZE, select_buffer);
glRenderMode(GL_SELECT);
//Set the viewport to draw only what's under the mouse
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT,viewport);
gluPickMatrix(X, Y, 1,1,viewport);
glOrtho(0.0, user_config->m_width, 0.0, user_config->m_height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glInitNames();
glPushName(WGT_NONE);
glPushMatrix();
for( int i = 0; i < NUM_WGTS; ++i )
{
if(!(m_widgets[i].active)) continue;
//Simple bounding box test
if(( X > m_widgets[i].widget->m_x ) &&
( X < m_widgets[i].widget->m_x + m_widgets[i].widget->m_width ) &&
( Y > m_widgets[i].widget->m_y ) &&
( Y < m_widgets[i].widget->m_y + m_widgets[i].widget->m_height ))
glLoadName( m_widgets[i].token );
glPushMatrix();
m_widgets[i].widget->applyTransformations();
//In case this ever becomes a performance bottleneck:
//the m_rect_list includes texture coordinates, which are not
//needed for selection.
glCallList( m_widgets[i].widget->m_rect_list );
glPopMatrix();
}
glFlush();
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glFlush();
GLuint* position = select_buffer;
const GLint NUM_HITS = glRenderMode(GL_RENDER);
if( NUM_HITS > 0 )
{
float dist;
float near_dist = 9999999.0f;
int nearest = WGT_NONE;
int wgt_x_center, wgt_y_center;
int curr_wgt;
for( int i = 0; i < NUM_HITS; ++i )
{
m_selected_wgt_token = m_widgets[i].token;
return m_selected_wgt_token;
position += 3;
curr_wgt = *position;
wgt_x_center = m_widgets[curr_wgt].widget->m_x + m_widgets[i].widget->m_width / 2;
wgt_y_center = m_widgets[curr_wgt].widget->m_y + m_widgets[i].widget->m_height / 2;
//Check if it's the closest one to the mouse
dist = ( fabsf(X - wgt_x_center) + fabsf(Y - wgt_y_center));
if(dist < near_dist )
{
near_dist = dist;
nearest = curr_wgt;
}
++position;
}
if( nearest == m_selected_wgt_token ) return WGT_NONE;
m_selected_wgt_token = nearest;
return m_selected_wgt_token;
}
return WGT_NONE;
@ -1293,7 +1359,7 @@ int
WidgetManager::handleLeft()
{
if( m_selected_wgt_token == WGT_NONE ) return WGT_NONE;
return handleFinish(findLeftWidget(findId(m_selected_wgt_token)));
}
@ -1301,7 +1367,7 @@ WidgetManager::handleLeft()
WidgetManager::handleRight()
{
if( m_selected_wgt_token == WGT_NONE ) return WGT_NONE;
return handleFinish(findRightWidget(findId(m_selected_wgt_token)));
}
@ -1309,7 +1375,7 @@ int
WidgetManager::handleUp()
{
if( m_selected_wgt_token == WGT_NONE ) return WGT_NONE;
return handleFinish(findTopWidget(findId(m_selected_wgt_token)));
}
@ -1317,7 +1383,7 @@ int
WidgetManager::handleDown()
{
if( m_selected_wgt_token == WGT_NONE ) return WGT_NONE;
return handleFinish(findBottomWidget(findId(m_selected_wgt_token)));
}
@ -1326,9 +1392,9 @@ WidgetManager::handleFinish(const int next_wgt)
{
if( next_wgt == WGT_NONE)
return WGT_NONE;
m_selected_wgt_token = m_widgets[next_wgt].token;
return m_selected_wgt_token;
}