Generate a native cursor for the crosshair on the sprite

In this way we can get pixel from the window surface and put pixels in
the new cursor surface which will represent the crosshair on the
mouse/screen position. In this way we avoid an effect of a slow mouse
response on high refresh rates.

Maybe related to: https://community.aseprite.org/t/3354
This commit is contained in:
David Capello 2021-02-25 16:56:33 -03:00
parent a24f271e3a
commit ef4f691459
2 changed files with 169 additions and 33 deletions

View File

@ -27,6 +27,7 @@
#include "app/ui/editor/tool_loop_impl.h"
#include "app/ui_context.h"
#include "app/util/wrap_value.h"
#include "base/scoped_value.h"
#include "doc/algo.h"
#include "doc/blend_internals.h"
#include "doc/brush.h"
@ -35,6 +36,8 @@
#include "doc/layer.h"
#include "doc/primitives.h"
#include "os/display.h"
#include "os/surface.h"
#include "os/system.h"
#include "render/render.h"
#include "ui/manager.h"
#include "ui/system.h"
@ -45,17 +48,13 @@ using namespace doc;
BrushPreview::BrushPreview(Editor* editor)
: m_editor(editor)
, m_type(CROSSHAIR)
, m_onScreen(false)
, m_withModifiedPixels(false)
, m_withRealPreview(false)
, m_screenPosition(0, 0)
, m_editorPosition(0, 0)
{
}
BrushPreview::~BrushPreview()
{
if (m_cursor)
m_cursor->dispose();
}
BrushRef BrushPreview::getCurrentBrush()
@ -311,8 +310,13 @@ void BrushPreview::show(const gfx::Point& screenPos)
ui::ScreenGraphics g;
ui::SetClip clip(&g);
gfx::Color uiCursorColor = color_utils::color_for_ui(appCursorColor);
forEachBrushPixel(&g, m_screenPosition, spritePos, uiCursorColor, &BrushPreview::savePixelDelegate);
forEachBrushPixel(&g, m_screenPosition, spritePos, uiCursorColor, &BrushPreview::drawPixelDelegate);
createNativeCursor();
if (m_cursor)
forEachLittleCrossPixel(&g, m_screenPosition, uiCursorColor, &BrushPreview::putPixelInCursorDelegate);
forEachBrushPixel(&g, spritePos, uiCursorColor, &BrushPreview::savePixelDelegate);
forEachBrushPixel(&g, spritePos, uiCursorColor, &BrushPreview::drawPixelDelegate);
m_withModifiedPixels = true;
}
@ -337,6 +341,12 @@ void BrushPreview::hide()
if (!m_onScreen)
return;
// Don't hide the cursor to avoid flickering, the native mouse
// cursor will be changed anyway after the hide() by the caller.
//
//if (m_cursor)
// m_editor->manager()->getDisplay()->setNativeMouseCursor(os::NativeCursor::kNoCursor);
// Get drawable region
m_editor->getDrawableRegion(m_clippingRegion, ui::Widget::kCutTopWindows);
@ -348,7 +358,7 @@ void BrushPreview::hide()
// Restore pixels
ui::ScreenGraphics g;
ui::SetClip clip(&g);
forEachBrushPixel(&g, m_screenPosition, m_editorPosition, gfx::ColorNone,
forEachBrushPixel(&g, m_editorPosition, gfx::ColorNone,
&BrushPreview::clearPixelDelegate);
}
@ -370,6 +380,7 @@ void BrushPreview::hide()
}
m_onScreen = false;
m_clippingRegion.clear();
m_oldClippingRegion.clear();
}
@ -433,30 +444,89 @@ void BrushPreview::generateBoundaries()
delete mask;
}
void BrushPreview::forEachBrushPixel(
void BrushPreview::createNativeCursor()
{
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->dispose();
m_cursor = nullptr;
}
}
if (cursorBounds.isEmpty()) {
ASSERT(!m_cursor);
m_editor->manager()->getDisplay()->setNativeMouseCursor(os::NativeCursor::kNoCursor);
return;
}
if (!m_cursor) {
m_cursor = os::instance()->createRgbaSurface(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,
const gfx::Point& spritePos,
gfx::Color color,
PixelDelegate pixelDelegate)
{
m_savedPixelsIterator = 0;
if (m_type & CROSSHAIR)
traceCrossPixels(g, screenPos, color, pixelDelegate);
if (m_type & SELECTION_CROSSHAIR)
traceSelectionCrossPixels(g, spritePos, color, 1, pixelDelegate);
if (m_type & BRUSH_BOUNDARIES)
traceBrushBoundaries(g, spritePos, color, pixelDelegate);
// 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);
}
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<bool> restore(m_blackAndWhiteNegative, false,
m_blackAndWhiteNegative);
(this->*pixelDelegate)(g, screenPos, gfx::ColorNone);
}
if (m_cursor) {
m_editor->manager()->getDisplay()->setNativeMouseCursor(
m_cursor, m_cursorCenter,
m_editor->manager()->getDisplay()->scale());
}
}
void BrushPreview::forEachBrushPixel(
ui::Graphics* g,
const gfx::Point& spritePos,
gfx::Color color,
PixelDelegate pixelDelegate)
{
m_savedPixelsIterator = 0;
if (m_type & SELECTION_CROSSHAIR)
traceSelectionCrossPixels(g, spritePos, color, 1, pixelDelegate);
if (m_type & BRUSH_BOUNDARIES)
traceBrushBoundaries(g, spritePos, color, pixelDelegate);
m_savedPixelsLimit = m_savedPixelsIterator;
}
@ -489,9 +559,7 @@ void BrushPreview::traceCrossPixels(
}
}
//////////////////////////////////////////////////////////////////////
// Old Thick Cross
// Old thick cross (used for selection tools)
void BrushPreview::traceSelectionCrossPixels(
ui::Graphics* g,
const gfx::Point& pt, gfx::Color color,
@ -528,9 +596,7 @@ void BrushPreview::traceSelectionCrossPixels(
}
}
//////////////////////////////////////////////////////////////////////
// Current Brush Bounds
// Current brush edges
void BrushPreview::traceBrushBoundaries(ui::Graphics* g,
gfx::Point pos,
gfx::Color color,
@ -558,6 +624,33 @@ 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)) {

View File

@ -26,6 +26,10 @@ namespace doc {
class Sprite;
}
namespace os {
class Surface;
}
namespace ui {
class Graphics;
}
@ -37,10 +41,34 @@ namespace app {
public:
// Brush type
enum {
CROSSHAIR = 1,
// A simple cursor in the mouse position for drawing tools. The
// crosshair is painted in real-time with black and white
// depending on the pixel of the screen.
//
// |
// |
// --- * ---
// |
// |
CROSSHAIR = 1,
// Crosshair used in the selection tools in the sprite position.
//
// | |
// -- --
// *
// -- --
// | |
SELECTION_CROSSHAIR = 2,
BRUSH_BOUNDARIES = 4,
NATIVE_CROSSHAIR = 8,
// The boundaries of the brush used in the sprite position
// (depends on the shape of the brush generated with
// doc::MaskBoundaries).
BRUSH_BOUNDARIES = 4,
// Use a pre-defined native cursor that is a crosshair in the
// mouse position.
NATIVE_CROSSHAIR = 8,
};
BrushPreview(Editor* editor);
@ -63,9 +91,16 @@ namespace app {
static doc::color_t getBrushColor(doc::Sprite* sprite, doc::Layer* layer);
void generateBoundaries();
void forEachBrushPixel(
// 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 forEachBrushPixel(
ui::Graphics* g,
const gfx::Point& spritePos,
gfx::Color color,
PixelDelegate pixelDelegate);
@ -74,28 +109,36 @@ namespace app {
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);
Editor* m_editor;
int m_type;
int m_type = CROSSHAIR;
// The brush preview shows the cross or brush boundaries as black
// & white negative.
bool m_blackAndWhiteNegative;
// The brush preview is on the screen.
bool m_onScreen;
bool m_withModifiedPixels;
bool m_withRealPreview;
bool m_onScreen = false;
bool m_withRealPreview = false;
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::Surface* m_cursor = nullptr;
gfx::Point m_cursorCenter;
// Information about current brush
doc::MaskBoundaries m_brushBoundaries;
int m_brushGen;
// True if we've modified pixels in the display surface
// (e.g. drawing the selection crosshair or the brush edges).
bool m_withModifiedPixels = false;
std::vector<gfx::Color> m_savedPixels;
int m_savedPixelsIterator;
int m_savedPixelsLimit;