diff options
author | Dominique Louis <savagesoftware@gmail.com> | 2017-11-01 21:37:54 +0300 |
---|---|---|
committer | Dominique Louis <savagesoftware@gmail.com> | 2018-08-08 09:33:19 +0300 |
commit | b6c0aeba09b2167c3f9d83004f3942c3e6ae066e (patch) | |
tree | c0433c95632f770675034e1ff65cfc6699600034 /Xamarin.PropertyEditing.Mac/Controls | |
parent | a7b11a56e99a00177c705031b65de932f217dd11 (diff) |
[Mac] Initial CommonRatio implementation.
Diffstat (limited to 'Xamarin.PropertyEditing.Mac/Controls')
11 files changed, 607 insertions, 216 deletions
diff --git a/Xamarin.PropertyEditing.Mac/Controls/BasePointEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/BasePointEditorControl.cs index b438f2c..287a92f 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/BasePointEditorControl.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/BasePointEditorControl.cs @@ -15,7 +15,7 @@ namespace Xamarin.PropertyEditing.Mac internal NumericSpinEditor<T> YEditor { get; set; } public override NSView FirstKeyView => XEditor; - public override NSView LastKeyView => YEditor; + public override NSView LastKeyView => YEditor.DecrementButton; public BasePointEditorControl () { diff --git a/Xamarin.PropertyEditing.Mac/Controls/BaseRectangleEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/BaseRectangleEditorControl.cs index 5cdd872..1c57694 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/BaseRectangleEditorControl.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/BaseRectangleEditorControl.cs @@ -21,7 +21,7 @@ namespace Xamarin.PropertyEditing.Mac protected NumericSpinEditor<T> HeightEditor { get; set; } public override NSView FirstKeyView => XEditor; - public override NSView LastKeyView => HeightEditor; + public override NSView LastKeyView => HeightEditor.DecrementButton; public BaseRectangleEditorControl () { diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs index edb17f5..1aabaa0 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs @@ -2,6 +2,8 @@ using AppKit; using CoreGraphics; using Foundation; +using Xamarin.PropertyEditing.Drawing; +using Xamarin.PropertyEditing.Themes; namespace Xamarin.PropertyEditing.Mac { @@ -12,8 +14,21 @@ namespace Xamarin.PropertyEditing.Mac internal class NumericSpinEditor : NSView, INSAccessibilityGroup { NumericTextField numericEditor; - NSStepper stepper; - bool editing; + public NumericTextField NumericEditor { + get { return numericEditor; } + } + + UpSpinnerButton incrementButton; + public UpSpinnerButton IncrementButton { + get { return incrementButton; } + } + + DownSpinnerButton decrementButton; + public DownSpinnerButton DecrementButton { + get { return decrementButton; } + } + + protected bool editing; public event EventHandler ValueChanged; public event EventHandler EditingEnded; @@ -31,28 +46,16 @@ namespace Xamarin.PropertyEditing.Mac set { ((NSTextFieldCell)numericEditor.Cell).PlaceholderString = value; } } - NSStepper Stepper { - get { return stepper; } - } - - public NSTextField TextField { - get { return numericEditor; } - } - public override CGSize IntrinsicContentSize { get { - var baseSize = stepper.IntrinsicContentSize; - return new CGSize (baseSize.Width + 40, baseSize.Height); + var baseSize = numericEditor.IntrinsicContentSize; + return new CGSize (baseSize.Width + 20, baseSize.Height); } } public NSColor BackgroundColor { - get { - return numericEditor.BackgroundColor; - } - set { - numericEditor.BackgroundColor = value; - } + get { return numericEditor.BackgroundColor; } + set { numericEditor.BackgroundColor = value; } } public override nfloat BaselineOffsetFromBottom { @@ -66,7 +69,7 @@ namespace Xamarin.PropertyEditing.Mac public double Value { - get { return stepper.DoubleValue; } + get { return numericEditor.DoubleValue; } set { SetValue (value); } } @@ -76,47 +79,38 @@ namespace Xamarin.PropertyEditing.Mac set { SetValue (value); } } - public bool Wrap { - get { return stepper.ValueWraps; } - set { stepper.ValueWraps = value; } - } - public double MinimumValue { - get { return stepper.MinValue; } + get { return formatter.Minimum.DoubleValue; } set { - stepper.MinValue = value; formatter.Minimum = new NSNumber (value); } } public double MaximumValue { - get { return stepper.MaxValue; } + get { return formatter.Maximum.DoubleValue; } set { - stepper.MaxValue = value; formatter.Maximum = new NSNumber (value); } } + double incrementValue = 1.0f; public double IncrementValue { - get { return stepper.Increment; } - set { stepper.Increment = value; } + get { return incrementValue; } + set { incrementValue = value; } } public bool Enabled { - get { - return numericEditor.Enabled; - } + get { return numericEditor.Enabled; } set { numericEditor.Enabled = value; - stepper.Enabled = value; + incrementButton.Enabled = value; + decrementButton.Enabled = value; } } NSNumberFormatter formatter; public NSNumberFormatter Formatter { - get { - return formatter; - } + get { return formatter; } set { formatter = value; numericEditor.Formatter = formatter; @@ -124,32 +118,31 @@ namespace Xamarin.PropertyEditing.Mac } public bool IsIndeterminate { - get { - return !string.IsNullOrEmpty (numericEditor.StringValue); - } + get { return !string.IsNullOrEmpty (numericEditor.StringValue); } set { if (value) numericEditor.StringValue = string.Empty; - else - numericEditor.DoubleValue = stepper.DoubleValue; } } public bool Editable { - get { - return numericEditor.Editable; - } + get { return numericEditor.Editable; } set { numericEditor.Editable = value; - stepper.Enabled = value; + incrementButton.Enabled = value; + decrementButton.Enabled = value; } } public NSNumberFormatterStyle NumberStyle { get { return formatter.NumberStyle; } - set { - formatter.NumberStyle = value; - } + set { formatter.NumberStyle = value; } + } + + public bool AllowRatios + { + get { return numericEditor.AllowRatios; } + set { numericEditor.AllowRatios = value; } } protected virtual void OnConfigureNumericTextField () @@ -157,6 +150,12 @@ namespace Xamarin.PropertyEditing.Mac numericEditor.Formatter = formatter; } + public bool AllowNegativeValues + { + get { return numericEditor.AllowNegativeValues; } + set { numericEditor.AllowNegativeValues = value; } + } + public virtual void Reset () { } @@ -166,99 +165,110 @@ namespace Xamarin.PropertyEditing.Mac TranslatesAutoresizingMaskIntoConstraints = false; var controlSize = NSControlSize.Small; - stepper = new NSStepper { - TranslatesAutoresizingMaskIntoConstraints = false, - ValueWraps = false, - ControlSize = controlSize, - }; + incrementButton = new UpSpinnerButton (); + + decrementButton = new DownSpinnerButton (); formatter = new NSNumberFormatter { FormatterBehavior = NSNumberFormatterBehavior.Version_10_4, Locale = NSLocale.CurrentLocale, MaximumFractionDigits = 15, + Maximum = double.MaxValue, + Minimum = double.MinValue, NumberStyle = NSNumberFormatterStyle.Decimal, UsesGroupingSeparator = false, }; numericEditor = new NumericTextField { Alignment = NSTextAlignment.Right, - Formatter = formatter, TranslatesAutoresizingMaskIntoConstraints = false, Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize), ControlSize = controlSize, }; - stepper.Activated += (s, e) => { - OnStepperActivated (s, e); - }; + incrementButton.OnMouseLeftDown += (sender, e) => { IncrementNumericValue (); }; + decrementButton.OnMouseLeftDown += (sender, e) => { DecrementNumericValue (); }; - numericEditor.KeyArrowUp += (sender, e) => { IncrementNumericValue (); }; - numericEditor.KeyArrowDown += (sender, e) => { DecrementNumericValue (); }; + numericEditor.KeyArrowUp += (sender, e) => { IncrementNumericValue (e); }; + numericEditor.KeyArrowDown += (sender, e) => { DecrementNumericValue (e); }; numericEditor.ValidatedEditingEnded += (s, e) => { OnEditingEnded (s, e); }; - AddSubview (stepper); AddSubview (numericEditor); + AddSubview (incrementButton); + AddSubview (decrementButton); this.DoConstraints (new[] { numericEditor.ConstraintTo (this, (n, c) => n.Width == c.Width - 16), - numericEditor.ConstraintTo (this, (n, c) => n.Height == 19), - stepper.ConstraintTo (numericEditor, (s, n) => s.Left == n.Right + 4), - stepper.ConstraintTo (numericEditor, (s, n) => s.Top == n.Top), + numericEditor.ConstraintTo (this, (n, c) => n.Height == PropertyEditorControl.DefaultControlHeight - 3), + incrementButton.ConstraintTo (numericEditor, (s, n) => s.Left == n.Right + 5), + incrementButton.ConstraintTo (numericEditor, (s, n) => s.Top == n.Top + 1), + incrementButton.ConstraintTo (numericEditor, (s, n) => s.Width == 9), + incrementButton.ConstraintTo (numericEditor, (s, n) => s.Height == 9), + decrementButton.ConstraintTo (numericEditor, (s, n) => s.Left == n.Right + 5), + decrementButton.ConstraintTo (numericEditor, (s, n) => s.Top == n.Top + 10), + decrementButton.ConstraintTo (numericEditor, (s, n) => s.Width == 9), + decrementButton.ConstraintTo (numericEditor, (s, n) => s.Height == 9), }); + + PropertyEditorPanel.ThemeManager.ThemeChanged += ThemeManager_ThemeChanged; + + UpdateTheme (); } - protected virtual void SetStepperActivated () + protected override void Dispose (bool disposing) { - SetValue (stepper.DoubleValue); + if (disposing) { + PropertyEditorPanel.ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged; + } } - protected void OnStepperActivated (object sender, EventArgs e) + void ThemeManager_ThemeChanged (object sender, EventArgs e) { - if (!editing) { - editing = true; - SetStepperActivated (); - if (ValueChanged != null) - ValueChanged (this, EventArgs.Empty); - editing = false; - } + UpdateTheme (); } - protected void OnValueChanged (object sender, EventArgs e) + protected void UpdateTheme () { - if (!editing) { - editing = true; - SetValue (numericEditor.StringValue); - if (ValueChanged != null) - ValueChanged (this, EventArgs.Empty); - editing = false; + this.Appearance = PropertyEditorPanel.ThemeManager.CurrentAppearance; + + // Skin the inc/dec buttons until we support theming files + switch (PropertyEditorPanel.ThemeManager.Theme) { + case PropertyEditorTheme.Dark: + incrementButton.Image = NSImage.ImageNamed ("stepper-up"); // TODO path to proper image + decrementButton.Image = NSImage.ImageNamed ("stepper-down"); // TODO path to proper image + break; + + case PropertyEditorTheme.Light: + incrementButton.Image = NSImage.ImageNamed ("stepper-up"); // TODO path to proper image + decrementButton.Image = NSImage.ImageNamed ("stepper-down"); // TODO path to proper image + break; + + case PropertyEditorTheme.None: + incrementButton.Image = NSImage.ImageNamed ("stepper-up"); // TODO path to proper image + decrementButton.Image = NSImage.ImageNamed ("stepper-down"); // TODO path to proper image + break; } + } - protected void OnEditingEnded (object sender, EventArgs e) + virtual protected void OnEditingEnded (object sender, EventArgs e) { if (!editing) { editing = true; SetValue (numericEditor.StringValue); - if (EditingEnded != null) - EditingEnded (this, EventArgs.Empty); - if (ValueChanged != null) - ValueChanged (this, EventArgs.Empty); + EditingEnded?.Invoke (this, EventArgs.Empty); editing = false; } } void SetValue (string value) { - //Regulates maximun and minium out of range - if (!string.IsNullOrEmpty (value)) { - stepper.DoubleValue = CoerceValue (FieldValidation.FixInitialValue (value, Value.ToEditorString ()).ToEditorDouble ()); - numericEditor.StringValue = FieldValidation.RoundDoubleValue (stepper.DoubleValue.ToEditorString (), NumericMode == ValidationType.Decimal ? FieldValidation.DefaultXcodeMaxRoundDigits : 0); - } else { - stepper.StringValue = value; + if (numericEditor.StringValue != value) { numericEditor.StringValue = value; + NotifyingValueChanged (EventArgs.Empty); } } @@ -267,21 +277,40 @@ namespace Xamarin.PropertyEditing.Mac SetValue (value.ToString ()); } - protected double CoerceValue (double val) + protected void NotifyingValueChanged (EventArgs eventArgs) + { + ValueChanged?.Invoke (this, eventArgs); + } + + public void IncrementNumericValue (bool shiftPressed = false) + { + if (!editing) { + editing = true; + SetIncrementOrDecrementValue (shiftPressed ? 10 * incrementValue : incrementValue); + editing = false; + } + } + + public void DecrementNumericValue (bool shiftPressed = false) { - return FieldValidation.CoerceValue (val, MinimumValue, MaximumValue); + if (!editing) { + editing = true; + SetIncrementOrDecrementValue (-(shiftPressed ? 10 * incrementValue : incrementValue)); + editing = false; + } } - public void IncrementNumericValue () + virtual protected void SetIncrementOrDecrementValue (double incDevValue) { - SetValue (stepper.DoubleValue + IncrementValue); - OnStepperActivated (stepper, EventArgs.Empty); + // Constrain our value to our Min/Max before we set it + var newValue = Clamp (numericEditor.DoubleValue + incDevValue); + + SetValue (newValue); } - public void DecrementNumericValue () + public double Clamp (double value) { - SetValue (stepper.DoubleValue - IncrementValue); - OnStepperActivated (stepper, EventArgs.Empty); + return (double)Decimal.Round ((decimal)(value < MinimumValue ? MinimumValue : value > MaximumValue ? MaximumValue : value), Digits); } } } diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericTextField.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericTextField.cs index bd3a045..f324c9b 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericTextField.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericTextField.cs @@ -25,8 +25,8 @@ namespace Xamarin.PropertyEditing.Mac get; set; } - public event EventHandler KeyArrowUp; - public event EventHandler KeyArrowDown; + public event EventHandler<bool> KeyArrowUp; + public event EventHandler<bool> KeyArrowDown; public event EventHandler ValidatedEditingEnded; public NumericTextField () @@ -34,15 +34,15 @@ namespace Xamarin.PropertyEditing.Mac AllowNegativeValues = true; var keyUpDownDelegate = new KeyUpDownDelegate (); - keyUpDownDelegate.KeyArrowUp += (sender, e) => { OnKeyArrowUp (); }; - keyUpDownDelegate.KeyArrowDown += (sender, e) => { OnKeyArrowDown (); }; + keyUpDownDelegate.KeyArrowUp += (sender, e) => { OnKeyArrowUp (e); }; + keyUpDownDelegate.KeyArrowDown += (sender, e) => { OnKeyArrowDown (e); }; Delegate = keyUpDownDelegate; } - public override bool ShouldBeginEditing (NSText fieldEditor) + public override bool ShouldBeginEditing (NSText textObject) { - CachedCurrentEditor = fieldEditor; - cachedValueString = fieldEditor.Value; + CachedCurrentEditor = textObject; + cachedValueString = textObject.Value; if (AllowRatios) CachedCurrentEditor.Delegate = new RatioValidateDelegate (this); @@ -52,27 +52,19 @@ namespace Xamarin.PropertyEditing.Mac return true; } - protected virtual void OnKeyArrowUp () + protected virtual void OnKeyArrowUp (bool shiftPressed) { - var handler = KeyArrowUp; - if (handler != null) { - handler (this, EventArgs.Empty); - } + KeyArrowUp?.Invoke (this, shiftPressed); } - protected virtual void OnKeyArrowDown () + protected virtual void OnKeyArrowDown (bool shiftPressed) { - var handler = KeyArrowDown; - if (handler != null) { - handler (this, EventArgs.Empty); - } + KeyArrowDown?.Invoke (this, shiftPressed); } - public virtual void RaiseValidatedEditingEnded () + public virtual void NotifyValidatedEditingEnded () { - var handler = ValidatedEditingEnded; - if (handler != null) - handler (this, EventArgs.Empty); + ValidatedEditingEnded?.Invoke (this, EventArgs.Empty); } public virtual void ResetInvalidInput () @@ -80,12 +72,66 @@ namespace Xamarin.PropertyEditing.Mac this.StringValue = cachedValueString; } + public static bool CheckIfNumber (string finalString, ValidationType mode, bool allowNegativeValues) + { + return mode == ValidationType.Decimal ? + ValidateDecimal (finalString, allowNegativeValues) : + ValidateInteger (finalString, allowNegativeValues); + } + + public static bool ValidateDecimal (string finalString, bool allowNegativeValues) + { + double value; + //Checks parsing to number + if (!double.TryParse (finalString, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentUICulture, out value)) + return false; + //Checks if needs to be possitive value + if (!allowNegativeValues && value < 0) + return false; + //Checks a common validation + return ViewModels.RatioViewModel.IsValidInput (finalString); + } + + public static bool ValidateInteger (string finalString, bool allowNegativeValues) + { + int value; + //Checks parsing to number + if (!int.TryParse (finalString, out value)) + return false; + //Checks if needs to be possitive value + if (!allowNegativeValues && value < 0) + return false; + //Checks a common validation + return ViewModels.RatioViewModel.IsValidInput (finalString); + } + + public static bool CheckIfRatio (string finalString, ValidationType mode, bool allowNegativeValues) + { + var parts = finalString.Split (ViewModels.RatioViewModel.SplitSeparators, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 2) { + bool parsed = true; + + if (!CheckIfNumber (parts[0], mode, allowNegativeValues)) + parsed = false; + + if (!String.IsNullOrEmpty (parts[1]) && !CheckIfNumber (parts[1], mode, allowNegativeValues)) + parsed = false; + + if (parsed) + return true; + } else if (parts.Length == 1) { // We have a potential whole number, let's make sure + if (CheckIfNumber (parts[0], mode, allowNegativeValues)) { + return true; + } + } + return false; + } } class KeyUpDownDelegate : NSTextFieldDelegate { - public event EventHandler KeyArrowUp; - public event EventHandler KeyArrowDown; + public event EventHandler<bool> KeyArrowUp; + public event EventHandler<bool> KeyArrowDown; public override bool DoCommandBySelector (NSControl control, NSTextView textView, Selector commandSelector) { @@ -96,27 +142,27 @@ namespace Xamarin.PropertyEditing.Mac case "moveDown:": OnKeyArrowDown (); break; + case "moveUpAndModifySelection:": + OnKeyArrowUp (true); + break; + case "moveDownAndModifySelection:": + OnKeyArrowDown (true); + break; default: return false; } - return false; + return true; } - protected virtual void OnKeyArrowUp () + protected virtual void OnKeyArrowUp (bool shiftPressed = false) { - var handler = KeyArrowUp; - if (handler != null) { - handler (this, EventArgs.Empty); - } + KeyArrowUp?.Invoke (this, shiftPressed); } - protected virtual void OnKeyArrowDown () + protected virtual void OnKeyArrowDown (bool shiftPressed = false) { - var handler = KeyArrowDown; - if (handler != null) { - handler (this, EventArgs.Empty); - } + KeyArrowDown?.Invoke (this, shiftPressed); } } @@ -138,17 +184,22 @@ namespace Xamarin.PropertyEditing.Mac public override bool TextShouldEndEditing (NSText textObject) { - if (!ValidateFinalString (TextField.StringValue)) { + bool shouldEndEditing = false; + if (!ValidateFinalString (textObject.Value)) { TextField.ResetInvalidInput (); AppKitFramework.NSBeep (); - return false; + TextField.ShouldEndEditing (textObject); + } else { + shouldEndEditing = TextField.ShouldEndEditing (textObject); } - return TextField.ShouldEndEditing (textObject); + + return shouldEndEditing; } public override void TextDidEndEditing (NSNotification notification) { - TextField.RaiseValidatedEditingEnded (); + TextField.NotifyValidatedEditingEnded (); + TextField.DidEndEditing (notification); } protected abstract bool ValidateFinalString (string value); @@ -165,8 +216,8 @@ namespace Xamarin.PropertyEditing.Mac protected override bool ValidateFinalString (string value) { return TextField.NumericMode == ValidationType.Decimal ? - FieldValidation.ValidateDecimal (value, TextField.AllowNegativeValues) : - FieldValidation.ValidateInteger (value, TextField.AllowNegativeValues); + NumericTextField.ValidateDecimal (value, TextField.AllowNegativeValues) : + NumericTextField.ValidateInteger (value, TextField.AllowNegativeValues); } } @@ -180,7 +231,9 @@ namespace Xamarin.PropertyEditing.Mac protected override bool ValidateFinalString (string value) { - return FieldValidation.ValidateRatio (value, ValidationType.Decimal, TextField.AllowNegativeValues); + if (NumericTextField.CheckIfRatio (value, ValidationType.Decimal, false) || NumericTextField.CheckIfNumber (value, ValidationType.Decimal, false)) + return true; + return false; } } } diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs index a300e18..77a2cb3 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs @@ -2,11 +2,12 @@ using AppKit; using CoreGraphics; using Xamarin.PropertyEditing.Mac.Resources; +using Xamarin.PropertyEditing.Themes; using Xamarin.PropertyEditing.ViewModels; namespace Xamarin.PropertyEditing.Mac { - public class PropertyButton : NSImageView + public class PropertyButton : UnfocusableButton { NSMenu popUpContextMenu; @@ -35,106 +36,101 @@ namespace Xamarin.PropertyEditing.Mac AccessibilityHelp = LocalizationResources.AccessibilityPropertiesButtonDescription; Enabled = true; Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-10"); - ImageScaling = NSImageScale.AxesIndependently; + ImageScaling = NSImageScale.None; ToolTip = Properties.Resources.Default; TranslatesAutoresizingMaskIntoConstraints = false; + + OnMouseEntered += (sender, e) => { + ToggleFocusImage (true); + }; + + OnMouseExited += (sender, e) => { + ToggleFocusImage (); + }; + + OnMouseLeftDown += (sender, e) => { + PopUpContextMenu (); + }; } - public override void MouseDown (NSEvent theEvent) + private void PopUpContextMenu () { - if (theEvent.Type == NSEventType.LeftMouseDown) { - if (this.popUpContextMenu == null) { - this.popUpContextMenu = new NSMenu (); - - if (this.viewModel.TargetPlatform.SupportsCustomExpressions) { - var mi = new NSMenuItem (Properties.Resources.CustomExpressionEllipsis) { - AttributedTitle = new Foundation.NSAttributedString ( - Properties.Resources.CustomExpressionEllipsis, - new CoreText.CTStringAttributes () { - Font = new CoreText.CTFont (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize + 1), - }) - }; - - mi.Activated += OnCustomExpression; - - this.popUpContextMenu.AddItem (mi); - this.popUpContextMenu.AddItem (NSMenuItem.SeparatorItem); - } - - // TODO If we add more menu items consider making the Label/Command a dictionary that we can iterate over to populate everything. - this.popUpContextMenu.AddItem (new CommandMenuItem (Properties.Resources.Reset, viewModel.ClearValueCommand) { + if (this.popUpContextMenu == null) { + this.popUpContextMenu = new NSMenu (); + + if (this.viewModel.TargetPlatform.SupportsCustomExpressions) { + var mi = new NSMenuItem (Properties.Resources.CustomExpressionEllipsis) { AttributedTitle = new Foundation.NSAttributedString ( - Properties.Resources.Reset, - new CoreText.CTStringAttributes () { - Font = new CoreText.CTFont (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize + 1), - }) - }); - } + Properties.Resources.CustomExpressionEllipsis, + new CoreText.CTStringAttributes () { + Font = new CoreText.CTFont (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize + 1), + }) + }; - var menuOrigin = this.Superview.ConvertPointToView (new CGPoint (Frame.Location.X - 1, Frame.Location.Y + Frame.Size.Height + 4), null); + mi.Activated += OnCustomExpression; - var popupMenuEvent = NSEvent.MouseEvent (NSEventType.LeftMouseDown, menuOrigin, (NSEventModifierMask)0x100, 0, this.Window.WindowNumber, this.Window.GraphicsContext, 0, 1, 1); + this.popUpContextMenu.AddItem (mi); + this.popUpContextMenu.AddItem (NSMenuItem.SeparatorItem); + } - NSMenu.PopUpContextMenu (popUpContextMenu, popupMenuEvent, this); + // TODO If we add more menu items consider making the Label/Command a dictionary that we can iterate over to populate everything. + this.popUpContextMenu.AddItem (new CommandMenuItem (Properties.Resources.Reset, viewModel.ClearValueCommand) { + AttributedTitle = new Foundation.NSAttributedString ( + Properties.Resources.Reset, + new CoreText.CTStringAttributes () { + Font = new CoreText.CTFont (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize + 1), + }) + }); } - } - public override void MouseEntered (NSEvent theEvent) - { - ChangeActiveImage (viewModel.ValueSource, true); - } + var menuOrigin = this.Superview.ConvertPointToView (new CGPoint (Frame.Location.X - 1, Frame.Location.Y + Frame.Size.Height + 4), null); - public override void MouseExited (NSEvent theEvent) - { - ChangeActiveImage (viewModel.ValueSource); + var popupMenuEvent = NSEvent.MouseEvent (NSEventType.LeftMouseDown, menuOrigin, (NSEventModifierMask)0x100, 0, this.Window.WindowNumber, this.Window.GraphicsContext, 0, 1, 1); + + NSMenu.PopUpContextMenu (popUpContextMenu, popupMenuEvent, this); } - public override void UpdateTrackingAreas () + protected override void UpdateTheme () { - base.UpdateTrackingAreas (); - - foreach (var item in TrackingAreas ()) { - RemoveTrackingArea (item); - } - - var options = NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveAlways; + base.UpdateTheme (); - var trackingArea = new NSTrackingArea (this.Bounds, options, this, null); - - AddTrackingArea (trackingArea); + ToggleFocusImage (); } - private void ChangeActiveImage (ValueSource valueSource, bool activeImage = false) + private void ToggleFocusImage (bool focused = false) { - switch (valueSource) { + if (viewModel != null) { + + switch (viewModel.ValueSource) { case ValueSource.Binding: - Image = activeImage ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-bound-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-bound-mac-10"); + Image = focused ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-bound-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-bound-mac-10"); break; case ValueSource.Default: - Image = activeImage ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-10"); - return; + Image = focused ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-10"); + break; case ValueSource.Local: - Image = activeImage ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-local-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-local-mac-10"); + Image = focused ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-local-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-local-mac-10"); break; case ValueSource.Inherited: - Image = activeImage ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-10"); + Image = focused ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-10"); break; case ValueSource.Resource: - Image = activeImage ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-10"); + Image = focused ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-inherited-mac-10"); break; case ValueSource.Unset: - Image = activeImage ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-10"); + Image = focused ? PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-active-10") : PropertyEditorPanel.ThemeManager.GetImageForTheme ("property-button-default-mac-10"); break; default: // To Handle ValueSource.DefaultStyle, ValueSource.Style etc. Image = null; break; + } } } @@ -171,7 +167,7 @@ namespace Xamarin.PropertyEditing.Mac break; } - ChangeActiveImage (valueSource, false); + UpdateTheme (); } private void OnPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/RatioEditor.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/RatioEditor.cs new file mode 100644 index 0000000..507eee3 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/RatioEditor.cs @@ -0,0 +1,80 @@ +using System; +using AppKit; +using Foundation; +using Xamarin.PropertyEditing.Drawing; +using Xamarin.PropertyEditing.Mac; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class RatioEditor<T> : NumericSpinEditor<T> + { + private bool fullSelection; + + public RatioEditor () + { + AllowNegativeValues = false; + AllowRatios = true; + BackgroundColor = NSColor.Clear; + StringValue = string.Empty; + TranslatesAutoresizingMaskIntoConstraints = false; + } + + protected override void OnEditingEnded (object sender, EventArgs e) + { + if (!editing) { + editing = true; + NotifyingValueChanged (new RatioEventArgs (0, 0, 0)); + editing = false; + } + } + + protected override void SetIncrementOrDecrementValue (double incDevValue) + { + nint caretLocation = 0; + nint selectionLength = 0; + + GetEditorCaretLocationAndLength (out caretLocation, out selectionLength); + + // Fire A Value change, so things are updated + NotifyingValueChanged (new RatioEventArgs ((int)caretLocation, (int)selectionLength, incDevValue)); + + // Resposition our caret so it doesn't jump around. + SetEditorCaretLocationAndLength (caretLocation, selectionLength); + } + + private void SetEditorCaretLocationAndLength (nint caretLocation, nint selectionLength) + { + if (NumericEditor.CurrentEditor != null) { + if (fullSelection && (selectionLength != NumericEditor.StringValue.Length)) { + selectionLength = NumericEditor.StringValue.Length; + } + NumericEditor.CurrentEditor.SelectedRange = new NSRange (caretLocation, selectionLength); + } + } + + private void GetEditorCaretLocationAndLength (out nint caretLocation, out nint selectionLength) + { + caretLocation = 0; + selectionLength = 0; + if (NumericEditor.CurrentEditor != null) { + caretLocation = NumericEditor.CurrentEditor.SelectedRange.Location; + selectionLength = NumericEditor.CurrentEditor.SelectedRange.Length; + fullSelection = NumericEditor.StringValue.Length == selectionLength; + } + } + + public class RatioEventArgs : EventArgs + { + public RatioEventArgs (int caretPosition, int selectionLength, double incrementValue) + { + CaretPosition = caretPosition; + SelectionLength = selectionLength; + IncrementValue = incrementValue; + } + + public int CaretPosition { get; } + public int SelectionLength { get; } + public double IncrementValue { get; } + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/SpinnerButton.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/SpinnerButton.cs new file mode 100644 index 0000000..0ba72dc --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/SpinnerButton.cs @@ -0,0 +1,49 @@ +using System; +using AppKit; +using ObjCRuntime; +using Xamarin.PropertyEditing.Themes; + +namespace Xamarin.PropertyEditing.Mac +{ + public class UpSpinnerButton : UnfocusableButton + { + public UpSpinnerButton () + { + OnMouseEntered += (sender, e) => { + Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("stepper-up-focus-blue"); + }; + + OnMouseExited += (sender, e) => { + Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("stepper-up"); + }; + } + + protected override void UpdateTheme () + { + base.UpdateTheme (); + + Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("stepper-up"); + } + } + + public class DownSpinnerButton : UnfocusableButton + { + public DownSpinnerButton () + { + OnMouseEntered += (sender, e) => { + Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("stepper-down-focus-blue"); + }; + + OnMouseExited += (sender, e) => { + Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("stepper-down"); + }; + } + + protected override void UpdateTheme () + { + base.UpdateTheme (); + + Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("stepper-down"); + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/UnfocusableButton.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/UnfocusableButton.cs new file mode 100644 index 0000000..3a18805 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/UnfocusableButton.cs @@ -0,0 +1,104 @@ +using System; +using AppKit; + +namespace Xamarin.PropertyEditing.Mac +{ + public abstract class UnfocusableButton : NSImageView + { + public event EventHandler OnMouseEntered; + public event EventHandler OnMouseExited; + public event EventHandler OnMouseLeftDown; + public event EventHandler OnMouseRightDown; + + public UnfocusableButton () + { + Enabled = true; + ImageScaling = NSImageScale.AxesIndependently; + TranslatesAutoresizingMaskIntoConstraints = false; + + PropertyEditorPanel.ThemeManager.ThemeChanged += ThemeManager_ThemeChanged; + + UpdateTheme (); + } + + #region Overridden Methods + protected override void Dispose (bool disposing) + { + if (disposing) { + PropertyEditorPanel.ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged; + } + } + + public override void MouseDown (NSEvent theEvent) + { + switch (theEvent.Type) { + case NSEventType.LeftMouseDown: + NotifyMouseLeftDown (); + break; + + case NSEventType.RightMouseDown: + NotifyMouseRightDown (); + break; + } + } + + public override void MouseEntered (NSEvent theEvent) + { + NotifyMouseEntered (); + } + + public override void MouseExited (NSEvent theEvent) + { + NotifyMouseExited (); + } + + public override void UpdateTrackingAreas () + { + base.UpdateTrackingAreas (); + + foreach (var item in TrackingAreas ()) { + RemoveTrackingArea (item); + } + + var options = NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveAlways; + + var trackingArea = new NSTrackingArea (this.Bounds, options, this, null); + + AddTrackingArea (trackingArea); + } + #endregion + + + #region Local Methods + private void NotifyMouseEntered () + { + OnMouseEntered?.Invoke (this, EventArgs.Empty); + } + + private void NotifyMouseExited () + { + OnMouseExited?.Invoke (this, EventArgs.Empty); + } + + private void NotifyMouseLeftDown () + { + OnMouseLeftDown?.Invoke (this, EventArgs.Empty); + } + + private void NotifyMouseRightDown () + { + OnMouseRightDown?.Invoke (this, EventArgs.Empty); + } + + void ThemeManager_ThemeChanged (object sender, EventArgs e) + { + UpdateTheme (); + } + + protected virtual void UpdateTheme () + { + this.Appearance = PropertyEditorPanel.ThemeManager.CurrentAppearance; + } + #endregion + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/NumericEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/NumericEditorControl.cs index 916957a..b0ab2b9 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/NumericEditorControl.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/NumericEditorControl.cs @@ -57,7 +57,7 @@ namespace Xamarin.PropertyEditing.Mac } public override NSView FirstKeyView => NumericEditor; - public override NSView LastKeyView => NumericEditor; + public override NSView LastKeyView => NumericEditor.DecrementButton; protected NSNumberFormatterStyle NumberStyle { get { diff --git a/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs index 04c7c48..d4cc0d6 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs @@ -54,7 +54,7 @@ namespace Xamarin.PropertyEditing.Mac } } - public void UpdateKeyViews (bool backward = true, bool forward = true) + public void UpdateKeyViews () { if (TableRow < 0) return; @@ -62,15 +62,17 @@ namespace Xamarin.PropertyEditing.Mac PropertyEditorControl ctrl = null; //FIXME: don't hardcode column - if (backward && TableRow > 0 && (ctrl = TableView.GetView (1, TableRow - 1, false) as PropertyEditorControl) != null) { - ctrl.LastKeyView.NextKeyView = FirstKeyView; - ctrl.UpdateKeyViews (forward: false); - } - - //FIXME: don't hardcode column - if (forward && TableRow < TableView.RowCount - 1 && (ctrl = TableView.GetView (1, TableRow + 1, false) as PropertyEditorControl) != null) { - LastKeyView.NextKeyView = ctrl.FirstKeyView; - ctrl.UpdateKeyViews (backward: false); + var tr = TableRow; + if (tr > 0) { + do { + tr--; + ctrl = TableView.GetView (1, tr, false) as PropertyEditorControl; + } while (tr > 0 && ctrl == null); + + if (ctrl != null) { + ctrl.LastKeyView.NextKeyView = FirstKeyView; + ctrl.UpdateKeyViews (); + } } } diff --git a/Xamarin.PropertyEditing.Mac/Controls/RatioEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/RatioEditorControl.cs new file mode 100644 index 0000000..a859652 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/RatioEditorControl.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using System.ComponentModel; +using AppKit; +using Xamarin.PropertyEditing.Mac.Resources; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class RatioEditorControl<T> : PropertyEditorControl<RatioViewModel> + { + RatioEditor<T> ratioEditor; + + public RatioEditorControl () + { + base.TranslatesAutoresizingMaskIntoConstraints = false; + + ratioEditor = new RatioEditor<T> { + AllowNegativeValues = false, + AllowRatios = true, + BackgroundColor = NSColor.Clear, + StringValue = string.Empty, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + // update the value on keypress + ratioEditor.ValueChanged += (sender, e) => { + if (e is RatioEditor<T>.RatioEventArgs ratioEventArgs) { + ViewModel.ValueChanged (ratioEditor.StringValue, ratioEventArgs.CaretPosition, ratioEventArgs.SelectionLength, ratioEventArgs.IncrementValue); + } + }; + AddSubview (ratioEditor); + + this.DoConstraints (new[] { + ratioEditor.ConstraintTo (this, (re, c) => re.Top == c.Top - 2), + ratioEditor.ConstraintTo (this, (re, c) => re.Left == c.Left + 4), + ratioEditor.ConstraintTo (this, (re, c) => re.Width == c.Width - 33), + ratioEditor.ConstraintTo (this, (re, c) => re.Height == DefaultControlHeight), + }); + + UpdateTheme (); + } + + public override NSView FirstKeyView => ratioEditor.NumericEditor; + public override NSView LastKeyView => ratioEditor.DecrementButton; + + protected override void HandleErrorsChanged (object sender, DataErrorsChangedEventArgs e) + { + UpdateErrorsDisplayed (ViewModel.GetErrors (ViewModel.Property.Name)); + } + + protected override void SetEnabled () + { + ratioEditor.Editable = ViewModel.Property.CanWrite; + } + + protected override void UpdateAccessibilityValues () + { + ratioEditor.AccessibilityEnabled = ratioEditor.Enabled; + ratioEditor.AccessibilityTitle = string.Format (LocalizationResources.AccessibilityNumeric, ViewModel.Property.Name); + } + + protected override void UpdateErrorsDisplayed (IEnumerable errors) + { + if (ViewModel.HasErrors) { + SetErrors (errors); + } else { + SetErrors (null); + SetEnabled (); + } + } + + protected override void UpdateValue () + { + ratioEditor.StringValue = ViewModel.ValueString; + } + } +} |