diff options
Diffstat (limited to 'Xamarin.PropertyEditing.Mac')
16 files changed, 1667 insertions, 39 deletions
diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BaseSelectorOutlineView.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BaseSelectorOutlineView.cs new file mode 100644 index 0000000..e18c197 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BaseSelectorOutlineView.cs @@ -0,0 +1,38 @@ +using System; +using AppKit; +using Foundation; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class BaseSelectorOutlineView : NSOutlineView + { + public BaseSelectorOutlineView () + { + Initialize (); + } + + // Called when created from unmanaged code + public BaseSelectorOutlineView (IntPtr handle) : base (handle) + { + Initialize (); + } + + // Called when created directly from a XIB file + [Export ("initWithCoder:")] + public BaseSelectorOutlineView (NSCoder coder) : base (coder) + { + Initialize (); + } + + public override bool ValidateProposedFirstResponder (NSResponder responder, NSEvent forEvent) + { + return true; + } + + private void Initialize () + { + HeaderView = null; + TranslatesAutoresizingMaskIntoConstraints = false; + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingEditorWindow.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingEditorWindow.cs new file mode 100644 index 0000000..f41673f --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingEditorWindow.cs @@ -0,0 +1,569 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AppKit; +using CoreGraphics; +using Foundation; +using Xamarin.PropertyEditing.Common; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class BindingEditorWindow : NSPanel + { + private NSButton buttonDone; + private NSButton buttonCancel; + + private NSPopUpButton bindingTypePopup; + + private NSPopUpButton valueConverterPopup; + + private NSButton addConverterButton; + + private NSView ancestorTypeBox; + + private NSView mainContainer; + private NSView bindingPropertiesView; + private NSView flagsPropertiesView; + private NSButton buttonMoreSettings; + + private nfloat MoreSettingsViewHeight { get; set; } + + private const float AddConverterButtonSize = 20; + + private new ModalWindowCloseDelegate Delegate + { + get => (ModalWindowCloseDelegate)base.Delegate; + set => base.Delegate = value; + } + + private readonly PropertyEditorSelector editorSelector = new PropertyEditorSelector (); + + private const string BindingPropertiesIdentifier = "BindingProperties"; + private const string FlagPropertiesIdentifier = "FlagProperties"; + internal const float HeaderHeight = 28f; + + internal CreateBindingViewModel ViewModel { get; } + + private HeaderView typeHeader; + + private UnfocusableTextField longDescription; + private UnfocusableTextField labelOtherSettings; + private TypeSelectorControl typeSelectorControl; + + private nfloat heightConstant, titleBarHeight; + + private NSLayoutConstraint ancestorTypeBoxHeightConstraint; + private NSLayoutConstraint bindingPropertiesViewHeightConstraint; + + private BindingPathSelectorControl pathSelectorControl; + + internal BindingEditorWindow (IHostResourceProvider hostResources, PropertyViewModel propertyViewModel) + : base (new CGRect (0, 0, 728, 445), NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Resizable, NSBackingStore.Buffered, true) + { + if (hostResources == null) + throw new ArgumentNullException (nameof (hostResources)); + if (propertyViewModel == null) + throw new ArgumentNullException (nameof (propertyViewModel)); + + ViewModel = new CreateBindingViewModel (propertyViewModel.TargetPlatform, propertyViewModel.Editors.Single (), propertyViewModel.Property, includeAddValueConverter: false); + + Delegate = new ModalWindowCloseDelegate (); + + FloatingPanel = true; + + MaxSize = new CGSize (960, 720); // TODO discuss what the Max/Min Size should be and if we should have one. + MinSize = new CGSize (320, 240); + + this.mainContainer = new NSView { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + this.buttonDone = new NSButton { + BezelStyle = NSBezelStyle.Rounded, + ControlSize = NSControlSize.Regular, + Highlighted = true, + KeyEquivalent = "\r", // Fire when enter pressed + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.mainContainer.AddSubview (this.buttonDone); + + this.buttonCancel = new NSButton { + BezelStyle = NSBezelStyle.Rounded, + ControlSize = NSControlSize.Regular, + Title = Properties.Resources.Cancel, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.buttonCancel.Activated += (sender, e) => { + Delegate.Response = NSModalResponse.Cancel; + Close (); + }; + + this.mainContainer.AddSubview (this.buttonCancel); + + var bindingTypeLabel = new UnfocusableTextField { + Alignment = NSTextAlignment.Right, + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, 13), + StringValue = Properties.Resources.BindingType + ":", + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.mainContainer.AddSubview (bindingTypeLabel); + + this.bindingTypePopup = new FocusablePopUpButton { + ControlSize = NSControlSize.Regular, + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, 13), + StringValue = String.Empty, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + var bindingTypeMenuList = new NSMenu (); + this.bindingTypePopup.Menu = bindingTypeMenuList; + this.mainContainer.AddSubview (this.bindingTypePopup); + + var valueConverterLabel = new UnfocusableTextField { + Alignment = NSTextAlignment.Right, + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, 13), + StringValue = Properties.Resources.Converter + ":", + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.ancestorTypeBox = new NSView { + TranslatesAutoresizingMaskIntoConstraints = false, + WantsLayer = true, + + // Layer out of alphabetical order so that WantsLayer creates the layer first + Layer = { + CornerRadius = 1.0f, + BorderColor = new CGColor (.5f, .5f, .5f, 1.0f), + BorderWidth = 1, + }, + }; + + this.mainContainer.AddSubview (this.ancestorTypeBox); + + this.mainContainer.AddSubview (valueConverterLabel); + + this.valueConverterPopup = new FocusablePopUpButton { + TranslatesAutoresizingMaskIntoConstraints = false, + StringValue = String.Empty, + ControlSize = NSControlSize.Regular, + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, 13), + }; + + var valueConverterMenuList = new NSMenu (); + this.valueConverterPopup.Menu = valueConverterMenuList; + this.mainContainer.AddSubview (this.valueConverterPopup); + + this.addConverterButton = new CommandButton { + BezelStyle = NSBezelStyle.Rounded, + Command = ViewModel.RequestAddValueConverterCommand, + Image = NSImage.ImageNamed (NSImageName.AddTemplate), + Title = string.Empty, + ToolTip = Properties.Resources.AddValueConverterEllipsis, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.mainContainer.AddSubview (this.addConverterButton); + + this.buttonMoreSettings = new NSButton { + BezelStyle = NSBezelStyle.Disclosure, + Title = string.Empty, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + this.buttonMoreSettings.SetButtonType (NSButtonType.PushOnPushOff); + + this.mainContainer.AddSubview (this.buttonMoreSettings); + + this.labelOtherSettings = new UnfocusableTextField { + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, 13), + StringValue = Properties.Resources.OtherSettings, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.mainContainer.AddSubview (this.labelOtherSettings); + + this.bindingPropertiesView = new NSView { + Hidden = true, + TranslatesAutoresizingMaskIntoConstraints = false, + WantsLayer = true, + + // Layer out of alphabetical order so that WantsLayer creates the layer first + Layer = { + CornerRadius = 1.0f, + BorderColor = new CGColor (.5f, .5f, .5f, .5f), + BorderWidth = 1, + }, + }; + + this.mainContainer.AddSubview (this.bindingPropertiesView); + + this.flagsPropertiesView = new NSView { + Hidden = true, + TranslatesAutoresizingMaskIntoConstraints = false, + WantsLayer = true, + + // Layer out of alphabetical order so that WantsLayer creates the layer first + Layer = { + CornerRadius = 1.0f, + BorderColor = new CGColor (.5f, .5f, .5f, .5f), + BorderWidth = 1, + }, + }; + + this.mainContainer.AddSubview (this.flagsPropertiesView); + + //Work out the titlebar height + this.titleBarHeight = Frame.Size.Height - ContentRectFor (Frame).Size.Height; + + this.ancestorTypeBoxHeightConstraint = NSLayoutConstraint.Create (this.ancestorTypeBox, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this.mainContainer, NSLayoutAttribute.Bottom, 1f, -145f); + this.heightConstant = this.ancestorTypeBoxHeightConstraint.Constant; + + this.bindingPropertiesViewHeightConstraint = NSLayoutConstraint.Create (this.bindingPropertiesView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 124); + + this.buttonMoreSettings.Activated += OnButtonMoreSettingsToggled; + + this.pathSelectorControl = new BindingPathSelectorControl (ViewModel); + this.mainContainer.AddSubview (this.pathSelectorControl); + + this.mainContainer.AddConstraints (new[] { + NSLayoutConstraint.Create (bindingTypeLabel, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.bindingTypePopup, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (bindingTypeLabel, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.mainContainer, NSLayoutAttribute.Left, 1f, 21f), + NSLayoutConstraint.Create (bindingTypeLabel, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 20), + + NSLayoutConstraint.Create (this.bindingTypePopup, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.mainContainer, NSLayoutAttribute.Top, 1f, 18f), + NSLayoutConstraint.Create (this.bindingTypePopup, NSLayoutAttribute.Left, NSLayoutRelation.Equal, bindingTypeLabel, NSLayoutAttribute.Right, 1f, 10f), + + NSLayoutConstraint.Create (this.ancestorTypeBox, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.bindingTypePopup, NSLayoutAttribute.Bottom, 1f, 18f), + NSLayoutConstraint.Create (this.ancestorTypeBox, NSLayoutAttribute.Left, NSLayoutRelation.Equal, bindingTypeLabel, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.ancestorTypeBox, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.pathSelectorControl, NSLayoutAttribute.Left, 1f, -8f), + this.ancestorTypeBoxHeightConstraint, + + NSLayoutConstraint.Create (this.pathSelectorControl, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.pathSelectorControl, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.mainContainer, NSLayoutAttribute.Right, 1f, -21f), + NSLayoutConstraint.Create (this.pathSelectorControl, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, -30f), + NSLayoutConstraint.Create (this.pathSelectorControl, NSLayoutAttribute.Height, NSLayoutRelation.Equal,this.ancestorTypeBox, NSLayoutAttribute.Height, 1f, 0f), + + NSLayoutConstraint.Create (valueConverterLabel, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Bottom, 1f, 22f), + NSLayoutConstraint.Create (valueConverterLabel, NSLayoutAttribute.Left, NSLayoutRelation.Equal, bindingTypeLabel, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (valueConverterLabel, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 20), + + NSLayoutConstraint.Create (this.valueConverterPopup, NSLayoutAttribute.Top, NSLayoutRelation.Equal, valueConverterLabel, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.valueConverterPopup, NSLayoutAttribute.Left, NSLayoutRelation.Equal, valueConverterLabel, NSLayoutAttribute.Right, 1f, 10f), + NSLayoutConstraint.Create (this.valueConverterPopup, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, -AddConverterButtonSize - 83), // TODO Need a better calculation here + + NSLayoutConstraint.Create (this.addConverterButton, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.valueConverterPopup, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.addConverterButton, NSLayoutAttribute.Right, NSLayoutRelation.Equal,this.ancestorTypeBox, NSLayoutAttribute.Right, 1f, 0f), + NSLayoutConstraint.Create (this.addConverterButton, NSLayoutAttribute.Width, NSLayoutRelation.Equal, 1f, AddConverterButtonSize), + NSLayoutConstraint.Create (this.addConverterButton, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, AddConverterButtonSize), + + NSLayoutConstraint.Create (this.buttonMoreSettings, NSLayoutAttribute.Top, NSLayoutRelation.Equal, valueConverterLabel, NSLayoutAttribute.Bottom, 1f, 18f), + NSLayoutConstraint.Create (this.buttonMoreSettings, NSLayoutAttribute.Left, NSLayoutRelation.Equal, bindingTypeLabel, NSLayoutAttribute.Left, 1f, -5f), + NSLayoutConstraint.Create (this.buttonMoreSettings, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 20), + NSLayoutConstraint.Create (this.buttonMoreSettings, NSLayoutAttribute.Width, NSLayoutRelation.Equal, 1f, 20), + + NSLayoutConstraint.Create (this.labelOtherSettings, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.buttonMoreSettings, NSLayoutAttribute.Top, 1f, -1f), + NSLayoutConstraint.Create (this.labelOtherSettings, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.buttonMoreSettings, NSLayoutAttribute.Right, 1f, 0f), + NSLayoutConstraint.Create (this.labelOtherSettings, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 20), + + NSLayoutConstraint.Create (this.bindingPropertiesView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.buttonMoreSettings, NSLayoutAttribute.Bottom, 1f, 10f), + NSLayoutConstraint.Create (this.bindingPropertiesView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, bindingTypeLabel, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.bindingPropertiesView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.flagsPropertiesView, NSLayoutAttribute.Left, 1f, -8f), + bindingPropertiesViewHeightConstraint, + NSLayoutConstraint.Create (this.bindingPropertiesView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, 0f), + + NSLayoutConstraint.Create (this.flagsPropertiesView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.bindingPropertiesView, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.flagsPropertiesView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.pathSelectorControl, NSLayoutAttribute.Right, 1f, 0f), + NSLayoutConstraint.Create (this.flagsPropertiesView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.pathSelectorControl, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (this.flagsPropertiesView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this.bindingPropertiesView, NSLayoutAttribute.Height, 1f, 0f), + + NSLayoutConstraint.Create (this.buttonDone, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.mainContainer, NSLayoutAttribute.Bottom, 1f, -44f), + NSLayoutConstraint.Create (this.buttonDone, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.pathSelectorControl, NSLayoutAttribute.Right, 1f, 0f), + NSLayoutConstraint.Create (this.buttonDone, NSLayoutAttribute.Width, NSLayoutRelation.GreaterThanOrEqual, 1f, 80f), + + NSLayoutConstraint.Create (this.buttonCancel, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.buttonDone, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.buttonCancel, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.buttonDone, NSLayoutAttribute.Left, 1f, -10f), + NSLayoutConstraint.Create (this.buttonCancel, NSLayoutAttribute.Width, NSLayoutRelation.GreaterThanOrEqual, 1f, 80f), + }); + + // put the MainContainer inside this panel's ContentView + ContentView.AddSubview (this.mainContainer); + + ContentView.AddConstraints (new[] { + NSLayoutConstraint.Create (this.mainContainer, NSLayoutAttribute.Top, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.mainContainer, NSLayoutAttribute.Left, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.mainContainer, NSLayoutAttribute.Height, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Height, 1f, 0f), + NSLayoutConstraint.Create (this.mainContainer, NSLayoutAttribute.Width, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Width, 1f, 0f), + + }); + + Title = ViewModel.PropertyDisplay; + this.buttonDone.Title = Properties.Resources.CreateBindingTitle; + this.buttonDone.Enabled = ViewModel.CanCreateBinding; + + ViewModel.BindingSources.Task.ContinueWith (t => { + foreach (BindingSource item in ViewModel.BindingSources.Value) { + this.bindingTypePopup.Menu.AddItem (new NSMenuItem (item.Name) { + RepresentedObject = new NSObjectFacade (item) + }); + } + }, TaskScheduler.FromCurrentSynchronizationContext ()); + + this.bindingTypePopup.Activated += (o, e) => { + if (this.bindingTypePopup.Menu.HighlightedItem.RepresentedObject is NSObjectFacade facade) { + ViewModel.SelectedBindingSource = (BindingSource)facade.Target; + } + }; + + this.valueConverterPopup.Activated += (o, e) => { + if (this.valueConverterPopup.Menu.HighlightedItem.RepresentedObject is NSObjectFacade facade) { + ViewModel.SelectedValueConverter = (Resource)facade.Target; + } + }; + + RepopulateValueConverterPopup (); + + this.typeHeader = new HeaderView { + Title = Properties.Resources.Type, + }; + + this.ancestorTypeBox.AddSubview (this.typeHeader); + + this.ancestorTypeBox.AddConstraints (new[] { + NSLayoutConstraint.Create (this.typeHeader, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.typeHeader, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.typeHeader, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (this.typeHeader, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, HeaderHeight), + }); + + this.typeSelectorControl = new TypeSelectorControl { + Flush = true, + Hidden = true, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.ancestorTypeBox.AddSubview (this.typeSelectorControl); + + this.ancestorTypeBox.AddConstraints (new[] { + NSLayoutConstraint.Create (this.typeSelectorControl, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.typeHeader, NSLayoutAttribute.Bottom, 1f, 0f), + NSLayoutConstraint.Create (this.typeSelectorControl, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.typeSelectorControl, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (this.typeSelectorControl, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Height, 1f, -10f) + }); + + var resourceSelectorControl = new BindingResourceSelectorControl (ViewModel) { + Hidden = true, + }; + + this.ancestorTypeBox.AddSubview (resourceSelectorControl); + + this.ancestorTypeBox.AddConstraints (new[] { + NSLayoutConstraint.Create (resourceSelectorControl, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (resourceSelectorControl, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (resourceSelectorControl, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (resourceSelectorControl, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Height, 1f, 0f) + }); + + var objectSelectorControl = new BindingObjectSelectorControl (ViewModel) { + Hidden = true, + }; + + this.ancestorTypeBox.AddSubview (objectSelectorControl); + + this.ancestorTypeBox.AddConstraints (new[] { + NSLayoutConstraint.Create (objectSelectorControl, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (objectSelectorControl, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (objectSelectorControl, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (objectSelectorControl, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Height, 1f, 0f) + }); + + this.longDescription = new UnfocusableTextField { + Alignment = NSTextAlignment.Left, + TranslatesAutoresizingMaskIntoConstraints = false, + StringValue = string.Empty, + }; + + this.ancestorTypeBox.AddSubview (this.longDescription); + + this.ancestorTypeBox.AddConstraints (new[] { + NSLayoutConstraint.Create (this.longDescription, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Top, 1f, 10f), + NSLayoutConstraint.Create (this.longDescription, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Left, 1f, 10f), + NSLayoutConstraint.Create (this.longDescription, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.ancestorTypeBox, NSLayoutAttribute.Width, 1f, -10f), + NSLayoutConstraint.Create (this.longDescription, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 24), + }); + + ViewModel.PropertyChanged += OnPropertyChanged; + + ViewModel.CreateValueConverterRequested += OnCreateValueConverterRequested; + + this.buttonDone.Activated += OnButtonDoneActivated; + + CreateMorePropertiesEditors (hostResources); + } + + private void OnButtonDoneActivated (object sender, EventArgs e) + { + if (!string.IsNullOrEmpty (this.pathSelectorControl.CustomPath)) { + ViewModel.Path = this.pathSelectorControl.CustomPath; + } + Delegate.Response = NSModalResponse.OK; + Close (); + } + + + private void OnButtonMoreSettingsToggled (object sender, EventArgs e) + { + if (sender is NSButton moreButton) { + this.bindingPropertiesView.Hidden = moreButton.State == NSCellStateValue.Off; + this.flagsPropertiesView.Hidden = this.bindingPropertiesView.Hidden; + + this.bindingPropertiesViewHeightConstraint.Constant = this.MoreSettingsViewHeight; + this.ancestorTypeBoxHeightConstraint.Constant = this.bindingPropertiesView.Hidden ? this.heightConstant : this.heightConstant - (MoreSettingsViewHeight + 20); + this.mainContainer.SetFrameSize (new CGSize (this.mainContainer.Frame.Width, this.bindingPropertiesView.Hidden ? this.mainContainer.Frame.Height - MoreSettingsViewHeight : this.mainContainer.Frame.Height + MoreSettingsViewHeight)); + SetFrame (new CGRect (new CGPoint (Frame.X, Frame.Y), new CGSize (Frame.Width, this.mainContainer.Frame.Height + this.titleBarHeight)), false, true); + } + } + + + private void OnPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof (CreateBindingViewModel.ShowLongDescription)) { + this.longDescription.Hidden = !ViewModel.ShowLongDescription; + this.longDescription.StringValue = ViewModel.ShowLongDescription ? ViewModel.SelectedBindingSource.Description : string.Empty; + this.typeHeader.Hidden = ViewModel.ShowLongDescription; + } + + if (e.PropertyName == nameof (CreateBindingViewModel.ShowObjectSelector)) { + if (ViewModel.ShowObjectSelector) { + this.typeHeader.Title = Properties.Resources.SelectObjectTitle; + } + } + + if (e.PropertyName == nameof (CreateBindingViewModel.ShowTypeSelector)) { + this.typeSelectorControl.Hidden = !ViewModel.ShowTypeSelector; + + if (ViewModel.ShowTypeSelector) { + this.typeHeader.Title = Properties.Resources.SelectTypeTitle; + + if (ViewModel.ShowTypeSelector && ViewModel.TypeSelector != null) { + this.typeSelectorControl.ViewModel = ViewModel.TypeSelector; + } + } + } + + if (e.PropertyName == nameof (CreateBindingViewModel.ShowResourceSelector)) { + if (ViewModel.ShowResourceSelector) { + this.typeHeader.Title = Properties.Resources.SelectResourceTitle; + } + } + + if (e.PropertyName == nameof (CreateBindingViewModel.SelectedValueConverter)) { + RepopulateValueConverterPopup (); + } + + if (e.PropertyName == nameof (CreateBindingViewModel.PropertyDisplay)) { + Title = ViewModel.PropertyDisplay; + } + + if (e.PropertyName == nameof (CreateBindingViewModel.CanCreateBinding)) { + this.buttonDone.Enabled = ViewModel.CanCreateBinding; + } + } + + + private void CreateMorePropertiesEditors (IHostResourceProvider hostResources) + { + var controlTop = 8; + int editorHeight; + + foreach (PropertyViewModel vm in ViewModel.BindingProperties) { + IEditorView editor = this.editorSelector.GetEditor (hostResources, vm); + + NSView nSView = new EditorContainer (hostResources, editor, false) { + Identifier = BindingPropertiesIdentifier, + Label = vm.Property.Name, + TranslatesAutoresizingMaskIntoConstraints = false, + ViewModel = vm, + }; + + this.bindingPropertiesView.AddSubview (nSView); + + editorHeight = (int)editor.GetHeight (vm); + this.bindingPropertiesView.AddConstraints (new[] { + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.bindingPropertiesView, NSLayoutAttribute.Top, 1f, controlTop), + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.bindingPropertiesView, NSLayoutAttribute.Right, 1f, -9f), + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.bindingPropertiesView, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, editorHeight), + }); + + controlTop += editorHeight; + } + + var boundsHeight = controlTop; + + controlTop = 8; + foreach (PropertyViewModel vm in ViewModel.FlagsProperties) { + + IEditorView editor = this.editorSelector.GetEditor (hostResources, vm); + + NSView nSView = new EditorContainer (hostResources, editor, false) { + Identifier = FlagPropertiesIdentifier, + Label = vm.Property.Name, + TranslatesAutoresizingMaskIntoConstraints = false, + ViewModel = vm, + }; + + this.flagsPropertiesView.AddSubview (nSView); + + editorHeight = (int)editor.GetHeight (vm); + this.flagsPropertiesView.AddConstraints (new[] { + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.flagsPropertiesView, NSLayoutAttribute.Top, 1f, controlTop), + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.flagsPropertiesView, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.flagsPropertiesView, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (nSView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, editorHeight) + }); + + controlTop += editorHeight; + } + + if (boundsHeight < controlTop) + boundsHeight = controlTop; + + MoreSettingsViewHeight = boundsHeight + 8; + } + + private void RepopulateValueConverterPopup () + { + this.valueConverterPopup.RemoveAllItems (); + foreach (Resource item in ViewModel.ValueConverters.Value) { + this.valueConverterPopup.Menu.AddItem (new NSMenuItem (item.Name) { + RepresentedObject = new NSObjectFacade (item) + }); + } + } + + private void OnCreateValueConverterRequested (object sender, CreateValueConverterEventArgs e) + { + ITypeInfo valueConverter = ViewModel.TargetPlatform.EditorProvider.KnownTypes[typeof (CommonValueConverter)]; + + var typesTask = ViewModel.TargetPlatform.EditorProvider.GetAssignableTypesAsync (valueConverter, childTypes: false) + .ContinueWith (t => t.Result.GetTypeTree (), TaskScheduler.Default); + + var createValueConverterWindow = new CreateValueConverterWindow (ViewModel, new AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> (typesTask)) { + Appearance = EffectiveAppearance, + }; + + var result = (NSModalResponse)(int)NSApplication.SharedApplication.RunModalForWindow (createValueConverterWindow); + if (result == NSModalResponse.OK) { + if (createValueConverterWindow.ViewModel.SelectedType != null) { + e.Name = createValueConverterWindow.ValueConverterName; + e.ConverterType = createValueConverterWindow.ViewModel.SelectedType; + e.Source = createValueConverterWindow.ViewModel.Source; + } + } + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingObjectSelectorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingObjectSelectorControl.cs new file mode 100644 index 0000000..8e82477 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingObjectSelectorControl.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using AppKit; +using Foundation; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class BindingObjectSelectorControl + : NotifyingView<CreateBindingViewModel> + { + private ObjectOutlineView objectOutlineView; + + private const string ObjectSelectorColId = "ObjectSelectorColumn"; + + internal BindingObjectSelectorControl (CreateBindingViewModel viewModel) + { + if (viewModel == null) + throw new ArgumentNullException (nameof (viewModel)); + + ViewModel = viewModel; + + this.objectOutlineView = new ObjectOutlineView (); + TranslatesAutoresizingMaskIntoConstraints = false; + + this.objectOutlineView.Activated += OnObjectOutlineViewSelected; + + var resourceColumn = new NSTableColumn (ObjectSelectorColId); + this.objectOutlineView.AddColumn (resourceColumn); + + // Set OutlineTableColumn or the arrows showing children/expansion will not be drawn + this.objectOutlineView.OutlineTableColumn = resourceColumn; + + // create a table view and a scroll view + var outlineViewContainer = new NSScrollView { + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + // add the panel to the window + outlineViewContainer.DocumentView = this.objectOutlineView; + AddSubview (outlineViewContainer); + + AddConstraints (new[] { + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this, NSLayoutAttribute.Top, 1f, 28f), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterX, 1, 0), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal,this, NSLayoutAttribute.Bottom, 1f, 0f), + }); + + viewModel.PropertyChanged += OnPropertyChanged; + } + + public override void OnPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof (CreateBindingViewModel.ShowObjectSelector)) { + Hidden = !ViewModel.ShowObjectSelector; + + if (ViewModel.ShowObjectSelector && ViewModel.ObjectElementRoots != null) { + this.objectOutlineView.ItemsSource = ViewModel.ObjectElementRoots.Value; + }; + } + } + + private void OnObjectOutlineViewSelected (object sender, EventArgs e) + { + if (sender is ObjectOutlineView rov) { + if (rov.SelectedRow != -1) { + if (rov.ItemAtRow (rov.SelectedRow) is NSObjectFacade item) { + if (item.Target is Resource resource) { + ViewModel.SelectedResource = resource; + } + } + } + } + } + + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingPathSelectorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingPathSelectorControl.cs new file mode 100644 index 0000000..2d84be3 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingPathSelectorControl.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using AppKit; +using CoreGraphics; +using Foundation; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class BindingPathSelectorControl + : NotifyingView<CreateBindingViewModel> + { + private readonly PathOutlineView pathOutlineView; + internal const string PathSelectorColumnColId = "PathSelectorColumn"; + + public string CustomPath { + get { + return this.customPathControl.StringValue; + } + } + private NSTextField customPathControl { get; } + + private const float HorizontalCustomOffSet = 30.5f; + + private HeaderView pathHeader; + private NSView pathBox; + + public BindingPathSelectorControl (CreateBindingViewModel viewModel) + { + if (viewModel == null) + throw new ArgumentNullException (nameof (viewModel)); + + ViewModel = viewModel; + + TranslatesAutoresizingMaskIntoConstraints = false; + + this.pathBox = new NSView { + TranslatesAutoresizingMaskIntoConstraints = false, + WantsLayer = true, + + // Layer out of alphabetical order so that WantsLayer creates the layer first + Layer = { + CornerRadius = 1.0f, + BorderColor = new CGColor (.5f, .5f, .5f, 1.0f), + BorderWidth = 1, + }, + }; + + AddSubview (this.pathBox); + + this.pathHeader = new HeaderView { + Title = Properties.Resources.Path, + }; + this.pathHeader.HorizonalTitleOffset = -HorizontalCustomOffSet; + + this.pathBox.AddSubview (this.pathHeader); + + this.pathBox.AddConstraints (new[] { + NSLayoutConstraint.Create (this.pathHeader, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.pathHeader, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.pathHeader, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (this.pathHeader, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, BindingEditorWindow.HeaderHeight), + }); + + var customCheckBox = new NSButton { + ControlSize = NSControlSize.Small, + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize), + Title = Properties.Resources.Custom, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + customCheckBox.SetButtonType (NSButtonType.Switch); + + this.pathHeader.AddSubview (customCheckBox); + this.pathHeader.AddConstraints (new[] { + NSLayoutConstraint.Create (customCheckBox, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this.pathHeader, NSLayoutAttribute.CenterX, 1, HorizontalCustomOffSet), + NSLayoutConstraint.Create (customCheckBox, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal, this.pathHeader, NSLayoutAttribute.CenterY, 1, 0f), + }); + + this.customPathControl = new NSTextField { + ControlSize = NSControlSize.Regular, + Enabled = false, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + var customPathHeightConstraint = NSLayoutConstraint.Create (this.customPathControl, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 0); + + this.pathBox.AddSubview (this.customPathControl); + this.pathBox.AddConstraints (new[] { + NSLayoutConstraint.Create (this.customPathControl, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.Top, 1f, 28f), + NSLayoutConstraint.Create (this.customPathControl, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (this.customPathControl, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.CenterX, 1, 0), + customPathHeightConstraint, + }); + + // create a table view and a scroll view + var outlineViewContainer = new NSScrollView { + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + customCheckBox.Activated += (sender, e) => { + this.customPathControl.Enabled = customCheckBox.State == NSCellStateValue.On; + customPathHeightConstraint.Constant = this.customPathControl.Enabled ? 22 : 0; + }; + + this.customPathControl.Changed += (sender, e) => { + viewModel.Path = this.customPathControl.StringValue; + }; + + this.pathOutlineView = new PathOutlineView { + + }; + + this.pathOutlineView.Activated += OnPathOutlineViewSelected; + + var pathColumn = new NSTableColumn (PathSelectorColumnColId); + this.pathOutlineView.AddColumn (pathColumn); + + // Set OutlineTableColumn or the arrows showing children/expansion will not be drawn + this.pathOutlineView.OutlineTableColumn = pathColumn; + + // add the panel to the window + outlineViewContainer.DocumentView = this.pathOutlineView; + + this.pathBox.AddSubview (outlineViewContainer); + + this.pathBox.AddConstraints (new[] { + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.customPathControl, NSLayoutAttribute.Bottom, 1f, 0f), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.CenterX, 1, 0), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this.pathBox, NSLayoutAttribute.Bottom, 1f, 0f), + }); + + AddConstraints (new[] { + NSLayoutConstraint.Create (this.pathBox, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (this.pathBox, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.pathBox, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this, NSLayoutAttribute.Height, 1f, 0f), + NSLayoutConstraint.Create (this.pathBox, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this, NSLayoutAttribute.Width, 1f, 0f), + }); + + viewModel.PropertyChanged += OnPropertyChanged; + } + + public override async void OnPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof (CreateBindingViewModel.PropertyRoot)) { + if (ViewModel.PropertyRoot != null) { + this.pathOutlineView.PropertyTreeRoot = await ViewModel.PropertyRoot.Task; + } else { + this.pathOutlineView.PropertyTreeRoot = null; + } + } + + if (e.PropertyName == nameof (CreateBindingViewModel.Path)) { + this.customPathControl.StringValue = ViewModel.Path ?? string.Empty; + } + } + + private void OnPathOutlineViewSelected (object sender, EventArgs e) + { + if (sender is PathOutlineView pov) { + if (pov.SelectedRow != -1) { + if (pov.ItemAtRow (pov.SelectedRow) is NSObjectFacade facade) { + switch (facade.Target) { + case PropertyTreeElement propertyTreeElement: + ViewModel.SelectedPropertyElement = propertyTreeElement; + break; + + default: + break; + } + } + } + } + } + + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingResourceOutlineView.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingResourceOutlineView.cs new file mode 100644 index 0000000..025fcba --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingResourceOutlineView.cs @@ -0,0 +1,139 @@ +using System; +using System.Linq; +using AppKit; +using Foundation; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class BindingResourceOutlineView : BaseSelectorOutlineView + { + private ILookup<ResourceSource, Resource> itemsSource; + public ILookup<ResourceSource, Resource> ItemsSource + { + get => this.itemsSource; + set + { + if (this.itemsSource != value) { + this.itemsSource = value; + + DataSource = new BindingResourceOutlineViewDataSource (this.itemsSource); + Delegate = new BindingResourceOutlineViewDelegate (); + } + + ReloadData (); + + ExpandItem (null, true); + } + } + } + + internal class BindingResourceOutlineViewDelegate : NSOutlineViewDelegate + { + private const string ResourceIdentifier = "resource"; + + public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) + { + var labelContainer = (UnfocusableTextField)outlineView.MakeView (ResourceIdentifier, this); + if (labelContainer == null) { + labelContainer = new UnfocusableTextField { + Identifier = ResourceIdentifier, + }; + } + var target = (item as NSObjectFacade).Target; + + switch (target) { + case IGrouping<ResourceSource, Resource> kvp: + labelContainer.StringValue = kvp.Key.Name; + break; + case Resource resource: + labelContainer.StringValue = resource.Name; + break; + default: + labelContainer.StringValue = Properties.Resources.ResourceNotSupported; + break; + } + + return labelContainer; + } + + public override bool ShouldSelectItem (NSOutlineView outlineView, NSObject item) + { + var target = (item as NSObjectFacade).Target; + switch (target) { + case IGrouping<ResourceSource, Resource> kvp: + return false; + case Resource resource: + return true; + + default: + return false; + } + } + } + + internal class BindingResourceOutlineViewDataSource : NSOutlineViewDataSource + { + public ILookup<ResourceSource, Resource> ItemsSource { get; } + + internal BindingResourceOutlineViewDataSource (ILookup<ResourceSource, Resource> itemsSource) + { + if (itemsSource == null) + throw new ArgumentNullException (nameof (itemsSource)); + + ItemsSource = itemsSource; + } + + public override nint GetChildrenCount (NSOutlineView outlineView, NSObject item) + { + if (item == null) { + return ItemsSource != null ? ItemsSource.Count : 0; + } else { + var target = (item as NSObjectFacade).Target; + switch (target) { + case IGrouping<ResourceSource, Resource> kvp: + return kvp.Count (); + case Resource resource: + return 0; + default: + return 0; + } + } + } + + public override NSObject GetChild (NSOutlineView outlineView, nint childIndex, NSObject item) + { + object element; + + if (item == null) { + element = ItemsSource.ElementAt ((int)childIndex); + } else { + var target = (item as NSObjectFacade).Target; + switch (target) { + case IGrouping<ResourceSource, Resource> kvp: + element = kvp.ElementAt ((int)childIndex); + break; + case Resource resource: + element = resource; + break; + default: + return null; + } + } + + return new NSObjectFacade (element); + } + + public override bool ItemExpandable (NSOutlineView outlineView, NSObject item) + { + var target = (item as NSObjectFacade).Target; + switch (target) { + case IGrouping<ResourceSource, Resource> kvp: + return kvp.Any (); + case Resource resource: + return false; + default: + return false; + } + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingResourceSelectorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingResourceSelectorControl.cs new file mode 100644 index 0000000..54a5e4b --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/BindingResourceSelectorControl.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AppKit; +using Foundation; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class BindingResourceSelectorControl + : NotifyingView<CreateBindingViewModel> + { + private const string ResourceSelectorColId = "ResourceSelectorColumn"; + + public BindingResourceOutlineView resourceOutlineView; + + internal BindingResourceSelectorControl (CreateBindingViewModel viewModel) + { + if (viewModel == null) + throw new ArgumentNullException (nameof (viewModel)); + + ViewModel = viewModel; + + TranslatesAutoresizingMaskIntoConstraints = false; + + this.resourceOutlineView = new BindingResourceOutlineView (); + this.resourceOutlineView.Activated += OnResourceOutlineViewSelected; + + var resourceColumn = new NSTableColumn (ResourceSelectorColId); + this.resourceOutlineView.AddColumn (resourceColumn); + + // Set OutlineTableColumn or the arrows showing children/expansion will not be drawn + this.resourceOutlineView.OutlineTableColumn = resourceColumn; + + // create a table view and a scroll view + var outlineViewContainer = new NSScrollView { + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + // add the panel to the window + outlineViewContainer.DocumentView = this.resourceOutlineView; + AddSubview (outlineViewContainer); + + AddConstraints (new[] { + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this, NSLayoutAttribute.Top, 1f, 28f), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterX, 1, 0), + NSLayoutConstraint.Create (outlineViewContainer, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal,this, NSLayoutAttribute.Bottom, 1f, 0f), + }); + + viewModel.PropertyChanged += OnPropertyChanged; + } + + public override void OnPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof (CreateBindingViewModel.ShowResourceSelector)) { + Hidden = !ViewModel.ShowResourceSelector; + + if (ViewModel.ShowResourceSelector && ViewModel.SourceResources != null) { + this.resourceOutlineView.ItemsSource = ViewModel.SourceResources.Value; + }; + } + } + + private void OnResourceOutlineViewSelected (object sender, EventArgs e) + { + if (sender is BindingResourceOutlineView rov) { + if (rov.SelectedRow != -1) { + if (rov.ItemAtRow (rov.SelectedRow) is NSObjectFacade item) { + if (item.Target is Resource resource) { + ViewModel.SelectedResource = resource; + } + } + } + } + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/CreateValueConverterWindow.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/CreateValueConverterWindow.cs new file mode 100644 index 0000000..b8c5c0e --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/CreateValueConverterWindow.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AppKit; +using CoreGraphics; +using Foundation; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class CreateValueConverterWindow : NSPanel + { + private new ModalWindowCloseDelegate Delegate { + get => (ModalWindowCloseDelegate)base.Delegate; + set => base.Delegate = value; + } + + public AddValueConverterViewModel ViewModel { get; } + + private NSTextField valueConverterName; + public string ValueConverterName { + get { return this.valueConverterName.Cell.Title; } + } + + public CreateValueConverterWindow (CreateBindingViewModel viewModel, AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> typetasks) + { + if (viewModel == null) + throw new ArgumentNullException (nameof (viewModel)); + + Delegate = new ModalWindowCloseDelegate (); + ViewModel = new AddValueConverterViewModel (viewModel.TargetPlatform, viewModel.Target, typetasks); + + StyleMask |= NSWindowStyle.Resizable; + + Title = Properties.Resources.AddValueConverterTitle; + + MaxSize = new CGSize (500, 560); // TODO discuss what the Max/Min Size should be and if we should have one. + MinSize = new CGSize (200, 320); + + var container = new NSView (new CGRect (CGPoint.Empty, new CGSize (400, 400))) { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + var valueConverterLabel = new UnfocusableTextField { + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, 13), + StringValue = Properties.Resources.ValueConverterName + ":", + TranslatesAutoresizingMaskIntoConstraints = false, + }; + container.AddSubview (valueConverterLabel); + + container.AddConstraints (new[] { + NSLayoutConstraint.Create (valueConverterLabel, NSLayoutAttribute.Top, NSLayoutRelation.Equal, container, NSLayoutAttribute.Top, 1f, 18f), + NSLayoutConstraint.Create (valueConverterLabel, NSLayoutAttribute.Left, NSLayoutRelation.Equal, container, NSLayoutAttribute.Left, 1f, 21f), + NSLayoutConstraint.Create (valueConverterLabel, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1f, 20), + }); + + this.valueConverterName = new NSTextField { + ControlSize = NSControlSize.Regular, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + container.AddSubview (this.valueConverterName); + + container.AddConstraints (new[] { + NSLayoutConstraint.Create (this.valueConverterName, NSLayoutAttribute.Top, NSLayoutRelation.Equal, valueConverterLabel, NSLayoutAttribute.Bottom, 1f, 1f), + NSLayoutConstraint.Create (this.valueConverterName, NSLayoutAttribute.Left, NSLayoutRelation.Equal, valueConverterLabel, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (this.valueConverterName, NSLayoutAttribute.Width, NSLayoutRelation.Equal, container, NSLayoutAttribute.Width, 1f, -40f), + }); + + var typeSelectorControl = new TypeSelectorControl { + Flush = true, + ViewModel = ViewModel, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + container.AddSubview (typeSelectorControl); + + container.AddConstraints (new[] { + NSLayoutConstraint.Create (typeSelectorControl, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.valueConverterName, NSLayoutAttribute.Bottom, 1f, 8f), + NSLayoutConstraint.Create (typeSelectorControl, NSLayoutAttribute.Left, NSLayoutRelation.Equal, valueConverterLabel, NSLayoutAttribute.Left, 1f, 0f), + NSLayoutConstraint.Create (typeSelectorControl, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.valueConverterName, NSLayoutAttribute.Width, 1f, 0f), + NSLayoutConstraint.Create (typeSelectorControl, NSLayoutAttribute.Height, NSLayoutRelation.Equal, container, NSLayoutAttribute.Height, 1f, -95f), + }); + + var buttonSelect = new NSButton { + BezelStyle = NSBezelStyle.Rounded, + ControlSize = NSControlSize.Regular, + Enabled = false, + Highlighted = true, + KeyEquivalent = "\r", // Fire when enter pressed + Title = Properties.Resources.Select, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + buttonSelect.Activated += (sender, e) => { + Delegate.Response = NSModalResponse.OK; + Close (); + }; + + container.AddSubview (buttonSelect); + + container.AddConstraints (new[] { + NSLayoutConstraint.Create (buttonSelect, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, container, NSLayoutAttribute.Bottom, 1f, -20f), + NSLayoutConstraint.Create (buttonSelect, NSLayoutAttribute.Right, NSLayoutRelation.Equal, typeSelectorControl, NSLayoutAttribute.Right, 1f, 0f), + NSLayoutConstraint.Create (buttonSelect, NSLayoutAttribute.Width, NSLayoutRelation.GreaterThanOrEqual, 1f, 80f), + }); + + var buttonCancel = new NSButton { + BezelStyle = NSBezelStyle.Rounded, + ControlSize = NSControlSize.Regular, + Title = Properties.Resources.Cancel, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + buttonCancel.Activated += (sender, e) => { + Delegate.Response = NSModalResponse.Cancel; + Close (); + }; + + container.AddSubview (buttonCancel); + + container.AddConstraints (new[] { + NSLayoutConstraint.Create (buttonCancel, NSLayoutAttribute.Top, NSLayoutRelation.Equal, buttonSelect, NSLayoutAttribute.Top, 1f, 0f), + NSLayoutConstraint.Create (buttonCancel, NSLayoutAttribute.Right, NSLayoutRelation.Equal, buttonSelect, NSLayoutAttribute.Left, 1f, -10f), + NSLayoutConstraint.Create (buttonCancel, NSLayoutAttribute.Width, NSLayoutRelation.GreaterThanOrEqual, 1f, 80f), + }); + + ContentViewController = new NSViewController (null, null) { + View = container, + }; + + ViewModel.PropertyChanged += (sender, e) => { + if (e.PropertyName == nameof (AddValueConverterViewModel.SelectedType)) { + this.valueConverterName.StringValue = ViewModel.SelectedType != null ? ViewModel.SelectedType.Name : string.Empty; + buttonSelect.Enabled = ViewModel.SelectedType != null; + } + }; + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/HeaderView.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/HeaderView.cs new file mode 100644 index 0000000..833a76b --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/HeaderView.cs @@ -0,0 +1,47 @@ +using System; +using AppKit; +using CoreAnimation; +using CoreGraphics; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class HeaderView : NSView + { + public string Title { + get { return this.headerText.StringValue; } + set { this.headerText.StringValue = value; } + } + + private NSLayoutConstraint horizonalHeaderTextAlignment; + public nfloat HorizonalTitleOffset { + get { return this.horizonalHeaderTextAlignment.Constant; } + set { this.horizonalHeaderTextAlignment.Constant = value; } + } + + private UnfocusableTextField headerText = new UnfocusableTextField { + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + internal HeaderView () + { + TranslatesAutoresizingMaskIntoConstraints = false; + WantsLayer = true; + + // Layer out of alphabetical order so that WantsLayer creates the layer first + Layer = new CALayer { + CornerRadius = 1.0f, + BorderColor = new CGColor (.5f, .5f, .5f, 1.0f), + BorderWidth = 1, + }; + + this.horizonalHeaderTextAlignment = NSLayoutConstraint.Create (this.headerText, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterX, 1, 0); + + AddSubview (this.headerText); + AddConstraints (new[] { + NSLayoutConstraint.Create (this.headerText, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this, NSLayoutAttribute.Height, 1f, 0f), + this.horizonalHeaderTextAlignment, + NSLayoutConstraint.Create (this.headerText, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterY, 1, 0f), + }); + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/ObjectOutlineView.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/ObjectOutlineView.cs new file mode 100644 index 0000000..18ee0cf --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/ObjectOutlineView.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using AppKit; +using Foundation; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class ObjectOutlineView : BaseSelectorOutlineView + { + private IReadOnlyList<ObjectTreeElement> itemsSource; + public IReadOnlyList<ObjectTreeElement> ItemsSource + { + get => this.itemsSource; + set + { + if (this.itemsSource != value) { + this.itemsSource = value; + + DataSource = new ObjectOutlineViewDataSource (this.itemsSource); ; + Delegate = new ObjectOutlineViewDelegate (); + } + + ReloadData (); + + ExpandItem (null, true); + } + } + } + + internal class ObjectOutlineViewDelegate : NSOutlineViewDelegate + { + private const string TypeIdentifier = "type"; + + public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) + { + var labelContainer = (UnfocusableTextField)outlineView.MakeView (TypeIdentifier, this); + if (labelContainer == null) { + labelContainer = new UnfocusableTextField { + Identifier = TypeIdentifier, + }; + } + var target = (item as NSObjectFacade).Target; + + switch (target) { + case KeyValuePair<string, SimpleCollectionView> kvp: + labelContainer.StringValue = kvp.Key; + break; + case TypeInfo info: + labelContainer.StringValue = info.Name; + break; + default: + labelContainer.StringValue = Properties.Resources.TypeNotSupported; + break; + } + + return labelContainer; + } + + public override bool ShouldSelectItem (NSOutlineView outlineView, NSObject item) + { + var target = (item as NSObjectFacade).Target; + switch (target) { + case KeyValuePair<string, SimpleCollectionView> kvp: + return false; + case TypeInfo info: + return true; + + default: + return false; + } + } + } + + internal class ObjectOutlineViewDataSource : NSOutlineViewDataSource + { + public IReadOnlyList<ObjectTreeElement> ItemsSource { get; } + + internal ObjectOutlineViewDataSource (IReadOnlyList<ObjectTreeElement> itemsSource) + { + if (itemsSource == null) + throw new ArgumentNullException (nameof (itemsSource)); + + ItemsSource = itemsSource; + } + + public override nint GetChildrenCount (NSOutlineView outlineView, NSObject item) + { + if (item == null) { + return ItemsSource != null ? ItemsSource.Count : 0; + } else { + var target = (item as NSObjectFacade).Target; + switch (target) { + case KeyValuePair<string, SimpleCollectionView> kvp: + return kvp.Value.Count; + case TypeInfo info: + return 0; + default: + return 0; + } + } + } + + public override NSObject GetChild (NSOutlineView outlineView, nint childIndex, NSObject item) + { + object element; + + if (item == null) { + element = ItemsSource.ElementAt ((int)childIndex); + } else { + var target = (item as NSObjectFacade).Target; + switch (target) { + case KeyValuePair<string, SimpleCollectionView> kvp: + element = kvp.Value[(int)childIndex]; + break; + case TypeInfo info: + element = info; + break; + default: + return null; + } + } + + return new NSObjectFacade (element); + } + + public override bool ItemExpandable (NSOutlineView outlineView, NSObject item) + { + var target = (item as NSObjectFacade).Target; + switch (target) { + case KeyValuePair<string, SimpleCollectionView> kvp: + return kvp.Value.Count > 0; + case TypeInfo info: + return false; + default: + return false; + } + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/PathOutlineView.cs b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/PathOutlineView.cs new file mode 100644 index 0000000..124cee6 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/BindingEditor/PathOutlineView.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using AppKit; +using Foundation; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class PathOutlineView : BaseSelectorOutlineView + { + private IReadOnlyCollection<object> itemsSource; + private PropertyTreeRoot propertyTreeRoot; + + private void SetItemsSource (IReadOnlyCollection<object> value, string targetName) + { + if (this.itemsSource != value) { + this.itemsSource = value; + + DataSource = new PathOutlineViewDataSource (this.itemsSource, targetName); ; + Delegate = new PathOutlineViewDelegate (); + + ReloadData (); + + ExpandItem (ItemAtRow (0)); + } + } + + public PropertyTreeRoot PropertyTreeRoot + { + get { return this.propertyTreeRoot; } + internal set + { + if (this.propertyTreeRoot != value) { + this.propertyTreeRoot = value; + if (this.propertyTreeRoot != null) { + SetItemsSource (this.propertyTreeRoot.Children, this.propertyTreeRoot.TargetType.Name); + } else { + SetItemsSource (null, string.Empty); + } + } + } + } + } + + internal class PathOutlineViewDelegate : NSOutlineViewDelegate + { + private const string PathIdentifier = "path"; + + public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) + { + var labelContainer = (UnfocusableTextField)outlineView.MakeView (PathIdentifier, this); + if (labelContainer == null) { + labelContainer = new UnfocusableTextField { + Identifier = PathIdentifier, + }; + } + var target = (item as NSObjectFacade).Target; + + switch (target) { + case PropertyTreeElement propertyTreeElement: + labelContainer.StringValue = string.Format ("{0}: ({1})", propertyTreeElement.Property.Name, propertyTreeElement.Property.RealType.Name); + break; + + case string targetName: + labelContainer.StringValue = targetName; + break; + + default: + labelContainer.StringValue = Properties.Resources.TypeNotSupported; + break; + } + + return labelContainer; + } + + public override bool ShouldSelectItem (NSOutlineView outlineView, NSObject item) + { + if (item is NSObjectFacade facade) { + switch (facade.Target) { + case PropertyTreeElement propertyTreeElement: + var propertyTreeResult = propertyTreeElement.Children.Task.Result; + return propertyTreeResult.Count == 0; + + default: + return false; + } + } else { + return false; + } + } + } + + internal class PathOutlineViewDataSource : NSOutlineViewDataSource + { + private readonly IReadOnlyCollection<object> itemsSource; + private readonly string targetName; + + internal PathOutlineViewDataSource (IReadOnlyCollection<object> itemsSource, string targetName) + { + this.itemsSource = itemsSource; + this.targetName = targetName; + } + + public override nint GetChildrenCount (NSOutlineView outlineView, NSObject item) + { + if (item == null) { + return this.itemsSource != null ? this.itemsSource.Count + 1 : 0; + } else { + var target = (item as NSObjectFacade).Target; + switch (target) { + case PropertyTreeElement propertyTreeElement: + IReadOnlyCollection<PropertyTreeElement> propertyTrees = propertyTreeElement.Children.Task.Result; + return propertyTrees.Count; + + case string targetName: + return this.itemsSource.Count; + + default: + return 0; + } + } + } + + public override NSObject GetChild (NSOutlineView outlineView, nint childIndex, NSObject item) + { + if (childIndex == 0 && item == null) { + return new NSObjectFacade (targetName); + } + + if (item is NSObjectFacade objectFacade) { + var target = objectFacade.Target; + switch (target) { + case PropertyTreeElement propertyTreeElement: + IReadOnlyCollection<PropertyTreeElement> propertyTrees = propertyTreeElement.Children.Task.Result; + return new NSObjectFacade (propertyTrees.ElementAt ((int)childIndex)); + + case string targetName: + return new NSObjectFacade (this.itemsSource.ElementAt ((int)childIndex)); + + default: + return null; + } + + } + return null; + } + + public override bool ItemExpandable (NSOutlineView outlineView, NSObject item) + { + if (item is NSObjectFacade objectFacade) { + var target = objectFacade.Target; + switch (target) { + case PropertyTreeElement propertyTreeElement: + IReadOnlyCollection<PropertyTreeElement> propertyTrees = propertyTreeElement.Children.Task.Result; + return propertyTrees.Count > 0; + + case string targetName: + return true; + + default: + return false; + } + } + + return false; + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/CommandButton.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/CommandButton.cs index 4904bd8..4fde65f 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/CommandButton.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/CommandButton.cs @@ -2,7 +2,7 @@ using System; using System.Windows.Input; using AppKit; -namespace Xamarin.PropertyEditing.Mac.Controls.Custom +namespace Xamarin.PropertyEditing.Mac { internal class CommandButton : NSButton { @@ -11,8 +11,7 @@ namespace Xamarin.PropertyEditing.Mac.Controls.Custom public ICommand Command { get { return this.command; } - set - { + set { if (this.command != null) this.command.CanExecuteChanged -= CanExecuteChanged; diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs index 5768136..90ca577 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/PropertyButton.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AppKit; using CoreGraphics; using Xamarin.PropertyEditing.ViewModels; @@ -19,12 +20,16 @@ namespace Xamarin.PropertyEditing.Mac set { if (this.viewModel != null) { this.viewModel.PropertyChanged -= OnPropertyChanged; + if (this.viewModel.SupportsBindings) + this.viewModel.CreateBindingRequested -= OnBindingRequested; } this.viewModel = value; if (this.viewModel != null) { this.viewModel.PropertyChanged += OnPropertyChanged; + if (this.viewModel.SupportsBindings) + this.viewModel.CreateBindingRequested += OnBindingRequested; ValueSourceChanged (this.viewModel.ValueSource); } @@ -113,10 +118,22 @@ namespace Xamarin.PropertyEditing.Mac this.popUpContextMenu.AddItem (mi2); } + if (this.viewModel.SupportsBindings) { + this.popUpContextMenu.AddItem (NSMenuItem.SeparatorItem); + + this.popUpContextMenu.AddItem (new CommandMenuItem (Properties.Resources.CreateDataBindingMenuItem, this.viewModel.RequestCreateBindingCommand) { + AttributedTitle = new Foundation.NSAttributedString ( + Properties.Resources.CreateDataBindingMenuItem, + new CoreText.CTStringAttributes { + Font = new CoreText.CTFont (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize + 1), + }) + }); + } + 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) { + this.popUpContextMenu.AddItem (new CommandMenuItem (Properties.Resources.Reset, this.viewModel.ClearValueCommand) { AttributedTitle = new Foundation.NSAttributedString ( Properties.Resources.Reset, new CoreText.CTStringAttributes { @@ -242,5 +259,17 @@ namespace Xamarin.PropertyEditing.Mac resourceSelectorPopOver.Show (requestResourceView.Frame, (NSView)this, NSRectEdge.MinYEdge); } + + private void OnBindingRequested (object sender, CreateBindingRequestedEventArgs e) + { + var bindingEditorWindow = new BindingEditorWindow (this.hostResources, this.viewModel) { + Appearance = EffectiveAppearance, + }; + + var result = (NSModalResponse)(int)NSApplication.SharedApplication.RunModalForWindow (bindingEditorWindow); + if (result == NSModalResponse.OK) { + e.BindingObject = bindingEditorWindow.ViewModel.SelectedObjects.Single (); + } + } } } diff --git a/Xamarin.PropertyEditing.Mac/Controls/EditorContainer.cs b/Xamarin.PropertyEditing.Mac/Controls/EditorContainer.cs index 2175b29..c22e438 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/EditorContainer.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/EditorContainer.cs @@ -6,8 +6,8 @@ namespace Xamarin.PropertyEditing.Mac internal class EditorContainer : PropertyContainer { - public EditorContainer (IHostResourceProvider hostResources, IEditorView editorView) - : base (hostResources, editorView, editorView?.NeedsPropertyButton ?? false) + public EditorContainer (IHostResourceProvider hostResources, IEditorView editorView, bool? needsPropertyButton = null) + : base (hostResources, editorView, needsPropertyButton ?? editorView?.NeedsPropertyButton ?? false) { } @@ -22,7 +22,7 @@ namespace Xamarin.PropertyEditing.Mac EditorView.ViewModel = value; - if (EditorView.NeedsPropertyButton) + if (PropertyButton != null) PropertyButton.ViewModel = value as PropertyViewModel; } } @@ -57,8 +57,8 @@ namespace Xamarin.PropertyEditing.Mac public override void ViewWillMoveToSuperview (NSView newSuperview) { - if (newSuperview == null && EditorView != null) - EditorView.ViewModel = null; + if (newSuperview == null) + ViewModel = null; base.ViewWillMoveToSuperview (newSuperview); } diff --git a/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs index 4b1e9b4..aff0ae5 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/PropertyEditorControl.cs @@ -75,23 +75,25 @@ namespace Xamarin.PropertyEditing.Mac public void UpdateKeyViews () { - nint row = TableView.RowForView (this); - if (row <= 0) - return; - - NSView view; - PropertyEditorControl ctrl = null; - do { - row--; - view = TableView.GetView (0, row, makeIfNecessary: false); - ctrl = (view as EditorContainer)?.EditorView?.NativeView as PropertyEditorControl; - } while (row > 0 && ctrl == null); - - if (ctrl != null) { - ctrl.LastKeyView.NextKeyView = FirstKeyView; - ctrl.UpdateKeyViews (); - } else if (row == 0 && view is PanelHeaderEditorControl header) { - header.SetNextKeyView (FirstKeyView); + if (TableView != null) { + nint row = TableView.RowForView (this); + if (row <= 0) + return; + + NSView view; + PropertyEditorControl ctrl = null; + do { + row--; + view = TableView.GetView (0, row, makeIfNecessary: false); + ctrl = (view as EditorContainer)?.EditorView?.NativeView as PropertyEditorControl; + } while (row > 0 && ctrl == null); + + if (ctrl != null) { + ctrl.LastKeyView.NextKeyView = FirstKeyView; + ctrl.UpdateKeyViews (); + } else if (row == 0 && view is PanelHeaderEditorControl header) { + header.SetNextKeyView (FirstKeyView); + } } } diff --git a/Xamarin.PropertyEditing.Mac/Controls/TypeSelectorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/TypeSelectorControl.cs index 747ca64..d5574cb 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/TypeSelectorControl.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/TypeSelectorControl.cs @@ -11,6 +11,25 @@ namespace Xamarin.PropertyEditing.Mac internal class TypeSelectorControl : NotifyingView<TypeSelectorViewModel> { + private bool flush; + public bool Flush { + get { return this.flush; } + set { + this.flush = value; + this.filterTop.Constant = this.flush ? 0: 10; + this.filterWidth.Constant = this.flush ? 0 : -20; + this.scrollTop.Constant = this.flush ? 0 : 8; + this.checkBoxBottom.Constant = this.flush ? -28 : -10; + this.checkBoxLeft.Constant = this.flush ? 8 : 0; + } + } + + private NSLayoutConstraint filterTop; + private NSLayoutConstraint filterWidth; + private NSLayoutConstraint scrollTop; + private NSLayoutConstraint checkBoxBottom; + private NSLayoutConstraint checkBoxLeft; + public TypeSelectorControl() { this.checkbox = NSButton.CreateCheckbox (Properties.Resources.ShowAllAssemblies, OnCheckedChanged); @@ -36,7 +55,7 @@ namespace Xamarin.PropertyEditing.Mac AddSubview (scroll); - this.filter = new NSTextField { + this.filter = new NSSearchField { TranslatesAutoresizingMaskIntoConstraints = false, PlaceholderString = "Filter" }; @@ -44,18 +63,24 @@ namespace Xamarin.PropertyEditing.Mac AddSubview (this.filter); + this.filterTop = NSLayoutConstraint.Create (this.filter, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this, NSLayoutAttribute.Top, 1, 10); + this.filterWidth = NSLayoutConstraint.Create (this.filter, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this, NSLayoutAttribute.Width, 1, -20); + this.scrollTop = NSLayoutConstraint.Create (scroll, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.filter, NSLayoutAttribute.Bottom, 1, 8); + this.checkBoxBottom = NSLayoutConstraint.Create (this.checkbox, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this, NSLayoutAttribute.Bottom, 1, -10); + this.checkBoxLeft = NSLayoutConstraint.Create (this.checkbox, NSLayoutAttribute.Left, NSLayoutRelation.Equal, scroll, NSLayoutAttribute.Left, 1, 0); + AddConstraints (new[] { - NSLayoutConstraint.Create (this.filter, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this, NSLayoutAttribute.Top, 1, 10), - NSLayoutConstraint.Create (this.filter, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this, NSLayoutAttribute.Width, 1, -20), + this.filterTop, + this.filterWidth, NSLayoutConstraint.Create (this.filter, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterX, 1, 0), - NSLayoutConstraint.Create (scroll, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.filter, NSLayoutAttribute.Bottom, 1, 8), + this.scrollTop, NSLayoutConstraint.Create (scroll, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.filter, NSLayoutAttribute.Width, 1, 0), NSLayoutConstraint.Create (scroll, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterX, 1, 0), NSLayoutConstraint.Create (scroll, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this.checkbox, NSLayoutAttribute.Top, 1, -8), - NSLayoutConstraint.Create (this.checkbox, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this, NSLayoutAttribute.Bottom, 1, -10), - NSLayoutConstraint.Create (this.checkbox, NSLayoutAttribute.Left, NSLayoutRelation.Equal, scroll, NSLayoutAttribute.Left, 1, 0) + this.checkBoxBottom, + this.checkBoxLeft, }); } @@ -109,6 +134,8 @@ namespace Xamarin.PropertyEditing.Mac for (int i = 0; i < this.outlineView.RowCount; i++) { this.outlineView.ExpandItem (this.outlineView.ItemAtRow (i)); } + } else { + this.outlineView.ExpandItem (null, true); } } diff --git a/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs b/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs index 39e4f63..28071a7 100644 --- a/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs +++ b/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs @@ -37,22 +37,19 @@ namespace Xamarin.PropertyEditing.Mac var facade = (NSObjectFacade)item; if (facade?.Target is ObjectPropertyViewModel ovm) return ovm.ValueModel.Properties.Count; - + int headerCount = (ShowHeader && !Filtering) ? 1 : 0; - int childCount; if (this.vm.ArrangeMode == PropertyArrangeMode.Name) - childCount = this.vm.ArrangedEditors[0].Editors.Count + headerCount; + return this.vm.ArrangedEditors[0].Editors.Count + headerCount; else { if (item == null) - childCount = this.vm.ArrangedEditors.Count + headerCount; + return this.vm.ArrangedEditors.Count + headerCount; else { var group = (PanelGroupViewModel)((NSObjectFacade)item).Target; - childCount = group.Editors.Count + group.UncommonEditors.Count; + return group.Editors.Count + group.UncommonEditors.Count; } } - - return childCount; } public override NSObject GetChild (NSOutlineView outlineView, nint childIndex, NSObject item) |