From 1cf596b3791396853321d6034014250d9d703dec Mon Sep 17 00:00:00 2001
From: NinjaPug <36635276+programmingPug@users.noreply.github.com>
Date: Sun, 13 Apr 2025 14:06:05 -0400
Subject: [PATCH] Project updates for OLED
---
PCPalConfigurator/App.config | 21 +
PCPalConfigurator/ConfigData.cs | 8 +-
.../ConfiguratorForm.Designer.cs | 133 +++-
PCPalConfigurator/ConfiguratorForm.cs | 609 +++++++++++++++++-
.../IconBrowser/IconBrowser.Designer.cs | 317 +++++++++
PCPalConfigurator/IconBrowser/IconBrowser.cs | 215 +++++++
.../IconBrowser/IconBrowser.resx | 120 ++++
PCPalConfigurator/PCPalConfigurator.csproj | 21 +
.../PCPalConfigurator.csproj.user | 3 +
.../Properties/Settings.Designer.cs | 38 ++
.../Properties/Settings.settings | 9 +
PCPalService/Worker.cs | 151 ++++-
RGB666_tests/RGB666_tests.ino | 170 +++++
13 files changed, 1781 insertions(+), 34 deletions(-)
create mode 100644 PCPalConfigurator/App.config
create mode 100644 PCPalConfigurator/IconBrowser/IconBrowser.Designer.cs
create mode 100644 PCPalConfigurator/IconBrowser/IconBrowser.cs
create mode 100644 PCPalConfigurator/IconBrowser/IconBrowser.resx
create mode 100644 PCPalConfigurator/Properties/Settings.Designer.cs
create mode 100644 PCPalConfigurator/Properties/Settings.settings
create mode 100644 RGB666_tests/RGB666_tests.ino
diff --git a/PCPalConfigurator/App.config b/PCPalConfigurator/App.config
new file mode 100644
index 0000000..f675b2f
--- /dev/null
+++ b/PCPalConfigurator/App.config
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PCPalConfigurator/ConfigData.cs b/PCPalConfigurator/ConfigData.cs
index 53fd12d..5b9a2ed 100644
--- a/PCPalConfigurator/ConfigData.cs
+++ b/PCPalConfigurator/ConfigData.cs
@@ -2,8 +2,11 @@
{
public class ConfigData
{
+ // Common settings
public string LastUsedPort { get; set; }
- public string ScreenType { 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; }
@@ -11,5 +14,8 @@
public string Line1PostText { get; set; }
public string Line2PostText { get; set; }
+ // OLED-specific settings
+ public string OledMarkup { get; set; }
+ public string LastIconDirectory { get; set; }
}
}
\ No newline at end of file
diff --git a/PCPalConfigurator/ConfiguratorForm.Designer.cs b/PCPalConfigurator/ConfiguratorForm.Designer.cs
index 875a71f..b6319f6 100644
--- a/PCPalConfigurator/ConfiguratorForm.Designer.cs
+++ b/PCPalConfigurator/ConfiguratorForm.Designer.cs
@@ -7,6 +7,8 @@
private TabControl tabControl;
private TabPage tab1602;
private TabPage tabTFT;
+ private TabPage tabOLED;
+ private TabPage tabHelp;
private TabPage tabAbout;
private ComboBox cmbLine1;
@@ -15,7 +17,7 @@
private ComboBox cmbLine2;
private TextBox txtLine2;
private TextBox txtLine2Post;
- private Button btnApply;
+ private Button btnSave;
private Label lblLine1;
private Label lblLine1Prefix;
@@ -24,6 +26,19 @@
private Label lblLine2Prefix;
private Label lblLine2Suffix;
+ // OLED tab controls
+ private TextBox txtOledMarkup;
+ private Panel panelOledButtons;
+ private Button btnInsertIcon;
+ private Button btnLoadOledExample;
+ private Button btnSaveOled;
+ private Panel panelOledPreview;
+ private Button btnPreview;
+ private Label lblPreviewHeader;
+
+ // Help tab controls
+ private TextBox txtHelpContent;
+
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
@@ -37,6 +52,8 @@
this.tabControl = new TabControl();
this.tab1602 = new TabPage("1602 LCD");
this.tabTFT = new TabPage("4.6\" TFT LCD");
+ this.tabOLED = new TabPage("OLED Display");
+ this.tabHelp = new TabPage("Help");
this.tabAbout = new TabPage("About");
this.cmbLine1 = new ComboBox();
@@ -45,7 +62,7 @@
this.cmbLine2 = new ComboBox();
this.txtLine2 = new TextBox();
this.txtLine2Post = new TextBox();
- this.btnApply = new Button();
+ this.btnSave = new Button();
this.lblLine1 = new Label();
this.lblLine1Prefix = new Label();
@@ -54,11 +71,25 @@
this.lblLine2Prefix = new Label();
this.lblLine2Suffix = new Label();
+ // OLED tab controls
+ this.txtOledMarkup = new TextBox();
+ this.panelOledButtons = new Panel();
+ this.btnInsertIcon = new Button();
+ this.btnLoadOledExample = new Button();
+ this.btnSaveOled = new Button();
+ this.panelOledPreview = new Panel();
+ this.btnPreview = new Button();
+ this.lblPreviewHeader = new Label();
+
+ // Help tab controls
+ this.txtHelpContent = new TextBox();
+
// === TabControl ===
- this.tabControl.Location = new System.Drawing.Point(10, 10);
- this.tabControl.Size = new System.Drawing.Size(620, 280);
+ this.tabControl.Dock = DockStyle.Fill;
this.tabControl.TabPages.Add(this.tab1602);
this.tabControl.TabPages.Add(this.tabTFT);
+ this.tabControl.TabPages.Add(this.tabOLED);
+ this.tabControl.TabPages.Add(this.tabHelp);
this.tabControl.TabPages.Add(this.tabAbout);
this.Controls.Add(this.tabControl);
@@ -106,20 +137,98 @@
this.txtLine2Post.Location = new System.Drawing.Point(420, 118);
this.txtLine2Post.Size = new System.Drawing.Size(120, 21);
- // === Apply Button ===
- this.btnApply.Text = "Apply";
- this.btnApply.Location = new System.Drawing.Point(20, 170);
- this.btnApply.Size = new System.Drawing.Size(100, 30);
- this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
+ // === Save Button (renamed from Apply) ===
+ this.btnSave.Text = "Save";
+ this.btnSave.Location = new System.Drawing.Point(20, 170);
+ this.btnSave.Size = new System.Drawing.Size(100, 30);
+ this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
// === Add to 1602 Tab ===
this.tab1602.Controls.AddRange(new Control[]
{
lblLine1, cmbLine1, lblLine1Prefix, txtLine1, lblLine1Suffix, txtLine1Post,
lblLine2, cmbLine2, lblLine2Prefix, txtLine2, lblLine2Suffix, txtLine2Post,
- btnApply
+ btnSave
});
+ // === OLED Tab Setup ===
+ // Main panel for layout
+ Panel oledMainPanel = new Panel();
+ oledMainPanel.Dock = DockStyle.Fill;
+ this.tabOLED.Controls.Add(oledMainPanel);
+
+ // Markup editor
+ this.txtOledMarkup = new TextBox();
+ this.txtOledMarkup.Multiline = true;
+ this.txtOledMarkup.ScrollBars = ScrollBars.Vertical;
+ this.txtOledMarkup.Font = new Font("Consolas", 9.75F);
+ this.txtOledMarkup.TextChanged += new EventHandler(this.txtOledMarkup_TextChanged);
+ this.txtOledMarkup.Size = new Size(580, 130);
+ this.txtOledMarkup.Location = new Point(10, 10);
+ oledMainPanel.Controls.Add(this.txtOledMarkup);
+
+ // Button panel
+ this.panelOledButtons = new Panel();
+ this.panelOledButtons.Size = new Size(580, 40);
+ this.panelOledButtons.Location = new Point(10, 150);
+ oledMainPanel.Controls.Add(this.panelOledButtons);
+
+ this.btnInsertIcon = new Button();
+ this.btnInsertIcon.Text = "Insert Icon";
+ this.btnInsertIcon.Location = new Point(0, 7);
+ this.btnInsertIcon.Size = new Size(120, 26);
+ this.btnInsertIcon.Click += new EventHandler(this.btnInsertIcon_Click);
+ this.panelOledButtons.Controls.Add(this.btnInsertIcon);
+
+ this.btnLoadOledExample = new Button();
+ this.btnLoadOledExample.Text = "Load Example";
+ this.btnLoadOledExample.Location = new Point(130, 7);
+ this.btnLoadOledExample.Size = new Size(120, 26);
+ this.btnLoadOledExample.Click += new EventHandler(this.btnLoadOledExample_Click);
+ this.panelOledButtons.Controls.Add(this.btnLoadOledExample);
+
+ this.btnPreview = new Button();
+ this.btnPreview.Text = "Update Preview";
+ this.btnPreview.Location = new Point(260, 7);
+ this.btnPreview.Size = new Size(120, 26);
+ this.btnPreview.Click += new EventHandler(this.btnPreview_Click);
+ this.panelOledButtons.Controls.Add(this.btnPreview);
+
+ this.btnSaveOled = new Button();
+ this.btnSaveOled.Text = "Save";
+ this.btnSaveOled.Location = new Point(480, 7);
+ this.btnSaveOled.Size = new Size(100, 26);
+ this.btnSaveOled.Click += new EventHandler(this.btnSave_Click); // Uses same event handler
+ this.panelOledButtons.Controls.Add(this.btnSaveOled);
+
+ // Preview header
+ this.lblPreviewHeader = new Label();
+ this.lblPreviewHeader.Text = "OLED Preview (256x64)";
+ this.lblPreviewHeader.Location = new Point(10, 195);
+ this.lblPreviewHeader.Size = new Size(580, 20);
+ this.lblPreviewHeader.TextAlign = ContentAlignment.MiddleCenter;
+ this.lblPreviewHeader.BackColor = System.Drawing.SystemColors.ControlLight;
+ this.lblPreviewHeader.BorderStyle = BorderStyle.FixedSingle;
+ oledMainPanel.Controls.Add(this.lblPreviewHeader);
+
+ // Preview panel
+ this.panelOledPreview = new Panel();
+ this.panelOledPreview.Location = new Point(10, 215);
+ this.panelOledPreview.Size = new Size(580, 110);
+ this.panelOledPreview.BackColor = System.Drawing.Color.Black;
+ this.panelOledPreview.BorderStyle = BorderStyle.FixedSingle;
+ this.panelOledPreview.Paint += new PaintEventHandler(this.panelOledPreview_Paint);
+ oledMainPanel.Controls.Add(this.panelOledPreview);
+
+ // === Help Tab Setup ===
+ this.txtHelpContent = new TextBox();
+ this.txtHelpContent.Dock = DockStyle.Fill;
+ this.txtHelpContent.Multiline = true;
+ this.txtHelpContent.ReadOnly = true;
+ this.txtHelpContent.ScrollBars = ScrollBars.Vertical;
+ this.txtHelpContent.Font = new Font("Segoe UI", 9F);
+ this.tabHelp.Controls.Add(this.txtHelpContent);
+
// === TFT Tab Placeholder ===
Label lblTFT = new Label()
{
@@ -140,9 +249,9 @@
// === Form ===
this.Text = "Display Configurator";
- this.ClientSize = new System.Drawing.Size(640, 300);
+ this.ClientSize = new System.Drawing.Size(600, 380); // Adjusted size for compact layout
this.ResumeLayout(false);
this.PerformLayout();
}
}
-}
+}
\ No newline at end of file
diff --git a/PCPalConfigurator/ConfiguratorForm.cs b/PCPalConfigurator/ConfiguratorForm.cs
index c5f0e1b..0e65267 100644
--- a/PCPalConfigurator/ConfiguratorForm.cs
+++ b/PCPalConfigurator/ConfiguratorForm.cs
@@ -1,9 +1,12 @@
using System;
using System.IO;
+using System.Text;
+using System.Drawing;
using System.Windows.Forms;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
using Newtonsoft.Json;
using LibreHardwareMonitor.Hardware;
-using PCPalConfigurator;
namespace PCPalConfigurator
{
@@ -12,12 +15,129 @@ namespace PCPalConfigurator
private ConfigData config;
private readonly string ConfigFile = GetConfigPath();
private Computer computer;
+ private System.Windows.Forms.Timer sensorUpdateTimer;
+
+ // Preview rendering data
+ private List previewElements = new List();
+ private Dictionary sensorValues = new Dictionary();
+ private bool autoUpdatePreview = true;
+ private const int OledWidth = 256;
+ private const int OledHeight = 64;
+
+ // Classes for preview rendering
+ private abstract class PreviewElement
+ {
+ public abstract void Draw(Graphics g);
+ }
+
+ private class TextElement : PreviewElement
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ public int Size { get; set; }
+ public string Text { get; set; }
+
+ public override void Draw(Graphics g)
+ {
+ // Choose font size based on the size parameter
+ Font font;
+ switch (Size)
+ {
+ case 1: font = new Font("Consolas", 8); break;
+ case 2: font = new Font("Consolas", 10); break;
+ case 3: font = new Font("Consolas", 12); break;
+ default: font = new Font("Consolas", 8); break;
+ }
+
+ // Draw text with white color
+ g.DrawString(Text, font, Brushes.White, X, Y - font.Height);
+ }
+ }
+
+ private class BarElement : PreviewElement
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public int Value { get; set; }
+
+ public override void Draw(Graphics g)
+ {
+ // Draw outline rectangle
+ g.DrawRectangle(Pens.White, X, Y, Width, Height);
+
+ // Calculate fill width based on value (0-100)
+ int fillWidth = (int)(Width * (Value / 100.0));
+ if (fillWidth > 0)
+ {
+ // Draw filled portion
+ g.FillRectangle(Brushes.White, X + 1, Y + 1, fillWidth - 1, Height - 2);
+ }
+ }
+ }
+
+ private 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; }
+
+ public override void Draw(Graphics g)
+ {
+ if (Filled)
+ {
+ // Draw filled box
+ g.FillRectangle(Brushes.White, X, Y, Width, Height);
+ }
+ else
+ {
+ // Draw outline rectangle
+ g.DrawRectangle(Pens.White, X, Y, Width, Height);
+ }
+ }
+ }
+
+ private class LineElement : PreviewElement
+ {
+ public int X1 { get; set; }
+ public int Y1 { get; set; }
+ public int X2 { get; set; }
+ public int Y2 { get; set; }
+
+ public override void Draw(Graphics g)
+ {
+ g.DrawLine(Pens.White, X1, Y1, X2, Y2);
+ }
+ }
+
+ private class IconElement : PreviewElement
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ public string Name { get; set; }
+
+ public override void Draw(Graphics g)
+ {
+ // For preview, just draw a placeholder rectangle with icon name
+ g.DrawRectangle(Pens.Gray, X, Y, 24, 24);
+ using (Font font = new Font("Arial", 6))
+ {
+ g.DrawString(Name, font, Brushes.White, X + 2, Y + 8);
+ }
+ }
+ }
public ConfiguratorForm()
{
InitializeComponent();
InitHardware();
+ InitSensorTimer();
LoadConfig();
+ PopulateHelpContent();
+ UpdatePreview(); // Initial preview rendering
}
private void InitHardware()
@@ -33,6 +153,44 @@ namespace PCPalConfigurator
};
computer.Open();
PopulateSensorOptions();
+ UpdateSensorValues();
+ }
+
+ private void InitSensorTimer()
+ {
+ // Create a timer to regularly update sensor values for the preview
+ sensorUpdateTimer = new System.Windows.Forms.Timer();
+ sensorUpdateTimer.Interval = 1000; // Update every second
+ sensorUpdateTimer.Tick += SensorUpdateTimer_Tick;
+ sensorUpdateTimer.Start();
+ }
+
+ private void SensorUpdateTimer_Tick(object sender, EventArgs e)
+ {
+ // Only update when the OLED tab is selected to save resources
+ if (tabControl.SelectedTab == tabOLED)
+ {
+ UpdateSensorValues();
+ UpdatePreview();
+ }
+ }
+
+ private void UpdateSensorValues()
+ {
+ // Update all hardware readings
+ 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;
+ }
+ }
+ }
}
private static string GetConfigPath()
@@ -58,22 +216,37 @@ namespace PCPalConfigurator
private void ApplyConfig()
{
+ // LCD settings
cmbLine1.SelectedItem = config.Line1Selection;
txtLine1.Text = config.Line1CustomText;
txtLine1Post.Text = config.Line1PostText;
cmbLine2.SelectedItem = config.Line2Selection;
txtLine2.Text = config.Line2CustomText;
txtLine2Post.Text = config.Line2PostText;
+
+ // OLED settings
+ txtOledMarkup.Text = config.OledMarkup;
+
+ // Select the appropriate tab based on screen type
+ if (config.ScreenType == "1602")
+ tabControl.SelectedTab = tab1602;
+ else if (config.ScreenType == "TFT4_6")
+ tabControl.SelectedTab = tabTFT;
+ else if (config.ScreenType == "OLED")
+ tabControl.SelectedTab = tabOLED;
}
private void SaveConfig()
{
- // ScreenType based on selected tab
+ // Determine screen type based on selected tab
if (tabControl.SelectedTab == tab1602)
config.ScreenType = "1602";
else if (tabControl.SelectedTab == tabTFT)
config.ScreenType = "TFT4_6";
+ else if (tabControl.SelectedTab == tabOLED)
+ config.ScreenType = "OLED";
+ // Save LCD settings
config.Line1Selection = cmbLine1.SelectedItem?.ToString();
config.Line1CustomText = txtLine1.Text;
config.Line1PostText = txtLine1Post.Text;
@@ -81,12 +254,16 @@ namespace PCPalConfigurator
config.Line2CustomText = txtLine2.Text;
config.Line2PostText = txtLine2Post.Text;
+ // Save OLED settings
+ config.OledMarkup = txtOledMarkup.Text;
+
string json = JsonConvert.SerializeObject(config, Formatting.Indented);
File.WriteAllText(ConfigFile, json);
MessageBox.Show("Settings saved!");
}
- private void btnApply_Click(object sender, EventArgs e)
+ // Renamed from btnApply_Click to btnSave_Click
+ private void btnSave_Click(object sender, EventArgs e)
{
SaveConfig();
}
@@ -116,5 +293,429 @@ namespace PCPalConfigurator
cmbLine1.Items.Add("Custom Text");
cmbLine2.Items.Add("Custom Text");
}
+
+ private void PopulateHelpContent()
+ {
+ // Create help content for both LCD and OLED displays
+ StringBuilder sb = new StringBuilder();
+
+ // General Information
+ sb.AppendLine("PCPal Display Configurator Help");
+ sb.AppendLine("==============================");
+ sb.AppendLine();
+
+ // LCD Display Help
+ sb.AppendLine("LCD DISPLAY CONFIGURATION");
+ sb.AppendLine("------------------------");
+ sb.AppendLine("1. Select a sensor for each line or choose 'Custom Text'");
+ sb.AppendLine("2. Set prefix text to appear before the sensor value");
+ sb.AppendLine("3. Set suffix/units to appear after the sensor value");
+ sb.AppendLine("4. Click 'Save' to apply your settings");
+ sb.AppendLine();
+
+ // OLED Display Help
+ sb.AppendLine("OLED MARKUP REFERENCE");
+ sb.AppendLine("---------------------");
+ sb.AppendLine("The OLED display accepts markup tags to create your layout.");
+ sb.AppendLine();
+ sb.AppendLine("Text: Hello World");
+ sb.AppendLine(" - x, y: position coordinates (0,0 is top-left)");
+ sb.AppendLine(" - size: 1-3 (small to large)");
+ sb.AppendLine();
+ sb.AppendLine("Progress Bar: ");
+ sb.AppendLine(" - x, y: position");
+ sb.AppendLine(" - w, h: width and height");
+ sb.AppendLine(" - val: value 0-100");
+ sb.AppendLine();
+ sb.AppendLine("Icon: ");
+ sb.AppendLine(" - Inserts a bitmap icon from the SD card");
+ sb.AppendLine(" - Use the 'Insert Icon' button to browse available icons");
+ sb.AppendLine();
+ sb.AppendLine("Rectangle (outline): ");
+ sb.AppendLine();
+ sb.AppendLine("Filled Box: ");
+ sb.AppendLine();
+ sb.AppendLine("Line: ");
+ sb.AppendLine();
+
+ // Sensor variables
+ sb.AppendLine("SENSOR VARIABLES");
+ sb.AppendLine("----------------");
+ sb.AppendLine("Use {SensorName} syntax to include sensor values.");
+ sb.AppendLine("Example: CPU: {CPU_Core_i7_Total_Load}%");
+
+ // Add available sensor variables with their current values
+ sb.AppendLine();
+ sb.AppendLine("Available sensor variables:");
+
+ var sensors = new Dictionary>();
+
+ foreach (var hardware in computer.Hardware)
+ {
+ hardware.Update();
+ foreach (var sensor in hardware.Sensors)
+ {
+ if (sensor.Value.HasValue &&
+ (sensor.SensorType == SensorType.Load ||
+ sensor.SensorType == SensorType.Temperature ||
+ sensor.SensorType == SensorType.Clock ||
+ sensor.SensorType == SensorType.Fan ||
+ sensor.SensorType == SensorType.Power ||
+ sensor.SensorType == SensorType.Data))
+ {
+ string sensorId = GetSensorVariableName(hardware.Name, sensor.Name);
+ sensors[sensorId] = new Tuple(
+ sensor.Value.Value,
+ $"{hardware.Name} {sensor.Name}",
+ sensor.SensorType);
+ }
+ }
+ }
+
+ // Group sensors by type for easier reading
+ var sensorGroups = sensors.GroupBy(s => s.Value.Item3);
+ foreach (var group in sensorGroups)
+ {
+ sb.AppendLine();
+ sb.AppendLine($"-- {group.Key} Sensors --");
+
+ foreach (var sensor in group)
+ {
+ string unit = GetSensorUnit(group.Key);
+ string value = FormatSensorValue(sensor.Value.Item1, group.Key);
+ sb.AppendLine($"{{{sensor.Key}}} = {value}{unit} ({sensor.Value.Item2})");
+ }
+ }
+
+ txtHelpContent.Text = sb.ToString();
+ }
+
+ private 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 => "%",
+ _ => ""
+ };
+ }
+
+ private string FormatSensorValue(float value, SensorType sensorType)
+ {
+ // Format different sensor types appropriately
+ 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 GetSensorVariableName(string hardwareName, string sensorName)
+ {
+ // Create a simplified and safe variable name
+ string name = $"{hardwareName}_{sensorName}"
+ .Replace(" ", "_")
+ .Replace("%", "Percent")
+ .Replace("#", "Num")
+ .Replace("/", "_")
+ .Replace("\\", "_")
+ .Replace("(", "")
+ .Replace(")", "")
+ .Replace(",", "");
+
+ return name;
+ }
+
+ private void btnInsertIcon_Click(object sender, EventArgs e)
+ {
+ using IconBrowser browser = new();
+ if (browser.ShowDialog() == DialogResult.OK)
+ {
+ // Get the icon markup and insert it at the cursor position
+ string iconMarkup = browser.GetIconMarkup();
+ if (!string.IsNullOrEmpty(iconMarkup))
+ {
+ int selectionStart = txtOledMarkup.SelectionStart;
+ txtOledMarkup.Text = txtOledMarkup.Text.Insert(selectionStart, iconMarkup);
+ txtOledMarkup.SelectionStart = selectionStart + iconMarkup.Length;
+ txtOledMarkup.Focus();
+
+ // Update preview
+ if (autoUpdatePreview)
+ UpdatePreview();
+ }
+ }
+ }
+
+ private void btnLoadOledExample_Click(object sender, EventArgs e)
+ {
+ // Create an appropriate example based on the user's hardware
+ string exampleMarkup = "";
+ 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);
+
+ exampleMarkup = "System Monitor\n";
+
+ if (!string.IsNullOrEmpty(cpuLoad) && !string.IsNullOrEmpty(cpuTemp))
+ {
+ exampleMarkup += $"CPU: {{{cpuLoad}}}% ({{{cpuTemp}}}°C)\n";
+ exampleMarkup += $"\n";
+ }
+ else if (!string.IsNullOrEmpty(cpuLoad))
+ {
+ exampleMarkup += $"CPU: {{{cpuLoad}}}%\n";
+ exampleMarkup += $"\n";
+ }
+
+ if (!string.IsNullOrEmpty(gpuLoad) && !string.IsNullOrEmpty(gpuTemp))
+ {
+ exampleMarkup += $"GPU: {{{gpuLoad}}}% ({{{gpuTemp}}}°C)\n";
+ exampleMarkup += $"\n";
+ }
+ else if (!string.IsNullOrEmpty(gpuLoad))
+ {
+ exampleMarkup += $"GPU: {{{gpuLoad}}}%\n";
+ exampleMarkup += $"\n";
+ }
+
+ if (!string.IsNullOrEmpty(ramUsed))
+ {
+ exampleMarkup += $"RAM: {{{ramUsed}}} GB\n";
+ }
+
+ txtOledMarkup.Text = exampleMarkup;
+
+ // Update preview
+ if (autoUpdatePreview)
+ UpdatePreview();
+ }
+
+ private 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;
+ }
+
+ private void btnPreview_Click(object sender, EventArgs e)
+ {
+ UpdateSensorValues();
+ UpdatePreview();
+ }
+
+ private void txtOledMarkup_TextChanged(object sender, EventArgs e)
+ {
+ // If auto-update is enabled, update the preview when the markup changes
+ if (autoUpdatePreview)
+ {
+ UpdatePreview();
+ }
+ }
+
+ private void UpdatePreview()
+ {
+ try
+ {
+ // Clear existing elements
+ previewElements.Clear();
+
+ // Parse the markup into preview elements
+ string markup = ProcessVariablesInMarkup(txtOledMarkup.Text);
+ ParseMarkup(markup);
+
+ // Trigger repaint of the preview panel
+ if (panelOledPreview != null)
+ panelOledPreview.Invalidate();
+ }
+ catch (Exception ex)
+ {
+ // Don't show errors during preview - just skip rendering
+ Console.WriteLine($"Preview error: {ex.Message}");
+ }
+ }
+
+ private 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;
+ }
+
+ private void ParseMarkup(string markup)
+ {
+ if (string.IsNullOrEmpty(markup))
+ return;
+
+ // Parse text elements - Hello
+ foreach (Match match in Regex.Matches(markup, @"([^<]*)"))
+ {
+ previewElements.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 -
+ foreach (Match match in Regex.Matches(markup, @""))
+ {
+ previewElements.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 -
+ foreach (Match match in Regex.Matches(markup, @""))
+ {
+ previewElements.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 -
+ foreach (Match match in Regex.Matches(markup, @""))
+ {
+ previewElements.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 -
+ foreach (Match match in Regex.Matches(markup, @""))
+ {
+ previewElements.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 -
+ foreach (Match match in Regex.Matches(markup, @""))
+ {
+ previewElements.Add(new IconElement
+ {
+ X = int.Parse(match.Groups[1].Value),
+ Y = int.Parse(match.Groups[2].Value),
+ Name = match.Groups[3].Value
+ });
+ }
+ }
+
+ private void panelOledPreview_Paint(object sender, PaintEventArgs e)
+ {
+ // Create graphics object with smooth rendering
+ e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+
+ // Draw OLED display boundary
+ int panelWidth = panelOledPreview.Width;
+ int panelHeight = panelOledPreview.Height;
+
+ // Calculate scale to fit preview in panel while maintaining aspect ratio
+ float scaleX = (float)panelWidth / OledWidth;
+ float scaleY = (float)panelHeight / OledHeight;
+ float scale = Math.Min(scaleX, scaleY);
+
+ // Calculate centered position
+ int displayWidth = (int)(OledWidth * scale);
+ int displayHeight = (int)(OledHeight * scale);
+ int offsetX = (panelWidth - displayWidth) / 2;
+ int offsetY = (panelHeight - displayHeight) / 2;
+
+ // Draw display outline
+ Rectangle displayRect = new Rectangle(offsetX, offsetY, displayWidth, displayHeight);
+ e.Graphics.DrawRectangle(Pens.DarkGray, displayRect);
+
+ // Set up transformation to scale the preview elements
+ e.Graphics.TranslateTransform(offsetX, offsetY);
+ e.Graphics.ScaleTransform(scale, scale);
+
+ // Draw all elements
+ foreach (var element in previewElements)
+ {
+ element.Draw(e.Graphics);
+ }
+
+ // Reset transformation
+ e.Graphics.ResetTransform();
+
+ // Draw labels and guidelines
+ e.Graphics.DrawString($"OLED: {OledWidth}x{OledHeight}", new Font("Arial", 8), Brushes.Gray, 5, 5);
+ }
+
+ protected override void OnFormClosing(FormClosingEventArgs e)
+ {
+ base.OnFormClosing(e);
+
+ // Clean up resources
+ sensorUpdateTimer?.Stop();
+ sensorUpdateTimer?.Dispose();
+ computer?.Close();
+ }
}
-}
+}
\ No newline at end of file
diff --git a/PCPalConfigurator/IconBrowser/IconBrowser.Designer.cs b/PCPalConfigurator/IconBrowser/IconBrowser.Designer.cs
new file mode 100644
index 0000000..0018372
--- /dev/null
+++ b/PCPalConfigurator/IconBrowser/IconBrowser.Designer.cs
@@ -0,0 +1,317 @@
+namespace PCPalConfigurator
+{
+ partial class IconBrowser
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.panelTop = new System.Windows.Forms.Panel();
+ this.btnBrowse = new System.Windows.Forms.Button();
+ this.txtIconDirectory = new System.Windows.Forms.TextBox();
+ this.label1 = new System.Windows.Forms.Label();
+ this.panelBottom = new System.Windows.Forms.Panel();
+ this.txtIconDimensions = new System.Windows.Forms.TextBox();
+ this.label5 = new System.Windows.Forms.Label();
+ this.numY = new System.Windows.Forms.NumericUpDown();
+ this.numX = new System.Windows.Forms.NumericUpDown();
+ this.label4 = new System.Windows.Forms.Label();
+ this.label3 = new System.Windows.Forms.Label();
+ this.txtIconName = new System.Windows.Forms.TextBox();
+ this.label2 = new System.Windows.Forms.Label();
+ this.btnCancel = new System.Windows.Forms.Button();
+ this.btnInsert = new System.Windows.Forms.Button();
+ this.lblStatus = new System.Windows.Forms.Label();
+ this.splitContainer = new System.Windows.Forms.SplitContainer();
+ this.lstIcons = new System.Windows.Forms.ListBox();
+ this.flowLayoutPanel = new System.Windows.Forms.FlowLayoutPanel();
+ this.panelTop.SuspendLayout();
+ this.panelBottom.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.numY)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.numX)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
+ this.splitContainer.Panel1.SuspendLayout();
+ this.splitContainer.Panel2.SuspendLayout();
+ this.splitContainer.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // panelTop
+ //
+ this.panelTop.Controls.Add(this.btnBrowse);
+ this.panelTop.Controls.Add(this.txtIconDirectory);
+ this.panelTop.Controls.Add(this.label1);
+ this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
+ this.panelTop.Location = new System.Drawing.Point(0, 0);
+ this.panelTop.Name = "panelTop";
+ this.panelTop.Size = new System.Drawing.Size(584, 40);
+ this.panelTop.TabIndex = 0;
+ //
+ // btnBrowse
+ //
+ this.btnBrowse.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ this.btnBrowse.Location = new System.Drawing.Point(497, 9);
+ this.btnBrowse.Name = "btnBrowse";
+ this.btnBrowse.Size = new System.Drawing.Size(75, 23);
+ this.btnBrowse.TabIndex = 2;
+ this.btnBrowse.Text = "Browse...";
+ this.btnBrowse.UseVisualStyleBackColor = true;
+ this.btnBrowse.Click += new System.EventHandler(this.btnBrowse_Click);
+ //
+ // txtIconDirectory
+ //
+ this.txtIconDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.txtIconDirectory.Location = new System.Drawing.Point(119, 10);
+ this.txtIconDirectory.Name = "txtIconDirectory";
+ this.txtIconDirectory.Size = new System.Drawing.Size(372, 23);
+ this.txtIconDirectory.TabIndex = 1;
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(12, 13);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(101, 15);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "Icons Directory:";
+ //
+ // panelBottom
+ //
+ this.panelBottom.Controls.Add(this.txtIconDimensions);
+ this.panelBottom.Controls.Add(this.label5);
+ this.panelBottom.Controls.Add(this.numY);
+ this.panelBottom.Controls.Add(this.numX);
+ this.panelBottom.Controls.Add(this.label4);
+ this.panelBottom.Controls.Add(this.label3);
+ this.panelBottom.Controls.Add(this.txtIconName);
+ this.panelBottom.Controls.Add(this.label2);
+ this.panelBottom.Controls.Add(this.btnCancel);
+ this.panelBottom.Controls.Add(this.btnInsert);
+ this.panelBottom.Controls.Add(this.lblStatus);
+ this.panelBottom.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.panelBottom.Location = new System.Drawing.Point(0, 341);
+ this.panelBottom.Name = "panelBottom";
+ this.panelBottom.Size = new System.Drawing.Size(584, 120);
+ this.panelBottom.TabIndex = 1;
+ //
+ // txtIconDimensions
+ //
+ this.txtIconDimensions.Location = new System.Drawing.Point(119, 39);
+ this.txtIconDimensions.Name = "txtIconDimensions";
+ this.txtIconDimensions.ReadOnly = true;
+ this.txtIconDimensions.Size = new System.Drawing.Size(100, 23);
+ this.txtIconDimensions.TabIndex = 10;
+ //
+ // label5
+ //
+ this.label5.AutoSize = true;
+ this.label5.Location = new System.Drawing.Point(12, 42);
+ this.label5.Name = "label5";
+ this.label5.Size = new System.Drawing.Size(77, 15);
+ this.label5.TabIndex = 9;
+ this.label5.Text = "Dimensions:";
+ //
+ // numY
+ //
+ this.numY.Location = new System.Drawing.Point(119, 82);
+ this.numY.Maximum = new decimal(new int[] {
+ 200,
+ 0,
+ 0,
+ 0});
+ this.numY.Name = "numY";
+ this.numY.Size = new System.Drawing.Size(60, 23);
+ this.numY.TabIndex = 8;
+ //
+ // numX
+ //
+ this.numX.Location = new System.Drawing.Point(119, 68);
+ this.numX.Maximum = new decimal(new int[] {
+ 200,
+ 0,
+ 0,
+ 0});
+ this.numX.Name = "numX";
+ this.numX.Size = new System.Drawing.Size(60, 23);
+ this.numX.TabIndex = 7;
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(12, 84);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(67, 15);
+ this.label4.TabIndex = 6;
+ this.label4.Text = "Y Position:";
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(12, 70);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(67, 15);
+ this.label3.TabIndex = 5;
+ this.label3.Text = "X Position:";
+ //
+ // txtIconName
+ //
+ this.txtIconName.Location = new System.Drawing.Point(119, 10);
+ this.txtIconName.Name = "txtIconName";
+ this.txtIconName.ReadOnly = true;
+ this.txtIconName.Size = new System.Drawing.Size(200, 23);
+ this.txtIconName.TabIndex = 4;
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(12, 13);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(101, 15);
+ this.label2.TabIndex = 3;
+ this.label2.Text = "Selected Icon:";
+ //
+ // btnCancel
+ //
+ this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.btnCancel.Location = new System.Drawing.Point(497, 85);
+ this.btnCancel.Name = "btnCancel";
+ this.btnCancel.Size = new System.Drawing.Size(75, 23);
+ this.btnCancel.TabIndex = 2;
+ this.btnCancel.Text = "Cancel";
+ this.btnCancel.UseVisualStyleBackColor = true;
+ this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
+ //
+ // btnInsert
+ //
+ this.btnInsert.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.btnInsert.Location = new System.Drawing.Point(416, 85);
+ this.btnInsert.Name = "btnInsert";
+ this.btnInsert.Size = new System.Drawing.Size(75, 23);
+ this.btnInsert.TabIndex = 1;
+ this.btnInsert.Text = "Insert";
+ this.btnInsert.UseVisualStyleBackColor = true;
+ this.btnInsert.Click += new System.EventHandler(this.btnInsert_Click);
+ //
+ // lblStatus
+ //
+ this.lblStatus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+ this.lblStatus.AutoSize = true;
+ this.lblStatus.Location = new System.Drawing.Point(325, 13);
+ this.lblStatus.Name = "lblStatus";
+ this.lblStatus.Size = new System.Drawing.Size(0, 15);
+ this.lblStatus.TabIndex = 0;
+ //
+ // splitContainer
+ //
+ this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.splitContainer.Location = new System.Drawing.Point(0, 40);
+ this.splitContainer.Name = "splitContainer";
+ //
+ // splitContainer.Panel1
+ //
+ this.splitContainer.Panel1.Controls.Add(this.lstIcons);
+ this.splitContainer.Panel1Collapsed = true;
+ this.splitContainer.Panel1MinSize = 0;
+ //
+ // splitContainer.Panel2
+ //
+ this.splitContainer.Panel2.Controls.Add(this.flowLayoutPanel);
+ this.splitContainer.Size = new System.Drawing.Size(584, 301);
+ this.splitContainer.SplitterDistance = 194;
+ this.splitContainer.TabIndex = 2;
+ //
+ // lstIcons
+ //
+ this.lstIcons.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.lstIcons.FormattingEnabled = true;
+ this.lstIcons.ItemHeight = 15;
+ this.lstIcons.Location = new System.Drawing.Point(0, 0);
+ this.lstIcons.Name = "lstIcons";
+ this.lstIcons.Size = new System.Drawing.Size(194, 301);
+ this.lstIcons.TabIndex = 0;
+ //
+ // flowLayoutPanel
+ //
+ this.flowLayoutPanel.AutoScroll = true;
+ this.flowLayoutPanel.BackColor = System.Drawing.SystemColors.ControlLightLight;
+ this.flowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.flowLayoutPanel.Location = new System.Drawing.Point(0, 0);
+ this.flowLayoutPanel.Name = "flowLayoutPanel";
+ this.flowLayoutPanel.Padding = new System.Windows.Forms.Padding(10);
+ this.flowLayoutPanel.Size = new System.Drawing.Size(584, 301);
+ this.flowLayoutPanel.TabIndex = 0;
+ //
+ // IconBrowser
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(584, 461);
+ this.Controls.Add(this.splitContainer);
+ this.Controls.Add(this.panelBottom);
+ this.Controls.Add(this.panelTop);
+ this.MinimizeBox = false;
+ this.MinimumSize = new System.Drawing.Size(400, 400);
+ this.Name = "IconBrowser";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "Icon Browser";
+ this.Load += new System.EventHandler(this.IconBrowser_Load);
+ this.panelTop.ResumeLayout(false);
+ this.panelTop.PerformLayout();
+ this.panelBottom.ResumeLayout(false);
+ this.panelBottom.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.numY)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.numX)).EndInit();
+ this.splitContainer.Panel1.ResumeLayout(false);
+ this.splitContainer.Panel2.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
+ this.splitContainer.ResumeLayout(false);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private Panel panelTop;
+ private Button btnBrowse;
+ private TextBox txtIconDirectory;
+ private Label label1;
+ private Panel panelBottom;
+ private Button btnCancel;
+ private Button btnInsert;
+ private Label lblStatus;
+ private SplitContainer splitContainer;
+ private ListBox lstIcons;
+ private FlowLayoutPanel flowLayoutPanel;
+ private TextBox txtIconName;
+ private Label label2;
+ private NumericUpDown numY;
+ private NumericUpDown numX;
+ private Label label4;
+ private Label label3;
+ private TextBox txtIconDimensions;
+ private Label label5;
+ }
+}
\ No newline at end of file
diff --git a/PCPalConfigurator/IconBrowser/IconBrowser.cs b/PCPalConfigurator/IconBrowser/IconBrowser.cs
new file mode 100644
index 0000000..d2941b6
--- /dev/null
+++ b/PCPalConfigurator/IconBrowser/IconBrowser.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using System.Windows.Forms;
+
+namespace PCPalConfigurator
+{
+ public partial class IconBrowser : Form
+ {
+ private string _selectedIconPath = "";
+ private int _selectedIconWidth = 0;
+ private int _selectedIconHeight = 0;
+ public string SelectedIconName { get; private set; } = "";
+
+ public IconBrowser()
+ {
+ InitializeComponent();
+ }
+
+ private void IconBrowser_Load(object? sender, EventArgs e)
+ {
+ // Load last used directory from settings if available
+ string lastDir = Properties.Settings.Default.LastIconDirectory;
+ if (!string.IsNullOrEmpty(lastDir) && Directory.Exists(lastDir))
+ {
+ txtIconDirectory.Text = lastDir;
+ LoadIconsFromDirectory(lastDir);
+ }
+ }
+
+ private void btnBrowse_Click(object? sender, EventArgs e)
+ {
+ using FolderBrowserDialog dialog = new();
+ if (!string.IsNullOrEmpty(txtIconDirectory.Text) && Directory.Exists(txtIconDirectory.Text))
+ {
+ dialog.InitialDirectory = txtIconDirectory.Text;
+ }
+
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ txtIconDirectory.Text = dialog.SelectedPath;
+ LoadIconsFromDirectory(dialog.SelectedPath);
+
+ // Save the directory for next time
+ Properties.Settings.Default.LastIconDirectory = dialog.SelectedPath;
+ Properties.Settings.Default.Save();
+ }
+ }
+
+ private void LoadIconsFromDirectory(string directory)
+ {
+ try
+ {
+ lstIcons.Items.Clear();
+ flowLayoutPanel.Controls.Clear();
+
+ string[] xbmFiles = Directory.GetFiles(directory, "*.bin");
+ if (xbmFiles.Length == 0)
+ {
+ MessageBox.Show("No XBM icon files found in the selected directory.",
+ "No Icons Found", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
+ }
+
+ foreach (string file in xbmFiles)
+ {
+ // Create a button for each icon
+ Button iconButton = new();
+
+ // Parse the XBM file to get dimensions
+ (int width, int height) = ParseXbmDimensions(file);
+
+ string fileName = Path.GetFileNameWithoutExtension(file);
+ iconButton.Text = $"{fileName} ({width}x{height})";
+ iconButton.Tag = new IconInfo
+ {
+ Path = file,
+ Name = fileName,
+ Width = width,
+ Height = height
+ };
+
+ iconButton.Size = new Size(150, 40);
+ iconButton.Click += IconButton_Click;
+ flowLayoutPanel.Controls.Add(iconButton);
+ }
+
+ lblStatus.Text = $"Found {xbmFiles.Length} XBM files";
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Error loading icons: {ex.Message}",
+ "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ private (int width, int height) ParseXbmDimensions(string filePath)
+ {
+ int width = 0;
+ int height = 0;
+
+ try
+ {
+ // Read the first few lines of the XBM file
+ using StreamReader reader = new(filePath);
+ string line;
+ while ((line = reader.ReadLine()) != null && (width == 0 || height == 0))
+ {
+ // Look for width and height definitions in XBM format
+ if (line.Contains("_width"))
+ {
+ width = ExtractNumberFromLine(line);
+ }
+ else if (line.Contains("_height"))
+ {
+ height = ExtractNumberFromLine(line);
+ }
+ }
+ }
+ catch
+ {
+ // If we can't parse, default to unknown dimensions
+ width = 0;
+ height = 0;
+ }
+
+ return (width, height);
+ }
+
+ private int ExtractNumberFromLine(string line)
+ {
+ // Extract the numeric value from a line like "#define icon_width 16"
+ try
+ {
+ string[] parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length >= 3)
+ {
+ if (int.TryParse(parts[2], out int result))
+ {
+ return result;
+ }
+ }
+ }
+ catch { }
+
+ return 0;
+ }
+
+ private void IconButton_Click(object? sender, EventArgs e)
+ {
+ if (sender is Button button && button.Tag is IconInfo info)
+ {
+ // Highlight the selected button
+ foreach (Control control in flowLayoutPanel.Controls)
+ {
+ if (control is Button btn)
+ {
+ btn.BackColor = SystemColors.Control;
+ }
+ }
+ button.BackColor = Color.LightBlue;
+
+ // Store the selected icon info
+ SelectedIconName = info.Name;
+ _selectedIconPath = info.Path;
+ _selectedIconWidth = info.Width;
+ _selectedIconHeight = info.Height;
+
+ // Update UI
+ txtIconName.Text = info.Name;
+ txtIconDimensions.Text = $"{info.Width} x {info.Height}";
+ numX.Value = 0;
+ numY.Value = 0;
+ }
+ }
+
+ private void btnInsert_Click(object? sender, EventArgs e)
+ {
+ if (string.IsNullOrEmpty(SelectedIconName))
+ {
+ MessageBox.Show("Please select an icon first.",
+ "No Icon Selected", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
+ }
+
+ // Mark as success and close
+ DialogResult = DialogResult.OK;
+ Close();
+ }
+
+ private void btnCancel_Click(object? sender, EventArgs e)
+ {
+ DialogResult = DialogResult.Cancel;
+ Close();
+ }
+
+ public string GetIconMarkup()
+ {
+ if (string.IsNullOrEmpty(SelectedIconName))
+ return string.Empty;
+
+ return $"";
+ }
+
+ // Helper class for icon information
+ private class IconInfo
+ {
+ public string Path { get; set; } = "";
+ public string Name { get; set; } = "";
+ public int Width { get; set; }
+ public int Height { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PCPalConfigurator/IconBrowser/IconBrowser.resx b/PCPalConfigurator/IconBrowser/IconBrowser.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/PCPalConfigurator/IconBrowser/IconBrowser.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/PCPalConfigurator/PCPalConfigurator.csproj b/PCPalConfigurator/PCPalConfigurator.csproj
index 7b256e7..acd2bae 100644
--- a/PCPalConfigurator/PCPalConfigurator.csproj
+++ b/PCPalConfigurator/PCPalConfigurator.csproj
@@ -13,4 +13,25 @@
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PCPalConfigurator/PCPalConfigurator.csproj.user b/PCPalConfigurator/PCPalConfigurator.csproj.user
index c480a93..06b365e 100644
--- a/PCPalConfigurator/PCPalConfigurator.csproj.user
+++ b/PCPalConfigurator/PCPalConfigurator.csproj.user
@@ -4,5 +4,8 @@
Form
+
+ Form
+
\ No newline at end of file
diff --git a/PCPalConfigurator/Properties/Settings.Designer.cs b/PCPalConfigurator/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..23018e9
--- /dev/null
+++ b/PCPalConfigurator/Properties/Settings.Designer.cs
@@ -0,0 +1,38 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace PCPalConfigurator.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.13.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string LastIconDirectory {
+ get {
+ return ((string)(this["LastIconDirectory"]));
+ }
+ set {
+ this["LastIconDirectory"] = value;
+ }
+ }
+ }
+}
diff --git a/PCPalConfigurator/Properties/Settings.settings b/PCPalConfigurator/Properties/Settings.settings
new file mode 100644
index 0000000..faa019a
--- /dev/null
+++ b/PCPalConfigurator/Properties/Settings.settings
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PCPalService/Worker.cs b/PCPalService/Worker.cs
index 100c20e..68fdae4 100644
--- a/PCPalService/Worker.cs
+++ b/PCPalService/Worker.cs
@@ -6,6 +6,7 @@ using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Collections.Generic;
using Newtonsoft.Json;
using LibreHardwareMonitor.Hardware;
@@ -16,6 +17,7 @@ namespace PCPalService
private readonly ILogger _logger;
private SerialPort serialPort;
private Computer computer;
+ private readonly Dictionary _sensorValues = new();
private const string AppDataFolder = "PCPal";
private const string ConfigFileName = "config.json";
@@ -33,7 +35,9 @@ namespace PCPalService
IsCpuEnabled = true,
IsMemoryEnabled = true,
IsGpuEnabled = true,
- IsMotherboardEnabled = true
+ IsMotherboardEnabled = true,
+ IsControllerEnabled = true,
+ IsStorageEnabled = true
};
computer.Open();
@@ -58,33 +62,93 @@ namespace PCPalService
{
serialPort = new SerialPort(port, 115200);
serialPort.Open();
- _logger.LogInformation($"Connected to ESP32 on {port}");
+ _logger.LogInformation($"Connected to display device on {port}");
}
else
{
- _logger.LogWarning("ESP32 not found. Retrying in 5 seconds...");
+ _logger.LogWarning("Display device not found. Retrying in 5 seconds...");
await Task.Delay(5000, stoppingToken);
continue;
}
}
- string line1 = GetSensorLine(config.Line1Selection, config.Line1CustomText, config.Line1PostText);
- string line2 = GetSensorLine(config.Line2Selection, config.Line2CustomText, config.Line2PostText);
+ // Update sensor values
+ UpdateSensorValues();
- serialPort.WriteLine($"CMD:LCD,0,{line1}");
- serialPort.WriteLine($"CMD:LCD,1,{line2}");
- _logger.LogInformation("Data sent to LCD.");
+ // Send appropriate commands based on screen type
+ if (config.ScreenType == "1602" || config.ScreenType == "TFT4_6")
+ {
+ // LCD display handling
+ string line1 = GetSensorLine(config.Line1Selection, config.Line1CustomText, config.Line1PostText);
+ string line2 = GetSensorLine(config.Line2Selection, config.Line2CustomText, config.Line2PostText);
+
+ serialPort.WriteLine($"CMD:LCD,0,{line1}");
+ serialPort.WriteLine($"CMD:LCD,1,{line2}");
+ _logger.LogDebug("Data sent to LCD.");
+ }
+ else if (config.ScreenType == "OLED")
+ {
+ // OLED display handling
+ string processedMarkup = ProcessVariablesInMarkup(config.OledMarkup);
+ serialPort.WriteLine($"CMD:OLED,{processedMarkup}");
+ _logger.LogDebug("Data sent to OLED.");
+ }
+ else
+ {
+ _logger.LogWarning($"Unknown screen type: {config.ScreenType}");
+ }
await Task.Delay(5000, stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in main loop");
+ DisconnectFromDevice();
await Task.Delay(5000, stoppingToken);
}
}
- serialPort?.Close();
+ DisconnectFromDevice();
+ computer.Close();
+ _logger.LogInformation("Service shutting down...");
+ }
+
+ private void DisconnectFromDevice()
+ {
+ if (serialPort != null && serialPort.IsOpen)
+ {
+ try
+ {
+ serialPort.Close();
+ serialPort.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error disconnecting from device");
+ }
+ finally
+ {
+ serialPort = null;
+ }
+ }
+ }
+
+ private void UpdateSensorValues()
+ {
+ // Update all hardware readings
+ 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;
+ }
+ }
+ }
}
private string GetConfigPath()
@@ -109,11 +173,15 @@ namespace PCPalService
using (SerialPort test = new SerialPort(port, 115200))
{
test.Open();
- test.WriteLine("CMD:GET_LCD_TYPE");
+ test.WriteLine("CMD:GET_DISPLAY_TYPE");
Thread.Sleep(500);
string response = test.ReadExisting();
- if (response.Contains("LCD_TYPE:1602A"))
+
+ if (response.Contains("DISPLAY_TYPE:1602A") ||
+ response.Contains("DISPLAY_TYPE:OLED"))
+ {
return port;
+ }
}
}
catch { }
@@ -123,15 +191,15 @@ namespace PCPalService
private string GetSensorLine(string selection, string prefix, string suffix)
{
- if (selection == "Custom Text")
- return prefix;
+ if (selection == "Custom Text" || string.IsNullOrEmpty(selection))
+ return prefix ?? string.Empty;
var parsed = ParseSensorSelection(selection);
if (parsed == null)
return "N/A";
string value = GetSensorValue(parsed.Value.hardwareType, parsed.Value.sensorName, parsed.Value.sensorType);
- return $"{prefix}{value}{suffix}";
+ return $"{prefix ?? ""}{value}{suffix ?? ""}";
}
private (HardwareType hardwareType, string sensorName, SensorType sensorType)? ParseSensorSelection(string input)
@@ -167,7 +235,6 @@ namespace PCPalService
{
if (hardware.HardwareType == type)
{
- hardware.Update();
foreach (var sensor in hardware.Sensors)
{
if (sensor.SensorType == sensorType && sensor.Name == name)
@@ -179,16 +246,66 @@ namespace PCPalService
}
return "N/A";
}
+
+ private 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;
+ }
+
+ private string GetSensorVariableName(string hardwareName, string sensorName)
+ {
+ // Create a simplified and safe variable name
+ string name = $"{hardwareName}_{sensorName}"
+ .Replace(" ", "_")
+ .Replace("%", "Percent")
+ .Replace("#", "Num")
+ .Replace("/", "_")
+ .Replace("\\", "_")
+ .Replace("(", "")
+ .Replace(")", "")
+ .Replace(",", "");
+
+ return name;
+ }
}
public class ConfigData
{
+ // Existing properties
+ public string LastUsedPort { get; set; }
+ public string ScreenType { get; set; } // Could be "1602", "TFT4_6", or "OLED"
public string Line1Selection { get; set; }
public string Line1CustomText { get; set; }
- public string Line1PostText { get; set; }
public string Line2Selection { get; set; }
public string Line2CustomText { get; set; }
+ public string Line1PostText { get; set; }
public string Line2PostText { get; set; }
- public string ScreenType { get; set; }
+
+ // New properties for OLED support
+ public string OledMarkup { get; set; } // Store the full markup for OLED
+ public string LastIconDirectory { get; set; } // Remember last used icon directory
}
}
\ No newline at end of file
diff --git a/RGB666_tests/RGB666_tests.ino b/RGB666_tests/RGB666_tests.ino
new file mode 100644
index 0000000..9cb50de
--- /dev/null
+++ b/RGB666_tests/RGB666_tests.ino
@@ -0,0 +1,170 @@
+// SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
+//
+// SPDX-License-Identifier: MIT
+
+#include
+#include
+#include
+
+Arduino_XCA9554SWSPI *expander = new Arduino_XCA9554SWSPI(
+ PCA_TFT_RESET, PCA_TFT_CS, PCA_TFT_SCK, PCA_TFT_MOSI,
+ &Wire, 0x3F);
+
+Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
+ TFT_DE, TFT_VSYNC, TFT_HSYNC, TFT_PCLK,
+ TFT_R1, TFT_R2, TFT_R3, TFT_R4, TFT_R5,
+ TFT_G0, TFT_G1, TFT_G2, TFT_G3, TFT_G4, TFT_G5,
+ TFT_B1, TFT_B2, TFT_B3, TFT_B4, TFT_B5,
+ 1 /* hsync_polarity */, 50 /* hsync_front_porch */, 2 /* hsync_pulse_width */, 44 /* hsync_back_porch */,
+ 1 /* vsync_polarity */, 16 /* vsync_front_porch */, 2 /* vsync_pulse_width */, 18 /* vsync_back_porch */
+// ,1, 30000000
+ );
+
+Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
+// 2.1" 480x480 round display
+// 480 /* width */, 480 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
+// expander, GFX_NOT_DEFINED /* RST */, TL021WVC02_init_operations, sizeof(TL021WVC02_init_operations));
+
+// 2.8" 480x480 round display
+// 480 /* width */, 480 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
+// expander, GFX_NOT_DEFINED /* RST */, TL028WVC01_init_operations, sizeof(TL028WVC01_init_operations));
+
+// 3.4" 480x480 square display
+// 480 /* width */, 480 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
+// expander, GFX_NOT_DEFINED /* RST */, tl034wvs05_b1477a_init_operations, sizeof(tl034wvs05_b1477a_init_operations));
+
+// 3.2" 320x820 rectangle bar display
+// 320 /* width */, 820 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
+// expander, GFX_NOT_DEFINED /* RST */, tl032fwv01_init_operations, sizeof(tl032fwv01_init_operations));
+
+// 3.7" 240x960 rectangle bar display
+ 240 /* width */, 960 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
+ expander, GFX_NOT_DEFINED /* RST */, HD371001C40_init_operations, sizeof(HD371001C40_init_operations), 120 /* col_offset1 */);
+
+// 4.0" 720x720 square display
+// 720 /* width */, 720 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
+// expander, GFX_NOT_DEFINED /* RST */, NULL, 0);
+
+// 4.0" 720x720 round display
+// 720 /* width */, 720 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
+// expander, GFX_NOT_DEFINED /* RST */, hd40015c40_init_operations, sizeof(hd40015c40_init_operations));
+// needs also the rgbpanel to have these pulse/sync values:
+// 1 /* hync_polarity */, 46 /* hsync_front_porch */, 2 /* hsync_pulse_width */, 44 /* hsync_back_porch */,
+// 1 /* vsync_polarity */, 50 /* vsync_front_porch */, 16 /* vsync_pulse_width */, 16 /* vsync_back_porch */
+
+uint16_t *colorWheel;
+
+// The Capacitive touchscreen overlays uses hardware I2C (SCL/SDA)
+
+// Most touchscreens use FocalTouch with I2C Address often but not always 0x48!
+#define I2C_TOUCH_ADDR 0x48
+
+// 2.1" 480x480 round display use CST826 touchscreen with I2C Address at 0x15
+//#define I2C_TOUCH_ADDR 0x15 // often but not always 0x48!
+
+Adafruit_FT6206 focal_ctp = Adafruit_FT6206(); // this library also supports FT5336U!
+Adafruit_CST8XX cst_ctp = Adafruit_CST8XX();
+bool touchOK = false; // we will check if the touchscreen exists
+bool isFocalTouch = false;
+
+void setup(void)
+{
+ Serial.begin(115200);
+ //while (!Serial) delay(100);
+
+#ifdef GFX_EXTRA_PRE_INIT
+ GFX_EXTRA_PRE_INIT();
+#endif
+
+ Serial.println("Beginning");
+ // Init Display
+
+ Wire.setClock(1000000); // speed up I2C
+ if (!gfx->begin()) {
+ Serial.println("gfx->begin() failed!");
+ }
+
+ Serial.println("Initialized!");
+
+ gfx->fillScreen(BLACK);
+
+ expander->pinMode(PCA_TFT_BACKLIGHT, OUTPUT);
+ expander->digitalWrite(PCA_TFT_BACKLIGHT, HIGH);
+
+ colorWheel = (uint16_t *) ps_malloc(gfx->width() * gfx->height() * sizeof(uint16_t));
+ if (colorWheel) {
+ generateColorWheel(colorWheel);
+ gfx->draw16bitRGBBitmap(0, 0, colorWheel, gfx->width(), gfx->height());
+ }
+
+ if (!focal_ctp.begin(0, &Wire, I2C_TOUCH_ADDR)) {
+ // Try the CST826 Touch Screen
+ if (!cst_ctp.begin(&Wire, I2C_TOUCH_ADDR)) {
+ Serial.print("No Touchscreen found at address 0x");
+ Serial.println(I2C_TOUCH_ADDR, HEX);
+ touchOK = false;
+ } else {
+ Serial.println("CST826 Touchscreen found");
+ touchOK = true;
+ isFocalTouch = false;
+ }
+ } else {
+ Serial.println("Focal Touchscreen found");
+ touchOK = true;
+ isFocalTouch = true;
+ }
+}
+
+void loop()
+{
+ if (touchOK) {
+ if (isFocalTouch && focal_ctp.touched()) {
+ TS_Point p = focal_ctp.getPoint(0);
+ Serial.printf("(%d, %d)\n", p.x, p.y);
+ gfx->fillRect(p.x, p.y, 5, 5, WHITE);
+ } else if (!isFocalTouch && cst_ctp.touched()) {
+ CST_TS_Point p = cst_ctp.getPoint(0);
+ Serial.printf("(%d, %d)\n", p.x, p.y);
+ gfx->fillRect(p.x, p.y, 5, 5, WHITE);
+ }
+ }
+
+ // use the buttons to turn off
+ if (! expander->digitalRead(PCA_BUTTON_DOWN)) {
+ expander->digitalWrite(PCA_TFT_BACKLIGHT, LOW);
+ }
+ // and on the backlight
+ if (! expander->digitalRead(PCA_BUTTON_UP)) {
+ expander->digitalWrite(PCA_TFT_BACKLIGHT, HIGH);
+ }
+}
+
+// https://chat.openai.com/share/8edee522-7875-444f-9fea-ae93a8dfa4ec
+void generateColorWheel(uint16_t *colorWheel) {
+ int width = gfx->width();
+ int height = gfx->height();
+ int half_width = width / 2;
+ int half_height = height / 2;
+ float angle;
+ uint8_t r, g, b;
+ int index, scaled_index;
+
+ for(int y = 0; y < half_height; y++) {
+ for(int x = 0; x < half_width; x++) {
+ index = y * half_width + x;
+ angle = atan2(y - half_height / 2, x - half_width / 2);
+ r = uint8_t(127.5 * (cos(angle) + 1));
+ g = uint8_t(127.5 * (sin(angle) + 1));
+ b = uint8_t(255 - (r + g) / 2);
+ uint16_t color = RGB565(r, g, b);
+
+ // Scale this pixel into 4 pixels in the full buffer
+ for(int dy = 0; dy < 2; dy++) {
+ for(int dx = 0; dx < 2; dx++) {
+ scaled_index = (y * 2 + dy) * width + (x * 2 + dx);
+ colorWheel[scaled_index] = color;
+ }
+ }
+ }
+ }
+}