Process os::Event::MouseEnter/Leave in correct order

When we received a MouseEnter/Leave, we were processing those laf-os
events immediately without taking care of all the MouseMove/Down/Up
events in the middle. Now we enqueue all the events as
messages (MouseEnter -> MouseMove/Down/Up -> MouseLeave).

This is important because when we process MouseLeave we are calling
setMouse(nullptr), which resets the mouse widget HAS_MOUSE flag, and
some widgets needs to know if they have the mouse above before the
MouseUp event. With this change we can remove the
Widget::hasMouseOver() function (that was checking the global
ui::get_mouse_position() instead of the HAS_MOUSE flag directly).

Another change was introduced to keep the old behavior now that
hasMouseOver() is not available: the ui::Manager doesn't assign the
HAS_MOUSE flag if the mouse is captured (it only assign the HAS_MOUSE
to the widget with the mouse captured, or to no widget, at least until
the capture is released).
This commit is contained in:
David Capello 2023-07-10 10:54:37 -03:00
parent 48275d51c2
commit bd91a6430f
14 changed files with 138 additions and 92 deletions

View File

@ -277,7 +277,7 @@ void ColorButton::onPaint(PaintEvent& ev)
draw_color_button(g, rc, draw_color_button(g, rc,
color, color,
(doc::ColorMode)m_pixelFormat, (doc::ColorMode)m_pixelFormat,
hasMouseOver(), false); hasMouse(), false);
// Draw text // Draw text
std::string str = m_color.toHumanReadableString(m_pixelFormat, std::string str = m_color.toHumanReadableString(m_pixelFormat,

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2022 Igara Studio S.A. // Copyright (C) 2022-2023 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -56,7 +56,7 @@ void IconButton::onPaint(PaintEvent& ev)
fg = theme->colors.menuitemHighlightText(); fg = theme->colors.menuitemHighlightText();
bg = theme->colors.menuitemHighlightFace(); bg = theme->colors.menuitemHighlightFace();
} }
else if (isEnabled() && hasMouseOver()) { else if (isEnabled() && hasMouse()) {
fg = theme->colors.menuitemHotText(); fg = theme->colors.menuitemHotText();
bg = theme->colors.menuitemHotFace(); bg = theme->colors.menuitemHotFace();
} }

View File

@ -62,7 +62,7 @@ void Notifications::onPaint(PaintEvent& ev)
Graphics* g = ev.graphics(); Graphics* g = ev.graphics();
PaintWidgetPartInfo info; PaintWidgetPartInfo info;
if (hasMouseOver()) info.styleFlags |= ui::Style::Layer::kMouse; if (hasMouse()) info.styleFlags |= ui::Style::Layer::kMouse;
if (m_red) info.styleFlags |= ui::Style::Layer::kFocus; if (m_red) info.styleFlags |= ui::Style::Layer::kFocus;
if (isSelected()) info.styleFlags |= ui::Style::Layer::kSelected; if (isSelected()) info.styleFlags |= ui::Style::Layer::kSelected;

View File

