Project updates for OLED

This commit is contained in:
NinjaPug
2025-04-13 14:06:05 -04:00
parent 7622c3972a
commit 1cf596b379
13 changed files with 1781 additions and 34 deletions

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="PCPalConfigurator.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
<section name="PCPalConfigurator.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<PCPalConfigurator.Properties.Settings>
<setting name="LastIconDirectory" serializeAs="String">
<value></value>
</setting>
</PCPalConfigurator.Properties.Settings>
<PCPalConfigurator.Settings>
<setting name="LastIconDirectory" serializeAs="String">
<value />
</setting>
</PCPalConfigurator.Settings>
</userSettings>
</configuration>

View File

@@ -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; }
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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<PreviewElement> previewElements = new List<PreviewElement>();
private Dictionary<string, float> sensorValues = new Dictionary<string, float>();
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: <text x=0 y=10 size=1>Hello World</text>");
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: <bar x=0 y=20 w=100 h=8 val=75 />");
sb.AppendLine(" - x, y: position");
sb.AppendLine(" - w, h: width and height");
sb.AppendLine(" - val: value 0-100");
sb.AppendLine();
sb.AppendLine("Icon: <icon x=0 y=0 name=cpu />");
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): <rect x=0 y=0 w=20 h=10 />");
sb.AppendLine();
sb.AppendLine("Filled Box: <box x=0 y=0 w=20 h=10 />");
sb.AppendLine();
sb.AppendLine("Line: <line x1=0 y1=0 x2=20 y2=20 />");
sb.AppendLine();
// Sensor variables
sb.AppendLine("SENSOR VARIABLES");
sb.AppendLine("----------------");
sb.AppendLine("Use {SensorName} syntax to include sensor values.");
sb.AppendLine("Example: <text x=0 y=10>CPU: {CPU_Core_i7_Total_Load}%</text>");
// Add available sensor variables with their current values
sb.AppendLine();
sb.AppendLine("Available sensor variables:");
var sensors = new Dictionary<string, Tuple<float, string, SensorType>>();
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<float, string, SensorType>(
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 = "<text x=0 y=12 size=2>System Monitor</text>\n";
if (!string.IsNullOrEmpty(cpuLoad) && !string.IsNullOrEmpty(cpuTemp))
{
exampleMarkup += $"<text x=0 y=30 size=1>CPU: {{{cpuLoad}}}% ({{{cpuTemp}}}°C)</text>\n";
exampleMarkup += $"<bar x=0 y=35 w=128 h=6 val={{{cpuLoad}}} />\n";
}
else if (!string.IsNullOrEmpty(cpuLoad))
{
exampleMarkup += $"<text x=0 y=30 size=1>CPU: {{{cpuLoad}}}%</text>\n";
exampleMarkup += $"<bar x=0 y=35 w=128 h=6 val={{{cpuLoad}}} />\n";
}
if (!string.IsNullOrEmpty(gpuLoad) && !string.IsNullOrEmpty(gpuTemp))
{
exampleMarkup += $"<text x=130 y=30 size=1>GPU: {{{gpuLoad}}}% ({{{gpuTemp}}}°C)</text>\n";
exampleMarkup += $"<bar x=130 y=35 w=120 h=6 val={{{gpuLoad}}} />\n";
}
else if (!string.IsNullOrEmpty(gpuLoad))
{
exampleMarkup += $"<text x=130 y=30 size=1>GPU: {{{gpuLoad}}}%</text>\n";
exampleMarkup += $"<bar x=130 y=35 w=120 h=6 val={{{gpuLoad}}} />\n";
}
if (!string.IsNullOrEmpty(ramUsed))
{
exampleMarkup += $"<text x=0 y=50 size=1>RAM: {{{ramUsed}}} GB</text>\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 - <text x=0 y=10 size=1>Hello</text>
foreach (Match match in Regex.Matches(markup, @"<text\s+x=(\d+)\s+y=(\d+)(?:\s+size=(\d+))?>([^<]*)</text>"))
{
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 - <bar x=0 y=20 w=100 h=8 val=75 />
foreach (Match match in Regex.Matches(markup, @"<bar\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s+val=(\d+)\s*/>"))
{
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 - <rect x=0 y=0 w=20 h=10 />
foreach (Match match in Regex.Matches(markup, @"<rect\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s*/>"))
{
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 - <box x=0 y=0 w=20 h=10 />
foreach (Match match in Regex.Matches(markup, @"<box\s+x=(\d+)\s+y=(\d+)\s+w=(\d+)\s+h=(\d+)\s*/>"))
{
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 - <line x1=0 y1=0 x2=20 y2=20 />
foreach (Match match in Regex.Matches(markup, @"<line\s+x1=(\d+)\s+y1=(\d+)\s+x2=(\d+)\s+y2=(\d+)\s*/>"))
{
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 - <icon x=0 y=0 name=cpu />
foreach (Match match in Regex.Matches(markup, @"<icon\s+x=(\d+)\s+y=(\d+)\s+name=([a-zA-Z0-9_]+)\s*/>"))
{
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();
}
}
}
}

View File

@@ -0,0 +1,317 @@
namespace PCPalConfigurator
{
partial class IconBrowser
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@@ -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 $"<icon x={numX.Value} y={numY.Value} name={SelectedIconName} />";
}
// 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; }
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -13,4 +13,25 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Core\" />
<Folder Include="Rendering\Elements\" />
<Folder Include="UI\" />
</ItemGroup>
</Project>

View File

@@ -4,5 +4,8 @@
<Compile Update="ConfiguratorForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="IconBrowser\IconBrowser.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,38 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}

View File

@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="PCPalConfigurator" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="LastIconDirectory" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View File

@@ -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<Worker> _logger;
private SerialPort serialPort;
private Computer computer;
private readonly Dictionary<string, float> _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
}
}

View File

@@ -0,0 +1,170 @@
// SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
//
// SPDX-License-Identifier: MIT
#include <Arduino_GFX_Library.h>
#include <Adafruit_FT6206.h>
#include <Adafruit_CST8XX.h>
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;
}
}
}
}
}