Fix several issues with the "overflow" button (#4708, #4720)

* Re-use the ToolStrip widget/logic to show the list of hidden
  tools. This fixes some issues with the size/alignment of the group
  and keep consistent with the current look & feel.
* Simplify ToolStrip logic using a vector of tools inside instead of
  iterating the whole toolbox/filtering the group.
* Fixed the "overflow" button style to be highlighted when we hover
  the mouse on it.
* Don't create one "Show more..." tooltip window for each mouse move
  above the button.
* Show tooltips for tools inside the overflow popup window.
* Clicking the button again closes the group.
This commit is contained in:
David Capello 2024-11-11 12:26:06 -03:00
parent 9b90159d1b
commit a600c00127
2 changed files with 115 additions and 174 deletions

View File

@ -31,7 +31,9 @@
#include "os/surface.h"
#include "ui/ui.h"
#include <algorithm>
#include <string>
#include <vector>
namespace app {
@ -44,10 +46,13 @@ using namespace tools;
// This widget is inside the ToolBar::m_popupWindow
class ToolBar::ToolStrip : public Widget {
public:
ToolStrip(ToolGroup* group, ToolBar* toolbar);
using Tools = std::vector<Tool*>;
ToolStrip(ToolBar* toolbar, const Tools& tools, ToolGroup* group);
~ToolStrip();
ToolGroup* toolGroup() { return m_group; }
const Tools& tools() const { return m_tools; }
obs::signal<void(Tool*)> ToolSelected;
@ -59,12 +64,13 @@ protected:
private:
Rect getToolBounds(int index);
ToolBar* m_toolbar;
Tools m_tools;
ToolGroup* m_group;
Tool* m_hotTool;
ToolBar* m_toolbar;
};
static Size getToolIconSize(Widget* widget)
static Size getToolIconSize(const Widget* widget)
{
auto theme = SkinTheme::get(widget);
os::Surface* icon = theme->getToolIcon("configuration");
@ -122,6 +128,7 @@ bool ToolBar::isToolVisible(Tool* tool)
bool ToolBar::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kPaintMessage: {
auto toolbox = App::instance()->toolBox();
auto lastToolBounds = getToolGroupBounds(toolbox->getGroupsCount());
@ -131,7 +138,9 @@ bool ToolBar::onProcessMessage(Message* msg)
m_minHeight = minHeight;
invalidate();
}
} break;
break;
}
case kMouseDownMessage: {
auto mouseMsg = static_cast<const MouseMessage*>(msg);
const Point mousePos = mouseMsg->positionForDisplay(display());
@ -149,7 +158,7 @@ bool ToolBar::onProcessMessage(Message* msg)
if (mousePos.y >= toolrc.y && mousePos.y < toolrc.y + toolrc.h) {
selectTool(tool);
openPopupWindow(c, tool_group);
openPopupWindow(GroupType::Regular, c, tool_group);
// We capture the mouse so the user can continue navigating
// the ToolBar to open other groups while he is pressing the
@ -161,13 +170,14 @@ bool ToolBar::onProcessMessage(Message* msg)
if (hidden > 0) {
toolrc = getToolGroupBounds(groups);
if (mousePos.y >= toolrc.y && mousePos.y < toolrc.y + toolrc.h) {
if (m_popupWindow &&
m_popupWindow->firstChild()->type() == WidgetType::kBoxWidget) {
// Show (or close if it's already visible) the
// hidden/overflow group of tools.
if (m_popupWindow) {
closePopupWindow();
closeTipWindow();
}
else {
showHiddenToolPopup();
openPopupWindow(GroupType::Overflow, groups);
}
}
}
@ -215,7 +225,7 @@ bool ToolBar::onProcessMessage(Message* msg)
new_hot_index = c;
if ((m_openOnHot) && (m_hotTool != new_hot_tool) && hasCapture()) {
openPopupWindow(c, tool_group);
openPopupWindow(GroupType::Regular, c, tool_group);
}
break;
}
@ -224,7 +234,8 @@ bool ToolBar::onProcessMessage(Message* msg)
if (hidden > 0) {
toolrc = getToolGroupBounds(groups);
if (mousePos.y >= toolrc.y && mousePos.y < toolrc.y + toolrc.h) {
openTipWindow(groups, nullptr);
// Mouse over the overflow button
new_hot_index = groups;
}
}
@ -375,17 +386,12 @@ void ToolBar::onPaint(ui::PaintEvent& ev)
}
if (hiddenGroups > 0) {
int lastGroup = visibleGroupCount;
Rect toolrc = getToolGroupBounds(lastGroup);
toolrc.offset(-origin());
nw = (m_hotIndex >= visibleGroupCount) ? theme->parts.toolbuttonHot() :
theme->parts.toolbuttonLast();
theme->drawRect(g, toolrc, nw.get());
icon = theme->parts.listView()->bitmap(0);
drawToolIcon(g, lastGroup, nw, icon);
drawToolIcon(g, visibleGroupCount, nw, icon);
}
// Draw button to show/hide preview
@ -429,7 +435,9 @@ int ToolBar::getToolGroupIndex(ToolGroup* group)
return -1;
}
void ToolBar::openPopupWindow(int group_index, ToolGroup* tool_group)
void ToolBar::openPopupWindow(GroupType group_type,
int group_index,
tools::ToolGroup* tool_group)
{
if (m_popupWindow) {
// If we've already open the given group, do nothing.
@ -446,33 +454,53 @@ void ToolBar::openPopupWindow(int group_index, ToolGroup* tool_group)
// Close tip window
closeTipWindow();
// If this group contains only one tool, do not show the popup
// Here we build the list of tools to show in the ToolStrip widget
ToolBox* toolbox = App::instance()->toolBox();
int count = 0;
for (ToolIterator it = toolbox->begin(); it != toolbox->end(); ++it) {
Tool* tool = *it;
if (tool->getGroup() == tool_group)
++count;
ToolStrip::Tools tools;
switch (group_type) {
case GroupType::Regular:
for (Tool* tool : *toolbox) {
if (tool->getGroup() == tool_group)
tools.push_back(tool);
}
break;
case GroupType::Overflow: {
ToolGroupList::iterator it = toolbox->begin_group();
for (int i = 0; i < toolbox->getGroupsCount(); ++i, ++it) {
if (i < toolbox->getGroupsCount() - getHiddenGroups())
continue;
ToolGroup* it_group = *it;
for (Tool* tool : *toolbox) {
if (tool->getGroup() == it_group)
tools.push_back(tool);
}
}
break;
}
}
m_openOnHot = true;
if (count <= 1)
// If this group contains only one tool, do not show the popup
if (tools.size() <= 1)
return;
// In case this tool contains more than just one tool, show the popup window
m_openOnHot = true;
m_popupWindow = new TransparentPopupWindow(PopupWindow::ClickBehavior::CloseOnClickOutsideHotRegion);
m_closeConn = m_popupWindow->Close.connect([this]{ onClosePopup(); });
m_openedRecently = true;
ToolStrip* toolstrip = new ToolStrip(tool_group, this);
ToolStrip* toolstrip = new ToolStrip(this, tools, tool_group);
m_currentStrip = toolstrip;
m_popupWindow->addChild(toolstrip);
Rect rc = getToolGroupBounds(group_index);
int w = 0;
for (Tool* tool : *toolbox) {
if (tool->getGroup() == tool_group)
w += bounds().w - border().width() - 1 * guiscale();
for (const auto* tool : tools) {
(void)tool;
w += bounds().w - border().width() - 1 * guiscale();
}
rc.x -= w;
@ -528,22 +556,16 @@ Rect ToolBar::getToolGroupBounds(int group_index)
return rc;
}
Point ToolBar::getToolPositionInGroup(int group_index, Tool* tool)
Point ToolBar::getToolPositionInGroup(const Tool* tool) const
{
ToolBox* toolbox = App::instance()->toolBox();
Size iconsize = getToolIconSize(this);
int nth = 0;
if (!m_currentStrip)
return Point(0, 0);
for (ToolIterator it = toolbox->begin(); it != toolbox->end(); ++it) {
if (tool == *it)
break;
const Size iconsize = getToolIconSize(this);
const auto& tools = m_currentStrip->tools();
const int nth = std::find(tools.begin(), tools.end(), tool) - tools.begin();
if ((*it)->getGroup() == tool->getGroup()) {
++nth;
}
}
return Point(iconsize.w / 2 + iconsize.w * nth, iconsize.h);
return Point(iconsize.w/2 + nth*(iconsize.w-1*guiscale()), iconsize.h);
}
void ToolBar::openTipWindow(ToolGroup* tool_group, Tool* tool)
@ -596,7 +618,7 @@ void ToolBar::openTipWindow(int group_index, Tool* tool)
m_tipWindow->remapWindow();
Rect toolrc = getToolGroupBounds(group_index);
Point arrow = (tool ? getToolPositionInGroup(group_index, tool) : Point(0, 0));
Point arrow = (tool ? getToolPositionInGroup(tool) : Point(0, 0));
if (tool && m_popupWindow && m_popupWindow->isVisible())
toolrc.x += arrow.x - m_popupWindow->bounds().w;
@ -675,106 +697,31 @@ void ToolBar::drawToolIcon(Graphics* g,
}
}
int ToolBar::getHiddenGroups()
int ToolBar::getHiddenGroups() const
{
auto* toolbox = App::instance()->toolBox();
const int height = size().h;
if (height < m_minHeight) {
int hidden = (m_minHeight - height) / getToolIconSize(this).h;
if (hidden >= 1)
return std::clamp(hidden + 1, 2, App::instance()->toolBox()->getGroupsCount() - 1);
return std::clamp(hidden + 1, 2, toolbox->getGroupsCount() - 1);
}
return 0;
}
void ToolBar::showHiddenToolPopup()
{
if (m_popupWindow) {
if (m_closeConn)
m_closeConn.disconnect();
onClosePopup();
closePopupWindow();
}
closeTipWindow();
auto toolbox = App::instance()->toolBox();
m_popupWindow = new TransparentPopupWindow(PopupWindow::ClickBehavior::CloseOnClickOutsideHotRegion);
m_closeConn = m_popupWindow->Close.connect([this] { onClosePopup(); });
std::vector<ButtonSet::Item*> items;
auto toolIconSize = getToolIconSize(this);
auto theme = SkinTheme::get(this);
ToolGroupList::iterator it = toolbox->begin_group();
for (int i = 0; i < toolbox->getGroupsCount(); ++i, ++it) {
if (i < toolbox->getGroupsCount() - getHiddenGroups())
continue;
ToolGroup* tool_group = *it;
for (ToolIterator bit = toolbox->begin(); bit != toolbox->end(); ++bit) {
Tool* tool = *bit;
if (tool->getGroup() == tool_group) {
auto item = new ButtonSet::Item();
// TODO: Tooltip with tool->getText(), implement in ButtonSet?
item->setIcon(theme->getToolPart(tool->getId().c_str()));
item->setId(tool->getId().c_str());
item->setMaxSize(toolIconSize); // TODO: Need a better way to enforce the button/icon size, this one doesn't look great
items.push_back(item);
}
}
}
std::reverse(items.begin(), items.end());
ButtonSet* hiddenTools = new ButtonSet(items.size());
for (auto* item : items)
hiddenTools->addItem(item);
hiddenTools->initTheme();
hiddenTools->ItemChange.connect([this](ButtonSet::Item* toolSelection) {
selectTool(App::instance()->toolBox()->getToolById(toolSelection->id()));
m_popupWindow->deferDelete();
m_popupWindow->closeWindow(nullptr);
m_popupWindow = nullptr;
});
VBox* box = new VBox();
box->InitTheme.connect([box] { box->noBorderNoChildSpacing(); });
box->initTheme();
box->addChild(hiddenTools);
m_popupWindow->addChild(box);
Rect rc = getToolGroupBounds(toolbox->getGroupsCount() - getHiddenGroups());
rc.setSize(box->sizeHint());
rc.x -= rc.w;
// Set hotregion of popup window
m_popupWindow->setAutoRemap(false);
ui::fit_bounds(display(), m_popupWindow, rc);
m_popupWindow->setBounds(rc);
Region rgn(m_popupWindow->boundsOnScreen().enlarge(16 * guiscale()));
rgn.createUnion(rgn, Region(boundsOnScreen()));
m_popupWindow->setHotRegion(rgn);
m_popupWindow->openWindow();
invalidate();
}
//////////////////////////////////////////////////////////////////////
// ToolStrip
//////////////////////////////////////////////////////////////////////
ToolBar::ToolStrip::ToolStrip(ToolGroup* group, ToolBar* toolbar)
ToolBar::ToolStrip::ToolStrip(ToolBar* toolbar,
const Tools& tools,
ToolGroup* group)
: Widget(kGenericWidget)
, m_toolbar(toolbar)
, m_tools(tools)
, m_group(group)
, m_hotTool(nullptr)
{
m_group = group;
m_hotTool = NULL;
m_toolbar = toolbar;
setDoubleBuffered(true);
setTransparent(true);
}
@ -794,18 +741,15 @@ bool ToolBar::ToolStrip::onProcessMessage(Message* msg)
case kMouseMoveMessage: {
auto mouseMsg = static_cast<const MouseMessage*>(msg);
const Point mousePos = mouseMsg->positionForDisplay(display());
ToolBox* toolbox = App::instance()->toolBox();
Tool* hot_tool = NULL;
Rect toolrc;
int index = 0;
for (Tool* tool : *toolbox) {
if (tool->getGroup() == m_group) {
toolrc = getToolBounds(index++);
if (toolrc.contains(Point(mousePos.x, mousePos.y))) {
hot_tool = tool;
break;
}
for (Tool* tool : m_tools) {
toolrc = getToolBounds(index++);
if (toolrc.contains(Point(mousePos.x, mousePos.y))) {
hot_tool = tool;
break;
}
}
@ -815,8 +759,15 @@ bool ToolBar::ToolStrip::onProcessMessage(Message* msg)
invalidate();
// Show the tooltip for the hot tool
if (m_hotTool && !hasCapture())
m_toolbar->openTipWindow(m_group, m_hotTool);
if (m_hotTool && !hasCapture()) {
if (m_group)
m_toolbar->openTipWindow(m_group, m_hotTool);
else {
int groups = App::instance()->toolBox()->getGroupsCount()
- m_toolbar->getHiddenGroups();
m_toolbar->openTipWindow(groups, m_hotTool);
}
}
else
m_toolbar->closeTipWindow();
@ -856,51 +807,38 @@ bool ToolBar::ToolStrip::onProcessMessage(Message* msg)
void ToolBar::ToolStrip::onSizeHint(SizeHintEvent& ev)
{
ToolBox* toolbox = App::instance()->toolBox();
int c = 0;
for (ToolIterator it = toolbox->begin(); it != toolbox->end(); ++it) {
Tool* tool = *it;
if (tool->getGroup() == m_group) {
++c;
}
}
Size iconsize = getToolIconSize(this);
ev.setSizeHint(Size(iconsize.w * c, iconsize.h));
ev.setSizeHint(Size(iconsize.w * m_tools.size(), iconsize.h));
}
void ToolBar::ToolStrip::onPaint(PaintEvent& ev)
{
Graphics* g = ev.graphics();
auto theme = SkinTheme::get(this);
ToolBox* toolbox = App::instance()->toolBox();
Tool* activeTool = App::instance()->activeTool();
Rect toolrc;
int index = 0;
for (Tool* tool : *toolbox) {
if (tool->getGroup() == m_group) {
SkinPartPtr nw;
for (Tool* tool : m_tools) {
SkinPartPtr nw;
if (activeTool == tool || m_hotTool == tool) {
nw = theme->parts.toolbuttonHot();
}
else {
nw = theme->parts.toolbuttonLast();
}
if (activeTool == tool || m_hotTool == tool) {
nw = theme->parts.toolbuttonHot();
}
else {
nw = theme->parts.toolbuttonLast();
}
toolrc = getToolBounds(index++);
toolrc.offset(-bounds().x, -bounds().y);
theme->drawRect(g, toolrc, nw.get());
toolrc = getToolBounds(index++);
toolrc.offset(-bounds().x, -bounds().y);
theme->drawRect(g, toolrc, nw.get());
// Draw the tool icon
os::Surface* icon = theme->getToolIcon(tool->getId().c_str());
if (icon) {
g->drawRgbaSurface(icon,
CALC_FOR_CENTER(toolrc.x, toolrc.w, icon->width()),
CALC_FOR_CENTER(toolrc.y, toolrc.h, icon->height()));
}
// Draw the tool icon
os::Surface* icon = theme->getToolIcon(tool->getId().c_str());
if (icon) {
g->drawRgbaSurface(icon,
CALC_FOR_CENTER(toolrc.x, toolrc.w, icon->width()),
CALC_FOR_CENTER(toolrc.y, toolrc.h, icon->height()));
}
}
}

View File

@ -57,16 +57,19 @@ namespace app {
void onVisible(bool visible) override;
private:
enum class GroupType { Regular, Overflow };
int getToolGroupIndex(tools::ToolGroup* group);
void openPopupWindow(int group_index, tools::ToolGroup* group);
void openPopupWindow(GroupType group_type,
int group_index = 0,
tools::ToolGroup* tool_group = nullptr);
void closePopupWindow();
gfx::Rect getToolGroupBounds(int group_index);
gfx::Point getToolPositionInGroup(int group_index, tools::Tool* tool);
gfx::Point getToolPositionInGroup(const tools::Tool* tool) const;
void openTipWindow(int group_index, tools::Tool* tool);
void onClosePopup();
void drawToolIcon(ui::Graphics* g, int group_index, skin::SkinPartPtr skin, os::Surface* icon);
int getHiddenGroups();
void showHiddenToolPopup();
int getHiddenGroups() const;
// ActiveToolObserver impl
void onActiveToolChange(tools::Tool* tool) override;