Add support for multiple native windows (#139, #250, #962, etc.)

Each ui::Window now can have a related native os::Window. This
connection is done through the ui::Display class added recently in
c3d52f0bbe5242923c9e38495e1be1135ca0934f.
This commit is contained in:
David Capello 2021-03-02 13:50:49 -03:00
parent 74961f58c0
commit 8034b0cbcc
44 changed files with 737 additions and 246 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2018-2020 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2021 Igara Studio S.A. -->
<!-- Copyright (C) 2014-2018 David Capello -->
<preferences>
@ -203,6 +203,7 @@
<option id="api" type="std::string" />
</section>
<section id="experimental" text="Experimental">
<option id="multiple_windows" type="bool" default="true" />
<option id="new_render_engine" type="bool" default="true" />
<option id="new_blend" type="bool" default="true" />
<option id="use_native_clipboard" type="bool" default="true" />

View File

@ -1336,6 +1336,7 @@ disable_extension = &Disable
uninstall_extension = &Uninstall
open_extension_folder = Open &Folder
user_interface = User Interface
multiple_windows = UI with multiple windows
new_blend = New layer blending method
new_render_engine = New render engine for sprite editor
native_clipboard = Use native clipboard

View File

@ -1,5 +1,5 @@
<!-- Aseprite -->
<!-- Copyright (C) 2018-2020 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2021 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<window id="options" text="@.title">
@ -484,6 +484,8 @@
<!-- Experimental -->
<vbox id="section_experimental">
<separator text="@.user_interface" horizontal="true" />
<check id="multiple_windows" text="@.multiple_windows"
pref="experimental.multiple_windows" />
<hbox>
<check text="@.new_render_engine"
pref="experimental.new_render_engine" />

2
laf

@ -1 +1 @@
Subproject commit b2c8b1e63dec366439d78d304f3ce48803eb1fd6
Subproject commit 60d8b87ce826dc52651154bcd90c4251b69cbdb8

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -142,8 +142,9 @@ protected:
updateEditorBoxFromRect();
}
virtual void onBroadcastMouseMessage(WidgetsList& targets) override {
Window::onBroadcastMouseMessage(targets);
virtual void onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets) override {
Window::onBroadcastMouseMessage(screenPos, targets);
// Add the editor as receptor of mouse events too.
targets.push_back(View::getView(m_editor));

View File

@ -550,8 +550,9 @@ private:
return Window::onProcessMessage(msg);
}
void onBroadcastMouseMessage(WidgetsList& targets) override {
Window::onBroadcastMouseMessage(targets);
void onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets) override {
Window::onBroadcastMouseMessage(screenPos, targets);
// Add the editor as receptor of mouse events too.
if (m_editor)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -186,12 +186,16 @@ void EyedropperCommand::onLoadParams(const Params& params)
void EyedropperCommand::onExecute(Context* context)
{
gfx::Point mousePos = ui::get_mouse_position();
Widget* widget = ui::Manager::getDefault()->pick(mousePos);
Widget* widget = ui::Manager::getDefault()->pickFromScreenPos(mousePos);
if (!widget || widget->type() != Editor::Type())
return;
Editor* editor = static_cast<Editor*>(widget);
executeOnMousePos(context, editor, mousePos, !m_background);
executeOnMousePos(
context,
editor,
editor->display()->nativeWindow()->pointFromScreen(mousePos),
!m_background);
}
void EyedropperCommand::executeOnMousePos(Context* context,

View File

@ -74,7 +74,7 @@ public:
gfx::Rect vp = view->viewportBounds();
gfx::Point scroll = view->viewScroll();
m_oldMousePos = ui::get_mouse_position();
m_oldMousePos = mousePosInDisplay();
m_pos.x = -scroll.x + vp.x + editor->padding().x;
m_pos.y = -scroll.y + vp.y + editor->padding().y;

View File

@ -195,8 +195,9 @@ protected:
return Window::onProcessMessage(msg);
}
void onBroadcastMouseMessage(WidgetsList& targets) override {
Window::onBroadcastMouseMessage(targets);
void onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets) override {
Window::onBroadcastMouseMessage(screenPos, targets);
// Add the editor as receptor of mouse events too.
if (m_editor)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
//
// This program is distributed under the terms of
@ -82,7 +82,7 @@ void SelectTileCommand::onExecute(Context* ctx)
{
gfx::Rect gridBounds = writer.site()->gridBounds();
gfx::Point pos = current_editor->screenToEditor(ui::get_mouse_position());
gfx::Point pos = current_editor->screenToEditor(current_editor->mousePosInDisplay());
pos = snap_to_grid(gridBounds, pos, PreferSnapTo::BoxOrigin);
gridBounds.setOrigin(pos);

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -83,7 +84,7 @@ void ZoomCommand::onExecute(Context* context)
gfx::Point mousePos = ui::get_mouse_position();
// Try to use the editor above the mouse.
ui::Widget* pick = ui::Manager::getDefault()->pick(mousePos);
ui::Widget* pick = ui::Manager::getDefault()->pickFromScreenPos(mousePos);
if (pick && pick->type() == Editor::Type())
editor = static_cast<Editor*>(pick);
@ -112,7 +113,8 @@ void ZoomCommand::onExecute(Context* context)
}
editor->setZoomAndCenterInMouse(
zoom, mousePos,
zoom,
editor->display()->nativeWindow()->pointFromScreen(mousePos),
(focus == Focus::Center ? Editor::ZoomBehavior::CENTER:
Editor::ZoomBehavior::MOUSE));
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -71,12 +71,12 @@ bool ColorCurveEditor::onProcessMessage(Message* msg)
switch (static_cast<KeyMessage*>(msg)->scancode()) {
case kKeyInsert: {
addPoint(screenToView(get_mouse_position()));
addPoint(screenToView(mousePosInDisplay()));
break;
}
case kKeyDel: {
if (gfx::Point* point = getClosestPoint(screenToView(get_mouse_position())))
if (gfx::Point* point = getClosestPoint(screenToView(mousePosInDisplay())))
removePoint(*point);
break;
}

View File

@ -219,7 +219,7 @@ int init_module_gui()
Message* msg = new Message(kResizeDisplayMessage);
msg->setDisplay(display);
msg->setRecipient(manager);
msg->setPropagateToChildren(true);
msg->setPropagateToChildren(false);
manager->enqueueMessage(msg);
manager->dispatchMessages();
@ -295,6 +295,8 @@ static void load_gui_config(int& w, int& h, bool& maximized,
h = get_config_int("GfxMode", "Height", defSize.h);
maximized = get_config_bool("GfxMode", "Maximized", false);
windowLayout = get_config_string("GfxMode", "WindowLayout", "");
ui::set_multiple_displays(Preferences::instance().experimental.multipleWindows());
}
static void save_gui_config()
@ -331,21 +333,20 @@ void update_screen_for_document(const Doc* document)
}
}
void load_window_pos(Widget* window, const char* section,
void load_window_pos(Window* window, const char* section,
const bool limitMinSize)
{
Size desktopSize = ui::get_desktop_size();
// Default position
Rect orig_pos = window->bounds();
Rect pos = orig_pos;
Rect origPos = window->bounds();
// Load configurated position
pos = get_config_rect(section, "WindowPos", pos);
Rect pos = get_config_rect(section, "WindowPos", origPos);
if (limitMinSize) {
pos.w = base::clamp(pos.w, orig_pos.w, desktopSize.w);
pos.h = base::clamp(pos.h, orig_pos.h, desktopSize.h);
pos.w = base::clamp(pos.w, origPos.w, desktopSize.w);
pos.h = base::clamp(pos.h, origPos.h, desktopSize.h);
}
else {
pos.w = std::min(pos.w, desktopSize.w);
@ -356,11 +357,36 @@ void load_window_pos(Widget* window, const char* section,
base::clamp(pos.y, 0, desktopSize.h-pos.h)));
window->setBounds(pos);
if (get_multiple_displays()) {
Rect frame = get_config_rect(section, "WindowFrame", gfx::Rect());
if (!frame.isEmpty()) {
// TODO limit window area to current workspace / all available screen limits (?)
window->loadNativeFrame(frame);
}
}
else {
del_config_value(section, "WindowFrame");
}
}
void save_window_pos(Widget* window, const char *section)
void save_window_pos(Window* window, const char *section)
{
set_config_rect(section, "WindowPos", window->bounds());
gfx::Rect rc;
if (!window->lastNativeFrame().isEmpty()) {
os::Window* mainNativeWindow = manager->display()->nativeWindow();
int scale = mainNativeWindow->scale();
rc = window->lastNativeFrame();
set_config_rect(section, "WindowFrame", rc);
rc.offset(-mainNativeWindow->frame().origin());
rc /= scale;
}
else {
rc = window->bounds();
}
set_config_rect(section, "WindowPos", rc);
}
// TODO Replace this with new theme styles
@ -405,12 +431,15 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
switch (msg->type()) {
case kCloseDisplayMessage: {
// Execute the "Exit" command.
Command* command = Commands::instance()->byId(CommandId::Exit());
UIContext::instance()->executeCommandFromMenuOrShortcut(command);
case kCloseDisplayMessage:
// When the main display is closed...
if (msg->display() == this->display()) {
// Execute the "Exit" command.
Command* command = Commands::instance()->byId(CommandId::Exit());
UIContext::instance()->executeCommandFromMenuOrShortcut(command);
return true;
}
break;
}
case kDropFilesMessage:
// Files are processed only when the main window is the current
@ -580,7 +609,7 @@ bool CustomizedGuiManager::onProcessDevModeKeyDown(KeyMessage* msg)
return true; // This line should not be executed anyway
}
// F1 switches screen/UI scaling
// Ctrl+F1 switches screen/UI scaling
if (msg->ctrlPressed() &&
msg->scancode() == kKeyF1) {
try {

View File

@ -38,9 +38,9 @@ namespace app {
void update_windows_color_profile_from_preferences();
void update_screen_for_document(const Doc* document);
void load_window_pos(ui::Widget* window, const char* section,
void load_window_pos(ui::Window* window, const char* section,
const bool limitMinSize = true);
void save_window_pos(ui::Widget* window, const char* section);
void save_window_pos(ui::Window* window, const char* section);
ui::Widget* setup_mini_font(ui::Widget* widget);
ui::Widget* setup_mini_look(ui::Widget* widget);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -27,7 +27,7 @@ void VelocitySensor::reset()
m_velocity = Vec2(0.0f, 0.0f);
}
void VelocitySensor::updateWithScreenPoint(const gfx::Point& screenPoint)
void VelocitySensor::updateWithDisplayPoint(const gfx::Point& screenPoint)
{
const base::tick_t t = base::current_tick();
const base::tick_t dt = t - m_lastUpdate;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -34,7 +34,7 @@ public:
const Vec2& velocity() const { return m_velocity; }
void updateWithScreenPoint(const gfx::Point& screenPoint);
void updateWithDisplayPoint(const gfx::Point& screenPoint);
private:
bool m_firstPoint;

View File

@ -589,7 +589,7 @@ void BrowserView::onTabPopup(Workspace* workspace)
if (!menu)
return;
menu->showPopup(ui::get_mouse_position());
menu->showPopup(mousePosInDisplay());
}
} // namespace app

View File

@ -145,6 +145,7 @@ bool ColorButton::onProcessMessage(Message* msg)
break;
case kMouseMoveMessage:
// TODO code similar to TileButton::onProcessMessage()
if (hasCapture()) {
gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
app::Color color = m_color;
@ -157,7 +158,7 @@ bool ColorButton::onProcessMessage(Message* msg)
// surface, and finally from the desktop. The desktop must be
// a last resource method, because in macOS it will ask for
// permissions to record the screen.
if (!colorSource) {
if (!colorSource && get_multiple_displays()) {
os::Window* nativeWindow = display()->nativeWindow();
gfx::Point screenPos = nativeWindow->pointToScreen(mousePos);

View File

@ -413,7 +413,7 @@ void DataRecoveryView::onTabPopup(Workspace* workspace)
if (!menu)
return;
menu->showPopup(ui::get_mouse_position());
menu->showPopup(mousePosInDisplay());
}
void DataRecoveryView::onOpen()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -140,7 +140,7 @@ void DevConsoleView::onTabPopup(Workspace* workspace)
if (!menu)
return;
menu->showPopup(ui::get_mouse_position());
menu->showPopup(mousePosInDisplay());
}
bool DevConsoleView::onProcessMessage(Message* msg)

View File

@ -128,7 +128,7 @@ protected:
PointerType::Unknown,
(lmb->isPressed(msg, *keys) ? kButtonLeft: kButtonRight),
msg->modifiers(),
ui::get_mouse_position());
mousePosInDisplay());
sendMessage(&mouseMsg);
return true;
@ -362,7 +362,7 @@ void DocView::onTabPopup(Workspace* workspace)
ctx->setActiveView(this);
ctx->updateFlags();
menu->showPopup(ui::get_mouse_position());
menu->showPopup(mousePosInDisplay());
}
bool DocView::onProcessMessage(Message* msg)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -430,7 +430,7 @@ bool DynamicsPopup::onProcessMessage(Message* msg)
}
if (m_dynamics->velocityPlaceholder()->isVisible()) {
m_velocity.updateWithScreenPoint(mouseMsg->position());
m_velocity.updateWithDisplayPoint(mouseMsg->position());
float v = m_velocity.velocity().magnitude()
/ tools::VelocitySensor::kScreenPixelsForFullVelocity;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -214,7 +214,7 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg)
false, m_processScrollChange);
// Update velocity sensor.
m_velocity.updateWithScreenPoint(msg->position());
m_velocity.updateWithDisplayPoint(msg->position());
// The autoScroll() function controls the "infinite scroll" when we
// touch the viewport borders.
@ -279,10 +279,10 @@ bool DrawingState::onKeyUp(Editor* editor, KeyMessage* msg)
bool DrawingState::onScrollChange(Editor* editor)
{
if (m_processScrollChange) {
gfx::Point mousePos = ui::get_mouse_position();
gfx::Point mousePos = editor->mousePosInDisplay();
// Update velocity sensor.
m_velocity.updateWithScreenPoint(mousePos); // TODO add scroll as velocity?
m_velocity.updateWithDisplayPoint(mousePos); // TODO add scroll as velocity?
handleMouseMovement(
tools::Pointer(editor->screenToEditor(mousePos),

View File

@ -301,7 +301,7 @@ void Editor::setStateInternal(const EditorStatePtr& newState)
invalidate();
// Setup the new mouse cursor
setCursor(ui::get_mouse_position());
setCursor(mousePosInDisplay());
updateStatusBar();
}
@ -539,7 +539,7 @@ void Editor::setEditorScroll(const gfx::Point& scroll)
void Editor::setEditorZoom(const render::Zoom& zoom)
{
setZoomAndCenterInMouse(
zoom, ui::get_mouse_position(),
zoom, mousePosInDisplay(),
Editor::ZoomBehavior::CENTER);
}
@ -1402,7 +1402,8 @@ gfx::Point Editor::autoScroll(MouseMessage* msg, AutoScroll dir)
setEditorScroll(scroll);
mousePos -= delta;
ui::set_mouse_position(mousePos);
ui::set_mouse_position(mousePos,
display());
m_oldPos = mousePos;
mousePos = gfx::Point(
@ -1976,7 +1977,7 @@ bool Editor::onProcessMessage(Message* msg)
updateAutoCelGuides(msg);
if (hasMouse()) {
updateQuicktool();
setCursor(ui::get_mouse_position());
setCursor(mousePosInDisplay());
}
if (used)
@ -1993,7 +1994,7 @@ bool Editor::onProcessMessage(Message* msg)
updateAutoCelGuides(msg);
if (hasMouse()) {
updateQuicktool();
setCursor(ui::get_mouse_position());
setCursor(mousePosInDisplay());
}
if (used)
@ -2040,7 +2041,7 @@ bool Editor::onProcessMessage(Message* msg)
// (e.g. in the case of the Eraser tool).
m_document->setExtraCel(ExtraCelRef(nullptr));
showBrushPreview(ui::get_mouse_position());
showBrushPreview(mousePosInDisplay());
}
else {
m_document->setExtraCel(ExtraCelRef(nullptr));
@ -2174,7 +2175,7 @@ void Editor::onActiveToolChange(tools::Tool* tool)
m_state->onActiveToolChange(this, tool);
if (hasMouse()) {
updateStatusBar();
setCursor(ui::get_mouse_position());
setCursor(mousePosInDisplay());
}
}
@ -2277,15 +2278,15 @@ void Editor::onRemoveSlice(DocEvent& ev)
}
}
void Editor::setCursor(const gfx::Point& mouseScreenPos)
void Editor::setCursor(const gfx::Point& mouseDisplayPos)
{
Rect vp = View::getView(this)->viewportBounds();
if (!vp.contains(mouseScreenPos))
if (!vp.contains(mouseDisplayPos))
return;
bool used = false;
if (m_sprite)
used = m_state->onSetCursor(this, mouseScreenPos);
used = m_state->onSetCursor(this, mouseDisplayPos);
if (!used)
showMouseCursor(kArrowCursor);
@ -2302,7 +2303,7 @@ bool Editor::canDraw()
bool Editor::isInsideSelection()
{
gfx::Point spritePos = screenToEditor(ui::get_mouse_position());
gfx::Point spritePos = screenToEditor(mousePosInDisplay());
spritePos -= mainTilePosition();
KeyAction action = m_customizationDelegate->getPressedKeyAction(KeyContext::SelectionTool);
@ -2617,7 +2618,7 @@ void Editor::notifyScrollChanged()
// Update status bar and mouse cursor
if (hasMouse()) {
updateStatusBar();
setCursor(ui::get_mouse_position());
setCursor(mousePosInDisplay());
}
}
@ -2753,7 +2754,7 @@ void Editor::showAnimationSpeedMultiplierPopup(Option<bool>& playOnce,
menu.addChild(item);
}
menu.showPopup(ui::get_mouse_position());
menu.showPopup(mousePosInDisplay());
if (isPlaying()) {
// Re-play
@ -2916,7 +2917,7 @@ void Editor::updateAutoCelGuides(ui::Message* msg)
ColorPicker picker;
picker.pickColor(getSite(),
screenToEditorF(mouseMsg ? mouseMsg->position():
ui::get_mouse_position()),
mousePosInDisplay()),
m_proj, ColorPicker::FromComposition);
m_showGuidesThisCel = (picker.layer() ? picker.layer()->cel(m_frame):
nullptr);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -172,6 +172,11 @@ namespace app {
void flashCurrentLayer();
// Convert ui::Display coordinates (pixel relative to the top-left
// corner of the in the display content bounds) from/to
// editor/sprite coordinates (pixel in the canvas).
//
// TODO we should rename these functions to displayToEditor() and editorToDisplay()
gfx::Point screenToEditor(const gfx::Point& pt);
gfx::PointF screenToEditorF(const gfx::Point& pt);
gfx::Point editorToScreen(const gfx::Point& pt);
@ -368,7 +373,7 @@ namespace app {
const int dottedY);
gfx::Rect getCelScreenBounds(const Cel* cel);
void setCursor(const gfx::Point& mouseScreenPos);
void setCursor(const gfx::Point& mouseDisplayPos);
// Draws the specified portion of sprite in the editor. Warning:
// You should setup the clip of the screen before calling this

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -66,7 +66,6 @@
#include "os/system.h"
#include "ui/alert.h"
#include "ui/message.h"
#include "ui/system.h"
#include "ui/view.h"
#include <cmath>
@ -527,7 +526,7 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
tools::Ink* ink = editor->getCurrentEditorInk();
const Sprite* sprite = editor->sprite();
gfx::PointF spritePos =
editor->screenToEditorF(ui::get_mouse_position())
editor->screenToEditorF(editor->mousePosInDisplay())
- gfx::PointF(editor->mainTilePosition());
if (!sprite) {
@ -680,7 +679,7 @@ bool StandbyState::checkStartDrawingStraightLine(Editor* editor,
if (drawingState) {
drawingState->sendMovementToToolLoop(
tools::Pointer(
pointer ? pointer->point(): editor->screenToEditor(ui::get_mouse_position()),
pointer ? pointer->point(): editor->screenToEditor(editor->mousePosInDisplay()),
tools::Vec2(0.0f, 0.0f),
pointerButton,
pointer ? pointer->type(): tools::Pointer::Type::Unknown,

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -136,7 +136,7 @@ void HomeView::onTabPopup(Workspace* workspace)
if (!menu)
return;
menu->showPopup(ui::get_mouse_position());
menu->showPopup(mousePosInDisplay());
}
void HomeView::onWorkspaceViewSelected()

View File

@ -728,7 +728,7 @@ void Tabs::calculateHot()
gfx::Rect rect = bounds();
gfx::Rect box(rect.x+m_border*guiscale(), rect.y, 0, rect.h-1);
gfx::Point mousePos = ui::get_mouse_position();
gfx::Point mousePos = mousePosInDisplay();
TabPtr hot(nullptr);
bool hotCloseButton = false;
@ -1040,7 +1040,7 @@ void Tabs::updateDragCopyCursor(ui::Message* msg)
(tab && m_delegate && m_delegate->canCloneTab(this, tab->view)));
if (oldDragCopy != m_dragCopy) {
updateDragTabIndexes(get_mouse_position().x, true);
updateDragTabIndexes(mousePosInDisplay().x, true);
updateMouseCursor();
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2020-2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -20,6 +20,7 @@
#include "ui/paint_event.h"
#include "ui/size_hint_event.h"
#include "ui/system.h"
#include "ui/window.h"
namespace app {
@ -88,18 +89,34 @@ bool TileButton::onProcessMessage(Message* msg)
break;
case kMouseMoveMessage:
// TODO code similar to ColorButton::onProcessMessage()
if (hasCapture()) {
gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
Widget* picked = manager()->pick(mousePos);
doc::tile_t tile = m_tile;
if (picked && picked != this) {
// Pick a tile from a ITileSource
if (ITileSource* tileSource = dynamic_cast<ITileSource*>(picked)) {
tile = tileSource->getTileByPosition(mousePos);
// Pick a tile from a ITileSource
Widget* picked = window()->pick(mousePos);
ITileSource* tileSource = (picked != this ? dynamic_cast<ITileSource*>(picked): nullptr);
// If there is no tile source in this window, try to get the
// tile from other display, i.e. and editor in other native
// window.
if (!tileSource && get_multiple_displays()) {
os::Window* nativeWindow = display()->nativeWindow();
gfx::Point screenPos = nativeWindow->pointToScreen(mousePos);
picked = manager()->pickFromScreenPos(screenPos);
tileSource = (picked != this ? dynamic_cast<ITileSource*>(picked): nullptr);
if (tileSource) {
nativeWindow = picked->display()->nativeWindow();
mousePos = nativeWindow->pointFromScreen(screenPos);
}
}
if (tileSource) {
tile = tileSource->getTileByPosition(mousePos);
}
// Did the tile change?
if (tile != m_tile) {
setTile(tile);

View File

@ -888,7 +888,7 @@ bool Timeline::onProcessMessage(Message* msg)
m_state = STATE_COLLAPSING_LAYERS;
setLayerCollapsedFlag(m_clk.layer, m_state == STATE_COLLAPSING_LAYERS);
updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
updateByMousePos(msg, mousePosInClientBounds());
// The m_clk might have changed because we've
// expanded/collapsed a group just right now (i.e. we've
@ -1075,7 +1075,7 @@ bool Timeline::onProcessMessage(Message* msg)
m_clk = hit;
if (hit.part == PART_ROW_CONTINUOUS_ICON) {
setLayerCollapsedFlag(hit.layer, m_state == STATE_COLLAPSING_LAYERS);
updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
updateByMousePos(msg, mousePosInClientBounds());
}
break;
@ -1483,7 +1483,7 @@ bool Timeline::onProcessMessage(Message* msg)
}
}
updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
updateByMousePos(msg, mousePosInClientBounds());
if (used)
return true;
@ -1502,7 +1502,7 @@ bool Timeline::onProcessMessage(Message* msg)
}
}
updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
updateByMousePos(msg, mousePosInClientBounds());
if (used)
return true;

View File

@ -375,7 +375,8 @@ bool ComboBox::onProcessMessage(Message* msg)
closeListBox();
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
Widget* pick = manager()->pick(mouseMsg->position());
Widget* pick = manager()->pickFromScreenPos(
display()->nativeWindow()->pointFromScreen(mouseMsg->position()));
if (pick && pick->hasAncestor(this))
return true;
}
@ -503,7 +504,8 @@ bool ComboBoxEntry::onProcessMessage(Message* msg)
case kMouseMoveMessage:
if (hasCapture()) {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
Widget* pick = manager()->pick(mouseMsg->position());
Widget* pick = manager()->pickFromScreenPos(
display()->nativeWindow()->pointToScreen(mouseMsg->position()));
Widget* listbox = m_comboBox->m_listbox;
if (pick != nullptr &&
@ -615,7 +617,7 @@ void ComboBox::openListBox()
View* view = new View();
m_listbox = new ComboBoxListBox(this);
// TODO create a real native window for comboboxes
m_window->setDisplay(display());
m_window->setDisplay(display(), false);
m_window->setOnTop(true);
m_window->setWantFocus(false);
m_window->setSizeable(false);
@ -667,6 +669,7 @@ void ComboBox::closeListBox()
m_window->closeWindow(this);
delete m_window; // window, frame
m_window = nullptr;
m_listbox = nullptr;

View File

@ -18,9 +18,11 @@
namespace ui {
Display::Display(const os::WindowRef& nativeWindow,
Display::Display(Display* parentDisplay,
const os::WindowRef& nativeWindow,
Widget* containedWidget)
: m_nativeWindow(nativeWindow)
: m_parentDisplay(parentDisplay)
, m_nativeWindow(nativeWindow)
, m_containedWidget(containedWidget)
{
ASSERT(m_nativeWindow);
@ -46,7 +48,7 @@ gfx::Size Display::size() const
void Display::dirtyRect(const gfx::Rect& bounds)
{
m_dirtyRegion.createUnion(m_dirtyRegion, gfx::Region(bounds));
m_dirtyRegion |= gfx::Region(bounds);
}
void Display::flipDisplay()

View File

@ -25,9 +25,11 @@ namespace ui {
// (tooltips?)
class Display {
public:
Display(const os::WindowRef& nativeWindow,
Display(Display* parentDisplay,
const os::WindowRef& nativeWindow,
Widget* containedWidget);
Display* parentDisplay() { return m_parentDisplay; }
os::Window* nativeWindow() { return m_nativeWindow.get(); }
os::Surface* surface() const;
@ -71,6 +73,7 @@ namespace ui {
const std::vector<Window*>& getWindows() const { return m_windows; }
private:
Display* m_parentDisplay;
os::WindowRef m_nativeWindow;
Widget* m_containedWidget; // A ui::Manager or a ui::Window
std::vector<Window*> m_windows; // Sub-windows in this display

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -90,7 +90,8 @@ bool IntEntry::onProcessMessage(Message* msg)
case kMouseMoveMessage:
if (hasCapture()) {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
Widget* pick = manager()->pick(mouseMsg->position());
Widget* pick = manager()->pickFromScreenPos(
display()->nativeWindow()->pointToScreen(mouseMsg->position()));
if (pick == &m_slider) {
releaseMouse();

View File

@ -27,6 +27,7 @@
#include "os/surface.h"
#include "os/system.h"
#include "os/window.h"
#include "os/window_spec.h"
#include "ui/intern.h"
#include "ui/ui.h"
@ -91,6 +92,16 @@ static base::concurrent_queue<Message*> concurrent_msg_queue;
static Filters msg_filters[NFILTERS]; // Filters for every enqueued message
static int filter_locks = 0;
// Current display with the mouse, used to avoid processing a
// os::Event::MouseLeave of the non-current display/window as when we
// move the mouse between two windows we can receive:
//
// 1. A os::Event::MouseEnter of the new window
// 2. A os::Event::MouseLeave of the old window
//
// Instead of the MouseLeave event of the old window first.
static Display* mouse_display = nullptr;
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
@ -164,7 +175,7 @@ bool Manager::widgetAssociatedToManager(Widget* widget)
Manager::Manager(const os::WindowRef& nativeWindow)
: Widget(kManagerWidget)
, m_display(nativeWindow, this)
, m_display(nullptr, nativeWindow, this)
, m_eventQueue(os::instance()->eventQueue())
, m_lockedWindow(nullptr)
, m_mouseButton(kButtonNone)
@ -264,6 +275,13 @@ void Manager::flipAllDisplays()
overlays->drawOverlays();
m_display.flipDisplay();
if (get_multiple_displays()) {
for (auto child : children()) {
auto window = static_cast<Window*>(child);
if (window->ownDisplay())
window->display()->flipDisplay();
}
}
}
void Manager::updateAllDisplaysWithNewScale(int scale)
@ -271,6 +289,17 @@ void Manager::updateAllDisplaysWithNewScale(int scale)
os::Window* nativeWindow = m_display.nativeWindow();
nativeWindow->setScale(scale);
if (get_multiple_displays()) {
for (auto child : children()) {
auto window = static_cast<Window*>(child);
if (window->ownDisplay()) {
Display* display = static_cast<Window*>(child)->display();
display->nativeWindow()->setScale(scale);
onNewDisplayConfiguration(display);
}
}
}
onNewDisplayConfiguration(&m_display);
}
@ -403,7 +432,7 @@ void Manager::generateMessagesFromOSEvents()
Message* msg = new Message(kResizeDisplayMessage);
msg->setDisplay(display);
msg->setRecipient(this);
msg->setPropagateToChildren(true);
msg->setPropagateToChildren(false);
enqueueMessage(msg);
break;
}
@ -438,26 +467,34 @@ void Manager::generateMessagesFromOSEvents()
}
case os::Event::MouseEnter: {
_internal_set_mouse_position(osEvent.position());
if (get_multiple_displays()) {
if (osEvent.window()) {
ASSERT(display != nullptr);
_internal_set_mouse_display(display);
}
}
set_mouse_cursor(kArrowCursor);
lastMouseMoveEvent = osEvent;
mouse_display = display;
break;
}
case os::Event::MouseLeave: {
set_mouse_cursor(kOutsideDisplay);
setMouse(nullptr);
if (mouse_display == display) {
set_mouse_cursor(kOutsideDisplay);
setMouse(nullptr);
_internal_no_mouse_position();
_internal_no_mouse_position();
mouse_display = nullptr;
// To avoid calling kSetCursorMessage when the mouse leaves
// the window.
lastMouseMoveEvent = os::Event();
// 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(
display,
osEvent.position(),
@ -510,8 +547,6 @@ void Manager::generateMessagesFromOSEvents()
}
case os::Event::TouchMagnify: {
_internal_set_mouse_position(osEvent.position());
handleTouchMagnify(display,
osEvent.position(),
osEvent.modifiers(),
@ -549,7 +584,7 @@ void Manager::handleMouseMove(Display* display,
const PointerType pointerType,
const float pressure)
{
updateMouseWidgets(mousePos);
updateMouseWidgets(mousePos, display);
// Send the mouse movement message
Widget* dst = (capture_widget ? capture_widget: mouse_widget);
@ -669,6 +704,8 @@ void Manager::handleWindowZOrder()
if ((window) &&
// We cannot change Z-order of desktop windows
(!window->isDesktop()) &&
// We cannot change window order of native windows (they are handled by the OS)
(!window->ownDisplay()) &&
// 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.
@ -705,16 +742,44 @@ void Manager::handleWindowZOrder()
setFocus(mouse_widget);
}
void Manager::updateMouseWidgets(const gfx::Point& mousePos)
// If display is nullptr, mousePos is in screen coordinates, if not,
// it's relative to the display content rect.
void Manager::updateMouseWidgets(const gfx::Point& mousePos,
Display* display)
{
gfx::Point screenPos = (display ? display->nativeWindow()->pointToScreen(mousePos):
mousePos);
// Get the list of widgets to send mouse messages.
mouse_widgets_list.clear();
broadcastMouseMessage(mouse_widgets_list);
broadcastMouseMessage(screenPos,
mouse_widgets_list);
// Get the widget under the mouse
Widget* widget = nullptr;
for (auto mouseWidget : mouse_widgets_list) {
widget = mouseWidget->pick(mousePos);
if (get_multiple_displays()) {
gfx::Point displayPos;
if (display) {
if (display != mouseWidget->display()) {
displayPos = mouseWidget->display()->nativeWindow()->pointFromScreen(screenPos);
widget = mouseWidget->display()->containedWidget()->pick(displayPos);
}
else {
widget = mouseWidget->pick(mousePos);
}
}
else {
displayPos = mouseWidget->display()->nativeWindow()->pointFromScreen(screenPos);
widget = mouseWidget->display()->containedWidget()->pick(displayPos);
}
}
else {
if (display)
widget = mouseWidget->pick(mousePos);
else
widget = mouseWidget->pickFromScreenPos(screenPos);
}
if (widget) {
// Get the first ancestor of the picked widget that doesn't
// ignore mouse events.
@ -726,10 +791,12 @@ void Manager::updateMouseWidgets(const gfx::Point& mousePos)
// Fixup "mouse" flag
if (widget != mouse_widget) {
if (!widget)
if (!widget) {
freeMouse();
else
}
else {
setMouse(widget);
}
}
}
@ -793,6 +860,16 @@ Window* Manager::getForegroundWindow()
return nullptr;
}
Display* Manager::getForegroundDisplay()
{
if (get_multiple_displays()) {
Window* window = getForegroundWindow();
if (window)
return window->display();
}
return &m_display;
}
Widget* Manager::getFocus()
{
return focus_widget;
@ -885,11 +962,12 @@ void Manager::setMouse(Widget* widget)
mouse_widget = widget;
if (widget) {
Display* display = mouse_widget->display();
gfx::Point mousePos = display->nativeWindow()->pointFromScreen(get_mouse_position());
auto msg = newMouseMessage(
kMouseEnterMessage,
display, nullptr,
get_mouse_position(),
mousePos,
PointerType::Unknown,
m_mouseButton,
kKeyUninitializedModifier);
@ -899,7 +977,7 @@ void Manager::setMouse(Widget* widget)
msg->setCommonAncestor(commonAncestor);
enqueueMessage(msg);
generateSetCursorMessage(display,
get_mouse_position(),
mousePos,
kKeyUninitializedModifier,
PointerType::Unknown);
@ -1050,6 +1128,24 @@ void Manager::removeMessagesForTimer(Timer* timer)
}
}
void Manager::removePaintMessagesForDisplay(Display* display)
{
#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() == kPaintMessage &&
static_cast<PaintMessage*>(msg)->display() == display) {
delete msg;
it = msg_queue.erase(it);
}
else
++it;
}
}
void Manager::addMessageFilter(int message, Widget* widget)
{
#ifdef DEBUG_UI_THREADS
@ -1116,12 +1212,37 @@ bool Manager::isFocusMovementMessage(Message* msg)
Widget* Manager::pickFromScreenPos(const gfx::Point& screenPos) const
{
return pick(gfx::Point(screenPos) - display()->nativeWindow()->contentRect().origin());
Display* mainDisplay = display();
if (get_multiple_displays()) {
for (auto child : children()) {
auto window = static_cast<Window*>(child);
if (window->ownDisplay() ||
window->display() != mainDisplay) {
os::Window* nativeWindow = window->display()->nativeWindow();
if (nativeWindow->frame().contains(screenPos))
return window->pick(nativeWindow->pointFromScreen(screenPos));
}
}
gfx::Point displayPos = display()->nativeWindow()->pointFromScreen(screenPos);
for (auto child : children()) {
auto window = static_cast<Window*>(child);
if (window->display() == mainDisplay) {
if (auto picked = window->pick(displayPos))
return picked;
}
}
}
return Widget::pickFromScreenPos(screenPos);
}
// Configures the window for begin the loop
void Manager::_openWindow(Window* window)
void Manager::_openWindow(Window* window, bool center)
{
Display* parentDisplay = getForegroundDisplay();
ASSERT(parentDisplay);
// Free all widgets of special states.
if (window->isWantFocus()) {
freeCapture();
@ -1139,10 +1260,79 @@ void Manager::_openWindow(Window* window)
}
// Relayout
window->layout();
if (center)
window->centerWindow();
else
window->layout();
// Same display as the manager.
window->setDisplay(this->display());
// If the window already was set a display, we don't setup it
// (i.e. in the case of combobox popup/window the display field is
// set to the same display where the ComboBox widget is located)
if (window->display() == &m_display) {
// In other case, we can try to create a display/native window for
// the UI window.
if (get_multiple_displays()
&& !window->isDesktop()
#if 1 // TODO Add support for menuboxes and tooltips with native windows
&& window->isSizeable()
#endif
) {
const int scale = parentDisplay->nativeWindow()->scale();
os::WindowSpec spec;
gfx::Rect frame;
if (!window->lastNativeFrame().isEmpty()) {
frame = window->lastNativeFrame();
}
else {
gfx::Rect relativeToFrame = parentDisplay->nativeWindow()->contentRect();
frame = window->bounds();
frame *= scale;
frame.offset(relativeToFrame.origin());
}
spec.position(os::WindowSpec::Position::Frame);
spec.frame(frame);
spec.scale(scale);
// Only desktop will have the real native window title bar
// TODO in the future other windows could use the native title bar
// when there are no special decorators (or we could just add
// the possibility to create new buttons in the native window
// title bar)
spec.titled(window->isDesktop());
spec.floating(!window->isDesktop());
spec.resizable(window->isDesktop() || window->isSizeable());
spec.maximizable(spec.resizable());
spec.minimizable(window->isDesktop());
spec.borderless(!window->isDesktop());
if (!window->isDesktop()) {
spec.parent(parentDisplay->nativeWindow());
}
os::WindowRef newNativeWindow = os::instance()->makeWindow(spec);
ui::Display* newDisplay = new ui::Display(parentDisplay, newNativeWindow, window);
newNativeWindow->setUserData(newDisplay);
window->setDisplay(newDisplay, true);
// Set native title bar text
newNativeWindow->setTitle(window->text());
// Activate only non-floating windows
if (!spec.floating())
newNativeWindow->activate();
else
m_display.nativeWindow()->activate();
// Move all widgets to the os::Display origin (0,0)
window->offsetWidgets(-window->origin().x, -window->origin().y);
}
else {
// Same display for desktop window or when multiple displays is
// disabled.
window->setDisplay(this->display(), false);
}
}
// Dirty the entire window and show it
window->setVisible(true);
@ -1160,19 +1350,51 @@ void Manager::_openWindow(Window* window)
// Update mouse widget (as it can be a widget below the
// recently opened window).
updateMouseWidgets(ui::get_mouse_position());
updateMouseWidgets(ui::get_mouse_position(), nullptr);
}
void Manager::_closeWindow(Window* window, bool redraw_background)
{
window->setDisplay(nullptr);
Display* windowDisplay = window->display();
Display* parentDisplay;
if (// The display can be nullptr if the window was not opened or
// was closed before.
window->ownDisplay()) {
parentDisplay = windowDisplay->parentDisplay();
ASSERT(parentDisplay);
ASSERT(windowDisplay);
ASSERT(windowDisplay != this->display());
// Remove all paint messages for this display.
removePaintMessagesForDisplay(windowDisplay);
window->setDisplay(nullptr, false);
windowDisplay->nativeWindow()->setUserData<void*>(nullptr);
// Remove the mouse cursor from the display that we are going to
// delete.
_internal_set_mouse_display(parentDisplay);
// The ui::Display should destroy the os::Window
delete windowDisplay;
// Activate main windows
parentDisplay->nativeWindow()->activate();
}
else {
parentDisplay = windowDisplay;
window->setDisplay(nullptr, false);
}
if (!hasChild(window))
return;
gfx::Region reg1;
if (redraw_background)
window->getRegion(reg1);
if (!window->ownDisplay()) {
if (redraw_background)
window->getRegion(reg1);
}
// Close all windows to this desktop
if (window->isDesktop()) {
@ -1183,7 +1405,7 @@ void Manager::_closeWindow(Window* window, bool redraw_background)
else {
gfx::Region reg2;
window->getRegion(reg2);
reg1.createUnion(reg1, reg2);
reg1 |= reg2;
_closeWindow(child, false);
}
@ -1213,15 +1435,22 @@ void Manager::_closeWindow(Window* window, bool redraw_background)
removeChild(window);
// Redraw background.
invalidateRegion(reg1);
parentDisplay->containedWidget()->invalidateRegion(reg1);
// Update mouse widget (as it can be a widget below the
// recently closed window).
updateMouseWidgets(ui::get_mouse_position());
updateMouseWidgets(ui::get_mouse_position(), nullptr);
redrawState = RedrawState::AWindowHasJustBeenClosed;
}
void Manager::_runModalWindow(Window* window)
{
MessageLoop loop(manager());
while (!window->hasFlags(HIDDEN))
loop.pumpMessages();
}
bool Manager::onProcessMessage(Message* msg)
{
switch (msg->type()) {
@ -1234,6 +1463,15 @@ bool Manager::onProcessMessage(Message* msg)
// finally closed.)
return true;
case kCloseDisplayMessage: {
if (msg->display() != &m_display) {
if (Window* window = dynamic_cast<Window*>(msg->display()->containedWidget())) {
window->closeWindow(this);
}
}
break;
}
case kResizeDisplayMessage:
onNewDisplayConfiguration(msg->display());
break;
@ -1291,6 +1529,9 @@ void Manager::onResize(ResizeEvent& ev)
for (auto child : children()) {
Window* window = static_cast<Window*>(child);
if (window->ownDisplay())
continue;
if (window->isDesktop()) {
window->setBounds(new_pos);
break;
@ -1329,13 +1570,14 @@ void Manager::onResize(ResizeEvent& ev)
}
}
void Manager::onBroadcastMouseMessage(WidgetsList& targets)
void Manager::onBroadcastMouseMessage(const gfx::Point& screenPos,
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);
widget->broadcastMouseMessage(screenPos, targets);
}
void Manager::onInitTheme(InitThemeEvent& ev)
@ -1382,7 +1624,7 @@ void Manager::onNewDisplayConfiguration(Display* display)
_internal_set_mouse_display(display);
container->invalidate();
flushRedraw();
container->flushRedraw();
}
void Manager::onSizeHint(SizeHintEvent& ev)
@ -1576,15 +1818,13 @@ bool Manager::sendMessageToWidget(Message* msg, Widget* widget)
#endif
#endif
if (surface) {
// Call the message handler
used = widget->sendMessage(msg);
// Restore clip region for paint messages.
surface->restoreClip();
}
// 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".
paintMsg->display()->subtractInvalidRegion(gfx::Region(paintMsg->rect()));
@ -1600,6 +1840,8 @@ bool Manager::sendMessageToWidget(Message* msg, Widget* widget)
// 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).
//
// TODO similar to Window::onInvalidateRegion
void Manager::onInvalidateRegion(const gfx::Region& region)
{
if (!isVisible() || region.contains(bounds()) == gfx::Region::Out)
@ -1619,6 +1861,11 @@ void Manager::onInvalidateRegion(const gfx::Region& region)
ASSERT(child->type() == kWindowWidget);
Window* window = static_cast<Window*>(child);
// Invalidating the manager only works for the main display, to
// invalidate windows you have to invalidate them.
if (window->ownDisplay())
continue;
// Invalidate regions of this window
window->invalidateRegion(reg1);

View File

@ -62,6 +62,7 @@ namespace ui {
Window* getTopWindow();
Window* getForegroundWindow();
Display* getForegroundDisplay();
Widget* getFocus();
Widget* getMouse();
@ -79,6 +80,7 @@ namespace ui {
void removeMessagesFor(Widget* widget);
void removeMessagesFor(Widget* widget, MessageType type);
void removeMessagesForTimer(Timer* timer);
void removePaintMessagesForDisplay(Display* display);
void addMessageFilter(int message, Widget* widget);
void removeMessageFilter(int message, Widget* widget);
@ -89,17 +91,19 @@ namespace ui {
bool isFocusMovementMessage(Message* msg);
bool processFocusMovementMessage(Message* msg);
Widget* pickFromScreenPos(const gfx::Point& screenPos) const;
Widget* pickFromScreenPos(const gfx::Point& screenPos) const override;
void _openWindow(Window* window);
void _openWindow(Window* window, bool center);
void _closeWindow(Window* window, bool redraw_background);
void _runModalWindow(Window* window);
protected:
bool onProcessMessage(Message* msg) override;
void onInvalidateRegion(const gfx::Region& region) override;
void onResize(ResizeEvent& ev) override;
void onSizeHint(SizeHintEvent& ev) override;
void onBroadcastMouseMessage(WidgetsList& targets) override;
void onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets) override;
void onInitTheme(InitThemeEvent& ev) override;
virtual LayoutIO* onGetLayoutIO();
virtual void onNewDisplayConfiguration(Display* display);
@ -142,7 +146,8 @@ namespace ui {
const KeyModifiers modifiers,
const double magnification);
void handleWindowZOrder();
void updateMouseWidgets(const gfx::Point& mousePos);
void updateMouseWidgets(const gfx::Point& mousePos,
Display* display);
int pumpQueue();
bool sendMessageToWidget(Message* msg, Widget* widget);

View File

@ -454,7 +454,8 @@ bool MenuBox::onProcessMessage(Message* msg)
// popuped menu-box) to detect if the user press outside of
// the widget
if (msg->type() == kMouseDownMessage && m_base != nullptr) {
Widget* picked = manager()->pick(mousePos);
Widget* picked = manager()->pickFromScreenPos(
display()->nativeWindow()->pointToScreen(mousePos));
// If one of these conditions are accomplished we have to
// close all menus (back to menu-bar or close the popuped

View File

@ -35,6 +35,9 @@ namespace ui {
// thread. (Which might be catastrofic.)
base::thread::native_id_type main_gui_thread;
// Multiple displays (create one os::Window for each ui::Window)
bool multi_displays = false;
// Current mouse cursor type.
static CursorType mouse_cursor_type = kOutsideDisplay;
static const Cursor* mouse_cursor_custom = nullptr;
@ -44,10 +47,8 @@ static OverlayRef mouse_cursor_overlay = nullptr;
static bool use_native_mouse_cursor = true;
static bool support_native_custom_cursor = false;
// Mouse information (button and position).
static gfx::Point m_mouse_pos;
// Mouse information
static int mouse_cursor_scale = 1;
static int mouse_scares = 0;
static void update_mouse_overlay(const Cursor* cursor)
@ -60,7 +61,7 @@ static void update_mouse_overlay(const Cursor* cursor)
mouse_cursor_overlay = base::make_ref<Overlay>(
mouse_display,
mouse_cursor->getSurface(),
get_mouse_position(),
mouse_display->nativeWindow()->pointFromScreen(get_mouse_position()),
Overlay::MouseZOrder);
OverlayManager::instance()->addOverlay(mouse_cursor_overlay);
@ -77,17 +78,14 @@ static void update_mouse_overlay(const Cursor* cursor)
}
}
static bool update_custom_native_cursor(const Cursor* cursor)
static bool set_native_cursor_on_all_displays(Display* display,
const Cursor* cursor)
{
bool result = false;
// Check if we can use a custom native mouse in this platform
if (support_native_custom_cursor &&
mouse_display) {
os::Window* nativeWindow = mouse_display->nativeWindow();
while (display) {
os::Window* nativeWindow = display->nativeWindow();
if (cursor) {
result = nativeWindow->setNativeMouseCursor(
result |= nativeWindow->setNativeMouseCursor(
// The surface is already scaled by guiscale()
cursor->getSurface().get(),
cursor->getFocus(),
@ -95,11 +93,41 @@ static bool update_custom_native_cursor(const Cursor* cursor)
nativeWindow->scale() * mouse_cursor_scale);
}
else if (mouse_cursor_type == kOutsideDisplay) {
result = nativeWindow->setNativeMouseCursor(os::NativeCursor::Arrow);
result |= nativeWindow->setNativeMouseCursor(os::NativeCursor::Arrow);
}
else {
result = nativeWindow->setNativeMouseCursor(os::NativeCursor::Hidden);
result |= nativeWindow->setNativeMouseCursor(os::NativeCursor::Hidden);
}
display = display->parentDisplay();
}
return result;
}
static bool set_native_cursor_on_all_displays(Display* display,
const os::NativeCursor cursor)
{
bool result = false;
while (display) {
os::Window* nativeWindow = display->nativeWindow();
if (mouse_cursor_type == kOutsideDisplay) {
result |= nativeWindow->setNativeMouseCursor(os::NativeCursor::Arrow);
}
else {
result |= nativeWindow->setNativeMouseCursor(cursor);
}
display = display->parentDisplay();
}
return result;
}
static bool update_custom_native_cursor(const Cursor* cursor)
{
bool result = false;
// Check if we can use a custom native mouse in this platform
if (support_native_custom_cursor &&
mouse_display) {
result = set_native_cursor_on_all_displays(mouse_display, cursor);
}
return result;
@ -149,7 +177,7 @@ static void update_mouse_cursor()
// Set native cursor
if (mouse_display) {
bool ok = mouse_display->nativeWindow()->setNativeMouseCursor(nativeCursor);
bool ok = set_native_cursor_on_all_displays(mouse_display, nativeCursor);
// It looks like the specific native cursor is not supported,
// so we can should use the internal overlay (even when we
@ -227,15 +255,26 @@ void _internal_set_mouse_display(Display* display)
set_mouse_cursor(cursor); // Restore mouse cursor
}
void _internal_free_mouse_display(Display* display)
void set_multiple_displays(bool multi)
{
if (mouse_display == display)
_internal_set_mouse_display(nullptr);
multi_displays = multi;
}
bool get_multiple_displays()
{
return multi_displays;
}
gfx::Size get_desktop_size()
{
return Manager::getDefault()->display()->size();
if (get_multiple_displays()) {
return
os::instance()->mainScreen()->workarea().size() /
Manager::getDefault()->display()->nativeWindow()->scale();
}
else {
return Manager::getDefault()->display()->size();
}
}
void set_clipboard_text(const std::string& text)
@ -260,7 +299,8 @@ void update_cursor_overlay()
{
if (mouse_cursor_overlay != nullptr && mouse_scares == 0) {
gfx::Point newPos =
get_mouse_position() - mouse_cursor->getFocus();
mouse_display->nativeWindow()->pointFromScreen(get_mouse_position())
- mouse_cursor->getFocus();
if (newPos != mouse_cursor_overlay->position()) {
mouse_cursor_overlay->moveOverlay(newPos);
@ -317,22 +357,21 @@ void _internal_no_mouse_position()
update_mouse_overlay(nullptr);
}
void _internal_set_mouse_position(const gfx::Point& newPos)
gfx::Point get_mouse_position()
{
m_mouse_pos = newPos;
return os::instance()->mousePosition();
}
const gfx::Point& get_mouse_position()
void set_mouse_position(const gfx::Point& newPos,
Display* display)
{
return m_mouse_pos;
}
if (display && display != mouse_display)
_internal_set_mouse_display(display);
void set_mouse_position(const gfx::Point& newPos)
{
if (mouse_display)
mouse_display->nativeWindow()->setMousePosition(newPos);
_internal_set_mouse_position(newPos);
if (display)
display->nativeWindow()->setMousePosition(newPos);
else
os::instance()->setMousePosition(newPos);
}
void execute_from_ui_thread(std::function<void()>&& func)

View File

@ -40,6 +40,8 @@ namespace ui {
ClipboardDelegate* m_clipboardDelegate;
};
void set_multiple_displays(bool multi);
bool get_multiple_displays();
gfx::Size get_desktop_size();
void set_clipboard_text(const std::string& text);
@ -60,12 +62,15 @@ namespace ui {
void show_mouse_cursor();
void _internal_set_mouse_display(Display* display);
void _internal_free_mouse_display(Display* display);
void _internal_no_mouse_position();
void _internal_set_mouse_position(const gfx::Point& newPos);
const gfx::Point& get_mouse_position();
void set_mouse_position(const gfx::Point& newPos);
// Returns desktop/screen mouse position (relative to no-display)
gfx::Point get_mouse_position();
// Sets the mouse position relative to a specific display (or
// relative to the desktop if it's nullptr)
void set_mouse_position(const gfx::Point& newPos,
Display* display);
void execute_from_ui_thread(std::function<void()>&& func);
bool is_ui_thread();

View File

@ -120,13 +120,13 @@ void TooltipManager::onTick()
int arrowAlign = m_target.tipInfo.arrowAlign;
gfx::Rect target = m_target.widget->bounds();
if (!arrowAlign)
target.setOrigin(ui::get_mouse_position()+12*guiscale());
target.setOrigin(m_target.widget->mousePosInDisplay()+12*guiscale());
if (m_tipWindow->pointAt(arrowAlign,
target,
m_target.widget->display())) {
// TODO create a native transparent window for the tooltip
m_tipWindow->setDisplay(m_target.widget->display());
m_tipWindow->setDisplay(m_target.widget->display(), false);
m_tipWindow->openWindow();
}
else {

View File

@ -499,6 +499,11 @@ Widget* Widget::pick(const gfx::Point& pt,
return const_cast<Widget*>(picked);
}
Widget* Widget::pickFromScreenPos(const gfx::Point& screenPos) const
{
return pick(display()->nativeWindow()->pointFromScreen(screenPos));
}
bool Widget::hasChild(Widget* child)
{
ASSERT_VALID_WIDGET(child);
@ -762,13 +767,13 @@ void Widget::getRegion(gfx::Region& region)
void Widget::getDrawableRegion(gfx::Region& region, DrawableRegionFlags flags)
{
Window* window = this->window();
Display* display = this->display();
getRegion(region);
// Cut the top windows areas
if (flags & kCutTopWindows) {
Window* window = this->window();
Display* display = this->display();
const auto& uiWindows = display->getWindows();
// Reverse iterator
@ -815,7 +820,7 @@ void Widget::getDrawableRegion(gfx::Region& region, DrawableRegionFlags flags)
// Intersect with the parent area
if (!hasFlags(DECORATIVE)) {
Widget* p = this->parent();
while (p) {
while (p && p->type() != kManagerWidget) {
region &= Region(p->childrenBounds());
p = p->parent();
}
@ -827,25 +832,15 @@ void Widget::getDrawableRegion(gfx::Region& region, DrawableRegionFlags flags)
}
}
// Limit to the manager area
{
Window* window = this->window();
Manager* manager = (window ? window->manager(): nullptr);
while (manager) {
View* view = View::getView(manager);
// Limit to the displayable area
View* view = View::getView(display->containedWidget());
Rect cpos;
if (view)
cpos = static_cast<View*>(view)->viewportBounds();
else
cpos = display->containedWidget()->bounds();
Rect cpos;
if (view)
cpos = static_cast<View*>(view)->viewportBounds();
else
cpos = manager->childrenBounds();
region &= Region(cpos);
window = manager->window();
manager = (window ? window->manager(): nullptr);
}
}
region &= Region(cpos);
}
int Widget::textWidth() const
@ -982,7 +977,6 @@ void Widget::flushRedraw()
processing.push(this);
}
Display* display = this->display();
Manager* manager = this->manager();
ASSERT(manager);
if (!manager)
@ -1017,6 +1011,7 @@ void Widget::flushRedraw()
Region::const_iterator it = widget->m_updateRegion.begin();
// Draw the widget
Display* display = widget->display();
int count = nrects-1;
for (c=0; c<nrects; ++c, ++it, --count) {
// Create the draw message
@ -1094,23 +1089,28 @@ bool Widget::paintEvent(Graphics* graphics,
enableFlags(HIDDEN);
if (parent()) {
if (parent()->display() == display()) {
gfx::Region rgn(parent()->bounds());
rgn &= gfx::Region(
graphics->getClipBounds().offset(
graphics->getInternalDeltaX(),
graphics->getInternalDeltaY()));
parent()->paint(graphics, rgn, true);
}
else {
// TODO clear surface with transparent color, the following
// line doesn't work because we have to specify the
// SkBlendMode::kSrc mode instead of
// SkBlendMode::kSrcOver
Widget* parentWidget;
if (type() == kWindowWidget) {
parentWidget = display()->containedWidget();
}
else {
parentWidget = parent();
}
if (parentWidget) {
gfx::Region rgn(parentWidget->bounds());
rgn &= gfx::Region(
graphics->getClipBounds().offset(
graphics->getInternalDeltaX(),
graphics->getInternalDeltaY()));
parentWidget->paint(graphics, rgn, true);
}
else {
// TODO clear surface with transparent color, the following
// line doesn't work because we have to specify the
// SkBlendMode::kSrc mode instead of
// SkBlendMode::kSrcOver
//graphics->fillRect(gfx::rgba(0, 0, 0, 0), clientBounds());
}
//graphics->fillRect(gfx::rgba(0, 0, 0, 0), clientBounds());
}
disableFlags(HIDDEN);
@ -1231,9 +1231,10 @@ void Widget::closeWindow()
w->closeWindow(this);
}
void Widget::broadcastMouseMessage(WidgetsList& targets)
void Widget::broadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets)
{
onBroadcastMouseMessage(targets);
onBroadcastMouseMessage(screenPos, targets);
}
// ===============================================================
@ -1384,7 +1385,12 @@ bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
bool Widget::hasMouseOver() const
{
return (this == pick(get_mouse_position()));
return (this == pickFromScreenPos(get_mouse_position()));
}
gfx::Point Widget::mousePosInDisplay() const
{
return display()->nativeWindow()->pointFromScreen(get_mouse_position());
}
void Widget::setMnemonic(int mnemonic)
@ -1566,7 +1572,8 @@ void Widget::onPaint(PaintEvent& ev)
clientBounds());
}
void Widget::onBroadcastMouseMessage(WidgetsList& targets)
void Widget::onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets)
{
// Do nothing
}

View File

@ -185,6 +185,8 @@ namespace ui {
Widget* pick(const gfx::Point& pt,
const bool checkParentsVisibility = true) const;
virtual Widget* pickFromScreenPos(const gfx::Point& screenPos) const;
bool hasChild(Widget* child);
bool hasAncestor(Widget* ancestor);
Widget* findChild(const char* id);
@ -318,7 +320,8 @@ namespace ui {
bool sendMessage(Message* msg);
void closeWindow();
void broadcastMouseMessage(WidgetsList& targets);
void broadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets);
// ===============================================================
// SIZE & POSITION
@ -342,8 +345,20 @@ namespace ui {
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;
// Returns the mouse position relative to the top-left corner of
// the ui::Display's client area/content rect.
gfx::Point mousePosInDisplay() const;
// Returns the mouse position relative to the top-left cornder of
// the widget bounds.
gfx::Point mousePosInClientBounds() const {
return toClient(mousePosInDisplay());
}
// Offer the capture to widgets of the given type. Returns true if
// the capture was passed to other widget.
bool offerCapture(ui::MouseMessage* mouseMsg, int widget_type);
@ -380,7 +395,8 @@ namespace ui {
virtual void onSaveLayout(SaveLayoutEvent& ev);
virtual void onResize(ResizeEvent& ev);
virtual void onPaint(PaintEvent& ev);
virtual void onBroadcastMouseMessage(WidgetsList& targets);
virtual void onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets);
virtual void onInitTheme(InitThemeEvent& ev);
virtual void onSetDecorativeWidgetBounds();
virtual void onVisible(bool visible);

View File

@ -112,6 +112,7 @@ Window::Window(Type type, const std::string& text)
, m_closer(nullptr)
, m_titleLabel(nullptr)
, m_closeButton(nullptr)
, m_ownDisplay(false)
, m_isDesktop(type == DesktopWindow)
, m_isMoveable(!m_isDesktop)
, m_isSizeable(!m_isDesktop)
@ -145,12 +146,16 @@ Display* Window::display() const
return nullptr;
}
void Window::setDisplay(Display* display)
void Window::setDisplay(Display* display, const bool own)
{
if (m_display)
if (m_display) {
if (m_ownDisplay)
m_lastFrame = m_display->nativeWindow()->frame();
m_display->removeWindow(this);
}
m_display = display;
m_ownDisplay = own;
if (m_display)
m_display->addWindow(this);
@ -198,7 +203,7 @@ void Window::onHitTest(HitTestEvent& ev)
{
HitTest ht = HitTestNowhere;
// If this window is not movable or we are not completely visible.
// If this window is not movable
if (!m_isMoveable) {
ev.setHit(ht);
return;
@ -207,7 +212,7 @@ void Window::onHitTest(HitTestEvent& ev)
// TODO check why this is necessary, there should be a bug in
// the manager where we are receiving mouse events and are not
// the top most window.
Widget* picked = manager()->pick(ev.point());
Widget* picked = pick(ev.point());
if (picked &&
picked != this &&
picked->type() != kWindowTitleLabelWidget) {
@ -230,6 +235,14 @@ void Window::onHitTest(HitTestEvent& ev)
}
// Resize
else if (m_isSizeable) {
#ifdef __APPLE__
// TODO on macOS we cannot start resize actions on native windows
if (ownDisplay()) {
ev.setHit(ht);
return;
}
#endif
if ((x >= pos.x) && (x < cpos.x)) {
if ((y >= pos.y) && (y < cpos.y))
ht = HitTestBorderNW;
@ -326,19 +339,28 @@ void Window::expandWindow(const gfx::Size& size)
{
const gfx::Rect oldBounds = bounds();
setBounds(gfx::Rect(bounds().origin(), size));
if (ownDisplay()) {
os::Window* nativeWindow = display()->nativeWindow();
const int scale = nativeWindow->scale();
gfx::Rect frame = nativeWindow->frame();
frame.setSize(size * scale);
nativeWindow->setFrame(frame);
layout();
manager()->invalidateRect(oldBounds);
layout();
invalidate();
}
else {
setBounds(gfx::Rect(bounds().origin(), size));
layout();
manager()->invalidateRect(oldBounds);
}
}
void Window::openWindow()
{
if (!parent()) {
if (m_isAutoRemap)
centerWindow();
Manager::getDefault()->_openWindow(this);
Manager::getDefault()->_openWindow(this, m_isAutoRemap);
}
}
@ -348,9 +370,7 @@ void Window::openWindowInForeground()
openWindow();
MessageLoop loop(manager());
while (!hasFlags(HIDDEN))
loop.pumpMessages();
Manager::getDefault()->_runModalWindow(this);
m_isForeground = false;
}
@ -401,6 +421,26 @@ bool Window::onProcessMessage(Message* msg)
else
*clickedWindowPos = bounds();
// Handle native window action
if (ownDisplay()) {
os::WindowAction action = os::WindowAction::Cancel;
switch (m_hitTest) {
case HitTestCaption: action = os::WindowAction::Move; break;
case HitTestBorderNW: action = os::WindowAction::ResizeFromTopLeft; break;
case HitTestBorderN: action = os::WindowAction::ResizeFromTop; break;
case HitTestBorderNE: action = os::WindowAction::ResizeFromTopRight; break;
case HitTestBorderW: action = os::WindowAction::ResizeFromLeft; break;
case HitTestBorderE: action = os::WindowAction::ResizeFromRight; break;
case HitTestBorderSW: action = os::WindowAction::ResizeFromBottomLeft; break;
case HitTestBorderS: action = os::WindowAction::ResizeFromBottom; break;
case HitTestBorderSE: action = os::WindowAction::ResizeFromBottomRight; break;
}
if (action != os::WindowAction::Cancel) {
display()->nativeWindow()->performWindowAction(action, nullptr);
return true;
}
}
captureMouse();
return true;
}
@ -521,6 +561,50 @@ bool Window::onProcessMessage(Message* msg)
return Widget::onProcessMessage(msg);
}
// TODO similar to Manager::onInvalidateRegion
void Window::onInvalidateRegion(const gfx::Region& region)
{
if (!ownDisplay()) {
Widget::onInvalidateRegion(region);
return;
}
if (!isVisible() || region.contains(bounds()) == gfx::Region::Out)
return;
Display* display = this->display();
// Intersect only with window bounds, we don't need to use
// getDrawableRegion() because each sub-window in the display will
// be processed in the following for() loop
gfx::Region reg1;
reg1.createIntersection(region, gfx::Region(bounds()));
// Redraw windows from top to background.
for (auto window : display->getWindows()) {
// Invalidating the manager only works for the main display, to
// invalidate windows you have to invalidate them.
if (window->ownDisplay()) {
ASSERT(this == window);
break;
}
// Invalidate regions of this window
window->invalidateRegion(reg1);
// Clip this window area for the next window.
gfx::Region reg2;
window->getRegion(reg2);
reg1 -= reg2;
}
// TODO we should be able to modify m_updateRegion directly here,
// so we avoid the getDrawableRegion() call from
// Widget::onInvalidateRegion().
if (!reg1.isEmpty())
Widget::onInvalidateRegion(reg1);
}
void Window::onResize(ResizeEvent& ev)
{
windowSetPosition(ev.bounds());
@ -555,9 +639,11 @@ void Window::onSizeHint(SizeHintEvent& ev)
}
}
void Window::onBroadcastMouseMessage(WidgetsList& targets)
void Window::onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets)
{
targets.push_back(this);
if (!ownDisplay() || display()->nativeWindow()->frame().contains(screenPos))
targets.push_back(this);
// Continue sending the message to siblings windows until a desktop
// or foreground window.
@ -566,7 +652,7 @@ void Window::onBroadcastMouseMessage(WidgetsList& targets)
Widget* sibling = nextSibling();
if (sibling)
sibling->broadcastMouseMessage(targets);
sibling->broadcastMouseMessage(screenPos, targets);
}
void Window::onSetText()

View File

@ -28,8 +28,9 @@ namespace ui {
explicit Window(Type type, const std::string& text = "");
~Window();
bool ownDisplay() const { return m_ownDisplay; }
Display* display() const;
void setDisplay(Display* display);
void setDisplay(Display* display, const bool own);
Widget* closer() const { return m_closer; }
@ -61,14 +62,22 @@ namespace ui {
HitTest hitTest(const gfx::Point& point);
// Last native window frame bounds. Saved just before we close the
// native window so we can save this information in the
// configuration file.
gfx::Rect lastNativeFrame() const { return m_lastFrame; }
void loadNativeFrame(const gfx::Rect& frame) { m_lastFrame = frame; }
// Signals
obs::signal<void (CloseEvent&)> Close;
protected:
virtual bool onProcessMessage(Message* msg) override;
virtual void onInvalidateRegion(const gfx::Region& region) override;
virtual void onResize(ResizeEvent& ev) override;
virtual void onSizeHint(SizeHintEvent& ev) override;
virtual void onBroadcastMouseMessage(WidgetsList& targets) override;
virtual void onBroadcastMouseMessage(const gfx::Point& screenPos,
WidgetsList& targets) override;
virtual void onSetText() override;
// New events
@ -88,6 +97,7 @@ namespace ui {
Widget* m_closer;
Label* m_titleLabel;
ButtonBase* m_closeButton;
bool m_ownDisplay : 1;
bool m_isDesktop : 1;
bool m_isMoveable : 1;
bool m_isSizeable : 1;
@ -96,6 +106,7 @@ namespace ui {
bool m_isForeground : 1;
bool m_isAutoRemap : 1;
int m_hitTest;
gfx::Rect m_lastFrame;
};
} // namespace ui