summaryrefslogtreecommitdiff
path: root/indra/llui/llmultislider.cpp
diff options
context:
space:
mode:
authorBrad Kittenbrink <brad@lindenlab.com>2008-02-27 18:58:14 +0000
committerBrad Kittenbrink <brad@lindenlab.com>2008-02-27 18:58:14 +0000
commit6d52efe452aa8469e0343da1c7d108f3f52ab651 (patch)
treea87be48e9840d7fc1f7ee514d7c7f994e71fdb3c /indra/llui/llmultislider.cpp
parent6027ad2630b8650cabcf00628ee9b0d25bedd67f (diff)
Merge of windlight into release (QAR-286). This includes all changes in
windlight14 which have passed QA (up through r79932). svn merge -r 80831:80833 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge_windlight14_r80620
Diffstat (limited to 'indra/llui/llmultislider.cpp')
-rw-r--r--indra/llui/llmultislider.cpp677
1 files changed, 677 insertions, 0 deletions
diff --git a/indra/llui/llmultislider.cpp b/indra/llui/llmultislider.cpp
new file mode 100644
index 0000000000..b4bf3a92d5
--- /dev/null
+++ b/indra/llui/llmultislider.cpp
@@ -0,0 +1,677 @@
+/**
+ * @file llmultisldr.cpp
+ * @brief LLMultiSlider base class
+ *
+ * $LicenseInfo:firstyear=2007&license=viewergpl$
+ *
+ * Copyright (c) 2007-2007, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlife.com/developers/opensource/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at http://secondlife.com/developers/opensource/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llmultislider.h"
+#include "llui.h"
+
+#include "llgl.h"
+#include "llwindow.h"
+#include "llfocusmgr.h"
+#include "llkeyboard.h" // for the MASK constants
+#include "llcontrol.h"
+#include "llimagegl.h"
+
+#include <sstream>
+
+const S32 MULTI_THUMB_WIDTH = 8;
+const S32 MULTI_TRACK_HEIGHT = 6;
+const F32 FLOAT_THRESHOLD = 0.00001f;
+const S32 EXTRA_TRIANGLE_WIDTH = 2;
+const S32 EXTRA_TRIANGLE_HEIGHT = -2;
+
+S32 LLMultiSlider::mNameCounter = 0;
+
+LLMultiSlider::LLMultiSlider(
+ const LLString& name,
+ const LLRect& rect,
+ void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata),
+ void* callback_userdata,
+ F32 initial_value,
+ F32 min_value,
+ F32 max_value,
+ F32 increment,
+ S32 max_sliders,
+ BOOL allow_overlap,
+ BOOL draw_track,
+ BOOL use_triangle,
+ const LLString& control_name)
+ :
+ LLUICtrl( name, rect, TRUE, on_commit_callback, callback_userdata,
+ FOLLOWS_LEFT | FOLLOWS_TOP),
+
+ mInitialValue( initial_value ),
+ mMinValue( min_value ),
+ mMaxValue( max_value ),
+ mIncrement( increment ),
+ mMaxNumSliders(max_sliders),
+ mAllowOverlap(allow_overlap),
+ mDrawTrack(draw_track),
+ mUseTriangle(use_triangle),
+ mMouseOffset( 0 ),
+ mDragStartThumbRect( 0, getRect().getHeight(), MULTI_THUMB_WIDTH, 0 ),
+ mTrackColor( LLUI::sColorsGroup->getColor( "MultiSliderTrackColor" ) ),
+ mThumbOutlineColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbOutlineColor" ) ),
+ mThumbCenterColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbCenterColor" ) ),
+ mThumbCenterSelectedColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbCenterSelectedColor" ) ),
+ mDisabledThumbColor(LLUI::sColorsGroup->getColor( "MultiSliderDisabledThumbColor" ) ),
+ mTriangleColor(LLUI::sColorsGroup->getColor( "MultiSliderTriangleColor" ) ),
+ mMouseDownCallback( NULL ),
+ mMouseUpCallback( NULL )
+{
+ mValue.emptyMap();
+ mCurSlider = LLString::null;
+
+ // properly handle setting the starting thumb rect
+ // do it this way to handle both the operating-on-settings
+ // and standalone ways of using this
+ setControlName(control_name, NULL);
+ setValue(getValue());
+}
+
+EWidgetType LLMultiSlider::getWidgetType() const
+{
+ return WIDGET_TYPE_MULTI_SLIDER_BAR;
+}
+
+LLString LLMultiSlider::getWidgetTag() const
+{
+ return LL_MULTI_SLIDER_TAG;
+}
+
+void LLMultiSlider::setSliderValue(const LLString& name, F32 value, BOOL from_event)
+{
+ // exit if not there
+ if(!mValue.has(name)) {
+ return;
+ }
+
+ value = llclamp( value, mMinValue, mMaxValue );
+
+ // Round to nearest increment (bias towards rounding down)
+ value -= mMinValue;
+ value += mIncrement/2.0001f;
+ value -= fmod(value, mIncrement);
+ F32 newValue = mMinValue + value;
+
+ // now, make sure no overlap
+ // if we want that
+ if(!mAllowOverlap) {
+ bool hit = false;
+
+ // look at the current spot
+ // and see if anything is there
+ LLSD::map_iterator mIt = mValue.beginMap();
+ for(;mIt != mValue.endMap(); mIt++) {
+
+ F32 testVal = (F32)mIt->second.asReal() - newValue;
+ if(testVal > -FLOAT_THRESHOLD && testVal < FLOAT_THRESHOLD &&
+ mIt->first != name) {
+ hit = true;
+ break;
+ }
+ }
+
+ // if none found, stop
+ if(hit) {
+ return;
+ }
+ }
+
+
+ // now set it in the map
+ mValue[name] = newValue;
+
+ // set the control if it's the current slider and not from an event
+ if (!from_event && name == mCurSlider)
+ {
+ setControlValue(mValue);
+ }
+
+ F32 t = (newValue - mMinValue) / (mMaxValue - mMinValue);
+
+ S32 left_edge = MULTI_THUMB_WIDTH/2;
+ S32 right_edge = getRect().getWidth() - (MULTI_THUMB_WIDTH/2);
+
+ S32 x = left_edge + S32( t * (right_edge - left_edge) );
+ mThumbRects[name].mLeft = x - (MULTI_THUMB_WIDTH/2);
+ mThumbRects[name].mRight = x + (MULTI_THUMB_WIDTH/2);
+}
+
+void LLMultiSlider::setValue(const LLSD& value)
+{
+ // only do if it's a map
+ if(value.isMap()) {
+
+ // add each value... the first in the map becomes the current
+ LLSD::map_const_iterator mIt = value.beginMap();
+ mCurSlider = mIt->first;
+
+ for(; mIt != value.endMap(); mIt++) {
+ setSliderValue(mIt->first, (F32)mIt->second.asReal(), TRUE);
+ }
+ }
+}
+
+F32 LLMultiSlider::getSliderValue(const LLString& name) const
+{
+ return (F32)mValue[name].asReal();
+}
+
+void LLMultiSlider::setCurSlider(const LLString& name)
+{
+ if(mValue.has(name)) {
+ mCurSlider = name;
+ }
+}
+
+const LLString& LLMultiSlider::addSlider()
+{
+ return addSlider(mInitialValue);
+}
+
+const LLString& LLMultiSlider::addSlider(F32 val)
+{
+ std::stringstream newName;
+ F32 initVal = val;
+
+ if(mValue.size() >= mMaxNumSliders) {
+ return LLString::null;
+ }
+
+ // create a new name
+ newName << "sldr" << mNameCounter;
+ mNameCounter++;
+
+ bool foundOne = findUnusedValue(initVal);
+ if(!foundOne) {
+ return LLString::null;
+ }
+
+ // add a new thumb rect
+ mThumbRects[newName.str()] = LLRect( 0, getRect().getHeight(), MULTI_THUMB_WIDTH, 0 );
+
+ // add the value and set the current slider to this one
+ mValue.insert(newName.str(), initVal);
+ mCurSlider = newName.str();
+
+ // move the slider
+ setSliderValue(mCurSlider, initVal, TRUE);
+
+ return mCurSlider;
+}
+
+bool LLMultiSlider::findUnusedValue(F32& initVal)
+{
+ bool firstTry = true;
+
+ // find the first open slot starting with
+ // the initial value
+ while(true) {
+
+ bool hit = false;
+
+ // look at the current spot
+ // and see if anything is there
+ LLSD::map_iterator mIt = mValue.beginMap();
+ for(;mIt != mValue.endMap(); mIt++) {
+
+ F32 testVal = (F32)mIt->second.asReal() - initVal;
+ if(testVal > -FLOAT_THRESHOLD && testVal < FLOAT_THRESHOLD) {
+ hit = true;
+ break;
+ }
+ }
+
+ // if we found one
+ if(!hit) {
+ break;
+ }
+
+ // increment and wrap if need be
+ initVal += mIncrement;
+ if(initVal > mMaxValue) {
+ initVal = mMinValue;
+ }
+
+ // stop if it's filled
+ if(initVal == mInitialValue && !firstTry) {
+ llwarns << "Whoa! Too many multi slider elements to add one to" << llendl;
+ return false;
+ }
+
+ firstTry = false;
+ continue;
+ }
+
+ return true;
+}
+
+
+void LLMultiSlider::deleteSlider(const LLString& name)
+{
+ // can't delete last slider
+ if(mValue.size() <= 0) {
+ return;
+ }
+
+ // get rid of value from mValue and its thumb rect
+ mValue.erase(name);
+ mThumbRects.erase(name);
+
+ // set to the last created
+ if(mValue.size() > 0) {
+ std::map<LLString, LLRect>::iterator mIt = mThumbRects.end();
+ mIt--;
+ mCurSlider = mIt->first;
+ }
+}
+
+void LLMultiSlider::clear()
+{
+ while(mThumbRects.size() > 0) {
+ deleteCurSlider();
+ }
+
+ LLUICtrl::clear();
+}
+
+BOOL LLMultiSlider::handleHover(S32 x, S32 y, MASK mask)
+{
+ if( gFocusMgr.getMouseCapture() == this )
+ {
+ S32 left_edge = MULTI_THUMB_WIDTH/2;
+ S32 right_edge = getRect().getWidth() - (MULTI_THUMB_WIDTH/2);
+
+ x += mMouseOffset;
+ x = llclamp( x, left_edge, right_edge );
+
+ F32 t = F32(x - left_edge) / (right_edge - left_edge);
+ setCurSliderValue(t * (mMaxValue - mMinValue) + mMinValue );
+ onCommit();
+
+ getWindow()->setCursor(UI_CURSOR_ARROW);
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;
+ }
+ else
+ {
+ getWindow()->setCursor(UI_CURSOR_ARROW);
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;
+ }
+ return TRUE;
+}
+
+BOOL LLMultiSlider::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = FALSE;
+
+ if( gFocusMgr.getMouseCapture() == this )
+ {
+ gFocusMgr.setMouseCapture( NULL );
+
+ if( mMouseUpCallback )
+ {
+ mMouseUpCallback( this, mCallbackUserData );
+ }
+ handled = TRUE;
+ make_ui_sound("UISndClickRelease");
+ }
+ else
+ {
+ handled = TRUE;
+ }
+
+ return handled;
+}
+
+BOOL LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask)
+{
+ // only do sticky-focus on non-chrome widgets
+ if (!getIsChrome())
+ {
+ setFocus(TRUE);
+ }
+ if( mMouseDownCallback )
+ {
+ mMouseDownCallback( this, mCallbackUserData );
+ }
+
+ if (MASK_CONTROL & mask) // if CTRL is modifying
+ {
+ setCurSliderValue(mInitialValue);
+ onCommit();
+ }
+ else
+ {
+ // scroll through thumbs to see if we have a new one selected and select that one
+ std::map<LLString, LLRect>::iterator mIt = mThumbRects.begin();
+ for(; mIt != mThumbRects.end(); mIt++) {
+
+ // check if inside. If so, set current slider and continue
+ if(mIt->second.pointInRect(x,y)) {
+ mCurSlider = mIt->first;
+ break;
+ }
+ }
+
+ // Find the offset of the actual mouse location from the center of the thumb.
+ if (mThumbRects[mCurSlider].pointInRect(x,y))
+ {
+ mMouseOffset = (mThumbRects[mCurSlider].mLeft + MULTI_THUMB_WIDTH/2) - x;
+ }
+ else
+ {
+ mMouseOffset = 0;
+ }
+
+ // Start dragging the thumb
+ // No handler needed for focus lost since this class has no state that depends on it.
+ gFocusMgr.setMouseCapture( this );
+ mDragStartThumbRect = mThumbRects[mCurSlider];
+ }
+ make_ui_sound("UISndClick");
+
+ return TRUE;
+}
+
+BOOL LLMultiSlider::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent)
+{
+ BOOL handled = FALSE;
+ if( getVisible() && getEnabled() && !called_from_parent )
+ {
+ switch(key)
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ // eat up and down keys to be consistent
+ handled = TRUE;
+ break;
+ case KEY_LEFT:
+ setCurSliderValue(getCurSliderValue() - getIncrement());
+ onCommit();
+ handled = TRUE;
+ break;
+ case KEY_RIGHT:
+ setCurSliderValue(getCurSliderValue() + getIncrement());
+ onCommit();
+ handled = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ return handled;
+}
+
+void LLMultiSlider::draw()
+{
+ LLColor4 curThumbColor;
+
+ std::map<LLString, LLRect>::iterator mIt;
+ std::map<LLString, LLRect>::iterator curSldrIt;
+ if( getVisible() )
+ {
+ // Draw background and thumb.
+
+ // drawing solids requires texturing be disabled
+ LLGLSNoTexture no_texture;
+
+ LLRect rect(mDragStartThumbRect);
+
+ F32 opacity = getEnabled() ? 1.f : 0.3f;
+
+ // Track
+ LLUUID thumb_image_id;
+ thumb_image_id.set(LLUI::sAssetsGroup->getString("rounded_square.tga"));
+ LLPointer<LLImageGL> thumb_imagep(LLUI::sImageProvider->getUIImageByID(thumb_image_id)->getImage());
+
+ S32 height_offset = (getRect().getHeight() - MULTI_TRACK_HEIGHT) / 2;
+ LLRect track_rect(0, getRect().getHeight() - height_offset, getRect().getWidth(), height_offset );
+
+
+ if(mDrawTrack)
+ {
+ track_rect.stretch(-1);
+ gl_draw_scaled_image_with_border(track_rect.mLeft, track_rect.mBottom, 16, 16, track_rect.getWidth(), track_rect.getHeight(),
+ thumb_imagep, mTrackColor % opacity);
+ }
+
+ // if we're supposed to use a drawn triangle
+ // simple gl call for the triangle
+ if(mUseTriangle) {
+
+ for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
+
+ gl_triangle_2d(
+ mIt->second.mLeft - EXTRA_TRIANGLE_WIDTH,
+ mIt->second.mTop + EXTRA_TRIANGLE_HEIGHT,
+ mIt->second.mRight + EXTRA_TRIANGLE_WIDTH,
+ mIt->second.mTop + EXTRA_TRIANGLE_HEIGHT,
+ mIt->second.mLeft + mIt->second.getWidth() / 2,
+ mIt->second.mBottom - EXTRA_TRIANGLE_HEIGHT,
+ mTriangleColor, TRUE);
+ }
+ }
+ else if (!thumb_imagep)
+ {
+ // draw all the thumbs
+ curSldrIt = mThumbRects.end();
+ for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
+
+ // choose the color
+ curThumbColor = mThumbCenterColor;
+ if(mIt->first == mCurSlider) {
+
+ curSldrIt = mIt;
+ continue;
+ //curThumbColor = mThumbCenterSelectedColor;
+ }
+
+ // the draw command
+ gl_rect_2d(mIt->second, curThumbColor, TRUE);
+ }
+
+ // now draw the current slider
+ if(curSldrIt != mThumbRects.end()) {
+ gl_rect_2d(curSldrIt->second, mThumbCenterSelectedColor, TRUE);
+ }
+
+ // and draw the drag start
+ if (gFocusMgr.getMouseCapture() == this)
+ {
+ gl_rect_2d(mDragStartThumbRect, mThumbCenterColor % opacity, FALSE);
+ }
+ }
+ else if( gFocusMgr.getMouseCapture() == this )
+ {
+ // draw drag start
+ gl_draw_scaled_image_with_border(mDragStartThumbRect.mLeft,
+ mDragStartThumbRect.mBottom, 16, 16,
+ mDragStartThumbRect.getWidth(),
+ mDragStartThumbRect.getHeight(),
+ thumb_imagep, mThumbCenterColor % 0.3f, TRUE);
+
+ // draw the highlight
+ if (hasFocus())
+ {
+ F32 lerp_amt = gFocusMgr.getFocusFlashAmt();
+ LLRect highlight_rect = mThumbRects[mCurSlider];
+ highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt)));
+ gl_draw_scaled_image_with_border(highlight_rect.mLeft,
+ highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(),
+ highlight_rect.getHeight(),
+ thumb_imagep, gFocusMgr.getFocusColor());
+ }
+
+ // draw the thumbs
+ curSldrIt = mThumbRects.end();
+ for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
+
+ // choose the color
+ curThumbColor = mThumbCenterColor;
+ if(mIt->first == mCurSlider) {
+ // don't draw now, draw last
+ curSldrIt = mIt;
+ continue;
+ }
+
+ // the draw command
+ gl_draw_scaled_image_with_border(
+ mIt->second.mLeft,
+ mIt->second.mBottom, 16, 16,
+ mIt->second.getWidth(),
+ mIt->second.getHeight(), thumb_imagep,
+ curThumbColor, TRUE);
+ }
+
+ // draw cur slider last
+ if(curSldrIt != mThumbRects.end()) {
+ gl_draw_scaled_image_with_border(
+ curSldrIt->second.mLeft,
+ curSldrIt->second.mBottom, 16, 16,
+ curSldrIt->second.getWidth(),
+ curSldrIt->second.getHeight(), thumb_imagep,
+ mThumbCenterSelectedColor, TRUE);
+ }
+
+ }
+ else
+ {
+ // draw highlight
+ if (hasFocus())
+ {
+ F32 lerp_amt = gFocusMgr.getFocusFlashAmt();
+ LLRect highlight_rect = mThumbRects[mCurSlider];
+ highlight_rect.stretch(llround(lerp(1.f, 3.f, lerp_amt)));
+ gl_draw_scaled_image_with_border(highlight_rect.mLeft, highlight_rect.mBottom, 16, 16, highlight_rect.getWidth(), highlight_rect.getHeight(),
+ thumb_imagep, gFocusMgr.getFocusColor());
+ }
+
+ // draw thumbs
+ curSldrIt = mThumbRects.end();
+ for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) {
+
+ // choose the color
+ curThumbColor = mThumbCenterColor;
+ if(mIt->first == mCurSlider) {
+ curSldrIt = mIt;
+ continue;
+ //curThumbColor = mThumbCenterSelectedColor;
+ }
+
+ // the draw command
+ gl_draw_scaled_image_with_border(
+ mIt->second.mLeft,
+ mIt->second.mBottom, 16, 16,
+ mIt->second.getWidth(),
+ mIt->second.getHeight(), thumb_imagep,
+ curThumbColor % opacity, TRUE);
+ }
+
+ if(curSldrIt != mThumbRects.end()) {
+ gl_draw_scaled_image_with_border(
+ curSldrIt->second.mLeft,
+ curSldrIt->second.mBottom, 16, 16,
+ curSldrIt->second.getWidth(),
+ curSldrIt->second.getHeight(), thumb_imagep,
+ mThumbCenterSelectedColor % opacity, TRUE);
+ }
+ }
+
+ LLUICtrl::draw();
+ }
+}
+
+// virtual
+LLXMLNodePtr LLMultiSlider::getXML(bool save_children) const
+{
+ LLXMLNodePtr node = LLUICtrl::getXML();
+
+ node->createChild("initial_val", TRUE)->setFloatValue(getInitialValue());
+ node->createChild("min_val", TRUE)->setFloatValue(getMinValue());
+ node->createChild("max_val", TRUE)->setFloatValue(getMaxValue());
+ node->createChild("increment", TRUE)->setFloatValue(getIncrement());
+
+ return node;
+}
+
+
+//static
+LLView* LLMultiSlider::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
+{
+ LLString name("multi_slider_bar");
+ node->getAttributeString("name", name);
+
+ LLRect rect;
+ createRect(node, rect, parent, LLRect());
+
+ F32 initial_value = 0.f;
+ node->getAttributeF32("initial_val", initial_value);
+
+ F32 min_value = 0.f;
+ node->getAttributeF32("min_val", min_value);
+
+ F32 max_value = 1.f;
+ node->getAttributeF32("max_val", max_value);
+
+ F32 increment = 0.1f;
+ node->getAttributeF32("increment", increment);
+
+ S32 max_sliders = 1;
+ node->getAttributeS32("max_sliders", max_sliders);
+
+ BOOL allow_overlap = FALSE;
+ node->getAttributeBOOL("allow_overlap", allow_overlap);
+
+ BOOL draw_track = TRUE;
+ node->getAttributeBOOL("draw_track", draw_track);
+
+ BOOL use_triangle = FALSE;
+ node->getAttributeBOOL("use_triangle", use_triangle);
+
+ LLMultiSlider* multiSlider = new LLMultiSlider(name,
+ rect,
+ NULL,
+ NULL,
+ initial_value,
+ min_value,
+ max_value,
+ increment,
+ max_sliders,
+ allow_overlap,
+ draw_track,
+ use_triangle);
+
+ multiSlider->initFromXML(node, parent);
+
+ return multiSlider;
+}