/** * @file llbutton.cpp * @brief LLButton base class * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #define LLBUTTON_CPP #include "llbutton.h" // Linden library includes #include "v4color.h" #include "llstring.h" // Project includes #include "llkeyboard.h" #include "llui.h" #include "lluiconstants.h" #include "llresmgr.h" #include "llcriticaldamp.h" #include "llfloater.h" #include "llfloaterreg.h" #include "llfocusmgr.h" #include "llwindow.h" #include "llnotificationsutil.h" #include "llrender.h" #include "lluictrlfactory.h" #include "lluiusage.h" #include "llhelp.h" #include "lldockablefloater.h" #include "llviewereventrecorder.h" static LLDefaultChildRegistry::Register r("button"); // Compiler optimization, generate extern template template class LLButton* LLView::getChild( std::string_view name, bool recurse) const; // globals S32 LLBUTTON_H_PAD = 4; S32 BTN_HEIGHT_SMALL= 23; S32 BTN_HEIGHT = 23; S32 BTN_DROP_SHADOW = 2; LLButton::Params::Params() : label_selected("label_selected"), // requires is_toggle true label_shadow("label_shadow", true), auto_resize("auto_resize", false), use_ellipses("use_ellipses", false), use_font_color("use_font_color", true), image_unselected("image_unselected"), image_selected("image_selected"), image_hover_selected("image_hover_selected"), image_hover_unselected("image_hover_unselected"), image_disabled_selected("image_disabled_selected"), image_disabled("image_disabled"), image_pressed("image_pressed"), image_pressed_selected("image_pressed_selected"), image_overlay("image_overlay"), image_overlay_alignment("image_overlay_alignment", std::string("center")), image_top_pad("image_top_pad"), image_bottom_pad("image_bottom_pad"), imgoverlay_label_space("imgoverlay_label_space", 1), label_color("label_color"), label_color_selected("label_color_selected"), // requires is_toggle true label_color_disabled("label_color_disabled"), label_color_disabled_selected("label_color_disabled_selected"), image_color("image_color"), image_color_disabled("image_color_disabled"), image_overlay_color("image_overlay_color", LLColor4::white % 0.75f), image_overlay_disabled_color("image_overlay_disabled_color", LLColor4::white % 0.3f), image_overlay_selected_color("image_overlay_selected_color", LLColor4::white), flash_color("flash_color"), pad_right("pad_right", LLBUTTON_H_PAD), pad_left("pad_left", LLBUTTON_H_PAD), pad_bottom("pad_bottom"), click_callback("click_callback"), mouse_down_callback("mouse_down_callback"), mouse_up_callback("mouse_up_callback"), mouse_held_callback("mouse_held_callback"), is_toggle("is_toggle", false), scale_image("scale_image", true), hover_glow_amount("hover_glow_amount"), commit_on_return("commit_on_return", true), commit_on_capture_lost("commit_on_capture_lost", false), display_pressed_state("display_pressed_state", true), use_draw_context_alpha("use_draw_context_alpha", true), badge("badge"), handle_right_mouse("handle_right_mouse"), held_down_delay("held_down_delay"), button_flash_enable("button_flash_enable", false), button_flash_count("button_flash_count"), button_flash_rate("button_flash_rate") { addSynonym(is_toggle, "toggle"); changeDefault(initial_value, LLSD(false)); } LLButton::LLButton(const LLButton::Params& p) : LLUICtrl(p), LLBadgeOwner(getHandle()), mFontBuffer(false), mMouseDownFrame(0), mMouseHeldDownCount(0), mFlashing( false ), mCurGlowStrength(0.f), mNeedsHighlight(false), mUnselectedLabel(p.label()), mSelectedLabel(p.label_selected()), mGLFont(p.font), mHeldDownDelay(p.held_down_delay.seconds), // seconds until held-down callback is called mHeldDownFrameDelay(p.held_down_delay.frames), mImageUnselected(p.image_unselected), mImageSelected(p.image_selected), mImageDisabled(p.image_disabled), mImageDisabledSelected(p.image_disabled_selected), mImageFlash(p.image_flash), mImagePressed(p.image_pressed), mImagePressedSelected(p.image_pressed_selected), mImageHoverSelected(p.image_hover_selected), mImageHoverUnselected(p.image_hover_unselected), mUnselectedLabelColor(p.label_color()), mSelectedLabelColor(p.label_color_selected()), mDisabledLabelColor(p.label_color_disabled()), mDisabledSelectedLabelColor(p.label_color_disabled_selected()), mImageColor(p.image_color()), mFlashBgColor(p.flash_color()), mDisabledImageColor(p.image_color_disabled()), mImageOverlay(p.image_overlay()), mImageOverlayColor(p.image_overlay_color()), mImageOverlayDisabledColor(p.image_overlay_disabled_color()), mImageOverlaySelectedColor(p.image_overlay_selected_color()), mImageOverlayAlignment(LLFontGL::hAlignFromName(p.image_overlay_alignment)), mImageOverlayTopPad(p.image_top_pad), mImageOverlayBottomPad(p.image_bottom_pad), mImgOverlayLabelSpace(p.imgoverlay_label_space), mIsToggle(p.is_toggle), mScaleImage(p.scale_image), mDropShadowedText(p.label_shadow), mAutoResize(p.auto_resize), mUseEllipses( p.use_ellipses ), mUseFontColor( p.use_font_color), mHAlign(p.font_halign), mLeftHPad(p.pad_left), mRightHPad(p.pad_right), mBottomVPad(p.pad_bottom), mHoverGlowStrength(p.hover_glow_amount), mCommitOnReturn(p.commit_on_return), mCommitOnCaptureLost(p.commit_on_capture_lost), mFadeWhenDisabled(false), mForcePressedState(false), mDisplayPressedState(p.display_pressed_state), mLastDrawCharsCount(0), mMouseDownSignal(NULL), mMouseUpSignal(NULL), mHeldDownSignal(NULL), mUseDrawContextAlpha(p.use_draw_context_alpha), mHandleRightMouse(p.handle_right_mouse), mFlashingTimer(NULL) { if (p.button_flash_enable) { // If optional parameter "p.button_flash_count" is not provided, LLFlashTimer will be // used instead it a "default" value from gSavedSettings.getS32("FlashCount")). // Likewise, missing "p.button_flash_rate" is replaced by gSavedSettings.getF32("FlashPeriod"). // Note: flashing should be allowed in settings.xml (boolean key "EnableButtonFlashing"). S32 flash_count = p.button_flash_count.isProvided()? p.button_flash_count : 0; F32 flash_rate = p.button_flash_rate.isProvided()? p.button_flash_rate : 0.0f; mFlashingTimer = new LLFlashTimer ((LLFlashTimer::callback_t)NULL, flash_count, flash_rate); } else { mButtonFlashCount = p.button_flash_count; mButtonFlashRate = p.button_flash_rate; } static LLUICachedControl llbutton_orig_h_pad ("UIButtonOrigHPad", 0); static Params default_params(LLUICtrlFactory::getDefaultParams()); if (!p.label_selected.isProvided()) { mSelectedLabel = mUnselectedLabel; } // Hack to make sure there is space for at least one character if (getRect().mRight >= 0 && getRect().getWidth() > 0 && getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) { // Use old defaults mLeftHPad = llbutton_orig_h_pad; mRightHPad = llbutton_orig_h_pad; } mMouseDownTimer.stop(); // if custom unselected button image provided... if (p.image_unselected != default_params.image_unselected) { //...fade it out for disabled image by default... if (p.image_disabled() == default_params.image_disabled() ) { mImageDisabled = p.image_unselected; mFadeWhenDisabled = true; } if (p.image_pressed_selected == default_params.image_pressed_selected) { mImagePressedSelected = mImageUnselected; } } // if custom selected button image provided... if (p.image_selected != default_params.image_selected) { //...fade it out for disabled image by default... if (p.image_disabled_selected() == default_params.image_disabled_selected()) { mImageDisabledSelected = p.image_selected; mFadeWhenDisabled = true; } if (p.image_pressed == default_params.image_pressed) { mImagePressed = mImageSelected; } } if (!p.image_pressed.isProvided()) { mImagePressed = mImageSelected; } if (!p.image_pressed_selected.isProvided()) { mImagePressedSelected = mImageUnselected; } if (mImageUnselected.isNull()) { LL_WARNS() << "Button: " << getName() << " with no image!" << LL_ENDL; } if (p.click_callback.isProvided()) { setCommitCallback(initCommitCallback(p.click_callback)); // alias -> commit_callback } if (p.mouse_down_callback.isProvided()) { setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); } if (p.mouse_up_callback.isProvided()) { setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); } if (p.mouse_held_callback.isProvided()) { setHeldDownCallback(initCommitCallback(p.mouse_held_callback)); } if (p.badge.isProvided()) { LLBadgeOwner::initBadgeParams(p.badge()); } } LLButton::~LLButton() { delete mMouseDownSignal; delete mMouseUpSignal; delete mHeldDownSignal; if (mFlashingTimer) { mFlashingTimer->unset(); } } // HACK: Committing a button is the same as instantly clicking it. // virtual void LLButton::onCommit() { // WARNING: Sometimes clicking a button destroys the floater or // panel containing it. Therefore we need to call LLUICtrl::onCommit() // LAST, otherwise this becomes deleted memory. if (mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); if (getSoundFlags() & MOUSE_DOWN) { make_ui_sound("UISndClick"); } if (getSoundFlags() & MOUSE_UP) { make_ui_sound("UISndClickRelease"); } if (mIsToggle) { toggleState(); } // do this last, as it can result in destroying this button LLUICtrl::onCommit(); } void LLButton::setUnselectedLabelColor(const LLUIColor& c) { mUnselectedLabelColor = c; mFontBuffer.reset(); } void LLButton::setSelectedLabelColor(const LLUIColor& c) { mSelectedLabelColor = c; mFontBuffer.reset(); } void LLButton::setUseEllipses(bool use_ellipses) { mUseEllipses = use_ellipses; mFontBuffer.reset(); } void LLButton::setUseFontColor(bool use_font_color) { mUseFontColor = use_font_color; mFontBuffer.reset(); } boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb) { return setClickedCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setMouseDownCallback(const CommitCallbackParam& cb) { return setMouseDownCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setMouseUpCallback(const CommitCallbackParam& cb) { return setMouseUpCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setHeldDownCallback(const CommitCallbackParam& cb) { return setHeldDownCallback(initCommitCallback(cb)); } boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) { if (!mCommitSignal) mCommitSignal = new commit_signal_t(); return mCommitSignal->connect(cb); } boost::signals2::connection LLButton::setMouseDownCallback( const commit_signal_t::slot_type& cb ) { if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); return mMouseDownSignal->connect(cb); } boost::signals2::connection LLButton::setMouseUpCallback( const commit_signal_t::slot_type& cb ) { if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); return mMouseUpSignal->connect(cb); } boost::signals2::connection LLButton::setHeldDownCallback( const commit_signal_t::slot_type& cb ) { if (!mHeldDownSignal) mHeldDownSignal = new commit_signal_t(); return mHeldDownSignal->connect(cb); } // *TODO: Deprecate (for backwards compatibility only) boost::signals2::connection LLButton::setClickedCallback( button_callback_t cb, void* data ) { return setClickedCallback(boost::bind(cb, data)); } boost::signals2::connection LLButton::setMouseDownCallback( button_callback_t cb, void* data ) { return setMouseDownCallback(boost::bind(cb, data)); } boost::signals2::connection LLButton::setMouseUpCallback( button_callback_t cb, void* data ) { return setMouseUpCallback(boost::bind(cb, data)); } boost::signals2::connection LLButton::setHeldDownCallback( button_callback_t cb, void* data ) { return setHeldDownCallback(boost::bind(cb, data)); } bool LLButton::postBuild() { autoResize(); addBadgeToParentHolder(); return LLUICtrl::postBuild(); } void LLButton::onVisibilityChange(bool new_visibility) { mFontBuffer.reset(); return LLUICtrl::onVisibilityChange(new_visibility); } void LLButton::reshape(S32 width, S32 height, bool called_from_parent) { S32 delta_width = width - getRect().getWidth(); S32 delta_height = height - getRect().getHeight(); if (delta_width || delta_height || sForceReshape) { LLUICtrl::reshape(width, height, called_from_parent); mFontBuffer.reset(); } } void LLButton::translate(S32 x, S32 y) { LLUICtrl::translate(x, y); mFontBuffer.reset(); } void LLButton::setRect(const LLRect& rect) { LLUICtrl::setRect(rect); mFontBuffer.reset(); } void LLButton::dirtyRect() { LLUICtrl::dirtyRect(); mFontBuffer.reset(); } bool LLButton::handleUnicodeCharHere(llwchar uni_char) { bool handled = false; if(' ' == uni_char && !gKeyboard->getKeyRepeated(' ')) { if (mIsToggle) { toggleState(); } LLUICtrl::onCommit(); handled = true; } return handled; } bool LLButton::handleKeyHere(KEY key, MASK mask ) { bool handled = false; if( mCommitOnReturn && KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) { if (mIsToggle) { toggleState(); } handled = true; LLUICtrl::onCommit(); } return handled; } bool LLButton::handleMouseDown(S32 x, S32 y, MASK mask) { if (!childrenHandleMouseDown(x, y, mask)) { // Route future Mouse messages here preemptively. (Release on mouse up.) gFocusMgr.setMouseCapture( this ); if (hasTabStop() && !getIsChrome()) { setFocus(true); } if (!mFunctionName.empty()) { LL_DEBUGS("UIUsage") << "calling mouse down function " << mFunctionName << LL_ENDL; LLUIUsage::instance().logCommand(mFunctionName); LLUIUsage::instance().logControl(getPathname()); } /* * ATTENTION! This call fires another mouse down callback. * If you wish to remove this call emit that signal directly * by calling LLUICtrl::mMouseDownSignal(x, y, mask); */ LLUICtrl::handleMouseDown(x, y, mask); LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); mMouseDownTimer.start(); mMouseDownFrame = (S32) LLFrameTimer::getFrameCount(); mMouseHeldDownCount = 0; if (getSoundFlags() & MOUSE_DOWN) { make_ui_sound("UISndClick"); } } return true; } bool LLButton::handleMouseUp(S32 x, S32 y, MASK mask) { // We only handle the click if the click both started and ended within us if( hasMouseCapture() ) { // reset timers before focus change, to not cause // additional commits if mCommitOnCaptureLost. resetMouseDownTimer(); // Always release the mouse gFocusMgr.setMouseCapture( NULL ); /* * ATTENTION! This call fires another mouse up callback. * If you wish to remove this call emit that signal directly * by calling LLUICtrl::mMouseUpSignal(x, y, mask); */ LLUICtrl::handleMouseUp(x, y, mask); LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); // Regardless of where mouseup occurs, handle callback if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked. // If mouseup in the widget, it's been clicked if (pointInView(x, y)) { if (getSoundFlags() & MOUSE_UP) { make_ui_sound("UISndClickRelease"); } if (mIsToggle) { toggleState(); } LLUICtrl::onCommit(); } } else { childrenHandleMouseUp(x, y, mask); } return true; } bool LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask) { if (mHandleRightMouse && !childrenHandleRightMouseDown(x, y, mask)) { // Route future Mouse messages here preemptively. (Release on mouse up.) gFocusMgr.setMouseCapture( this ); if (hasTabStop() && !getIsChrome()) { setFocus(true); } // if (pointInView(x, y)) // { // } // send the mouse down signal LLUICtrl::handleRightMouseDown(x,y,mask); // *TODO: Return result of LLUICtrl call above? Should defer to base class // but this might change the mouse handling of existing buttons in a bad way // if they are not mouse opaque. } return true; } bool LLButton::handleRightMouseUp(S32 x, S32 y, MASK mask) { if (mHandleRightMouse) { // We only handle the click if the click both started and ended within us if( hasMouseCapture() ) { // Always release the mouse gFocusMgr.setMouseCapture( NULL ); // if (pointInView(x, y)) // { // mRightMouseUpSignal(this, x,y,mask); // } } else { childrenHandleRightMouseUp(x, y, mask); } // send the mouse up signal LLUICtrl::handleRightMouseUp(x,y,mask); // *TODO: Return result of LLUICtrl call above? Should defer to base class // but this might change the mouse handling of existing buttons in a bad way. // if they are not mouse opaque. } return true; } void LLButton::onMouseLeave(S32 x, S32 y, MASK mask) { LLUICtrl::onMouseLeave(x, y, mask); setHighlight(false); } void LLButton::setHighlight(bool b) { if (mNeedsHighlight != b) { mNeedsHighlight = b; mFontBuffer.reset(); } } bool LLButton::handleHover(S32 x, S32 y, MASK mask) { if (isInEnabledChain() && (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this)) { setHighlight(true); } if (!childrenHandleHover(x, y, mask)) { if (mMouseDownTimer.getStarted()) { F32 elapsed = getHeldDownTime(); if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame) { LLSD param; param["count"] = mMouseHeldDownCount++; if (mHeldDownSignal) (*mHeldDownSignal)(this, param); } } // We only handle the click if the click both started and ended within us getWindow()->setCursor(UI_CURSOR_ARROW); LL_DEBUGS("UserInput") << "hover handled by " << getName() << LL_ENDL; } return true; } void LLButton::getOverlayImageSize(S32& overlay_width, S32& overlay_height) { overlay_width = mImageOverlay->getWidth(); overlay_height = mImageOverlay->getHeight(); F32 scale_factor = llmin((F32)getRect().getWidth() / (F32)overlay_width, (F32)getRect().getHeight() / (F32)overlay_height, 1.f); overlay_width = ll_round((F32)overlay_width * scale_factor); overlay_height = ll_round((F32)overlay_height * scale_factor); } // virtual void LLButton::draw() { static LLCachedControl sEnableButtonFlashing(*LLUI::getInstance()->mSettingGroups["config"], "EnableButtonFlashing", true); F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); bool pressed_by_keyboard = false; if (hasFocus()) { pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); } bool mouse_pressed_and_over = false; if (hasMouseCapture()) { S32 local_mouse_x ; S32 local_mouse_y; LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); mouse_pressed_and_over = pointInView(local_mouse_x, local_mouse_y); } bool enabled = isInEnabledChain(); bool pressed = pressed_by_keyboard || mouse_pressed_and_over || mForcePressedState; bool selected = getToggleState(); bool use_glow_effect = false; LLColor4 highlighting_color = LLColor4::white; LLColor4 glow_color = LLColor4::white; LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA; LLUIImage* imagep = NULL; LLUIImage* image_glow = NULL; // Cancel sticking of color, if the button is pressed, // or when a flashing of the previously selected button is ended if (mFlashingTimer && ((selected && !mFlashingTimer->isFlashingInProgress() && !mForceFlashing) || pressed)) { mFlashing = false; } bool flash = mFlashing && sEnableButtonFlashing; if (pressed && mDisplayPressedState) { imagep = selected ? mImagePressedSelected : mImagePressed; } else if ( mNeedsHighlight ) { if (selected) { if (mImageHoverSelected) { imagep = mImageHoverSelected; } else { imagep = mImageSelected; use_glow_effect = true; } } else { if (mImageHoverUnselected) { imagep = mImageHoverUnselected; } else { imagep = mImageUnselected; use_glow_effect = true; } } } else { imagep = selected ? mImageSelected : mImageUnselected; } // Override if more data is available // HACK: Use gray checked state to mean either: // enabled and tentative // or // disabled but checked if (!mImageDisabledSelected.isNull() && ( (enabled && getTentative()) || (!enabled && selected ) ) ) { imagep = mImageDisabledSelected; } else if (!mImageDisabled.isNull() && !enabled && !selected) { imagep = mImageDisabled; } image_glow = imagep; if (mFlashing) { if (flash && mImageFlash) { // if button should flash and we have icon for flashing, use it as image for button image_glow = mImageFlash; } // provide fade-in and fade-out via flash_color if (mFlashingTimer) { LLColor4 flash_color = mFlashBgColor.get(); use_glow_effect = true; glow_type = LLRender::BT_ALPHA; // blend the glow if (mFlashingTimer->isCurrentlyHighlighted() || !mFlashingTimer->isFlashingInProgress()) { glow_color = flash_color; } else if (mNeedsHighlight) { glow_color = highlighting_color; } else { // will fade from highlight color glow_color = flash_color; } } } if (mNeedsHighlight && !imagep) { use_glow_effect = true; } // Figure out appropriate color for the text LLColor4 label_color; // label changes when button state changes, not when pressed if ( enabled ) { if ( getToggleState() ) { label_color = mSelectedLabelColor.get(); } else { label_color = mUnselectedLabelColor.get(); } } else { if ( getToggleState() ) { label_color = mDisabledSelectedLabelColor.get(); } else { label_color = mDisabledLabelColor.get(); } } // Highlight if needed if( ll::ui::SearchableControl::getHighlighted() ) label_color = ll::ui::SearchableControl::getHighlightColor(); // overlay with keyboard focus border if (hasFocus()) { F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, ll_round(lerp(1.f, 3.f, lerp_amt))); } if (use_glow_effect) { mCurGlowStrength = lerp(mCurGlowStrength, mFlashing ? (mFlashingTimer->isCurrentlyHighlighted() || !mFlashingTimer->isFlashingInProgress() || mNeedsHighlight? 1.f : 0.f) : mHoverGlowStrength, LLSmoothInterpolation::getInterpolant(0.05f)); } else { mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f)); } // Draw button image, if available. // Otherwise draw basic rectangular button. if (imagep != NULL) { // apply automatic 50% alpha fade to disabled image LLColor4 disabled_color = mFadeWhenDisabled ? mDisabledImageColor.get() % 0.5f : mDisabledImageColor.get(); if ( mScaleImage) { imagep->draw(getLocalRect(), (enabled ? mImageColor.get() : disabled_color) % alpha ); if (mCurGlowStrength > 0.01f) { gGL.setSceneBlendType(glow_type); image_glow->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha)); gGL.setSceneBlendType(LLRender::BT_ALPHA); } } else { S32 y = getLocalRect().getHeight() - imagep->getHeight(); imagep->draw(0, y, (enabled ? mImageColor.get() : disabled_color) % alpha); if (mCurGlowStrength > 0.01f) { gGL.setSceneBlendType(glow_type); image_glow->drawSolid(0, y, glow_color % (mCurGlowStrength * alpha)); gGL.setSceneBlendType(LLRender::BT_ALPHA); } } } else { // no image LL_DEBUGS() << "No image for button " << getName() << LL_ENDL; // draw it in pink so we can find it gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4::pink1 % alpha, false); } // let overlay image and text play well together S32 text_left = mLeftHPad; S32 text_right = getRect().getWidth() - mRightHPad; S32 text_width = getRect().getWidth() - mLeftHPad - mRightHPad; // draw overlay image if (mImageOverlay.notNull()) { // get max width and height (discard level 0) S32 overlay_width; S32 overlay_height; getOverlayImageSize(overlay_width, overlay_height); S32 center_x = getLocalRect().getCenterX(); S32 center_y = getLocalRect().getCenterY(); //FUGLY HACK FOR "DEPRESSED" BUTTONS if (pressed && mDisplayPressedState) { center_y--; center_x++; } center_y += (mImageOverlayBottomPad - mImageOverlayTopPad); // fade out overlay images on disabled buttons LLColor4 overlay_color = mImageOverlayColor.get(); if (!enabled) { overlay_color = mImageOverlayDisabledColor.get(); } else if (getToggleState()) { overlay_color = mImageOverlaySelectedColor.get(); } overlay_color.mV[VALPHA] *= alpha; switch(mImageOverlayAlignment) { case LLFontGL::LEFT: text_left += overlay_width + mImgOverlayLabelSpace; text_width -= overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( mLeftHPad, center_y - (overlay_height / 2), overlay_width, overlay_height, overlay_color); break; case LLFontGL::HCENTER: mImageOverlay->draw( center_x - (overlay_width / 2), center_y - (overlay_height / 2), overlay_width, overlay_height, overlay_color); break; case LLFontGL::RIGHT: text_right -= overlay_width + mImgOverlayLabelSpace; text_width -= overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( getRect().getWidth() - mRightHPad - overlay_width, center_y - (overlay_height / 2), overlay_width, overlay_height, overlay_color); break; default: // draw nothing break; } } // Draw label if( !getCurrentLabel().empty() ) // Unselected label assignments { LLWString label = getCurrentLabel(); LLWStringUtil::trim(label); S32 x; switch( mHAlign ) { case LLFontGL::RIGHT: x = text_right; break; case LLFontGL::HCENTER: x = text_left + (text_width / 2); break; case LLFontGL::LEFT: default: x = text_left; break; } if (pressed && mDisplayPressedState) { x++; } // *NOTE: mantipov: before mUseEllipses is implemented in EXT-279 U32_MAX has been passed as // max_chars. // LLFontGL::render expects S32 max_chars variable but process in a separate way -1 value. // Due to U32_MAX is equal to S32 -1 value I have rest this value for non-ellipses mode. // Not sure if it is really needed. Probably S32_MAX should be always passed as max_chars. mLastDrawCharsCount = mFontBuffer.render(mGLFont, label, 0, (F32)x, (F32)(getRect().getHeight() / 2 + mBottomVPad), label_color % alpha, mHAlign, LLFontGL::VCENTER, LLFontGL::NORMAL, mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, S32_MAX, text_width, NULL, mUseEllipses, mUseFontColor); } LLUICtrl::draw(); } void LLButton::drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size) { if (imagep == NULL) return; if (mScaleImage) { imagep->drawBorder(getLocalRect(), color, size); } else { S32 y = getLocalRect().getHeight() - imagep->getHeight(); imagep->drawBorder(0, y, color, size); } } bool LLButton::getToggleState() const { return getValue().asBoolean(); } void LLButton::setToggleState(bool b) { if( b != getToggleState() ) { setControlValue(b); // will fire LLControlVariable callbacks (if any) setValue(b); // may or may not be redundant setFlashing(false); // stop flash state whenever the selected/unselected state if reset // Unselected label assignments autoResize(); mFontBuffer.reset(); } } void LLButton::setFlashing(bool b, bool force_flashing/* = false */) { mForceFlashing = force_flashing; if (mFlashingTimer) { mFlashing = b; (b ? mFlashingTimer->startFlashing() : mFlashingTimer->stopFlashing()); } else if (b != mFlashing) { mFlashing = b; mFrameTimer.reset(); } } bool LLButton::toggleState() { bool flipped = ! getToggleState(); setToggleState(flipped); return flipped; } void LLButton::setLabel( const std::string& label ) { mUnselectedLabel = mSelectedLabel = label; mFontBuffer.reset(); } void LLButton::setLabel( const LLUIString& label ) { mUnselectedLabel = mSelectedLabel = label; mFontBuffer.reset(); } void LLButton::setLabel( const LLStringExplicit& label ) { setLabelUnselected(label); setLabelSelected(label); } //virtual bool LLButton::setLabelArg( const std::string& key, const LLStringExplicit& text ) { mUnselectedLabel.setArg(key, text); mSelectedLabel.setArg(key, text); mFontBuffer.reset(); return true; } void LLButton::setLabelUnselected( const LLStringExplicit& label ) { mUnselectedLabel = label; mFontBuffer.reset(); } void LLButton::setLabelSelected( const LLStringExplicit& label ) { mSelectedLabel = label; mFontBuffer.reset(); } void LLButton::setDisabledLabelColor(const LLUIColor& c) { mDisabledLabelColor = c; mFontBuffer.reset(); } void LLButton::setFont(const LLFontGL* font) { mGLFont = (font ? font : LLFontGL::getFontSansSerif()); mFontBuffer.reset(); } bool LLButton::labelIsTruncated() const { return getCurrentLabel().getString().size() > mLastDrawCharsCount; } const LLUIString& LLButton::getCurrentLabel() const { return getToggleState() ? mSelectedLabel : mUnselectedLabel; } void LLButton::setDropShadowedText(bool b) { mDropShadowedText = b; mFontBuffer.reset(); } void LLButton::setImageUnselected(LLPointer image) { mImageUnselected = image; if (mImageUnselected.isNull()) { LL_WARNS() << "Setting default button image for: " << getName() << " to NULL" << LL_ENDL; } } void LLButton::autoResize() { resize(getCurrentLabel()); } void LLButton::resize(const LLUIString& label) { // get label length S32 label_width = mGLFont->getWidth(label.getWString().c_str()); // get current btn length S32 btn_width =getRect().getWidth(); // check if it need resize if (mAutoResize) { S32 min_width = label_width + mLeftHPad + mRightHPad; if (mImageOverlay) { S32 overlay_width = mImageOverlay->getWidth(); F32 scale_factor = (getRect().getHeight() - (mImageOverlayBottomPad + mImageOverlayTopPad)) / (F32)mImageOverlay->getHeight(); overlay_width = ll_round((F32)overlay_width * scale_factor); switch(mImageOverlayAlignment) { case LLFontGL::LEFT: case LLFontGL::RIGHT: min_width += overlay_width + mImgOverlayLabelSpace; break; case LLFontGL::HCENTER: min_width = llmax(min_width, overlay_width + mLeftHPad + mRightHPad); break; default: // draw nothing break; } } if (btn_width < min_width) { reshape(min_width, getRect().getHeight()); } } } void LLButton::setImages( const std::string &image_name, const std::string &selected_name ) { setImageUnselected(LLUI::getUIImage(image_name)); setImageSelected(LLUI::getUIImage(selected_name)); } void LLButton::setImageSelected(LLPointer image) { mImageSelected = image; } void LLButton::setImageColor(const LLUIColor& c) { mImageColor = c; } void LLButton::setColor(const LLUIColor& color) { setImageColor(color); } void LLButton::setImageDisabled(LLPointer image) { mImageDisabled = image; mDisabledImageColor = mImageColor; mFadeWhenDisabled = true; } void LLButton::setImageDisabledSelected(LLPointer image) { mImageDisabledSelected = image; mDisabledImageColor = mImageColor; mFadeWhenDisabled = true; mFontBuffer.reset(); } void LLButton::setImagePressed(LLPointer image) { mImagePressed = image; } void LLButton::setImageHoverSelected(LLPointer image) { mImageHoverSelected = image; } void LLButton::setImageHoverUnselected(LLPointer image) { mImageHoverUnselected = image; } void LLButton::setImageFlash(LLPointer image) { mImageFlash = image; } void LLButton::setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment, const LLColor4& color) { if (image_name.empty()) { mImageOverlay = NULL; } else { mImageOverlay = LLUI::getUIImage(image_name); mImageOverlayAlignment = alignment; mImageOverlayColor = color; } } void LLButton::setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignment, const LLColor4& color) { if (image_id.isNull()) { mImageOverlay = NULL; } else { mImageOverlay = LLUI::getUIImageByID(image_id); mImageOverlayAlignment = alignment; mImageOverlayColor = color; } } void LLButton::onMouseCaptureLost() { if (mCommitOnCaptureLost && mMouseDownTimer.getStarted()) { if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); if (mIsToggle) { toggleState(); } LLUICtrl::onCommit(); } resetMouseDownTimer(); } //------------------------------------------------------------------------- // Utilities //------------------------------------------------------------------------- S32 round_up(S32 grid, S32 value) { S32 mod = value % grid; if (mod > 0) { // not even multiple return value + (grid - mod); } else { return value; } } void LLButton::addImageAttributeToXML(LLXMLNodePtr node, const std::string& image_name, const LLUUID& image_id, const std::string& xml_tag_name) const { if( !image_name.empty() ) { node->createChild(xml_tag_name.c_str(), true)->setStringValue(image_name); } else if( image_id != LLUUID::null ) { node->createChild((xml_tag_name + "_id").c_str(), true)->setUUIDValue(image_id); } } // static void LLButton::toggleFloaterAndSetToggleState(LLUICtrl* ctrl, const LLSD& sdname) { bool floater_vis = LLFloaterReg::toggleInstance(sdname.asString()); LLButton* button = dynamic_cast(ctrl); if (button) button->setToggleState(floater_vis); } // static // Gets called once void LLButton::setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) { LLButton* button = dynamic_cast(ctrl); if (!button) return; // Get the visibility control name for the floater std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); // Set the clicked callback to toggle the floater button->setClickedCallback([=](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::toggleInstance(sdname.asString(), LLSD()); }); } // static void LLButton::setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) { LLButton* button = dynamic_cast(ctrl); if (!button) return; // Get the visibility control name for the floater std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); // Set the clicked callback to toggle the floater button->setClickedCallback(boost::bind(&LLDockableFloater::toggleInstance, sdname)); } // static void LLButton::showHelp(LLUICtrl* ctrl, const LLSD& sdname) { // search back through the button's parents for a panel // with a help_topic string defined std::string help_topic; if (LLUI::getInstance()->mHelpImpl && ctrl->findHelpTopic(help_topic)) { LLUI::getInstance()->mHelpImpl->showTopic(help_topic); return; // success } // display an error if we can't find a help_topic string. // fix this by adding a help_topic attribute to the xui file LLNotificationsUtil::add("UnableToFindHelpTopic"); } void LLButton::resetMouseDownTimer() { mMouseDownTimer.stop(); mMouseDownTimer.reset(); } bool LLButton::handleDoubleClick(S32 x, S32 y, MASK mask) { // just treat a double click as a second click return handleMouseDown(x, y, mask); }