add: 久坐提醒项目文件
This commit is contained in:
91
.gitignore
vendored
Normal file
91
.gitignore
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
## .NET 项目忽略文件
|
||||
|
||||
# 编译输出
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
|
||||
# Visual Studio 用户特定文件
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Visual Studio 缓存/选项目录
|
||||
.vs/
|
||||
.vscode/
|
||||
|
||||
# Visual Studio 代码分析结果
|
||||
*.sln.iml
|
||||
|
||||
# 构建结果
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# NuGet 包
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
**/packages/*
|
||||
!**/packages/build/
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# MSTest 测试结果
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# .NET Core 项目文件
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# 用户特定文件
|
||||
*.user
|
||||
*.suo
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
*.log
|
||||
*.cache
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# 备份文件
|
||||
*.bak
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# 设置文件(如果包含敏感信息)
|
||||
# settings.json
|
||||
182
ActivityMonitor.cs
Normal file
182
ActivityMonitor.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TimerApp
|
||||
{
|
||||
public enum MonitorState
|
||||
{
|
||||
Idle,
|
||||
Working,
|
||||
Resting
|
||||
}
|
||||
|
||||
public class ActivityMonitor
|
||||
{
|
||||
private System.Windows.Forms.Timer _timer;
|
||||
private DateTime _lastWorkStartTime;
|
||||
private TimeSpan _accumulatedWorkTime;
|
||||
private DateTime _restStartTime;
|
||||
|
||||
// 配置 (默认值)
|
||||
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
|
||||
public TimeSpan RestDuration { get; set; } = TimeSpan.FromMinutes(1);
|
||||
public TimeSpan IdleThreshold { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
public MonitorState CurrentState { get; private set; } = MonitorState.Idle;
|
||||
|
||||
// 事件
|
||||
public event EventHandler<TimeSpan> WorkProgressChanged; // 剩余工作时间
|
||||
public event EventHandler<TimeSpan> RestProgressChanged; // 剩余休息时间
|
||||
public event EventHandler RestStarted;
|
||||
public event EventHandler RestEnded;
|
||||
public event EventHandler StateChanged;
|
||||
|
||||
public ActivityMonitor()
|
||||
{
|
||||
_timer = new System.Windows.Forms.Timer();
|
||||
_timer.Interval = 1000; // 1秒检查一次
|
||||
_timer.Tick += Timer_Tick;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_timer.Start();
|
||||
ResetWork();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
private void ResetWork()
|
||||
{
|
||||
_accumulatedWorkTime = TimeSpan.Zero;
|
||||
_lastWorkStartTime = DateTime.Now;
|
||||
ChangeState(MonitorState.Idle);
|
||||
}
|
||||
|
||||
private void ChangeState(MonitorState newState)
|
||||
{
|
||||
if (CurrentState != newState)
|
||||
{
|
||||
CurrentState = newState;
|
||||
StateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void Timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
long idleMs = NativeMethods.GetIdleTime();
|
||||
TimeSpan idleTime = TimeSpan.FromMilliseconds(idleMs);
|
||||
|
||||
if (CurrentState == MonitorState.Resting)
|
||||
{
|
||||
// 休息模式逻辑
|
||||
TimeSpan timeInRest = DateTime.Now - _restStartTime;
|
||||
TimeSpan remainingRest = RestDuration - timeInRest;
|
||||
|
||||
if (remainingRest <= TimeSpan.Zero)
|
||||
{
|
||||
// 休息结束
|
||||
RestEnded?.Invoke(this, EventArgs.Empty);
|
||||
ResetWork(); // 重新开始工作周期
|
||||
}
|
||||
else
|
||||
{
|
||||
RestProgressChanged?.Invoke(this, remainingRest);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 工作/空闲模式逻辑
|
||||
if (idleTime > IdleThreshold)
|
||||
{
|
||||
// 用户离开了
|
||||
if (CurrentState == MonitorState.Working)
|
||||
{
|
||||
// 如果正在工作,但离开了,暂停工作计时?
|
||||
// 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积
|
||||
// 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲
|
||||
ChangeState(MonitorState.Idle);
|
||||
}
|
||||
|
||||
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
|
||||
// 是否应该重置工作计时器?
|
||||
// 假设用户去开会了1小时,回来应该重新计算20分钟。
|
||||
if (idleTime > RestDuration)
|
||||
{
|
||||
_accumulatedWorkTime = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 用户在活动
|
||||
if (CurrentState == MonitorState.Idle)
|
||||
{
|
||||
// 从空闲变为工作
|
||||
ChangeState(MonitorState.Working);
|
||||
_lastWorkStartTime = DateTime.Now;
|
||||
}
|
||||
|
||||
// 累加工作时间
|
||||
// 简单的累加逻辑:这一秒是工作的
|
||||
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
|
||||
|
||||
// 检查是否达到工作时长
|
||||
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
||||
|
||||
if (remainingWork <= TimeSpan.Zero)
|
||||
{
|
||||
// 触发休息
|
||||
_restStartTime = DateTime.Now;
|
||||
ChangeState(MonitorState.Resting);
|
||||
RestStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
WorkProgressChanged?.Invoke(this, remainingWork);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用于强制重置或测试
|
||||
public void ForceRest()
|
||||
{
|
||||
_restStartTime = DateTime.Now;
|
||||
ChangeState(MonitorState.Resting);
|
||||
RestStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void RefreshStatus()
|
||||
{
|
||||
if (CurrentState == MonitorState.Working)
|
||||
{
|
||||
TimeSpan remaining = WorkDuration - _accumulatedWorkTime;
|
||||
WorkProgressChanged?.Invoke(this, remaining);
|
||||
}
|
||||
else if (CurrentState == MonitorState.Resting)
|
||||
{
|
||||
TimeSpan timeInRest = DateTime.Now - _restStartTime;
|
||||
TimeSpan remaining = RestDuration - timeInRest;
|
||||
RestProgressChanged?.Invoke(this, remaining);
|
||||
}
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
_accumulatedWorkTime = TimeSpan.Zero;
|
||||
_lastWorkStartTime = DateTime.Now;
|
||||
|
||||
// Ensure timer is running
|
||||
if (!_timer.Enabled) _timer.Start();
|
||||
|
||||
// Force state to Working since user manually restarted
|
||||
ChangeState(MonitorState.Working);
|
||||
|
||||
// Immediately refresh UI to show full duration
|
||||
RefreshStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
46
AppSettings.cs
Normal file
46
AppSettings.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TimerApp
|
||||
{
|
||||
public class AppSettings
|
||||
{
|
||||
public int WorkMinutes { get; set; } = 20;
|
||||
public int RestMinutes { get; set; } = 1;
|
||||
public int IdleThresholdSeconds { get; set; } = 30;
|
||||
public bool IsDarkMode { get; set; } = true;
|
||||
|
||||
private static string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
|
||||
|
||||
public static AppSettings Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(ConfigPath))
|
||||
{
|
||||
string json = File.ReadAllText(ConfigPath);
|
||||
return JsonSerializer.Deserialize<AppSettings>(json);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore errors, return default
|
||||
}
|
||||
return new AppSettings();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(ConfigPath, json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
IconGenerator.cs
Normal file
67
IconGenerator.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.IO;
|
||||
|
||||
namespace TimerApp
|
||||
{
|
||||
public static class IconGenerator
|
||||
{
|
||||
public static Icon GenerateClockIcon()
|
||||
{
|
||||
int size = 64;
|
||||
using (Bitmap bmp = new Bitmap(size, size))
|
||||
using (Graphics g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.Clear(Color.Transparent);
|
||||
|
||||
// 绘制闹钟主体
|
||||
int margin = 4;
|
||||
int clockSize = size - margin * 2;
|
||||
Rectangle rect = new Rectangle(margin, margin, clockSize, clockSize);
|
||||
|
||||
// 外圈
|
||||
using (Pen pen = new Pen(Color.White, 4))
|
||||
{
|
||||
g.DrawEllipse(pen, rect);
|
||||
}
|
||||
|
||||
// 两个耳朵 (闹钟铃)
|
||||
using (Brush earBrush = new SolidBrush(Color.White))
|
||||
{
|
||||
// 左耳
|
||||
g.FillPie(earBrush, 0, 0, size/2, size/2, 200, 50);
|
||||
// 右耳
|
||||
g.FillPie(earBrush, size/2, 0, size/2, size/2, 290, 50);
|
||||
}
|
||||
|
||||
// 重绘外圈盖住耳朵连接处
|
||||
using (Brush bgBrush = new SolidBrush(Color.FromArgb(30, 30, 30))) // 深色背景
|
||||
{
|
||||
g.FillEllipse(bgBrush, rect);
|
||||
}
|
||||
using (Pen pen = new Pen(Color.White, 3))
|
||||
{
|
||||
g.DrawEllipse(pen, rect);
|
||||
}
|
||||
|
||||
// 指针
|
||||
Point center = new Point(size / 2, size / 2);
|
||||
using (Pen handPen = new Pen(Color.LightGreen, 3))
|
||||
{
|
||||
handPen.EndCap = LineCap.Round;
|
||||
// 时针
|
||||
g.DrawLine(handPen, center, new Point(center.X + 10, center.Y - 10));
|
||||
// 分针
|
||||
g.DrawLine(handPen, center, new Point(center.X, center.Y - 18));
|
||||
}
|
||||
|
||||
// 转换为图标
|
||||
// 注意:GetHicon 需要释放
|
||||
IntPtr hIcon = bmp.GetHicon();
|
||||
return Icon.FromHandle(hIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
431
MainForm.Designer.cs
generated
Normal file
431
MainForm.Designer.cs
generated
Normal file
@@ -0,0 +1,431 @@
|
||||
namespace TimerApp
|
||||
{
|
||||
partial class MainForm
|
||||
{
|
||||
/// <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.components = new System.ComponentModel.Container();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.btnWorkMinus = new System.Windows.Forms.Button();
|
||||
this.txtWork = new System.Windows.Forms.TextBox();
|
||||
this.btnWorkPlus = new System.Windows.Forms.Button();
|
||||
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.btnRestMinus = new System.Windows.Forms.Button();
|
||||
this.txtRest = new System.Windows.Forms.TextBox();
|
||||
this.btnRestPlus = new System.Windows.Forms.Button();
|
||||
|
||||
this.btnStartStop = new System.Windows.Forms.Button();
|
||||
this.btnReset = new System.Windows.Forms.Button();
|
||||
this.lblStatus = new System.Windows.Forms.Label();
|
||||
this.lblTimeLeft = new System.Windows.Forms.Label();
|
||||
this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
|
||||
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
|
||||
this.toolStripMenuItemShow = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripMenuItemExit = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.btnHide = new System.Windows.Forms.Button();
|
||||
this.pnlTitle = new System.Windows.Forms.Panel();
|
||||
this.lblTitle = new System.Windows.Forms.Label();
|
||||
this.btnClose = new System.Windows.Forms.Button();
|
||||
this.btnMin = new System.Windows.Forms.Button();
|
||||
this.btnTheme = new System.Windows.Forms.Button();
|
||||
this.pnlSettings = new System.Windows.Forms.Panel();
|
||||
|
||||
this.contextMenuStrip1.SuspendLayout();
|
||||
this.pnlTitle.SuspendLayout();
|
||||
this.pnlSettings.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
|
||||
//
|
||||
// pnlTitle
|
||||
//
|
||||
this.pnlTitle.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.pnlTitle.Controls.Add(this.btnTheme);
|
||||
this.pnlTitle.Controls.Add(this.btnMin);
|
||||
this.pnlTitle.Controls.Add(this.btnClose);
|
||||
this.pnlTitle.Controls.Add(this.lblTitle);
|
||||
this.pnlTitle.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.pnlTitle.Location = new System.Drawing.Point(0, 0);
|
||||
this.pnlTitle.Name = "pnlTitle";
|
||||
this.pnlTitle.Size = new System.Drawing.Size(320, 40);
|
||||
this.pnlTitle.TabIndex = 10;
|
||||
this.pnlTitle.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pnlTitle_MouseDown);
|
||||
|
||||
//
|
||||
// lblTitle
|
||||
//
|
||||
this.lblTitle.AutoSize = true;
|
||||
this.lblTitle.Font = new System.Drawing.Font("Microsoft YaHei UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.lblTitle.ForeColor = System.Drawing.Color.LightGray;
|
||||
this.lblTitle.Location = new System.Drawing.Point(12, 9);
|
||||
this.lblTitle.Name = "lblTitle";
|
||||
this.lblTitle.Size = new System.Drawing.Size(82, 23);
|
||||
this.lblTitle.TabIndex = 0;
|
||||
this.lblTitle.Text = "久坐提醒";
|
||||
this.lblTitle.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pnlTitle_MouseDown);
|
||||
|
||||
//
|
||||
// btnClose
|
||||
//
|
||||
this.btnClose.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.btnClose.FlatAppearance.BorderSize = 0;
|
||||
this.btnClose.FlatAppearance.MouseOverBackColor = System.Drawing.Color.Red;
|
||||
this.btnClose.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnClose.ForeColor = System.Drawing.Color.White;
|
||||
this.btnClose.Location = new System.Drawing.Point(280, 0);
|
||||
this.btnClose.Name = "btnClose";
|
||||
this.btnClose.Size = new System.Drawing.Size(40, 40);
|
||||
this.btnClose.TabIndex = 1;
|
||||
this.btnClose.Text = "✕";
|
||||
this.btnClose.UseVisualStyleBackColor = true;
|
||||
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
||||
|
||||
//
|
||||
// btnTheme
|
||||
//
|
||||
this.btnTheme.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.btnTheme.FlatAppearance.BorderSize = 0;
|
||||
this.btnTheme.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnTheme.ForeColor = System.Drawing.Color.White;
|
||||
this.btnTheme.Location = new System.Drawing.Point(200, 0);
|
||||
this.btnTheme.Name = "btnTheme";
|
||||
this.btnTheme.Size = new System.Drawing.Size(40, 40);
|
||||
this.btnTheme.TabIndex = 3;
|
||||
this.btnTheme.Text = "☀";
|
||||
this.btnTheme.UseVisualStyleBackColor = true;
|
||||
this.btnTheme.Click += new System.EventHandler(this.btnTheme_Click);
|
||||
|
||||
//
|
||||
// btnMin
|
||||
//
|
||||
this.btnMin.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.btnMin.FlatAppearance.BorderSize = 0;
|
||||
this.btnMin.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnMin.ForeColor = System.Drawing.Color.White;
|
||||
this.btnMin.Location = new System.Drawing.Point(240, 0);
|
||||
this.btnMin.Name = "btnMin";
|
||||
this.btnMin.Size = new System.Drawing.Size(40, 40);
|
||||
this.btnMin.TabIndex = 2;
|
||||
this.btnMin.Text = "―";
|
||||
this.btnMin.UseVisualStyleBackColor = true;
|
||||
this.btnMin.Click += new System.EventHandler(this.btnMin_Click);
|
||||
|
||||
//
|
||||
// lblTimeLeft
|
||||
//
|
||||
this.lblTimeLeft.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.lblTimeLeft.Font = new System.Drawing.Font("Segoe UI Light", 48F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.lblTimeLeft.ForeColor = System.Drawing.Color.White;
|
||||
this.lblTimeLeft.Location = new System.Drawing.Point(0, 40);
|
||||
this.lblTimeLeft.Name = "lblTimeLeft";
|
||||
this.lblTimeLeft.Size = new System.Drawing.Size(320, 120);
|
||||
this.lblTimeLeft.TabIndex = 6;
|
||||
this.lblTimeLeft.Text = "00:00";
|
||||
this.lblTimeLeft.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
|
||||
//
|
||||
// lblStatus
|
||||
//
|
||||
this.lblStatus.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.lblStatus.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.lblStatus.ForeColor = System.Drawing.Color.Gray;
|
||||
this.lblStatus.Location = new System.Drawing.Point(0, 160);
|
||||
this.lblStatus.Name = "lblStatus";
|
||||
this.lblStatus.Size = new System.Drawing.Size(320, 40);
|
||||
this.lblStatus.TabIndex = 5;
|
||||
this.lblStatus.Text = "Idle";
|
||||
this.lblStatus.TextAlign = System.Drawing.ContentAlignment.TopCenter;
|
||||
|
||||
//
|
||||
// pnlSettings
|
||||
//
|
||||
this.pnlSettings.Controls.Add(this.btnReset);
|
||||
this.pnlSettings.Controls.Add(this.btnStartStop);
|
||||
this.pnlSettings.Controls.Add(this.btnRestPlus);
|
||||
this.pnlSettings.Controls.Add(this.txtRest);
|
||||
this.pnlSettings.Controls.Add(this.btnRestMinus);
|
||||
this.pnlSettings.Controls.Add(this.label2);
|
||||
this.pnlSettings.Controls.Add(this.btnWorkPlus);
|
||||
this.pnlSettings.Controls.Add(this.txtWork);
|
||||
this.pnlSettings.Controls.Add(this.btnWorkMinus);
|
||||
this.pnlSettings.Controls.Add(this.label1);
|
||||
this.pnlSettings.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.pnlSettings.Location = new System.Drawing.Point(0, 180);
|
||||
this.pnlSettings.Name = "pnlSettings";
|
||||
this.pnlSettings.Size = new System.Drawing.Size(320, 170);
|
||||
this.pnlSettings.TabIndex = 11;
|
||||
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.label1.ForeColor = System.Drawing.Color.LightGray;
|
||||
this.label1.Location = new System.Drawing.Point(40, 20);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(100, 23);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "工作 (分):";
|
||||
|
||||
//
|
||||
// btnWorkMinus
|
||||
//
|
||||
this.btnWorkMinus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.btnWorkMinus.FlatAppearance.BorderSize = 0;
|
||||
this.btnWorkMinus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnWorkMinus.ForeColor = System.Drawing.Color.White;
|
||||
this.btnWorkMinus.Location = new System.Drawing.Point(160, 18);
|
||||
this.btnWorkMinus.Name = "btnWorkMinus";
|
||||
this.btnWorkMinus.Size = new System.Drawing.Size(30, 30);
|
||||
this.btnWorkMinus.TabIndex = 1;
|
||||
this.btnWorkMinus.Text = "-";
|
||||
this.btnWorkMinus.UseVisualStyleBackColor = false;
|
||||
|
||||
//
|
||||
// txtWork
|
||||
//
|
||||
this.txtWork.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.txtWork.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
this.txtWork.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.txtWork.ForeColor = System.Drawing.Color.White;
|
||||
this.txtWork.Location = new System.Drawing.Point(190, 18);
|
||||
this.txtWork.Name = "txtWork";
|
||||
this.txtWork.ReadOnly = false;
|
||||
this.txtWork.Size = new System.Drawing.Size(60, 30);
|
||||
this.txtWork.TabIndex = 2;
|
||||
this.txtWork.Text = "20";
|
||||
this.txtWork.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
|
||||
//
|
||||
// btnWorkPlus
|
||||
//
|
||||
this.btnWorkPlus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.btnWorkPlus.FlatAppearance.BorderSize = 0;
|
||||
this.btnWorkPlus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnWorkPlus.ForeColor = System.Drawing.Color.White;
|
||||
this.btnWorkPlus.Location = new System.Drawing.Point(250, 18);
|
||||
this.btnWorkPlus.Name = "btnWorkPlus";
|
||||
this.btnWorkPlus.Size = new System.Drawing.Size(30, 30);
|
||||
this.btnWorkPlus.TabIndex = 3;
|
||||
this.btnWorkPlus.Text = "+";
|
||||
this.btnWorkPlus.UseVisualStyleBackColor = false;
|
||||
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.label2.ForeColor = System.Drawing.Color.LightGray;
|
||||
this.label2.Location = new System.Drawing.Point(40, 60);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(100, 23);
|
||||
this.label2.TabIndex = 4;
|
||||
this.label2.Text = "休息 (分):";
|
||||
|
||||
//
|
||||
// btnRestMinus
|
||||
//
|
||||
this.btnRestMinus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.btnRestMinus.FlatAppearance.BorderSize = 0;
|
||||
this.btnRestMinus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnRestMinus.ForeColor = System.Drawing.Color.White;
|
||||
this.btnRestMinus.Location = new System.Drawing.Point(160, 58);
|
||||
this.btnRestMinus.Name = "btnRestMinus";
|
||||
this.btnRestMinus.Size = new System.Drawing.Size(30, 30);
|
||||
this.btnRestMinus.TabIndex = 5;
|
||||
this.btnRestMinus.Text = "-";
|
||||
this.btnRestMinus.UseVisualStyleBackColor = false;
|
||||
|
||||
//
|
||||
// txtRest
|
||||
//
|
||||
this.txtRest.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.txtRest.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
this.txtRest.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.txtRest.ForeColor = System.Drawing.Color.White;
|
||||
this.txtRest.Location = new System.Drawing.Point(190, 58);
|
||||
this.txtRest.Name = "txtRest";
|
||||
this.txtRest.ReadOnly = false;
|
||||
this.txtRest.Size = new System.Drawing.Size(60, 30);
|
||||
this.txtRest.TabIndex = 6;
|
||||
this.txtRest.Text = "1";
|
||||
this.txtRest.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
|
||||
//
|
||||
// btnRestPlus
|
||||
//
|
||||
this.btnRestPlus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.btnRestPlus.FlatAppearance.BorderSize = 0;
|
||||
this.btnRestPlus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnRestPlus.ForeColor = System.Drawing.Color.White;
|
||||
this.btnRestPlus.Location = new System.Drawing.Point(250, 58);
|
||||
this.btnRestPlus.Name = "btnRestPlus";
|
||||
this.btnRestPlus.Size = new System.Drawing.Size(30, 30);
|
||||
this.btnRestPlus.TabIndex = 7;
|
||||
this.btnRestPlus.Text = "+";
|
||||
this.btnRestPlus.UseVisualStyleBackColor = false;
|
||||
|
||||
//
|
||||
// btnStartStop
|
||||
//
|
||||
this.btnStartStop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
|
||||
this.btnStartStop.FlatAppearance.BorderSize = 0;
|
||||
this.btnStartStop.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnStartStop.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.btnStartStop.ForeColor = System.Drawing.Color.White;
|
||||
this.btnStartStop.Location = new System.Drawing.Point(170, 110);
|
||||
this.btnStartStop.Name = "btnStartStop";
|
||||
this.btnStartStop.Size = new System.Drawing.Size(110, 35);
|
||||
this.btnStartStop.TabIndex = 4;
|
||||
this.btnStartStop.Text = "应用设置";
|
||||
this.btnStartStop.UseVisualStyleBackColor = false;
|
||||
this.btnStartStop.Click += new System.EventHandler(this.btnStartStop_Click);
|
||||
|
||||
//
|
||||
// btnReset
|
||||
//
|
||||
this.btnReset.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
|
||||
this.btnReset.FlatAppearance.BorderSize = 0;
|
||||
this.btnReset.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnReset.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.btnReset.ForeColor = System.Drawing.Color.White;
|
||||
this.btnReset.Location = new System.Drawing.Point(40, 110);
|
||||
this.btnReset.Name = "btnReset";
|
||||
this.btnReset.Size = new System.Drawing.Size(110, 35);
|
||||
this.btnReset.TabIndex = 8;
|
||||
this.btnReset.Text = "重置计时";
|
||||
this.btnReset.UseVisualStyleBackColor = false;
|
||||
this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
|
||||
|
||||
//
|
||||
// btnHide
|
||||
//
|
||||
this.btnHide.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
|
||||
this.btnHide.FlatAppearance.BorderSize = 0;
|
||||
this.btnHide.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnHide.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.btnHide.ForeColor = System.Drawing.Color.White;
|
||||
this.btnHide.Location = new System.Drawing.Point(40, 160);
|
||||
this.btnHide.Name = "btnHide";
|
||||
this.btnHide.Size = new System.Drawing.Size(220, 35);
|
||||
this.btnHide.TabIndex = 7;
|
||||
this.btnHide.Text = "最小化到托盘";
|
||||
this.btnHide.UseVisualStyleBackColor = false;
|
||||
this.btnHide.Visible = false;
|
||||
this.btnHide.Click += new System.EventHandler(this.btnHide_Click);
|
||||
|
||||
//
|
||||
// notifyIcon1
|
||||
//
|
||||
this.notifyIcon1.ContextMenuStrip = this.contextMenuStrip1;
|
||||
this.notifyIcon1.Text = "TimerApp";
|
||||
this.notifyIcon1.Visible = true;
|
||||
this.notifyIcon1.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.notifyIcon1_MouseDoubleClick);
|
||||
|
||||
//
|
||||
// contextMenuStrip1
|
||||
//
|
||||
this.contextMenuStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
|
||||
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.toolStripMenuItemShow,
|
||||
this.toolStripMenuItemExit});
|
||||
this.contextMenuStrip1.Name = "contextMenuStrip1";
|
||||
this.contextMenuStrip1.Size = new System.Drawing.Size(109, 52);
|
||||
|
||||
//
|
||||
// toolStripMenuItemShow
|
||||
//
|
||||
this.toolStripMenuItemShow.Name = "toolStripMenuItemShow";
|
||||
this.toolStripMenuItemShow.Size = new System.Drawing.Size(108, 24);
|
||||
this.toolStripMenuItemShow.Text = "显示";
|
||||
this.toolStripMenuItemShow.Click += new System.EventHandler(this.toolStripMenuItemShow_Click);
|
||||
|
||||
//
|
||||
// toolStripMenuItemExit
|
||||
//
|
||||
this.toolStripMenuItemExit.Name = "toolStripMenuItemExit";
|
||||
this.toolStripMenuItemExit.Size = new System.Drawing.Size(108, 24);
|
||||
this.toolStripMenuItemExit.Text = "退出";
|
||||
this.toolStripMenuItemExit.Click += new System.EventHandler(this.toolStripMenuItemExit_Click);
|
||||
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(30)))), ((int)(((byte)(30)))));
|
||||
this.ClientSize = new System.Drawing.Size(320, 380);
|
||||
this.Controls.Add(this.pnlSettings); // Add panel first to be at bottom of z-order
|
||||
this.Controls.Add(this.lblStatus);
|
||||
this.Controls.Add(this.lblTimeLeft);
|
||||
this.Controls.Add(this.pnlTitle);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.Name = "MainForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Focus Timer";
|
||||
this.Load += new System.EventHandler(this.MainForm_Load);
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
|
||||
|
||||
// Ensure pnlSettings matches form background initially
|
||||
this.pnlSettings.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(30)))), ((int)(((byte)(30)))));
|
||||
|
||||
this.contextMenuStrip1.ResumeLayout(false);
|
||||
this.pnlTitle.ResumeLayout(false);
|
||||
this.pnlTitle.PerformLayout();
|
||||
this.pnlSettings.ResumeLayout(false);
|
||||
this.pnlSettings.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Button btnWorkMinus;
|
||||
private System.Windows.Forms.TextBox txtWork;
|
||||
private System.Windows.Forms.Button btnWorkPlus;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Button btnRestMinus;
|
||||
private System.Windows.Forms.TextBox txtRest;
|
||||
private System.Windows.Forms.Button btnRestPlus;
|
||||
private System.Windows.Forms.Button btnStartStop;
|
||||
private System.Windows.Forms.Button btnReset;
|
||||
private System.Windows.Forms.Label lblStatus;
|
||||
private System.Windows.Forms.Label lblTimeLeft;
|
||||
private System.Windows.Forms.NotifyIcon notifyIcon1;
|
||||
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
|
||||
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemShow;
|
||||
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemExit;
|
||||
private System.Windows.Forms.Button btnHide;
|
||||
private System.Windows.Forms.Panel pnlTitle;
|
||||
private System.Windows.Forms.Button btnClose;
|
||||
private System.Windows.Forms.Button btnMin;
|
||||
private System.Windows.Forms.Button btnTheme;
|
||||
private System.Windows.Forms.Label lblTitle;
|
||||
private System.Windows.Forms.Panel pnlSettings;
|
||||
}
|
||||
}
|
||||
477
MainForm.cs
Normal file
477
MainForm.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TimerApp
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private ActivityMonitor _monitor;
|
||||
private AppSettings _settings;
|
||||
private RestForm _restForm;
|
||||
|
||||
// Colors
|
||||
private Color _darkBg = Color.FromArgb(30, 30, 30);
|
||||
private Color _lightBg = Color.FromArgb(240, 240, 240);
|
||||
private Color _darkPanel = Color.FromArgb(45, 45, 48);
|
||||
private Color _lightPanel = Color.FromArgb(200, 200, 200);
|
||||
private Color _darkText = Color.White;
|
||||
private Color _lightText = Color.Black;
|
||||
|
||||
// Drag window support
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool ReleaseCapture();
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight);
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool ShowCaret(IntPtr hWnd);
|
||||
|
||||
private const int WM_NCLBUTTONDOWN = 0xA1;
|
||||
private const int HT_CAPTION = 0x2;
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void MainForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
// Load settings
|
||||
_settings = AppSettings.Load();
|
||||
txtWork.Text = _settings.WorkMinutes.ToString();
|
||||
txtRest.Text = _settings.RestMinutes.ToString();
|
||||
|
||||
// Apply Theme
|
||||
ApplyTheme();
|
||||
|
||||
// Init monitor
|
||||
_monitor = new ActivityMonitor();
|
||||
ApplySettings();
|
||||
|
||||
// Bind events
|
||||
_monitor.StateChanged += Monitor_StateChanged;
|
||||
_monitor.WorkProgressChanged += Monitor_WorkProgressChanged;
|
||||
_monitor.RestStarted += Monitor_RestStarted;
|
||||
_monitor.RestEnded += Monitor_RestEnded;
|
||||
_monitor.RestProgressChanged += Monitor_RestProgressChanged;
|
||||
|
||||
// Numeric Buttons
|
||||
btnWorkMinus.Click += (s, ev) => AdjustTime(txtWork, -1, 1, 120);
|
||||
btnWorkPlus.Click += (s, ev) => AdjustTime(txtWork, 1, 1, 120);
|
||||
btnRestMinus.Click += (s, ev) => AdjustTime(txtRest, -1, 1, 30);
|
||||
btnRestPlus.Click += (s, ev) => AdjustTime(txtRest, 1, 1, 30);
|
||||
|
||||
// Manual input validation
|
||||
txtWork.KeyPress += ValidateDigitInput;
|
||||
txtRest.KeyPress += ValidateDigitInput;
|
||||
txtWork.Leave += (s, ev) => ValidateRange((TextBox)s, 1, 120);
|
||||
txtRest.Leave += (s, ev) => ValidateRange((TextBox)s, 1, 30);
|
||||
|
||||
// Focus handling (remove custom caret)
|
||||
txtWork.KeyDown += TextBox_KeyDown;
|
||||
txtRest.KeyDown += TextBox_KeyDown;
|
||||
|
||||
// Setup TextBoxes with Panels for vertical centering
|
||||
SetupTextBoxPanel(txtWork, pnlSettings);
|
||||
SetupTextBoxPanel(txtRest, pnlSettings);
|
||||
|
||||
_monitor.Start();
|
||||
|
||||
// Set tray icon
|
||||
try
|
||||
{
|
||||
// Generate and set custom icon
|
||||
Icon icon = IconGenerator.GenerateClockIcon();
|
||||
this.Icon = icon;
|
||||
notifyIcon1.Icon = icon;
|
||||
}
|
||||
catch
|
||||
{
|
||||
notifyIcon1.Icon = SystemIcons.Application;
|
||||
}
|
||||
|
||||
UpdateStatusUI();
|
||||
}
|
||||
|
||||
private void AdjustTime(TextBox txt, int delta, int min, int max)
|
||||
{
|
||||
if (int.TryParse(txt.Text, out int val))
|
||||
{
|
||||
val += delta;
|
||||
if (val < min) val = min;
|
||||
if (val > max) val = max;
|
||||
txt.Text = val.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateDigitInput(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
// Allow control keys (backspace, etc.) and digits
|
||||
if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar))
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTextBoxPanel(TextBox txt, Panel parent)
|
||||
{
|
||||
// Create a container panel for the textbox
|
||||
Panel container = new Panel();
|
||||
container.Size = txt.Size; // 60x30
|
||||
container.Location = txt.Location;
|
||||
container.BackColor = txt.BackColor;
|
||||
|
||||
// Adjust textbox to be centered inside
|
||||
txt.Parent = container;
|
||||
txt.Location = new Point(0, (container.Height - txt.Height) / 2); // Center vertically
|
||||
txt.Dock = DockStyle.None;
|
||||
txt.Width = container.Width;
|
||||
txt.BorderStyle = BorderStyle.None;
|
||||
|
||||
// Add container to parent
|
||||
parent.Controls.Add(container);
|
||||
|
||||
// Ensure correct tab order and tagging
|
||||
container.Tag = txt.Tag; // Copy tag if any
|
||||
}
|
||||
|
||||
private void ValidateRange(TextBox txt, int min, int max)
|
||||
{
|
||||
if (int.TryParse(txt.Text, out int val))
|
||||
{
|
||||
if (val < min) txt.Text = min.ToString();
|
||||
if (val > max) txt.Text = max.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
txt.Text = min.ToString(); // Default to min if invalid/empty
|
||||
}
|
||||
|
||||
// Clear selection on leave
|
||||
txt.SelectionLength = 0;
|
||||
}
|
||||
|
||||
// CustomCaret removed to use system caret with centered text
|
||||
|
||||
private void TextBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter)
|
||||
{
|
||||
// Lose focus by focusing the label
|
||||
lblStatus.Focus();
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true; // Prevent 'ding' sound
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
bool dark = _settings.IsDarkMode;
|
||||
Color bg = dark ? _darkBg : _lightBg;
|
||||
Color panel = dark ? _darkPanel : _lightPanel;
|
||||
Color text = dark ? _darkText : _lightText;
|
||||
Color btnBg = dark ? Color.FromArgb(45, 45, 48) : Color.White;
|
||||
|
||||
this.BackColor = bg;
|
||||
pnlTitle.BackColor = panel;
|
||||
pnlSettings.BackColor = bg; // Revert to bg color (user preference)
|
||||
|
||||
lblTitle.ForeColor = dark ? Color.LightGray : Color.FromArgb(64, 64, 64);
|
||||
lblTimeLeft.ForeColor = text;
|
||||
|
||||
label1.ForeColor = dark ? Color.LightGray : Color.DimGray;
|
||||
label2.ForeColor = dark ? Color.LightGray : Color.DimGray;
|
||||
|
||||
// Update buttons
|
||||
UpdateButtonStyle(btnStartStop, dark);
|
||||
UpdateButtonStyle(btnReset, dark);
|
||||
UpdateButtonStyle(btnHide, dark);
|
||||
|
||||
// Numeric buttons and text
|
||||
UpdateNumericButtonStyle(btnWorkMinus, dark);
|
||||
UpdateNumericButtonStyle(btnWorkPlus, dark);
|
||||
UpdateNumericButtonStyle(btnRestMinus, dark);
|
||||
UpdateNumericButtonStyle(btnRestPlus, dark);
|
||||
UpdateTextBoxStyle(txtWork, dark);
|
||||
UpdateTextBoxStyle(txtRest, dark);
|
||||
|
||||
// Title buttons
|
||||
btnClose.ForeColor = text;
|
||||
btnMin.ForeColor = text;
|
||||
|
||||
// Theme button with Segoe MDL2 Assets
|
||||
btnTheme.ForeColor = text;
|
||||
btnTheme.Font = new Font("Segoe MDL2 Assets", 10F, FontStyle.Regular, GraphicsUnit.Point);
|
||||
btnTheme.Text = dark ? "\uE706" : "\uE708"; // Sun : Moon
|
||||
|
||||
// Context Menu Theme
|
||||
// 浅色模式下使用白色背景,深色模式使用深色面板色
|
||||
Color menuBg = dark ? panel : Color.White;
|
||||
var menuRenderer = new ToolStripProfessionalRenderer(new CustomColorTable(dark, menuBg));
|
||||
contextMenuStrip1.Renderer = menuRenderer;
|
||||
contextMenuStrip1.BackColor = menuBg;
|
||||
contextMenuStrip1.ForeColor = dark ? text : Color.Black;
|
||||
|
||||
UpdateStatusUI(); // Re-apply status colors
|
||||
}
|
||||
|
||||
private class CustomColorTable : ProfessionalColorTable
|
||||
{
|
||||
private bool _dark;
|
||||
private Color _backColor;
|
||||
|
||||
public CustomColorTable(bool dark, Color backColor)
|
||||
{
|
||||
_dark = dark;
|
||||
_backColor = backColor;
|
||||
}
|
||||
|
||||
public override Color MenuItemSelected => _dark ? Color.FromArgb(63, 63, 70) : base.MenuItemSelected;
|
||||
public override Color MenuItemBorder => _dark ? Color.FromArgb(63, 63, 70) : base.MenuItemBorder;
|
||||
public override Color MenuBorder => _dark ? Color.FromArgb(45, 45, 48) : base.MenuBorder;
|
||||
|
||||
// Fix white margin in dark mode, and set margin color to background color in light mode
|
||||
public override Color ImageMarginGradientBegin => _backColor;
|
||||
public override Color ImageMarginGradientMiddle => _backColor;
|
||||
public override Color ImageMarginGradientEnd => _backColor;
|
||||
}
|
||||
|
||||
private void UpdateButtonStyle(Button btn, bool dark)
|
||||
{
|
||||
btn.BackColor = dark ? Color.FromArgb(63, 63, 70) : Color.White;
|
||||
btn.ForeColor = dark ? Color.White : Color.Black;
|
||||
}
|
||||
|
||||
private void UpdateNumericButtonStyle(Button btn, bool dark)
|
||||
{
|
||||
btn.BackColor = dark ? Color.FromArgb(45, 45, 48) : Color.FromArgb(230, 230, 230);
|
||||
btn.ForeColor = dark ? Color.White : Color.Black;
|
||||
}
|
||||
|
||||
private void UpdateTextBoxStyle(TextBox txt, bool dark)
|
||||
{
|
||||
Color bgColor = dark ? Color.FromArgb(45, 45, 48) : Color.White;
|
||||
txt.BackColor = bgColor;
|
||||
txt.ForeColor = dark ? Color.White : Color.Black;
|
||||
|
||||
// Also update parent container if it exists
|
||||
if (txt.Parent is Panel p)
|
||||
{
|
||||
p.BackColor = bgColor;
|
||||
}
|
||||
}
|
||||
|
||||
private void btnTheme_Click(object sender, EventArgs e)
|
||||
{
|
||||
_settings.IsDarkMode = !_settings.IsDarkMode;
|
||||
_settings.Save();
|
||||
ApplyTheme();
|
||||
}
|
||||
|
||||
private void btnStartStop_Click(object sender, EventArgs e)
|
||||
{
|
||||
// Save settings
|
||||
if (int.TryParse(txtWork.Text, out int w)) _settings.WorkMinutes = w;
|
||||
if (int.TryParse(txtRest.Text, out int r)) _settings.RestMinutes = r;
|
||||
_settings.Save();
|
||||
|
||||
ApplySettings();
|
||||
_monitor.RefreshStatus(); // Force update UI
|
||||
// MessageBox removed to prevent blocking timer
|
||||
}
|
||||
|
||||
private void btnReset_Click(object sender, EventArgs e)
|
||||
{
|
||||
_monitor.Restart();
|
||||
}
|
||||
|
||||
private void ApplySettings()
|
||||
{
|
||||
int workMin = 20;
|
||||
int restMin = 1;
|
||||
int.TryParse(txtWork.Text, out workMin);
|
||||
int.TryParse(txtRest.Text, out restMin);
|
||||
|
||||
_monitor.WorkDuration = TimeSpan.FromMinutes(workMin);
|
||||
_monitor.RestDuration = TimeSpan.FromMinutes(restMin);
|
||||
_monitor.IdleThreshold = TimeSpan.FromSeconds(_settings.IdleThresholdSeconds);
|
||||
}
|
||||
|
||||
private void Monitor_StateChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action<object, EventArgs>(Monitor_StateChanged), sender, e);
|
||||
return;
|
||||
}
|
||||
UpdateStatusUI();
|
||||
}
|
||||
|
||||
private void UpdateStatusUI()
|
||||
{
|
||||
if (_monitor == null) return;
|
||||
|
||||
bool dark = _settings.IsDarkMode;
|
||||
|
||||
switch (_monitor.CurrentState)
|
||||
{
|
||||
case MonitorState.Idle:
|
||||
lblStatus.Text = "状态: 空闲";
|
||||
lblStatus.ForeColor = Color.Gray;
|
||||
lblTimeLeft.Text = "--:--";
|
||||
lblTimeLeft.ForeColor = Color.Gray;
|
||||
break;
|
||||
case MonitorState.Working:
|
||||
lblStatus.Text = "状态: 工作中";
|
||||
lblStatus.ForeColor = dark ? Color.LightGreen : Color.Green;
|
||||
lblTimeLeft.ForeColor = dark ? Color.White : Color.Black;
|
||||
break;
|
||||
case MonitorState.Resting:
|
||||
lblStatus.Text = "状态: 休息中";
|
||||
lblStatus.ForeColor = dark ? Color.LightSkyBlue : Color.Blue;
|
||||
lblTimeLeft.ForeColor = dark ? Color.LightSkyBlue : Color.Blue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Monitor_WorkProgressChanged(object sender, TimeSpan remaining)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action<object, TimeSpan>(Monitor_WorkProgressChanged), sender, remaining);
|
||||
return;
|
||||
}
|
||||
lblTimeLeft.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
|
||||
|
||||
// Update tray tooltip
|
||||
if (remaining.TotalMinutes < 1)
|
||||
{
|
||||
notifyIcon1.Text = $"即将休息: {remaining.Seconds}秒";
|
||||
}
|
||||
else
|
||||
{
|
||||
notifyIcon1.Text = $"工作中: 剩余 {remaining.Minutes} 分钟";
|
||||
}
|
||||
}
|
||||
|
||||
private void Monitor_RestStarted(object sender, EventArgs e)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action<object, EventArgs>(Monitor_RestStarted), sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show rest form
|
||||
if (_restForm == null || _restForm.IsDisposed)
|
||||
{
|
||||
_restForm = new RestForm();
|
||||
_restForm.SkipRequested += RestForm_SkipRequested;
|
||||
}
|
||||
|
||||
_restForm.Show();
|
||||
}
|
||||
|
||||
private void RestForm_SkipRequested(object sender, EventArgs e)
|
||||
{
|
||||
_monitor.Stop();
|
||||
_monitor.Start();
|
||||
}
|
||||
|
||||
private void Monitor_RestProgressChanged(object sender, TimeSpan remaining)
|
||||
{
|
||||
if (_restForm != null && !_restForm.IsDisposed && _restForm.Visible)
|
||||
{
|
||||
_restForm.UpdateTime(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
private void Monitor_RestEnded(object sender, EventArgs e)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action<object, EventArgs>(Monitor_RestEnded), sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_restForm != null && !_restForm.IsDisposed)
|
||||
{
|
||||
_restForm.Close();
|
||||
}
|
||||
|
||||
notifyIcon1.ShowBalloonTip(3000, "休息结束", "继续加油工作吧!", ToolTipIcon.Info);
|
||||
}
|
||||
|
||||
private bool _hasShownMinimizeTip = false;
|
||||
|
||||
private void btnHide_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.Hide();
|
||||
if (!_hasShownMinimizeTip)
|
||||
{
|
||||
notifyIcon1.ShowBalloonTip(2000, "已隐藏", "程序仍在后台运行,双击托盘图标恢复。", ToolTipIcon.Info);
|
||||
_hasShownMinimizeTip = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (e.CloseReason == CloseReason.UserClosing)
|
||||
{
|
||||
e.Cancel = true;
|
||||
this.Hide();
|
||||
if (!_hasShownMinimizeTip)
|
||||
{
|
||||
notifyIcon1.ShowBalloonTip(2000, "已隐藏", "程序仍在后台运行,双击托盘图标恢复。", ToolTipIcon.Info);
|
||||
_hasShownMinimizeTip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
|
||||
{
|
||||
ShowForm();
|
||||
}
|
||||
|
||||
private void toolStripMenuItemShow_Click(object sender, EventArgs e)
|
||||
{
|
||||
ShowForm();
|
||||
}
|
||||
|
||||
private void ShowForm()
|
||||
{
|
||||
this.Show();
|
||||
this.WindowState = FormWindowState.Normal;
|
||||
this.Activate();
|
||||
}
|
||||
|
||||
private void toolStripMenuItemExit_Click(object sender, EventArgs e)
|
||||
{
|
||||
_monitor.Stop();
|
||||
notifyIcon1.Visible = false;
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
private void pnlTitle_MouseDown(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
ReleaseCapture();
|
||||
SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void btnMin_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.WindowState = FormWindowState.Minimized;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
NativeMethods.cs
Normal file
52
NativeMethods.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TimerApp
|
||||
{
|
||||
public static class NativeMethods
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct LASTINPUTINFO
|
||||
{
|
||||
public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO));
|
||||
|
||||
[MarshalAs(UnmanagedType.U4)]
|
||||
public UInt32 cbSize;
|
||||
[MarshalAs(UnmanagedType.U4)]
|
||||
public UInt32 dwTime;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统空闲时间(毫秒)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetIdleTime()
|
||||
{
|
||||
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
|
||||
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
|
||||
lastInputInfo.dwTime = 0;
|
||||
|
||||
if (GetLastInputInfo(ref lastInputInfo))
|
||||
{
|
||||
// Environment.TickCount 可能会在大约 24.9 天后翻转为负数,
|
||||
// 但 GetLastInputInfo 返回的也是 uint (DWORD),所以我们统一转为 long 处理差值
|
||||
// 或者直接使用 unchecked 减法处理溢出
|
||||
// 更稳健的做法是使用 GetTickCount64 (Vista+),但 Environment.TickCount 在 .NET Core 3.1+ 已经是 64位了(Environment.TickCount64)
|
||||
// 这里为了兼容性,我们简单处理。注意 GetLastInputInfo 返回的是 uint 毫秒数。
|
||||
|
||||
long envTicks = Environment.TickCount;
|
||||
// 处理 TickCount 翻转问题 (Environment.TickCount 是 int,GetLastInputInfo 是 uint)
|
||||
// 简单的做法:
|
||||
return (long)GetTickCount() - (long)lastInputInfo.dwTime;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern uint GetTickCount();
|
||||
}
|
||||
}
|
||||
16
Program.cs
Normal file
16
Program.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace TimerApp;
|
||||
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
162
RestForm.cs
Normal file
162
RestForm.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TimerApp
|
||||
{
|
||||
public class RestForm : Form
|
||||
{
|
||||
private Label lblMessage;
|
||||
private Label lblTimer;
|
||||
private Button btnSkip;
|
||||
|
||||
public event EventHandler SkipRequested;
|
||||
|
||||
public RestForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.lblMessage = new System.Windows.Forms.Label();
|
||||
this.lblTimer = new System.Windows.Forms.Label();
|
||||
// this.btnSkip = new RoundedButton(); // Removed custom button
|
||||
this.SuspendLayout();
|
||||
|
||||
// ... (keep form settings)
|
||||
//
|
||||
// Form 设置
|
||||
//
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.WindowState = FormWindowState.Maximized;
|
||||
this.TopMost = true;
|
||||
this.BackColor = System.Drawing.Color.Black;
|
||||
this.Opacity = 0.90; // 90% 不透明度
|
||||
this.ShowInTaskbar = false;
|
||||
this.Name = "RestForm";
|
||||
this.Text = "Rest Now";
|
||||
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);
|
||||
|
||||
//
|
||||
// lblMessage
|
||||
//
|
||||
this.lblMessage.AutoSize = false;
|
||||
this.lblMessage.Font = new System.Drawing.Font("Microsoft YaHei UI", 36F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
|
||||
this.lblMessage.ForeColor = System.Drawing.Color.White;
|
||||
this.lblMessage.Location = new System.Drawing.Point(100, 100);
|
||||
this.lblMessage.Name = "lblMessage";
|
||||
// 初始大小,会在CenterControls中动态调整
|
||||
this.lblMessage.Size = new System.Drawing.Size(800, 100);
|
||||
this.lblMessage.TabIndex = 0;
|
||||
this.lblMessage.Text = "休息一下,看看远方";
|
||||
this.lblMessage.TextAlign = ContentAlignment.MiddleCenter;
|
||||
|
||||
//
|
||||
// lblTimer
|
||||
//
|
||||
this.lblTimer.AutoSize = false;
|
||||
this.lblTimer.Font = new System.Drawing.Font("Segoe UI Light", 72F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.lblTimer.ForeColor = System.Drawing.Color.LightGreen;
|
||||
this.lblTimer.Location = new System.Drawing.Point(100, 200);
|
||||
this.lblTimer.Name = "lblTimer";
|
||||
// 初始大小,会在CenterControls中动态调整
|
||||
this.lblTimer.Size = new System.Drawing.Size(400, 180);
|
||||
this.lblTimer.TabIndex = 1;
|
||||
this.lblTimer.Text = "01:00";
|
||||
this.lblTimer.TextAlign = ContentAlignment.MiddleCenter;
|
||||
|
||||
//
|
||||
// btnSkip
|
||||
//
|
||||
this.btnSkip = new System.Windows.Forms.Button();
|
||||
this.btnSkip.BackColor = System.Drawing.Color.FromArgb(63, 63, 70);
|
||||
this.btnSkip.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnSkip.FlatAppearance.BorderSize = 0;
|
||||
this.btnSkip.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(80, 80, 90);
|
||||
this.btnSkip.Font = new System.Drawing.Font("Microsoft YaHei UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.btnSkip.ForeColor = System.Drawing.Color.White;
|
||||
this.btnSkip.Location = new System.Drawing.Point(100, 400);
|
||||
this.btnSkip.Name = "btnSkip";
|
||||
this.btnSkip.Size = new System.Drawing.Size(120, 46);
|
||||
this.btnSkip.TabIndex = 2;
|
||||
this.btnSkip.Text = "跳过";
|
||||
this.btnSkip.UseVisualStyleBackColor = false;
|
||||
this.btnSkip.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.btnSkip.Click += new System.EventHandler(this.btnSkip_Click);
|
||||
|
||||
// Add controls
|
||||
this.Controls.Add(this.btnSkip);
|
||||
this.Controls.Add(this.lblMessage);
|
||||
this.Controls.Add(this.lblTimer);
|
||||
|
||||
this.btnSkip.BringToFront();
|
||||
|
||||
this.Load += new System.EventHandler(this.RestForm_Load);
|
||||
this.Resize += new System.EventHandler(this.RestForm_Resize);
|
||||
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
}
|
||||
|
||||
private void RestForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
CenterControls();
|
||||
}
|
||||
|
||||
private void RestForm_Resize(object sender, EventArgs e)
|
||||
{
|
||||
CenterControls();
|
||||
}
|
||||
|
||||
private void CenterControls()
|
||||
{
|
||||
// 简单的居中计算
|
||||
int centerX = this.Width / 2;
|
||||
int centerY = this.Height / 2;
|
||||
|
||||
// Equal spacing logic
|
||||
int spacing = 80;
|
||||
|
||||
// 使用足够大的固定大小,确保文本完整显示
|
||||
// 使用屏幕宽度的85%作为最大宽度,确保在不同分辨率下都能完整显示
|
||||
int maxWidth = (int)(this.Width * 0.85);
|
||||
|
||||
// 消息文本:使用足够大的尺寸
|
||||
lblMessage.Size = new Size(maxWidth, 120);
|
||||
|
||||
// 时间文本:使用足够大的尺寸(72pt字体需要较大空间)
|
||||
lblTimer.Size = new Size(maxWidth, 200);
|
||||
|
||||
// Timer Center = centerY
|
||||
lblTimer.Location = new Point(centerX - lblTimer.Width / 2, centerY - lblTimer.Height / 2);
|
||||
|
||||
// Message above Timer
|
||||
lblMessage.Location = new Point(centerX - lblMessage.Width / 2, lblTimer.Top - spacing - lblMessage.Height);
|
||||
|
||||
// Button below Timer
|
||||
btnSkip.Location = new Point(centerX - btnSkip.Width / 2, lblTimer.Bottom + spacing);
|
||||
}
|
||||
|
||||
|
||||
public void UpdateTime(TimeSpan remaining)
|
||||
{
|
||||
if (lblTimer.InvokeRequired)
|
||||
{
|
||||
lblTimer.Invoke(new Action<TimeSpan>(UpdateTime), remaining);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 只更新文本,不重新居中,避免闪烁
|
||||
// 由于lblTimer已设置固定大小,文本居中显示,不会因为文本变化而改变位置
|
||||
lblTimer.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
|
||||
}
|
||||
}
|
||||
|
||||
private void btnSkip_Click(object sender, EventArgs e)
|
||||
{
|
||||
SkipRequested?.Invoke(this, EventArgs.Empty);
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
TimerApp.csproj
Normal file
11
TimerApp.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user