From 8d0fdf11b704a126310d1276c24260ee6ecbf024 Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Thu, 24 Jul 2025 00:14:32 +0900 Subject: [PATCH 1/2] Added StorageRepository --- RunCat365/ContextMenuRenderer.cs | 26 ++++++ RunCat365/CustomToolStripMenuItem.cs | 49 +++++++++++ RunCat365/FPSMaxLimit.cs | 5 +- RunCat365/Program.cs | 53 +++++++---- RunCat365/Runner.cs | 2 +- RunCat365/StorageRepository.cs | 126 +++++++++++++++++++++++++++ RunCat365/Theme.cs | 2 +- 7 files changed, 242 insertions(+), 21 deletions(-) create mode 100644 RunCat365/ContextMenuRenderer.cs create mode 100644 RunCat365/CustomToolStripMenuItem.cs create mode 100644 RunCat365/StorageRepository.cs diff --git a/RunCat365/ContextMenuRenderer.cs b/RunCat365/ContextMenuRenderer.cs new file mode 100644 index 00000000..1e876842 --- /dev/null +++ b/RunCat365/ContextMenuRenderer.cs @@ -0,0 +1,26 @@ +namespace RunCat365 +{ + internal class ContextMenuRenderer : ToolStripProfessionalRenderer + { + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) + { + if (!string.IsNullOrEmpty(e.Text) && e.Item is CustomToolStripMenuItem item) + { + var textRectangle = e.TextRectangle; + textRectangle.Height = e.Item.Bounds.Height; + TextRenderer.DrawText( + e.Graphics, + e.Text, + e.TextFont, + textRectangle, + e.TextColor, + item.Flags() + ); + } + else + { + base.OnRenderItemText(e); + } + } + } +} diff --git a/RunCat365/CustomToolStripMenuItem.cs b/RunCat365/CustomToolStripMenuItem.cs new file mode 100644 index 00000000..6b50b0bb --- /dev/null +++ b/RunCat365/CustomToolStripMenuItem.cs @@ -0,0 +1,49 @@ +namespace RunCat365 +{ + internal class CustomToolStripMenuItem : ToolStripMenuItem + { + public CustomToolStripMenuItem(string? text) : base(text) { } + + public CustomToolStripMenuItem(string? text, Image? image, EventHandler? onClick) : base(text, image, onClick) { } + + public CustomToolStripMenuItem(string? text, Image? image, params ToolStripItem[]? dropDownItems) : base(text, image, dropDownItems) { } + + private readonly TextFormatFlags multiLineTextFlags = + TextFormatFlags.LeftAndRightPadding | + TextFormatFlags.VerticalCenter | + TextFormatFlags.WordBreak | + TextFormatFlags.TextBoxControl; + + private readonly TextFormatFlags singleLineTextFlags = + TextFormatFlags.LeftAndRightPadding | + TextFormatFlags.VerticalCenter | + TextFormatFlags.EndEllipsis; + + public override Size GetPreferredSize(Size constrainingSize) + { + Size baseSize = base.GetPreferredSize(constrainingSize); + if (string.IsNullOrEmpty(Text) || !Text.Contains('\n')) + { + return new Size(baseSize.Width, 22); + // return baseSize; + } + var textRenderWidth = Math.Max(constrainingSize.Width - 20, 1); + + SizeF measuredSize = TextRenderer.MeasureText( + Text, + Font, + new Size(textRenderWidth, int.MaxValue), + multiLineTextFlags + ); + var calculatedHeight = (int)Math.Ceiling(measuredSize.Height); + var height = Math.Max(baseSize.Height, calculatedHeight + 4); + return new Size(baseSize.Width, height); + } + + internal TextFormatFlags Flags() + { + if (string.IsNullOrEmpty(Text) || !Text.Contains('\n')) return singleLineTextFlags; + return multiLineTextFlags; + } + } +} \ No newline at end of file diff --git a/RunCat365/FPSMaxLimit.cs b/RunCat365/FPSMaxLimit.cs index 0ff7ee93..8440bf9f 100644 --- a/RunCat365/FPSMaxLimit.cs +++ b/RunCat365/FPSMaxLimit.cs @@ -24,7 +24,7 @@ enum FPSMaxLimit FPS10, } - static class FPSMaxLimitExtensions + internal static class FPSMaxLimitExtension { internal static string GetString(this FPSMaxLimit fpsMaxLimit) { @@ -49,10 +49,7 @@ internal static float GetRate(this FPSMaxLimit fPSMaxLimit) _ => 1f, }; } - } - static class _FPSMaxLimit - { internal static bool TryParse([NotNullWhen(true)] string? value, out FPSMaxLimit result) { FPSMaxLimit? nullableResult = value switch diff --git a/RunCat365/Program.cs b/RunCat365/Program.cs index ea426e7f..d5f35721 100644 --- a/RunCat365/Program.cs +++ b/RunCat365/Program.cs @@ -48,10 +48,11 @@ public class RunCat365ApplicationContext : ApplicationContext private const int CPU_VALUES_LIMIT_SIZE = 5; private const int ANIMATE_TIMER_DEFAULT_INTERVAL = 200; private readonly PerformanceCounter cpuCounter; - private readonly ToolStripMenuItem runnerMenu; - private readonly ToolStripMenuItem themeMenu; - private readonly ToolStripMenuItem startupMenu; - private readonly ToolStripMenuItem fpsMaxLimitMenu; + private readonly CustomToolStripMenuItem systemInfoMenu; + private readonly CustomToolStripMenuItem runnerMenu; + private readonly CustomToolStripMenuItem themeMenu; + private readonly CustomToolStripMenuItem fpsMaxLimitMenu; + private readonly CustomToolStripMenuItem startupMenu; private readonly NotifyIcon notifyIcon; private readonly FormsTimer animateTimer; private readonly FormsTimer cpuTimer; @@ -77,6 +78,11 @@ public RunCat365ApplicationContext() cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"); _ = cpuCounter.NextValue(); // discards first return value + systemInfoMenu = new CustomToolStripMenuItem("-\n-\n-\n-\n-") + { + Enabled = false + }; + runnerMenu = CreateMenuFromEnum( "Runner", r => r.GetString(), @@ -98,28 +104,31 @@ public RunCat365ApplicationContext() fps => fpsMaxLimit == fps ); - startupMenu = new ToolStripMenuItem("Startup", null, SetStartup); + startupMenu = new CustomToolStripMenuItem("Startup", null, SetStartup); if (IsStartupEnabled()) { startupMenu.Checked = true; } var appVersion = $"{Application.ProductName} v{Application.ProductVersion}"; - var appVersionMenu = new ToolStripMenuItem(appVersion) + var appVersionMenu = new CustomToolStripMenuItem(appVersion) { Enabled = false }; var contextMenuStrip = new ContextMenuStrip(new Container()); contextMenuStrip.Items.AddRange( + systemInfoMenu, + new ToolStripSeparator(), runnerMenu, themeMenu, fpsMaxLimitMenu, startupMenu, new ToolStripSeparator(), appVersionMenu, - new ToolStripMenuItem("Exit", null, Exit) + new CustomToolStripMenuItem("Exit", null, Exit) ); + contextMenuStrip.Renderer = new ContextMenuRenderer(); SetIcons(); @@ -127,7 +136,7 @@ public RunCat365ApplicationContext() { Icon = icons[0], ContextMenuStrip = contextMenuStrip, - Text = "0.0%", + Text = "-", Visible = true }; @@ -144,25 +153,27 @@ public RunCat365ApplicationContext() }; cpuTimer.Tick += new EventHandler(CPUTick); cpuTimer.Start(); + + FetchSystemInfo(0); } - private static ToolStripMenuItem CreateMenuFromEnum( + private static CustomToolStripMenuItem CreateMenuFromEnum( string title, Func getTitle, EventHandler onClickEvent, Func isChecked ) where T : Enum { - var items = new List(); + var items = new List(); foreach (T value in Enum.GetValues(typeof(T))) { - var item = new ToolStripMenuItem(getTitle(value), null, onClickEvent) + var item = new CustomToolStripMenuItem(getTitle(value), null, onClickEvent) { Checked = isChecked(value) }; items.Add(item); } - return new ToolStripMenuItem(title, null, [.. items]); + return new CustomToolStripMenuItem(title, null, [.. items]); } private void OnApplicationExit(object? sender, EventArgs e) @@ -264,7 +275,7 @@ private void SetFPSMaxLimit(object? sender, EventArgs e) HandleMenuItemSelection( sender, fpsMaxLimitMenu, - (string? s, out FPSMaxLimit f) => _FPSMaxLimit.TryParse(s, out f), + (string? s, out FPSMaxLimit f) => FPSMaxLimitExtension.TryParse(s, out f), value => fpsMaxLimit = value ); } @@ -322,14 +333,26 @@ private void CPUTick(object? state, EventArgs e) var averageValue = cpuValues.Average(); cpuValues.Clear(); - notifyIcon.Text = $"CPU: {averageValue:f1}%"; + FetchSystemInfo(averageValue); + // Range of interval: 25-500 (ms) = 2-40 (fps) interval = 500.0f / (float)Math.Max(1.0f, (averageValue / 5.0f) * fpsMaxLimit.GetRate()); - animateTimer.Stop(); animateTimer.Interval = (int)interval; animateTimer.Start(); } + + private void FetchSystemInfo(float cpuValue) + { + notifyIcon.Text = $"CPU: {cpuValue:f1}%"; + + var systemInfoValues = new List + { + $"CPU: {cpuValue:f1}%" + }; + systemInfoValues.AddRange(StorageRepository.Get().GenerateTree()); + systemInfoMenu.Text = string.Join("\n", [.. systemInfoValues]); + } } public delegate bool CustomTryParseDelegate(string? value, out T result); diff --git a/RunCat365/Runner.cs b/RunCat365/Runner.cs index ae708757..8b01c3fb 100644 --- a/RunCat365/Runner.cs +++ b/RunCat365/Runner.cs @@ -21,7 +21,7 @@ enum Runner Horse, } - static class RunnerExtensions + internal static class RunnerExtension { internal static string GetString(this Runner runner) { diff --git a/RunCat365/StorageRepository.cs b/RunCat365/StorageRepository.cs new file mode 100644 index 00000000..655f9104 --- /dev/null +++ b/RunCat365/StorageRepository.cs @@ -0,0 +1,126 @@ +// Copyright 2020 Takuto Nakamura +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using RunCat365; + +namespace RunCat365 +{ + enum Drive + { + C, + D + } + + internal static class DriveExtension + { + internal static string GetString(this Drive drive) + { + return drive switch + { + Drive.C => "C Drive", + Drive.D => "D Drive", + _ => "", + }; + } + + internal static Drive? CreateFromString(string? value) + { + return value switch + { + "C:\\" => Drive.C, + "D:\\" => Drive.D, + _ => null, + }; + } + } + + struct StorageInfo + { + internal Drive Drive { get; set; } + internal long TotalSize { get; set; } + internal long AvailableSpaceSize { get; set; } + internal long UsedSpaceSize { get; set; } + } + + static class ByteFormatter + { + internal static string ToByteFormatted(this long bytes) + { + string[] units = ["B", "KB", "MB", "GB", "TB"]; + int i = 0; + double doubleBytes = bytes; + while (1024 <= doubleBytes && i < units.Length - 1) + { + doubleBytes /= 1024; + i++; + } + return string.Format("{0:0.##} {1}", doubleBytes, units[i]); + } + } + + static class StorageRepository + { + internal static List Get() + { + var storageInfoList = new List(); + var allDrives = DriveInfo.GetDrives(); + foreach (DriveInfo driveInfo in allDrives) + { + if (driveInfo.IsReady && DriveExtension.CreateFromString(driveInfo.Name) is Drive drive) + { + try + { + var storageInfo = new StorageInfo + { + Drive = drive, + TotalSize = driveInfo.TotalSize, + AvailableSpaceSize = driveInfo.AvailableFreeSpace, + UsedSpaceSize = driveInfo.TotalSize - driveInfo.AvailableFreeSpace + }; + storageInfoList.Add(storageInfo); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } + } + return storageInfoList; + } + + internal static List GenerateTree(this List storageInfoList) + { + var resultLines = new List + { + "Storage:" + }; + + if (storageInfoList.Count == 0) return resultLines; + + for (int i = 0; i < storageInfoList.Count; i++) + { + var info = storageInfoList[i]; + var isLastItem = (i == storageInfoList.Count - 1); + var parentPrefix = isLastItem ? " └─ " : " ├─ "; + var childIndent = isLastItem ? " " : " │ "; + var percentage = ((double)info.UsedSpaceSize / info.TotalSize) * 100.0; + resultLines.Add($"{parentPrefix}{info.Drive.GetString()}: {percentage:f1}%"); + resultLines.Add($"{childIndent} ├─ Used: {info.UsedSpaceSize.ToByteFormatted()}"); + resultLines.Add($"{childIndent} └─ Available: {info.AvailableSpaceSize.ToByteFormatted()}"); + } + + return resultLines; + } + } +} \ No newline at end of file diff --git a/RunCat365/Theme.cs b/RunCat365/Theme.cs index c828d337..761e307e 100644 --- a/RunCat365/Theme.cs +++ b/RunCat365/Theme.cs @@ -21,7 +21,7 @@ enum Theme Dark, } - static class ThemeExtensions + internal static class ThemeExtension { internal static string GetString(this Theme theme) { From 8ef24951ff1fa0e4dd3219392affdef85a4e4213 Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Fri, 25 Jul 2025 01:44:01 +0900 Subject: [PATCH 2/2] Miner fix --- RunCat365/ContextMenuRenderer.cs | 4 ++-- RunCat365/CustomToolStripMenuItem.cs | 1 - RunCat365/Program.cs | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RunCat365/ContextMenuRenderer.cs b/RunCat365/ContextMenuRenderer.cs index 1e876842..3ca22b84 100644 --- a/RunCat365/ContextMenuRenderer.cs +++ b/RunCat365/ContextMenuRenderer.cs @@ -12,8 +12,8 @@ protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) e.Graphics, e.Text, e.TextFont, - textRectangle, - e.TextColor, + textRectangle, + e.Item.ForeColor, item.Flags() ); } diff --git a/RunCat365/CustomToolStripMenuItem.cs b/RunCat365/CustomToolStripMenuItem.cs index 6b50b0bb..947c887d 100644 --- a/RunCat365/CustomToolStripMenuItem.cs +++ b/RunCat365/CustomToolStripMenuItem.cs @@ -25,7 +25,6 @@ public override Size GetPreferredSize(Size constrainingSize) if (string.IsNullOrEmpty(Text) || !Text.Contains('\n')) { return new Size(baseSize.Width, 22); - // return baseSize; } var textRenderWidth = Math.Max(constrainingSize.Width - 20, 1); diff --git a/RunCat365/Program.cs b/RunCat365/Program.cs index a2c86521..bb6f24ac 100644 --- a/RunCat365/Program.cs +++ b/RunCat365/Program.cs @@ -126,6 +126,7 @@ public RunCat365ApplicationContext() startupMenu, new ToolStripSeparator(), appVersionMenu, + new ToolStripSeparator(), new CustomToolStripMenuItem("Exit", null, Exit) ); contextMenuStrip.Renderer = new ContextMenuRenderer();