Fix GIF and Overlay issues; Release NightShot v0.19

This commit is contained in:
f1den committed 2026-08-10 16:20:38 +03:00
1 parent dfcbacfb0c
commit ceb0b5552e
10 files changed
+528 -208

No files matched your search

+140 -125
View File
@@ -141,19 +141,34 @@ Tool toolFromAction(Action action) {
}
}
void drawArrow(Graphics& g, const Pen& pen, PointF start, PointF end) {
g.DrawLine(&pen, start, end);
void drawArrow(Graphics& g, const Pen& pen, const Color& color, PointF start, PointF end) {
const float dx = end.X - start.X;
const float dy = end.Y - start.Y;
const float len = std::sqrt(dx * dx + dy * dy);
if (len < 1.0f) return;
const float angle = std::atan2(dy, dx);
const float head = std::clamp(len * 0.22f, 9.0f, 18.0f);
const float spread = 0.55f;
PointF a(end.X - head * std::cos(angle - spread), end.Y - head * std::sin(angle - spread));
PointF b(end.X - head * std::cos(angle + spread), end.Y - head * std::sin(angle + spread));
g.DrawLine(&pen, end, a);
g.DrawLine(&pen, end, b);
const float length = std::sqrt(dx * dx + dy * dy);
if (length < 1.0f) return;
const float ux = dx / length;
const float uy = dy / length;
const float nx = -uy;
const float ny = ux;
const float stroke = std::max(1.0f, pen.GetWidth());
const float headLength = std::clamp(stroke * 4.6f + 7.0f, 11.0f, 27.0f);
const float halfHeadWidth = std::clamp(headLength * 0.48f, 5.0f, 14.0f);
// Lightshot-style arrow: one clean rounded shaft with a filled triangular
// head. The shaft runs under the head slightly so antialiasing never leaves
// a visible seam between the two shapes.
const float shaftInset = std::min(headLength * 0.58f, length * 0.45f);
PointF shaftEnd(end.X - ux * shaftInset, end.Y - uy * shaftInset);
g.DrawLine(&pen, start, shaftEnd);
PointF baseCenter(end.X - ux * headLength, end.Y - uy * headLength);
PointF triangle[3]{
end,
PointF(baseCenter.X + nx * halfHeadWidth, baseCenter.Y + ny * halfHeadWidth),
PointF(baseCenter.X - nx * halfHeadWidth, baseCenter.Y - ny * halfHeadWidth)};
SolidBrush headBrush(color);
g.FillPolygon(&headBrush, triangle, 3);
}
void drawAnnotation(Graphics& g, const Annotation& a, float offsetX, float offsetY) {
@@ -184,7 +199,7 @@ void drawAnnotation(Graphics& g, const Annotation& a, float offsetX, float offse
if (a.points.size() >= 2) g.DrawLine(&pen, shifted(a.points[0]), shifted(a.points[1]));
break;
case Tool::Arrow:
if (a.points.size() >= 2) drawArrow(g, pen, shifted(a.points[0]), shifted(a.points[1]));
if (a.points.size() >= 2) drawArrow(g, pen, a.color, shifted(a.points[0]), shifted(a.points[1]));
break;
case Tool::Rectangle:
if (a.points.size() >= 2) {
@@ -231,7 +246,7 @@ void drawAnnotationClipped(Graphics& g, const Annotation& annotation, const RECT
maxY = std::max(maxY, point.Y + offsetY);
}
float padding = annotation.width * 0.5f + 5.0f;
if (annotation.tool == Tool::Arrow) padding += 19.0f;
if (annotation.tool == Tool::Arrow) padding += 30.0f;
if (annotation.tool == Tool::Text) {
const float size = std::max(14.0f, annotation.textSize);
maxX += std::max(size * 2.0f, static_cast<float>(annotation.text.size()) * size * 0.72f);
@@ -562,8 +577,8 @@ struct OverlayWindow::Impl {
markerWidth = static_cast<float>(stored.markerWidth);
savedPalette = stored.paletteArgb;
savedPaletteOccupied = stored.paletteOccupied;
color = color_picker_math::colorFromArgb(savedPalette[0]);
colorAlpha = color_picker_math::alphaFromArgb(savedPalette[0]);
color = color_picker_math::colorFromArgb(stored.selectedColorArgb);
colorAlpha = color_picker_math::alphaFromArgb(stored.selectedColorArgb);
pickerHsv = color_picker_math::rgbToHsv(GetRValue(color), GetGValue(color), GetBValue(color));
POINT cursor{};
@@ -600,8 +615,6 @@ struct OverlayWindow::Impl {
Tool tool = Tool::Drag;
COLORREF color = theme::kAccent;
std::vector<Annotation> annotations;
LONG appliedSelectionMoveDx = 0;
LONG appliedSelectionMoveDy = 0;
std::vector<Button> verticalButtons;
std::vector<Button> horizontalButtons;
std::array<RECT, 8> handleRects{};
@@ -797,6 +810,7 @@ struct OverlayWindow::Impl {
value.arrowWidth = static_cast<int>(std::lround(arrowWidth));
value.rectangleWidth = static_cast<int>(std::lround(rectangleWidth));
value.markerWidth = static_cast<int>(std::lround(markerWidth));
value.selectedColorArgb = currentArgb();
value.paletteArgb = savedPalette;
value.paletteOccupied = savedPaletteOccupied;
const bool saved = settings::save(value);
@@ -837,6 +851,7 @@ struct OverlayWindow::Impl {
color = nextColor;
colorAlpha = nextAlpha;
if (syncHsv) syncPickerFromRgb();
persistEditorPreferences();
}
std::uint32_t currentArgb() const {
@@ -929,22 +944,22 @@ struct OverlayWindow::Impl {
changed = true;
break;
case ColorField::Alpha:
colorAlpha = static_cast<BYTE>(std::clamp(value, 0, 255));
setCurrentColor(color, static_cast<BYTE>(std::clamp(value, 0, 255)), false);
changed = true;
break;
case ColorField::Hue:
pickerHsv.h = static_cast<float>((value % 360 + 360) % 360);
color = color_picker_math::hsvToRgb(pickerHsv.h, pickerHsv.s, pickerHsv.v);
setCurrentColor(color_picker_math::hsvToRgb(pickerHsv.h, pickerHsv.s, pickerHsv.v), colorAlpha, false);
changed = true;
break;
case ColorField::Saturation:
pickerHsv.s = static_cast<float>(std::clamp(value, 0, 100)) / 100.0f;
color = color_picker_math::hsvToRgb(pickerHsv.h, pickerHsv.s, pickerHsv.v);
setCurrentColor(color_picker_math::hsvToRgb(pickerHsv.h, pickerHsv.s, pickerHsv.v), colorAlpha, false);
changed = true;
break;
case ColorField::Value:
pickerHsv.v = static_cast<float>(std::clamp(value, 0, 100)) / 100.0f;
color = color_picker_math::hsvToRgb(pickerHsv.h, pickerHsv.s, pickerHsv.v);
setCurrentColor(color_picker_math::hsvToRgb(pickerHsv.h, pickerHsv.s, pickerHsv.v), colorAlpha, false);
changed = true;
break;
default: break;
@@ -1051,19 +1066,19 @@ struct OverlayWindow::Impl {
}
bool sampleCapturedColor(POINT point) {
if (!capture.pixels || point.x < 0 || point.y < 0 ||
if (point.x < 0 || point.y < 0 ||
point.x >= capture.width || point.y >= capture.height) return false;
const auto* row = static_cast<const std::uint8_t*>(capture.pixels) +
static_cast<std::size_t>(point.y) * static_cast<std::size_t>(capture.stride);
const auto* pixel = row + static_cast<std::size_t>(point.x) * 4u;
setCurrentColor(RGB(pixel[2], pixel[1], pixel[0]), 255);
COLORREF sampled{};
if (!capture.samplePixel(point.x, point.y, sampled)) return false;
setCurrentColor(sampled, 255);
return true;
}
RECT brushPreviewBounds(POINT point) const {
if (!hasSelection || !isDrawingTool(tool) || !pointInRect(selection, point)) return RECT{0, 0, 0, 0};
const int diameter = std::max(3, static_cast<int>(std::ceil(currentStrokeWidth())));
const int radius = diameter / 2 + 4;
if (!hasSelection || !isDrawingTool(tool) || point.x < 0 || point.y < 0 ||
point.x >= capture.width || point.y >= capture.height) return RECT{0, 0, 0, 0};
const int diameter = std::max(1, static_cast<int>(std::ceil(currentStrokeWidth())));
const int radius = (diameter + 1) / 2 + 2;
return clampToClient(RECT{point.x - radius, point.y - radius,
point.x + radius + 1, point.y + radius + 1});
}
@@ -1082,7 +1097,7 @@ struct OverlayWindow::Impl {
const float step = tool == Tool::Marker ? 2.0f : 1.0f;
const float maximum = tool == Tool::Marker ? 96.0f : 64.0f;
const RECT previous = brushPreviewRect;
*width = std::clamp(*width + (wheelDelta > 0 ? step : -step), 1.0f, maximum);
*width = std::clamp(*width + (wheelDelta > 0 ? -step : step), 1.0f, maximum);
persistEditorPreferences();
brushPreviewRect = brushPreviewBounds(cursorPoint);
invalidateRectPadded(previous, 3);
@@ -1333,7 +1348,7 @@ struct OverlayWindow::Impl {
maxY = std::max(maxY, point.Y);
}
int padding = static_cast<int>(std::ceil(annotation.width * 0.5f)) + 5;
if (annotation.tool == Tool::Arrow) padding += 19;
if (annotation.tool == Tool::Arrow) padding += 30;
if (annotation.tool == Tool::Text) {
const float size = std::max(14.0f, annotation.textSize);
maxX += std::max(size * 2.0f, static_cast<float>(annotation.text.size()) * size * 0.72f);
@@ -1726,6 +1741,7 @@ struct OverlayWindow::Impl {
pickerDraggingSv = false;
pickerDraggingHue = false;
pickerDraggingAlpha = false;
persistEditorPreferences();
ReleaseCapture();
}
@@ -1806,16 +1822,24 @@ struct OverlayWindow::Impl {
std::unique_ptr<Bitmap> renderSelectionBitmap() {
if (!hasSelection || rectWidth(selection) <= 0 || rectHeight(selection) <= 0) return {};
auto output = std::make_unique<Bitmap>(rectWidth(selection), rectHeight(selection), PixelFormat32bppARGB);
// Screenshots are opaque. RGB avoids the zero-alpha semantics of GDI
// writes into an ARGB GDI+ bitmap while the sparse monitor capture is
// copied into this temporary crop.
auto output = std::make_unique<Bitmap>(rectWidth(selection), rectHeight(selection), PixelFormat32bppRGB);
Graphics g(output.get());
g.Clear(Color(255, 255, 255, 255));
g.Clear(Color(255, 0, 0, 0));
HDC outputDc = g.GetHDC();
if (!outputDc) return {};
const bool copied = capture.blitTo(outputDc, 0, 0, selection);
g.ReleaseHDC(outputDc);
if (!copied) return {};
g.SetSmoothingMode(SmoothingModeAntiAlias);
g.SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
Bitmap source(capture.bitmap, nullptr);
Rect destination(0, 0, rectWidth(selection), rectHeight(selection));
g.DrawImage(&source, destination, selection.left, selection.top,
rectWidth(selection), rectHeight(selection), UnitPixel);
const Rect destination(0, 0, rectWidth(selection), rectHeight(selection));
g.SetClip(destination);
// Annotations live in virtual-desktop coordinates. Selection is only a
// crop rectangle, so moving/resizing it never mutates drawing data.
for (const auto& annotation : annotations)
drawAnnotation(g, annotation, static_cast<float>(-selection.left), static_cast<float>(-selection.top));
return output;
@@ -2418,44 +2442,35 @@ struct OverlayWindow::Impl {
}
void drawBrushPreview(Graphics& g, RECT dirty) {
if (!hasSelection || selecting || drawing || draggingSelection || resizeHandle >= 0 ||
if (!hasSelection || selecting || draggingSelection || resizeHandle >= 0 ||
paletteOpen || eyedropperActive || !isDrawingTool(tool) ||
!rectsIntersect(brushPreviewRect, dirty) ||
!pointInRect(selection, cursorPoint)) return;
const float width = std::max(1.0f, currentStrokeWidth());
const float visualDiameter = std::max(4.0f, width);
const float x = static_cast<float>(cursorPoint.x) - visualDiameter * 0.5f;
const float y = static_cast<float>(cursorPoint.y) - visualDiameter * 0.5f;
!rectsIntersect(brushPreviewRect, dirty)) return;
const float diameter = std::max(1.0f, currentStrokeWidth());
const float x = static_cast<float>(cursorPoint.x) - diameter * 0.5f;
const float y = static_cast<float>(cursorPoint.y) - diameter * 0.5f;
const GraphicsState state = g.Save();
g.SetSmoothingMode(SmoothingModeAntiAlias);
g.SetPixelOffsetMode(PixelOffsetModeHalf);
SolidBrush fill(Color(tool == Tool::Marker ? std::min<int>(colorAlpha, 96) : std::max<int>(70, colorAlpha),
GetRValue(color), GetGValue(color), GetBValue(color)));
g.FillEllipse(&fill, x, y, visualDiameter, visualDiameter);
Pen shadow(Color(205, 0, 0, 0), 3.0f);
Pen border(Color(245, 255, 255, 255), 1.0f);
g.DrawEllipse(&shadow, x, y, visualDiameter, visualDiameter);
g.DrawEllipse(&border, x, y, visualDiameter, visualDiameter);
if (width < 4.0f) {
SolidBrush center(toGdiColor(color, colorAlpha));
const float centerSize = std::max(1.0f, width);
g.FillEllipse(&center,
static_cast<float>(cursorPoint.x) - centerSize * 0.5f,
static_cast<float>(cursorPoint.y) - centerSize * 0.5f,
centerSize, centerSize);
}
// Preview is deliberately hollow: it represents the exact physical
// brush diameter without covering the pixels the user is trying to aim
// at. Keep the border at one physical pixel on every monitor.
Pen border(toGdiColor(color, static_cast<BYTE>(std::max<int>(colorAlpha, 160))), 1.0f);
g.DrawEllipse(&border, x, y, diameter, diameter);
g.Restore(state);
}
void drawScene(Graphics& g, RECT dirty) {
// Drawing is a virtual-desktop layer, independent from the crop. It is
// intentionally visible even outside the selected rectangle and keeps
// absolute coordinates while the selection moves or resizes.
for (const auto& annotation : annotations) {
drawAnnotationClipped(g, annotation, dirty);
}
if (hasSelection || selecting) {
const RECT r = selection;
const GraphicsState annotationState = g.Save();
g.SetClip(Rect(r.left, r.top, std::max(0, rectWidth(r)), std::max(0, rectHeight(r))), CombineModeIntersect);
for (const auto& annotation : annotations) {
drawAnnotationClipped(g, annotation, dirty);
}
g.Restore(annotationState);
const int borderPadding = std::max(10, ui.handle + 6);
const std::array<RECT, 4> borderBands{{
@@ -2473,7 +2488,7 @@ struct OverlayWindow::Impl {
static_cast<REAL>(std::max(0, rectWidth(r) - 1)),
static_cast<REAL>(std::max(0, rectHeight(r) - 1)));
if (hasSelection && !selecting) {
if (hasSelection && !selecting && tool == Tool::Drag) {
SolidBrush handleFill(toGdiColor(theme::kAccentHover));
Pen handleBorder(Color(210, 255, 255, 255), 1.0f);
for (const RECT& handle : handleRects) {
@@ -2597,7 +2612,7 @@ struct OverlayWindow::Impl {
const int height = rectHeight(tile);
if (width <= 0 || height <= 0 || !ensureScratchBuffer(target, width, height)) return;
BitBlt(scratchDc, 0, 0, width, height, capture.memoryDc, tile.left, tile.top, SRCCOPY);
static_cast<void>(capture.blitTo(scratchDc, 0, 0, tile));
dimScratchRect(tile);
Graphics graphics(scratchDc);
@@ -2666,11 +2681,11 @@ struct OverlayWindow::Impl {
const int padding = static_cast<int>(std::ceil(annotation.width * 0.5f)) + 4;
if ((annotation.tool == Tool::Line || annotation.tool == Tool::Arrow) && annotation.points.size() >= 2) {
invalidateSegmentTiles(annotation.points[0], annotation.points[1],
padding + (annotation.tool == Tool::Arrow ? 10 : 0));
padding + (annotation.tool == Tool::Arrow ? 30 : 0));
if (annotation.tool == Tool::Arrow) {
const PointF end = annotation.points[1];
invalidateRectPadded(RECT{static_cast<LONG>(end.X) - 24, static_cast<LONG>(end.Y) - 24,
static_cast<LONG>(end.X) + 25, static_cast<LONG>(end.Y) + 25}, 1);
invalidateRectPadded(RECT{static_cast<LONG>(end.X) - 36, static_cast<LONG>(end.Y) - 36,
static_cast<LONG>(end.X) + 37, static_cast<LONG>(end.Y) + 37}, 1);
}
} else if (annotation.tool == Tool::Rectangle && annotation.points.size() >= 2) {
invalidateRectangleOutline(annotation.points[0], annotation.points[1], padding);
@@ -2680,7 +2695,9 @@ struct OverlayWindow::Impl {
}
void beginAnnotation(POINT p) {
if (tool == Tool::Drag || !pointInRect(selection, p)) return;
if (!hasSelection || tool == Tool::Drag) return;
p.x = std::clamp(p.x, 0L, static_cast<LONG>(capture.width - 1));
p.y = std::clamp(p.y, 0L, static_cast<LONG>(capture.height - 1));
if (tool == Tool::Text) {
beginTextEdit(p);
return;
@@ -2701,8 +2718,8 @@ struct OverlayWindow::Impl {
void updateAnnotation(POINT p) {
if (!drawing || annotations.empty()) return;
p.x = std::clamp(p.x, selection.left, selection.right);
p.y = std::clamp(p.y, selection.top, selection.bottom);
p.x = std::clamp(p.x, 0L, static_cast<LONG>(capture.width - 1));
p.y = std::clamp(p.y, 0L, static_cast<LONG>(capture.height - 1));
Annotation& annotation = annotations.back();
if (annotation.tool == Tool::Pen || annotation.tool == Tool::Marker) {
@@ -2746,7 +2763,7 @@ struct OverlayWindow::Impl {
if (!textEditing) return;
const int monitorIndex = monitorIndexForPoint(textAnchor);
const UiMetrics metrics = metricsForMonitor(monitorIndex);
RECT safe = intersectRects(monitors[monitorIndex].workBounds, selection);
RECT safe = monitors[monitorIndex].workBounds;
RECT insetSafe = safe;
InflateRect(&insetSafe, -metrics.margin, -metrics.margin);
if (rectWidth(insetSafe) >= 40 && rectHeight(insetSafe) >= 24) safe = insetSafe;
@@ -2779,7 +2796,7 @@ struct OverlayWindow::Impl {
commitTextEdit();
const int monitorIndex = monitorIndexForPoint(p);
const UiMetrics metrics = metricsForMonitor(monitorIndex);
RECT available = intersectRects(monitors[monitorIndex].workBounds, selection);
RECT available = monitors[monitorIndex].workBounds;
InflateRect(&available, -metrics.margin, -metrics.margin);
if (rectWidth(available) < 50 || rectHeight(available) < 32) return;
@@ -2900,9 +2917,6 @@ struct OverlayWindow::Impl {
const RECT previousUi = uiBounds();
const RECT previousBadge = dimensionBadgeRect;
const RECT previousPreview = brushPreviewRect;
if (!annotations.empty()) {
for (const auto& annotation : annotations) invalidateAnnotationShape(annotation);
}
const LONG requestedDx = p.x - dragStart.x;
const LONG requestedDy = p.y - dragStart.y;
@@ -2910,29 +2924,11 @@ struct OverlayWindow::Impl {
OffsetRect(&nextLogical, requestedDx, requestedDy);
nextLogical = keepLogicalSelectionReachable(nextLogical);
const LONG actualDx = nextLogical.left - originalLogicalSelection.left;
const LONG actualDy = nextLogical.top - originalLogicalSelection.top;
logicalSelection = nextLogical;
selection = visibleSelectionFromLogical(logicalSelection);
const LONG deltaDx = actualDx - appliedSelectionMoveDx;
const LONG deltaDy = actualDy - appliedSelectionMoveDy;
if (deltaDx != 0 || deltaDy != 0) {
for (auto& annotation : annotations) {
for (auto& point : annotation.points) {
point.X += static_cast<REAL>(deltaDx);
point.Y += static_cast<REAL>(deltaDy);
}
for (RECT& bounds : annotation.strokeChunkBounds) OffsetRect(&bounds, deltaDx, deltaDy);
if (annotation.hasStrokeBounds) OffsetRect(&annotation.strokeBounds, deltaDx, deltaDy);
}
appliedSelectionMoveDx = actualDx;
appliedSelectionMoveDy = actualDy;
}
if (!annotations.empty()) {
for (const auto& annotation : annotations) invalidateAnnotationShape(annotation);
}
// Annotations deliberately stay in absolute virtual-desktop
// coordinates. Selection is only the crop rectangle.
updateLayout();
brushPreviewRect = brushPreviewBounds(cursorPoint);
invalidateSelectionTransition(previousSelection, previousUi, previousBadge);
@@ -2955,26 +2951,41 @@ struct OverlayWindow::Impl {
if (pointInRect(pickerEyedropperRect, p)) { SetCursor(LoadCursorW(nullptr, IDC_HAND)); return; }
}
if (!hasSelection) { SetCursor(LoadCursorW(nullptr, IDC_CROSS)); return; }
const int handle = hitHandle(p);
switch (handle) {
case 0: case 4: SetCursor(LoadCursorW(nullptr, IDC_SIZENWSE)); return;
case 2: case 6: SetCursor(LoadCursorW(nullptr, IDC_SIZENESW)); return;
case 1: case 5: SetCursor(LoadCursorW(nullptr, IDC_SIZENS)); return;
case 3: case 7: SetCursor(LoadCursorW(nullptr, IDC_SIZEWE)); return;
default: break;
}
if (hitButton(p) >= 0 || hitPalette(p)) { SetCursor(LoadCursorW(nullptr, IDC_HAND)); return; }
if (hitToolbarPanel(p)) { SetCursor(LoadCursorW(nullptr, IDC_ARROW)); return; }
if (hitSelectionBorder(p)) { SetCursor(LoadCursorW(nullptr, IDC_SIZEALL)); return; }
if (pointInRect(selection, p)) {
if (tool == Tool::Drag) SetCursor(LoadCursorW(nullptr, IDC_SIZEALL));
else if (tool == Tool::Text) SetCursor(LoadCursorW(nullptr, IDC_IBEAM));
else if (isDrawingTool(tool)) SetCursor(nullptr);
else SetCursor(LoadCursorW(nullptr, IDC_CROSS));
// The selection is interactive only in Drag mode. Drawing tools never
// accidentally resize/move the crop when a stroke starts near its edge.
if (tool == Tool::Drag) {
const int handle = hitHandle(p);
switch (handle) {
case 0: case 4: SetCursor(LoadCursorW(nullptr, IDC_SIZENWSE)); return;
case 2: case 6: SetCursor(LoadCursorW(nullptr, IDC_SIZENESW)); return;
case 1: case 5: SetCursor(LoadCursorW(nullptr, IDC_SIZENS)); return;
case 3: case 7: SetCursor(LoadCursorW(nullptr, IDC_SIZEWE)); return;
default: break;
}
if (hitSelectionBorder(p) || pointInRect(selection, p)) {
SetCursor(LoadCursorW(nullptr, IDC_SIZEALL));
return;
}
SetCursor(LoadCursorW(nullptr, IDC_ARROW));
return;
}
SetCursor(LoadCursorW(nullptr, IDC_ARROW));
if (tool == Tool::Text) {
SetCursor(LoadCursorW(nullptr, IDC_IBEAM));
return;
}
if (isDrawingTool(tool)) {
// The hollow brush preview becomes the cursor over the entire
// virtual desktop, not just over the crop.
SetCursor(nullptr);
return;
}
SetCursor(LoadCursorW(nullptr, IDC_CROSS));
}
};
OverlayWindow::OverlayWindow(ScreenCapture&& capture, HWND notificationTarget, OverlayMode mode)
@@ -3087,6 +3098,16 @@ LRESULT OverlayWindow::handleMessage(UINT message, WPARAM wParam, LPARAM lParam)
}
// Panel padding/gaps are UI, not transparent drawing holes.
if (s.hitToolbarPanel(p)) return 0;
if (s.tool != Tool::Drag) {
// Drawing/text tools own the entire virtual desktop. The
// selection border and resize handles are intentionally
// inert until the user explicitly switches back to Drag.
s.beginAnnotation(p);
s.updateBrushPreview(p);
return 0;
}
const int handle = s.hitHandle(p);
if (handle >= 0) {
// A manual resize commits the currently visible rectangle as
@@ -3099,26 +3120,21 @@ LRESULT OverlayWindow::handleMessage(UINT message, WPARAM wParam, LPARAM lParam)
SetCapture(s.hwnd);
return 0;
}
if (s.hitSelectionBorder(p) ||
(s.tool == Tool::Drag && pointInRect(s.selection, p))) {
if (s.hitSelectionBorder(p) || pointInRect(s.selection, p)) {
s.draggingSelection = true;
s.originalSelection = s.selection;
s.originalLogicalSelection = s.logicalSelection;
s.appliedSelectionMoveDx = 0;
s.appliedSelectionMoveDy = 0;
s.dragStart = p;
SetCapture(s.hwnd);
return 0;
}
if (pointInRect(s.selection, p)) {
s.beginAnnotation(p);
return 0;
}
// Drag mode + click outside the crop starts a new crop. Global
// annotations are deliberately preserved.
const RECT abandonedSelection = s.selection;
const RECT abandonedUi = s.uiBounds();
const RECT abandonedBadge = s.dimensionBadgeRect;
const RECT abandonedPreview = s.brushPreviewRect;
s.annotations.clear();
s.hasSelection = false;
s.selecting = true;
s.dragStart = p;
@@ -3171,6 +3187,7 @@ LRESULT OverlayWindow::handleMessage(UINT message, WPARAM wParam, LPARAM lParam)
s.moveSelectionTo(p);
} else if (s.drawing) {
s.updateAnnotation(p);
s.updateBrushPreview(p);
} else {
if (!s.hasSelection) {
const int nextMonitor = s.monitorIndexForPoint(p);
@@ -3220,8 +3237,6 @@ LRESULT OverlayWindow::handleMessage(UINT message, WPARAM wParam, LPARAM lParam)
s.invalidateUi();
} else if (s.draggingSelection) {
s.draggingSelection = false;
s.appliedSelectionMoveDx = 0;
s.appliedSelectionMoveDy = 0;
ReleaseCapture();
s.updateLayout();
s.brushPreviewRect = s.brushPreviewBounds(p);
@@ -3272,7 +3287,7 @@ LRESULT OverlayWindow::handleMessage(UINT message, WPARAM wParam, LPARAM lParam)
ScreenToClient(s.hwnd, &point);
s.cursorPoint = point;
if (!s.paletteOpen && !s.eyedropperActive && s.hasSelection &&
pointInRect(s.selection, point) && Impl::isDrawingTool(s.tool)) {
Impl::isDrawingTool(s.tool)) {
s.adjustCurrentStrokeWidth(GET_WHEEL_DELTA_WPARAM(wParam));
s.updateCursor(point);
return 0;