@ -1489,16 +1489,16 @@ void SkinTheme::paintSlider(PaintEvent& ev)
SkinPartPtr empty_part; SkinPartPtr empty_part;
if (isMiniLook) { if (isMiniLook) {
full_part = widget->hasMouseOver() ? parts.miniSliderFullFocused(): full_part = (widget->hasMouse() ? parts.miniSliderFullFocused():
parts.miniSliderFull(); parts.miniSliderFull());
empty_part = widget->hasMouseOver() ? parts.miniSliderEmptyFocused(): empty_part = (widget->hasMouse() ? parts.miniSliderEmptyFocused():
parts.miniSliderEmpty(); parts.miniSliderEmpty());
} }
else { else {
full_part = widget->hasFocus() ? parts.sliderFullFocused(): full_part = (widget->hasFocus() ? parts.sliderFullFocused():
parts.sliderFull(); parts.sliderFull());
empty_part = widget->hasFocus() ? parts.sliderEmptyFocused(): empty_part = (widget->hasFocus() ? parts.sliderEmptyFocused():
parts.sliderEmpty(); parts.sliderEmpty());
} }
if (value == min) if (value == min)

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -1042,7 +1042,7 @@ void Tabs::updateDragTabIndexes(int mouseX, bool startAni)
startAni = true; startAni = true;
} }
} }
else if (hasMouseOver()) { else if (hasMouse()) {
i = std::clamp(i, 0, int(m_list.size())-1); i = std::clamp(i, 0, int(m_list.size())-1);
if (i != m_dragTabIndex) { if (i != m_dragTabIndex) {
m_list.erase(m_list.begin()+m_dragTabIndex); m_list.erase(m_list.begin()+m_dragTabIndex);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (c) 2020-2022 Igara Studio S.A. // Copyright (c) 2020-2023 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -164,7 +164,7 @@ void TileButton::onPaint(PaintEvent& ev)
Site site = UIContext::instance()->activeSite(); Site site = UIContext::instance()->activeSite();
draw_tile_button(g, rc, draw_tile_button(g, rc,
site, m_tile, site, m_tile,
hasMouseOver(), false); hasMouse(), false);
// Draw text // Draw text
if (m_tile != doc::notile) { if (m_tile != doc::notile) {

View File

@ -3241,7 +3241,7 @@ void Timeline::updateScrollBars()
void Timeline::updateByMousePos(ui::Message* msg, const gfx::Point& mousePos) void Timeline::updateByMousePos(ui::Message* msg, const gfx::Point& mousePos)
{ {
Hit hit = hitTest(msg, mousePos); Hit hit = hitTest(msg, mousePos);
if (hasMouseOver()) if (hasMouse())
setCursor(msg, hit); setCursor(msg, hit);
setHot(hit); setHot(hit);
} }

View File

@ -204,7 +204,7 @@ bool ButtonBase::onProcessMessage(Message* msg)
if (hasCapture()) { if (hasCapture()) {
releaseMouse(); releaseMouse();
if (hasMouseOver()) { if (hasMouse()) {
switch (m_behaviorType) { switch (m_behaviorType) {
case kButtonWidget: case kButtonWidget:
@ -270,7 +270,7 @@ void ButtonBase::onStartDrag()
void ButtonBase::onSelectWhenDragging() void ButtonBase::onSelectWhenDragging()
{ {
bool hasMouse = hasMouseOver(); const bool hasMouse = this->hasMouse();
// Switch state when the mouse go out // Switch state when the mouse go out
if ((hasMouse && isSelected() != m_pressedStatus) || if ((hasMouse && isSelected() != m_pressedStatus) ||

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018 Igara Studio S.A. // Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -48,7 +48,7 @@ bool LinkLabel::onProcessMessage(Message* msg)
case kSetCursorMessage: case kSetCursorMessage:
// TODO theme stuff // TODO theme stuff
if (isEnabled() && hasMouseOver()) { if (isEnabled() && hasMouse()) {
set_mouse_cursor(kHandCursor); set_mouse_cursor(kHandCursor);
return true; return true;
} }
@ -66,7 +66,7 @@ bool LinkLabel::onProcessMessage(Message* msg)
case kMouseMoveMessage: case kMouseMoveMessage:
if (isEnabled() && hasCapture()) if (isEnabled() && hasCapture())
setSelected(hasMouseOver()); setSelected(hasMouse());
break; break;
case kMouseDownMessage: case kMouseDownMessage:
@ -83,7 +83,7 @@ bool LinkLabel::onProcessMessage(Message* msg)
setSelected(false); setSelected(false);
invalidate(); // TODO theme specific invalidate(); // TODO theme specific
if (hasMouseOver()) if (hasMouse())
onClick(); onClick();
} }
break; break;

View File

@ -496,30 +496,37 @@ void Manager::generateMessagesFromOSEvents()
} }
case os::Event::MouseEnter: { case os::Event::MouseEnter: {
if (get_multiple_displays()) { auto msg = new CallbackMessage([osEvent, display]{
if (osEvent.window()) { if (get_multiple_displays()) {
ASSERT(display != nullptr); if (osEvent.window()) {
_internal_set_mouse_display(display); ASSERT(display != nullptr);
_internal_set_mouse_display(display);
}
} }
} set_mouse_cursor(kArrowCursor);
set_mouse_cursor(kArrowCursor); mouse_display = display;
});
enqueueMessage(msg);
lastMouseMoveEvent = osEvent; lastMouseMoveEvent = osEvent;
mouse_display = display;
break; break;
} }
case os::Event::MouseLeave: { case os::Event::MouseLeave: {
if (mouse_display == display) { auto msg = new CallbackMessage([this, display]{
set_mouse_cursor(kOutsideDisplay); if (mouse_display == display) {
setMouse(nullptr); set_mouse_cursor(kOutsideDisplay);
setMouse(nullptr);
_internal_no_mouse_position(); _internal_no_mouse_position();
mouse_display = nullptr; mouse_display = nullptr;
}
});
// To avoid calling kSetCursorMessage when the mouse leaves enqueueMessage(msg);
// the window.
lastMouseMoveEvent = os::Event(); // To avoid calling kSetCursorMessage when the mouse leaves
} // the window.
lastMouseMoveEvent = os::Event();
break; break;
} }
@ -1000,54 +1007,61 @@ void Manager::setMouse(Widget* widget)
(widget ? widget->id(): "")); (widget ? widget->id(): ""));
#endif #endif
if ((mouse_widget != widget) && (!capture_widget)) { if (mouse_widget == widget)
Widget* commonAncestor = findLowestCommonAncestor(mouse_widget, widget); return;
// Fetch the mouse Widget* commonAncestor = findLowestCommonAncestor(mouse_widget, widget);
if (mouse_widget && mouse_widget != commonAncestor) {
auto msg = new Message(kMouseLeaveMessage);
msg->setRecipient(mouse_widget);
msg->setPropagateToParent(true);
msg->setCommonAncestor(commonAncestor);
enqueueMessage(msg);
// Remove HAS_MOUSE from all the hierarchy // Fetch the mouse
auto a = mouse_widget; if (mouse_widget && mouse_widget != commonAncestor) {
while (a && a != commonAncestor) { auto msg = new Message(kMouseLeaveMessage);
a->disableFlags(HAS_MOUSE); msg->setRecipient(mouse_widget);
a = a->parent(); msg->setPropagateToParent(true);
} msg->setCommonAncestor(commonAncestor);
enqueueMessage(msg);
// Remove HAS_MOUSE from all the hierarchy
auto a = mouse_widget;
while (a && a != commonAncestor) {
a->disableFlags(HAS_MOUSE);
a = a->parent();
} }
}
// Put the mouse // If the mouse is captured, we can just put the HAS_MOUSE flag in
mouse_widget = widget; // the captured widget (or in none).
if (widget) { if (capture_widget && capture_widget != widget) {
Display* display = mouse_widget->display(); widget = nullptr;
gfx::Point mousePos = display->nativeWindow()->pointFromScreen(get_mouse_position()); }
auto msg = newMouseMessage( // Put the mouse
kMouseEnterMessage, mouse_widget = widget;
display, nullptr, if (widget) {
mousePos, Display* display = mouse_widget->display();
PointerType::Unknown, gfx::Point mousePos = display->nativeWindow()->pointFromScreen(get_mouse_position());
m_mouseButton,
kKeyUninitializedModifier);
msg->setRecipient(widget); auto msg = newMouseMessage(
msg->setPropagateToParent(true); kMouseEnterMessage,
msg->setCommonAncestor(commonAncestor); display, nullptr,
enqueueMessage(msg); mousePos,
generateSetCursorMessage(display, PointerType::Unknown,
mousePos, m_mouseButton,
kKeyUninitializedModifier, kKeyUninitializedModifier);
PointerType::Unknown);
// Add HAS_MOUSE to all the hierarchy msg->setRecipient(widget);
auto a = mouse_widget; msg->setPropagateToParent(true);
while (a && a != commonAncestor) { msg->setCommonAncestor(commonAncestor);
a->enableFlags(HAS_MOUSE); enqueueMessage(msg);
a = a->parent(); generateSetCursorMessage(display,
} mousePos,
kKeyUninitializedModifier,
PointerType::Unknown);
// Add HAS_MOUSE to all the hierarchy
auto a = mouse_widget;
while (a && a != commonAncestor) {
a->enableFlags(HAS_MOUSE);
a = a->parent();
} }
} }
} }
@ -1929,9 +1943,10 @@ bool Manager::sendMessageToWidget(Message* msg, Widget* widget)
"kSetCursorMessage", "kSetCursorMessage",
"kMouseWheelMessage", "kMouseWheelMessage",
"kTouchMagnifyMessage", "kTouchMagnifyMessage",
"kCallbackMessage",
}; };
static_assert(kOpenMessage == 0 && static_assert(kOpenMessage == 0 &&
kTouchMagnifyMessage == sizeof(msg_name)/sizeof(const char*)-1, kCallbackMessage == sizeof(msg_name)/sizeof(const char*)-1,
"MessageType enum has changed"); "MessageType enum has changed");
const char* string = const char* string =
(msg->type() >= 0 && (msg->type() >= 0 &&

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -18,6 +18,8 @@
#include "ui/mouse_button.h" #include "ui/mouse_button.h"
#include "ui/pointer_type.h" #include "ui/pointer_type.h"
#include <functional>
namespace ui { namespace ui {
class Display; class Display;
@ -81,6 +83,18 @@ namespace ui {
KeyModifiers m_modifiers; // Key modifiers pressed when message was created KeyModifiers m_modifiers; // Key modifiers pressed when message was created
}; };
class CallbackMessage : public Message {
public:
CallbackMessage(std::function<void()>&& callback)
: Message(kCallbackMessage)
, m_callback(std::move(callback)) { }
void call() {
m_callback();
}
private:
std::function<void()> m_callback;
};
class KeyMessage : public Message { class KeyMessage : public Message {
public: public:
KeyMessage(MessageType type, KeyMessage(MessageType type,

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -45,6 +45,11 @@ namespace ui {
// TODO Drag'n'drop messages... // TODO Drag'n'drop messages...
// k...DndMessage // k...DndMessage
// Call a generic function when we are processing the queue of
// messages. Used to process an laf-os event in the same order
// they were received in some cases (e.g. mouse leave/enter).
kCallbackMessage,
// User widgets. // User widgets.
kFirstRegisteredMessage, kFirstRegisteredMessage,
kLastRegisteredMessage = 0x7fffffff kLastRegisteredMessage = 0x7fffffff

View File

@ -1496,11 +1496,6 @@ bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
return false; return false;
} }
bool Widget::hasMouseOver() const
{
return (this == pickFromScreenPos(get_mouse_position()));
}
gfx::Point Widget::mousePosInDisplay() const gfx::Point Widget::mousePosInDisplay() const
{ {
return display()->nativeWindow()->pointFromScreen(get_mouse_position()); return display()->nativeWindow()->pointFromScreen(get_mouse_position());

View File

@ -349,12 +349,29 @@ namespace ui {
void captureMouse(); void captureMouse();
void releaseMouse(); void releaseMouse();
// True when the widget has the keyboard focus (only widgets with
// FOCUS_STOP flag will receive the HAS_FOCUS flag/receive the
// focus when the user press the tab key to navigate widgets).
bool hasFocus() const { return hasFlags(HAS_FOCUS); } bool hasFocus() const { return hasFlags(HAS_FOCUS); }
bool hasMouse() const { return hasFlags(HAS_MOUSE); }
bool hasCapture() const { return hasFlags(HAS_CAPTURE); }
// Checking if the mouse is currently above the widget. // True when the widget has the mouse above. If the mouse leaves
bool hasMouseOver() const; // the widget, the widget will lose the HAS_MOUSE flag. If some
// widget captures the mouse, no other widget will have this flag,
// so in this case there are just two options:
//
// 1) The widget with the capture (hasCapture()) will has the
// mouse flag too.
// 2) Or no other widget will have the mouse flag until the widget
// releases the capture (releaseCapture())
bool hasMouse() const { return hasFlags(HAS_MOUSE); }
// True when the widget has captured the mouse, e.g. generally
// when the user press a mouse button above a clickeable widget
// (e.g. ui::Button), the widget will capture the mouse
// temporarily until the mouse button is released. If a widget
// captures the mouse, it will receive all mouse events until it
// release (even if the mouse moves outside the widget).
bool hasCapture() const { return hasFlags(HAS_CAPTURE); }
// Returns the mouse position relative to the top-left corner of // Returns the mouse position relative to the top-left corner of
// the ui::Display's client area/content rect. // the ui::Display's client area/content rect.