defect updates, bug still in code

This commit is contained in:
NinjaPug
2025-05-15 10:56:45 -04:00
parent 943cdec951
commit 36f8ebfebe
8 changed files with 282 additions and 1036 deletions

View File

@@ -5,7 +5,6 @@ using PCPal.Configurator.Views.LCD;
using PCPal.Configurator.Views.OLED;
using PCPal.Configurator.Views.TFT;
using System.ComponentModel;
using PCPal.Configurator.Models;
using System.Collections.ObjectModel;
namespace PCPal.Configurator;

View File

@@ -0,0 +1,242 @@
using Microsoft.Maui.Controls.Shapes;
using PCPal.Core.Models;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace PCPal.Configurator.Controls;
// This is a new control that only handles display functionality - no editing
public class OledDisplayCanvas : GraphicsView
{
// Bindable properties for the control
public static readonly BindableProperty ElementsProperty = BindableProperty.Create(
nameof(Elements),
typeof(IList<PreviewElement>),
typeof(OledDisplayCanvas),
null,
propertyChanged: OnElementsChanged);
public static readonly BindableProperty ScaleProperty = BindableProperty.Create(
nameof(Scale),
typeof(float),
typeof(OledDisplayCanvas),
1.0f,
propertyChanged: OnScaleChanged);
public static readonly BindableProperty CanvasWidthProperty = BindableProperty.Create(
nameof(CanvasWidth),
typeof(int),
typeof(OledDisplayCanvas),
256);
public static readonly BindableProperty CanvasHeightProperty = BindableProperty.Create(
nameof(CanvasHeight),
typeof(int),
typeof(OledDisplayCanvas),
64);
// Property accessors
public IList<PreviewElement> Elements
{
get => (IList<PreviewElement>)GetValue(ElementsProperty);
set => SetValue(ElementsProperty, value);
}
public float Scale
{
get => (float)GetValue(ScaleProperty);
set => SetValue(ScaleProperty, value);
}
public int CanvasWidth
{
get => (int)GetValue(CanvasWidthProperty);
set => SetValue(CanvasWidthProperty, value);
}
public int CanvasHeight
{
get => (int)GetValue(CanvasHeightProperty);
set => SetValue(CanvasHeightProperty, value);
}
// Constructor
public OledDisplayCanvas()
{
// Set default drawing
Drawable = new OledDisplayDrawable(this);
// 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 = (OledDisplayCanvas)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();
}
// Scale change handler
private static void OnScaleChanged(BindableObject bindable, object oldValue, object newValue)
{
var canvas = (OledDisplayCanvas)bindable;
float scale = (float)newValue;
// Update the size of the canvas based on the scale
canvas.WidthRequest = canvas.CanvasWidth * scale;
canvas.HeightRequest = canvas.CanvasHeight * scale;
canvas.Invalidate();
}
// Collection changed event handler
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Invalidate();
}
}
// The drawable that renders the OLED canvas
public class OledDisplayDrawable : IDrawable
{
private readonly OledDisplayCanvas _canvas;
public OledDisplayDrawable(OledDisplayCanvas canvas)
{
_canvas = canvas;
}
public void Draw(ICanvas canvas, RectF dirtyRect)
{
try
{
float scale = _canvas.Scale;
// Clear background
canvas.FillColor = Colors.Black;
canvas.FillRectangle(0, 0, dirtyRect.Width, dirtyRect.Height);
// Draw elements
if (_canvas.Elements != null)
{
foreach (var element in _canvas.Elements)
{
DrawElement(canvas, element, scale);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error drawing canvas: {ex.Message}");
}
}
private void DrawElement(ICanvas canvas, PreviewElement element, float scale)
{
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);
}
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);
}
}
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);
}
}
else if (element is LineElement lineElement)
{
canvas.DrawLine(
lineElement.X1 * scale,
lineElement.Y1 * scale,
lineElement.X2 * scale,
lineElement.Y2 * scale);
}
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);
}
}
}

