v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
445
source/windowing/StarScrollArea.cpp
Normal file
445
source/windowing/StarScrollArea.cpp
Normal file
|
@ -0,0 +1,445 @@
|
|||
#include "StarScrollArea.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// TODO: I hate these hardcoded values. Please smite with fire.
|
||||
static int const ScrollAreaBorder = 9;
|
||||
static int const ScrollButtonStackSize = 6;
|
||||
static int const ScrollThumbSize = 3;
|
||||
static int const ScrollThumbOverhead = ScrollThumbSize + ScrollThumbSize;
|
||||
static int const ScrollBarTrackOverhead = ScrollButtonStackSize + ScrollButtonStackSize + ScrollThumbOverhead;
|
||||
static int64_t const ScrollAdvanceTimer = 100;
|
||||
|
||||
ScrollThumb::ScrollThumb(GuiDirection direction) {
|
||||
m_hovered = false;
|
||||
m_pressed = false;
|
||||
m_direction = direction;
|
||||
auto assets = Root::singleton().assets();
|
||||
setImages(assets->json("/interface.config:scrollArea.thumbs"));
|
||||
}
|
||||
|
||||
void ScrollThumb::setImages(ImageStretchSet const& base, ImageStretchSet const& hover, ImageStretchSet const& pressed) {
|
||||
m_baseThumb = base;
|
||||
m_hoverThumb = hover;
|
||||
m_pressedThumb = pressed;
|
||||
}
|
||||
|
||||
void ScrollThumb::setImages(Json const& images) {
|
||||
String directionString;
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
directionString = "vertical";
|
||||
} else {
|
||||
directionString = "horizontal";
|
||||
}
|
||||
|
||||
m_baseThumb.begin = images.get(directionString).get("base").getString("begin");
|
||||
m_baseThumb.end = images.get(directionString).get("base").getString("end");
|
||||
m_baseThumb.inner = images.get(directionString).get("base").getString("inner");
|
||||
|
||||
m_hoverThumb.begin = images.get(directionString).get("hover").getString("begin");
|
||||
m_hoverThumb.end = images.get(directionString).get("hover").getString("end");
|
||||
m_hoverThumb.inner = images.get(directionString).get("hover").getString("inner");
|
||||
|
||||
m_pressedThumb.begin = images.get(directionString).get("pressed").getString("begin");
|
||||
m_pressedThumb.end = images.get(directionString).get("pressed").getString("end");
|
||||
m_pressedThumb.inner = images.get(directionString).get("pressed").getString("inner");
|
||||
}
|
||||
|
||||
bool ScrollThumb::isHovered() const {
|
||||
return m_hovered;
|
||||
}
|
||||
|
||||
bool ScrollThumb::isPressed() const {
|
||||
return m_pressed;
|
||||
}
|
||||
|
||||
void ScrollThumb::setHovered(bool hovered) {
|
||||
m_hovered = hovered;
|
||||
}
|
||||
|
||||
void ScrollThumb::setPressed(bool pressed) {
|
||||
m_pressed = pressed;
|
||||
}
|
||||
|
||||
void ScrollThumb::mouseOver() {
|
||||
setHovered(true);
|
||||
}
|
||||
|
||||
void ScrollThumb::mouseOut() {
|
||||
setHovered(false);
|
||||
}
|
||||
|
||||
void ScrollThumb::renderImpl() {
|
||||
ImageStretchSet workingSet = m_baseThumb;
|
||||
if (isHovered())
|
||||
workingSet = m_hoverThumb;
|
||||
if (isPressed())
|
||||
workingSet = m_pressedThumb;
|
||||
|
||||
if (workingSet.fullyPopulated()) {
|
||||
context()->drawImageStretchSet(workingSet,
|
||||
RectF::withSize(Vec2F(m_parent->screenPosition() + position()), Vec2F(size())),
|
||||
m_direction);
|
||||
}
|
||||
}
|
||||
|
||||
Vec2U ScrollThumb::baseSize() const {
|
||||
return context()->textureSize(m_baseThumb.begin);
|
||||
}
|
||||
|
||||
ScrollBar::ScrollBar(GuiDirection direction, WidgetCallbackFunc forwardFunc, WidgetCallbackFunc backwardFunc)
|
||||
: m_direction(direction) {
|
||||
m_forward = make_shared<ButtonWidget>();
|
||||
m_forward->setCallback(forwardFunc);
|
||||
m_forward->setSustainCallbackOnDownHold(true);
|
||||
m_forward->setPressedOffset({0, 0});
|
||||
|
||||
m_backward = make_shared<ButtonWidget>();
|
||||
m_backward->setCallback(backwardFunc);
|
||||
m_backward->setSustainCallbackOnDownHold(true);
|
||||
m_backward->setPressedOffset({0, 0});
|
||||
|
||||
m_thumb = make_shared<ScrollThumb>(m_direction);
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
setButtonImages(assets->json("/interface.config:scrollArea.buttons"));
|
||||
|
||||
addChild("thumb", m_thumb);
|
||||
addChild("forward", m_forward);
|
||||
addChild("backward", m_backward);
|
||||
}
|
||||
|
||||
void ScrollBar::setButtonImages(Json const& images) {
|
||||
String directionString;
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
directionString = "vertical";
|
||||
} else {
|
||||
directionString = "horizontal";
|
||||
}
|
||||
|
||||
m_forward->setImages(images.get(directionString).get("forward").getString("base"),
|
||||
images.get(directionString).get("forward").getString("hover"),
|
||||
images.get(directionString).get("forward").getString("pressed"));
|
||||
|
||||
m_backward->setImages(images.get(directionString).get("backward").getString("base"),
|
||||
images.get(directionString).get("backward").getString("hover"),
|
||||
images.get(directionString).get("backward").getString("pressed"));
|
||||
}
|
||||
|
||||
Vec2I ScrollBar::size() const {
|
||||
if (m_parent)
|
||||
return m_parent->size();
|
||||
return {};
|
||||
}
|
||||
|
||||
int ScrollBar::trackSize() const {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
auto size = scrollArea->size();
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
if (scrollArea->horizontalScroll()) {
|
||||
return size[1] - (ScrollBarTrackOverhead + ScrollAreaBorder);
|
||||
} else {
|
||||
return size[1] - ScrollBarTrackOverhead;
|
||||
}
|
||||
} else {
|
||||
return size[0] - ScrollBarTrackOverhead;
|
||||
}
|
||||
}
|
||||
|
||||
throw GuiException("Somehow have a Scroll Bar without a parent.");
|
||||
}
|
||||
|
||||
float ScrollBar::sizeRatio() const {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
return scrollArea->contentSize()[1] / (float)(scrollArea->areaSize()[1]);
|
||||
} else {
|
||||
return scrollArea->contentSize()[0] / (float)(scrollArea->areaSize()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
throw GuiException("Somehow have a Scroll Bar without a parent.");
|
||||
}
|
||||
|
||||
float ScrollBar::scrollRatio() const {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
if (scrollArea->maxScrollPosition()[1] == 0)
|
||||
return 0;
|
||||
return scrollArea->scrollOffset()[1] / (float)(scrollArea->maxScrollPosition()[1]);
|
||||
} else {
|
||||
if (scrollArea->maxScrollPosition()[0] == 0)
|
||||
return 0;
|
||||
return scrollArea->scrollOffset()[0] / (float)(scrollArea->maxScrollPosition()[0]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ButtonWidgetPtr ScrollBar::forwardButton() const {
|
||||
return m_forward;
|
||||
}
|
||||
|
||||
ButtonWidgetPtr ScrollBar::backwardButton() const {
|
||||
return m_backward;
|
||||
}
|
||||
|
||||
ScrollThumbPtr ScrollBar::thumb() const {
|
||||
return m_thumb;
|
||||
}
|
||||
|
||||
void ScrollBar::drawChildren() {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
|
||||
float ratio = sizeRatio();
|
||||
if (ratio < 1)
|
||||
ratio = 1;
|
||||
|
||||
int innerSize = (int)max(0.0f, ceil(trackSize() / ratio));
|
||||
int offsetBegin = (int)ceil((trackSize() - innerSize) * scrollRatio());
|
||||
innerSize += ScrollThumbOverhead;
|
||||
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
if (scrollArea->horizontalScroll()) {
|
||||
m_forward->setPosition(m_parent->size() - Vec2I(ScrollAreaBorder, ScrollButtonStackSize));
|
||||
m_backward->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollAreaBorder});
|
||||
m_thumb->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollAreaBorder + ScrollButtonStackSize + offsetBegin});
|
||||
} else {
|
||||
m_forward->setPosition(m_parent->size() - Vec2I(ScrollAreaBorder, ScrollButtonStackSize));
|
||||
m_backward->setPosition({m_parent->size()[0] - ScrollAreaBorder, 0});
|
||||
m_thumb->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollButtonStackSize + offsetBegin});
|
||||
}
|
||||
m_thumb->setSize(Vec2I(m_thumb->baseSize()[0], innerSize));
|
||||
} else {
|
||||
m_forward->setPosition({m_parent->size()[0] - ScrollButtonStackSize, 0});
|
||||
m_backward->setPosition({0, 0});
|
||||
m_thumb->setPosition({ScrollButtonStackSize + offsetBegin, 0});
|
||||
m_thumb->setSize(Vec2I(innerSize, m_thumb->baseSize()[1]));
|
||||
}
|
||||
|
||||
for (auto child : m_members) {
|
||||
child->render(m_drawingArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec2I ScrollBar::offsetFromThumbPosition(Vec2I const& thumbPosition) const {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
int scrollSpan = trackSize() - m_thumb->size()[1];
|
||||
int scrollOffset = clamp((thumbPosition[1] - ScrollButtonStackSize), 0, scrollSpan);
|
||||
float scrollRatio = (float)scrollOffset / (float)scrollSpan;
|
||||
return {scrollArea->scrollOffset()[0], ceil(scrollArea->maxScrollPosition()[1] * scrollRatio)};
|
||||
} else {
|
||||
int scrollSpan = trackSize() - m_thumb->size()[0];
|
||||
int scrollOffset = clamp((thumbPosition[0] - ScrollButtonStackSize), 0, scrollSpan);
|
||||
float scrollRatio = (float)scrollOffset / (float)scrollSpan;
|
||||
return {(int)ceil(scrollArea->maxScrollPosition()[0] * scrollRatio), scrollArea->scrollOffset()[1]};
|
||||
}
|
||||
}
|
||||
|
||||
ScrollArea::ScrollArea() {
|
||||
auto assets = Root::singleton().assets();
|
||||
m_buttonAdvance = assets->json("/interface.config:scrollArea.buttonAdvance").toInt();
|
||||
m_advanceLimiter = Time::monotonicMilliseconds();
|
||||
|
||||
WidgetCallbackFunc vAdvance = [this](Widget*) { scrollAreaBy({0, advanceFactorHelper()}); };
|
||||
WidgetCallbackFunc vRetreat = [this](Widget*) { scrollAreaBy({0, -advanceFactorHelper()}); };
|
||||
WidgetCallbackFunc hAdvance = [this](Widget*) { scrollAreaBy({advanceFactorHelper(), 0}); };
|
||||
WidgetCallbackFunc hRetreat = [this](Widget*) { scrollAreaBy({-advanceFactorHelper(), 0}); };
|
||||
|
||||
m_vBar = make_shared<ScrollBar>(GuiDirection::Vertical, vAdvance, vRetreat);
|
||||
m_hBar = make_shared<ScrollBar>(GuiDirection::Horizontal, hAdvance, hRetreat);
|
||||
|
||||
m_dragActive = false;
|
||||
|
||||
addChild("vScrollBar", m_vBar);
|
||||
addChild("hScrollBar", m_hBar);
|
||||
|
||||
m_horizontalScroll = false;
|
||||
m_verticalScroll = true;
|
||||
}
|
||||
|
||||
void ScrollArea::setButtonImages(Json const& images) {
|
||||
m_vBar->setButtonImages(images);
|
||||
m_hBar->setButtonImages(images);
|
||||
}
|
||||
|
||||
void ScrollArea::setThumbImages(Json const& images) {
|
||||
m_vBar->thumb()->setImages(images);
|
||||
m_hBar->thumb()->setImages(images);
|
||||
}
|
||||
|
||||
RectI ScrollArea::contentBoundRect() const {
|
||||
RectI res = RectI::null();
|
||||
for (auto child : m_members) {
|
||||
if (child == m_vBar || child == m_hBar) // scroll bars don't count
|
||||
continue;
|
||||
if (!child->active()) // neither do hidden members
|
||||
continue;
|
||||
res.combine(child->relativeBoundRect());
|
||||
}
|
||||
res.setMax({res.max()[0], res.max()[1] + 1});
|
||||
return res;
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::contentSize() const {
|
||||
return contentBoundRect().size();
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::areaSize() const {
|
||||
Vec2I s = size();
|
||||
if (horizontalScroll())
|
||||
s[1] -= ScrollAreaBorder;
|
||||
if (verticalScroll())
|
||||
s[0] -= ScrollAreaBorder;
|
||||
return s;
|
||||
}
|
||||
|
||||
void ScrollArea::scrollAreaBy(Vec2I const& offset) {
|
||||
m_scrollOffset += offset;
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::scrollOffset() const {
|
||||
return m_scrollOffset;
|
||||
}
|
||||
|
||||
bool ScrollArea::horizontalScroll() const {
|
||||
return m_horizontalScroll;
|
||||
}
|
||||
|
||||
void ScrollArea::setHorizontalScroll(bool horizontal) {
|
||||
m_horizontalScroll = horizontal;
|
||||
}
|
||||
|
||||
bool ScrollArea::verticalScroll() const {
|
||||
return m_verticalScroll;
|
||||
}
|
||||
|
||||
void ScrollArea::setVerticalScroll(bool vertical) {
|
||||
m_verticalScroll = vertical;
|
||||
}
|
||||
|
||||
bool ScrollArea::sendEvent(InputEvent const& event) {
|
||||
if (!m_visible)
|
||||
return false;
|
||||
|
||||
if (m_dragActive) {
|
||||
if (event.is<MouseButtonUpEvent>()) {
|
||||
blur();
|
||||
m_dragActive = false;
|
||||
m_vBar->thumb()->setPressed(false);
|
||||
m_hBar->thumb()->setPressed(false);
|
||||
return true;
|
||||
} else if (event.is<MouseMoveEvent>()) {
|
||||
auto thumbDragPosition = *context()->mousePosition(event) - screenPosition() - m_dragOffset;
|
||||
if (m_dragDirection == GuiDirection::Vertical) {
|
||||
m_scrollOffset = m_vBar->offsetFromThumbPosition(thumbDragPosition);
|
||||
} else {
|
||||
m_scrollOffset = m_hBar->offsetFromThumbPosition(thumbDragPosition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto mousePos = context()->mousePosition(event);
|
||||
if (mousePos && !inMember(*mousePos))
|
||||
return false;
|
||||
|
||||
if (event.is<MouseButtonDownEvent>()) {
|
||||
if (m_vBar->thumb()->inMember(*mousePos)) {
|
||||
focus();
|
||||
m_dragOffset = *mousePos - screenPosition() - m_vBar->thumb()->position();
|
||||
m_dragDirection = GuiDirection::Vertical;
|
||||
m_dragActive = true;
|
||||
m_vBar->thumb()->setPressed(true);
|
||||
return true;
|
||||
} else if (m_hBar->thumb()->inMember(*mousePos)) {
|
||||
focus();
|
||||
m_dragOffset = *mousePos - screenPosition() - m_hBar->thumb()->position();
|
||||
m_dragDirection = GuiDirection::Horizontal;
|
||||
m_dragActive = true;
|
||||
m_hBar->thumb()->setPressed(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Widget::sendEvent(event))
|
||||
return true;
|
||||
|
||||
if (auto mouseWheel = event.ptr<MouseWheelEvent>()) {
|
||||
if (mouseWheel->mouseWheel == MouseWheel::Up)
|
||||
scrollAreaBy({0, m_buttonAdvance * 3});
|
||||
else
|
||||
scrollAreaBy({0, -m_buttonAdvance * 3});
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScrollArea::update() {
|
||||
if (!m_visible)
|
||||
return;
|
||||
|
||||
auto maxScroll = maxScrollPosition();
|
||||
|
||||
// keep vertical scroll bars same distance from the *top* on resize
|
||||
if (m_verticalScroll && maxScroll != m_lastMaxScroll)
|
||||
m_scrollOffset += (maxScroll - m_lastMaxScroll);
|
||||
|
||||
m_scrollOffset = m_scrollOffset.piecewiseClamp(Vec2I(), maxScroll);
|
||||
m_lastMaxScroll = maxScroll;
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::maxScrollPosition() const {
|
||||
return Vec2I().piecewiseMax(contentSize() - size());
|
||||
}
|
||||
|
||||
void ScrollArea::drawChildren() {
|
||||
RectI innerDrawingArea = m_drawingArea;
|
||||
|
||||
if (horizontalScroll())
|
||||
innerDrawingArea.setYMin(ScrollAreaBorder);
|
||||
if (verticalScroll())
|
||||
innerDrawingArea.setXMax(innerDrawingArea.xMax() - ScrollAreaBorder);
|
||||
|
||||
auto contentBoundRect = ScrollArea::contentBoundRect();
|
||||
auto contentOffset = contentBoundRect.min();
|
||||
auto contentSize = contentBoundRect.size();
|
||||
|
||||
auto offset = contentOffset + scrollOffset();
|
||||
|
||||
auto areaSize = ScrollArea::areaSize();
|
||||
|
||||
if (contentSize[1] < areaSize[1])
|
||||
offset[1] = offset[1] - (areaSize[1] - contentSize[1]);
|
||||
|
||||
for (auto child : m_members) {
|
||||
if (child == m_vBar || child == m_hBar)
|
||||
continue;
|
||||
child->setDrawingOffset(-offset);
|
||||
child->render(innerDrawingArea);
|
||||
}
|
||||
|
||||
if (m_horizontalScroll)
|
||||
m_hBar->render(m_drawingArea);
|
||||
if (m_verticalScroll)
|
||||
m_vBar->render(m_drawingArea);
|
||||
}
|
||||
|
||||
int ScrollArea::advanceFactorHelper() {
|
||||
auto t = Time::monotonicMilliseconds() - m_advanceLimiter;
|
||||
m_advanceLimiter = Time::monotonicMilliseconds();
|
||||
if ((t > ScrollAdvanceTimer) || (t < 0))
|
||||
t = ScrollAdvanceTimer;
|
||||
return (int)std::ceil((m_buttonAdvance * t) / (float)ScrollAdvanceTimer);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue