From afbe41019cbc8fce5b5ea60b022c449a2f16a387 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 9 Jul 2022 22:15:34 +0200 Subject: [PATCH] CGUITabControl: Center selected tab whenever possible This greatly improves the navigation speed by clicking through the tabs without losing track of the current scroll position. --- source/Irrlicht/CGUITabControl.cpp | 129 ++++++++++++++++++++--------- source/Irrlicht/CGUITabControl.h | 7 +- 2 files changed, 93 insertions(+), 43 deletions(-) diff --git a/source/Irrlicht/CGUITabControl.cpp b/source/Irrlicht/CGUITabControl.cpp index 04f36f9a..30a31c39 100644 --- a/source/Irrlicht/CGUITabControl.cpp +++ b/source/Irrlicht/CGUITabControl.cpp @@ -454,7 +454,7 @@ void CGUITabControl::scrollRight() recalculateScrollBar(); } -s32 CGUITabControl::calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, bool withScrollControl) const +s32 CGUITabControl::calcTabWidth(IGUIFont* font, const wchar_t* text) const { if ( !font ) return 0; @@ -463,26 +463,11 @@ s32 CGUITabControl::calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, b if ( TabMaxWidth > 0 && len > TabMaxWidth ) len = TabMaxWidth; - // check if we miss the place to draw the tab-button - if ( withScrollControl && ScrollControl && pos+len > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) - { - s32 tabMinWidth = font->getDimension(L"A").Width; - if ( TabExtraWidth > 0 && TabExtraWidth > tabMinWidth ) - tabMinWidth = TabExtraWidth; - - if ( ScrollControl && pos+tabMinWidth <= UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) - { - len = UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 - pos; - } - } return len; } -bool CGUITabControl::needScrollControl(s32 startIndex, bool withScrollControl) +bool CGUITabControl::needScrollControl(s32 startIndex, bool withScrollControl, s32 *pos_rightmost) { - if ( startIndex >= (s32)Tabs.size() ) - startIndex -= 1; - if ( startIndex < 0 ) startIndex = 0; @@ -492,17 +477,18 @@ bool CGUITabControl::needScrollControl(s32 startIndex, bool withScrollControl) IGUIFont* font = skin->getFont(); - core::rect frameRect(AbsoluteRect); - if (Tabs.empty()) return false; if (!font) return false; - s32 pos = frameRect.UpperLeftCorner.X + 2; + s32 pos = AbsoluteRect.UpperLeftCorner.X + 2; + const s32 pos_right = withScrollControl ? + UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 : + AbsoluteRect.LowerRightCorner.X; - for (s32 i=startIndex; i<(s32)Tabs.size(); ++i) + for (s32 i = startIndex; i < (s32)Tabs.size(); ++i) { // get Text const wchar_t* text = 0; @@ -511,26 +497,71 @@ bool CGUITabControl::needScrollControl(s32 startIndex, bool withScrollControl) text = Tabs[i]->getText(); // get text length - s32 len = calcTabWidth(pos, font, text, false); // always without withScrollControl here or len would be shortened - - frameRect.LowerRightCorner.X += len; - - frameRect.UpperLeftCorner.X = pos; - frameRect.LowerRightCorner.X = frameRect.UpperLeftCorner.X + len; + s32 len = calcTabWidth(font, text); // always without withScrollControl here or len would be shortened pos += len; } - if ( withScrollControl && pos > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2) - return true; - - if ( !withScrollControl && pos > AbsoluteRect.LowerRightCorner.X ) + if (pos > pos_right) return true; } + if (pos_rightmost) + *pos_rightmost = pos; return false; } +s32 CGUITabControl::calculateScrollIndexFromActive() +{ + if (!ScrollControl || Tabs.empty()) + return 0; + + IGUISkin *skin = Environment->getSkin(); + if (!skin) + return false; + + IGUIFont *font = skin->getFont(); + if (!font) + return false; + + const s32 pos_left = AbsoluteRect.UpperLeftCorner.X + 2; + const s32 pos_right = UpButton->getAbsolutePosition().UpperLeftCorner.X - 2; + + // Move from center to the left border left until it is reached + s32 pos_cl = (pos_left + pos_right) / 2; + s32 i = ActiveTabIndex; + for (; i > 0; --i) { + if (!Tabs[i]) + continue; + + s32 len = calcTabWidth(font, Tabs[i]->getText()); + if (i == ActiveTabIndex) + len /= 2; + if (pos_cl - len < pos_left) + break; + + pos_cl -= len; + } + if (i == 0) + return i; + + // Is scrolling to right still possible? + s32 pos_rr = 0; + if (needScrollControl(i, true, &pos_rr)) + return i; // Yes? -> OK + + // No? -> Decrease "i" more. Append tabs until scrolling becomes necessary + for (--i; i > 0; --i) { + if (!Tabs[i]) + continue; + + pos_rr += calcTabWidth(font, Tabs[i]->getText()); + if (pos_rr > pos_right) + break; + } + return i + 1; +} + core::rect CGUITabControl::calcTabPos() { core::rect r; @@ -613,7 +644,7 @@ void CGUITabControl::draw() IGUITab *activeTab = 0; // Draw all tab-buttons except the active one - for (u32 i=CurrentScrollTabIndex; igetText(); // get text length - s32 len = calcTabWidth(pos, font, text, true); - if ( ScrollControl && pos+len > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) - { - needRightScroll = true; - break; + s32 len = calcTabWidth(font, text); + if (ScrollControl) { + s32 space = UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 - pos; + if (space < len) { + needRightScroll = true; + len = space; + } } frameRect.LowerRightCorner.X += len; @@ -794,6 +827,7 @@ s32 CGUITabControl::getTabExtraWidth() const void CGUITabControl::recalculateScrollBar() { + // Down: to right, Up: to left if (!UpButton || !DownButton) return; @@ -894,7 +928,8 @@ s32 CGUITabControl::getTabAt(s32 xpos, s32 ypos) const if (!frameRect.isPointInside(p)) return -1; - for (s32 i=CurrentScrollTabIndex; i<(s32)Tabs.size(); ++i) + bool abort = false; + for (s32 i = CurrentScrollTabIndex; i < (s32)Tabs.size() && !abort; ++i) { // get Text const wchar_t* text = 0; @@ -902,9 +937,15 @@ s32 CGUITabControl::getTabAt(s32 xpos, s32 ypos) const text = Tabs[i]->getText(); // get text length - s32 len = calcTabWidth(pos, font, text, true); - if ( ScrollControl && pos+len > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) - return -1; + s32 len = calcTabWidth(font, text); + if (ScrollControl) { + // TODO: merge this with draw() ? + s32 space = UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 - pos; + if (space < len) { + abort = true; + len = space; + } + } frameRect.UpperLeftCorner.X = pos; frameRect.LowerRightCorner.X = frameRect.UpperLeftCorner.X + len; @@ -915,6 +956,7 @@ s32 CGUITabControl::getTabAt(s32 xpos, s32 ypos) const { return i; } + } return -1; } @@ -948,6 +990,11 @@ bool CGUITabControl::setActiveTab(s32 idx) Parent->OnEvent(event); } + if (ScrollControl) { + CurrentScrollTabIndex = calculateScrollIndexFromActive(); + recalculateScrollBar(); + } + return true; } diff --git a/source/Irrlicht/CGUITabControl.h b/source/Irrlicht/CGUITabControl.h index ffc31f21..d5e8315f 100644 --- a/source/Irrlicht/CGUITabControl.h +++ b/source/Irrlicht/CGUITabControl.h @@ -153,8 +153,11 @@ namespace gui void scrollLeft(); void scrollRight(); - bool needScrollControl( s32 startIndex=0, bool withScrollControl=false ); - s32 calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, bool withScrollControl ) const; + //! Indicates whether the tabs overflow in X direction + bool needScrollControl( s32 startIndex=0, bool withScrollControl=false, s32 *pos_rightmost=nullptr ); + //! Left index calculation based on the selected tab + s32 calculateScrollIndexFromActive(); + s32 calcTabWidth(IGUIFont* font, const wchar_t* text) const; core::rect calcTabPos(); void setVisibleTab(s32 idx); void removeTabButNotChild(s32 idx);