2026-01-26 18:56:53 +09:00

192 lines
6.8 KiB
C#

// GuideOverlayWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.IO;
using System.Windows.Input;
namespace VisionCommonUI.UserGuide {
public partial class GuideOverlayWindow : Window {
public Action OnNextClicked;
public Action OnPrevClicked;
public Action OnExitClicked;
public bool IsClosing { get; private set; } = false;
protected override void OnClosing(System.ComponentModel.CancelEventArgs e) {
IsClosing = true;
base.OnClosing(e);
}
private RectangleGeometry _highlightGeometry;
public GuideOverlayWindow() {
InitializeComponent();
Loaded += (_, __) =>
{
Mouse.Capture(this, CaptureMode.SubTree);
};
Deactivated += (_, __) => OnExitClicked?.Invoke();
}
public void Highlight(GuideStep step) {
var area = step.HighlightArea;
double x = Math.Floor(area.X);
double y = Math.Floor(area.Y);
double w = Math.Ceiling(area.Width);
double h = Math.Ceiling(area.Height);
double canvasW = Math.Ceiling(RootCanvas.ActualWidth);
double canvasH = Math.Ceiling(RootCanvas.ActualHeight);
// Top
OverlayTop.Width = canvasW;
OverlayTop.Height = y;
Canvas.SetLeft(OverlayTop, 0);
Canvas.SetTop(OverlayTop, 0);
// Bottom
OverlayBottom.Width = canvasW;
OverlayBottom.Height = canvasH - (y + h);
Canvas.SetLeft(OverlayBottom, 0);
Canvas.SetTop(OverlayBottom, y + h);
// Left
OverlayLeft.Width = x;
OverlayLeft.Height = h;
Canvas.SetLeft(OverlayLeft, 0);
Canvas.SetTop(OverlayLeft, y);
// Right
OverlayRight.Width = canvasW - (x + w);
OverlayRight.Height = h;
Canvas.SetLeft(OverlayRight, x + w);
Canvas.SetTop(OverlayRight, y);
HighlightRect.Width = w;
HighlightRect.Height = h;
Canvas.SetLeft(HighlightRect, x);
Canvas.SetTop(HighlightRect, y);
MessageText.Text = step.Message;
MessageText.FontSize = step.FontSize;
MessageText.Width = this.ActualWidth;
MessageText.TextAlignment = TextAlignment.Left;
MessageText.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var textSize = MessageText.DesiredSize;
Point textPosition = (step.Placement == MessagePlacement.Above)
? new Point(area.X, Math.Max(area.Y - textSize.Height - 10, 0))
: new Point(area.X, area.Bottom + 10);
Canvas.SetLeft(MessageText, textPosition.X);
Canvas.SetTop(MessageText, textPosition.Y);
if (step.Imagesc != null) {
MessageImage.Source = step.Imagesc;
MessageImage.Visibility = Visibility.Visible;
// 초기 크기 설정
double baseWidth = this.ActualWidth * 0.7;
double baseHeight = baseWidth * 0.66;
Size imageSize = new Size(baseWidth, baseHeight);
// 적절한 위치 계산
Point textPos = new Point(Canvas.GetLeft(MessageText), Canvas.GetTop(MessageText));
var smartPos = GetSmartImagePosition(step.HighlightArea, textPos, imageSize, out Size finalSize);
MessageImage.Width = finalSize.Width;
MessageImage.Height = finalSize.Height;
Canvas.SetLeft(MessageImage, smartPos.X);
Canvas.SetTop(MessageImage, smartPos.Y);
}
else {
MessageImage.Visibility = Visibility.Collapsed;
}
}
private Point GetSmartImagePosition(Rect highlight, Point textPosition, Size originalSize, out Size finalSize) {
double canvasWidth = RootCanvas.ActualWidth;
double canvasHeight = RootCanvas.ActualHeight;
double padding = 10;
double textBottom = textPosition.Y + MessageText.DesiredSize.Height;
var below = new Point(highlight.X, textBottom + padding);
var right = new Point(highlight.Right + padding, highlight.Top);
var top = new Point(highlight.X, highlight.Y - originalSize.Height - padding);
// 기본: 크기 유지
finalSize = originalSize;
bool FitsBelow = below.Y + originalSize.Height < canvasHeight;
bool FitsRight = right.X + originalSize.Width < canvasWidth;
bool FitsTop = top.Y > 0;
if (FitsBelow) {
return below;
}
if (FitsRight) {
return right;
}
if (FitsTop) {
return top;
}
// 축소 시도
double scale = 0.8;
while (scale > 0.3) {
var scaled = new Size(originalSize.Width * scale, originalSize.Height * scale);
if (textBottom + padding + scaled.Height < canvasHeight) {
finalSize = scaled;
return new Point(highlight.X, textBottom + padding);
}
scale -= 0.1;
}
// Fallback
finalSize = new Size(200, 150);
return new Point(10, 10);
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e) {
if (e.OriginalSource is DependencyObject source && IsInsideGuideOverlay(source)) {
OnNextClicked?.Invoke();
}
else {
OnExitClicked?.Invoke();
}
}
private void Grid_PreviewMouseWheel(object sender, MouseWheelEventArgs e) {
if (e.Delta > 0) {
OnPrevClicked?.Invoke();
e.Handled = true;
}
}
private bool IsInsideGuideOverlay(DependencyObject source) {
while (source != null) {
if (source == this) {
return true;
}
source = VisualTreeHelper.GetParent(source);
}
return false;
}
private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
Point clickPos = e.GetPosition(RootCanvas);
// HitTest 대상 찾기
HitTestResult result = VisualTreeHelper.HitTest(RootCanvas, clickPos);
FrameworkElement hit = result != null ? result.VisualHit as FrameworkElement : null;
if (hit == null || string.IsNullOrEmpty(hit.Name)) {
OnExitClicked?.Invoke();
}
}
}
}