123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "console/console.h"
- #include "console/consoleTypes.h"
- #include "console/engineAPI.h"
- #include "gfx/gfxTextureManager.h"
- #include "gui/controls/guiSliderCtrl.h"
- #include "gui/core/guiDefaultControlRender.h"
- #include "gfx/primBuilder.h"
- #include "gfx/gfxDrawUtil.h"
- #include "sfx/sfxSystem.h"
- #include "sfx/sfxTrack.h"
- IMPLEMENT_CONOBJECT( GuiSliderCtrl );
- ConsoleDocClass( GuiSliderCtrl,
- "@brief A control that displays a value between its minimal and maximal bounds using a slider placed on a vertical "
- "or horizontal axis.\n\n"
-
- "A slider displays a value and allows that value to be changed by dragging a thumb control along the axis of the "
- "slider. In this way, the value is changed between its allowed minimum and maximum.\n\n"
-
- "To hook up script code to the value changes of a slider, use the #command and #altCommand properties. #command is "
- "executed once the thumb is released by the user whereas #altCommand is called any time the slider value changes. "
- "When changing the slider value from script, however, trigger of #altCommand is suppressed by default.\n\n"
-
- "The orientation of a slider is automatically determined from the ratio of its width to its height. If a slider is "
- "taller than it is wide, it will be rendered with a vertical orientation. If it is wider than it is tall, it will be "
- "rendered with a horizontal orientation.\n\n"
-
- "The rendering of a slider depends on the bitmap in the slider's profile. This bitmap must be a bitmap array comprised "
- "of at least five bitmap rectangles. The rectangles are used such that:\n\n"
-
- "- Rectangle #1: Left edge of slider\n"
- "- Rectangle #2: Center piece of slider; this is stretched between the left and right edge\n"
- "- Rectangle #3: Right edge of slider\n"
- "- Rectangle #4: Thumb button in normal state\n"
- "- Rectangle #5: Thumb button in highlighted (mouse-over) state\n\n"
-
- "@tsexample\n"
- "// Create a sound source and a slider that changes the volume of the source.\n"
- "\n"
- "%source = sfxPlayOnce( \"art/sound/testing\", AudioLoop2D );\n"
- "\n"
- "new GuiSlider()\n"
- "{\n"
- " // Update the sound source volume when the slider is being dragged and released.\n"
- " command = %source @ \".setVolume( $ThisControl.value );\";\n"
- "\n"
- " // Limit the range to 0..1 since that is the allowable range for sound volumes.\n"
- " range = \"0 1\";\n"
- "};\n"
- "@endtsexample\n\n"
-
- "@see GuiTextEditSliderCtrl\n"
- "@see GuiTextEditSliderBitmapCtrl\n\n"
-
- "@ingroup GuiValues"
- );
- IMPLEMENT_CALLBACK( GuiSliderCtrl, onMouseDragged, void, (), (),
- "Called when the left mouse button is dragged across the slider." );
- //----------------------------------------------------------------------------
- GuiSliderCtrl::GuiSliderCtrl()
- : mRange( 0., 1.f ),
- mTicks( 10 ),
- mRenderTicks(true),
- mSnap( false ),
- mValue( 0.5f ),
- mThumbSize( 8, 20 ),
- mShiftPoint( 5 ),
- mShiftExtent( 10 ),
- mIncAmount( 0.f ),
- mDisplayValue( false ),
- mMouseOver( false ),
- mDepressed( false ),
- mMouseDragged( false ),
- mHasTexture(false),
- mUseFillBar(false),
- mFillBarColor(ColorI(255,255,255)),
- mBitmapBounds(NULL)
- {
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::initPersistFields()
- {
- addGroup( "Slider" );
-
- addField( "range", TypePoint2F, Offset( mRange, GuiSliderCtrl ),
- "Min and max values corresponding to left and right slider position." );
- addField( "ticks", TypeS32, Offset( mTicks, GuiSliderCtrl ),
- "Spacing between tick marks in pixels. 0=off." );
- addField( "snap", TypeBool, Offset( mSnap, GuiSliderCtrl ),
- "Whether to snap the slider to tick marks." );
- addProtectedField( "value", TypeF32, Offset( mValue, GuiSliderCtrl ),
- _setValue, defaultProtectedGetFn,
- "The value corresponding to the current slider position." );
- addField("useFillBar", TypeBool, Offset(mUseFillBar, GuiSliderCtrl),
- "Whether to render the tick marks.");
- addField("fillBarColor", TypeColorI, Offset(mFillBarColor, GuiSliderCtrl),
- "Whether to render the tick marks.");
- addField("renderTicks", TypeBool, Offset(mRenderTicks, GuiSliderCtrl),
- "Whether to render the tick marks.");
-
- endGroup( "Slider" );
- Parent::initPersistFields();
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::setValue(F32 val, bool doCallback)
- {
- _updateThumb( val, mSnap, false, doCallback );
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::setActive( bool value )
- {
- if( !value && mDepressed )
- {
- // We're in the middle of a drag. Finish it here as once we've
- // been deactivated, we are not going to see a mouse-up event.
- mDepressed = false;
- mouseUnlock();
- execConsoleCallback();
- }
- Parent::setActive( value );
- }
- //----------------------------------------------------------------------------
- bool GuiSliderCtrl::onWake()
- {
- if( !Parent::onWake() )
- return false;
-
- mHasTexture = mProfile->constructBitmapArray() >= NumBitmaps;
- if( mHasTexture )
- {
- mBitmapBounds = mProfile->mBitmapArrayRects.address();
- mThumbSize = Point2I( mBitmapBounds[ SliderButtonNormal ].extent.x, mBitmapBounds[ SliderButtonNormal ].extent.y );
- }
- F32 value;
- if( mConsoleVariable[ 0 ] )
- value = getFloatVariable();
- else
- value = mValue;
- mValue = mClampF( value, mRange.x, mRange.y );
- // mouse scroll increment percentage is 5% of the range
- mIncAmount = ( ( mRange.y - mRange.x ) * 0.05 );
- if( ( mThumbSize.y + mProfile->mFont->getHeight() - 4 ) <= getExtent().y )
- mDisplayValue = true;
- else
- mDisplayValue = false;
- _updateThumb( mValue, mSnap, true );
- return true;
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::onMouseDown(const GuiEvent &event)
- {
- if ( !mActive || !mAwake || !mVisible )
- return;
- mouseLock();
- setFirstResponder();
- mDepressed = true;
- if (mProfile->isSoundButtonDownValid())
- SFX->playOnce(mProfile->getSoundButtonDownProfile());
- Point2I curMousePos = globalToLocalCoord( event.mousePoint );
- F32 value;
- if (getWidth() >= getHeight())
- value = F32(curMousePos.x-mShiftPoint) / F32(getWidth()-mShiftExtent)*(mRange.y-mRange.x) + mRange.x;
- else
- value = F32(curMousePos.y) / F32(getHeight())*(mRange.y-mRange.x) + mRange.x;
-
- _updateThumb( value, mSnap || ( event.modifier & SI_SHIFT ) );
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::onMouseDragged( const GuiEvent &event )
- {
- if ( !mActive || !mAwake || !mVisible )
- return;
-
- mMouseDragged = true;
- F32 value = _getThumbValue( event );
- _updateThumb( value, mSnap || ( event.modifier & SI_SHIFT ) );
- onMouseDragged_callback();
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::onMouseUp( const GuiEvent& event )
- {
- if ( !mActive || !mAwake || !mVisible )
- return;
- mouseUnlock();
- mDepressed = false;
- mMouseDragged = false;
- _updateThumb( _getThumbValue( event ), event.modifier & SI_SHIFT );
-
- execConsoleCallback();
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::onMouseEnter(const GuiEvent &event)
- {
- setUpdate();
- if( isMouseLocked() )
- {
- mDepressed = true;
- mMouseOver = true;
- }
- else
- {
- if( mActive && mProfile->mSoundButtonOver )
- {
- //F32 pan = (F32(event.mousePoint.x)/F32(getRoot()->getWidth())*2.0f-1.0f)*0.8f;
- if (mProfile->isSoundButtonOverValid())
- SFX->playOnce(mProfile->getSoundButtonOverProfile());
- }
-
- mMouseOver = true;
- }
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::onMouseLeave(const GuiEvent &)
- {
- setUpdate();
- if( isMouseLocked() )
- mDepressed = false;
- mMouseOver = false;
- }
- //----------------------------------------------------------------------------
- bool GuiSliderCtrl::onMouseWheelUp(const GuiEvent &event)
- {
- if ( !mActive || !mAwake || !mVisible )
- return Parent::onMouseWheelUp(event);
- _updateThumb( mValue + mIncAmount, ( event.modifier & SI_SHIFT ) );
- execConsoleCallback();
- return true;
- }
- //----------------------------------------------------------------------------
- bool GuiSliderCtrl::onMouseWheelDown(const GuiEvent &event)
- {
- if ( !mActive || !mAwake || !mVisible )
- return Parent::onMouseWheelUp(event);
- _updateThumb( mValue - mIncAmount, ( event.modifier & SI_SHIFT ) );
- execConsoleCallback();
- return true;
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::_updateThumb( F32 _value, bool snap, bool onWake, bool doCallback )
- {
- if( snap && mTicks > 0 )
- {
- // If the shift key is held, snap to the nearest tick, if any are being drawn
- F32 tickStep = (mRange.y - mRange.x) / F32(mTicks + 1);
- F32 tickSteps = (_value - mRange.x) / tickStep;
- S32 actualTick = S32(tickSteps + 0.5);
- _value = actualTick * tickStep + mRange.x;
- }
-
- // Clamp the thumb to legal values.
- if( _value < mRange.x )
- _value = mRange.x;
- if( _value > mRange.y )
- _value = mRange.y;
-
- // If value hasn't changed and this isn't the initial update on
- // waking, do nothing.
- if( mValue == _value && !onWake )
- return;
- mValue = _value;
- Point2I ext = getExtent();
- ext.x -= ( mShiftExtent + mThumbSize.x ) / 2;
- // update the bounding thumb rect
- if (getWidth() >= getHeight())
- { // HORZ thumb
- S32 mx = (S32)((F32(ext.x) * (mValue-mRange.x) / (mRange.y-mRange.x)));
- S32 my = ext.y/2;
- if(mDisplayValue)
- my = mThumbSize.y/2;
- mThumb.point.x = mx - (mThumbSize.x/2);
- mThumb.point.y = my - (mThumbSize.y/2);
- mThumb.extent = mThumbSize;
- }
- else
- { // VERT thumb
- S32 mx = ext.x/2;
- S32 my = (S32)((F32(ext.y) * (mValue-mRange.x) / (mRange.y-mRange.x)));
- mThumb.point.x = mx - (mThumbSize.y/2);
- mThumb.point.y = my - (mThumbSize.x/2);
- mThumb.extent.x = mThumbSize.y;
- mThumb.extent.y = mThumbSize.x;
- }
-
- setFloatVariable(mValue);
- setUpdate();
- // Use the alt console command if you want to continually update:
- if ( !onWake && doCallback )
- execAltConsoleCallback();
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::onRender(Point2I offset, const RectI &updateRect)
- {
- Point2I pos(offset.x+mShiftPoint, offset.y);
- Point2I ext(getWidth() - mShiftExtent, getHeight());
- RectI thumb = mThumb;
- GFXDrawUtil* drawUtil = GFX->getDrawUtil();
- if (mUseFillBar)
- {
- drawUtil->drawRectFill(RectI(offset.x, offset.y, getWidth() * mValue, getHeight()), mFillBarColor);
- renderChildControls(offset, updateRect);
- return;
- }
- if( mHasTexture )
- {
- if(mTicks > 0 && mRenderTicks)
- {
- // TODO: tick marks should be positioned based on the bitmap dimensions.
- Point2I mid(ext.x, ext.y/2);
- Point2I oldpos = pos;
- pos += Point2I(1, 0);
- PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f );
- PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 );
- // tick marks
- for (U32 t = 0; t <= (mTicks+1); t++)
- {
- S32 x = (S32)(F32(mid.x+1)/F32(mTicks+1)*F32(t)) + pos.x;
- S32 y = pos.y + mid.y;
- PrimBuild::vertex2i(x, y + mShiftPoint);
- PrimBuild::vertex2i(x, y + mShiftPoint*2 + 2);
- }
- PrimBuild::end();
- // TODO: it would be nice, if the primitive builder were a little smarter,
- // so that we could change colors midstream.
- PrimBuild::color4f(0.9f, 0.9f, 0.9f, 1.0f);
- PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 );
- // tick marks
- for (U32 t = 0; t <= (mTicks+1); t++)
- {
- S32 x = (S32)(F32(mid.x+1)/F32(mTicks+1)*F32(t)) + pos.x + 1;
- S32 y = pos.y + mid.y + 1;
- PrimBuild::vertex2i(x, y + mShiftPoint );
- PrimBuild::vertex2i(x, y + mShiftPoint * 2 + 3);
- }
- PrimBuild::end();
- pos = oldpos;
- }
- S32 index = SliderButtonNormal;
- if(mMouseOver)
- index = SliderButtonHighlight;
- drawUtil->clearBitmapModulation();
- //left border
- drawUtil->drawBitmapSR(mProfile->getBitmapResource(), Point2I(offset.x,offset.y), mBitmapBounds[SliderLineLeft]);
- //right border
- drawUtil->drawBitmapSR(mProfile->getBitmapResource(), Point2I(offset.x + getWidth() - mBitmapBounds[SliderLineRight].extent.x, offset.y), mBitmapBounds[SliderLineRight]);
- //draw our center piece to our slider control's border and stretch it
- RectI destRect;
- destRect.point.x = offset.x + mBitmapBounds[SliderLineLeft].extent.x;
- destRect.extent.x = getWidth() - mBitmapBounds[SliderLineLeft].extent.x - mBitmapBounds[SliderLineRight].extent.x;
- destRect.point.y = offset.y;
- destRect.extent.y = mBitmapBounds[SliderLineCenter].extent.y;
- RectI stretchRect;
- stretchRect = mBitmapBounds[SliderLineCenter];
- stretchRect.inset(1,0);
- drawUtil->drawBitmapStretchSR(mProfile->getBitmapResource(), destRect, stretchRect);
- //draw our control slider button
- thumb.point += pos;
- drawUtil->drawBitmapSR(mProfile->getBitmapResource(),Point2I(thumb.point.x,offset.y ),mBitmapBounds[index]);
- }
- else if (getWidth() >= getHeight())
- {
- Point2I mid(ext.x, ext.y/2);
- if(mDisplayValue)
- mid.set(ext.x, mThumbSize.y/2);
- PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f );
- PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 + 2);
- // horz rule
- PrimBuild::vertex2i( pos.x, pos.y + mid.y );
- PrimBuild::vertex2i( pos.x + mid.x, pos.y + mid.y );
- // tick marks
- if (mRenderTicks)
- {
- for (U32 t = 0; t <= (mTicks + 1); t++)
- {
- S32 x = (S32)(F32(mid.x - 1) / F32(mTicks + 1) * F32(t));
- PrimBuild::vertex2i(pos.x + x, pos.y + mid.y - mShiftPoint);
- PrimBuild::vertex2i(pos.x + x, pos.y + mid.y + mShiftPoint);
- }
- }
- PrimBuild::end();
- }
- else
- {
- Point2I mid(ext.x/2, ext.y);
- PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f );
- PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 + 2);
- // horz rule
- PrimBuild::vertex2i( pos.x + mid.x, pos.y );
- PrimBuild::vertex2i( pos.x + mid.x, pos.y + mid.y );
- // tick marks
- if (mRenderTicks)
- {
- for (U32 t = 0; t <= (mTicks + 1); t++)
- {
- S32 y = (S32)(F32(mid.y - 1) / F32(mTicks + 1) * F32(t));
- PrimBuild::vertex2i(pos.x + mid.x - mShiftPoint, pos.y + y);
- PrimBuild::vertex2i(pos.x + mid.x + mShiftPoint, pos.y + y);
- }
- }
- PrimBuild::end();
- mDisplayValue = false;
- }
- // draw the thumb
- thumb.point += pos;
- renderRaisedBox(thumb, mProfile);
- if(mDisplayValue)
- {
- char buf[20];
- dSprintf(buf,sizeof(buf),"%0.3f",mValue);
- Point2I textStart = thumb.point;
- S32 txt_w = mProfile->mFont->getStrWidth((const UTF8 *)buf);
- textStart.x += (S32)((thumb.extent.x/2.0f));
- textStart.y += thumb.extent.y - 2; //19
- textStart.x -= (txt_w/2);
- if(textStart.x < offset.x)
- textStart.x = offset.x;
- else if(textStart.x + txt_w > offset.x+getWidth())
- textStart.x -=((textStart.x + txt_w) - (offset.x+getWidth()));
- drawUtil->setBitmapModulation(mProfile->mFontColor);
- drawUtil->drawText(mProfile->mFont, textStart, buf, mProfile->mFontColors);
- }
- renderChildControls(offset, updateRect);
- }
- //----------------------------------------------------------------------------
- bool GuiSliderCtrl::resize( const Point2I& newPosition, const Point2I& newSize )
- {
- if( !Parent::resize( newPosition, newSize ) )
- return false;
-
- _updateThumb( mValue, false, true, false );
- return true;
- }
- //----------------------------------------------------------------------------
- void GuiSliderCtrl::parentResized( const RectI& oldParentRect, const RectI& newParentRect )
- {
- Parent::parentResized( oldParentRect, newParentRect );
-
- _updateThumb( mValue, false, true, false );
- }
- //----------------------------------------------------------------------------
- F32 GuiSliderCtrl::_getThumbValue( const GuiEvent& event )
- {
- Point2I curMousePos = globalToLocalCoord( event.mousePoint );
- F32 value;
- if( getWidth() >= getHeight() )
- value = F32( curMousePos.x - mShiftPoint ) / F32( getWidth() - mShiftExtent ) * ( mRange.y - mRange.x ) + mRange.x;
- else
- value = F32( curMousePos.y ) / F32( getHeight() ) * ( mRange.y - mRange.x ) + mRange.x;
- if(value > mRange.y )
- value = mRange.y;
- else if( value < mRange.x )
- value = mRange.x;
- if( mSnap || ( event.modifier & SI_SHIFT && mTicks >= 1 ) )
- {
- // If the shift key is held, snap to the nearest tick, if any are being drawn
- F32 tickStep = ( mRange.y - mRange.x ) / F32( mTicks + 1 );
- F32 tickSteps = (value - mRange.x ) / tickStep;
- S32 actualTick = S32( tickSteps + 0.5 );
- value = actualTick * tickStep + mRange.x;
- AssertFatal( value <= mRange.y && value >= mRange.x, "Error, out of bounds value generated from shift-snap of slider" );
- }
- return value;
- }
- //=============================================================================
- // Console Methods.
- //=============================================================================
- //-----------------------------------------------------------------------------
- DefineEngineMethod( GuiSliderCtrl, getValue, F32, (),,
- "Get the current value of the slider based on the position of the thumb.\n"
- "@return Slider position (from range.x to range.y)." )
- {
- return object->getValue();
- }
- //----------------------------------------------------------------------------
- DefineEngineMethod( GuiSliderCtrl, setValue, void, ( F32 pos, bool doCallback ), ( false ),
- "Set position of the thumb on the slider.\n"
- "@param pos New slider position (from range.x to range.y)\n"
- "@param doCallback If true, the altCommand callback will be invoked\n" )
- {
- object->setValue( pos, doCallback );
- }
- //----------------------------------------------------------------------------
- DefineEngineMethod( GuiSliderCtrl, isThumbBeingDragged, bool, (),,
- "Returns true if the thumb is currently being dragged by the user. This method is mainly useful "
- "for scrubbing type sliders where the slider position is sync'd to a changing value. When the "
- "user is dragging the thumb, however, the sync'ing should pause and not get in the way of the user." )
- {
- return object->isThumbBeingDragged();
- }
|