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.
This commit is contained in:
SmallJoker 2022-07-09 22:15:34 +02:00 committed by sfan5
parent 05a00a8d91
commit afbe41019c
2 changed files with 93 additions and 43 deletions

@ -454,7 +454,7 @@ void CGUITabControl::scrollRight()
recalculateScrollBar(); 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 ) if ( !font )
return 0; return 0;
@ -463,26 +463,11 @@ s32 CGUITabControl::calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, b
if ( TabMaxWidth > 0 && len > TabMaxWidth ) if ( TabMaxWidth > 0 && len > TabMaxWidth )
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; 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 ) if ( startIndex < 0 )
startIndex = 0; startIndex = 0;
@ -492,17 +477,18 @@ bool CGUITabControl::needScrollControl(s32 startIndex, bool withScrollControl)
IGUIFont* font = skin->getFont(); IGUIFont* font = skin->getFont();
core::rect<s32> frameRect(AbsoluteRect);
if (Tabs.empty()) if (Tabs.empty())
return false; return false;
if (!font) if (!font)
return false; 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 // get Text
const wchar_t* text = 0; const wchar_t* text = 0;
@ -511,26 +497,71 @@ bool CGUITabControl::needScrollControl(s32 startIndex, bool withScrollControl)
text = Tabs[i]->getText(); text = Tabs[i]->getText();
// get text length // get text length
s32 len = calcTabWidth(pos, font, text, false); // always without withScrollControl here or len would be shortened s32 len = calcTabWidth(font, text); // always without withScrollControl here or len would be shortened
frameRect.LowerRightCorner.X += len;
frameRect.UpperLeftCorner.X = pos;
frameRect.LowerRightCorner.X = frameRect.UpperLeftCorner.X + len;
pos += len; pos += len;
} }
if ( withScrollControl && pos > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2) if (pos > pos_right)
return true;
if ( !withScrollControl && pos > AbsoluteRect.LowerRightCorner.X )
return true; return true;
} }
if (pos_rightmost)
*pos_rightmost = pos;
return false; 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<s32> CGUITabControl::calcTabPos() core::rect<s32> CGUITabControl::calcTabPos()
{ {
core::rect<s32> r; core::rect<s32> r;
@ -613,7 +644,7 @@ void CGUITabControl::draw()
IGUITab *activeTab = 0; IGUITab *activeTab = 0;
// Draw all tab-buttons except the active one // Draw all tab-buttons except the active one
for (u32 i=CurrentScrollTabIndex; i<Tabs.size(); ++i) for (u32 i = CurrentScrollTabIndex; i < Tabs.size() && !needRightScroll; ++i)
{ {
// get Text // get Text
const wchar_t* text = 0; const wchar_t* text = 0;
@ -621,11 +652,13 @@ void CGUITabControl::draw()
text = Tabs[i]->getText(); text = Tabs[i]->getText();
// get text length // get text length
s32 len = calcTabWidth(pos, font, text, true); s32 len = calcTabWidth(font, text);
if ( ScrollControl && pos+len > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) if (ScrollControl) {
{ s32 space = UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 - pos;
if (space < len) {
needRightScroll = true; needRightScroll = true;
break; len = space;
}
} }
frameRect.LowerRightCorner.X += len; frameRect.LowerRightCorner.X += len;
@ -794,6 +827,7 @@ s32 CGUITabControl::getTabExtraWidth() const
void CGUITabControl::recalculateScrollBar() void CGUITabControl::recalculateScrollBar()
{ {
// Down: to right, Up: to left
if (!UpButton || !DownButton) if (!UpButton || !DownButton)
return; return;
@ -894,7 +928,8 @@ s32 CGUITabControl::getTabAt(s32 xpos, s32 ypos) const
if (!frameRect.isPointInside(p)) if (!frameRect.isPointInside(p))
return -1; 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 // get Text
const wchar_t* text = 0; const wchar_t* text = 0;
@ -902,9 +937,15 @@ s32 CGUITabControl::getTabAt(s32 xpos, s32 ypos) const
text = Tabs[i]->getText(); text = Tabs[i]->getText();
// get text length // get text length
s32 len = calcTabWidth(pos, font, text, true); s32 len = calcTabWidth(font, text);
if ( ScrollControl && pos+len > UpButton->getAbsolutePosition().UpperLeftCorner.X - 2 ) if (ScrollControl) {
return -1; // 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.UpperLeftCorner.X = pos;
frameRect.LowerRightCorner.X = frameRect.UpperLeftCorner.X + len; frameRect.LowerRightCorner.X = frameRect.UpperLeftCorner.X + len;
@ -915,6 +956,7 @@ s32 CGUITabControl::getTabAt(s32 xpos, s32 ypos) const
{ {
return i; return i;
} }
} }
return -1; return -1;
} }
@ -948,6 +990,11 @@ bool CGUITabControl::setActiveTab(s32 idx)
Parent->OnEvent(event); Parent->OnEvent(event);
} }
if (ScrollControl) {
CurrentScrollTabIndex = calculateScrollIndexFromActive();
recalculateScrollBar();
}
return true; return true;
} }

@ -153,8 +153,11 @@ namespace gui
void scrollLeft(); void scrollLeft();
void scrollRight(); void scrollRight();
bool needScrollControl( s32 startIndex=0, bool withScrollControl=false ); //! Indicates whether the tabs overflow in X direction
s32 calcTabWidth(s32 pos, IGUIFont* font, const wchar_t* text, bool withScrollControl ) const; 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<s32> calcTabPos(); core::rect<s32> calcTabPos();
void setVisibleTab(s32 idx); void setVisibleTab(s32 idx);
void removeTabButNotChild(s32 idx); void removeTabButNotChild(s32 idx);