Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MagickCrop-Package/MagickCrop-Package.wapproj
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.2270" PrivateAssets="all" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.2526" PrivateAssets="all" />
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
</ItemGroup>
<ItemGroup>
Expand Down
29 changes: 28 additions & 1 deletion MagickCrop/Controls/AngleMeasurementControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,33 @@ public AngleMeasurementControl()
UpdatePositions();
}

public bool IsDragGizmoVisible
{
get => Point1.Visibility == Visibility.Visible;
set
{
Visibility visibility = value ? Visibility.Visible : Visibility.Collapsed;
Point1.Visibility = visibility;
VertexPoint.Visibility = visibility;
Point3.Visibility = visibility;
}
}

public bool IsEndpointCapVisible
{
set
{
double size = value ? 6 : 12;
Point1.Width = size;
Point1.Height = size;
VertexPoint.Width = size;
VertexPoint.Height = size;
Point3.Width = size;
Point3.Height = size;
UpdatePositions();
}
}

public void InitializePositions(double canvasWidth, double canvasHeight)
{
// Place points at reasonable starting positions
Expand Down Expand Up @@ -72,7 +99,7 @@ private void UpdatePositions()
double angle = CalculateAngle();
UpdateAngleArc();

AngleTextBlock.Text = $"{angle:F1}°";
AngleTextBlock.Text = $"{angle:F1}\u00B0";

// Position the measurement text near the vertex
Canvas.SetLeft(MeasurementText, vertexPosition.X + 15);
Expand Down
26 changes: 25 additions & 1 deletion MagickCrop/Controls/CircleMeasurementControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@ public CircleMeasurementControl()
UpdatePositions();
}

public bool IsDragGizmoVisible
{
get => CenterPoint.Visibility == Visibility.Visible;
set
{
Visibility visibility = value ? Visibility.Visible : Visibility.Collapsed;
CenterPoint.Visibility = visibility;
EdgePoint.Visibility = visibility;
}
}

public bool IsEndpointCapVisible
{
set
{
double size = value ? 6 : 12;
CenterPoint.Width = size;
CenterPoint.Height = size;
EdgePoint.Width = size;
EdgePoint.Height = size;
UpdatePositions();
}
}

public void InitializePositions(double canvasWidth, double canvasHeight)
{
center = new Point(canvasWidth * 0.5, canvasHeight * 0.5);
Expand Down Expand Up @@ -106,7 +130,7 @@ private void UpdateMeasurementText()
double scaledCircumference = circumference * ScaleFactor;
double scaledArea = area * ScaleFactor * ScaleFactor; // Area scales by factor squared

CircleTextBlock.Text = $"r: {scaledRadius:N2} {Units}, C: {scaledCircumference:N2} {Units}, A: {scaledArea:N2} {Units}²";
CircleTextBlock.Text = $"r: {scaledRadius:N2} {Units}, C: {scaledCircumference:N2} {Units}, A: {scaledArea:N2} {Units}\u00B2";
}

private void MeasurementPoint_MouseDown(object sender, MouseButtonEventArgs e)
Expand Down
52 changes: 52 additions & 0 deletions MagickCrop/Controls/CornerNavButton.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace MagickCrop.Controls;

/// <summary>
/// A small circular button placed beside a transform handle that jumps the viewport to the
/// neighbouring handle in the polygon. Derives from <see cref="Button"/> so it can be styled
/// implicitly and identified during hit testing.
/// </summary>
public class CornerNavButton : Button
{
private readonly RotateTransform arrowRotation = new();

public CornerNavButton()
{
Path arrow = new()
{
Data = Geometry.Parse("M 0,0 L 6,4.5 L 0,9 Z"),
Fill = Brushes.White,
Stretch = Stretch.None,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
RenderTransformOrigin = new Point(0.5, 0.5),
RenderTransform = arrowRotation,
IsHitTestVisible = false,
};

Content = arrow;
}

/// <summary>
/// The handle this button navigates to.
/// </summary>
public Ellipse? Target { get; set; }

/// <summary>
/// The handle this button is anchored beside.
/// </summary>
public Ellipse? Anchor { get; set; }

/// <summary>
/// Rotates the arrow glyph so it points at the target handle.
/// </summary>
public double ArrowAngle
{
get => arrowRotation.Angle;
set => arrowRotation.Angle = value;
}
}
24 changes: 24 additions & 0 deletions MagickCrop/Controls/DistanceMeasurementControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@ public DistanceMeasurementControl()
UpdatePositions();
}

public bool IsDragGizmoVisible
{
get => StartPoint.Visibility == Visibility.Visible;
set
{
Visibility visibility = value ? Visibility.Visible : Visibility.Collapsed;
StartPoint.Visibility = visibility;
EndPoint.Visibility = visibility;
}
}

public bool IsEndpointCapVisible
{
set
{
double size = value ? 6 : 12;
StartPoint.Width = size;
StartPoint.Height = size;
EndPoint.Width = size;
EndPoint.Height = size;
UpdatePositions();
}
}

public void InitializePositions(double canvasWidth, double canvasHeight)
{
// Place points at reasonable starting positions
Expand Down
11 changes: 11 additions & 0 deletions MagickCrop/Controls/MarkupShapeControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ public double StrokeThickness
}
}

public bool IsDragGizmoVisible
{
get => Point1Handle.Visibility == Visibility.Visible;
set
{
Visibility visibility = value ? Visibility.Visible : Visibility.Collapsed;
Point1Handle.Visibility = visibility;
Point2Handle.Visibility = visibility;
}
}

