diff --git a/src/app/ui/color_button.cpp b/src/app/ui/color_button.cpp index 85767706c..fcb5ab216 100644 --- a/src/app/ui/color_button.cpp +++ b/src/app/ui/color_button.cpp @@ -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, diff --git a/src/app/ui/icon_button.cpp b/src/app/ui/icon_button.cpp index 9187cb803..048376e45 100644 --- a/src/app/ui/icon_button.cpp +++ b/src/app/ui/icon_button.cpp @@ -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(); } diff --git a/src/app/ui/notifications.cpp b/src/app/ui/notifications.cpp index a24f95a89..03eccc8eb 100644 --- a/src/app/ui/notifications.cpp +++ b/src/app/ui/notifications.cpp @@ -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; diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index 99150aa57..c7d8a99b5 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -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) diff --git a/src/app/ui/tabs.cpp b/src/app/ui/tabs.cpp index 0db535b80..22831c4c3 100644 --- a/src/app/ui/tabs.cpp +++ b/src/app/ui/tabs.cpp @@ -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); diff --git a/src/app/ui/tile_button.cpp b/src/app/ui/tile_button.cpp index d74e2ed5b..7d62a3d36 100644 --- a/src/app/ui/tile_button.cpp +++ b/src/app/ui/tile_button.cpp @@ -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) { diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index 26fe2082c..f3ba83514 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -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); } diff --git a/src/ui/button.cpp b/src/ui/button.cpp index 36e062659..bffef4200 100644 --- a/src/ui/button.cpp +++ b/src/ui/button.cpp @@ -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) || diff --git a/src/ui/link_label.cpp b/src/ui/link_label.cpp index 491cee3db..7c31bae28 100644 --- a/src/ui/link_label.cpp +++ b/src/ui/link_label.cpp @@ -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; diff --git a/src/ui/manager.cpp b/src/ui/manager.cpp index 7b1206a61..b4969d0f6 100644 --- a/src/ui/manager.cpp +++ b/src/ui/manager.cpp @@ -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 && diff --git a/src/ui/message.h b/src/ui/message.h index 0fd5d1d08..321381930 100644 --- a/src/ui/message.h +++ b/src/ui/message.h @@ -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 + 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&& callback) + : Message(kCallbackMessage) + , m_callback(std::move(callback)) { } + void call() { + m_callback(); + } + private: + std::function m_callback; + }; + class KeyMessage : public Message { public: KeyMessage(MessageType type, diff --git a/src/ui/message_type.h b/src/ui/message_type.h index 16dbb7312..7536e26dc 100644 --- a/src/ui/message_type.h +++ b/src/ui/message_type.h @@ -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 diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index 917204c35..d9a2a1a78 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -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()); diff --git a/src/ui/widget.h b/src/ui/widget.h index 260e501e8..44b666673 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -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.