Files
Upsilon/escher/src/scroll_view.cpp
2020-07-13 10:36:47 +02:00

295 lines
10 KiB
C++

#include <escher/scroll_view.h>
#include <escher/palette.h>
#include <new>
extern "C" {
#include <assert.h>
}
#include <algorithm>
ScrollView::ScrollView(View * contentView, ScrollViewDataSource * dataSource) :
View(),
m_contentView(contentView),
m_dataSource(dataSource),
m_topMargin(0),
m_rightMargin(0),
m_bottomMargin(0),
m_leftMargin(0),
m_innerView(this),
m_decorators(),
m_backgroundColor(Palette::BackgroundApps)
{
assert(m_dataSource != nullptr);
setDecoratorType(Decorator::Type::Bars);
}
ScrollView::ScrollView(ScrollView&& other) :
m_contentView(other.m_contentView),
m_dataSource(other.m_dataSource),
m_topMargin(other.m_topMargin),
m_rightMargin(other.m_rightMargin),
m_bottomMargin(other.m_bottomMargin),
m_leftMargin(other.m_leftMargin),
m_innerView(this),
m_backgroundColor(other.m_backgroundColor)
{
setDecoratorType(other.m_decoratorType);
}
KDSize ScrollView::minimalSizeForOptimalDisplay() const {
KDSize contentSize = m_contentView->minimalSizeForOptimalDisplay();
return KDSize(
contentSize.width() + m_leftMargin + m_rightMargin,
contentSize.height() + m_topMargin + m_bottomMargin);
}
void ScrollView::setMargins(KDCoordinate top, KDCoordinate right, KDCoordinate bottom, KDCoordinate left) {
m_topMargin = top;
m_rightMargin = right;
m_bottomMargin = bottom;
m_leftMargin = left;
}
void ScrollView::scrollToContentPoint(KDPoint p, bool allowOverscroll) {
if (!allowOverscroll && !m_contentView->bounds().contains(p)) {
return;
}
KDRect visibleRect = visibleContentRect();
if (visibleRect.width() < 0 || visibleRect.height() < 0) {
return;
}
KDCoordinate offsetX = 0;
KDCoordinate offsetY = 0;
if (visibleRect.left() > p.x()) {
offsetX = p.x() - visibleRect.left();
}
if (visibleRect.right() < p.x()) {
offsetX = p.x() - visibleRect.right();
}
if (visibleRect.top() > p.y()) {
offsetY = p.y() - visibleRect.top();
}
if (visibleRect.bottom() < p.y()) {
offsetY = p.y() - visibleRect.bottom();
}
if (offsetX != 0 || offsetY != 0) {
setContentOffset(contentOffset().translatedBy(KDPoint(offsetX, offsetY)));
}
// Handle cases when the size of the view has decreased.
setContentOffset(KDPoint(
std::min(
contentOffset().x(),
std::max<KDCoordinate>(
minimalSizeForOptimalDisplay().width() - bounds().width(),
KDCoordinate(0))),
std::min(
contentOffset().y(),
std::max<KDCoordinate>(
minimalSizeForOptimalDisplay().height() - bounds().height(),
KDCoordinate(0)))));
}
void ScrollView::scrollToContentRect(KDRect rect, bool allowOverscroll) {
KDPoint tl = rect.topLeft();
KDPoint br = rect.bottomRight();
KDRect visibleRect = visibleContentRect();
/* We first check that we can display the whole rect. If we can't, we focus
* the scroll to the closest part of the rect. */
if (visibleRect.height() < rect.height()) {
// The visible rect is too small to display 'rect'
if (rect.top() >= visibleRect.top()) {
// We scroll to display the top part of rect
br = KDPoint(br.x(), rect.top() + visibleRect.height());
} else {
// We scroll to display the bottom part of rect
tl = KDPoint(tl.x(), rect.bottom() - visibleRect.height());
}
}
if (visibleRect.width() < rect.width()) {
// The visible rect is too small to display 'rect'
if (rect.left() >= visibleRect.left()) {
// We scroll to display the left part of rect
br = KDPoint(rect.left() + visibleRect.width(), br.y());
} else {
// We scroll to display the right part of rect
tl = KDPoint(rect.right() - visibleRect.width(), tl.y());
}
}
scrollToContentPoint(tl, allowOverscroll);
scrollToContentPoint(br, allowOverscroll);
}
KDRect ScrollView::visibleContentRect() {
return KDRect(
contentOffset().x(),
contentOffset().y(),
m_frame.width() - m_leftMargin - m_rightMargin,
m_frame.height() - m_topMargin - m_bottomMargin);
}
void ScrollView::layoutSubviews(bool force) {
if (bounds().isEmpty()) {
return;
}
KDRect r1 = KDRectZero;
KDRect r2 = KDRectZero;
KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2, force);
if (!r1.isEmpty()) {
markRectAsDirty(r1);
}
if (!r2.isEmpty()) {
markRectAsDirty(r2);
}
m_innerView.setFrame(innerFrame, force);
KDPoint absoluteOffset = contentOffset().opposite().translatedBy(KDPoint(m_leftMargin - innerFrame.x(), m_topMargin - innerFrame.y()));
KDRect contentFrame = KDRect(absoluteOffset, contentSize());
m_contentView->setFrame(contentFrame, force);
}
void ScrollView::setContentOffset(KDPoint offset, bool forceRelayout) {
if (m_dataSource->setOffset(offset) || forceRelayout) {
layoutSubviews();
}
}
void ScrollView::InnerView::drawRect(KDContext * ctx, KDRect rect) const {
KDCoordinate height = bounds().height();
KDCoordinate width = bounds().width();
KDCoordinate offsetX = m_scrollView->contentOffset().x() + m_frame.x();
KDCoordinate offsetY = m_scrollView->contentOffset().y() + m_frame.y();
KDCoordinate contentHeight = m_scrollView->m_contentView->bounds().height();
KDCoordinate contentWidth = m_scrollView->m_contentView->bounds().width();
ctx->fillRect(KDRect(0, 0, width, m_scrollView->m_topMargin-offsetY), m_scrollView->m_backgroundColor);
ctx->fillRect(KDRect(0, contentHeight+m_scrollView->m_topMargin-offsetY, width, height - contentHeight - m_scrollView->m_topMargin + offsetY), m_scrollView->m_backgroundColor);
ctx->fillRect(KDRect(0, 0, m_scrollView->m_leftMargin-offsetX, height), m_scrollView->m_backgroundColor);
ctx->fillRect(KDRect(contentWidth + m_scrollView->m_leftMargin - offsetX, 0, width - contentWidth - m_scrollView->m_leftMargin + offsetX, height), m_scrollView->m_backgroundColor);
}
View * ScrollView::BarDecorator::indicatorAtIndex(int index) {
if (index == 1) {
return &m_verticalBar;
}
assert(index == 2);
return &m_horizontalBar;
}
KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) {
bool hBarWasVisible = m_horizontalBar.visible();
bool hBarIsVisible = m_horizontalBar.update(content.width(), offset.x(), frame.width());
bool vBarWasVisible = m_verticalBar.visible();
bool vBarIsVisible = m_verticalBar.update(content.height(), offset.y(), frame.height());
KDCoordinate hBarFrameBreadth = k_barsFrameBreadth * hBarIsVisible;
KDCoordinate vBarFrameBreadth = k_barsFrameBreadth * vBarIsVisible;
// Mark the vertical and horizontal rects as dirty id needed
*dirtyRect1 = hBarWasVisible == hBarIsVisible ? KDRectZero : KDRect(frame.width() - k_barsFrameBreadth, 0, k_barsFrameBreadth, frame.height());
*dirtyRect2 = vBarWasVisible == vBarIsVisible ? KDRectZero : KDRect(0, frame.height() - k_barsFrameBreadth, frame.width(), k_barsFrameBreadth);
/* If the two indicators are visible, we leave an empty rectangle in the right
* bottom corner. Otherwise, the only indicator uses all the height/width. */
m_verticalBar.setFrame(KDRect(
frame.width() - vBarFrameBreadth, 0,
vBarFrameBreadth, frame.height() - hBarFrameBreadth),
force);
m_horizontalBar.setFrame(KDRect(
0, frame.height() - hBarFrameBreadth,
frame.width() - vBarFrameBreadth, hBarFrameBreadth),
force);
return frame;
}
View * ScrollView::ArrowDecorator::indicatorAtIndex(int index) {
switch(index) {
case 1:
return &m_topArrow;
case 2:
return &m_rightArrow;
case 3:
return &m_bottomArrow;
default:
assert(index == 4);
return &m_leftArrow;
}
}
KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) {
// There is no need to dirty the rects
KDSize arrowSize = KDFont::LargeFont->glyphSize();
KDCoordinate topArrowFrameBreadth = arrowSize.height() * m_topArrow.update(0 < offset.y());
KDCoordinate rightArrowFrameBreadth = arrowSize.width() * m_rightArrow.update(offset.x() + frame.width() < content.width());
KDCoordinate bottomArrowFrameBreadth = arrowSize.height() * m_bottomArrow.update(offset.y() + frame.height() < content.height());
KDCoordinate leftArrowFrameBreadth = arrowSize.width() * m_leftArrow.update(0 < offset.x());
m_topArrow.setFrame(KDRect(
0, 0,
frame.width(), topArrowFrameBreadth),
force);
m_rightArrow.setFrame(KDRect(
frame.width() - rightArrowFrameBreadth, 0,
rightArrowFrameBreadth, frame.height()),
force);
m_bottomArrow.setFrame(KDRect(
0, frame.height() - bottomArrowFrameBreadth,
frame.width(), bottomArrowFrameBreadth),
force);
m_leftArrow.setFrame(KDRect(
0, 0,
leftArrowFrameBreadth, frame.height()),
force);
return KDRect(
frame.x() + leftArrowFrameBreadth,
frame.y() + topArrowFrameBreadth,
frame.width() - leftArrowFrameBreadth - rightArrowFrameBreadth,
frame.height() - topArrowFrameBreadth - bottomArrowFrameBreadth
);
}
void ScrollView::ArrowDecorator::setBackgroundColor(KDColor c) {
const int indicatorsCount = numberOfIndicators();
for (int index = 1; index <= indicatorsCount; index++) {
static_cast<ScrollViewArrow *>(indicatorAtIndex(index))->setBackgroundColor(c);
}
}
ScrollView::Decorators::Decorators() {
/* We need to initiate the Union at construction to avoid destructing an
* uninitialized object when changing the decorator type. */
new (this) Decorator();
}
ScrollView::Decorators::~Decorators() {
activeDecorator()->~Decorator();
}
void ScrollView::Decorators::setActiveDecorator(Decorator::Type t) {
/* Decorator destructor is virtual so calling ~Decorator() on a Decorator
* pointer will call the appropriate destructor. */
activeDecorator()->~Decorator();
switch (t) {
case Decorator::Type::Bars:
new (&m_bars) BarDecorator();
break;
case Decorator::Type::Arrows:
new (&m_arrows) ArrowDecorator();
break;
default:
assert(t == Decorator::Type::None);
new (&m_none) Decorator();
}
}
#if ESCHER_VIEW_LOGGING
const char * ScrollView::className() const {
return "ScrollView";
}
void ScrollView::logAttributes(std::ostream &os) const {
View::logAttributes(os);
os << " offset=\"" << (int)contentOffset().x << "," << (int)contentOffset().y << "\"";
}
#endif