Skip to content

Commit 26423b2

Browse files
Layer UI controls (#18)
* main view model refactor * add image feature * performance enhancements * syntax updates * syntax fixes from 2bit AI * centroid refactor. static refs
1 parent 4bdb6fc commit 26423b2

50 files changed

Lines changed: 2330 additions & 1689 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.clinerules/.clinerules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- We should NEVER keep legacy or duplicate code. A refactor or update means refactoring to a clean state and not leaving behind bloat and obselete code.
1717
- No underscores
1818
- No regions
19+
- DO NOT use abbreviations for anything. Variable names or otherwise
1920

2021
## Testing
2122

Components/MiniMapView.xaml.cs

Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ public partial class MiniMapView : ContentView
2020
private IMessageBus? messageBus;
2121
private IMessageBus? MessageBus
2222
{
23-
get
24-
{
25-
if (messageBus != null) return messageBus;
26-
messageBus = Handler?.MauiContext?.Services.GetService<IMessageBus>()
27-
?? IPlatformApplication.Current?.Services.GetService<IMessageBus>();
28-
return messageBus;
29-
}
23+
get
24+
{
25+
if (messageBus != null) return messageBus;
26+
messageBus = Handler?.MauiContext?.Services.GetService<IMessageBus>()
27+
?? IPlatformApplication.Current?.Services.GetService<IMessageBus>();
28+
return messageBus;
29+
}
3030
}
3131

3232
public MiniMapView()
@@ -35,10 +35,9 @@ public MiniMapView()
3535

3636
this.Loaded += (s, e) =>
3737
{
38-
// Redraw when canvas is invalidated (drawing changes)
39-
MessageBus?.Listen<CanvasInvalidateMessage>()
40-
.Throttle(TimeSpan.FromMilliseconds(30), RxApp.MainThreadScheduler) // Throttle to avoid excessive redraws
41-
.Subscribe(_ => miniMapCanvas?.InvalidateSurface());
38+
MessageBus?.Listen<CanvasInvalidateMessage>()
39+
.Throttle(TimeSpan.FromMilliseconds(30), RxApp.MainThreadScheduler)
40+
.Subscribe(_ => miniMapCanvas?.InvalidateSurface());
4241
};
4342
}
4443

@@ -63,7 +62,7 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
6362

6463
canvas.Clear(SKColors.White);
6564

66-
// 1. Calculate Bounds of all elements
65+
// Calculate bounds of all elements
6766
var contentBounds = SKRect.Empty;
6867
bool hasContent = false;
6968

@@ -86,27 +85,23 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
8685

8786
if (!hasContent)
8887
{
89-
// Default to standard size if empty
9088
contentBounds = new SKRect(0, 0, 1000, 1000);
9189
}
9290

93-
// Add some padding to bounds
9491
contentBounds.Inflate(50, 50);
9592

96-
// 2. Calculate Fit Matrix (World -> MiniMap)
97-
// Scale to fit contentBounds into info.Rect
93+
// Calculate fit matrix (world to minimap)
9894
float scaleX = info.Width / contentBounds.Width;
9995
float scaleY = info.Height / contentBounds.Height;
10096
float scale = Math.Min(scaleX, scaleY);
10197

102-
// Center it
10398
float tx = (info.Width - contentBounds.Width * scale) / 2 - contentBounds.Left * scale;
10499
float ty = (info.Height - contentBounds.Height * scale) / 2 - contentBounds.Top * scale;
105100

106101
fitMatrix = SKMatrix.CreateScale(scale, scale);
107102
fitMatrix = SKMatrix.Concat(SKMatrix.CreateTranslation(tx, ty), fitMatrix);
108103

109-
// 3. Draw Content
104+
// Draw content
110105
canvas.Save();
111106
canvas.Concat(fitMatrix);
112107

@@ -117,29 +112,27 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
117112
foreach (var element in layer.Elements)
118113
{
119114
if (element.IsVisible)
120-
{
115+
{
121116
element.Draw(canvas);
122117
}
123118
}
124119
}
125120
}
126121
canvas.Restore();
127122

