diff options
author | Dominique Louis <savagesoftware@gmail.com> | 2018-07-10 17:57:50 +0300 |
---|---|---|
committer | Dominique Louis <savagesoftware@gmail.com> | 2018-09-03 20:40:45 +0300 |
commit | 8c91aaf515db6d64e422ec466b13599ee9c87706 (patch) | |
tree | 74b0ed5c0a8fd3065e2d4856a9c56f1e4f7e0950 /Xamarin.PropertyEditing.Mac/Controls/RequestResource | |
parent | 017c948e29a0be922186a72b80a377d2f6370cf0 (diff) |
[Mac] Initial Request Resource Implementation.
Diffstat (limited to 'Xamarin.PropertyEditing.Mac/Controls/RequestResource')
5 files changed, 631 insertions, 0 deletions
diff --git a/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourcePanel.cs b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourcePanel.cs new file mode 100644 index 0000000..394dec8 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourcePanel.cs @@ -0,0 +1,147 @@ +using System; +using System.ComponentModel; +using AppKit; +using CoreGraphics; +using Xamarin.PropertyEditing.Drawing; +using Xamarin.PropertyEditing.Mac.Resources; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class RequestResourcePanel : NSView + { + internal const string ResourceImageColId = "ResourceImage"; + internal const string ResourceTypeColId = "ResourceType"; + internal const string ResourceNameColId = "ResourceName"; + internal const string ResourceValueColId = "ResourceValue"; + + private NSTableView resourceTable; + private ResourceTableDataSource dataSource; + + private ResourceSelectorViewModel viewModel; + public ResourceSelectorViewModel ViewModel => this.viewModel; + private SimpleCollectionView collectionView => this.viewModel.Resources as SimpleCollectionView; + public Resource SelectedResource { + get { + return (this.resourceTable.SelectedRow != -1) ? this.collectionView [((int)this.resourceTable.SelectedRow)] as Resource : null; + } + } + + NSProgressIndicator progressIndicator; + + NSScrollView tableContainer; + + RequestResourcePreviewPanel previewPanel; + + public event EventHandler ResourceSelected; + public event EventHandler DoubleClicked; + + private object selectedValue; + + public RequestResourcePanel (ResourceSelectorViewModel viewModel, object value) + { + this.viewModel = viewModel; + this.viewModel.PropertyChanged += OnPropertyChanged; + Initialize (value); + } + + private void OnPropertyChanged (object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof (this.viewModel.IsLoading)) { + this.progressIndicator.Hidden = !this.viewModel.IsLoading; + this.tableContainer.Hidden = !this.progressIndicator.Hidden; + if (this.viewModel.IsLoading) { + this.progressIndicator.StartAnimation (null); + } else { + this.progressIndicator.StopAnimation (null); + } + } + } + + private void Initialize (object selectedValue) + { + this.selectedValue = selectedValue; + Frame = new CGRect (10, 35, 630, 305); + + var FrameHeightHalf = (Frame.Height - 32) / 2; + var FrameWidthHalf = (Frame.Width - 32) / 2; + var FrameWidthThird = (Frame.Width - 32) / 3; + + this.progressIndicator = new NSProgressIndicator (new CGRect (FrameWidthThird, FrameHeightHalf, 32, 32)) { + Hidden = true, + Style = NSProgressIndicatorStyle.Spinning, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + AddSubview (this.progressIndicator); + + this.resourceTable = new FirstResponderTableView { + AutoresizingMask = NSViewResizingMask.WidthSizable, + HeaderView = null, + }; + + this.dataSource = new ResourceTableDataSource (viewModel); + var resourceTableDelegate = new ResourceTableDelegate (dataSource); + resourceTableDelegate.ResourceSelected += (sender, e) => { + this.previewPanel.SelectedResource = SelectedResource; + + this.selectedValue = BrushPropertyViewModel.GetCommonBrushForResource (SelectedResource); + + ResourceSelected?.Invoke (this, EventArgs.Empty); + }; + this.resourceTable.Delegate = resourceTableDelegate; + this.resourceTable.DataSource = dataSource; + + NSTableColumn resourceImages = new NSTableColumn (ResourceImageColId) { Title = LocalizationResources.ResourceTableImageColumnTitle, Width = 32 }; + this.resourceTable.AddColumn (resourceImages); + + NSTableColumn resourceTypes = new NSTableColumn (ResourceTypeColId) { Title = LocalizationResources.ResourceTableTypeColumnTitle, Width = 150 }; + this.resourceTable.AddColumn (resourceTypes); + + NSTableColumn resourceName = new NSTableColumn (ResourceNameColId) { Title = LocalizationResources.ResourceTableNameColumnTitle, Width = 150 }; + resourceTable.AddColumn (resourceName); + + NSTableColumn resourceValue = new NSTableColumn (ResourceValueColId) { Title = LocalizationResources.ResourceTableValueColumnTitle, Width = 45 }; + this.resourceTable.AddColumn (resourceValue); + + this.resourceTable.DoubleClick += (object sender, EventArgs e) => { + DoubleClicked?.Invoke (this, EventArgs.Empty); + }; + + // create a table view and a scroll view + this.tableContainer = new NSScrollView (new CGRect (0, 0, resourceTable.TableColumns ()[0].Width + resourceTable.TableColumns ()[1].Width + resourceTable.TableColumns ()[2].Width + 10, Frame.Height)) { + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.tableContainer.DocumentView = resourceTable; + AddSubview (this.tableContainer); + + this.previewPanel = new RequestResourcePreviewPanel (new CGRect (Frame.Width - FrameWidthThird, 0, FrameWidthThird, Frame.Height)); + AddSubview (this.previewPanel); + + this.DoConstraints (new NSLayoutConstraint[] { + this.tableContainer.ConstraintTo (this, (t, c) => t.Width == c.Width - 190), + this.tableContainer.ConstraintTo (this, (t, c) => t.Height == c.Height), + }); + + ReloadData (); + } + + internal void ReloadData () + { + this.resourceTable.ReloadData (); + + if (collectionView.Count > 0 && this.selectedValue != null) { + for (int i = 0; i < collectionView.Count; i++) { + var element = collectionView[i] as Resource; + var eType = element.GetType (); + var valuePropertyInfo = eType.GetProperty ("Value"); + var elementValue = valuePropertyInfo.GetValue (element); + if (elementValue == this.selectedValue) { + this.resourceTable.SelectRow (i, false); + break; + } + } + } + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourcePreviewPanel.cs b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourcePreviewPanel.cs new file mode 100644 index 0000000..8e46150 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourcePreviewPanel.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using AppKit; +using CoreGraphics; +using Xamarin.PropertyEditing.Drawing; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class RequestResourcePreviewPanel : NSView + { + private NSTextField noPreviewAvailable; + private NSView previewView; + + private Resource selectedResource; + public Resource SelectedResource + { + internal get + { + return selectedResource; + } + + set + { + if (selectedResource != value) { + selectedResource = value; + + if (selectedResource != null) { + // Let's find the next View + var pView = GetPreviewView (selectedResource); + + if (pView == null) { + ShowNoPreviewText (); + } else { + noPreviewAvailable.Hidden = true; + previewView.Hidden = false; + + switch (selectedResource) { + case Resource<CommonColor> colour: + if (pView is CommonBrushView cc) { + cc.Brush = new CommonSolidBrush (colour.Value); + } + break; + + case Resource<CommonGradientBrush> gradient: + if (pView is CommonBrushView vg) { + vg.Brush = gradient.Value; + } + break; + + case Resource<CommonSolidBrush> solid: + if (pView is CommonBrushView vs) { + vs.Brush = solid.Value; + } + break; + } + + // Only 1 subview allowed (must be a better way to handle this??) + if (previewView.Subviews.Count () > 0) { + previewView.Subviews[0].RemoveFromSuperview (); + } + // Free up anything from the previous view + previewView.AddSubview (pView); + } + } else { + ShowNoPreviewText (); + } + } + } + } + + private void ShowNoPreviewText () + { + noPreviewAvailable.Hidden = false; + previewView.Hidden = true; + } + + public RequestResourcePreviewPanel (CGRect frame) : base (frame) + { + var FrameHeightHalf = (Frame.Height - 32) / 2; + var FrameWidthHalf = (Frame.Width - 32) / 2; + var FrameWidthThird = (Frame.Width - 32) / 3; + + noPreviewAvailable = new UnfocusableTextField { + BackgroundColor = NSColor.Clear, + StringValue = Properties.Resources.NoPreviewAvailable, + Frame = new CGRect (50, FrameHeightHalf, 150, 50), + }; + + AddSubview (noPreviewAvailable); + + previewView = new NSView (new CGRect (20, 0, frame.Width - 30, frame.Height)); + previewView.Hidden = true; // Hidden until a resource is selected and a preview is available for it. + AddSubview (previewView); + } + + NSView GetPreviewView (Resource resource) + { + Type[] genericArgs = null; + Type previewRenderType; + if (!PreviewValueTypes.TryGetValue (resource.RepresentationType, out previewRenderType)) { + if (resource.RepresentationType.IsConstructedGenericType) { + genericArgs = resource.RepresentationType.GetGenericArguments (); + var type = resource.RepresentationType.GetGenericTypeDefinition (); + PreviewValueTypes.TryGetValue (type, out previewRenderType); + } + } + if (previewRenderType == null) + return null; + + if (previewRenderType.IsGenericTypeDefinition) { + if (genericArgs == null) + genericArgs = resource.RepresentationType.GetGenericArguments (); + previewRenderType = previewRenderType.MakeGenericType (genericArgs); + } + + return SetUpPreviewer (previewRenderType); + } + + private NSView SetUpPreviewer (Type previewRenderType) + { + var view = (NSView)Activator.CreateInstance (previewRenderType); + view.Identifier = previewRenderType.Name; + view.Frame = new CGRect (0, 0, previewView.Frame.Width, previewView.Frame.Height); + + return view; + } + + internal static readonly Dictionary<Type, Type> PreviewValueTypes = new Dictionary<Type, Type> { + {typeof (CommonSolidBrush), typeof (CommonBrushView)}, + {typeof (CommonColor), typeof (CommonBrushView)}, + }; + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourceView.cs b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourceView.cs new file mode 100644 index 0000000..034a561 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/RequestResourceView.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using AppKit; +using CoreGraphics; +using Foundation; +using Xamarin.PropertyEditing.Drawing; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class FirstResponderTableView : NSTableView + { + [Export ("validateProposedFirstResponder:forEvent:")] + public bool validateProposedFirstResponder (NSResponder responder, NSEvent ev) + { + return true; + } + } + + internal class RequestResourceView : BasePopOverViewModelControl + { + NSSearchField searchResources; + NSSegmentedControl segmentedControl; + NSButton showPreviewImage; + RequestResourcePanel resourceSelectorPanel; + + public NSPopover PopOver { get; internal set; } + + private bool showPreview; + public bool ShowPreview + { + get { return showPreview; } + set { + this.showPreview = value; + + Frame = this.showPreview ? new CGRect (Frame.X, Frame.Y, 640, 380) : new CGRect (Frame.X, Frame.Y, 460, 380); + + if (PopOver != null) { + PopOver.ContentSize = Frame.Size; + } + } + } + + public RequestResourceView (PropertyViewModel propertyViewModel) : base (propertyViewModel, Properties.Resources.SelectResourceTitle, "resource-editor-32") + { + Initialize (propertyViewModel); + } + + private void Initialize (PropertyViewModel propertyViewModel) + { + this.ShowPreview = true; + TranslatesAutoresizingMaskIntoConstraints = false; + + var FrameWidthThird = Frame.Width / 3; + var FrameWidthHalf = Frame.Width / 2; + var FrameHeightHalf = Frame.Height / 2; + + NSControlSize controlSize = NSControlSize.Small; + + this.searchResources = new NSSearchField { + ControlSize = controlSize, + Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize), + PlaceholderString = Properties.Resources.SearchResourcesTitle, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.searchResources.Changed += OnSearchResourcesChanged; + + AddSubview (this.searchResources); + + var vmType = propertyViewModel.GetType (); + var valuePropertyInfo = vmType.GetProperty ("Value"); + var resourceValue = valuePropertyInfo.GetValue (propertyViewModel); + var resourceSelectorPropertyInfo = vmType.GetProperty ("ResourceSelector"); + var resourceSelector = resourceSelectorPropertyInfo.GetValue (propertyViewModel) as ResourceSelectorViewModel; + + if (resourceSelector != null) { + this.resourceSelectorPanel = new RequestResourcePanel (resourceSelector, resourceValue); + } else { + this.resourceSelectorPanel = new RequestResourcePanel (new ResourceSelectorViewModel (propertyViewModel.TargetPlatform.ResourceProvider, propertyViewModel.Editors.Select (ed => ed.Target), propertyViewModel.Property), resourceValue); + } + this.resourceSelectorPanel.ResourceSelected += (sender, e) => { + propertyViewModel.Resource = this.resourceSelectorPanel.SelectedResource; + }; + this.resourceSelectorPanel.DoubleClicked += (sender, e) => { + PopOver.Close (); + }; + + AddSubview (this.resourceSelectorPanel); + + segmentedControl = NSSegmentedControl.FromLabels (new string[] { Properties.Resources.AllResources, Properties.Resources.Local, Properties.Resources.Shared }, NSSegmentSwitchTracking.SelectOne, () => { + //Switch Resource Types + switch (this.segmentedControl.SelectedSegment) { + case 0: + this.resourceSelectorPanel.ViewModel.ShowBothResourceTypes = true; + this.segmentedControl.SetImage (PropertyEditorPanel.ThemeManager.GetImageForTheme ("resource-editor-16"), 2); + break; + case 1: + this.resourceSelectorPanel.ViewModel.ShowOnlyLocalResources = true; + this.segmentedControl.SetImage (PropertyEditorPanel.ThemeManager.GetImageForTheme ("resource-editor-16"), 2); + break; + case 2: + this.resourceSelectorPanel.ViewModel.ShowOnlySystemResources = true; + this.segmentedControl.SetImage (PropertyEditorPanel.ThemeManager.GetImageForTheme ("resource-editor-16", "~sel"), 2); + break; + } + + this.resourceSelectorPanel.ReloadData (); + }); + this.segmentedControl.SetImage (PropertyEditorPanel.ThemeManager.GetImageForTheme ("resource-editor-16"), 2); + this.segmentedControl.Frame = new CGRect ((FrameWidthThird - (segmentedControl.Bounds.Width) / 2), 5, (Frame.Width - (FrameWidthThird)) - 10, 24); + this.segmentedControl.Font = NSFont.FromFontName (PropertyEditorControl.DefaultFontName, PropertyEditorControl.DefaultFontSize); + this.segmentedControl.TranslatesAutoresizingMaskIntoConstraints = false; + this.segmentedControl.SetSelected (true, 0); + this.resourceSelectorPanel.ViewModel.ShowBothResourceTypes = true; + + AddSubview (this.segmentedControl); + + this.showPreviewImage = new NSButton { + Bordered = false, + ControlSize = controlSize, + Image = NSImage.ImageNamed (NSImageName.QuickLookTemplate), + Title = string.Empty, + TranslatesAutoresizingMaskIntoConstraints = false, + }; + + this.showPreviewImage.Activated += (o, e) => { + ShowPreview = !ShowPreview; + RepositionControls (); + }; + + AddSubview (this.showPreviewImage); + + this.Appearance = PropertyEditorPanel.ThemeManager.CurrentAppearance; + + OnSearchResourcesChanged(null, null); + + RepositionControls (); + } + + private void RepositionControls () + { + var FrameWidthThird = Frame.Width / 3; + var FrameWidthHalf = Frame.Width / 2; + var FrameHeightHalf = Frame.Height / 2; + + this.searchResources.Frame = new CGRect (FrameWidthThird, Frame.Height - 30, (Frame.Width - (FrameWidthThird)) - 10, 30); + + this.showPreviewImage.Frame = new CGRect (Frame.Width - 35, 10, 24, 24); + } + + private void OnSearchResourcesChanged (object sender, EventArgs e) + { + this.resourceSelectorPanel.ViewModel.FilterText = searchResources.Cell.Title; + this.resourceSelectorPanel.ReloadData (); + } + } +}
\ No newline at end of file diff --git a/Xamarin.PropertyEditing.Mac/Controls/RequestResource/ResourceTableDataSource.cs b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/ResourceTableDataSource.cs new file mode 100644 index 0000000..6c4ad8a --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/ResourceTableDataSource.cs @@ -0,0 +1,27 @@ +using System; +using AppKit; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class ResourceTableDataSource + : NSTableViewDataSource + { + private ResourceSelectorViewModel viewModel; + internal ResourceTableDataSource (ResourceSelectorViewModel resourceSelectorViewModel) + { + if (resourceSelectorViewModel == null) + throw new ArgumentNullException (nameof (resourceSelectorViewModel)); + + this.viewModel = resourceSelectorViewModel; + } + + public ResourceSelectorViewModel ViewModel => this.viewModel; + public nint ResourceCount => this.viewModel.Resources.Count; + + public override nint GetRowCount (NSTableView tableView) + { + return ResourceCount; + } + } +} diff --git a/Xamarin.PropertyEditing.Mac/Controls/RequestResource/ResourceTableDelegate.cs b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/ResourceTableDelegate.cs new file mode 100644 index 0000000..829f405 --- /dev/null +++ b/Xamarin.PropertyEditing.Mac/Controls/RequestResource/ResourceTableDelegate.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using AppKit; +using Foundation; +using Xamarin.PropertyEditing.Drawing; +using Xamarin.PropertyEditing.ViewModels; + +namespace Xamarin.PropertyEditing.Mac +{ + internal class ResourceTableDelegate + : NSTableViewDelegate + { + private ResourceTableDataSource datasource; + + const string iconIdentifier = "icon"; + const string typeIdentifier = "type"; + const string nameIdentifier = "name"; + const string valueIdentifier = "value"; + + public event EventHandler ResourceSelected; + + private nint previousRow = -1; + + public ResourceTableDelegate (ResourceTableDataSource resourceTableDatasource) + { + this.datasource = resourceTableDatasource; + } + + // the table is looking for this method, picks it up automagically + public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row) + { + var resource = datasource.ViewModel.Resources [(int)row] as Resource; + + // Setup view based on the column + switch (tableColumn.Identifier) { + case RequestResourcePanel.ResourceImageColId: + if (resource.Source.Type != ResourceSourceType.Application) { + var iconView = (NSImageView)tableView.MakeView (iconIdentifier, this); + if (iconView == null) { + iconView = new NSImageView { + Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("resource-editor-32"), + ImageScaling = NSImageScale.None, + Identifier = iconIdentifier, + }; + } + return iconView; + } + + // If we get here its a local resource + return new NSView (); + + case RequestResourcePanel.ResourceTypeColId: + var typeView = (UnfocusableTextField)tableView.MakeView (typeIdentifier, this); + if (typeView == null) { + typeView = new UnfocusableTextField { + Identifier = typeIdentifier, + BackgroundColor = NSColor.Clear, + }; + } + + typeView.StringValue = resource.Source.Name; + return typeView; + + case RequestResourcePanel.ResourceNameColId: + var nameView = (UnfocusableTextField)tableView.MakeView (nameIdentifier, this); + if (nameView == null) { + nameView = new UnfocusableTextField { + BackgroundColor = NSColor.Clear, + Identifier = nameIdentifier, + }; + } + + nameView.StringValue = resource.Name; + return nameView; + + case RequestResourcePanel.ResourceValueColId: + var valueView = MakeValueView (resource, tableView); + + // If still null we have no editor yet. + valueView = valueView ?? new NSView (); + + return valueView; + + default: + return base.GetViewForItem (tableView, tableColumn, row); + } + } + + public override nfloat GetRowHeight (NSTableView tableView, nint row) + { + return PropertyEditorControl.DefaultControlHeight; + } + + public override void SelectionDidChange (NSNotification notification) + { + if (notification.Object is NSTableView tableView) { + if (previousRow != -1 + && previousRow < tableView.RowCount + && tableView.GetView (0, previousRow, false) is NSImageView previousIconColumn) { + previousIconColumn.Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("resource-editor-32"); + } + + if (tableView.SelectedRow != -1 + && tableView.GetView (0, tableView.SelectedRow, false) is NSImageView selectedIconColumn) { + selectedIconColumn.Image = PropertyEditorPanel.ThemeManager.GetImageForTheme ("resource-editor-32", "~sel"); + previousRow = tableView.SelectedRow; + } + } + ResourceSelected?.Invoke (this, EventArgs.Empty); + } + + private NSView MakeValueView (Resource resource, NSTableView tableView) + { + var view = (NSView)tableView.MakeView (valueIdentifier, this); + if (view == null) { + view = GetValueView (resource.RepresentationType); + } + + CommonBrush commonBrush = BrushPropertyViewModel.GetCommonBrushForResource (resource); + + if (commonBrush != null && view is CommonBrushView commonBrushView) { + commonBrushView.Brush = commonBrush; + } + + return view; + } + + NSView GetValueView (Type representationType) + { + Type[] genericArgs = null; + Type valueRenderType; + if (!ValueTypes.TryGetValue (representationType, out valueRenderType)) { + if (representationType.IsConstructedGenericType) { + genericArgs = representationType.GetGenericArguments (); + var type = representationType.GetGenericTypeDefinition (); + ValueTypes.TryGetValue (type, out valueRenderType); + } + } + if (valueRenderType == null) + return null; + + if (valueRenderType.IsGenericTypeDefinition) { + if (genericArgs == null) + genericArgs = representationType.GetGenericArguments (); + valueRenderType = valueRenderType.MakeGenericType (genericArgs); + } + + return SetUpRenderer (valueRenderType); + } + + // set up the editor based on the type of view model + private NSView SetUpRenderer (Type valueRenderType) + { + var view = (NSView)Activator.CreateInstance (valueRenderType); + + return view; + } + + internal static readonly Dictionary<Type, Type> ValueTypes = new Dictionary<Type, Type> { + {typeof (CommonSolidBrush), typeof (CommonBrushView)}, + {typeof (CommonColor), typeof (CommonBrushView)}, + }; + } +} |