[v2.0.0] Phase L3-7 다중 디스플레이 지원

LauncherWindow.Animations.cs:
- CenterOnScreen(): SystemParameters.WorkArea(주 모니터 고정) → 마우스 커서 위치 모니터 기반으로 변경
- GetCurrentMonitorWorkArea() 신규: Screen.FromPoint(Cursor.Position)으로 현재 모니터 탐색
  · PresentationSource.TransformFromDevice로 물리 픽셀 → WPF DIP 정확 변환
  · PresentationSource 미사용 가능 시 SystemParameters.WorkArea 폴백
- using AxCopilot.Services 추가

AppSettings.cs:
- LauncherSettings에 MonitorDockPositions 딕셔너리 추가
  · key=모니터 디바이스 이름 (\.\DISPLAY1 등), value=[Left, Top]
  · JSON 직렬화 지원

App.Settings.cs:
- ToggleDockBar(): 독 바 위치 변경 시 per-monitor 위치 저장 (GetMonitorDeviceNameAt)
- 독 바 복원 시 IsDockPositionOnAnyMonitor로 유효성 검사
  · 연결 끊긴 모니터 위치면 중앙 하단(-1,-1)으로 자동 초기화
- GetMonitorDeviceNameAt(): WPF DIP → 물리 픽셀 변환 후 모니터 디바이스명 반환
- IsDockPositionOnAnyMonitor(): Screen.AllScreens 범위 검사

docs/LAUNCHER_ROADMAP.md: L3-7  완료 표시

효과:
- 듀얼 모니터에서 핫키 → 마우스가 있는 모니터에 런처 표시
- 독 바 드래그 시 해당 모니터 이름으로 위치 기억
- 모니터 분리 후 재실행 시 독 바 위치 자동 복원

빌드: 경고 0, 오류 0
This commit is contained in:
2026-04-04 09:00:15 +09:00
parent cb9d197969
commit 1b215579d2
4 changed files with 124 additions and 15 deletions

View File

@@ -154,23 +154,87 @@ public partial class App
var launcher = _settings?.Settings.Launcher;
var dockItems = launcher?.DockBarItems ?? new() { "launcher", "clipboard", "capture", "agent", "clock", "cpu" };
_dockBar.BuildFromSettings(dockItems);
// Phase L3-7: 독 바 위치 변경 시 모니터별로 저장
_dockBar.OnPositionChanged = (left, top) =>
{
if (_settings != null)
{
_settings.Settings.Launcher.DockBarLeft = left;
_settings.Settings.Launcher.DockBarTop = top;
_settings.Save();
}
if (_settings == null) return;
var lnch = _settings.Settings.Launcher;
lnch.DockBarLeft = left;
lnch.DockBarTop = top;
// 현재 독 바가 속한 모니터 디바이스 이름으로 위치 기억
var deviceName = GetMonitorDeviceNameAt(_dockBar, left, top);
if (deviceName != null)
lnch.MonitorDockPositions[deviceName] = new[] { left, top };
_settings.Save();
};
_dockBar.Show();
// Phase L3-7: 저장 위치 유효성 검사 — 연결이 끊긴 모니터면 중앙 하단으로 리셋
var savedLeft = launcher?.DockBarLeft ?? -1;
var savedTop = launcher?.DockBarTop ?? -1;
if (!IsDockPositionOnAnyMonitor(savedLeft, savedTop))
{
LogService.Info("[MultiMonitor] 독 바 저장 위치가 연결된 모니터 밖 → 중앙 하단으로 초기화");
savedLeft = -1;
savedTop = -1;
}
_dockBar.ApplySettings(
launcher?.DockBarOpacity ?? 0.92,
launcher?.DockBarLeft ?? -1,
launcher?.DockBarTop ?? -1,
savedLeft,
savedTop,
launcher?.DockBarRainbowGlow ?? false);
}
// ─── Phase L3-7: 다중 모니터 헬퍼 ─────────────────────────────────────
/// <summary>
/// WPF 논리 좌표를 물리적 좌표로 변환하여 해당 모니터의 디바이스 이름을 반환합니다.
/// </summary>
private static string? GetMonitorDeviceNameAt(
System.Windows.Media.Visual? visual, double wpfLeft, double wpfTop)
{
try
{
System.Drawing.Point physPt;
var src = visual != null ? PresentationSource.FromVisual(visual) : null;
if (src?.CompositionTarget != null)
{
var m = src.CompositionTarget.TransformToDevice; // logical→physical
var pt = m.Transform(new System.Windows.Point(wpfLeft + 10, wpfTop + 10));
physPt = new System.Drawing.Point((int)pt.X, (int)pt.Y);
}
else
{
physPt = new System.Drawing.Point((int)wpfLeft + 10, (int)wpfTop + 10);
}
return System.Windows.Forms.Screen.FromPoint(physPt).DeviceName;
}
catch { return null; }
}
/// <summary>
/// 저장된 독 바 위치(WPF 논리 좌표)가 현재 연결된 모니터 중 하나에 있는지 검사합니다.
/// 모니터가 분리되거나 해상도가 변경되어 위치가 화면 밖에 있으면 false를 반환합니다.
/// </summary>
private static bool IsDockPositionOnAnyMonitor(double left, double top)
{
if (left < 0 || top < 0) return false; // -1 기본값은 항상 재계산 대상
try
{
// 참고: WPF 논리 좌표와 물리적 픽셀의 차이는 DPI에 따라 다르나
// "화면 밖인지" 판단에는 근사 비교로 충분합니다.
return System.Windows.Forms.Screen.AllScreens
.Any(s => left >= s.Bounds.Left && left < s.Bounds.Right
&& top >= s.Bounds.Top && top < s.Bounds.Bottom);
}
catch { return true; } // 오류 시 보수적으로 true (위치 유지)
}
/// <summary>독 바를 현재 설정으로 즉시 새로고침합니다.</summary>
public void RefreshDockBar()
{