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