193 lines
6.7 KiB
C#
193 lines
6.7 KiB
C#
using System.Drawing;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
using System.IO;
|
||
|
||
namespace AxCopilot.Views;
|
||
|
||
/// <summary>
|
||
/// 전체 화면 위에 반투명 오버레이를 표시하고 마우스 드래그로 캡처 영역을 선택합니다.
|
||
/// ShowDialog() 후 SelectedRect로 결과를 얻습니다.
|
||
/// </summary>
|
||
public partial class RegionSelectWindow : Window
|
||
{
|
||
public System.Drawing.Rectangle? SelectedRect { get; private set; }
|
||
|
||
private readonly System.Drawing.Rectangle _screenBounds;
|
||
private System.Windows.Point _startPoint;
|
||
private System.Windows.Point _endPoint;
|
||
private bool _isDragging;
|
||
|
||
public RegionSelectWindow(Bitmap fullScreenshot, System.Drawing.Rectangle screenBounds)
|
||
{
|
||
InitializeComponent();
|
||
|
||
_screenBounds = screenBounds;
|
||
|
||
// 창을 모든 모니터 전체에 걸쳐 표시
|
||
Left = screenBounds.X;
|
||
Top = screenBounds.Y;
|
||
Width = screenBounds.Width;
|
||
Height = screenBounds.Height;
|
||
|
||
// 전체 화면 스크린샷을 배경으로 렌더링
|
||
RootCanvas.Background = CreateFrozenBrush(fullScreenshot);
|
||
|
||
// 오버레이 초기 크기 설정
|
||
OverlayTop.Width = screenBounds.Width;
|
||
OverlayTop.Height = screenBounds.Height;
|
||
OverlayBottom.Width = screenBounds.Width;
|
||
OverlayLeft.Width = 0;
|
||
OverlayRight.Width = 0;
|
||
|
||
// 가이드 텍스트 중앙 배치 (Loaded 이후)
|
||
Loaded += (_, _) =>
|
||
{
|
||
Canvas.SetLeft(GuideText, (Width - GuideText.ActualWidth) / 2);
|
||
Canvas.SetTop(GuideText, (Height - GuideText.ActualHeight) / 2);
|
||
};
|
||
}
|
||
|
||
private static ImageBrush CreateFrozenBrush(Bitmap bmp)
|
||
{
|
||
using var ms = new MemoryStream();
|
||
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
|
||
ms.Position = 0;
|
||
var img = new BitmapImage();
|
||
img.BeginInit();
|
||
img.StreamSource = ms;
|
||
img.CacheOption = BitmapCacheOption.OnLoad;
|
||
img.EndInit();
|
||
img.Freeze();
|
||
var brush = new ImageBrush(img) { Stretch = Stretch.None, AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top };
|
||
brush.Freeze();
|
||
return brush;
|
||
}
|
||
|
||
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
|
||
{
|
||
if (e.ChangedButton != MouseButton.Left) return;
|
||
_startPoint = e.GetPosition(RootCanvas);
|
||
_isDragging = true;
|
||
GuideText.Visibility = Visibility.Collapsed;
|
||
SelectionBorder.Visibility = Visibility.Visible;
|
||
SizeLabel.Visibility = Visibility.Visible;
|
||
CaptureMouse();
|
||
}
|
||
|
||
private void Window_MouseMove(object sender, MouseEventArgs e)
|
||
{
|
||
if (!_isDragging) return;
|
||
var cur = e.GetPosition(RootCanvas);
|
||
UpdateSelection(_startPoint, cur);
|
||
}
|
||
|
||
private void Window_MouseUp(object sender, MouseButtonEventArgs e)
|
||
{
|
||
if (!_isDragging || e.ChangedButton != MouseButton.Left) return;
|
||
_isDragging = false;
|
||
ReleaseMouseCapture();
|
||
|
||
var cur = e.GetPosition(RootCanvas);
|
||
_endPoint = cur;
|
||
var rect = MakeRect(_startPoint, cur);
|
||
|
||
if (rect.Width < 4 || rect.Height < 4)
|
||
{
|
||
// 너무 작으면 무시, 다시 드래그 가능
|
||
return;
|
||
}
|
||
|
||
// 선택 완료 — 화살표 키로 미세 조정 후 Enter/더블클릭으로 확정, Esc로 취소
|
||
// SizeLabel 하단에 힌트 표시
|
||
SizeLabelText.Text += " · ↑↓←→ 미세조정 · Enter 확정";
|
||
}
|
||
|
||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||
{
|
||
if (e.Key == Key.Escape)
|
||
{
|
||
SelectedRect = null;
|
||
Close();
|
||
return;
|
||
}
|
||
|
||
// 드래그 완료 후 화살표 키로 선택 영역 미세 조정
|
||
if (!_isDragging && SelectionBorder.Visibility == Visibility.Visible)
|
||
{
|
||
double dx = 0, dy = 0;
|
||
bool shift = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
|
||
double step = shift ? 10 : 1; // Shift 누르면 10px 단위
|
||
|
||
switch (e.Key)
|
||
{
|
||
case Key.Left: dx = -step; break;
|
||
case Key.Right: dx = step; break;
|
||
case Key.Up: dy = -step; break;
|
||
case Key.Down: dy = step; break;
|
||
case Key.Enter:
|
||
// Enter 키로 현재 선택 확정
|
||
var r = MakeRect(_startPoint, _endPoint);
|
||
if (r.Width >= 4 && r.Height >= 4)
|
||
{
|
||
var dpi = VisualTreeHelper.GetDpi(this);
|
||
SelectedRect = new System.Drawing.Rectangle(
|
||
(int)(r.X * dpi.DpiScaleX) + _screenBounds.X,
|
||
(int)(r.Y * dpi.DpiScaleY) + _screenBounds.Y,
|
||
(int)(r.Width * dpi.DpiScaleX),
|
||
(int)(r.Height * dpi.DpiScaleY));
|
||
}
|
||
Close();
|
||
return;
|
||
default: return;
|
||
}
|
||
|
||
_endPoint = new System.Windows.Point(_endPoint.X + dx, _endPoint.Y + dy);
|
||
UpdateSelection(_startPoint, _endPoint);
|
||
e.Handled = true;
|
||
}
|
||
}
|
||
|
||
private void UpdateSelection(System.Windows.Point a, System.Windows.Point b)
|
||
{
|
||
var rect = MakeRect(a, b);
|
||
|
||
Canvas.SetLeft(SelectionBorder, rect.X);
|
||
Canvas.SetTop(SelectionBorder, rect.Y);
|
||
SelectionBorder.Width = rect.Width;
|
||
SelectionBorder.Height = rect.Height;
|
||
|
||
// 바깥 마스크 업데이트
|
||
OverlayTop.Width = Width;
|
||
OverlayTop.Height = rect.Y;
|
||
|
||
OverlayBottom.Width = Width;
|
||
OverlayBottom.Height = Height - rect.Y - rect.Height;
|
||
Canvas.SetTop(OverlayBottom, rect.Y + rect.Height);
|
||
|
||
OverlayLeft.Width = rect.X;
|
||
OverlayLeft.Height = rect.Height;
|
||
Canvas.SetTop(OverlayLeft, rect.Y);
|
||
|
||
OverlayRight.Width = Width - rect.X - rect.Width;
|
||
OverlayRight.Height = rect.Height;
|
||
Canvas.SetLeft(OverlayRight, rect.X + rect.Width);
|
||
Canvas.SetTop(OverlayRight, rect.Y);
|
||
|
||
// 크기 레이블
|
||
var dpiScale = VisualTreeHelper.GetDpi(this);
|
||
var pw = (int)(rect.Width * dpiScale.DpiScaleX);
|
||
var ph = (int)(rect.Height * dpiScale.DpiScaleY);
|
||
SizeLabelText.Text = $"{pw} × {ph}";
|
||
Canvas.SetLeft(SizeLabel, rect.X + rect.Width + 8);
|
||
Canvas.SetTop(SizeLabel, rect.Y + rect.Height + 4);
|
||
}
|
||
|
||
private static Rect MakeRect(System.Windows.Point a, System.Windows.Point b) =>
|
||
new(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y),
|
||
Math.Abs(b.X - a.X), Math.Abs(b.Y - a.Y));
|
||
}
|