// Aseprite UI Library // 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. // Read LICENSE.txt for more information. #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ui/fit_bounds.h" #include "base/clamp.h" #include "gfx/rect.h" #include "os/screen.h" #include "os/system.h" #include "ui/base.h" #include "ui/display.h" #include "ui/system.h" #include "ui/window.h" namespace ui { static gfx::Region get_workarea_region() { gfx::Region wa; os::ScreenList screens; os::instance()->listScreens(screens); for (const auto& screen : screens) wa |= gfx::Region(screen->workarea()); return wa; } int fit_bounds(Display* display, int arrowAlign, const gfx::Rect& target, gfx::Rect& bounds) { bounds.x = target.x; bounds.y = target.y; int trycount = 0; for (; trycount < 4; ++trycount) { switch (arrowAlign) { case TOP | LEFT: bounds.x = target.x + target.w; bounds.y = target.y + target.h; break; case TOP | RIGHT: bounds.x = target.x - bounds.w; bounds.y = target.y + target.h; break; case BOTTOM | LEFT: bounds.x = target.x + target.w; bounds.y = target.y - bounds.h; break; case BOTTOM | RIGHT: bounds.x = target.x - bounds.w; bounds.y = target.y - bounds.h; break; case TOP: bounds.x = target.x + target.w/2 - bounds.w/2; bounds.y = target.y + target.h; break; case BOTTOM: bounds.x = target.x + target.w/2 - bounds.w/2; bounds.y = target.y - bounds.h; break; case LEFT: bounds.x = target.x + target.w; bounds.y = target.y + target.h/2 - bounds.h/2; break; case RIGHT: bounds.x = target.x - bounds.w; bounds.y = target.y + target.h/2 - bounds.h/2; break; } gfx::Size displaySize = display->size(); bounds.x = base::clamp(bounds.x, 0, displaySize.w-bounds.w); bounds.y = base::clamp(bounds.y, 0, displaySize.h-bounds.h); if (target.intersects(bounds)) { switch (trycount) { case 0: case 2: // Switch position if (arrowAlign & (TOP | BOTTOM)) arrowAlign ^= TOP | BOTTOM; if (arrowAlign & (LEFT | RIGHT)) arrowAlign ^= LEFT | RIGHT; break; case 1: // Rotate positions if (arrowAlign & (TOP | LEFT)) arrowAlign ^= TOP | LEFT; if (arrowAlign & (BOTTOM | RIGHT)) arrowAlign ^= BOTTOM | RIGHT; break; } } else break; } 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(); // Limit to the current screen workarea (instead of using all the // available workarea between all monitors, get_workarea_region()) 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); } } // 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 void limit_with_workarea(Display* parentDisplay, gfx::Rect& frame) { if (!get_multiple_displays()) return; ASSERT(parentDisplay); gfx::Rect waBounds = parentDisplay->nativeWindow()->screen()->workarea(); 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(); } } // namespace ui