mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-28 16:11:35 +00:00
Avoid capturing the mouse from widgets in background windows
This avoids clicking buttons that are not in the foreground/modal window. At the same time the onBroadcastMouseMessage() functionality was expanded in such a way that added widgets to the broadcast are the only ones allowed to re-capture the mouse (even if they are not in the current foreground window). This is required so the Editor can be scrolled when we are visualizing a Filter window with preview. New functions added to simplify some code: * Manager::transferAsMouseDownMessage() * Manager::allowCapture() * base::contains() Related to #4963 and #4973.
This commit is contained in:
parent
6c69840184
commit
d5738fb492
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 339a0fa13584853bda8559de486e715e743a5763
|
||||
Subproject commit 65829107c838817987f3cf6374cc68c583e5d538
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2024 Igara Studio S.A.
|
||||
// Copyright (c) 2024-2025 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -51,18 +51,12 @@ bool FontEntry::FontFace::onProcessMessage(Message* msg)
|
||||
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||
const gfx::Point screenPos = mouseMsg->display()->nativeWindow()->pointToScreen(
|
||||
mouseMsg->position());
|
||||
Widget* pick = manager()->pickFromScreenPos(screenPos);
|
||||
Manager* mgr = manager();
|
||||
Widget* pick = mgr->pickFromScreenPos(screenPos);
|
||||
Widget* target = m_popup->getListBox();
|
||||
|
||||
if (pick && (pick == target || pick->hasAncestor(target))) {
|
||||
releaseMouse();
|
||||
|
||||
MouseMessage mouseMsg2(kMouseDownMessage,
|
||||
*mouseMsg,
|
||||
mouseMsg->positionForDisplay(pick->display()));
|
||||
mouseMsg2.setRecipient(pick);
|
||||
mouseMsg2.setDisplay(pick->display());
|
||||
pick->sendMessage(&mouseMsg2);
|
||||
mgr->transferAsMouseDownMessage(this, pick, mouseMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -266,17 +266,10 @@ bool ToolBar::onProcessMessage(Message* msg)
|
||||
// mouse over the ToolBar.
|
||||
if (hasCapture()) {
|
||||
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||
Widget* pick = manager()->pickFromScreenPos(mouseMsg->screenPosition());
|
||||
Manager* mgr = manager();
|
||||
Widget* pick = mgr->pickFromScreenPos(mouseMsg->screenPosition());
|
||||
if (ToolStrip* strip = dynamic_cast<ToolStrip*>(pick)) {
|
||||
releaseMouse();
|
||||
|
||||
MouseMessage* mouseMsg2 = new MouseMessage(
|
||||
kMouseDownMessage,
|
||||
*mouseMsg,
|
||||
mouseMsg->positionForDisplay(strip->display()));
|
||||
mouseMsg2->setRecipient(strip);
|
||||
mouseMsg2->setDisplay(strip->display());
|
||||
manager()->enqueueMessage(mouseMsg2);
|
||||
mgr->transferAsMouseDownMessage(this, strip, mouseMsg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -755,16 +748,10 @@ bool ToolBar::ToolStrip::onProcessMessage(Message* msg)
|
||||
if (m_hotTool)
|
||||
m_toolbar->selectTool(m_hotTool);
|
||||
|
||||
Widget* pick = manager()->pickFromScreenPos(mouseMsg->screenPosition());
|
||||
Manager* mgr = manager();
|
||||
Widget* pick = mgr->pickFromScreenPos(mouseMsg->screenPosition());
|
||||
if (ToolBar* bar = dynamic_cast<ToolBar*>(pick)) {
|
||||
releaseMouse();
|
||||
|
||||
MouseMessage* mouseMsg2 = new MouseMessage(kMouseDownMessage,
|
||||
*mouseMsg,
|
||||
mouseMsg->positionForDisplay(pick->display()));
|
||||
mouseMsg2->setRecipient(bar);
|
||||
mouseMsg2->setDisplay(pick->display());
|
||||
manager()->enqueueMessage(mouseMsg2);
|
||||
mgr->transferAsMouseDownMessage(this, bar, mouseMsg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -503,18 +503,17 @@ bool ComboBoxEntry::onProcessMessage(Message* msg)
|
||||
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||
gfx::Point screenPos = mouseMsg->display()->nativeWindow()->pointToScreen(
|
||||
mouseMsg->position());
|
||||
Widget* pick = manager()->pickFromScreenPos(screenPos);
|
||||
Manager* mgr = manager();
|
||||
Widget* pick = mgr->pickFromScreenPos(screenPos);
|
||||
Widget* listbox = m_comboBox->m_listbox;
|
||||
|
||||
if (pick != nullptr && (pick == listbox || pick->hasAncestor(listbox))) {
|
||||
releaseMouse();
|
||||
|
||||
MouseMessage mouseMsg2(kMouseDownMessage,
|
||||
*mouseMsg,
|
||||
mouseMsg->positionForDisplay(pick->display()));
|
||||
mouseMsg2.setRecipient(pick);
|
||||
mouseMsg2.setDisplay(pick->display());
|
||||
pick->sendMessage(&mouseMsg2);
|
||||
mgr->transferAsMouseDownMessage(this,
|
||||
pick,
|
||||
mouseMsg,
|
||||
// Send the message right now, if we enqueue
|
||||
// the message the popup window is closed.
|
||||
true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -89,17 +89,11 @@ bool IntEntry::onProcessMessage(Message* msg)
|
||||
case kMouseMoveMessage:
|
||||
if (hasCapture()) {
|
||||
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||
Widget* pick = manager()->pickFromScreenPos(
|
||||
Manager* mgr = manager();
|
||||
Widget* pick = mgr->pickFromScreenPos(
|
||||
display()->nativeWindow()->pointToScreen(mouseMsg->position()));
|
||||
if (pick == m_slider.get()) {
|
||||
releaseMouse();
|
||||
|
||||
MouseMessage mouseMsg2(kMouseDownMessage,
|
||||
*mouseMsg,
|
||||
mouseMsg->positionForDisplay(pick->display()));
|
||||
mouseMsg2.setRecipient(pick);
|
||||
mouseMsg2.setDisplay(pick->display());
|
||||
pick->sendMessage(&mouseMsg2);
|
||||
mgr->transferAsMouseDownMessage(this, pick, mouseMsg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -13,6 +13,7 @@
|
||||
// #define DEBUG_PAINT_MESSAGES 1
|
||||
// #define LIMIT_DISPATCH_TIME 1
|
||||
#define GARBAGE_TRACE(...) // TRACE(__VA_ARGS__)
|
||||
#define CAPTURE_TRACE(...) // TRACE(__VA_ARGS__)
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@ -21,6 +22,8 @@
|
||||
#include "ui/manager.h"
|
||||
|
||||
#include "base/concurrent_queue.h"
|
||||
#include "base/contains.h"
|
||||
#include "base/remove_from_container.h"
|
||||
#include "base/scoped_value.h"
|
||||
#include "base/thread.h"
|
||||
#include "base/time.h"
|
||||
@ -183,8 +186,7 @@ os::Hit handle_native_hittest(os::Window* osWindow, const gfx::Point& pos)
|
||||
bool Manager::widgetAssociatedToManager(Widget* widget)
|
||||
{
|
||||
return (focus_widget == widget || mouse_widget == widget || capture_widget == widget ||
|
||||
std::find(mouse_widgets_list.begin(), mouse_widgets_list.end(), widget) !=
|
||||
mouse_widgets_list.end());
|
||||
base::contains(mouse_widgets_list, widget));
|
||||
}
|
||||
|
||||
Manager::Manager(const os::WindowRef& nativeWindow)
|
||||
@ -889,12 +891,12 @@ void Manager::enqueueMessage(Message* msg)
|
||||
concurrent_msg_queue.push(msg);
|
||||
}
|
||||
|
||||
Window* Manager::getTopWindow()
|
||||
Window* Manager::getTopWindow() const
|
||||
{
|
||||
return static_cast<Window*>(UI_FIRST_WIDGET(children()));
|
||||
}
|
||||
|
||||
Window* Manager::getDesktopWindow()
|
||||
Window* Manager::getDesktopWindow() const
|
||||
{
|
||||
for (auto child : children()) {
|
||||
Window* window = static_cast<Window*>(child);
|
||||
@ -904,7 +906,7 @@ Window* Manager::getDesktopWindow()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Window* Manager::getForegroundWindow()
|
||||
Window* Manager::getForegroundWindow() const
|
||||
{
|
||||
for (auto child : children()) {
|
||||
Window* window = static_cast<Window*>(child);
|
||||
@ -1033,8 +1035,33 @@ void Manager::setMouse(Widget* widget)
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::setCapture(Widget* widget)
|
||||
void Manager::setCapture(Widget* widget, bool force)
|
||||
{
|
||||
ASSERT(widget);
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
CAPTURE_TRACE("Manager::setCapture %s\n", typeid(*widget).name());
|
||||
if (!force &&
|
||||
// The given "widget" cannot capture the mouse if it's not
|
||||
// "clickable". The definition of "clickable" generally means
|
||||
// that the widget is in the current foreground/modal window, or
|
||||
// in the desktop window (or in any floating non-modal window).
|
||||
// But it can also be in a top window, e.g. a combobox popup.
|
||||
!isWidgetClickable(widget) &&
|
||||
// In some special cases, a widget transfers a mouse message to
|
||||
// another widget which doesn't belong to the current foreground
|
||||
// modal window, this is done using onBroadcastMouseMessage()
|
||||
// and/or transferAsMouseDownMessage(), so here we allow capturing
|
||||
// the mouse from widgets inside the "mouse_widgets_list"
|
||||
// (widgets that are allowed to capture the mouse / added with
|
||||
// allowCapture() function).
|
||||
(widget != mouse_widget && !base::contains(mouse_widgets_list, widget))) {
|
||||
CAPTURE_TRACE("-> FILTERED!\n");
|
||||
return;
|
||||
}
|
||||
CAPTURE_TRACE("-> OK\n");
|
||||
|
||||
// To set the capture, we set first the mouse_widget (because
|
||||
// mouse_widget shouldn't be != capture_widget)
|
||||
setMouse(widget);
|
||||
@ -1048,6 +1075,13 @@ void Manager::setCapture(Widget* widget)
|
||||
display->nativeWindow()->captureMouse();
|
||||
}
|
||||
|
||||
void Manager::allowCapture(Widget* widget)
|
||||
{
|
||||
ASSERT(widget);
|
||||
if (!base::contains(mouse_widgets_list, widget))
|
||||
mouse_widgets_list.push_back(widget);
|
||||
}
|
||||
|
||||
// Sets the focus to the "magnetic" widget inside the window
|
||||
void Manager::attractFocus(Widget* widget)
|
||||
{
|
||||
@ -1082,6 +1116,8 @@ void Manager::freeMouse()
|
||||
void Manager::freeCapture()
|
||||
{
|
||||
if (capture_widget) {
|
||||
CAPTURE_TRACE("Manager::freeCapture() %s\n", typeid(*capture_widget).name());
|
||||
|
||||
Display* display = capture_widget->display();
|
||||
|
||||
capture_widget->disableFlags(HAS_CAPTURE);
|
||||
@ -1113,9 +1149,7 @@ void Manager::freeWidget(Widget* widget)
|
||||
if (widget->hasMouse() || (widget == mouse_widget))
|
||||
freeMouse();
|
||||
|
||||
auto it = std::find(mouse_widgets_list.begin(), mouse_widgets_list.end(), widget);
|
||||
if (it != mouse_widgets_list.end())
|
||||
mouse_widgets_list.erase(it);
|
||||
base::remove_from_container(mouse_widgets_list, widget);
|
||||
|
||||
ASSERT(!Manager::widgetAssociatedToManager(widget));
|
||||
}
|
||||
@ -1279,6 +1313,53 @@ Widget* Manager::pickFromScreenPos(const gfx::Point& screenPos) const
|
||||
return Widget::pickFromScreenPos(screenPos);
|
||||
}
|
||||
|
||||
void Manager::transferAsMouseDownMessage(Widget* from,
|
||||
Widget* to,
|
||||
const MouseMessage* mouseMsg,
|
||||
const bool sendNow)
|
||||
{
|
||||
ASSERT(to);
|
||||
ASSERT(from);
|
||||
|
||||
// Remove the capture from the "from" widget.
|
||||
if (from->hasCapture())
|
||||
from->releaseMouse();
|
||||
|
||||
// Allow the "to" widget to re-capture the mouse.
|
||||
allowCapture(to);
|
||||
|
||||
// We enqueue a copy of the mouse message but as a kMouseDownMessage.
|
||||
auto mouseMsg2 = std::make_unique<MouseMessage>(kMouseDownMessage,
|
||||
*mouseMsg,
|
||||
mouseMsg->positionForDisplay(to->display()));
|
||||
mouseMsg2->setRecipient(to);
|
||||
mouseMsg2->setDisplay(to->display());
|
||||
|
||||
if (sendNow)
|
||||
to->sendMessage(mouseMsg2.get());
|
||||
else
|
||||
enqueueMessage(mouseMsg2.release());
|
||||
}
|
||||
|
||||
bool Manager::isWidgetClickable(const Widget* widget) const
|
||||
{
|
||||
Window* widgetWindow = widget->window();
|
||||
if (!widgetWindow)
|
||||
return false;
|
||||
|
||||
for (auto* child : children()) {
|
||||
Window* window = static_cast<Window*>(child);
|
||||
|
||||
if (widgetWindow == window)
|
||||
return true;
|
||||
|
||||
if (window->isForeground() || window->isDesktop())
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Manager::_closingAppWithException()
|
||||
{
|
||||
redrawState = RedrawState::ClosingApp;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -72,9 +72,9 @@ public:
|
||||
void addToGarbage(Widget* widget);
|
||||
void collectGarbage();
|
||||
|
||||
Window* getTopWindow();
|
||||
Window* getDesktopWindow();
|
||||
Window* getForegroundWindow();
|
||||
Window* getTopWindow() const;
|
||||
Window* getDesktopWindow() const;
|
||||
Window* getForegroundWindow() const;
|
||||
Display* getForegroundDisplay();
|
||||
|
||||
Widget* getFocus();
|
||||
@ -83,7 +83,7 @@ public:
|
||||
|
||||
void setFocus(Widget* widget);
|
||||
void setMouse(Widget* widget);
|
||||
void setCapture(Widget* widget);
|
||||
void setCapture(Widget* widget, bool force = false);
|
||||
void attractFocus(Widget* widget);
|
||||
void focusFirstChild(Widget* widget);
|
||||
void freeFocus();
|
||||
@ -107,6 +107,30 @@ public:
|
||||
|
||||
Widget* pickFromScreenPos(const gfx::Point& screenPos) const override;
|
||||
|
||||
// Transfers the given MouseMessage received "from" (generally a
|
||||
// kMouseMoveMessage) to the given "to" widget, sending a copy of
|
||||
// the message but changing its type to kMouseDownMessage. By
|
||||
// default it enqueues the message.
|
||||
//
|
||||
// If "from" has the mouse capture, it will be released as it is
|
||||
// highly probable that the "to" widget will recapture the mouse
|
||||
// again.
|
||||
//
|
||||
// This is used in cases were the user presses the mouse button on
|
||||
// one widget, and then drags the mouse to another widget. With this
|
||||
// we can transfer the mouse capture between widgets (from -> to)
|
||||
// simulating a kMouseDownMessage for the new widget "to".
|
||||
void transferAsMouseDownMessage(Widget* from,
|
||||
Widget* to,
|
||||
const MouseMessage* mouseMsg,
|
||||
bool sendNow = false);
|
||||
|
||||
// Returns true if the widget is accessible with the mouse, i.e. the
|
||||
// widget is in the current foreground window (or a top window above
|
||||
// the foreground, e.g. a combobox popup), or there is no foreground
|
||||
// window and the widget is in the desktop window.
|
||||
bool isWidgetClickable(const Widget* widget) const;
|
||||
|
||||
void _openWindow(Window* window, bool center);
|
||||
void _closeWindow(Window* window, bool redraw_background);
|
||||
void _runModalWindow(Window* window);
|
||||
@ -164,6 +188,7 @@ private:
|
||||
const double magnification);
|
||||
bool handleWindowZOrder();
|
||||
void updateMouseWidgets(const gfx::Point& mousePos, Display* display);
|
||||
void allowCapture(Widget* widget);
|
||||
|
||||
int pumpQueue();
|
||||
bool sendMessageToWidget(Message* msg, Widget* widget);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -186,7 +186,7 @@ void View::updateView(const bool restoreScrollPos)
|
||||
// Restore the mouse capture if it changed, which means that a
|
||||
// scroll bar (when it was temporarily removed) lost the capture.
|
||||
if (man && man->getCapture() != mouseCapture && mouseCapture->isVisible())
|
||||
man->setCapture(mouseCapture);
|
||||
man->setCapture(mouseCapture, true); // Force the capture
|
||||
}
|
||||
|
||||
Viewport* View::viewport()
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -1507,22 +1507,15 @@ void Widget::releaseMouse()
|
||||
}
|
||||
}
|
||||
|
||||
bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
|
||||
bool Widget::offerCapture(ui::MouseMessage* mouseMsg, const WidgetType widgetType)
|
||||
{
|
||||
if (hasCapture()) {
|
||||
const gfx::Point screenPos = mouseMsg->display()->nativeWindow()->pointToScreen(
|
||||
mouseMsg->position());
|
||||
auto man = manager();
|
||||
Widget* pick = (man ? man->pickFromScreenPos(screenPos) : nullptr);
|
||||
if (pick && pick != this && pick->type() == widget_type) {
|
||||
releaseMouse();
|
||||
|
||||
MouseMessage* mouseMsg2 = new MouseMessage(kMouseDownMessage,
|
||||
*mouseMsg,
|
||||
mouseMsg->positionForDisplay(pick->display()));
|
||||
mouseMsg2->setDisplay(pick->display());
|
||||
mouseMsg2->setRecipient(pick);
|
||||
man->enqueueMessage(mouseMsg2);
|
||||
Manager* mgr = manager();
|
||||
Widget* pick = (mgr ? mgr->pickFromScreenPos(screenPos) : nullptr);
|
||||
if (pick && pick != this && pick->type() == widgetType) {
|
||||
mgr->transferAsMouseDownMessage(this, pick, mouseMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -336,6 +336,11 @@ public:
|
||||
|
||||
void requestFocus();
|
||||
void releaseFocus();
|
||||
|
||||
// Captures the mouse to continue receiving its messages until we
|
||||
// release the capture. Useful for widgets with painting-like
|
||||
// capabilities, where we want to keep track of the mouse until the
|
||||
// user releases the mouse button, or drag-and-drop behaviors.
|
||||
void captureMouse();
|
||||
void releaseMouse();
|
||||
|
||||
@ -371,9 +376,9 @@ public:
|
||||
// the widget bounds.
|
||||
gfx::Point mousePosInClientBounds() const { return toClient(mousePosInDisplay()); }
|
||||
|
||||
// Offer the capture to widgets of the given type. Returns true if
|
||||
// Offers the capture to widgets of the given type. Returns true if
|
||||
// the capture was passed to other widget.
|
||||
bool offerCapture(MouseMessage* mouseMsg, int widget_type);
|
||||
bool offerCapture(MouseMessage* mouseMsg, WidgetType widgetType);
|
||||
|
||||
// Returns lower-case letter that represet the mnemonic of the widget
|
||||
// (the underscored character, i.e. the letter after & symbol).
|
||||
|
Loading…
x
Reference in New Issue
Block a user