/// レイアウト要素のClipping領域(Fitオプションなし)の変更を試みる /// @param layoutElement レイアウト要素 /// @param target 変更箇所 /// @param value 変更値 /// @param[out] changed 変更後、制約を満たす形に訂正されたClipping領域 /// @return 試行結果(訂正箇所) public static TryResult TryChange( LayoutElement layoutElement, Names target, int value, out ClientRect changed) { Debug.Assert(layoutElement.IsWindowValid); Debug.Assert(!layoutElement.Fit); // 準備 var original = ClippingInputCorrector.GetOriginal(layoutElement); var window = Utilities.GetWindowRect(layoutElement.WindowType, layoutElement.Window); // 訂正 switch (target) { case Names.X: case Names.Y: { return ClippingInputCorrector.TryChangePosition( original, target, value, window, Constants.MinimumClippingSize, out changed); } case Names.Width: case Names.Height: { return ClippingInputCorrector.TryChangeSize( original, target, value, window, Constants.MinimumClippingSize, out changed); } default: Debug.Fail("switch"); throw new System.ArgumentException(); } }
//=================================================================== // コンストラクタ //=================================================================== /// コンストラクタ public ScreenCaptureRequest(LayoutElement layoutElement) { Debug.Assert(layoutElement.IsWindowValid, "Invalid Window", "ScreenCaptureRequest"); this.Window = layoutElement.Window; this.ClippingX = layoutElement.ClippingXWithFit; this.ClippingY = layoutElement.ClippingYWithFit; this.ClippingWidth = layoutElement.ClippingWidthWithFit; this.ClippingHeight = layoutElement.ClippingHeightWithFit; this.ShowCursor = layoutElement.ShowCursor; this.ShowLayeredWindow = layoutElement.ShowLayeredWindow; }
//================================================================= // Bound X/Y/Width/Height //================================================================= /// BoundX表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @param isDummy ダミープレビューサイズかどうか /// @param sampleWidth サンプルの幅 /// @param sampleHeight サンプルの高さ /// @param[out] x BoundXの文字列 /// @param[out] y BoundYの文字列 /// @param[out] width BoundWidthの文字列 /// @param[out] height BoundHeightの文字列 /// @return BoundX表示用文字列 public static void GetBoundRectString(LayoutElement layoutElement, bool isDummy, int sampleWidth, int sampleHeight, out string x, out string y, out string width, out string height) { var boundRect = layoutElement.GetBoundRect(sampleWidth, sampleHeight); x = isDummy ? string.Format("({0})", boundRect.X) : boundRect.X.ToString(); y = isDummy ? string.Format("({0})", boundRect.Y) : boundRect.Y.ToString(); width = isDummy ? string.Format("({0})", boundRect.Width) : boundRect.Width.ToString(); height = isDummy ? string.Format("({0})", boundRect.Height) : boundRect.Height.ToString(); }
//=================================================================== // ヒットテスト //=================================================================== /// ヒットテスト public static bool TryHitTest(Profile profile, RelativePoint mousePoint, out LayoutElement hitElement, out HitModes hitMode) { // 計算途中の結果をまとめるスタック var moveStack = new Stack<LayoutElement>(); var sizeStack = new Stack<LayoutElement>(); // レイアウト要素を線形探索 foreach (var layoutElement in profile.LayoutElements) { // ヒットテスト対象外判定 var maximumBoundRect = HitTest.GetMaximumBoundRect(layoutElement); if (!maximumBoundRect.Contains(mousePoint)) continue; var moveRect = HitTest.GetMoveRect(layoutElement); if (moveRect.Contains(mousePoint)) { // 移動用領域 moveStack.Push(layoutElement); } else { // 移動用領域でない=サイズ変更用領域 sizeStack.Push(layoutElement); } } // sizeStack優先 foreach (var layoutElement in sizeStack) { // 見つかり次第終了 hitMode = HitTest.GetHitMode(layoutElement, mousePoint); hitElement = layoutElement; return true; } // moveStack foreach (var layoutElement in moveStack) { // 見つかり次第終了 hitMode = HitModes.Move; hitElement = layoutElement; return true; } hitElement = null; hitMode = HitModes.Neutral; return false; }
/// レイアウト要素のヘッダーの生成 /// @param layoutElement 対象のレイアウト要素 /// @param index レイアウト要素のインデックス /// @param boundRect ヘッダー表示領域 /// @param textBrush テキスト描画用ブラシ /// @return DrawingContext.DrawTextで描画可能なDrawingVisualオブジェクト private FormattedText CreateHeader(LayoutElement layoutElement, int index, Rect boundRect, Brush textBrush) { var isCurrent = layoutElement == App.Profile.Current; var isDummy = !App.RuntimeOptions.IsCurrentProcessIDValid; // Caption var header = StringConverter.GetHeaderStringForLayoutEdit(layoutElement, index, isCurrent, isDummy, App.RuntimeOptions.CurrentSampleWidth, App.RuntimeOptions.CurrentSampleHeight); // FormattedText var formattedText = new FormattedText(header, System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface("Segoe UI, Tahoma"), CaptionFontSize, textBrush); // Clipping formattedText.MaxTextWidth = boundRect.Width; formattedText.MaxTextHeight = boundRect.Height; formattedText.MaxLineCount = 1; return formattedText; }
//=================================================================== // ヒットテスト用RelativeRectの生成 //=================================================================== /// レイアウト要素のうちボーダーを含めたRectを取得 private static RelativeRect GetMaximumBoundRect(LayoutElement layoutElement) { return new RelativeRect( layoutElement.BoundRelativeLeft - Constants.BorderRelativeThickness, layoutElement.BoundRelativeTop - Constants.BorderRelativeThickness, layoutElement.BoundRelativeWidth + Constants.BorderRelativeThickness * 2, layoutElement.BoundRelativeHeight + Constants.BorderRelativeThickness * 2); }
/// レイアウト要素のうちボーダーを含まないRectを取得 private static RelativeRect GetMoveRect(LayoutElement layoutElement) { return new RelativeRect( layoutElement.BoundRelativeLeft + Constants.BorderRelativeThickness, layoutElement.BoundRelativeTop + Constants.BorderRelativeThickness, Math.Max(layoutElement.BoundRelativeWidth - Constants.BorderRelativeThickness * 2, 0.0), Math.Max(layoutElement.BoundRelativeHeight - Constants.BorderRelativeThickness * 2, 0.0)); }
//================================================================= // Header //================================================================= /// LayoutParameter用ヘッダー文字列 /// @param layoutElement データ取得元のレイアウト要素 /// @param index レイアウト要素のインデックス /// @param maxLength 文字列の長さの上限 /// @return LayoutParameter用ヘッダー文字列 public static string GetHeaderStringForLayoutParameter(LayoutElement layoutElement, int index, int maxLength) { var header = string.Format("Layout {0:D}: {1}", index + 1, layoutElement.WindowCaption); return header.Substring(0, Math.Min(header.Length, maxLength)); }
/// SWScaleChromaVShift表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return SWScaleChromaVShift表示用文字列 public static string GetSWScaleChromaVShiftString(LayoutElement layoutElement) { return layoutElement.SWScaleChromaVShift.ToString("F2"); }
/// SWScaleChromaGBlur表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return SWScaleChromaGBlur表示用文字列 public static string GetSWScaleChromaGBlurString(LayoutElement layoutElement) { return(layoutElement.SWScaleChromaGBlur.ToString("F2")); }
/// SWScaleChromaVShift表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return SWScaleChromaVShift表示用文字列 public static string GetSWScaleChromaVShiftString(LayoutElement layoutElement) { return(layoutElement.SWScaleChromaVShift.ToString("F2")); }
//=================================================================== // 動作 //=================================================================== /// 開始 public void Start(LayoutElement target, HitModes hitMode, RelativePoint mousePoint, SnapGuide snapGuide) { this.target = target; this.hitMode = hitMode; this.mouseOffset = new RelativeMouseOffset(target, mousePoint); this.originalLTRB = new RelativeLTRB(target.BoundRelativeLeft, target.BoundRelativeTop, target.BoundRelativeRight, target.BoundRelativeBottom); this.snapGuide = snapGuide; this.lastUpdateControl = Environment.TickCount; this.MousePoint = mousePoint; this.ShouldUpdateControl = false; }
/// BoundRelativeRight表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return BoundRelativeRight表示用文字列 public static string GetBoundRelativeRightString(LayoutElement layoutElement) { return(layoutElement.BoundRelativeRight.ToString("F3")); }
/// DrawBorder用のスタイル(Brush,Pen)を生成 private void CreateBorderStyle(LayoutElement layoutElement, out Pen framePen, out Brush textBrush) { var isCurrent = layoutElement == App.Profile.Current; // framePen/textBrush switch (layoutElement.WindowType) { case WindowTypes.Normal: { framePen = isCurrent ? BrushesAndPens.CurrentNormalPen : BrushesAndPens.NormalPen; textBrush = isCurrent ? BrushesAndPens.CurrentNormalBrush : BrushesAndPens.NormalBrush; break; } case WindowTypes.DXGI: { framePen = isCurrent ? BrushesAndPens.CurrentDXGIPen : BrushesAndPens.DXGIPen; textBrush = isCurrent ? BrushesAndPens.CurrentDXGIBrush : BrushesAndPens.DXGIBrush; break; } case WindowTypes.Desktop: { framePen = isCurrent ? BrushesAndPens.CurrentDesktopPen : BrushesAndPens.DesktopPen; textBrush = isCurrent ? BrushesAndPens.CurrentDesktopBrush : BrushesAndPens.DesktopBrush; break; } default: Debug.Fail("switch"); throw new System.ArgumentException(); } }
//=================================================================== // ヒットモード計算 //=================================================================== /// レイアウト要素とマウス座標(相対座標系)からHitModesを調べる /// @param layoutElement レイアウト要素 /// @param mousePoint マウス座標(相対座標系) /// @return HitModes.SizeXXXのいずれか private static HitModes GetHitMode(LayoutElement layoutElement, RelativePoint mousePoint) { // --------------- // | |1 |2 | // --------------- // H1 var borderWRight = layoutElement.BoundRelativeLeft + Constants.BorderRelativeThickness; // H2 var borderELeft = layoutElement.BoundRelativeRight - Constants.BorderRelativeThickness; // V1 var borderNBottom = layoutElement.BoundRelativeTop + Constants.BorderRelativeThickness; // v2 var borderSTop = layoutElement.BoundRelativeBottom - Constants.BorderRelativeThickness; // X座標→Y座標 /// @attention 浮動小数点数の比較 if (mousePoint.X < borderWRight) { // W if (mousePoint.Y < borderNBottom) { // N return HitModes.SizeNW; } else if (mousePoint.Y < borderSTop) { // (N)-(S) return HitModes.SizeW; } else { // S return HitModes.SizeSW; } } else if (mousePoint.X < borderELeft) { // (W)-(E) if (mousePoint.Y < borderNBottom) { // N return HitModes.SizeN; } else if (mousePoint.Y < borderSTop) { // (N)-(S) Debug.Fail("Move?", "HitTest.GetHitMode"); return HitModes.Move; } else { // S return HitModes.SizeS; } } else { // E if (mousePoint.Y < borderNBottom) { // N return HitModes.SizeNE; } else if (mousePoint.Y < borderSTop) { // (N)-(S) return HitModes.SizeE; } else { // S return HitModes.SizeSE; } } }
/// ClippingHeight表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return ClippingHeight表示用文字列 public static string GetClippingHeightString(LayoutElement layoutElement) { if (layoutElement.Fit && !layoutElement.IsWindowValid) { return "n/a"; } else if (layoutElement.Fit) { return string.Format("{0}*", layoutElement.ClippingHeightWithFit); } else { return layoutElement.ClippingHeightWithFit.ToString(); } }
/// BoundRelativeTop表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return BoundRelativeTop表示用文字列 public static string GetBoundRelativeTopString(LayoutElement layoutElement) { return layoutElement.BoundRelativeTop.ToString("F3"); }
//================================================================= // SWScale //================================================================= /// SWScaleLumaGBlur表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return SWScaleLumaGBlur表示用文字列 public static string GetSWScaleLumaGBlurString(LayoutElement layoutElement) { return layoutElement.SWScaleLumaGBlur.ToString("F2"); }
//================================================================= // Window //================================================================= /// 表示用のウィンドウ名を取得 public static string GetWindowCaption(LayoutElement layoutElement) { if (layoutElement.IsWindowValid) { return layoutElement.WindowCaption; } else { return string.Format("n/a ({0})", layoutElement.WindowCaption); } }
/// SWScaleLumaSharpen表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return SWScaleLumaSharpen表示用文字列 public static string GetSWScaleLumaSharpenString(LayoutElement layoutElement) { return layoutElement.SWScaleLumaSharpen.ToString("F2"); }
/// 結果を取得 /// @param layoutElement 対象のレイアウト要素 /// @return 生成済みならBitmapSourceを。でなければNullを返す。 public BitmapSource GetBitmapSource(LayoutElement layoutElement) { if (!layoutElement.IsWindowValid) return null; var request = new ScreenCaptureRequest(layoutElement); /// ディクショナリはスレッドセーフではない lock (this.sharedLock) { BitmapSource result; var success = this.cache.TryGetValue(request, out result); Debug.WriteLineIf(!success, "No request in cache", "ScreenCaptureTimer"); return result; } }
/// 枠線とキャプションの描画 /// @param dc 描画先 /// @param layoutElement 描画対象 /// @param index 描画対象のインデックス private void DrawBorder(DrawingContext dc, LayoutElement layoutElement, int index) { // フレームサイズ var boundRect = layoutElement.GetBoundRect( App.RuntimeOptions.CurrentSampleWidth, App.RuntimeOptions.CurrentSampleHeight).ToRect(); // スタイルの取得 Brush textBrush; Pen framePen; this.CreateBorderStyle(layoutElement, out framePen, out textBrush); // フレームの描画 var inflateValue = framePen.Thickness / 2; var infraleRect = Rect.Inflate(boundRect, -inflateValue, -inflateValue); dc.DrawRectangle(null, framePen, infraleRect); // ヘッダーのドロップシャドウの描画 var shadowOffset = 1.0; var shadowPoint = new Point(boundRect.X + shadowOffset, boundRect.Y + shadowOffset); var shadowBoundRect = new Rect(boundRect.X + shadowOffset, boundRect.Y + shadowOffset, boundRect.Width - shadowOffset, boundRect.Height - shadowOffset); var shadow = this.CreateHeader(layoutElement, index, shadowBoundRect, BrushesAndPens.DropShadowBrush); dc.DrawText(shadow, shadowPoint); // ヘッダーの描画 var headerPoint = new Point(boundRect.X, boundRect.Y); var header = this.CreateHeader(layoutElement, index, boundRect, textBrush); dc.DrawText(header, headerPoint); }
/// フィールドを初期化 private void Clear() { this.target = null; this.hitMode = HitModes.Neutral; this.mouseOffset = null; this.originalLTRB = null; this.snapGuide = null; this.lastUpdateControl = 0; this.MousePoint = null; this.ShouldUpdateControl = false; }
/// プレビュー画像の描画 /// @param dc 描画先 /// @param layoutElement 描画対象 private void DrawPreview(DrawingContext dc, LayoutElement layoutElement) { var bitmap = App.ScreenCaptureTimer.GetBitmapSource(layoutElement); if (bitmap == null) { // エラーが起きた場合は赤色の四角形を描画 var boundRect = layoutElement.GetBoundRect( App.RuntimeOptions.CurrentSampleWidth, App.RuntimeOptions.CurrentSampleHeight).ToRect(); dc.DrawRectangle(Brushes.Red, null, boundRect); } else { // プレビューの描画 var actualBoundRect = layoutElement.GetActualBoundRect( App.RuntimeOptions.CurrentSampleWidth, App.RuntimeOptions.CurrentSampleHeight).ToRect(); dc.DrawImage(bitmap, actualBoundRect); } }
/// BoundRelativeBottom表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return BoundRelativeBottom表示用文字列 public static string GetBoundRelativeBottomString(LayoutElement layoutElement) { return(layoutElement.BoundRelativeBottom.ToString("F3")); }
/// コンストラクタ public RelativeMouseOffset(LayoutElement layoutElement, RelativePoint mousePoint) : base(mousePoint.X - layoutElement.BoundRelativeLeft, mousePoint.Y - layoutElement.BoundRelativeTop, mousePoint.X - layoutElement.BoundRelativeRight, mousePoint.Y - layoutElement.BoundRelativeBottom) { // nop }
/// SWScaleChromaSharpen表示用 /// @param layoutElement データ取得元のレイアウト要素 /// @return SWScaleChromaSharpen表示用文字列 public static string GetSWScaleChromaSharpenString(LayoutElement layoutElement) { return(layoutElement.SWScaleChromaSharpen.ToString("F2")); }
//------------------------------------------------------------------- /// レイアウト要素からClipping領域(Fitオプションなし)を取得 private static ClientRect GetOriginal(LayoutElement layoutElement) { return new ClientRect(layoutElement.ClippingXWithoutFit, layoutElement.ClippingYWithoutFit, layoutElement.ClippingWidthWithoutFit, layoutElement.ClippingHeightWithoutFit); }
/// 全てのレイアウト要素を一気に更新する public void SetLayoutElements(List<LayoutElement> layoutElements, LayoutElement current) { this.LayoutElements = layoutElements; this.Current = current; }
/// LayoutEdit用ヘッダー文字列 /// @param layoutElement データ取得元のレイアウト要素 /// @param index レイアウト要素のインデックス /// @param isCurrent 現在選択中のLayoutElementか /// @param isDummy ダミープレビューサイズかどうか /// @param sampleWidth サンプルの幅 /// @param sampleHeight サンプルの高さ /// @return LayoutEdit用ヘッダー文字列 public static string GetHeaderStringForLayoutEdit( LayoutElement layoutElement, int index, bool isCurrent, bool isDummy, int sampleWidth, int sampleHeight) { if (isCurrent && isDummy) { return string.Format(" [{0}] {1}", index + 1, layoutElement.WindowCaption); } else if (isCurrent) { var boundRect = layoutElement.GetBoundRect(sampleWidth, sampleHeight); return string.Format(" [{0}] ({1}x{2}) {3}", index + 1, boundRect.Width, boundRect.Height, layoutElement.WindowCaption); } else { return string.Format(" [{0}]", index + 1); } }