Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions RunCat365/ContextMenuRenderer.cs
Original file line number Diff line number Diff line change
@@ -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.Item.ForeColor,
item.Flags()
);
}
else
{
base.OnRenderItemText(e);
}
}
}
}
48 changes: 48 additions & 0 deletions RunCat365/CustomToolStripMenuItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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);
}
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;
}
}
}
5 changes: 1 addition & 4 deletions RunCat365/FPSMaxLimit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum FPSMaxLimit
FPS10,
}

static class FPSMaxLimitExtensions
internal static class FPSMaxLimitExtension
{
internal static string GetString(this FPSMaxLimit fpsMaxLimit)
{
Expand All @@ -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
Expand Down
54 changes: 39 additions & 15 deletions RunCat365/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>(
"Runner",
r => r.GetString(),
Expand All @@ -98,36 +104,40 @@ 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 ToolStripSeparator(),
new CustomToolStripMenuItem("Exit", null, Exit)
);
contextMenuStrip.Renderer = new ContextMenuRenderer();

SetIcons();

notifyIcon = new NotifyIcon()
{
Icon = icons[0],
ContextMenuStrip = contextMenuStrip,
Text = "0.0%",
Text = "-",
Visible = true
};

Expand All @@ -144,6 +154,8 @@ public RunCat365ApplicationContext()
};
cpuTimer.Tick += new EventHandler(CPUTick);
cpuTimer.Start();

FetchSystemInfo(0);
}

private static Bitmap? GetRunnerThumbnailBitmap(Runner runner)
Expand All @@ -153,25 +165,25 @@ public RunCat365ApplicationContext()
return obj is Icon icon ? icon.ToBitmap() : null;
}

private static ToolStripMenuItem CreateMenuFromEnum<T>(
private static CustomToolStripMenuItem CreateMenuFromEnum<T>(
string title,
Func<T, string> getTitle,
EventHandler onClickEvent,
Func<T, bool> isChecked
) where T : Enum
{
var items = new List<ToolStripMenuItem>();
var items = new List<CustomToolStripMenuItem>();
foreach (T value in Enum.GetValues(typeof(T)))
{
string entityName = getTitle(value);
Image? iconImage = value is Runner runner ? GetRunnerThumbnailBitmap(runner) : null;
var item = new ToolStripMenuItem(entityName, iconImage, onClickEvent)
var item = new CustomToolStripMenuItem(entityName, iconImage, 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)
Expand Down Expand Up @@ -273,7 +285,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
);
}
Expand Down Expand Up @@ -331,14 +343,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<string>
{
$"CPU: {cpuValue:f1}%"
};
systemInfoValues.AddRange(StorageRepository.Get().GenerateTree());
systemInfoMenu.Text = string.Join("\n", [.. systemInfoValues]);
}
}

public delegate bool CustomTryParseDelegate<T>(string? value, out T result);
Expand Down
2 changes: 1 addition & 1 deletion RunCat365/Runner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum Runner
Horse,
}

static class RunnerExtensions
internal static class RunnerExtension
{
internal static string GetString(this Runner runner)
{
Expand Down
126 changes: 126 additions & 0 deletions RunCat365/StorageRepository.cs
Original file line number Diff line number Diff line change
@@ -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<StorageInfo> Get()
{
var storageInfoList = new List<StorageInfo>();
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<string> GenerateTree(this List<StorageInfo> storageInfoList)
{
var resultLines = new List<string>
{
"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;
}
}
}
2 changes: 1 addition & 1 deletion RunCat365/Theme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum Theme
Dark,
}

static class ThemeExtensions
internal static class ThemeExtension
{
internal static string GetString(this Theme theme)
{
Expand Down