| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- // zlib open source license
- //
- // Copyright (c) 2020 to 2022 David Forsgren Piuva
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source
- // distribution.
- #include "ScrollBarImpl.h"
- using namespace dsr;
- void ScrollBarImpl::loadTheme(VisualTheme theme, const ColorRgbI32 &color) {
- this->scalableImage_scrollTop = theme_getScalableImage(theme, U"ScrollUp");
- this->scalableImage_scrollBottom = theme_getScalableImage(theme, U"ScrollDown");
- this->scalableImage_verticalScrollKnob = theme_getScalableImage(theme, U"VerticalScrollKnob");
- this->scalableImage_verticalScrollBackground = theme_getScalableImage(theme, U"VerticalScrollList");
- component_generateImage(theme, this->scalableImage_scrollTop, this->scrollBarThickness, this->scrollButtonLength, color.red, color.green, color.blue, 0)(this->scrollButtonTopImage_normal);
- component_generateImage(theme, this->scalableImage_scrollTop, this->scrollBarThickness, this->scrollButtonLength, color.red, color.green, color.blue, 1)(this->scrollButtonTopImage_pressed);
- component_generateImage(theme, this->scalableImage_scrollBottom, this->scrollBarThickness, this->scrollButtonLength, color.red, color.green, color.blue, 0)(this->scrollButtonBottomImage_normal);
- component_generateImage(theme, this->scalableImage_scrollBottom, this->scrollBarThickness, this->scrollButtonLength, color.red, color.green, color.blue, 1)(this->scrollButtonBottomImage_pressed);
- }
- void ScrollBarImpl::updateScrollRange(const ScrollRange &range) {
- this->scrollRange = range;
- }
- void ScrollBarImpl::setValue(int64_t newValue) {
- this->value = newValue;
- }
- int64_t ScrollBarImpl::getValue() {
- return this->value;
- }
- void ScrollBarImpl::limitScrolling(const IRect &parentLocation, bool keepPinValueInRange, int64_t pinValue) {
- // Big enough list to need scrolling but big enough list-box to fit two buttons inside
- this->visible = this->scrollRange.minValue < this->scrollRange.maxValue && parentLocation.width() >= scrollBarThickness * 2 && parentLocation.height() >= this->scrollButtonLength * 3;
- if (keepPinValueInRange) {
- // Constrain to keep pinValue in range.
- int64_t maxScroll = pinValue;
- int64_t minScroll = pinValue + 1 - this->scrollRange.visibleItems;
- if (this->value > maxScroll) this->value = maxScroll;
- if (this->value < minScroll) this->value = minScroll;
- }
- // Constrain value to be within the inclusive minValue..maxValue interval, in case that pinValue is out of bound.
- if (this->value > scrollRange.maxValue) this->value = scrollRange.maxValue;
- if (this->value < scrollRange.minValue) this->value = scrollRange.minValue;
- }
- IRect ScrollBarImpl::getScrollBarLocation_excludingButtons(int32_t parentWidth, int32_t parentHeight) {
- return this->vertical ? IRect(parentWidth - this->scrollBarThickness, this->scrollButtonLength, this->scrollBarThickness, parentHeight - (this->scrollButtonLength * 2))
- : IRect(this->scrollButtonLength, parentHeight - this->scrollBarThickness, parentWidth - (this->scrollButtonLength * 2), this->scrollBarThickness);
- }
- IRect ScrollBarImpl::getKnobLocation(int32_t parentWidth, int32_t parentHeight) {
- // Eroded scroll-bar excluding buttons
- // The final knob is a sub-set of this region corresponding to the visibility
- IRect scrollBarRegion = this->getScrollBarLocation_excludingButtons(parentWidth, parentHeight);
- // The knob should represent the selected range within the total range.
- int64_t barLength = this->vertical ? scrollBarRegion.height() : scrollBarRegion.width();
- int64_t barThickness = this->vertical ? scrollBarRegion.width() : scrollBarRegion.height();
- int64_t knobLength = (barLength * this->scrollRange.visibleItems) / (this->scrollRange.maxValue + this->scrollRange.visibleItems);
- if (knobLength < barThickness) {
- knobLength = barThickness;
- }
- // Visual range for center
- int64_t scrollStart = (this->vertical ? scrollBarRegion.top() : scrollBarRegion.left()) + knobLength / 2;
- int64_t scrollDistance = barLength - knobLength;
- int64_t knobStart = scrollStart + ((this->value * scrollDistance) / this->scrollRange.maxValue) - (knobLength / 2);
- return this->vertical ? IRect(scrollBarRegion.left(), knobStart, barThickness, knobLength)
- : IRect(knobStart, scrollBarRegion.top(), knobLength, barThickness);
- }
- void ScrollBarImpl::draw(OrderedImageRgbaU8 &target, VisualTheme &theme, const ColorRgbI32 &color) {
- if (this->visible) {
- int32_t parentWidth = image_getWidth(target);
- int32_t parentHeight = image_getHeight(target);
- IRect whole = this->vertical ? IRect(parentWidth - this->scrollBarThickness, 0, this->scrollBarThickness, parentHeight)
- : IRect(0, parentHeight - this->scrollBarThickness, parentWidth, this->scrollBarThickness);
- IRect upper = this->vertical ? IRect(whole.left(), whole.top(), whole.width(), this->scrollButtonLength)
- : IRect(whole.left(), whole.top(), this->scrollButtonLength, whole.height());
- IRect lower = this->vertical ? IRect(whole.left(), whole.bottom() - this->scrollButtonLength, whole.width(), this->scrollButtonLength)
- : IRect(whole.right() - this->scrollButtonLength, whole.top(), this->scrollButtonLength, whole.height());
- IRect knob = this->getKnobLocation(parentWidth, parentHeight);
- // Only redraw the knob image if its dimensions changed
- if (!image_exists(this->scrollKnobImage)
- || image_getWidth(this->scrollKnobImage) != knob.width()
- || image_getHeight(this->scrollKnobImage) != knob.height()) {
- component_generateImage(theme, this->scalableImage_verticalScrollKnob, knob.width(), knob.height(), color.red, color.green, color.blue, 0)(this->scrollKnobImage);
- }
- // Only redraw the scroll list if its dimenstions changed
- if (!image_exists(this->verticalScrollBarImage)
- || image_getWidth(this->verticalScrollBarImage) != whole.width()
- || image_getHeight(this->verticalScrollBarImage) != whole.height()) {
- component_generateImage(theme, this->scalableImage_verticalScrollBackground, whole.width(), whole.height(), color.red, color.green, color.blue, 0)(this->verticalScrollBarImage);
- }
- // Draw the scroll-bar
- draw_alphaFilter(target, this->verticalScrollBarImage, whole.left(), whole.top());
- draw_alphaFilter(target, this->scrollKnobImage, knob.left(), knob.top());
- draw_alphaFilter(target, this->pressScrollUp ? this->scrollButtonTopImage_pressed : this->scrollButtonTopImage_normal, upper.left(), upper.top());
- draw_alphaFilter(target, this->pressScrollDown ? this->scrollButtonBottomImage_pressed : this->scrollButtonBottomImage_normal, lower.left(), lower.top());
- }
- }
- bool ScrollBarImpl::pressScrollBar(const IRect &parentLocation, int64_t localCoordinate) {
- int64_t oldValue = this->value;
- IRect knobLocation = this->getKnobLocation(parentLocation.width(), parentLocation.height());
- int64_t knobLength = this->vertical ? knobLocation.height() : knobLocation.width();
- int64_t endDistance = this->scrollButtonLength + knobLength / 2;
- int64_t barLength = (this->vertical ? parentLocation.height() : parentLocation.width()) - (endDistance * 2);
- this->value = ((localCoordinate - endDistance) * this->scrollRange.maxValue + (barLength / 2)) / barLength;
- this->limitScrolling(parentLocation);
- // Avoid expensive redrawing if the index did not change
- return this->value != oldValue;
- }
- bool ScrollBarImpl::receiveMouseEvent(const IRect &parentLocation, const MouseEvent& event) {
- bool intercepted = false;
- IVector2D localPosition = event.position - parentLocation.upperLeft();
- int64_t usedCoordinate = (this->vertical ? localPosition.y : localPosition.x);
- bool onScrollBar = this->visible && (this->vertical ? (localPosition.x >= parentLocation.width() - scrollBarThickness) : (localPosition.y >= parentLocation.height() - scrollBarThickness));
- if (event.mouseEventType == MouseEventType::MouseDown) {
- if (onScrollBar) {
- intercepted = true;
- if (this->vertical) {
- if (localPosition.y < this->scrollButtonLength) {
- // Upper scroll button
- this->pressScrollUp = true;
- this->value--;
- } else if (localPosition.y > parentLocation.height() - this->scrollButtonLength) {
- // Lower scroll button
- this->pressScrollDown = true;
- this->value++;
- } else {
- // Start scrolling with the mouse using the relative height on the scroll bar.
- IRect knobLocation = this->getKnobLocation(parentLocation.width(), parentLocation.height());
- int64_t halfKnobLength = knobLocation.height() / 2;
- this->knobHoldOffset = localPosition.y - (knobLocation.top() + halfKnobLength);
- if (this->knobHoldOffset < -halfKnobLength || this->knobHoldOffset > halfKnobLength) {
- // If pressing outside of the knob, pull it directly to the pressed location before pulling from the center.
- this->knobHoldOffset = 0;
- this->pressScrollBar(parentLocation, usedCoordinate - this->knobHoldOffset);
- }
- this->holdingScrollBar = true;
- }
- } else {
- if (localPosition.x < this->scrollButtonLength) {
- // Upper scroll button
- this->pressScrollUp = true;
- this->value--;
- } else if (localPosition.x > parentLocation.width() - this->scrollButtonLength) {
- // Lower scroll button
- this->pressScrollDown = true;
- this->value++;
- } else {
- // Start scrolling with the mouse using the relative width on the scroll bar.
- IRect knobLocation = this->getKnobLocation(parentLocation.width(), parentLocation.height());
- int64_t halfKnobLength = knobLocation.width() / 2;
- this->knobHoldOffset = localPosition.x - (knobLocation.width() + halfKnobLength);
- if (this->knobHoldOffset < -halfKnobLength || this->knobHoldOffset > halfKnobLength) {
- // If pressing outside of the knob, pull it directly to the pressed location before pulling from the center.
- this->knobHoldOffset = 0;
- this->pressScrollBar(parentLocation, usedCoordinate - this->knobHoldOffset);
- }
- this->holdingScrollBar = true;
- }
- }
- }
- this->limitScrolling(parentLocation);
- } else if (event.mouseEventType == MouseEventType::MouseUp) {
- this->pressScrollUp = false;
- this->pressScrollDown = false;
- this->holdingScrollBar = false;
- intercepted = true;
- } else if (event.mouseEventType == MouseEventType::Scroll) {
- if (this->vertical) {
- if (event.key == MouseKeyEnum::ScrollUp) {
- this->value--;
- } else if (event.key == MouseKeyEnum::ScrollDown) {
- this->value++;
- }
- this->limitScrolling(parentLocation);
- }
- this->holdingScrollBar = false;
- intercepted = true;
- } else if (event.mouseEventType == MouseEventType::MouseMove) {
- if (this->holdingScrollBar) {
- intercepted = this->pressScrollBar(parentLocation, usedCoordinate - this->knobHoldOffset);
- }
- }
- return intercepted;
- }
|