From bcd69495ce65aa934a9410a23134117f5c60c870 Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 19 Mar 2021 18:57:56 -0300 Subject: [PATCH] Fix several issues locating windows with multiple UI windows We've refactored the code to support locating windows (and popups windows + hot regions) correctly in both modes: with one ui::Display and with multiple ui::Displays. For this we've added a new ui::fit_bounds() function that works in both modes. --- laf | 2 +- src/app/commands/cmd_canvas_size.cpp | 13 ++- src/app/commands/cmd_change_pixel_format.cpp | 7 +- src/app/commands/cmd_keyboard_shortcuts.cpp | 15 ++- src/app/commands/cmd_paste_text.cpp | 6 +- src/app/console.cpp | 17 +-- src/app/modules/gui.cpp | 12 ++- src/app/ui/browser_view.cpp | 2 +- src/app/ui/brush_popup.cpp | 35 +++--- src/app/ui/brush_popup.h | 5 +- src/app/ui/color_bar.cpp | 11 +- src/app/ui/color_button.cpp | 94 +++++++++++----- src/app/ui/color_button.h | 7 ++ src/app/ui/color_wheel.cpp | 4 +- src/app/ui/context_bar.cpp | 36 ++++--- src/app/ui/data_recovery_view.cpp | 4 +- src/app/ui/devconsole_view.cpp | 2 +- src/app/ui/doc_view.cpp | 2 +- src/app/ui/dynamics_popup.cpp | 19 ++-- src/app/ui/editor/editor.cpp | 2 +- src/app/ui/editor/standby_state.cpp | 2 +- src/app/ui/font_popup.cpp | 17 ++- src/app/ui/font_popup.h | 4 +- src/app/ui/home_view.cpp | 2 +- src/app/ui/main_window.cpp | 8 +- src/app/ui/notifications.cpp | 8 +- src/app/ui/palette_popup.cpp | 15 ++- src/app/ui/palette_popup.h | 4 +- src/app/ui/popup_window_pin.cpp | 6 +- src/app/ui/preview_editor.cpp | 39 +++---- src/app/ui/status_bar.cpp | 2 + src/app/ui/timeline/timeline.cpp | 16 +-- src/ui/combobox.cpp | 2 +- src/ui/display.h | 1 + src/ui/entry.cpp | 2 +- src/ui/fit_bounds.cpp | 49 ++++++++- src/ui/fit_bounds.h | 22 +++- src/ui/int_entry.cpp | 37 ++++--- src/ui/manager.cpp | 57 ++++++++-- src/ui/manager.h | 1 + src/ui/menu.cpp | 106 ++++++++++--------- src/ui/menu.h | 7 +- src/ui/popup_window.cpp | 26 +++-- src/ui/popup_window.h | 10 +- src/ui/widget.cpp | 10 ++ src/ui/widget.h | 3 + src/ui/window.cpp | 21 +++- src/ui/window.h | 8 +- 48 files changed, 517 insertions(+), 263 deletions(-) diff --git a/laf b/laf index d290eaf37f..1106d7488d 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit d290eaf37fcb1a48cee433e6b9a1b73854671df0 +Subproject commit 1106d7488dcf1410815601c9eaa38407bc243893 diff --git a/src/app/commands/cmd_canvas_size.cpp b/src/app/commands/cmd_canvas_size.cpp index 840108c10e..53d9e361e9 100644 --- a/src/app/commands/cmd_canvas_size.cpp +++ b/src/app/commands/cmd_canvas_size.cpp @@ -342,12 +342,17 @@ void CanvasSizeCommand::onExecute(Context* context) // Find best position for the window on the editor if (DocView* docView = static_cast(context)->activeView()) { - window->positionWindow( - docView->bounds().x2() - window->bounds().w, - docView->bounds().y); + Display* display = ui::Manager::getDefault()->display(); + ui::fit_bounds(display, + window.get(), + gfx::Rect(docView->bounds().x2() - window->bounds().w, + docView->bounds().y, + window->bounds().w, + window->bounds().h)); } - else + else { window->centerWindow(); + } load_window_pos(window.get(), "CanvasSize"); window->setVisible(true); diff --git a/src/app/commands/cmd_change_pixel_format.cpp b/src/app/commands/cmd_change_pixel_format.cpp index 09bb0ae1a7..138dcf0831 100644 --- a/src/app/commands/cmd_change_pixel_format.cpp +++ b/src/app/commands/cmd_change_pixel_format.cpp @@ -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 @@ -246,10 +246,7 @@ class ColorModeWindow : public app::gen::ColorMode { advancedCheck()->Click.connect( [this](ui::Event&){ advanced()->setVisible(advancedCheck()->isSelected()); - - const gfx::Rect origBounds = bounds(); - setBounds(gfx::Rect(bounds().origin(), sizeHint())); - manager()->invalidateRect(origBounds); + expandWindow(sizeHint()); }); } else { diff --git a/src/app/commands/cmd_keyboard_shortcuts.cpp b/src/app/commands/cmd_keyboard_shortcuts.cpp index 56df8062ee..1c56f0acac 100644 --- a/src/app/commands/cmd_keyboard_shortcuts.cpp +++ b/src/app/commands/cmd_keyboard_shortcuts.cpp @@ -32,6 +32,7 @@ #include "base/string.h" #include "fmt/format.h" #include "ui/alert.h" +#include "ui/fit_bounds.h" #include "ui/graphics.h" #include "ui/listitem.h" #include "ui/message.h" @@ -882,11 +883,19 @@ void KeyboardShortcutsCommand::onExecute(Context* context) std::string neededSearchCopy = m_search; KeyboardShortcutsWindow window(keys, menuKeys, neededSearchCopy); - gfx::Size displaySize = ui::get_desktop_size(); - window.setBounds(gfx::Rect(0, 0, displaySize.w*3/4, displaySize.h*3/4)); + ui::Display* mainDisplay = Manager::getDefault()->display(); + ui::fit_bounds(mainDisplay, &window, + gfx::Rect(mainDisplay->size()), + [](const gfx::Rect& workarea, + gfx::Rect& bounds, + std::function getWidgetBounds) { + gfx::Point center = bounds.center(); + bounds.setSize(workarea.size()*3/4); + bounds.setOrigin(center - gfx::Point(bounds.size()/2)); + }); + window.loadLayout(); - window.centerWindow(); window.setVisible(true); window.openWindowInForeground(); diff --git a/src/app/commands/cmd_paste_text.cpp b/src/app/commands/cmd_paste_text.cpp index 1619fc7404..5b6d308ac9 100644 --- a/src/app/commands/cmd_paste_text.cpp +++ b/src/app/commands/cmd_paste_text.cpp @@ -124,11 +124,7 @@ class PasteTextWindow : public app::gen::PasteText { } if (!m_fontPopup->isVisible()) { - gfx::Size displaySize = manager()->display()->size(); - gfx::Rect bounds = fontFace()->bounds(); - m_fontPopup->showPopup( - gfx::Rect(bounds.x, bounds.y+bounds.h, - displaySize.w/2, displaySize.h/2)); + m_fontPopup->showPopup(display(), fontFace()->bounds()); } else { m_fontPopup->closeWindow(NULL); diff --git a/src/app/console.cpp b/src/app/console.cpp index 2429042326..7d0804d915 100644 --- a/src/app/console.cpp +++ b/src/app/console.cpp @@ -71,14 +71,15 @@ class Console::ConsoleWindow : public Window { void centerConsole() { initTheme(); - remapWindow(); - - // TODO center to main window or screen workspace - gfx::Size displaySize = manager()->display()->size(); - setBounds(gfx::Rect(0, 0, displaySize.w*9/10, displaySize.h*6/10)); - - centerWindow(); - invalidate(); + Display* display = ui::Manager::getDefault()->display(); + const gfx::Rect displayRc = display->bounds(); + gfx::Rect rc; + rc.w = displayRc.w*9/10; + rc.h = displayRc.h*6/10; + rc.x = displayRc.x + displayRc.w/2 - rc.w/2; + rc.y = displayRc.y + displayRc.h/2 - rc.h/2; + + ui::fit_bounds(display, this, rc); } private: diff --git a/src/app/modules/gui.cpp b/src/app/modules/gui.cpp index 47b5cd36e0..11ed312b2e 100644 --- a/src/app/modules/gui.cpp +++ b/src/app/modules/gui.cpp @@ -693,11 +693,15 @@ void CustomizedGuiManager::onInitTheme(InitThemeEvent& ev) void CustomizedGuiManager::onNewDisplayConfiguration(Display* display) { Manager::onNewDisplayConfiguration(display); - save_gui_config(); - // TODO Should we provide a more generic way for all ui::Window to - // detect the os::Window (or UI Screen Scaling) change? - Console::notifyNewDisplayConfiguration(); + // Only whne the main display/window is modified + if (display == this->display()) { + save_gui_config(); + + // TODO Should we provide a more generic way for all ui::Window to + // detect the os::Window (or UI Screen Scaling) change? + Console::notifyNewDisplayConfiguration(); + } } std::string CustomizedGuiManager::loadLayout(Widget* widget) diff --git a/src/app/ui/browser_view.cpp b/src/app/ui/browser_view.cpp index 0e22a1bb85..22bc63bc31 100644 --- a/src/app/ui/browser_view.cpp +++ b/src/app/ui/browser_view.cpp @@ -589,7 +589,7 @@ void BrowserView::onTabPopup(Workspace* workspace) if (!menu) return; - menu->showPopup(mousePosInDisplay()); + menu->showPopup(mousePosInDisplay(), display()); } } // namespace app diff --git a/src/app/ui/brush_popup.cpp b/src/app/ui/brush_popup.cpp index 63d78df548..403d32b098 100644 --- a/src/app/ui/brush_popup.cpp +++ b/src/app/ui/brush_popup.cpp @@ -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 @@ -36,6 +36,7 @@ #include "os/surface.h" #include "os/system.h" #include "ui/button.h" +#include "ui/fit_bounds.h" #include "ui/link_label.h" #include "ui/listitem.h" #include "ui/menu.h" @@ -52,22 +53,24 @@ using namespace ui; namespace { -void show_popup_menu(PopupWindow* popupWindow, Menu* popupMenu, - const gfx::Point& pt) +void show_popup_menu(PopupWindow* popupWindow, + Menu* popupMenu, + const gfx::Point& pt, + Display* display) { // Here we make the popup window temporaly floating, so it's // not closed by the popup menu. popupWindow->makeFloating(); - popupMenu->showPopup(pt); + popupMenu->showPopup(pt, display); // Add the menu popup region to the window popup hot region so it's // not closed after we close the menu. popupWindow->makeFixed(); gfx::Region rgn; - rgn.createUnion(gfx::Region(popupWindow->bounds()), - gfx::Region(popupMenu->bounds())); + rgn.createUnion(gfx::Region(popupWindow->boundsOnScreen()), + gfx::Region(popupMenu->boundsOnScreen())); popupWindow->setHotRegion(rgn); } @@ -194,7 +197,8 @@ class BrushOptionsItem : public ButtonSet::Item { m_changeFlags = true; show_popup_menu(m_popup, &menu, - gfx::Point(origin().x, origin().y+bounds().h)); + gfx::Point(origin().x, origin().y+bounds().h), + display()); if (m_changeFlags) { brush = m_brushes.getBrushSlot(m_slot); @@ -299,8 +303,10 @@ class NewBrushOptionsItem : public ButtonSet::Item { params.shade()->setSelected(saveBrush.shade()); params.pixelPerfect()->setSelected(saveBrush.pixelPerfect()); - show_popup_menu(static_cast(window()), &menu, - gfx::Point(origin().x, origin().y+bounds().h)); + show_popup_menu(static_cast(window()), + &menu, + gfx::Point(origin().x, origin().y+bounds().h), + display()); // Save preferences if (saveBrush.brushType() != params.brushType()->isSelected()) @@ -384,7 +390,8 @@ void BrushPopup::setBrush(Brush* brush) } } -void BrushPopup::regenerate(const gfx::Rect& box) +void BrushPopup::regenerate(ui::Display* display, + const gfx::Point& pos) { auto& brushSlots = App::instance()->brushes().getBrushSlots(); @@ -425,8 +432,8 @@ void BrushPopup::regenerate(const gfx::Rect& box) m_box.addChild(m_customBrushes); // Resize the window and change the hot region. - setBounds(gfx::Rect(box.origin(), sizeHint())); - setHotRegion(gfx::Region(bounds())); + fit_bounds(display, this, gfx::Rect(pos, sizeHint())); + setHotRegion(gfx::Region(boundsOnScreen())); } void BrushPopup::onBrushChanges() @@ -435,7 +442,9 @@ void BrushPopup::onBrushChanges() gfx::Region rgn; getDrawableRegion(rgn, kCutTopWindowsAndUseChildArea); - regenerate(bounds()); + Display* mainDisplay = manager()->display(); + regenerate(mainDisplay, + mainDisplay->nativeWindow()->pointFromScreen(boundsOnScreen().origin())); invalidate(); parent()->invalidateRegion(rgn); diff --git a/src/app/ui/brush_popup.h b/src/app/ui/brush_popup.h index a5dc1af770..34d79f046c 100644 --- a/src/app/ui/brush_popup.h +++ b/src/app/ui/brush_popup.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2020 Igara Studio S.A. +// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2001-2015 David Capello // // This program is distributed under the terms of @@ -21,7 +21,8 @@ namespace app { BrushPopup(); void setBrush(doc::Brush* brush); - void regenerate(const gfx::Rect& box); + void regenerate(ui::Display* display, + const gfx::Point& pos); static os::SurfaceRef createSurfaceForBrush(const doc::BrushRef& brush); diff --git a/src/app/ui/color_bar.cpp b/src/app/ui/color_bar.cpp index 40db5d3917..9c1eeef01b 100644 --- a/src/app/ui/color_bar.cpp +++ b/src/app/ui/color_bar.cpp @@ -1868,7 +1868,7 @@ void ColorBar::showPaletteSortOptions() asc.Click.connect([this]{ setAscending(true); }); des.Click.connect([this]{ setAscending(false); }); - menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu.showPopup(gfx::Point(bounds.x, bounds.y2()), display()); } void ColorBar::showPalettePresets() @@ -1884,16 +1884,13 @@ void ColorBar::showPalettePresets() } if (!m_palettePopup->isVisible()) { - gfx::Size displaySize = ui::get_desktop_size(); gfx::Rect bounds = m_buttons.getItem( static_cast(PalButton::PRESETS))->bounds(); - m_palettePopup->showPopup( - gfx::Rect(bounds.x, bounds.y+bounds.h, - displaySize.w/2, displaySize.h*3/4)); + m_palettePopup->showPopup(display(), bounds); } else { - m_palettePopup->closeWindow(NULL); + m_palettePopup->closeWindow(nullptr); } } @@ -1904,7 +1901,7 @@ void ColorBar::showPaletteOptions() gfx::Rect bounds = m_buttons.getItem( static_cast(PalButton::OPTIONS))->bounds(); - menu->showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu->showPopup(gfx::Point(bounds.x, bounds.y2()), display()); } } diff --git a/src/app/ui/color_button.cpp b/src/app/ui/color_button.cpp index 8546701232..c3102192b6 100644 --- a/src/app/ui/color_button.cpp +++ b/src/app/ui/color_button.cpp @@ -53,6 +53,7 @@ ColorButton::ColorButton(const app::Color& color, , m_color(color) , m_pixelFormat(pixelFormat) , m_window(nullptr) + , m_desktopCoords(false) , m_dependOnLayer(false) , m_options(options) { @@ -306,9 +307,12 @@ void ColorButton::onLoadLayout(ui::LoadLayoutEvent& ev) { if (canPin()) { bool pinned = false; + + m_desktopCoords = false; ev.stream() >> pinned; if (ev.stream() && pinned) - ev.stream() >> m_windowDefaultBounds; + ev.stream() >> m_windowDefaultBounds + >> m_desktopCoords; m_hiddenPopupBounds = m_windowDefaultBounds; } @@ -316,8 +320,12 @@ void ColorButton::onLoadLayout(ui::LoadLayoutEvent& ev) void ColorButton::onSaveLayout(ui::SaveLayoutEvent& ev) { - if (canPin() && m_window && m_window->isPinned()) - ev.stream() << 1 << ' ' << m_window->bounds(); + if (canPin() && m_window && m_window->isPinned()) { + if (m_desktopCoords) + ev.stream() << 1 << ' ' << m_window->lastNativeFrame() << ' ' << 1; + else + ev.stream() << 1 << ' ' << m_window->bounds() << ' ' << 0; + } else ev.stream() << 0; } @@ -339,37 +347,39 @@ void ColorButton::openPopup(const bool forcePinned) } m_window->setColor(m_color, ColorPopup::ChangeType); - m_window->openWindow(); - - gfx::Size displaySize = ui::get_desktop_size(); - gfx::Rect winBounds; - if (!pinned || (forcePinned && m_hiddenPopupBounds.isEmpty())) { - winBounds = gfx::Rect(m_window->bounds().origin(), - m_window->sizeHint()); - winBounds.x = base::clamp(bounds().x, 0, displaySize.w-winBounds.w); - if (bounds().y2() <= displaySize.h-winBounds.h) - winBounds.y = std::max(0, bounds().y2()); - else - winBounds.y = std::max(0, bounds().y-winBounds.h); - } - else if (forcePinned) { - winBounds = m_hiddenPopupBounds; - } - else { - winBounds = m_windowDefaultBounds; - } - winBounds.x = base::clamp(winBounds.x, 0, displaySize.w-winBounds.w); - winBounds.y = base::clamp(winBounds.y, 0, displaySize.h-winBounds.h); - m_window->setBounds(winBounds); + m_window->remapWindow(); + + fit_bounds( + display(), + m_window, + gfx::Rect(m_window->sizeHint()), + [this, pinned, forcePinned](const gfx::Rect& workarea, + gfx::Rect& winBounds, + std::function getWidgetBounds) { + if (!pinned || (forcePinned && m_hiddenPopupBounds.isEmpty())) { + gfx::Rect bounds = getWidgetBounds(this); + + winBounds.x = base::clamp(bounds.x, workarea.x, workarea.x2()-winBounds.w); + if (bounds.y2()+winBounds.h <= workarea.y2()) + winBounds.y = std::max(0, bounds.y2()); + else + winBounds.y = std::max(0, bounds.y-winBounds.h); + } + else if (forcePinned) { + winBounds = convertBounds(m_hiddenPopupBounds); + } + else { + winBounds = convertBounds(m_windowDefaultBounds); + } + }); - m_window->manager()->dispatchMessages(); - m_window->layout(); + m_window->openWindow(); m_window->setPinned(pinned); // Add the ColorButton area to the ColorPopup hot-region if (!pinned) { - gfx::Rect rc = bounds().createUnion(m_window->bounds()); + gfx::Rect rc = boundsOnScreen().createUnion(m_window->boundsOnScreen()); rc.enlarge(8); gfx::Region rgn(rc); static_cast(m_window)->setHotRegion(rgn); @@ -386,7 +396,14 @@ void ColorButton::closePopup() void ColorButton::onWindowClose(ui::CloseEvent& ev) { - m_hiddenPopupBounds = m_window->bounds(); + if (get_multiple_displays()) { + m_desktopCoords = true; + m_hiddenPopupBounds = m_window->lastNativeFrame(); + } + else { + m_desktopCoords = false; + m_hiddenPopupBounds = m_window->bounds(); + } } void ColorButton::onWindowColorChange(const app::Color& color) @@ -418,4 +435,23 @@ void ColorButton::onActiveSiteChange(const Site& site) } } +gfx::Rect ColorButton::convertBounds(const gfx::Rect& bounds) const +{ + // Convert to desktop + if (get_multiple_displays() && !m_desktopCoords) { + auto nativeWindow = display()->nativeWindow(); + return gfx::Rect(nativeWindow->pointToScreen(bounds.origin()), + nativeWindow->pointToScreen(bounds.point2())); + } + // Convert to display + else if (!get_multiple_displays() && m_desktopCoords) { + auto nativeWindow = display()->nativeWindow(); + return gfx::Rect(nativeWindow->pointFromScreen(bounds.origin()), + nativeWindow->pointFromScreen(bounds.point2())); + } + // No conversion is required + else + return bounds; +} + } // namespace app diff --git a/src/app/ui/color_button.h b/src/app/ui/color_button.h index f55b7ab310..6fe9ba3e8c 100644 --- a/src/app/ui/color_button.h +++ b/src/app/ui/color_button.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -68,11 +69,17 @@ namespace app { void onWindowColorChange(const app::Color& color); bool canPin() const { return m_options.canPinSelector; } + // Used to convert saved bounds (m_window/hiddenDefaultBounds, + // which can be relative to the display or relative to the screen) + // to the current system of coordinates. + gfx::Rect convertBounds(const gfx::Rect& bounds) const; + app::Color m_color; PixelFormat m_pixelFormat; ColorPopup* m_window; gfx::Rect m_windowDefaultBounds; gfx::Rect m_hiddenPopupBounds; + bool m_desktopCoords; // True if m_windowDefault/hiddenPopupBounds are screen coordinates bool m_dependOnLayer; ColorButtonOptions m_options; }; diff --git a/src/app/ui/color_wheel.cpp b/src/app/ui/color_wheel.cpp index df5a2757a4..684fdcc3e1 100644 --- a/src/app/ui/color_wheel.cpp +++ b/src/app/ui/color_wheel.cpp @@ -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 @@ -412,7 +412,7 @@ void ColorWheel::onOptions() } gfx::Rect rc = m_options.bounds(); - menu.showPopup(gfx::Point(rc.x+rc.w, rc.y)); + menu.showPopup(gfx::Point(rc.x2(), rc.y), display()); } int ColorWheel::convertHueAngle(int hue, int dir) const diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index a67a5ab5ab..64ce77e343 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -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,6 +66,7 @@ #include "render/ordered_dither.h" #include "ui/button.h" #include "ui/combobox.h" +#include "ui/fit_bounds.h" #include "ui/int_entry.h" #include "ui/label.h" #include "ui/listbox.h" @@ -209,20 +210,19 @@ class ContextBar::BrushTypeField : public ButtonSet { private: // Returns a little rectangle that can be used by the popup as the // first brush position. - gfx::Rect getPopupBox() { + gfx::Point popupPosCandidate() const { Rect rc = bounds(); rc.y += rc.h - 2*guiscale(); - rc.setSize(sizeHint()); - return rc; + return rc.origin(); } void openPopup() { doc::BrushRef brush = m_owner->activeBrush(); - m_popupWindow.regenerate(getPopupBox()); + m_popupWindow.regenerate(display(), popupPosCandidate()); m_popupWindow.setBrush(brush.get()); - Region rgn(m_popupWindow.bounds().createUnion(bounds())); + Region rgn(m_popupWindow.boundsOnScreen().createUnion(boundsOnScreen())); m_popupWindow.setHotRegion(rgn); m_popupWindow.openWindow(); @@ -450,7 +450,8 @@ class ContextBar::PaintBucketSettingsField : public ButtonSet { } }); - menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu.showPopup(gfx::Point(bounds.x, bounds.y2()), + display()); deselectItems(); } @@ -502,7 +503,8 @@ class ContextBar::InkTypeField : public ButtonSet { AppMenus::instance() ->getInkPopupMenu() - ->showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + ->showPopup(gfx::Point(bounds.x, bounds.y2()), + display()); deselectItems(); } @@ -627,7 +629,8 @@ class ContextBar::InkShadesField : public HBox { } } - menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu.showPopup(gfx::Point(bounds.x, bounds.y2()), + display()); m_button.invalidate(); } @@ -808,7 +811,8 @@ class ContextBar::TransparentColorField : public HBox { masked.Click.connect([this]{ setOpaque(false); }); automatic.Click.connect([this]{ onAutomatic(); }); - menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu.showPopup(gfx::Point(bounds.x, bounds.y2()), + display()); } void onChangeColor() { @@ -905,7 +909,8 @@ class ContextBar::PivotField : public ButtonSet { app::gen::PivotPosition(buttonset.selectedItem())); }); - menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu.showPopup(gfx::Point(bounds.x, bounds.y2()), + display()); } void onPivotChange() { @@ -1151,8 +1156,10 @@ class ContextBar::DynamicsField : public ButtonSet const gfx::Rect bounds = this->bounds(); m_popup->remapWindow(); - m_popup->positionWindow(bounds.x, bounds.y+bounds.h); - m_popup->setHotRegion(gfx::Region(m_popup->bounds())); + fit_bounds(display(), m_popup.get(), + gfx::Rect(gfx::Point(bounds.x, bounds.y2()), + m_popup->sizeHint())); + m_popup->setHotRegion(gfx::Region(m_popup->boundsOnScreen())); m_popup->openWindow(); } @@ -1436,7 +1443,8 @@ class ContextBar::SymmetryField : public ButtonSet { doc->notifyGeneralUpdate(); }); - menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu.showPopup(gfx::Point(bounds.x, bounds.y2()), + display()); } } }; diff --git a/src/app/ui/data_recovery_view.cpp b/src/app/ui/data_recovery_view.cpp index 86dda5e5a7..94dd0fad60 100644 --- a/src/app/ui/data_recovery_view.cpp +++ b/src/app/ui/data_recovery_view.cpp @@ -413,7 +413,7 @@ void DataRecoveryView::onTabPopup(Workspace* workspace) if (!menu) return; - menu->showPopup(mousePosInDisplay()); + menu->showPopup(mousePosInDisplay(), display()); } void DataRecoveryView::onOpen() @@ -461,7 +461,7 @@ void DataRecoveryView::onOpenMenu() rawFrames.Click.connect([this]{ onOpenRaw(crash::RawImagesAs::kFrames); }); rawLayers.Click.connect([this]{ onOpenRaw(crash::RawImagesAs::kLayers); }); - menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h)); + menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h), display()); } void DataRecoveryView::onDelete() diff --git a/src/app/ui/devconsole_view.cpp b/src/app/ui/devconsole_view.cpp index 0ef400e046..d6a5b580bf 100644 --- a/src/app/ui/devconsole_view.cpp +++ b/src/app/ui/devconsole_view.cpp @@ -140,7 +140,7 @@ void DevConsoleView::onTabPopup(Workspace* workspace) if (!menu) return; - menu->showPopup(mousePosInDisplay()); + menu->showPopup(mousePosInDisplay(), display()); } bool DevConsoleView::onProcessMessage(Message* msg) diff --git a/src/app/ui/doc_view.cpp b/src/app/ui/doc_view.cpp index e623797392..d5449f4f5e 100644 --- a/src/app/ui/doc_view.cpp +++ b/src/app/ui/doc_view.cpp @@ -362,7 +362,7 @@ void DocView::onTabPopup(Workspace* workspace) ctx->setActiveView(this); ctx->updateFlags(); - menu->showPopup(mousePosInDisplay()); + menu->showPopup(mousePosInDisplay(), display()); } bool DocView::onProcessMessage(Message* msg) diff --git a/src/app/ui/dynamics_popup.cpp b/src/app/ui/dynamics_popup.cpp index 72cd9871ac..9e89bedc9d 100644 --- a/src/app/ui/dynamics_popup.cpp +++ b/src/app/ui/dynamics_popup.cpp @@ -370,15 +370,10 @@ void DynamicsPopup::onValuesChange(ButtonSet::Item* item) m_dynamics->velocityLabel()->setVisible(hasVelocity); m_dynamics->velocityPlaceholder()->setVisible(hasVelocity); - auto oldBounds = bounds(); - layout(); - setBounds(gfx::Rect(origin(), sizeHint())); + expandWindow(sizeHint()); - m_hotRegion |= gfx::Region(bounds()); + m_hotRegion |= gfx::Region(boundsOnScreen()); setHotRegion(m_hotRegion); - - if (isVisible()) - manager()->invalidateRect(oldBounds); } void DynamicsPopup::updateFromToText() @@ -400,7 +395,7 @@ bool DynamicsPopup::onProcessMessage(Message* msg) switch (msg->type()) { case kOpenMessage: - m_hotRegion = gfx::Region(bounds()); + m_hotRegion = gfx::Region(boundsOnScreen()); setHotRegion(m_hotRegion); manager()->addMessageFilter(kMouseMoveMessage, this); manager()->addMessageFilter(kMouseDownMessage, this); @@ -442,8 +437,13 @@ bool DynamicsPopup::onProcessMessage(Message* msg) } case kMouseDownMessage: { + if (!msg->display()) + break; + + os::Window* nativeWindow = msg->display()->nativeWindow(); auto mouseMsg = static_cast(msg); - auto picked = manager()->pick(mouseMsg->position()); + auto screenPos = nativeWindow->pointToScreen(mouseMsg->position()); + auto picked = manager()->pickFromScreenPos(screenPos); if ((picked == nullptr) || (picked->window() != this && picked->window() != m_ditheringSel->getWindowWidget())) { @@ -451,6 +451,7 @@ bool DynamicsPopup::onProcessMessage(Message* msg) } break; } + } return PopupWindow::onProcessMessage(msg); } diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 42078212ed..9cf9a4fd4b 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -2754,7 +2754,7 @@ void Editor::showAnimationSpeedMultiplierPopup(Option& playOnce, menu.addChild(item); } - menu.showPopup(mousePosInDisplay()); + menu.showPopup(mousePosInDisplay(), display()); if (isPlaying()) { // Re-play diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index e57f10b4fd..b23cb75080 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -256,7 +256,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) if (!editor->hasSelectedSlices()) params.set("id", base::convert_to(hit.slice()->id()).c_str()); AppMenuItem::setContextParams(params); - popupMenu->showPopup(msg->position()); + popupMenu->showPopup(msg->position(), editor->display()); AppMenuItem::setContextParams(Params()); } } diff --git a/src/app/ui/font_popup.cpp b/src/app/ui/font_popup.cpp index 4bdf44457b..c251c17d7f 100644 --- a/src/app/ui/font_popup.cpp +++ b/src/app/ui/font_popup.cpp @@ -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 @@ -29,6 +29,7 @@ #include "os/system.h" #include "ui/box.h" #include "ui/button.h" +#include "ui/fit_bounds.h" #include "ui/graphics.h" #include "ui/listitem.h" #include "ui/paint_event.h" @@ -183,15 +184,23 @@ FontPopup::FontPopup() m_listBox.addChild(new ListItem("No system fonts were found")); } -void FontPopup::showPopup(const gfx::Rect& bounds) +void FontPopup::showPopup(Display* display, + const gfx::Rect& buttonBounds) { m_popup->loadFont()->setEnabled(false); m_listBox.selectChild(NULL); - moveWindow(bounds); + ui::fit_bounds(display, this, + gfx::Rect(buttonBounds.x, buttonBounds.y2(), 32, 32), + [](const gfx::Rect& workarea, + gfx::Rect& bounds, + std::function getWidgetBounds) { + bounds.w = workarea.w / 2; + bounds.h = workarea.h / 2; + }); // Setup the hot-region - setHotRegion(gfx::Region(gfx::Rect(bounds).enlarge(32 * guiscale()))); + setHotRegion(gfx::Region(gfx::Rect(boundsOnScreen()).enlarge(32*guiscale()*display->scale()))); openWindow(); } diff --git a/src/app/ui/font_popup.h b/src/app/ui/font_popup.h index 2e51371d44..c9372d0b7d 100644 --- a/src/app/ui/font_popup.h +++ b/src/app/ui/font_popup.h @@ -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 @@ -26,7 +27,8 @@ namespace app { public: FontPopup(); - void showPopup(const gfx::Rect& bounds); + void showPopup(ui::Display* display, + const gfx::Rect& buttonBounds); obs::signal Load; diff --git a/src/app/ui/home_view.cpp b/src/app/ui/home_view.cpp index 5136cb55aa..b4f2d59be2 100644 --- a/src/app/ui/home_view.cpp +++ b/src/app/ui/home_view.cpp @@ -136,7 +136,7 @@ void HomeView::onTabPopup(Workspace* workspace) if (!menu) return; - menu->showPopup(mousePosInDisplay()); + menu->showPopup(mousePosInDisplay(), display()); } void HomeView::onWorkspaceViewSelected() diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 50b108d8ab..3322474dbd 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -40,7 +40,6 @@ #include "app/ui_context.h" #include "base/fs.h" #include "os/system.h" -#include "os/window.h" #include "ui/message.h" #include "ui/splitter.h" #include "ui/system.h" @@ -368,13 +367,12 @@ void MainWindow::onResize(ui::ResizeEvent& ev) { app::gen::MainWindow::onResize(ev); - gfx::Size desktopSize = ui::get_desktop_size(); ui::Display* display = this->display(); if ((display) && - (display->nativeWindow()->scale()*ui::guiscale() > 2) && + (display->scale()*ui::guiscale() > 2) && (!m_scalePanic) && - (desktopSize.w/ui::guiscale() < 320 || - desktopSize.h/ui::guiscale() < 260)) { + (display->size().w / ui::guiscale() < 320 || + display->size().h / ui::guiscale() < 260)) { showNotification(m_scalePanic = new ScreenScalePanic); } } diff --git a/src/app/ui/notifications.cpp b/src/app/ui/notifications.cpp index 95eb3263d0..4b6b857b9b 100644 --- a/src/app/ui/notifications.cpp +++ b/src/app/ui/notifications.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -76,9 +76,11 @@ void Notifications::onClick(ui::Event& ev) invalidate(); gfx::Rect bounds = this->bounds(); - m_popup.showPopup(gfx::Point( + m_popup.showPopup( + gfx::Point( bounds.x - m_popup.sizeHint().w, - bounds.y + bounds.h)); + bounds.y2()), + display()); } } // namespace app diff --git a/src/app/ui/palette_popup.cpp b/src/app/ui/palette_popup.cpp index 39a831cfef..1b80e1df70 100644 --- a/src/app/ui/palette_popup.cpp +++ b/src/app/ui/palette_popup.cpp @@ -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 @@ -22,6 +22,7 @@ #include "app/ui_context.h" #include "ui/box.h" #include "ui/button.h" +#include "ui/fit_bounds.h" #include "ui/scale.h" #include "ui/theme.h" #include "ui/view.h" @@ -58,13 +59,21 @@ PalettePopup::PalettePopup() initTheme(); } -void PalettePopup::showPopup(const gfx::Rect& bounds) +void PalettePopup::showPopup(ui::Display* display, + const gfx::Rect& buttonPos) { m_popup->loadPal()->setEnabled(false); m_popup->openFolder()->setEnabled(false); m_paletteListBox.selectChild(NULL); - moveWindow(bounds); + fit_bounds(display, this, + gfx::Rect(buttonPos.x, buttonPos.y2(), 32, 32), + [](const gfx::Rect& workarea, + gfx::Rect& bounds, + std::function getWidgetBounds) { + bounds.w = workarea.w/2; + bounds.h = workarea.h*3/4; + }); openWindowInForeground(); } diff --git a/src/app/ui/palette_popup.h b/src/app/ui/palette_popup.h index a4f63ee5be..c1d75a0026 100644 --- a/src/app/ui/palette_popup.h +++ b/src/app/ui/palette_popup.h @@ -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 @@ -26,7 +27,8 @@ namespace app { public: PalettePopup(); - void showPopup(const gfx::Rect& bounds); + void showPopup(ui::Display* display, + const gfx::Rect& buttonPos); protected: void onPalChange(doc::Palette* palette); diff --git a/src/app/ui/popup_window_pin.cpp b/src/app/ui/popup_window_pin.cpp index 597b51f96b..badebde0e8 100644 --- a/src/app/ui/popup_window_pin.cpp +++ b/src/app/ui/popup_window_pin.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -40,8 +40,8 @@ void PopupWindowPin::setPinned(const bool pinned) if (m_pinned) makeFloating(); else { - gfx::Rect rc = bounds(); - rc.enlarge(8); + gfx::Rect rc = boundsOnScreen(); + rc.enlarge(8 * guiscale()); setHotRegion(gfx::Region(rc)); makeFixed(); } diff --git a/src/app/ui/preview_editor.cpp b/src/app/ui/preview_editor.cpp index 4759c72f54..f8ab781ee8 100644 --- a/src/app/ui/preview_editor.cpp +++ b/src/app/ui/preview_editor.cpp @@ -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 @@ -33,6 +33,7 @@ #include "ui/base.h" #include "ui/button.h" #include "ui/close_event.h" +#include "ui/fit_bounds.h" #include "ui/message.h" #include "ui/system.h" @@ -219,25 +220,27 @@ bool PreviewEditorWindow::onProcessMessage(ui::Message* msg) { switch (msg->type()) { - case kOpenMessage: - { - SkinTheme* theme = SkinTheme::instance(); - - // Default bounds - gfx::Size desktopSize = ui::get_desktop_size(); - const int width = desktopSize.w/4; - const int height = desktopSize.h/4; - int extra = 2*theme->dimensions.miniScrollbarSize(); - setBounds( - gfx::Rect( - desktopSize.w - width - ToolBar::instance()->bounds().w - extra, - desktopSize.h - height - StatusBar::instance()->bounds().h - extra, - width, height)); - - load_window_pos(this, "MiniEditor", false); - invalidate(); + case kOpenMessage: { + Manager* manager = this->manager(); + Display* mainDisplay = manager->display(); + + gfx::Rect defaultBounds(mainDisplay->size() / 4); + SkinTheme* theme = SkinTheme::instance(); + gfx::Rect mainWindow = manager->bounds(); + + int extra = theme->dimensions.miniScrollbarSize(); + if (get_multiple_displays()) { + extra *= mainDisplay->scale(); } + defaultBounds.x = mainWindow.x2() - ToolBar::instance()->sizeHint().w - defaultBounds.w - extra; + defaultBounds.y = mainWindow.y2() - StatusBar::instance()->sizeHint().h - defaultBounds.h - extra; + + fit_bounds(mainDisplay, this, defaultBounds); + + load_window_pos(this, "MiniEditor", false); + invalidate(); break; + } case kCloseMessage: save_window_pos(this, "MiniEditor"); diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp index 7e4745af5f..76c09803c9 100644 --- a/src/app/ui/status_bar.cpp +++ b/src/app/ui/status_bar.cpp @@ -878,6 +878,8 @@ void StatusBar::showSnapToGridWarning(bool state) if (!m_snapToGridWindow) m_snapToGridWindow = new SnapToGridWindow; + m_snapToGridWindow->setDisplay(display(), false); + if (!m_snapToGridWindow->isVisible()) { m_snapToGridWindow->openWindow(); m_snapToGridWindow->remapWindow(); diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index cafed4de3e..3cb06bda16 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -1243,12 +1243,12 @@ bool Timeline::onProcessMessage(Message* msg) if (!m_confPopup->isVisible()) { gfx::Rect bounds = m_confPopup->bounds(); ui::fit_bounds(display(), BOTTOM, gearBounds, bounds); - - m_confPopup->moveWindow(bounds); + ui::fit_bounds(display(), m_confPopup, bounds); m_confPopup->openWindow(); } - else - m_confPopup->closeWindow(NULL); + else { + m_confPopup->closeWindow(nullptr); + } break; } @@ -1258,7 +1258,7 @@ bool Timeline::onProcessMessage(Message* msg) if (m_clk.frame == m_hot.frame) { Menu* popupMenu = AppMenus::instance()->getFramePopupMenu(); if (popupMenu) { - popupMenu->showPopup(mouseMsg->position()); + popupMenu->showPopup(mouseMsg->position(), display()); m_state = STATE_STANDBY; invalidate(); @@ -1273,7 +1273,7 @@ bool Timeline::onProcessMessage(Message* msg) if (m_clk.layer == m_hot.layer) { Menu* popupMenu = AppMenus::instance()->getLayerPopupMenu(); if (popupMenu) { - popupMenu->showPopup(mouseMsg->position()); + popupMenu->showPopup(mouseMsg->position(), display()); m_state = STATE_STANDBY; invalidate(); @@ -1293,7 +1293,7 @@ bool Timeline::onProcessMessage(Message* msg) AppMenus::instance()->getCelMovementPopupMenu(): AppMenus::instance()->getCelPopupMenu(); if (popupMenu) { - popupMenu->showPopup(mouseMsg->position()); + popupMenu->showPopup(mouseMsg->position(), display()); // Do not drop in this function, the drop is done from // the menu in case we've used the @@ -1321,7 +1321,7 @@ bool Timeline::onProcessMessage(Message* msg) Menu* popupMenu = AppMenus::instance()->getTagPopupMenu(); if (popupMenu) { AppMenuItem::setContextParams(params); - popupMenu->showPopup(mouseMsg->position()); + popupMenu->showPopup(mouseMsg->position(), display()); AppMenuItem::setContextParams(Params()); m_state = STATE_STANDBY; diff --git a/src/ui/combobox.cpp b/src/ui/combobox.cpp index cf8f944b22..7e19c7c405 100644 --- a/src/ui/combobox.cpp +++ b/src/ui/combobox.cpp @@ -376,7 +376,7 @@ bool ComboBox::onProcessMessage(Message* msg) MouseMessage* mouseMsg = static_cast(msg); Widget* pick = manager()->pickFromScreenPos( - display()->nativeWindow()->pointFromScreen(mouseMsg->position())); + mouseMsg->display()->nativeWindow()->pointFromScreen(mouseMsg->position())); if (pick && pick->hasAncestor(this)) return true; } diff --git a/src/ui/display.h b/src/ui/display.h index cad5c99865..544411f5d5 100644 --- a/src/ui/display.h +++ b/src/ui/display.h @@ -33,6 +33,7 @@ namespace ui { os::Window* nativeWindow() { return m_nativeWindow.get(); } os::Surface* surface() const; + int scale() const { return m_nativeWindow->scale(); } gfx::Size size() const; gfx::Rect bounds() const { return gfx::Rect(size()); } diff --git a/src/ui/entry.cpp b/src/ui/entry.cpp index b78f2ec8eb..90b82a5fba 100644 --- a/src/ui/entry.cpp +++ b/src/ui/entry.cpp @@ -819,7 +819,7 @@ void Entry::showEditPopupMenu(const gfx::Point& pt) paste.setEnabled(false); } - menu.showPopup(pt); + menu.showPopup(pt, display()); } class Entry::CalcBoxesTextDelegate : public os::DrawTextDelegate { diff --git a/src/ui/fit_bounds.cpp b/src/ui/fit_bounds.cpp index ec8f059222..339cd4047f 100644 --- a/src/ui/fit_bounds.cpp +++ b/src/ui/fit_bounds.cpp @@ -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-2016 David Capello // // This file is released under the terms of the MIT license. @@ -16,6 +16,7 @@ #include "ui/base.h" #include "ui/display.h" #include "ui/system.h" +#include "ui/window.h" namespace ui { @@ -87,4 +88,50 @@ int fit_bounds(Display* display, int arrowAlign, const gfx::Rect& target, gfx::R return arrowAlign; } +void fit_bounds(Display* parentDisplay, + Window* window, + const gfx::Rect& candidateBoundsRelativeToParentDisplay, + std::function getWidgetBounds)> fitLogic) +{ + gfx::Point pos = candidateBoundsRelativeToParentDisplay.origin(); + + if (get_multiple_displays() && window->shouldCreateNativeWindow()) { + const os::Window* nativeWindow = parentDisplay->nativeWindow(); + const gfx::Rect workarea = nativeWindow->screen()->workarea(); + const int scale = nativeWindow->scale(); + + // Screen frame bounds + gfx::Rect frame( + nativeWindow->pointToScreen(pos), + candidateBoundsRelativeToParentDisplay.size() * scale); + + if (fitLogic) + fitLogic(workarea, frame, [](Widget* widget){ return widget->boundsOnScreen(); }); + + frame.x = base::clamp(frame.x, workarea.x, workarea.x2() - frame.w); + frame.y = base::clamp(frame.y, workarea.y, workarea.y2() - frame.h); + + // Set frame bounds directly + window->setBounds(gfx::Rect(0, 0, frame.w / scale, frame.h / scale)); + window->loadNativeFrame(frame); + + if (window->isVisible()) + window->expandWindow(frame.size() / scale); + } + else { + const gfx::Rect displayBounds(parentDisplay->size()); + gfx::Rect frame(candidateBoundsRelativeToParentDisplay); + + if (fitLogic) + fitLogic(displayBounds, frame, [](Widget* widget){ return widget->bounds(); }); + + frame.x = base::clamp(frame.x, 0, displayBounds.w - frame.w); + frame.y = base::clamp(frame.y, 0, displayBounds.h - frame.h); + + window->setBounds(frame); + } +} + } // namespace ui diff --git a/src/ui/fit_bounds.h b/src/ui/fit_bounds.h index f6d1451f67..a7bf4a586d 100644 --- a/src/ui/fit_bounds.h +++ b/src/ui/fit_bounds.h @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2016 David Capello // // This file is released under the terms of the MIT license. @@ -11,12 +11,32 @@ #include "gfx/fwd.h" +#include + namespace ui { class Display; + class Widget; + class Window; int fit_bounds(Display* display, int arrowAlign, const gfx::Rect& target, gfx::Rect& bounds); + // Fits a possible rectangle for the given window fitting it with a + // special logic. It works for both cases: + // 1. With multiple windows (so the limit is the parentDisplay screen workarea) + // 2. Or with one window (so the limit is the just display area) + // + // The getWidgetBounds() can be used to get other widgets positions + // in the "fitLogic" (the bounds will be relative to the screen or + // to the display depending if get_multiple_displays() is true or + // false). + void fit_bounds(Display* parentDisplay, + Window* window, + const gfx::Rect& candidateBoundsRelativeToParentDisplay, + std::function getWidgetBounds)> fitLogic = nullptr); + } // namespace ui #endif diff --git a/src/ui/int_entry.cpp b/src/ui/int_entry.cpp index 9d7d8e77e7..224c4002ba 100644 --- a/src/ui/int_entry.cpp +++ b/src/ui/int_entry.cpp @@ -16,6 +16,7 @@ #include "gfx/rect.h" #include "gfx/region.h" #include "os/font.h" +#include "ui/fit_bounds.h" #include "ui/manager.h" #include "ui/message.h" #include "ui/popup_window.h" @@ -182,20 +183,28 @@ void IntEntry::openPopup() m_popupWindow->addChild(&m_slider); m_popupWindow->Close.connect(&IntEntry::onPopupClose, this); - Rect rc = bounds(); - gfx::Size sz = m_popupWindow->sizeHint(); - gfx::Size displaySize = display()->size(); - rc.w = 128*guiscale(); - if (rc.x+rc.w > displaySize.w) - rc.x = rc.x-rc.w+bounds().w; - if (rc.y+rc.h+sz.h < displaySize.h) - rc.y += rc.h; - else - rc.y -= sz.h; - m_popupWindow->setBounds(rc); - - Region rgn(rc.createUnion(bounds())); - rgn.createUnion(rgn, Region(bounds())); + fit_bounds( + display(), + m_popupWindow, + gfx::Rect(0, 0, 128*guiscale(), m_popupWindow->sizeHint().h), + [this](const gfx::Rect& workarea, + gfx::Rect& rc, + std::function getWidgetBounds) { + Rect entryBounds = getWidgetBounds(this); + + rc.x = entryBounds.x; + rc.y = entryBounds.y2(); + + if (rc.x2() > workarea.x2()) + rc.x = rc.x-rc.w+entryBounds.w; + + if (rc.y2() > workarea.y2()) + rc.y = entryBounds.y-entryBounds.h; + + m_popupWindow->setBounds(rc); + }); + + Region rgn(m_popupWindow->boundsOnScreen().createUnion(boundsOnScreen())); m_popupWindow->setHotRegion(rgn); m_popupWindow->openWindow(); diff --git a/src/ui/manager.cpp b/src/ui/manager.cpp index b2b33a9360..1ef5dc1ea8 100644 --- a/src/ui/manager.cpp +++ b/src/ui/manager.cpp @@ -24,6 +24,7 @@ #include "base/time.h" #include "os/event.h" #include "os/event_queue.h" +#include "os/screen.h" #include "os/surface.h" #include "os/system.h" #include "os/window.h" @@ -1132,6 +1133,21 @@ void Manager::removeMessagesForTimer(Timer* timer) } } +void Manager::removeMessagesForDisplay(Display* display) +{ +#ifdef DEBUG_UI_THREADS + ASSERT(manager_thread == base::this_thread::native_id()); +#endif + + for (Message* msg : msg_queue) + if (msg->display() == display) + msg->removeRecipient(msg->recipient()); + + for (Message* msg : used_msg_queue) + if (msg->display() == display) + msg->removeRecipient(msg->recipient()); +} + void Manager::removePaintMessagesForDisplay(Display* display) { #ifdef DEBUG_UI_THREADS @@ -1141,7 +1157,7 @@ void Manager::removePaintMessagesForDisplay(Display* display) for (auto it=msg_queue.begin(); it != msg_queue.end(); ) { Message* msg = *it; if (msg->type() == kPaintMessage && - static_cast(msg)->display() == display) { + msg->display() == display) { delete msg; it = msg_queue.erase(it); } @@ -1269,22 +1285,18 @@ void Manager::_openWindow(Window* window, bool center) // Relayout if (center) - window->centerWindow(); + window->centerWindow(parentDisplay); else window->layout(); // 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) { + if (!window->hasDisplaySet()) { // 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 - ) { + && window->shouldCreateNativeWindow()) { const int scale = parentDisplay->nativeWindow()->scale(); os::WindowSpec spec; @@ -1298,6 +1310,31 @@ void Manager::_openWindow(Window* window, bool center) frame *= scale; frame.offset(relativeToFrame.origin()); } + + // Limit window position using the union of all workareas + // + // TODO at least the title bar should be visible so we can + // resize it, because workareas can form an irregular shape + // (not rectangular) the calculation is a little more + // complex + { + gfx::Region wa; + os::ScreenList screens; + os::instance()->listScreens(screens); + for (const auto& screen : screens) + wa |= gfx::Region(screen->workarea()); + + // TODO use a "visibleFrameRegion = frame & wa" to check the + // visible regions and calculate if we should move the frame + // position + + gfx::Rect waBounds = wa.bounds(); + if (frame.x < waBounds.x) frame.x = waBounds.x; + if (frame.y < waBounds.y) frame.y = waBounds.y; + if (frame.x2() > waBounds.x2()) frame.w -= frame.x2() - waBounds.x2(); + if (frame.y2() > waBounds.y2()) frame.h -= frame.y2() - waBounds.y2(); + } + spec.position(os::WindowSpec::Position::Frame); spec.frame(frame); spec.scale(scale); @@ -1374,8 +1411,8 @@ void Manager::_closeWindow(Window* window, bool redraw_background) ASSERT(windowDisplay); ASSERT(windowDisplay != this->display()); - // Remove all paint messages for this display. - removePaintMessagesForDisplay(windowDisplay); + // Remove all messages for this display. + removeMessagesForDisplay(windowDisplay); window->setDisplay(nullptr, false); windowDisplay->nativeWindow()->setUserData(nullptr); diff --git a/src/ui/manager.h b/src/ui/manager.h index 00b91973cb..3ab29d82dc 100644 --- a/src/ui/manager.h +++ b/src/ui/manager.h @@ -80,6 +80,7 @@ namespace ui { void removeMessagesFor(Widget* widget); void removeMessagesFor(Widget* widget, MessageType type); void removeMessagesForTimer(Timer* timer); + void removeMessagesForDisplay(Display* display); void removePaintMessagesForDisplay(Display* display); void addMessageFilter(int message, Widget* widget); diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp index 8e45da1cbf..e13ff97eb9 100644 --- a/src/ui/menu.cpp +++ b/src/ui/menu.cpp @@ -292,7 +292,8 @@ bool MenuItem::hasSubmenu() const return (m_submenu && !m_submenu->children().empty()); } -void Menu::showPopup(const gfx::Point& pos) +void Menu::showPopup(const gfx::Point& pos, + Display* parentDisplay) { // Generally, when we call showPopup() the menu shouldn't contain a // parent menu-box, because we're filtering kMouseDownMessage to @@ -322,11 +323,9 @@ void Menu::showPopup(const gfx::Point& pos) window->remapWindow(); - // Menubox position - gfx::Size displaySize = display()->size(); - window->positionWindow( - base::clamp(pos.x, 0, displaySize.w - window->bounds().w), - base::clamp(pos.y, 0, displaySize.h - window->bounds().h)); + fit_bounds(parentDisplay, + window.get(), + gfx::Rect(pos, window->size())); add_scrollbars_if_needed(window.get()); @@ -442,20 +441,20 @@ bool MenuBox::onProcessMessage(Message* msg) case kMouseDownMessage: case kDoubleClickMessage: - if (menu) { + if (menu && msg->display()) { ASSERT(menu->parent() == this); if (get_base(this)->is_processing) break; - gfx::Point mousePos = static_cast(msg)->position(); + const gfx::Point mousePos = static_cast(msg)->position(); + const gfx::Point screenPos = msg->display()->nativeWindow()->pointToScreen(mousePos); // Here we catch the filtered messages (menu-bar or the // popuped menu-box) to detect if the user press outside of // the widget if (msg->type() == kMouseDownMessage && m_base != nullptr) { - Widget* picked = manager()->pickFromScreenPos( - display()->nativeWindow()->pointToScreen(mousePos)); + Widget* picked = manager()->pickFromScreenPos(screenPos); // If one of these conditions are accomplished we have to // close all menus (back to menu-bar or close the popuped @@ -466,16 +465,17 @@ bool MenuBox::onProcessMessage(Message* msg) (get_base_menubox(picked) != this || (this->type() == kMenuBarWidget && picked->type() == kMenuWidget))) { - // The user click outside all the menu-box/menu-items, close all menu->closeAll(); + + // Change to "return false" if you want to send the click + // to the window after closing all menus. return true; } } // Get the widget below the mouse cursor - Widget* picked = menu->pick(mousePos); - if (picked) { + if (Widget* picked = menu->pickFromScreenPos(screenPos)) { if ((picked->type() == kMenuItemWidget) && !(picked->hasFlags(DISABLED))) { MenuItem* pickedItem = static_cast(picked); @@ -814,9 +814,6 @@ bool MenuItem::onProcessMessage(Message* msg) ASSERT(base->is_processing); ASSERT(hasSubmenu()); - Rect old_pos = window()->bounds(); - old_pos.w -= 1*guiscale(); - MenuBox* menubox = new MenuBox(); m_submenu_menubox = menubox; menubox->setMenu(m_submenu); @@ -824,42 +821,47 @@ bool MenuItem::onProcessMessage(Message* msg) // New window and new menu-box auto window = new MenuBoxWindow(menubox); - // Menubox position - Rect pos = window->bounds(); - Size displaySize = display()->size(); - - if (inBar()) { - pos.x = base::clamp(bounds().x, 0, displaySize.w-pos.w); - pos.y = std::max(0, bounds().y2()); - } - else { - int x_left = old_pos.x - pos.w; - int x_right = old_pos.x2(); - int x, y = bounds().y-3*guiscale(); - Rect r1(0, 0, pos.w, pos.h), r2(0, 0, pos.w, pos.h); - - r1.x = x_left = base::clamp(x_left, 0, displaySize.w-pos.w); - r2.x = x_right = base::clamp(x_right, 0, displaySize.w-pos.w); - r1.y = r2.y = y = base::clamp(y, 0, displaySize.h-pos.h); - - // Calculate both intersections - gfx::Rect s1 = r1.createIntersection(old_pos); - gfx::Rect s2 = r2.createIntersection(old_pos); - - if (s2.isEmpty()) - x = x_right; // Use the right because there aren't intersection with it - else if (s1.isEmpty()) - x = x_left; // Use the left because there are not intersection - else if (r2.w*r2.h <= r1.w*r1.h) - x = x_right; // Use the right because there are less intersection area - else - x = x_left; // Use the left because there are less intersection area - - pos.x = x; - pos.y = y; - } + fit_bounds( + display(), window, window->bounds(), + [this](const gfx::Rect& workarea, + gfx::Rect& pos, + std::function getWidgetBounds){ + Rect parentPos = getWidgetBounds(this->window()); + Rect bounds = getWidgetBounds(this); + + if (inBar()) { + pos.x = base::clamp(bounds.x, workarea.x, workarea.x2()-pos.w); + pos.y = std::max(workarea.y, bounds.y2()); + } + else { + int x_left = parentPos.x - pos.w + 1*guiscale(); + int x_right = parentPos.x2() - 1*guiscale(); + int x, y = bounds.y-3*guiscale(); + Rect r1(0, 0, pos.w, pos.h); + Rect r2(0, 0, pos.w, pos.h); + + r1.x = x_left = base::clamp(x_left, 0, workarea.w-pos.w); + r2.x = x_right = base::clamp(x_right, 0, workarea.w-pos.w); + r1.y = r2.y = y = base::clamp(y, 0, workarea.h-pos.h); + + // Calculate both intersections + const gfx::Rect s1 = r1.createIntersection(parentPos); + const gfx::Rect s2 = r2.createIntersection(parentPos); + + if (s2.isEmpty()) + x = x_right; // Use the right because there aren't intersection with it + else if (s1.isEmpty()) + x = x_left; // Use the left because there are not intersection + else if (s2.w*s2.h <= s1.w*s1.h) + x = x_right; // Use the right because there are less intersection area + else + x = x_left; // Use the left because there are less intersection area + + pos.x = x; + pos.y = y; + } + }); - window->positionWindow(pos.x, pos.y); add_scrollbars_if_needed(window); // Set the focus to the new menubox @@ -1133,7 +1135,7 @@ void Menu::unhighlightItem() highlightItem(nullptr, false, false, false); } -bool MenuItem::inBar() +bool MenuItem::inBar() const { return (parent() && diff --git a/src/ui/menu.h b/src/ui/menu.h index 432393463a..e641d47d9a 100644 --- a/src/ui/menu.h +++ b/src/ui/menu.h @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -28,7 +28,8 @@ namespace ui { Menu(); ~Menu(); - void showPopup(const gfx::Point& pos); + void showPopup(const gfx::Point& pos, + Display* parentDisplay); Widget* findItemById(const char* id); // Returns the MenuItem that has as submenu this menu. @@ -141,7 +142,7 @@ namespace ui { virtual void onClick(); virtual void onValidate(); - bool inBar(); + bool inBar() const; private: void openSubmenu(bool select_first); diff --git a/src/ui/popup_window.cpp b/src/ui/popup_window.cpp index dbf53c9dbe..68412ea8cd 100644 --- a/src/ui/popup_window.cpp +++ b/src/ui/popup_window.cpp @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. @@ -27,8 +27,6 @@ PopupWindow::PopupWindow(const std::string& text, : Window(text.empty() ? WithoutTitleBar: WithTitleBar, text) , m_clickBehavior(clickBehavior) , m_enterBehavior(enterBehavior) - , m_filtering(false) - , m_fixed(false) { setSizeable(false); setMoveable(false); @@ -53,11 +51,11 @@ PopupWindow::~PopupWindow() stopFilteringMessages(); } -void PopupWindow::setHotRegion(const gfx::Region& region) +void PopupWindow::setHotRegion(const gfx::Region& screenRegion) { startFilteringMessages(); - m_hotRegion = region; + m_hotRegion = screenRegion; } void PopupWindow::setClickBehavior(ClickBehavior behavior) @@ -137,26 +135,30 @@ bool PopupWindow::onProcessMessage(Message* msg) case kMouseDownMessage: if (m_filtering && - manager()->getTopWindow() == this) { + manager()->getTopWindow() == this && + msg->display()) { gfx::Point mousePos = static_cast(msg)->position(); + gfx::Point screenPos = msg->display()->nativeWindow()->pointToScreen(mousePos); switch (m_clickBehavior) { // If the user click outside the window, we have to close // the tooltip window. case ClickBehavior::CloseOnClickInOtherWindow: { - Widget* picked = pick(mousePos); + Widget* picked = pickFromScreenPos(screenPos); if (!picked || picked->window() != this) { closeWindow(nullptr); } break; } - case ClickBehavior::CloseOnClickOutsideHotRegion: - if (!m_hotRegion.contains(mousePos)) { + case ClickBehavior::CloseOnClickOutsideHotRegion: { + // Convert the mousePos from display() coordinates to screen + if (!m_hotRegion.contains(screenPos)) { closeWindow(nullptr); } break; + } } } break; @@ -164,12 +166,14 @@ bool PopupWindow::onProcessMessage(Message* msg) case kMouseMoveMessage: if (m_fixed && !m_hotRegion.isEmpty() && - manager()->getCapture() == nullptr) { + manager()->getCapture() == nullptr && + msg->display()) { gfx::Point mousePos = static_cast(msg)->position(); + gfx::Point screenPos = msg->display()->nativeWindow()->pointToScreen(mousePos); // If the mouse is outside the hot-region we have to close the // window. - if (!m_hotRegion.contains(mousePos)) + if (!m_hotRegion.contains(screenPos)) closeWindow(nullptr); } break; diff --git a/src/ui/popup_window.h b/src/ui/popup_window.h index a97f0d04ab..469b32a871 100644 --- a/src/ui/popup_window.h +++ b/src/ui/popup_window.h @@ -1,4 +1,5 @@ // Aseprite UI Library +// Copyright (C) 2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. @@ -33,7 +34,10 @@ namespace ui { // Sets the hot region. This region indicates the area where the // mouse can be located and the window will be kept open. - void setHotRegion(const gfx::Region& region); + // + // The screenRegion must be specified in native screen coordinates. + void setHotRegion(const gfx::Region& screenRegion); + void setClickBehavior(ClickBehavior behavior); void setEnterBehavior(EnterBehavior behavior); @@ -54,8 +58,8 @@ namespace ui { ClickBehavior m_clickBehavior; EnterBehavior m_enterBehavior; gfx::Region m_hotRegion; - bool m_filtering; - bool m_fixed; + bool m_filtering = false; + bool m_fixed = false; }; class TransparentPopupWindow : public PopupWindow { diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index 1fc599f68c..2c5e567127 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -698,6 +698,16 @@ Rect Widget::clientChildrenBounds() const m_bounds.h - border().height()); } +gfx::Rect Widget::boundsOnScreen() const +{ + gfx::Rect rc = bounds(); + os::Window* nativeWindow = display()->nativeWindow(); + rc = gfx::Rect( + nativeWindow->pointToScreen(rc.origin()), + nativeWindow->pointToScreen(rc.point2())); + return rc; +} + void Widget::setBounds(const Rect& rc) { ResizeEvent ev(this, rc); diff --git a/src/ui/widget.h b/src/ui/widget.h index d934d4b5dc..791a65ca22 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -242,6 +242,9 @@ namespace ui { gfx::Rect childrenBounds() const; gfx::Rect clientChildrenBounds() const; + // Bounds of this widget or window on native screen/desktop coordinates. + gfx::Rect boundsOnScreen() const; + // Sets the bounds of the widget generating a onResize() event. void setBounds(const gfx::Rect& rc); diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 4668fe0c07..635da749b7 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -309,19 +309,24 @@ void Window::remapWindow() invalidate(); } -void Window::centerWindow() +void Window::centerWindow(Display* parentDisplay) { - Widget* manager = this->manager(); - if (m_isAutoRemap) remapWindow(); - positionWindow(manager->bounds().w/2 - bounds().w/2, - manager->bounds().h/2 - bounds().h/2); + if (!parentDisplay) + parentDisplay = manager()->getDefault()->display(); + + gfx::Size displaySize = parentDisplay->size(); + positionWindow(displaySize.w/2 - bounds().w/2, + displaySize.h/2 - bounds().h/2); } void Window::positionWindow(int x, int y) { + // TODO don't use in ownDisplay() windows + ASSERT(!ownDisplay()); + if (m_isAutoRemap) remapWindow(); @@ -437,6 +442,12 @@ bool Window::onProcessMessage(Message* msg) } if (action != os::WindowAction::Cancel) { display()->nativeWindow()->performWindowAction(action, nullptr); + + // As Window::moveWindow() will not be called, we have to + // call onWindowMovement() event from here. + if (action == os::WindowAction::Move) + onWindowMovement(); + return true; } } diff --git a/src/ui/window.h b/src/ui/window.h index e2db276b66..38066f03f7 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -31,6 +31,7 @@ namespace ui { bool ownDisplay() const { return m_ownDisplay; } Display* display() const; void setDisplay(Display* display, const bool own); + bool hasDisplaySet() const { return m_display != nullptr; } Widget* closer() const { return m_closer; } @@ -41,7 +42,7 @@ namespace ui { void setWantFocus(bool state); void remapWindow(); - void centerWindow(); + void centerWindow(Display* parentDisplay = nullptr); void positionWindow(int x, int y); void moveWindow(const gfx::Rect& rect); @@ -60,6 +61,11 @@ namespace ui { bool isSizeable() const { return m_isSizeable; } bool isMoveable() const { return m_isMoveable; } + bool shouldCreateNativeWindow() const { + return (!isDesktop() && + !isTransparent()); + } + HitTest hitTest(const gfx::Point& point); // Last native window frame bounds. Saved just before we close the