update mouse indicator

This commit is contained in:
Đăng Nguyễn 2025-10-02 14:16:20 +07:00
parent 3b44ea6d8d
commit 811a5821ba
4 changed files with 126 additions and 187 deletions

View File

@ -18,39 +18,13 @@
<i class="mdi mdi-restore icon-button"></i>
</button>
</MudTooltip>
<div class="ms-auto d-flex align-items-center">
<div class="zoom-info-container">
<small class="zoom-info">
<span class="info-item">
<i class="mdi mdi-magnify"></i>
Zoom: @($"{ZoomScale:F2}x")
</span>
<span class="info-separator">|</span>
<span class="info-item">
<i class="mdi mdi-crosshairs-gps"></i>
Mouse: (@($"{MouseX:F0}"), @($"{MouseY:F0}"))
</span>
<span class="info-separator">|</span>
<span class="info-item">
<i class="mdi mdi-map-marker"></i>
World: (@($"{WorldMouseX:F2}m"), @($"{WorldMouseY:F2}m"))
</span>
<span class="info-separator">|</span>
<span class="info-item">
<i class="mdi mdi-map-marker"></i>
Translate: (@($"{CanvasTranslateX:F2}"), @($"{CanvasTranslateY:F2}"))
</span>
</small>
</div>
</div>
</div>
<div @ref="ViewContainerRef" class="d-flex position-relative w-100 flex-grow-1 overflow-hidden">
<div @ref="ViewContainerRef">
<canvas @ref="CanvasRef"
@onwheel="HandleWheel"
@onwheel:preventDefault="true"
@onmousemove="HandleMouseMove"
@onmouseleave="HandleMouseLeave"
style="display: block; cursor: crosshair; transform: scale(1, -1)"></canvas>
@onmouseleave="HandleMouseLeave"></canvas>
</div>
</div>
@ -106,18 +80,96 @@
await ctx.ClearRectAsync(0, 0, CanvasWidth, CanvasHeight);
// Draw rulers first (outside transform)
await DrawRulers(ctx);
await ctx.SaveAsync();
await ctx.TranslateAsync(CanvasTranslateX, CanvasTranslateY);
await ctx.ScaleAsync(ZoomScale, ZoomScale);
await DrawGrid(ctx);
await DrawAxes(ctx);
await ctx.RestoreAsync();
// Draw mouse indicator (outside transform)
if (IsMouseInCanvas)
{
await DrawMouseIndicator(ctx);
}
}
private async Task DrawMouseIndicator(Context2D ctx)
{
await ctx.SaveAsync();
await ctx.StrokeStyleAsync("rgba(255, 50, 50, 0.8)");
await ctx.LineWidthAsync(1);
await ctx.SetLineDashAsync(new double[] { 3, 3 });
await ctx.BeginPathAsync();
await ctx.MoveToAsync(MouseX, RulerHeight);
await ctx.LineToAsync(MouseX, CanvasHeight);
await ctx.StrokeAsync();
await ctx.BeginPathAsync();
await ctx.MoveToAsync(RulerHeight, MouseY);
await ctx.LineToAsync(CanvasWidth, MouseY);
await ctx.StrokeAsync();
await ctx.SetLineDashAsync(new double[] { });
const double labelPadding = 7;
const double labelMargin = 8;
string coordinateText = $"({WorldMouseX:F2}m, {WorldMouseY:F2}m)";
await ctx.FontAsync("bold 12px Arial");
var textMetrics = await ctx.MeasureTextAsync(coordinateText);
double textWidth = textMetrics.Width;
double textHeight = 16;
double labelX = MouseX + labelMargin;
double labelY = MouseY - textHeight - labelPadding * 2 - labelMargin;
if (labelX + textWidth + labelPadding * 2 > CanvasWidth)
{
labelX = MouseX - textWidth - labelPadding * 2 - labelMargin;
}
if (labelY - textHeight - labelPadding * 2 < RulerHeight)
{
labelY = MouseY + labelMargin;
}
await ctx.FillStyleAsync("rgba(0, 0, 0, 0.8)");
await ctx.FillRectAsync(labelX, labelY, textWidth + labelPadding * 2, textHeight + labelPadding * 2);
await ctx.StrokeStyleAsync("rgba(255, 255, 255, 0.6)");
await ctx.LineWidthAsync(1);
await ctx.StrokeRectAsync(labelX, labelY, textWidth + labelPadding * 2, textHeight + labelPadding * 2);
await ctx.FillStyleAsync("rgba(255, 50, 50, 0.9)");
await ctx.BeginPathAsync();
await ctx.ArcAsync(MouseX, MouseY, 3, 0, Math.PI * 2);
await ctx.FillAsync(FillRule.NonZero);
await ctx.StrokeStyleAsync("rgba(255, 255, 255, 0.8)");
await ctx.LineWidthAsync(2);
await ctx.BeginPathAsync();
await ctx.ArcAsync(MouseX, MouseY, 6, 0, Math.PI * 2);
await ctx.StrokeAsync();
await ctx.SaveAsync();
await ctx.TranslateAsync(labelX + labelPadding + textWidth / 2, labelY + textHeight / 2);
await ctx.ScaleAsync(1, -1);
await ctx.FillStyleAsync("white");
await ctx.FontAsync("bold 12px Arial");
await ctx.TextAlignAsync(TextAlign.Center);
await ctx.TextBaseLineAsync(TextBaseLine.Bottom);
await ctx.FillTextAsync(coordinateText, 0, 0);
await ctx.RestoreAsync();
}
private async Task DrawRulers(Context2D ctx)
@ -264,18 +316,7 @@
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";
}
return scaleInterval < 1.0 ? $"{worldValue:F1}m" : $"{worldValue:F0}m";
}
private async Task DrawAxes(Context2D ctx)
@ -422,8 +463,8 @@
{
CanvasTranslateX += e.MovementX;
CanvasTranslateY -= e.MovementY;
await DrawCanvas();
}
await DrawCanvas();
}
private async Task HandleMouseLeave(MouseEventArgs e)