public MarkupShapeControl()
{
InitializeComponent();
Expand Down
49 changes: 49 additions & 0 deletions MagickCrop/Controls/MiniMap.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<UserControl
x:Class="MagickCrop.Controls.MiniMap"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
mc:Ignorable="d">
<Border
Padding="6"
Background="#F01E2026"
BorderBrush="#806A6D78"
BorderThickness="1"
CornerRadius="8">
<Border.Effect>
<DropShadowEffect
BlurRadius="12"
Opacity="0.35"
ShadowDepth="2" />
</Border.Effect>
<Grid
x:Name="MapHost"
Width="160"
Height="120"
ClipToBounds="True"
Cursor="SizeAll"
LostMouseCapture="MapHost_LostMouseCapture"
MouseLeftButtonDown="MapHost_MouseLeftButtonDown"
MouseLeftButtonUp="MapHost_MouseLeftButtonUp"
MouseMove="MapHost_MouseMove"
ToolTip="Drag to pan the canvas">
<Rectangle Fill="#FF2A2D33" />
<Image x:Name="MiniImage" Stretch="Fill" />
<Path
x:Name="OutsideDim"
Fill="#73000000"
IsHitTestVisible="False" />
<Rectangle
x:Name="ViewportRectangle"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsHitTestVisible="False"
Stroke="#0066FF"
StrokeThickness="1.5" />
<Border BorderBrush="#59FFFFFF" BorderThickness="1" />
</Grid>
</Border>
</UserControl>
126 changes: 126 additions & 0 deletions MagickCrop/Controls/MiniMap.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace MagickCrop.Controls;

/// <summary>
/// Shows a thumbnail of the current image with a rectangle indicating which part of it
/// is currently visible in the canvas viewport. Dragging inside the map pans the canvas.
/// </summary>
public partial class MiniMap : UserControl
{
private const double MaxMapWidth = 168;
private const double MaxMapHeight = 140;

/// <summary>
/// Raised with a point in canvas coordinates that should be centered in the viewport.
/// </summary>
public event EventHandler<Point>? ViewportCenterRequested;

private double mapScale = 1;
private bool isDragging;

public MiniMap()
{
InitializeComponent();
}

/// <summary>
/// Updates the map contents.
/// </summary>
/// <param name="source">The image currently shown on the canvas.</param>
/// <param name="imageCanvasSize">The size of the image in canvas coordinates.</param>
/// <param name="viewportInCanvas">The visible canvas region in canvas coordinates.</param>
/// <returns><see langword="true"/> when the map has valid content to display.</returns>
public bool UpdateMap(ImageSource? source, Size imageCanvasSize, Rect viewportInCanvas)
{
if (source is null || imageCanvasSize.Width <= 0 || imageCanvasSize.Height <= 0)
{
MiniImage.Source = null;
return false;
}

if (!ReferenceEquals(MiniImage.Source, source))
MiniImage.Source = source;

mapScale = Math.Min(MaxMapWidth / imageCanvasSize.Width, MaxMapHeight / imageCanvasSize.Height);

double mapWidth = Math.Max(1, imageCanvasSize.Width * mapScale);
double mapHeight = Math.Max(1, imageCanvasSize.Height * mapScale);
MapHost.Width = mapWidth;
MapHost.Height = mapHeight;

double left = Math.Clamp(viewportInCanvas.Left * mapScale, 0, mapWidth);
double top = Math.Clamp(viewportInCanvas.Top * mapScale, 0, mapHeight);
double right = Math.Clamp(viewportInCanvas.Right * mapScale, 0, mapWidth);
double bottom = Math.Clamp(viewportInCanvas.Bottom * mapScale, 0, mapHeight);

Rect viewportOnMap = new(left, top, Math.Max(0, right - left), Math.Max(0, bottom - top));

ViewportRectangle.Margin = new Thickness(viewportOnMap.X, viewportOnMap.Y, 0, 0);
ViewportRectangle.Width = viewportOnMap.Width;
ViewportRectangle.Height = viewportOnMap.Height;

OutsideDim.Data = new CombinedGeometry(
GeometryCombineMode.Exclude,
new RectangleGeometry(new Rect(0, 0, mapWidth, mapHeight)),
new RectangleGeometry(viewportOnMap));

return true;
}

private void RequestCenter(Point mapPoint)
{
if (mapScale <= 0)
return;

ViewportCenterRequested?.Invoke(this, new Point(mapPoint.X / mapScale, mapPoint.Y / mapScale));
}

private void MapHost_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isDragging = true;
MapHost.CaptureMouse();
RequestCenter(e.GetPosition(MapHost));
e.Handled = true;
}

private void MapHost_MouseMove(object sender, MouseEventArgs e)
{
if (!isDragging)
return;

if (e.LeftButton != MouseButtonState.Pressed)
{
EndDrag();
return;
}

RequestCenter(e.GetPosition(MapHost));
e.Handled = true;
}

private void MapHost_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!isDragging)
return;

EndDrag();
e.Handled = true;
}

private void MapHost_LostMouseCapture(object sender, MouseEventArgs e)
{
isDragging = false;
}

private void EndDrag()
{
isDragging = false;

if (MapHost.IsMouseCaptured)
MapHost.ReleaseMouseCapture();
}
}
Loading
Loading