update ruller
This commit is contained in:
parent
93097412b0
commit
3b44ea6d8d
|
|
@ -68,8 +68,8 @@
|
||||||
private double MouseX;
|
private double MouseX;
|
||||||
private double MouseY;
|
private double MouseY;
|
||||||
|
|
||||||
private double OriginX = 0;
|
private double OriginX = 0;
|
||||||
private double OriginY = 0;
|
private double OriginY = 0;
|
||||||
|
|
||||||
private double WorldMouseX;
|
private double WorldMouseX;
|
||||||
private double WorldMouseY;
|
private double WorldMouseY;
|
||||||
|
|
@ -80,6 +80,8 @@
|
||||||
private double CanvasTranslateX = 0;
|
private double CanvasTranslateX = 0;
|
||||||
private double CanvasTranslateY = 0;
|
private double CanvasTranslateY = 0;
|
||||||
|
|
||||||
|
private const double RulerHeight = 20;
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool first_render)
|
protected override async Task OnAfterRenderAsync(bool first_render)
|
||||||
{
|
{
|
||||||
await base.OnAfterRenderAsync(first_render);
|
await base.OnAfterRenderAsync(first_render);
|
||||||
|
|
@ -103,6 +105,10 @@
|
||||||
await using var ctx = await JS.GetContext2DAsync(CanvasRef);
|
await using var ctx = await JS.GetContext2DAsync(CanvasRef);
|
||||||
|
|
||||||
await ctx.ClearRectAsync(0, 0, CanvasWidth, CanvasHeight);
|
await ctx.ClearRectAsync(0, 0, CanvasWidth, CanvasHeight);
|
||||||
|
|
||||||
|
// Draw rulers first (outside transform)
|
||||||
|
await DrawRulers(ctx);
|
||||||
|
|
||||||
await ctx.SaveAsync();
|
await ctx.SaveAsync();
|
||||||
|
|
||||||
await ctx.TranslateAsync(CanvasTranslateX, CanvasTranslateY);
|
await ctx.TranslateAsync(CanvasTranslateX, CanvasTranslateY);
|
||||||
|
|
@ -114,6 +120,164 @@
|
||||||
await ctx.RestoreAsync();
|
await ctx.RestoreAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task DrawRulers(Context2D ctx)
|
||||||
|
{
|
||||||
|
double visibleWorldLeft = CanvasToWorldX(0);
|
||||||
|
double visibleWorldRight = CanvasToWorldX(CanvasWidth);
|
||||||
|
double visibleWorldTop = CanvasToWorldY(0);
|
||||||
|
double visibleWorldBottom = CanvasToWorldY(CanvasHeight);
|
||||||
|
|
||||||
|
double scaleInterval = GetRulerScaleInterval();
|
||||||
|
|
||||||
|
await DrawXRuler(ctx, RulerHeight, visibleWorldLeft, visibleWorldRight, scaleInterval);
|
||||||
|
await DrawYRuler(ctx, RulerHeight, visibleWorldTop, visibleWorldBottom, scaleInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetRulerScaleInterval()
|
||||||
|
{
|
||||||
|
double pixelsPerMeter = BASE_PIXELS_PER_METER * ZoomScale;
|
||||||
|
|
||||||
|
if (pixelsPerMeter >= 400) return 0.1;
|
||||||
|
else if (pixelsPerMeter >= 200) return 0.2;
|
||||||
|
else if (pixelsPerMeter >= 100) return 0.5;
|
||||||
|
else if (pixelsPerMeter >= 50) return 1.0;
|
||||||
|
else if (pixelsPerMeter >= 25) return 2.0;
|
||||||
|
else if (pixelsPerMeter >= 12) return 5.0;
|
||||||
|
else if (pixelsPerMeter >= 6) return 10.0;
|
||||||
|
else return 20.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DrawXRuler(Context2D ctx, double rulerHeight, double visibleWorldLeft, double visibleWorldRight, double scaleInterval)
|
||||||
|
{
|
||||||
|
await ctx.FillStyleAsync("rgba(240, 240, 240, 0.9)");
|
||||||
|
await ctx.FillRectAsync(0, 0, CanvasWidth, rulerHeight);
|
||||||
|
|
||||||
|
await ctx.StrokeStyleAsync("rgba(100, 100, 100, 0.8)");
|
||||||
|
await ctx.LineWidthAsync(1);
|
||||||
|
await ctx.BeginPathAsync();
|
||||||
|
await ctx.MoveToAsync(0, rulerHeight);
|
||||||
|
await ctx.LineToAsync(CanvasWidth, rulerHeight);
|
||||||
|
await ctx.StrokeAsync();
|
||||||
|
|
||||||
|
double startWorld = Math.Floor(visibleWorldLeft / scaleInterval) * scaleInterval;
|
||||||
|
double endWorld = Math.Ceiling(visibleWorldRight / scaleInterval) * scaleInterval;
|
||||||
|
|
||||||
|
startWorld -= scaleInterval;
|
||||||
|
endWorld += scaleInterval;
|
||||||
|
|
||||||
|
for (double worldX = startWorld; worldX <= endWorld; worldX += scaleInterval)
|
||||||
|
{
|
||||||
|
double canvasX = WorldToCanvasX(worldX);
|
||||||
|
|
||||||
|
if (canvasX < -50 || canvasX > CanvasWidth + 50) continue;
|
||||||
|
|
||||||
|
bool isMajorTick = IsNearMultiple(worldX, scaleInterval * 2) || Math.Abs(worldX) < 0.001;
|
||||||
|
double tickHeight = isMajorTick ? rulerHeight * 0.4 : rulerHeight * 0.2;
|
||||||
|
|
||||||
|
await ctx.StrokeStyleAsync("rgba(60, 60, 60, 0.8)");
|
||||||
|
await ctx.LineWidthAsync(1);
|
||||||
|
await ctx.BeginPathAsync();
|
||||||
|
await ctx.MoveToAsync(canvasX, rulerHeight);
|
||||||
|
await ctx.LineToAsync(canvasX, rulerHeight - tickHeight);
|
||||||
|
await ctx.StrokeAsync();
|
||||||
|
|
||||||
|
if (isMajorTick && canvasX >= -20 && canvasX <= CanvasWidth + 20)
|
||||||
|
{
|
||||||
|
await ctx.SaveAsync();
|
||||||
|
|
||||||
|
await ctx.TranslateAsync(canvasX, rulerHeight - tickHeight - 8);
|
||||||
|
await ctx.ScaleAsync(1, -1);
|
||||||
|
|
||||||
|
await ctx.FillStyleAsync("blue");
|
||||||
|
await ctx.FontAsync("bold 10px Arial");
|
||||||
|
await ctx.TextAlignAsync(TextAlign.Center);
|
||||||
|
|
||||||
|
string labelText = FormatRulerLabel(worldX, scaleInterval);
|
||||||
|
await ctx.FillTextAsync(labelText, 0, 0);
|
||||||
|
|
||||||
|
await ctx.RestoreAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DrawYRuler(Context2D ctx, double rulerWidth, double visibleWorldTop, double visibleWorldBottom, double scaleInterval)
|
||||||
|
{
|
||||||
|
await ctx.FillStyleAsync("rgba(240, 240, 240, 0.9)");
|
||||||
|
await ctx.FillRectAsync(0, 0, rulerWidth, CanvasHeight);
|
||||||
|
|
||||||
|
await ctx.StrokeStyleAsync("rgba(100, 100, 100, 0.8)");
|
||||||
|
await ctx.LineWidthAsync(1);
|
||||||
|
await ctx.BeginPathAsync();
|
||||||
|
await ctx.MoveToAsync(rulerWidth, 0);
|
||||||
|
await ctx.LineToAsync(rulerWidth, CanvasHeight);
|
||||||
|
await ctx.StrokeAsync();
|
||||||
|
|
||||||
|
double startWorld = Math.Floor(visibleWorldTop / scaleInterval) * scaleInterval;
|
||||||
|
double endWorld = Math.Ceiling(visibleWorldBottom / scaleInterval) * scaleInterval;
|
||||||
|
|
||||||
|
startWorld -= scaleInterval;
|
||||||
|
endWorld += scaleInterval;
|
||||||
|
|
||||||
|
for (double worldY = startWorld; worldY <= endWorld; worldY += scaleInterval)
|
||||||
|
{
|
||||||
|
double canvasY = WorldToCanvasY(worldY);
|
||||||
|
|
||||||
|
if (canvasY < -50 || canvasY > CanvasHeight + 50) continue;
|
||||||
|
|
||||||
|
bool isMajorTick = IsNearMultiple(worldY, scaleInterval * 2) || Math.Abs(worldY) < 0.001;
|
||||||
|
double tickWidth = isMajorTick ? rulerWidth * 0.4 : rulerWidth * 0.2;
|
||||||
|
|
||||||
|
await ctx.StrokeStyleAsync("rgba(60, 60, 60, 0.8)");
|
||||||
|
await ctx.LineWidthAsync(1);
|
||||||
|
await ctx.BeginPathAsync();
|
||||||
|
await ctx.MoveToAsync(rulerWidth, canvasY);
|
||||||
|
await ctx.LineToAsync(rulerWidth - tickWidth, canvasY);
|
||||||
|
await ctx.StrokeAsync();
|
||||||
|
|
||||||
|
if (isMajorTick && canvasY >= -20 && canvasY <= CanvasHeight + 20)
|
||||||
|
{
|
||||||
|
await ctx.SaveAsync();
|
||||||
|
|
||||||
|
await ctx.TranslateAsync(rulerWidth - tickWidth - 2, canvasY);
|
||||||
|
await ctx.ScaleAsync(1, -1);
|
||||||
|
await ctx.RotateAsync(-Math.PI / 2);
|
||||||
|
|
||||||
|
await ctx.FillStyleAsync("blue");
|
||||||
|
await ctx.FontAsync("bold 10px Arial");
|
||||||
|
await ctx.TextAlignAsync(TextAlign.Center);
|
||||||
|
|
||||||
|
string labelText = FormatRulerLabel(worldY, scaleInterval);
|
||||||
|
await ctx.FillTextAsync(labelText, 0, 0);
|
||||||
|
|
||||||
|
await ctx.RestoreAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNearMultiple(double value, double multiple)
|
||||||
|
{
|
||||||
|
if (multiple == 0) return false;
|
||||||
|
double remainder = Math.Abs(value % multiple);
|
||||||
|
double epsilon = multiple * 0.001;
|
||||||
|
return remainder < epsilon || remainder > multiple - epsilon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatRulerLabel(double worldValue, double scaleInterval)
|
||||||
|
{
|
||||||
|
if (scaleInterval < 1.0)
|
||||||
|
{
|
||||||
|
return $"{worldValue:F1}m";
|
||||||
|
}
|
||||||
|
else if (scaleInterval < 10.0)
|
||||||
|
{
|
||||||
|
return $"{worldValue:F0}m";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{worldValue:F0}m";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DrawAxes(Context2D ctx)
|
private async Task DrawAxes(Context2D ctx)
|
||||||
{
|
{
|
||||||
double originCanvasX = OriginX * BASE_PIXELS_PER_METER;
|
double originCanvasX = OriginX * BASE_PIXELS_PER_METER;
|
||||||
|
|
@ -139,18 +303,18 @@
|
||||||
await ctx.StrokeAsync();
|
await ctx.StrokeAsync();
|
||||||
|
|
||||||
double gridSpacingMeters = GetGridSpacingMeters();
|
double gridSpacingMeters = GetGridSpacingMeters();
|
||||||
double arrowLength = gridSpacingMeters * BASE_PIXELS_PER_METER;
|
double arrowLength = gridSpacingMeters * BASE_PIXELS_PER_METER;
|
||||||
double arrowHeadSize = 16 / ZoomScale;
|
double arrowHeadSize = 16 / ZoomScale;
|
||||||
|
|
||||||
await ctx.FillStyleAsync("blue");
|
await ctx.FillStyleAsync("blue");
|
||||||
await ctx.StrokeStyleAsync("blue");
|
await ctx.StrokeStyleAsync("blue");
|
||||||
await ctx.LineWidthAsync(4 / ZoomScale);
|
await ctx.LineWidthAsync(4 / ZoomScale);
|
||||||
|
|
||||||
await ctx.BeginPathAsync();
|
await ctx.BeginPathAsync();
|
||||||
await ctx.MoveToAsync(originCanvasX, originCanvasY);
|
await ctx.MoveToAsync(originCanvasX, originCanvasY);
|
||||||
await ctx.LineToAsync(originCanvasX + arrowLength, originCanvasY);
|
await ctx.LineToAsync(originCanvasX + arrowLength, originCanvasY);
|
||||||
await ctx.StrokeAsync();
|
await ctx.StrokeAsync();
|
||||||
|
|
||||||
await ctx.BeginPathAsync();
|
await ctx.BeginPathAsync();
|
||||||
await ctx.MoveToAsync(originCanvasX + arrowLength, originCanvasY);
|
await ctx.MoveToAsync(originCanvasX + arrowLength, originCanvasY);
|
||||||
await ctx.LineToAsync(originCanvasX + arrowLength - arrowHeadSize, originCanvasY - arrowHeadSize / 2);
|
await ctx.LineToAsync(originCanvasX + arrowLength - arrowHeadSize, originCanvasY - arrowHeadSize / 2);
|
||||||
|
|
@ -161,12 +325,12 @@
|
||||||
await ctx.FillStyleAsync("red");
|
await ctx.FillStyleAsync("red");
|
||||||
await ctx.StrokeStyleAsync("red");
|
await ctx.StrokeStyleAsync("red");
|
||||||
await ctx.LineWidthAsync(4 / ZoomScale);
|
await ctx.LineWidthAsync(4 / ZoomScale);
|
||||||
|
|
||||||
await ctx.BeginPathAsync();
|
await ctx.BeginPathAsync();
|
||||||
await ctx.MoveToAsync(originCanvasX, originCanvasY);
|
await ctx.MoveToAsync(originCanvasX, originCanvasY);
|
||||||
await ctx.LineToAsync(originCanvasX, originCanvasY + arrowLength);
|
await ctx.LineToAsync(originCanvasX, originCanvasY + arrowLength);
|
||||||
await ctx.StrokeAsync();
|
await ctx.StrokeAsync();
|
||||||
|
|
||||||
await ctx.BeginPathAsync();
|
await ctx.BeginPathAsync();
|
||||||
await ctx.MoveToAsync(originCanvasX, originCanvasY + arrowLength);
|
await ctx.MoveToAsync(originCanvasX, originCanvasY + arrowLength);
|
||||||
await ctx.LineToAsync(originCanvasX - arrowHeadSize / 2, originCanvasY + arrowLength - arrowHeadSize);
|
await ctx.LineToAsync(originCanvasX - arrowHeadSize / 2, originCanvasY + arrowLength - arrowHeadSize);
|
||||||
|
|
@ -178,8 +342,8 @@
|
||||||
private async Task DrawGrid(Context2D ctx)
|
private async Task DrawGrid(Context2D ctx)
|
||||||
{
|
{
|
||||||
await ctx.StrokeStyleAsync("rgba(200, 200, 200, 0.4)");
|
await ctx.StrokeStyleAsync("rgba(200, 200, 200, 0.4)");
|
||||||
await ctx.LineWidthAsync(1 / ZoomScale);
|
await ctx.LineWidthAsync(1 / ZoomScale);
|
||||||
await ctx.SetLineDashAsync(new double[] { 5 / ZoomScale, 5 / ZoomScale });
|
await ctx.SetLineDashAsync(new double[] { 5 / ZoomScale, 5 / ZoomScale });
|
||||||
|
|
||||||
double gridSpacingMeters = GetGridSpacingMeters();
|
double gridSpacingMeters = GetGridSpacingMeters();
|
||||||
double gridSpacingPixels = gridSpacingMeters * BASE_PIXELS_PER_METER;
|
double gridSpacingPixels = gridSpacingMeters * BASE_PIXELS_PER_METER;
|
||||||
|
|
@ -213,13 +377,14 @@
|
||||||
|
|
||||||
private double GetGridSpacingMeters()
|
private double GetGridSpacingMeters()
|
||||||
{
|
{
|
||||||
if (BASE_PIXELS_PER_METER * ZoomScale >= 300) return 0.2;
|
double PixelsPerMeter = BASE_PIXELS_PER_METER * ZoomScale;
|
||||||
else if (BASE_PIXELS_PER_METER * ZoomScale >= 150) return 0.5;
|
if (PixelsPerMeter >= 300) return 0.2;
|
||||||
else if (BASE_PIXELS_PER_METER * ZoomScale >= 75) return 1.0;
|
else if (PixelsPerMeter >= 150) return 0.5;
|
||||||
else if (BASE_PIXELS_PER_METER * ZoomScale >= 40) return 2.0;
|
else if (PixelsPerMeter >= 75) return 1.0;
|
||||||
else if (BASE_PIXELS_PER_METER * ZoomScale >= 20) return 5.0;
|
else if (PixelsPerMeter >= 40) return 2.0;
|
||||||
else if (BASE_PIXELS_PER_METER * ZoomScale >= 10) return 10.0;
|
else if (PixelsPerMeter >= 20) return 5.0;
|
||||||
else return 20.0;
|
else if (PixelsPerMeter >= 10) return 10.0;
|
||||||
|
else return 20.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double CanvasToWorldX(double canvasX)
|
private double CanvasToWorldX(double canvasX)
|
||||||
|
|
@ -253,10 +418,10 @@
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
||||||
if (e.Buttons == 4)
|
if (e.Buttons == 4)
|
||||||
{
|
{
|
||||||
CanvasTranslateX += e.MovementX;
|
CanvasTranslateX += e.MovementX;
|
||||||
CanvasTranslateY -= e.MovementY;
|
CanvasTranslateY -= e.MovementY;
|
||||||
await DrawCanvas();
|
await DrawCanvas();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -275,18 +440,15 @@
|
||||||
if (e.Buttons == 4) return;
|
if (e.Buttons == 4) return;
|
||||||
const double zoomFactor = 0.1;
|
const double zoomFactor = 0.1;
|
||||||
double oldZoom = ZoomScale;
|
double oldZoom = ZoomScale;
|
||||||
|
|
||||||
if (e.DeltaY < 0) ZoomScale = Math.Min(MAX_ZOOM, ZoomScale * (1 + zoomFactor));
|
if (e.DeltaY < 0) ZoomScale = Math.Min(MAX_ZOOM, ZoomScale * (1 + zoomFactor));
|
||||||
else ZoomScale = Math.Max(MIN_ZOOM, ZoomScale * (1 - zoomFactor));
|
else ZoomScale = Math.Max(MIN_ZOOM, ZoomScale * (1 - zoomFactor));
|
||||||
|
|
||||||
if (Math.Abs(ZoomScale - oldZoom) < 0.001) return;
|
if (Math.Abs(ZoomScale - oldZoom) < 0.001) return;
|
||||||
|
|
||||||
MouseX = e.OffsetX;
|
MouseX = e.OffsetX;
|
||||||
MouseY = e.OffsetY;
|
MouseY = e.OffsetY;
|
||||||
|
|
||||||
WorldMouseX = CanvasToWorldX(MouseX);
|
|
||||||
WorldMouseY = CanvasToWorldY(MouseY);
|
|
||||||
|
|
||||||
double zoomPointWorldX = (MouseX - CanvasTranslateX) / oldZoom / BASE_PIXELS_PER_METER - OriginX;
|
double zoomPointWorldX = (MouseX - CanvasTranslateX) / oldZoom / BASE_PIXELS_PER_METER - OriginX;
|
||||||
double zoomPointWorldY = (MouseY - CanvasTranslateY) / oldZoom / BASE_PIXELS_PER_METER - OriginY;
|
double zoomPointWorldY = (MouseY - CanvasTranslateY) / oldZoom / BASE_PIXELS_PER_METER - OriginY;
|
||||||
|
|
||||||
|
|
@ -296,6 +458,9 @@
|
||||||
CanvasTranslateX = MouseX - newZoomPointCanvasX;
|
CanvasTranslateX = MouseX - newZoomPointCanvasX;
|
||||||
CanvasTranslateY = MouseY - newZoomPointCanvasY;
|
CanvasTranslateY = MouseY - newZoomPointCanvasY;
|
||||||
|
|
||||||
|
WorldMouseX = CanvasToWorldX(MouseX);
|
||||||
|
WorldMouseY = CanvasToWorldY(MouseY);
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
await DrawCanvas();
|
await DrawCanvas();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user