View File

@ -50,124 +50,21 @@
color: var(--mud-palette-primary);
}
.view canvas {
transition: cursor 0.2s ease;
.view > div {
display: flex;
position: relative;
width: 100%;
flex-grow: 1;
overflow: hidden;
}
.view canvas:hover {
.view > div > canvas {
transition: cursor 0.2s ease;
display: block;
transform: scale(1, -1);
}
.view > div > canvas:hover {
cursor: crosshair;
}
/* Enhanced zoom and coordinate info styling */
.zoom-info-container {
display: flex;
align-items: center;
background-color: var(--mud-palette-background);
border: 1px solid var(--mud-palette-divider);
border-radius: 6px;
padding: 0.5rem 0.75rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.zoom-info {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 0.8rem;
color: var(--mud-palette-text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
.info-item {
display: flex;
align-items: center;
gap: 0.25rem;
font-weight: 500;
}
.info-item i {
font-size: 0.9rem;
color: var(--mud-palette-primary);
}
.info-separator {
color: var(--mud-palette-divider);
font-weight: 300;
margin: 0 0.25rem;
}
/* Responsive design for info container */
@media (max-width: 768px) {
.zoom-info-container {
padding: 0.25rem 0.5rem;
}
.zoom-info {
font-size: 0.7rem;
gap: 0.25rem;
}
.info-item {
gap: 0.15rem;
}
.info-item i {
font-size: 0.8rem;
}
}
/* Very small screens - stack vertically */
@media (max-width: 480px) {
.zoom-info {
flex-direction: column;
gap: 0.15rem;
text-align: center;
}
.info-separator {
display: none;
}
.zoom-info-container {
padding: 0.35rem;
}
}
/* Hover effects for info container */
.zoom-info-container:hover {
background-color: var(--mud-palette-action-hover);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Animation for coordinate updates */
.info-item {
transition: color 0.2s ease;
}
.info-item:hover {
color: var(--mud-palette-primary);
}
/* High contrast mode support */
@media (prefers-contrast: high) {
.zoom-info-container {
border-width: 2px;
background-color: var(--mud-palette-surface);
}
.info-item {
font-weight: 600;
}
}
/* Dark mode adjustments */
@media (prefers-color-scheme: dark) {
.zoom-info-container {
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.zoom-info-container:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.4);
}
}

View File

@ -1,10 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace RobotApp.Controllers;
namespace RobotApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class ImagesController : ControllerBase
{
}
}

Binary file not shown.