#pragma once #include "WidgetBase.hpp" namespace tp { template class ScrollBarWidget : public Widget { public: ScrollBarWidget() { this->createConfig("ScrollBar"); this->addColor("Default", "Base"); this->addColor("Handle", "Accent"); this->addColor("Hovered", "Interaction"); this->addColor("Scrolling", "Action"); this->addValue("Padding", "Padding"); this->addValue("HandleSize", 20.f); this->addValue("MinSize", 20.f); this->addValue("Rounding", "Rounding"); } // takes whole area void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; this->mVisible = areaParent.isOverlap(aArea); if (!this->mVisible) return; auto area = getHandle(); mHovered = getHandleHandle().isInside(events.getPointer()); if (mSizeFraction > 1.f) { mPositionFraction = 0; return; } if (!areaParent.isOverlap(area)) { mIsScrolling = false; return; } if (events.getScrollY() != 0 && areaParent.isInside(events.getPointer())) { auto offset = events.getScrollY() < 0 ? 1.0f : -1.0f; if (scrollInertia * offset > 0) { scrollInertia += offset; } else { scrollInertia = -scrollInertia + offset; } } if (tp::abs(scrollInertia) > 0.1f) { auto offset = scrollInertia * mScrollFactor; mPositionFraction += offset; mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction); scrollInertia *= 0.f; return; } if (events.isPressed(InputID::MOUSE1) && area.isInside(events.getPointer())) { mIsScrolling = true; } else if (events.isReleased(InputID::MOUSE1)) { mIsScrolling = false; } if (mIsScrolling) { tp::halnf pos = events.getPointer().y; pos = (pos - area.y - mSizeFraction * area.w / 2.f) / area.w; mPositionFraction = tp::clamp(pos, 0.f, 1.f - mSizeFraction); } mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction); } void draw(Canvas& canvas) override { if (!this->mVisible) return; auto area = getHandle(); if (mSizeFraction > 1.f) return; // if (!areaParent.isOverlap(getHandle())) return; tp::RGBA col = this->getColor("Handle"); if (mIsScrolling) { col = this->getColor("Scrolling"); } else if (mHovered) { col = this->getColor("Hovered"); } canvas.rect(area, this->getColor("Default"), this->getValue("Rounding")); canvas.rect(getHandleHandle(), col, this->getValue("Rounding")); } RectF getHandleHandle() const { auto minSize = this->getValue("MinSize"); if (mIsScrolling) { minSize = this->getValue("MinSize") * 2; } else if (mHovered) { minSize = this->getValue("MinSize") * 2; } auto area = getHandle(); auto sliderSize = tp::clamp(area.w * mSizeFraction, minSize, area.w); auto diffSize = sliderSize - area.w * mSizeFraction; return { area.x, area.y + (area.w - diffSize) * mPositionFraction, area.z, sliderSize }; } RectF getViewport() const { if (mSizeFraction > 1.f) { return this->mArea; } return { this->mArea.x, this->mArea.y, this->mArea.z - this->getValue("HandleSize"), this->mArea.w }; } RectF getHandle() const { return { this->mArea.x + this->mArea.z - this->getValue("HandleSize") + this->getValue("Padding"), this->mArea.y + this->getValue("Padding"), this->getValue("HandleSize") - this->getValue("Padding") * 2, this->mArea.w - this->getValue("Padding") * 2 }; } public: halnf mScrollFactor = 0.f; halnf scrollInertia = 0.f; bool mIsScrolling = false; halnf mSizeFraction = 1.f; halnf mPositionFraction = 0.f; bool mHovered = false; }; template class ScrollableWindow : public Widget { public: ScrollableWindow() { this->createConfig("ScrollableWindow"); this->addColor("Default", "Base"); this->addValue("Padding", "Padding"); } ~ScrollableWindow() { for (auto content : mContents) { delete content.data(); } } // takes whole area void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { this->mArea = aArea; this->mVisible = areaParent.isOverlap(aArea); if (!this->mVisible) return; updateContents(); updateContentSize(); const auto padding = this->getValue("Padding"); mScroller.mSizeFraction = this->mArea.w / mContentSize; mScroller.proc(events, this->mArea, this->mArea); if (mScroller.mSizeFraction > 1.f) { setOffset(0); } else { setOffset((-mScroller.mPositionFraction) * mContentSize); } for (auto widget : mContents) { widget->proc( events, this->mArea, { this->mArea.x + padding, widget->mArea.y, mScroller.getViewport().z - padding * 2, widget->mArea.w } ); } } void draw(Canvas& canvas) override { if (!this->mVisible) return; mScroller.draw(canvas); canvas.pushClamp(this->mArea); for (auto widget : mContents) { widget->draw(canvas); } canvas.popClamp(); } private: void updateContents() { if (mContents.size()) { const auto padding = this->getValue("Padding"); const halnf offset = mContents.first()->mArea.y + padding; halnf start = 0; for (auto widget : mContents) { widget->mArea.y = start; start += widget->mArea.w + padding; } for (auto widget : mContents) { widget->mArea.y += offset; } } } void updateContentSize() { mContentSize = 0; if (mContents.size()) { const auto padding = this->getValue("Padding"); mContentSize = mContents.last()->mArea.y - mContents.first()->mArea.y; mContentSize += mContents.last()->mArea.w; mContentSize += 2 * padding; } } void setOffset(const halnf offset) { if (!mContents.size()) return; const auto padding = this->getValue("Padding"); auto newOffset = offset - mContents.first()->mArea.y + padding; for (auto widget : mContents) { widget->mArea.y += newOffset; } } public: halnf mContentSize = 0; Buffer*> mContents; ScrollBarWidget mScroller; }; }