/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2012 The Mana Developers
*
* This file is part of The Mana Client.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "gui/widgets/scrollarea.h"
#include "graphics.h"
#include "gui/gui.h"
#include
ScrollArea::ScrollArea()
{
init();
}
ScrollArea::ScrollArea(gcn::Widget *widget):
gcn::ScrollArea(widget)
{
init();
}
ScrollArea::~ScrollArea()
{
delete getContent();
}
void ScrollArea::setShowButtons(bool showButtons)
{
mShowButtons = showButtons;
}
void ScrollArea::init()
{
// Draw background by default
setOpaque(true);
auto theme = gui->getTheme();
int scrollBarWidth = theme->getSkin(SkinType::ScrollAreaVBar).width;
if (scrollBarWidth > 0)
setScrollbarWidth(scrollBarWidth);
auto &scrollAreaSkin = theme->getSkin(SkinType::ScrollArea);
setShowButtons(scrollAreaSkin.showButtons);
if (auto content = getContent())
content->setFrameSize(scrollAreaSkin.padding);
// The base color is only used when rendering a square in the corner where
// the scrollbars meet. We disable rendering of this square by setting the
// base color to transparent.
setBaseColor(gcn::Color(0, 0, 0, 0));
setUpButtonScrollAmount(5);
setDownButtonScrollAmount(5);
setLeftButtonScrollAmount(5);
setRightButtonScrollAmount(5);
}
void ScrollArea::logic()
{
if (!isVisible())
return;
gcn::ScrollArea::logic();
gcn::Widget *content = getContent();
// When no scrollbar in a certain direction, adapt content size to match
// the content dimension exactly.
if (content)
{
if (getHorizontalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER)
{
content->setWidth(getChildrenArea().width -
2 * content->getFrameSize());
}
if (getVerticalScrollPolicy() == gcn::ScrollArea::SHOW_NEVER)
{
content->setHeight(getChildrenArea().height -
2 * content->getFrameSize());
}
}
if (mUpButtonPressed)
{
setVerticalScrollAmount(getVerticalScrollAmount() -
mUpButtonScrollAmount);
}
else if (mDownButtonPressed)
{
setVerticalScrollAmount(getVerticalScrollAmount() +
mDownButtonScrollAmount);
}
else if (mLeftButtonPressed)
{
setHorizontalScrollAmount(getHorizontalScrollAmount() -
mLeftButtonScrollAmount);
}
else if (mRightButtonPressed)
{
setHorizontalScrollAmount(getHorizontalScrollAmount() +
mRightButtonScrollAmount);
}
}
void ScrollArea::draw(gcn::Graphics *graphics)
{
if (getFrameSize() == 0)
drawFrame(graphics);
gcn::ScrollArea::draw(graphics);
}
void ScrollArea::drawFrame(gcn::Graphics *graphics)
{
if (!mOpaque)
return;
const int bs = getFrameSize();
WidgetState state(this);
state.width += bs * 2;
state.height += + bs * 2;
gui->getTheme()->drawSkin(static_cast(graphics), SkinType::ScrollArea, state);
}
void ScrollArea::drawChildren(gcn::Graphics *graphics)
{
auto g = static_cast(graphics);
g->pushClipRect(getChildrenArea());
gcn::ScrollArea::drawChildren(graphics);
g->popClipRect();
}
void ScrollArea::setOpaque(bool opaque)
{
mOpaque = opaque;
auto &skin = gui->getTheme()->getSkin(SkinType::ScrollArea);
setFrameSize(mOpaque ? skin.frameSize : 0);
}
void ScrollArea::drawBackground(gcn::Graphics *graphics)
{
// background is drawn as part of the frame instead
}
static void drawButton(gcn::Graphics *graphics,
SkinType skinType,
bool pressed,
const gcn::Rectangle &dim)
{
WidgetState state(dim);
if (pressed)
state.flags |= STATE_SELECTED;
gui->getTheme()->drawSkin(static_cast(graphics), skinType, state);
}
void ScrollArea::drawUpButton(gcn::Graphics *graphics)
{
if (!mShowButtons)
return;
drawButton(graphics, SkinType::ButtonUp, mUpButtonPressed, getUpButtonDimension());
}
void ScrollArea::drawDownButton(gcn::Graphics *graphics)
{
if (!mShowButtons)
return;
drawButton(graphics, SkinType::ButtonDown, mDownButtonPressed, getDownButtonDimension());
}
void ScrollArea::drawLeftButton(gcn::Graphics *graphics)
{
if (!mShowButtons)
return;
drawButton(graphics, SkinType::ButtonLeft, mLeftButtonPressed, getLeftButtonDimension());
}
void ScrollArea::drawRightButton(gcn::Graphics *graphics)
{
if (!mShowButtons)
return;
drawButton(graphics, SkinType::ButtonRight, mRightButtonPressed, getRightButtonDimension());
}
void ScrollArea::drawVBar(gcn::Graphics *graphics)
{
WidgetState state(getVerticalBarDimension());
if (mHasMouse && (mX > (getWidth() - getScrollbarWidth())))
state.flags |= STATE_HOVERED;
gui->getTheme()->drawSkin(static_cast(graphics), SkinType::ScrollAreaVBar, state);
}
void ScrollArea::drawHBar(gcn::Graphics *graphics)
{
WidgetState state(getHorizontalBarDimension());
if (mHasMouse && (mY > (getHeight() - getScrollbarWidth())))
state.flags |= STATE_HOVERED;
gui->getTheme()->drawSkin(static_cast(graphics), SkinType::ScrollAreaHBar, state);
}
void ScrollArea::drawVMarker(gcn::Graphics *graphics)
{
WidgetState state(getVerticalMarkerDimension());
if (state.height == 0)
return;
if (mHasMouse && (mX > (getWidth() - getScrollbarWidth())))
state.flags |= STATE_HOVERED;
gui->getTheme()->drawSkin(static_cast(graphics), SkinType::ScrollAreaVMarker, state);
}
void ScrollArea::drawHMarker(gcn::Graphics *graphics)
{
WidgetState state(getHorizontalMarkerDimension());
if (state.width == 0)
return;
if (mHasMouse && (mY > (getHeight() - getScrollbarWidth())))
state.flags |= STATE_HOVERED;
gui->getTheme()->drawSkin(static_cast(graphics), SkinType::ScrollAreaHMarker, state);
}
/**
* Code copied from gcn::ScrollArea::checkPolicies to make sure it takes the
* frame size of the content into account.
*/
void ScrollArea::checkPolicies()
{
int w = getWidth();
int h = getHeight();
mHBarVisible = false;
mVBarVisible = false;
if (!getContent())
{
mHBarVisible = (mHPolicy == SHOW_ALWAYS);
mVBarVisible = (mVPolicy == SHOW_ALWAYS);
return;
}
const int contentFrameSize = getContent()->getFrameSize();
w -= 2 * contentFrameSize;
h -= 2 * contentFrameSize;
if (mHPolicy == SHOW_AUTO &&
mVPolicy == SHOW_AUTO)
{
if (getContent()->getWidth() <= w
&& getContent()->getHeight() <= h)
{
mHBarVisible = false;
mVBarVisible = false;
}
if (getContent()->getWidth() > w)
{
mHBarVisible = true;
}
if ((getContent()->getHeight() > h)
|| (mHBarVisible && getContent()->getHeight() > h - mScrollbarWidth))
{
mVBarVisible = true;
}
if (mVBarVisible && getContent()->getWidth() > w - mScrollbarWidth)
{
mHBarVisible = true;
}
return;
}
switch (mHPolicy)
{
case SHOW_NEVER:
mHBarVisible = false;
break;
case SHOW_ALWAYS:
mHBarVisible = true;
break;
case SHOW_AUTO:
if (mVPolicy == SHOW_NEVER)
{
mHBarVisible = getContent()->getWidth() > w;
}
else // (mVPolicy == SHOW_ALWAYS)
{
mHBarVisible = getContent()->getWidth() > w - mScrollbarWidth;
}
break;
default:
throw GCN_EXCEPTION("Horizontal scroll policy invalid.");
}
switch (mVPolicy)
{
case SHOW_NEVER:
mVBarVisible = false;
break;
case SHOW_ALWAYS:
mVBarVisible = true;
break;
case SHOW_AUTO:
if (mHPolicy == SHOW_NEVER)
{
mVBarVisible = getContent()->getHeight() > h;
}
else // (mHPolicy == SHOW_ALWAYS)
{
mVBarVisible = getContent()->getHeight() > h - mScrollbarWidth;
}
break;
default:
throw GCN_EXCEPTION("Vertical scroll policy invalid.");
}
}
void ScrollArea::mouseMoved(gcn::MouseEvent& event)
{
mX = event.getX();
mY = event.getY();
}
void ScrollArea::mouseEntered(gcn::MouseEvent& event)
{
mHasMouse = true;
}
void ScrollArea::mouseExited(gcn::MouseEvent& event)
{
mHasMouse = false;
}
/**
* Code copied from gcn::ScrollArea::mousePressed to make it call our custom
* getVerticalMarkerDimension and getHorizontalMarkerDimension functions.
*/
void ScrollArea::mousePressed(gcn::MouseEvent &mouseEvent)
{
int x = mouseEvent.getX();
int y = mouseEvent.getY();
if (getUpButtonDimension().isPointInRect(x, y))
{
setVerticalScrollAmount(getVerticalScrollAmount()
- mUpButtonScrollAmount);
mUpButtonPressed = true;
}
else if (getDownButtonDimension().isPointInRect(x, y))
{
setVerticalScrollAmount(getVerticalScrollAmount()
+ mDownButtonScrollAmount);
mDownButtonPressed = true;
}
else if (getLeftButtonDimension().isPointInRect(x, y))
{
setHorizontalScrollAmount(getHorizontalScrollAmount()
- mLeftButtonScrollAmount);
mLeftButtonPressed = true;
}
else if (getRightButtonDimension().isPointInRect(x, y))
{
setHorizontalScrollAmount(getHorizontalScrollAmount()
+ mRightButtonScrollAmount);
mRightButtonPressed = true;
}
else if (getVerticalMarkerDimension().isPointInRect(x, y))
{
mIsHorizontalMarkerDragged = false;
mIsVerticalMarkerDragged = true;
mVerticalMarkerDragOffset = y - getVerticalMarkerDimension().y;
}
else if (getVerticalBarDimension().isPointInRect(x,y))
{
if (y < getVerticalMarkerDimension().y)
{
setVerticalScrollAmount(getVerticalScrollAmount()
- (int)(getChildrenArea().height * 0.95));
}
else
{
setVerticalScrollAmount(getVerticalScrollAmount()
+ (int)(getChildrenArea().height * 0.95));
}
}
else if (getHorizontalMarkerDimension().isPointInRect(x, y))
{
mIsHorizontalMarkerDragged = true;
mIsVerticalMarkerDragged = false;
mHorizontalMarkerDragOffset = x - getHorizontalMarkerDimension().x;
}
else if (getHorizontalBarDimension().isPointInRect(x,y))
{
if (x < getHorizontalMarkerDimension().x)
{
setHorizontalScrollAmount(getHorizontalScrollAmount()
- (int)(getChildrenArea().width * 0.95));
}
else
{
setHorizontalScrollAmount(getHorizontalScrollAmount()
+ (int)(getChildrenArea().width * 0.95));
}
}
}
/**
* Code copied from gcn::ScrollArea::mouseDragged to make it call our custom
* getVerticalMarkerDimension and getHorizontalMarkerDimension functions.
*/
void ScrollArea::mouseDragged(gcn::MouseEvent &mouseEvent)
{
if (mIsVerticalMarkerDragged)
{
int pos = mouseEvent.getY() - getVerticalBarDimension().y - mVerticalMarkerDragOffset;
int length = getVerticalMarkerDimension().height;
gcn::Rectangle barDim = getVerticalBarDimension();
if ((barDim.height - length) > 0)
{
setVerticalScrollAmount((getVerticalMaxScroll() * pos)
/ (barDim.height - length));
}
else
{
setVerticalScrollAmount(0);
}
}
if (mIsHorizontalMarkerDragged)
{
int pos = mouseEvent.getX() - getHorizontalBarDimension().x - mHorizontalMarkerDragOffset;
int length = getHorizontalMarkerDimension().width;
gcn::Rectangle barDim = getHorizontalBarDimension();
if ((barDim.width - length) > 0)
{
setHorizontalScrollAmount((getHorizontalMaxScroll() * pos)
/ (barDim.width - length));
}
else
{
setHorizontalScrollAmount(0);
}
}
mouseEvent.consume();
}
gcn::Rectangle ScrollArea::getUpButtonDimension()
{
if (!mVBarVisible || !mShowButtons)
return gcn::Rectangle();
return gcn::Rectangle(getWidth() - mScrollbarWidth, 0, mScrollbarWidth, mScrollbarWidth);
}
gcn::Rectangle ScrollArea::getDownButtonDimension()
{
if (!mVBarVisible || !mShowButtons)
return gcn::Rectangle();
gcn::Rectangle dim(getWidth() - mScrollbarWidth,
getHeight() - mScrollbarWidth,
mScrollbarWidth,
mScrollbarWidth);
if (mHBarVisible)
dim.y -= mScrollbarWidth;
return dim;
}
gcn::Rectangle ScrollArea::getLeftButtonDimension()
{
if (!mHBarVisible || !mShowButtons)
return gcn::Rectangle();
return gcn::Rectangle(0, getHeight() - mScrollbarWidth, mScrollbarWidth, mScrollbarWidth);
}
gcn::Rectangle ScrollArea::getRightButtonDimension()
{
if (!mHBarVisible || !mShowButtons)
return gcn::Rectangle();
gcn::Rectangle dim(getWidth() - mScrollbarWidth,
getHeight() - mScrollbarWidth,
mScrollbarWidth,
mScrollbarWidth);
if (mVBarVisible)
dim.x -= mScrollbarWidth;
return dim;
}
gcn::Rectangle ScrollArea::getVerticalBarDimension()
{
if (!mVBarVisible)
return gcn::Rectangle();
gcn::Rectangle dim(getWidth() - mScrollbarWidth,
getUpButtonDimension().height,
mScrollbarWidth,
getHeight()
- getUpButtonDimension().height
- getDownButtonDimension().height);
if (mHBarVisible)
dim.height -= mScrollbarWidth;
if (dim.height < 0)
dim.height = 0;
return dim;
}
gcn::Rectangle ScrollArea::getHorizontalBarDimension()
{
if (!mHBarVisible)
return gcn::Rectangle();
gcn::Rectangle dim(getLeftButtonDimension().width,
getHeight() - mScrollbarWidth,
getWidth()
- getLeftButtonDimension().width
- getRightButtonDimension().width,
mScrollbarWidth);
if (mVBarVisible)
dim.width -= mScrollbarWidth;
if (dim.width < 0)
dim.width = 0;
return dim;
}
static void getMarkerValues(int barSize,
int maxScroll, int scrollAmount,
int contentHeight, int viewHeight,
int fixedMarkerSize, int minMarkerSize,
int &markerSize, int &markerPos)
{
if (fixedMarkerSize == 0)
{
if (contentHeight != 0 && contentHeight > viewHeight)
markerSize = std::max((barSize * viewHeight) / contentHeight, minMarkerSize);
else
markerSize = barSize;
}
else
{
if (contentHeight > viewHeight)
markerSize = fixedMarkerSize;
else
markerSize = 0;
}
// Hide the marker when it doesn't fit
if (markerSize > barSize)
markerSize = 0;
if (maxScroll != 0)
markerPos = ((barSize - markerSize) * scrollAmount + maxScroll / 2) / maxScroll;
else
markerPos = 0;
}
gcn::Rectangle ScrollArea::getVerticalMarkerDimension()
{
if (!mVBarVisible)
return gcn::Rectangle();
auto &markerSkin = gui->getTheme()->getSkin(SkinType::ScrollAreaVMarker);
const gcn::Rectangle barDim = getVerticalBarDimension();
int contentHeight = 0;
if (auto content = getContent())
contentHeight = content->getHeight() + content->getFrameSize() * 2;
int length;
int pos;
getMarkerValues(barDim.height,
getVerticalMaxScroll(),
getVerticalScrollAmount(),
contentHeight,
getChildrenArea().height,
markerSkin.height,
mScrollbarWidth,
length,
pos);
return gcn::Rectangle(barDim.x, barDim.y + pos, mScrollbarWidth, length);
}
gcn::Rectangle ScrollArea::getHorizontalMarkerDimension()
{
if (!mHBarVisible)
return gcn::Rectangle();
auto &markerSkin = gui->getTheme()->getSkin(SkinType::ScrollAreaHMarker);
const gcn::Rectangle barDim = getHorizontalBarDimension();
int contentWidth = 0;
if (auto content = getContent())
contentWidth = content->getWidth() + content->getFrameSize() * 2;
int length;
int pos;
getMarkerValues(barDim.width,
getHorizontalMaxScroll(),
getHorizontalScrollAmount(),
contentWidth,
getChildrenArea().width,
markerSkin.width,
mScrollbarWidth,
length,
pos);
return gcn::Rectangle(barDim.x + pos, barDim.y, length, mScrollbarWidth);
}