aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llui/llmultislider.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--linden/indra/llui/llmultislider.cpp677
1 files changed, 677 insertions, 0 deletions
diff --git a/linden/indra/llui/llmultislider.cpp b/linden/indra/llui/llmultislider.cpp
new file mode 100644
index 0000000..d0c9002
--- /dev/null
+++ b/linden/indra/llui/llmultislider.cpp
@@ -0,0 +1,677 @@
1/**
2 * @file llmultisldr.cpp
3 * @brief LLMultiSlider base class
4 *
5 * $LicenseInfo:firstyear=2007&license=viewergpl$
6 *
7 * Copyright (c) 2007-2008, Linden Research, Inc.
8 *
9 * Second Life Viewer Source Code
10 * The source code in this file ("Source Code") is provided by Linden Lab
11 * to you under the terms of the GNU General Public License, version 2.0
12 * ("GPL"), unless you have obtained a separate licensing agreement
13 * ("Other License"), formally executed by you and Linden Lab. Terms of
14 * the GPL can be found in doc/GPL-license.txt in this distribution, or
15 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
16 *
17 * There are special exceptions to the terms and conditions of the GPL as
18 * it is applied to this Source Code. View the full text of the exception
19 * in the file doc/FLOSS-exception.txt in this software distribution, or
20 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
21 *
22 * By copying, modifying or distributing this software, you acknowledge
23 * that you have read and understood your obligations described above,
24 * and agree to abide by those obligations.
25 *
26 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
27 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
28 * COMPLETENESS OR PERFORMANCE.
29 * $/LicenseInfo$
30 */
31
32#include "linden_common.h"
33
34#include "llmultislider.h"
35#include "llui.h"
36
37#include "llgl.h"
38#include "llwindow.h"
39#include "llfocusmgr.h"
40#include "llkeyboard.h" // for the MASK constants
41#include "llcontrol.h"
42#include "llimagegl.h"
43
44#include <sstream>
45
46const S32 MULTI_THUMB_WIDTH = 8;
47const S32 MULTI_TRACK_HEIGHT = 6;
48const F32 FLOAT_THRESHOLD = 0.00001f;
49const S32 EXTRA_TRIANGLE_WIDTH = 2;
50const S32 EXTRA_TRIANGLE_HEIGHT = -2;
51
52S32 LLMultiSlider::mNameCounter = 0;
53
54LLMultiSlider::LLMultiSlider(
55 const LLString& name,
56 const LLRect& rect,
57 void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata),
58 void* callback_userdata,
59 F32 initial_value,
60 F32 min_value,
61 F32 max_value,
62 F32 increment,
63 S32 max_sliders,
64 BOOL allow_overlap,
65 BOOL draw_track,
66 BOOL use_triangle,
67 const LLString& control_name)
68 :
69 LLUICtrl( name, rect, TRUE, on_commit_callback, callback_userdata,
70 FOLLOWS_LEFT | FOLLOWS_TOP),
71
72 mInitialValue( initial_value ),
73 mMinValue( min_value ),
74 mMaxValue( max_value ),
75 mIncrement( increment ),
76 mMaxNumSliders(max_sliders),
77 mAllowOverlap(allow_overlap),
78 mDrawTrack(draw_track),
79 mUseTriangle(use_triangle),
80 mMouseOffset( 0 ),
81 mDragStartThumbRect( 0, getRect().getHeight(), MULTI_THUMB_WIDTH, 0 ),
82 mTrackColor( LLUI::sColorsGroup->getColor( "MultiSliderTrackColor" ) ),
83 mThumbOutlineColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbOutlineColor" ) ),
84 mThumbCenterColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbCenterColor" ) ),
85 mThumbCenterSelectedColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbCenterSelectedColor" ) ),
86 mDisabledThumbColor(LLUI::sColorsGroup->getColor( "MultiSliderDisabledThumbColor" ) ),
87 mTriangleColor(LLUI::sColorsGroup->getColor( "MultiSliderTriangleColor" ) ),
88 mMouseDownCallback( NULL ),
89 mMouseUpCallback( NULL )
90{
91 mValue.emptyMap();
92 mCurSlider = LLString::null;
93
94 // properly handle setting the starting thumb rect
95 // do it this way to handle both the operating-on-settings
96 // and standalone ways of using this
97 setControlName(control_name, NULL);
98 setValue(getValue());
99}
100
101EWidgetType LLMultiSlider::getWidgetType() const
102{
103 return WIDGET_TYPE_MULTI_SLIDER_BAR;
104}
105
106LLString LLMultiSlider::getWidgetTag() const
107{
108 return LL_MULTI_SLIDER_TAG;
109}
110
111void LLMultiSlider::setSliderValue(const LLString& name, F32 value, BOOL from_event)
112{
113 // exit if not there
114 if(!mValue.has(name)) {
115 return;
116 }
117
118 value = llclamp( value, mMinValue, mMaxValue );
119
120 // Round to nearest increment (bias towards rounding down)
121 value -= mMinValue;
122 value += mIncrement/2.0001f;
123 value -= fmod(value, mIncrement);
124 F32 newValue = mMinValue + value;
125
126 // now, make sure no overlap
127 // if we want that
128 if(!mAllowOverlap) {
129 bool hit = false;
130
131 // look at the current spot
132 // and see if anything is there
133 LLSD::map_iterator mIt = mValue.beginMap();
134 for(;mIt != mValue.endMap(); mIt++) {
135
136 F32 testVal = (F32)mIt->second.asReal() - newValue;
137 if(testVal > -FLOAT_THRESHOLD && testVal < FLOAT_THRESHOLD &&
138 mIt->first != name) {
139 hit = true;
140 break;
141 }
142 }
143
144 // if none found, stop
145 if(hit) {
146 return;
147 }
148 }
149
150
151 // now set it in the map
152 mValue[name] = newValue;
153
154 // set the control if it's the current slider and not from an event
155 if (!from_event && name == mCurSlider)
156 {
157 setControlValue(mValue);
158 }
159
160 F32 t = (newValue - mMinValue) / (mMaxValue - mMinValue);
161
162 S32 left_edge = MULTI_THUMB_WIDTH/2;
163 S32 right_edge = getRect().getWidth() - (MULTI_THUMB_WIDTH/2);
164
165 S32 x = left_edge + S32( t * (right_edge - left_edge) );
166 mThumbRects[name].mLeft = x - (MULTI_THUMB_WIDTH/2);
167 mThumbRects[name].mRight = x + (MULTI_THUMB_WIDTH/2);
168}
169
170void LLMultiSlider::setValue(const LLSD& value)
171{
172 // only do if it's a map
173 if(value.isMap()) {
174
175 // add each value... the first in the map becomes the current
176 LLSD::map_const_iterator mIt = value.beginMap();
177 mCurSlider = mIt->first;
178
179 for(; mIt != value.endMap(); mIt++) {
180 setSliderValue(mIt->first, (F32)mIt->second.asReal(), TRUE);
181 }
182 }
183}
184
185F32 LLMultiSlider::getSliderValue(const LLString& name) const
186{
187 return (F32)mValue[name].asReal();
188}
189
190void LLMultiSlider::setCurSlider(const LLString& name)
191{
192 if(mValue.has(name)) {
193 mCurSlider = name;
194 }
195}
196
197const LLString& LLMultiSlider::addSlider()
198{
199 return addSlider(mInitialValue);
200}
201
202const LLString& LLMultiSlider::addSlider(F32 val)
203{
204 std::stringstream newName;
205 F32 initVal = val;
206
207 if(mValue.size() >= mMaxNumSliders) {
208 return LLString::null;
209 }
210
211 // create a new name
212 newName << "sldr" << mNameCounter;
213 mNameCounter++;
214
215 bool foundOne = findUnusedValue(initVal);
216 if(!foundOne) {
217 return LLString::null;
218 }
219
220 // add a new thumb rect
221 mThumbRects[newName.str()] = LLRect( 0, getRect().getHeight(), MULTI_THUMB_WIDTH, 0 );
222
223 // add the value and set the current slider to this one
224 mValue.insert(newName.str(), initVal);
225 mCurSlider = newName.str();
226
227 // move the slider
228 setSliderValue(mCurSlider, initVal, TRUE);
229
230 return mCurSlider;
231}
232
233bool LLMultiSlider::findUnusedValue(F32& initVal)
234{
235 bool firstTry = true;
236
237 // find the first open slot starting with
238 // the initial value
239 while(true) {
240
241 bool hit = false;
242
243 // look at the current spot
244 // and see if anything is there
245 LLSD::map_iterator mIt = mValue.beginMap();
246 for(;mIt != mValue.endMap(); mIt++) {
247
248 F32 testVal = (F32)mIt->second.asReal() - initVal;
249 if(testVal > -FLOAT_THRESHOLD && testVal < FLOAT_THRESHOLD) {
250 hit = true;
251 break;
252 }
253 }
254
255 // if we found one
256 if(!hit) {
257 break;
258 }
259
260 // increment and wrap if need be
261 initVal += mIncrement;
262 if(initVal > mMaxValue) {
263 initVal = mMinValue;
264 }
265
266 // stop if it's filled
267 if(initVal == mInitialValue && !firstTry) {
268 llwarns << "Whoa! Too many multi slider elements to add one to" << llendl;
269 return false;
270 }
271
272 firstTry = false;
273 continue;
274 }
275
276 return true;
277}
278
279
280void LLMultiSlider::deleteSlider(const LLString& name)
281{
282 // can't delete last slider
283 if(mValue.size() <= 0) {
284 return;
285 }
286
287 // get rid of value from mValue and its thumb rect
288 mValue.erase(name);
289 mThumbRects.erase(name);
290
291 // set to the last created
292 if(mValue.size() > 0) {
293 std::map<LLString, LLRect>::iterator mIt = mThumbRects.end();
294 mIt--;
295 mCurSlider = mIt->first;
296 }
297}
298
299void LLMultiSlider::clear()
300{
301 while(mThumbRects.size() > 0) {
302 deleteCurSlider();
303 }
304
305 LLUICtrl::clear();
306}
307
308BOOL LLMultiSlider::handleHover(S32 x, S32 y, MASK mask)
309{
310 if( gFocusMgr.getMouseCapture() == this )
311 {
312 S32 left_edge = MULTI_THUMB_WIDTH/2;
313 S32 right_edge = getRect().getWidth() - (MULTI_THUMB_WIDTH/2);
314
315 x += mMouseOffset;
316 x = llclamp( x, left_edge, right_edge );
317
318 F32 t = F32(x - left_edge) / (right_edge - left_edge);
319 setCurSliderValue(t * (mMaxValue - mMinValue) + mMinValue );
320 onCommit();
321
322 getWindow()->setCursor(UI_CURSOR_ARROW);
323 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;
324 }
325 else
326 {
327 getWindow()->setCursor(UI_CURSOR_ARROW);
328 lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;
329 }
330 return TRUE;
331}
332
333BOOL LLMultiSlider::handleMouseUp(S32 x, S32 y, MASK mask)
334{
335 BOOL handled = FALSE;
336
337 if( gFocusMgr.getMouseCapture() == this )
338 {
339 gFocusMgr.setMouseCapture( NULL );
340
341 if( mMouseUpCallback )
342 {
343 mMouseUpCallback( this, mCallbackUserData );
344 }
345 handled = TRUE;
346 make_ui_sound("UISndClickRelease");
347 }
348 else
349 {
350 handled = TRUE;
351 }
352
353 return handled;
354}
355
356BOOL LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask)
357{
358 // only do sticky-focus on non-chrome widgets
359 if (!getIsChrome())
360 {
361 setFocus(TRUE);
362 }
363 if( mMouseDownCallback )
364 {
365 mMouseDownCallback( this, mCallbackUserData );
366 }
367
368 if (MASK_CONTROL & mask) // if CTRL is modifying
369 {
370 setCurSliderValue(mInitialValue);
371 onCommit();
372 }
373 else
374 {
375 // scroll through thumbs to see if we have a new one selected and select that one
376 std::map<LLString, LLRect>::iterator mIt = mThumbRects.begin();
377 for(; mIt != mThumbRects.end(); mIt++) {
378
379 // check if inside. If so, set current slider and continue
380 if(mIt->second.pointInRect(x,y)) {
381 mCurSlider = mIt->first;
382 break;
383 }
384 }
385
386 // Find the offset of the actual mouse location from the center of the thumb.
387 if (mThumbRects[mCurSlider].pointInRect(x,y))
388 {
389 mMouseOffset = (mThumbRects[mCurSlider].mLeft + MULTI_THUMB_WIDTH/2) - x;
390 }
391 else
392 {
393 mMouseOffset = 0;
394 }
395
396 // Start dragging the thumb
397 // No handler needed for focus lost since this class has no state that depends on it.
398 gFocusMgr.setMouseCapture( this );
399 mDragStartThumbRect = mThumbRects[mCurSlider];
400 }
401 make_ui_sound("UISndClick");
402
403 return TRUE;
404}
405
406BOOL LLMultiSlider::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent)
407{
408 BOOL handled = FALSE;
409 if( getVisible() && getEnabled() && !called_from_parent )
410 {
411 switch(key)
412 {
413 case KEY_UP:
414 case KEY_DOWN:
415 // eat up and down keys to be consistent
416 handled = TRUE;
417 break;
418 case KEY_LEFT:
419 setCurSliderValue(getCurSliderValue() - getIncrement());
420 onCommit();
421 handled = TRUE;
422 break;
423 case KEY_RIGHT:
424 setCurSliderValue(getCurSliderValue() + getIncrement());
425 onCommit();
426 handled = TRUE;
427 break;
428 default:
429 break;
430 }
431 }
432 return handled;
433}
434
435void LLMultiSlider::draw()
436{
437 LLColor4 curThumbColor;
438
439 std::map<LLString, LLRect>::iterator mIt;
440 std::map<LLString, LLRect>::iterator curSldrIt;
441 if( getVisible() )
442 {
443 // Draw background and thumb.
444
445 // drawing solids requires texturing be disabled
446 LLGLSNoTexture no_texture;
447
448 LLRect rect(mDragStartThumbRect);
449
450 F32 opacity = getEnabled() ? 1.f : 0.3f;
451
452 // Track
453 LLUUID thumb_image_id;
454 thumb_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga"));
455 LLPointer<LLImageGL> thumb_imagep(LLUI::sImageProvider->getUIImageByID(thumb_image_id)->getImage());
456
457 S32 height_offset = (getRect().getHeight() - MULTI_TRACK_HEIGHT) / 2;
458 LLRect track_rect(0, getRect().getHeight() - height_offset, getRect().getWidth(), height_offset );
459
460
461 if(mDrawTrack)
462 {
463 track_rect.stretch(-1);
464 gl_draw_scaled_image_with_border(track_rect.mLeft, track_rect.mBottom, 16, 16, track_rect.getWidth(), track_rect.getHeight(),
465 thumb_imagep, mTrackColor % opacity);
466 }
467
468 // if we're supposed to use a drawn triangle
469 // simple gl call for the triangle
470 if(mUseTriangle) {
471
472 for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
473
474 gl_triangle_2d(
475 mIt->second.mLeft - EXTRA_TRIANGLE_WIDTH,
476 mIt->second.mTop + EXTRA_TRIANGLE_HEIGHT,
477 mIt->second.mRight + EXTRA_TRIANGLE_WIDTH,
478 mIt->second.mTop + EXTRA_TRIANGLE_HEIGHT,
479 mIt->second.mLeft + mIt->second.getWidth() / 2,
480 mIt->second.mBottom - EXTRA_TRIANGLE_HEIGHT,
481 mTriangleColor, TRUE);
482 }
483 }
484 else if (!thumb_imagep)
485 {
486 // draw all the thumbs
487 curSldrIt = mThumbRects.end();
488 for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
489
490 // choose the color
491 curThumbColor = mThumbCenterColor;
492 if(mIt->first == mCurSlider) {
493
494 curSldrIt = mIt;
495 continue;
496 //curThumbColor = mThumbCenterSelectedColor;
497 }
498
499 // the draw command
500 gl_rect_2d(mIt->second, curThumbColor, TRUE);
501 }
502
503 // now draw the current slider
504 if(curSldrIt != mThumbRects.end()) {
505 gl_rect_2d(curSldrIt->second, mThumbCenterSelectedColor, TRUE);
506 }
507
508 // and draw the drag start
509 if (gFocusMgr.getMouseCapture() == this)
510 {
511 gl_rect_2d(mDragStartThumbRect, mThumbCenterColor % opacity, FALSE);
512 }
513 }
514 else if( gFocusMgr.getMouseCapture() == this )
515 {
516 // draw drag start
517 gl_draw_scaled_image_with_border(mDragStartThumbRect.mLeft,
518 mDragStartThumbRect.mBottom, 16, 16,
519 mDragStartThumbRect.getWidth(),
520 mDragStartThumbRect.getHeight(),
521 thumb_imagep, mThumbCenterColor % 0.3f, TRUE);
522
523 // draw the highlight
524 if (hasFocus())
525 {
526 F32 lerp_amt = gFocusMgr.getFocusFlashAmt();
527 LLRect highlight_rect = mThumbRects[mCurSlider];
528 highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt)));
529 gl_draw_scaled_image_with_border(highlight_rect.mLeft,
530 highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(),
531 highlight_rect.getHeight(),
532 thumb_imagep, gFocusMgr.getFocusColor());
533 }
534
535 // draw the thumbs
536 curSldrIt = mThumbRects.end();
537 for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
538
539 // choose the color
540 curThumbColor = mThumbCenterColor;
541 if(mIt->first == mCurSlider) {
542 // don't draw now, draw last
543 curSldrIt = mIt;
544 continue;
545 }
546
547 // the draw command
548 gl_draw_scaled_image_with_border(
549 mIt->second.mLeft,
550 mIt->second.mBottom, 16, 16,
551 mIt->second.getWidth(),
552 mIt->second.getHeight(), thumb_imagep,
553 curThumbColor, TRUE);
554 }
555
556 // draw cur slider last
557 if(curSldrIt != mThumbRects.end()) {
558 gl_draw_scaled_image_with_border(
559 curSldrIt->second.mLeft,
560 curSldrIt->second.mBottom, 16, 16,
561 curSldrIt->second.getWidth(),
562 curSldrIt->second.getHeight(), thumb_imagep,
563 mThumbCenterSelectedColor, TRUE);
564 }
565
566 }
567 else
568 {
569 // draw highlight
570 if (hasFocus())
571 {
572 F32 lerp_amt = gFocusMgr.getFocusFlashAmt();
573 LLRect highlight_rect = mThumbRects[mCurSlider];
574 highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt)));
575 gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(),
576 thumb_imagep, gFocusMgr.getFocusColor());
577 }
578
579 // draw thumbs
580 curSldrIt = mThumbRects.end();
581 for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
582
583 // choose the color
584 curThumbColor = mThumbCenterColor;
585 if(mIt->first == mCurSlider) {
586 curSldrIt = mIt;
587 continue;
588 //curThumbColor = mThumbCenterSelectedColor;
589 }
590
591 // the draw command
592 gl_draw_scaled_image_with_border(
593 mIt->second.mLeft,
594 mIt->second.mBottom, 16, 16,
595 mIt->second.getWidth(),
596 mIt->second.getHeight(), thumb_imagep,
597 curThumbColor % opacity, TRUE);
598 }
599
600 if(curSldrIt != mThumbRects.end()) {
601 gl_draw_scaled_image_with_border(
602 curSldrIt->second.mLeft,
603 curSldrIt->second.mBottom, 16, 16,
604 curSldrIt->second.getWidth(),
605 curSldrIt->second.getHeight(), thumb_imagep,
606 mThumbCenterSelectedColor % opacity, TRUE);
607 }
608 }
609
610 LLUICtrl::draw();
611 }
612}
613
614// virtual
615LLXMLNodePtr LLMultiSlider::getXML(bool save_children) const
616{
617 LLXMLNodePtr node = LLUICtrl::getXML();
618
619 node->createChild("initial_val", TRUE)->setFloatValue(getInitialValue());
620 node->createChild("min_val", TRUE)->setFloatValue(getMinValue());
621 node->createChild("max_val", TRUE)->setFloatValue(getMaxValue());
622 node->createChild("increment", TRUE)->setFloatValue(getIncrement());
623
624 return node;
625}
626
627
628//static
629LLView* LLMultiSlider::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
630{
631 LLString name("multi_slider_bar");
632 node->getAttributeString("name", name);
633
634 LLRect rect;
635 createRect(node, rect, parent, LLRect());
636
637 F32 initial_value = 0.f;
638 node->getAttributeF32("initial_val", initial_value);
639
640 F32 min_value = 0.f;
641 node->getAttributeF32("min_val", min_value);
642
643 F32 max_value = 1.f;
644 node->getAttributeF32("max_val", max_value);
645
646 F32 increment = 0.1f;
647 node->getAttributeF32("increment", increment);
648
649 S32 max_sliders = 1;
650 node->getAttributeS32("max_sliders", max_sliders);
651
652 BOOL allow_overlap = FALSE;
653 node->getAttributeBOOL("allow_overlap", allow_overlap);
654
655 BOOL draw_track = TRUE;
656 node->getAttributeBOOL("draw_track", draw_track);
657
658 BOOL use_triangle = FALSE;
659 node->getAttributeBOOL("use_triangle", use_triangle);
660
661 LLMultiSlider* multiSlider = new LLMultiSlider(name,
662 rect,
663 NULL,
664 NULL,
665 initial_value,
666 min_value,
667 max_value,
668 increment,
669 max_sliders,
670 allow_overlap,
671 draw_track,
672 use_triangle);
673
674 multiSlider->initFromXML(node, parent);
675
676 return multiSlider;
677}