View File

@@ -1,5 +1,4 @@
using Microsoft.Maui.Controls.Shapes;
using PCPal.Configurator.ViewModels;
using PCPal.Core.Models;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -16,20 +15,6 @@ public class OledPreviewCanvas : GraphicsView
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),
@@ -56,18 +41,6 @@ public class OledPreviewCanvas : GraphicsView
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);
@@ -92,11 +65,6 @@ public class OledPreviewCanvas : GraphicsView
// 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;
@@ -123,13 +91,6 @@ public class OledPreviewCanvas : GraphicsView
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)
{
@@ -148,225 +109,6 @@ public class OledPreviewCanvas : GraphicsView
{
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 || Elements.Count == 0) return;
// Need to adjust for scale
float x = (float)point.X / Scale;
float y = (float)point.Y / Scale;
// Check in reverse order (top elements first)
for (int i = Elements.Count - 1; i >= 0; i--)
{
var element = Elements[i];
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 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;
try
{
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 PCPal.Configurator.ViewModels.OledConfigViewModel;
if (viewModel != null)
{
// Update the view model properties to reflect the new position
viewModel.OnPropertyChanged(nameof(viewModel.SelectedElementX));
viewModel.OnPropertyChanged(nameof(viewModel.SelectedElementY));
// Update the markup
viewModel.UpdateMarkupFromElements();
}
// Invalidate the canvas to redraw
Invalidate();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error in drag interaction: {ex.Message}");
}
}
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
@@ -389,59 +131,12 @@ public class OledCanvasDrawable : IDrawable
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);
DrawElement(canvas, element, scale);
}
}
}
@@ -451,20 +146,10 @@ public class OledCanvasDrawable : IDrawable
}
}
private void DrawElement(ICanvas canvas, PreviewElement element, float scale, bool isSelected)
private void DrawElement(ICanvas canvas, PreviewElement element, float scale)
{
// Set selection highlighting if needed
if (isSelected)
{
canvas.StrokeColor = Colors.Cyan;
canvas.StrokeSize = 2;
}
else
{
canvas.StrokeColor = Colors.White;
canvas.StrokeSize = 1;
}
canvas.StrokeColor = Colors.White;
canvas.StrokeSize = 1;
canvas.FillColor = Colors.White;
if (element is TextElement textElement)
@@ -485,17 +170,6 @@ public class OledCanvasDrawable : IDrawable
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)
{
@@ -516,17 +190,6 @@ public class OledCanvasDrawable : IDrawable
(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)
{
@@ -548,17 +211,6 @@ public class OledCanvasDrawable : IDrawable
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)
{
@@ -567,22 +219,6 @@ public class OledCanvasDrawable : IDrawable
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)
{
@@ -600,17 +236,6 @@ public class OledCanvasDrawable : IDrawable
(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);
}
}
}
}

View File

@@ -36,7 +36,6 @@ public static class MauiProgram
// OLED
builder.Services.AddTransient<OledConfigView>();
builder.Services.AddTransient<OledConfigViewModel>();
builder.Services.AddTransient<OledVisualEditorView>();
builder.Services.AddTransient<OledMarkupEditorView>();
builder.Services.AddTransient<OledTemplatesView>();

View File

