Running Alpha V2
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ PCPalService/service_log.txt
|
||||
/PCPal/Configurator/obj
|
||||
/PCPal/Core/bin
|
||||
/PCPal/Core/obj
|
||||
/PCPal/Configurator/bin
|
||||
|
||||
@@ -1,72 +1,77 @@
|
||||
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="PCPal.Configurator.AppShell">
|
||||
xmlns:views="clr-namespace:PCPal.Configurator.Views"
|
||||
x:Class="PCPal.Configurator.AppShell"
|
||||
Shell.NavBarIsVisible="False"
|
||||
Shell.FlyoutBehavior="Disabled">
|
||||
|
||||
<Shell.ContentTemplate>
|
||||
<Grid ColumnDefinitions="220,*">
|
||||
<!-- Sidebar -->
|
||||
<StackLayout Grid.Column="0"
|
||||
Padding="10"
|
||||
Spacing="10"
|
||||
BackgroundColor="{StaticResource Surface}">
|
||||
<ShellContent>
|
||||
<ContentPage>
|
||||
<Grid ColumnDefinitions="220,*">
|
||||
<!-- Sidebar -->
|
||||
<StackLayout Grid.Column="0"
|
||||
Padding="10"
|
||||
Spacing="10"
|
||||
BackgroundColor="{StaticResource Surface}">
|
||||
|
||||
<!-- App logo and title -->
|
||||
<HorizontalStackLayout Spacing="10" Margin="0,10,0,20">
|
||||
<Image Source="app_icon.png" HeightRequest="32" WidthRequest="32" />
|
||||
<Label Text="PCPal"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Primary}"
|
||||
VerticalOptions="Center" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Navigation menu -->
|
||||
<CollectionView x:Name="NavMenu"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="OnNavMenuSelectionChanged">
|
||||
<CollectionView.ItemsSource>
|
||||
<x:Array Type="{x:Type x:String}">
|
||||
<x:String>1602 LCD Display</x:String>
|
||||
<x:String>4.6" TFT Display</x:String>
|
||||
<x:String>OLED Display</x:String>
|
||||
<x:String>Settings</x:String>
|
||||
<x:String>Help</x:String>
|
||||
</x:Array>
|
||||
</CollectionView.ItemsSource>
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="15,10"
|
||||
StrokeShape="RoundRectangle 8,8,8,8"
|
||||
BackgroundColor="{Binding Source={x:Reference NavMenu}, Path=SelectedItem, Converter={StaticResource StringMatchConverter}, ConverterParameter={Binding .}}">
|
||||
<HorizontalStackLayout Spacing="10">
|
||||
<Label Text="{Binding .}"
|
||||
VerticalOptions="Center"
|
||||
TextColor="{Binding Source={x:Reference NavMenu}, Path=SelectedItem, Converter={StaticResource StringMatchTextConverter}, ConverterParameter={Binding .}}" />
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
|
||||
<!-- Connection status -->
|
||||
<StackLayout VerticalOptions="EndAndExpand" Margin="0,30,0,10">
|
||||
<HorizontalStackLayout Spacing="8">
|
||||
<Ellipse Fill="{Binding IsConnected, Converter={StaticResource ConnectionStatusColorConverter}}"
|
||||
WidthRequest="12"
|
||||
HeightRequest="12" />
|
||||
<Label Text="{Binding ConnectionStatus}"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<!-- App logo and title -->
|
||||
<HorizontalStackLayout Spacing="10" Margin="0,10,0,20">
|
||||
<Image Source="app_icon.png" HeightRequest="32" WidthRequest="32" />
|
||||
<Label Text="PCPal"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Primary}"
|
||||
VerticalOptions="Center" />
|
||||
</HorizontalStackLayout>
|
||||
<Label Text="{Binding LastUpdateTime, StringFormat='Updated: {0}'}"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
Margin="0,5,0,0" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
<!-- Content area -->
|
||||
<ContentView Grid.Column="1" x:Name="ContentContainer" />
|
||||
</Grid>
|
||||
</Shell.ContentTemplate>
|
||||
<!-- Navigation menu -->
|
||||
<CollectionView x:Name="NavMenu"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="OnNavMenuSelectionChanged">
|
||||
<CollectionView.ItemsSource>
|
||||
<x:Array Type="{x:Type x:String}">
|
||||
<x:String>1602 LCD Display</x:String>
|
||||
<x:String>4.6" TFT Display</x:String>
|
||||
<x:String>OLED Display</x:String>
|
||||
<x:String>Settings</x:String>
|
||||
<x:String>Help</x:String>
|
||||
</x:Array>
|
||||
</CollectionView.ItemsSource>
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="15,10"
|
||||
StrokeShape="RoundRectangle 8,8,8,8"
|
||||
BackgroundColor="{Binding Source={x:Reference NavMenu}, Path=SelectedItem, Converter={StaticResource StringMatchConverter}, ConverterParameter={Binding .}}">
|
||||
<HorizontalStackLayout Spacing="10">
|
||||
<Label Text="{Binding .}"
|
||||
VerticalOptions="Center"
|
||||
TextColor="{Binding Source={x:Reference NavMenu}, Path=SelectedItem, Converter={StaticResource StringMatchTextConverter}, ConverterParameter={Binding .}}" />
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
|
||||
<!-- Connection status -->
|
||||
<StackLayout VerticalOptions="EndAndExpand" Margin="0,30,0,10">
|
||||
<HorizontalStackLayout Spacing="8">
|
||||
<Ellipse Fill="{Binding IsConnected, Converter={StaticResource ConnectionStatusColorConverter}}"
|
||||
WidthRequest="12"
|
||||
HeightRequest="12" />
|
||||
<Label Text="{Binding ConnectionStatus}"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
</HorizontalStackLayout>
|
||||
<Label Text="{Binding LastUpdateTime, StringFormat='Updated: {0}'}"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
Margin="0,5,0,0" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
<!-- Content area -->
|
||||
<ContentView Grid.Column="1" x:Name="ContentContainer" />
|
||||
</Grid>
|
||||
</ContentPage>
|
||||
</ShellContent>
|
||||
</Shell>
|
||||
@@ -5,7 +5,6 @@ using PCPal.Configurator.Views.LCD;
|
||||
using PCPal.Configurator.Views.OLED;
|
||||
using PCPal.Configurator.Views.TFT;
|
||||
using System.ComponentModel;
|
||||
//using UIKit;
|
||||
|
||||
namespace PCPal.Configurator;
|
||||
|
||||
@@ -14,6 +13,7 @@ public partial class AppShell : Shell, INotifyPropertyChanged
|
||||
private bool _isConnected;
|
||||
private string _connectionStatus;
|
||||
private DateTime _lastUpdateTime;
|
||||
private CancellationTokenSource _monitoringCts;
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
@@ -60,7 +60,8 @@ public partial class AppShell : Shell, INotifyPropertyChanged
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_serviceProvider = IPlatformApplication.Current.Services;
|
||||
_serviceProvider = IPlatformApplication.Current?.Services;
|
||||
_monitoringCts = new CancellationTokenSource();
|
||||
|
||||
// Set initial connection status
|
||||
IsConnected = false;
|
||||
@@ -74,17 +75,29 @@ public partial class AppShell : Shell, INotifyPropertyChanged
|
||||
StartConnectivityMonitoring();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
_monitoringCts?.Cancel();
|
||||
}
|
||||
|
||||
~AppShell()
|
||||
{
|
||||
_monitoringCts?.Cancel();
|
||||
_monitoringCts?.Dispose();
|
||||
}
|
||||
|
||||
private void OnNavMenuSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.CurrentSelection.FirstOrDefault() is string selection)
|
||||
{
|
||||
ContentView view = selection switch
|
||||
{
|
||||
"1602 LCD Display" => _serviceProvider.GetService<LcdConfigView>(),
|
||||
"4.6 TFT Display" => _serviceProvider.GetService<TftConfigView>(),
|
||||
"OLED Display" => _serviceProvider.GetService<OledConfigView>(),
|
||||
"Settings" => _serviceProvider.GetService<SettingsView>(),
|
||||
"Help" => _serviceProvider.GetService<HelpView>(),
|
||||
"1602 LCD Display" => _serviceProvider?.GetService<LcdConfigView>(),
|
||||
"4.6\" TFT Display" => _serviceProvider?.GetService<TftConfigView>(),
|
||||
"OLED Display" => _serviceProvider?.GetService<OledConfigView>(),
|
||||
"Settings" => _serviceProvider?.GetService<SettingsView>(),
|
||||
"Help" => _serviceProvider?.GetService<HelpView>(),
|
||||
_ => null
|
||||
};
|
||||
|
||||
@@ -97,7 +110,7 @@ public partial class AppShell : Shell, INotifyPropertyChanged
|
||||
|
||||
private async void StartConnectivityMonitoring()
|
||||
{
|
||||
var serialPortService = _serviceProvider.GetService<ISerialPortService>();
|
||||
var serialPortService = _serviceProvider?.GetService<ISerialPortService>();
|
||||
if (serialPortService != null)
|
||||
{
|
||||
// Subscribe to connection status changes
|
||||
@@ -112,19 +125,31 @@ public partial class AppShell : Shell, INotifyPropertyChanged
|
||||
};
|
||||
|
||||
// Start periodic connection check
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
try
|
||||
while (!_monitoringCts.Token.IsCancellationRequested)
|
||||
{
|
||||
await serialPortService.CheckConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error but don't crash the app
|
||||
System.Diagnostics.Debug.WriteLine($"Connection check error: {ex.Message}");
|
||||
await Task.Delay(5000, _monitoringCts.Token);
|
||||
|
||||
if (_monitoringCts.Token.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
await serialPortService.CheckConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error but don't crash the app
|
||||
System.Diagnostics.Debug.WriteLine($"Connection check error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected when cancellation is requested
|
||||
System.Diagnostics.Debug.WriteLine("Connectivity monitoring canceled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
//using Android.Sax;
|
||||
using Microsoft.Maui.Controls.Shapes;
|
||||
using Microsoft.Maui.Controls.Shapes;
|
||||
using PCPal.Configurator.ViewModels;
|
||||
using PCPal.Core.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -162,138 +161,142 @@ public class OledPreviewCanvas : GraphicsView
|
||||
dragStartPoint = point;
|
||||
|
||||
// Check if an element was clicked
|
||||
if (Elements != null)
|
||||
if (Elements == null || Elements.Count == 0) return;
|
||||
|
||||
// Need to adjust for scale
|
||||
float x = (float)point.X / Scale;
|
||||
float y = (float)point.Y / Scale;
|
||||
|
||||
// Check in reverse order (top elements first)
|
||||
for (int i = Elements.Count - 1; i >= 0; i--)
|
||||
{
|
||||
// Need to adjust for scale
|
||||
float x = (float)point.X / Scale;
|
||||
float y = (float)point.Y / Scale;
|
||||
var element = Elements[i];
|
||||
|
||||
foreach (var element in Elements)
|
||||
if (element is TextElement textElement)
|
||||
{
|
||||
if (element is TextElement textElement)
|
||||
// Simple bounding box check
|
||||
if (x >= textElement.X && x <= textElement.X + 100 &&
|
||||
y >= textElement.Y - 20 && y <= textElement.Y)
|
||||
{
|
||||
// Simple bounding box check
|
||||
if (x >= textElement.X && x <= textElement.X + 100 &&
|
||||
y >= textElement.Y - 20 && y <= textElement.Y)
|
||||
// Find the OledElement that corresponds to this PreviewElement
|
||||
var oledElement = FindOledElementForPreviewElement(textElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
// Find the OledElement that corresponds to this PreviewElement
|
||||
var oledElement = FindOledElementForPreviewElement(textElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is BarElement barElement)
|
||||
{
|
||||
if (x >= barElement.X && x <= barElement.X + barElement.Width &&
|
||||
y >= barElement.Y && y <= barElement.Y + barElement.Height)
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(barElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is RectElement rectElement)
|
||||
{
|
||||
if (x >= rectElement.X && x <= rectElement.X + rectElement.Width &&
|
||||
y >= rectElement.Y && y <= rectElement.Y + rectElement.Height)
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(rectElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is LineElement lineElement)
|
||||
{
|
||||
// Simplified line hit detection
|
||||
float lineLength = (float)Math.Sqrt(
|
||||
Math.Pow(lineElement.X2 - lineElement.X1, 2) +
|
||||
Math.Pow(lineElement.Y2 - lineElement.Y1, 2));
|
||||
|
||||
// Check if point is close to the line
|
||||
float distance = DistancePointToLine(
|
||||
x, y,
|
||||
lineElement.X1, lineElement.Y1,
|
||||
lineElement.X2, lineElement.Y2);
|
||||
|
||||
if (distance < 10) // 10 pixel tolerance
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(lineElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is IconElement iconElement)
|
||||
{
|
||||
if (x >= iconElement.X && x <= iconElement.X + 24 &&
|
||||
y >= iconElement.Y && y <= iconElement.Y + 24)
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(iconElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is BarElement barElement)
|
||||
{
|
||||
if (x >= barElement.X && x <= barElement.X + barElement.Width &&
|
||||
y >= barElement.Y && y <= barElement.Y + barElement.Height)
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(barElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is RectElement rectElement)
|
||||
{
|
||||
if (x >= rectElement.X && x <= rectElement.X + rectElement.Width &&
|
||||
y >= rectElement.Y && y <= rectElement.Y + rectElement.Height)
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(rectElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is LineElement lineElement)
|
||||
{
|
||||
// Simplified line hit detection
|
||||
float distance = DistancePointToLine(
|
||||
x, y,
|
||||
lineElement.X1, lineElement.Y1,
|
||||
lineElement.X2, lineElement.Y2);
|
||||
|
||||
// No element was clicked, deselect
|
||||
SelectedElement = null;
|
||||
if (distance < 10) // 10 pixel tolerance
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(lineElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element is IconElement iconElement)
|
||||
{
|
||||
if (x >= iconElement.X && x <= iconElement.X + 24 &&
|
||||
y >= iconElement.Y && y <= iconElement.Y + 24)
|
||||
{
|
||||
var oledElement = FindOledElementForPreviewElement(iconElement);
|
||||
if (oledElement != null)
|
||||
{
|
||||
draggedElement = oledElement;
|
||||
SelectedElement = oledElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No element was clicked, deselect
|
||||
SelectedElement = null;
|
||||
}
|
||||
|
||||
private void OnDragInteraction(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (!IsEditable || draggedElement == null) return;
|
||||
|
||||
var point = e.Touches[0];
|
||||
|
||||
// Calculate the delta from the start point
|
||||
float deltaX = (float)(point.X - dragStartPoint.X) / Scale;
|
||||
float deltaY = (float)(point.Y - dragStartPoint.Y) / Scale;
|
||||
|
||||
// Update the position of the dragged element
|
||||
draggedElement.X += (int)deltaX;
|
||||
draggedElement.Y += (int)deltaY;
|
||||
|
||||
// Keep element within bounds
|
||||
draggedElement.X = Math.Max(0, Math.Min(Width - 10, draggedElement.X));
|
||||
draggedElement.Y = Math.Max(0, Math.Min(Height - 10, draggedElement.Y));
|
||||
|
||||
// Update the start point for the next move
|
||||
dragStartPoint = point;
|
||||
|
||||
// Notify property changes
|
||||
var viewModel = BindingContext as OledConfigViewModel;
|
||||
if (viewModel != null)
|
||||
try
|
||||
{
|
||||
// Update the view model properties to reflect the new position
|
||||
viewModel.OnPropertyChanged(nameof(viewModel.SelectedElementX));
|
||||
viewModel.OnPropertyChanged(nameof(viewModel.SelectedElementY));
|
||||
var point = e.Touches[0];
|
||||
|
||||
// Update the markup
|
||||
viewModel.UpdateMarkupFromElements();
|
||||
// Calculate the delta from the start point
|
||||
float deltaX = (float)(point.X - dragStartPoint.X) / Scale;
|
||||
float deltaY = (float)(point.Y - dragStartPoint.Y) / Scale;
|
||||
|
||||
// Update the position of the dragged element
|
||||
draggedElement.X += (int)deltaX;
|
||||
draggedElement.Y += (int)deltaY;
|
||||
|
||||
// Keep element within bounds
|
||||
draggedElement.X = Math.Max(0, Math.Min(Width - 10, draggedElement.X));
|
||||
draggedElement.Y = Math.Max(0, Math.Min(Height - 10, draggedElement.Y));
|
||||
|
||||
// Update the start point for the next move
|
||||
dragStartPoint = point;
|
||||
|
||||
// Notify property changes
|
||||
var viewModel = BindingContext as PCPal.Configurator.ViewModels.OledConfigViewModel;
|
||||
if (viewModel != null)
|
||||
{
|
||||
// Update the view model properties to reflect the new position
|
||||
viewModel.OnPropertyChanged(nameof(viewModel.SelectedElementX));
|
||||
viewModel.OnPropertyChanged(nameof(viewModel.SelectedElementY));
|
||||
|
||||
// Update the markup
|
||||
viewModel.UpdateMarkupFromElements();
|
||||
}
|
||||
|
||||
// Invalidate the canvas to redraw
|
||||
Invalidate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error in drag interaction: {ex.Message}");
|
||||
}
|
||||
|
||||
// Invalidate the canvas to redraw
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void OnEndInteraction(object sender, TouchEventArgs e)
|
||||
@@ -378,66 +381,73 @@ public class OledCanvasDrawable : IDrawable
|
||||
|
||||
public void Draw(ICanvas canvas, RectF dirtyRect)
|
||||
{
|
||||
float scale = _canvas.Scale;
|
||||
|
||||
// Clear background
|
||||
canvas.FillColor = Colors.Black;
|
||||
canvas.FillRectangle(0, 0, dirtyRect.Width, dirtyRect.Height);
|
||||
|
||||
// Draw grid if requested
|
||||
if (_canvas.IsEditable && _canvas.Parent?.BindingContext is OledConfigViewModel viewModel && viewModel.ShowGridLines)
|
||||
try
|
||||
{
|
||||
canvas.StrokeColor = new Color(64, 64, 64, 64); // Semi-transparent gray
|
||||
canvas.StrokeSize = 1;
|
||||
float scale = _canvas.Scale;
|
||||
|
||||
// Draw vertical grid lines
|
||||
for (int x = 0; x <= _canvas.Width; x += 10)
|
||||
{
|
||||
canvas.DrawLine(x * scale, 0, x * scale, _canvas.Height * scale);
|
||||
}
|
||||
// Clear background
|
||||
canvas.FillColor = Colors.Black;
|
||||
canvas.FillRectangle(0, 0, dirtyRect.Width, dirtyRect.Height);
|
||||
|
||||
// Draw horizontal grid lines
|
||||
for (int y = 0; y <= _canvas.Height; y += 10)
|
||||
// Draw grid if requested
|
||||
if (_canvas.IsEditable && _canvas.Parent?.BindingContext is OledConfigViewModel viewModel && viewModel.ShowGridLines)
|
||||
{
|
||||
canvas.DrawLine(0, y * scale, _canvas.Width * scale, y * scale);
|
||||
}
|
||||
}
|
||||
canvas.StrokeColor = new Color(64, 64, 64, 64); // Semi-transparent gray
|
||||
canvas.StrokeSize = 1;
|
||||
|
||||
// Draw elements
|
||||
if (_canvas.Elements != null)
|
||||
{
|
||||
foreach (var element in _canvas.Elements)
|
||||
{
|
||||
// Check if this element is selected
|
||||
bool isSelected = false;
|
||||
if (_canvas.SelectedElement != null && _canvas.IsEditable)
|
||||
// Draw vertical grid lines
|
||||
for (int x = 0; x <= _canvas.Width; x += 10)
|
||||
{
|
||||
if (element is TextElement textElement && _canvas.SelectedElement.Type == "text")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == textElement.X && _canvas.SelectedElement.Y == textElement.Y;
|
||||
}
|
||||
else if (element is BarElement barElement && _canvas.SelectedElement.Type == "bar")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == barElement.X && _canvas.SelectedElement.Y == barElement.Y;
|
||||
}
|
||||
else if (element is RectElement rectElement &&
|
||||
(_canvas.SelectedElement.Type == "rect" || _canvas.SelectedElement.Type == "box"))
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == rectElement.X && _canvas.SelectedElement.Y == rectElement.Y;
|
||||
}
|
||||
else if (element is LineElement lineElement && _canvas.SelectedElement.Type == "line")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == lineElement.X1 && _canvas.SelectedElement.Y == lineElement.Y1;
|
||||
}
|
||||
else if (element is IconElement iconElement && _canvas.SelectedElement.Type == "icon")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == iconElement.X && _canvas.SelectedElement.Y == iconElement.Y;
|
||||
}
|
||||
canvas.DrawLine(x * scale, 0, x * scale, _canvas.Height * scale);
|
||||
}
|
||||
|
||||
// Draw the element with appropriate styling
|
||||
DrawElement(canvas, element, scale, isSelected);
|
||||
// Draw horizontal grid lines
|
||||
for (int y = 0; y <= _canvas.Height; y += 10)
|
||||
{
|
||||
canvas.DrawLine(0, y * scale, _canvas.Width * scale, y * scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw elements
|
||||
if (_canvas.Elements != null)
|
||||
{
|
||||
foreach (var element in _canvas.Elements)
|
||||
{
|
||||
// Check if this element is selected
|
||||
bool isSelected = false;
|
||||
if (_canvas.SelectedElement != null && _canvas.IsEditable)
|
||||
{
|
||||
if (element is TextElement textElement && _canvas.SelectedElement.Type == "text")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == textElement.X && _canvas.SelectedElement.Y == textElement.Y;
|
||||
}
|
||||
else if (element is BarElement barElement && _canvas.SelectedElement.Type == "bar")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == barElement.X && _canvas.SelectedElement.Y == barElement.Y;
|
||||
}
|
||||
else if (element is RectElement rectElement &&
|
||||
(_canvas.SelectedElement.Type == "rect" || _canvas.SelectedElement.Type == "box"))
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == rectElement.X && _canvas.SelectedElement.Y == rectElement.Y;
|
||||
}
|
||||
else if (element is LineElement lineElement && _canvas.SelectedElement.Type == "line")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == lineElement.X1 && _canvas.SelectedElement.Y == lineElement.Y1;
|
||||
}
|
||||
else if (element is IconElement iconElement && _canvas.SelectedElement.Type == "icon")
|
||||
{
|
||||
isSelected = _canvas.SelectedElement.X == iconElement.X && _canvas.SelectedElement.Y == iconElement.Y;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the element with appropriate styling
|
||||
DrawElement(canvas, element, scale, isSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error drawing canvas: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,20 @@
|
||||
|
||||
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
|
||||
|
||||
<Color x:Key="Primary">#512BD4</Color>
|
||||
<Color x:Key="PrimaryDark">#ac99ea</Color>
|
||||
<Color x:Key="PrimaryDarkText">#242424</Color>
|
||||
<Color x:Key="Secondary">#DFD8F7</Color>
|
||||
<Color x:Key="SecondaryDarkText">#9880e5</Color>
|
||||
<Color x:Key="Primary">#1E88E5</Color>
|
||||
<Color x:Key="PrimaryDark">#1565C0</Color>
|
||||
<Color x:Key="PrimaryLight">#E3F2FD</Color>
|
||||
<Color x:Key="Secondary">#CFD8DC</Color>
|
||||
<Color x:Key="Tertiary">#2B0B98</Color>
|
||||
<Color x:Key="Accent">#03A9F4</Color>
|
||||
<Color x:Key="Background">#F4F6F8</Color>
|
||||
<Color x:Key="Surface">#FFFFFF</Color>
|
||||
<Color x:Key="TextPrimary">#333333</Color>
|
||||
<Color x:Key="TextSecondary">#555555</Color>
|
||||
<Color x:Key="BorderColor">#E1E4E8</Color>
|
||||
<Color x:Key="Success">#4CAF50</Color>
|
||||
<Color x:Key="Error">#F44336</Color>
|
||||
<Color x:Key="Warning">#FF9800</Color>
|
||||
|
||||
<Color x:Key="White">White</Color>
|
||||
<Color x:Key="Black">Black</Color>
|
||||
|
||||
@@ -83,15 +83,21 @@ public class StringMatchConverter : IValueConverter
|
||||
{
|
||||
if (currentValue == targetValue)
|
||||
{
|
||||
// Handle different return types based on an additional parameter
|
||||
if (parameter is string param2 && param2 == "TextColor")
|
||||
// Default to background coloring
|
||||
Color returnColor = Application.Current.Resources["PrimaryLight"] as Color ?? Colors.LightBlue;
|
||||
|
||||
// Check for additional parameter context
|
||||
if (targetValue.EndsWith(":TextColor"))
|
||||
{
|
||||
// Return text color for selected item
|
||||
return Application.Current.Resources["Primary"] as Color ?? Colors.Black;
|
||||
// Strip the ":TextColor" suffix before comparison
|
||||
string strippedTarget = targetValue.Substring(0, targetValue.Length - 10);
|
||||
if (currentValue == strippedTarget)
|
||||
{
|
||||
return Application.Current.Resources["Primary"] as Color ?? Colors.Black;
|
||||
}
|
||||
}
|
||||
|
||||
// Return background color for selected item
|
||||
return Application.Current.Resources["PrimaryLight"] as Color ?? Colors.LightBlue;
|
||||
return returnColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,12 +86,6 @@
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=White}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TabBar">
|
||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}" />
|
||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light=#777777, Dark=#777777}" />
|
||||
<Setter Property="BarSelectedTextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
</Style>
|
||||
|
||||
<!-- Custom Styles -->
|
||||
|
||||
<Style x:Key="HeaderLabelStyle" TargetType="Label">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,12 +16,13 @@ public interface ISerialPortService
|
||||
event EventHandler<bool> ConnectionStatusChanged;
|
||||
}
|
||||
|
||||
public class SerialPortService : ISerialPortService
|
||||
public class SerialPortService : ISerialPortService, IDisposable
|
||||
{
|
||||
private SerialPort _serialPort;
|
||||
private bool _isConnected;
|
||||
private string _currentPort;
|
||||
private readonly ILogger<SerialPortService> _logger;
|
||||
private bool _disposed = false;
|
||||
|
||||
public bool IsConnected => _isConnected;
|
||||
public string CurrentPort => _currentPort;
|
||||
@@ -30,13 +31,19 @@ public class SerialPortService : ISerialPortService
|
||||
|
||||
public SerialPortService(ILogger<SerialPortService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_isConnected = false;
|
||||
_currentPort = string.Empty;
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync(string port)
|
||||
{
|
||||
if (string.IsNullOrEmpty(port))
|
||||
{
|
||||
_logger.LogWarning("Cannot connect to null or empty port name");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Disconnect if already connected
|
||||
@@ -71,6 +78,7 @@ public class SerialPortService : ISerialPortService
|
||||
// Not a valid PCPal device, close the connection
|
||||
_serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
_serialPort = null;
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -81,6 +89,7 @@ public class SerialPortService : ISerialPortService
|
||||
{
|
||||
_serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
_serialPort = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -126,8 +135,15 @@ public class SerialPortService : ISerialPortService
|
||||
|
||||
public async Task<bool> SendCommandAsync(string command)
|
||||
{
|
||||
if (string.IsNullOrEmpty(command))
|
||||
{
|
||||
_logger.LogWarning("Cannot send null or empty command");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_isConnected || _serialPort == null || !_serialPort.IsOpen)
|
||||
{
|
||||
_logger.LogWarning("Cannot send command: Device not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -155,9 +171,10 @@ public class SerialPortService : ISerialPortService
|
||||
|
||||
foreach (string port in ports)
|
||||
{
|
||||
SerialPort testPort = null;
|
||||
try
|
||||
{
|
||||
using SerialPort testPort = new SerialPort(port, 115200)
|
||||
testPort = new SerialPort(port, 115200)
|
||||
{
|
||||
ReadTimeout = 1000,
|
||||
WriteTimeout = 1000
|
||||
@@ -173,15 +190,33 @@ public class SerialPortService : ISerialPortService
|
||||
response.Contains("DISPLAY_TYPE:OLED") ||
|
||||
response.Contains("DISPLAY_TYPE:TFT"))
|
||||
{
|
||||
// Make sure to close and dispose the port before returning
|
||||
testPort.Close();
|
||||
testPort.Dispose();
|
||||
return port;
|
||||
}
|
||||
|
||||
testPort.Close();
|
||||
testPort.Dispose();
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Skip ports that can't be opened
|
||||
continue;
|
||||
_logger.LogDebug(ex, $"Failed to check port {port}");
|
||||
|
||||
// Clean up the port if an exception occurs
|
||||
if (testPort != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (testPort.IsOpen)
|
||||
testPort.Close();
|
||||
testPort.Dispose();
|
||||
}
|
||||
catch (Exception disposalEx)
|
||||
{
|
||||
_logger.LogDebug(disposalEx, $"Error disposing test port {port}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,8 +259,10 @@ public class SerialPortService : ISerialPortService
|
||||
|
||||
return connected;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error checking connection");
|
||||
|
||||
if (_isConnected)
|
||||
{
|
||||
_isConnected = false;
|
||||
@@ -236,4 +273,40 @@ public class SerialPortService : ISerialPortService
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_serialPort != null && _serialPort.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
_serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error disposing serial port");
|
||||
}
|
||||
_serialPort = null;
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~SerialPortService()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user