aseprite/src/ui/manager.cpp
David Capello 79f9e28ce8 Added pen pressure sensitivity (fix #710)
- Added support to detect eraser tip on Linux (#610)
- Related to #139
- Still needs works for gradients and better brush interpolations
  between stroke points
- Requested several times, e.g. https://community.aseprite.org/t/1077
  https://community.aseprite.org/t/1881, steam forum, etc.
2020-04-21 22:27:49 -03:00

1922 lines
48 KiB
C++

// Aseprite UI Library
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
// #define REPORT_EVENTS
// #define REPORT_FOCUS_MOVEMENT
// #define DEBUG_PAINT_EVENTS
// #define LIMIT_DISPATCH_TIME
// #define DEBUG_UI_THREADS
#define GARBAGE_TRACE(...)
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ui/manager.h"
#include "base/clamp.h"
#include "base/concurrent_queue.h"
#include "base/scoped_value.h"
#include "base/time.h"
#include "os/display.h"
#include "os/event.h"
#include "os/event_queue.h"
#include "os/surface.h"
#include "os/system.h"
#include "ui/intern.h"
#include "ui/ui.h"
#if defined(DEBUG_PAINT_EVENTS) || defined(DEBUG_UI_THREADS)
#include "base/thread.h"
#endif
#ifdef REPORT_EVENTS
#include <iostream>
#endif
#include <algorithm>
#include <limits>
#include <list>
#include <memory>
#include <utility>
#include <vector>
namespace ui {
namespace {
// The redraw state is used to avoid drawing the manager when a window
// has been just closed by the user, so we delay the redrawing (the
// kPaintMessages generation) for the next generateMessages() round.
enum class RedrawState {
Normal,
AWindowHasJustBeenClosed,
RedrawDelayed,
};
RedrawState redrawState = RedrawState::Normal;
} // anonymous namespace
static const int NFILTERS = (int)(kFirstRegisteredMessage+1);
struct Filter {
int message;
Widget* widget;
Filter(int message, Widget* widget)
: message(message)
, widget(widget) { }
};
typedef std::list<Message*> Messages;
typedef std::list<Filter*> Filters;
Manager* Manager::m_defaultManager = nullptr;
gfx::Region Manager::m_dirtyRegion;
#ifdef DEBUG_UI_THREADS
static base::thread::native_id_type manager_thread = 0;
#endif
static WidgetsList mouse_widgets_list; // List of widgets to send mouse events
static Messages msg_queue; // Messages queue
static Messages used_msg_queue; // Messages queue
static base::concurrent_queue<Message*> concurrent_msg_queue;
static Filters msg_filters[NFILTERS]; // Filters for every enqueued message
static int filter_locks = 0;
static Widget* focus_widget; // The widget with the focus
static Widget* mouse_widget; // The widget with the mouse
static Widget* capture_widget; // The widget that captures the mouse
static bool first_time = true; // true when we don't enter in poll yet
// Don't adjust window positions automatically when it's false. Used
// when Screen/UI scaling is changed to avoid adjusting windows as
// when the os::Display is resized by the user.
static bool auto_window_adjustment = true;
// Keyboard focus movement stuff
inline bool does_accept_focus(Widget* widget)
{
return ((((widget)->flags() & (FOCUS_STOP |
DISABLED |
HIDDEN |
DECORATIVE)) == FOCUS_STOP) &&
((widget)->isVisible()));
}
static int count_widgets_accept_focus(Widget* widget);
static bool child_accept_focus(Widget* widget, bool first);
static Widget* next_widget(Widget* widget);
static int cmp_left(Widget* widget, int x, int y);
static int cmp_right(Widget* widget, int x, int y);
static int cmp_up(Widget* widget, int x, int y);
static int cmp_down(Widget* widget, int x, int y);
namespace {
class LockFilters {
public:
LockFilters() {
++filter_locks;
}
~LockFilters() {
ASSERT(filter_locks > 0);
--filter_locks;
if (filter_locks == 0) {
// Clear empty filters
for (Filters& msg_filter : msg_filters) {
for (auto it = msg_filter.begin(); it != msg_filter.end(); ) {
Filter* filter = *it;
if (filter->widget == nullptr) {
delete filter;
it = msg_filter.erase(it);
}
else {
++it;
}
}
}
}
}
};
} // anonymous namespace
// static
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());
}
Manager::Manager()
: Widget(kManagerWidget)
, m_display(nullptr)
, m_eventQueue(nullptr)
, m_lockedWindow(nullptr)
, m_mouseButton(kButtonNone)
{
#ifdef DEBUG_UI_THREADS
ASSERT(!manager_thread);
manager_thread = base::this_thread::native_id();
#endif
if (!m_defaultManager) {
// Empty lists
ASSERT(msg_queue.empty());
mouse_widgets_list.clear();
// Reset variables
focus_widget = nullptr;
mouse_widget = nullptr;
capture_widget = nullptr;
}
setBounds(gfx::Rect(0, 0, ui::display_w(), ui::display_h()));
setVisible(true);
m_dirtyRegion = bounds();
// Default manager is the first one (and is always visible).
if (!m_defaultManager)
m_defaultManager = this;
}
Manager::~Manager()
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
// There are some messages in queue? Dispatch everything.
dispatchMessages();
collectGarbage();
// Finish the main manager.
if (m_defaultManager == this) {
// No more cursor
set_mouse_cursor(kNoCursor);
// Destroy timers
ASSERT(!Timer::haveTimers());
// Destroy filters
#ifdef _DEBUG
for (Filters& msg_filter : msg_filters)
ASSERT(msg_filter.empty());
#endif
// No more default manager
m_defaultManager = nullptr;
// Shutdown system
ASSERT(msg_queue.empty());
mouse_widgets_list.clear();
}
}
void Manager::setDisplay(os::Display* display)
{
base::ScopedValue<bool> lock(
auto_window_adjustment, false,
auto_window_adjustment);
m_display = display;
m_eventQueue = os::instance()->eventQueue();
onNewDisplayConfiguration();
}
void Manager::run()
{
MessageLoop loop(this);
if (first_time) {
first_time = false;
Manager::getDefault()->invalidate();
set_mouse_cursor(kArrowCursor);
}
while (!children().empty())
loop.pumpMessages();
}
void Manager::flipDisplay()
{
if (!m_display)
return;
OverlayManager* overlays = OverlayManager::instance();
update_cursor_overlay();
// Draw overlays.
overlays->drawOverlays();
// Invalidate the dirty region in the laf::os::Display (the real OS window).
m_dirtyRegion.createIntersection(
m_dirtyRegion,
gfx::Region(gfx::Rect(0, 0, ui::display_w(), ui::display_h())));
if (!m_dirtyRegion.isEmpty()) {
m_display->invalidateRegion(m_dirtyRegion);
m_dirtyRegion.clear();
}
}
bool Manager::generateMessages()
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
// First check: there are windows to manage?
if (children().empty())
return false;
// Generate messages from other threads
if (!concurrent_msg_queue.empty()) {
Message* msg = nullptr;
while (concurrent_msg_queue.try_pop(msg))
msg_queue.push_back(msg);
}
// Generate messages from OS input
generateMessagesFromOSEvents();
// Generate messages for timers
Timer::pollTimers();
// Returns true if we have to dispatch messages (if the redraw was
// delayed, we have to pump messages because there is where paint
// messages are flushed)
if (!msg_queue.empty() || redrawState != RedrawState::Normal)
return true;
else
return false;
}
void Manager::generateSetCursorMessage(const gfx::Point& mousePos,
KeyModifiers modifiers,
PointerType pointerType)
{
if (get_mouse_cursor() == kOutsideDisplay)
return;
Widget* dst = (capture_widget ? capture_widget: mouse_widget);
if (dst)
enqueueMessage(
newMouseMessage(
kSetCursorMessage, dst,
mousePos,
pointerType,
m_mouseButton,
modifiers));
else
set_mouse_cursor(kArrowCursor);
}
static MouseButton mouse_button_from_os_to_ui(const os::Event& osEvent)
{
static_assert((int)os::Event::NoneButton == (int)ui::kButtonNone &&
(int)os::Event::LeftButton == (int)ui::kButtonLeft &&
(int)os::Event::RightButton == (int)ui::kButtonRight &&
(int)os::Event::MiddleButton == (int)ui::kButtonMiddle &&
(int)os::Event::X1Button == (int)ui::kButtonX1 &&
(int)os::Event::X2Button == (int)ui::kButtonX2,
"Mouse button constants do not match");
return (MouseButton)osEvent.button();
}
void Manager::generateMessagesFromOSEvents()
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
os::Event lastMouseMoveEvent;
// Events from laf-os
os::Event osEvent;
for (;;) {
// TODO Add timers to laf::os library so we can wait for then in
// the OS message loop.
bool canWait = (msg_queue.empty() &&
redrawState == RedrawState::Normal &&
!Timer::haveRunningTimers());
if (canWait && used_msg_queue.empty())
collectGarbage();
#if _DEBUG
else if (!m_garbage.empty()) {
GARBAGE_TRACE("collectGarbage() wasn't called #objects=%d"
" (msg_queue=%d used_msg_queue=%d redrawState=%d runningTimers=%d)\n",
int(m_garbage.size()),
msg_queue.size(),
used_msg_queue.size(),
int(redrawState),
Timer::haveRunningTimers());
}
#endif
m_eventQueue->getEvent(osEvent, canWait);
if (osEvent.type() == os::Event::None)
break;
switch (osEvent.type()) {
case os::Event::CloseDisplay: {
Message* msg = new Message(kCloseDisplayMessage);
msg->setRecipient(this);
msg->setPropagateToChildren(true);
enqueueMessage(msg);
break;
}
case os::Event::ResizeDisplay: {
Message* msg = new Message(kResizeDisplayMessage);
msg->setRecipient(this);
msg->setPropagateToChildren(true);
enqueueMessage(msg);
break;
}
case os::Event::DropFiles: {
Message* msg = new DropFilesMessage(osEvent.files());
msg->setRecipient(this);
enqueueMessage(msg);
break;
}
case os::Event::KeyDown:
case os::Event::KeyUp: {
Message* msg = new KeyMessage(
(osEvent.type() == os::Event::KeyDown ?
kKeyDownMessage:
kKeyUpMessage),
osEvent.scancode(),
osEvent.modifiers(),
osEvent.unicodeChar(),
osEvent.repeat());
if (osEvent.isDeadKey())
static_cast<KeyMessage*>(msg)->setDeadKey(true);
broadcastKeyMsg(msg);
enqueueMessage(msg);
break;
}
case os::Event::MouseEnter: {
_internal_set_mouse_position(osEvent.position());
set_mouse_cursor(kArrowCursor);
lastMouseMoveEvent = osEvent;
break;
}
case os::Event::MouseLeave: {
set_mouse_cursor(kOutsideDisplay);
setMouse(nullptr);
_internal_no_mouse_position();
// To avoid calling kSetCursorMessage when the mouse leaves
// the window.
lastMouseMoveEvent = os::Event();
break;
}
case os::Event::MouseMove: {
_internal_set_mouse_position(osEvent.position());
handleMouseMove(
osEvent.position(),
osEvent.modifiers(),
osEvent.pointerType(),
osEvent.pressure());
lastMouseMoveEvent = osEvent;
break;
}
case os::Event::MouseDown: {
handleMouseDown(
osEvent.position(),
m_mouseButton = mouse_button_from_os_to_ui(osEvent),
osEvent.modifiers(),
osEvent.pointerType());
break;
}
case os::Event::MouseUp: {
handleMouseUp(
osEvent.position(),
mouse_button_from_os_to_ui(osEvent),
osEvent.modifiers(),
osEvent.pointerType());
m_mouseButton = kButtonNone;
break;
}
case os::Event::MouseDoubleClick: {
handleMouseDoubleClick(
osEvent.position(),
m_mouseButton = mouse_button_from_os_to_ui(osEvent),
osEvent.modifiers(),
osEvent.pointerType());
break;
}
case os::Event::MouseWheel: {
handleMouseWheel(osEvent.position(),
osEvent.modifiers(),
osEvent.pointerType(),
osEvent.wheelDelta(),
osEvent.preciseWheel());
break;
}
case os::Event::TouchMagnify: {
_internal_set_mouse_position(osEvent.position());
handleTouchMagnify(osEvent.position(),
osEvent.modifiers(),
osEvent.magnification());
break;
}
case os::Event::Callback: {
// Call from the UI thread
osEvent.execCallback();
break;
}
}
}
// Generate just one kSetCursorMessage for the last mouse position
if (lastMouseMoveEvent.type() != os::Event::None) {
osEvent = lastMouseMoveEvent;
generateSetCursorMessage(osEvent.position(),
osEvent.modifiers(),
osEvent.pointerType());
}
}
void Manager::handleMouseMove(const gfx::Point& mousePos,
const KeyModifiers modifiers,
const PointerType pointerType,
const float pressure)
{
// Get the list of widgets to send mouse messages.
mouse_widgets_list.clear();
broadcastMouseMessage(mouse_widgets_list);
// Get the widget under the mouse
Widget* widget = nullptr;
for (auto mouseWidget : mouse_widgets_list) {
widget = mouseWidget->pick(mousePos);
if (widget) {
// Get the first ancestor of the picked widget that doesn't
// ignore mouse events.
while (widget && widget->hasFlags(IGNORE_MOUSE))
widget = widget->parent();
break;
}
}
// Fixup "mouse" flag
if (widget != mouse_widget) {
if (!widget)
freeMouse();
else
setMouse(widget);
}
// Send the mouse movement message
Widget* dst = (capture_widget ? capture_widget: mouse_widget);
enqueueMessage(
newMouseMessage(
kMouseMoveMessage, dst,
mousePos,
pointerType,
m_mouseButton,
modifiers,
gfx::Point(0, 0),
false,
pressure));
}
void Manager::handleMouseDown(const gfx::Point& mousePos,
MouseButton mouseButton,
KeyModifiers modifiers,
PointerType pointerType)
{
handleWindowZOrder();
enqueueMessage(
newMouseMessage(
kMouseDownMessage,
(capture_widget ? capture_widget: mouse_widget),
mousePos,
pointerType,
mouseButton,
modifiers));
}
void Manager::handleMouseUp(const gfx::Point& mousePos,
MouseButton mouseButton,
KeyModifiers modifiers,
PointerType pointerType)
{
enqueueMessage(
newMouseMessage(
kMouseUpMessage,
(capture_widget ? capture_widget: mouse_widget),
mousePos,
pointerType,
mouseButton,
modifiers));
}
void Manager::handleMouseDoubleClick(const gfx::Point& mousePos,
MouseButton mouseButton,
KeyModifiers modifiers,
PointerType pointerType)
{
Widget* dst = (capture_widget ? capture_widget: mouse_widget);
if (dst) {
enqueueMessage(
newMouseMessage(
kDoubleClickMessage,
dst, mousePos, pointerType,
mouseButton, modifiers));
}
}
void Manager::handleMouseWheel(const gfx::Point& mousePos,
KeyModifiers modifiers,
PointerType pointerType,
const gfx::Point& wheelDelta,
bool preciseWheel)
{
enqueueMessage(newMouseMessage(
kMouseWheelMessage,
(capture_widget ? capture_widget: mouse_widget),
mousePos, pointerType, m_mouseButton, modifiers,
wheelDelta, preciseWheel));
}
void Manager::handleTouchMagnify(const gfx::Point& mousePos,
const KeyModifiers modifiers,
const double magnification)
{
Widget* widget = (capture_widget ? capture_widget: mouse_widget);
if (widget) {
Message* msg = new TouchMessage(
kTouchMagnifyMessage,
modifiers,
mousePos,
magnification);
msg->setRecipient(widget);
enqueueMessage(msg);
}
}
// Handles Z order: Send the window to top (only when you click in a
// window that aren't the desktop).
void Manager::handleWindowZOrder()
{
if (capture_widget || !mouse_widget)
return;
// The clicked window
Window* window = mouse_widget->window();
Manager* win_manager = (window ? window->manager(): nullptr);
if ((window) &&
// We cannot change Z-order of desktop windows
(!window->isDesktop()) &&
// We cannot change Z order of foreground windows because a
// foreground window can launch other background windows
// which should be kept on top of the foreground one.
(!window->isForeground()) &&
// If the window is not already the top window of the manager.
(window != win_manager->getTopWindow())) {
base::ScopedValue<Widget*> scoped(m_lockedWindow, window, nullptr);
// Put it in the top of the list
win_manager->removeChild(window);
if (window->isOnTop())
win_manager->insertChild(0, window);
else {
int pos = (int)win_manager->children().size();
for (auto it=win_manager->children().rbegin(),
end=win_manager->children().rend();
it != end; ++it) {
if (static_cast<Window*>(*it)->isOnTop())
break;
--pos;
}
win_manager->insertChild(pos, window);
}
window->invalidate();
}
// Put the focus
setFocus(mouse_widget);
}
void Manager::dispatchMessages()
{
// Send messages in the queue (mouse/key/timer/etc. events) This
// might change the state of widgets, etc. In case pumpQueue()
// returns a number greater than 0, it means that we've processed
// some messages, so we've to redraw the screen.
if (pumpQueue() > 0 || redrawState == RedrawState::RedrawDelayed) {
// If a window has just been closed with Manager::_closeWindow()
// after processing messages, we'll wait the next event generation
// to process painting events (so the manager doesn't lost the
// DIRTY flag right now).
if (redrawState == RedrawState::AWindowHasJustBeenClosed) {
redrawState = RedrawState::RedrawDelayed;
}
else {
if (redrawState == RedrawState::RedrawDelayed)
redrawState = RedrawState::Normal;
// Generate and send just kPaintMessages with the latest UI state.
flushRedraw();
pumpQueue();
// Flip the back-buffer to the real display.
flipDisplay();
}
}
}
void Manager::addToGarbage(Widget* widget)
{
ASSERT(widget);
m_garbage.push_back(widget);
}
void Manager::enqueueMessage(Message* msg)
{
ASSERT(msg);
if (is_ui_thread())
msg_queue.push_back(msg);
else
concurrent_msg_queue.push(msg);
}
Window* Manager::getTopWindow()
{
return static_cast<Window*>(UI_FIRST_WIDGET(children()));
}
Window* Manager::getForegroundWindow()
{
for (auto child : children()) {
Window* window = static_cast<Window*>(child);
if (window->isForeground() ||
window->isDesktop())
return window;
}
return nullptr;
}
Widget* Manager::getFocus()
{
return focus_widget;
}
Widget* Manager::getMouse()
{
return mouse_widget;
}
Widget* Manager::getCapture()
{
return capture_widget;
}
void Manager::setFocus(Widget* widget)
{
if ((focus_widget != widget)
&& (!(widget)
|| (!(widget->hasFlags(DISABLED))
&& !(widget->hasFlags(HIDDEN))
&& !(widget->hasFlags(DECORATIVE))
&& someParentIsFocusStop(widget)))) {
Widget* commonAncestor = findLowestCommonAncestor(focus_widget, widget);
// Fetch the focus
if (focus_widget && focus_widget != commonAncestor) {
auto msg = new Message(kFocusLeaveMessage);
msg->setRecipient(focus_widget);
msg->setPropagateToParent(true);
msg->setCommonAncestor(commonAncestor);
enqueueMessage(msg);
// Remove HAS_FOCUS from all hierarchy
auto a = focus_widget;
while (a && a != commonAncestor) {
a->disableFlags(HAS_FOCUS);
a = a->parent();
}
}
// Put the focus
focus_widget = widget;
if (widget) {
auto msg = new Message(kFocusEnterMessage);
msg->setRecipient(widget);
msg->setPropagateToParent(true);
msg->setCommonAncestor(commonAncestor);
enqueueMessage(msg);
// Add HAS_FOCUS to all hierarchy
auto a = focus_widget;
while (a && a != commonAncestor) {
if (a->hasFlags(FOCUS_STOP))
a->enableFlags(HAS_FOCUS);
a = a->parent();
}
}
}
}
void Manager::setMouse(Widget* widget)
{
#ifdef REPORT_EVENTS
std::cout << "Manager::setMouse ";
if (widget) {
std::cout << typeid(*widget).name();
if (!widget->id().empty())
std::cout << " (" << widget->id() << ")";
}
else {
std::cout << "null";
}
std::cout << std::endl;
#endif
if ((mouse_widget != widget) && (!capture_widget)) {
Widget* commonAncestor = findLowestCommonAncestor(mouse_widget, widget);
// 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) {
auto msg = newMouseMessage(
kMouseEnterMessage, nullptr,
get_mouse_position(),
PointerType::Unknown,
m_mouseButton,
kKeyUninitializedModifier);
msg->setRecipient(widget);
msg->setPropagateToParent(true);
msg->setCommonAncestor(commonAncestor);
enqueueMessage(msg);
generateSetCursorMessage(get_mouse_position(),
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();
}
}
}
}
void Manager::setCapture(Widget* widget)
{
// To set the capture, we set first the mouse_widget (because
// mouse_widget shouldn't be != capture_widget)
setMouse(widget);
widget->enableFlags(HAS_CAPTURE);
capture_widget = widget;
m_display->captureMouse();
}
// Sets the focus to the "magnetic" widget inside the window
void Manager::attractFocus(Widget* widget)
{
// Get the magnetic widget
Widget* magnet = findMagneticWidget(widget->window());
// If magnetic widget exists and it doesn't have the focus
if (magnet && !magnet->hasFocus())
setFocus(magnet);
}
void Manager::focusFirstChild(Widget* widget)
{
for (Widget* it=widget->window(); it; it=next_widget(it)) {
if (does_accept_focus(it) && !(child_accept_focus(it, true))) {
setFocus(it);
break;
}
}
}
void Manager::freeFocus()
{
setFocus(nullptr);
}
void Manager::freeMouse()
{
setMouse(nullptr);
}
void Manager::freeCapture()
{
if (capture_widget) {
capture_widget->disableFlags(HAS_CAPTURE);
capture_widget = nullptr;
m_display->releaseMouse();
}
}
void Manager::freeWidget(Widget* widget)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
if (widget->hasFocus() || (widget == focus_widget))
freeFocus();
// We shouldn't free widgets that are locked, it means, widgets that
// will be re-added soon (e.g. when the stack of windows is
// temporarily modified).
if (m_lockedWindow == widget)
return;
// Break any relationship with the GUI manager
if (widget->hasCapture() || (widget == capture_widget))
freeCapture();
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);
ASSERT(!Manager::widgetAssociatedToManager(widget));
}
void Manager::removeMessagesFor(Widget* widget)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
for (Message* msg : msg_queue)
msg->removeRecipient(widget);
for (Message* msg : used_msg_queue)
msg->removeRecipient(widget);
}
void Manager::removeMessagesFor(Widget* widget, MessageType type)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
for (Message* msg : msg_queue)
if (msg->type() == type)
msg->removeRecipient(widget);
for (Message* msg : used_msg_queue)
if (msg->type() == type)
msg->removeRecipient(widget);
}
void Manager::removeMessagesForTimer(Timer* timer)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
for (auto it=msg_queue.begin(); it != msg_queue.end(); ) {
Message* msg = *it;
if (msg->type() == kTimerMessage &&
static_cast<TimerMessage*>(msg)->timer() == timer) {
delete msg;
it = msg_queue.erase(it);
}
else
++it;
}
}
void Manager::addMessageFilter(int message, Widget* widget)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
LockFilters lock;
int c = message;
if (c >= kFirstRegisteredMessage)
c = kFirstRegisteredMessage;
msg_filters[c].push_back(new Filter(message, widget));
}
void Manager::removeMessageFilter(int message, Widget* widget)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
LockFilters lock;
int c = message;
if (c >= kFirstRegisteredMessage)
c = kFirstRegisteredMessage;
Filters& msg_filter = msg_filters[c];
for (Filter* filter : msg_filter) {
if (filter->widget == widget)
filter->widget = nullptr;
}
}
void Manager::removeMessageFilterFor(Widget* widget)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
LockFilters lock;
for (Filters& msg_filter : msg_filters) {
for (Filter* filter : msg_filter) {
if (filter->widget == widget)
filter->widget = nullptr;
}
}
}
bool Manager::isFocusMovementMessage(Message* msg)
{
if (msg->type() != kKeyDownMessage &&
msg->type() != kKeyUpMessage)
return false;
switch (static_cast<KeyMessage*>(msg)->scancode()) {
case kKeyTab:
case kKeyLeft:
case kKeyRight:
case kKeyUp:
case kKeyDown:
return true;
}
return false;
}
void Manager::dirtyRect(const gfx::Rect& bounds)
{
m_dirtyRegion.createUnion(m_dirtyRegion, gfx::Region(bounds));
}
// Configures the window for begin the loop
void Manager::_openWindow(Window* window)
{
// Free all widgets of special states.
if (window->isWantFocus()) {
freeCapture();
freeMouse();
freeFocus();
}
// Add the window to manager.
insertChild(0, window);
// Broadcast the open message.
{
std::unique_ptr<Message> msg(new Message(kOpenMessage));
window->sendMessage(msg.get());
}
// Relayout
window->layout();
// Dirty the entire window and show it
window->setVisible(true);
window->invalidate();
// Attract the focus to the magnetic widget...
// 1) get the magnetic widget
Widget* magnet = findMagneticWidget(window);
// 2) if magnetic widget exists and it doesn't have the focus
if (magnet && !magnet->hasFocus())
setFocus(magnet);
// 3) if not, put the focus in the first child
else if (window->isWantFocus())
focusFirstChild(window);
// Update mouse widget (as it can be a widget below the
// recently opened window).
Widget* widget = pick(ui::get_mouse_position());
if (widget)
setMouse(widget);
}
void Manager::_closeWindow(Window* window, bool redraw_background)
{
if (!hasChild(window))
return;
gfx::Region reg1;
if (redraw_background)
window->getRegion(reg1);
// Close all windows to this desktop
if (window->isDesktop()) {
while (!children().empty()) {
Window* child = static_cast<Window*>(children().front());
if (child == window)
break;
else {
gfx::Region reg2;
window->getRegion(reg2);
reg1.createUnion(reg1, reg2);
_closeWindow(child, false);
}
}
}
// Free all widgets of special states.
if (capture_widget && capture_widget->window() == window)
freeCapture();
if (mouse_widget && mouse_widget->window() == window)
freeMouse();
if (focus_widget && focus_widget->window() == window)
freeFocus();
// Hide window.
window->setVisible(false);
// Close message.
{
std::unique_ptr<Message> msg(new Message(kCloseMessage));
window->sendMessage(msg.get());
}
// Update manager list stuff.
removeChild(window);
// Redraw background.
invalidateRegion(reg1);
// Update mouse widget (as it can be a widget below the
// recently closed window).
Widget* widget = pick(ui::get_mouse_position());
if (widget)
setMouse(widget);
redrawState = RedrawState::AWindowHasJustBeenClosed;
}
bool Manager::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kPaintMessage:
// Draw nothing (the manager should be invisible). On Windows,
// after closing the main window, the manager will not refresh
// the os::Display content, so we'll avoid a gray background
// (the last main window content is kept until the Display is
// finally closed.)
return true;
case kResizeDisplayMessage:
onNewDisplayConfiguration();
break;
case kKeyDownMessage:
case kKeyUpMessage: {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
keymsg->setPropagateToChildren(true);
keymsg->setPropagateToParent(false);
// Continue sending the message to the children of all windows
// (until a desktop or foreground window).
Window* win = nullptr;
for (auto manchild : children()) {
win = static_cast<Window*>(manchild);
// Send to the window.
for (auto winchild : win->children())
if (winchild->sendMessage(msg))
return true;
if (win->isForeground() ||
win->isDesktop())
break;
}
// Check the focus movement for foreground (non-desktop) windows.
if (win && win->isForeground()) {
if (msg->type() == kKeyDownMessage)
processFocusMovementMessage(msg);
return true;
}
else
return false;
}
}
return Widget::onProcessMessage(msg);
}
void Manager::onResize(ResizeEvent& ev)
{
gfx::Rect old_pos = bounds();
gfx::Rect new_pos = ev.bounds();
setBoundsQuietly(new_pos);
// The whole manager area is invalid now.
m_invalidRegion = gfx::Region(new_pos);
const int dx = new_pos.x - old_pos.x;
const int dy = new_pos.y - old_pos.y;
const int dw = new_pos.w - old_pos.w;
const int dh = new_pos.h - old_pos.h;
for (auto child : children()) {
Window* window = static_cast<Window*>(child);
if (window->isDesktop()) {
window->setBounds(new_pos);
break;
}
gfx::Rect bounds = window->bounds();
const int cx = bounds.x+bounds.w/2;
const int cy = bounds.y+bounds.h/2;
if (auto_window_adjustment) {
if (cx > old_pos.x+old_pos.w*3/5) {
bounds.x += dw;
}
else if (cx > old_pos.x+old_pos.w*2/5) {
bounds.x += dw / 2;
}
if (cy > old_pos.y+old_pos.h*3/5) {
bounds.y += dh;
}
else if (cy > old_pos.y+old_pos.h*2/5) {
bounds.y += dh / 2;
}
bounds.offset(dx, dy);
}
else {
if (bounds.x2() > new_pos.x2()) {
bounds.x = new_pos.x2() - bounds.w;
}
if (bounds.y2() > new_pos.y2()) {
bounds.y = new_pos.y2() - bounds.h;
}
}
window->setBounds(bounds);
}
}
void Manager::onBroadcastMouseMessage(WidgetsList& targets)
{
// Ask to the first window in the "children" list to know how to
// propagate mouse messages.
Widget* widget = UI_FIRST_WIDGET(children());
if (widget)
widget->broadcastMouseMessage(targets);
}
void Manager::onInitTheme(InitThemeEvent& ev)
{
Widget::onInitTheme(ev);
// Remap the windows
const int oldUIScale = ui::details::old_guiscale();
const int newUIScale = ui::guiscale();
for (auto widget : children()) {
if (widget->type() == kWindowWidget) {
auto window = static_cast<Window*>(widget);
if (window->isDesktop()) {
window->layout();
}
else {
gfx::Rect bounds = window->bounds();
bounds *= newUIScale;
bounds /= oldUIScale;
bounds.x = base::clamp(bounds.x, 0, m_display->width() - bounds.w);
bounds.y = base::clamp(bounds.y, 0, m_display->height() - bounds.h);
window->setBounds(bounds);
}
}
}
}
LayoutIO* Manager::onGetLayoutIO()
{
return nullptr;
}
void Manager::onNewDisplayConfiguration()
{
if (m_display) {
int w = m_display->width() / m_display->scale();
int h = m_display->height() / m_display->scale();
if ((bounds().w != w ||
bounds().h != h)) {
setBounds(gfx::Rect(0, 0, w, h));
}
}
_internal_set_mouse_display(m_display);
invalidate();
flushRedraw();
}
void Manager::onSizeHint(SizeHintEvent& ev)
{
int w = 0, h = 0;
if (!parent()) { // hasn' parent?
w = bounds().w;
h = bounds().h;
}
else {
gfx::Rect pos = parent()->childrenBounds();
for (auto child : children()) {
gfx::Rect cpos = child->bounds();
pos = pos.createUnion(cpos);
}
w = pos.w;
h = pos.h;
}
ev.setSizeHint(gfx::Size(w, h));
}
int Manager::pumpQueue()
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
#ifdef LIMIT_DISPATCH_TIME
base::tick_t t = base::current_tick();
#endif
int count = 0; // Number of processed messages
while (!msg_queue.empty()) {
#ifdef LIMIT_DISPATCH_TIME
if (base::current_tick()-t > 250)
break;
#endif
// The message to process
auto it = msg_queue.begin();
Message* msg = *it;
ASSERT(msg);
// Move the message from msg_queue to used_msg_queue
msg_queue.erase(it);
auto eraseIt = used_msg_queue.insert(used_msg_queue.end(), msg);
// Call Timer::tick() if this is a tick message.
if (msg->type() == kTimerMessage) {
ASSERT(static_cast<TimerMessage*>(msg)->timer() != nullptr);
static_cast<TimerMessage*>(msg)->timer()->tick();
}
bool done = false;
// Send this message to filters
{
Filters& msg_filter = msg_filters[std::min(msg->type(), kFirstRegisteredMessage)];
if (!msg_filter.empty()) {
LockFilters lock;
for (Filter* filter : msg_filter) {
// The widget can be nullptr in case that the filter was
// "pre-removed" (it'll finally erased from the
// msg_filter list from ~LockFilters()).
if (filter->widget != nullptr &&
msg->type() == filter->message) {
msg->setFromFilter(true);
done = sendMessageToWidget(msg, filter->widget);
msg->setFromFilter(false);
if (done)
break;
}
}
}
}
if (!done) {
// Then send the message to its recipient
if (Widget* widget = msg->recipient())
done = sendMessageToWidget(msg, widget);
}
// Remove the message from the used_msg_queue
used_msg_queue.erase(eraseIt);
// Destroy the message
delete msg;
++count;
}
return count;
}
bool Manager::sendMessageToWidget(Message* msg, Widget* widget)
{
#ifdef DEBUG_UI_THREADS
ASSERT(manager_thread == base::this_thread::native_id());
#endif
if (!widget)
return false;
#ifdef REPORT_EVENTS
{
static const char* msg_name[] = {
"kOpenMessage",
"kCloseMessage",
"kCloseDisplayMessage",
"kResizeDisplayMessage",
"kPaintMessage",
"kTimerMessage",
"kDropFilesMessage",
"kWinMoveMessage",
"kKeyDownMessage",
"kKeyUpMessage",
"kFocusEnterMessage",
"kFocusLeaveMessage",
"kMouseDownMessage",
"kMouseUpMessage",
"kDoubleClickMessage",
"kMouseEnterMessage",
"kMouseLeaveMessage",
"kMouseMoveMessage",
"kSetCursorMessage",
"kMouseWheelMessage",
"kTouchMagnifyMessage",
};
static_assert(kOpenMessage == 0 &&
kTouchMagnifyMessage == sizeof(msg_name)/sizeof(const char*)-1,
"MessageType enum has changed");
const char* string =
(msg->type() >= 0 &&
msg->type() < sizeof(msg_name)/sizeof(const char*)) ?
msg_name[msg->type()]: "Unknown";
std::cout << "Event " << msg->type() << " (" << string << ") "
<< "for " << ((void*)widget) << std::flush;
std::cout << " (" << typeid(*widget).name() << ")";
if (!widget->id().empty())
std::cout << " (" << widget->id() << ")";
std::cout << std::endl;
}
#endif
bool used = false;
// We need to configure the clip region for paint messages
// before we call Widget::sendMessage().
if (msg->type() == kPaintMessage) {
if (widget->hasFlags(HIDDEN))
return false;
PaintMessage* paintMsg = static_cast<PaintMessage*>(msg);
// Restore overlays in the region that we're going to paint.
OverlayManager::instance()->restoreOverlappedAreas(paintMsg->rect());
os::Surface* surface = m_display->getSurface();
surface->saveClip();
if (surface->clipRect(paintMsg->rect())) {
#ifdef REPORT_EVENTS
std::cout << " - clip("
<< paintMsg->rect().x << ", "
<< paintMsg->rect().y << ", "
<< paintMsg->rect().w << ", "
<< paintMsg->rect().h << ")"
<< std::endl;
#endif
#ifdef DEBUG_PAINT_EVENTS
{
os::SurfaceLock lock(surface);
surface->fillRect(gfx::rgba(0, 0, 255), paintMsg->rect());
}
if (m_display) {
m_display->invalidateRegion(
gfx::Region(gfx::Rect(0, 0, display_w(), display_h())));
// TODO m_display->update() ??
}
base::this_thread::sleep_for(0.002);
#endif
if (surface) {
// Call the message handler
used = widget->sendMessage(msg);
// Restore clip region for paint messages.
surface->restoreClip();
}
}
// As this kPaintMessage's rectangle was updated, we can
// remove it from "m_invalidRegion".
m_invalidRegion -= gfx::Region(paintMsg->rect());
}
else {
// Call the message handler
used = widget->sendMessage(msg);
}
return used;
}
// It's like Widget::onInvalidateRegion() but optimized for the
// Manager (as we know that all children in a Manager will be windows,
// we can use this knowledge to avoid some calculations).
void Manager::onInvalidateRegion(const gfx::Region& region)
{
if (!isVisible() || region.contains(bounds()) == gfx::Region::Out)
return;
// Intersect only with manager bounds, we don't need to use
// getDrawableRegion() because each window will be processed in the
// following for() loop (and it's highly probable that a desktop
// Window will use the whole manager portion anyway).
gfx::Region reg1;
reg1.createIntersection(region, gfx::Region(bounds()));
// Redraw windows from top to background.
bool withDesktop = false;
for (auto child : children()) {
ASSERT(dynamic_cast<Window*>(child));
ASSERT(child->type() == kWindowWidget);
Window* window = static_cast<Window*>(child);
// Invalidate regions of this window
window->invalidateRegion(reg1);
// There is desktop?
if (window->isDesktop()) {
withDesktop = true;
break; // Work done
}
// Clip this window area for the next window.
gfx::Region reg2;
window->getRegion(reg2);
reg1.createSubtraction(reg1, reg2);
}
// Invalidate areas outside windows (only when there are not a
// desktop window).
if (!withDesktop) {
// TODO we should be able to modify m_updateRegion directly here,
// so we avoid the getDrawableRegion() call from
// Widget::onInvalidateRegion().
Widget::onInvalidateRegion(reg1);
}
}
LayoutIO* Manager::getLayoutIO()
{
return onGetLayoutIO();
}
void Manager::collectGarbage()
{
if (m_garbage.empty())
return;
GARBAGE_TRACE("Manager::collectGarbage() #objects=%d\n", int(m_garbage.size()));
for (auto widget : m_garbage) {
GARBAGE_TRACE(" -> deleting %s %s ---\n",
typeid(*widget).name(),
widget->id().c_str());
delete widget;
}
m_garbage.clear();
}
/**********************************************************************
Internal routines
**********************************************************************/
// static
Widget* Manager::findLowestCommonAncestor(Widget* a, Widget* b)
{
if (!a || !b)
return nullptr;
Widget* u = a;
Widget* v = b;
int aDepth = 0;
int bDepth = 0;
while (u) {
++aDepth;
u = u->parent();
}
while (v) {
++bDepth;
v = v->parent();
}
while (aDepth > bDepth) {
--aDepth;
a = a->parent();
}
while (bDepth > aDepth) {
--bDepth;
b = b->parent();
}
while (a && b) {
if (a == b)
break;
a = a->parent();
b = b->parent();
}
return a;
}
// static
bool Manager::someParentIsFocusStop(Widget* widget)
{
if (widget->isFocusStop())
return true;
if (widget->parent())
return someParentIsFocusStop(widget->parent());
else
return false;
}
// static
Widget* Manager::findMagneticWidget(Widget* widget)
{
Widget* found;
for (auto child : widget->children()) {
found = findMagneticWidget(child);
if (found)
return found;
}
if (widget->isFocusMagnet())
return widget;
else
return nullptr;
}
// static
Message* Manager::newMouseMessage(
MessageType type,
Widget* widget,
const gfx::Point& mousePos,
PointerType pointerType,
MouseButton button,
KeyModifiers modifiers,
const gfx::Point& wheelDelta,
bool preciseWheel,
float pressure)
{
#ifdef __APPLE__
// Convert Ctrl+left click -> right-click
if (widget &&
widget->isVisible() &&
widget->isEnabled() &&
widget->hasFlags(CTRL_RIGHT_CLICK) &&
(modifiers & kKeyCtrlModifier) &&
(button == kButtonLeft)) {
modifiers = KeyModifiers(int(modifiers) & ~int(kKeyCtrlModifier));
button = kButtonRight;
}
#endif
Message* msg = new MouseMessage(
type, pointerType, button, modifiers, mousePos,
wheelDelta, preciseWheel, pressure);
if (widget)
msg->setRecipient(widget);
return msg;
}
// static
void Manager::broadcastKeyMsg(Message* msg)
{
// Send the message to the widget with capture
if (capture_widget) {
msg->setRecipient(capture_widget);
}
// Send the msg to the focused widget
else if (focus_widget) {
msg->setRecipient(focus_widget);
}
// Finally, send the message to the manager, it'll know what to do
else {
msg->setRecipient(this);
}
}
/***********************************************************************
Focus Movement
***********************************************************************/
// TODO rewrite this function, it is based in an old code from the
// Allegro library GUI code
bool Manager::processFocusMovementMessage(Message* msg)
{
int (*cmp)(Widget*, int, int) = nullptr;
Widget* focus = nullptr;
Widget* it;
bool ret = false;
Window* window = nullptr;
int c, count;
// Who have the focus
if (focus_widget) {
window = focus_widget->window();
}
else if (!this->children().empty()) {
window = this->getTopWindow();
}
if (!window)
return false;
// How many children want the focus in this window?
count = count_widgets_accept_focus(window);
// One at least
if (count > 0) {
std::vector<Widget*> list(count);
c = 0;
// Create a list of possible candidates to receive the focus
for (it=focus_widget; it; it=next_widget(it)) {
if (does_accept_focus(it) && !(child_accept_focus(it, true)))
list[c++] = it;
}
for (it=window; it != focus_widget; it=next_widget(it)) {
if (does_accept_focus(it) && !(child_accept_focus(it, true)))
list[c++] = it;
}
// Depending on the pressed key...
switch (static_cast<KeyMessage*>(msg)->scancode()) {
case kKeyTab:
// Reverse tab
if ((msg->modifiers() & (kKeyShiftModifier | kKeyCtrlModifier | kKeyAltModifier)) != 0) {
focus = list[count-1];
}
// Normal tab
else if (count > 1) {
focus = list[1];
}
ret = true;
break;
// Arrow keys
case kKeyLeft: if (!cmp) cmp = cmp_left;
case kKeyRight: if (!cmp) cmp = cmp_right;
case kKeyUp: if (!cmp) cmp = cmp_up;
case kKeyDown: if (!cmp) cmp = cmp_down;
// More than one widget
if (count > 1) {
// Position where the focus come
gfx::Point pt = (focus_widget ? focus_widget->bounds().center():
window->bounds().center());
c = (focus_widget ? 1: 0);
// Rearrange the list
for (int i=c; i<count-1; ++i) {
for (int j=i+1; j<count; ++j) {
// Sort the list in ascending order
if ((*cmp)(list[i], pt.x, pt.y) > (*cmp)(list[j], pt.x, pt.y))
std::swap(list[i], list[j]);
}
}
#ifdef REPORT_FOCUS_MOVEMENT
// Print list of widgets
for (int i=c; i<count-1; ++i) {
TRACE("list[%d] = %d (%s)\n",
i, (*cmp)(list[i], pt.x, pt.y),
typeid(*list[i]).name());
}
#endif
// Check if the new widget to put the focus is not in the wrong way.
if ((*cmp)(list[c], pt.x, pt.y) < std::numeric_limits<int>::max())
focus = list[c];
}
// If only there are one widget, put the focus in this
else
focus = list[0];
ret = true;
break;
}
if ((focus) && (focus != focus_widget))
setFocus(focus);
}
return ret;
}
static int count_widgets_accept_focus(Widget* widget)
{
int count = 0;
for (auto child : widget->children())
count += count_widgets_accept_focus(child);
if ((count == 0) && (does_accept_focus(widget)))
count++;
return count;
}
static bool child_accept_focus(Widget* widget, bool first)
{
for (auto child : widget->children())
if (child_accept_focus(child, false))
return true;
return (first ? false: does_accept_focus(widget));
}
static Widget* next_widget(Widget* widget)
{
if (!widget->children().empty())
return UI_FIRST_WIDGET(widget->children());
while (widget->parent() &&
widget->parent()->type() != kManagerWidget) {
WidgetsList::const_iterator begin = widget->parent()->children().begin();
WidgetsList::const_iterator end = widget->parent()->children().end();
WidgetsList::const_iterator it = std::find(begin, end, widget);
ASSERT(it != end);
if ((it+1) != end)
return *(it+1);
else
widget = widget->parent();
}
return nullptr;
}
static int cmp_left(Widget* widget, int x, int y)
{
int z = x - (widget->bounds().x+widget->bounds().w/2);
if (z <= 0)
return std::numeric_limits<int>::max();
return z + ABS((widget->bounds().y+widget->bounds().h/2) - y) * 8;
}
static int cmp_right(Widget* widget, int x, int y)
{
int z = (widget->bounds().x+widget->bounds().w/2) - x;
if (z <= 0)
return std::numeric_limits<int>::max();
return z + ABS((widget->bounds().y+widget->bounds().h/2) - y) * 8;
}
static int cmp_up(Widget* widget, int x, int y)
{
int z = y - (widget->bounds().y+widget->bounds().h/2);
if (z <= 0)
return std::numeric_limits<int>::max();
return z + ABS((widget->bounds().x+widget->bounds().w/2) - x) * 8;
}
static int cmp_down(Widget* widget, int x, int y)
{
int z = (widget->bounds().y+widget->bounds().h/2) - y;
if (z <= 0)
return std::numeric_limits<int>::max();
return z + ABS((widget->bounds().x+widget->bounds().w/2) - x) * 8;
}
} // namespace ui