diff options
Diffstat (limited to 'indra/llui/llscrollbar.cpp')
-rw-r--r-- | indra/llui/llscrollbar.cpp | 1332 |
1 files changed, 666 insertions, 666 deletions
diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp index ab7d0bc38f..9c73b1ba3f 100644 --- a/indra/llui/llscrollbar.cpp +++ b/indra/llui/llscrollbar.cpp @@ -1,666 +1,666 @@ -/**
- * @file llscrollbar.cpp
- * @brief Scrollbar UI widget
- *
- * $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"
-
-#include "llscrollbar.h"
-
-#include "llmath.h"
-#include "lltimer.h"
-#include "v3color.h"
-
-#include "llbutton.h"
-#include "llcriticaldamp.h"
-#include "llkeyboard.h"
-#include "llui.h"
-#include "llfocusmgr.h"
-#include "llwindow.h"
-#include "llcontrol.h"
-#include "llrender.h"
-#include "lluictrlfactory.h"
-
-static LLDefaultChildRegistry::Register<LLScrollbar> register_scrollbar("scroll_bar");
-
-LLScrollbar::Params::Params()
-: orientation ("orientation", HORIZONTAL),
- doc_size ("doc_size", 0),
- doc_pos ("doc_pos", 0),
- page_size ("page_size", 0),
- step_size ("step_size", 1),
- thumb_image_vertical("thumb_image_vertical"),
- thumb_image_horizontal("thumb_image_horizontal"),
- track_image_vertical("track_image_vertical"),
- track_image_horizontal("track_image_horizontal"),
- track_color("track_color"),
- thumb_color("thumb_color"),
- thickness("thickness"),
- up_button("up_button"),
- down_button("down_button"),
- left_button("left_button"),
- right_button("right_button"),
- bg_visible("bg_visible", false),
- bg_color("bg_color", LLColor4::black)
-{}
-
-LLScrollbar::LLScrollbar(const Params & p)
-: LLUICtrl(p),
- mChangeCallback( p.change_callback() ),
- mOrientation( p.orientation ),
- mDocSize( p.doc_size ),
- mDocPos( p.doc_pos ),
- mPageSize( p.page_size ),
- mStepSize( p.step_size ),
- mDocChanged(false),
- mDragStartX( 0 ),
- mDragStartY( 0 ),
- mHoverGlowStrength(0.15f),
- mCurGlowStrength(0.f),
- mTrackColor( p.track_color() ),
- mThumbColor ( p.thumb_color() ),
- mThumbImageV(p.thumb_image_vertical),
- mThumbImageH(p.thumb_image_horizontal),
- mTrackImageV(p.track_image_vertical),
- mTrackImageH(p.track_image_horizontal),
- mThickness(p.thickness.isProvided() ? p.thickness : LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize")),
- mBGVisible(p.bg_visible),
- mBGColor(p.bg_color)
-{
- updateThumbRect();
-
- // Page up and page down buttons
- LLRect line_up_rect;
- LLRect line_down_rect;
-
- if( VERTICAL == mOrientation )
- {
- line_up_rect.setLeftTopAndSize( 0, getRect().getHeight(), mThickness, mThickness );
- line_down_rect.setOriginAndSize( 0, 0, mThickness, mThickness );
- }
- else // HORIZONTAL
- {
- line_up_rect.setOriginAndSize( 0, 0, mThickness, mThickness );
- line_down_rect.setOriginAndSize( getRect().getWidth() - mThickness, 0, mThickness, mThickness );
- }
-
- LLButton::Params up_btn(mOrientation == VERTICAL ? p.up_button : p.left_button);
- up_btn.name(std::string("Line Up"));
- up_btn.rect(line_up_rect);
- up_btn.click_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2));
- up_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2));
- up_btn.tab_stop(false);
- up_btn.follows.flags = (mOrientation == VERTICAL ? (FOLLOWS_RIGHT | FOLLOWS_TOP) : (FOLLOWS_LEFT | FOLLOWS_BOTTOM));
-
- addChild(LLUICtrlFactory::create<LLButton>(up_btn));
-
- LLButton::Params down_btn(mOrientation == VERTICAL ? p.down_button : p.right_button);
- down_btn.name(std::string("Line Down"));
- down_btn.rect(line_down_rect);
- down_btn.follows.flags(FOLLOWS_RIGHT|FOLLOWS_BOTTOM);
- down_btn.click_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2));
- down_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2));
- down_btn.tab_stop(false);
-
- addChild(LLUICtrlFactory::create<LLButton>(down_btn));
-}
-
-
-LLScrollbar::~LLScrollbar()
-{
- // Children buttons killed by parent class
-}
-
-void LLScrollbar::setDocParams( S32 size, S32 pos )
-{
- mDocSize = size;
- setDocPos(pos);
- mDocChanged = true;
-
- updateThumbRect();
-}
-
-// returns true if document position really changed
-bool LLScrollbar::setDocPos(S32 pos, bool update_thumb)
-{
- pos = llclamp(pos, 0, getDocPosMax());
- if (pos != mDocPos)
- {
- mDocPos = pos;
- mDocChanged = true;
-
- if( mChangeCallback )
- {
- mChangeCallback( mDocPos, this );
- }
-
- if( update_thumb )
- {
- updateThumbRect();
- }
- return true;
- }
- return false;
-}
-
-void LLScrollbar::setDocSize(S32 size)
-{
- if (size != mDocSize)
- {
- mDocSize = size;
- setDocPos(mDocPos);
- mDocChanged = true;
-
- updateThumbRect();
- }
-}
-
-void LLScrollbar::setPageSize( S32 page_size )
-{
- if (page_size != mPageSize)
- {
- mPageSize = page_size;
- setDocPos(mDocPos);
- mDocChanged = true;
-
- updateThumbRect();
- }
-}
-
-bool LLScrollbar::isAtBeginning() const
-{
- return mDocPos == 0;
-}
-
-bool LLScrollbar::isAtEnd() const
-{
- return mDocPos == getDocPosMax();
-}
-
-
-void LLScrollbar::updateThumbRect()
-{
-// llassert( 0 <= mDocSize );
-// llassert( 0 <= mDocPos && mDocPos <= getDocPosMax() );
-
- const S32 THUMB_MIN_LENGTH = 16;
-
- S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? getRect().getWidth() : getRect().getHeight();
- S32 thumb_bg_length = llmax(0, window_length - 2 * mThickness);
- S32 visible_lines = llmin( mDocSize, mPageSize );
- S32 thumb_length = mDocSize ? llmin(llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH), thumb_bg_length) : thumb_bg_length;
-
- S32 variable_lines = mDocSize - visible_lines;
-
- if( mOrientation == LLScrollbar::VERTICAL )
- {
- S32 thumb_start_max = thumb_bg_length + mThickness;
- S32 thumb_start_min = mThickness + THUMB_MIN_LENGTH;
- S32 thumb_start = variable_lines ? llmin( llmax(thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_max;
-
- mThumbRect.mLeft = 0;
- mThumbRect.mTop = thumb_start;
- mThumbRect.mRight = mThickness;
- mThumbRect.mBottom = thumb_start - thumb_length;
- }
- else
- {
- // Horizontal
- S32 thumb_start_max = thumb_bg_length + mThickness - thumb_length;
- S32 thumb_start_min = mThickness;
- S32 thumb_start = variable_lines ? llmin(llmax( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_min;
-
- mThumbRect.mLeft = thumb_start;
- mThumbRect.mTop = mThickness;
- mThumbRect.mRight = thumb_start + thumb_length;
- mThumbRect.mBottom = 0;
- }
-}
-
-bool LLScrollbar::handleMouseDown(S32 x, S32 y, MASK mask)
-{
- // Check children first
- bool handled_by_child = LLView::childrenHandleMouseDown(x, y, mask) != NULL;
- if( !handled_by_child )
- {
- if( mThumbRect.pointInRect(x,y) )
- {
- // Start dragging the thumb
- // No handler needed for focus lost since this clas has no state that depends on it.
- gFocusMgr.setMouseCapture( this );
- mDragStartX = x;
- mDragStartY = y;
- mOrigRect.mTop = mThumbRect.mTop;
- mOrigRect.mBottom = mThumbRect.mBottom;
- mOrigRect.mLeft = mThumbRect.mLeft;
- mOrigRect.mRight = mThumbRect.mRight;
- mLastDelta = 0;
- }
- else
- {
- if(
- ( (LLScrollbar::VERTICAL == mOrientation) && (mThumbRect.mTop < y) ) ||
- ( (LLScrollbar::HORIZONTAL == mOrientation) && (x < mThumbRect.mLeft) )
- )
- {
- // Page up
- pageUp(0);
- }
- else
- if(
- ( (LLScrollbar::VERTICAL == mOrientation) && (y < mThumbRect.mBottom) ) ||
- ( (LLScrollbar::HORIZONTAL == mOrientation) && (mThumbRect.mRight < x) )
- )
- {
- // Page down
- pageDown(0);
- }
- }
- }
-
- return true;
-}
-
-
-bool LLScrollbar::handleHover(S32 x, S32 y, MASK mask)
-{
- // Note: we don't bother sending the event to the children (the arrow buttons)
- // because they'll capture the mouse whenever they need hover events.
-
- bool handled = false;
- if( hasMouseCapture() )
- {
- S32 height = getRect().getHeight();
- S32 width = getRect().getWidth();
-
- if( VERTICAL == mOrientation )
- {
-// S32 old_pos = mThumbRect.mTop;
-
- S32 delta_pixels = y - mDragStartY;
- if( mOrigRect.mBottom + delta_pixels < mThickness )
- {
- delta_pixels = mThickness - mOrigRect.mBottom - 1;
- }
- else
- if( mOrigRect.mTop + delta_pixels > height - mThickness )
- {
- delta_pixels = height - mThickness - mOrigRect.mTop + 1;
- }
-
- mThumbRect.mTop = mOrigRect.mTop + delta_pixels;
- mThumbRect.mBottom = mOrigRect.mBottom + delta_pixels;
-
- S32 thumb_length = mThumbRect.getHeight();
- S32 thumb_track_length = height - 2 * mThickness;
-
-
- if( delta_pixels != mLastDelta || mDocChanged)
- {
- // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page).
- S32 usable_track_length = thumb_track_length - thumb_length;
- if( 0 < usable_track_length )
- {
- S32 variable_lines = getDocPosMax();
- S32 pos = mThumbRect.mTop;
- F32 ratio = F32(pos - mThickness - thumb_length) / usable_track_length;
-
- S32 new_pos = llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines );
- // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly
- // out of sync (less than a line's worth) to make the thumb feel responsive.
- changeLine( new_pos - mDocPos, false );
- }
- }
-
- mLastDelta = delta_pixels;
-
- }
- else
- {
- // Horizontal
-// S32 old_pos = mThumbRect.mLeft;
-
- S32 delta_pixels = x - mDragStartX;
-
- if( mOrigRect.mLeft + delta_pixels < mThickness )
- {
- delta_pixels = mThickness - mOrigRect.mLeft - 1;
- }
- else
- if( mOrigRect.mRight + delta_pixels > width - mThickness )
- {
- delta_pixels = width - mThickness - mOrigRect.mRight + 1;
- }
-
- mThumbRect.mLeft = mOrigRect.mLeft + delta_pixels;
- mThumbRect.mRight = mOrigRect.mRight + delta_pixels;
-
- S32 thumb_length = mThumbRect.getWidth();
- S32 thumb_track_length = width - 2 * mThickness;
-
- if( delta_pixels != mLastDelta || mDocChanged)
- {
- // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page).
- S32 usable_track_length = thumb_track_length - thumb_length;
- if( 0 < usable_track_length )
- {
- S32 variable_lines = getDocPosMax();
- S32 pos = mThumbRect.mLeft;
- F32 ratio = F32(pos - mThickness) / usable_track_length;
-
- S32 new_pos = llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines);
-
- // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly
- // out of sync (less than a line's worth) to make the thumb feel responsive.
- changeLine( new_pos - mDocPos, false );
- }
- }
-
- mLastDelta = delta_pixels;
- }
-
- getWindow()->setCursor(UI_CURSOR_ARROW);
- LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL;
- handled = true;
- }
- else
- {
- handled = childrenHandleHover( x, y, mask ) != NULL;
- }
-
- // Opaque
- if( !handled )
- {
- getWindow()->setCursor(UI_CURSOR_ARROW);
- LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL;
- handled = true;
- }
-
- mDocChanged = false;
- return handled;
-} // end handleHover
-
-
-bool LLScrollbar::handleScrollWheel(S32 x, S32 y, S32 clicks)
-{
- bool handled = changeLine( clicks * mStepSize, true );
- return handled;
-}
-
-bool LLScrollbar::handleScrollHWheel(S32 x, S32 y, S32 clicks)
-{
- bool handled = false;
- if (LLScrollbar::HORIZONTAL == mOrientation)
- {
- handled = changeLine(clicks * mStepSize, true);
- }
- return handled;
-}
-
-bool LLScrollbar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop,
- EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string &tooltip_msg)
-{
- // enable this to get drag and drop to control scrollbars
- //if (!drop)
- //{
- // //TODO: refactor this
- // S32 variable_lines = getDocPosMax();
- // S32 pos = (VERTICAL == mOrientation) ? y : x;
- // S32 thumb_length = (VERTICAL == mOrientation) ? mThumbRect.getHeight() : mThumbRect.getWidth();
- // S32 thumb_track_length = (VERTICAL == mOrientation) ? (getRect().getHeight() - 2 * SCROLLBAR_SIZE) : (getRect().getWidth() - 2 * SCROLLBAR_SIZE);
- // S32 usable_track_length = thumb_track_length - thumb_length;
- // F32 ratio = (VERTICAL == mOrientation) ? F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length
- // : F32(pos - SCROLLBAR_SIZE) / usable_track_length;
- // S32 new_pos = (VERTICAL == mOrientation) ? llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines )
- // : llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines );
- // changeLine( new_pos - mDocPos, true );
- //}
- //return true;
- return false;
-}
-
-bool LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask)
-{
- bool handled = false;
- if( hasMouseCapture() )
- {
- gFocusMgr.setMouseCapture( NULL );
- handled = true;
- }
- else
- {
- // Opaque, so don't just check children
- handled = LLView::handleMouseUp( x, y, mask );
- }
-
- return handled;
-}
-
-bool LLScrollbar::handleDoubleClick(S32 x, S32 y, MASK mask)
-{
- // just treat a double click as a second click
- return handleMouseDown(x, y, mask);
-}
-
-
-void LLScrollbar::reshape(S32 width, S32 height, bool called_from_parent)
-{
- if (width == getRect().getWidth() && height == getRect().getHeight()) return;
- LLView::reshape( width, height, called_from_parent );
- LLButton* up_button = getChild<LLButton>("Line Up");
- LLButton* down_button = getChild<LLButton>("Line Down");
-
- if (mOrientation == VERTICAL)
- {
- up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness));
- down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness));
- up_button->setOrigin(0, getRect().getHeight() - up_button->getRect().getHeight());
- down_button->setOrigin(0, 0);
- }
- else
- {
- up_button->reshape(llmin(getRect().getWidth() / 2, mThickness), up_button->getRect().getHeight());
- down_button->reshape(llmin(getRect().getWidth() / 2, mThickness), down_button->getRect().getHeight());
- up_button->setOrigin(0, 0);
- down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), 0);
- }
- updateThumbRect();
-}
-
-
-void LLScrollbar::draw()
-{
- if (!getRect().isValid()) return;
-
- if(mBGVisible)
- {
- gl_rect_2d(getLocalRect(), mBGColor.get(), true);
- }
-
- S32 local_mouse_x;
- S32 local_mouse_y;
- LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y);
- bool other_captor = gFocusMgr.getMouseCapture() && gFocusMgr.getMouseCapture() != this;
- bool hovered = getEnabled() && !other_captor && (hasMouseCapture() || mThumbRect.pointInRect(local_mouse_x, local_mouse_y));
- if (hovered)
- {
- mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLSmoothInterpolation::getInterpolant(0.05f));
- }
- else
- {
- mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f));
- }
-
- // Draw background and thumb.
- if ( ( mOrientation == VERTICAL&&(mThumbImageV.isNull() || mThumbImageH.isNull()) )
- || (mOrientation == HORIZONTAL&&(mTrackImageH.isNull() || mTrackImageV.isNull()) ))
- {
- gl_rect_2d(mOrientation == HORIZONTAL ? mThickness : 0,
- mOrientation == VERTICAL ? getRect().getHeight() - 2 * mThickness : getRect().getHeight(),
- mOrientation == HORIZONTAL ? getRect().getWidth() - 2 * mThickness : getRect().getWidth(),
- mOrientation == VERTICAL ? mThickness : 0, mTrackColor.get(), true);
-
- gl_rect_2d(mThumbRect, mThumbColor.get(), true);
-
- }
- else
- {
- // Thumb
- LLRect outline_rect = mThumbRect;
- outline_rect.stretch(2);
- // Background
-
- if(mOrientation == HORIZONTAL)
- {
- mTrackImageH->drawSolid(mThickness //S32 x
- , 0 //S32 y
- , getRect().getWidth() - 2 * mThickness //S32 width
- , getRect().getHeight() //S32 height
- , mTrackColor.get()); //const LLColor4& color
-
- if (gFocusMgr.getKeyboardFocus() == this)
- {
- mTrackImageH->draw(outline_rect, gFocusMgr.getFocusColor());
- }
-
- mThumbImageH->draw(mThumbRect, mThumbColor.get());
- if (mCurGlowStrength > 0.01f)
- {
- gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA);
- mThumbImageH->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength));
- gGL.setSceneBlendType(LLRender::BT_ALPHA);
- }
-
- }
- else if(mOrientation == VERTICAL)
- {
- mTrackImageV->drawSolid( 0 //S32 x
- , mThickness //S32 y
- , getRect().getWidth() //S32 width
- , getRect().getHeight() - 2 * mThickness //S32 height
- , mTrackColor.get()); //const LLColor4& color
- if (gFocusMgr.getKeyboardFocus() == this)
- {
- mTrackImageV->draw(outline_rect, gFocusMgr.getFocusColor());
- }
-
- mThumbImageV->draw(mThumbRect, mThumbColor.get());
- if (mCurGlowStrength > 0.01f)
- {
- gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA);
- mThumbImageV->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength));
- gGL.setSceneBlendType(LLRender::BT_ALPHA);
- }
- }
- }
-
- // Draw children
- LLView::draw();
-} // end draw
-
-
-bool LLScrollbar::changeLine( S32 delta, bool update_thumb )
-{
- return setDocPos(mDocPos + delta, update_thumb);
-}
-
-void LLScrollbar::setValue(const LLSD& value)
-{
- setDocPos((S32) value.asInteger());
-}
-
-
-bool LLScrollbar::handleKeyHere(KEY key, MASK mask)
-{
- if (getDocPosMax() == 0 && !getVisible())
- {
- return false;
- }
-
- bool handled = false;
-
- switch( key )
- {
- case KEY_HOME:
- setDocPos( 0 );
- handled = true;
- break;
-
- case KEY_END:
- setDocPos( getDocPosMax() );
- handled = true;
- break;
-
- case KEY_DOWN:
- setDocPos( getDocPos() + mStepSize );
- handled = true;
- break;
-
- case KEY_UP:
- setDocPos( getDocPos() - mStepSize );
- handled = true;
- break;
-
- case KEY_PAGE_DOWN:
- pageDown(1);
- break;
-
- case KEY_PAGE_UP:
- pageUp(1);
- break;
- }
-
- return handled;
-}
-
-void LLScrollbar::pageUp(S32 overlap)
-{
- if (mDocSize > mPageSize)
- {
- changeLine( -(mPageSize - overlap), true );
- }
-}
-
-void LLScrollbar::pageDown(S32 overlap)
-{
- if (mDocSize > mPageSize)
- {
- changeLine( mPageSize - overlap, true );
- }
-}
-
-void LLScrollbar::onLineUpBtnPressed( const LLSD& data )
-{
- changeLine( -mStepSize, true );
-}
-
-void LLScrollbar::onLineDownBtnPressed( const LLSD& data )
-{
- changeLine( mStepSize, true );
-}
-
-void LLScrollbar::setThickness(S32 thickness)
-{
- mThickness = thickness < 0 ? LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize") : thickness;
-}
+/** + * @file llscrollbar.cpp + * @brief Scrollbar UI widget + * + * $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" + +#include "llscrollbar.h" + +#include "llmath.h" +#include "lltimer.h" +#include "v3color.h" + +#include "llbutton.h" +#include "llcriticaldamp.h" +#include "llkeyboard.h" +#include "llui.h" +#include "llfocusmgr.h" +#include "llwindow.h" +#include "llcontrol.h" +#include "llrender.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register<LLScrollbar> register_scrollbar("scroll_bar"); + +LLScrollbar::Params::Params() +: orientation ("orientation", HORIZONTAL), + doc_size ("doc_size", 0), + doc_pos ("doc_pos", 0), + page_size ("page_size", 0), + step_size ("step_size", 1), + thumb_image_vertical("thumb_image_vertical"), + thumb_image_horizontal("thumb_image_horizontal"), + track_image_vertical("track_image_vertical"), + track_image_horizontal("track_image_horizontal"), + track_color("track_color"), + thumb_color("thumb_color"), + thickness("thickness"), + up_button("up_button"), + down_button("down_button"), + left_button("left_button"), + right_button("right_button"), + bg_visible("bg_visible", false), + bg_color("bg_color", LLColor4::black) +{} + +LLScrollbar::LLScrollbar(const Params & p) +: LLUICtrl(p), + mChangeCallback( p.change_callback() ), + mOrientation( p.orientation ), + mDocSize( p.doc_size ), + mDocPos( p.doc_pos ), + mPageSize( p.page_size ), + mStepSize( p.step_size ), + mDocChanged(false), + mDragStartX( 0 ), + mDragStartY( 0 ), + mHoverGlowStrength(0.15f), + mCurGlowStrength(0.f), + mTrackColor( p.track_color() ), + mThumbColor ( p.thumb_color() ), + mThumbImageV(p.thumb_image_vertical), + mThumbImageH(p.thumb_image_horizontal), + mTrackImageV(p.track_image_vertical), + mTrackImageH(p.track_image_horizontal), + mThickness(p.thickness.isProvided() ? p.thickness : LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize")), + mBGVisible(p.bg_visible), + mBGColor(p.bg_color) +{ + updateThumbRect(); + + // Page up and page down buttons + LLRect line_up_rect; + LLRect line_down_rect; + + if( VERTICAL == mOrientation ) + { + line_up_rect.setLeftTopAndSize( 0, getRect().getHeight(), mThickness, mThickness ); + line_down_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); + } + else // HORIZONTAL + { + line_up_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); + line_down_rect.setOriginAndSize( getRect().getWidth() - mThickness, 0, mThickness, mThickness ); + } + + LLButton::Params up_btn(mOrientation == VERTICAL ? p.up_button : p.left_button); + up_btn.name(std::string("Line Up")); + up_btn.rect(line_up_rect); + up_btn.click_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2)); + up_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2)); + up_btn.tab_stop(false); + up_btn.follows.flags = (mOrientation == VERTICAL ? (FOLLOWS_RIGHT | FOLLOWS_TOP) : (FOLLOWS_LEFT | FOLLOWS_BOTTOM)); + + addChild(LLUICtrlFactory::create<LLButton>(up_btn)); + + LLButton::Params down_btn(mOrientation == VERTICAL ? p.down_button : p.right_button); + down_btn.name(std::string("Line Down")); + down_btn.rect(line_down_rect); + down_btn.follows.flags(FOLLOWS_RIGHT|FOLLOWS_BOTTOM); + down_btn.click_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2)); + down_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2)); + down_btn.tab_stop(false); + + addChild(LLUICtrlFactory::create<LLButton>(down_btn)); +} + + +LLScrollbar::~LLScrollbar() +{ + // Children buttons killed by parent class +} + +void LLScrollbar::setDocParams( S32 size, S32 pos ) +{ + mDocSize = size; + setDocPos(pos); + mDocChanged = true; + + updateThumbRect(); +} + +// returns true if document position really changed +bool LLScrollbar::setDocPos(S32 pos, bool update_thumb) +{ + pos = llclamp(pos, 0, getDocPosMax()); + if (pos != mDocPos) + { + mDocPos = pos; + mDocChanged = true; + + if( mChangeCallback ) + { + mChangeCallback( mDocPos, this ); + } + + if( update_thumb ) + { + updateThumbRect(); + } + return true; + } + return false; +} + +void LLScrollbar::setDocSize(S32 size) +{ + if (size != mDocSize) + { + mDocSize = size; + setDocPos(mDocPos); + mDocChanged = true; + + updateThumbRect(); + } +} + +void LLScrollbar::setPageSize( S32 page_size ) +{ + if (page_size != mPageSize) + { + mPageSize = page_size; + setDocPos(mDocPos); + mDocChanged = true; + + updateThumbRect(); + } +} + +bool LLScrollbar::isAtBeginning() const +{ + return mDocPos == 0; +} + +bool LLScrollbar::isAtEnd() const +{ + return mDocPos == getDocPosMax(); +} + + +void LLScrollbar::updateThumbRect() +{ +// llassert( 0 <= mDocSize ); +// llassert( 0 <= mDocPos && mDocPos <= getDocPosMax() ); + + const S32 THUMB_MIN_LENGTH = 16; + + S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? getRect().getWidth() : getRect().getHeight(); + S32 thumb_bg_length = llmax(0, window_length - 2 * mThickness); + S32 visible_lines = llmin( mDocSize, mPageSize ); + S32 thumb_length = mDocSize ? llmin(llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH), thumb_bg_length) : thumb_bg_length; + + S32 variable_lines = mDocSize - visible_lines; + + if( mOrientation == LLScrollbar::VERTICAL ) + { + S32 thumb_start_max = thumb_bg_length + mThickness; + S32 thumb_start_min = mThickness + THUMB_MIN_LENGTH; + S32 thumb_start = variable_lines ? llmin( llmax(thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_max; + + mThumbRect.mLeft = 0; + mThumbRect.mTop = thumb_start; + mThumbRect.mRight = mThickness; + mThumbRect.mBottom = thumb_start - thumb_length; + } + else + { + // Horizontal + S32 thumb_start_max = thumb_bg_length + mThickness - thumb_length; + S32 thumb_start_min = mThickness; + S32 thumb_start = variable_lines ? llmin(llmax( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_min; + + mThumbRect.mLeft = thumb_start; + mThumbRect.mTop = mThickness; + mThumbRect.mRight = thumb_start + thumb_length; + mThumbRect.mBottom = 0; + } +} + +bool LLScrollbar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Check children first + bool handled_by_child = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + if( !handled_by_child ) + { + if( mThumbRect.pointInRect(x,y) ) + { + // Start dragging the thumb + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this ); + mDragStartX = x; + mDragStartY = y; + mOrigRect.mTop = mThumbRect.mTop; + mOrigRect.mBottom = mThumbRect.mBottom; + mOrigRect.mLeft = mThumbRect.mLeft; + mOrigRect.mRight = mThumbRect.mRight; + mLastDelta = 0; + } + else + { + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (mThumbRect.mTop < y) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (x < mThumbRect.mLeft) ) + ) + { + // Page up + pageUp(0); + } + else + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (y < mThumbRect.mBottom) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (mThumbRect.mRight < x) ) + ) + { + // Page down + pageDown(0); + } + } + } + + return true; +} + + +bool LLScrollbar::handleHover(S32 x, S32 y, MASK mask) +{ + // Note: we don't bother sending the event to the children (the arrow buttons) + // because they'll capture the mouse whenever they need hover events. + + bool handled = false; + if( hasMouseCapture() ) + { + S32 height = getRect().getHeight(); + S32 width = getRect().getWidth(); + + if( VERTICAL == mOrientation ) + { +// S32 old_pos = mThumbRect.mTop; + + S32 delta_pixels = y - mDragStartY; + if( mOrigRect.mBottom + delta_pixels < mThickness ) + { + delta_pixels = mThickness - mOrigRect.mBottom - 1; + } + else + if( mOrigRect.mTop + delta_pixels > height - mThickness ) + { + delta_pixels = height - mThickness - mOrigRect.mTop + 1; + } + + mThumbRect.mTop = mOrigRect.mTop + delta_pixels; + mThumbRect.mBottom = mOrigRect.mBottom + delta_pixels; + + S32 thumb_length = mThumbRect.getHeight(); + S32 thumb_track_length = height - 2 * mThickness; + + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mTop; + F32 ratio = F32(pos - mThickness - thumb_length) / usable_track_length; + + S32 new_pos = llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ); + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, false ); + } + } + + mLastDelta = delta_pixels; + + } + else + { + // Horizontal +// S32 old_pos = mThumbRect.mLeft; + + S32 delta_pixels = x - mDragStartX; + + if( mOrigRect.mLeft + delta_pixels < mThickness ) + { + delta_pixels = mThickness - mOrigRect.mLeft - 1; + } + else + if( mOrigRect.mRight + delta_pixels > width - mThickness ) + { + delta_pixels = width - mThickness - mOrigRect.mRight + 1; + } + + mThumbRect.mLeft = mOrigRect.mLeft + delta_pixels; + mThumbRect.mRight = mOrigRect.mRight + delta_pixels; + + S32 thumb_length = mThumbRect.getWidth(); + S32 thumb_track_length = width - 2 * mThickness; + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mLeft; + F32 ratio = F32(pos - mThickness) / usable_track_length; + + S32 new_pos = llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines); + + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, false ); + } + } + + mLastDelta = delta_pixels; + } + + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; + handled = true; + } + else + { + handled = childrenHandleHover( x, y, mask ) != NULL; + } + + // Opaque + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; + handled = true; + } + + mDocChanged = false; + return handled; +} // end handleHover + + +bool LLScrollbar::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + bool handled = changeLine( clicks * mStepSize, true ); + return handled; +} + +bool LLScrollbar::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + bool handled = false; + if (LLScrollbar::HORIZONTAL == mOrientation) + { + handled = changeLine(clicks * mStepSize, true); + } + return handled; +} + +bool LLScrollbar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string &tooltip_msg) +{ + // enable this to get drag and drop to control scrollbars + //if (!drop) + //{ + // //TODO: refactor this + // S32 variable_lines = getDocPosMax(); + // S32 pos = (VERTICAL == mOrientation) ? y : x; + // S32 thumb_length = (VERTICAL == mOrientation) ? mThumbRect.getHeight() : mThumbRect.getWidth(); + // S32 thumb_track_length = (VERTICAL == mOrientation) ? (getRect().getHeight() - 2 * SCROLLBAR_SIZE) : (getRect().getWidth() - 2 * SCROLLBAR_SIZE); + // S32 usable_track_length = thumb_track_length - thumb_length; + // F32 ratio = (VERTICAL == mOrientation) ? F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length + // : F32(pos - SCROLLBAR_SIZE) / usable_track_length; + // S32 new_pos = (VERTICAL == mOrientation) ? llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ) + // : llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines ); + // changeLine( new_pos - mDocPos, true ); + //} + //return true; + return false; +} + +bool LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + if( hasMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL ); + handled = true; + } + else + { + // Opaque, so don't just check children + handled = LLView::handleMouseUp( x, y, mask ); + } + + return handled; +} + +bool LLScrollbar::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + // just treat a double click as a second click + return handleMouseDown(x, y, mask); +} + + +void LLScrollbar::reshape(S32 width, S32 height, bool called_from_parent) +{ + if (width == getRect().getWidth() && height == getRect().getHeight()) return; + LLView::reshape( width, height, called_from_parent ); + LLButton* up_button = getChild<LLButton>("Line Up"); + LLButton* down_button = getChild<LLButton>("Line Down"); + + if (mOrientation == VERTICAL) + { + up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); + down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); + up_button->setOrigin(0, getRect().getHeight() - up_button->getRect().getHeight()); + down_button->setOrigin(0, 0); + } + else + { + up_button->reshape(llmin(getRect().getWidth() / 2, mThickness), up_button->getRect().getHeight()); + down_button->reshape(llmin(getRect().getWidth() / 2, mThickness), down_button->getRect().getHeight()); + up_button->setOrigin(0, 0); + down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), 0); + } + updateThumbRect(); +} + + +void LLScrollbar::draw() +{ + if (!getRect().isValid()) return; + + if(mBGVisible) + { + gl_rect_2d(getLocalRect(), mBGColor.get(), true); + } + + S32 local_mouse_x; + S32 local_mouse_y; + LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); + bool other_captor = gFocusMgr.getMouseCapture() && gFocusMgr.getMouseCapture() != this; + bool hovered = getEnabled() && !other_captor && (hasMouseCapture() || mThumbRect.pointInRect(local_mouse_x, local_mouse_y)); + if (hovered) + { + mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLSmoothInterpolation::getInterpolant(0.05f)); + } + else + { + mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f)); + } + + // Draw background and thumb. + if ( ( mOrientation == VERTICAL&&(mThumbImageV.isNull() || mThumbImageH.isNull()) ) + || (mOrientation == HORIZONTAL&&(mTrackImageH.isNull() || mTrackImageV.isNull()) )) + { + gl_rect_2d(mOrientation == HORIZONTAL ? mThickness : 0, + mOrientation == VERTICAL ? getRect().getHeight() - 2 * mThickness : getRect().getHeight(), + mOrientation == HORIZONTAL ? getRect().getWidth() - 2 * mThickness : getRect().getWidth(), + mOrientation == VERTICAL ? mThickness : 0, mTrackColor.get(), true); + + gl_rect_2d(mThumbRect, mThumbColor.get(), true); + + } + else + { + // Thumb + LLRect outline_rect = mThumbRect; + outline_rect.stretch(2); + // Background + + if(mOrientation == HORIZONTAL) + { + mTrackImageH->drawSolid(mThickness //S32 x + , 0 //S32 y + , getRect().getWidth() - 2 * mThickness //S32 width + , getRect().getHeight() //S32 height + , mTrackColor.get()); //const LLColor4& color + + if (gFocusMgr.getKeyboardFocus() == this) + { + mTrackImageH->draw(outline_rect, gFocusMgr.getFocusColor()); + } + + mThumbImageH->draw(mThumbRect, mThumbColor.get()); + if (mCurGlowStrength > 0.01f) + { + gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); + mThumbImageH->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength)); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + + } + else if(mOrientation == VERTICAL) + { + mTrackImageV->drawSolid( 0 //S32 x + , mThickness //S32 y + , getRect().getWidth() //S32 width + , getRect().getHeight() - 2 * mThickness //S32 height + , mTrackColor.get()); //const LLColor4& color + if (gFocusMgr.getKeyboardFocus() == this) + { + mTrackImageV->draw(outline_rect, gFocusMgr.getFocusColor()); + } + + mThumbImageV->draw(mThumbRect, mThumbColor.get()); + if (mCurGlowStrength > 0.01f) + { + gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); + mThumbImageV->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength)); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + } + } + + // Draw children + LLView::draw(); +} // end draw + + +bool LLScrollbar::changeLine( S32 delta, bool update_thumb ) +{ + return setDocPos(mDocPos + delta, update_thumb); +} + +void LLScrollbar::setValue(const LLSD& value) +{ + setDocPos((S32) value.asInteger()); +} + + +bool LLScrollbar::handleKeyHere(KEY key, MASK mask) +{ + if (getDocPosMax() == 0 && !getVisible()) + { + return false; + } + + bool handled = false; + + switch( key ) + { + case KEY_HOME: + setDocPos( 0 ); + handled = true; + break; + + case KEY_END: + setDocPos( getDocPosMax() ); + handled = true; + break; + + case KEY_DOWN: + setDocPos( getDocPos() + mStepSize ); + handled = true; + break; + + case KEY_UP: + setDocPos( getDocPos() - mStepSize ); + handled = true; + break; + + case KEY_PAGE_DOWN: + pageDown(1); + break; + + case KEY_PAGE_UP: + pageUp(1); + break; + } + + return handled; +} + +void LLScrollbar::pageUp(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( -(mPageSize - overlap), true ); + } +} + +void LLScrollbar::pageDown(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( mPageSize - overlap, true ); + } +} + +void LLScrollbar::onLineUpBtnPressed( const LLSD& data ) +{ + changeLine( -mStepSize, true ); +} + +void LLScrollbar::onLineDownBtnPressed( const LLSD& data ) +{ + changeLine( mStepSize, true ); +} + +void LLScrollbar::setThickness(S32 thickness) +{ + mThickness = thickness < 0 ? LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize") : thickness; +} |