|
|
@@ -11,6 +11,7 @@ using System.Windows.Data;
|
|
|
using System.Windows.Documents;
|
|
|
using System.Windows.Input;
|
|
|
using System.Windows.Media;
|
|
|
+using System.Windows.Media.Animation;
|
|
|
using System.Windows.Media.Imaging;
|
|
|
using System.Windows.Navigation;
|
|
|
using System.Windows.Shapes;
|
|
|
@@ -302,25 +303,196 @@ namespace TeamAAS_VP.DxfModule
|
|
|
|
|
|
private void BtnZoomIn_Click(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
- Zoom(_zoomFactor);
|
|
|
+ // Smooth zoom centered at viewport center
|
|
|
+ var sv = ScrollViewer;
|
|
|
+ if (sv != null)
|
|
|
+ {
|
|
|
+ double prevScale = CanvasScaleTransform.ScaleX;
|
|
|
+ double target = prevScale * _zoomFactor;
|
|
|
+ target = Math.Max(0.05, Math.Min(target, 50.0));
|
|
|
+ AnimateScale(target, null);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Zoom(_zoomFactor);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private void BtnZoomOut_Click(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
- Zoom(1.0 / _zoomFactor);
|
|
|
+ var sv = ScrollViewer;
|
|
|
+ if (sv != null)
|
|
|
+ {
|
|
|
+ double prevScale = CanvasScaleTransform.ScaleX;
|
|
|
+ double target = prevScale / _zoomFactor;
|
|
|
+ target = Math.Max(0.05, Math.Min(target, 50.0));
|
|
|
+ AnimateScale(target, null);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Zoom(1.0 / _zoomFactor);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private void BtnFitToScreen_Click(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
- CanvasScaleTransform.ScaleX = 1.0;
|
|
|
- CanvasScaleTransform.ScaleY = 1.0;
|
|
|
- UpdateZoomDisplay();
|
|
|
+ var sv = ScrollViewer;
|
|
|
+ if (sv == null)
|
|
|
+ {
|
|
|
+ CanvasScaleTransform.ScaleX = 1.0;
|
|
|
+ CanvasScaleTransform.ScaleY = 1.0;
|
|
|
+ UpdateZoomDisplay();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Compute scale to fit the canvas into viewport with some margin
|
|
|
+ double marginFactor = 0.95;
|
|
|
+ double targetScaleX = (sv.ViewportWidth * marginFactor) / DrawingCanvas.Width;
|
|
|
+ double targetScaleY = (sv.ViewportHeight * marginFactor) / DrawingCanvas.Height;
|
|
|
+ double target = Math.Min(targetScaleX, targetScaleY);
|
|
|
+ if (double.IsNaN(target) || target <= 0)
|
|
|
+ target = 1.0;
|
|
|
+
|
|
|
+ target = Math.Max(0.05, Math.Min(target, 50.0));
|
|
|
+
|
|
|
+ // Animate to target and center
|
|
|
+ AnimateScale(target, null);
|
|
|
}
|
|
|
|
|
|
private void DrawingCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
|
|
|
{
|
|
|
+ // Zoom centered at current mouse position inside the ScrollViewer viewport
|
|
|
+ var sv = ScrollViewer;
|
|
|
+ if (sv == null) // fallback to previous behavior
|
|
|
+ {
|
|
|
+ double zoom1 = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor;
|
|
|
+ Zoom(zoom1);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mouse position relative to the ScrollViewer (viewport)
|
|
|
+ System.Windows.Point mousePosInViewport = e.GetPosition(sv);
|
|
|
+ // Mouse position relative to the content (unscaled content coordinates)
|
|
|
+ System.Windows.Point mousePosInContent = e.GetPosition(DrawingCanvas);
|
|
|
+
|
|
|
+ double prevScale = CanvasScaleTransform.ScaleX;
|
|
|
double zoom = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor;
|
|
|
- Zoom(zoom);
|
|
|
+ double newScale = prevScale * zoom;
|
|
|
+
|
|
|
+ // Clamp scale to reasonable range
|
|
|
+ newScale = Math.Max(0.05, Math.Min(newScale, 50.0));
|
|
|
+
|
|
|
+ // Absolute position of the content point after scaling
|
|
|
+ double absX = mousePosInContent.X * newScale;
|
|
|
+ double absY = mousePosInContent.Y * newScale;
|
|
|
+
|
|
|
+ // Apply scale
|
|
|
+ CanvasScaleTransform.ScaleX = newScale;
|
|
|
+ CanvasScaleTransform.ScaleY = newScale;
|
|
|
+
|
|
|
+ // Ensure layout updated so ScrollViewer extents are refreshed
|
|
|
+ sv.UpdateLayout();
|
|
|
+
|
|
|
+ // Calculate target offsets so that the content point stays under the mouse cursor
|
|
|
+ double targetOffsetX = absX - mousePosInViewport.X;
|
|
|
+ double targetOffsetY = absY - mousePosInViewport.Y;
|
|
|
+
|
|
|
+ // Clamp offsets to valid scrollable range
|
|
|
+ double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
|
|
|
+ double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
|
|
|
+
|
|
|
+ targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX));
|
|
|
+ targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY));
|
|
|
+
|
|
|
+ sv.ScrollToHorizontalOffset(targetOffsetX);
|
|
|
+ sv.ScrollToVerticalOffset(targetOffsetY);
|
|
|
+
|
|
|
+ UpdateZoomDisplay();
|
|
|
+ e.Handled = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Animate scale transform to targetScale. If anchorInContent is null, use viewport center as anchor.
|
|
|
+ /// anchorInContent, when provided, is in unscaled canvas coordinates (content space).
|
|
|
+ /// </summary>
|
|
|
+ private void AnimateScale(double targetScale, System.Windows.Point? anchorInContent)
|
|
|
+ {
|
|
|
+ var sv = ScrollViewer;
|
|
|
+ if (sv == null)
|
|
|
+ {
|
|
|
+ CanvasScaleTransform.ScaleX = targetScale;
|
|
|
+ CanvasScaleTransform.ScaleY = targetScale;
|
|
|
+ UpdateZoomDisplay();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ double prevScale = CanvasScaleTransform.ScaleX;
|
|
|
+ if (Math.Abs(prevScale - targetScale) < 1e-6)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Determine anchor in content space and its position in viewport
|
|
|
+ System.Windows.Point anchorContent;
|
|
|
+ System.Windows.Point anchorViewport;
|
|
|
+
|
|
|
+ if (anchorInContent.HasValue)
|
|
|
+ {
|
|
|
+ anchorContent = anchorInContent.Value;
|
|
|
+ anchorViewport = new System.Windows.Point(anchorContent.X * prevScale - sv.HorizontalOffset, anchorContent.Y * prevScale - sv.VerticalOffset);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // use viewport center
|
|
|
+ anchorViewport = new System.Windows.Point(sv.ViewportWidth / 2.0, sv.ViewportHeight / 2.0);
|
|
|
+ anchorContent = new System.Windows.Point((sv.HorizontalOffset + anchorViewport.X) / prevScale, (sv.VerticalOffset + anchorViewport.Y) / prevScale);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculate target offsets so that the anchor stays in the same viewport position after scaling
|
|
|
+ double targetOffsetX = anchorContent.X * targetScale - anchorViewport.X;
|
|
|
+ double targetOffsetY = anchorContent.Y * targetScale - anchorViewport.Y;
|
|
|
+
|
|
|
+ double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
|
|
|
+ double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
|
|
|
+
|
|
|
+ targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX));
|
|
|
+ targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY));
|
|
|
+
|
|
|
+ var duration = TimeSpan.FromMilliseconds(200);
|
|
|
+
|
|
|
+ var animX = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() };
|
|
|
+ var animY = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() };
|
|
|
+
|
|
|
+ int completed = 0;
|
|
|
+ EventHandler whenDone = (s, e) =>
|
|
|
+ {
|
|
|
+ completed++;
|
|
|
+ if (completed >= 2)
|
|
|
+ {
|
|
|
+ // Ensure final values
|
|
|
+ CanvasScaleTransform.ScaleX = targetScale;
|
|
|
+ CanvasScaleTransform.ScaleY = targetScale;
|
|
|
+
|
|
|
+ // Update layout so extents reflect final scale
|
|
|
+ sv.UpdateLayout();
|
|
|
+
|
|
|
+ // Recalculate clamped offsets based on final extents
|
|
|
+ double maxX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
|
|
|
+ double maxY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
|
|
|
+
|
|
|
+ double finalOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxX));
|
|
|
+ double finalOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxY));
|
|
|
+
|
|
|
+ sv.ScrollToHorizontalOffset(finalOffsetX);
|
|
|
+ sv.ScrollToVerticalOffset(finalOffsetY);
|
|
|
+
|
|
|
+ UpdateZoomDisplay();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ animX.Completed += whenDone;
|
|
|
+ animY.Completed += whenDone;
|
|
|
+
|
|
|
+ CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, animX);
|
|
|
+ CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, animY);
|
|
|
}
|
|
|
|
|
|
private void Zoom(double factor)
|