diff --git a/src/Greenshot.Base/Greenshot.Base.csproj b/src/Greenshot.Base/Greenshot.Base.csproj index 5ce97f6e3..805098491 100644 --- a/src/Greenshot.Base/Greenshot.Base.csproj +++ b/src/Greenshot.Base/Greenshot.Base.csproj @@ -6,12 +6,12 @@ - - - - - - + + + + + + diff --git a/src/Greenshot.Editor/Controls/EmojiPicker.xaml.cs b/src/Greenshot.Editor/Controls/EmojiPicker.xaml.cs index 1ea25deee..960dc5218 100644 --- a/src/Greenshot.Editor/Controls/EmojiPicker.xaml.cs +++ b/src/Greenshot.Editor/Controls/EmojiPicker.xaml.cs @@ -20,6 +20,9 @@ using System.Windows.Input; namespace Greenshot.Editor.Controls { + /// + /// The event which is created when the emoji is picked + /// public class EmojiPickedEventArgs : EventArgs { public EmojiPickedEventArgs() { } @@ -61,8 +64,7 @@ namespace Greenshot.Editor.Controls public event EmojiPickedEventHandler Picked; - private static void OnSelectionPropertyChanged(DependencyObject source, - DependencyPropertyChangedEventArgs e) + private static void OnSelectionPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { (source as EmojiPicker)?.OnSelectionChanged(e.NewValue as string); } @@ -75,24 +77,21 @@ namespace Greenshot.Editor.Controls private void OnSelectionChanged(string s) { - var is_disabled = string.IsNullOrEmpty(s); - Image.Emoji = is_disabled ? "???" : s; - Image.Opacity = is_disabled ? 0.3 : 1.0; + var isDisabled = string.IsNullOrEmpty(s); + Image.Emoji = isDisabled ? "???" : s; + Image.Opacity = isDisabled ? 0.3 : 1.0; SelectionChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Selection))); } private void OnEmojiPicked(object sender, RoutedEventArgs e) { - if (sender is Control control && control.DataContext is EmojiData.Emoji emoji) - { - if (emoji.VariationList.Count == 0 || sender is Button) - { - Selection = emoji.Text; - Button_INTERNAL.IsChecked = false; - e.Handled = true; - Picked?.Invoke(this, new EmojiPickedEventArgs(Selection)); - } - } + if (sender is not Control { DataContext: EmojiData.Emoji emoji }) return; + if (emoji.VariationList.Count != 0 && sender is not Button) return; + + Selection = emoji.Text; + Button_INTERNAL.IsChecked = false; + e.Handled = true; + Picked?.Invoke(this, new EmojiPickedEventArgs(Selection)); } public static readonly DependencyProperty SelectionProperty = DependencyProperty.Register( @@ -101,31 +100,37 @@ namespace Greenshot.Editor.Controls private void OnPopupKeyDown(object sender, KeyEventArgs e) { - if (e.Key == Key.Escape && sender is Popup popup) - { - popup.IsOpen = false; - e.Handled = true; - } + if (e.Key != Key.Escape || sender is not Popup popup) return; + popup.IsOpen = false; + e.Handled = true; } private void OnPopupLoaded(object sender, RoutedEventArgs e) { - if (!(sender is Popup popup)) - return; + if (sender is not Popup popup) return; var child = popup.Child; - IInputElement old_focus = null; + IInputElement oldFocus = null; child.Focusable = true; child.IsVisibleChanged += (o, ea) => { - if (child.IsVisible) - { - old_focus = Keyboard.FocusedElement; - Keyboard.Focus(child); - } + if (!child.IsVisible) return; + oldFocus = Keyboard.FocusedElement; + Keyboard.Focus(child); }; - popup.Closed += (o, ea) => Keyboard.Focus(old_focus); + popup.Closed += (o, ea) => Keyboard.Focus(oldFocus); + } + + public void ShowPopup(bool show) + { + foreach (var child in Children) + { + if (child is ToggleButton button) + { + button.IsChecked = show; + } + } } } } diff --git a/src/Greenshot.Editor/Drawing/CropContainer.cs b/src/Greenshot.Editor/Drawing/CropContainer.cs index 2742b73db..d450e9dd2 100644 --- a/src/Greenshot.Editor/Drawing/CropContainer.cs +++ b/src/Greenshot.Editor/Drawing/CropContainer.cs @@ -250,8 +250,7 @@ namespace Greenshot.Editor.Drawing break; } } - var scaleOptions = (this as IHaveScaleOptions)?.GetScaleOptions(); - _boundsAfterResize = ScaleHelper.Scale(_boundsBeforeResize, x, y, GetAngleRoundProcessor(), scaleOptions); + _boundsAfterResize = ScaleHelper.Scale(_boundsBeforeResize, Positions.TopLeft, x, y, GetAngleRoundProcessor()); // apply scaled bounds to this DrawableContainer ApplyBounds(_boundsAfterResize); diff --git a/src/Greenshot.Editor/Drawing/DrawableContainer.cs b/src/Greenshot.Editor/Drawing/DrawableContainer.cs index 1de2cd07b..164ed7177 100644 --- a/src/Greenshot.Editor/Drawing/DrawableContainer.cs +++ b/src/Greenshot.Editor/Drawing/DrawableContainer.cs @@ -534,7 +534,7 @@ namespace Greenshot.Editor.Drawing _boundsAfterResize = new NativeRectFloat(_boundsBeforeResize.Left, _boundsBeforeResize.Top, x - _boundsAfterResize.Left, y - _boundsAfterResize.Top); var scaleOptions = (this as IHaveScaleOptions)?.GetScaleOptions(); - _boundsAfterResize = ScaleHelper.Scale(_boundsAfterResize, x, y, GetAngleRoundProcessor(), scaleOptions); + _boundsAfterResize = ScaleHelper.Scale(_boundsAfterResize, Positions.TopLeft, x, y,GetAngleRoundProcessor(), scaleOptions); // apply scaled bounds to this DrawableContainer ApplyBounds(_boundsAfterResize); diff --git a/src/Greenshot.Editor/Drawing/EmojiContainer.cs b/src/Greenshot.Editor/Drawing/EmojiContainer.cs index 58b4b7cbd..5a8436989 100644 --- a/src/Greenshot.Editor/Drawing/EmojiContainer.cs +++ b/src/Greenshot.Editor/Drawing/EmojiContainer.cs @@ -20,11 +20,9 @@ */ using System; -using System.Drawing; -using System.Drawing.Drawing2D; +using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; -using System.Windows.Controls.Primitives; using System.Windows.Forms; using System.Windows.Forms.Integration; using Greenshot.Base.Core; @@ -33,7 +31,6 @@ using Greenshot.Editor.Controls; using Greenshot.Editor.Drawing.Adorners; using Greenshot.Editor.Helpers; using Image = System.Drawing.Image; -using Matrix = System.Drawing.Drawing2D.Matrix; namespace Greenshot.Editor.Drawing { @@ -41,17 +38,15 @@ namespace Greenshot.Editor.Drawing /// Description of EmojiContainer. /// [Serializable] - public class EmojiContainer : DrawableContainer, IEmojiContainer, IHaveScaleOptions + public class EmojiContainer : VectorGraphicsContainer, IEmojiContainer, IHaveScaleOptions { [NonSerialized] private static EmojiContainer _currentContainer; [NonSerialized] private static ElementHost _emojiPickerHost; [NonSerialized] private static EmojiPicker _emojiPicker; [NonSerialized] private bool _justCreated = true; - [NonSerialized] private Image _cachedImage = null; private string _emoji; - private int _rotationAngle; private bool _useSystemFont; public string Emoji @@ -111,7 +106,7 @@ namespace Greenshot.Editor.Drawing private void GetOrCreatePickerControl() { // Create one picker control by surface - // TODO: This is not ideal, replace with a different solution. + // TODO: This is not ideal, as we need to controls from the surface, should replace this with a different solution. _emojiPickerHost = _parent.Controls.Find("EmojiPickerHost", false).OfType().FirstOrDefault(); if (_emojiPickerHost != null) return; @@ -123,10 +118,12 @@ namespace Greenshot.Editor.Drawing _currentContainer.Invalidate(); }; - _emojiPickerHost = new ElementHost(); - _emojiPickerHost.Dock = DockStyle.None; - _emojiPickerHost.Child = _emojiPicker; - _emojiPickerHost.Name = "EmojiPickerHost"; + _emojiPickerHost = new ElementHost + { + Dock = DockStyle.None, + Child = _emojiPicker, + Name = "EmojiPickerHost" + }; _parent.Controls.Add(_emojiPickerHost); } @@ -136,91 +133,46 @@ namespace Greenshot.Editor.Drawing _emojiPicker?.ShowPopup(false); } - protected override void OnDeserialized(StreamingContext streamingContext) + /// + /// Make sure we register the property changed, to manage the state of the picker + /// + protected override void Init() { - base.OnDeserialized(streamingContext); - Init(); - } - - private void Init() - { - Adorners.Add(new ResizeAdorner(this, Positions.TopLeft)); - Adorners.Add(new ResizeAdorner(this, Positions.TopRight)); - Adorners.Add(new ResizeAdorner(this, Positions.BottomLeft)); - Adorners.Add(new ResizeAdorner(this, Positions.BottomRight)); - + base.Init(); PropertyChanged += OnPropertyChanged; } - protected override void Dispose(bool disposing) + + /// + /// Handle the state of the Emoji Picker + /// + /// object + /// PropertyChangedEventArgs + private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (disposing) + if (!e.PropertyName.Equals(nameof(Selected))) return; + + if (!Selected) { - ResetCachedBitmap(); + HideEmojiPicker(); } - - base.Dispose(disposing); - } - - public override void Transform(Matrix matrix) - { - _rotationAngle += CalculateAngle(matrix); - _rotationAngle %= 360; - - ResetCachedBitmap(); - - base.Transform(matrix); - } - - private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName.Equals(nameof(Selected))) + else if (Status == EditStatus.IDLE && _justCreated) { - if (!Selected) - { - HideEmojiPicker(); - } - else if (Status == EditStatus.IDLE && _justCreated) - { - // Show picker just after creation - ShowEmojiPicker(); - _justCreated = false; - } + // Show picker just after creation + ShowEmojiPicker(); + _justCreated = false; } } - public override void Draw(Graphics graphics, RenderMode rm) + protected override Image ComputeBitmap() { - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + var iconSize = Math.Min(Bounds.Width, Bounds.Height); + if (iconSize <= 0) return null; - var rect = Bounds; - - var iconSize = Math.Min(rect.Width, rect.Height); - if (iconSize <= 0) return; - if (_cachedImage == null) - { - // First draw or cache was invalidated - _cachedImage = ComputeBitmap(iconSize); - } - else if (iconSize != _cachedImage.Width) - { - // The elements was resized => recompute - _cachedImage.Dispose(); - _cachedImage = ComputeBitmap(iconSize); - } - - graphics.DrawImage(_cachedImage, Bounds); - } - - private Image ComputeBitmap(int iconSize) - { var image = EmojiRenderer.GetBitmap(Emoji, iconSize, useSystemFont: _useSystemFont); - if (_rotationAngle != 0) + if (RotationAngle != 0) { - var newImage = image.Rotate(_rotationAngle); + var newImage = image.Rotate(RotationAngle); image.Dispose(); return newImage; } @@ -228,29 +180,9 @@ namespace Greenshot.Editor.Drawing return image; } - private void ResetCachedBitmap() - { - _cachedImage?.Dispose(); - _cachedImage = null; - } - public ScaleHelper.ScaleOptions GetScaleOptions() { return ScaleHelper.ScaleOptions.Rational; } } - - internal static class PickerExtensions - { - public static void ShowPopup(this EmojiPicker emojiPicker, bool show) - { - foreach (var child in emojiPicker.Children) - { - if (child is ToggleButton button) - { - button.IsChecked = show; - } - } - } - } } \ No newline at end of file diff --git a/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs b/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs index 0c467c0b8..45238caaf 100644 --- a/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs +++ b/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs @@ -25,6 +25,7 @@ using System.Drawing.Drawing2D; using System.Runtime.Serialization; using Greenshot.Base.Interfaces; using Greenshot.Base.Interfaces.Drawing; +using Greenshot.Editor.Drawing.Adorners; namespace Greenshot.Editor.Drawing { @@ -35,28 +36,54 @@ namespace Greenshot.Editor.Drawing [Serializable] public abstract class VectorGraphicsContainer : DrawableContainer { - protected int RotationAngle; + private int _rotationAngle; + protected int RotationAngle + { + get => _rotationAngle; + set => _rotationAngle = value; + } /// /// This is the cached version of the bitmap, pre-rendered to save performance - /// Do not serialized, it can be rebuild with some other information. + /// Do not serialized, it can be rebuild with other information. /// [NonSerialized] private Image _cachedImage; + /// + /// Constructor takes care of calling Init + /// + /// ISurface public VectorGraphicsContainer(ISurface parent) : base(parent) { Init(); } + /// + /// Make sure Init is called after deserializing + /// + /// StreamingContext protected override void OnDeserialized(StreamingContext streamingContext) { base.OnDeserialized(streamingContext); Init(); } - private void Init() + /// + /// Init is called after creating the instance, and from OnDeserialized + /// This is the place to generate your adorners + /// + protected virtual void Init() { - CreateDefaultAdorners(); + // Check if the adorners are already defined! + if (Adorners.Count > 0) + { + return; + } + + Adorners.Add(new ResizeAdorner(this, Positions.TopLeft)); + Adorners.Add(new ResizeAdorner(this, Positions.TopRight)); + Adorners.Add(new ResizeAdorner(this, Positions.BottomLeft)); + Adorners.Add(new ResizeAdorner(this, Positions.BottomRight)); } /// @@ -96,7 +123,10 @@ namespace Greenshot.Editor.Drawing } _cachedImage ??= ComputeBitmap(); - + if (_cachedImage == null) + { + return; + } graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.CompositingQuality = CompositingQuality.HighQuality; @@ -106,9 +136,16 @@ namespace Greenshot.Editor.Drawing graphics.DrawImage(_cachedImage, Bounds); } + /// + /// Implement this to compute the new bitmap according to the size of the container + /// + /// Image protected abstract Image ComputeBitmap(); - private void ResetCachedBitmap() + /// + /// Dispose of the cached bitmap, forcing the code to regenerate it + /// + protected void ResetCachedBitmap() { _cachedImage?.Dispose(); _cachedImage = null; diff --git a/src/Greenshot.Editor/Helpers/ScaleHelper.cs b/src/Greenshot.Editor/Helpers/ScaleHelper.cs index b037c805c..b5e2c61e2 100644 --- a/src/Greenshot.Editor/Helpers/ScaleHelper.cs +++ b/src/Greenshot.Editor/Helpers/ScaleHelper.cs @@ -58,7 +58,7 @@ namespace Greenshot.Editor.Helpers /// the size of the element to be resized /// the target size of the element /// in case the aspect ratio of currentSize and targetSize differs: shall the scaled size fit into targetSize (i.e. that one of its dimensions is smaller - false) or vice versa (true) - /// a new SizeF object indicating the width and height the element should be scaled to + /// NativeSizeFloat object indicating the width and height the element should be scaled to public static NativeSizeFloat GetScaledSize(NativeSizeFloat currentSize, NativeSizeFloat targetSize, bool crop) { float wFactor = targetSize.Width / currentSize.Width; @@ -71,10 +71,10 @@ namespace Greenshot.Editor.Helpers /// /// calculates the position of an element depending on the desired alignment within a RectangleF /// - /// the bounds of the element to be aligned - /// the rectangle reference for alignment of the element + /// NativeRectFloat the bounds of the element to be aligned + /// NativeRectFloat with the rectangle for alignment of the element /// the System.Drawing.ContentAlignment value indicating how the element is to be aligned should the width or height differ from targetSize - /// a new RectangleF object with Location aligned aligned to targetRect + /// NativeRectFloat object with Location aligned aligned to targetRect public static NativeRectFloat GetAlignedRectangle(NativeRectFloat currentRect, NativeRectFloat targetRect, ContentAlignment alignment) { var newRect = new NativeRectFloat(targetRect.Location, currentRect.Size); @@ -96,9 +96,9 @@ namespace Greenshot.Editor.Helpers /// /// Calculates target size of a given rectangle scaled by dragging one of its handles (corners) /// - /// bounds of the current rectangle - /// position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT - /// coordinates of the used handle/gripper + /// NativeRectFloat bounds of the current rectangle + /// Positions with the position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT + /// NativePointFloat coordinates of the used handle/gripper /// ScaleOptions to use when scaling /// NativeRectFloat scaled originalRectangle public static NativeRectFloat Scale(NativeRectFloat originalRectangle, Positions resizeHandlePosition, NativePointFloat resizeHandleCoords, ScaleOptions? options) @@ -134,9 +134,9 @@ namespace Greenshot.Editor.Helpers /// /// Calculates target size of a given rectangle scaled by dragging one of its handles (corners) /// - /// bounds of the current rectangle - /// position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT - /// coordinates of the used handle/gripper + /// NativeRectFloat bounds of the current rectangle + /// Positions with the position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT + /// NativePointFloat with coordinates of the used handle/gripper /// NativeRectFloat with the scaled originalRectangle private static NativeRectFloat Scale(NativeRectFloat originalRectangle, Positions resizeHandlePosition, NativePointFloat resizeHandleCoords) { @@ -158,9 +158,9 @@ namespace Greenshot.Editor.Helpers /// To avoid objects growing near infinity unexpectedly in certain combinations, the adjustment will choose the /// option resulting in the smaller rectangle. /// - /// bounds of the current rectangle - /// position of the handle/gripper being used for resized, see Position - /// coordinates of the used handle/gripper + /// NativeRectFloat with the bounds of the current rectangle + /// Positions with the position of the handle/gripper being used for resized, see Position + /// NativePointFloat with coordinates of the used handle/gripper /// NativePointFloat with the adjusted coordinates private static NativePointFloat AdjustCoordsForRationalScale(NativeRectFloat originalRectangle, Positions resizeHandlePosition, NativePointFloat resizeHandleCoords) { @@ -203,7 +203,7 @@ namespace Greenshot.Editor.Helpers /// /// NativeSizeFloat to be considered for keeping aspect ratio /// NativeSizeFloat selection size (i.e. the size we'd produce if we wouldn't keep aspect ratio) - /// + /// NativeSizeFloat private static NativeSizeFloat GetNewSizeForRationalScale(NativeSizeFloat originalSize, NativeSizeFloat selectedSize) { NativeSizeFloat newSize = selectedSize; @@ -227,17 +227,17 @@ namespace Greenshot.Editor.Helpers return newSize; } - public static NativeRectFloat Scale(NativeRect boundsBeforeResize, int cursorX, int cursorY, ScaleOptions? options) - { - return Scale(boundsBeforeResize, cursorX, cursorY, null, options); - } - - public static NativeRectFloat Scale(NativeRect boundsBeforeResize, int cursorX, int cursorY, IDoubleProcessor angleRoundBehavior, ScaleOptions? options) - { - return Scale(boundsBeforeResize, Positions.TopLeft, cursorX, cursorY, angleRoundBehavior, options); - } - - public static NativeRectFloat Scale(NativeRect boundsBeforeResize, Positions gripperPosition, int cursorX, int cursorY, IDoubleProcessor angleRoundBehavior, ScaleOptions? options) + /// + /// Scale the boundsBeforeResize with the specified position and new location, using the angle angleRoundBehavior + /// + /// NativeRect + /// Positions + /// int + /// int + /// IDoubleProcessor + /// ScaleOptionsProcessor + /// NativeRectFloat + public static NativeRectFloat Scale(NativeRect boundsBeforeResize, Positions gripperPosition, int cursorX, int cursorY, IDoubleProcessor angleRoundBehavior, ScaleOptions? options = null) { options ??= GetScaleOptions();