From 30ff03b0a6c62aade8cc2b347f0e316e9709a8ce Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 26 Jan 2017 15:33:15 +0000 Subject: [PATCH] MacOS (#650) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * [MacOS] Add SwitchRenderer * [MacOS] Add TimePickerRenderer * [MacOS] Cleanup TimePcikerRender * [MacOS] Add WebViewRenderer * [MacOS] Add Javascript evaluate to webview * [MacOS] Fix build error on WebViewRenderer * [MacOS] Add Base and TextCell renderers * [MacOS] Start on ListViewRenderer * [MacOS] Cleanup * [MacOS] Vertical center text on default NSTextField * [MacOS] Center NSTextField vertically * [MacOS] Add ImageCellRenderer * [MacOS] Add SwitchCellRenderer * [MacOS] Add SwitchCellRenderer * [iOS] Allow to set background color on other CellRenderers * [MacOS] Fix selection mode on ListView * [MacOS] Set background on Entry of entry cell * [MacOS] Fix casting bug on CellRenderer * [MacOS] Other fix on CellRenderer background * [MacOS] Add ViewCellRenderer * [MacOS] Fixes and cleanup on cells * [MacOS] Add NSScrollView so NSTableView can scroll * [MacOS] Add HeaderView to ListView * [MacOS] Cleanup * [Controls] Add Header support to ListView * [MacOS] NSView reuse on NSTableView * [MacOS] Some fix on layour order * [MacOS] Add CarouselPageRenderer * [MacOS] Implement EventTracker on PageRenderer * [MacOS] Cleanup CarouselPageRenderer * [MacOS] Add MasterDetailPage renderer * [MacOS] MDP renderer don't allow drag of splitter * [MacOS] Add TabbedPage renderer * [MacOS] Initial sketch of NavigationPageRenderer * [MacOS] Send disappearing of CurrentPage on Dispose on NavigationPageRenderer * [MacOS] Add Gallery page for Mac * [MacOS] Add MacOSExpressionSearch * [MacOS] Fix ColorExtension * [MacOS] Fix MDP renderer layout * [MacOS] Implement native selection on ListViewRenderer * [MacOS] Deselect a item on NSTableView * [MacOS] Remove previous SplitViewItems * [MacOS] Fix navigationpage height * [MacOS] Add toolbar for NavigationPageRenderer * [MacOS] Don't remove selection for now (crashing) * [MacOS] Refactor page and back button title on NavigationPageRenderer * [MacOS] Fix bug when native navigate back * [MacOS] Hide layer when transition * [MacOS] ListviewRenderer fix BbackgroundColor * [MacOS] Fix background on ScrollViewRenderer * [MacOS] Fix header measure on ListViewRenderer * [MacOS] Add Mac twitter demo * [Controls] Spaces for easy reading * [MacOS] More xaml cleanup * [Core] Add Mac as aTargetPlatform * [MacOS] Add alerts and actionsheets * [MacOS] Add GestureRecognizers * [MacOS] Fix Layout issues when adding children, enable transformations * [MacOS] Fix title on tab item, move to tabbed navigation based on segmented control * [MacOS] Hide toolbar when not needed, this allows to work with tabbed page, cleanup * [MacOS] Add NativeBindings and NativeViewWrapper * [MacOS] Fix AssemblyInfo * [MacOS] FIX NRE on SetBackgroundColor BoxView * [MacOS] Fix NavigationPageRenderer * [MacOS] Fix build * [MacOS] Also update page when it resizes * [MacOS] Add LayoutRenderer for handle items position when the bounds change. * [MacOS] Refactor/Cleanup * [MacOS] Add toolbar items support to NavigationPage * [MacOS] Resize images for TabViewITems * [MacOS] Fix TabbedPage resize issues , allow users to override some features when creating TVI * [MacOS] Fix hide/show Navigation toolbar * [MacOS] Redo CarouselPageRenderer with NSPageController * [MacOS] Add support for Modal pages * [MacOS] Refactor navigation from platform * [Nuget] Add nuget for MacOS * [Nuget]Fix nuspec * [Nuget] Add variables for CI * [Controls] Remove MainMenu from MacOS * [MacOS] Add TableView renderer (no headers yet) * [MacOS] Refactoring, marking extensions as internal * [MacOS] Add group headers for TableViewRenderer * [MacOS] Workaround for updates on listview collection * [MacOS] Handle updates of rows in the ListViewRenderer properly * [MacOS] Fix navigation animation * Fix navigation header issues with modal pages * [MacOS] Fix MDP issues with resizing * [MacOS] Fix general dispose * [MacOS] Add a ViewControllerWrapper for NSSplitView * [MacOS] MDP renderer fix animation * [MacOS] Fix ListView selection bug * [MacOS] Fix rendering MDP Layout inside wrappers * [MacOS] Re write the MainToolbar handler * [MacOS] Don't use Sierra new extensions so we can run in stable channel * [MacOS] Another way to hide the toolbar (smarter i think) * [MacOS] Fix MDP bug and remove debug color * [Controls] Add HanselForms sample * [MacOS] Fix NRE WebviewRenderer * [MacOS] Fix uneven rows on ListView renderer * [MacOS] Fix NRE on load (can+t find the reason this happens) * [MacOS] Fix uneven rows * [MacOS] Fix header sizing on ListViewRenderer * [Controls] More stuff on HanselForms * [MacOS] Remove warning from ListViewRenderer * [MacOS] Fix PageRenderer bug double init * [MacOS] Don't calculate height if RowHeight is provided * [Controls] More Hanselforms stuff * [MacOS] Once again a new implementation for the NavigationBar, this time using a custom view to support BackgroundColor * [MacOS] Fix build * [MacOS] Refactoring AwesomeBar related controls * Fix build * [MacOS] NavigationBar update background and t test colors * [MacOS] Fix when we remove navigation so it works when the NavigationRenderer wasn't removed from the parent controller like in a TabbedPage * [MacOS] Add support for ListView grouping * [MacOS] Fix image extension method. * [MacOS] Add base Maps project * [MacOS] Export MapRenderer * [MacOS] Add pin click and geocoderbacked for Maps * [MacOS] Add extra binding project for API not in stable. * [MacOS] Add MacOS Maps lib * [MacOS]Fix build on alpha * [MacOS] Remove MacOS Maps extra binding * [UITest] Basic macOS setup * [UITest] Add MacOSApp wrapper implementation * [MacOS] Set AutomationID * [UITests] Add ActionSheetUITests to MacOS UITest * [MacOS] Fix bug on Picker * [UITests] Link basic uitest basefixture and related files * [MacOS] Fix pickers reuse * [UItests] Fix MacOS app path * [UITest] Ignore UItest for appearing on macOS for now * [UITest] Update macOS for 2.0.3 * [UITest] Refactor EnterText MacOS app * [UITest]Fix ViewQuery on MacOS * [UITest]Fix IsEnabled UItest on macOS * [UITest] Implement Enter, mark some tests inconclusive fix others * [MacOS] Implement Entry Completed event * [UITests] Fix UITest for IsVisible, ignore ToolbarItem test for now * [UITests] Fix ISVisible again add extra category * [Controls] Cleanup macOS gallery * [MacOS] Fix Assembly info * [Docs] Fix docs * Fix build * [Nuget] Fix nuspec * [Controls] Link files on MacOS * [Core] Update Forms stack before firing a event saying page was removed, possible breaking change * [MacOS] Implement RemovePage on NavigationPAgeRenderer * [UItest] Ignore some , implement back on MacOS UITest app * [MacOS] Add default back button name (needs to be translated) * [MacOS] Fix dispose * [UITest] Make 29257 work on MacOS * [MacOS] Rename stuff * [MacOS] More renaming and cleanup * [MacOS] Share implementations for iOS * [MacOS] Reuse more IOS extensions * [MacOS] Reuse FontExtensions * [MacOS] Share NativeViewWrapper related stuff * [MAcOS] Share event args and ExportRenderer * [MacOS] Share platform effect * [MacOS] Fix build * [Docs]Fixing docs * [MacOS] Fix ViewCell reuse * [Core] Support ListView CachingStrategy on MacOS * [MacOS] Fix issues with TextCell and ImageCell (we can’t set null to a NSControl value) * [MacOS] Fix MDP child sizing bug [UITest] Query marked by id and text * [MacOS] Comment test related with context actions * [MacOS] Implement missing stuff on ticker * [MacOS] Make sure VisualElemenTracker calls the ticker update * [UITests]Ignore context actions and not possible to test * [MacOS] Fix Grouping bug on Listview * [MacOS] Fix selection on Listview when using grouping * [MacOS] Update navbar when page is popped * [MacOS] Cleanup NavigationBar * [Controls] More info on exceptions * [MacOS] Fix bug animation pop modal * [MacOS] Bring back BackgroundColor of NavigationBar * [MacOS] Fix UITest animation delay * [MacOS] Treat warnings as errors * [MacOS] Center title on toolbar * [Core] Add Platform configuration specific for MacOS * [MacOS] Implement TabbedPage platform specific to handle TabItems on NavigationPage bar * [MacOS] Fix warning * [MacOS] Fix bug on SearchBar color * [MacOS]Fix build * [MacOS] remove extra dll from maps * [Docs] Update docs * [MacOS]Cleanup and refactoring * Revert "[MacOS] remove extra dll from maps" This reverts commit 73b948937001fea3f28449a963d0b94943e07aa0. * [MacOS] Fix wrong refactoring * [MacOS] Remove gallery and uitest project * [MacOS] dix formatting * [MacOS] Remove extra stuff * Merge branch 'master' into macOS-gallery * [MacOS] Fix rebase * [MacOS] Fix TargetPlatform * fix docs * [MacOS] Fix bug on TabbedPageRenderer no title * [MacOS] Remove FormsNSView * [MacOS] Cleanup on dispose on MDP renderer * [MacOS] Update current page when source changes * [MacOS] More cleanup * [MacOS] Make sure we show the previous page when popping a Modal * [MacOS] Fix issue with sizing the Header and visibility, remove for now header renderer reuse * [MacOS] Clean CustomNSTableView * [MacOS] Share LabelRenderer with iOS * [MacOS] Share ResourcesProvider with iOS * [MAcoS] Share VisualElementPackager with iOS * [MacOS] Share ViewRenderer with iOS * [MacOS] Merge with VisualElementTracker from iOS * [MacOS] Merge with EventTracker from iOS * [MacOS] Merge with VisualElementRenderer of iOS * [MacOS] Make sure we always have a layer * [MacOS] Fix Tracker merge with iOS version * [MacOS] Fix bug with tabbed page on modal without navigation * [Core] Rever change on core * [MacOS] Clear renderers before setting them MDP * [MacOS] Update tabbedPage ContainerArea * [MacOS] Fix ListViewRenderer * [MacOS] Make sure we don’t pass null to TextField string value * [MacOS] Support for multiple clicks in same selected item on NSTableView * [MacOS] Support Focus on EntryRenderer * [MacOS] Fix index bug on TablevIewDataSource * [MacOS] Fix SelectedItem TableViewDataSource * [Nuget] Add Mac to Maps nuspec * [Nuget]Fix path * [macOS] Fixed Tab NSImage crash in TabbedPageRenderer (#705) * [macOS] Fixed Tab NSImage crash in TabbedPageRenderer * Coding Style * Coding Style * [MacOS] Fix previous merge with master * [MacOS] Possible simple fix for click on views overlapping * [MacOS] Rename to IsOnViewCell * [MacOS] Cleanup, Address feedback from Samantha’s review --- .nuspec/Xamarin.Forms.Maps.nuspec | 15 +- .nuspec/Xamarin.Forms.nuspec | 14 +- .../GalleryPages/MacOSTestGallery.cs | 401 +++++++++++++++++++ Xamarin.Forms.Core/Device.cs | 7 +- Xamarin.Forms.Core/ListView.cs | 2 +- .../PlatformConfiguration/ExtensionPoints.cs | 1 + .../macOSSpecific/TabbedPage.cs | 50 +++ .../macOSSpecific/TabsStyle.cs | 12 + Xamarin.Forms.Core/Properties/AssemblyInfo.cs | 3 +- Xamarin.Forms.Core/TargetPlatform.cs | 2 +- Xamarin.Forms.Core/Xamarin.Forms.Core.csproj | 5 + Xamarin.Forms.Maps.MacOS.Extra/ApiDefinition.cs | 33 ++ .../Properties/AssemblyInfo.cs | 34 ++ .../Xamarin.Forms.Maps.MacOS.Extra.csproj | 43 +++ .../Libs/Xamarin.Forms.Maps.MacOS.Extra.dll | Bin 0 -> 13312 bytes .../Properties/AssemblyInfo.cs | 16 + .../Xamarin.Forms.Maps.macOS.csproj | 93 +++++ Xamarin.Forms.Maps.iOS/FormsMaps.cs | 7 +- Xamarin.Forms.Maps.iOS/GeocoderBackend.cs | 35 +- Xamarin.Forms.Maps.iOS/MapPool.cs | 4 + Xamarin.Forms.Maps.iOS/MapRenderer.cs | 76 +++- Xamarin.Forms.Maps/Properties/AssemblyInfo.cs | 1 + .../CADisplayLinkTicker.cs | 78 ++++ Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs | 167 ++++++++ Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs | 68 ++++ .../Cells/EntryCellRenderer.cs | 144 +++++++ .../Cells/ImageCellRenderer.cs | 68 ++++ .../Cells/NSTableViewCellStyle.cs | 12 + .../Cells/SwitchCellRenderer.cs | 88 +++++ .../Cells/TextCellRenderer.cs | 66 ++++ .../Cells/ViewCellNSView.cs | 105 +++++ .../Cells/ViewCellRenderer.cs | 46 +++ .../Controls/FormsImageView.cs | 16 + .../Controls/FormsPageControllerDelegate.cs | 29 ++ .../Controls/MacOSOpenGLView.cs | 12 + .../Controls/NSToolbarItemGroup.cs | 102 +++++ .../Controls/NavigationChildPageWrapper.cs | 41 ++ .../Controls/ScrollViewScrollChangedEventArgs.cs | 10 + .../Controls/VerticallyCenteredTextFieldCell.cs | 36 ++ .../Extensions/AlignmentExtensions.cs | 21 + .../Extensions/ButtonExtensions.cs | 25 ++ .../Extensions/NSButtonExtensions.cs | 25 ++ .../Extensions/NSImageExtensions.cs | 22 ++ .../Extensions/NSScrollViewExtensions.cs | 36 ++ .../Extensions/NSTableViewExtensions.cs | 22 ++ .../Extensions/NSTextFieldExtensions.cs | 46 +++ .../Extensions/NSViewControllerExtensions.cs | 24 ++ .../Extensions/PageExtensions.cs | 28 ++ .../FormsApplicationDelegate.cs | 85 ++++ .../ImageSourceHandlers.cs | 64 +++ Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs | 118 ++++++ .../NativeToolbarTracker.cs | 429 +++++++++++++++++++++ Xamarin.Forms.Platform.MacOS/Platform.cs | 262 +++++++++++++ Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs | 113 ++++++ Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs | 50 +++ .../Properties/AssemblyInfo.cs | 53 +++ .../Renderers/ActivityIndicatorRenderer.cs | 70 ++++ .../Renderers/BoxViewRenderer.cs | 39 ++ .../Renderers/ButtonRenderer.cs | 131 +++++++ .../Renderers/CarouselPageRenderer.cs | 229 +++++++++++ .../Renderers/CustomNSTableHeaderView.cs | 46 +++ .../Renderers/DatePickerRenderer.cs | 138 +++++++ .../Renderers/DefaultRenderer.cs | 6 + .../Renderers/EditorRenderer.cs | 128 ++++++ .../Renderers/EntryRenderer.cs | 205 ++++++++++ .../Renderers/FrameRenderer.cs | 57 +++ .../Renderers/ImageRenderer.cs | 117 ++++++ .../Renderers/LayoutRenderer.cs | 39 ++ .../Renderers/ListViewDataSource.cs | 299 ++++++++++++++ .../Renderers/ListViewRenderer.cs | 342 ++++++++++++++++ .../Renderers/MasterDetailPageRenderer.cs | 204 ++++++++++ .../Renderers/NSPageContainer.cs | 17 + .../Renderers/NavigationPageRenderer.cs | 355 +++++++++++++++++ .../Renderers/OpenGLViewRenderer.cs | 104 +++++ .../Renderers/PageControllerDelegate.cs | 30 ++ .../Renderers/PageRenderer.cs | 182 +++++++++ .../Renderers/PickerRenderer.cs | 154 ++++++++ .../Renderers/ProgressBarRenderer.cs | 66 ++++ .../Renderers/ScrollViewRenderer.cs | 214 ++++++++++ .../Renderers/SearchBarRenderer.cs | 179 +++++++++ .../Renderers/SliderRenderer.cs | 77 ++++ .../Renderers/StepperRenderer.cs | 84 ++++ .../Renderers/SwitchRenderer.cs | 61 +++ .../Renderers/TabbedPageRenderer.cs | 403 +++++++++++++++++++ .../Renderers/TableViewDataSource.cs | 131 +++++++ .../Renderers/TableViewRenderer.cs | 98 +++++ .../Renderers/TimePickerRenderer.cs | 104 +++++ .../Renderers/WebViewRenderer.cs | 149 +++++++ .../Xamarin.Forms.Platform.macOS.csproj | 239 ++++++++++++ Xamarin.Forms.Platform.iOS/Deserializer.cs | 5 + Xamarin.Forms.Platform.iOS/EffectUtilities.cs | 6 + .../ElementChangedEventArgs.cs | 8 +- Xamarin.Forms.Platform.iOS/EventTracker.cs | 110 ++++-- .../ExportRendererAttribute.cs | 12 +- .../Extensions/ColorExtensions.cs | 47 ++- .../Extensions/DateExtensions.cs | 5 + .../Extensions/LayoutExtensions.cs | 21 +- .../Extensions/PlatformConfigurationExtensions.cs | 11 +- .../Extensions/UIViewExtensions.cs | 45 ++- Xamarin.Forms.Platform.iOS/Forms.cs | 57 ++- .../IVisualElementRenderer.cs | 13 +- .../NativeValueConverterService.cs | 13 +- .../NativeViewPropertyListener.cs | 7 +- Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs | 12 +- .../NativeViewWrapperRenderer.cs | 30 +- Xamarin.Forms.Platform.iOS/PlatformEffect.cs | 9 +- Xamarin.Forms.Platform.iOS/RendererPool.cs | 8 +- .../Renderers/FontExtensions.cs | 108 +++++- .../Renderers/FormattedStringExtensions.cs | 35 +- .../Renderers/LabelRenderer.cs | 97 ++++- Xamarin.Forms.Platform.iOS/ResourcesProvider.cs | 16 +- .../ViewInitializedEventArgs.cs | 9 +- Xamarin.Forms.Platform.iOS/ViewRenderer.cs | 52 ++- .../VisualElementPackager.cs | 10 +- .../VisualElementRenderer.cs | 94 +++-- Xamarin.Forms.Platform.iOS/VisualElementTracker.cs | 61 ++- Xamarin.Forms.sln | 63 +++ .../TabbedPage.xml | 176 +++++++++ .../Xamarin.Forms.PlatformConfiguration/macOS.xml | 35 ++ docs/Xamarin.Forms.Core/Xamarin.Forms/Device.xml | 15 + .../Xamarin.Forms.Core/Xamarin.Forms/TabsStyle.xml | 87 +++++ docs/Xamarin.Forms.Core/index.xml | 115 ++++++ ...n.Forms.PlatformConfiguration.macOSSpecific.xml | 6 + docs/Xamarin.Forms.Maps/index.xml | 2 +- 124 files changed, 9298 insertions(+), 164 deletions(-) create mode 100644 Xamarin.Forms.Controls/GalleryPages/MacOSTestGallery.cs create mode 100644 Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabbedPage.cs create mode 100644 Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabsStyle.cs create mode 100644 Xamarin.Forms.Maps.MacOS.Extra/ApiDefinition.cs create mode 100644 Xamarin.Forms.Maps.MacOS.Extra/Properties/AssemblyInfo.cs create mode 100644 Xamarin.Forms.Maps.MacOS.Extra/Xamarin.Forms.Maps.MacOS.Extra.csproj create mode 100755 Xamarin.Forms.Maps.MacOS/Libs/Xamarin.Forms.Maps.MacOS.Extra.dll create mode 100644 Xamarin.Forms.Maps.MacOS/Properties/AssemblyInfo.cs create mode 100644 Xamarin.Forms.Maps.MacOS/Xamarin.Forms.Maps.macOS.csproj create mode 100644 Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs create mode 100644 Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs create mode 100644 Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs create mode 100644 Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs create mode 100644 Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Platform.cs create mode 100644 Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs create mode 100644 Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj create mode 100644 docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration.macOSSpecific/TabbedPage.xml create mode 100644 docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration/macOS.xml create mode 100644 docs/Xamarin.Forms.Core/Xamarin.Forms/TabsStyle.xml create mode 100644 docs/Xamarin.Forms.Core/ns-Xamarin.Forms.PlatformConfiguration.macOSSpecific.xml diff --git a/.nuspec/Xamarin.Forms.Maps.nuspec b/.nuspec/Xamarin.Forms.Maps.nuspec index 9e7aef4..7221c1c 100644 --- a/.nuspec/Xamarin.Forms.Maps.nuspec +++ b/.nuspec/Xamarin.Forms.Maps.nuspec @@ -30,7 +30,7 @@ - + @@ -56,11 +56,16 @@ + + + + + - - + + @@ -127,6 +132,10 @@ + + + + diff --git a/.nuspec/Xamarin.Forms.nuspec b/.nuspec/Xamarin.Forms.nuspec index ce12380..879280a 100644 --- a/.nuspec/Xamarin.Forms.nuspec +++ b/.nuspec/Xamarin.Forms.nuspec @@ -76,6 +76,12 @@ + + + + + + @@ -219,10 +225,16 @@ - + + + + + + + diff --git a/Xamarin.Forms.Controls/GalleryPages/MacOSTestGallery.cs b/Xamarin.Forms.Controls/GalleryPages/MacOSTestGallery.cs new file mode 100644 index 0000000..3b44683 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/MacOSTestGallery.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Forms.Controls +{ + public class MacOSTestGallery : ContentPage + { + public MacOSTestGallery() + { + mainDemoStack.Children.Add(MakeNewStackLayout()); + var items = new List(); + for (int i = 0; i < 5000; i++) + { + items.Add(new MyItem1 { Reference = "Hello this is a big text " + i.ToString(), ShowButton = i % 2 == 0, Image = "bank.png" }); + } + + var header = new Label { Text = "HELLO HEADER ", FontSize = 40, BackgroundColor = Color.Pink }; + var lst4 = new ListView { Header = header, ItemTemplate = new DataTemplate(typeof(DemoViewCell)), BackgroundColor = Color.Yellow, HeightRequest = 300, RowHeight = 50, ItemsSource = items, }; + + var lst = new ListView { ItemTemplate = new DataTemplate(typeof(DemoEntryCell)), BackgroundColor = Color.Yellow, HeightRequest = 300, RowHeight = 50, ItemsSource = items, }; + var lst1 = new ListView { ItemTemplate = new DataTemplate(typeof(DemoTextCell)), BackgroundColor = Color.Yellow, HeightRequest = 300, RowHeight = 50, ItemsSource = items, }; + var lst2 = new ListView { ItemTemplate = new DataTemplate(typeof(DemoSwitchCell)), BackgroundColor = Color.Yellow, HeightRequest = 300, RowHeight = 50, ItemsSource = items, }; + var lst3 = new ListView { ItemTemplate = new DataTemplate(typeof(DemoImageCell)), BackgroundColor = Color.Yellow, HeightRequest = 300, RowHeight = 50, ItemsSource = items, }; + + var bigbUtton = new Button { WidthRequest = 200, HeightRequest = 300, Image = "bank.png" }; + + var picker = new DatePicker(); + + var timePicker = new TimePicker { Format = "T", Time = TimeSpan.FromHours(2) }; + + var editor = new Editor { Text = "Edit this text on editor", HeightRequest = 100, TextColor = Color.Yellow, BackgroundColor = Color.Gray }; + + var entry = new Entry { Placeholder = "Edit this text on entry", PlaceholderColor = Color.Pink, TextColor = Color.Yellow, BackgroundColor = Color.Green }; + + var frame = new Frame { HasShadow = true, BackgroundColor = Color.Maroon, OutlineColor = Color.Lime, MinimumHeightRequest = 100 }; + + + var image = new Image { HeightRequest = 100, Source = "crimson.jpg" }; + + var picker1 = new Picker { Title = "Select a team player", TextColor = Color.Pink, BackgroundColor = Color.Silver }; + picker1.Items.Add("Rui"); + picker1.Items.Add("Jason"); + picker1.Items.Add("Ez"); + picker1.Items.Add("Stephane"); + picker1.Items.Add("Samantha"); + picker1.Items.Add("Paul"); + + picker1.SelectedIndex = 1; + + var progress = new ProgressBar { BackgroundColor = Color.Purple, Progress = 0.5, HeightRequest = 50 }; + + picker1.SelectedIndexChanged += (sender, e) => + { + entry.Text = $"Selected {picker1.Items[picker1.SelectedIndex]}"; + + progress.Progress += 0.1; + }; + + var searchBar = new SearchBar { BackgroundColor = Color.Olive, TextColor = Color.Maroon, CancelButtonColor = Color.Pink }; + searchBar.Placeholder = "Please search"; + searchBar.PlaceholderColor = Color.Orange; + searchBar.SearchButtonPressed += (sender, e) => + { + searchBar.Text = "Search was pressed"; + }; + + var slider = new Slider { BackgroundColor = Color.Lime, Value = 0.5 }; + + slider.ValueChanged += (sender, e) => + { + editor.Text = $"Slider value changed {slider.Value}"; + }; + + var stepper = new Stepper { BackgroundColor = Color.Yellow, Maximum = 100, Minimum = 0, Value = 10, Increment = 0.5 }; + + stepper.ValueChanged += (sender, e) => + { + editor.Text = $"Stepper value changed {stepper.Value}"; + }; + + var labal = new Label { Text = "This is a Switch" }; + var switchR = new Switch { BackgroundColor = Color.Fuchsia, IsToggled = true }; + switchR.Toggled += (sender, e) => + { + entry.Text = $"switchR is toogle {switchR.IsToggled}"; + }; + var layoutSwitch = new StackLayout { Orientation = StackOrientation.Horizontal, BackgroundColor = Color.Green }; + layoutSwitch.Children.Add(labal); + layoutSwitch.Children.Add(switchR); + + var webView = new WebView { HeightRequest = 200, Source = "http://google.pt" }; + + var mainStck = new StackLayout + { + Spacing = 10, + BackgroundColor = Color.Blue, + VerticalOptions = LayoutOptions.Center, + HorizontalOptions = LayoutOptions.Center, + Children = + { + lst4, + lst, + lst1, + lst2, + lst3, + webView, + layoutSwitch, + stepper, + slider, + searchBar, + progress, + picker1, + image, + frame, + entry, + editor, + picker, + timePicker, + bigbUtton, + new Button { Text = "Click Me", BackgroundColor = Color.Gray }, + new Button { Image = "bank.png", BackgroundColor = Color.Gray }, + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Left, 10)), + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Top, 10)), + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Bottom, 10)), + CreateButton(new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Right, 10)), + mainDemoStack + } + }; + var lbl = new Label { Text = "Second label", TextColor = Color.White, VerticalTextAlignment = TextAlignment.Start, HorizontalTextAlignment = TextAlignment.Center }; + mainStck.Children.Add(new Label { Text = "HELLO XAMARIN FORMS MAC", TextColor = Color.White, HorizontalTextAlignment = TextAlignment.Center }); + mainStck.Children.Add(lbl); + mainStck.Children.Add(new BoxView { Color = Color.Pink, HeightRequest = 200 }); + + var scroller = new ScrollView { BackgroundColor = Color.Yellow, HorizontalOptions = LayoutOptions.Center }; + + scroller.Scrolled += (sender, e) => + { + lbl.Text = $"Current postion {scroller.ScrollY}"; + }; + + scroller.Content = mainStck; + + var actv = new ActivityIndicator { BackgroundColor = Color.White, Color = Color.Fuchsia, IsRunning = true }; + mainStck.Children.Add(actv); + + bigbUtton.Clicked += async (sender, e) => + { + await scroller.ScrollToAsync(actv, ScrollToPosition.Center, true); + actv.Color = Color.Default; + }; + + Content = scroller; + } + + public static ContentPage MacDemoContentPage() + { + return new MacOSTestGallery(); + } + + public static NavigationPage MacDemoNavigationPage() + { + var np = new NavigationPage(GetNewPage()) { BarTextColor = Color.Red, BarBackgroundColor = Color.Yellow }; + + np.Pushed += (sender, e) => System.Diagnostics.Debug.WriteLine("Pushed + " + np.CurrentPage.Title); + + np.Popped += (sender, e) => System.Diagnostics.Debug.WriteLine("Popped"); + + np.PoppedToRoot += (sender, e) => System.Diagnostics.Debug.WriteLine("Popped to root"); + + return np; + } + + public static TabbedPage MacDemoTabbedPage() + { + + var btnGo = new Button { Text = "Change Title" }; + var btnGo1 = new Button { Text = "Change Icon" }; + + var lyout = new StackLayout(); + lyout.Children.Add(btnGo); + //lyout.Children.Add(btnGo1); + + var tp = new TabbedPage { BarTextColor = Color.Red, BarBackgroundColor = Color.Yellow }; + + var master = new ContentPage { Icon = "bank.png", BackgroundColor = Color.Red, Title = "Master", Content = lyout }; + + var detail = new ContentPage { Icon = "bank.png", BackgroundColor = Color.Blue, Title = "Detail", Content = new Label { Text = "This is Detail Page" } }; + + tp.Children.Add(master); + tp.Children.Add(detail); + + tp.CurrentPage = detail; + + tp.CurrentPageChanged += (sender, e) => + { + System.Diagnostics.Debug.WriteLine(tp.CurrentPage.Title); + }; + + btnGo.Clicked += (sender, e) => + { + tp.CurrentPage.Title = "Tile changed"; + tp.CurrentPage.Icon = null; + }; + + btnGo1.Clicked += (sender, e) => + { + + }; + return tp; + } + + public static MasterDetailPage MacDemoMasterDetailPage() + { + var mdp = new MasterDetailPage(); + + var master = new ContentPage { BackgroundColor = Color.Red, Title = "Master", Content = new Label { Text = "This is Master Page" } }; + + var detail = new ContentPage { BackgroundColor = Color.Blue, Title = "Detail", Content = new Label { Text = "This is Detail Page" } }; + + mdp.Master = master; + mdp.Detail = detail; + + return mdp; + } + + public static CarouselPage MacDemoCarouselPage() + { + + var carouselPage = new CarouselPage { BackgroundColor = Color.Yellow }; + + var btnGo = new Button { Text = "Goto To Page 1 " }; + var btnGo1 = new Button { Text = "Goto To Page 3 " }; + var stck = new StackLayout(); + stck.Children.Add(btnGo); + stck.Children.Add(btnGo1); + var page = new ContentPage { Title = "Page1", BackgroundColor = Color.Red, Content = new Label { Text = "Page 1 label", TextColor = Color.White, VerticalTextAlignment = TextAlignment.Start, HorizontalTextAlignment = TextAlignment.Center } }; + var page2 = new ContentPage { Title = "Page2", BackgroundColor = Color.Blue, Content = stck }; + var page3 = new ContentPage { Title = "Page3", BackgroundColor = Color.Green, Content = new Label { Text = "Page 3 label" } }; + + carouselPage.Children.Add(page); + carouselPage.Children.Add(page2); + carouselPage.Children.Add(page3); + + carouselPage.CurrentPage = page2; + + btnGo.Clicked += (sender, e) => + { + carouselPage.CurrentPage = page; + }; + + btnGo1.Clicked += (sender, e) => + { + carouselPage.CurrentPage = page3; + }; + + carouselPage.CurrentPageChanged += (sender, e) => + { + System.Diagnostics.Debug.WriteLine(carouselPage.CurrentPage.Title); + }; + return carouselPage; + } + + static int _pageID; + + static StackLayout mainDemoStack = new StackLayout { BackgroundColor = Color.Blue }; + + static ContentPage GetNewPage() + { + var label = new Label { Text = $"Page {_pageID}" }; + var btnGo = new Button { Text = "Push Page" }; + var btnGo1 = new Button { Text = "Pop Page" }; + var lyout = new StackLayout(); + lyout.Children.Add(label); + lyout.Children.Add(btnGo); + lyout.Children.Add(btnGo1); + + btnGo.Clicked += async (sender, e) => + { + _pageID++; + await (lyout.Parent as Page).Navigation?.PushAsync(GetNewPage()); + + }; + + btnGo1.Clicked += async (sender, e) => + { + _pageID--; + await (lyout.Parent as Page).Navigation?.PopAsync(); + + }; + + return new ContentPage { Icon = "bank.png", BackgroundColor = _pageID % 2 == 0 ? Color.Blue : Color.Green, Title = label.Text, Content = lyout }; + } + + static StackLayout MakeNewStackLayout() + { + var count = 0; + var stacklayout = new StackLayout { BackgroundColor = Color.Red }; + + stacklayout.Children.Add(new Label { Text = $"HEllO {count}" }); + stacklayout.Children.Add(new Button + { + Text = "Change layout", + Command = new Command(() => + { + count += 2; + stacklayout.Children.RemoveAt(2); + + var ll = new StackLayout(); + ll.Children.Add(new Label { Text = $"HEllO {count}" }); + ll.Children.Add(new Label { Text = $"HEllO {count + 1}" }); + stacklayout.Children.Add(ll); + }) + }); + stacklayout.Children.Add(new Label { Text = $"HEllO {count + 1}" }); + count += 2; + return stacklayout; + } + + + + static Button CreateButton(Button.ButtonContentLayout layout) + { + return new Button + { + Text = "Click Me On Mac", + Image = "bank.png", + Font = Font.OfSize("Helvetica", 14), + ContentLayout = layout, + BackgroundColor = Color.Black, + TextColor = Color.White + }; + } + + class DemoSwitchCell : SwitchCell + { + public DemoSwitchCell() + { + SetBinding(TextProperty, new Binding("Reference")); + SetBinding(OnProperty, new Binding("ShowButton")); + } + } + + class DemoImageCell : ImageCell + { + public DemoImageCell() + { + SetBinding(TextProperty, new Binding("Reference")); + SetBinding(DetailProperty, new Binding("ShowButton")); + SetBinding(ImageSourceProperty, new Binding("Image")); + } + } + + class DemoTextCell : TextCell + { + public DemoTextCell() + { + SetBinding(TextProperty, new Binding("Reference")); + SetBinding(DetailProperty, new Binding("ShowButton")); + } + } + + class DemoEntryCell : EntryCell + { + public DemoEntryCell() + { + SetBinding(LabelProperty, new Binding("Reference")); + SetBinding(TextProperty, new Binding("ShowButton")); + LabelColor = Color.Red; + Placeholder = "This is a entry cell"; + } + } + + class DemoViewCell : ViewCell + { + public DemoViewCell() + { + var box = new Image { BackgroundColor = Color.Pink, WidthRequest = 100, HeightRequest = 40, Source = "bank.png" }; + var label = new Label { TextColor = Color.White }; + var labelDetail = new Label { TextColor = Color.White }; + + label.SetBinding(Label.TextProperty, new Binding("Reference")); + labelDetail.SetBinding(Label.TextProperty, new Binding("ShowButton")); + + var grid = new Grid { BackgroundColor = Color.Black }; + + grid.Children.Add(box, 0, 1, 0, 1); + grid.Children.Add(label, 1, 0); + grid.Children.Add(labelDetail, 1, 1); + + View = grid; + } + } + + public class MyItem1 + { + public string Reference { get; set; } + public string Image { get; set; } + public bool ShowButton { get; set; } + } + } +} diff --git a/Xamarin.Forms.Core/Device.cs b/Xamarin.Forms.Core/Device.cs index f18251d..ba83ca0 100644 --- a/Xamarin.Forms.Core/Device.cs +++ b/Xamarin.Forms.Core/Device.cs @@ -12,6 +12,7 @@ namespace Xamarin.Forms public const string Android = "Android"; public const string WinPhone = "WinPhone"; public const string Windows = "Windows"; + public const string macOS = "macOS"; internal static DeviceInfo info; @@ -21,8 +22,10 @@ namespace Xamarin.Forms [Obsolete("Use RuntimePlatform instead.")] #pragma warning disable 0618 - public static TargetPlatform OS { - get { + public static TargetPlatform OS + { + get + { TargetPlatform platform; if (Enum.TryParse(RuntimePlatform, out platform)) return platform; diff --git a/Xamarin.Forms.Core/ListView.cs b/Xamarin.Forms.Core/ListView.cs index 0751e1c..8f234cf 100644 --- a/Xamarin.Forms.Core/ListView.cs +++ b/Xamarin.Forms.Core/ListView.cs @@ -71,7 +71,7 @@ namespace Xamarin.Forms public ListView([Parameter("CachingStrategy")] ListViewCachingStrategy cachingStrategy) : this() { - if (Device.RuntimePlatform == Device.Android || Device.RuntimePlatform == Device.iOS) + if (Device.RuntimePlatform == Device.Android || Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.macOS) CachingStrategy = cachingStrategy; } diff --git a/Xamarin.Forms.Core/PlatformConfiguration/ExtensionPoints.cs b/Xamarin.Forms.Core/PlatformConfiguration/ExtensionPoints.cs index b74e9b9..fbddfeb 100644 --- a/Xamarin.Forms.Core/PlatformConfiguration/ExtensionPoints.cs +++ b/Xamarin.Forms.Core/PlatformConfiguration/ExtensionPoints.cs @@ -5,4 +5,5 @@ namespace Xamarin.Forms.PlatformConfiguration public sealed class iOS : IConfigPlatform { } public sealed class Windows : IConfigPlatform { } public sealed class Tizen : IConfigPlatform { } + public sealed class macOS : IConfigPlatform { } } diff --git a/Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabbedPage.cs b/Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabbedPage.cs new file mode 100644 index 0000000..3c51805 --- /dev/null +++ b/Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabbedPage.cs @@ -0,0 +1,50 @@ +namespace Xamarin.Forms.PlatformConfiguration.macOSSpecific +{ + using FormsElement = Forms.TabbedPage; + + public static class TabbedPage + { + #region TabsStyle + public static readonly BindableProperty TabsStyleProperty = BindableProperty.Create("TabsStyle", typeof(TabsStyle), typeof(TabbedPage), TabsStyle.Default); + + public static TabsStyle GetTabsStyle(BindableObject element) + { + return (TabsStyle)element.GetValue(TabsStyleProperty); + } + + public static void SetTabsStyle(BindableObject element, TabsStyle value) + { + element.SetValue(TabsStyleProperty, value); + } + + public static TabsStyle GetTabsStyle(this IPlatformElementConfiguration config) + { + return GetTabsStyle(config.Element); + } + + public static IPlatformElementConfiguration SetShowTabs(this IPlatformElementConfiguration config, TabsStyle value) + { + SetTabsStyle(config.Element, value); + return config; + } + + public static IPlatformElementConfiguration ShowTabsOnNavigation(this IPlatformElementConfiguration config) + { + SetTabsStyle(config.Element, TabsStyle.OnNavigation); + return config; + } + + public static IPlatformElementConfiguration ShowTabs(this IPlatformElementConfiguration config) + { + SetTabsStyle(config.Element, TabsStyle.Default); + return config; + } + + public static IPlatformElementConfiguration HideTabs(this IPlatformElementConfiguration config) + { + SetTabsStyle(config.Element, TabsStyle.Hidden); + return config; + } + #endregion + } +} diff --git a/Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabsStyle.cs b/Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabsStyle.cs new file mode 100644 index 0000000..0b498ae --- /dev/null +++ b/Xamarin.Forms.Core/PlatformConfiguration/macOSSpecific/TabsStyle.cs @@ -0,0 +1,12 @@ +using System; +namespace Xamarin.Forms +{ + public enum TabsStyle + { + Default = 0, + Hidden = 1, + Icons = 2, + OnNavigation = 3, + OnBottom = 4 + } +} diff --git a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs index 6bbdb60..8f50765 100644 --- a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs @@ -28,7 +28,7 @@ using Xamarin.Forms.Internals; [assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT.Tablet")] [assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WinRT.Phone")] [assembly: InternalsVisibleTo("Xamarin.Forms.Platform.WP8")] -[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.MacOS")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.macOS")] [assembly: InternalsVisibleTo("iOSUnitTests")] [assembly: InternalsVisibleTo("Xamarin.Forms.Controls")] [assembly: InternalsVisibleTo("Xamarin.Forms.Core.Design")] @@ -47,6 +47,7 @@ using Xamarin.Forms.Internals; [assembly: InternalsVisibleTo("Xamarin.Forms.Core.iOS.UITests")] [assembly: InternalsVisibleTo("Xamarin.Forms.Core.Android.UITests")] [assembly: InternalsVisibleTo("Xamarin.Forms.Core.Windows.UITests")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Core.macOS.UITests")] [assembly: InternalsVisibleTo("Xamarin.Forms.iOS.UITests")] [assembly: InternalsVisibleTo("Xamarin.Forms.Android.UITests")] [assembly: InternalsVisibleTo("Xamarin.Forms.Loader")] diff --git a/Xamarin.Forms.Core/TargetPlatform.cs b/Xamarin.Forms.Core/TargetPlatform.cs index 6125695..ff34d83 100644 --- a/Xamarin.Forms.Core/TargetPlatform.cs +++ b/Xamarin.Forms.Core/TargetPlatform.cs @@ -9,6 +9,6 @@ namespace Xamarin.Forms iOS, Android, WinPhone, - Windows, + Windows } } diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj index ebc9f8b..6b978ac 100644 --- a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj +++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj @@ -445,6 +445,8 @@ + + @@ -459,4 +461,7 @@ + + + \ No newline at end of file diff --git a/Xamarin.Forms.Maps.MacOS.Extra/ApiDefinition.cs b/Xamarin.Forms.Maps.MacOS.Extra/ApiDefinition.cs new file mode 100644 index 0000000..c6a92d9 --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS.Extra/ApiDefinition.cs @@ -0,0 +1,33 @@ +using CoreLocation; +using Foundation; + +namespace Xamarin.Forms.Maps.MacOS.Extra +{ + delegate void CLGeocodeCompletionHandler(CLPlacemark[] placemarks, NSError error); + + [BaseType(typeof(NSObject))] + interface CLGeocoder + { + [Export("isGeocoding")] + bool Geocoding { get; } + + [Export("reverseGeocodeLocation:completionHandler:")] + [Async] + void ReverseGeocodeLocation(CLLocation location, CLGeocodeCompletionHandler completionHandler); + + [Export("geocodeAddressDictionary:completionHandler:")] + [Async] + void GeocodeAddress(NSDictionary addressDictionary, CLGeocodeCompletionHandler completionHandler); + + [Export("geocodeAddressString:completionHandler:")] + [Async] + void GeocodeAddress(string addressString, CLGeocodeCompletionHandler completionHandler); + + [Export("geocodeAddressString:inRegion:completionHandler:")] + [Async] + void GeocodeAddress(string addressString, CLRegion region, CLGeocodeCompletionHandler completionHandler); + + [Export("cancelGeocode")] + void CancelGeocode(); + } +} diff --git a/Xamarin.Forms.Maps.MacOS.Extra/Properties/AssemblyInfo.cs b/Xamarin.Forms.Maps.MacOS.Extra/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9e5eaa0 --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS.Extra/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +using Foundation; + +// This attribute allows you to mark your assemblies as “safe to link”. +// When the attribute is present, the linker—if enabled—will process the assembly +// even if you’re using the “Link SDK assemblies only” option, which is the default for device builds. + +[assembly: LinkerSafe] + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("Xamarin.Forms.Maps.MacOS.Extra")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/Xamarin.Forms.Maps.MacOS.Extra/Xamarin.Forms.Maps.MacOS.Extra.csproj b/Xamarin.Forms.Maps.MacOS.Extra/Xamarin.Forms.Maps.MacOS.Extra.csproj new file mode 100644 index 0000000..af4f4aa --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS.Extra/Xamarin.Forms.Maps.MacOS.Extra.csproj @@ -0,0 +1,43 @@ + + + + Debug + AnyCPU + {6346D187-BA87-4F18-A165-F27C5B7F25D0} + {810C163F-4746-4721-8B8E-88A3673A62EA};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Xamarin.Forms.Maps.MacOS.Extra + Xamarin.Forms.Maps.MacOS.Extra + Resources + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + + prompt + 4 + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Maps.MacOS/Libs/Xamarin.Forms.Maps.MacOS.Extra.dll b/Xamarin.Forms.Maps.MacOS/Libs/Xamarin.Forms.Maps.MacOS.Extra.dll new file mode 100755 index 0000000000000000000000000000000000000000..0c17fff6801ad5f3b7450e0c4a0741c3c85780df GIT binary patch literal 13312 zcmeHOdvILkbwBsHt1DZUR(>q;gEg}8+Ljktwy|Ukku1qZV9ByqHg+CJtKI9>#k+f# zySt7Rz*!HFQb+)&19`v@9-Y!Q6Ox9cO`)ZZ>`d4$!o_o&u&Ue1^`X2XQG4i4NNg*N?zxUoF`X=st^$EBzD5JUM(q~%e z>rF4O{id}4<+bC}Rw0si?6i}}Mv{qK&Mro#%!pIUMXX$8@IXA0wNqwn@nU10r+RFN zXurf3RIk@aUfV1Mi8{g4UH&82;U2+n5(IOXne=tG>4=Ps>^tBa=C{{F26+!>*CIGYvUp>1)i7NuD zVot$Hf)ch(0^vl(@T zC6Wa9{ev}f6eAm(j(it4If{rHHcU3kP^iM!_AZ5P_0-Czo6r(907#aj&A#kumW{#_ zBf1zsj);@{m%1_{xpQSuSDf9t0JL{$)Qesyg|y>JL>kv|fPLIv>m%lco8ui77g4-$ksIn< zZ10k3H~d6FwKbaTSjE&Rf~Xrk-Wnj1AlMieY)*}i`5@6vjN)?cLx9BUR_3+B`BH`C ziRmbk-^x|dzc*7-(-waz*=FZ!ND`jpM=FZ!NBR{tbbG0D zm?ttNa7PYd`)P<6(aiw8+`820*@v4P?PN{_sOO$=uH-q#k^!R!+4Y+XP=k4jn7H%Q zFE$A9yu1;2%za*eQCPIJhoKUMxM9e*us^N?`Yu>S!X_c2iv>oV#dVh*dp!ozds&li z$Dj!&?1^8uNAgY-Tu&#r$GT$My0&fQ1qLf@2JrhZq~kQsArSZ!ejV|mW98BXmiPxO zFFnXs$Kg0F!ajh|QOCZ+!-Kf50{v}_*0CpJPq8pAh_>GF(Z^RevDxoP+X#h)l#AmK zkIxek6Pu3V$0l&#uELLHV1~Fu2kQ|y^D%n_j#LYIz7wSvaT{EvbMzm8hO$o4=^^ED zz&{Z1c|e1HsJsczyUGf+g?6fAfX4w1x1J9F`(0PfCjx1V43qmW_ghD z-XP0|4TinODlGKJ0CoC(gZq9%z?T6HdKWS}wKlV-8w4B_Fe%`z0zM$%R|R~j`CPM3 z=bJ-|UZC{|yiVH}tw6E5kz~lxYo#!(SS%>a2kNKGXe-VZOGrZVeo&tWcMX*>WeSvr zWGi?Y+CG8a+_sMHMB784xGjo`Y8B5hiz$S&l!CTru#GRKr8qb-Db>Lg+Ujet#n{qRz-KJnOMtB<>3Cl_|eExyxj*7(8kK6jlL` znr=Fx^2~Aj-3JN>29J8SX+m9FpIT|=8ISOyv>u1&jdaSRnvBn4 z9S&pBb=#g)JLw}H)nq)UZlUlxzwO&<7fpK99^-Yjo8I-Pl<|hTo%*nvK}%EFh{-$X zEMmupuGs!V4iHwLz7*Hoxn zAfkLwP`=JSk7AwO+MWvau+ggxRVdbZ9sSMa%s5LA7<;wrN!uVO5&2QtC@8k`ur^9# zg8CLs1eeeOx=TvP3tcgk_oroDWy%_F1hy=dPN*jd52J&mBTTRpw&& z8a=o`ueq%fRrO1BnZ_Qi6L2%2OjlQMiFmj4IgI<9`+zH+q$|T5iJqyEXI!Gb0BhI> zDA6xOD{oS@xI&}gs6tYr8$|0(HLzC0cfps19=I%IW^+`bO{>#3TSg-0_^`26wH7wU z+lW`aO9Wg?PbovRmi|uJkCk-0auB;WxAp`6TsjD-$j9h&YDi81UoKxlPpYfrCi;rH z7My3*jey@#V}SpxcFPw1w|XUTNxKR#q;&)G3Uo8z-SUjkc7{Hwy(FKd$F#qf&(N2( zC3wHWBQBzi`oGJ6L}U7ufFIQ(%AX2te~H$IltpxD;4#1}15Ya71Lw=izX9GY^!!*k zLq7}rRC$y1;D0G^)0&{7N)qclL)Qm8)Jw$7JWJETAm;N|)R=mPvcc`@n{;ciS545} zn76$`z8_GLhXp(+;4uLc0$KvzEZ~fQX8_M>X91VsG!w+G+X6qP0hiM4fUD?Lz;(1y zYQf8>y8*Y;W6}nkh}yAl-Xbx)T{@}7=pN|{fWIT)Ljpb_;FqLF!GBud&q_}Le?fW! z@FnTz+Awy#cARh-CTTO>K#vLh2?1ZAX6YU2M*?3fu}p`60|F)lObU3rfR73Ing_4K zA@4P5fP(Um1czkSKr-_q0*?rMt-vSc0op513cL*bm|Pb4qXIq;&d=oM1^$|Vqzuq{ zg%s8q5wH)O4=a5FpA@hR&fN-W?b6eVsy#vX%YE8a(h_x2z?9&W1^%dj=L9FEvt$tG zqITUS zZn${UmGOgt;(A&?#7FG9>(BHL3tqk4<5*nyOsHXr(NK>Xp4$|h1Qk@Ag346zUiQ2) zLWX*fHmXA5?h!jx%9z(sf8H82k6SsbXxTX$F$;x68cz#!G?7i<74E$rz}W>5_i`JzKE z!=Wj>+CMP{hLt;MpFoKo&S8A(xRsd7m;>A^rRa)M!MP%1P4SN#VyPKmyZ*&@;$(yv#Eb@J?V`n|byh+7ekCQfw6P1eRe4^%^sxy1-QZ6MzOI)fJX6HUL zXF7?ZnW{?pGsT^r!OoevB32hOGg~}f#Aq~h zDsMZ*YS+=Y>+#-9B27ctJRmk^jO2Z;6dTXYvU&p;|UaLS}bCem_64iIcJ^28xvHOW1pI-`ql5GOId_sw3NvV zol2T{vGNGX{T6bQ$WYuYmhyr_gD^48;Tg^qxjfn9Q~8XQw2Cy4F%vmoaCiL@)L)p% zC9g?NO!VjMTpYt$tCDPdjFULl~_|_93%S$`pAO;7OV_i_>+^KSb+<$mvAV&d(4pJ|2zliX0a#IoU)mkv3C%OF6NDjM-MMZnn;vho}kD zvGbK_IDqkxy8#<(7dBT!Zi)+#M(mtDHY^r47GaaB^~iH%94m}jgx~#+lfVwiaPF$Y z)2=cFcVBaSVj`PfmrNw5%`O^)i&zb6rapxI;>B6Pt>(Q}PRuR(5bUPm!iejy*NA1z zSC6BBRmj^2id**+yoy$q*9JUy?QGC6;qn^k5qfc+~>W@10s|hNjdZs}ch!dW7*bx&83x#`D zz$9<9Z==1kbTpQ5)v!^ndS1SFUz44f=tAmvOQt4Xv!XY{(R_W?E?wsC>)3y0A+o_rG zvlfvfIA*~>j`yHBRZ6ED?%;z~BAv4fh$p6|5JLyv%?sq6Ie1fnt5X*5##J-2g{1B9 z;m3b)z|%qnZ~PDuOJy?rFw$gEjw~wT(?fz%_~(#G1^gQXHwnH)HY5_@&p;*#Doc5M z>o6;A*P=Ctp3HObP*txd4mq6oAd$xFYSy0ibW&Re+LGw)k3uV%%7JG>+de>Ux1rl4 zx*=Uh_g~Q3U83*8*6t!Yu#nzb>n~CtPExgY*V|dIkE3!3(tboPa)cEY@>3ffFS>D# z?n(S`_64k!F!2SVKGqjG*0KTgtk;Ab)2DuL`phrs@bK3T-uBAh4*u02lcI+-S#Oc` zFtD%^AxXT|@4SLaFvg)PkWh=JDt8))lG8OoQDBSXEA2mE{S&F(Z+uP603OuvU%b67!-g#p`p-_2IH1U zL48?hL|Pp>81U}F2rUa8mfERPq7C(yx##hQR>4y#yhEa{hCQsDyRkRy8k1d;oO!r`z^3Lu!O#CN<+ z6L;pW72WJ{FjR>7 zZ+EJ^%8{ls;-6_cBffPoF191q)fpMUnX}}WSLe(UUWaBnBV(m0Jgr}E&Wz&>o4a~v z(oFX3NbQ_56FYbI_L$40ak({q!pcJv-Zr_T1Y&{x&xW!6!944JFxVIk86zjs1+7ruv0aSZVFh2t@R(X#kwVvHh2aj$hX zZFw<1-VEZ-a{e_MHLLSFIasxpde)u6~rz0BbN`=#M{xrD&Gr8;0{;W#jI_)E literal 0 HcmV?d00001 diff --git a/Xamarin.Forms.Maps.MacOS/Properties/AssemblyInfo.cs b/Xamarin.Forms.Maps.MacOS/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..26a0c01 --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Xamarin.Forms; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Maps; +using Xamarin.Forms.Maps.MacOS; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("Xamarin.Forms.Maps.macOS")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +[assembly: ExportRenderer(typeof(Map), typeof(MapRenderer))] +[assembly: Preserve] diff --git a/Xamarin.Forms.Maps.MacOS/Xamarin.Forms.Maps.macOS.csproj b/Xamarin.Forms.Maps.MacOS/Xamarin.Forms.Maps.macOS.csproj new file mode 100644 index 0000000..af3da20 --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS/Xamarin.Forms.Maps.macOS.csproj @@ -0,0 +1,93 @@ + + + + Debug + AnyCPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884} + {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Xamarin.Forms.Maps.MacOS + Xamarin.Forms.Maps.macOS + v2.0 + Xamarin.Mac + Resources + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + false + false + false + false + + + + + + + true + bin\Release + + prompt + 4 + false + false + false + false + false + + + + + + + + + + + Libs\Xamarin.Forms.Maps.MacOS.Extra.dll + + + + + + FormsMaps.cs + + + GeocoderBackend.cs + + + MapRenderer.cs + + + Properties\GlobalAssemblyInfo.cs + + + MapPool.cs + + + + + {57B8B73D-C3B5-4C42-869E-7B2F17D354AC} + Xamarin.Forms.Core + + + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C} + Xamarin.Forms.Platform.MacOS + + + {7D13BAC2-C6A4-416A-B07E-C169B199E52B} + Xamarin.Forms.Maps + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Maps.iOS/FormsMaps.cs b/Xamarin.Forms.Maps.iOS/FormsMaps.cs index a87cb8d..0f5df42 100644 --- a/Xamarin.Forms.Maps.iOS/FormsMaps.cs +++ b/Xamarin.Forms.Maps.iOS/FormsMaps.cs @@ -1,11 +1,16 @@ +#if __MOBILE__ using UIKit; using Xamarin.Forms.Maps.iOS; +#else +using Xamarin.Forms.Maps.MacOS; +#endif namespace Xamarin { public static class FormsMaps { static bool s_isInitialized; +#if __MOBILE__ static bool? s_isiOs8OrNewer; static bool? s_isiOs9OrNewer; static bool? s_isiOs10OrNewer; @@ -39,7 +44,7 @@ namespace Xamarin return s_isiOs10OrNewer.Value; } } - +#endif public static void Init() { if (s_isInitialized) diff --git a/Xamarin.Forms.Maps.iOS/GeocoderBackend.cs b/Xamarin.Forms.Maps.iOS/GeocoderBackend.cs index 08ffbbe..e5d1b25 100644 --- a/Xamarin.Forms.Maps.iOS/GeocoderBackend.cs +++ b/Xamarin.Forms.Maps.iOS/GeocoderBackend.cs @@ -1,10 +1,22 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using AddressBookUI; +using Contacts; using CoreLocation; +#if __MOBILE__ +using AddressBookUI; +using CCLGeocoder = CoreLocation.CLGeocoder; +#else +using Xamarin.Forms.Maps.MacOS.Extra; +using CCLGeocoder = Xamarin.Forms.Maps.MacOS.Extra.CLGeocoder; +#endif + +#if __MOBILE__ namespace Xamarin.Forms.Maps.iOS +#else +namespace Xamarin.Forms.Maps.MacOS +#endif { internal class GeocoderBackend { @@ -17,21 +29,36 @@ namespace Xamarin.Forms.Maps.iOS static Task> GetAddressesForPositionAsync(Position position) { var location = new CLLocation(position.Latitude, position.Longitude); - var geocoder = new CLGeocoder(); + var geocoder = new CCLGeocoder(); var source = new TaskCompletionSource>(); geocoder.ReverseGeocodeLocation(location, (placemarks, error) => { if (placemarks == null) placemarks = new CLPlacemark[0]; - IEnumerable addresses = placemarks.Select(p => ABAddressFormatting.ToString(p.AddressDictionary, false)); + List addresses = new List(); +#if __MOBILE__ + addresses = placemarks.Select(p => ABAddressFormatting.ToString(p.AddressDictionary, false)).ToList(); +#else + foreach (var item in placemarks) + { + var address = new CNMutablePostalAddress(); + address.Street = item.AddressDictionary["Street"] == null ? "" : item.AddressDictionary["Street"].ToString(); + address.State = item.AddressDictionary["State"] == null ? "" : item.AddressDictionary["State"].ToString(); + address.City = item.AddressDictionary["City"] == null ? "" : item.AddressDictionary["City"].ToString(); + address.Country = item.AddressDictionary["Country"] == null ? "" : item.AddressDictionary["Country"].ToString(); + address.PostalCode = item.AddressDictionary["ZIP"] == null ? "" : item.AddressDictionary["ZIP"].ToString(); + addresses.Add(CNPostalAddressFormatter.GetStringFrom(address, CNPostalAddressFormatterStyle.MailingAddress)); + } +#endif source.SetResult(addresses); + }); return source.Task; } static Task> GetPositionsForAddressAsync(string address) { - var geocoder = new CLGeocoder(); + var geocoder = new CCLGeocoder(); var source = new TaskCompletionSource>(); geocoder.GeocodeAddress(address, (placemarks, error) => { diff --git a/Xamarin.Forms.Maps.iOS/MapPool.cs b/Xamarin.Forms.Maps.iOS/MapPool.cs index dc8a9c4..1b3823c 100644 --- a/Xamarin.Forms.Maps.iOS/MapPool.cs +++ b/Xamarin.Forms.Maps.iOS/MapPool.cs @@ -1,7 +1,11 @@ using System.Collections.Concurrent; using MapKit; +#if __MOBILE__ namespace Xamarin.Forms.Maps.iOS +#else +namespace Xamarin.Forms.Maps.MacOS +#endif { // A static pool of MKMapView instances we can reuse internal class MapPool diff --git a/Xamarin.Forms.Maps.iOS/MapRenderer.cs b/Xamarin.Forms.Maps.iOS/MapRenderer.cs index 794a4a0..0616fa9 100644 --- a/Xamarin.Forms.Maps.iOS/MapRenderer.cs +++ b/Xamarin.Forms.Maps.iOS/MapRenderer.cs @@ -5,21 +5,27 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using CoreLocation; -using Foundation; using MapKit; using ObjCRuntime; -using UIKit; -using Xamarin.Forms.Platform.iOS; using RectangleF = CoreGraphics.CGRect; +using Foundation; +#if __MOBILE__ +using UIKit; +using Xamarin.Forms.Platform.iOS; namespace Xamarin.Forms.Maps.iOS +#else +using AppKit; +using Xamarin.Forms.Platform.MacOS; +namespace Xamarin.Forms.Maps.MacOS +#endif { internal class MapDelegate : MKMapViewDelegate { // keep references alive, removing this will cause a segfault readonly List List = new List(); Map _map; - UIView _lastTouchedView; + object _lastTouchedView; bool _disposed; internal MapDelegate(Map map) @@ -49,9 +55,10 @@ namespace Xamarin.Forms.Maps.iOS return mapPin; } - +#if __MOBILE__ void AttachGestureToPin(MKPinAnnotationView mapPin, IMKAnnotation annotation) { + UIGestureRecognizer[] recognizers = mapPin.GestureRecognizers; if (recognizers != null) @@ -71,9 +78,33 @@ namespace Xamarin.Forms.Maps.iOS List.Add(action); List.Add(recognizer); mapPin.AddGestureRecognizer(recognizer); - } + } +#else + void AttachGestureToPin(MKPinAnnotationView mapPin, IMKAnnotation annotation) + { + NSGestureRecognizer[] recognizers = mapPin.GestureRecognizers; + + if (recognizers != null) + { + foreach (NSGestureRecognizer r in recognizers) + { + mapPin.RemoveGestureRecognizer(r); + } + } + + Action action = g => OnClick(annotation, g); + var recognizer = new NSClickGestureRecognizer(action); + List.Add(action); + List.Add(recognizer); + mapPin.AddGestureRecognizer(recognizer); + } +#endif +#if __MOBILE__ void OnClick(object annotationObject, UITapGestureRecognizer recognizer) +#else + void OnClick(object annotationObject, NSClickGestureRecognizer recognizer) +#endif { // https://bugzilla.xamarin.com/show_bug.cgi?id=26416 NSObject annotation = Runtime.GetNSObject(((IMKAnnotation)annotationObject).Handle); @@ -126,9 +157,9 @@ namespace Xamarin.Forms.Maps.iOS public class MapRenderer : ViewRenderer { - CLLocationManager _locationManager; + CLLocationManager _locationManager; bool _shouldUpdateRegion; - bool _disposed; + bool _disposed; const string MoveMessageName = "MapMoveToRegion"; @@ -142,8 +173,10 @@ namespace Xamarin.Forms.Maps.iOS // as much as possible to prevent creating new ones and losing more memory // For the time being, we don't want ViewRenderer handling disposal of the MKMapView - // if we're on iOS 9 or 10; during Dispose we'll be putting the MKMapView in a pool instead + // if we're on iOS 10; during Dispose we'll be putting the MKMapView in a pool instead +#if __MOBILE__ protected override bool ManageNativeControlLifetime => !FormsMaps.IsiOs9OrNewer; +#endif protected override void Dispose(bool disposing) { @@ -169,16 +202,15 @@ namespace Xamarin.Forms.Maps.iOS mkMapView.Delegate.Dispose(); mkMapView.Delegate = null; mkMapView.RemoveFromSuperview(); - +#if __MOBILE__ if (FormsMaps.IsiOs9OrNewer) { // This renderer is done with the MKMapView; we can put it in the pool // for other rendererers to use in the future MapPool.Add(mkMapView); } - +#endif // For iOS versions < 9, the MKMapView will be disposed in ViewRenderer's Dispose method - if (_locationManager != null) { _locationManager.Dispose(); @@ -207,13 +239,13 @@ namespace Xamarin.Forms.Maps.iOS if (Control == null) { MKMapView mapView = null; - +#if __MOBILE__ if (FormsMaps.IsiOs9OrNewer) { // See if we've got an MKMapView available in the pool; if so, use it mapView = MapPool.Get(); } - +#endif if (mapView == null) { // If this is iOS 8 or lower, or if there weren't any MKMapViews in the pool, @@ -260,9 +292,22 @@ namespace Xamarin.Forms.Maps.iOS _shouldUpdateRegion = true; } +#if __MOBILE__ public override void LayoutSubviews() { base.LayoutSubviews(); + UpdateRegion(); + } +#else + public override void Layout() + { + base.Layout(); + UpdateRegion(); + } +#endif + + void UpdateRegion() + { if (_shouldUpdateRegion) { MoveToRegion(((Map)Element).LastMoveToRegion, false); @@ -343,12 +388,13 @@ namespace Xamarin.Forms.Maps.iOS void UpdateIsShowingUser() { +#if __MOBILE__ if (FormsMaps.IsiOs8OrNewer && ((Map)Element).IsShowingUser) { _locationManager = new CLLocationManager(); _locationManager.RequestWhenInUseAuthorization(); } - +#endif ((MKMapView)Control).ShowsUserLocation = ((Map)Element).IsShowingUser; } diff --git a/Xamarin.Forms.Maps/Properties/AssemblyInfo.cs b/Xamarin.Forms.Maps/Properties/AssemblyInfo.cs index 2220fff..1e3c2f3 100644 --- a/Xamarin.Forms.Maps/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms.Maps/Properties/AssemblyInfo.cs @@ -13,6 +13,7 @@ using Xamarin.Forms.Internals; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.macOS")] [assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS")] [assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS.Classic")] [assembly: InternalsVisibleTo("Xamarin.Forms.Maps.Android")] diff --git a/Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs b/Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs new file mode 100644 index 0000000..1e965ff --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/CADisplayLinkTicker.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using Foundation; +using Xamarin.Forms.Internals; +using CoreVideo; +using AppKit; +using CoreAnimation; + +namespace Xamarin.Forms.Platform.MacOS +{ + // ReSharper disable once InconsistentNaming + internal class CADisplayLinkTicker : Ticker + { + readonly BlockingCollection _queue = new BlockingCollection(); + CVDisplayLink _link; + + public CADisplayLinkTicker() + { + var thread = new Thread(StartThread); + thread.Start(); + } + + internal new static CADisplayLinkTicker Default => Ticker.Default as CADisplayLinkTicker; + + public void Invoke(Action action) + { + _queue.Add(action); + } + + protected override void DisableTimer() + { + _link?.Stop(); + _link?.Dispose(); + _link = null; + } + + protected override void EnableTimer() + { + _link = new CVDisplayLink(); + _link.SetOutputCallback(DisplayLinkOutputCallback); + _link.Start(); + } + + public CVReturn DisplayLinkOutputCallback(CVDisplayLink displayLink, ref CVTimeStamp inNow, + ref CVTimeStamp inOutputTime, CVOptionFlags flagsIn, ref CVOptionFlags flagsOut) + { + // There is no autorelease pool when this method is called because it will be called from a background thread + // It's important to create one or you will leak objects + // ReSharper disable once UnusedVariable + using (var pool = new NSAutoreleasePool()) + { + Device.BeginInvokeOnMainThread(() => SendSignals()); + } + return CVReturn.Success; + } + + void StartThread() + { + while (true) + { + Action action = _queue.Take(); + bool previous = NSApplication.CheckForIllegalCrossThreadCalls; + NSApplication.CheckForIllegalCrossThreadCalls = false; + + CATransaction.Begin(); + action.Invoke(); + + while (_queue.TryTake(out action)) + action.Invoke(); + CATransaction.Commit(); + + NSApplication.CheckForIllegalCrossThreadCalls = previous; + } + // ReSharper disable once FunctionNeverReturns + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs b/Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs new file mode 100644 index 0000000..1ba964e --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/CellNSView.cs @@ -0,0 +1,167 @@ +using System; +using System.ComponentModel; +using AppKit; +using CoreGraphics; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class CellNSView : NSView, INativeElementView + { + static readonly NSColor s_defaultChildViewsBackground = NSColor.Clear; + static readonly CGColor s_defaultHeaderViewsBackground = NSColor.LightGray.CGColor; + Cell _cell; + readonly NSTableViewCellStyle _style; + + public Action PropertyChanged; + + public CellNSView(NSTableViewCellStyle style) + { + WantsLayer = true; + _style = style; + CreateUI(); + } + + public NSTextField TextLabel { get; private set; } + + public NSTextField DetailTextLabel { get; private set; } + + public NSImageView ImageView { get; private set; } + + public NSView AccessoryView { get; private set; } + + public Element Element => Cell; + + public Cell Cell + { + get { return _cell; } + set + { + if (_cell == value) + return; + + ICellController cellController = _cell; + + if (cellController != null) + Device.BeginInvokeOnMainThread(cellController.SendDisappearing); + + _cell = value; + cellController = value; + + if (cellController != null) + Device.BeginInvokeOnMainThread(cellController.SendAppearing); + } + } + + public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(this, e); + } + + public override void Layout() + { + const int padding = 10; + nfloat availableHeight = Frame.Height; + nfloat availableWidth = Frame.Width - padding * 2; + nfloat imageWidth = 0; + nfloat accessoryViewWidth = 0; + + if (ImageView != null) + { + nfloat imageHeight = imageWidth = availableHeight; + ImageView.Frame = new CGRect(padding, 0, imageWidth, imageHeight); + } + + if (AccessoryView != null) + { + accessoryViewWidth = _style == NSTableViewCellStyle.Value1 ? 50 : availableWidth - 100; + AccessoryView.Frame = new CGRect(availableWidth - accessoryViewWidth + padding, 0, accessoryViewWidth, + availableHeight); + foreach (var subView in AccessoryView.Subviews) + { + //try to find the size the control wants, if no width use default width + var size = subView.FittingSize; + if (size.Width == 0) + size.Width = accessoryViewWidth; + + var x = AccessoryView.Bounds.Width - size.Width; + var y = (AccessoryView.Bounds.Height - size.Height) / 2; + subView.Frame = new CGRect(new CGPoint(x, y), size); + } + } + + nfloat labelHeights = availableHeight; + nfloat labelWidth = availableWidth - imageWidth - accessoryViewWidth; + + if (!string.IsNullOrEmpty(DetailTextLabel?.StringValue)) + { + labelHeights = availableHeight / 2; + DetailTextLabel.CenterTextVertically(new CGRect(imageWidth + padding, 0, labelWidth, labelHeights)); + } + + TextLabel.CenterTextVertically(new CGRect(imageWidth + padding, availableHeight - labelHeights, labelWidth, + labelHeights)); + base.Layout(); + } + + internal static NSView GetNativeCell(NSTableView tableView, Cell cell, string templateId = "", bool isHeader = false, + bool isRecycle = false) + { + var reusable = tableView.MakeView(templateId, tableView); + NSView nativeCell; + if (reusable == null || !isRecycle) + { + var renderer = (CellRenderer)Registrar.Registered.GetHandler(cell.GetType()); + nativeCell = renderer.GetCell(cell, null, tableView); + } + else + { + nativeCell = reusable; + } + + if (string.IsNullOrEmpty(nativeCell.Identifier)) + nativeCell.Identifier = templateId; + + if (!isHeader) return nativeCell; + if (nativeCell.Layer != null) nativeCell.Layer.BackgroundColor = s_defaultHeaderViewsBackground; + return nativeCell; + } + + void CreateUI() + { + var style = _style; + + AddSubview(TextLabel = new NSTextField + { + Bordered = false, + Selectable = false, + Editable = false, + Font = NSFont.LabelFontOfSize(NSFont.SystemFontSize) + }); + + TextLabel.Cell.BackgroundColor = s_defaultChildViewsBackground; + + if (style == NSTableViewCellStyle.Image || style == NSTableViewCellStyle.Subtitle || + style == NSTableViewCellStyle.ImageSubtitle) + { + AddSubview(DetailTextLabel = new NSTextField + { + Bordered = false, + Selectable = false, + Editable = false, + Font = NSFont.LabelFontOfSize(NSFont.SmallSystemFontSize) + }); + DetailTextLabel.Cell.BackgroundColor = s_defaultChildViewsBackground; + } + + if (style == NSTableViewCellStyle.Image || style == NSTableViewCellStyle.ImageSubtitle) + AddSubview(ImageView = new NSImageView()); + + if (style == NSTableViewCellStyle.Value1 || style == NSTableViewCellStyle.Value2) + { + var accessoryView = new NSView { WantsLayer = true }; + accessoryView.Layer.BackgroundColor = s_defaultChildViewsBackground.CGColor; + AddSubview(AccessoryView = accessoryView); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs new file mode 100644 index 0000000..54e540a --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/CellRenderer.cs @@ -0,0 +1,68 @@ +using System; +using AppKit; +using CoreGraphics; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class CellRenderer : IRegisterable + { + static readonly BindableProperty s_realCellProperty = BindableProperty.CreateAttached("RealCell", typeof(NSView), + typeof(Cell), null); + + EventHandler _onForceUpdateSizeRequested; + + public virtual NSView GetCell(Cell item, NSView reusableView, NSTableView tv) + { + var tvc = reusableView as CellNSView ?? new CellNSView(NSTableViewCellStyle.Default); + + tvc.Cell = item; + + WireUpForceUpdateSizeRequested(item, tvc, tv); + + tvc.TextLabel.StringValue = item.ToString(); + + UpdateBackground(tvc, item); + + return tvc; + } + + protected void UpdateBackground(NSView tableViewCell, Cell cell) + { + tableViewCell.WantsLayer = true; + var bgColor = NSColor.White; + var element = cell.RealParent as VisualElement; + if (element != null) + bgColor = element.BackgroundColor == Color.Default ? bgColor : element.BackgroundColor.ToNSColor(); + + UpdateBackgroundChild(cell, bgColor); + + tableViewCell.Layer.BackgroundColor = bgColor.CGColor; + } + + protected void WireUpForceUpdateSizeRequested(ICellController cell, NSView nativeCell, NSTableView tableView) + { + cell.ForceUpdateSizeRequested -= _onForceUpdateSizeRequested; + + _onForceUpdateSizeRequested = (sender, e) => + { + //TODO: Implement ForceUpdateSize + }; + + cell.ForceUpdateSizeRequested += _onForceUpdateSizeRequested; + } + + internal virtual void UpdateBackgroundChild(Cell cell, NSColor backgroundColor) + { + } + + internal static NSView GetRealCell(BindableObject cell) + { + return (NSView)cell.GetValue(s_realCellProperty); + } + + internal static void SetRealCell(BindableObject cell, NSView renderer) + { + cell.SetValue(s_realCellProperty, renderer); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs new file mode 100644 index 0000000..43789dc --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/EntryCellRenderer.cs @@ -0,0 +1,144 @@ +using System; +using System.ComponentModel; +using AppKit; +using CoreGraphics; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class EntryCellRenderer : CellRenderer + { + static readonly Color s_defaultTextColor = Color.Black; + + public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv) + { + NSTextField nsEntry = null; + var tvc = reusableView as CellNSView; + if (tvc == null) + tvc = new CellNSView(NSTableViewCellStyle.Value2); + else + { + tvc.Cell.PropertyChanged -= OnCellPropertyChanged; + + nsEntry = tvc.AccessoryView.Subviews[0] as NSTextField; + if (nsEntry != null) + { + nsEntry.RemoveFromSuperview(); + nsEntry.Changed -= OnTextFieldTextChanged; + } + } + + SetRealCell(item, tvc); + + if (nsEntry == null) + tvc.AccessoryView.AddSubview(nsEntry = new NSTextField()); + + var entryCell = (EntryCell)item; + + tvc.Cell = item; + tvc.Cell.PropertyChanged += OnCellPropertyChanged; + nsEntry.Changed += OnTextFieldTextChanged; + + WireUpForceUpdateSizeRequested(item, tvc, tv); + + UpdateBackground(tvc, entryCell); + UpdateLabel(tvc, entryCell); + UpdateText(tvc, entryCell); + UpdatePlaceholder(tvc, entryCell); + UpdateLabelColor(tvc, entryCell); + UpdateHorizontalTextAlignment(tvc, entryCell); + UpdateIsEnabled(tvc, entryCell); + + return tvc; + } + + internal override void UpdateBackgroundChild(Cell cell, NSColor backgroundColor) + { + var realCell = (CellNSView)GetRealCell(cell); + + var nsTextField = realCell.AccessoryView.Subviews[0] as NSTextField; + if (nsTextField != null) + nsTextField.BackgroundColor = backgroundColor; + + base.UpdateBackgroundChild(cell, backgroundColor); + } + + static void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var entryCell = (EntryCell)sender; + var realCell = (CellNSView)GetRealCell(entryCell); + + if (e.PropertyName == EntryCell.LabelProperty.PropertyName) + UpdateLabel(realCell, entryCell); + else if (e.PropertyName == EntryCell.TextProperty.PropertyName) + UpdateText(realCell, entryCell); + else if (e.PropertyName == EntryCell.PlaceholderProperty.PropertyName) + UpdatePlaceholder(realCell, entryCell); + else if (e.PropertyName == EntryCell.LabelColorProperty.PropertyName) + UpdateLabelColor(realCell, entryCell); + else if (e.PropertyName == EntryCell.HorizontalTextAlignmentProperty.PropertyName) + UpdateHorizontalTextAlignment(realCell, entryCell); + else if (e.PropertyName == Cell.IsEnabledProperty.PropertyName) + UpdateIsEnabled(realCell, entryCell); + } + + static void OnTextFieldTextChanged(object sender, EventArgs eventArgs) + { + var notification = (NSNotification)sender; + var view = (NSView)notification.Object; + var field = (NSTextField)view; + + CellNSView realCell = null; + while (view.Superview != null && realCell == null) + { + view = view.Superview; + realCell = view as CellNSView; + } + + if (realCell != null) + ((EntryCell)realCell.Cell).Text = field.StringValue; + } + + static void UpdateHorizontalTextAlignment(CellNSView cell, EntryCell entryCell) + { + var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField; + if (nsTextField != null) + nsTextField.Alignment = entryCell.HorizontalTextAlignment.ToNativeTextAlignment(); + } + + static void UpdateIsEnabled(CellNSView cell, EntryCell entryCell) + { + cell.TextLabel.Enabled = entryCell.IsEnabled; + var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField; + if (nsTextField != null) + nsTextField.Enabled = entryCell.IsEnabled; + } + + static void UpdateLabel(CellNSView cell, EntryCell entryCell) + { + cell.TextLabel.StringValue = entryCell.Label ?? ""; + } + + static void UpdateLabelColor(CellNSView cell, EntryCell entryCell) + { + cell.TextLabel.TextColor = entryCell.LabelColor.ToNSColor(s_defaultTextColor); + } + + static void UpdatePlaceholder(CellNSView cell, EntryCell entryCell) + { + var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField; + if (nsTextField != null) + nsTextField.PlaceholderString = entryCell.Placeholder ?? ""; + } + + static void UpdateText(CellNSView cell, EntryCell entryCell) + { + var nsTextField = cell.AccessoryView.Subviews[0] as NSTextField; + if (nsTextField != null && nsTextField.StringValue == entryCell.Text) + return; + + if (nsTextField != null) + nsTextField.StringValue = entryCell.Text ?? ""; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs new file mode 100644 index 0000000..8bd7677 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/ImageCellRenderer.cs @@ -0,0 +1,68 @@ +using System.ComponentModel; +using System.Threading.Tasks; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ImageCellRenderer : TextCellRenderer + { + public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv) + { + var tvc = reusableView as CellNSView ?? new CellNSView(NSTableViewCellStyle.ImageSubtitle); + + var result = (CellNSView)base.GetCell(item, tvc, tv); + + var imageCell = (ImageCell)item; + + WireUpForceUpdateSizeRequested(item, result, tv); + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + SetImage(imageCell, result); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + return result; + } + + protected override async void HandlePropertyChanged(object sender, PropertyChangedEventArgs args) + { + var tvc = (CellNSView)sender; + var imageCell = (ImageCell)tvc.Cell; + + base.HandlePropertyChanged(sender, args); + + if (args.PropertyName == ImageCell.ImageSourceProperty.PropertyName) + await SetImage(imageCell, tvc); + } + + static async Task SetImage(ImageCell cell, CellNSView target) + { + var source = cell.ImageSource; + + target.ImageView.Image = null; + + IImageSourceHandler handler; + + if (source != null && (handler = Registrar.Registered.GetHandler(source.GetType())) != null) + { + NSImage uiimage; + try + { + uiimage = await handler.LoadImageAsync(source).ConfigureAwait(false); + } + catch (TaskCanceledException) + { + uiimage = null; + } + + NSRunLoop.Main.BeginInvokeOnMainThread(() => + { + target.ImageView.Image = uiimage; + target.NeedsLayout = true; + }); + } + else + target.ImageView.Image = null; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs b/Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs new file mode 100644 index 0000000..3e0235d --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/NSTableViewCellStyle.cs @@ -0,0 +1,12 @@ +namespace Xamarin.Forms.Platform.MacOS +{ + internal enum NSTableViewCellStyle + { + Default, + Value1, + Value2, + Subtitle, + Image, + ImageSubtitle + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs new file mode 100644 index 0000000..5f086b4 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/SwitchCellRenderer.cs @@ -0,0 +1,88 @@ +using System; +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class SwitchCellRenderer : CellRenderer + { + public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv) + { + var tvc = reusableView as CellNSView; + NSButton nsSwitch = null; + if (tvc == null) + tvc = new CellNSView(NSTableViewCellStyle.Value1); + else + { + nsSwitch = tvc.AccessoryView.Subviews[0] as NSButton; + if (nsSwitch != null) + { + nsSwitch.RemoveFromSuperview(); + nsSwitch.Activated -= OnSwitchValueChanged; + } + tvc.Cell.PropertyChanged -= OnCellPropertyChanged; + } + + SetRealCell(item, tvc); + + if (nsSwitch == null) + { + nsSwitch = new NSButton { AllowsMixedState = false, Title = string.Empty }; + nsSwitch.SetButtonType(NSButtonType.Switch); + } + + var boolCell = (SwitchCell)item; + + tvc.Cell = item; + tvc.Cell.PropertyChanged += OnCellPropertyChanged; + tvc.AccessoryView.AddSubview(nsSwitch); + tvc.TextLabel.StringValue = boolCell.Text ?? ""; + + nsSwitch.State = boolCell.On ? NSCellStateValue.On : NSCellStateValue.Off; + nsSwitch.Activated += OnSwitchValueChanged; + WireUpForceUpdateSizeRequested(item, tvc, tv); + + UpdateBackground(tvc, item); + UpdateIsEnabled(tvc, boolCell); + + return tvc; + } + + static void UpdateIsEnabled(CellNSView cell, SwitchCell switchCell) + { + cell.TextLabel.Enabled = switchCell.IsEnabled; + var uiSwitch = cell.AccessoryView.Subviews[0] as NSButton; + if (uiSwitch != null) + uiSwitch.Enabled = switchCell.IsEnabled; + } + + void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var boolCell = (SwitchCell)sender; + var realCell = (CellNSView)GetRealCell(boolCell); + + if (e.PropertyName == SwitchCell.OnProperty.PropertyName) + ((NSButton)realCell.AccessoryView.Subviews[0]).State = boolCell.On ? NSCellStateValue.On : NSCellStateValue.Off; + else if (e.PropertyName == SwitchCell.TextProperty.PropertyName) + realCell.TextLabel.StringValue = boolCell.Text ?? ""; + else if (e.PropertyName == Cell.IsEnabledProperty.PropertyName) + UpdateIsEnabled(realCell, boolCell); + } + + void OnSwitchValueChanged(object sender, EventArgs eventArgs) + { + var view = (NSView)sender; + var sw = (NSButton)view; + + CellNSView realCell = null; + while (view.Superview != null && realCell == null) + { + view = view.Superview; + realCell = view as CellNSView; + } + + if (realCell != null) + ((SwitchCell)realCell.Cell).On = sw.State == NSCellStateValue.On; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs new file mode 100644 index 0000000..6e36ce7 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/TextCellRenderer.cs @@ -0,0 +1,66 @@ +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class TextCellRenderer : CellRenderer + { + static readonly Color s_defaultDetailColor = new Color(.32, .4, .57); + static readonly Color s_defaultTextColor = Color.Black; + + public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv) + { + var textCell = (TextCell)item; + + var tvc = reusableView as CellNSView ?? new CellNSView(NSTableViewCellStyle.Subtitle); + + if (tvc.Cell != null) + tvc.Cell.PropertyChanged -= tvc.HandlePropertyChanged; + + tvc.Cell = textCell; + textCell.PropertyChanged += tvc.HandlePropertyChanged; + tvc.PropertyChanged = HandlePropertyChanged; + + tvc.TextLabel.StringValue = textCell.Text ?? ""; + tvc.DetailTextLabel.StringValue = textCell.Detail ?? ""; + tvc.TextLabel.TextColor = textCell.TextColor.ToNSColor(s_defaultTextColor); + tvc.DetailTextLabel.TextColor = textCell.DetailColor.ToNSColor(s_defaultDetailColor); + + WireUpForceUpdateSizeRequested(item, tvc, tv); + + UpdateIsEnabled(tvc, textCell); + + UpdateBackground(tvc, item); + + return tvc; + } + + protected virtual void HandlePropertyChanged(object sender, PropertyChangedEventArgs args) + { + var tvc = (CellNSView)sender; + var textCell = (TextCell)tvc.Cell; + if (args.PropertyName == TextCell.TextProperty.PropertyName) + { + tvc.TextLabel.StringValue = textCell.Text ?? ""; + tvc.TextLabel.SizeToFit(); + } + else if (args.PropertyName == TextCell.DetailProperty.PropertyName) + { + tvc.DetailTextLabel.StringValue = textCell.Detail ?? ""; + tvc.DetailTextLabel.SizeToFit(); + } + else if (args.PropertyName == TextCell.TextColorProperty.PropertyName) + tvc.TextLabel.TextColor = textCell.TextColor.ToNSColor(s_defaultTextColor); + else if (args.PropertyName == TextCell.DetailColorProperty.PropertyName) + tvc.DetailTextLabel.TextColor = textCell.DetailColor.ToNSColor(s_defaultTextColor); + else if (args.PropertyName == Cell.IsEnabledProperty.PropertyName) + UpdateIsEnabled(tvc, textCell); + } + + static void UpdateIsEnabled(CellNSView cell, TextCell entryCell) + { + cell.TextLabel.Enabled = entryCell.IsEnabled; + cell.DetailTextLabel.Enabled = entryCell.IsEnabled; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs new file mode 100644 index 0000000..0dd766a --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellNSView.cs @@ -0,0 +1,105 @@ +using System; +using AppKit; +using RectangleF = CoreGraphics.CGRect; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ViewCellNSView : NSView, INativeElementView + { + WeakReference _rendererRef; + + ViewCell _viewCell; + + public Element Element => ViewCell; + + public ViewCell ViewCell + { + get { return _viewCell; } + set + { + if (_viewCell == value) + return; + UpdateCell(value); + } + } + + public override void Layout() + { + LayoutSubviews(); + base.Layout(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + IVisualElementRenderer renderer; + if (_rendererRef != null && _rendererRef.TryGetTarget(out renderer) && renderer.Element != null) + { + Platform.DisposeModelAndChildrenRenderers(renderer.Element); + + _rendererRef = null; + } + } + + base.Dispose(disposing); + } + + void LayoutSubviews() + { + var contentFrame = Frame; + var view = ViewCell.View; + + Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(view, contentFrame.ToRectangle()); + + if (_rendererRef == null) + return; + + IVisualElementRenderer renderer; + if (_rendererRef.TryGetTarget(out renderer)) + renderer.NativeView.Frame = view.Bounds.ToRectangleF(); + } + + IVisualElementRenderer GetNewRenderer() + { + var newRenderer = Platform.CreateRenderer(_viewCell.View); + _rendererRef = new WeakReference(newRenderer); + AddSubview(newRenderer.NativeView); + return newRenderer; + } + + void UpdateCell(ViewCell cell) + { + ICellController cellController = _viewCell; + if (cellController != null) + Device.BeginInvokeOnMainThread(cellController.SendDisappearing); + + _viewCell = cell; + cellController = cell; + + Device.BeginInvokeOnMainThread(cellController.SendAppearing); + + IVisualElementRenderer renderer; + if (_rendererRef == null || !_rendererRef.TryGetTarget(out renderer)) + renderer = GetNewRenderer(); + else + { + if (renderer.Element != null && renderer == Platform.GetRenderer(renderer.Element)) + renderer.Element.ClearValue(Platform.RendererProperty); + + var type = Registrar.Registered.GetHandlerType(_viewCell.View.GetType()); + if (renderer.GetType() == type || (renderer is DefaultRenderer && type == null)) + renderer.SetElement(_viewCell.View); + else + { + //when cells are getting reused the element could be already set to another cell + //so we should dispose based on the renderer and not the renderer.Element + Platform.DisposeRendererAndChildren(renderer); + renderer = GetNewRenderer(); + } + } + + Platform.SetRenderer(_viewCell.View, renderer); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs new file mode 100644 index 0000000..8b34518 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Cells/ViewCellRenderer.cs @@ -0,0 +1,46 @@ +using System.ComponentModel; +using AppKit; + +// ReSharper disable UnusedParameter.Local + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ViewCellRenderer : CellRenderer + { + public override NSView GetCell(Cell item, NSView reusableView, NSTableView tv) + { + var viewCell = (ViewCell)item; + + var cell = reusableView as ViewCellNSView; + if (cell == null) + cell = new ViewCellNSView(); + else + cell.ViewCell.PropertyChanged -= ViewCellPropertyChanged; + + viewCell.PropertyChanged += ViewCellPropertyChanged; + cell.ViewCell = viewCell; + + SetRealCell(item, cell); + + WireUpForceUpdateSizeRequested(item, cell, tv); + + UpdateBackground(cell, item); + UpdateIsEnabled(cell, viewCell); + return cell; + } + + static void UpdateIsEnabled(ViewCellNSView cell, ViewCell viewCell) + { + //TODO: Implement IsEnabled on ViewCell + } + + static void ViewCellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var viewCell = (ViewCell)sender; + var realCell = (ViewCellNSView)GetRealCell(viewCell); + + if (e.PropertyName == Cell.IsEnabledProperty.PropertyName) + UpdateIsEnabled(realCell, viewCell); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs b/Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs new file mode 100644 index 0000000..33ed73b --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs @@ -0,0 +1,16 @@ +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class FormsNSImageView : NSImageView + { + bool _isOpaque; + + public void SetIsOpaque(bool isOpaque) + { + _isOpaque = isOpaque; + } + + public override bool IsOpaque => _isOpaque; + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs b/Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs new file mode 100644 index 0000000..5fb7455 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Controls/FormsPageControllerDelegate.cs @@ -0,0 +1,29 @@ +using System; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class FormsPageControllerDelegate : NSPageControllerDelegate + { + readonly Func _getIdentifier; + readonly Func _getViewController; + + public FormsPageControllerDelegate(Func getIdentifier, + Func getViewController) + { + _getIdentifier = getIdentifier; + _getViewController = getViewController; + } + + public override NSViewController GetViewController(NSPageController pageController, string identifier) + { + return _getViewController(identifier); + } + + public override string GetIdentifier(NSPageController pv, NSObject obj) + { + return _getIdentifier(obj); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs b/Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs new file mode 100644 index 0000000..1fc2387 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Controls/MacOSOpenGLView.cs @@ -0,0 +1,12 @@ +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + //TODO: Still not implemented on MacOS + public class MacOSOpenGLView : NSView + { + public MacOSOpenGLView() + { + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs b/Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs new file mode 100644 index 0000000..ee02293 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Controls/NSToolbarItemGroup.cs @@ -0,0 +1,102 @@ +using System; +using System.Runtime.InteropServices; +using AppKit; +using Foundation; +using ObjCRuntime; + +[Register("NSToolbarItemGroup", true)] +// ReSharper disable once CheckNamespace +// ReSharper disable once InconsistentNaming +public class NSToolbarItemGroup : NSToolbarItem +{ + const string SelSetSubitems = "setSubitems:"; + const string SelSubitems = "subitems"; + const string SelInitWithItemIdentifier = "initWithItemIdentifier:"; + static readonly IntPtr s_selSetSubitemsHandle = Selector.GetHandle(SelSetSubitems); + static readonly IntPtr s_selSubitemsHandle = Selector.GetHandle(SelSubitems); + static readonly IntPtr s_selInitWithItemIdentifierHandle = Selector.GetHandle(SelInitWithItemIdentifier); + static readonly IntPtr s_classPtr = Class.GetHandle("NSToolbarItemGroup"); + + [Export("init")] + public NSToolbarItemGroup() : base(NSObjectFlag.Empty) + { + InitializeHandle( + IsDirectBinding + ? IntPtr_objc_msgSend(Handle, Selector.GetHandle("init")) + : IntPtr_objc_msgSendSuper(SuperHandle, Selector.GetHandle("init")), "init"); + } + + [Export("initWithItemIdentifier:")] + public NSToolbarItemGroup(string itemIdentifier) + : base(NSObjectFlag.Empty) + { + NSApplication.EnsureUIThread(); + if (itemIdentifier == null) + throw new ArgumentNullException(nameof(itemIdentifier)); + IntPtr nsitemIdentifier = NSString.CreateNative(itemIdentifier); + + InitializeHandle( + IsDirectBinding + ? IntPtr_objc_msgSend_IntPtr(Handle, s_selInitWithItemIdentifierHandle, nsitemIdentifier) + : IntPtr_objc_msgSendSuper_IntPtr(SuperHandle, s_selInitWithItemIdentifierHandle, nsitemIdentifier), + "initWithItemIdentifier:"); + NSString.ReleaseNative(nsitemIdentifier); + } + + protected internal NSToolbarItemGroup(IntPtr handle) : base(handle) + { + } + + protected NSToolbarItemGroup(NSObjectFlag t) : base(t) + { + } + + public override IntPtr ClassHandle => s_classPtr; + + public virtual NSToolbarItem[] Subitems + { + [Export(SelSubitems, ArgumentSemantic.Copy)] + get + { + NSApplication.EnsureUIThread(); + NSToolbarItem[] ret = + NSArray.ArrayFromHandle(IsDirectBinding + ? IntPtr_objc_msgSend(Handle, s_selSubitemsHandle) + : IntPtr_objc_msgSendSuper(SuperHandle, s_selSubitemsHandle)); + return ret; + } + + [Export(SelSetSubitems, ArgumentSemantic.Copy)] + set + { + NSApplication.EnsureUIThread(); + if (value == null) + throw new ArgumentNullException(nameof(value)); + // ReSharper disable once CoVariantArrayConversion + NSArray nsaValue = NSArray.FromNSObjects(value); + + if (IsDirectBinding) + void_objc_msgSend_IntPtr(Handle, s_selSetSubitemsHandle, nsaValue.Handle); + else void_objc_msgSendSuper_IntPtr(SuperHandle, s_selSetSubitemsHandle, nsaValue.Handle); + nsaValue.Dispose(); + } + } + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] + public static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, IntPtr selector); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] + public static extern IntPtr IntPtr_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSendSuper")] + public static extern IntPtr IntPtr_objc_msgSendSuper(IntPtr receiver, IntPtr selector); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSendSuper")] + public static extern IntPtr IntPtr_objc_msgSendSuper_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] + public static extern void void_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSendSuper")] + public static extern void void_objc_msgSendSuper_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1); +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs b/Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs new file mode 100644 index 0000000..a0e6dd4 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Controls/NavigationChildPageWrapper.cs @@ -0,0 +1,41 @@ +using System; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class NavigationChildPageWrapper : NSObject + { + bool _disposed; + + public NavigationChildPageWrapper(Page page) + { + Page = page; + Page.PropertyChanged += PagePropertyChanged; + Identifier = Guid.NewGuid().ToString(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + if (Page != null) + Page.PropertyChanged -= PagePropertyChanged; + Page = null; + } + base.Dispose(disposing); + } + + void PagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName + || e.PropertyName == Page.TitleProperty.PropertyName + || e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName) + Platform.NativeToolbarTracker.UpdateToolBar(); + } + + public string Identifier { get; set; } + + public Page Page { get; private set; } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs b/Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs new file mode 100644 index 0000000..a8a825c --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Controls/ScrollViewScrollChangedEventArgs.cs @@ -0,0 +1,10 @@ +using System; +using PointF = CoreGraphics.CGPoint; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class ScrollViewScrollChangedEventArgs : EventArgs + { + public PointF CurrentScrollPoint { get; set; } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs b/Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs new file mode 100644 index 0000000..397f632 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Controls/VerticallyCenteredTextFieldCell.cs @@ -0,0 +1,36 @@ +using System; +using AppKit; +using CoreGraphics; + +namespace Xamarin.Forms.Platform.MacOS +{ + sealed class VerticallyCenteredTextFieldCell : NSTextFieldCell + { + readonly nfloat _yOffset; + + public VerticallyCenteredTextFieldCell(nfloat yOffset, NSFont font = null) + { + if (font != null) + Font = font; + _yOffset = yOffset; + } + + public override CGRect DrawingRectForBounds(CGRect theRect) + { + // Get the parent's idea of where we should draw. + CGRect newRect = base.DrawingRectForBounds(theRect); + + // Ideal size for the text. + CGSize textSize = CellSizeForBounds(theRect); + + // Center in the rect. + nfloat heightDelta = newRect.Size.Height - textSize.Height; + if (heightDelta > 0) + { + newRect.Size = new CGSize(newRect.Width, newRect.Height - heightDelta); + newRect.Location = new CGPoint(newRect.X, newRect.Y + heightDelta / 2 + _yOffset); + } + return newRect; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs new file mode 100644 index 0000000..35d0f26 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/AlignmentExtensions.cs @@ -0,0 +1,21 @@ +using System; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal static class AlignmentExtensions + { + internal static NSTextAlignment ToNativeTextAlignment(this TextAlignment alignment) + { + switch (alignment) + { + case TextAlignment.Center: + return NSTextAlignment.Center; + case TextAlignment.End: + return NSTextAlignment.Right; + default: + return NSTextAlignment.Left; + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs new file mode 100644 index 0000000..1bdc62a --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/ButtonExtensions.cs @@ -0,0 +1,25 @@ +using System; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal static class ButtonExtensions + { + public static NSCellImagePosition ToNSCellImagePosition(this Button control) + { + switch (control.ContentLayout.Position) + { + case Button.ButtonContentLayout.ImagePosition.Left: + return NSCellImagePosition.ImageLeft; + case Button.ButtonContentLayout.ImagePosition.Top: + return NSCellImagePosition.ImageAbove; + case Button.ButtonContentLayout.ImagePosition.Right: + return NSCellImagePosition.ImageRight; + case Button.ButtonContentLayout.ImagePosition.Bottom: + return NSCellImagePosition.ImageBelow; + default: + return NSCellImagePosition.ImageOnly; + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs new file mode 100644 index 0000000..d37f1a3 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSButtonExtensions.cs @@ -0,0 +1,25 @@ +using System; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public static class NSButtonExtensions + { + public static NSButton CreateButton(string text, Action activate = null) + { + return CreateButton(text, null, activate); + } + + public static NSButton CreateButton(string text, NSImage image = null, Action activate = null) + { + var btn = new NSButton { Title = text }; + btn.BezelStyle = NSBezelStyle.TexturedRounded; + + if (image != null) + btn.Image = image; + if (activate != null) + btn.Activated += (sender, e) => activate(); + return btn; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs new file mode 100644 index 0000000..0952173 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSImageExtensions.cs @@ -0,0 +1,22 @@ +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public static class NSImageExtensions + { + public static NSImage ResizeTo(this NSImage self, CoreGraphics.CGSize newSize) + { + if (self == null) + return null; + self.ResizingMode = NSImageResizingMode.Stretch; + var resizedImage = new NSImage(newSize); + resizedImage.LockFocus(); + self.Size = newSize; + NSGraphicsContext.CurrentContext.ImageInterpolation = NSImageInterpolation.High; + self.Draw(CoreGraphics.CGPoint.Empty, new CoreGraphics.CGRect(0, 0, newSize.Width, newSize.Height), + NSCompositingOperation.Copy, 1.0f); + resizedImage.UnlockFocus(); + return resizedImage; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs new file mode 100644 index 0000000..f9104d4 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSScrollViewExtensions.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using AppKit; +using PointF = CoreGraphics.CGPoint; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal static class NSScrollViewExtensions + { + public static Task ScrollToPositionAsync(this NSScrollView scrollView, PointF point, bool animate, + double duration = 0.5) + { + if (!animate) + { + var nsView = scrollView.DocumentView as NSView; + nsView?.ScrollPoint(point); + return Task.FromResult(true); + } + + TaskCompletionSource source = new TaskCompletionSource(); + + NSAnimationContext.BeginGrouping(); + + NSAnimationContext.CurrentContext.CompletionHandler += () => { source.TrySetResult(true); }; + + NSAnimationContext.CurrentContext.Duration = duration; + + var animator = scrollView.ContentView.Animator as NSView; + + animator?.SetBoundsOrigin(point); + + NSAnimationContext.EndGrouping(); + + return source.Task; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs new file mode 100644 index 0000000..c81e31e --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSTableViewExtensions.cs @@ -0,0 +1,22 @@ +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal static class NSTableViewExtensions + { + public static NSTableView AsListViewLook(this NSTableView self) + { + self.SelectionHighlightStyle = NSTableViewSelectionHighlightStyle.SourceList; + + self.AllowsColumnReordering = false; + self.AllowsColumnResizing = false; + self.AllowsColumnSelection = false; + + //this is needed .. can we go around it ? + self.AddColumn(new NSTableColumn("1")); + //this line hides the header by default + self.HeaderView = new CustomNSTableHeaderView(); + return self; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs new file mode 100644 index 0000000..9905fcd --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSTextFieldExtensions.cs @@ -0,0 +1,46 @@ +using AppKit; +using CoreGraphics; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal static class NSTextFieldExtensions + { + public static NSTextField CreateLabel(string text) + { + var textField = new NSTextField(); + textField.StringValue = text; + textField.DrawsBackground = false; + textField.Editable = false; + textField.Bezeled = false; + textField.Selectable = false; + textField.SizeToFit(); + textField.CenterTextVertically(); + return textField; + } + + public static NSTextFieldCell CreateLabelCentered(string text) + { + var textField = new VerticallyCenteredTextFieldCell(0); + textField.StringValue = text; + textField.DrawsBackground = false; + textField.Editable = false; + textField.Bezeled = false; + textField.Selectable = false; + return textField; + } + + public static void CenterTextVertically(this NSTextField self) + { + self.CenterTextVertically(self.Frame); + } + + public static void CenterTextVertically(this NSTextField self, CGRect frame) + { + var stringHeight = self.Cell.AttributedStringValue.Size.Height; + var titleRect = self.Cell.TitleRectForBounds(frame); + var newTitleRect = new CGRect(titleRect.X, frame.Y + (frame.Height - stringHeight) / 2.0, titleRect.Width, + stringHeight); + self.Frame = newTitleRect; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs new file mode 100644 index 0000000..f556232 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/NSViewControllerExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal static class NSViewControllerExtensions + { + public static Task HandleAsyncAnimation(this NSViewController container, NSViewController fromViewController, + NSViewController toViewController, NSViewControllerTransitionOptions transitonOption, + Action animationFinishedCallback, T result) + { + var tcs = new TaskCompletionSource(); + + container.TransitionFromViewController(fromViewController, toViewController, transitonOption, () => + { + tcs.SetResult(result); + animationFinishedCallback?.Invoke(); + }); + + return tcs.Task; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs new file mode 100644 index 0000000..24c9a52 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Extensions/PageExtensions.cs @@ -0,0 +1,28 @@ +using System; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public static class PageExtensions + { + public static NSViewController CreateViewController(this Page view) + { + if (!Forms.IsInitialized) + throw new InvalidOperationException("call Forms.Init() before this"); + + if (!(view.RealParent is Application)) + { + Application app = new DefaultApplication(); + app.MainPage = view; + } + + var result = new Platform(); + result.SetPage(view); + return result.ViewController; + } + + class DefaultApplication : Application + { + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs b/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs new file mode 100644 index 0000000..4899698 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs @@ -0,0 +1,85 @@ +using System; +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public abstract class FormsApplicationDelegate : NSApplicationDelegate + { + Application _application; + bool _isSuspended; + + public abstract NSWindow MainWindow { get; } + + protected override void Dispose(bool disposing) + { + if (disposing && _application != null) + _application.PropertyChanged -= ApplicationOnPropertyChanged; + + base.Dispose(disposing); + } + + protected void LoadApplication(Application application) + { + if (application == null) + throw new ArgumentNullException(nameof(application)); + + Application.Current = application; + _application = application; + + application.PropertyChanged += ApplicationOnPropertyChanged; + } + + public override void DidFinishLaunching(Foundation.NSNotification notification) + { + if (MainWindow == null) + throw new InvalidOperationException("Please provide a main window in your app"); + + MainWindow.Display(); + MainWindow.MakeKeyAndOrderFront(NSApplication.SharedApplication); + if (_application == null) + throw new InvalidOperationException("You MUST invoke LoadApplication () before calling base.FinishedLaunching ()"); + + SetMainPage(); + _application.SendStart(); + } + + public override void DidBecomeActive(Foundation.NSNotification notification) + { + // applicationDidBecomeActive + // execute any OpenGL ES drawing calls + if (_application == null || !_isSuspended) return; + _isSuspended = false; + _application.SendResume(); + } + + public override async void DidResignActive(Foundation.NSNotification notification) + { + // applicationWillResignActive + if (_application == null) return; + _isSuspended = true; + await _application.SendSleepAsync(); + } + + void ApplicationOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Application.MainPage)) + UpdateMainPage(); + } + + void SetMainPage() + { + UpdateMainPage(); + } + + void UpdateMainPage() + { + if (_application.MainPage == null) + return; + + var platformRenderer = (PlatformRenderer)MainWindow.ContentViewController; + MainWindow.ContentViewController = _application.MainPage.CreateViewController(); + (platformRenderer?.Platform as IDisposable)?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs b/Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs new file mode 100644 index 0000000..7a73ace --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/ImageSourceHandlers.cs @@ -0,0 +1,64 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public interface IImageSourceHandler : IRegisterable + { + Task LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), + float scale = 1); + } + + public sealed class FileImageSourceHandler : IImageSourceHandler + { + public Task LoadImageAsync(ImageSource imagesource, + CancellationToken cancelationToken = default(CancellationToken), float scale = 1f) + { + NSImage image = null; + var filesource = imagesource as FileImageSource; + var file = filesource?.File; + if (!string.IsNullOrEmpty(file)) + image = File.Exists(file) ? new NSImage(file) : null; + return Task.FromResult(image); + } + } + + public sealed class StreamImagesourceHandler : IImageSourceHandler + { + public async Task LoadImageAsync(ImageSource imagesource, + CancellationToken cancelationToken = default(CancellationToken), float scale = 1f) + { + NSImage image = null; + var streamsource = imagesource as StreamImageSource; + if (streamsource?.Stream == null) return null; + using ( + var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false)) + { + if (streamImage != null) + image = NSImage.FromStream(streamImage); + } + return image; + } + } + + public sealed class ImageLoaderSourceHandler : IImageSourceHandler + { + public async Task LoadImageAsync(ImageSource imagesource, + CancellationToken cancelationToken = default(CancellationToken), float scale = 1f) + { + NSImage image = null; + var imageLoader = imagesource as UriImageSource; + if (imageLoader != null && imageLoader.Uri != null) + { + using (var streamImage = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false)) + { + if (streamImage != null) + image = NSImage.FromStream(streamImage); + } + } + return image; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs b/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs new file mode 100644 index 0000000..c492f4c --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/ModalPageTracker.cs @@ -0,0 +1,118 @@ +using System; +using System.Threading.Tasks; +using System.Linq; +using AppKit; +using System.Collections.Generic; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class ModalPageTracker : IDisposable + { + NSViewController _renderer; + List _modals; + bool _disposed; + + public ModalPageTracker(NSViewController mainRenderer) + { + if (mainRenderer == null) + throw new ArgumentNullException(nameof(mainRenderer)); + _renderer = mainRenderer; + _renderer.View.WantsLayer = true; + _modals = new List(); + } + + public List ModalStack => _modals; + + public Task PushAsync(Page modal, bool animated) + { + _modals.Add(modal); + modal.DescendantRemoved += HandleChildRemoved; + Platform.NativeToolbarTracker.TryHide(modal as NavigationPage); + return PresentModalAsync(modal, animated); + } + + public Task PopAsync(bool animated) + { + var modal = _modals.LastOrDefault(); + if (modal == null) + throw new InvalidOperationException("No Modal pages found in the stack, make sure you pushed a modal page"); + _modals.Remove(modal); + modal.DescendantRemoved -= HandleChildRemoved; + return HideModalAsync(modal, animated); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + foreach (var modal in _modals) + Platform.DisposeModelAndChildrenRenderers(modal); + _renderer = null; + } + _disposed = true; + } + } + + void HandleChildRemoved(object sender, ElementEventArgs e) + { + var view = e.Element; + Platform.DisposeModelAndChildrenRenderers(view); + } + + Task PresentModalAsync(Page modal, bool animated) + { + var modalRenderer = Platform.GetRenderer(modal); + if (modalRenderer == null) + { + modalRenderer = Platform.CreateRenderer(modal); + Platform.SetRenderer(modal, modalRenderer); + modalRenderer.SetElementSize(new Size(_renderer.View.Bounds.Width, _renderer.View.Bounds.Height)); + } + + var toViewController = modalRenderer as NSViewController; + + var i = Math.Max(0, _renderer.ChildViewControllers.Length - 1); + var fromViewController = _renderer.ChildViewControllers[i]; + + _renderer.AddChildViewController(toViewController); + + NSViewControllerTransitionOptions option = animated + ? NSViewControllerTransitionOptions.SlideUp + : NSViewControllerTransitionOptions.None; + + var task = _renderer.HandleAsyncAnimation(fromViewController, toViewController, option, + () => + { + //Hack: adjust if needed + toViewController.View.Frame = _renderer.View.Bounds; + fromViewController.View.Layer.Hidden = true; + }, true); + return task; + } + + Task HideModalAsync(Page modal, bool animated) + { + var controller = Platform.GetRenderer(modal) as NSViewController; + + var i = Math.Max(0, _renderer.ChildViewControllers.Length - 2); + var toViewController = _renderer.ChildViewControllers[i]; + + toViewController.View.Layer.Hidden = false; + + NSViewControllerTransitionOptions option = animated + ? NSViewControllerTransitionOptions.SlideDown + : NSViewControllerTransitionOptions.None; + + var task = _renderer.HandleAsyncAnimation(controller, toViewController, option, + () => Platform.DisposeModelAndChildrenRenderers(modal), modal); + return task; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs b/Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs new file mode 100644 index 0000000..826b5b7 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs @@ -0,0 +1,429 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AppKit; +using CoreGraphics; +using Xamarin.Forms.Internals; +using Xamarin.Forms.PlatformConfiguration.macOSSpecific; + +namespace Xamarin.Forms.Platform.MacOS +{ + class NativeToolbarGroup + { + public class Item + { + public NSToolbarItem ToolbarItem; + public NSButton Button; + } + + public NativeToolbarGroup(NSToolbarItemGroup itemGroup) + { + Group = itemGroup; + Items = new List(); + } + + public NSToolbarItemGroup Group { get; } + + public List Items { get; } + } + + internal class NativeToolbarTracker : NSToolbarDelegate + { + const string ToolBarId = "AwesomeBarToolbar"; + + INavigationPageController NavigationController => _navigation; + + readonly string _defaultBackButtonTitle = "Back"; + readonly ToolbarTracker _toolbarTracker; + + NSToolbar _toolbar; + NavigationPage _navigation; + + bool _hasTabs; + + const double BackButtonItemWidth = 36; + const double ToolbarItemWidth = 44; + const double ToolbarItemHeight = 25; + const double ToolbarItemSpacing = 6; + const double ToolbarHeight = 30; + const double NavigationTitleMinSize = 300; + + const string NavigationGroupIdentifier = "NavigationGroup"; + const string TabbedGroupIdentifier = "TabbedGroup"; + const string ToolbarItemsGroupIdentifier = "ToolbarGroup"; + const string TitleGroupIdentifier = "TitleGroup"; + + NativeToolbarGroup _navigationGroup; + NativeToolbarGroup _tabbedGroup; + NativeToolbarGroup _toolbarGroup; + NativeToolbarGroup _titleGroup; + + NSView _nsToolbarItemViewer; + + public NativeToolbarTracker() + { + _toolbarTracker = new ToolbarTracker(); + _toolbarTracker.CollectionChanged += ToolbarTrackerOnCollectionChanged; + } + + public NavigationPage Navigation + { + get { return _navigation; } + set + { + if (_navigation == value) + return; + + if (_navigation != null) + _navigation.PropertyChanged -= NavigationPagePropertyChanged; + + _navigation = value; + + if (_navigation != null) + { + var parentTabbedPage = _navigation.Parent as TabbedPage; + if (parentTabbedPage != null) + { + _hasTabs = parentTabbedPage.OnThisPlatform().GetTabsStyle() == TabsStyle.OnNavigation; + } + _toolbarTracker.Target = _navigation.CurrentPage; + _navigation.PropertyChanged += NavigationPagePropertyChanged; + } + + UpdateToolBar(); + } + } + + public void TryHide(NavigationPage navPage = null) + { + if (navPage == null || navPage == _navigation) + { + Navigation = null; + } + } + + public override string[] AllowedItemIdentifiers(NSToolbar toolbar) + { + return new string[] { }; + } + + public override string[] DefaultItemIdentifiers(NSToolbar toolbar) + { + return new string[] { }; + } + + public override NSToolbarItem WillInsertItem(NSToolbar toolbar, string itemIdentifier, bool willBeInserted) + { + var group = new NSToolbarItemGroup(itemIdentifier); + var view = new NSView(); + group.View = view; + + if (itemIdentifier == NavigationGroupIdentifier) + _navigationGroup = new NativeToolbarGroup(group); + else if (itemIdentifier == TitleGroupIdentifier) + _titleGroup = new NativeToolbarGroup(group); + else if (itemIdentifier == TabbedGroupIdentifier) + _tabbedGroup = new NativeToolbarGroup(group); + else if (itemIdentifier == ToolbarItemsGroupIdentifier) + _toolbarGroup = new NativeToolbarGroup(group); + + return group; + } + + protected virtual bool HasTabs => _hasTabs; + + protected virtual NSToolbar ConfigureToolbar() + { + var toolbar = new NSToolbar(ToolBarId) + { + DisplayMode = NSToolbarDisplayMode.Icon, + AllowsUserCustomization = false, + ShowsBaselineSeparator = true, + SizeMode = NSToolbarSizeMode.Regular, + Delegate = this + }; + + return toolbar; + } + + internal void UpdateToolBar() + { + if (NSApplication.SharedApplication.MainWindow == null) + return; + + if (NavigationController == null) + { + if (_toolbar != null) + _toolbar.Visible = false; + _toolbar = null; + return; + } + + var currentPage = NavigationController.Peek(); + + if (NavigationPage.GetHasNavigationBar(currentPage)) + { + if (_toolbar == null) + { + _toolbar = ConfigureToolbar(); + NSApplication.SharedApplication.MainWindow.Toolbar = _toolbar; + + _toolbar.InsertItem(NavigationGroupIdentifier, 0); + _toolbar.InsertItem( + HasTabs ? NSToolbar.NSToolbarSpaceItemIdentifier : NSToolbar.NSToolbarFlexibleSpaceItemIdentifier, 1); + _toolbar.InsertItem(HasTabs ? TabbedGroupIdentifier : TitleGroupIdentifier, 2); + _toolbar.InsertItem(NSToolbar.NSToolbarFlexibleSpaceItemIdentifier, 3); + _toolbar.InsertItem(ToolbarItemsGroupIdentifier, 4); + } + + _toolbar.Visible = true; + UpdateToolbarItems(); + UpdateTitle(); + UpdateNavigationItems(); + if (HasTabs) + UpdateTabbedItems(); + UpdateBarBackgroundColor(); + } + else + { + if (_toolbar != null) + { + _toolbar.Visible = false; + } + } + } + + void UpdateBarBackgroundColor() + { + var bgColor = GetBackgroundColor().CGColor; + + if (_nsToolbarItemViewer?.Superview?.Superview == null || + _nsToolbarItemViewer.Superview.Superview.Superview == null) return; + // NSTitlebarView + _nsToolbarItemViewer.Superview.Superview.Superview.WantsLayer = true; + _nsToolbarItemViewer.Superview.Superview.Superview.Layer.BackgroundColor = bgColor; + } + + void NavigationPagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName.Equals(NavigationPage.BarTextColorProperty.PropertyName) || + e.PropertyName.Equals(NavigationPage.BarBackgroundColorProperty.PropertyName)) + UpdateToolBar(); + } + + void ToolbarTrackerOnCollectionChanged(object sender, EventArgs eventArgs) + { + UpdateToolbarItems(); + } + + async Task NavigateBackFrombackButton() + { + var popAsyncInner = NavigationController?.PopAsyncInner(true, true); + if (popAsyncInner != null) + await popAsyncInner; + } + + bool ShowBackButton() + { + if (_navigation == null) + return false; + + return NavigationPage.GetHasBackButton(_navigation.CurrentPage) && !IsRootPage(); + } + + bool IsRootPage() + { + if (NavigationController == null) + return true; + return NavigationController.StackDepth <= 1; + } + + NSColor GetBackgroundColor() + { + var backgroundNSColor = NSColor.Clear; + if (Navigation != null && Navigation.BarBackgroundColor != Color.Default) + backgroundNSColor = Navigation.BarBackgroundColor.ToNSColor(); + return backgroundNSColor; + } + + NSColor GetTitleColor() + { + var titleNSColor = NSColor.Black; + if (Navigation != null && Navigation?.BarTextColor != Color.Default) + titleNSColor = Navigation.BarTextColor.ToNSColor(); + + return titleNSColor; + } + + string GetCurrentPageTitle() + { + if (NavigationController == null) + return string.Empty; + return NavigationController.Peek().Title ?? ""; + } + + string GetPreviousPageTitle() + { + if (NavigationController == null || NavigationController.StackDepth <= 1) + return string.Empty; + + return NavigationController.Peek(1).Title ?? _defaultBackButtonTitle; + } + + List GetToolbarItems() + { + return _toolbarTracker.ToolbarItems.ToList(); + } + + void UpdateTitle() + { + if (_toolbar == null || _navigation == null || _titleGroup == null) + return; + + var title = GetCurrentPageTitle(); + var item = new NSToolbarItem(title); + var view = new NSView(); + var titleField = new NSTextField + { + AllowsEditingTextAttributes = true, + Bordered = false, + DrawsBackground = false, + Bezeled = false, + Editable = false, + Selectable = false, + Cell = new VerticallyCenteredTextFieldCell(0f, NSFont.TitleBarFontOfSize(18)), + StringValue = title + }; + titleField.Cell.TextColor = GetTitleColor(); + titleField.SizeToFit(); + _titleGroup.Group.MinSize = new CGSize(NavigationTitleMinSize, ToolbarHeight); + _titleGroup.Group.Subitems = new NSToolbarItem[] { item }; + view.AddSubview(titleField); + _titleGroup.Group.View = view; + //save a reference so we can paint this for the background + _nsToolbarItemViewer = _titleGroup.Group.View.Superview; + //position is hard .. we manually set the title to be centered + var totalWidth = _titleGroup.Group.View.Superview.Superview.Frame.Width; + var fieldWidth = titleField.Frame.Width; + var x = ((totalWidth - fieldWidth) / 2) - _nsToolbarItemViewer.Frame.X; + titleField.Frame = new CGRect(x, 0, fieldWidth, ToolbarHeight); + } + + void UpdateToolbarItems() + { + if (_toolbar == null || _navigation == null || _toolbarGroup == null) + return; + + var currentPage = NavigationController.Peek(); + UpdateGroup(_toolbarGroup, currentPage.ToolbarItems, ToolbarItemWidth, ToolbarItemSpacing); + } + + void UpdateNavigationItems() + { + if (_toolbar == null || _navigation == null || _navigationGroup == null) + return; + var items = new List(); + if (ShowBackButton()) + { + var backButtonItem = new ToolbarItem + { + Text = GetPreviousPageTitle(), + Command = new Command(async () => await NavigateBackFrombackButton()) + }; + items.Add(backButtonItem); + } + + UpdateGroup(_navigationGroup, items, BackButtonItemWidth, -1); + + var navItemBack = _navigationGroup.Items.FirstOrDefault(); + if (navItemBack != null) + { + navItemBack.Button.Image = NSImage.ImageNamed(NSImageName.GoLeftTemplate); + navItemBack.Button.SizeToFit(); + navItemBack.Button.AccessibilityTitle = "NSBackButton"; + } + } + + void UpdateTabbedItems() + { + if (_toolbar == null || _navigation == null || _tabbedGroup == null) + return; + + var items = new List(); + + var tabbedPage = _navigation.Parent as TabbedPage; + if (tabbedPage != null) + { + foreach (var item in tabbedPage.Children) + { + var tbI = new ToolbarItem + { + Text = item.Title, + Icon = item.Icon, + Command = new Command(() => tabbedPage.SelectedItem = item) + }; + items.Add(tbI); + } + } + + UpdateGroup(_tabbedGroup, items, ToolbarItemWidth, ToolbarItemSpacing); + } + + static void UpdateGroup(NativeToolbarGroup group, IList toolbarItems, double itemWidth, + double itemSpacing) + { + int count = toolbarItems.Count; + group.Items.Clear(); + if (count > 0) + { + var subItems = new NSToolbarItem[count]; + var view = new NSView(); + nfloat totalWidth = 0; + var currentX = 0.0; + for (int i = 0; i < toolbarItems.Count; i++) + { + var element = toolbarItems[i]; + + var item = new NSToolbarItem(element.Text); + item.Activated += (sender, e) => (element as IMenuItemController).Activate(); + + var button = new NSButton(); + button.Title = element.Text; + button.SizeToFit(); + var buttonWidth = itemWidth; + if (button.FittingSize.Width > itemWidth) + { + buttonWidth = button.FittingSize.Width + 10; + } + button.Frame = new CGRect(currentX + i * itemSpacing, 0, buttonWidth, ToolbarItemHeight); + currentX += buttonWidth; + totalWidth += button.Frame.Width; + button.Activated += (sender, e) => (element as IMenuItemController).Activate(); + + button.BezelStyle = NSBezelStyle.TexturedRounded; + if (!string.IsNullOrEmpty(element.Icon)) + button.Image = new NSImage(element.Icon); + + button.SizeToFit(); + view.AddSubview(button); + + item.Label = item.PaletteLabel = item.ToolTip = button.ToolTip = element.Text; + + subItems[i] = item; + + group.Items.Add(new NativeToolbarGroup.Item { ToolbarItem = item, Button = button }); + } + view.Frame = new CGRect(0, 0, totalWidth + (itemSpacing * (count - 1)), ToolbarItemHeight); + + group.Group.Subitems = subItems; + group.Group.View = view; + } + else + { + group.Group.Subitems = new NSToolbarItem[] { }; + group.Group.View = new NSView(); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Platform.cs b/Xamarin.Forms.Platform.MacOS/Platform.cs new file mode 100644 index 0000000..8629684 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Platform.cs @@ -0,0 +1,262 @@ +using System; +using AppKit; +using RectangleF = CoreGraphics.CGRect; +using System.Linq; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class Platform : BindableObject, IPlatform, IDisposable + { + internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", + typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer), + propertyChanged: (bindable, oldvalue, newvalue) => + { + var view = bindable as VisualElement; + if (view != null) + view.IsPlatformEnabled = newvalue != null; + }); + + readonly PlatformRenderer PlatformRenderer; + + bool _appeared; + bool _disposed; + + internal static NativeToolbarTracker NativeToolbarTracker = new NativeToolbarTracker(); + + internal Platform() + { + PlatformRenderer = new PlatformRenderer(this); + + MessagingCenter.Subscribe(this, Page.AlertSignalName, (Page sender, AlertArguments arguments) => + { + var alert = NSAlert.WithMessage(arguments.Title, arguments.Cancel, arguments.Accept, null, arguments.Message); + var result = alert.RunModal(); + arguments.SetResult(result == 1); + }); + + MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, (Page sender, ActionSheetArguments arguments) => + { + var alert = NSAlert.WithMessage(arguments.Title, arguments.Cancel, arguments.Destruction, null, ""); + if (arguments.Buttons != null) + { + alert.AccessoryView = GetExtraButton(arguments); + alert.Layout(); + } + + var result = (int)alert.RunSheetModal(NSApplication.SharedApplication.MainWindow); + var titleResult = string.Empty; + if (result == 1) + titleResult = arguments.Cancel; + else if (result == 0) + titleResult = arguments.Destruction; + else if (result > 1 && arguments.Buttons != null && result - 2 <= arguments.Buttons.Count()) + titleResult = arguments.Buttons.ElementAt(result - 2); + + arguments.SetResult(titleResult); + }); + } + + SizeRequest IPlatform.GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint) + { + var renderView = GetRenderer(view); + if (renderView == null || renderView.NativeView == null) + return new SizeRequest(Size.Zero); + + return renderView.GetDesiredSize(widthConstraint, heightConstraint); + } + + Page Page { get; set; } + + Application TargetApplication + { + get + { + if (Page == null) + return null; + return Page.RealParent as Application; + } + } + + void IDisposable.Dispose() + { + if (_disposed) + return; + _disposed = true; + + Page.DescendantRemoved -= HandleChildRemoved; + MessagingCenter.Unsubscribe(this, Page.ActionSheetSignalName); + MessagingCenter.Unsubscribe(this, Page.AlertSignalName); + MessagingCenter.Unsubscribe(this, Page.BusySetSignalName); + + DisposeModelAndChildrenRenderers(Page); + PlatformRenderer.Dispose(); + } + + public static IVisualElementRenderer CreateRenderer(VisualElement element) + { + var t = element.GetType(); + var renderer = Registrar.Registered.GetHandler(t) ?? new DefaultRenderer(); + renderer.SetElement(element); + return renderer; + } + + public static IVisualElementRenderer GetRenderer(VisualElement bindable) + { + return (IVisualElementRenderer)bindable.GetValue(RendererProperty); + } + + public static void SetRenderer(VisualElement bindable, IVisualElementRenderer value) + { + bindable.SetValue(RendererProperty, value); + } + + protected override void OnBindingContextChanged() + { + SetInheritedBindingContext(Page, BindingContext); + + base.OnBindingContextChanged(); + } + + internal NSViewController ViewController => PlatformRenderer; + + internal static void DisposeModelAndChildrenRenderers(Element view) + { + IVisualElementRenderer renderer; + foreach (VisualElement child in view.Descendants()) + DisposeModelAndChildrenRenderers(child); + + renderer = GetRenderer((VisualElement)view); + if (renderer?.ViewController?.ParentViewController != null) + renderer?.ViewController?.RemoveFromParentViewController(); + + renderer?.NativeView?.RemoveFromSuperview(); + renderer?.Dispose(); + + view.ClearValue(RendererProperty); + } + + internal static void DisposeRendererAndChildren(IVisualElementRenderer rendererToRemove) + { + if (rendererToRemove == null || rendererToRemove.Element == null) + return; + + if (GetRenderer(rendererToRemove.Element) == rendererToRemove) + rendererToRemove.Element.ClearValue(RendererProperty); + + if (rendererToRemove.NativeView != null) + { + var subviews = rendererToRemove.NativeView.Subviews; + for (var i = 0; i < subviews.Length; i++) + { + var childRenderer = subviews[i] as IVisualElementRenderer; + if (childRenderer != null) + DisposeRendererAndChildren(childRenderer); + } + + rendererToRemove.NativeView.RemoveFromSuperview(); + } + rendererToRemove.Dispose(); + } + + internal void LayoutSubviews() + { + if (Page == null) + return; + + var rootRenderer = GetRenderer(Page); + + if (rootRenderer == null) + return; + + rootRenderer.SetElementSize(new Size(PlatformRenderer.View.Bounds.Width, PlatformRenderer.View.Bounds.Height)); + } + + internal void SetPage(Page newRoot) + { + if (newRoot == null) + return; + if (Page != null) + throw new NotImplementedException(); + Page = newRoot; + + if (_appeared == false) + return; + + Page.Platform = this; + AddChild(Page); + + Page.DescendantRemoved += HandleChildRemoved; + + TargetApplication.NavigationProxy.Inner = PlatformRenderer.Navigation; + } + + internal void DidAppear() + { + PlatformRenderer.Navigation.AnimateModalPages = false; + TargetApplication.NavigationProxy.Inner = PlatformRenderer.Navigation; + PlatformRenderer.Navigation.AnimateModalPages = true; + } + + internal void WillAppear() + { + if (_appeared) + return; + + Page.Platform = this; + AddChild(Page); + + Page.DescendantRemoved += HandleChildRemoved; + + _appeared = true; + } + + static NSView GetExtraButton(ActionSheetArguments arguments) + { + var newView = new NSView(); + int height = 50; + int width = 300; + int i = 0; + foreach (var button in arguments.Buttons) + { + var btn = new NSButton { Title = button, Tag = i }; + btn.SetButtonType(NSButtonType.MomentaryPushIn); + btn.Activated += + (s, e) => + { + NSApplication.SharedApplication.EndSheet(NSApplication.SharedApplication.MainWindow.AttachedSheet, + ((NSButton)s).Tag + 2); + }; + btn.Frame = new RectangleF(0, height * i, width, height); + newView.AddSubview(btn); + i++; + } + newView.Frame = new RectangleF(0, 0, width, height * i); + return newView; + } + + void AddChild(VisualElement view) + { + if (!Application.IsApplicationOrNull(view.RealParent)) + Console.Error.WriteLine("Tried to add parented view to canvas directly"); + + if (GetRenderer(view) == null) + { + var viewRenderer = CreateRenderer(view); + SetRenderer(view, viewRenderer); + + PlatformRenderer.View.AddSubview(viewRenderer.NativeView); + if (viewRenderer.ViewController != null) + PlatformRenderer.AddChildViewController(viewRenderer.ViewController); + viewRenderer.SetElementSize(new Size(PlatformRenderer.View.Bounds.Width, PlatformRenderer.View.Bounds.Height)); + } + else + Console.Error.WriteLine("A Renderer was already found, potential view double add"); + } + + void HandleChildRemoved(object sender, ElementEventArgs e) + { + var view = e.Element; + DisposeModelAndChildrenRenderers(view); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs b/Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs new file mode 100644 index 0000000..7af8f07 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/PlatformNavigation.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class PlatformNavigation : INavigation, IDisposable + { + ModalPageTracker _modalTracker; + PlatformRenderer _platformRenderer; + bool _animateModals; + bool _disposed; + + public PlatformNavigation(PlatformRenderer mainRenderer) + { + _platformRenderer = mainRenderer; + _modalTracker = new ModalPageTracker(_platformRenderer); + _animateModals = true; + } + + public IReadOnlyList ModalStack => _modalTracker.ModalStack; + + public IReadOnlyList NavigationStack => new List(); + + public bool AnimateModalPages + { + get { return _animateModals; } + set { _animateModals = value; } + } + + Task INavigation.PopAsync() + { + return ((INavigation)this).PopAsync(true); + } + + Task INavigation.PopAsync(bool animated) + { + throw new InvalidOperationException("PopAsync is not supported globally on MacOS, please use a NavigationPage."); + } + + Task INavigation.PopToRootAsync() + { + return ((INavigation)this).PopToRootAsync(true); + } + + Task INavigation.PopToRootAsync(bool animated) + { + throw new InvalidOperationException("PopToRootAsync is not supported globally on MacOS, please use a NavigationPage."); + } + + Task INavigation.PushAsync(Page root) + { + return ((INavigation)this).PushAsync(root, true); + } + + Task INavigation.PushAsync(Page root, bool animated) + { + throw new InvalidOperationException("PushAsync is not supported globally on MacOS, please use a NavigationPage."); + } + + Task INavigation.PushModalAsync(Page modal) + { + return ((INavigation)this).PushModalAsync(modal, true); + } + + Task INavigation.PopModalAsync() + { + return ((INavigation)this).PopModalAsync(true); + } + + Task INavigation.PushModalAsync(Page modal, bool animated) + { + modal.Platform = _platformRenderer.Platform; + return _modalTracker.PushAsync(modal, _animateModals && animated); + } + + Task INavigation.PopModalAsync(bool animated) + { + return _modalTracker.PopAsync(animated); + } + + void INavigation.RemovePage(Page page) + { + throw new InvalidOperationException("RemovePage is not supported globally on macOS, please use a NavigationPage."); + } + + void INavigation.InsertPageBefore(Page page, Page before) + { + throw new InvalidOperationException( + "InsertPageBefore is not supported globally on macOS, please use a NavigationPage."); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _modalTracker.Dispose(); + _modalTracker = null; + _platformRenderer = null; + } + + _disposed = true; + } + } + + public void Dispose() + { + Dispose(true); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs b/Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs new file mode 100644 index 0000000..c0fcae3 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/PlatformRenderer.cs @@ -0,0 +1,50 @@ +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class PlatformRenderer : NSViewController + { + PlatformNavigation _platformNavigation; + bool _disposed; + + internal PlatformRenderer(Platform platform) + { + Platform = platform; + View = new NSView(NSApplication.SharedApplication.Windows[0].Frame); + _platformNavigation = new PlatformNavigation(this); + } + + public Platform Platform { get; set; } + + public PlatformNavigation Navigation => _platformNavigation; + + public override void ViewDidAppear() + { + Platform.DidAppear(); + base.ViewDidAppear(); + } + + public override void ViewDidLayout() + { + base.ViewDidLayout(); + Platform.LayoutSubviews(); + } + + public override void ViewWillAppear() + { + Platform.WillAppear(); + base.ViewWillAppear(); + } + + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + _platformNavigation.Dispose(); + _platformNavigation = null; + } + _disposed = true; + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a9383ba --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Xamarin.Forms.Platform.MacOS; +using Xamarin.Forms; +using Xamarin.Forms.Internals; + +[assembly: AssemblyTitle("Xamarin.Forms.Platform.macOS")] +[assembly: AssemblyDescription("macOS Backend for Xamarin.Forms")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + +[assembly: Xamarin.Forms.Dependency(typeof(Deserializer))] +[assembly: Xamarin.Forms.Dependency(typeof(ResourcesProvider))] +[assembly: ExportImageSourceHandler(typeof(FileImageSource), typeof(FileImageSourceHandler))] +[assembly: ExportImageSourceHandler(typeof(StreamImageSource), typeof(StreamImagesourceHandler))] +[assembly: ExportImageSourceHandler(typeof(UriImageSource), typeof(ImageLoaderSourceHandler))] +[assembly: ExportRenderer(typeof(Page), typeof(PageRenderer))] +[assembly: ExportRenderer(typeof(CarouselPage), typeof(CarouselPageRenderer))] +[assembly: ExportRenderer(typeof(MasterDetailPage), typeof(MasterDetailPageRenderer))] +[assembly: ExportRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer))] +[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))] +[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))] +[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))] +[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))] +[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))] +[assembly: ExportRenderer(typeof(ActivityIndicator), typeof(ActivityIndicatorRenderer))] +[assembly: ExportRenderer(typeof(DatePicker), typeof(DatePickerRenderer))] +[assembly: ExportRenderer(typeof(Editor), typeof(EditorRenderer))] +[assembly: ExportRenderer(typeof(Entry), typeof(EntryRenderer))] +[assembly: ExportRenderer(typeof(Frame), typeof(FrameRenderer))] +[assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))] +[assembly: ExportRenderer(typeof(OpenGLView), typeof(OpenGLViewRenderer))] +[assembly: ExportRenderer(typeof(Picker), typeof(PickerRenderer))] +[assembly: ExportRenderer(typeof(ProgressBar), typeof(ProgressBarRenderer))] +[assembly: ExportRenderer(typeof(SearchBar), typeof(SearchBarRenderer))] +[assembly: ExportRenderer(typeof(Slider), typeof(SliderRenderer))] +[assembly: ExportRenderer(typeof(Stepper), typeof(StepperRenderer))] +[assembly: ExportRenderer(typeof(Switch), typeof(SwitchRenderer))] +[assembly: ExportRenderer(typeof(TimePicker), typeof(TimePickerRenderer))] +[assembly: ExportRenderer(typeof(WebView), typeof(WebViewRenderer))] +[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))] +[assembly: ExportRenderer(typeof(TableView), typeof(TableViewRenderer))] +[assembly: ExportRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer))] +[assembly: ExportRenderer(typeof(Layout), typeof(LayoutRenderer))] +[assembly: ExportCell(typeof(Cell), typeof(CellRenderer))] +[assembly: ExportCell(typeof(TextCell), typeof(TextCellRenderer))] +[assembly: ExportCell(typeof(ImageCell), typeof(ImageCellRenderer))] +[assembly: ExportCell(typeof(EntryCell), typeof(EntryCellRenderer))] +[assembly: ExportCell(typeof(ViewCell), typeof(ViewCellRenderer))] +[assembly: ExportCell(typeof(SwitchCell), typeof(SwitchCellRenderer))] \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs new file mode 100644 index 0000000..2e17916 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ActivityIndicatorRenderer.cs @@ -0,0 +1,70 @@ +using System.ComponentModel; +using System.Drawing; +using AppKit; +using CoreImage; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ActivityIndicatorRenderer : ViewRenderer + { + static CIColorPolynomial s_currentColorFilter; + static NSColor s_currentColor; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (e.NewElement != null) + { + if (Control == null) + SetNativeControl(new NSProgressIndicator(RectangleF.Empty) { Style = NSProgressIndicatorStyle.Spinning }); + + UpdateColor(); + UpdateIsRunning(); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == ActivityIndicator.ColorProperty.PropertyName) + UpdateColor(); + else if (e.PropertyName == ActivityIndicator.IsRunningProperty.PropertyName) + UpdateIsRunning(); + } + + void UpdateColor() + { + var color = Element.Color; + if (s_currentColorFilter == null && color.IsDefault) + return; + + if (color.IsDefault) + Control.ContentFilters = new CIFilter[0]; + + var newColor = Element.Color.ToNSColor(); + if (Equals(s_currentColor, newColor)) + return; + + s_currentColor = newColor; + + s_currentColorFilter = new CIColorPolynomial + { + RedCoefficients = new CIVector(s_currentColor.RedComponent), + BlueCoefficients = new CIVector(s_currentColor.BlueComponent), + GreenCoefficients = new CIVector(s_currentColor.GreenComponent) + }; + + Control.ContentFilters = new CIFilter[] { s_currentColorFilter }; + } + + void UpdateIsRunning() + { + if (Element.IsRunning) + Control.StartAnimation(this); + else + Control.StopAnimation(this); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs new file mode 100644 index 0000000..d4e9ad0 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/BoxViewRenderer.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class BoxViewRenderer : ViewRenderer + { + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new NSView()); + } + SetBackgroundColor(Element.Color); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + if (e.PropertyName == BoxView.ColorProperty.PropertyName) + SetBackgroundColor(Element.BackgroundColor); + else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName && Element.IsVisible) + SetNeedsDisplayInRect(Bounds); + } + + protected override void SetBackgroundColor(Color color) + { + if (Element == null || Control == null) + return; + Control.WantsLayer = true; + Control.Layer.BackgroundColor = color.ToCGColor(); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs new file mode 100644 index 0000000..05b87fa --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ButtonRenderer.cs @@ -0,0 +1,131 @@ +using System; +using System.ComponentModel; +using AppKit; +using Foundation; +using SizeF = CoreGraphics.CGSize; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ButtonRenderer : ViewRenderer + { + protected override void Dispose(bool disposing) + { + if (Control != null) + Control.Activated -= OnButtonActivated; + + base.Dispose(disposing); + } + + protected override void OnElementChanged(ElementChangedEventArgs