From 8f838ba36dd28b7ac5ab88c6b8065fec544a8d51 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 24 Jun 2020 21:34:55 -0300 Subject: [PATCH 1/2] Backport FullscreenMode command (#464) Original from ae02600a63a3a61713a9199ec86c701354975acf --- data/gui.xml | 4 +- data/strings/en.ini | 2 + src/app/CMakeLists.txt | 1 + src/app/commands/cmd_fullscreen_mode.cpp | 60 ++++++++++++++++++++++++ src/app/commands/commands_list.h | 1 + 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/app/commands/cmd_fullscreen_mode.cpp diff --git a/data/gui.xml b/data/gui.xml index 18937f129..c42efe43d 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -128,6 +128,7 @@ + @@ -929,7 +930,8 @@ - + + diff --git a/data/strings/en.ini b/data/strings/en.ini index 63f08ea45..5d1abf107 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -275,6 +275,7 @@ Flip_Selection = Selection Flip_Vertically = Vertically FrameProperties = Frame Properties FrameTagProperties = Tag Properties +FullscreenMode = Toggle Fullscreen Mode FullscreenPreview = Fullscreen Preview GotoFirstFrame = Go to First Frame GotoFirstFrameInTag = Go to First Frame In Tag @@ -946,6 +947,7 @@ view_set_loop_section = Set &Loop Section view_show_onion_skin = Show &Onion Skin view_timeline = &Timeline view_preview = Previe&w +view_advanced_mode = &Advanced Mode view_full_screen_mode = &Full Screen Mode view_full_screen_preview = F&ull Screen Preview view_home = &Home diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 5e578947f..826072348 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -225,6 +225,7 @@ if(ENABLE_UI) commands/cmd_fit_screen.cpp commands/cmd_frame_properties.cpp commands/cmd_frame_tag_properties.cpp + commands/cmd_fullscreen_mode.cpp commands/cmd_fullscreen_preview.cpp commands/cmd_goto_frame.cpp commands/cmd_goto_layer.cpp diff --git a/src/app/commands/cmd_fullscreen_mode.cpp b/src/app/commands/cmd_fullscreen_mode.cpp new file mode 100644 index 000000000..c2530f051 --- /dev/null +++ b/src/app/commands/cmd_fullscreen_mode.cpp @@ -0,0 +1,60 @@ +// Aseprite +// Copyright (C) 2020-2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ui/ui.h" + +#include "app/app.h" +#include "app/commands/command.h" +#include "app/commands/commands.h" +#include "app/context.h" +#include "os/system.h" +#include "os/window.h" + +namespace app { + +class FullscreenModeCommand : public Command { +public: + FullscreenModeCommand(); + +protected: + void onExecute(Context* context) override; +}; + +FullscreenModeCommand::FullscreenModeCommand() + : Command(CommandId::FullscreenMode(), CmdUIOnlyFlag) +{ +} + +// Shows the sprite using the complete screen. +void FullscreenModeCommand::onExecute(Context* ctx) +{ + if (!ctx->isUIAvailable()) + return; + + ui::Manager* manager = ui::Manager::getDefault(); + ASSERT(manager); + if (!manager) + return; + + os::Window* window = manager->display(); + ASSERT(window); + if (!window) + return; + + window->setFullscreen( + !window->isFullscreen()); +} + +Command* CommandFactory::createFullscreenModeCommand() +{ + return new FullscreenModeCommand; +} + +} // namespace app diff --git a/src/app/commands/commands_list.h b/src/app/commands/commands_list.h index 760f3bbaa..c73b223d9 100644 --- a/src/app/commands/commands_list.h +++ b/src/app/commands/commands_list.h @@ -71,6 +71,7 @@ FOR_EACH_COMMAND(Fill) FOR_EACH_COMMAND(FitScreen) FOR_EACH_COMMAND(FrameProperties) FOR_EACH_COMMAND(FrameTagProperties) +FOR_EACH_COMMAND(FullscreenMode) FOR_EACH_COMMAND(FullscreenPreview) FOR_EACH_COMMAND(GotoFirstFrame) FOR_EACH_COMMAND(GotoFirstFrameInTag) From 5a6bd465f5edafb8e0bf071f0d0b8a2f8677db53 Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 9 Jul 2021 10:21:16 -0300 Subject: [PATCH 2/2] Cache mouse cursors to avoid re-generating them on each setCursor() This should improve the mouse movement, where a new mouse cursor was created on each mouse movement with black & white pixels. It's a regression introduced in ef4f691459d13b45459c56383ab3673ca6fbec0a (which was originally introduced to improve the mouse movement perception in a 100Hz monitor). This might be a possible fix for: https://github.com/aseprite/aseprite/issues/2713 --- laf | 2 +- src/app/commands/cmd_options.cpp | 6 +- src/app/ui/editor/brush_preview.cpp | 324 +++++++++++++++++----------- src/app/ui/editor/brush_preview.h | 17 +- src/app/ui/editor/editor.cpp | 1 + src/app/ui/skin/skin_theme.cpp | 2 +- src/app/ui/skin/skin_theme.h | 8 +- src/ui/cursor.cpp | 28 ++- src/ui/cursor.h | 17 +- src/ui/system.cpp | 31 ++- src/ui/theme.h | 4 +- 11 files changed, 270 insertions(+), 170 deletions(-) diff --git a/laf b/laf index 11ffdbd9c..80ec051ec 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit 11ffdbd9cc6232faaff5eecd8cc628bb5a2c706f +Subproject commit 80ec051ecf4b702d769d4b2483e1a34b52368bde diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 995d6999f..0be02c97b 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -331,7 +331,7 @@ public: // If the platform supports native cursors... if ((int(os::instance()->capabilities()) & - int(os::Capabilities::CustomNativeMouseCursor)) != 0) { + int(os::Capabilities::CustomMouseCursor)) != 0) { if (m_pref.cursor.useNativeCursor()) nativeCursor()->setSelected(true); nativeCursor()->Click.connect([this]{ onNativeCursorChange(); }); @@ -855,9 +855,9 @@ private: void onNativeCursorChange() { bool state = - // If the platform supports native cursors... + // If the platform supports custom cursors... (((int(os::instance()->capabilities()) & - int(os::Capabilities::CustomNativeMouseCursor)) != 0) && + int(os::Capabilities::CustomMouseCursor)) != 0) && // If the native cursor option is not selec !nativeCursor()->isSelected()); diff --git a/src/app/ui/editor/brush_preview.cpp b/src/app/ui/editor/brush_preview.cpp index 2b78d766b..28066f1b6 100644 --- a/src/app/ui/editor/brush_preview.cpp +++ b/src/app/ui/editor/brush_preview.cpp @@ -42,10 +42,57 @@ #include "ui/manager.h" #include "ui/system.h" +#include + namespace app { using namespace doc; +static int g_crosshair_pattern[7*7] = { + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, +}; + +// We're going to keep a cache of native mouse cursor for each +// possibility of the following crosshair: +// +// 2 +// 2 +// 22 3 22 +// 2 +// 2 +// +// Number of crosshair cursors 2^8 * 3 + 2 = 770 +// Here the center can be black, white or hidden. When the center is +// black or white, the crosshair can be empty. +// +// The index/key of this array is calculated in +// BrushPreview::createCrosshairCursor(). +// +// Win32: This is needed to avoid converting from a os::Surface -> +// HCURSOR calling CreateIconIndirect() as many times as possible for +// each mouse movement (because it's a slow function). +// +static std::array g_bwCursors; +static int g_cacheCursorScale = 0; + +// 3 cached cursors when we use a solid cursor (1 dot, crosshair +// without dot at the center, crosshair with dot in the center) +static std::array g_solidCursors; +static gfx::Color g_solidCursorColor = gfx::ColorNone; + +// static +void BrushPreview::destroyInternals() +{ + g_bwCursors.fill(nullptr); + g_solidCursors.fill(nullptr); +} + BrushPreview::BrushPreview(Editor* editor) : m_editor(editor) { @@ -53,7 +100,6 @@ BrushPreview::BrushPreview(Editor* editor) BrushPreview::~BrushPreview() { - m_cursor.reset(); } BrushRef BrushPreview::getCurrentBrush() @@ -310,9 +356,9 @@ void BrushPreview::show(const gfx::Point& screenPos) ui::SetClip clip(&g); gfx::Color uiCursorColor = color_utils::color_for_ui(appCursorColor); - createNativeCursor(); - if (m_cursor) - forEachLittleCrossPixel(&g, m_screenPosition, uiCursorColor, &BrushPreview::putPixelInCursorDelegate); + if (!(m_type & NATIVE_CROSSHAIR)) { + createCrosshairCursor(&g, uiCursorColor); + } forEachBrushPixel(&g, spritePos, uiCursorColor, &BrushPreview::savePixelDelegate); forEachBrushPixel(&g, spritePos, uiCursorColor, &BrushPreview::drawPixelDelegate); @@ -344,7 +390,7 @@ void BrushPreview::hide() // cursor will be changed anyway after the hide() by the caller. // //if (m_cursor) - // m_editor->manager()->getDisplay()->setNativeMouseCursor(os::NativeCursor::kNoCursor); + // m_editor->manager()->getDisplay()->setCursor(os::NativeCursor::Hidden); // Get drawable region m_editor->getDrawableRegion(m_clippingRegion, ui::Widget::kCutTopWindows); @@ -443,81 +489,165 @@ void BrushPreview::generateBoundaries() delete mask; } -void BrushPreview::createNativeCursor() +void BrushPreview::createCrosshairCursor(ui::Graphics* g, + const gfx::Color cursorColor) { + ASSERT(!(m_type & NATIVE_CROSSHAIR)); + gfx::Rect cursorBounds; - - if (m_type & CROSSHAIR) { - cursorBounds |= gfx::Rect(-3, -3, 7, 7); - m_cursorCenter = -cursorBounds.origin(); - } - // Special case of a cursor for one pixel - else if (!(m_type & NATIVE_CROSSHAIR) && - m_editor->zoom().scale() >= 4.0) { - cursorBounds = gfx::Rect(0, 0, 1, 1); - m_cursorCenter = gfx::Point(0, 0); - } - - if (m_cursor) { - if (m_cursor->width() != cursorBounds.w || - m_cursor->height() != cursorBounds.h) { - m_cursor.reset(); - } - } - - if (cursorBounds.isEmpty()) { - ASSERT(!m_cursor); - if (!(m_type & NATIVE_CROSSHAIR)) { - // TODO should we use ui::set_mouse_cursor()? - ui::set_mouse_cursor_reset_info(); - m_editor->manager()->display()->setNativeMouseCursor(os::NativeCursor::Hidden); - } - return; - } - - if (!m_cursor) { - m_cursor = os::instance()->makeRgbaSurface(cursorBounds.w, cursorBounds.h); - - // Cannot clear the cursor on each iteration because it can - // generate a flicker effect when zooming in the same mouse - // position. That's strange. - m_cursor->clear(); - } -} - -void BrushPreview::forEachLittleCrossPixel( - ui::Graphics* g, - const gfx::Point& screenPos, - gfx::Color color, - PixelDelegate pixelDelegate) -{ - if (m_type & CROSSHAIR) - traceCrossPixels(g, screenPos, color, pixelDelegate); + gfx::Point cursorCenter; // Depending on the editor zoom, maybe we need subpixel movement (a // little dot inside the active pixel) - if (!(m_type & NATIVE_CROSSHAIR) && - m_editor->zoom().scale() >= 4.0) { - (this->*pixelDelegate)(g, screenPos, color); + const bool requireLittleCenterDot = (m_editor->zoom().scale() >= 4.0); + + if (m_type & CROSSHAIR) { + // Regular crosshair of 7x7 + cursorBounds |= gfx::Rect(-3, -3, 7, 7); + cursorCenter = -cursorBounds.origin(); + } + else if (requireLittleCenterDot) { + // Special case of a cursor for one pixel + cursorBounds = gfx::Rect(0, 0, 1, 1); + cursorCenter = gfx::Point(0, 0); } else { - // We'll remove the pixel (as we didn't called Surface::clear() to - // avoid a flickering issue when zooming in the same mouse - // position). - base::ScopedValue restore(m_blackAndWhiteNegative, false, - m_blackAndWhiteNegative); - (this->*pixelDelegate)(g, screenPos, gfx::ColorNone); - } - - if (m_cursor) { - ASSERT(m_cursor); - // TODO should we use ui::set_mouse_cursor()? ui::set_mouse_cursor_reset_info(); - m_editor->manager()->display()->setNativeMouseCursor( - m_cursor.get(), - m_cursorCenter, - m_editor->manager()->display()->scale()); + m_editor->manager()->display()->setCursor(os::NativeCursor::Hidden); + return; + } + + os::Window* window = m_editor->manager()->display(); + const int scale = window->scale(); + os::CursorRef cursor = nullptr; + + // Invalidate the entire cache if the scale has changed + if (g_cacheCursorScale != scale) { + g_cacheCursorScale = scale; + g_bwCursors.fill(nullptr); + g_solidCursors.fill(nullptr); + } + + // Cursor with black/white colors (we create a key/index for + // g_cachedCursors depending on the colors on the screen) + if (m_blackAndWhiteNegative) { + int k = 0; + if (m_type & CROSSHAIR) { + int bit = 0; + for (int v=0; v<7; v++) { + for (int u=0; u<7; u++) { + if (g_crosshair_pattern[v*7+u]) { + color_t c = g->getPixel(m_screenPosition.x-3+u, + m_screenPosition.y-3+v); + c = color_utils::blackandwhite_neg(c); + if (rgba_getr(c) == 255) { // White + k |= (1 << bit); + } + ++bit; + } + } + } + } + if (requireLittleCenterDot) { + color_t c = g->getPixel(m_screenPosition.x, + m_screenPosition.y); + c = color_utils::blackandwhite_neg(c); + if (rgba_getr(c) == 255) { // White + k |= (m_type & CROSSHAIR ? 0x200: 0x301); + } + else { // Black + k |= (m_type & CROSSHAIR ? 0x100: 0x300); + } + } + + ASSERT(k < int(g_bwCursors.size())); + if (k >= int(g_bwCursors.size())) // Unexpected key value in release mode + return; + + // Use cached cursor + if (g_bwCursors[k]) { + cursor = g_bwCursors[k]; + } + else { + const gfx::Color black = gfx::rgba(0, 0, 0); + const gfx::Color white = gfx::rgba(255, 255, 255); + os::SurfaceRef cursorSurface = + os::instance()->makeRgbaSurface(cursorBounds.w, + cursorBounds.h); + cursorSurface->clear(); + int bit = 0; + if (m_type & CROSSHAIR) { + for (int v=0; v<7; v++) { + for (int u=0; u<7; u++) { + if (g_crosshair_pattern[v*7+u]) { + cursorSurface->putPixel( + (k & (1 << bit) ? white: black), u, v); + ++bit; + } + } + } + } + if (requireLittleCenterDot) { + cursorSurface->putPixel( + (k == 0x100 || k == 0x300 ? black: white), + cursorBounds.w/2, cursorBounds.h/2); + } + + cursor = g_bwCursors[k] = + os::instance()->makeCursor( + cursorSurface.get(), + cursorCenter, + scale); + } + } + // Cursor with solid color (easiest case, we don't have to check the + // colors in the screen to create the crosshair) + else { + // We have to recreate all cursors if the color has changed. + if (g_solidCursorColor != cursorColor) { + g_solidCursors.fill(nullptr); + g_solidCursorColor = cursorColor; + } + + int k = 0; + if (m_type & CROSSHAIR) { + if (requireLittleCenterDot) + k = 2; + else + k = 1; + } + + // Use cached cursor + if (g_solidCursors[k]) { + cursor = g_solidCursors[k]; + } + else { + os::SurfaceRef cursorSurface = + os::instance()->makeRgbaSurface(cursorBounds.w, + cursorBounds.h); + cursorSurface->clear(); + if (m_type & CROSSHAIR) { + for (int v=0; v<7; v++) + for (int u=0; u<7; u++) + if (g_crosshair_pattern[v*7+u]) + cursorSurface->putPixel(cursorColor, u, v); + } + if (requireLittleCenterDot) + cursorSurface->putPixel(cursorColor, cursorBounds.w/2, cursorBounds.h/2); + + cursor = g_solidCursors[k] = + os::instance()->makeCursor( + cursorSurface.get(), + cursorCenter, + scale); + } + } + + if (cursor) { + // TODO should we use ui::set_mouse_cursor()? + ui::set_mouse_cursor_reset_info(); + window->setCursor(cursor); } } @@ -538,34 +668,6 @@ void BrushPreview::forEachBrushPixel( m_savedPixelsLimit = m_savedPixelsIterator; } -void BrushPreview::traceCrossPixels( - ui::Graphics* g, - const gfx::Point& pt, gfx::Color color, - PixelDelegate pixelDelegate) -{ - static int cross[7*7] = { - 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, - }; - gfx::Point out; - int u, v; - - for (v=0; v<7; v++) { - for (u=0; u<7; u++) { - if (cross[v*7+u]) { - out.x = pt.x-3+u; - out.y = pt.y-3+v; - (this->*pixelDelegate)(g, out, color); - } - } - } -} - // Old thick cross (used for selection tools) void BrushPreview::traceSelectionCrossPixels( ui::Graphics* g, @@ -634,30 +736,6 @@ void BrushPreview::traceBrushBoundaries(ui::Graphics* g, ////////////////////////////////////////////////////////////////////// // Pixel delegates -void BrushPreview::putPixelInCursorDelegate(ui::Graphics* g, const gfx::Point& pt, gfx::Color color) -{ - ASSERT(m_cursor); - - if (!m_clippingRegion.contains(pt)) - return; - - if (m_blackAndWhiteNegative) { - color_t c = g->getPixel(pt.x, pt.y); - int r = gfx::getr(c); - int g = gfx::getg(c); - int b = gfx::getb(c); - - m_cursor->putPixel(color_utils::blackandwhite_neg(gfx::rgba(r, g, b)), - pt.x - m_screenPosition.x + m_cursorCenter.x, - pt.y - m_screenPosition.y + m_cursorCenter.y); - } - else { - m_cursor->putPixel(color, - pt.x - m_screenPosition.x + m_cursorCenter.x, - pt.y - m_screenPosition.y + m_cursorCenter.y); - } -} - void BrushPreview::savePixelDelegate(ui::Graphics* g, const gfx::Point& pt, gfx::Color color) { if (m_clippingRegion.contains(pt)) { diff --git a/src/app/ui/editor/brush_preview.h b/src/app/ui/editor/brush_preview.h index d3673dd36..c94f2085a 100644 --- a/src/app/ui/editor/brush_preview.h +++ b/src/app/ui/editor/brush_preview.h @@ -19,6 +19,7 @@ #include "gfx/rect.h" #include "gfx/region.h" #include "os/surface.h" +#include "ui/cursor.h" #include @@ -72,6 +73,8 @@ namespace app { NATIVE_CROSSHAIR = 8, }; + static void destroyInternals(); + BrushPreview(Editor* editor); ~BrushPreview(); @@ -94,23 +97,17 @@ namespace app { void generateBoundaries(); // Creates a little native cursor to draw the CROSSHAIR - void createNativeCursor(); - void forEachLittleCrossPixel( - ui::Graphics* g, - const gfx::Point& screenPos, - gfx::Color color, - PixelDelegate pixelDelegate); + void createCrosshairCursor(ui::Graphics* g, const gfx::Color cursorColor); + void forEachBrushPixel( ui::Graphics* g, const gfx::Point& spritePos, gfx::Color color, PixelDelegate pixelDelegate); - void traceCrossPixels(ui::Graphics* g, const gfx::Point& pt, gfx::Color color, PixelDelegate pixel); void traceSelectionCrossPixels(ui::Graphics* g, const gfx::Point& pt, gfx::Color color, int thickness, PixelDelegate pixel); void traceBrushBoundaries(ui::Graphics* g, gfx::Point pos, gfx::Color color, PixelDelegate pixel); - void putPixelInCursorDelegate(ui::Graphics* g, const gfx::Point& pt, gfx::Color color); void savePixelDelegate(ui::Graphics* g, const gfx::Point& pt, gfx::Color color); void drawPixelDelegate(ui::Graphics* g, const gfx::Point& pt, gfx::Color color); void clearPixelDelegate(ui::Graphics* g, const gfx::Point& pt, gfx::Color color); @@ -129,10 +126,6 @@ namespace app { gfx::Point m_screenPosition; // Position in the screen (view) gfx::Point m_editorPosition; // Position in the editor (model) - // Native mouse cursor to draw crosshair - os::SurfaceRef m_cursor; - gfx::Point m_cursorCenter; - // Information about current brush doc::MaskBoundaries m_brushBoundaries; int m_brushGen; diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 07f9454cf..6fa3c59b0 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -227,6 +227,7 @@ Editor::~Editor() void Editor::destroyEditorSharedInternals() { + BrushPreview::destroyInternals(); if (m_renderEngine) { delete m_renderEngine; m_renderEngine = nullptr; diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index 2e3bd1611..5eae0d039 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -222,12 +222,12 @@ SkinTheme* SkinTheme::instance() SkinTheme::SkinTheme() : m_sheet(nullptr) - , m_standardCursors(ui::kCursorTypes, nullptr) , m_defaultFont(nullptr) , m_miniFont(nullptr) , m_preferredScreenScaling(-1) , m_preferredUIScaling(-1) { + m_standardCursors.fill(nullptr); } SkinTheme::~SkinTheme() diff --git a/src/app/ui/skin/skin_theme.h b/src/app/ui/skin/skin_theme.h index ea25e1645..316c2a48d 100644 --- a/src/app/ui/skin/skin_theme.h +++ b/src/app/ui/skin/skin_theme.h @@ -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 @@ -12,15 +12,17 @@ #include "app/ui/skin/skin_part.h" #include "gfx/color.h" #include "gfx/fwd.h" +#include "ui/cursor.h" +#include "ui/cursor_type.h" #include "ui/manager.h" #include "ui/scale.h" #include "ui/theme.h" #include "theme.xml.h" +#include #include #include -#include namespace ui { class Entry; @@ -155,7 +157,7 @@ namespace app { std::map m_colors_by_id; std::map m_dimensions_by_id; std::map m_cursors; - std::vector m_standardCursors; + std::array m_standardCursors; std::map m_styles; std::map m_fonts; std::map m_themeFonts; diff --git a/src/ui/cursor.cpp b/src/ui/cursor.cpp index 908636434..8ff7cc9a4 100644 --- a/src/ui/cursor.cpp +++ b/src/ui/cursor.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-2016 David Capello // // This file is released under the terms of the MIT license. @@ -13,14 +13,36 @@ #include "base/debug.h" #include "os/surface.h" +#include "os/system.h" namespace ui { -Cursor::Cursor(const os::SurfaceRef& surface, const gfx::Point& focus) +Cursor::Cursor(const os::SurfaceRef& surface, + const gfx::Point& focus) : m_surface(surface) , m_focus(focus) + , m_scale(0) { - ASSERT(m_surface != nullptr); +} + +void Cursor::reset() +{ + m_surface.reset(); + m_cursor.reset(); + m_focus = gfx::Point(0, 0); + m_scale = 0; +} + +os::CursorRef Cursor::nativeCursor(const int scale) const +{ + if (m_cursor && m_scale == scale) + return m_cursor; + + m_cursor = os::instance()->makeCursor( + m_surface.get(), + m_focus, + m_scale = scale); + return m_cursor; } } // namespace ui diff --git a/src/ui/cursor.h b/src/ui/cursor.h index e22434eb8..7f9cb0b09 100644 --- a/src/ui/cursor.h +++ b/src/ui/cursor.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-2017 David Capello // // This file is released under the terms of the MIT license. @@ -10,22 +10,27 @@ #pragma once #include "gfx/point.h" +#include "os/cursor.h" #include "os/surface.h" -namespace os { class Surface; } - namespace ui { class Cursor { public: - Cursor(const os::SurfaceRef& surface, const gfx::Point& focus); + Cursor(const os::SurfaceRef& surface = nullptr, + const gfx::Point& focus = gfx::Point(0, 0)); - const os::SurfaceRef& getSurface() const { return m_surface; } - const gfx::Point& getFocus() const { return m_focus; } + const os::SurfaceRef& surface() const { return m_surface; } + const gfx::Point& focus() const { return m_focus; } + os::CursorRef nativeCursor(const int scale) const; + + void reset(); private: os::SurfaceRef m_surface; gfx::Point m_focus; + mutable os::CursorRef m_cursor; + mutable int m_scale; }; } // namespace ui diff --git a/src/ui/system.cpp b/src/ui/system.cpp index bc4461215..53225edc2 100644 --- a/src/ui/system.cpp +++ b/src/ui/system.cpp @@ -59,14 +59,14 @@ static void update_mouse_overlay(const Cursor* cursor) if (mouse_cursor && mouse_scares == 0) { if (!mouse_cursor_overlay) { mouse_cursor_overlay = base::make_ref( - mouse_cursor->getSurface(), + mouse_cursor->surface(), get_mouse_position(), Overlay::MouseZOrder); OverlayManager::instance()->addOverlay(mouse_cursor_overlay); } else { - mouse_cursor_overlay->setSurface(mouse_cursor->getSurface()); + mouse_cursor_overlay->setSurface(mouse_cursor->surface()); update_cursor_overlay(); } } @@ -84,19 +84,18 @@ static bool update_custom_native_cursor(const Cursor* cursor) // Check if we can use a custom native mouse in this platform if (support_native_custom_cursor && mouse_display) { - if (cursor) { - result = mouse_display->setNativeMouseCursor( - // The surface is already scaled by guiscale() - cursor->getSurface().get(), - cursor->getFocus(), - // We scale the cursor by the os::Display scale - mouse_display->scale() * mouse_cursor_scale); + if (cursor && cursor->surface()) { + // The cursor surface is already scaled by guiscale(), we scale + // the cursor by the os::Display scale and mouse scale. + const int scale = mouse_display->scale() * mouse_cursor_scale; + if (auto osCursor = cursor->nativeCursor(scale)) + result = mouse_display->setCursor(osCursor); } else if (mouse_cursor_type == kOutsideDisplay) { - result = mouse_display->setNativeMouseCursor(os::NativeCursor::Arrow); + result = mouse_display->setCursor(os::NativeCursor::Arrow); } else { - result = mouse_display->setNativeMouseCursor(os::NativeCursor::Hidden); + result = mouse_display->setCursor(os::NativeCursor::Hidden); } } @@ -147,7 +146,7 @@ static void update_mouse_cursor() // Set native cursor if (mouse_display) { - bool ok = mouse_display->setNativeMouseCursor(nativeCursor); + bool ok = mouse_display->setCursor(nativeCursor); // It looks like the specific native cursor is not supported, // so we can should use the internal overlay (even when we @@ -192,7 +191,7 @@ UISystem::UISystem() support_native_custom_cursor = ((os::instance() && (int(os::instance()->capabilities()) & - int(os::Capabilities::CustomNativeMouseCursor))) ? + int(os::Capabilities::CustomMouseCursor))) ? true: false); details::initWidgets(); @@ -200,8 +199,6 @@ UISystem::UISystem() UISystem::~UISystem() { - OverlayManager::destroyInstance(); - // finish theme set_theme(nullptr, guiscale()); @@ -211,6 +208,8 @@ UISystem::~UISystem() if (!update_custom_native_cursor(nullptr)) update_mouse_overlay(nullptr); + OverlayManager::destroyInstance(); + ASSERT(g_instance == this); g_instance = nullptr; } @@ -262,7 +261,7 @@ void update_cursor_overlay() { if (mouse_cursor_overlay != nullptr && mouse_scares == 0) { gfx::Point newPos = - get_mouse_position() - mouse_cursor->getFocus(); + get_mouse_position() - mouse_cursor->focus(); if (newPos != mouse_cursor_overlay->position()) { mouse_cursor_overlay->moveOverlay(newPos); diff --git a/src/ui/theme.h b/src/ui/theme.h index 05d0341d2..ea486ded0 100644 --- a/src/ui/theme.h +++ b/src/ui/theme.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-2017 David Capello // // This file is released under the terms of the MIT license. @@ -59,7 +59,7 @@ namespace ui { virtual os::Font* getDefaultFont() const = 0; virtual os::Font* getWidgetFont(const Widget* widget) const = 0; - virtual Cursor* getStandardCursor(CursorType type) = 0; + virtual ui::Cursor* getStandardCursor(CursorType type) = 0; virtual void initWidget(Widget* widget) = 0; virtual void getWindowMask(Widget* widget, gfx::Region& region) = 0; virtual void setDecorativeWidgetBounds(Widget* widget);