192 lines
6.8 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|