128-
// 4. Draw Viewport Indicator
129-
// Viewport is the inverse of the Main Canvas Matrix applied to the Main Canvas Screen Rect
130-
if (viewModel.NavigationModel.TotalMatrix.TryInvert(out var mainInverse))
123+
// Draw viewport indicator
124+
if (viewModel.NavigationModel.ViewMatrix.TryInvert(out var mainInverse))
131125
{
132-
// Main Canvas Screen Size (Approximate if not bound, but we can use the ViewModel's stored size)
133126
var mainScreenRect = viewModel.CanvasSize;
134127
if (mainScreenRect.Width > 0)
135128
{
136-
// Map Screen Rect Corners -> World Points
129+
// Map screen corners to world points
137130
var tl = mainInverse.MapPoint(new SKPoint(mainScreenRect.Left, mainScreenRect.Top));
138131
var tr = mainInverse.MapPoint(new SKPoint(mainScreenRect.Right, mainScreenRect.Top));
139132
var br = mainInverse.MapPoint(new SKPoint(mainScreenRect.Right, mainScreenRect.Bottom));
140133
var bl = mainInverse.MapPoint(new SKPoint(mainScreenRect.Left, mainScreenRect.Bottom));
141134

142-
// Map World Points -> MiniMap Points
135+
// Map world points to minimap points
143136
var mTl = fitMatrix.MapPoint(tl);
144137
var mTr = fitMatrix.MapPoint(tr);
145138
var mBr = fitMatrix.MapPoint(br);
@@ -175,59 +168,31 @@ private void OnTouch(object? sender, SKTouchEventArgs e)
175168
{
176169
if (viewModel == null) return;
177170

178-
// Only process if the user is actually touching/clicking (avoids hover issues)
179-
if (!e.InContact)
180-
return;
171+
if (!e.InContact) return;
181172

182173
var canvasView = sender as SKCanvasView;
183174
if (canvasView == null) return;
184175

185176
switch (e.ActionType)
186-
{
177+
{
187178
case SKTouchAction.Pressed:
188179
case SKTouchAction.Moved:
189-
// Convert DIPs (Touch Location) to Pixels (Canvas Coordinates)
190-
// SKTouchEventArgs.Location is ALREADY in pixels (canvas coordinates) for SKCanvasView.
191-
// We do NOT need to multiply by density.
192180
var touchPointPixels = e.Location;
193181

194-
// Move main view to this location
195182
if (fitMatrix.TryInvert(out var inverseFit))
196183
{
197184
var worldPoint = inverseFit.MapPoint(touchPointPixels);
198185

199-
// We want to center the Main View on this worldPoint.
200-
// Main View Matrix: Scale * Translation.
201-
// We want to keep current Scale.
202-
203-
float currentScaleX = viewModel.NavigationModel.TotalMatrix.ScaleX;
204-
float currentScaleY = viewModel.NavigationModel.TotalMatrix.ScaleY;
205-
206-
// If rotated, extracting scale is harder.
207-
// Let's assume we want to Pan the view so that worldPoint is at Center of Screen.
208-
209-
// Target: TotalMatrix.MapPoint(worldPoint) = ScreenCenter
210-
// TotalMatrix = [Existing Rotation/Scale] * [New Translation] ?
211-
// No, we just want to adjust Translation.
212-
213-
// Let's calculate the required translation.
214-
// ViewPoint = Matrix * WorldPoint
215-
// We want ViewPoint = ScreenCenter.
216-
217-
// If we reconstruct the matrix:
218-
// NewMatrix = OldMatrix without Translation * NewTranslation?
219-
// This clears rotation if we aren't careful.
220-
221-
// Better approach: Calculate the difference.
222-
var currentViewPoint = viewModel.NavigationModel.TotalMatrix.MapPoint(worldPoint);
186+
// Calculate where this world point currently appears on screen
187+
var currentViewPoint = viewModel.NavigationModel.ViewMatrix.MapPoint(worldPoint);
223188
var screenCenter = new SKPoint(viewModel.CanvasSize.Width / 2, viewModel.CanvasSize.Height / 2);
224189

190+
// Calculate the delta to center it
225191
var delta = screenCenter - currentViewPoint;
226192

227-
// Translate the view by delta
193+
// Apply translation to view matrix
228194
var translation = SKMatrix.CreateTranslation(delta.X, delta.Y);
229-
// Update UserMatrix using PostConcat to accumulate transformations (Legacy style)
230-
viewModel.NavigationModel.UserMatrix = viewModel.NavigationModel.UserMatrix.PostConcat(translation);
195+
viewModel.NavigationModel.ViewMatrix = viewModel.NavigationModel.ViewMatrix.PostConcat(translation);
231196

232197
MessageBus?.SendMessage(new CanvasInvalidateMessage());
233198
}

Components/ToolbarView.xaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,16 @@
120120
<Label Text="Shapes"
121121
HorizontalTextAlignment="Center"/>
122122
</StackLayout>
123+
124+
<StackLayout HorizontalOptions="Center">
125+
<Button x:Name="ImageButton"
126+
Style="{StaticResource ToolbarButtonStyle}"
127+
Text="🖼️"
128+
Command="{Binding ImportImageCommand}"/>
129+
<Label Text="Image"
130+
HorizontalTextAlignment="Center"/>
131+
</StackLayout>
132+
123133
<StackLayout HorizontalOptions="Center">
124134
<Button x:Name="EraserButton"
125135
Style="{StaticResource ToolbarButtonStyle}"

0 commit comments

Comments
 (0)