diff options
| author | Brad Kittenbrink <brad@lindenlab.com> | 2008-02-27 18:58:14 +0000 |
|---|---|---|
| committer | Brad Kittenbrink <brad@lindenlab.com> | 2008-02-27 18:58:14 +0000 |
| commit | 6d52efe452aa8469e0343da1c7d108f3f52ab651 (patch) | |
| tree | a87be48e9840d7fc1f7ee514d7c7f994e71fdb3c /indra/llui/llmultislider.cpp | |
| parent | 6027ad2630b8650cabcf00628ee9b0d25bedd67f (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.cpp | 677 |
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; +} |
