Files
AX-Copilot-Codex/src/AxCopilot/Views/ReminderPopupWindow.xaml.cs

142 lines
5.2 KiB
C#

using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using AxCopilot.Services;
namespace AxCopilot.Views;
/// <summary>
/// 잠금 해제 시 화면 모서리에 표시되는 격려 팝업.
/// 테마는 Application.Current.Resources의 DynamicResource로 자동 적용됩니다.
/// 포커스를 빼앗지 않으며(WS_EX_NOACTIVATE), 지정된 초 후 자동으로 닫힙니다.
/// </summary>
public partial class ReminderPopupWindow : Window
{
// ─── 포커스 방지 P/Invoke (64비트 안전) ────────────────────────────────────
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr(IntPtr hwnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr(IntPtr hwnd, int nIndex, IntPtr dwNewLong);
private const int GWL_EXSTYLE = -20;
private const int WS_EX_NOACTIVATE = 0x08000000;
private const int WS_EX_TOOLWINDOW = 0x00000080;
// ─── 타이머 ───────────────────────────────────────────────────────────────
private readonly DispatcherTimer _timer;
private readonly EventHandler _tickHandler;
private int _remaining;
public ReminderPopupWindow(
string quoteText,
string? author,
TimeSpan todayUsage,
SettingsService settings)
{
InitializeComponent();
var cfg = settings.Settings.Reminder;
// ── 문구 / 출처 ──
QuoteText.Text = quoteText;
if (!string.IsNullOrWhiteSpace(author))
{
AuthorText.Text = $"— {author}";
AuthorText.Visibility = Visibility.Visible;
}
// ── 근무 시간 ──
var h = (int)todayUsage.TotalHours;
var m = todayUsage.Minutes;
UsageText.Text = h > 0
? $"오늘 총 {h}시간 {m}분 근무 중"
: m >= 1
? $"오늘 총 {m}분 근무 중"
: "오늘 방금 시작했습니다";
// ── 카운트다운 ──
_remaining = Math.Max(3, cfg.DisplaySeconds);
CountdownBar.Maximum = _remaining;
CountdownBar.Value = _remaining;
// ── 위치: 레이아웃 완료 후 설정 ──
Loaded += (_, _) =>
{
SetNoActivate();
PositionWindow(cfg.Corner);
AnimateIn();
};
// ── 타이머 ──
_tickHandler = (_, _) =>
{
_remaining--;
CountdownBar.Value = _remaining;
if (_remaining <= 0) Close();
};
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += _tickHandler;
_timer.Start();
// ── Esc 키 닫기 ──
KeyDown += (_, e) =>
{
if (e.Key == System.Windows.Input.Key.Escape) Close();
};
}
// ─── 포커스 방지 ─────────────────────────────────────────────────────────
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
SetNoActivate();
}
private void SetNoActivate()
{
var hwnd = new WindowInteropHelper(this).Handle;
if (hwnd == IntPtr.Zero) return;
var style = (long)GetWindowLongPtr(hwnd, GWL_EXSTYLE);
SetWindowLongPtr(hwnd, GWL_EXSTYLE, (IntPtr)(style | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW));
}
// ─── 위치 계산 ────────────────────────────────────────────────────────────
private void PositionWindow(string corner)
{
var area = SystemParameters.WorkArea;
const double margin = 20;
Left = corner.Contains("left")
? area.Left + margin
: area.Right - ActualWidth - margin;
Top = corner.Contains("top")
? area.Top + margin
: area.Bottom - ActualHeight - margin;
}
// ─── 등장 애니메이션 ──────────────────────────────────────────────────────
private void AnimateIn()
{
Opacity = 0;
var anim = new System.Windows.Media.Animation.DoubleAnimation(0, 1,
TimeSpan.FromMilliseconds(280));
BeginAnimation(OpacityProperty, anim);
}
// ─── 이벤트 ───────────────────────────────────────────────────────────────
private void CloseBtn_Click(object sender, RoutedEventArgs e) => Close();
protected override void OnClosed(EventArgs e)
{
_timer.Stop();
_timer.Tick -= _tickHandler;
base.OnClosed(e);
}
}