"Edit cell boxes" mode: the selected cell grows resize handles on the preview -- drag to move/resize with live outline tracking and a re-render on drop, or type exact X/Y/W/H in the new geometry fields. Plain clicks still select and cycle stacked cells (click-vs-drag resolved by a movement threshold on mouse-up). The move/resize math is pure and unit-tested (OverlayRegionEdit: corner/edge/move handle hit-testing with tolerance, min-size clamped resizing that pins the opposite edge). "Save template" persists the shared cell geometry back to the regions.json; "Reload template" re-reads it to discard unsaved box edits. Cursor feedback per handle; template save/reload disabled when no template path was supplied. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
98 lines
3.6 KiB
C#
98 lines
3.6 KiB
C#
namespace RioJoy.Core.Overlay;
|
|
|
|
/// <summary>Which part of a region's box a drag grabs.</summary>
|
|
public enum OverlayBoxHandle
|
|
{
|
|
None,
|
|
Move,
|
|
North, South, East, West,
|
|
NorthWest, NorthEast, SouthWest, SouthEast,
|
|
}
|
|
|
|
/// <summary>
|
|
/// The pure math behind the wallpaper maker's box editor: which handle a
|
|
/// template-space point grabs on a region, and the region's new rectangle after
|
|
/// dragging that handle. Kept UI-free so it is unit-testable; the canvas only
|
|
/// converts mouse pixels to template space and applies the result.
|
|
/// </summary>
|
|
public static class OverlayRegionEdit
|
|
{
|
|
/// <summary>
|
|
/// The handle at a template-space point: corners win over edges, edges over
|
|
/// <see cref="OverlayBoxHandle.Move"/> (anywhere else inside the box), all
|
|
/// within <paramref name="tolerance"/> template pixels of the border.
|
|
/// </summary>
|
|
public static OverlayBoxHandle HitHandle(OverlayRegion region, double x, double y, double tolerance)
|
|
{
|
|
if (region is null) throw new ArgumentNullException(nameof(region));
|
|
|
|
double l = region.X, t = region.Y, r = region.X + region.Width, b = region.Y + region.Height;
|
|
if (x < l - tolerance || x > r + tolerance || y < t - tolerance || y > b + tolerance)
|
|
return OverlayBoxHandle.None;
|
|
|
|
bool west = Math.Abs(x - l) <= tolerance;
|
|
bool east = Math.Abs(x - r) <= tolerance;
|
|
bool north = Math.Abs(y - t) <= tolerance;
|
|
bool south = Math.Abs(y - b) <= tolerance;
|
|
|
|
if (north && west) return OverlayBoxHandle.NorthWest;
|
|
if (north && east) return OverlayBoxHandle.NorthEast;
|
|
if (south && west) return OverlayBoxHandle.SouthWest;
|
|
if (south && east) return OverlayBoxHandle.SouthEast;
|
|
if (north) return OverlayBoxHandle.North;
|
|
if (south) return OverlayBoxHandle.South;
|
|
if (west) return OverlayBoxHandle.West;
|
|
if (east) return OverlayBoxHandle.East;
|
|
|
|
return x >= l && x < r && y >= t && y < b ? OverlayBoxHandle.Move : OverlayBoxHandle.None;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The rectangle after dragging <paramref name="handle"/> by (dx, dy) template
|
|
/// pixels from the drag-start rectangle. Resizes clamp so width/height never
|
|
/// drop below <paramref name="minSize"/> (the opposite edge stays put).
|
|
/// </summary>
|
|
public static (double X, double Y, double Width, double Height) Apply(
|
|
double x, double y, double width, double height,
|
|
OverlayBoxHandle handle, double dx, double dy, double minSize = 4)
|
|
{
|
|
double l = x, t = y, r = x + width, b = y + height;
|
|
|
|
switch (handle)
|
|
{
|
|
case OverlayBoxHandle.Move:
|
|
return (x + dx, y + dy, width, height);
|
|
case OverlayBoxHandle.West:
|
|
case OverlayBoxHandle.NorthWest:
|
|
case OverlayBoxHandle.SouthWest:
|
|
l = Math.Min(l + dx, r - minSize);
|
|
break;
|
|
}
|
|
|
|
switch (handle)
|
|
{
|
|
case OverlayBoxHandle.East:
|
|
case OverlayBoxHandle.NorthEast:
|
|
case OverlayBoxHandle.SouthEast:
|
|
r = Math.Max(r + dx, l + minSize);
|
|
break;
|
|
}
|
|
|
|
switch (handle)
|
|
{
|
|
case OverlayBoxHandle.North:
|
|
case OverlayBoxHandle.NorthWest:
|
|
case OverlayBoxHandle.NorthEast:
|
|
t = Math.Min(t + dy, b - minSize);
|
|
break;
|
|
case OverlayBoxHandle.South:
|
|
case OverlayBoxHandle.SouthWest:
|
|
case OverlayBoxHandle.SouthEast:
|
|
b = Math.Max(b + dy, t + minSize);
|
|
break;
|
|
}
|
|
|
|
return (l, t, r - l, b - t);
|
|
}
|
|
}
|