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.
This commit is contained in:
David Capello 2021-03-19 18:57:56 -03:00
parent 7079801697
commit bcd69495ce
48 changed files with 508 additions and 254 deletions

2
laf

@ -1 +1 @@
Subproject commit d290eaf37fcb1a48cee433e6b9a1b73854671df0
Subproject commit 1106d7488dcf1410815601c9eaa38407bc243893

View File

@ -342,12 +342,17 @@ void CanvasSizeCommand::onExecute(Context* context)
// Find best position for the window on the editor
if (DocView* docView = static_cast<UIContext*>(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);

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
@ -246,10 +246,7 @@ public:
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 {

View File

@ -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<gfx::Rect(Widget*)> 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();

View File

@ -124,11 +124,7 @@ private:
}
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);

View File

@ -71,14 +71,15 @@ public:
void centerConsole() {
initTheme();
remapWindow();
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;
// 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();
ui::fit_bounds(display, this, rc);
}
private:

View File

@ -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)

View File

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

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
@ -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 @@ private:
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 @@ private:
params.shade()->setSelected(saveBrush.shade());
params.pixelPerfect()->setSelected(saveBrush.pixelPerfect());
show_popup_menu(static_cast<PopupWindow*>(window()), &menu,
gfx::Point(origin().x, origin().y+bounds().h));
show_popup_menu(static_cast<PopupWindow*>(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);

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-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);

View File

@ -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<int>(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<int>(PalButton::OPTIONS))->bounds();
menu->showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
menu->showPopup(gfx::Point(bounds.x, bounds.y2()), display());
}
}

View File

@ -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->remapWindow();
fit_bounds(
display(),
m_window,
gfx::Rect(m_window->sizeHint()),
[this, pinned, forcePinned](const gfx::Rect& workarea,
gfx::Rect& winBounds,
std::function<gfx::Rect(Widget*)> 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->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->manager()->dispatchMessages();
m_window->layout();
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<PopupWindow*>(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

View File

@ -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;
};

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
@ -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

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,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 @@ protected:
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 @@ protected:
}
});
menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
menu.showPopup(gfx::Point(bounds.x, bounds.y2()),
display());
deselectItems();
}
@ -502,7 +503,8 @@ protected:
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 @@ private:
}
}
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 @@ private:
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 @@ private:
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 @@ public:
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 @@ private:
doc->notifyGeneralUpdate();
});
menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
menu.showPopup(gfx::Point(bounds.x, bounds.y2()),
display());
}
}
};

View File

@ -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()

View File

@ -140,7 +140,7 @@ void DevConsoleView::onTabPopup(Workspace* workspace)
if (!menu)
return;
menu->showPopup(mousePosInDisplay());
menu->showPopup(mousePosInDisplay(), display());
}
bool DevConsoleView::onProcessMessage(Message* msg)

View File

@ -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)

View File

@ -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<MouseMessage*>(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);
}

View File

@ -2754,7 +2754,7 @@ void Editor::showAnimationSpeedMultiplierPopup(Option<bool>& playOnce,
menu.addChild(item);
}
menu.showPopup(mousePosInDisplay());
menu.showPopup(mousePosInDisplay(), display());
if (isPlaying()) {
// Re-play

View File

@ -256,7 +256,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
if (!editor->hasSelectedSlices())
params.set("id", base::convert_to<std::string>(hit.slice()->id()).c_str());
AppMenuItem::setContextParams(params);
popupMenu->showPopup(msg->position());
popupMenu->showPopup(msg->position(), editor->display());
AppMenuItem::setContextParams(Params());
}
}

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
@ -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<gfx::Rect(Widget*)> 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();
}

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
@ -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<void(const std::string&)> Load;

View File

@ -136,7 +136,7 @@ void HomeView::onTabPopup(Workspace* workspace)
if (!menu)
return;
menu->showPopup(mousePosInDisplay());
menu->showPopup(mousePosInDisplay(), display());
}
void HomeView::onWorkspaceViewSelected()

View File

@ -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);
}
}

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-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

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
@ -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<gfx::Rect(Widget*)> getWidgetBounds) {
bounds.w = workarea.w/2;
bounds.h = workarea.h*3/4;
});
openWindowInForeground();
}

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
@ -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);

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-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();
}

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
@ -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();
case kOpenMessage: {
Manager* manager = this->manager();
Display* mainDisplay = manager->display();
// 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));
gfx::Rect defaultBounds(mainDisplay->size() / 4);
SkinTheme* theme = SkinTheme::instance();
gfx::Rect mainWindow = manager->bounds();
load_window_pos(this, "MiniEditor", false);
invalidate();
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");

View File

@ -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();

View File

@ -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;

View File

@ -376,7 +376,7 @@ bool ComboBox::onProcessMessage(Message* msg)
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
Widget* pick = manager()->pickFromScreenPos(
display()->nativeWindow()->pointFromScreen(mouseMsg->position()));
mouseMsg->display()->nativeWindow()->pointFromScreen(mouseMsg->position()));
if (pick && pick->hasAncestor(this))
return true;
}

View File

@ -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()); }

View File

@ -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 {

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-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<void(const gfx::Rect& workarea,
gfx::Rect& bounds,
std::function<gfx::Rect(Widget*)> 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

View File

@ -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 <functional>
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<void(const gfx::Rect& workarea,
gfx::Rect& bounds,
std::function<gfx::Rect(Widget*)> getWidgetBounds)> fitLogic = nullptr);
} // namespace ui
#endif

View File

@ -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);
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<gfx::Rect(Widget*)> getWidgetBounds) {
Rect entryBounds = getWidgetBounds(this);
Region rgn(rc.createUnion(bounds()));
rgn.createUnion(rgn, Region(bounds()));
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();

View File

@ -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<PaintMessage*>(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<void*>(nullptr);

View File

@ -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);

View File

@ -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<MouseMessage*>(msg)->position();
const gfx::Point mousePos = static_cast<MouseMessage*>(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<MenuItem*>(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();
fit_bounds(
display(), window, window->bounds(),
[this](const gfx::Rect& workarea,
gfx::Rect& pos,
std::function<gfx::Rect(Widget*)> getWidgetBounds){
Rect parentPos = getWidgetBounds(this->window());
Rect bounds = getWidgetBounds(this);
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);
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, 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);
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
gfx::Rect s1 = r1.createIntersection(old_pos);
gfx::Rect s2 = r2.createIntersection(old_pos);
// 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 (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
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;
}
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() &&

View File

@ -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);

View File

@ -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<MouseMessage*>(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<MouseMessage*>(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;

View File

@ -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 {

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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