@@ -5,7 +5,6 @@ using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Text.RegularExpressions;
using System.Diagnostics;
//using Javax.Xml.Transform;
namespace PCPal.Configurator.ViewModels;
@@ -16,7 +15,6 @@ public class OledConfigViewModel : BaseViewModel
private readonly ISerialPortService _serialPortService;
// Tab selection
private bool _isVisualEditorSelected;
private bool _isMarkupEditorSelected;
private bool _isTemplatesSelected;
private ContentView _currentView;
@@ -25,14 +23,6 @@ public class OledConfigViewModel : BaseViewModel
private string _oledMarkup;
private List<PreviewElement> _previewElements;
// Visual editor data
private ObservableCollection<OledElement> _oledElements;
private OledElement _selectedElement;
private bool _showGridLines;
private float _zoomLevel;
private string _currentSensorFilter;
private ObservableCollection<SensorItem> _filteredSensors;
// Common properties
private ObservableCollection<SensorItem> _availableSensors;
@@ -43,7 +33,6 @@ public class OledConfigViewModel : BaseViewModel
private string _newTemplateName;
// Views
private readonly OledVisualEditorView _visualEditorView;
private readonly OledMarkupEditorView _markupEditorView;
private readonly OledTemplatesView _templatesView;
@@ -54,12 +43,6 @@ public class OledConfigViewModel : BaseViewModel
#region Properties
// Tab selection properties
public bool IsVisualEditorSelected
{
get => _isVisualEditorSelected;
set => SetProperty(ref _isVisualEditorSelected, value);
}
public bool IsMarkupEditorSelected
{
get => _isMarkupEditorSelected;
@@ -91,80 +74,6 @@ public class OledConfigViewModel : BaseViewModel
set => SetProperty(ref _previewElements, value);
}
// Visual editor properties
public ObservableCollection<OledElement> OledElements
{
get => _oledElements;
set => SetProperty(ref _oledElements, value);
}
public OledElement SelectedElement
{
get => _selectedElement;
set
{
if (SetProperty(ref _selectedElement, value))
{
OnPropertyChanged(nameof(HasSelectedElement));
OnPropertyChanged(nameof(IsTextElementSelected));
OnPropertyChanged(nameof(IsBarElementSelected));
OnPropertyChanged(nameof(IsRectangleElementSelected));
OnPropertyChanged(nameof(IsLineElementSelected));
OnPropertyChanged(nameof(IsIconElementSelected));
// Update element properties
OnPropertyChanged(nameof(SelectedElementX));
OnPropertyChanged(nameof(SelectedElementY));
OnPropertyChanged(nameof(SelectedElementText));
OnPropertyChanged(nameof(SelectedElementSize));
OnPropertyChanged(nameof(SelectedElementWidth));
OnPropertyChanged(nameof(SelectedElementHeight));
OnPropertyChanged(nameof(SelectedElementValue));
OnPropertyChanged(nameof(SelectedElementX2));
OnPropertyChanged(nameof(SelectedElementY2));
OnPropertyChanged(nameof(SelectedElementIconName));
OnPropertyChanged(nameof(SelectedElementSensor));
}
}
}
public bool HasSelectedElement => SelectedElement != null;
public bool IsTextElementSelected => SelectedElement?.Type == "text";
public bool IsBarElementSelected => SelectedElement?.Type == "bar";
public bool IsRectangleElementSelected => SelectedElement?.Type == "rect" || SelectedElement?.Type == "box";
public bool IsLineElementSelected => SelectedElement?.Type == "line";
public bool IsIconElementSelected => SelectedElement?.Type == "icon";
public bool ShowGridLines
{
get => _showGridLines;
set => SetProperty(ref _showGridLines, value);
}
public float ZoomLevel
{
get => _zoomLevel;
set => SetProperty(ref _zoomLevel, value);
}
public string CurrentSensorFilter
{
get => _currentSensorFilter;
set
{
if (SetProperty(ref _currentSensorFilter, value))
{
ApplySensorFilter();
}
}
}
public ObservableCollection<SensorItem> FilteredSensors
{
get => _filteredSensors;
set => SetProperty(ref _filteredSensors, value);
}
// Common properties
public ObservableCollection<SensorItem> AvailableSensors
{
@@ -205,177 +114,11 @@ public class OledConfigViewModel : BaseViewModel
set => SetProperty(ref _newTemplateName, value);
}
// Selected element properties
public string SelectedElementX
{
get => SelectedElement?.X.ToString() ?? string.Empty;
set
{
if (SelectedElement != null && int.TryParse(value, out int x))
{
SelectedElement.X = x;
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementX));
}
}
}
public string SelectedElementY
{
get => SelectedElement?.Y.ToString() ?? string.Empty;
set
{
if (SelectedElement != null && int.TryParse(value, out int y))
{
SelectedElement.Y = y;
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementY));
}
}
}
public string SelectedElementText
{
get => SelectedElement?.Properties.GetValueOrDefault("content") ?? string.Empty;
set
{
if (SelectedElement != null)
{
SelectedElement.Properties["content"] = value;
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementText));
}
}
}
public string SelectedElementSize
{
get => SelectedElement?.Properties.GetValueOrDefault("size") ?? "1";
set
{
if (SelectedElement != null)
{
SelectedElement.Properties["size"] = value;
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementSize));
}
}
}
public string SelectedElementWidth
{
get => SelectedElement?.Properties.GetValueOrDefault("width") ?? string.Empty;
set
{
if (SelectedElement != null && int.TryParse(value, out int width))
{
SelectedElement.Properties["width"] = width.ToString();
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementWidth));
}
}
}
public string SelectedElementHeight
{
get => SelectedElement?.Properties.GetValueOrDefault("height") ?? string.Empty;
set
{
if (SelectedElement != null && int.TryParse(value, out int height))
{
SelectedElement.Properties["height"] = height.ToString();
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementHeight));
}
}
}
public float SelectedElementValue
{
get
{
if (SelectedElement != null && float.TryParse(SelectedElement.Properties.GetValueOrDefault("value"), out float value))
{
return value;
}
return 0;
}
set
{
if (SelectedElement != null)
{
SelectedElement.Properties["value"] = value.ToString("F0");
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementValue));
}
}
}
public string SelectedElementX2
{
get => SelectedElement?.Properties.GetValueOrDefault("x2") ?? string.Empty;
set
{
if (SelectedElement != null && int.TryParse(value, out int x2))
{
SelectedElement.Properties["x2"] = x2.ToString();
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementX2));
}
}
}
public string SelectedElementY2
{
get => SelectedElement?.Properties.GetValueOrDefault("y2") ?? string.Empty;
set
{
if (SelectedElement != null && int.TryParse(value, out int y2))
{
SelectedElement.Properties["y2"] = y2.ToString();
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementY2));
}
}
}
public string SelectedElementIconName
{
get => SelectedElement?.Properties.GetValueOrDefault("name") ?? string.Empty;
set
{
if (SelectedElement != null)
{
SelectedElement.Properties["name"] = value;
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementIconName));
}
}
}
public string SelectedElementSensor
{
get => SelectedElement?.Properties.GetValueOrDefault("sensor") ?? string.Empty;
set
{
if (SelectedElement != null)
{
SelectedElement.Properties["sensor"] = value;
UpdateMarkupFromElements();
OnPropertyChanged(nameof(SelectedElementSensor));
}
}
}
// Lists for populating pickers
public List<string> FontSizes => new List<string> { "1", "2", "3" };
#endregion
#region Commands
// Tab selection commands
public ICommand SwitchToVisualEditorCommand { get; }
public ICommand SwitchToMarkupEditorCommand { get; }
public ICommand SwitchToTemplatesCommand { get; }
@@ -384,15 +127,6 @@ public class OledConfigViewModel : BaseViewModel
public ICommand PreviewCommand { get; }
public ICommand ResetCommand { get; }
// Visual editor commands
public ICommand AddElementCommand { get; }
public ICommand DeleteElementCommand { get; }
public ICommand ZoomInCommand { get; }
public ICommand ZoomOutCommand { get; }
public ICommand FilterSensorsCommand { get; }
public ICommand AddSensorToDisplayCommand { get; }
public ICommand BrowseIconsCommand { get; }
// Markup editor commands
public ICommand InsertMarkupCommand { get; }
public ICommand InsertSensorVariableCommand { get; }
@@ -416,10 +150,8 @@ public class OledConfigViewModel : BaseViewModel
_serialPortService = serialPortService ?? throw new ArgumentNullException(nameof(serialPortService));
// Initialize collections
_oledElements = new ObservableCollection<OledElement>();
_previewElements = new List<PreviewElement>();
_availableSensors = new ObservableCollection<SensorItem>();
_filteredSensors = new ObservableCollection<SensorItem>();
_templateList = new ObservableCollection<Template>();
_customTemplates = new ObservableCollection<Template>();
@@ -427,19 +159,14 @@ public class OledConfigViewModel : BaseViewModel
_sensorUpdateCts = new CancellationTokenSource();
// Create views
_visualEditorView = new OledVisualEditorView { BindingContext = this };
_markupEditorView = new OledMarkupEditorView { BindingContext = this };
_templatesView = new OledTemplatesView { BindingContext = this };
// Default values
_isVisualEditorSelected = true;
_currentView = _visualEditorView;
_showGridLines = false;
_zoomLevel = 3.0f;
_currentSensorFilter = "All";
_isMarkupEditorSelected = true;
_currentView = _markupEditorView;
// Tab selection commands
SwitchToVisualEditorCommand = new Command(() => SwitchTab("visual"));
SwitchToMarkupEditorCommand = new Command(() => SwitchTab("markup"));
SwitchToTemplatesCommand = new Command(() => SwitchTab("templates"));
@@ -448,15 +175,6 @@ public class OledConfigViewModel : BaseViewModel
PreviewCommand = new Command(async () => await PreviewOnDeviceAsync());
ResetCommand = new Command(async () => await ResetLayoutAsync());
// Visual editor commands
AddElementCommand = new Command<string>(type => AddElement(type));
DeleteElementCommand = new Command(DeleteSelectedElement);
ZoomInCommand = new Command(ZoomIn);
ZoomOutCommand = new Command(ZoomOut);
FilterSensorsCommand = new Command<string>(filter => CurrentSensorFilter = filter);
AddSensorToDisplayCommand = new Command<string>(sensorId => AddSensorToDisplay(sensorId));
BrowseIconsCommand = new Command(async () => await BrowseIconsAsync());
// Markup editor commands
InsertMarkupCommand = new Command<string>(type => InsertMarkupTemplate(type));
InsertSensorVariableCommand = new Command(async () => await InsertSensorVariableAsync());
@@ -507,8 +225,8 @@ public class OledConfigViewModel : BaseViewModel
// Load templates
await LoadTemplatesAsync();
// Switch to visual editor by default
SwitchTab("visual");
// Switch to markup editor by default
SwitchTab("markup");
// Start sensor updates
_sensorUpdateTimer.Change(0, 2000); // Update every 2 seconds
@@ -544,7 +262,6 @@ public class OledConfigViewModel : BaseViewModel
// Update on main thread to ensure thread safety
await MainThread.InvokeOnMainThreadAsync(() => {
AvailableSensors = new ObservableCollection<SensorItem>(sensors);
FilteredSensors = new ObservableCollection<SensorItem>(sensors);
});
}
catch (Exception ex)
@@ -563,7 +280,6 @@ public class OledConfigViewModel : BaseViewModel
if (!string.IsNullOrEmpty(config.OledMarkup))
{
OledMarkup = config.OledMarkup;
await ParseMarkupToElementsAsync(OledMarkup);
UpdatePreviewFromMarkup();
}
else
@@ -653,19 +369,14 @@ public class OledConfigViewModel : BaseViewModel
private void SwitchTab(string tab)
{
IsVisualEditorSelected = tab == "visual";
IsMarkupEditorSelected = tab == "markup";
IsTemplatesSelected = tab == "templates";
OnPropertyChanged(nameof(IsVisualEditorSelected));
OnPropertyChanged(nameof(IsMarkupEditorSelected));
OnPropertyChanged(nameof(IsTemplatesSelected));
switch (tab)
{
case "visual":
CurrentView = _visualEditorView;
break;
case "markup":
CurrentView = _markupEditorView;
break;
@@ -784,26 +495,6 @@ public class OledConfigViewModel : BaseViewModel
}
}
public void UpdateMarkupFromElements()
{
try
{
var sb = new System.Text.StringBuilder();
foreach (var element in OledElements)
{
sb.AppendLine(element.ToMarkup());
}
OledMarkup = sb.ToString();
UpdatePreviewFromMarkup();
}
catch (Exception ex)
{
Debug.WriteLine($"Error updating markup from elements: {ex.Message}");
}
}
private async Task UpdateSensorDataAsync()
{
try
@@ -829,317 +520,6 @@ public class OledConfigViewModel : BaseViewModel
}
}
private void ApplySensorFilter()
{
try
{
if (string.IsNullOrEmpty(CurrentSensorFilter) || CurrentSensorFilter == "All")
{
MainThread.BeginInvokeOnMainThread(() => {
FilteredSensors = new ObservableCollection<SensorItem>(AvailableSensors);
});
return;
}
var filtered = AvailableSensors.Where(s =>
s.HardwareName.Contains(CurrentSensorFilter, StringComparison.OrdinalIgnoreCase)).ToList();
MainThread.BeginInvokeOnMainThread(() => {
FilteredSensors = new ObservableCollection<SensorItem>(filtered);
});
}
catch (Exception ex)
{
Debug.WriteLine($"Error applying sensor filter: {ex.Message}");
}
}
private void AddElement(string type)
{
try
{
var element = new OledElement
{
Type = type,
X = 10,
Y = 10
};
// Set default properties based on type
switch (type)
{
case "text":
element.Properties["size"] = "1";
element.Properties["content"] = "New Text";
break;
case "bar":
element.Properties["width"] = "100";
element.Properties["height"] = "8";
element.Properties["value"] = "50";
break;
case "rect":
case "box":
element.Properties["width"] = "20";
element.Properties["height"] = "10";
break;
case "line":
element.Properties["x2"] = "30";
element.Properties["y2"] = "30";
break;
case "icon":
element.Properties["name"] = "cpu";
break;
}
OledElements.Add(element);
SelectedElement = element;
// Update markup
UpdateMarkupFromElements();
}
catch (Exception ex)
{
Debug.WriteLine($"Error adding element: {ex.Message}");
}
}
private void DeleteSelectedElement()
{
try
{
if (SelectedElement != null)
{
OledElements.Remove(SelectedElement);
SelectedElement = null;
// Update markup
UpdateMarkupFromElements();
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error deleting element: {ex.Message}");
}
}
private async Task ParseMarkupToElementsAsync(string markup)
{
if (string.IsNullOrEmpty(markup))
{
await MainThread.InvokeOnMainThreadAsync(() => {
OledElements.Clear();
});
return;
}
var elements = new List<OledElement>();
try
{
// Parse text elements
foreach (Match match in Regex.Matches(markup, @"<text\s+x=(\d+)\s+y=(\d+)(?:\s+size=(\d+))?>([^<]*)</text>"))
{
var element = new OledElement { Type = "text" };
element.X = int.Parse(match.Groups[1].Value);
element.Y = int.Parse(match.Groups[2].Value);
if (match.Groups[3].Success)
{
element.Properties["size"] = match.Groups[3].Value;
}
else
{
element.Properties["size"] = "1";
}
element.Properties["content"] = match.Groups[4].Value;
elements.Add(element);
}
// Parse bar elements
foreach (Match match in Regex.Matches(markup, @"<bar\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s+val=(\d+|\{[^}]+\})\s*/>"))
{
var element = new OledElement { Type = "bar" };
element.X = int.Parse(match.Groups[1].Value);
element.Y = int.Parse(match.Groups[2].Value);
element.Properties["width"] = match.Groups[3].Value;
element.Properties["height"] = match.Groups[4].Value;
element.Properties["value"] = match.Groups[5].Value;
elements.Add(element);
}
// Parse rect elements
foreach (Match match in Regex.Matches(markup, @"<rect\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s*/>"))
{
var element = new OledElement { Type = "rect" };
element.X = int.Parse(match.Groups[1].Value);
element.Y = int.Parse(match.Groups[2].Value);
element.Properties["width"] = match.Groups[3].Value;
element.Properties["height"] = match.Groups[4].Value;
elements.Add(element);
}
// Parse box elements
foreach (Match match in Regex.Matches(markup, @"<box\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s*/>"))
{
var element = new OledElement { Type = "box" };
element.X = int.Parse(match.Groups[1].Value);
element.Y = int.Parse(match.Groups[2].Value);
element.Properties["width"] = match.Groups[3].Value;
element.Properties["height"] = match.Groups[4].Value;
elements.Add(element);
}
// Parse line elements
foreach (Match match in Regex.Matches(markup, @"<line\s+x1=(\d+)\s+y1=(\d+)\s+x2=(\d+)\s+y2=(\d+)\s*/>"))
{
var element = new OledElement { Type = "line" };
element.X = int.Parse(match.Groups[1].Value);
element.Y = int.Parse(match.Groups[2].Value);
element.Properties["x2"] = match.Groups[3].Value;
element.Properties["y2"] = match.Groups[4].Value;
elements.Add(element);
}
// Parse icon elements
foreach (Match match in Regex.Matches(markup, @"<icon\s+x=(\d+)\s+y=(\d+)\s+name=([a-zA-Z0-9_]+)\s*/>"))
{
var element = new OledElement { Type = "icon" };
element.X = int.Parse(match.Groups[1].Value);
element.Y = int.Parse(match.Groups[2].Value);
element.Properties["name"] = match.Groups[3].Value;
elements.Add(element);
}
// Update the collection on the UI thread
await MainThread.InvokeOnMainThreadAsync(() =>
{
OledElements.Clear();
foreach (var element in elements)
{
OledElements.Add(element);
}
});
}
catch (Exception ex)
{
Debug.WriteLine($"Error parsing markup: {ex.Message}");
}
}
private async Task<List<PreviewElement>> ParseMarkupToPreviewElements(string markup)
{
try
{
var markupParser = new MarkupParser(_sensorService.GetAllSensorValues());
return markupParser.ParseMarkup(markup);
}
catch (Exception ex)
{
Debug.WriteLine($"Error parsing markup for preview: {ex.Message}");
return new List<PreviewElement>();
}
}
private void ZoomIn()
{
if (ZoomLevel < 5.0f)
{
ZoomLevel += 0.5f;
}
}
private void ZoomOut()
{
if (ZoomLevel > 1.0f)
{
ZoomLevel -= 0.5f;
}
}
private void AddSensorToDisplay(string sensorId)
{
try
{
// Find the sensor
var sensor = AvailableSensors.FirstOrDefault(s => s.Id == sensorId);
if (sensor == null)
{
return;
}
// Determine appropriate Y position (avoid overlap)
int yPos = 15;
if (OledElements.Any())
{
yPos = OledElements.Max(e => e.Y) + 15;
}
// Create text element with the sensor variable
var element = new OledElement
{
Type = "text",
X = 10,
Y = yPos
};
element.Properties["size"] = "1";
element.Properties["content"] = $"{sensor.Name}: {{{sensorId}}} {sensor.Unit}";
OledElements.Add(element);
SelectedElement = element;
// Create a progress bar if it's a load/percentage sensor
if (sensor.SensorType == LibreHardwareMonitor.Hardware.SensorType.Load ||
sensor.SensorType == LibreHardwareMonitor.Hardware.SensorType.Level)
{
var barElement = new OledElement
{
Type = "bar",
X = 10,
Y = yPos + 5
};
barElement.Properties["width"] = "100";
barElement.Properties["height"] = "8";
barElement.Properties["value"] = $"{{{sensorId}}}";
OledElements.Add(barElement);
}
// Update markup
UpdateMarkupFromElements();
}
catch (Exception ex)
{
Debug.WriteLine($"Error adding sensor to display: {ex.Message}");
}
}
private async Task BrowseIconsAsync()
{
try
{
// A mock implementation - in a real app, you'd implement a proper icon browser
var icons = new string[] { "cpu", "gpu", "ram", "disk", "network", "fan" };
string result = await Shell.Current.DisplayActionSheet("Select an icon", "Cancel", null, icons);
if (!string.IsNullOrEmpty(result) && result != "Cancel")
{
SelectedElementIconName = result;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error browsing icons: {ex.Message}");
}
}
private void InsertMarkupTemplate(string type)
{
try
@@ -1216,7 +596,6 @@ public class OledConfigViewModel : BaseViewModel
try
{
OledMarkup = await _sensorService.CreateExampleMarkupAsync();
await ParseMarkupToElementsAsync(OledMarkup);
UpdatePreviewFromMarkup();
}
catch (Exception ex)
@@ -1225,6 +604,20 @@ public class OledConfigViewModel : BaseViewModel
}
}
private async Task<List<PreviewElement>> ParseMarkupToPreviewElements(string markup)
{
try
{
var markupParser = new MarkupParser(_sensorService.GetAllSensorValues());
return markupParser.ParseMarkup(markup);
}
catch (Exception ex)
{
Debug.WriteLine($"Error parsing markup for preview: {ex.Message}");
return new List<PreviewElement>();
}
}
private async Task<string> CreateSystemMonitorTemplateAsync()
{
try
@@ -1324,7 +717,6 @@ public class OledConfigViewModel : BaseViewModel
if (confirm)
{
OledMarkup = SelectedTemplate.Markup;
await ParseMarkupToElementsAsync(OledMarkup);
UpdatePreviewFromMarkup();
SwitchTab("markup");
}
@@ -1424,7 +816,6 @@ public class OledConfigViewModel : BaseViewModel
if (confirm)
{
OledMarkup = template.Markup;
await ParseMarkupToElementsAsync(OledMarkup);
UpdatePreviewFromMarkup();
SwitchTab("markup");
}
@@ -1472,9 +863,15 @@ public class OledConfigViewModel : BaseViewModel
public class Template
{
public Template()
{
// Initialize the PreviewElements collection
PreviewElements = new List<PreviewElement>();
}
public string Name { get; set; }
public string Description { get; set; }
public string Markup { get; set; }
public List<PreviewElement> PreviewElements { get; set; } = new List<PreviewElement>();
public List<PreviewElement> PreviewElements { get; set; }
public bool IsSelected { get; set; }
}

View File

@@ -14,7 +14,7 @@
FontSize="22"
FontAttributes="Bold"
TextColor="{StaticResource TextPrimary}" />
<Label Text="Design your custom OLED display layout with the visual editor or markup"
<Label Text="Design your custom OLED display layout with markup"
FontSize="14"
TextColor="{StaticResource TextSecondary}" />
<BoxView HeightRequest="1" Color="{StaticResource BorderColor}" Margin="0,10,0,0" />
@@ -22,20 +22,6 @@
<!-- 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 BoolToTextColorConverter}, 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"

View File

@@ -34,13 +34,12 @@
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" />
<controls:OledDisplayCanvas Elements="{Binding PreviewElements}"
CanvasWidth="256"
CanvasHeight="64"
HorizontalOptions="Center"
VerticalOptions="Center"
Scale="2" />
</Grid>
</Grid>
</Border>

View File

@@ -34,13 +34,12 @@
Margin="0,0,0,10" />
<Grid Grid.Row="1" BackgroundColor="Black" HeightRequest="100">
<controls:OledPreviewCanvas Elements="{Binding PreviewElements}"
Width="256"
Height="64"
<controls:OledDisplayCanvas Elements="{Binding PreviewElements}"
CanvasWidth="256"
CanvasHeight="64"
HorizontalOptions="Center"
VerticalOptions="Center"
Scale="1.5"
IsEditable="False" />
Scale="1.5" />
</Grid>
<Label Grid.Row="2"