//----------------------------------------------------------------------------- // 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 "platform/platform.h" #include "gui/buttons/guiBitmapButtonCtrl.h" #include "core/util/path.h" #include "console/console.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "gui/core/guiCanvas.h" #include "gui/core/guiDefaultControlRender.h" #include "gfx/gfxDrawUtil.h" #include "gfx/gfxTextureManager.h" #include "gui/editor/inspector/group.h" #include "gui/editor/inspector/field.h" #include "gui/editor/guiInspector.h" ImplementEnumType( GuiBitmapMode, "Rendering behavior when placing bitmaps in controls.\n\n" "@ingroup GuiImages" ) { GuiBitmapButtonCtrl::BitmapStretched, "Stretched", "Stretch bitmap to fit control extents." }, { GuiBitmapButtonCtrl::BitmapCentered, "Centered", "Center bitmap in control." }, EndImplementEnumType; //============================================================================= // GuiBitmapButtonCtrl //============================================================================= IMPLEMENT_CONOBJECT(GuiBitmapButtonCtrl); ConsoleDocClass( GuiBitmapButtonCtrl, "@brief A button that renders its various states (mouse over, pushed, etc.) from separate bitmaps.\n\n" "A bitmapped button is a push button that uses one or more texture images for rendering its individual states.\n\n" "To find the individual textures associated with the button, a naming scheme is used. For each state " "a suffix is appended to the texture file name given in the GuiBitmapButtonCtrl::bitmap field:\n" "- \"_n\": Normal state. This one will be active when no other state applies.\n" "- \"_h\": Highlighted state. This applies when the mouse is hovering over the button.\n" "- \"_d\": Depressed state. This applies when the left mouse button has been clicked on the button but not yet released.\n" "- \"_i\": Inactive state. This applies when the button control has been deactivated (GuiControl::setActive())\n\n" "If a bitmap for a particular state cannot be found, the default bitmap will be used. To disable all state-based " "bitmap functionality, set useStates to false which will make the control solely render from the bitmap specified " "in the bitmap field.\n\n" "@section guibitmapbutton_modifiers Per-Modifier Button Actions\n" "If GuiBitmapButtonCtrl::useModifiers is set to true, per-modifier button actions and textures are enabled. This functionality " "allows to associate different images and different actions with a button depending on which modifiers are pressed " "on the keyboard by the user.\n\n" "When enabled, this functionality alters the texture lookup above by prepending the following strings to the " "suffixes listed above:\n" "- \"\": Default. No modifier is pressed.\n" "- \"_ctrl\": Image to use when CTRL/CMD is down.\n" "- \"_alt\": Image to use when ALT is down.\n" "- \"_shift\": Image to use when SHIFT is down\n\n" "When this functionality is enabled, a new set of callbacks is used:\n" "- onDefaultClick: Button was clicked without a modifier being presssed.\n" "- onCtrlClick: Button was clicked with the CTRL/CMD key down.\n" "- onAltClick: Button was clicked with the ALT key down.\n" "- onShiftClick: Button was clicked with the SHIFT key down.\n\n" "GuiControl::command or GuiControl::onAction() still work as before when per-modifier functionality is enabled.\n\n" "Note that modifiers cannot be mixed. If two or more modifiers are pressed, a single one will take precedence over " "the remaining modifiers. The order of precedence corresponds to the order listed above.\n\n" "@tsexample\n" "// Create an OK button that will trigger an onOk() call on its parent when clicked:\n" "%okButton = new GuiBitmapButtonCtrl()\n" "{\n" " bitmap = \"art/gui/okButton\";\n" " autoFitExtents = true;\n" " command = \"$ThisControl.getParent().onOk();\";\n" "};\n" "@endtsexample\n\n" "@ingroup GuiButtons" ); IMPLEMENT_CALLBACK( GuiBitmapButtonCtrl, onDefaultClick, void, (), (), "Called when per-modifier functionality is enabled and the user clicks on the button without any modifier pressed.\n" "@ref guibitmapbutton_modifiers" ); IMPLEMENT_CALLBACK( GuiBitmapButtonCtrl, onCtrlClick, void, (), (), "Called when per-modifier functionality is enabled and the user clicks on the button with the CTRL key pressed.\n" "@ref guibitmapbutton_modifiers" ); IMPLEMENT_CALLBACK( GuiBitmapButtonCtrl, onAltClick, void, (), (), "Called when per-modifier functionality is enabled and the user clicks on the button with the ALT key pressed.\n" "@ref guibitmapbutton_modifiers" ); IMPLEMENT_CALLBACK( GuiBitmapButtonCtrl, onShiftClick, void, (), (), "Called when per-modifier functionality is enabled and the user clicks on the button with the SHIFT key pressed.\n" "@ref guibitmapbutton_modifiers" ); //----------------------------------------------------------------------------- GuiBitmapButtonCtrl::GuiBitmapButtonCtrl() { mBitmapMode = BitmapStretched; mAutoFitExtents = false; mUseModifiers = false; mUseStates = true; setExtent( 140, 30 ); mMasked = false; mColor = ColorI::WHITE; INIT_ASSET(Bitmap); } //----------------------------------------------------------------------------- void GuiBitmapButtonCtrl::initPersistFields() { docsURL; addGroup( "Bitmap" ); addProtectedField("Bitmap", TypeImageFilename, Offset(mBitmapName, GuiBitmapButtonCtrl), _setBitmapFieldData, &defaultProtectedGetFn, "Texture file to display on this button.\n" "If useStates is false, this will be the file that renders on the control. Otherwise, this will " "specify the default texture name to which the various state and modifier suffixes are appended " "to find the per-state and per-modifier (if enabled) textures.", AbstractClassRep::FIELD_HideInInspectors); \ addProtectedField("BitmapAsset", TypeImageAssetId, Offset(mBitmapAssetId, GuiBitmapButtonCtrl), _setBitmapFieldData, &defaultProtectedGetFn, "Texture file to display on this button.\n" "If useStates is false, this will be the file that renders on the control. Otherwise, this will " "specify the default texture name to which the various state and modifier suffixes are appended " "to find the per-state and per-modifier (if enabled) textures."); addField("color", TypeColorI, Offset(mColor, GuiBitmapButtonCtrl), "color mul"); addField( "bitmapMode", TYPEID< BitmapMode >(), Offset( mBitmapMode, GuiBitmapButtonCtrl ), "Behavior for fitting the bitmap to the control extents.\n" "If set to 'Stretched', the bitmap will be stretched both verticall and horizontally to fit inside " "the control's extents.\n\n" "If set to 'Centered', the bitmap will stay at its original resolution centered in the control's " "rectangle (getting clipped if the control is smaller than the texture)." ); addProtectedField( "autoFitExtents", TypeBool, Offset( mAutoFitExtents, GuiBitmapButtonCtrl ), &_setAutoFitExtents, &defaultProtectedGetFn, "If true, the control's extents will be set to match the bitmap's extents when setting the bitmap.\n" "The bitmap extents will always be taken from the default/normal bitmap (in case the extents of the various " "bitmaps do not match up.)" ); addField( "useModifiers", TypeBool, Offset( mUseModifiers, GuiBitmapButtonCtrl ), "If true, per-modifier button functionality is enabled.\n" "@ref guibitmapbutton_modifiers" ); addField( "useStates", TypeBool, Offset( mUseStates, GuiBitmapButtonCtrl ), "If true, per-mouse state button functionality is enabled.\n" "Defaults to true.\n\n" "If you do not use per-state images on this button set this to false to speed up the loading process " "by inhibiting searches for the individual images." ); addField("masked", TypeBool, Offset(mMasked, GuiBitmapButtonCtrl),"Use alpha masking for interaction."); endGroup( "Bitmap" ); Parent::initPersistFields(); } //----------------------------------------------------------------------------- bool GuiBitmapButtonCtrl::onWake() { if (! Parent::onWake()) return false; setActive( true ); setBitmap( getBitmap() ); return true; } //----------------------------------------------------------------------------- void GuiBitmapButtonCtrl::onSleep() { if( dStricmp(mBitmapName, "texhandle") != 0 ) for( U32 i = 0; i < NumModifiers; ++ i ) { mTextures[ i ].mTextureNormal = NULL; mTextures[ i ].mTextureHilight = NULL; mTextures[ i ].mTextureDepressed = NULL; mTextures[ i ].mTextureInactive = NULL; } if (mBitmapAsset.notNull()) mBitmap = NULL; Parent::onSleep(); } //----------------------------------------------------------------------------- bool GuiBitmapButtonCtrl::_setAutoFitExtents( void *object, const char *index, const char *data ) { GuiBitmapButtonCtrl* ctrl = reinterpret_cast< GuiBitmapButtonCtrl* >( object ); ctrl->setAutoFitExtents( dAtob( data ) ); return false; } //----------------------------------------------------------------------------- /*bool GuiBitmapButtonCtrl::_setBitmap(void* object, const char* index, const char* data) { GuiBitmapButtonCtrl* ctrl = reinterpret_cast< GuiBitmapButtonCtrl* >( object ); ctrl->setBitmap( StringTable->insert(data) ); return false; }*/ //----------------------------------------------------------------------------- // Legacy method. Can just assign to bitmap field. /*DefineEngineMethod(GuiBitmapButtonCtrl, setBitmap, void, (const char* path), , "Set the bitmap to show on the button.\n" "@param path Path to the texture file in any of the supported formats.\n" ) { object->setBitmap( StringTable->insert(path) ); }*/ //----------------------------------------------------------------------------- void GuiBitmapButtonCtrl::inspectPostApply() { Parent::inspectPostApply(); setBitmap(getBitmap()); // if the extent is set to (0,0) in the gui editor and appy hit, this control will // set it's extent to be exactly the size of the normal bitmap (if present) if ((getWidth() == 0) && (getHeight() == 0) && mTextures[ 0 ].mTextureNormal) { setExtent( mTextures[ 0 ].mTextureNormal->getWidth(), mTextures[ 0 ].mTextureNormal->getHeight()); } } //----------------------------------------------------------------------------- void GuiBitmapButtonCtrl::setAutoFitExtents( bool state ) { mAutoFitExtents = state; if( mAutoFitExtents ) setBitmap( mBitmapName ); } //----------------------------------------------------------------------------- void GuiBitmapButtonCtrl::setBitmap( StringTableEntry name ) { PROFILE_SCOPE( GuiBitmapButtonCtrl_setBitmap ); _setBitmap(name); if( !isAwake() ) return; if( mBitmapAsset.notNull()) { if( dStricmp( getBitmap(), "texhandle" ) != 0 ) { const U32 count = mUseModifiers ? NumModifiers : 1; for( U32 i = 0; i < count; ++ i ) { static String modifiers[] = { "", "_ctrl", "_alt", "_shift" }; static String s_n[2] = { "_n", "_n_image" }; static String s_d[2] = { "_d", "_d_image" }; static String s_h[2] = { "_h", "_h_image" }; static String s_i[2] = { "_i", "_i_image" }; String baseName = mBitmapAssetId; //strip any pre-assigned suffix, just in case baseName = baseName.replace("_n_image", ""); baseName = baseName.replace("_n", ""); if( mUseModifiers ) baseName += modifiers[ i ]; mTextures[ i ].mTextureNormal = GFXTexHandle( mBitmapAsset->getImagePath(), &GFXDefaultGUIProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__)); if( mUseStates ) { //normal lookup StringTableEntry lookupName; for (U32 s = 0; s < 2; s++) { if (!mTextures[i].mTextureNormal) { lookupName = StringTable->insert(String(baseName + s_n[s]).c_str()); if (AssetDatabase.isDeclaredAsset(lookupName)) { mTextures[i].mTextureNormalAssetId = lookupName; mTextures[i].mTextureNormalAsset = mTextures[i].mTextureNormalAssetId; } if (mTextures[i].mTextureNormalAsset.notNull() && mTextures[i].mTextureNormalAsset->getStatus() == AssetBase::Ok) { mTextures[i].mTextureNormal = GFXTexHandle(mTextures[i].mTextureNormalAsset->getImagePath(), &GFXDefaultGUIProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__)); break; } } } //Hilight lookup for (U32 s = 0; s < 2; s++) { lookupName = StringTable->insert(String(baseName + s_h[s]).c_str()); if (AssetDatabase.isDeclaredAsset(lookupName)) { mTextures[i].mTextureHilightAssetId = lookupName; mTextures[i].mTextureHilightAsset = mTextures[i].mTextureHilightAssetId; } if (mTextures[i].mTextureHilightAsset.notNull() && mTextures[i].mTextureHilightAsset->getStatus() == AssetBase::Ok) { mTextures[i].mTextureHilight = GFXTexHandle(mTextures[i].mTextureHilightAsset->getImagePath(), &GFXDefaultGUIProfile, avar("%s() - mTextureHighlight (line %d)", __FUNCTION__, __LINE__)); break; } } if( !mTextures[ i ].mTextureHilight ) mTextures[ i ].mTextureHilight = mTextures[ i ].mTextureNormal; //Depressed lookup for (U32 s = 0; s < 2; s++) { lookupName = StringTable->insert(String(baseName + s_d[s]).c_str()); if (AssetDatabase.isDeclaredAsset(lookupName)) { mTextures[i].mTextureDepressedAssetId = lookupName; mTextures[i].mTextureDepressedAsset = mTextures[i].mTextureDepressedAssetId; } if (mTextures[i].mTextureDepressedAsset.notNull() && mTextures[i].mTextureDepressedAsset->getStatus() == AssetBase::Ok) { mTextures[i].mTextureDepressed = GFXTexHandle(mTextures[i].mTextureDepressedAsset->getImagePath(), &GFXDefaultGUIProfile, avar("%s() - mTextureDepressed (line %d)", __FUNCTION__, __LINE__)); break; } } if( !mTextures[ i ].mTextureDepressed ) mTextures[ i ].mTextureDepressed = mTextures[ i ].mTextureHilight; //Depressed lookup for (U32 s = 0; s < 2; s++) { lookupName = StringTable->insert(String(baseName + s_i[s]).c_str()); if (AssetDatabase.isDeclaredAsset(lookupName)) { mTextures[i].mTextureInactiveAssetId = lookupName; mTextures[i].mTextureInactiveAsset = mTextures[i].mTextureInactiveAssetId; } if (mTextures[i].mTextureInactiveAsset.notNull() && mTextures[i].mTextureInactiveAsset->getStatus() == AssetBase::Ok) { mTextures[i].mTextureInactive = GFXTexHandle(mTextures[i].mTextureInactiveAsset->getImagePath(), &GFXDefaultGUIProfile, avar("%s() - mTextureInactive (line %d)", __FUNCTION__, __LINE__)); break; } } if( !mTextures[ i ].mTextureInactive ) mTextures[ i ].mTextureInactive = mTextures[ i ].mTextureNormal; } if( i == 0 && mTextures[ i ].mTextureNormal.isNull() && mTextures[ i ].mTextureHilight.isNull() && mTextures[ i ].mTextureDepressed.isNull() && mTextures[ i ].mTextureInactive.isNull() ) { Con::warnf( "GuiBitmapButtonCtrl::setBitmap - Unable to load texture: %s", mBitmapName ); this->setBitmap( StringTable->insert(GFXTextureManager::getUnavailableTexturePath().c_str()) ); return; } } } if( mAutoFitExtents && !mTextures[ 0 ].mTextureNormal.isNull() ) setExtent( mTextures[ 0 ].mTextureNormal.getWidth(), mTextures[ 0 ].mTextureNormal.getHeight() ); } else { for( U32 i = 0; i < NumModifiers; ++ i ) { mTextures[ i ].mTextureNormal = NULL; mTextures[ i ].mTextureHilight = NULL; mTextures[ i ].mTextureDepressed = NULL; mTextures[ i ].mTextureInactive = NULL; } } setUpdate(); } //----------------------------------------------------------------------------- void GuiBitmapButtonCtrl::setBitmapHandles(GFXTexHandle normal, GFXTexHandle highlighted, GFXTexHandle depressed, GFXTexHandle inactive) { const U32 count = mUseModifiers ? NumModifiers : 1; for( U32 i = 0; i < count; ++ i ) { mTextures[ i ].mTextureNormal = normal; mTextures[ i ].mTextureHilight = highlighted; mTextures[ i ].mTextureDepressed = depressed; mTextures[ i ].mTextureInactive = inactive; if (!mTextures[ i ].mTextureHilight) mTextures[ i ].mTextureHilight = mTextures[ i ].mTextureNormal; if (!mTextures[ i ].mTextureDepressed) mTextures[ i ].mTextureDepressed = mTextures[ i ].mTextureHilight; if (!mTextures[ i ].mTextureInactive) mTextures[ i ].mTextureInactive = mTextures[ i ].mTextureNormal; if (mTextures[ i ].mTextureNormal.isNull() && mTextures[ i ].mTextureHilight.isNull() && mTextures[ i ].mTextureDepressed.isNull() && mTextures[ i ].mTextureInactive.isNull()) { Con::warnf("GuiBitmapButtonCtrl::setBitmapHandles() - Invalid texture handles"); setBitmap( StringTable->insert(GFXTextureManager::getUnavailableTexturePath().c_str()) ); return; } } mBitmapName = "texhandle"; } //------------------------------------------------------------------------------ GuiBitmapButtonCtrl::Modifier GuiBitmapButtonCtrl::getCurrentModifier() { U8 modifierKeys = Input::getModifierKeys(); if( modifierKeys & SI_PRIMARY_CTRL ) return ModifierCtrl; else if( modifierKeys & SI_PRIMARY_ALT ) return ModifierAlt; else if( modifierKeys & SI_SHIFT ) return ModifierShift; return ModifierNone; } //------------------------------------------------------------------------------ GFXTexHandle& GuiBitmapButtonCtrl::getTextureForCurrentState() { U32 index = ModifierNone; if( mUseModifiers ) index = getCurrentModifier(); if( !mUseStates ) { if( mTextures[ index ].mTextureNormal ) return mTextures[ 0 ].mTextureNormal; else return mTextures[ index ].mTextureNormal; } switch( getState() ) { case NORMAL: if( !mTextures[ index ].mTextureNormal ) return mTextures[ 0 ].mTextureNormal; else return mTextures[ index ].mTextureNormal; case HILIGHT: if( !mTextures[ index ].mTextureHilight ) return mTextures[ 0 ].mTextureHilight; else return mTextures[ index ].mTextureHilight; case DEPRESSED: if( !mTextures[ index ].mTextureDepressed ) return mTextures[ 0 ].mTextureDepressed; else return mTextures[ index ].mTextureDepressed; default: if( !mTextures[ index ].mTextureInactive ) return mTextures[ 0 ].mTextureInactive; else return mTextures[ index ].mTextureInactive; } } //------------------------------------------------------------------------------ void GuiBitmapButtonCtrl::onAction() { Parent::onAction(); if( mUseModifiers ) { switch( getCurrentModifier() ) { case ModifierNone: onDefaultClick_callback(); break; case ModifierCtrl: onCtrlClick_callback(); break; case ModifierAlt: onAltClick_callback(); break; case ModifierShift: onShiftClick_callback(); break; default: break; } } } //------------------------------------------------------------------------------ void GuiBitmapButtonCtrl::onRender(Point2I offset, const RectI& updateRect) { GFXTexHandle& texture = getTextureForCurrentState(); if( texture ) { renderButton( texture, offset, updateRect ); renderChildControls( offset, updateRect ); } else Parent::onRender(offset, updateRect); } //------------------------------------------------------------------------------ void GuiBitmapButtonCtrl::renderButton( GFXTexHandle &texture, const Point2I &offset, const RectI& updateRect ) { GFX->getDrawUtil()->clearBitmapModulation(); GFX->getDrawUtil()->setBitmapModulation(mColor); switch( mBitmapMode ) { case BitmapStretched: { RectI rect( offset, getExtent() ); GFX->getDrawUtil()->drawBitmapStretch( texture, rect ); break; } case BitmapCentered: { Point2I p = offset; p.x += getExtent().x / 2 - texture.getWidth() / 2; p.y += getExtent().y / 2 - texture.getHeight() / 2; GFX->getDrawUtil()->drawBitmap( texture, p ); break; } } } //============================================================================= // GuiBitmapButtonTextCtrl. //============================================================================= IMPLEMENT_CONOBJECT( GuiBitmapButtonTextCtrl); ConsoleDocClass( GuiBitmapButtonTextCtrl, "@brief An extension of GuiBitmapButtonCtrl that additionally renders a text label on the bitmapped button.\n\n" "The text for the label is taken from the GuiButtonBaseCtrl::text property.\n\n" "For rendering, the label is placed, relative to the control's upper left corner, at the text offset specified in the " "control's profile (GuiControlProfile::textOffset) and justified according to the profile's setting (GuiControlProfile::justify).\n\n" "@see GuiControlProfile::textOffset\n" "@see GuiControlProfile::justify\n" "@ingroup GuiButtons" ); //----------------------------------------------------------------------------- void GuiBitmapButtonTextCtrl::renderButton( GFXTexHandle &texture, const Point2I &offset, const RectI& updateRect ) { Parent::renderButton( texture, offset, updateRect ); Point2I textPos = offset; if(mDepressed) textPos += Point2I(1,1); // Make sure we take the profile's textOffset into account. textPos += mProfile->mTextOffset; GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor ); renderJustifiedText(textPos, getExtent(), mButtonText); } bool GuiBitmapButtonCtrl::pointInControl(const Point2I& parentCoordPoint) { if (mMasked && getTextureForCurrentState()) { ColorI rColor(0, 0, 0, 0); GBitmap* bmp; const RectI &bounds = getBounds(); S32 xt = parentCoordPoint.x - bounds.point.x; S32 yt = parentCoordPoint.y - bounds.point.y; bmp = getTextureForCurrentState().getBitmap(); if (!bmp) { setBitmap(mBitmapName); bmp = getTextureForCurrentState().getBitmap(); } S32 relativeXRange = this->getExtent().x; S32 relativeYRange = this->getExtent().y; S32 fileXRange = bmp->getHeight(0); S32 fileYRange = bmp->getWidth(0); //Con::errorf("xRange:[%i -- %i], Range:[%i -- %i] pos:(%i,%i)",relativeXRange,fileXRange,relativeYRange,fileYRange,xt,yt); S32 fileX = (xt*fileXRange) / relativeXRange; S32 fileY = (yt*fileYRange) / relativeYRange; //Con::errorf("Checking %s @ (%i,%i)",this->getName(),fileX,fileY); bmp->getColor(fileX, fileY, rColor); if (rColor.alpha) return true; else return false; } else return Parent::pointInControl(parentCoordPoint); } DEF_ASSET_BINDS(GuiBitmapButtonCtrl, Bitmap);