From 6a6761eb9b3d8624ee6901fcde1c94db47250357 Mon Sep 17 00:00:00 2001 From: Eric Maupin Date: Tue, 15 Jan 2019 11:11:58 -0500 Subject: [mac] Add ObjectEditor --- .../Controls/ObjectEditorControl.cs | 184 +++++++++++++++++++++ .../Controls/TypeSelectorWindow.cs | 75 +++++++++ Xamarin.PropertyEditing.Mac/PropertyEditorPanel.cs | 2 +- .../PropertyEditorSelector.cs | 2 + .../PropertyTableDataSource.cs | 30 +++- .../Xamarin.PropertyEditing.Mac.csproj | 2 + .../ViewModels/ObjectPropertyViewModel.cs | 7 +- 7 files changed, 292 insertions(+), 10 deletions(-) create mode 100644 Xamarin.PropertyEditing.Mac/Controls/ObjectEditorControl.cs create mode 100644 Xamarin.PropertyEditing.Mac/Controls/TypeSelectorWindow.cs diff --git a/Xamarin.PropertyEditing.Mac/Controls/ObjectEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/ObjectEditorControl.cs new file mode 100644 index 0000000..ab9749b --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/ObjectEditorControl.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Threading.Tasks; +using AppKit; +using Foundation; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class ObjectEditorControl + : PropertyEditorControl + { + public ObjectEditorControl (IHostResourceProvider hostResources) + : base (hostResources) + { + this.typeLabel = new UnfocusableTextField { + TranslatesAutoresizingMaskIntoConstraints = false + }; + AddSubview (this.typeLabel); + + this.createObject = new NSButton { + Title = Properties.Resources.New, + TranslatesAutoresizingMaskIntoConstraints = false, + BezelStyle = NSBezelStyle.Rounded + }; + this.createObject.Activated += OnNewPressed; + AddSubview (this.createObject); + + this.buttonConstraint = NSLayoutConstraint.Create (this.createObject, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.typeLabel, NSLayoutAttribute.Trailing, 1f, 12); + + AddConstraints (new[] { + NSLayoutConstraint.Create (this.typeLabel, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this, NSLayoutAttribute.Leading, 1f, 0f), + NSLayoutConstraint.Create (this.typeLabel, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterY, 1f, 0f), + NSLayoutConstraint.Create (this.typeLabel, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this, NSLayoutAttribute.Height, 1, 0), + this.buttonConstraint, + NSLayoutConstraint.Create (this.createObject, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this, NSLayoutAttribute.Leading, 1, 0).WithPriority (NSLayoutPriority.DefaultLow), + NSLayoutConstraint.Create (this.createObject, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterY, 1f, 0f), + NSLayoutConstraint.Create (this.createObject, NSLayoutAttribute.Width, NSLayoutRelation.GreaterThanOrEqual, 1f, 70f), + }); + } + + public override NSView FirstKeyView => this.createObject; + + public override NSView LastKeyView => this.createObject; + + protected override void UpdateValue () + { + } + + protected override void UpdateErrorsDisplayed (IEnumerable errors) + { + } + + protected override void HandleErrorsChanged (object sender, DataErrorsChangedEventArgs e) + { + } + + protected override void SetEnabled () + { + this.createObject.Enabled = ViewModel.Property.CanWrite; + } + + protected override void UpdateAccessibilityValues () + { + this.createObject.AccessibilityTitle = String.Format (Properties.Resources.NewInstanceForProperty, ViewModel.Property.Name); + } + + protected override void OnViewModelChanged (PropertyViewModel oldModel) + { + base.OnViewModelChanged (oldModel); + + if (oldModel is ObjectPropertyViewModel ovm) { + ovm.TypeRequested -= OnTypeRequested; + ovm.CreateInstanceCommand.CanExecuteChanged -= OnCreateInstanceExecutableChanged; + } + + if (ViewModel != null) { + ViewModel.TypeRequested += OnTypeRequested; + ViewModel.CreateInstanceCommand.CanExecuteChanged += OnCreateInstanceExecutableChanged; + + OnPropertyChanged (ViewModel, new PropertyChangedEventArgs (null)); + } + } + + protected override void OnPropertyChanged (object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) { + case nameof (ObjectPropertyViewModel.ValueType): + UpdateTypeLabel (); + break; + case null: + case "": + UpdateTypeLabel (); + UpdateCreateInstanceCommand (); + break; + } + + base.OnPropertyChanged (sender, e); + } + + private readonly UnfocusableTextField typeLabel; + private readonly NSButton createObject; + private readonly NSLayoutConstraint buttonConstraint; + + private void OnCreateInstanceExecutableChanged (object sender, EventArgs e) + { + UpdateCreateInstanceCommand (); + } + + private void OnTypeRequested (object sender, TypeRequestedEventArgs e) + { + var tcs = new TaskCompletionSource (); + e.SelectedType = tcs.Task; + + var vm = new TypeSelectorViewModel (ViewModel.AssignableTypes); + var selector = new TypeSelectorControl { + ViewModel = vm, + Appearance = EffectiveAppearance + }; + + vm.PropertyChanged += (vms, ve) => { + if (ve.PropertyName == nameof (TypeSelectorViewModel.SelectedType)) { + tcs.TrySetResult (vm.SelectedType); + } + }; + + var popover = new NSPopover { + Behavior = NSPopoverBehavior.Transient, + Delegate = new PopoverDelegate (tcs), + ContentViewController = new NSViewController { + View = selector, + PreferredContentSize = new CoreGraphics.CGSize (360, 335) + }, + }; + + popover.SetAppearance (HostResources.GetVibrantAppearance (EffectiveAppearance)); + + tcs.Task.ContinueWith (t => { + popover.PerformClose (popover); + popover.Dispose (); + }, TaskScheduler.FromCurrentSynchronizationContext()); + + popover.Show (this.createObject.Frame, this, NSRectEdge.MinYEdge); + } + + private void UpdateTypeLabel () + { + if (ViewModel.ValueType == null) { + this.typeLabel.StringValue = String.Empty; + this.buttonConstraint.Active = false; + } else { + this.typeLabel.StringValue = $"({ViewModel.ValueType.Name})"; + this.buttonConstraint.Active = true; + } + } + + private void UpdateCreateInstanceCommand() + { + this.createObject.Enabled = ViewModel.CreateInstanceCommand.CanExecute (null); + } + + private void OnNewPressed (object sender, EventArgs e) + { + ViewModel.CreateInstanceCommand.Execute (null); + } + + private class PopoverDelegate + : NSPopoverDelegate + { + public PopoverDelegate (TaskCompletionSource tcs) + { + this.tcs = tcs; + } + + public override void WillClose (NSNotification notification) + { + this.tcs.TrySetCanceled (); + } + + private readonly TaskCompletionSource tcs; + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/TypeSelectorWindow.cs b/Xamarin.PropertyEditing.Mac/Controls/TypeSelectorWindow.cs new file mode 100644 index 0000000..de4da3c --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/TypeSelectorWindow.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using AppKit; +using CoreGraphics; + +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class TypeSelectorWindow + : NSWindow + { + public TypeSelectorWindow (TypeSelectorViewModel viewModel) + : base (new CGRect (0, 0, 300, 300), NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Resizable, NSBackingStore.Buffered, true) + { + Title = Properties.Resources.SelectObjectTitle; + + this.selector = new TypeSelectorControl { + ViewModel = viewModel, + TranslatesAutoresizingMaskIntoConstraints = false + }; + + ContentView.AddSubview (this.selector); + + this.ok = NSButton.CreateButton (Properties.Resources.OK, OnOked); + this.ok.TranslatesAutoresizingMaskIntoConstraints = false; + ContentView.AddSubview (this.ok); + + this.cancel = NSButton.CreateButton (Properties.Resources.Cancel, OnCanceled); + this.cancel.TranslatesAutoresizingMaskIntoConstraints = false; + ContentView.AddSubview (this.cancel); + + ContentView.AddConstraints (new[] { + NSLayoutConstraint.Create (this.selector, NSLayoutAttribute.Width, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Width, 1, -20), + NSLayoutConstraint.Create (this.selector, NSLayoutAttribute.Top, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Top, 1, 0), + NSLayoutConstraint.Create (this.selector, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.CenterX, 1, 0), + NSLayoutConstraint.Create (this.selector, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this.ok, NSLayoutAttribute.Top, 1, -10), + + NSLayoutConstraint.Create (this.ok, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Bottom, 1, -10), + NSLayoutConstraint.Create (this.ok, NSLayoutAttribute.Right, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Right, 1, -10), + + NSLayoutConstraint.Create (this.cancel, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.ok, NSLayoutAttribute.Left, 1, -10), + NSLayoutConstraint.Create (this.cancel, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Bottom, 1, -10), + }); + } + + private TypeSelectorControl selector; + private NSButton ok, cancel; + + private void OnOked () + { + NSApplication.SharedApplication.StopModalWithCode ((int)NSModalResponse.OK); + Close (); + } + + private void OnCanceled () + { + NSApplication.SharedApplication.StopModalWithCode ((int)NSModalResponse.Cancel); + Close (); + } + + public static ITypeInfo RequestType (AsyncValue>> assignableTypes) + { + var w = new TypeSelectorWindow (new TypeSelectorViewModel (assignableTypes)); + + var result = (NSModalResponse)(int)NSApplication.SharedApplication.RunModalForWindow (w); + if (result != NSModalResponse.OK) + return null; + + return w.selector.ViewModel.SelectedType; + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/PropertyEditorPanel.cs b/Xamarin.PropertyEditing.Mac/PropertyEditorPanel.cs index 566ca5b..51051be 100644 --- a/Xamarin.PropertyEditing.Mac/PropertyEditorPanel.cs +++ b/Xamarin.PropertyEditing.Mac/PropertyEditorPanel.cs @@ -183,7 +183,7 @@ namespace Xamarin.PropertyEditing.Mac var propertyEditors = new NSTableColumn (PropertyEditorColId); this.propertyTable.AddColumn (propertyEditors); - // Set OutlineTableColumn or the arrows showing children/expansion will not be drawn + // Set OutlineTableColumn or the arrows showing children/expansion or they will not be drawn this.propertyTable.OutlineTableColumn = propertyEditors; var tableContainer = new NSScrollView { diff --git a/Xamarin.PropertyEditing.Mac/PropertyEditorSelector.cs b/Xamarin.PropertyEditing.Mac/PropertyEditorSelector.cs index d3a7398..cb96191 100644 --- a/Xamarin.PropertyEditing.Mac/PropertyEditorSelector.cs +++ b/Xamarin.PropertyEditing.Mac/PropertyEditorSelector.cs @@ -54,6 +54,8 @@ namespace Xamarin.PropertyEditing.Mac {typeof (RatioViewModel), typeof (RatioEditorControl)}, {typeof (ThicknessPropertyViewModel), typeof (CommonThicknessEditorControl) }, {typeof (PropertyGroupViewModel), typeof (GroupEditorControl)}, + {typeof (ObjectPropertyViewModel), typeof (ObjectEditorControl)}, + }; } } diff --git a/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs b/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs index da40034..2c0e403 100644 --- a/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs +++ b/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using AppKit; using Foundation; @@ -27,8 +28,11 @@ namespace Xamarin.PropertyEditing.Mac if (this.vm.ArrangedEditors.Count == 0) return 0; - var childCount = 0; + var facade = (NSObjectFacade)item; + if (facade?.Target is ObjectPropertyViewModel ovm) + return ovm.ValueModel.Properties.Count; + var childCount = 0; if (this.vm.ArrangeMode == PropertyArrangeMode.Name) childCount = Filtering ? this.vm.ArrangedEditors[0].Editors.Count : this.vm.ArrangedEditors[0].Editors.Count + 1; else { @@ -47,17 +51,20 @@ namespace Xamarin.PropertyEditing.Mac { object element; + var f = ((NSObjectFacade)item); // We only want the Header to appear at the top of both Category and Name Modes, which means item is null in both. if (childIndex == 0 && item == null && !Filtering) element = null; - else { + else if (f?.Target is ObjectPropertyViewModel ovm) { + element = ovm.ValueModel.Properties[(int)childIndex]; + } else { if (this.vm.ArrangeMode == PropertyArrangeMode.Name) element = Filtering ? this.vm.ArrangedEditors[0].Editors[(int)childIndex] : this.vm.ArrangedEditors[0].Editors[(int)childIndex - 1]; else { if (item == null) element = Filtering ? this.vm.ArrangedEditors[(int)childIndex] : this.vm.ArrangedEditors[(int)childIndex - 1]; else { - var group = (PanelGroupViewModel)((NSObjectFacade)item).Target; + var group = (PanelGroupViewModel)f.Target; var list = group.Editors; if (childIndex >= list.Count) { childIndex -= list.Count; @@ -74,10 +81,25 @@ namespace Xamarin.PropertyEditing.Mac public override bool ItemExpandable (NSOutlineView outlineView, NSObject item) { + var f = (NSObjectFacade)item; + if (f.Target is ObjectPropertyViewModel ovm) { + PropertyChangedEventHandler changed = null; + changed = (o, e) => { + if (e.PropertyName != nameof (ObjectPropertyViewModel.CanDelve)) + return; + + ovm.PropertyChanged -= changed; + outlineView.ReloadItem (item); + }; + ovm.PropertyChanged += changed; + + return ovm.CanDelve; + } + if (this.vm.ArrangeMode == PropertyArrangeMode.Name) return false; - return ((NSObjectFacade)item).Target is PanelGroupViewModel; + return f.Target is PanelGroupViewModel; } public NSObject GetFacade (object element) diff --git a/Xamarin.PropertyEditing.Mac/Xamarin.PropertyEditing.Mac.csproj b/Xamarin.PropertyEditing.Mac/Xamarin.PropertyEditing.Mac.csproj index ce10228..708bcdd 100644 --- a/Xamarin.PropertyEditing.Mac/Xamarin.PropertyEditing.Mac.csproj +++ b/Xamarin.PropertyEditing.Mac/Xamarin.PropertyEditing.Mac.csproj @@ -145,7 +145,9 @@ + + diff --git a/Xamarin.PropertyEditing/ViewModels/ObjectPropertyViewModel.cs b/Xamarin.PropertyEditing/ViewModels/ObjectPropertyViewModel.cs index e1bae44..56192e1 100644 --- a/Xamarin.PropertyEditing/ViewModels/ObjectPropertyViewModel.cs +++ b/Xamarin.PropertyEditing/ViewModels/ObjectPropertyViewModel.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; using System.Threading.Tasks; using System.Windows.Input; -using Cadenza.Collections; namespace Xamarin.PropertyEditing.ViewModels { @@ -14,7 +11,7 @@ namespace Xamarin.PropertyEditing.ViewModels : EventArgs { /// - /// Gets or sets the type selected by the user from the UI + /// Gets or sets a task for the type selected by the user from the UI /// public Task SelectedType { @@ -101,7 +98,7 @@ namespace Xamarin.PropertyEditing.ViewModels if (CurrentValue?.Value != null) ValueModel.SelectedObjects.Add (CurrentValue.Value); - SetCanDelve (Editors.Any (e => e != null)); + SetCanDelve (ValueModel.SelectedObjects.Count > 0); } } -- cgit v1.2.3