#pragma once #include "WidgetBase.hpp" #include "Buffer.hpp" namespace tp { template class ScrollBarWidget : public Widget { public: ScrollBarWidget() { this->mId = "ScrollBar"; } // 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 = mHandleColor; if (mIsScrolling) { col = mScrollingColor; } else if (mHovered) { col = mHoveredColor; } canvas.rect(area, mDefaultColor, mRounding); canvas.rect(getHandleHandle(), col, mRounding); } RectF getHandleHandle() const { auto area = getHandle(); auto sliderSize = tp::clamp(area.w * mSizeFraction, mMinSize * 2, 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 - mHandleSize, this->mArea.w }; } RectF getHandle() const { return { this->mArea.x + this->mArea.z - mHandleSize + mPadding, this->mArea.y + mPadding, mHandleSize - mPadding * 2, this->mArea.w - mPadding * 2 }; } public: void setupConfig(WidgetManager& wm) { if (!wm.createWidgetConfig(this->mId)) return; wm.addReference(this->mId, "Default", "Base"); wm.addReference(this->mId, "Handle", "Accent"); wm.addReference(this->mId, "Hovered", "Interaction"); wm.addReference(this->mId, "Scrolling", "Action"); wm.addReference(this->mId, "Padding", "Padding"); wm.addNumber(this->mId, "HandleSize", 20.f); wm.addNumber(this->mId, "MinSize", 20.f); wm.addReference(this->mId, "Rounding", "Rounding"); } void updateConfigCache(const WidgetManager& wm) override { mDefaultColor = wm.getColor(this->mId, "Default"); mHandleColor = wm.getColor(this->mId, "Handle"); mHoveredColor = wm.getColor(this->mId, "Hovered"); mScrollingColor = wm.getColor(this->mId, "Scrolling"); mPadding = wm.getNumber(this->mId, "Padding"); mHandleSize = wm.getNumber(this->mId, "HandleSize"); mMinSize = wm.getNumber(this->mId, "MinSize"); mRounding = wm.getNumber(this->mId, "Rounding"); } public: halnf mScrollFactor = 0.f; halnf scrollInertia = 0.f; bool mIsScrolling = false; halnf mSizeFraction = 1.f; halnf mPositionFraction = 0.f; bool mHovered = false; RGBA mDefaultColor; RGBA mHandleColor; RGBA mHoveredColor; RGBA mScrollingColor; halnf mPadding = 0; halnf mHandleSize = 10; halnf mMinSize = 10; halnf mRounding = 10; }; template class ScrollableWindow : public Widget { public: ScrollableWindow() { this->mId = "ScrollableWindow"; } ~ScrollableWindow() = default; // 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 = mPadding; 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(); } public: void setupConfig(WidgetManager& wm) { if (!wm.createWidgetConfig(this->mId)) return; wm.addReference(this->mId, "Default", "Base"); wm.addReference(this->mId, "Padding", "Padding"); wm.addReference(this->mId, "Rounding", "Rounding"); mScroller.setupConfig(wm); } void updateConfigCache(const WidgetManager& wm) override { mDefaultColor = wm.getColor(this->mId, "Default"); mPadding = wm.getNumber(this->mId, "Padding"); mScroller.updateConfigCache(wm); for (auto item : mContents) { item->updateConfigCache(wm); } } private: void updateContents() { if (mContents.size()) { const halnf offset = mContents.first()->mArea.y + mPadding; halnf start = 0; for (auto widget : mContents) { widget->mArea.y = start; start += widget->mArea.w + mPadding; } for (auto widget : mContents) { widget->mArea.y += offset; } } } void updateContentSize() { mContentSize = 0; if (mContents.size()) { mContentSize = mContents.last()->mArea.y - mContents.first()->mArea.y; mContentSize += mContents.last()->mArea.w; mContentSize += 2 * mPadding; } } void setOffset(const halnf offset) { if (!mContents.size()) return; auto newOffset = offset - mContents.first()->mArea.y + mPadding; for (auto widget : mContents) { widget->mArea.y += newOffset; } } public: halnf mContentSize = 0; Buffer*> mContents; ScrollBarWidget mScroller; RGBA mDefaultColor; halnf mPadding = 0; }; }