V2 rebuild - with bugs -_-
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -5,3 +5,7 @@
|
||||
/PCPalService/bin
|
||||
/PCPalService/obj
|
||||
PCPalService/service_log.txt
|
||||
/PCPal/.vs
|
||||
/PCPal/Configurator/obj
|
||||
/PCPal/Core/bin
|
||||
/PCPal/Core/obj
|
||||
|
||||
69
PCPal/Configurator/App.xaml
Normal file
69
PCPal/Configurator/App.xaml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:PCPal.Configurator"
|
||||
x:Class="PCPal.Configurator.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Converters.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Application Colors -->
|
||||
<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="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>
|
||||
|
||||
<!-- Card Style -->
|
||||
<Style x:Key="CardStyle" TargetType="Border">
|
||||
<Setter Property="BackgroundColor" Value="{StaticResource Surface}" />
|
||||
<Setter Property="Padding" Value="16" />
|
||||
<Setter Property="StrokeShape" Value="RoundRectangle 8,8,8,8" />
|
||||
<Setter Property="Stroke" Value="{StaticResource BorderColor}" />
|
||||
<Setter Property="StrokeThickness" Value="1" />
|
||||
<Setter Property="Shadow">
|
||||
<Shadow Brush="#22000000"
|
||||
Offset="0,2"
|
||||
Radius="4" />
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Button Styles -->
|
||||
<Style x:Key="PrimaryButton" TargetType="Button">
|
||||
<Setter Property="BackgroundColor" Value="{StaticResource Primary}" />
|
||||
<Setter Property="TextColor" Value="White" />
|
||||
<Setter Property="Padding" Value="20,10" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="FontAttributes" Value="Bold" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="OutlineButton" TargetType="Button">
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="TextColor" Value="{StaticResource Primary}" />
|
||||
<Setter Property="BorderColor" Value="{StaticResource Primary}" />
|
||||
<Setter Property="BorderWidth" Value="1" />
|
||||
<Setter Property="Padding" Value="20,10" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TextButton" TargetType="Button">
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="TextColor" Value="{StaticResource Primary}" />
|
||||
<Setter Property="Padding" Value="10,5" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
22
PCPal/Configurator/App.xaml.cs
Normal file
22
PCPal/Configurator/App.xaml.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace PCPal.Configurator;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
MainPage = new AppShell();
|
||||
}
|
||||
|
||||
protected override Window CreateWindow(IActivationState activationState)
|
||||
{
|
||||
Window window = base.CreateWindow(activationState);
|
||||
|
||||
// Configure window properties
|
||||
window.Title = "PCPal Configurator";
|
||||
window.MinimumWidth = 1000;
|
||||
window.MinimumHeight = 700;
|
||||
|
||||
return window;
|
||||
}
|
||||
}
|
||||
72
PCPal/Configurator/AppShell.xaml
Normal file
72
PCPal/Configurator/AppShell.xaml
Normal file
@@ -0,0 +1,72 @@
|
||||
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="PCPal.Configurator.AppShell">
|
||||
|
||||
<Shell.ContentTemplate>
|
||||
<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}" />
|
||||
</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>
|
||||
</Shell>
|
||||
130
PCPal/Configurator/AppShell.xaml.cs
Normal file
130
PCPal/Configurator/AppShell.xaml.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using PCPal.Core.Services;
|
||||
using PCPal.Configurator.ViewModels;
|
||||
using PCPal.Configurator.Views;
|
||||
using PCPal.Configurator.Views.LCD;
|
||||
using PCPal.Configurator.Views.OLED;
|
||||
using PCPal.Configurator.Views.TFT;
|
||||
using System.ComponentModel;
|
||||
//using UIKit;
|
||||
|
||||
namespace PCPal.Configurator;
|
||||
|
||||
public partial class AppShell : Shell, INotifyPropertyChanged
|
||||
{
|
||||
private bool _isConnected;
|
||||
private string _connectionStatus;
|
||||
private DateTime _lastUpdateTime;
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set
|
||||
{
|
||||
if (_isConnected != value)
|
||||
{
|
||||
_isConnected = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ConnectionStatus
|
||||
{
|
||||
get => _connectionStatus;
|
||||
set
|
||||
{
|
||||
if (_connectionStatus != value)
|
||||
{
|
||||
_connectionStatus = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime LastUpdateTime
|
||||
{
|
||||
get => _lastUpdateTime;
|
||||
set
|
||||
{
|
||||
if (_lastUpdateTime != value)
|
||||
{
|
||||
_lastUpdateTime = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_serviceProvider = IPlatformApplication.Current.Services;
|
||||
|
||||
// Set initial connection status
|
||||
IsConnected = false;
|
||||
ConnectionStatus = "Not connected";
|
||||
LastUpdateTime = DateTime.Now;
|
||||
|
||||
// Start with LCD view
|
||||
NavMenu.SelectedItem = "1602 LCD Display";
|
||||
|
||||
// Start connection monitoring in the background
|
||||
StartConnectivityMonitoring();
|
||||
}
|
||||
|
||||
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>(),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (view != null)
|
||||
{
|
||||
ContentContainer.Content = view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartConnectivityMonitoring()
|
||||
{
|
||||
var serialPortService = _serviceProvider.GetService<ISerialPortService>();
|
||||
if (serialPortService != null)
|
||||
{
|
||||
// Subscribe to connection status changes
|
||||
serialPortService.ConnectionStatusChanged += (sender, isConnected) =>
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
ConnectionStatus = isConnected ? "Connected to " + serialPortService.CurrentPort : "Not connected";
|
||||
LastUpdateTime = DateTime.Now;
|
||||
});
|
||||
};
|
||||
|
||||
// Start periodic connection check
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
try
|
||||
{
|
||||
await serialPortService.CheckConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error but don't crash the app
|
||||
System.Diagnostics.Debug.WriteLine($"Connection check error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
PCPal/Configurator/Configurator.csproj
Normal file
99
PCPal/Configurator/Configurator.csproj
Normal file
@@ -0,0 +1,99 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||
|
||||
<!-- Note for MacCatalyst:
|
||||
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
|
||||
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
|
||||
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
|
||||
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
|
||||
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Configurator</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Configurator</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.companyname.configurator</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiXaml Update="Resources\Styles\Converters.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\HelpView\HelpView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\LCD\LcdConfigView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\OLED\OledConfigView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\OLED\OledMarkupEditorView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\OLED\OledTemplatesView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\OLED\OledVisualEditorView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\Settings\SettingsView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="Views\TFT\TftConfigView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
31
PCPal/Configurator/Configurator.csproj.user
Normal file
31
PCPal/Configurator/Configurator.csproj.user
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<IsFirstTimeProjectOpen>False</IsFirstTimeProjectOpen>
|
||||
<ActiveDebugFramework>net8.0-windows10.0.19041.0</ActiveDebugFramework>
|
||||
<ActiveDebugProfile>Windows Machine</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Update="App.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Update="AppShell.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Update="MainPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Update="Platforms\Windows\App.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Update="Platforms\Windows\Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Update="Resources\Styles\Colors.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Update="Resources\Styles\Styles.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
606
PCPal/Configurator/Controls/OledPreviewCanvas.cs
Normal file
606
PCPal/Configurator/Controls/OledPreviewCanvas.cs
Normal file
@@ -0,0 +1,606 @@
|
||||
//using Android.Sax;
|
||||
using Microsoft.Maui.Controls.Shapes;
|
||||
using PCPal.Configurator.ViewModels;
|
||||
using PCPal.Core.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace PCPal.Configurator.Controls;
|
||||
|
||||
public class OledPreviewCanvas : GraphicsView
|
||||
{
|
||||
// Bindable properties for the control
|
||||
public static readonly BindableProperty ElementsProperty = BindableProperty.Create(
|
||||
nameof(Elements),
|
||||
typeof(IList<PreviewElement>),
|
||||
typeof(OledPreviewCanvas),
|
||||
null,
|
||||
propertyChanged: OnElementsChanged);
|
||||
|
||||
public static readonly BindableProperty SelectedElementProperty = BindableProperty.Create(
|
||||
nameof(SelectedElement),
|
||||
typeof(OledElement),
|
||||
typeof(OledPreviewCanvas),
|
||||
null,
|
||||
BindingMode.TwoWay,
|
||||
propertyChanged: OnSelectedElementChanged);
|
||||
|
||||
public static readonly BindableProperty IsEditableProperty = BindableProperty.Create(
|
||||
nameof(IsEditable),
|
||||
typeof(bool),
|
||||
typeof(OledPreviewCanvas),
|
||||
false);
|
||||
|
||||
public static readonly BindableProperty ScaleProperty = BindableProperty.Create(
|
||||
nameof(Scale),
|
||||
typeof(float),
|
||||
typeof(OledPreviewCanvas),
|
||||
1.0f,
|
||||
propertyChanged: OnScaleChanged);
|
||||
|
||||
public static readonly BindableProperty WidthProperty = BindableProperty.Create(
|
||||
nameof(Width),
|
||||
typeof(int),
|
||||
typeof(OledPreviewCanvas),
|
||||
256);
|
||||
|
||||
public static readonly BindableProperty HeightProperty = BindableProperty.Create(
|
||||
nameof(Height),
|
||||
typeof(int),
|
||||
typeof(OledPreviewCanvas),
|
||||
64);
|
||||
|
||||
// Property accessors
|
||||
public IList<PreviewElement> Elements
|
||||
{
|
||||
get => (IList<PreviewElement>)GetValue(ElementsProperty);
|
||||
set => SetValue(ElementsProperty, value);
|
||||
}
|
||||
|
||||
public OledElement SelectedElement
|
||||
{
|
||||
get => (OledElement)GetValue(SelectedElementProperty);
|
||||
set => SetValue(SelectedElementProperty, value);
|
||||
}
|
||||
|
||||
public bool IsEditable
|
||||
{
|
||||
get => (bool)GetValue(IsEditableProperty);
|
||||
set => SetValue(IsEditableProperty, value);
|
||||
}
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get => (float)GetValue(ScaleProperty);
|
||||
set => SetValue(ScaleProperty, value);
|
||||
}
|
||||
|
||||
public new int Width
|
||||
{
|
||||
get => (int)GetValue(WidthProperty);
|
||||
set => SetValue(WidthProperty, value);
|
||||
}
|
||||
|
||||
public new int Height
|
||||
{
|
||||
get => (int)GetValue(HeightProperty);
|
||||
set => SetValue(HeightProperty, value);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
public OledPreviewCanvas()
|
||||
{
|
||||
// Set default drawing
|
||||
Drawable = new OledCanvasDrawable(this);
|
||||
|
||||
// Set up interaction handlers if editable
|
||||
StartInteraction += OnStartInteraction;
|
||||
DragInteraction += OnDragInteraction;
|
||||
EndInteraction += OnEndInteraction;
|
||||
|
||||
// Set up initial size
|
||||
WidthRequest = 256 * Scale;
|
||||
HeightRequest = 64 * Scale;
|
||||
}
|
||||
|
||||
// Element collection change handler
|
||||
private static void OnElementsChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var canvas = (OledPreviewCanvas)bindable;
|
||||
|
||||
// If old value is INotifyCollectionChanged, unsubscribe
|
||||
if (oldValue is INotifyCollectionChanged oldCollection)
|
||||
{
|
||||
oldCollection.CollectionChanged -= canvas.OnCollectionChanged;
|
||||
}
|
||||
|
||||
// If new value is INotifyCollectionChanged, subscribe
|
||||
if (newValue is INotifyCollectionChanged newCollection)
|
||||
{
|
||||
newCollection.CollectionChanged += canvas.OnCollectionChanged;
|
||||
}
|
||||
|
||||
// Invalidate the canvas to redraw
|
||||
canvas.Invalidate();
|
||||
}
|
||||
|
||||
// Selected element change handler
|
||||
private static void OnSelectedElementChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var canvas = (OledPreviewCanvas)bindable;
|
||||
canvas.Invalidate();
|
||||
}
|
||||
|
||||
// Scale change handler
|
||||
private static void OnScaleChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var canvas = (OledPreviewCanvas)bindable;
|
||||
float scale = (float)newValue;
|
||||
|
||||
// Update the size of the canvas based on the scale
|
||||
canvas.WidthRequest = canvas.Width * scale;
|
||||
canvas.HeightRequest = canvas.Height * scale;
|
||||
|
||||
canvas.Invalidate();
|
||||
}
|
||||
|
||||
// Collection changed event handler
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// Interaction handlers for element selection and manipulation
|
||||
private OledElement draggedElement;
|
||||
private Point dragStartPoint;
|
||||
|
||||
private void OnStartInteraction(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (!IsEditable) return;
|
||||
|
||||
var point = e.Touches[0];
|
||||
dragStartPoint = point;
|
||||
|
||||
// Check if an element was clicked
|
||||
if (Elements != null)
|
||||
{
|
||||
// Need to adjust for scale
|
||||
float x = (float)point.X / Scale;
|
||||
float y = (float)point.Y / Scale;
|
||||
|
||||
foreach (var element in Elements)
|
||||
{
|
||||
if (element is TextElement textElement)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
|
||||
private void OnEndInteraction(object sender, TouchEventArgs e)
|
||||
{
|
||||
draggedElement = null;
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
private OledElement FindOledElementForPreviewElement(PreviewElement previewElement)
|
||||
{
|
||||
var viewModel = BindingContext as OledConfigViewModel;
|
||||
if (viewModel == null || viewModel.OledElements == null) return null;
|
||||
|
||||
foreach (var oledElement in viewModel.OledElements)
|
||||
{
|
||||
// Match based on position and type
|
||||
if (previewElement is TextElement textElement && oledElement.Type == "text")
|
||||
{
|
||||
if (oledElement.X == textElement.X && oledElement.Y == textElement.Y)
|
||||
{
|
||||
return oledElement;
|
||||
}
|
||||
}
|
||||
else if (previewElement is BarElement barElement && oledElement.Type == "bar")
|
||||
{
|
||||
if (oledElement.X == barElement.X && oledElement.Y == barElement.Y)
|
||||
{
|
||||
return oledElement;
|
||||
}
|
||||
}
|
||||
else if (previewElement is RectElement rectElement)
|
||||
{
|
||||
if ((oledElement.Type == "rect" || oledElement.Type == "box") &&
|
||||
oledElement.X == rectElement.X && oledElement.Y == rectElement.Y)
|
||||
{
|
||||
return oledElement;
|
||||
}
|
||||
}
|
||||
else if (previewElement is LineElement lineElement && oledElement.Type == "line")
|
||||
{
|
||||
if (oledElement.X == lineElement.X1 && oledElement.Y == lineElement.Y1)
|
||||
{
|
||||
return oledElement;
|
||||
}
|
||||
}
|
||||
else if (previewElement is IconElement iconElement && oledElement.Type == "icon")
|
||||
{
|
||||
if (oledElement.X == iconElement.X && oledElement.Y == iconElement.Y)
|
||||
{
|
||||
return oledElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private float DistancePointToLine(float px, float py, float x1, float y1, float x2, float y2)
|
||||
{
|
||||
float lineLength = (float)Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2));
|
||||
if (lineLength == 0) return (float)Math.Sqrt(Math.Pow(px - x1, 2) + Math.Pow(py - y1, 2));
|
||||
|
||||
float t = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / (lineLength * lineLength);
|
||||
t = Math.Max(0, Math.Min(1, t));
|
||||
|
||||
float projX = x1 + t * (x2 - x1);
|
||||
float projY = y1 + t * (y2 - y1);
|
||||
|
||||
return (float)Math.Sqrt(Math.Pow(px - projX, 2) + Math.Pow(py - projY, 2));
|
||||
}
|
||||
}
|
||||
|
||||
// The drawable that renders the OLED canvas
|
||||
public class OledCanvasDrawable : IDrawable
|
||||
{
|
||||
private readonly OledPreviewCanvas _canvas;
|
||||
|
||||
public OledCanvasDrawable(OledPreviewCanvas canvas)
|
||||
{
|
||||
_canvas = canvas;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
canvas.StrokeColor = new Color(64, 64, 64, 64); // Semi-transparent gray
|
||||
canvas.StrokeSize = 1;
|
||||
|
||||
// Draw vertical grid lines
|
||||
for (int x = 0; x <= _canvas.Width; x += 10)
|
||||
{
|
||||
canvas.DrawLine(x * scale, 0, x * scale, _canvas.Height * scale);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawElement(ICanvas canvas, PreviewElement element, float scale, bool isSelected)
|
||||
{
|
||||
// Set selection highlighting if needed
|
||||
if (isSelected)
|
||||
{
|
||||
canvas.StrokeColor = Colors.Cyan;
|
||||
canvas.StrokeSize = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
canvas.StrokeColor = Colors.White;
|
||||
canvas.StrokeSize = 1;
|
||||
}
|
||||
|
||||
canvas.FillColor = Colors.White;
|
||||
|
||||
if (element is TextElement textElement)
|
||||
{
|
||||
float fontSize;
|
||||
switch (textElement.Size)
|
||||
{
|
||||
case 1: fontSize = 8 * scale; break;
|
||||
case 2: fontSize = 12 * scale; break;
|
||||
case 3: fontSize = 16 * scale; break;
|
||||
default: fontSize = 8 * scale; break;
|
||||
}
|
||||
|
||||
canvas.FontSize = fontSize;
|
||||
canvas.FontColor = Colors.White;
|
||||
canvas.DrawString(
|
||||
textElement.Text,
|
||||
textElement.X * scale,
|
||||
textElement.Y * scale,
|
||||
HorizontalAlignment.Left);
|
||||
|
||||
// Draw selection indicator for text elements
|
||||
if (isSelected)
|
||||
{
|
||||
var metrics = canvas.GetStringSize(textElement.Text, Microsoft.Maui.Graphics.Font.Default, fontSize);
|
||||
canvas.DrawRectangle(
|
||||
textElement.X * scale - 2,
|
||||
textElement.Y * scale - metrics.Height - 2,
|
||||
metrics.Width + 4,
|
||||
metrics.Height + 4);
|
||||
}
|
||||
}
|
||||
else if (element is BarElement barElement)
|
||||
{
|
||||
// Draw outline
|
||||
canvas.DrawRectangle(
|
||||
barElement.X * scale,
|
||||
barElement.Y * scale,
|
||||
barElement.Width * scale,
|
||||
barElement.Height * scale);
|
||||
|
||||
// Draw fill based on value
|
||||
int fillWidth = (int)(barElement.Width * (barElement.Value / 100.0));
|
||||
if (fillWidth > 0)
|
||||
{
|
||||
canvas.FillRectangle(
|
||||
(barElement.X + 1) * scale,
|
||||
(barElement.Y + 1) * scale,
|
||||
(fillWidth - 1) * scale,
|
||||
(barElement.Height - 2) * scale);
|
||||
}
|
||||
|
||||
// Draw selection indicator
|
||||
if (isSelected)
|
||||
{
|
||||
canvas.StrokeColor = Colors.Cyan;
|
||||
canvas.DrawRectangle(
|
||||
(barElement.X - 2) * scale,
|
||||
(barElement.Y - 2) * scale,
|
||||
(barElement.Width + 4) * scale,
|
||||
(barElement.Height + 4) * scale);
|
||||
}
|
||||
}
|
||||
else if (element is RectElement rectElement)
|
||||
{
|
||||
if (rectElement.Filled)
|
||||
{
|
||||
// Filled box
|
||||
canvas.FillRectangle(
|
||||
rectElement.X * scale,
|
||||
rectElement.Y * scale,
|
||||
rectElement.Width * scale,
|
||||
rectElement.Height * scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outline rectangle
|
||||
canvas.DrawRectangle(
|
||||
rectElement.X * scale,
|
||||
rectElement.Y * scale,
|
||||
rectElement.Width * scale,
|
||||
rectElement.Height * scale);
|
||||
}
|
||||
|
||||
// Draw selection indicator
|
||||
if (isSelected)
|
||||
{
|
||||
canvas.StrokeColor = Colors.Cyan;
|
||||
canvas.DrawRectangle(
|
||||
(rectElement.X - 2) * scale,
|
||||
(rectElement.Y - 2) * scale,
|
||||
(rectElement.Width + 4) * scale,
|
||||
(rectElement.Height + 4) * scale);
|
||||
}
|
||||
}
|
||||
else if (element is LineElement lineElement)
|
||||
{
|
||||
canvas.DrawLine(
|
||||
lineElement.X1 * scale,
|
||||
lineElement.Y1 * scale,
|
||||
lineElement.X2 * scale,
|
||||
lineElement.Y2 * scale);
|
||||
|
||||
// Draw selection indicator
|
||||
if (isSelected)
|
||||
{
|
||||
canvas.StrokeColor = Colors.Cyan;
|
||||
canvas.StrokeSize = 3;
|
||||
canvas.DrawLine(
|
||||
lineElement.X1 * scale,
|
||||
lineElement.Y1 * scale,
|
||||
lineElement.X2 * scale,
|
||||
lineElement.Y2 * scale);
|
||||
|
||||
// Draw endpoints
|
||||
canvas.FillCircle(lineElement.X1 * scale, lineElement.Y1 * scale, 4);
|
||||
canvas.FillCircle(lineElement.X2 * scale, lineElement.Y2 * scale, 4);
|
||||
}
|
||||
}
|
||||
else if (element is IconElement iconElement)
|
||||
{
|
||||
// Draw a placeholder for the icon
|
||||
canvas.DrawRectangle(
|
||||
iconElement.X * scale,
|
||||
iconElement.Y * scale,
|
||||
24 * scale,
|
||||
24 * scale);
|
||||
|
||||
// Draw icon name as text
|
||||
canvas.FontSize = 8 * scale;
|
||||
canvas.DrawString(
|
||||
iconElement.Name,
|
||||
(iconElement.X + 2) * scale,
|
||||
(iconElement.Y + 12) * scale,
|
||||
HorizontalAlignment.Left);
|
||||
|
||||
// Draw selection indicator
|
||||
if (isSelected)
|
||||
{
|
||||
canvas.StrokeColor = Colors.Cyan;
|
||||
canvas.DrawRectangle(
|
||||
(iconElement.X - 2) * scale,
|
||||
(iconElement.Y - 2) * scale,
|
||||
(24 + 4) * scale,
|
||||
(24 + 4) * scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
PCPal/Configurator/MainPage.xaml
Normal file
36
PCPal/Configurator/MainPage.xaml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Configurator.MainPage">
|
||||
|
||||
<ScrollView>
|
||||
<VerticalStackLayout
|
||||
Padding="30,0"
|
||||
Spacing="25">
|
||||
<Image
|
||||
Source="dotnet_bot.png"
|
||||
HeightRequest="185"
|
||||
Aspect="AspectFit"
|
||||
SemanticProperties.Description="dot net bot in a race car number eight" />
|
||||
|
||||
<Label
|
||||
Text="Hello, World!"
|
||||
Style="{StaticResource Headline}"
|
||||
SemanticProperties.HeadingLevel="Level1" />
|
||||
|
||||
<Label
|
||||
Text="Welcome to .NET Multi-platform App UI"
|
||||
Style="{StaticResource SubHeadline}"
|
||||
SemanticProperties.HeadingLevel="Level2"
|
||||
SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
|
||||
|
||||
<Button
|
||||
x:Name="CounterBtn"
|
||||
Text="Click me"
|
||||
SemanticProperties.Hint="Counts the number of times you click"
|
||||
Clicked="OnCounterClicked"
|
||||
HorizontalOptions="Fill" />
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
</ContentPage>
|
||||
25
PCPal/Configurator/MainPage.xaml.cs
Normal file
25
PCPal/Configurator/MainPage.xaml.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Configurator
|
||||
{
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnCounterClicked(object sender, EventArgs e)
|
||||
{
|
||||
count++;
|
||||
|
||||
if (count == 1)
|
||||
CounterBtn.Text = $"Clicked {count} time";
|
||||
else
|
||||
CounterBtn.Text = $"Clicked {count} times";
|
||||
|
||||
SemanticScreenReader.Announce(CounterBtn.Text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
61
PCPal/Configurator/MauiProgram.cs
Normal file
61
PCPal/Configurator/MauiProgram.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PCPal.Core.Services;
|
||||
using PCPal.Configurator.ViewModels;
|
||||
using PCPal.Configurator.Views;
|
||||
using PCPal.Configurator.Views.LCD;
|
||||
using PCPal.Configurator.Views.OLED;
|
||||
using PCPal.Configurator.Views.TFT;
|
||||
using PCPal.Configurator.Converters;
|
||||
|
||||
namespace PCPal.Configurator;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
fonts.AddFont("Consolas.ttf", "Consolas");
|
||||
});
|
||||
|
||||
// Register services
|
||||
builder.Services.AddSingleton<ISensorService, SensorService>();
|
||||
builder.Services.AddSingleton<ISerialPortService, SerialPortService>();
|
||||
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||
|
||||
// Register views and view models
|
||||
// LCD
|
||||
builder.Services.AddTransient<LcdConfigView>();
|
||||
builder.Services.AddTransient<LcdConfigViewModel>();
|
||||
|
||||
// OLED
|
||||
builder.Services.AddTransient<OledConfigView>();
|
||||
builder.Services.AddTransient<OledConfigViewModel>();
|
||||
builder.Services.AddTransient<OledVisualEditorView>();
|
||||
builder.Services.AddTransient<OledMarkupEditorView>();
|
||||
builder.Services.AddTransient<OledTemplatesView>();
|
||||
|
||||
// TFT
|
||||
builder.Services.AddTransient<TftConfigView>();
|
||||
builder.Services.AddTransient<TftConfigViewModel>();
|
||||
|
||||
// Settings
|
||||
builder.Services.AddTransient<SettingsView>();
|
||||
builder.Services.AddTransient<SettingsViewModel>();
|
||||
|
||||
// Help
|
||||
builder.Services.AddTransient<HelpView>();
|
||||
builder.Services.AddTransient<HelpViewModel>();
|
||||
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
6
PCPal/Configurator/Platforms/Android/AndroidManifest.xml
Normal file
6
PCPal/Configurator/Platforms/Android/AndroidManifest.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
10
PCPal/Configurator/Platforms/Android/MainActivity.cs
Normal file
10
PCPal/Configurator/Platforms/Android/MainActivity.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace Configurator;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
||||
16
PCPal/Configurator/Platforms/Android/MainApplication.cs
Normal file
16
PCPal/Configurator/Platforms/Android/MainApplication.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Android.App;
|
||||
using Android.Runtime;
|
||||
using PCPal.Configurator;
|
||||
|
||||
namespace Configurator;
|
||||
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#512BD4</color>
|
||||
<color name="colorPrimaryDark">#2B0B98</color>
|
||||
<color name="colorAccent">#2B0B98</color>
|
||||
</resources>
|
||||
10
PCPal/Configurator/Platforms/MacCatalyst/AppDelegate.cs
Normal file
10
PCPal/Configurator/Platforms/MacCatalyst/AppDelegate.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Foundation;
|
||||
using PCPal.Configurator;
|
||||
|
||||
namespace Configurator;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
14
PCPal/Configurator/Platforms/MacCatalyst/Entitlements.plist
Normal file
14
PCPal/Configurator/Platforms/MacCatalyst/Entitlements.plist
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
|
||||
<dict>
|
||||
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
38
PCPal/Configurator/Platforms/MacCatalyst/Info.plist
Normal file
38
PCPal/Configurator/Platforms/MacCatalyst/Info.plist
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- The Mac App Store requires you specify if the app uses encryption. -->
|
||||
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
|
||||
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
|
||||
<!-- Please indicate <true/> or <false/> here. -->
|
||||
|
||||
<!-- Specify the category for your app here. -->
|
||||
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
|
||||
<!-- <key>LSApplicationCategoryType</key> -->
|
||||
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
||||
15
PCPal/Configurator/Platforms/MacCatalyst/Program.cs
Normal file
15
PCPal/Configurator/Platforms/MacCatalyst/Program.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Configurator;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
16
PCPal/Configurator/Platforms/Tizen/Main.cs
Normal file
16
PCPal/Configurator/Platforms/Tizen/Main.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Hosting;
|
||||
|
||||
namespace Configurator;
|
||||
|
||||
class Program : MauiApplication
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var app = new Program();
|
||||
app.Run(args);
|
||||
}
|
||||
}
|
||||
15
PCPal/Configurator/Platforms/Tizen/tizen-manifest.xml
Normal file
15
PCPal/Configurator/Platforms/Tizen/tizen-manifest.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="8" xmlns="http://tizen.org/ns/packages">
|
||||
<profile name="common" />
|
||||
<ui-application appid="maui-application-id-placeholder" exec="Configurator.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||
<label>maui-application-title-placeholder</label>
|
||||
<icon>maui-appicon-placeholder</icon>
|
||||
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
||||
</ui-application>
|
||||
<shortcut-list />
|
||||
<privileges>
|
||||
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||
</privileges>
|
||||
<dependencies />
|
||||
<provides-appdefined-privileges />
|
||||
</manifest>
|
||||
8
PCPal/Configurator/Platforms/Windows/App.xaml
Normal file
8
PCPal/Configurator/Platforms/Windows/App.xaml
Normal file
@@ -0,0 +1,8 @@
|
||||
<maui:MauiWinUIApplication
|
||||
x:Class="Configurator.WinUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:maui="using:Microsoft.Maui"
|
||||
xmlns:local="using:Configurator.WinUI">
|
||||
|
||||
</maui:MauiWinUIApplication>
|
||||
25
PCPal/Configurator/Platforms/Windows/App.xaml.cs
Normal file
25
PCPal/Configurator/Platforms/Windows/App.xaml.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using PCPal.Configurator;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace Configurator.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : MauiWinUIApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
|
||||
46
PCPal/Configurator/Platforms/Windows/Package.appxmanifest
Normal file
46
PCPal/Configurator/Platforms/Windows/Package.appxmanifest
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="7F379C91-BED0-4B10-9F4B-D5675708EFA8" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>$placeholder$</DisplayName>
|
||||
<PublisherDisplayName>User Name</PublisherDisplayName>
|
||||
<Logo>$placeholder$.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="$placeholder$"
|
||||
Description="$placeholder$"
|
||||
Square150x150Logo="$placeholder$.png"
|
||||
Square44x44Logo="$placeholder$.png"
|
||||
BackgroundColor="transparent">
|
||||
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||
<uap:SplashScreen Image="$placeholder$.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
|
||||
</Package>
|
||||
15
PCPal/Configurator/Platforms/Windows/app.manifest
Normal file
15
PCPal/Configurator/Platforms/Windows/app.manifest
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="Configurator.WinUI.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
10
PCPal/Configurator/Platforms/iOS/AppDelegate.cs
Normal file
10
PCPal/Configurator/Platforms/iOS/AppDelegate.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Foundation;
|
||||
using PCPal.Configurator;
|
||||
|
||||
namespace Configurator;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
32
PCPal/Configurator/Platforms/iOS/Info.plist
Normal file
32
PCPal/Configurator/Platforms/iOS/Info.plist
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
||||
15
PCPal/Configurator/Platforms/iOS/Program.cs
Normal file
15
PCPal/Configurator/Platforms/iOS/Program.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Configurator;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
|
||||
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
|
||||
|
||||
You are responsible for adding extra entries as needed for your application.
|
||||
|
||||
More information: https://aka.ms/maui-privacy-manifest
|
||||
-->
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<!--
|
||||
The entry below is only needed when you're using the Preferences API in your app.
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict> -->
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
8
PCPal/Configurator/Properties/launchSettings.json
Normal file
8
PCPal/Configurator/Properties/launchSettings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
||||
4
PCPal/Configurator/Resources/AppIcon/appicon.svg
Normal file
4
PCPal/Configurator/Resources/AppIcon/appicon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 228 B |
8
PCPal/Configurator/Resources/AppIcon/appiconfg.svg
Normal file
8
PCPal/Configurator/Resources/AppIcon/appiconfg.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
PCPal/Configurator/Resources/Fonts/OpenSans-Regular.ttf
Normal file
BIN
PCPal/Configurator/Resources/Fonts/OpenSans-Regular.ttf
Normal file
Binary file not shown.
BIN
PCPal/Configurator/Resources/Fonts/OpenSans-Semibold.ttf
Normal file
BIN
PCPal/Configurator/Resources/Fonts/OpenSans-Semibold.ttf
Normal file
Binary file not shown.
BIN
PCPal/Configurator/Resources/Images/dotnet_bot.png
Normal file
BIN
PCPal/Configurator/Resources/Images/dotnet_bot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
15
PCPal/Configurator/Resources/Raw/AboutAssets.txt
Normal file
15
PCPal/Configurator/Resources/Raw/AboutAssets.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
Any raw assets you want to be deployed with your application can be placed in
|
||||
this directory (and child directories). Deployment of the asset to your application
|
||||
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
|
||||
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
|
||||
These files will be deployed with your package and will be accessible using Essentials:
|
||||
|
||||
async Task LoadMauiAsset()
|
||||
{
|
||||
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
var contents = reader.ReadToEnd();
|
||||
}
|
||||
8
PCPal/Configurator/Resources/Splash/splash.svg
Normal file
8
PCPal/Configurator/Resources/Splash/splash.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
45
PCPal/Configurator/Resources/Styles/Colors.xaml
Normal file
45
PCPal/Configurator/Resources/Styles/Colors.xaml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<!-- 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="Tertiary">#2B0B98</Color>
|
||||
|
||||
<Color x:Key="White">White</Color>
|
||||
<Color x:Key="Black">Black</Color>
|
||||
<Color x:Key="Magenta">#D600AA</Color>
|
||||
<Color x:Key="MidnightBlue">#190649</Color>
|
||||
<Color x:Key="OffBlack">#1f1f1f</Color>
|
||||
|
||||
<Color x:Key="Gray100">#E1E1E1</Color>
|
||||
<Color x:Key="Gray200">#C8C8C8</Color>
|
||||
<Color x:Key="Gray300">#ACACAC</Color>
|
||||
<Color x:Key="Gray400">#919191</Color>
|
||||
<Color x:Key="Gray500">#6E6E6E</Color>
|
||||
<Color x:Key="Gray600">#404040</Color>
|
||||
<Color x:Key="Gray900">#212121</Color>
|
||||
<Color x:Key="Gray950">#141414</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
||||
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
||||
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
||||
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
||||
|
||||
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
||||
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
||||
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
||||
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
||||
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
||||
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
||||
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
||||
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
||||
</ResourceDictionary>
|
||||
18
PCPal/Configurator/Resources/Styles/Converters.xaml
Normal file
18
PCPal/Configurator/Resources/Styles/Converters.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:converters="clr-namespace:PCPal.Configurator.Converters">
|
||||
|
||||
<!-- Value Converters -->
|
||||
<converters:BoolToColorConverter x:Key="BoolToColorConverter" />
|
||||
<converters:BoolToTextColorConverter x:Key="BoolToTextColorConverter" />
|
||||
<converters:ConnectionStatusColorConverter x:Key="ConnectionStatusColorConverter" />
|
||||
<converters:StringMatchConverter x:Key="StringMatchConverter" />
|
||||
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||
<converters:MenuIconConverter x:Key="MenuIconConverter" />
|
||||
<converters:SelectedItemColorConverter x:Key="SelectedItemColorConverter" />
|
||||
<converters:SelectedTextColorConverter x:Key="SelectedTextColorConverter" />
|
||||
<converters:BytesToSizeConverter x:Key="BytesToSizeConverter" />
|
||||
<converters:RelativeTimeConverter x:Key="RelativeTimeConverter" />
|
||||
|
||||
</ResourceDictionary>
|
||||
299
PCPal/Configurator/Resources/Styles/Converters.xaml.cs
Normal file
299
PCPal/Configurator/Resources/Styles/Converters.xaml.cs
Normal file
@@ -0,0 +1,299 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace PCPal.Configurator.Converters;
|
||||
|
||||
// Converts a boolean to a color (used for selected tabs)
|
||||
public class BoolToColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isSelected && isSelected)
|
||||
{
|
||||
// Return the parameter color if true (selected)
|
||||
if (parameter is Color color)
|
||||
{
|
||||
return color;
|
||||
}
|
||||
return Application.Current.Resources["Primary"] as Color ?? Colors.Transparent;
|
||||
}
|
||||
|
||||
// Return transparent if false (not selected)
|
||||
return Colors.Transparent;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a boolean to a text color (used for selected tabs)
|
||||
public class BoolToTextColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isSelected && isSelected)
|
||||
{
|
||||
// Return white for selected items
|
||||
if (parameter is Color color)
|
||||
{
|
||||
return color;
|
||||
}
|
||||
return Colors.White;
|
||||
}
|
||||
|
||||
// Return gray for non-selected items
|
||||
return Color.FromArgb("#555555");
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Converts connection status to a color
|
||||
public class ConnectionStatusColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isConnected && isConnected)
|
||||
{
|
||||
// Green for connected
|
||||
return Colors.Green;
|
||||
}
|
||||
|
||||
// Red for disconnected
|
||||
return Colors.Red;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a string to a color based on matching
|
||||
public class StringMatchConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
// For matching menu items
|
||||
if (value is string currentValue && parameter is string targetValue)
|
||||
{
|
||||
if (currentValue == targetValue)
|
||||
{
|
||||
// Handle different return types based on an additional parameter
|
||||
if (parameter is string param2 && param2 == "TextColor")
|
||||
{
|
||||
// Return text color for selected item
|
||||
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 default values
|
||||
if (parameter is string param && param == "TextColor")
|
||||
{
|
||||
return Application.Current.Resources["TextSecondary"] as Color ?? Colors.Gray;
|
||||
}
|
||||
|
||||
// Default background is transparent
|
||||
return Colors.Transparent;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Inverts a boolean
|
||||
public class InverseBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return !boolValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return !boolValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a menu name to an icon
|
||||
public class MenuIconConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string menuItem)
|
||||
{
|
||||
return menuItem switch
|
||||
{
|
||||
"1602 LCD Display" => "icon_lcd.png",
|
||||
"4.6\" TFT Display" => "icon_tft.png",
|
||||
"OLED Display" => "icon_oled.png",
|
||||
"Settings" => "icon_settings.png",
|
||||
"Help" => "icon_help.png",
|
||||
_ => "icon_default.png"
|
||||
};
|
||||
}
|
||||
|
||||
return "icon_default.png";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Converts selected item to background color
|
||||
public class SelectedItemColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isSelected && isSelected)
|
||||
{
|
||||
if (parameter is Color color)
|
||||
{
|
||||
return color;
|
||||
}
|
||||
return Application.Current.Resources["PrimaryLight"] as Color ?? Colors.LightBlue;
|
||||
}
|
||||
|
||||
return Colors.Transparent;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Converts selected item to text color
|
||||
public class SelectedTextColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isSelected && isSelected)
|
||||
{
|
||||
if (parameter is Color color)
|
||||
{
|
||||
return color;
|
||||
}
|
||||
return Application.Current.Resources["Primary"] as Color ?? Colors.Blue;
|
||||
}
|
||||
|
||||
return Application.Current.Resources["TextSecondary"] as Color ?? Colors.Gray;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Formats bytes to human-readable size
|
||||
public class BytesToSizeConverter : IValueConverter
|
||||
{
|
||||
private static readonly string[] SizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is long size)
|
||||
{
|
||||
return BytesToString(size);
|
||||
}
|
||||
|
||||
return "0 B";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static string BytesToString(long value, int decimalPlaces = 1)
|
||||
{
|
||||
if (value < 0) { return "-" + BytesToString(-value, decimalPlaces); }
|
||||
|
||||
int i = 0;
|
||||
decimal dValue = value;
|
||||
while (Math.Round(dValue, decimalPlaces) >= 1000)
|
||||
{
|
||||
dValue /= 1024;
|
||||
i++;
|
||||
}
|
||||
|
||||
return string.Format("{0:n" + decimalPlaces + "} {1}", dValue, SizeSuffixes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a date/time to relative time (e.g., "just now", "5 minutes ago")
|
||||
public class RelativeTimeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is DateTime dateTime)
|
||||
{
|
||||
var elapsed = DateTime.Now - dateTime;
|
||||
|
||||
if (elapsed.TotalSeconds < 60)
|
||||
return "just now";
|
||||
if (elapsed.TotalMinutes < 60)
|
||||
return $"{Math.Floor(elapsed.TotalMinutes)} minutes ago";
|
||||
if (elapsed.TotalHours < 24)
|
||||
return $"{Math.Floor(elapsed.TotalHours)} hours ago";
|
||||
if (elapsed.TotalDays < 7)
|
||||
return $"{Math.Floor(elapsed.TotalDays)} days ago";
|
||||
|
||||
return dateTime.ToString("g");
|
||||
}
|
||||
|
||||
return "unknown time";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Add this new converter for text color specifically
|
||||
public class StringMatchTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string currentValue && parameter is string targetValue)
|
||||
{
|
||||
if (currentValue == targetValue)
|
||||
{
|
||||
// Return text color for selected item
|
||||
return Application.Current.Resources["Primary"] as Color ?? Colors.Blue;
|
||||
}
|
||||
}
|
||||
|
||||
// Return default text color
|
||||
return Application.Current.Resources["TextSecondary"] as Color ?? Colors.Gray;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
119
PCPal/Configurator/Resources/Styles/Styles.xaml
Normal file
119
PCPal/Configurator/Resources/Styles/Styles.xaml
Normal file
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<!-- Default Styles -->
|
||||
|
||||
<Style TargetType="ActivityIndicator">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
<Setter Property="Padding" Value="14,10"/>
|
||||
<Setter Property="MinimumHeightRequest" Value="36"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
<Setter Property="MinimumHeightRequest" Value="24"/>
|
||||
<Setter Property="MinimumWidthRequest" Value="24"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Frame">
|
||||
<Setter Property="HasShadow" Value="False" />
|
||||
<Setter Property="BorderColor" Value="{StaticResource BorderColor}" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Entry">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimary}, Dark=White}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light=#AAAAAA, Dark=#777777}" />
|
||||
<Setter Property="MinimumHeightRequest" Value="36"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimary}, Dark=White}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Editor">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimary}, Dark=White}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light=#AAAAAA, Dark=#777777}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Picker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimary}, Dark=White}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="MinimumHeightRequest" Value="36"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ProgressBar">
|
||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RadioButton">
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Surface}, Dark=#333333}"/>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimary}, Dark=White}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Slider">
|
||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Secondary}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Switch">
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Primary}}" />
|
||||
<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">
|
||||
<Setter Property="FontSize" Value="22" />
|
||||
<Setter Property="FontAttributes" Value="Bold" />
|
||||
<Setter Property="TextColor" Value="{StaticResource TextPrimary}" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SubHeaderLabelStyle" TargetType="Label">
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="FontAttributes" Value="Bold" />
|
||||
<Setter Property="TextColor" Value="{StaticResource TextPrimary}" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BodyLabelStyle" TargetType="Label">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="TextColor" Value="{StaticResource TextSecondary}" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SmallLabelStyle" TargetType="Label">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="TextColor" Value="{StaticResource TextSecondary}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
42
PCPal/Configurator/ViewModels/BaseViewModel.cs
Normal file
42
PCPal/Configurator/ViewModels/BaseViewModel.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PCPal.Configurator.ViewModels;
|
||||
|
||||
public abstract class BaseViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private bool _isBusy;
|
||||
private string _title;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// Changed from protected to public so it can be called from outside
|
||||
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(storage, value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
storage = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
49
PCPal/Configurator/ViewModels/HelpViewModel.cs
Normal file
49
PCPal/Configurator/ViewModels/HelpViewModel.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace PCPal.Configurator.ViewModels;
|
||||
|
||||
public class HelpViewModel : BaseViewModel
|
||||
{
|
||||
private string _searchQuery;
|
||||
|
||||
public string SearchQuery
|
||||
{
|
||||
get => _searchQuery;
|
||||
set => SetProperty(ref _searchQuery, value);
|
||||
}
|
||||
|
||||
public ICommand SearchCommand { get; }
|
||||
public ICommand OpenUrlCommand { get; }
|
||||
|
||||
public HelpViewModel()
|
||||
{
|
||||
Title = "Help & Documentation";
|
||||
|
||||
// Initialize commands
|
||||
SearchCommand = new Command<string>(Search);
|
||||
OpenUrlCommand = new Command<string>(OpenUrl);
|
||||
}
|
||||
|
||||
private void Search(string query)
|
||||
{
|
||||
// In a real implementation, this would search through help topics
|
||||
// For now, we'll just update the search query property
|
||||
SearchQuery = query;
|
||||
}
|
||||
|
||||
private async void OpenUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await Browser.OpenAsync(url, BrowserLaunchMode.SystemPreferred);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle error or notify user
|
||||
await Shell.Current.DisplayAlert("Error", $"Cannot open URL: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
383
PCPal/Configurator/ViewModels/LcdConfigViewModel.cs
Normal file
383
PCPal/Configurator/ViewModels/LcdConfigViewModel.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
using PCPal.Core.Services;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace PCPal.Configurator.ViewModels;
|
||||
|
||||
public class LcdConfigViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ISensorService _sensorService;
|
||||
private readonly IConfigurationService _configService;
|
||||
private readonly ISerialPortService _serialPortService;
|
||||
|
||||
private ObservableCollection<string> _sensorOptions = new();
|
||||
private string _line1Selection;
|
||||
private string _line1CustomText;
|
||||
private string _line1PostText;
|
||||
private string _line2Selection;
|
||||
private string _line2CustomText;
|
||||
private string _line2PostText;
|
||||
private string _line1Preview;
|
||||
private string _line2Preview;
|
||||
|
||||
private Timer _previewUpdateTimer;
|
||||
|
||||
public ObservableCollection<string> SensorOptions
|
||||
{
|
||||
get => _sensorOptions;
|
||||
set => SetProperty(ref _sensorOptions, value);
|
||||
}
|
||||
|
||||
public string Line1Selection
|
||||
{
|
||||
get => _line1Selection;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _line1Selection, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(IsLine1CustomTextEnabled));
|
||||
OnPropertyChanged(nameof(IsLine1PostTextEnabled));
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Line1CustomText
|
||||
{
|
||||
get => _line1CustomText;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _line1CustomText, value))
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Line1PostText
|
||||
{
|
||||
get => _line1PostText;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _line1PostText, value))
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Line2Selection
|
||||
{
|
||||
get => _line2Selection;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _line2Selection, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(IsLine2CustomTextEnabled));
|
||||
OnPropertyChanged(nameof(IsLine2PostTextEnabled));
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Line2CustomText
|
||||
{
|
||||
get => _line2CustomText;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _line2CustomText, value))
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Line2PostText
|
||||
{
|
||||
get => _line2PostText;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _line2PostText, value))
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Line1Preview
|
||||
{
|
||||
get => _line1Preview;
|
||||
set => SetProperty(ref _line1Preview, value);
|
||||
}
|
||||
|
||||
public string Line2Preview
|
||||
{
|
||||
get => _line2Preview;
|
||||
set => SetProperty(ref _line2Preview, value);
|
||||
}
|
||||
|
||||
public bool IsLine1CustomTextEnabled => Line1Selection == "Custom Text" || !string.IsNullOrEmpty(Line1Selection);
|
||||
public bool IsLine1PostTextEnabled => Line1Selection != "Custom Text" && !string.IsNullOrEmpty(Line1Selection);
|
||||
public bool IsLine2CustomTextEnabled => Line2Selection == "Custom Text" || !string.IsNullOrEmpty(Line2Selection);
|
||||
public bool IsLine2PostTextEnabled => Line2Selection != "Custom Text" && !string.IsNullOrEmpty(Line2Selection);
|
||||
|
||||
public ICommand SaveConfigCommand { get; }
|
||||
public ICommand TestConnectionCommand { get; }
|
||||
|
||||
public LcdConfigViewModel(
|
||||
ISensorService sensorService,
|
||||
IConfigurationService configService,
|
||||
ISerialPortService serialPortService)
|
||||
{
|
||||
_sensorService = sensorService;
|
||||
_configService = configService;
|
||||
_serialPortService = serialPortService;
|
||||
|
||||
SaveConfigCommand = new Command(async () => await SaveConfigAsync());
|
||||
TestConnectionCommand = new Command(async () => await TestConnectionAsync());
|
||||
|
||||
// Setup timer for preview updates
|
||||
_previewUpdateTimer = new Timer(async (_) => await UpdateSensorDataAsync(), null, Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
try
|
||||
{
|
||||
await _sensorService.UpdateSensorValuesAsync();
|
||||
|
||||
// Load sensor options
|
||||
await LoadSensorOptionsAsync();
|
||||
|
||||
// Load configuration
|
||||
await LoadConfigAsync();
|
||||
|
||||
// Start preview updates
|
||||
_previewUpdateTimer.Change(0, 2000); // Update every 2 seconds
|
||||
|
||||
// Initial preview update
|
||||
await UpdateSensorDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to initialize: {ex.Message}", "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadSensorOptionsAsync()
|
||||
{
|
||||
await _sensorService.UpdateSensorValuesAsync();
|
||||
|
||||
var sensorGroups = _sensorService.GetAllSensorsGrouped();
|
||||
var options = new List<string>();
|
||||
|
||||
foreach (var group in sensorGroups)
|
||||
{
|
||||
foreach (var sensor in group.Sensors)
|
||||
{
|
||||
// Only include load, temperature, and data sensors for simplicity
|
||||
if (sensor.SensorType == LibreHardwareMonitor.Hardware.SensorType.Load ||
|
||||
sensor.SensorType == LibreHardwareMonitor.Hardware.SensorType.Temperature ||
|
||||
sensor.SensorType == LibreHardwareMonitor.Hardware.SensorType.Data)
|
||||
{
|
||||
options.Add($"{group.Type}: {sensor.Name} ({sensor.SensorType})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.Add("Custom Text");
|
||||
|
||||
SensorOptions = new ObservableCollection<string>(options);
|
||||
}
|
||||
|
||||
private async Task LoadConfigAsync()
|
||||
{
|
||||
var config = await _configService.LoadConfigAsync();
|
||||
|
||||
// If config is found, load into UI
|
||||
Line1Selection = config.Line1Selection;
|
||||
Line1CustomText = config.Line1CustomText;
|
||||
Line1PostText = config.Line1PostText;
|
||||
Line2Selection = config.Line2Selection;
|
||||
Line2CustomText = config.Line2CustomText;
|
||||
Line2PostText = config.Line2PostText;
|
||||
|
||||
// Set defaults if not configured
|
||||
if (string.IsNullOrEmpty(Line1Selection) && SensorOptions.Count > 0)
|
||||
{
|
||||
// Try to find a CPU load sensor as default
|
||||
var cpuOption = SensorOptions.FirstOrDefault(s => s.Contains("Cpu") && s.Contains("Load"));
|
||||
Line1Selection = cpuOption ?? SensorOptions.First();
|
||||
Line1CustomText = "CPU ";
|
||||
Line1PostText = "%";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Line2Selection) && SensorOptions.Count > 1)
|
||||
{
|
||||
// Try to find a Memory sensor as default
|
||||
var memoryOption = SensorOptions.FirstOrDefault(s => s.Contains("Memory") && s.Contains("Data"));
|
||||
Line2Selection = memoryOption ?? SensorOptions[1];
|
||||
Line2CustomText = "Memory ";
|
||||
Line2PostText = "GB";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveConfigAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
var config = await _configService.LoadConfigAsync();
|
||||
|
||||
// Update LCD configuration
|
||||
config.ScreenType = "1602";
|
||||
config.Line1Selection = Line1Selection;
|
||||
config.Line1CustomText = Line1CustomText;
|
||||
config.Line1PostText = Line1PostText;
|
||||
config.Line2Selection = Line2Selection;
|
||||
config.Line2CustomText = Line2CustomText;
|
||||
config.Line2PostText = Line2PostText;
|
||||
|
||||
await _configService.SaveConfigAsync(config);
|
||||
|
||||
// Send configuration to device if connected
|
||||
if (_serialPortService.IsConnected)
|
||||
{
|
||||
await _serialPortService.SendCommandAsync($"CMD:LCD,0,{Line1Preview}");
|
||||
await _serialPortService.SendCommandAsync($"CMD:LCD,1,{Line2Preview}");
|
||||
}
|
||||
|
||||
await Shell.Current.DisplayAlert("Success", "Configuration saved successfully!", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to save configuration: {ex.Message}", "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestConnectionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
if (!_serialPortService.IsConnected)
|
||||
{
|
||||
var result = await _serialPortService.ConnectToFirstAvailableAsync();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Connection Failed",
|
||||
"Could not connect to PCPal device. Please check that it's connected to your computer.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Send test message to the LCD
|
||||
await _serialPortService.SendCommandAsync($"CMD:LCD,0,PCPal Test");
|
||||
await _serialPortService.SendCommandAsync($"CMD:LCD,1,Connection OK!");
|
||||
|
||||
await Shell.Current.DisplayAlert("Connection Successful",
|
||||
$"Connected to PCPal device on {_serialPortService.CurrentPort}", "OK");
|
||||
|
||||
// Restore actual content after 3 seconds
|
||||
await Task.Delay(3000);
|
||||
await _serialPortService.SendCommandAsync($"CMD:LCD,0,{Line1Preview}");
|
||||
await _serialPortService.SendCommandAsync($"CMD:LCD,1,{Line2Preview}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Connection test failed: {ex.Message}", "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateSensorDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _sensorService.UpdateSensorValuesAsync();
|
||||
UpdatePreview();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error but don't display to user since this happens in background
|
||||
Debug.WriteLine($"Error updating sensor data: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePreview()
|
||||
{
|
||||
Line1Preview = GetSensorLinePreview(Line1Selection, Line1CustomText, Line1PostText);
|
||||
Line2Preview = GetSensorLinePreview(Line2Selection, Line2CustomText, Line2PostText);
|
||||
}
|
||||
|
||||
private string GetSensorLinePreview(string selection, string prefix, string suffix)
|
||||
{
|
||||
if (string.IsNullOrEmpty(selection))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (selection == "Custom Text")
|
||||
{
|
||||
return prefix ?? string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Parse sensor selection to get hardware type, sensor name and type
|
||||
var parts = selection.Split(':', 2);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
return "Error: Invalid selection";
|
||||
}
|
||||
|
||||
var hardwareTypeStr = parts[0].Trim();
|
||||
var sensorInfo = parts[1].Trim();
|
||||
|
||||
int idx = sensorInfo.LastIndexOf('(');
|
||||
if (idx == -1)
|
||||
{
|
||||
return "Error: Invalid format";
|
||||
}
|
||||
|
||||
string sensorName = sensorInfo.Substring(0, idx).Trim();
|
||||
string sensorTypeStr = sensorInfo.Substring(idx + 1).Trim().TrimEnd(')');
|
||||
|
||||
// Get sensor value
|
||||
var sensorGroups = _sensorService.GetAllSensorsGrouped();
|
||||
var sensor = sensorGroups
|
||||
.SelectMany(g => g.Sensors)
|
||||
.FirstOrDefault(s =>
|
||||
s.SensorType.ToString() == sensorTypeStr &&
|
||||
s.Name == sensorName);
|
||||
|
||||
if (sensor != null)
|
||||
{
|
||||
return $"{prefix ?? ""}{sensor.FormattedValue}{suffix ?? ""}";
|
||||
}
|
||||
|
||||
return "N/A";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Error";
|
||||
}
|
||||
}
|
||||
}
|
||||
1252
PCPal/Configurator/ViewModels/OledConfigViewModel.cs
Normal file
1252
PCPal/Configurator/ViewModels/OledConfigViewModel.cs
Normal file
File diff suppressed because it is too large
Load Diff
445
PCPal/Configurator/ViewModels/SettingsViewModel.cs
Normal file
445
PCPal/Configurator/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,445 @@
|
||||
using PCPal.Core.Services;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Ports;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace PCPal.Configurator.ViewModels;
|
||||
|
||||
public class SettingsViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IConfigurationService _configService;
|
||||
private readonly ISerialPortService _serialPortService;
|
||||
|
||||
private ObservableCollection<string> _availablePorts = new();
|
||||
private string _selectedPort;
|
||||
private bool _isAutoDetectEnabled;
|
||||
private string _connectionStatus;
|
||||
private bool _isConnected;
|
||||
private float _refreshRate;
|
||||
private bool _startWithWindows;
|
||||
private bool _minimizeToTray;
|
||||
private ObservableCollection<string> _availableThemes = new();
|
||||
private string _selectedTheme;
|
||||
private string _serviceStatus;
|
||||
private bool _isServiceRunning;
|
||||
|
||||
public ObservableCollection<string> AvailablePorts
|
||||
{
|
||||
get => _availablePorts;
|
||||
set => SetProperty(ref _availablePorts, value);
|
||||
}
|
||||
|
||||
public string SelectedPort
|
||||
{
|
||||
get => _selectedPort;
|
||||
set => SetProperty(ref _selectedPort, value);
|
||||
}
|
||||
|
||||
public bool IsAutoDetectEnabled
|
||||
{
|
||||
get => _isAutoDetectEnabled;
|
||||
set => SetProperty(ref _isAutoDetectEnabled, value);
|
||||
}
|
||||
|
||||
public string ConnectionStatus
|
||||
{
|
||||
get => _connectionStatus;
|
||||
set => SetProperty(ref _connectionStatus, value);
|
||||
}
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
public float RefreshRate
|
||||
{
|
||||
get => _refreshRate;
|
||||
set => SetProperty(ref _refreshRate, value);
|
||||
}
|
||||
|
||||
public bool StartWithWindows
|
||||
{
|
||||
get => _startWithWindows;
|
||||
set => SetProperty(ref _startWithWindows, value);
|
||||
}
|
||||
|
||||
public bool MinimizeToTray
|
||||
{
|
||||
get => _minimizeToTray;
|
||||
set => SetProperty(ref _minimizeToTray, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<string> AvailableThemes
|
||||
{
|
||||
get => _availableThemes;
|
||||
set => SetProperty(ref _availableThemes, value);
|
||||
}
|
||||
|
||||
public string SelectedTheme
|
||||
{
|
||||
get => _selectedTheme;
|
||||
set => SetProperty(ref _selectedTheme, value);
|
||||
}
|
||||
|
||||
public string ServiceStatus
|
||||
{
|
||||
get => _serviceStatus;
|
||||
set => SetProperty(ref _serviceStatus, value);
|
||||
}
|
||||
|
||||
public bool IsServiceRunning
|
||||
{
|
||||
get => _isServiceRunning;
|
||||
set => SetProperty(ref _isServiceRunning, value);
|
||||
}
|
||||
|
||||
// Commands
|
||||
public ICommand TestConnectionCommand { get; }
|
||||
public ICommand SaveSettingsCommand { get; }
|
||||
public ICommand ExportSettingsCommand { get; }
|
||||
public ICommand ImportSettingsCommand { get; }
|
||||
public ICommand ResetSettingsCommand { get; }
|
||||
public ICommand RefreshServiceStatusCommand { get; }
|
||||
public ICommand StartServiceCommand { get; }
|
||||
public ICommand StopServiceCommand { get; }
|
||||
public ICommand RestartServiceCommand { get; }
|
||||
public ICommand CheckForUpdatesCommand { get; }
|
||||
|
||||
public SettingsViewModel(IConfigurationService configService, ISerialPortService serialPortService)
|
||||
{
|
||||
Title = "Settings";
|
||||
|
||||
_configService = configService;
|
||||
_serialPortService = serialPortService;
|
||||
|
||||
// Default values
|
||||
IsConnected = false;
|
||||
ConnectionStatus = "Not connected";
|
||||
RefreshRate = 5.0f;
|
||||
IsAutoDetectEnabled = true;
|
||||
ServiceStatus = "Unknown";
|
||||
IsServiceRunning = false;
|
||||
|
||||
// Initialize themes
|
||||
AvailableThemes = new ObservableCollection<string>
|
||||
{
|
||||
"System Default",
|
||||
"Light",
|
||||
"Dark"
|
||||
};
|
||||
SelectedTheme = "System Default";
|
||||
|
||||
// Initialize commands
|
||||
TestConnectionCommand = new Command(async () => await TestConnectionAsync());
|
||||
SaveSettingsCommand = new Command(async () => await SaveSettingsAsync());
|
||||
ExportSettingsCommand = new Command(async () => await ExportSettingsAsync());
|
||||
ImportSettingsCommand = new Command(async () => await ImportSettingsAsync());
|
||||
ResetSettingsCommand = new Command(async () => await ResetSettingsAsync());
|
||||
RefreshServiceStatusCommand = new Command(async () => await RefreshServiceStatusAsync());
|
||||
StartServiceCommand = new Command(async () => await StartServiceAsync());
|
||||
StopServiceCommand = new Command(async () => await StopServiceAsync());
|
||||
RestartServiceCommand = new Command(async () => await RestartServiceAsync());
|
||||
CheckForUpdatesCommand = new Command(async () => await CheckForUpdatesAsync());
|
||||
|
||||
// Subscribe to serial port connection changes
|
||||
_serialPortService.ConnectionStatusChanged += OnConnectionStatusChanged;
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
try
|
||||
{
|
||||
// Load available ports
|
||||
await RefreshPortsAsync();
|
||||
|
||||
// Load settings
|
||||
await LoadSettingsAsync();
|
||||
|
||||
// Check service status
|
||||
await RefreshServiceStatusAsync();
|
||||
|
||||
// Check connection status
|
||||
await _serialPortService.CheckConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to initialize settings: {ex.Message}", "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshPortsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ports = SerialPort.GetPortNames();
|
||||
AvailablePorts = new ObservableCollection<string>(ports);
|
||||
|
||||
if (AvailablePorts.Count > 0 && string.IsNullOrEmpty(SelectedPort))
|
||||
{
|
||||
SelectedPort = AvailablePorts[0];
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error refreshing ports: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = await _configService.LoadConfigAsync();
|
||||
|
||||
// Port settings
|
||||
if (!string.IsNullOrEmpty(config.LastUsedPort) && AvailablePorts.Contains(config.LastUsedPort))
|
||||
{
|
||||
SelectedPort = config.LastUsedPort;
|
||||
}
|
||||
|
||||
// Other settings (these would be in extended config in a real implementation)
|
||||
// For now, we're using default values
|
||||
RefreshRate = 5.0f;
|
||||
StartWithWindows = false;
|
||||
MinimizeToTray = true;
|
||||
SelectedTheme = "System Default";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error loading settings: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestConnectionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
bool success;
|
||||
if (IsAutoDetectEnabled)
|
||||
{
|
||||
success = await _serialPortService.ConnectToFirstAvailableAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
success = await _serialPortService.ConnectAsync(SelectedPort);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Connection Test",
|
||||
$"Successfully connected to PCPal device on {_serialPortService.CurrentPort}", "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Connection Test",
|
||||
"Failed to connect to PCPal device. Please check your connection and try again.", "OK");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Connection test failed: {ex.Message}", "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
var config = await _configService.LoadConfigAsync();
|
||||
|
||||
// Update settings
|
||||
config.LastUsedPort = IsAutoDetectEnabled ? null : SelectedPort;
|
||||
|
||||
// Save settings
|
||||
await _configService.SaveConfigAsync(config);
|
||||
|
||||
// In a real implementation, we would also:
|
||||
// - Save refresh rate to the service configuration
|
||||
// - Configure Windows startup settings
|
||||
// - Configure minimize to tray behavior
|
||||
// - Set the application theme
|
||||
|
||||
await Shell.Current.DisplayAlert("Success", "Settings saved successfully!", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to save settings: {ex.Message}", "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// In a real implementation, we would:
|
||||
// - Show a file picker dialog
|
||||
// - Export settings to the selected file
|
||||
|
||||
await Shell.Current.DisplayAlert("Export", "This feature is not implemented in the demo.", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to export settings: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ImportSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// In a real implementation, we would:
|
||||
// - Show a file picker dialog
|
||||
// - Import settings from the selected file
|
||||
// - Apply the imported settings
|
||||
|
||||
await Shell.Current.DisplayAlert("Import", "This feature is not implemented in the demo.", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to import settings: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool confirm = await Shell.Current.DisplayAlert("Reset Settings",
|
||||
"Are you sure you want to reset all settings to default? This cannot be undone.",
|
||||
"Reset", "Cancel");
|
||||
|
||||
if (!confirm)
|
||||
return;
|
||||
|
||||
var config = new PCPal.Core.Models.DisplayConfig();
|
||||
await _configService.SaveConfigAsync(config);
|
||||
|
||||
// Reload settings
|
||||
await LoadSettingsAsync();
|
||||
|
||||
await Shell.Current.DisplayAlert("Success", "Settings have been reset to default.", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to reset settings: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshServiceStatusAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// In a real implementation, we would check the Windows Service status
|
||||
// For now, we'll just simulate it
|
||||
var random = new Random();
|
||||
IsServiceRunning = random.Next(2) == 1; // 50% chance of running
|
||||
|
||||
ServiceStatus = IsServiceRunning ? "Running" : "Stopped";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServiceStatus = "Error checking status";
|
||||
Debug.WriteLine($"Error checking service status: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// In a real implementation, we would start the Windows Service
|
||||
// For now, we'll just simulate it
|
||||
await Task.Delay(500); // Simulate some delay
|
||||
|
||||
IsServiceRunning = true;
|
||||
ServiceStatus = "Running";
|
||||
|
||||
await Shell.Current.DisplayAlert("Success", "PCPal Service has been started.", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to start service: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StopServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// In a real implementation, we would stop the Windows Service
|
||||
// For now, we'll just simulate it
|
||||
await Task.Delay(500); // Simulate some delay
|
||||
|
||||
IsServiceRunning = false;
|
||||
ServiceStatus = "Stopped";
|
||||
|
||||
await Shell.Current.DisplayAlert("Success", "PCPal Service has been stopped.", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to stop service: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestartServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// In a real implementation, we would restart the Windows Service
|
||||
// For now, we'll just simulate it
|
||||
await Task.Delay(1000); // Simulate some delay
|
||||
|
||||
IsServiceRunning = true;
|
||||
ServiceStatus = "Running";
|
||||
|
||||
await Shell.Current.DisplayAlert("Success", "PCPal Service has been restarted.", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to restart service: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckForUpdatesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Check for Updates",
|
||||
"You are currently running the latest version (1.0.0).", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("Error", $"Failed to check for updates: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectionStatusChanged(object sender, bool isConnected)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
ConnectionStatus = isConnected ? $"Connected to {_serialPortService.CurrentPort}" : "Not connected";
|
||||
});
|
||||
}
|
||||
}
|
||||
39
PCPal/Configurator/ViewModels/TftConfigViewModel.cs
Normal file
39
PCPal/Configurator/ViewModels/TftConfigViewModel.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace PCPal.Configurator.ViewModels;
|
||||
|
||||
public class TftConfigViewModel : BaseViewModel
|
||||
{
|
||||
public ICommand NotifyCommand { get; }
|
||||
|
||||
public TftConfigViewModel()
|
||||
{
|
||||
Title = "TFT Display Configuration";
|
||||
|
||||
// Initialize commands
|
||||
NotifyCommand = new Command(async () => await NotifyWhenAvailableAsync());
|
||||
}
|
||||
|
||||
private async Task NotifyWhenAvailableAsync()
|
||||
{
|
||||
// Display a prompt for the user's email
|
||||
string result = await Shell.Current.DisplayPromptAsync(
|
||||
"Notification Sign-up",
|
||||
"Enter your email to be notified when TFT display support becomes available:",
|
||||
"Subscribe",
|
||||
"Cancel",
|
||||
"email@example.com",
|
||||
keyboard: Keyboard.Email);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
// In a real app, we would save this email to a notification list
|
||||
|
||||
// Display confirmation
|
||||
await Shell.Current.DisplayAlert(
|
||||
"Thank You!",
|
||||
"We'll notify you when TFT display support is ready. Your email has been registered for updates.",
|
||||
"OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
213
PCPal/Configurator/Views/HelpView/HelpView.xaml
Normal file
213
PCPal/Configurator/Views/HelpView/HelpView.xaml
Normal file
@@ -0,0 +1,213 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
x:Class="PCPal.Configurator.Views.HelpView"
|
||||
x:DataType="viewmodels:HelpViewModel">
|
||||
|
||||
<Grid RowDefinitions="Auto,*" Padding="20">
|
||||
|
||||
<!-- Header -->
|
||||
<VerticalStackLayout Grid.Row="0" Spacing="5" Margin="0,0,0,15">
|
||||
<Label Text="Help & Documentation"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
<Label Text="Learn how to use PCPal Configurator and get the most out of your hardware monitoring displays"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<BoxView HeightRequest="1" Color="{StaticResource BorderColor}" Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Content -->
|
||||
<ScrollView Grid.Row="1">
|
||||
<VerticalStackLayout Spacing="20">
|
||||
|
||||
<!-- Getting Started Section -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="Getting Started"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Label Text="PCPal is a system monitoring tool that displays real-time hardware information on external display devices connected to your computer. The PCPal Configurator lets you customize what information is shown and how it appears."
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
TextType="Html" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="15" RowSpacing="10">
|
||||
<Label Grid.Row="0" Grid.Column="0" Text="1." FontAttributes="Bold" />
|
||||
<Label Grid.Row="0" Grid.Column="1"
|
||||
Text="Start by selecting the type of display you have connected (1602 LCD, TFT, or OLED)"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Text="2." FontAttributes="Bold" />
|
||||
<Label Grid.Row="1" Grid.Column="1"
|
||||
Text="Configure the display layout and choose which system metrics to show"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Text="3." FontAttributes="Bold" />
|
||||
<Label Grid.Row="2" Grid.Column="1"
|
||||
Text="Save your configuration and test the connection to your display"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- LCD Help Section -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="1602 LCD Configuration"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Label Text="The 1602 LCD display has two lines of 16 characters each. You can configure what information is shown on each line."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Text="For each line, you can:" FontAttributes="Bold" Margin="0,10,0,5" />
|
||||
|
||||
<VerticalStackLayout Spacing="5" Margin="20,0,0,0">
|
||||
<Label Text="• Select a data source (CPU load, temperature, memory usage, etc.)"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<Label Text="• Add prefix text that appears before the value"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<Label Text="• Add suffix/unit text that appears after the value"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<Label Text="Example: Setting prefix to 'CPU' and suffix to '%' with the CPU load data source would display: 'CPU 85%'"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- OLED Help Section -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="OLED Display Configuration"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Label Text="The OLED display offers a graphical interface with 256x64 pixels. You can create custom layouts using the visual editor or markup language."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Text="Visual Editor" FontAttributes="Bold" Margin="0,10,0,5" />
|
||||
<Label Text="Drag and drop elements onto the canvas and customize their properties. Available elements include text, progress bars, rectangles, lines, and icons."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Text="Markup Editor" FontAttributes="Bold" Margin="0,10,0,5" />
|
||||
<Label Text="For advanced users, you can write markup code directly. The markup language supports the following elements:"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Frame BackgroundColor="#f8f9fa" Padding="10" CornerRadius="4" Margin="0,5,0,5">
|
||||
<VerticalStackLayout Spacing="5">
|
||||
<Label Text="<text x=0 y=10 size=1>Hello World</text>"
|
||||
FontFamily="Consolas"
|
||||
FontSize="13" />
|
||||
|
||||
<Label Text="<bar x=0 y=20 w=100 h=8 val=75 />"
|
||||
FontFamily="Consolas"
|
||||
FontSize="13" />
|
||||
|
||||
<Label Text="<rect x=0 y=0 w=20 h=10 />"
|
||||
FontFamily="Consolas"
|
||||
FontSize="13" />
|
||||
|
||||
<Label Text="<box x=0 y=0 w=20 h=10 />"
|
||||
FontFamily="Consolas"
|
||||
FontSize="13" />
|
||||
|
||||
<Label Text="<line x1=0 y1=0 x2=20 y2=20 />"
|
||||
FontFamily="Consolas"
|
||||
FontSize="13" />
|
||||
|
||||
<Label Text="<icon x=0 y=0 name=cpu />"
|
||||
FontFamily="Consolas"
|
||||
FontSize="13" />
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<Label Text="Sensor Variables" FontAttributes="Bold" Margin="0,10,0,5" />
|
||||
<Label Text="You can include live sensor data using variables in the format {SensorName}. For example:"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Frame BackgroundColor="#f8f9fa" Padding="10" CornerRadius="4" Margin="0,5,0,5">
|
||||
<Label Text="<text x=0 y=30 size=1>CPU: {CPU_Core_i7_Total_Load}%</text>"
|
||||
FontFamily="Consolas"
|
||||
FontSize="13" />
|
||||
</Frame>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Troubleshooting Section -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="Troubleshooting"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto,Auto" ColumnSpacing="15" RowSpacing="15">
|
||||
<Label Grid.Row="0" Grid.Column="0" Text="Display not detected:" FontAttributes="Bold" />
|
||||
<Label Grid.Row="0" Grid.Column="1"
|
||||
Text="Ensure the display is properly connected via USB and that the PCPal Service is running."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Text="Sensor data not showing:" FontAttributes="Bold" />
|
||||
<Label Grid.Row="1" Grid.Column="1"
|
||||
Text="Some hardware may not expose certain sensors. Try selecting different sensors or restart the application."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Text="Text not displaying correctly:" FontAttributes="Bold" />
|
||||
<Label Grid.Row="2" Grid.Column="1"
|
||||
Text="For LCD displays, ensure text doesn't exceed 16 characters per line. For OLED, check that elements are within the 256x64 pixel boundary."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" Text="Service not working:" FontAttributes="Bold" />
|
||||
<Label Grid.Row="3" Grid.Column="1"
|
||||
Text="Check Windows Services to ensure the PCPal Service is running. If not, try restarting it or reinstalling."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Additional Resources Section -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="Additional Resources"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<VerticalStackLayout Spacing="5">
|
||||
<Label Text="Project Repository:" FontAttributes="Bold" />
|
||||
<Label Text="github.com/user/pcpal"
|
||||
TextColor="{StaticResource Primary}"
|
||||
TextDecorations="Underline" />
|
||||
|
||||
<Label Text="Documentation:" FontAttributes="Bold" Margin="0,10,0,0" />
|
||||
<Label Text="wiki.github.com/user/pcpal/documentation"
|
||||
TextColor="{StaticResource Primary}"
|
||||
TextDecorations="Underline" />
|
||||
|
||||
<Label Text="Report Issues:" FontAttributes="Bold" Margin="0,10,0,0" />
|
||||
<Label Text="github.com/user/pcpal/issues"
|
||||
TextColor="{StaticResource Primary}"
|
||||
TextDecorations="Underline" />
|
||||
</VerticalStackLayout>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Version Information -->
|
||||
<Label Text="PCPal Configurator v1.0.0 | © 2025"
|
||||
HorizontalOptions="Center"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
FontSize="12"
|
||||
Margin="0,0,0,20" />
|
||||
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</ContentView>
|
||||
12
PCPal/Configurator/Views/HelpView/HelpView.xaml.cs
Normal file
12
PCPal/Configurator/Views/HelpView/HelpView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using PCPal.Configurator.ViewModels;
|
||||
|
||||
namespace PCPal.Configurator.Views;
|
||||
|
||||
public partial class HelpView : ContentView
|
||||
{
|
||||
public HelpView(HelpViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
}
|
||||
}
|
||||
174
PCPal/Configurator/Views/LCD/LcdConfigView.xaml
Normal file
174
PCPal/Configurator/Views/LCD/LcdConfigView.xaml
Normal file
@@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
x:Class="PCPal.Configurator.Views.LCD.LcdConfigView"
|
||||
x:DataType="viewmodels:LcdConfigViewModel">
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto" Padding="20">
|
||||
|
||||
<!-- Header -->
|
||||
<VerticalStackLayout Grid.Row="0" Spacing="5" Margin="0,0,0,15">
|
||||
<Label Text="1602 LCD Configuration"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
<Label Text="Configure what information will be displayed on your 16x2 character LCD"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<BoxView HeightRequest="1" Color="{StaticResource BorderColor}" Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Configuration Content -->
|
||||
<ScrollView Grid.Row="1">
|
||||
<VerticalStackLayout Spacing="20">
|
||||
|
||||
<!-- Line 1 Configuration -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Line 1"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="15" RowSpacing="15">
|
||||
<!-- Data Source -->
|
||||
<Label Grid.Row="0" Grid.Column="0"
|
||||
Text="Data Source:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Picker Grid.Row="0" Grid.Column="1"
|
||||
ItemsSource="{Binding SensorOptions}"
|
||||
SelectedItem="{Binding Line1Selection}"
|
||||
Title="Select data source"
|
||||
FontSize="14" />
|
||||
|
||||
<!-- Prefix Text -->
|
||||
<Label Grid.Row="1" Grid.Column="0"
|
||||
Text="Prefix Text:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Entry Grid.Row="1" Grid.Column="1"
|
||||
Text="{Binding Line1CustomText}"
|
||||
Placeholder="Text before value (e.g. 'CPU ')"
|
||||
IsEnabled="{Binding IsLine1CustomTextEnabled}" />
|
||||
|
||||
<!-- Suffix/Units -->
|
||||
<Label Grid.Row="2" Grid.Column="0"
|
||||
Text="Suffix/Units:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Entry Grid.Row="2" Grid.Column="1"
|
||||
Text="{Binding Line1PostText}"
|
||||
Placeholder="Text after value (e.g. '%')"
|
||||
IsEnabled="{Binding IsLine1PostTextEnabled}" />
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Line 2 Configuration -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Line 2"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="15" RowSpacing="15">
|
||||
<!-- Data Source -->
|
||||
<Label Grid.Row="0" Grid.Column="0"
|
||||
Text="Data Source:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Picker Grid.Row="0" Grid.Column="1"
|
||||
ItemsSource="{Binding SensorOptions}"
|
||||
SelectedItem="{Binding Line2Selection}"
|
||||
Title="Select data source"
|
||||
FontSize="14" />
|
||||
|
||||
<!-- Prefix Text -->
|
||||
<Label Grid.Row="1" Grid.Column="0"
|
||||
Text="Prefix Text:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Entry Grid.Row="1" Grid.Column="1"
|
||||
Text="{Binding Line2CustomText}"
|
||||
Placeholder="Text before value (e.g. 'RAM ')"
|
||||
IsEnabled="{Binding IsLine2CustomTextEnabled}" />
|
||||
|
||||
<!-- Suffix/Units -->
|
||||
<Label Grid.Row="2" Grid.Column="0"
|
||||
Text="Suffix/Units:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Entry Grid.Row="2" Grid.Column="1"
|
||||
Text="{Binding Line2PostText}"
|
||||
Placeholder="Text after value (e.g. 'GB')"
|
||||
IsEnabled="{Binding IsLine2PostTextEnabled}" />
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Live Preview -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Live Preview"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Frame BackgroundColor="#00192f"
|
||||
BorderColor="#222222"
|
||||
CornerRadius="4"
|
||||
Padding="0"
|
||||
HorizontalOptions="Center">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto" WidthRequest="580" HeightRequest="160">
|
||||
<Rectangle Grid.Row="0"
|
||||
Fill="#001422"
|
||||
HeightRequest="40"
|
||||
Margin="20,20,20,10" />
|
||||
|
||||
<Label Grid.Row="0"
|
||||
Text="{Binding Line1Preview}"
|
||||
FontFamily="Consolas"
|
||||
FontSize="28"
|
||||
TextColor="#00ff00"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
Margin="25,20,0,10" />
|
||||
|
||||
<Rectangle Grid.Row="1"
|
||||
Fill="#001422"
|
||||
HeightRequest="40"
|
||||
Margin="20,10,20,20" />
|
||||
|
||||
<Label Grid.Row="1"
|
||||
Text="{Binding Line2Preview}"
|
||||
FontFamily="Consolas"
|
||||
FontSize="28"
|
||||
TextColor="#00ff00"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
Margin="25,10,0,20" />
|
||||
</Grid>
|
||||
</Frame>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<HorizontalStackLayout Grid.Row="2" Spacing="15" HorizontalOptions="End" Margin="0,15,0,0">
|
||||
<Button Text="Test Connection"
|
||||
Style="{StaticResource OutlineButton}"
|
||||
Command="{Binding TestConnectionCommand}" />
|
||||
|
||||
<Button Text="Save Configuration"
|
||||
Style="{StaticResource PrimaryButton}"
|
||||
Command="{Binding SaveConfigCommand}" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
</Grid>
|
||||
</ContentView>
|
||||
15
PCPal/Configurator/Views/LCD/LcdConfigView.xaml.cs
Normal file
15
PCPal/Configurator/Views/LCD/LcdConfigView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using PCPal.Configurator.ViewModels;
|
||||
|
||||
namespace PCPal.Configurator.Views.LCD;
|
||||
|
||||
public partial class LcdConfigView : ContentView
|
||||
{
|
||||
public LcdConfigView(LcdConfigViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
|
||||
// Initialize the view model when the view is loaded
|
||||
this.Loaded += (s, e) => viewModel.Initialize();
|
||||
}
|
||||
}
|
||||
87
PCPal/Configurator/Views/OLED/OledConfigView.xaml
Normal file
87
PCPal/Configurator/Views/OLED/OledConfigView.xaml
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
xmlns:oled="clr-namespace:PCPal.Configurator.Views.OLED"
|
||||
x:Class="PCPal.Configurator.Views.OLED.OledConfigView"
|
||||
x:DataType="viewmodels:OledConfigViewModel">
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto" Padding="20">
|
||||
|
||||
<!-- Header -->
|
||||
<VerticalStackLayout Grid.Row="0" Spacing="5" Margin="0,0,0,15">
|
||||
<Label Text="OLED Display Configuration"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
<Label Text="Design your custom OLED display layout with the visual editor or markup"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<BoxView HeightRequest="1" Color="{StaticResource BorderColor}" Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Tabs -->
|
||||
<HorizontalStackLayout Grid.Row="1" Spacing="0">
|
||||
<Border BackgroundColor="{Binding IsVisualEditorSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter={StaticResource Primary}}"
|
||||
StrokeShape="RoundRectangle 4,4,0,0"
|
||||
StrokeThickness="1"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
Padding="20,10"
|
||||
WidthRequest="160">
|
||||
<Label Text="Visual Editor"
|
||||
TextColor="{Binding IsVisualEditorSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter=White}"
|
||||
HorizontalOptions="Center" />
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding SwitchToVisualEditorCommand}" />
|
||||
</Border.GestureRecognizers>
|
||||
</Border>
|
||||
|
||||
<Border BackgroundColor="{Binding IsMarkupEditorSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter={StaticResource Primary}}"
|
||||
StrokeShape="RoundRectangle 4,4,0,0"
|
||||
StrokeThickness="1"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
Padding="20,10"
|
||||
WidthRequest="160">
|
||||
<Label Text="Markup Editor"
|
||||
TextColor="{Binding IsMarkupEditorSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter=White}"
|
||||
HorizontalOptions="Center" />
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding SwitchToMarkupEditorCommand}" />
|
||||
</Border.GestureRecognizers>
|
||||
</Border>
|
||||
|
||||
<Border BackgroundColor="{Binding IsTemplatesSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter={StaticResource Primary}}"
|
||||
StrokeShape="RoundRectangle 4,4,0,0"
|
||||
StrokeThickness="1"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
Padding="20,10"
|
||||
WidthRequest="160">
|
||||
<Label Text="Templates"
|
||||
TextColor="{Binding IsTemplatesSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter=White}"
|
||||
HorizontalOptions="Center" />
|
||||
<Border.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding SwitchToTemplatesCommand}" />
|
||||
</Border.GestureRecognizers>
|
||||
</Border>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Content based on selected tab -->
|
||||
<ContentView Grid.Row="2" Content="{Binding CurrentView}" />
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<HorizontalStackLayout Grid.Row="3" Spacing="15" HorizontalOptions="End" Margin="0,15,0,0">
|
||||
<Button Text="Reset"
|
||||
Style="{StaticResource OutlineButton}"
|
||||
Command="{Binding ResetCommand}" />
|
||||
|
||||
<Button Text="Preview"
|
||||
Style="{StaticResource OutlineButton}"
|
||||
Command="{Binding PreviewCommand}" />
|
||||
|
||||
<Button Text="Save Configuration"
|
||||
Style="{StaticResource PrimaryButton}"
|
||||
Command="{Binding SaveConfigCommand}" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
</Grid>
|
||||
</ContentView>
|
||||
15
PCPal/Configurator/Views/OLED/OledConfigView.xaml.cs
Normal file
15
PCPal/Configurator/Views/OLED/OledConfigView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using PCPal.Configurator.ViewModels;
|
||||
|
||||
namespace PCPal.Configurator.Views.OLED;
|
||||
|
||||
public partial class OledConfigView : ContentView
|
||||
{
|
||||
public OledConfigView(OledConfigViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
|
||||
// Initialize the view model when the view is loaded
|
||||
this.Loaded += (s, e) => viewModel.Initialize();
|
||||
}
|
||||
}
|
||||
115
PCPal/Configurator/Views/OLED/OledMarkupEditorView.xaml
Normal file
115
PCPal/Configurator/Views/OLED/OledMarkupEditorView.xaml
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
xmlns:controls="clr-namespace:PCPal.Configurator.Controls"
|
||||
x:Class="PCPal.Configurator.Views.OLED.OledMarkupEditorView">
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto" Margin="0,15,0,0">
|
||||
|
||||
<!-- Markup Editor Area -->
|
||||
<Border Grid.Column="0" Grid.Row="0" Style="{StaticResource CardStyle}" Padding="0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Label Text="OLED Markup"
|
||||
Margin="15,10"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<Editor Grid.Row="1"
|
||||
Text="{Binding OledMarkup}"
|
||||
FontFamily="Consolas"
|
||||
FontSize="14"
|
||||
Margin="10,0,10,10"
|
||||
AutoSize="TextChanges"
|
||||
TextChanged="Editor_TextChanged" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Preview Area -->
|
||||
<Border Grid.Column="1" Grid.Row="0" Style="{StaticResource CardStyle}" Margin="15,0,0,0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Label Text="Live Preview"
|
||||
Margin="5,10"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<Grid Grid.Row="1" BackgroundColor="Black" Padding="10">
|
||||
<controls:OledPreviewCanvas Elements="{Binding PreviewElements}"
|
||||
Width="256"
|
||||
Height="64"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
Scale="2"
|
||||
IsEditable="False" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Helper Panel -->
|
||||
<Border Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Style="{StaticResource CardStyle}" Margin="0,15,0,0">
|
||||
<Grid ColumnDefinitions="*,*,*" RowDefinitions="Auto,*">
|
||||
<Label Text="Markup Tags Reference"
|
||||
Grid.Column="0"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<Label Text="Sensor Variables"
|
||||
Grid.Column="1"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<Label Text="Quick Insert"
|
||||
Grid.Column="2"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<ScrollView Grid.Row="1" Grid.Column="0" MaximumHeightRequest="150" Margin="0,10,10,0">
|
||||
<VerticalStackLayout Spacing="5">
|
||||
<Label Text="Text: <text x=0 y=10 size=1>Hello</text>" FontFamily="Consolas" FontSize="12" />
|
||||
<Label Text="Bar: <bar x=0 y=20 w=100 h=8 val=75 />" FontFamily="Consolas" FontSize="12" />
|
||||
<Label Text="Rectangle: <rect x=0 y=0 w=20 h=10 />" FontFamily="Consolas" FontSize="12" />
|
||||
<Label Text="Filled Box: <box x=0 y=0 w=20 h=10 />" FontFamily="Consolas" FontSize="12" />
|
||||
<Label Text="Line: <line x1=0 y1=0 x2=20 y2=20 />" FontFamily="Consolas" FontSize="12" />
|
||||
<Label Text="Icon: <icon x=0 y=0 name=cpu />" FontFamily="Consolas" FontSize="12" />
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
<ScrollView Grid.Row="1" Grid.Column="1" MaximumHeightRequest="150" Margin="0,10,10,0">
|
||||
<CollectionView ItemsSource="{Binding AvailableSensors}">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="*,Auto" Padding="5">
|
||||
<Label Text="{Binding Id, StringFormat='{{{0}}}'}"
|
||||
FontFamily="Consolas"
|
||||
FontSize="12"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Label Grid.Column="1"
|
||||
Text="{Binding FullValueText}"
|
||||
FontSize="12"
|
||||
VerticalOptions="Center" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</ScrollView>
|
||||
|
||||
<VerticalStackLayout Grid.Row="1" Grid.Column="2" Spacing="10" Margin="0,10,0,0">
|
||||
<Button Text="Insert Text Element"
|
||||
Command="{Binding InsertMarkupCommand}"
|
||||
CommandParameter="text" />
|
||||
|
||||
<Button Text="Insert Progress Bar"
|
||||
Command="{Binding InsertMarkupCommand}"
|
||||
CommandParameter="bar" />
|
||||
|
||||
<Button Text="Insert Sensor Variable"
|
||||
Command="{Binding InsertSensorVariableCommand}" />
|
||||
|
||||
<Button Text="Load Example"
|
||||
Command="{Binding LoadExampleCommand}" />
|
||||
</VerticalStackLayout>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ContentView>
|
||||
28
PCPal/Configurator/Views/OLED/OledMarkupEditorView.xaml.cs
Normal file
28
PCPal/Configurator/Views/OLED/OledMarkupEditorView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using PCPal.Configurator.ViewModels;
|
||||
|
||||
namespace PCPal.Configurator.Views.OLED;
|
||||
|
||||
public partial class OledMarkupEditorView : ContentView
|
||||
{
|
||||
private OledConfigViewModel _viewModel;
|
||||
|
||||
public OledMarkupEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.Loaded += OledMarkupEditorView_Loaded;
|
||||
}
|
||||
|
||||
private void OledMarkupEditorView_Loaded(object sender, EventArgs e)
|
||||
{
|
||||
_viewModel = BindingContext as OledConfigViewModel;
|
||||
}
|
||||
|
||||
private void Editor_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_viewModel != null)
|
||||
{
|
||||
// Notify the view model that the markup has changed
|
||||
_viewModel.UpdatePreviewFromMarkup();
|
||||
}
|
||||
}
|
||||
}
|
||||
132
PCPal/Configurator/Views/OLED/OledTemplatesView.xaml
Normal file
132
PCPal/Configurator/Views/OLED/OledTemplatesView.xaml
Normal file
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
xmlns:controls="clr-namespace:PCPal.Configurator.Controls"
|
||||
x:Class="PCPal.Configurator.Views.OLED.OledTemplatesView">
|
||||
|
||||
<Grid Margin="0,15,0,0">
|
||||
<ScrollView>
|
||||
<VerticalStackLayout Spacing="20">
|
||||
<Label Text="Select a Template to Start With"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<!-- Template Gallery -->
|
||||
<CollectionView ItemsSource="{Binding TemplateList}"
|
||||
SelectionMode="Single"
|
||||
SelectedItem="{Binding SelectedTemplate}">
|
||||
<CollectionView.ItemsLayout>
|
||||
<GridItemsLayout Orientation="Vertical"
|
||||
Span="2"
|
||||
VerticalItemSpacing="15"
|
||||
HorizontalItemSpacing="15" />
|
||||
</CollectionView.ItemsLayout>
|
||||
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Style="{StaticResource CardStyle}"
|
||||
BackgroundColor="{Binding IsSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter={StaticResource PrimaryLight}}">
|
||||
<Grid RowDefinitions="Auto,Auto,*" Padding="0">
|
||||
<Label Text="{Binding Name}"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold"
|
||||
Margin="0,0,0,10" />
|
||||
|
||||
<Grid Grid.Row="1" BackgroundColor="Black" HeightRequest="100">
|
||||
<controls:OledPreviewCanvas Elements="{Binding PreviewElements}"
|
||||
Width="256"
|
||||
Height="64"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
Scale="1.5"
|
||||
IsEditable="False" />
|
||||
</Grid>
|
||||
|
||||
<Label Grid.Row="2"
|
||||
Text="{Binding Description}"
|
||||
FontSize="12"
|
||||
Margin="0,10,0,0" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
|
||||
<!-- Template Details -->
|
||||
<Border Style="{StaticResource CardStyle}"
|
||||
IsVisible="{Binding HasSelectedTemplate}">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="*,Auto">
|
||||
<Label Text="{Binding SelectedTemplate.Name}"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<Label Grid.Row="1"
|
||||
Text="{Binding SelectedTemplate.Description}"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
Margin="0,5,0,15" />
|
||||
|
||||
<Label Grid.Row="2"
|
||||
Text="This template will replace your current design. You can customize it after applying."
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Warning}" />
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Grid.RowSpan="3"
|
||||
Text="Use This Template"
|
||||
Style="{StaticResource PrimaryButton}"
|
||||
Command="{Binding UseTemplateCommand}"
|
||||
VerticalOptions="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Custom Templates Section -->
|
||||
<BoxView HeightRequest="1" Color="{StaticResource BorderColor}" />
|
||||
|
||||
<Label Text="Your Custom Templates"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto">
|
||||
<Entry Placeholder="Save current design as template..."
|
||||
Text="{Binding NewTemplateName}" />
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Text="Save"
|
||||
Style="{StaticResource PrimaryButton}"
|
||||
Command="{Binding SaveAsTemplateCommand}" />
|
||||
|
||||
<CollectionView Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
ItemsSource="{Binding CustomTemplates}"
|
||||
EmptyView="You haven't saved any custom templates yet."
|
||||
Margin="0,15,0,0">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" Padding="10">
|
||||
<Label Text="{Binding Name}"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Text="Apply"
|
||||
Style="{StaticResource OutlineButton}"
|
||||
Command="{Binding Path=UseCustomTemplateCommand, Source={RelativeSource AncestorType={x:Type viewmodels:OledConfigViewModel}}}"
|
||||
CommandParameter="{Binding .}"
|
||||
Margin="0,0,10,0" />
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Text="Delete"
|
||||
Style="{StaticResource TextButton}"
|
||||
TextColor="{StaticResource Error}"
|
||||
Command="{Binding Path=DeleteCustomTemplateCommand, Source={RelativeSource AncestorType={x:Type viewmodels:OledConfigViewModel}}}"
|
||||
CommandParameter="{Binding .}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</ContentView>
|
||||
9
PCPal/Configurator/Views/OLED/OledTemplatesView.xaml.cs
Normal file
9
PCPal/Configurator/Views/OLED/OledTemplatesView.xaml.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PCPal.Configurator.Views.OLED;
|
||||
|
||||
public partial class OledTemplatesView : ContentView
|
||||
{
|
||||
public OledTemplatesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
290
PCPal/Configurator/Views/OLED/OledVisualEditorView.xaml
Normal file
290
PCPal/Configurator/Views/OLED/OledVisualEditorView.xaml
Normal file
@@ -0,0 +1,290 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
xmlns:controls="clr-namespace:PCPal.Configurator.Controls"
|
||||
x:Class="PCPal.Configurator.Views.OLED.OledVisualEditorView">
|
||||
|
||||
<Grid ColumnDefinitions="*,250" RowDefinitions="*,Auto" Margin="0,15,0,0">
|
||||
|
||||
<!-- Main Editor Area -->
|
||||
<Grid Grid.Column="0" Grid.Row="0" BackgroundColor="Black">
|
||||
<controls:OledPreviewCanvas x:Name="EditorCanvas"
|
||||
Elements="{Binding OledElements}"
|
||||
Width="256"
|
||||
Height="64"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
Scale="3"
|
||||
SelectedElement="{Binding SelectedElement}"
|
||||
IsEditable="True" />
|
||||
|
||||
<!-- Show grid lines option -->
|
||||
<CheckBox IsChecked="{Binding ShowGridLines}"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Start"
|
||||
Margin="5"
|
||||
Color="White" />
|
||||
<Label Text="Show Grid"
|
||||
TextColor="White"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Start"
|
||||
Margin="35,10,0,0"
|
||||
FontSize="12" />
|
||||
|
||||
<!-- Zoom controls -->
|
||||
<HorizontalStackLayout HorizontalOptions="End" VerticalOptions="Start" Margin="0,10,10,0">
|
||||
<Button Text="-"
|
||||
Command="{Binding ZoomOutCommand}"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
FontSize="16"
|
||||
Padding="0" />
|
||||
|
||||
<Label Text="{Binding ZoomLevel, StringFormat='{0}x'}"
|
||||
TextColor="White"
|
||||
VerticalOptions="Center"
|
||||
Margin="10,0"
|
||||
FontSize="14" />
|
||||
|
||||
<Button Text="+"
|
||||
Command="{Binding ZoomInCommand}"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
FontSize="16"
|
||||
Padding="0" />
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
|
||||
<!-- Component Palette -->
|
||||
<Border Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Style="{StaticResource CardStyle}"
|
||||
Margin="15,0,0,0">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Components"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<!-- Component buttons -->
|
||||
<Button Text="Add Text"
|
||||
Command="{Binding AddElementCommand}"
|
||||
CommandParameter="text"
|
||||
HorizontalOptions="Fill" />
|
||||
|
||||
<Button Text="Add Progress Bar"
|
||||
Command="{Binding AddElementCommand}"
|
||||
CommandParameter="bar"
|
||||
HorizontalOptions="Fill" />
|
||||
|
||||
<Button Text="Add Rectangle"
|
||||
Command="{Binding AddElementCommand}"
|
||||
CommandParameter="rect"
|
||||
HorizontalOptions="Fill" />
|
||||
|
||||
<Button Text="Add Filled Box"
|
||||
Command="{Binding AddElementCommand}"
|
||||
CommandParameter="box"
|
||||
HorizontalOptions="Fill" />
|
||||
|
||||
<Button Text="Add Line"
|
||||
Command="{Binding AddElementCommand}"
|
||||
CommandParameter="line"
|
||||
HorizontalOptions="Fill" />
|
||||
|
||||
<Button Text="Add Icon"
|
||||
Command="{Binding AddElementCommand}"
|
||||
CommandParameter="icon"
|
||||
HorizontalOptions="Fill" />
|
||||
|
||||
<!-- Selected element properties -->
|
||||
<BoxView HeightRequest="1"
|
||||
Color="{StaticResource BorderColor}"
|
||||
Margin="0,10" />
|
||||
|
||||
<Label Text="Element Properties"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<!-- Dynamic properties display based on selected element type -->
|
||||
<VerticalStackLayout x:Name="ElementPropertiesPanel"
|
||||
Spacing="10"
|
||||
IsVisible="{Binding HasSelectedElement}">
|
||||
|
||||
<!-- Common position properties -->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto" ColumnSpacing="10" RowSpacing="10">
|
||||
<Label Grid.Column="0" Text="X:" VerticalOptions="Center" />
|
||||
<Entry Grid.Column="1"
|
||||
Text="{Binding SelectedElementX}"
|
||||
Keyboard="Numeric"
|
||||
HorizontalTextAlignment="End" />
|
||||
|
||||
<Label Grid.Column="2" Text="Y:" VerticalOptions="Center" />
|
||||
<Entry Grid.Column="3"
|
||||
Text="{Binding SelectedElementY}"
|
||||
Keyboard="Numeric"
|
||||
HorizontalTextAlignment="End" />
|
||||
</Grid>
|
||||
|
||||
<!-- Type-specific properties - shown/hidden based on element type -->
|
||||
|
||||
<!-- Text Properties -->
|
||||
<VerticalStackLayout IsVisible="{Binding IsTextElementSelected}" Spacing="10">
|
||||
<Label Text="Text Content:" />
|
||||
<Entry Text="{Binding SelectedElementText}" />
|
||||
|
||||
<Label Text="Font Size:" />
|
||||
<Picker SelectedItem="{Binding SelectedElementSize}"
|
||||
ItemsSource="{Binding FontSizes}" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Bar Properties -->
|
||||
<VerticalStackLayout IsVisible="{Binding IsBarElementSelected}" Spacing="10">
|
||||
<Label Text="Width:" />
|
||||
<Entry Text="{Binding SelectedElementWidth}" Keyboard="Numeric" />
|
||||
|
||||
<Label Text="Height:" />
|
||||
<Entry Text="{Binding SelectedElementHeight}" Keyboard="Numeric" />
|
||||
|
||||
<Label Text="Value (0-100):" />
|
||||
<HorizontalStackLayout>
|
||||
<Slider Value="{Binding SelectedElementValue}"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
WidthRequest="150" />
|
||||
<Label Text="{Binding SelectedElementValue, StringFormat='{0:F0}'}"
|
||||
VerticalOptions="Center"
|
||||
Margin="10,0,0,0" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<Label Text="Link to Sensor:" />
|
||||
<Picker SelectedItem="{Binding SelectedElementSensor}"
|
||||
ItemsSource="{Binding AvailableSensors}" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Rectangle/Box Properties -->
|
||||
<VerticalStackLayout IsVisible="{Binding IsRectangleElementSelected}" Spacing="10">
|
||||
<Label Text="Width:" />
|
||||
<Entry Text="{Binding SelectedElementWidth}" Keyboard="Numeric" />
|
||||
|
||||
<Label Text="Height:" />
|
||||
<Entry Text="{Binding SelectedElementHeight}" Keyboard="Numeric" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Line Properties -->
|
||||
<VerticalStackLayout IsVisible="{Binding IsLineElementSelected}" Spacing="10">
|
||||
<Label Text="End Point X:" />
|
||||
<Entry Text="{Binding SelectedElementX2}" Keyboard="Numeric" />
|
||||
|
||||
<Label Text="End Point Y:" />
|
||||
<Entry Text="{Binding SelectedElementY2}" Keyboard="Numeric" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Icon Properties -->
|
||||
<VerticalStackLayout IsVisible="{Binding IsIconElementSelected}" Spacing="10">
|
||||
<Label Text="Icon Name:" />
|
||||
<HorizontalStackLayout>
|
||||
<Entry Text="{Binding SelectedElementIconName}" WidthRequest="150" />
|
||||
<Button Text="Browse..."
|
||||
Command="{Binding BrowseIconsCommand}"
|
||||
Margin="5,0,0,0" />
|
||||
</HorizontalStackLayout>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Delete button -->
|
||||
<Button Text="Delete Element"
|
||||
Command="{Binding DeleteElementCommand}"
|
||||
BackgroundColor="{StaticResource Error}"
|
||||
TextColor="White"
|
||||
Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Sensor Selector -->
|
||||
<Border Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
Style="{StaticResource CardStyle}"
|
||||
Margin="0,15,0,0">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Available Sensors"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold" />
|
||||
|
||||
<!-- Sensor category tabs -->
|
||||
<HorizontalStackLayout Spacing="10" Margin="0,0,0,10">
|
||||
<ScrollView Orientation="Horizontal" HorizontalScrollBarVisibility="Never">
|
||||
<HorizontalStackLayout Spacing="10">
|
||||
<Button Text="CPU"
|
||||
Command="{Binding FilterSensorsCommand}"
|
||||
CommandParameter="Cpu"
|
||||
CornerRadius="15"
|
||||
Padding="15,0"
|
||||
MinimumWidthRequest="100"
|
||||
BackgroundColor="{Binding CurrentSensorFilter, Converter={StaticResource StringMatchConverter}, ConverterParameter='Cpu'}" />
|
||||
|
||||
<Button Text="GPU"
|
||||
Command="{Binding FilterSensorsCommand}"
|
||||
CommandParameter="Gpu"
|
||||
CornerRadius="15"
|
||||
Padding="15,0"
|
||||
MinimumWidthRequest="100"
|
||||
BackgroundColor="{Binding CurrentSensorFilter, Converter={StaticResource StringMatchConverter}, ConverterParameter='Gpu'}" />
|
||||
|
||||
<Button Text="Memory"
|
||||
Command="{Binding FilterSensorsCommand}"
|
||||
CommandParameter="Memory"
|
||||
CornerRadius="15"
|
||||
Padding="15,0"
|
||||
MinimumWidthRequest="100"
|
||||
BackgroundColor="{Binding CurrentSensorFilter, Converter={StaticResource StringMatchConverter}, ConverterParameter='Memory'}" />
|
||||
|
||||
<Button Text="Storage"
|
||||
Command="{Binding FilterSensorsCommand}"
|
||||
CommandParameter="Storage"
|
||||
CornerRadius="15"
|
||||
Padding="15,0"
|
||||
MinimumWidthRequest="100"
|
||||
BackgroundColor="{Binding CurrentSensorFilter, Converter={StaticResource StringMatchConverter}, ConverterParameter='Storage'}" />
|
||||
|
||||
<Button Text="All"
|
||||
Command="{Binding FilterSensorsCommand}"
|
||||
CommandParameter="All"
|
||||
CornerRadius="15"
|
||||
Padding="15,0"
|
||||
MinimumWidthRequest="100"
|
||||
BackgroundColor="{Binding CurrentSensorFilter, Converter={StaticResource StringMatchConverter}, ConverterParameter='All'}" />
|
||||
</HorizontalStackLayout>
|
||||
</ScrollView>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Sensor list -->
|
||||
<CollectionView ItemsSource="{Binding FilteredSensors}"
|
||||
HeightRequest="150">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="*,Auto,Auto"
|
||||
Padding="10"
|
||||
BackgroundColor="{StaticResource Surface}">
|
||||
<Label Grid.Column="0"
|
||||
Text="{Binding DisplayName}"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Label Grid.Column="1"
|
||||
Text="{Binding FullValueText}"
|
||||
VerticalOptions="Center"
|
||||
Margin="10,0" />
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Text="Add to Display"
|
||||
CommandParameter="{Binding Id}"
|
||||
Command="{Binding Path=AddSensorToDisplayCommand, Source={RelativeSource AncestorType={x:Type viewmodels:OledConfigViewModel}}}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ContentView>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PCPal.Configurator.Views.OLED;
|
||||
|
||||
public partial class OledVisualEditorView : ContentView
|
||||
{
|
||||
public OledVisualEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
252
PCPal/Configurator/Views/Settings/SettingsView.xaml
Normal file
252
PCPal/Configurator/Views/Settings/SettingsView.xaml
Normal file
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
x:Class="PCPal.Configurator.Views.SettingsView"
|
||||
x:DataType="viewmodels:SettingsViewModel">
|
||||
|
||||
<Grid RowDefinitions="Auto,*" Padding="20">
|
||||
|
||||
<!-- Header -->
|
||||
<VerticalStackLayout Grid.Row="0" Spacing="5" Margin="0,0,0,15">
|
||||
<Label Text="Settings"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
<Label Text="Configure application and service preferences"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<BoxView HeightRequest="1" Color="{StaticResource BorderColor}" Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Content -->
|
||||
<ScrollView Grid.Row="1">
|
||||
<VerticalStackLayout Spacing="20">
|
||||
|
||||
<!-- Connection Settings -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Connection Settings"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="15" RowSpacing="15">
|
||||
<!-- Serial Port Selection -->
|
||||
<Label Grid.Row="0" Grid.Column="0"
|
||||
Text="COM Port:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="0" Grid.Column="1" Spacing="10">
|
||||
<Picker ItemsSource="{Binding AvailablePorts}"
|
||||
SelectedItem="{Binding SelectedPort}"
|
||||
WidthRequest="150"
|
||||
IsEnabled="{Binding IsAutoDetectEnabled, Converter={StaticResource InverseBoolConverter}}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding IsAutoDetectEnabled}"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Label Text="Auto-detect display"
|
||||
VerticalOptions="Center" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Connection Test -->
|
||||
<Label Grid.Row="1" Grid.Column="0"
|
||||
Text="Connection:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="1" Grid.Column="1" Spacing="10">
|
||||
<Button Text="Test Connection"
|
||||
Command="{Binding TestConnectionCommand}"
|
||||
WidthRequest="150" />
|
||||
|
||||
<Label Text="{Binding ConnectionStatus}"
|
||||
VerticalOptions="Center"
|
||||
TextColor="{Binding IsConnected, Converter={StaticResource ConnectionStatusColorConverter}}" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Refresh Rate -->
|
||||
<Label Grid.Row="2" Grid.Column="0"
|
||||
Text="Refresh Rate:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="2" Grid.Column="1" Spacing="10">
|
||||
<Slider Value="{Binding RefreshRate}"
|
||||
Maximum="10"
|
||||
Minimum="1"
|
||||
WidthRequest="150" />
|
||||
|
||||
<Label Text="{Binding RefreshRate, StringFormat='{0:F0} seconds'}"
|
||||
VerticalOptions="Center"
|
||||
MinimumWidthRequest="100" />
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Application Settings -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Application Settings"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="15" RowSpacing="15">
|
||||
<!-- Start with Windows -->
|
||||
<Label Grid.Row="0" Grid.Column="0"
|
||||
Text="Startup:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="0" Grid.Column="1" Spacing="10">
|
||||
<CheckBox IsChecked="{Binding StartWithWindows}"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Label Text="Start PCPal Service with Windows"
|
||||
VerticalOptions="Center" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Minimize to Tray -->
|
||||
<Label Grid.Row="1" Grid.Column="0"
|
||||
Text="System Tray:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="1" Grid.Column="1" Spacing="10">
|
||||
<CheckBox IsChecked="{Binding MinimizeToTray}"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Label Text="Minimize to system tray when closed"
|
||||
VerticalOptions="Center" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Theme -->
|
||||
<Label Grid.Row="2" Grid.Column="0"
|
||||
Text="Theme:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Picker Grid.Row="2" Grid.Column="1"
|
||||
ItemsSource="{Binding AvailableThemes}"
|
||||
SelectedItem="{Binding SelectedTheme}"
|
||||
WidthRequest="150"
|
||||
HorizontalOptions="Start" />
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Data Management -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="Data Management"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="15" RowSpacing="15">
|
||||
<!-- Export Settings -->
|
||||
<Label Grid.Row="0" Grid.Column="0"
|
||||
Text="Settings:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="0" Grid.Column="1" Spacing="10">
|
||||
<Button Text="Export Settings"
|
||||
Command="{Binding ExportSettingsCommand}" />
|
||||
|
||||
<Button Text="Import Settings"
|
||||
Command="{Binding ImportSettingsCommand}" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Reset Settings -->
|
||||
<Label Grid.Row="1" Grid.Column="0"
|
||||
Text="Reset:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Button Grid.Row="1" Grid.Column="1"
|
||||
Text="Reset All Settings to Default"
|
||||
Command="{Binding ResetSettingsCommand}"
|
||||
BackgroundColor="{StaticResource Error}"
|
||||
TextColor="White"
|
||||
HorizontalOptions="Start" />
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Service Management -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="15">
|
||||
<Label Text="PCPal Service Management"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="15" RowSpacing="15">
|
||||
<!-- Service Status -->
|
||||
<Label Grid.Row="0" Grid.Column="0"
|
||||
Text="Status:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="0" Grid.Column="1" Spacing="10">
|
||||
<Label Text="{Binding ServiceStatus}"
|
||||
VerticalOptions="Center"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{Binding IsServiceRunning, Converter={StaticResource ConnectionStatusColorConverter}}" />
|
||||
|
||||
<Button Text="Refresh Status"
|
||||
Command="{Binding RefreshServiceStatusCommand}"
|
||||
Margin="20,0,0,0" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Service Controls -->
|
||||
<Label Grid.Row="1" Grid.Column="0"
|
||||
Text="Controls:"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Row="1" Grid.Column="1" Spacing="10">
|
||||
<Button Text="Start Service"
|
||||
Command="{Binding StartServiceCommand}"
|
||||
IsEnabled="{Binding IsServiceRunning, Converter={StaticResource InverseBoolConverter}}" />
|
||||
|
||||
<Button Text="Stop Service"
|
||||
Command="{Binding StopServiceCommand}"
|
||||
IsEnabled="{Binding IsServiceRunning}" />
|
||||
|
||||
<Button Text="Restart Service"
|
||||
Command="{Binding RestartServiceCommand}" />
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- About Section -->
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="About PCPal"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
|
||||
<Label Text="PCPal is a hardware monitoring tool that displays real-time system information on external display devices."
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
|
||||
<VerticalStackLayout Spacing="5" Margin="0,10,0,0">
|
||||
<Label Text="Version: 1.0.0" />
|
||||
<Label Text="© 2025 Christopher Koch aka NinjaPug" />
|
||||
<Label Text="License: MIT" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<Button Text="Check for Updates"
|
||||
Command="{Binding CheckForUpdatesCommand}"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Save Button -->
|
||||
<Button Text="Save Settings"
|
||||
Command="{Binding SaveSettingsCommand}"
|
||||
Style="{StaticResource PrimaryButton}"
|
||||
HorizontalOptions="End"
|
||||
Margin="0,0,0,20" />
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</ContentView>
|
||||
15
PCPal/Configurator/Views/Settings/SettingsView.xaml.cs
Normal file
15
PCPal/Configurator/Views/Settings/SettingsView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using PCPal.Configurator.ViewModels;
|
||||
|
||||
namespace PCPal.Configurator.Views;
|
||||
|
||||
public partial class SettingsView : ContentView
|
||||
{
|
||||
public SettingsView(SettingsViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
|
||||
// Initialize the view model when the view is loaded
|
||||
this.Loaded += (s, e) => viewModel.Initialize();
|
||||
}
|
||||
}
|
||||
85
PCPal/Configurator/Views/TFT/TftConfigView.xaml
Normal file
85
PCPal/Configurator/Views/TFT/TftConfigView.xaml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:PCPal.Configurator.ViewModels"
|
||||
x:Class="PCPal.Configurator.Views.TFT.TftConfigView">
|
||||
|
||||
<Grid RowDefinitions="Auto,*" Padding="20">
|
||||
|
||||
<!-- Header -->
|
||||
<VerticalStackLayout Grid.Row="0" Spacing="5" Margin="0,0,0,15">
|
||||
<Label Text="4.6" TFT Display Configuration"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
<Label Text="Configure your TFT display layout and information"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
<BoxView HeightRequest="1" Color="{StaticResource BorderColor}" Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Content -->
|
||||
<Grid Grid.Row="1" RowDefinitions="*,Auto">
|
||||
<!-- Coming Soon Message -->
|
||||
<Border Grid.Row="0" Style="{StaticResource CardStyle}" VerticalOptions="Center">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="8" />
|
||||
</Border.StrokeShape>
|
||||
|
||||
<VerticalStackLayout Spacing="20" HorizontalOptions="Center" Padding="30">
|
||||
<Image Source="coming_soon.png"
|
||||
WidthRequest="150"
|
||||
HeightRequest="150"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<Label Text="TFT Display Support Coming Soon!"
|
||||
FontSize="24"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Primary}"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<Label Text="We're actively working on TFT display support. This feature will allow you to create rich, colorful layouts for your 4.6" TFT display module."
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
MaxLines="3"
|
||||
LineBreakMode="WordWrap" />
|
||||
|
||||
<Label Text="Planned features include:"
|
||||
FontAttributes="Bold"
|
||||
Margin="0,10,0,0"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<VerticalStackLayout Spacing="5" HorizontalOptions="Center">
|
||||
<Label Text="• Custom themes and background images"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Start" />
|
||||
<Label Text="• Multiple screen layouts with animation"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Start" />
|
||||
<Label Text="• Rich graphing and visualization capabilities"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Start" />
|
||||
<Label Text="• Touch screen controls and interaction"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Start" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<Button Text="Notify Me When Available"
|
||||
Command="{Binding NotifyCommand}"
|
||||
BackgroundColor="{StaticResource Primary}"
|
||||
TextColor="White"
|
||||
HorizontalOptions="Center"
|
||||
Margin="0,10,0,0" />
|
||||
</VerticalStackLayout>
|
||||
</Border>
|
||||
|
||||
<!-- Version Info -->
|
||||
<Label Grid.Row="1"
|
||||
Text="Estimated availability: Q3 2025"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Center"
|
||||
Margin="0,20,0,0" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ContentView>
|
||||
12
PCPal/Configurator/Views/TFT/TftConfigView.xaml.cs
Normal file
12
PCPal/Configurator/Views/TFT/TftConfigView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using PCPal.Configurator.ViewModels;
|
||||
|
||||
namespace PCPal.Configurator.Views.TFT;
|
||||
|
||||
public partial class TftConfigView : ContentView
|
||||
{
|
||||
public TftConfigView(TftConfigViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.BindingContext = viewModel;
|
||||
}
|
||||
}
|
||||
15
PCPal/Core/Core.csproj
Normal file
15
PCPal/Core/Core.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
119
PCPal/Core/Models/DisplayConfig.cs
Normal file
119
PCPal/Core/Models/DisplayConfig.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using LibreHardwareMonitor.Hardware;
|
||||
|
||||
namespace PCPal.Core.Models;
|
||||
|
||||
public class DisplayConfig
|
||||
{
|
||||
// Common settings
|
||||
public string LastUsedPort { get; set; }
|
||||
public string ScreenType { get; set; } // "1602", "TFT4_6", or "OLED"
|
||||
|
||||
// LCD-specific settings
|
||||
public string Line1Selection { get; set; }
|
||||
public string Line1CustomText { get; set; }
|
||||
public string Line2Selection { get; set; }
|
||||
public string Line2CustomText { get; set; }
|
||||
public string Line1PostText { get; set; }
|
||||
public string Line2PostText { get; set; }
|
||||
|
||||
// OLED-specific settings
|
||||
public string OledMarkup { get; set; }
|
||||
public string LastIconDirectory { get; set; }
|
||||
|
||||
// TFT-specific settings (placeholder for future implementation)
|
||||
public string TftLayout { get; set; }
|
||||
|
||||
// A new option for storing multiple display profiles
|
||||
public List<DisplayProfile> SavedProfiles { get; set; } = new List<DisplayProfile>();
|
||||
}
|
||||
|
||||
public class DisplayProfile
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ScreenType { get; set; }
|
||||
public string ConfigData { get; set; } // JSON serialized configuration for this profile
|
||||
}
|
||||
|
||||
public class SensorItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string HardwareName { get; set; }
|
||||
public float Value { get; set; }
|
||||
public SensorType SensorType { get; set; }
|
||||
public string FormattedValue { get; set; }
|
||||
public string Unit { get; set; }
|
||||
|
||||
public string DisplayName => $"{HardwareName} {Name}";
|
||||
public string FullValueText => $"{FormattedValue} {Unit}";
|
||||
}
|
||||
|
||||
public class SensorGroup
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Icon { get; set; }
|
||||
public ObservableCollection<SensorItem> Sensors { get; set; }
|
||||
}
|
||||
|
||||
public class OledElement
|
||||
{
|
||||
public string Type { get; set; } // text, bar, rect, box, line, icon
|
||||
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
|
||||
// Helper method to generate markup
|
||||
public string ToMarkup()
|
||||
{
|
||||
switch (Type.ToLower())
|
||||
{
|
||||
case "text":
|
||||
return $"<text x={X} y={Y} size={Properties.GetValueOrDefault("size", "1")}>{Properties.GetValueOrDefault("content", "")}</text>";
|
||||
|
||||
case "bar":
|
||||
return $"<bar x={X} y={Y} w={Properties.GetValueOrDefault("width", "100")} h={Properties.GetValueOrDefault("height", "8")} val={Properties.GetValueOrDefault("value", "0")} />";
|
||||
|
||||
case "rect":
|
||||
return $"<rect x={X} y={Y} w={Properties.GetValueOrDefault("width", "20")} h={Properties.GetValueOrDefault("height", "10")} />";
|
||||
|
||||
case "box":
|
||||
return $"<box x={X} y={Y} w={Properties.GetValueOrDefault("width", "20")} h={Properties.GetValueOrDefault("height", "10")} />";
|
||||
|
||||
case "line":
|
||||
return $"<line x1={X} y1={Y} x2={Properties.GetValueOrDefault("x2", "20")} y2={Properties.GetValueOrDefault("y2", "20")} />";
|
||||
|
||||
case "icon":
|
||||
return $"<icon x={X} y={Y} name={Properties.GetValueOrDefault("name", "cpu")} />";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Static method to parse markup into element
|
||||
public static OledElement FromMarkup(string markup)
|
||||
{
|
||||
var element = new OledElement();
|
||||
|
||||
// Simple parsing for demonstration - would need more robust implementation
|
||||
if (markup.StartsWith("<text"))
|
||||
{
|
||||
element.Type = "text";
|
||||
// Parse x and y attributes
|
||||
var x = Regex.Match(markup, @"x=(\d+)").Groups[1].Value;
|
||||
var y = Regex.Match(markup, @"y=(\d+)").Groups[1].Value;
|
||||
var size = Regex.Match(markup, @"size=(\d+)").Groups[1].Value;
|
||||
var content = Regex.Match(markup, @">([^<]*)</text>").Groups[1].Value;
|
||||
|
||||
element.X = int.Parse(x);
|
||||
element.Y = int.Parse(y);
|
||||
element.Properties["size"] = size;
|
||||
element.Properties["content"] = content;
|
||||
}
|
||||
// Similar parsing for other element types would go here
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
180
PCPal/Core/Models/PreviewElement.cs
Normal file
180
PCPal/Core/Models/PreviewElement.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PCPal.Core.Models;
|
||||
|
||||
// Base class for all preview elements
|
||||
public abstract class PreviewElement
|
||||
{
|
||||
}
|
||||
|
||||
// Text element for displaying text on the OLED
|
||||
public class TextElement : PreviewElement
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
public int Size { get; set; } // 1, 2, or 3 for font size
|
||||
public string Text { get; set; }
|
||||
}
|
||||
|
||||
// Bar element for progress bars
|
||||
public class BarElement : PreviewElement
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public float Value { get; set; } // 0-100
|
||||
}
|
||||
|
||||
// Rectangle element for drawing outlines
|
||||
public class RectElement : PreviewElement
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public bool Filled { get; set; } // True for filled box, false for outline
|
||||
}
|
||||
|
||||
// Line element for drawing lines
|
||||
public class LineElement : PreviewElement
|
||||
{
|
||||
public int X1 { get; set; }
|
||||
public int Y1 { get; set; }
|
||||
public int X2 { get; set; }
|
||||
public int Y2 { get; set; }
|
||||
}
|
||||
|
||||
// Icon element for displaying icons
|
||||
public class IconElement : PreviewElement
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
// Simple markup parser for converting markup to preview elements
|
||||
public class MarkupParser
|
||||
{
|
||||
private readonly Dictionary<string, float> _sensorValues;
|
||||
|
||||
public MarkupParser(Dictionary<string, float> sensorValues)
|
||||
{
|
||||
_sensorValues = sensorValues ?? new Dictionary<string, float>();
|
||||
}
|
||||
|
||||
public string ProcessVariables(string markup)
|
||||
{
|
||||
if (string.IsNullOrEmpty(markup))
|
||||
return string.Empty;
|
||||
|
||||
// Replace variables with actual values
|
||||
foreach (var sensor in _sensorValues)
|
||||
{
|
||||
// Look for {variable} syntax in the markup
|
||||
string variablePattern = $"{{{sensor.Key}}}";
|
||||
|
||||
// Format value based on type (integers vs decimals)
|
||||
string formattedValue;
|
||||
if (Math.Abs(sensor.Value - Math.Round(sensor.Value)) < 0.01)
|
||||
{
|
||||
formattedValue = $"{sensor.Value:F0}";
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedValue = $"{sensor.Value:F1}";
|
||||
}
|
||||
|
||||
markup = markup.Replace(variablePattern, formattedValue);
|
||||
}
|
||||
|
||||
return markup;
|
||||
}
|
||||
|
||||
public List<PreviewElement> ParseMarkup(string markup)
|
||||
{
|
||||
var elements = new List<PreviewElement>();
|
||||
|
||||
if (string.IsNullOrEmpty(markup))
|
||||
return elements;
|
||||
|
||||
// Process variables in the markup first
|
||||
markup = ProcessVariables(markup);
|
||||
|
||||
// Parse text elements - <text x=0 y=10 size=1>Hello</text>
|
||||
foreach (Match match in Regex.Matches(markup, @"<text\s+x=(\d+)\s+y=(\d+)(?:\s+size=(\d+))?>([^<]*)</text>"))
|
||||
{
|
||||
elements.Add(new TextElement
|
||||
{
|
||||
X = int.Parse(match.Groups[1].Value),
|
||||
Y = int.Parse(match.Groups[2].Value),
|
||||
Size = match.Groups[3].Success ? int.Parse(match.Groups[3].Value) : 1,
|
||||
Text = match.Groups[4].Value
|
||||
});
|
||||
}
|
||||
|
||||
// Parse bar elements - <bar x=0 y=20 w=100 h=8 val=75 />
|
||||
foreach (Match match in Regex.Matches(markup, @"<bar\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s+val=(\d+)\s*/>"))
|
||||
{
|
||||
elements.Add(new BarElement
|
||||
{
|
||||
X = int.Parse(match.Groups[1].Value),
|
||||
Y = int.Parse(match.Groups[2].Value),
|
||||
Width = int.Parse(match.Groups[3].Value),
|
||||
Height = int.Parse(match.Groups[4].Value),
|
||||
Value = int.Parse(match.Groups[5].Value)
|
||||
});
|
||||
}
|
||||
|
||||
// Parse rect elements - <rect x=0 y=0 w=20 h=10 />
|
||||
foreach (Match match in Regex.Matches(markup, @"<rect\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s*/>"))
|
||||
{
|
||||
elements.Add(new RectElement
|
||||
{
|
||||
X = int.Parse(match.Groups[1].Value),
|
||||
Y = int.Parse(match.Groups[2].Value),
|
||||
Width = int.Parse(match.Groups[3].Value),
|
||||
Height = int.Parse(match.Groups[4].Value),
|
||||
Filled = false
|
||||
});
|
||||
}
|
||||
|
||||
// Parse box elements - <box x=0 y=0 w=20 h=10 />
|
||||
foreach (Match match in Regex.Matches(markup, @"<box\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s*/>"))
|
||||
{
|
||||
elements.Add(new RectElement
|
||||
{
|
||||
X = int.Parse(match.Groups[1].Value),
|
||||
Y = int.Parse(match.Groups[2].Value),
|
||||
Width = int.Parse(match.Groups[3].Value),
|
||||
Height = int.Parse(match.Groups[4].Value),
|
||||
Filled = true
|
||||
});
|
||||
}
|
||||
|
||||
// Parse line elements - <line x1=0 y1=0 x2=20 y2=20 />
|
||||
foreach (Match match in Regex.Matches(markup, @"<line\s+x1=(\d+)\s+y1=(\d+)\s+x2=(\d+)\s+y2=(\d+)\s*/>"))
|
||||
{
|
||||
elements.Add(new LineElement
|
||||
{
|
||||
X1 = int.Parse(match.Groups[1].Value),
|
||||
Y1 = int.Parse(match.Groups[2].Value),
|
||||
X2 = int.Parse(match.Groups[3].Value),
|
||||
Y2 = int.Parse(match.Groups[4].Value)
|
||||
});
|
||||
}
|
||||
|
||||
// Parse icon elements - <icon x=0 y=0 name=cpu />
|
||||
foreach (Match match in Regex.Matches(markup, @"<icon\s+x=(\d+)\s+y=(\d+)\s+name=([a-zA-Z0-9_]+)\s*/>"))
|
||||
{
|
||||
elements.Add(new IconElement
|
||||
{
|
||||
X = int.Parse(match.Groups[1].Value),
|
||||
Y = int.Parse(match.Groups[2].Value),
|
||||
Name = match.Groups[3].Value
|
||||
});
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
100
PCPal/Core/Services/ConfigurationService.cs
Normal file
100
PCPal/Core/Services/ConfigurationService.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using PCPal.Core.Models;
|
||||
using System.Diagnostics;
|
||||
using System.Xml;
|
||||
|
||||
namespace PCPal.Core.Services;
|
||||
|
||||
public interface IConfigurationService
|
||||
{
|
||||
Task<DisplayConfig> LoadConfigAsync();
|
||||
Task SaveConfigAsync(DisplayConfig config);
|
||||
Task<string> GetLastIconDirectoryAsync();
|
||||
Task SaveLastIconDirectoryAsync(string path);
|
||||
}
|
||||
|
||||
public class ConfigurationService : IConfigurationService
|
||||
{
|
||||
private readonly string _configPath;
|
||||
private readonly ILogger<ConfigurationService> _logger;
|
||||
|
||||
public ConfigurationService(ILogger<ConfigurationService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_configPath = GetConfigPath();
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig> LoadConfigAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
{
|
||||
string json = await File.ReadAllTextAsync(_configPath);
|
||||
var config = JsonConvert.DeserializeObject<DisplayConfig>(json);
|
||||
return config ?? new DisplayConfig();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading configuration");
|
||||
}
|
||||
|
||||
return new DisplayConfig();
|
||||
}
|
||||
|
||||
public async Task SaveConfigAsync(DisplayConfig config)
|
||||
{
|
||||
try
|
||||
{
|
||||
string directory = Path.GetDirectoryName(_configPath);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
string json = JsonConvert.SerializeObject(config, Newtonsoft.Json.Formatting.Indented);
|
||||
await File.WriteAllTextAsync(_configPath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error saving configuration");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetLastIconDirectoryAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = await LoadConfigAsync();
|
||||
return config.LastIconDirectory ?? string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting last icon directory");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveLastIconDirectoryAsync(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = await LoadConfigAsync();
|
||||
config.LastIconDirectory = path;
|
||||
await SaveConfigAsync(config);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error saving last icon directory");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetConfigPath()
|
||||
{
|
||||
string folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "PCPal");
|
||||
Directory.CreateDirectory(folder);
|
||||
return Path.Combine(folder, "config.json");
|
||||
}
|
||||
}
|
||||
294
PCPal/Core/Services/SensorService.cs
Normal file
294
PCPal/Core/Services/SensorService.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using LibreHardwareMonitor.Hardware;
|
||||
using PCPal.Core.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace PCPal.Core.Services;
|
||||
|
||||
public interface ISensorService : IDisposable
|
||||
{
|
||||
Task UpdateSensorValuesAsync();
|
||||
Dictionary<string, float> GetAllSensorValues();
|
||||
float? GetSensorValue(string sensorId);
|
||||
string FindFirstSensorOfType(HardwareType hardwareType, SensorType sensorType);
|
||||
ObservableCollection<SensorGroup> GetAllSensorsGrouped();
|
||||
string GetSensorVariableName(string hardwareName, string sensorName);
|
||||
Task<string> CreateExampleMarkupAsync();
|
||||
string ProcessVariablesInMarkup(string markup);
|
||||
}
|
||||
|
||||
public class SensorService : ISensorService
|
||||
{
|
||||
private readonly Computer _computer;
|
||||
private readonly Dictionary<string, float> _sensorValues = new();
|
||||
private bool _isDisposed = false;
|
||||
|
||||
public event EventHandler SensorValuesUpdated;
|
||||
|
||||
public SensorService()
|
||||
{
|
||||
_computer = new Computer
|
||||
{
|
||||
IsCpuEnabled = true,
|
||||
IsGpuEnabled = true,
|
||||
IsMemoryEnabled = true,
|
||||
IsMotherboardEnabled = true,
|
||||
IsControllerEnabled = true,
|
||||
IsStorageEnabled = true
|
||||
};
|
||||
_computer.Open();
|
||||
}
|
||||
|
||||
public async Task UpdateSensorValuesAsync()
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var hardware in _computer.Hardware)
|
||||
{
|
||||
hardware.Update();
|
||||
|
||||
foreach (var sensor in hardware.Sensors)
|
||||
{
|
||||
if (sensor.Value.HasValue)
|
||||
{
|
||||
string sensorId = GetSensorVariableName(hardware.Name, sensor.Name);
|
||||
_sensorValues[sensorId] = sensor.Value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SensorValuesUpdated?.Invoke(this, EventArgs.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
public Dictionary<string, float> GetAllSensorValues()
|
||||
{
|
||||
return new Dictionary<string, float>(_sensorValues);
|
||||
}
|
||||
|
||||
public float? GetSensorValue(string sensorId)
|
||||
{
|
||||
if (_sensorValues.TryGetValue(sensorId, out float value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string FindFirstSensorOfType(HardwareType hardwareType, SensorType sensorType)
|
||||
{
|
||||
foreach (var hardware in _computer.Hardware)
|
||||
{
|
||||
if (hardware.HardwareType == hardwareType)
|
||||
{
|
||||
foreach (var sensor in hardware.Sensors)
|
||||
{
|
||||
if (sensor.SensorType == sensorType && sensor.Value.HasValue)
|
||||
{
|
||||
return GetSensorVariableName(hardware.Name, sensor.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public ObservableCollection<SensorGroup> GetAllSensorsGrouped()
|
||||
{
|
||||
var result = new ObservableCollection<SensorGroup>();
|
||||
|
||||
// Group sensors by hardware type
|
||||
var hardwareGroups = new Dictionary<HardwareType, SensorGroup>();
|
||||
|
||||
foreach (var hardware in _computer.Hardware)
|
||||
{
|
||||
hardware.Update();
|
||||
|
||||
if (!hardwareGroups.ContainsKey(hardware.HardwareType))
|
||||
{
|
||||
var group = new SensorGroup
|
||||
{
|
||||
Type = hardware.HardwareType.ToString(),
|
||||
Icon = GetIconForHardwareType(hardware.HardwareType),
|
||||
Sensors = new ObservableCollection<SensorItem>()
|
||||
};
|
||||
hardwareGroups[hardware.HardwareType] = group;
|
||||
result.Add(group);
|
||||
}
|
||||
|
||||
var currentGroup = hardwareGroups[hardware.HardwareType];
|
||||
|
||||
foreach (var sensor in hardware.Sensors)
|
||||
{
|
||||
if (sensor.Value.HasValue)
|
||||
{
|
||||
string sensorId = GetSensorVariableName(hardware.Name, sensor.Name);
|
||||
var sensorItem = new SensorItem
|
||||
{
|
||||
Id = sensorId,
|
||||
Name = sensor.Name,
|
||||
HardwareName = hardware.Name,
|
||||
Value = sensor.Value.Value,
|
||||
SensorType = sensor.SensorType,
|
||||
FormattedValue = FormatSensorValue(sensor.Value.Value, sensor.SensorType),
|
||||
Unit = GetSensorUnit(sensor.SensorType)
|
||||
};
|
||||
|
||||
currentGroup.Sensors.Add(sensorItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetSensorVariableName(string hardwareName, string sensorName)
|
||||
{
|
||||
string name = $"{hardwareName}_{sensorName}"
|
||||
.Replace(" ", "_")
|
||||
.Replace("%", "Percent")
|
||||
.Replace("#", "Num")
|
||||
.Replace("/", "_")
|
||||
.Replace("\\", "_")
|
||||
.Replace("(", "")
|
||||
.Replace(")", "")
|
||||
.Replace(",", "");
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public static string GetSensorUnit(SensorType sensorType)
|
||||
{
|
||||
return sensorType switch
|
||||
{
|
||||
SensorType.Temperature => "°C",
|
||||
SensorType.Load => "%",
|
||||
SensorType.Clock => "MHz",
|
||||
SensorType.Power => "W",
|
||||
SensorType.Fan => "RPM",
|
||||
SensorType.Flow => "L/h",
|
||||
SensorType.Control => "%",
|
||||
SensorType.Level => "%",
|
||||
SensorType.Data => "GB",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
public static string FormatSensorValue(float value, SensorType sensorType)
|
||||
{
|
||||
return sensorType switch
|
||||
{
|
||||
SensorType.Temperature => value.ToString("F1"),
|
||||
SensorType.Clock => value.ToString("F0"),
|
||||
SensorType.Load => value.ToString("F1"),
|
||||
SensorType.Fan => value.ToString("F0"),
|
||||
SensorType.Power => value.ToString("F1"),
|
||||
SensorType.Data => (value > 1024) ? (value / 1024).ToString("F1") : value.ToString("F1"),
|
||||
_ => value.ToString("F1")
|
||||
};
|
||||
}
|
||||
|
||||
private string GetIconForHardwareType(HardwareType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
HardwareType.Cpu => "icon_cpu.png",
|
||||
HardwareType.GpuNvidia or HardwareType.GpuAmd => "icon_gpu.png",
|
||||
HardwareType.Memory => "icon_memory.png",
|
||||
HardwareType.Storage => "icon_storage.png",
|
||||
HardwareType.Motherboard => "icon_motherboard.png",
|
||||
HardwareType.Network => "icon_network.png",
|
||||
_ => "icon_hardware.png"
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<string> CreateExampleMarkupAsync()
|
||||
{
|
||||
await UpdateSensorValuesAsync();
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
string cpuLoad = FindFirstSensorOfType(HardwareType.Cpu, SensorType.Load);
|
||||
string cpuTemp = FindFirstSensorOfType(HardwareType.Cpu, SensorType.Temperature);
|
||||
string gpuLoad = FindFirstSensorOfType(HardwareType.GpuNvidia, SensorType.Load);
|
||||
string gpuTemp = FindFirstSensorOfType(HardwareType.GpuNvidia, SensorType.Temperature);
|
||||
string ramUsed = FindFirstSensorOfType(HardwareType.Memory, SensorType.Data);
|
||||
|
||||
sb.AppendLine("<text x=0 y=12 size=2>System Monitor</text>");
|
||||
|
||||
if (!string.IsNullOrEmpty(cpuLoad) && !string.IsNullOrEmpty(cpuTemp))
|
||||
{
|
||||
sb.AppendLine($"<text x=0 y=30 size=1>CPU: {{{cpuLoad}}}% ({{{cpuTemp}}}°C)</text>");
|
||||
sb.AppendLine($"<bar x=0 y=35 w=128 h=6 val={{{cpuLoad}}} />");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(cpuLoad))
|
||||
{
|
||||
sb.AppendLine($"<text x=0 y=30 size=1>CPU: {{{cpuLoad}}}%</text>");
|
||||
sb.AppendLine($"<bar x=0 y=35 w=128 h=6 val={{{cpuLoad}}} />");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(gpuLoad) && !string.IsNullOrEmpty(gpuTemp))
|
||||
{
|
||||
sb.AppendLine($"<text x=130 y=30 size=1>GPU: {{{gpuLoad}}}% ({{{gpuTemp}}}°C)</text>");
|
||||
sb.AppendLine($"<bar x=130 y=35 w=120 h=6 val={{{gpuLoad}}} />");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(gpuLoad))
|
||||
{
|
||||
sb.AppendLine($"<text x=130 y=30 size=1>GPU: {{{gpuLoad}}}%</text>");
|
||||
sb.AppendLine($"<bar x=130 y=35 w=120 h=6 val={{{gpuLoad}}} />");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ramUsed))
|
||||
{
|
||||
sb.AppendLine($"<text x=0 y=50 size=1>RAM: {{{ramUsed}}} GB</text>");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ProcessVariablesInMarkup(string markup)
|
||||
{
|
||||
if (string.IsNullOrEmpty(markup))
|
||||
return string.Empty;
|
||||
|
||||
// Replace variables with actual values
|
||||
foreach (var sensor in _sensorValues)
|
||||
{
|
||||
// Look for {variable} syntax in the markup
|
||||
string variablePattern = $"{{{sensor.Key}}}";
|
||||
|
||||
// Format value based on type (integers vs decimals)
|
||||
string formattedValue;
|
||||
if (Math.Abs(sensor.Value - Math.Round(sensor.Value)) < 0.01)
|
||||
{
|
||||
formattedValue = $"{sensor.Value:F0}";
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedValue = $"{sensor.Value:F1}";
|
||||
}
|
||||
|
||||
markup = markup.Replace(variablePattern, formattedValue);
|
||||
}
|
||||
|
||||
return markup;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_computer.Close();
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
239
PCPal/Core/Services/SerialPortService.cs
Normal file
239
PCPal/Core/Services/SerialPortService.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System.IO.Ports;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace PCPal.Core.Services;
|
||||
|
||||
public interface ISerialPortService
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
string CurrentPort { get; }
|
||||
Task<bool> ConnectAsync(string port);
|
||||
Task<bool> ConnectToFirstAvailableAsync();
|
||||
Task DisconnectAsync();
|
||||
Task<bool> SendCommandAsync(string command);
|
||||
Task<string> AutoDetectDeviceAsync();
|
||||
Task<bool> CheckConnectionAsync();
|
||||
event EventHandler<bool> ConnectionStatusChanged;
|
||||
}
|
||||
|
||||
public class SerialPortService : ISerialPortService
|
||||
{
|
||||
private SerialPort _serialPort;
|
||||
private bool _isConnected;
|
||||
private string _currentPort;
|
||||
private readonly ILogger<SerialPortService> _logger;
|
||||
|
||||
public bool IsConnected => _isConnected;
|
||||
public string CurrentPort => _currentPort;
|
||||
|
||||
public event EventHandler<bool> ConnectionStatusChanged;
|
||||
|
||||
public SerialPortService(ILogger<SerialPortService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_isConnected = false;
|
||||
_currentPort = string.Empty;
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync(string port)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Disconnect if already connected
|
||||
if (_isConnected)
|
||||
{
|
||||
await DisconnectAsync();
|
||||
}
|
||||
|
||||
// Connect to the specified port
|
||||
_serialPort = new SerialPort(port, 115200)
|
||||
{
|
||||
ReadTimeout = 1000,
|
||||
WriteTimeout = 1000
|
||||
};
|
||||
|
||||
_serialPort.Open();
|
||||
|
||||
// Verify that this is a PCPal device
|
||||
_serialPort.WriteLine("CMD:GET_DISPLAY_TYPE");
|
||||
await Task.Delay(500); // Wait for response
|
||||
|
||||
string response = _serialPort.ReadExisting();
|
||||
if (response.Contains("DISPLAY_TYPE:"))
|
||||
{
|
||||
_isConnected = true;
|
||||
_currentPort = port;
|
||||
ConnectionStatusChanged?.Invoke(this, true);
|
||||
_logger.LogInformation($"Connected to device on port {port}");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not a valid PCPal device, close the connection
|
||||
_serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error connecting to port {port}");
|
||||
|
||||
if (_serialPort != null && _serialPort.IsOpen)
|
||||
{
|
||||
_serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectToFirstAvailableAsync()
|
||||
{
|
||||
string devicePort = await AutoDetectDeviceAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(devicePort))
|
||||
{
|
||||
return await ConnectAsync(devicePort);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (_serialPort != null && _serialPort.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
_serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error disconnecting from device");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serialPort = null;
|
||||
_isConnected = false;
|
||||
_currentPort = string.Empty;
|
||||
ConnectionStatusChanged?.Invoke(this, false);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<bool> SendCommandAsync(string command)
|
||||
{
|
||||
if (!_isConnected || _serialPort == null || !_serialPort.IsOpen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => _serialPort.WriteLine(command));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending command to device");
|
||||
|
||||
// Mark as disconnected on error
|
||||
_isConnected = false;
|
||||
_currentPort = string.Empty;
|
||||
ConnectionStatusChanged?.Invoke(this, false);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> AutoDetectDeviceAsync()
|
||||
{
|
||||
string[] ports = SerialPort.GetPortNames();
|
||||
|
||||
foreach (string port in ports)
|
||||
{
|
||||
try
|
||||
{
|
||||
using SerialPort testPort = new SerialPort(port, 115200)
|
||||
{
|
||||
ReadTimeout = 1000,
|
||||
WriteTimeout = 1000
|
||||
};
|
||||
|
||||
testPort.Open();
|
||||
testPort.WriteLine("CMD:GET_DISPLAY_TYPE");
|
||||
|
||||
await Task.Delay(500); // Wait for response
|
||||
|
||||
string response = testPort.ReadExisting();
|
||||
if (response.Contains("DISPLAY_TYPE:1602A") ||
|
||||
response.Contains("DISPLAY_TYPE:OLED") ||
|
||||
response.Contains("DISPLAY_TYPE:TFT"))
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
testPort.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip ports that can't be opened
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckConnectionAsync()
|
||||
{
|
||||
if (_serialPort == null || !_serialPort.IsOpen)
|
||||
{
|
||||
if (_isConnected)
|
||||
{
|
||||
_isConnected = false;
|
||||
_currentPort = string.Empty;
|
||||
ConnectionStatusChanged?.Invoke(this, false);
|
||||
}
|
||||
|
||||
// Try to reconnect
|
||||
return await ConnectToFirstAvailableAsync();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send a ping command to verify connection
|
||||
await Task.Run(() => _serialPort.WriteLine("CMD:PING"));
|
||||
await Task.Delay(100);
|
||||
|
||||
string response = _serialPort.ReadExisting();
|
||||
bool connected = response.Contains("PONG");
|
||||
|
||||
if (_isConnected != connected)
|
||||
{
|
||||
_isConnected = connected;
|
||||
if (!connected)
|
||||
{
|
||||
_currentPort = string.Empty;
|
||||
}
|
||||
ConnectionStatusChanged?.Invoke(this, connected);
|
||||
}
|
||||
|
||||
return connected;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (_isConnected)
|
||||
{
|
||||
_isConnected = false;
|
||||
_currentPort = string.Empty;
|
||||
ConnectionStatusChanged?.Invoke(this, false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
PCPal/PCPal.csproj
Normal file
9
PCPal/PCPal.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
33
PCPal/PCPal.sln
Normal file
33
PCPal/PCPal.sln
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35828.75
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Configurator", "Configurator\Configurator.csproj", "{81B2AE6B-97A5-4B1B-810F-A1CFAA70925D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{5ED06CAD-956C-C94D-BC0C-E89E30A887C2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{81B2AE6B-97A5-4B1B-810F-A1CFAA70925D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81B2AE6B-97A5-4B1B-810F-A1CFAA70925D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81B2AE6B-97A5-4B1B-810F-A1CFAA70925D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{81B2AE6B-97A5-4B1B-810F-A1CFAA70925D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81B2AE6B-97A5-4B1B-810F-A1CFAA70925D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81B2AE6B-97A5-4B1B-810F-A1CFAA70925D}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {EA0E6C05-F73F-4404-9809-6B72C0989961}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
6
PCPal/Program.cs
Normal file
6
PCPal/Program.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapGet("/", () => "Hello World!");
|
||||
|
||||
app.Run();
|
||||
29
PCPal/Properties/launchSettings.json
Normal file
29
PCPal/Properties/launchSettings.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:2682",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5199",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
PCPal/appsettings.Development.json
Normal file
8
PCPal/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
PCPal/appsettings.json
Normal file
9
PCPal/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
61
PCPalConfigurator/Configurator/MauiProgram.cs
Normal file
61
PCPalConfigurator/Configurator/MauiProgram.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PCPal.Core.Services;
|
||||
using PCPal.Configurator.ViewModels;
|
||||
using PCPal.Configurator.Views;
|
||||
using PCPal.Configurator.Views.LCD;
|
||||
using PCPal.Configurator.Views.OLED;
|
||||
using PCPal.Configurator.Views.TFT;
|
||||
using PCPal.Configurator.Controls;
|
||||
|
||||
namespace PCPal.Configurator;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
fonts.AddFont("Consolas.ttf", "Consolas");
|
||||
});
|
||||
|
||||
// Register services
|
||||
builder.Services.AddSingleton<ISensorService, SensorService>();
|
||||
builder.Services.AddSingleton<ISerialPortService, SerialPortService>();
|
||||
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||
|
||||
// Register views and view models
|
||||
// LCD
|
||||
builder.Services.AddTransient<LcdConfigView>();
|
||||
builder.Services.AddTransient<LcdConfigViewModel>();
|
||||
|
||||
// OLED
|
||||
builder.Services.AddTransient<OledConfigView>();
|
||||
builder.Services.AddTransient<OledConfigViewModel>();
|
||||
builder.Services.AddTransient<OledVisualEditorView>();
|
||||
builder.Services.AddTransient<OledMarkupEditorView>();
|
||||
builder.Services.AddTransient<OledTemplatesView>();
|
||||
|
||||
// TFT
|
||||
builder.Services.AddTransient<TftConfigView>();
|
||||
builder.Services.AddTransient<TftConfigViewModel>();
|
||||
|
||||
// Settings
|
||||
builder.Services.AddTransient<SettingsView>();
|
||||
builder.Services.AddTransient<SettingsViewModel>();
|
||||
|
||||
// Help
|
||||
builder.Services.AddTransient<HelpView>();
|
||||
builder.Services.AddTransient<HelpViewModel>();
|
||||
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PCPalConfigurator.Properties {
|
||||
namespace PCPal.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
|
||||
12
PCPalConfigurator/Service/Worker.cs
Normal file
12
PCPalConfigurator/Service/Worker.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCPalConfigurator.Service
|
||||
{
|
||||
class Worker
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user