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,
color,
(doc::ColorMode)m_pixelFormat,
hasMouseOver(), false);
hasMouse(), false);
// Draw text
std::string str = m_color.toHumanReadableString(m_pixelFormat,

View File

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

View File

@ -62,7 +62,7 @@ void Notifications::onPaint(PaintEvent& ev)
Graphics* g = ev.graphics();
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 (isSelected()) info.styleFlags |= ui::Style::Layer::kSelected;

View File

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

View File

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

View File

@ -1,5 +1,5 @@
// 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
// the End-User License Agreement for Aseprite.
@ -164,7 +164,7 @@ void TileButton::onPaint(PaintEvent& ev)
Site site = UIContext::instance()->activeSite();
draw_tile_button(g, rc,
site, m_tile,
hasMouseOver(), false);
hasMouse(), false);
// Draw text
if (m_tile != doc::notile) {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
// 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
//
// This file is released under the terms of the MIT license.
@ -18,6 +18,8 @@
#include "ui/mouse_button.h"
#include "ui/pointer_type.h"
#include <functional>
namespace ui {
class Display;
@ -81,6 +83,18 @@ namespace ui {
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 {
public:
KeyMessage(MessageType type,

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -45,6 +45,11 @@ namespace ui {
// TODO Drag'n'drop messages...
// 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.
kFirstRegisteredMessage,
kLastRegisteredMessage = 0x7fffffff

View File

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

View File

@ -349,12 +349,29 @@ namespace ui {
void captureMouse();
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 hasMouse() const { return hasFlags(HAS_MOUSE); }
bool hasCapture() const { return hasFlags(HAS_CAPTURE); }
// Checking if the mouse is currently above the widget.
bool hasMouseOver() const;
// True when the widget has the mouse above. If the mouse leaves
// 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
// the ui::Display's client area/content rect.