initial
This commit is contained in:
155
OverlayWindow.xaml.cs
Normal file
155
OverlayWindow.xaml.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Bedtimer
|
||||
{
|
||||
public partial class OverlayWindow : Window
|
||||
{
|
||||
private readonly DispatcherTimer _ui;
|
||||
private readonly DateTime _procStart = DateTime.Now;
|
||||
|
||||
private string _cycleId = "";
|
||||
private DateTime WarnT, FlashT, BedT, LockT;
|
||||
|
||||
public OverlayWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Width = 360; Height = 96;
|
||||
Background = System.Windows.Media.Brushes.Transparent;
|
||||
WindowStyle = WindowStyle.None;
|
||||
AllowsTransparency = true;
|
||||
ShowInTaskbar = false;
|
||||
Topmost = true;
|
||||
|
||||
Loaded += (_, __) =>
|
||||
{
|
||||
PositionTopRight(12);
|
||||
MakeClickThrough(true);
|
||||
};
|
||||
|
||||
_ui = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Render, (_, __) => Tick(), Dispatcher);
|
||||
_ui.Start();
|
||||
}
|
||||
|
||||
// unified signature: includes cycleId
|
||||
public void SetSchedule(string cycleId, DateTime warnT, DateTime flashT, DateTime bedT, DateTime lockT)
|
||||
{
|
||||
_cycleId = cycleId;
|
||||
WarnT = warnT; FlashT = flashT; BedT = bedT; LockT = lockT;
|
||||
}
|
||||
|
||||
private void Tick()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// Prevent instant re-lock right after login/app start
|
||||
bool startupGrace = (now - _procStart) < TimeSpan.FromSeconds(90);
|
||||
|
||||
// If we already locked for this cycle, stay hidden until next schedule is computed
|
||||
if (!string.IsNullOrEmpty(_cycleId) && State.IsLockedForCycle(_cycleId))
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
if (now < WarnT)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
if (now >= WarnT && now < FlashT)
|
||||
{
|
||||
Visibility = Visibility.Visible;
|
||||
MakeClickThrough(true);
|
||||
ShowClock(now, withCountdown: false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (now >= FlashT && now < BedT)
|
||||
{
|
||||
Visibility = Visibility.Visible;
|
||||
MakeClickThrough(true);
|
||||
FlashBriefly(now);
|
||||
ShowClock(now, withCountdown: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (now >= BedT && now < LockT)
|
||||
{
|
||||
Visibility = Visibility.Visible;
|
||||
MakeClickThrough(true);
|
||||
ShowBedtime();
|
||||
return;
|
||||
}
|
||||
|
||||
if (now >= LockT)
|
||||
{
|
||||
if (!startupGrace && !string.IsNullOrEmpty(_cycleId) && !State.IsLockedForCycle(_cycleId))
|
||||
{
|
||||
TryLock();
|
||||
State.MarkLocked(_cycleId);
|
||||
}
|
||||
Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowClock(DateTime now, bool withCountdown)
|
||||
{
|
||||
BedLabel.Visibility = Visibility.Collapsed;
|
||||
HudPanel.Visibility = Visibility.Visible;
|
||||
var t = now.ToString("h:mm tt");
|
||||
HudText.Text = withCountdown ? $"{t} • Bed in {Pretty(BedT - now)}" : t;
|
||||
}
|
||||
|
||||
private void ShowBedtime()
|
||||
{
|
||||
HudPanel.Visibility = Visibility.Collapsed;
|
||||
BedLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private static string Pretty(TimeSpan ts)
|
||||
{
|
||||
if (ts.TotalMinutes < 1) return $"{ts.Seconds}s";
|
||||
if (ts.TotalHours < 1) return $"{(int)ts.TotalMinutes}m {ts.Seconds}s";
|
||||
return $"{(int)ts.TotalHours}h {ts.Minutes}m";
|
||||
}
|
||||
|
||||
private void FlashBriefly(DateTime now)
|
||||
{
|
||||
if (now.Second == 0)
|
||||
{
|
||||
var orig = Opacity; Opacity = 0.2;
|
||||
_ = Task.Delay(1000).ContinueWith(_ => Dispatcher.Invoke(() => Opacity = orig));
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")] private static extern bool LockWorkStation();
|
||||
private static void TryLock() { try { LockWorkStation(); } catch { } }
|
||||
|
||||
private void PositionTopRight(int margin)
|
||||
{
|
||||
var r = System.Windows.SystemParameters.WorkArea;
|
||||
Left = r.Right - Width - margin;
|
||||
Top = r.Top + margin;
|
||||
}
|
||||
|
||||
private const int GWL_EXSTYLE = -20, WS_EX_TRANSPARENT = 0x20, WS_EX_TOOLWINDOW = 0x80, WS_EX_NOACTIVATE = 0x08000000;
|
||||
[DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
[DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
private void MakeClickThrough(bool enable)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
int ex = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
ex |= WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE;
|
||||
ex = enable ? (ex | WS_EX_TRANSPARENT) : (ex & ~WS_EX_TRANSPARENT);
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user