1 /*
2 * Modification History
3 *
4 * 2001-September-16 Jason Rohrer
5 * Created.
6 *
7 * 2001-November-3 Jason Rohrer
8 * Fixed a gutter size bug that occurred when there was
9 * only one row or column.
10 * Changed so that rows are filled before adding new columns.
11 * Fixed a bug in setSelectedIndex.
12 */
13
14
15 #ifndef MULTI_BUTTON_GL_INCLUDED
16 #define MULTI_BUTTON_GL_INCLUDED
17
18 #include "GUIContainerGL.h"
19 #include "StickyButtonGL.h"
20
21 #include "minorGems/graphics/Image.h"
22
23 #include "minorGems/ui/event/ActionListenerList.h"
24 #include "minorGems/ui/event/ActionListener.h"
25 #include "minorGems/ui/GUIComponent.h"
26
27 #include <math.h>
28 #include <stdio.h>
29
30 /**
31 * A set of buttons that allows only one to be depressed
32 * at a time.
33 *
34 * @author Jason Rohrer
35 */
36 class MultiButtonGL : public GUIContainerGL,
37 public ActionListener,
38 public ActionListenerList {
39
40
41 public:
42
43
44
45 /**
46 * Constructs a set of buttons.
47 *
48 * @param inAnchorX the x position of the upper left corner
49 * of this component.
50 * @param inAnchorY the y position of the upper left corner
51 * of this component.
52 * @param inWidth the width of this component.
53 * @param inHeight the height of this component.
54 * @param inNumButtons the number of buttons in the set.
55 * @param inUnpressedImages the images to display
56 * when each button is unpressed. Images must have dimensions
57 * that are powers of 2.
58 * Will be destroyed when this class is destroyed.
59 * @param inPressedImages the images to display
60 * when each button is pressed. Images must have dimensions
61 * that are powers of 2.
62 * Will be destroyed when this class is destroyed.
63 * @param inGutterFraction the fraction of each row
64 * and column that should be taken up by the gutters
65 * (the spaces between buttons). In [0, 1.0].
66 */
67 MultiButtonGL(
68 double inAnchorX, double inAnchorY, double inWidth,
69 double inHeight, int inNumButtons,
70 Image **inUnpressedImages,
71 Image **inPressedImages,
72 double inGutterFraction );
73
74
75
76 ~MultiButtonGL();
77
78
79
80 /**
81 * Sets the currently depressed button.
82 *
83 * Note that if this function causes a change
84 * in the state of the set, an action will
85 * be fired to all registered listeners.
86 *
87 * @param inButtonIndex the index of the button to depress.
88 */
89 void setSelectedButton( int inButtonIndex );
90
91
92
93 /**
94 * Gets the index of the currently depressed button.
95 *
96 * Note that if this function causes a change
97 * in the state of the set, an action will
98 * be fired to all registered listeners.
99 *
100 * @return the index of the button that is depressed.
101 */
102 int getSelectedButton();
103
104
105
106 // we don't need to override any mouse
107 // or redraw functions, since the container
108 // and the buttons should handle these correctly
109
110
111 // implements the ActionListener interface
112 virtual void actionPerformed( GUIComponent *inTarget );
113
114
115 protected:
116 int mNumButtons;
117
118 StickyButtonGL **mButtons;
119
120
121 int mSelectedIndex;
122
123
124 char mIgnoreEvents;
125
126 /**
127 * Maps a button in mButtons to its index in the
128 * mButtons array.
129 *
130 * @param inButton the button to get an index for.
131 *
132 * @return the index of inButton in the mButtons array.
133 */
134 int buttonToIndex( StickyButtonGL *inButton );
135
136
137
138 };
139
140
141
MultiButtonGL(double inAnchorX,double inAnchorY,double inWidth,double inHeight,int inNumButtons,Image ** inUnpressedImages,Image ** inPressedImages,double inGutterFraction)142 inline MultiButtonGL::MultiButtonGL(
143 double inAnchorX, double inAnchorY, double inWidth,
144 double inHeight, int inNumButtons,
145 Image **inUnpressedImages,
146 Image **inPressedImages,
147 double inGutterFraction )
148 : GUIContainerGL( inAnchorX, inAnchorY, inWidth, inHeight ),
149 mNumButtons( inNumButtons ),
150 mButtons( new StickyButtonGL*[inNumButtons] ),
151 mSelectedIndex( 0 ),
152 mIgnoreEvents( false ) {
153
154 int numColumns = (int)( sqrt( mNumButtons ) );
155 int numRows = mNumButtons / numColumns;
156
157
158 while( numRows * numColumns < mNumButtons ) {
159 numRows++;
160 }
161
162 // determine gutter and button sizes
163
164 double gutterSizeX;
165 double gutterSizeY;
166
167 if( numRows == 1 ) {
168 gutterSizeY = 0;
169 }
170 else {
171 gutterSizeY =
172 ( inHeight * inGutterFraction ) / ( numRows - 1 );
173 }
174 if( numColumns == 1 ) {
175 gutterSizeX = 0;
176 }
177 else {
178 gutterSizeX =
179 ( inWidth * inGutterFraction ) / ( numColumns - 1 );
180 }
181
182
183 double buttonSizeX =
184 ( inWidth * ( 1 - inGutterFraction ) ) / ( numColumns );
185 double buttonSizeY =
186 ( inHeight * ( 1 - inGutterFraction ) ) / ( numRows );
187
188
189 // setup each button
190
191 int buttonIndex = 0;
192
193 for( int r=0; r<numRows; r++ ) {
194 for( int c=0; c<numColumns; c++ ) {
195
196 double anchorX = inAnchorX +
197 c * ( buttonSizeX + gutterSizeX );
198 double anchorY = inAnchorY +
199 r * ( buttonSizeY + gutterSizeY );
200
201 mButtons[ buttonIndex ] =
202 new StickyButtonGL( anchorX, anchorY,
203 buttonSizeX, buttonSizeY,
204 inUnpressedImages[ buttonIndex ],
205 inPressedImages[ buttonIndex ] );
206
207 GUIContainerGL::add( mButtons[ buttonIndex ] );
208
209 buttonIndex++;
210
211 if( buttonIndex >= mNumButtons ) {
212 // jump out of our loop if we run out of buttons
213 c = numColumns;
214 r = numRows;
215 }
216 }
217 }
218
219 // now we've added all of our buttons
220
221 // press one of them before we add any listeners
222 mButtons[ mSelectedIndex ]->setPressed( true );
223
224
225 // now add ourselves as an action listener to each button
226 for( int i=0; i<mNumButtons; i++ ) {
227 mButtons[ i ]->addActionListener( this );
228 }
229
230 // delete the arrays pointing to the images, since
231 // they are no longer needed
232 delete [] inUnpressedImages;
233 delete [] inPressedImages;
234
235 }
236
237
238
~MultiButtonGL()239 inline MultiButtonGL::~MultiButtonGL() {
240 // note that we don't need to delete the buttons
241 // themselves, since they will be deleted by GUIContainer
242 // destructor
243
244 delete [] mButtons;
245 }
246
247
248
setSelectedButton(int inButtonIndex)249 inline void MultiButtonGL::setSelectedButton( int inButtonIndex ) {
250 // simply press the appropriate button, and let the
251 // action handlers do the rest
252
253 mButtons[ inButtonIndex ]->setPressed( true );
254 }
255
256
257
getSelectedButton()258 inline int MultiButtonGL::getSelectedButton() {
259 return mSelectedIndex;
260 }
261
262
263
actionPerformed(GUIComponent * inTarget)264 inline void MultiButtonGL::actionPerformed( GUIComponent *inTarget ) {
265 if( !mIgnoreEvents ) {
266 int buttonIndex = buttonToIndex( (StickyButtonGL*)inTarget );
267
268 // if another button is being pressed
269 if( buttonIndex != mSelectedIndex
270 && mButtons[ buttonIndex ]->isPressed() ) {
271
272
273 // unpress the old button, ignoring the resulting event
274 mIgnoreEvents = true;
275 mButtons[ mSelectedIndex ]->setPressed( false );
276 mIgnoreEvents = false;
277
278 mSelectedIndex = buttonIndex;
279
280 fireActionPerformed( this );
281 }
282 // else if our selected button is being unpressed
283 else if( buttonIndex == mSelectedIndex
284 && !( mButtons[ buttonIndex ]->isPressed() ) ) {
285 // don't allow it to become unpressed
286
287 // re-press it, ignoring the resulting event
288 mIgnoreEvents = true;
289 mButtons[ mSelectedIndex ]->setPressed( true );
290 mIgnoreEvents = false;
291
292 // don't fire an action, since our state
293 // has not changed
294 }
295 }
296 }
297
298
299
buttonToIndex(StickyButtonGL * inButton)300 inline int MultiButtonGL::buttonToIndex( StickyButtonGL *inButton ) {
301 for( int i=0; i<mNumButtons; i++ ) {
302 if( mButtons[i] == inButton ) {
303 return i;
304 }
305 }
306
307 // return the first button index by default
308 printf( "MultiButtonGL: no matching button found.\n" );
309 return 0;
310 }
311
312
313
314 #endif
315