diff options
author | Rui Marinho <me@ruimarinho.net> | 2017-01-26 15:33:15 +0000 |
---|---|---|
committer | Kangho Hur <kangho.hur@samsung.com> | 2017-03-24 13:15:53 +0900 |
commit | 30ff03b0a6c62aade8cc2b347f0e316e9709a8ce (patch) | |
tree | d7e490c28c1be486d4a74d94228221191f0fe5da | |
parent | 4ad3748724c4c36e99fa55e8dc8c2626aa27625b (diff) | |
download | xamarin-forms-30ff03b0a6c62aade8cc2b347f0e316e9709a8ce.tar.gz xamarin-forms-30ff03b0a6c62aade8cc2b347f0e316e9709a8ce.tar.bz2 xamarin-forms-30ff03b0a6c62aade8cc2b347f0e316e9709a8ce.zip |
MacOS (#650)
* [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
124 files changed, 9298 insertions, 164 deletions
diff --git a/.nuspec/Xamarin.Forms.Maps.nuspec b/.nuspec/Xamarin.Forms.Maps.nuspec index 9e7aef47..7221c1c7 100644 --- a/.nuspec/Xamarin.Forms.Maps.nuspec +++ b/.nuspec/Xamarin.Forms.Maps.nuspec @@ -30,7 +30,7 @@ </group> </dependencies> <references> - <group targetFramework="portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10"> + <group targetFramework="portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20"> <reference file="Xamarin.Forms.Maps.dll" /> </group> <group targetFramework="Xamarin.iOS10"> @@ -56,11 +56,16 @@ <reference file="Xamarin.Forms.Maps.dll" /> <reference file="Xamarin.Forms.Maps.WP8.dll" /> </group> + <group targetFramework="Xamarin.Mac"> + <reference file="Xamarin.Forms.Maps.dll" /> + <reference file="Xamarin.Forms.Maps.macOS.dll" /> + <reference file="Xamarin.Forms.Maps.MacOS.Extra.dll" /> + </group> </references> </metadata> <files> - <file src="..\Xamarin.Forms.Maps\bin\$Configuration$\Xamarin.Forms.Maps.dll" target="lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10" /> - <file src="..\docs\Xamarin.Forms.Maps.xml" target="lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10" /> + <file src="..\Xamarin.Forms.Maps\bin\$Configuration$\Xamarin.Forms.Maps.dll" target="lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20" /> + <file src="..\docs\Xamarin.Forms.Maps.xml" target="lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20" /> <file src="..\Xamarin.Forms.Maps.Android\bin\$Configuration$\Xamarin.Forms.Maps.Android.dll" target="lib\MonoAndroid10" /> <file src="..\Xamarin.Forms.Maps\bin\$Configuration$\Xamarin.Forms.Maps.dll" target="lib\MonoAndroid10" /> @@ -127,6 +132,10 @@ <file src="..\Xamarin.Forms.Maps.Design\bin\$Configuration$\Xamarin.Forms.Maps.Design.dll" target="lib\win81\Design" /> <file src="..\Xamarin.Forms.Maps.Design\bin\$Configuration$\Xamarin.Forms.Maps.Design.dll" target="lib\uap10.0\Design" /> + <file src="..\Xamarin.Forms.Maps.MacOS\bin\$Configuration$\Xamarin.Forms.Maps.macOS.dll" target="lib\Xamarin.Mac" /> + <file src="..\Xamarin.Forms.Maps.MacOS\bin\$Configuration$\Xamarin.Forms.Maps.MacOS.Extra.dll" target="lib\Xamarin.Mac" /> + <file src="..\Xamarin.Forms.Maps\bin\$Configuration$\Xamarin.Forms.Maps.dll" target="lib\Xamarin.Mac" /> + <file src="..\docs\Xamarin.Forms.Maps.xml" target="lib\Xamarin.Mac" /> </files> </package> diff --git a/.nuspec/Xamarin.Forms.nuspec b/.nuspec/Xamarin.Forms.nuspec index ce123806..879280a9 100644 --- a/.nuspec/Xamarin.Forms.nuspec +++ b/.nuspec/Xamarin.Forms.nuspec @@ -76,6 +76,12 @@ <reference file="Xamarin.Forms.Xaml.dll" /> <reference file="Xamarin.Forms.Platform.WP8.dll" /> </group> + <group targetFramework="Xamarin.Mac"> + <reference file="Xamarin.Forms.Core.dll" /> + <reference file="Xamarin.Forms.Platform.MacOS.dll" /> + <reference file="Xamarin.Forms.Platform.dll" /> + <reference file="Xamarin.Forms.Xaml.dll" /> + </group> </references> </metadata> <files> @@ -219,10 +225,16 @@ <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\PageControl.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\Resources.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\FormsTextBoxStyle.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> - <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\AutoSuggestStyle.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> + <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\AutoSuggestStyle.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\MasterDetailControlStyle.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\PageControlStyle.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> <file src="..\Xamarin.Forms.Platform.UAP\bin\$Configuration$\TabbedPageStyle.xbf" target="lib\uap10.0\Xamarin.Forms.Platform.UAP" /> + + <!--Mac--> + <file src="..\Xamarin.Forms.Core\bin\$Configuration$\Xamarin.Forms.Core.dll" target="lib\Xamarin.Mac" /> + <file src="..\Xamarin.Forms.Xaml\bin\$Configuration$\Xamarin.Forms.Xaml.dll" target="lib\Xamarin.Mac" /> + <file src="..\Xamarin.Forms.Platform.MacOS\bin\$Configuration$\Xamarin.Forms.Platform.MacOS.dll" target="lib\Xamarin.Mac" /> + <file src="..\Xamarin.Forms.Platform.MacOS\bin\$Configuration$\Xamarin.Forms.Platform.dll" target="lib\Xamarin.Mac" /> <file src="..\Xamarin.Forms.Core\bin\$Configuration$\Xamarin.Forms.Core.dll" target="lib\uap10.0" /> <file src="..\docs\Xamarin.Forms.Core.xml" target="lib\uap10.0" /> diff --git a/Xamarin.Forms.Controls/GalleryPages/MacOSTestGallery.cs b/Xamarin.Forms.Controls/GalleryPages/MacOSTestGallery.cs new file mode 100644 index 00000000..3b44683f --- /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<MyItem1>(); + 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 f18251d2..ba83ca0e 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 0751e1c2..8f234cfd 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 b74e9b9a..fbddfeb7 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 00000000..3c51805b --- /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<macOS, FormsElement> config) + { + return GetTabsStyle(config.Element); + } + + public static IPlatformElementConfiguration<macOS, FormsElement> SetShowTabs(this IPlatformElementConfiguration<macOS, FormsElement> config, TabsStyle value) + { + SetTabsStyle(config.Element, value); + return config; + } + + public static IPlatformElementConfiguration<macOS, FormsElement> ShowTabsOnNavigation(this IPlatformElementConfiguration<macOS, FormsElement> config) + { + SetTabsStyle(config.Element, TabsStyle.OnNavigation); + return config; + } + + public static IPlatformElementConfiguration<macOS, FormsElement> ShowTabs(this IPlatformElementConfiguration<macOS, FormsElement> config) + { + SetTabsStyle(config.Element, TabsStyle.Default); + return config; + } + + public static IPlatformElementConfiguration<macOS, FormsElement> HideTabs(this IPlatformElementConfiguration<macOS, FormsElement> 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 00000000..0b498ae5 --- /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 6bbdb600..8f50765b 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 6125695f..ff34d83a 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 ebc9f8b0..6b978ac9 100644 --- a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj +++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj @@ -445,6 +445,8 @@ <Compile Include="ProvideCompiledAttribute.cs" /> <Compile Include="TypedBinding.cs" /> <Compile Include="XmlnsDefinitionAttribute.cs" /> + <Compile Include="PlatformConfiguration\macOSSpecific\TabbedPage.cs" /> + <Compile Include="PlatformConfiguration\macOSSpecific\TabsStyle.cs" /> </ItemGroup> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <ItemGroup> @@ -459,4 +461,7 @@ </PostBuildEvent> </PropertyGroup> <ItemGroup /> + <ItemGroup> + <Folder Include="PlatformConfiguration\macOSSpecific\" /> + </ItemGroup> </Project>
\ 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 00000000..c6a92d91 --- /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 00000000..9e5eaa0c --- /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 00000000..af4f4aac --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS.Extra/Xamarin.Forms.Maps.MacOS.Extra.csproj @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{6346D187-BA87-4F18-A165-F27C5B7F25D0}</ProjectGuid> + <ProjectTypeGuids>{810C163F-4746-4721-8B8E-88A3673A62EA};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <OutputType>Library</OutputType> + <RootNamespace>Xamarin.Forms.Maps.MacOS.Extra</RootNamespace> + <AssemblyName>Xamarin.Forms.Maps.MacOS.Extra</AssemblyName> + <MacResourcePrefix>Resources</MacResourcePrefix> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <AllowUnsafeBlocks>false</AllowUnsafeBlocks> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <DefineConstants></DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <AllowUnsafeBlocks>false</AllowUnsafeBlocks> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="Xamarin.Mac" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ObjcBindingApiDefinition Include="ApiDefinition.cs" /> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath)\Xamarin\Mac\Xamarin.Mac.ObjcBinding.CSharp.targets" /> +</Project>
\ 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 Binary files differnew file mode 100755 index 00000000..0c17fff6 --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS/Libs/Xamarin.Forms.Maps.MacOS.Extra.dll diff --git a/Xamarin.Forms.Maps.MacOS/Properties/AssemblyInfo.cs b/Xamarin.Forms.Maps.MacOS/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..26a0c014 --- /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 00000000..af3da201 --- /dev/null +++ b/Xamarin.Forms.Maps.MacOS/Xamarin.Forms.Maps.macOS.csproj @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}</ProjectGuid> + <ProjectTypeGuids>{A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <OutputType>Library</OutputType> + <RootNamespace>Xamarin.Forms.Maps.MacOS</RootNamespace> + <AssemblyName>Xamarin.Forms.Maps.macOS</AssemblyName> + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion> + <TargetFrameworkIdentifier>Xamarin.Mac</TargetFrameworkIdentifier> + <MonoMacResourcePrefix>Resources</MonoMacResourcePrefix> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <EnableCodeSigning>false</EnableCodeSigning> + <CreatePackage>false</CreatePackage> + <EnablePackageSigning>false</EnablePackageSigning> + <IncludeMonoRuntime>false</IncludeMonoRuntime> + <UseSGen>false</UseSGen> + <HttpClientHandler></HttpClientHandler> + <TlsProvider></TlsProvider> + <LinkMode></LinkMode> + <XamMacArch></XamMacArch> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <DefineConstants></DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <EnableCodeSigning>false</EnableCodeSigning> + <CreatePackage>false</CreatePackage> + <EnablePackageSigning>false</EnablePackageSigning> + <IncludeMonoRuntime>false</IncludeMonoRuntime> + <UseSGen>false</UseSGen> + <HttpClientHandler></HttpClientHandler> + <TlsProvider></TlsProvider> + <LinkMode></LinkMode> + <XamMacArch></XamMacArch> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="Xamarin.Mac" /> + <Reference Include="Xamarin.Forms.Maps.MacOS.Extra"> + <HintPath>Libs\Xamarin.Forms.Maps.MacOS.Extra.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="..\Xamarin.Forms.Maps.iOS\FormsMaps.cs"> + <Link>FormsMaps.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Maps.iOS\GeocoderBackend.cs"> + <Link>GeocoderBackend.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Maps.iOS\MapRenderer.cs"> + <Link>MapRenderer.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Core\Properties\GlobalAssemblyInfo.cs"> + <Link>Properties\GlobalAssemblyInfo.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Maps.iOS\MapPool.cs"> + <Link>MapPool.cs</Link> + </Compile> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> + <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project> + <Name>Xamarin.Forms.Core</Name> + </ProjectReference> + <ProjectReference Include="..\Xamarin.Forms.Platform.MacOS\Xamarin.Forms.Platform.MacOS.csproj"> + <Project>{C0059C45-EA1E-42F3-8A0E-794BB547EC3C}</Project> + <Name>Xamarin.Forms.Platform.MacOS</Name> + </ProjectReference> + <ProjectReference Include="..\Xamarin.Forms.Maps\Xamarin.Forms.Maps.csproj"> + <Project>{7D13BAC2-C6A4-416A-B07E-C169B199E52B}</Project> + <Name>Xamarin.Forms.Maps</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="Libs\Xamarin.Forms.Maps.MacOS.Extra.dll" /> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath)\Xamarin\Mac\Xamarin.Mac.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/Xamarin.Forms.Maps.iOS/FormsMaps.cs b/Xamarin.Forms.Maps.iOS/FormsMaps.cs index a87cb8dc..0f5df423 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 08ffbbec..e5d1b25c 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<IEnumerable<string>> GetAddressesForPositionAsync(Position position) { var location = new CLLocation(position.Latitude, position.Longitude); - var geocoder = new CLGeocoder(); + var geocoder = new CCLGeocoder(); var source = new TaskCompletionSource<IEnumerable<string>>(); geocoder.ReverseGeocodeLocation(location, (placemarks, error) => { if (placemarks == null) placemarks = new CLPlacemark[0]; - IEnumerable<string> addresses = placemarks.Select(p => ABAddressFormatting.ToString(p.AddressDictionary, false)); + List<string> addresses = new List<string>(); +#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<IEnumerable<Position>> GetPositionsForAddressAsync(string address) { - var geocoder = new CLGeocoder(); + var geocoder = new CCLGeocoder(); var source = new TaskCompletionSource<IEnumerable<Position>>(); geocoder.GeocodeAddress(address, (placemarks, error) => { diff --git a/Xamarin.Forms.Maps.iOS/MapPool.cs b/Xamarin.Forms.Maps.iOS/MapPool.cs index dc8a9c47..1b3823c7 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 794a4a04..0616fa9b 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<object> List = new List<object>(); 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<NSClickGestureRecognizer> 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 2220ffff..1e3c2f37 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 00000000..1e965ff5 --- /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<Action> _queue = new BlockingCollection<Action>(); + 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 00000000..1ba964ee --- /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<object, PropertyChangedEventArgs> 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 00000000..54e540a6 --- /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 00000000..43789dcc --- /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 00000000..8bd76772 --- /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<IImageSourceHandler>(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 00000000..3e0235d4 --- /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 00000000..5f086b49 --- /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 00000000..6e36ce7d --- /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 00000000..0dd766a2 --- /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<IVisualElementRenderer> _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<IVisualElementRenderer>(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 00000000..8b345181 --- /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 00000000..33ed73b6 --- /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 00000000..5fb7455c --- /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<NSObject, string> _getIdentifier; + readonly Func<string, NSViewController> _getViewController; + + public FormsPageControllerDelegate(Func<NSObject, string> getIdentifier, + Func<string, NSViewController> 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 00000000..1fc23876 --- /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 00000000..ee02293c --- /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<NSToolbarItem>(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 00000000..a0e6dd41 --- /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 00000000..a8a825c0 --- /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 00000000..397f632c --- /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 00000000..35d0f26c --- /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 00000000..1bdc62ae --- /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 00000000..d37f1a3d --- /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 00000000..09521731 --- /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 00000000..f9104d4a --- /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<bool> source = new TaskCompletionSource<bool>(); + + 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 00000000..c81e31eb --- /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 00000000..9905fcd5 --- /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 00000000..f5562322 --- /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<T> HandleAsyncAnimation<T>(this NSViewController container, NSViewController fromViewController, + NSViewController toViewController, NSViewControllerTransitionOptions transitonOption, + Action animationFinishedCallback, T result) + { + var tcs = new TaskCompletionSource<T>(); + + 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 00000000..24c9a527 --- /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 00000000..4899698e --- /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 00000000..7a73ace4 --- /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<NSImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), + float scale = 1); + } + + public sealed class FileImageSourceHandler : IImageSourceHandler + { + public Task<NSImage> 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<NSImage> 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<NSImage> 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 00000000..c492f4cd --- /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<Page> _modals; + bool _disposed; + + public ModalPageTracker(NSViewController mainRenderer) + { + if (mainRenderer == null) + throw new ArgumentNullException(nameof(mainRenderer)); + _renderer = mainRenderer; + _renderer.View.WantsLayer = true; + _modals = new List<Page>(); + } + + public List<Page> 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<Page> 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<Page> 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 00000000..826b5b73 --- /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<Item>(); + } + + public NSToolbarItemGroup Group { get; } + + public List<Item> 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<ToolbarItem> 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<ToolbarItem>(); + 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<ToolbarItem>(); + + 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<ToolbarItem> 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 00000000..8629684b --- /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<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName); + MessagingCenter.Unsubscribe<Page, AlertArguments>(this, Page.AlertSignalName); + MessagingCenter.Unsubscribe<Page, bool>(this, Page.BusySetSignalName); + + DisposeModelAndChildrenRenderers(Page); + PlatformRenderer.Dispose(); + } + + public static IVisualElementRenderer CreateRenderer(VisualElement element) + { + var t = element.GetType(); + var renderer = Registrar.Registered.GetHandler<IVisualElementRenderer>(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 00000000..7af8f074 --- /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<Page> ModalStack => _modalTracker.ModalStack; + + public IReadOnlyList<Page> NavigationStack => new List<Page>(); + + public bool AnimateModalPages + { + get { return _animateModals; } + set { _animateModals = value; } + } + + Task<Page> INavigation.PopAsync() + { + return ((INavigation)this).PopAsync(true); + } + + Task<Page> 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<Page> 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<Page> 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 00000000..c0fcae35 --- /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 00000000..a9383ba3 --- /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 00000000..2e179161 --- /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<ActivityIndicator, NSProgressIndicator> + { + static CIColorPolynomial s_currentColorFilter; + static NSColor s_currentColor; + + protected override void OnElementChanged(ElementChangedEventArgs<ActivityIndicator> 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 00000000..d4e9ad05 --- /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<BoxView, NSView> + { + protected override void OnElementChanged(ElementChangedEventArgs<BoxView> 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 00000000..05b87fa2 --- /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<Button, NSButton> + { + protected override void Dispose(bool disposing) + { + if (Control != null) + Control.Activated -= OnButtonActivated; + + base.Dispose(disposing); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Button> e) + { + base.OnElementChanged(e); + + if (e.NewElement != null) + { + if (Control == null) + { + var btn = new NSButton(); + btn.SetButtonType(NSButtonType.MomentaryPushIn); + SetNativeControl(btn); + + Control.Activated += OnButtonActivated; + } + + UpdateText(); + UpdateFont(); + UpdateBorder(); + UpdateImage(); + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Button.TextProperty.PropertyName || e.PropertyName == Button.TextColorProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == Button.FontProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Button.BorderWidthProperty.PropertyName || + e.PropertyName == Button.BorderRadiusProperty.PropertyName || + e.PropertyName == Button.BorderColorProperty.PropertyName) + UpdateBorder(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundVisibility(); + else if (e.PropertyName == Button.ImageProperty.PropertyName) + UpdateImage(); + } + + void OnButtonActivated(object sender, EventArgs eventArgs) + { + ((IButtonController)Element)?.SendClicked(); + } + + void UpdateBackgroundVisibility() + { + var model = Element; + var shouldDrawImage = model.BackgroundColor == Color.Default; + if (!shouldDrawImage) + Control.Cell.BackgroundColor = model.BackgroundColor.ToNSColor(); + } + + void UpdateBorder() + { + var uiButton = Control; + var button = Element; + + if (button.BorderColor != Color.Default) + uiButton.Layer.BorderColor = button.BorderColor.ToCGColor(); + + uiButton.Layer.BorderWidth = (float)button.BorderWidth; + uiButton.Layer.CornerRadius = button.BorderRadius; + + UpdateBackgroundVisibility(); + } + + void UpdateFont() + { + Control.Font = Element.Font.ToNSFont(); + } + + async void UpdateImage() + { + IImageSourceHandler handler; + FileImageSource source = Element.Image; + if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + NSImage uiimage; + try + { + uiimage = await handler.LoadImageAsync(source); + } + catch (OperationCanceledException) + { + uiimage = null; + } + NSButton button = Control; + if (button != null && uiimage != null) + { + button.Image = uiimage; + if (!string.IsNullOrEmpty(button.Title)) + button.ImagePosition = Element.ToNSCellImagePosition(); + } + } + ((IVisualElementController)Element).NativeSizeChanged(); + } + + void UpdateText() + { + var color = Element.TextColor; + if (color == Color.Default) + { + Control.Title = Element.Text ?? ""; + } + else + { + var textWithColor = new NSAttributedString(Element.Text ?? "", foregroundColor: color.ToNSColor()); + Control.AttributedTitle = textWithColor; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs new file mode 100644 index 00000000..014d503b --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/CarouselPageRenderer.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + [Register("CarouselPageRenderer")] + public class CarouselPageRenderer : NSPageController, IVisualElementRenderer + { + bool _appeared; + bool _disposed; + EventTracker _events; + VisualElementTracker _tracker; + + public CarouselPageRenderer() + { + View = new NSView + { + WantsLayer = true, + Layer = { BackgroundColor = NSColor.White.CGColor } + }; + } + + public CarouselPageRenderer(IntPtr handle) : base(handle) + { + } + + IElementController ElementController => Element; + + IPageController PageController => (IPageController)Element; + + public override nint SelectedIndex + { + get { return base.SelectedIndex; } + set + { + if (base.SelectedIndex == value) + return; + base.SelectedIndex = value; + if (Carousel != null) + Carousel.CurrentPage = (ContentPage)ElementController.LogicalChildren[(int)SelectedIndex]; + } + } + + public VisualElement Element { get; private set; } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return NativeView.GetSizeRequest(widthConstraint, heightConstraint); + } + + public NSView NativeView => View; + + public void SetElement(VisualElement element) + { + VisualElement oldElement = Element; + Element = element; + + Init(); + + OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); + } + + public void SetElementSize(Size size) + { + Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height)); + } + + public NSViewController ViewController => this; + + public override void ViewDidAppear() + { + base.ViewDidAppear(); + if (_appeared || _disposed) + return; + + _appeared = true; + PageController.SendAppearing(); + } + + public override void ViewDidDisappear() + { + base.ViewDidDisappear(); + + if (!_appeared || _disposed) + return; + + _appeared = false; + PageController.SendDisappearing(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + if (Carousel != null) + { + Carousel.PropertyChanged -= OnPropertyChanged; + Carousel.PagesChanged -= OnPagesChanged; + } + + Platform.SetRenderer(Element, null); + + if (_appeared) + { + _appeared = false; + PageController?.SendDisappearing(); + } + + if (_events != null) + { + _events.Dispose(); + _events = null; + } + + if (_tracker != null) + { + _tracker.Dispose(); + _tracker = null; + } + + Element = null; + _disposed = true; + } + + base.Dispose(disposing); + } + + void OnElementChanged(VisualElementChangedEventArgs e) + { + ElementChanged?.Invoke(this, e); + } + + void ConfigureNSPageController() + { + TransitionStyle = NSPageControllerTransitionStyle.HorizontalStrip; + } + + CarouselPage Carousel => Element as CarouselPage; + + void Init() + { + Delegate = new PageControllerDelegate(); + + _tracker = new VisualElementTracker(this); + _events = new EventTracker(this); + _events.LoadEvents(View); + + ConfigureNSPageController(); + + UpdateBackground(); + UpdateSource(); + + Carousel.PropertyChanged += OnPropertyChanged; + Carousel.PagesChanged += OnPagesChanged; + } + + void UpdateSource() + { + var pages = new List<NSPageContainer>(); + for (var i = 0; i < ElementController.LogicalChildren.Count; i++) + { + Element element = ElementController.LogicalChildren[i]; + var child = element as ContentPage; + if (child != null) + pages.Add(new NSPageContainer(child, i)); + } + + ArrangedObjects = pages.ToArray(); + UpdateCurrentPage(false); + } + + void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateSource(); + } + + void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TabbedPage.CurrentPage)) + UpdateCurrentPage(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackground(); + else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName) + UpdateBackground(); + } + + void UpdateBackground() + { + if (View.Layer == null) + return; + + string bgImage = ((Page)Element).BackgroundImage; + + if (!string.IsNullOrEmpty(bgImage)) + { + View.Layer.BackgroundColor = NSColor.FromPatternImage(NSImage.ImageNamed(bgImage)).CGColor; + return; + } + + Color bgColor = Element.BackgroundColor; + View.Layer.BackgroundColor = bgColor.IsDefault ? NSColor.White.CGColor : bgColor.ToCGColor(); + } + + void UpdateCurrentPage(bool animated = true) + { + ContentPage current = Carousel.CurrentPage; + if (current != null) + { + int index = Carousel.CurrentPage != null ? CarouselPage.GetIndex(Carousel.CurrentPage) : 0; + if (index < 0) + index = 0; + + if (SelectedIndex == index) + return; + + if (animated) + NSAnimationContext.RunAnimation(context => { ((NSPageController)Animator).SelectedIndex = index; }, + CompleteTransition); + else SelectedIndex = index; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs b/Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs new file mode 100644 index 00000000..79628b62 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/CustomNSTableHeaderView.cs @@ -0,0 +1,46 @@ +using AppKit; +using CoreGraphics; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + sealed class CustomNSTableHeaderView : NSTableHeaderView + { + public CustomNSTableHeaderView() : this(0, null) { } + public CustomNSTableHeaderView(double width, IVisualElementRenderer headerRenderer) + { + var view = new NSView { WantsLayer = true, Layer = { BackgroundColor = NSColor.Clear.CGColor } }; + AddSubview(view); + Update(width, headerRenderer); + } + + public void Update(double width, IVisualElementRenderer headerRenderer) + { + double height = 1; + if (headerRenderer != null) + { + var headerView = headerRenderer.Element; + var request = headerView.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins); + height = request.Request.Height; + var bounds = new Rectangle(0, 0, width, height); + Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(headerView, bounds); + headerRenderer.NativeView.Frame = bounds.ToRectangleF(); + AddSubview(headerRenderer.NativeView); + + } + Frame = new CGRect(0, 0, width, height); + } + + //hides default text field + public override NSAttributedString PageHeader => new NSAttributedString(""); + + public override void DrawRect(CGRect dirtyRect) { } + + public override void Layout() + { + foreach (var view in Subviews) + view.Frame = Frame; + base.Layout(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs new file mode 100644 index 00000000..f31fa361 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/DatePickerRenderer.cs @@ -0,0 +1,138 @@ +using System; +using System.ComponentModel; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class DatePickerRenderer : ViewRenderer<DatePicker, NSDatePicker> + { + NSDatePicker _picker; + NSColor _defaultTextColor; + NSColor _defaultBackgroundColor; + bool _disposed; + + IElementController ElementController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e) + { + base.OnElementChanged(e); + + if (e.OldElement == null) + { + if (Control == null) + { + _picker = new NSDatePicker + { + DatePickerMode = NSDatePickerMode.Single, + TimeZone = new NSTimeZone("UTC"), + DatePickerStyle = NSDatePickerStyle.TextFieldAndStepper, + DatePickerElements = NSDatePickerElementFlags.YearMonthDateDay + }; + _picker.ValidateProposedDateValue += HandleValueChanged; + _defaultTextColor = _picker.TextColor; + _defaultBackgroundColor = _picker.BackgroundColor; + + SetNativeControl(_picker); + } + } + + UpdateDateFromModel(); + UpdateMaximumDate(); + UpdateMinimumDate(); + UpdateTextColor(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == DatePicker.DateProperty.PropertyName || + e.PropertyName == DatePicker.FormatProperty.PropertyName) + UpdateDateFromModel(); + else if (e.PropertyName == DatePicker.MinimumDateProperty.PropertyName) + UpdateMinimumDate(); + else if (e.PropertyName == DatePicker.MaximumDateProperty.PropertyName) + UpdateMaximumDate(); + else if (e.PropertyName == DatePicker.TextColorProperty.PropertyName || + e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateTextColor(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + if (_picker != null) + _picker.ValidateProposedDateValue -= HandleValueChanged; + + _disposed = true; + } + base.Dispose(disposing); + } + + protected override void SetBackgroundColor(Color color) + { + base.SetBackgroundColor(color); + + if (Control == null) + return; + + if (color == Color.Default) + Control.BackgroundColor = _defaultBackgroundColor; + else + Control.BackgroundColor = color.ToNSColor(); + } + + void HandleValueChanged(object sender, NSDatePickerValidatorEventArgs e) + { + if (Control == null || Element == null) + return; + ElementController?.SetValueFromRenderer(DatePicker.DateProperty, _picker.DateValue.ToDateTime().Date); + } + + void OnEnded(object sender, EventArgs eventArgs) + { + ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false); + } + + void OnStarted(object sender, EventArgs eventArgs) + { + ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + + void UpdateDateFromModel() + { + if (Control == null || Element == null) + return; + if (_picker.DateValue.ToDateTime().Date != Element.Date.Date) + _picker.DateValue = Element.Date.ToNSDate(); + } + + void UpdateMaximumDate() + { + if (Control == null || Element == null) + return; + _picker.MaxDate = Element.MaximumDate.ToNSDate(); + } + + void UpdateMinimumDate() + { + if (Control == null || Element == null) + return; + _picker.MinDate = Element.MinimumDate.ToNSDate(); + } + + void UpdateTextColor() + { + if (Control == null || Element == null) + return; + var textColor = Element.TextColor; + + if (textColor.IsDefault || !Element.IsEnabled) + Control.TextColor = _defaultTextColor; + else + Control.TextColor = textColor.ToNSColor(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs new file mode 100644 index 00000000..3d1d7cd0 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/DefaultRenderer.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Forms.Platform.MacOS +{ + internal class DefaultRenderer : VisualElementRenderer<VisualElement> + { + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs new file mode 100644 index 00000000..75a6020b --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/EditorRenderer.cs @@ -0,0 +1,128 @@ +using System; +using System.ComponentModel; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class EditorRenderer : ViewRenderer<Editor, NSTextField> + { + const string NewLineSelector = "insertNewline"; + bool _disposed; + + IElementController ElementController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<Editor> e) + { + base.OnElementChanged(e); + + if (Control == null) + { + SetNativeControl(new NSTextField { UsesSingleLineMode = false }); + Control.Cell.Scrollable = true; + Control.Cell.Wraps = true; + Control.Changed += HandleChanged; + Control.EditingBegan += OnEditingBegan; + Control.EditingEnded += OnEditingEnded; + Control.DoCommandBySelector = (control, textView, commandSelector) => + { + var result = false; + if (commandSelector.Name.StartsWith(NewLineSelector, StringComparison.InvariantCultureIgnoreCase)) + { + textView.InsertText(new NSString(Environment.NewLine)); + result = true; + } + return result; + }; + } + + if (e.NewElement == null) return; + UpdateText(); + UpdateFont(); + UpdateTextColor(); + UpdateEditable(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Editor.TextProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateEditable(); + else if (e.PropertyName == Editor.TextColorProperty.PropertyName) + UpdateTextColor(); + else if (e.PropertyName == Editor.FontAttributesProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Editor.FontFamilyProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Editor.FontSizeProperty.PropertyName) + UpdateFont(); + } + + protected override void SetBackgroundColor(Color color) + { + if (Control == null) + return; + + Control.BackgroundColor = color == Color.Default ? NSColor.Clear : color.ToNSColor(); + + base.SetBackgroundColor(color); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + if (Control != null) + { + Control.Changed -= HandleChanged; + Control.EditingBegan -= OnEditingBegan; + Control.EditingEnded -= OnEditingEnded; + } + } + base.Dispose(disposing); + } + + void HandleChanged(object sender, EventArgs e) + { + ElementController.SetValueFromRenderer(Editor.TextProperty, Control.StringValue); + } + + void OnEditingEnded(object sender, EventArgs eventArgs) + { + Element.SetValue(VisualElement.IsFocusedPropertyKey, false); + Element.SendCompleted(); + } + + void OnEditingBegan(object sender, EventArgs eventArgs) + { + ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + + void UpdateEditable() + { + Control.Editable = Element.IsEnabled; + } + + void UpdateFont() + { + Control.Font = Element.ToNSFont(); + } + + void UpdateText() + { + if (Control.StringValue != Element.Text) + Control.StringValue = Element.Text ?? string.Empty; + } + + void UpdateTextColor() + { + var textColor = Element.TextColor; + + Control.TextColor = textColor.IsDefault ? NSColor.Black : textColor.ToNSColor(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs new file mode 100644 index 00000000..5fb65542 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/EntryRenderer.cs @@ -0,0 +1,205 @@ +using System; +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class EntryRenderer : ViewRenderer<Entry, NSTextField> + { + class BoolEventArgs : EventArgs + { + public BoolEventArgs(bool value) + { + Value = value; + } + public bool Value + { + get; + private set; + } + } + class FormsNSTextField : NSTextField + { + public EventHandler<BoolEventArgs> FocusChanged; + public override bool ResignFirstResponder() + { + FocusChanged?.Invoke(this, new BoolEventArgs(false)); + return base.ResignFirstResponder(); + } + public override bool BecomeFirstResponder() + { + FocusChanged?.Invoke(this, new BoolEventArgs(true)); + return base.BecomeFirstResponder(); + } + } + + bool _disposed; + NSColor _defaultTextColor; + + IElementController ElementController => Element; + + IEntryController EntryController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<Entry> e) + { + base.OnElementChanged(e); + + if (Control == null) + { + NSTextField textField; + if (e.NewElement.IsPassword) + textField = new NSSecureTextField(); + else + { + textField = new FormsNSTextField(); + (textField as FormsNSTextField).FocusChanged += TextFieldFocusChanged; + } + + SetNativeControl(textField); + + _defaultTextColor = textField.TextColor; + + textField.Changed += OnChanged; + textField.EditingBegan += OnEditingBegan; + textField.EditingEnded += OnEditingEnded; + } + + if (e.NewElement != null) + { + UpdatePlaceholder(); + UpdateText(); + UpdateColor(); + UpdateFont(); + UpdateAlignment(); + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Entry.PlaceholderProperty.PropertyName || + e.PropertyName == Entry.PlaceholderColorProperty.PropertyName) + UpdatePlaceholder(); + else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName) + UpdatePassword(); + else if (e.PropertyName == Entry.TextProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == Entry.TextColorProperty.PropertyName) + UpdateColor(); + else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName) + UpdateAlignment(); + else if (e.PropertyName == Entry.FontAttributesProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Entry.FontFamilyProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == Entry.FontSizeProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + { + UpdateColor(); + UpdatePlaceholder(); + } + + base.OnElementPropertyChanged(sender, e); + } + + protected override void SetBackgroundColor(Color color) + { + if (Control == null) + return; + Control.BackgroundColor = color == Color.Default ? NSColor.Clear : color.ToNSColor(); + + base.SetBackgroundColor(color); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + if (Control != null) + { + Control.EditingBegan -= OnEditingBegan; + Control.Changed -= OnChanged; + Control.EditingEnded -= OnEditingEnded; + var formsNSTextField = (Control as FormsNSTextField); + if (formsNSTextField != null) + formsNSTextField.FocusChanged -= TextFieldFocusChanged; + } + } + + base.Dispose(disposing); + } + void TextFieldFocusChanged(object sender, BoolEventArgs e) + { + ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, e.Value); + } + + void OnEditingBegan(object sender, EventArgs e) + { + ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + + void OnChanged(object sender, EventArgs eventArgs) + { + ElementController.SetValueFromRenderer(Entry.TextProperty, Control.StringValue); + } + + void OnEditingEnded(object sender, EventArgs e) + { + ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false); + EntryController?.SendCompleted(); + } + + void UpdateAlignment() + { + Control.Alignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(); + } + + void UpdateColor() + { + var textColor = Element.TextColor; + + if (textColor.IsDefault || !Element.IsEnabled) + Control.TextColor = _defaultTextColor; + else + Control.TextColor = textColor.ToNSColor(); + } + + void UpdatePassword() + { + if (Element.IsPassword && (Control is NSSecureTextField)) + return; + if (!Element.IsPassword && !(Control is NSSecureTextField)) + return; + } + + void UpdateFont() + { + Control.Font = Element.ToNSFont(); + } + + void UpdatePlaceholder() + { + var formatted = (FormattedString)Element.Placeholder; + + if (formatted == null) + return; + + var targetColor = Element.PlaceholderColor; + + // Placeholder default color is 70% gray + // https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITextField_Class/index.html#//apple_ref/occ/instp/UITextField/placeholder + + var color = Element.IsEnabled && !targetColor.IsDefault ? targetColor : ColorExtensions.SeventyPercentGrey.ToColor(); + + Control.PlaceholderAttributedString = formatted.ToAttributed(Element, color); + } + + void UpdateText() + { + // ReSharper disable once RedundantCheckBeforeAssignment + if (Control.StringValue != Element.Text) + Control.StringValue = Element.Text ?? string.Empty; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs new file mode 100644 index 00000000..7cbabc12 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/FrameRenderer.cs @@ -0,0 +1,57 @@ +using System.ComponentModel; +using System.Drawing; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class FrameRenderer : VisualElementRenderer<Frame> + { + protected override void OnElementChanged(ElementChangedEventArgs<Frame> e) + { + base.OnElementChanged(e); + + if (e.NewElement != null) + SetupLayer(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName || + e.PropertyName == Xamarin.Forms.Frame.OutlineColorProperty.PropertyName || + e.PropertyName == Xamarin.Forms.Frame.HasShadowProperty.PropertyName) + SetupLayer(); + } + + void SetupLayer() + { + Layer.CornerRadius = 5; + if (Element.BackgroundColor == Color.Default) + Layer.BackgroundColor = NSColor.White.CGColor; + else + Layer.BackgroundColor = Element.BackgroundColor.ToCGColor(); + + if (Element.HasShadow) + { + Layer.ShadowRadius = 5; + Layer.ShadowColor = NSColor.Black.CGColor; + Layer.ShadowOpacity = 0.8f; + Layer.ShadowOffset = new SizeF(); + } + else + Layer.ShadowOpacity = 0; + + if (Element.OutlineColor == Color.Default) + Layer.BorderColor = NSColor.Clear.CGColor; + else + { + Layer.BorderColor = Element.OutlineColor.ToCGColor(); + Layer.BorderWidth = 1; + } + + Layer.RasterizationScale = NSScreen.MainScreen.BackingScaleFactor; + Layer.ShouldRasterize = true; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs new file mode 100644 index 00000000..090df9a6 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs @@ -0,0 +1,117 @@ +using System; +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ImageRenderer : ViewRenderer<Image, NSImageView> + { + bool _isDisposed; + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + return; + + if (disposing) + { + NSImage oldUIImage; + if (Control != null && (oldUIImage = Control.Image) != null) + { + oldUIImage.Dispose(); + } + } + + _isDisposed = true; + + base.Dispose(disposing); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Image> e) + { + if (Control == null) + { + var imageView = new FormsNSImageView(); + SetNativeControl(imageView); + } + + if (e.NewElement != null) + { + SetAspect(); + SetImage(e.OldElement); + SetOpacity(); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + if (e.PropertyName == Image.SourceProperty.PropertyName) + SetImage(); + else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName) + SetOpacity(); + else if (e.PropertyName == Image.AspectProperty.PropertyName) + SetAspect(); + } + + void SetAspect() + { + //TODO: Implement set Image Aspect + //Control.ContentMode = Element.Aspect.ToUIViewContentMode(); + } + + async void SetImage(Image oldElement = null) + { + var source = Element.Source; + + if (oldElement != null) + { + var oldSource = oldElement.Source; + if (Equals(oldSource, source)) + return; + + var imageSource = oldSource as FileImageSource; + if (imageSource != null && source is FileImageSource && imageSource.File == ((FileImageSource)source).File) + return; + + Control.Image = null; + } + + IImageSourceHandler handler; + + ((IImageController)Element).SetIsLoading(true); + + if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + NSImage uiimage; + try + { + uiimage = await handler.LoadImageAsync(source, scale: (float)NSScreen.MainScreen.BackingScaleFactor); + } + catch (OperationCanceledException) + { + uiimage = null; + } + + var imageView = Control; + if (imageView != null) + imageView.Image = uiimage; + + if (!_isDisposed) + ((IVisualElementController)Element).NativeSizeChanged(); + } + else + Control.Image = null; + + if (!_isDisposed) + ((IImageController)Element).SetIsLoading(false); + } + + void SetOpacity() + { + (Control as FormsNSImageView)?.SetIsOpaque(Element.IsOpaque); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs new file mode 100644 index 00000000..1be5a1ad --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/LayoutRenderer.cs @@ -0,0 +1,39 @@ +using System; +using CoreGraphics; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class LayoutRenderer : DefaultRenderer + { + CGRect _bounds; + + public override void Layout() + { + base.Layout(); + + if (_bounds == Bounds) + return; + + _bounds = Bounds; + + //when the layout changes we might need to update the children position based in our new size, + //this is only needed in MacOS because of the inversion of the Y coordinate. + //Forms layout system doesn't know we need to relayout the other items ,(first ones for example) + //so we do it here + for (int i = 0; i < Subviews.Length; i++) + { + var item = Subviews[i] as IVisualElementRenderer; + if (item == null) + continue; + var oldFrame = item.NativeView.Frame; + + var newY = Math.Max(0, (float)(Element.Height - item.Element.Height - item.Element.Y)); + if (oldFrame.Y == newY) + continue; + var newPosition = new CGPoint(oldFrame.X, newY); + item.NativeView.Frame = new CGRect(newPosition, oldFrame.Size); + Console.WriteLine($"New Frame - {item.NativeView.Frame}"); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs new file mode 100644 index 00000000..6742053c --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class ListViewDataSource : NSTableViewSource + { + IVisualElementRenderer _prototype; + const int DefaultItemTemplateId = 1; + static int s_dataTemplateIncrementer = 2; // lets start at not 0 because + static int s_sectionCount; + readonly nfloat _defaultSectionHeight; + readonly Dictionary<DataTemplate, int> _templateToId = new Dictionary<DataTemplate, int>(); + readonly NSTableView _nsTableView; + protected readonly ListView List; + + IListViewController Controller => List; + + ITemplatedItemsView<Cell> TemplatedItemsView => List; + + bool _selectionFromNative; + + public virtual bool IsGroupingEnabled => List.IsGroupingEnabled; + + public Dictionary<int, int> Counts { get; set; } + + public ListViewDataSource(ListViewDataSource source) + { + List = source.List; + _nsTableView = source._nsTableView; + _defaultSectionHeight = source._defaultSectionHeight; + _selectionFromNative = source._selectionFromNative; + Counts = new Dictionary<int, int>(); + } + + public ListViewDataSource(ListView list, NSTableView tableView) + { + List = list; + List.ItemSelected += OnItemSelected; + _nsTableView = tableView; + Counts = new Dictionary<int, int>(); + } + + public void Update() + { + _nsTableView.ReloadData(); + } + + public void OnRowClicked() + { + var selectedRow = _nsTableView.SelectedRow; + if (selectedRow == -1) + return; + + Cell cell = null; + NSIndexPath indexPath = GetPathFromRow(selectedRow, ref cell); + + if (cell == null) + return; + + _selectionFromNative = true; + Controller.NotifyRowTapped((int)indexPath.Section, (int)indexPath.Item, cell); + } + + + public void OnItemSelected(object sender, SelectedItemChangedEventArgs eventArg) + { + if (_selectionFromNative) + { + _selectionFromNative = false; + return; + } + + var location = TemplatedItemsView.TemplatedItems.GetGroupAndIndexOfItem(eventArg.SelectedItem); + if (location.Item1 == -1 || location.Item2 == -1) + { + var row = _nsTableView.SelectedRow; + int groupIndex = 1; + var selectedIndexPath = NSIndexPath.FromItemSection(row, groupIndex); + if (selectedIndexPath != null) + _nsTableView.DeselectRow(selectedIndexPath.Item); + return; + } + + var rowId = location.Item2; + + _nsTableView.SelectRow(rowId, false); + } + + public override bool IsGroupRow(NSTableView tableView, nint row) + { + if (!IsGroupingEnabled) + return false; + + int sectionIndex; + bool isGroupHeader; + int itemIndexInSection; + + GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isGroupHeader); + return isGroupHeader; + } + + public override bool ShouldSelectRow(NSTableView tableView, nint row) + { + return !IsGroupRow(tableView, row); + } + + public override nfloat GetRowHeight(NSTableView tableView, nint row) + { + if (!List.HasUnevenRows) + return List.RowHeight == -1 ? ListViewRenderer.DefaultRowHeight : List.RowHeight; + + Cell cell = null; + GetPathFromRow(row, ref cell); + + return CalculateHeightForCell(tableView, cell); + } + + public override nint GetRowCount(NSTableView tableView) + { + var templatedItems = TemplatedItemsView.TemplatedItems; + nint count = 0; + + if (!IsGroupingEnabled) + { + count = templatedItems.Count; + } + else + { + var sections = templatedItems.Count; + for (int i = 0; i < sections; i++) + { + var group = (IList)((IList)templatedItems)[i]; + count += group.Count + 1; + } + s_sectionCount = sections; + } + return count; + } + + public override NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, nint row) + { + var sectionIndex = 0; + var itemIndexInSection = (int)row; + Cell cell; + + var isHeader = false; + + if (IsGroupingEnabled) + GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader); + + var indexPath = NSIndexPath.FromItemSection(itemIndexInSection, sectionIndex); + var templateId = isHeader ? "headerCell" : TemplateIdForPath(indexPath).ToString(); + + NSView nativeCell; + + var cachingStrategy = Controller.CachingStrategy; + if (cachingStrategy == ListViewCachingStrategy.RetainElement) + { + cell = GetCellForPath(indexPath, isHeader); + nativeCell = CellNSView.GetNativeCell(tableView, cell, templateId, isHeader); + } + else if (cachingStrategy == ListViewCachingStrategy.RecycleElement) + { + nativeCell = tableView.MakeView(templateId, tableView); + if (nativeCell == null) + { + cell = GetCellForPath(indexPath, isHeader); + nativeCell = CellNSView.GetNativeCell(tableView, cell, templateId, isHeader, true); + } + else + { + var templatedList = TemplatedItemsView.TemplatedItems.GetGroup(sectionIndex); + cell = (Cell)((INativeElementView)nativeCell).Element; + ICellController controller = cell; + controller.SendDisappearing(); + templatedList.UpdateContent(cell, itemIndexInSection); + controller.SendAppearing(); + } + } + else + throw new NotSupportedException(); + return nativeCell; + } + + protected virtual Cell GetCellForPath(NSIndexPath indexPath, bool isGroupHeader) + { + var templatedItems = TemplatedItemsView.TemplatedItems; + if (IsGroupingEnabled) + templatedItems = (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)templatedItems)[(int)indexPath.Section]; + + var cell = isGroupHeader ? templatedItems.HeaderContent : templatedItems[(int)indexPath.Item]; + return cell; + } + + int TemplateIdForPath(NSIndexPath indexPath) + { + var itemTemplate = List.ItemTemplate; + var selector = itemTemplate as DataTemplateSelector; + if (selector == null) + return DefaultItemTemplateId; + + var templatedList = TemplatedItemsView.TemplatedItems; + if (List.IsGroupingEnabled) + templatedList = (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)templatedList)[(int)indexPath.Section]; + + var item = templatedList.ListProxy[(int)indexPath.Item]; + + itemTemplate = selector.SelectTemplate(item, List); + int key; + if (!_templateToId.TryGetValue(itemTemplate, out key)) + { + s_dataTemplateIncrementer++; + key = s_dataTemplateIncrementer; + _templateToId[itemTemplate] = key; + } + return key; + } + + NSIndexPath GetPathFromRow(nint row, ref Cell cell) + { + var sectionIndex = 0; + bool isGroupHeader = false; + int itemIndexInSection; + if (IsGroupingEnabled) + GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isGroupHeader); + else + itemIndexInSection = (int)row; + NSIndexPath indexPath = NSIndexPath.FromItemSection(itemIndexInSection, sectionIndex); + cell = GetCellForPath(indexPath, isGroupHeader); + return indexPath; + } + + nfloat CalculateHeightForCell(NSTableView tableView, Cell cell) + { + var viewCell = cell as ViewCell; + double renderHeight; + if (List.RowHeight == -1 && viewCell?.View != null) + { + var target = viewCell.View; + if (_prototype == null) + { + _prototype = Platform.CreateRenderer(target); + Platform.SetRenderer(target, _prototype); + } + else + { + _prototype.SetElement(target); + Platform.SetRenderer(target, _prototype); + } + + var req = target.Measure(tableView.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins); + + target.ClearValue(Platform.RendererProperty); + foreach (var descendant in target.Descendants()) + descendant.ClearValue(Platform.RendererProperty); + + renderHeight = req.Request.Height; + } + else + { + renderHeight = cell.RenderHeight; + } + + return renderHeight > 0 ? (nfloat)renderHeight : ListViewRenderer.DefaultRowHeight; + } + + void GetComputedIndexes(nint row, out int sectionIndex, out int itemIndexInSection, out bool isHeader) + { + var templatedItems = TemplatedItemsView.TemplatedItems; + var totalItems = 0; + isHeader = false; + sectionIndex = 0; + itemIndexInSection = 0; + + for (int i = 0; i < s_sectionCount; i++) + { + var group = (IList)((IList)templatedItems)[i]; + var itemsInSection = group.Count + 1; + + if (row < totalItems + itemsInSection) + { + sectionIndex = i; + itemIndexInSection = (int)row - totalItems; + isHeader = itemIndexInSection == 0; + if (isHeader) + itemIndexInSection = -1; + else + itemIndexInSection = itemIndexInSection - 1; + break; + } + totalItems += itemsInSection; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs new file mode 100644 index 00000000..08265b33 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewRenderer.cs @@ -0,0 +1,342 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using AppKit; +using Foundation; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ListViewRenderer : ViewRenderer<ListView, NSView> + { + bool _disposed; + NSTableView _table; + ListViewDataSource _dataSource; + IVisualElementRenderer _headerRenderer; + IVisualElementRenderer _footerRenderer; + + IListViewController Controller => Element; + + ITemplatedItemsView<Cell> TemplatedItemsView => Element; + + public const int DefaultRowHeight = 16; + + public NSTableView NativeTableView => _table; + + public override void ViewWillDraw() + { + UpdateHeader(); + base.ViewWillDraw(); + } + + protected virtual NSTableView CreateNSTableView(ListView list) + { + NSTableView table = new FormsNSTableView().AsListViewLook(); + table.Source = _dataSource = new ListViewDataSource(list, table); + return table; + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + + var viewsToLookAt = new Stack<NSView>(Subviews); + while (viewsToLookAt.Count > 0) + { + var view = viewsToLookAt.Pop(); + var viewCellRenderer = view as ViewCellNSView; + if (viewCellRenderer != null) + viewCellRenderer.Dispose(); + else + { + foreach (var child in view.Subviews) + viewsToLookAt.Push(child); + } + } + + if (Element != null) + { + var templatedItems = TemplatedItemsView.TemplatedItems; + templatedItems.CollectionChanged -= OnCollectionChanged; + templatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged; + } + } + + if (disposing) + { + ClearHeader(); + if (_footerRenderer != null) + { + Platform.DisposeModelAndChildrenRenderers(_footerRenderer.Element); + _footerRenderer = null; + } + } + + base.Dispose(disposing); + } + + protected override void SetBackgroundColor(Color color) + { + base.SetBackgroundColor(color); + if (_table == null) + return; + + _table.BackgroundColor = color.ToNSColor(NSColor.White); + } + + protected override void OnElementChanged(ElementChangedEventArgs<ListView> e) + { + if (e.OldElement != null) + { + var controller = (IListViewController)e.OldElement; + controller.ScrollToRequested -= OnScrollToRequested; + + var templatedItems = ((ITemplatedItemsView<Cell>)e.OldElement).TemplatedItems; + templatedItems.CollectionChanged -= OnCollectionChanged; + templatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged; + } + + if (e.NewElement != null) + { + if (Control == null) + { + var scroller = new NSScrollView + { + AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable, + DocumentView = _table = CreateNSTableView(e.NewElement) + }; + SetNativeControl(scroller); + } + + var controller = (IListViewController)e.NewElement; + controller.ScrollToRequested += OnScrollToRequested; + + var templatedItems = ((ITemplatedItemsView<Cell>)e.NewElement).TemplatedItems; + templatedItems.CollectionChanged += OnCollectionChanged; + templatedItems.GroupedCollectionChanged += OnGroupedCollectionChanged; + + UpdateRowHeight(); + UpdateHeader(); + UpdateFooter(); + UpdatePullToRefreshEnabled(); + UpdateIsRefreshing(); + UpdateSeparatorColor(); + UpdateSeparatorVisibility(); + + var selected = e.NewElement.SelectedItem; + if (selected != null) + _dataSource.OnItemSelected(null, new SelectedItemChangedEventArgs(selected)); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + if (e.PropertyName == ListView.RowHeightProperty.PropertyName) + UpdateRowHeight(); + else if (e.PropertyName == ListView.IsGroupingEnabledProperty.PropertyName || + (e.PropertyName == ListView.HasUnevenRowsProperty.PropertyName)) + _dataSource.Update(); + else if (e.PropertyName == ListView.IsPullToRefreshEnabledProperty.PropertyName) + UpdatePullToRefreshEnabled(); + else if (e.PropertyName == ListView.IsRefreshingProperty.PropertyName) + UpdateIsRefreshing(); + else if (e.PropertyName == ListView.SeparatorColorProperty.PropertyName) + UpdateSeparatorColor(); + else if (e.PropertyName == ListView.SeparatorVisibilityProperty.PropertyName) + UpdateSeparatorVisibility(); + else if (e.PropertyName == "HeaderElement") + UpdateHeader(); + else if (e.PropertyName == "FooterElement") + UpdateFooter(); + else if (e.PropertyName == "RefreshAllowed") + UpdatePullToRefreshEnabled(); + } + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateItems(e, 0, true); + } + + void OnGroupedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + var til = (TemplatedItemsList<ItemsView<Cell>, Cell>)sender; + + var templatedItems = TemplatedItemsView.TemplatedItems; + var groupIndex = templatedItems.IndexOf(til.HeaderContent); + UpdateItems(e, groupIndex, false); + } + + void UpdateHeader() + { + var header = Controller.HeaderElement; + var headerView = (View)header; + + if (headerView != null) + { + //Header reuse is not working for now , problem with size of something that is not inside a layout + //if (_headerRenderer != null) + //{ + // if (header != null && _headerRenderer.GetType() == Registrar.Registered.GetHandlerType(header.GetType())) + // { + // _headerRenderer.SetElement(headerView); + // _table.HeaderView = new CustomNSTableHeaderView(Bounds.Width, _headerRenderer); + // // Layout(); + // //var customNSTableHeaderView = _table.HeaderView as CustomNSTableHeaderView; + // //customNSTableHeaderView?.Update(Bounds.Width, _headerRenderer); + // //NativeView.Layout(); + // //NativeView.SetNeedsDisplayInRect(NativeView.Bounds); + // //NativeView.LayoutSubtreeIfNeeded(); + // //_table.LayoutSubtreeIfNeeded(); + // //_table.NeedsDisplay = true; + // //NativeView.NeedsDisplay = true; + // return; + // } + ClearHeader(); + //} + + _headerRenderer = Platform.CreateRenderer(headerView); + Platform.SetRenderer(headerView, _headerRenderer); + _table.HeaderView = new CustomNSTableHeaderView(Bounds.Width, _headerRenderer); + + //We need this since the NSSCrollView doesn't know of the new size of our header + //TODO: Find a better solution + (Control as NSScrollView)?.ContentView.ScrollToPoint(new CoreGraphics.CGPoint(0, -_table.HeaderView.Frame.Height)); + } + else if (_headerRenderer != null) + { + ClearHeader(); + } + } + + void ClearHeader() + { + _table.HeaderView = null; + if (_headerRenderer == null) + return; + Platform.DisposeModelAndChildrenRenderers(_headerRenderer.Element); + _headerRenderer = null; + } + + void UpdateItems(NotifyCollectionChangedEventArgs e, int section, bool resetWhenGrouped) + { + var exArgs = e as NotifyCollectionChangedEventArgsEx; + if (exArgs != null) + _dataSource.Counts[section] = exArgs.Count; + + var groupReset = resetWhenGrouped && Element.IsGroupingEnabled; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex == -1 || groupReset) + goto case NotifyCollectionChangedAction.Reset; + + _table.BeginUpdates(); + _table.InsertRows(NSIndexSet.FromArray(Enumerable.Range(e.NewStartingIndex, e.NewItems.Count).ToArray()), + NSTableViewAnimation.SlideUp); + _table.EndUpdates(); + + break; + + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex == -1 || groupReset) + goto case NotifyCollectionChangedAction.Reset; + + _table.BeginUpdates(); + _table.RemoveRows(NSIndexSet.FromArray(Enumerable.Range(e.OldStartingIndex, e.OldItems.Count).ToArray()), + NSTableViewAnimation.SlideDown); + _table.EndUpdates(); + + break; + + case NotifyCollectionChangedAction.Move: + if (e.OldStartingIndex == -1 || e.NewStartingIndex == -1 || groupReset) + goto case NotifyCollectionChangedAction.Reset; + _table.BeginUpdates(); + for (var i = 0; i < e.OldItems.Count; i++) + { + var oldi = e.OldStartingIndex; + var newi = e.NewStartingIndex; + + if (e.NewStartingIndex < e.OldStartingIndex) + { + oldi += i; + newi += i; + } + + _table.MoveRow(oldi, newi); + } + _table.EndUpdates(); + + break; + + case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Reset: + _table.ReloadData(); + return; + } + } + + void UpdateRowHeight() + { + var rowHeight = Element.RowHeight; + if (Element.HasUnevenRows && rowHeight == -1) + { + // _table.RowHeight = NoIntrinsicMetric; + } + else + _table.RowHeight = rowHeight <= 0 ? DefaultRowHeight : rowHeight; + } + + //TODO: Implement UpdateIsRefreshing + void UpdateIsRefreshing() + { + } + + //TODO: Implement PullToRefresh + void UpdatePullToRefreshEnabled() + { + } + + //TODO: Implement SeparatorColor + void UpdateSeparatorColor() + { + } + + //TODO: Implement UpdateSeparatorVisibility + void UpdateSeparatorVisibility() + { + } + + //TODO: Implement ScrollTo + void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e) + { + } + + //TODO: Implement Footer + void UpdateFooter() + { + } + + class FormsNSTableView : NSTableView + { + //NSTableView doesn't support selection notfications after the items is already selected + //so we do it ourselves by hooking mouse down event + public override void MouseDown(NSEvent theEvent) + { + var clickLocation = ConvertPointFromView(theEvent.LocationInWindow, null); + var clickedRow = GetRow(clickLocation); + + base.MouseDown(theEvent); + if (clickedRow != -1) + (Source as ListViewDataSource)?.OnRowClicked(); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs new file mode 100644 index 00000000..8d2fd551 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/MasterDetailPageRenderer.cs @@ -0,0 +1,204 @@ +using System; +using System.ComponentModel; +using System.Linq; +using AppKit; +using CoreGraphics; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class MasterDetailPageRenderer : NSSplitViewController, IVisualElementRenderer, IEffectControlProvider + { + bool _disposed; + EventTracker _events; + VisualElementTracker _tracker; + MasterDetailPage _masterDetailPage; + + IPageController PageController => Element as IPageController; + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect != null) + platformEffect.Container = View; + } + + protected MasterDetailPage MasterDetailPage => _masterDetailPage ?? (_masterDetailPage = (MasterDetailPage)Element); + + protected override void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + PageController?.SendDisappearing(); + + if (Element != null) + { + Element.PropertyChanged -= HandlePropertyChanged; + Element = null; + } + + ClearControllers(); + + _tracker?.Dispose(); + _tracker = null; + _events?.Dispose(); + _events = null; + + _disposed = true; + } + base.Dispose(disposing); + } + + public NSViewController ViewController => this; + + public NSView NativeView => View; + + public VisualElement Element { get; private set; } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return NativeView.GetSizeRequest(widthConstraint, heightConstraint); + } + + public void SetElement(VisualElement element) + { + var oldElement = Element; + Element = element; + + UpdateControllers(); + + OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); + + EffectUtilities.RegisterEffectControlProvider(this, oldElement, element); + } + + public void SetElementSize(Size size) + { + Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height)); + UpdateChildrenLayout(); + } + + protected virtual void OnElementChanged(VisualElementChangedEventArgs e) + { + if (e.OldElement != null) + e.OldElement.PropertyChanged -= HandlePropertyChanged; + + if (e.NewElement != null) + e.NewElement.PropertyChanged += HandlePropertyChanged; + + ElementChanged?.Invoke(this, e); + } + + protected virtual double MasterWidthPercentage => 0.3; + + public override void ViewWillAppear() + { + UpdateBackground(); + _tracker = new VisualElementTracker(this); + _events = new EventTracker(this); + _events.LoadEvents(NativeView); + UpdateChildrenLayout(); + + base.ViewWillAppear(); + } + + public override CGRect GetEffectiveRect(NSSplitView splitView, CGRect proposedEffectiveRect, CGRect drawnRect, + nint dividerIndex) + { + return CGRect.Empty; + } + + void UpdateChildrenLayout() + { + if (View.Frame.Width == -1) + return; + var width = View.Frame.Width; + var masterWidth = MasterWidthPercentage * width; + if (SplitViewItems.Length > 0) + SplitViewItems[0].MaximumThickness = SplitViewItems[0].MinimumThickness = (nfloat)masterWidth; + } + + void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_tracker == null) + return; + + if (e.PropertyName == "Master" || e.PropertyName == "Detail") + UpdateControllers(); + } + + void UpdateControllers() + { + ClearControllers(); + + if (Platform.GetRenderer(MasterDetailPage.Master) == null) + Platform.SetRenderer(MasterDetailPage.Master, Platform.CreateRenderer(MasterDetailPage.Master)); + if (Platform.GetRenderer(MasterDetailPage.Detail) == null) + Platform.SetRenderer(MasterDetailPage.Detail, Platform.CreateRenderer(MasterDetailPage.Detail)); + + AddSplitViewItem(new NSSplitViewItem + { + ViewController = new ViewControllerWrapper(Platform.GetRenderer(MasterDetailPage.Master)) + }); + AddSplitViewItem(new NSSplitViewItem + { + ViewController = new ViewControllerWrapper(Platform.GetRenderer(MasterDetailPage.Detail)) + }); + + UpdateChildrenLayout(); + } + + void ClearControllers() + { + while (SplitViewItems.Length > 0) + { + var splitItem = SplitViewItems.Last(); + var childVisualRenderer = splitItem.ViewController as ViewControllerWrapper; + RemoveSplitViewItem(splitItem); + IVisualElementRenderer render = null; + if (childVisualRenderer.RendererWeakRef.TryGetTarget(out render)) + { + render.Dispose(); + } + childVisualRenderer.Dispose(); + childVisualRenderer = null; + } + } + + //TODO: Implement Background color on MDP + void UpdateBackground() + { + } + + sealed class ViewControllerWrapper : NSViewController + { + internal WeakReference<IVisualElementRenderer> RendererWeakRef; + + public ViewControllerWrapper(IVisualElementRenderer renderer) + { + RendererWeakRef = new WeakReference<IVisualElementRenderer>(renderer); + View = new NSView { WantsLayer = true }; + AddChildViewController(renderer.ViewController); + View.AddSubview(renderer.NativeView); + } + + public override void ViewWillLayout() + { + IVisualElementRenderer renderer; + if (RendererWeakRef.TryGetTarget(out renderer)) + renderer?.Element?.Layout(new Rectangle(0, 0, View.Bounds.Width, View.Bounds.Height)); + base.ViewWillLayout(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && RendererWeakRef != null) + { + RendererWeakRef = null; + } + base.Dispose(disposing); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs new file mode 100644 index 00000000..80f409cc --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/NSPageContainer.cs @@ -0,0 +1,17 @@ +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class NSPageContainer : NSObject + { + public NSPageContainer(Page element, int index) + { + Page = element; + Index = index; + } + + public Page Page { get; } + + public int Index { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs new file mode 100644 index 00000000..23d7f856 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/NavigationPageRenderer.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using AppKit; +using CoreAnimation; +using Foundation; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class NavigationPageRenderer : NSViewController, IVisualElementRenderer, IEffectControlProvider + { + bool _disposed; + bool _appeared; + EventTracker _events; + VisualElementTracker _tracker; + Stack<NavigationChildPageWrapper> _currentStack = new Stack<NavigationChildPageWrapper>(); + + IPageController PageController => Element as IPageController; + + IElementController ElementController => Element as IElementController; + + INavigationPageController NavigationController => Element as INavigationPageController; + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect != null) + platformEffect.Container = View; + } + + public NavigationPageRenderer() : this(IntPtr.Zero) + { + } + + public NavigationPageRenderer(IntPtr handle) + { + View = new NSView { WantsLayer = true }; + } + + public VisualElement Element { get; private set; } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return NativeView.GetSizeRequest(widthConstraint, heightConstraint); + } + + public NSViewController ViewController => this; + + public NSView NativeView => View; + + public void SetElement(VisualElement element) + { + var oldElement = Element; + Element = element; + + Init(); + + OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); + + EffectUtilities.RegisterEffectControlProvider(this, oldElement, element); + } + + public void SetElementSize(Size size) + { + Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height)); + } + + public Task<bool> PopToRootAsync(Page page, bool animated = true) + { + return OnPopToRoot(page, animated); + } + + public Task<bool> PopViewAsync(Page page, bool animated = true) + { + return OnPop(page, animated); + } + + public Task<bool> PushPageAsync(Page page, bool animated = true) + { + return OnPush(page, animated); + } + + protected override void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + if (Element != null) + { + PageController?.SendDisappearing(); + ((Element as IPageContainer<Page>)?.CurrentPage as IPageController)?.SendDisappearing(); + Element.PropertyChanged -= HandlePropertyChanged; + Element = null; + } + + _tracker?.Dispose(); + _tracker = null; + + _events?.Dispose(); + _events = null; + + _disposed = true; + } + base.Dispose(disposing); + } + + public override void ViewDidDisappear() + { + base.ViewDidDisappear(); + if (!_appeared) + return; + Platform.NativeToolbarTracker.TryHide(Element as NavigationPage); + _appeared = false; + PageController?.SendDisappearing(); + } + + public override void ViewDidAppear() + { + base.ViewDidAppear(); + Platform.NativeToolbarTracker.Navigation = (NavigationPage)Element; + if (_appeared) + return; + + _appeared = true; + PageController?.SendAppearing(); + } + + protected virtual void OnElementChanged(VisualElementChangedEventArgs e) + { + if (e.OldElement != null) + e.OldElement.PropertyChanged -= HandlePropertyChanged; + + if (e.NewElement != null) + e.NewElement.PropertyChanged += HandlePropertyChanged; + + ElementChanged?.Invoke(this, e); + } + + protected virtual void ConfigurePageRenderer() + { + View.WantsLayer = true; + } + + protected virtual Task<bool> OnPopToRoot(Page page, bool animated) + { + var renderer = Platform.GetRenderer(page); + if (renderer == null || renderer.ViewController == null) + return Task.FromResult(false); + + Platform.NativeToolbarTracker.UpdateToolBar(); + return Task.FromResult(true); + } + + protected virtual async Task<bool> OnPop(Page page, bool animated) + { + var removed = await PopPageAsync(page, animated); + Platform.NativeToolbarTracker.UpdateToolBar(); + return removed; + } + + protected virtual async Task<bool> OnPush(Page page, bool animated) + { + var shown = await AddPage(page, animated); + Platform.NativeToolbarTracker.UpdateToolBar(); + return shown; + } + + void Init() + { + ConfigurePageRenderer(); + + var navPage = (NavigationPage)Element; + + if (navPage.CurrentPage == null) + throw new InvalidOperationException( + "NavigationPage must have a root Page before being used. Either call PushAsync with a valid Page, or pass a Page to the constructor before usage."); + + Platform.NativeToolbarTracker.Navigation = navPage; + + NavigationController.PushRequested += OnPushRequested; + NavigationController.PopRequested += OnPopRequested; + NavigationController.PopToRootRequested += OnPopToRootRequested; + NavigationController.RemovePageRequested += OnRemovedPageRequested; + NavigationController.InsertPageBeforeRequested += OnInsertPageBeforeRequested; + + navPage.Popped += (sender, e) => Platform.NativeToolbarTracker.UpdateToolBar(); + navPage.PoppedToRoot += (sender, e) => Platform.NativeToolbarTracker.UpdateToolBar(); + + UpdateBarBackgroundColor(); + UpdateBarTextColor(); + + _events = new EventTracker(this); + _events.LoadEvents(NativeView); + _tracker = new VisualElementTracker(this); + + ((INavigationPageController)navPage).Pages.ForEach(async p => await PushPageAsync(p, false)); + + UpdateBackgroundColor(); + } + + IVisualElementRenderer CreateViewControllerForPage(Page page) + { + if (Platform.GetRenderer(page) == null) + Platform.SetRenderer(page, Platform.CreateRenderer(page)); + + var pageRenderer = Platform.GetRenderer(page); + return pageRenderer; + } + + //TODO: Implement InserPageBefore + void InsertPageBefore(Page page, Page before) + { + if (before == null) + throw new ArgumentNullException(nameof(before)); + if (page == null) + throw new ArgumentNullException(nameof(page)); + } + + void OnInsertPageBeforeRequested(object sender, NavigationRequestedEventArgs e) + { + InsertPageBefore(e.Page, e.BeforePage); + } + + void OnPopRequested(object sender, NavigationRequestedEventArgs e) + { + e.Task = PopViewAsync(e.Page, e.Animated); + } + + void OnPopToRootRequested(object sender, NavigationRequestedEventArgs e) + { + e.Task = PopToRootAsync(e.Page, e.Animated); + } + + void OnPushRequested(object sender, NavigationRequestedEventArgs e) + { + e.Task = PushPageAsync(e.Page, e.Animated); + } + + void OnRemovedPageRequested(object sender, NavigationRequestedEventArgs e) + { + RemovePage(e.Page, true); + Platform.NativeToolbarTracker.UpdateToolBar(); + } + + void RemovePage(Page page, bool removeFromStack) + { + (page as IPageController)?.SendDisappearing(); + var target = Platform.GetRenderer(page); + target?.NativeView?.RemoveFromSuperview(); + target?.ViewController?.RemoveFromParentViewController(); + target?.Dispose(); + if (removeFromStack) + { + var newStack = new Stack<NavigationChildPageWrapper>(); + foreach (var stack in _currentStack) + { + if (stack.Page != page) + { + newStack.Push(stack); + } + } + _currentStack = newStack; + } + } + + async Task<bool> PopPageAsync(Page page, bool animated) + { + if (page == null) + throw new ArgumentNullException(nameof(page)); + + var wrapper = _currentStack.Peek(); + if (page != wrapper.Page) + throw new NotSupportedException("Popped page does not appear on top of current navigation stack, please file a bug."); + + _currentStack.Pop(); + (page as IPageController)?.SendDisappearing(); + + var target = Platform.GetRenderer(page); + var previousPage = _currentStack.Peek().Page; + + if (animated) + { + var previousPageRenderer = Platform.GetRenderer(previousPage); + return await this.HandleAsyncAnimation(target.ViewController, previousPageRenderer.ViewController, + NSViewControllerTransitionOptions.SlideBackward, () => Platform.DisposeRendererAndChildren(target), true); + } + + RemovePage(page, false); + return true; + } + + async Task<bool> AddPage(Page page, bool animated) + { + if (page == null) + throw new ArgumentNullException(nameof(page)); + + Page oldPage = null; + if (_currentStack.Count >= 1) + oldPage = _currentStack.Peek().Page; + + _currentStack.Push(new NavigationChildPageWrapper(page)); + + var vc = CreateViewControllerForPage(page); + vc.SetElementSize(new Size(View.Bounds.Width, View.Bounds.Height)); + page.Layout(new Rectangle(0, 0, View.Bounds.Width, View.Frame.Height)); + + if (_currentStack.Count == 1 || !animated) + { + vc.NativeView.WantsLayer = true; + AddChildViewController(vc.ViewController); + View.AddSubview(vc.NativeView); + return true; + } + var vco = Platform.GetRenderer(oldPage); + AddChildViewController(vc.ViewController); + return await this.HandleAsyncAnimation(vco.ViewController, vc.ViewController, + NSViewControllerTransitionOptions.SlideForward, () => (page as IPageController)?.SendAppearing(), true); + } + + void UpdateBackgroundColor() + { + if (View.Layer == null) + return; + var color = Element.BackgroundColor == Color.Default ? Color.White : Element.BackgroundColor; + View.Layer.BackgroundColor = color.ToCGColor(); + } + + void UpdateBarBackgroundColor() + { + Platform.NativeToolbarTracker.UpdateToolBar(); + } + + void UpdateBarTextColor() + { + Platform.NativeToolbarTracker.UpdateToolBar(); + } + + void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_tracker == null) + return; + + if (e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName) + UpdateBarBackgroundColor(); + else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName) + UpdateBarTextColor(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundColor(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs new file mode 100644 index 00000000..dda6ac50 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/OpenGLViewRenderer.cs @@ -0,0 +1,104 @@ +using System; +using System.ComponentModel; +using CoreVideo; + +namespace Xamarin.Forms.Platform.MacOS +{ + // ReSharper disable once InconsistentNaming + internal class OpenGLViewRenderer : ViewRenderer<OpenGLView, MacOSOpenGLView> + { + CVDisplayLink _displayLink; + + public void Display(object sender, EventArgs eventArgs) + { + if (Element.HasRenderLoop) + return; + SetupRenderLoop(true); + } + + protected override void Dispose(bool disposing) + { + if (_displayLink != null) + { + _displayLink.Dispose(); + _displayLink = null; + + if (Element != null) + ((IOpenGlViewController)Element).DisplayRequested -= Display; + } + + base.Dispose(disposing); + } + + protected override void OnElementChanged(ElementChangedEventArgs<OpenGLView> e) + { + if (e.OldElement != null) + ((IOpenGlViewController)e.OldElement).DisplayRequested -= Display; + + if (e.NewElement != null) + { + //var context = new EAGLContext(EAGLRenderingAPI.OpenGLES2); + //var glkView = new GLKView(RectangleF.Empty) { Context = context, DrawableDepthFormat = GLKViewDrawableDepthFormat.Format24, Delegate = new Delegate(e.NewElement) }; + var glkView = new MacOSOpenGLView(); + SetNativeControl(glkView); + + ((IOpenGlViewController)e.NewElement).DisplayRequested += Display; + + SetupRenderLoop(false); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == OpenGLView.HasRenderLoopProperty.PropertyName) + SetupRenderLoop(false); + } + + void SetupRenderLoop(bool oneShot) + { + if (_displayLink != null) + return; + if (!oneShot && !Element.HasRenderLoop) + return; + + _displayLink = new CVDisplayLink(); + + //.Create(() => + //{ + // var control = Control; + // var model = Element; + // if (control != null) + // control.Display(); + // if (control == null || model == null || !model.HasRenderLoop) + // { + // _displayLink.Invalidate(); + // _displayLink.Dispose(); + // _displayLink = null; + // } + //}); + //_displayLink.(NSRunLoop.Current, NSRunLoop.NSDefaultRunLoopMode); + } + + //class Delegate : GLKViewDelegate + //{ + // readonly OpenGLView _model; + + // public Delegate(OpenGLView model) + // { + // _model = model; + // } + + // public override void DrawInRect(GLKView view, RectangleF rect) + // { + // var onDisplay = _model.OnDisplay; + // if (onDisplay == null) + // return; + // onDisplay(rect.ToRectangle()); + // } + //} + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs b/Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs new file mode 100644 index 00000000..d1f40d31 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/PageControllerDelegate.cs @@ -0,0 +1,30 @@ +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class PageControllerDelegate : NSPageControllerDelegate + { + public override string GetIdentifier(NSPageController pageController, NSObject targetObject) + { + return nameof(PageRenderer); + } + + public override NSViewController GetViewController(NSPageController pageController, string identifier) + { + return new PageRenderer(); + } + + public override void PrepareViewController(NSPageController pageController, NSViewController viewController, + NSObject targetObject) + { + var pageContainer = targetObject as NSPageContainer; + var pageRenderer = (viewController as PageRenderer); + if (pageContainer == null || pageRenderer == null) + return; + Page page = pageContainer.Page; + pageRenderer.SetElement(page); + Platform.SetRenderer(page, pageRenderer); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs new file mode 100644 index 00000000..e0845d5a --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/PageRenderer.cs @@ -0,0 +1,182 @@ +using System; +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class PageRenderer : NSViewController, IVisualElementRenderer, IEffectControlProvider + { + bool _init; + bool _appeared; + bool _disposed; + EventTracker _events; + VisualElementPackager _packager; + VisualElementTracker _tracker; + + IPageController PageController => Element as IPageController; + + public PageRenderer() + { + View = new NSView { WantsLayer = true }; + } + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect != null) + platformEffect.Container = View; + } + + public VisualElement Element { get; private set; } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return NativeView.GetSizeRequest(widthConstraint, heightConstraint); + } + + public NSView NativeView => _disposed ? null : View; + + public void SetElement(VisualElement element) + { + VisualElement oldElement = Element; + Element = element; + UpdateTitle(); + + OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); + + if (Element != null && !string.IsNullOrEmpty(Element.AutomationId)) + SetAutomationId(Element.AutomationId); + + EffectUtilities.RegisterEffectControlProvider(this, oldElement, element); + } + + public void SetElementSize(Size size) + { + Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height)); + } + + public NSViewController ViewController => _disposed ? null : this; + + public override void ViewDidAppear() + { + base.ViewDidAppear(); + + if (_appeared || _disposed) + return; + + _appeared = true; + PageController.SendAppearing(); + } + + public override void ViewDidDisappear() + { + base.ViewDidDisappear(); + + if (!_appeared || _disposed) + return; + + _appeared = false; + PageController.SendDisappearing(); + } + + public override void ViewWillAppear() + { + Init(); + base.ViewWillAppear(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + Element.PropertyChanged -= OnHandlePropertyChanged; + Platform.SetRenderer(Element, null); + if (_appeared) + PageController.SendDisappearing(); + + _appeared = false; + + if (_events != null) + { + _events.Dispose(); + _events = null; + } + + if (_packager != null) + { + _packager.Dispose(); + _packager = null; + } + + if (_tracker != null) + { + _tracker.Dispose(); + _tracker = null; + } + + Element = null; + _disposed = true; + } + + base.Dispose(disposing); + } + + void OnElementChanged(VisualElementChangedEventArgs e) + { + ElementChanged?.Invoke(this, e); + } + + void SetAutomationId(string id) + { + if (NativeView != null) + NativeView.AccessibilityIdentifier = id; + } + + void Init() + { + if (_init) + return; + UpdateBackground(); + + _packager = new VisualElementPackager(this); + _packager.Load(); + + Element.PropertyChanged += OnHandlePropertyChanged; + _tracker = new VisualElementTracker(this); + + _events = new EventTracker(this); + _events.LoadEvents(View); + _init = true; + } + + void OnHandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackground(); + else if (e.PropertyName == Page.BackgroundImageProperty.PropertyName) + UpdateBackground(); + else if (e.PropertyName == Page.TitleProperty.PropertyName) + UpdateTitle(); + } + + void UpdateBackground() + { + string bgImage = ((Page)Element).BackgroundImage; + if (!string.IsNullOrEmpty(bgImage)) + { + View.Layer.BackgroundColor = NSColor.FromPatternImage(NSImage.ImageNamed(bgImage)).CGColor; + return; + } + Color bgColor = Element.BackgroundColor; + View.Layer.BackgroundColor = bgColor.IsDefault ? NSColor.White.CGColor : bgColor.ToCGColor(); + } + + void UpdateTitle() + { + if (!string.IsNullOrWhiteSpace(((Page)Element).Title)) + Title = ((Page)Element).Title; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs new file mode 100644 index 00000000..ac07bfd2 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/PickerRenderer.cs @@ -0,0 +1,154 @@ +using System; +using AppKit; +using System.ComponentModel; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class PickerRenderer : ViewRenderer<Picker, NSComboBox> + { + bool _disposed; + NSColor _defaultTextColor; + NSColor _defaultBackgroundColor; + + IElementController ElementController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<Picker> e) + { + if (e.NewElement != null) + { + if (Control == null) + SetNativeControl(new NSComboBox { Editable = false }); + + _defaultTextColor = Control.TextColor; + _defaultBackgroundColor = Control.BackgroundColor; + + Control.UsesDataSource = true; + Control.DataSource = new ComboDataSource(this); + + Control.SelectionChanged += ComboBoxSelectionChanged; + + UpdatePicker(); + UpdateTextColor(); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + if (e.PropertyName == Picker.TitleProperty.PropertyName) + UpdatePicker(); + if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName) + UpdatePicker(); + if (e.PropertyName == Picker.TextColorProperty.PropertyName || + e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateTextColor(); + } + + protected override void SetBackgroundColor(Color color) + { + base.SetBackgroundColor(color); + + if (Control == null) + return; + + Control.BackgroundColor = color == Color.Default ? _defaultBackgroundColor : color.ToNSColor(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!_disposed) + { + _disposed = true; + if (Element != null) + { + //TODO: Implement ObservableList picker source change + //((ObservableList<string>)Element.Items).CollectionChanged -= RowsCollectionChanged; + } + + if (Control != null) + Control.SelectionChanged -= ComboBoxSelectionChanged; + } + } + base.Dispose(disposing); + } + + void ComboBoxSelectionChanged(object sender, EventArgs e) + { + ElementController?.SetValueFromRenderer(Picker.SelectedIndexProperty, (int)Control.SelectedIndex); + } + + void OnEnded(object sender, EventArgs eventArgs) + { + ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false); + } + + void OnStarted(object sender, EventArgs eventArgs) + { + ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + + void RowsCollectionChanged(object sender, EventArgs e) + { + UpdatePicker(); + } + + void UpdatePicker() + { + if (Control == null || Element == null) + return; + + var selectedIndex = Element.SelectedIndex; + var items = Element.Items; + Control.PlaceholderString = Element.Title ?? string.Empty; + Control.ReloadData(); + if (items == null || items.Count == 0 || selectedIndex < 0) + return; + + Control.SelectItem(selectedIndex); + } + + void UpdateTextColor() + { + if (Control == null || Element == null) + return; + + var textColor = Element.TextColor; + + if (textColor.IsDefault || !Element.IsEnabled) + Control.TextColor = _defaultTextColor; + else + Control.TextColor = textColor.ToNSColor(); + } + + class ComboDataSource : NSComboBoxDataSource + { + readonly PickerRenderer _renderer; + + public ComboDataSource(PickerRenderer model) + { + _renderer = model; + } + + public override nint ItemCount(NSComboBox comboBox) + { + return _renderer.Element.Items?.Count ?? 0; + } + + public override NSObject ObjectValueForItem(NSComboBox comboBox, nint index) + { + return new NSString(_renderer.Element.Items[(int)index]); + } + + public override nint IndexOfItem(NSComboBox comboBox, string value) + { + var index = _renderer.Element.Items?.IndexOf(value) ?? -1; + return index; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs new file mode 100644 index 00000000..7b3c84ae --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ProgressBarRenderer.cs @@ -0,0 +1,66 @@ +using System.ComponentModel; +using AppKit; +using CoreImage; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ProgressBarRenderer : ViewRenderer<ProgressBar, NSProgressIndicator> + { + static CIColorPolynomial s_currentColorFilter; + static NSColor s_currentColor; + + protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e) + { + if (e.NewElement == null) return; + if (Control == null) + SetNativeControl(new NSProgressIndicator + { + IsDisplayedWhenStopped = true, + Style = NSProgressIndicatorStyle.Bar, + MinValue = 0, + MaxValue = 1 + }); + UpdateProgress(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == ProgressBar.ProgressProperty.PropertyName) + UpdateProgress(); + } + + protected override void SetBackgroundColor(Color color) + { + if (Control == null) + return; + + if (s_currentColorFilter == null && color.IsDefault) + return; + + if (color.IsDefault) + Control.ContentFilters = new CIFilter[0]; + + var newColor = Element.BackgroundColor.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 UpdateProgress() + { + Control.DoubleValue = Element.Progress; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs new file mode 100644 index 00000000..c41d1d2b --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ScrollViewRenderer.cs @@ -0,0 +1,214 @@ +using System; +using System.ComponentModel; +using AppKit; +using RectangleF = CoreGraphics.CGRect; +using ObjCRuntime; +using Foundation; + +// ReSharper disable UnusedMember.Local +// ReSharper disable UnusedParameter.Local + +namespace Xamarin.Forms.Platform.MacOS +{ + public class ScrollViewRenderer : NSScrollView, IVisualElementRenderer + { + EventTracker _events; + VisualElementTracker _tracker; + ScrollToRequestedEventArgs _requestedScroll; + IVisualElementRenderer _contentRenderer; + + public ScrollViewRenderer() : base(RectangleF.Empty) + { + DrawsBackground = false; + ContentView.PostsBoundsChangedNotifications = true; + NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector(nameof(UpdateScrollPosition)), + BoundsChangedNotification, ContentView); + } + + IScrollViewController Controller => Element as IScrollViewController; + + public VisualElement Element { get; private set; } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return NativeView.GetSizeRequest(widthConstraint, heightConstraint, 44, 44); + } + + public NSView NativeView => this; + + public void SetElement(VisualElement element) + { + _requestedScroll = null; + var oldElement = Element; + Element = element; + + if (oldElement != null) + { + oldElement.PropertyChanged -= HandlePropertyChanged; + ((IScrollViewController)oldElement).ScrollToRequested -= OnScrollToRequested; + } + + if (element != null) + { + element.PropertyChanged += HandlePropertyChanged; + ((IScrollViewController)element).ScrollToRequested += OnScrollToRequested; + if (_tracker == null) + { + PackContent(); + + _events = new EventTracker(this); + _events.LoadEvents(this); + + _tracker = new VisualElementTracker(this); + _tracker.NativeControlUpdated += OnNativeControlUpdated; + } + + UpdateContentSize(); + UpdateBackgroundColor(); + + OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); + } + } + + public void SetElementSize(Size size) + { + Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(Element, + new Rectangle(Element.X, Element.Y, size.Width, size.Height)); + } + + public NSViewController ViewController => null; + + public override void Layout() + { + base.Layout(); + LayoutSubviews(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_tracker == null) + return; + + SetElement(null); + + _tracker.NativeControlUpdated -= OnNativeControlUpdated; + _tracker.Dispose(); + _tracker = null; + + _events.Dispose(); + _events = null; + + ClearContentRenderer(); + + //NSNotificationCenter.DefaultCenter.RemoveObserver(this, BoundsChangedNotification); + } + + base.Dispose(disposing); + } + + void OnElementChanged(VisualElementChangedEventArgs e) + { + ElementChanged?.Invoke(this, e); + } + + void PackContent() + { + ClearContentRenderer(); + + if (Controller.Children.Count == 0 || !(Controller.Children[0] is VisualElement)) + return; + + var content = (VisualElement)Controller.Children[0]; + if (Platform.GetRenderer(content) == null) + Platform.SetRenderer(content, Platform.CreateRenderer(content)); + + _contentRenderer = Platform.GetRenderer(content); + + DocumentView = _contentRenderer.NativeView; + } + + void LayoutSubviews() + { + if (_requestedScroll != null && Superview != null) + { + var request = _requestedScroll; + _requestedScroll = null; + OnScrollToRequested(this, request); + } + } + + void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == ScrollView.ContentSizeProperty.PropertyName) + UpdateContentSize(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundColor(); + } + + void HandleScrollAnimationEnded(object sender, EventArgs e) + { + Controller.SendScrollFinished(); + } + + void HandleScrolled(object sender, EventArgs e) + { + UpdateScrollPosition(); + } + + void OnNativeControlUpdated(object sender, EventArgs eventArgs) + { + UpdateContentSize(); + } + + void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e) + { + if (Superview == null) + { + _requestedScroll = e; + return; + } + + Point scrollPoint = (e.Mode == ScrollToMode.Position) + ? new Point(e.ScrollX, Element.Height - e.ScrollY) + : Controller.GetScrollPositionForElement(e.Element as VisualElement, e.Position); + + (DocumentView as NSView)?.ScrollPoint(scrollPoint.ToPointF()); + + Controller.SendScrollFinished(); + } + + void UpdateBackgroundColor() + { + BackgroundColor = Element.BackgroundColor.ToNSColor(Color.Transparent); + } + + void UpdateContentSize() + { + if (_contentRenderer == null) + return; + var contentSize = ((ScrollView)Element).ContentSize.ToSizeF(); + if (!contentSize.IsEmpty) + _contentRenderer.NativeView.Frame = new RectangleF(0, Element.Height - contentSize.Height, contentSize.Width, + contentSize.Height); + } + + [Export(nameof(UpdateScrollPosition))] + void UpdateScrollPosition() + { + var convertedPoint = (DocumentView as NSView)?.ConvertPointFromView(ContentView.Bounds.Location, ContentView); + if (convertedPoint.HasValue) + Controller.SetScrolledPosition(Math.Max(0, convertedPoint.Value.X), Math.Max(0, convertedPoint.Value.Y)); + } + + void ClearContentRenderer() + { + _contentRenderer?.NativeView?.RemoveFromSuperview(); + _contentRenderer?.Dispose(); + _contentRenderer = null; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs new file mode 100644 index 00000000..50c7d5fe --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/SearchBarRenderer.cs @@ -0,0 +1,179 @@ +using System; +using System.ComponentModel; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class SearchBarRenderer : ViewRenderer<SearchBar, NSSearchField> + { + NSColor _defaultTextColor; + + IElementController ElementController => Element; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (Control != null) + { + Control.Changed -= OnTextChanged; + Control.Cell.CancelButtonCell.Activated -= OnCancelClicked; + Control.Cell.SearchButtonCell.Activated -= OnSearchButtonClicked; + Control.EditingEnded -= OnEditingEnded; + Control.EditingBegan -= OnEditingStarted; + } + } + + base.Dispose(disposing); + } + + protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e) + { + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new NSSearchField { BackgroundColor = NSColor.Clear, DrawsBackground = false }); + + Control.Cell.CancelButtonCell.Activated += OnCancelClicked; + Control.Cell.SearchButtonCell.Activated += OnSearchButtonClicked; + + Control.Changed += OnTextChanged; + Control.EditingBegan += OnEditingStarted; + Control.EditingEnded += OnEditingEnded; + } + + UpdatePlaceholder(); + UpdateText(); + UpdateFont(); + UpdateIsEnabled(); + UpdateCancelButton(); + UpdateAlignment(); + UpdateTextColor(); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == SearchBar.PlaceholderProperty.PropertyName || + e.PropertyName == SearchBar.PlaceholderColorProperty.PropertyName) + UpdatePlaceholder(); + else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + { + UpdateIsEnabled(); + UpdateTextColor(); + UpdatePlaceholder(); + } + else if (e.PropertyName == SearchBar.TextColorProperty.PropertyName) + UpdateTextColor(); + else if (e.PropertyName == SearchBar.TextProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == SearchBar.CancelButtonColorProperty.PropertyName) + UpdateCancelButton(); + else if (e.PropertyName == SearchBar.FontAttributesProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == SearchBar.FontFamilyProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == SearchBar.FontSizeProperty.PropertyName) + UpdateFont(); + else if (e.PropertyName == SearchBar.HorizontalTextAlignmentProperty.PropertyName) + UpdateAlignment(); + } + + protected override void SetBackgroundColor(Color color) + { + base.SetBackgroundColor(color); + + if (Control == null) + return; + Control.BackgroundColor = color == Color.Default ? NSColor.Clear : color.ToNSColor(); + + UpdateCancelButton(); + } + + void OnCancelClicked(object sender, EventArgs args) + { + ElementController.SetValueFromRenderer(SearchBar.TextProperty, null); + Control.ResignFirstResponder(); + } + + void OnEditingEnded(object sender, EventArgs e) + { + ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false); + } + + void OnEditingStarted(object sender, EventArgs e) + { + ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + + void OnSearchButtonClicked(object sender, EventArgs e) + { + ((ISearchBarController)Element).OnSearchButtonPressed(); + Control.ResignFirstResponder(); + } + + void OnTextChanged(object sender, EventArgs a) + { + ElementController.SetValueFromRenderer(SearchBar.TextProperty, Control.StringValue); + } + + void UpdateAlignment() + { + Control.Alignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(); + } + + void UpdateCancelButton() + { + var cancelButtonColor = Element.CancelButtonColor; + + if (cancelButtonColor.IsDefault) + { + Control.Cell.CancelButtonCell.Title = ""; + } + else + { + var textWithColor = new NSAttributedString(Control.Cell.CancelButtonCell.Title ?? "", + foregroundColor: cancelButtonColor.ToNSColor()); + Control.Cell.CancelButtonCell.AttributedTitle = textWithColor; + } + } + + void UpdateFont() + { + Control.Font = Element.ToNSFont(); + } + + void UpdateIsEnabled() + { + Control.Enabled = Element.IsEnabled; + } + + void UpdatePlaceholder() + { + var formatted = (FormattedString)Element.Placeholder ?? string.Empty; + var targetColor = Element.PlaceholderColor; + var color = Element.IsEnabled && !targetColor.IsDefault ? targetColor : ColorExtensions.SeventyPercentGrey.ToColor(); + Control.PlaceholderAttributedString = formatted.ToAttributed(Element, color); + } + + void UpdateText() + { + Control.StringValue = Element.Text ?? ""; + UpdateCancelButton(); + } + + void UpdateTextColor() + { + _defaultTextColor = _defaultTextColor ?? Control.TextColor; + var targetColor = Element.TextColor; + + Control.TextColor = targetColor.ToNSColor(_defaultTextColor); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs new file mode 100644 index 00000000..9b495ded --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/SliderRenderer.cs @@ -0,0 +1,77 @@ +using System; +using SizeF = CoreGraphics.CGSize; +using AppKit; +using System.ComponentModel; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class SliderRenderer : ViewRenderer<Slider, NSSlider> + { + bool _disposed; + + IElementController ElementController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<Slider> e) + { + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new NSSlider { Continuous = true }); + Control.Activated += OnControlActivated; + } + + UpdateMaximum(); + UpdateMinimum(); + UpdateValue(); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Slider.MaximumProperty.PropertyName) + UpdateMaximum(); + else if (e.PropertyName == Slider.MinimumProperty.PropertyName) + UpdateMinimum(); + else if (e.PropertyName == Slider.ValueProperty.PropertyName) + UpdateValue(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + if (Control != null) + Control.Activated -= OnControlActivated; + } + + base.Dispose(disposing); + } + + void OnControlActivated(object sender, EventArgs eventArgs) + { + ElementController?.SetValueFromRenderer(Slider.ValueProperty, Control.DoubleValue); + } + + void UpdateMaximum() + { + Control.MaxValue = (float)Element.Maximum; + } + + void UpdateMinimum() + { + Control.MinValue = (float)Element.Minimum; + } + + void UpdateValue() + { + if (Math.Abs(Element.Value - Control.DoubleValue) > 0) + Control.DoubleValue = (float)Element.Value; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs new file mode 100644 index 00000000..f28bf1f7 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/StepperRenderer.cs @@ -0,0 +1,84 @@ +using System; +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class StepperRenderer : ViewRenderer<Stepper, NSStepper> + { + bool _disposed; + + IElementController ElementController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e) + { + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new NSStepper()); + Control.Activated += OnControlActivated; + } + + UpdateMinimum(); + UpdateMaximum(); + UpdateValue(); + UpdateIncrement(); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Stepper.MinimumProperty.PropertyName) + UpdateMinimum(); + else if (e.PropertyName == Stepper.MaximumProperty.PropertyName) + UpdateMaximum(); + else if (e.PropertyName == Stepper.ValueProperty.PropertyName) + UpdateValue(); + else if (e.PropertyName == Stepper.IncrementProperty.PropertyName) + UpdateIncrement(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + if (Control != null) + Control.Activated -= OnControlActivated; + } + + base.Dispose(disposing); + } + + void OnControlActivated(object sender, EventArgs e) + { + ElementController?.SetValueFromRenderer(Stepper.ValueProperty, Control.DoubleValue); + } + + void UpdateIncrement() + { + Control.Increment = Element.Increment; + } + + void UpdateMaximum() + { + Control.MaxValue = Element.Maximum; + } + + void UpdateMinimum() + { + Control.MinValue = Element.Minimum; + } + + void UpdateValue() + { + if (Math.Abs(Control.DoubleValue - Element.Value) > 0) + Control.DoubleValue = Element.Value; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs new file mode 100644 index 00000000..c93007e2 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/SwitchRenderer.cs @@ -0,0 +1,61 @@ +using System; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class SwitchRenderer : ViewRenderer<Switch, NSButton> + { + bool _disposed; + + IElementController ElementController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<Switch> e) + { + if (e.OldElement != null) + e.OldElement.Toggled -= OnElementToggled; + + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new NSButton { AllowsMixedState = false, Title = string.Empty }); + + Control.SetButtonType(NSButtonType.Switch); + Control.Activated += OnControlActivated; + } + + UpdateState(); + e.NewElement.Toggled += OnElementToggled; + } + + base.OnElementChanged(e); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + if (Control != null) + Control.Activated -= OnControlActivated; + } + + base.Dispose(disposing); + } + + void OnControlActivated(object sender, EventArgs e) + { + ElementController?.SetValueFromRenderer(Switch.IsToggledProperty, Control.State == NSCellStateValue.On); + } + + void OnElementToggled(object sender, EventArgs e) + { + UpdateState(); + } + + void UpdateState() + { + Control.State = Element.IsToggled ? NSCellStateValue.On : NSCellStateValue.Off; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs new file mode 100644 index 00000000..3aee095d --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/TabbedPageRenderer.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using AppKit; +using CoreGraphics; +using Xamarin.Forms.Internals; +using Xamarin.Forms.PlatformConfiguration.macOSSpecific; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class TabbedPageRenderer : NSTabViewController, IVisualElementRenderer, IEffectControlProvider + { + const float DefaultImageSizeSegmentedButton = 19; + const int TabHolderHeight = 30; + + bool _disposed; + bool _updatingControllers; + bool _barBackgroundColorWasSet; + bool _barTextColorWasSet; + bool _defaultBarTextColorSet; + bool _defaultBarColorSet; + VisualElementTracker _tracker; + bool _loaded; + Size _queuedSize; + + + public VisualElement Element { get; private set; } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return NativeView.GetSizeRequest(widthConstraint, heightConstraint); + } + + public NSView NativeView => View; + + public void SetElement(VisualElement element) + { + var oldElement = Element; + Element = element; + + if (oldElement != null) + { + oldElement.PropertyChanged -= OnPropertyChanged; + var tabbedPage = oldElement as TabbedPage; + if (tabbedPage != null) tabbedPage.PagesChanged -= OnPagesChanged; + } + + if (element != null) + { + if (_tracker == null) + { + _tracker = new VisualElementTracker(this); + _tracker.NativeControlUpdated += (sender, e) => UpdateNativeWidget(); + } + } + + OnElementChanged(new VisualElementChangedEventArgs(oldElement, element)); + + ConfigureTabView(); + + OnPagesChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + Tabbed.PropertyChanged += OnPropertyChanged; + Tabbed.PagesChanged += OnPagesChanged; + + UpdateBarBackgroundColor(); + + UpdateBarTextColor(); + + EffectUtilities.RegisterEffectControlProvider(this, oldElement, element); + } + + IPageController PageController => Element as IPageController; + + IElementController ElementController => Element; + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect != null) + platformEffect.Container = View; + } + + public void SetElementSize(Size size) + { + if (_loaded) + Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height)); + else + _queuedSize = size; + } + + public NSViewController ViewController => this; + + public override void ViewWillLayout() + { + base.ViewWillLayout(); + + if (Element == null) + return; + + if (!Element.Bounds.IsEmpty) + View.Frame = new System.Drawing.RectangleF((float)Element.X, (float)Element.Y, (float)Element.Width, (float)Element.Height); + + var frame = View.Frame; + PageController.ContainerArea = new Rectangle(0, 0, frame.Width, frame.Height - TabHolderHeight); + + if (!_queuedSize.IsZero) + { + Element.Layout(new Rectangle(Element.X, Element.Y, _queuedSize.Width, _queuedSize.Height)); + _queuedSize = Size.Zero; + } + + _loaded = true; + } + + + public override nint SelectedTabViewItemIndex + { + get { return base.SelectedTabViewItemIndex; } + set + { + base.SelectedTabViewItemIndex = value; + if (!_updatingControllers) + UpdateCurrentPage(); + } + } + + public override void ViewDidAppear() + { + PageController.SendAppearing(); + base.ViewDidAppear(); + } + + public override void ViewDidDisappear() + { + base.ViewDidDisappear(); + PageController.SendDisappearing(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + PageController.SendDisappearing(); + Tabbed.PropertyChanged -= OnPropertyChanged; + Tabbed.PagesChanged -= OnPagesChanged; + + if (_tracker != null) + { + _tracker.Dispose(); + _tracker = null; + } + } + + base.Dispose(disposing); + } + + protected virtual void ConfigureTabView() + { + View.WantsLayer = true; + TabView.WantsLayer = true; + TabView.DrawsBackground = false; + var tabStyle = Tabbed.OnThisPlatform().GetTabsStyle(); + switch (tabStyle) + { + case TabsStyle.OnNavigation: + case TabsStyle.Hidden: + TabStyle = NSTabViewControllerTabStyle.Unspecified; + break; + case TabsStyle.Icons: + TabStyle = NSTabViewControllerTabStyle.Toolbar; + break; + case TabsStyle.OnBottom: + TabStyle = NSTabViewControllerTabStyle.SegmentedControlOnBottom; + break; + default: + TabStyle = NSTabViewControllerTabStyle.SegmentedControlOnTop; + break; + } + + TabView.TabViewType = NSTabViewType.NSNoTabsNoBorder; + } + + protected virtual void OnElementChanged(VisualElementChangedEventArgs e) + { + ElementChanged?.Invoke(this, e); + } + + protected virtual NSTabViewItem GetTabViewItem(Page page, IVisualElementRenderer pageRenderer) + { + var tvi = new NSTabViewItem { ViewController = pageRenderer.ViewController, Label = page.Title ?? "" }; + if (!string.IsNullOrEmpty (page.Icon)) { + var image = GetTabViewItemIcon (page.Icon); + if (image != null) + tvi.Image = image; + } + return tvi; + } + + protected virtual NSImage GetTabViewItemIcon(string imageName) + { + var image = NSImage.ImageNamed (imageName); + if(image == null) + image = new NSImage (imageName); + + if (image == null) + return null; + + bool shouldResize = TabStyle == NSTabViewControllerTabStyle.SegmentedControlOnTop || + TabStyle == NSTabViewControllerTabStyle.SegmentedControlOnBottom; + if (shouldResize) + image = image.ResizeTo(new CGSize(DefaultImageSizeSegmentedButton, DefaultImageSizeSegmentedButton)); + return image; + } + + protected virtual void UpdateNativeWidget() + { + TabView.Layout(); + } + + protected TabbedPage Tabbed => (TabbedPage)Element; + + void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Page.TitleProperty.PropertyName) + { + var page = (Page)sender; + var index = TabbedPage.GetIndex(page); + TabViewItems[index].Label = page.Title; + } + else if (e.PropertyName == Page.IconProperty.PropertyName) + { + var page = (Page)sender; + + var index = TabbedPage.GetIndex(page); + TabViewItems[index].Label = page.Title; + + if (!string.IsNullOrEmpty(page.Icon)) + { + TabViewItems[index].Image = new NSImage(page.Icon); + } + else if (TabViewItems[index].Image != null) + { + TabViewItems[index].Image = new NSImage(); + } + } + } + + void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + e.Apply((o, i, c) => SetupPage((Page)o, i), (o, i) => TeardownPage((Page)o), Reset); + + SetControllers(); + + UpdateChildrenOrderIndex(); + + SetSelectedTabViewItem(); + } + + void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TabbedPage.CurrentPage)) + { + var current = Tabbed.CurrentPage; + if (current == null) + return; + + SetSelectedTabViewItem(); + } + else if (e.PropertyName == TabbedPage.BarBackgroundColorProperty.PropertyName) + UpdateBarBackgroundColor(); + else if (e.PropertyName == TabbedPage.BarTextColorProperty.PropertyName) + UpdateBarTextColor(); + } + + void Reset() + { + var i = 0; + foreach (var page in Tabbed.Children) + SetupPage(page, i++); + } + + void SetControllers() + { + _updatingControllers = true; + for (var i = 0; i < ElementController.LogicalChildren.Count; i++) + { + var child = ElementController.LogicalChildren[i]; + var page = child as Page; + if (page == null) + continue; + + var pageRenderer = Platform.GetRenderer(page); + if (pageRenderer != null) + { + pageRenderer.ViewController.Identifier = i.ToString(); + + NSTabViewItem newTvi = GetTabViewItem(page, pageRenderer); + + AddTabViewItem(newTvi); + } + } + _updatingControllers = false; + } + + void SetupPage(Page page, int index) + { + var renderer = Platform.GetRenderer(page); + if (renderer == null) + { + renderer = Platform.CreateRenderer(page); + Platform.SetRenderer(page, renderer); + } + + renderer.ViewController.Identifier = index.ToString(); + + page.PropertyChanged += OnPagePropertyChanged; + } + + void TeardownPage(Page page) + { + page.PropertyChanged -= OnPagePropertyChanged; + + Platform.SetRenderer(page, null); + } + + void SetSelectedTabViewItem() + { + if (Tabbed.CurrentPage == null) + return; + var selectedIndex = TabbedPage.GetIndex(Tabbed.CurrentPage); + SelectedTabViewItemIndex = selectedIndex; + } + + void UpdateChildrenOrderIndex() + { + for (var i = 0; i < TabViewItems.Length; i++) + { + int originalIndex; + if (int.TryParse(TabViewItems[i].ViewController.Identifier, out originalIndex)) + { + var page = PageController.InternalChildren[originalIndex]; + TabbedPage.SetIndex(page as Page, i); + } + } + } + + void UpdateCurrentPage() + { + var count = PageController.InternalChildren.Count; + Tabbed.CurrentPage = SelectedTabViewItemIndex >= 0 && SelectedTabViewItemIndex < count + ? Tabbed.GetPageByIndex((int)SelectedTabViewItemIndex) + : null; + } + + //TODO: Implement UpdateBarBackgroundColor + void UpdateBarBackgroundColor() + { + if (Tabbed == null || TabView == null) + return; + + var barBackgroundColor = Tabbed.BarBackgroundColor; + var isDefaultColor = barBackgroundColor.IsDefault; + + if (isDefaultColor && !_barBackgroundColorWasSet) + return; + + if (!_defaultBarColorSet) + { + //_defaultBarColor = TabView.color; + _defaultBarColorSet = true; + } + + if (!isDefaultColor) + _barBackgroundColorWasSet = true; + } + + //TODO: Implement UpdateBarTextColor + void UpdateBarTextColor() + { + if (Tabbed == null || TabView == null) + return; + + var barTextColor = Tabbed.BarTextColor; + var isDefaultColor = barTextColor.IsDefault; + + if (isDefaultColor && !_barTextColorWasSet) + return; + + if (!_defaultBarTextColorSet) + { + // _defaultBarTextColor = TabBar.TintColor; + _defaultBarTextColorSet = true; + } + + if (!isDefaultColor) + _barTextColorWasSet = true; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs new file mode 100644 index 00000000..90335ee2 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewDataSource.cs @@ -0,0 +1,131 @@ +using System; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + internal class TableViewDataSource : NSTableViewSource + { + static int s_sectionCount; + + const string HeaderIdentifier = nameof(TextCell); + const string ItemIdentifier = nameof(ViewCell); + + protected ITableViewController Controller => _tableView; + + readonly NSTableView _nsTableView; + readonly TableView _tableView; + + public TableViewDataSource(TableViewRenderer tableViewRenderer) + { + _tableView = tableViewRenderer.Element; + _nsTableView = tableViewRenderer.TableView; + Controller.ModelChanged += (s, e) => { _nsTableView?.ReloadData(); }; + AutomaticallyDeselect = true; + } + + public bool AutomaticallyDeselect { get; set; } + + public override void SelectionDidChange(NSNotification notification) + { + var row = _nsTableView.SelectedRow; + if (row == -1) + return; + + int sectionIndex; + bool isHeader; + int itemIndexInSection; + + GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader); + + var cell = Controller.Model.GetCell(sectionIndex, itemIndexInSection); + Controller.Model.RowSelected(cell); + if (AutomaticallyDeselect) + _nsTableView.DeselectRow(row); + } + + public override nint GetRowCount(NSTableView tableView) + { + nint count = 0; + s_sectionCount = Controller.Model.GetSectionCount(); + for (int i = 0; i < s_sectionCount; i++) + { + count += Controller.Model.GetRowCount(i) + 1; + } + + return count; + } + + public override bool ShouldSelectRow(NSTableView tableView, nint row) + { + int sectionIndex; + bool isHeader; + int itemIndexInSection; + + GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader); + + return !isHeader; + } + + public override NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, nint row) + { + int sectionIndex; + bool isHeader; + int itemIndexInSection; + + GetComputedIndexes(row, out sectionIndex, out itemIndexInSection, out isHeader); + + string id; + Cell cell; + if (isHeader) + { + id = HeaderIdentifier; + cell = Controller.Model.GetHeaderCell(sectionIndex) ?? + new TextCell { Text = Controller.Model.GetSectionTitle(sectionIndex) }; + } + else + { + id = ItemIdentifier; + cell = Controller.Model.GetCell(sectionIndex, itemIndexInSection); + } + + var nativeCell = CellNSView.GetNativeCell(tableView, cell, id, isHeader); + return nativeCell; + } + + void GetComputedIndexes(nint row, out int sectionIndex, out int itemIndexInSection, out bool isHeader) + { + var totalItems = 0; + isHeader = false; + sectionIndex = 0; + itemIndexInSection = 0; + + for (int i = 0; i < s_sectionCount; i++) + { + var groupCount = Controller.Model.GetRowCount(i); + var itemsInSection = groupCount + 1; + + if (row < totalItems + itemsInSection) + { + sectionIndex = i; + itemIndexInSection = (int)row - totalItems; + isHeader = itemIndexInSection == 0; + if (isHeader) + itemIndexInSection = -1; + else + itemIndexInSection = itemIndexInSection - 1; + break; + } + totalItems += itemsInSection; + } + } + } + + //TODO: Implement Uneven rows + internal class UnEvenTableViewModelRenderer : TableViewDataSource + { + public UnEvenTableViewModelRenderer(TableViewRenderer model) : base(model) + { + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs new file mode 100644 index 00000000..de447dcb --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/TableViewRenderer.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.ComponentModel; +using AppKit; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class TableViewRenderer : ViewRenderer<TableView, NSView> + { + const int DefaultRowHeight = 44; + + internal NSTableView TableView { get; set; } + + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return Control.GetSizeRequest(widthConstraint, heightConstraint, DefaultRowHeight, DefaultRowHeight); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + var viewsToLookAt = new Stack<NSView>(Subviews); + while (viewsToLookAt.Count > 0) + { + var view = viewsToLookAt.Pop(); + var viewCellRenderer = view as IVisualElementRenderer; + if (viewCellRenderer != null) + { + viewCellRenderer.Dispose(); + } + else + { + foreach (var child in view.Subviews) + viewsToLookAt.Push(child); + } + } + } + + base.Dispose(disposing); + } + + protected virtual NSTableView CreateNSTableView(TableView list) + { + return new NSTableView().AsListViewLook(); + } + + protected override void OnElementChanged(ElementChangedEventArgs<TableView> e) + { + if (e.NewElement != null) + { + if (Control == null) + { + var scroller = new NSScrollView + { + AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable, + DocumentView = TableView = CreateNSTableView(e.NewElement) + }; + + SetNativeControl(scroller); + } + + SetSource(); + UpdateRowHeight(); + UpdateBackgroundView(); + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Xamarin.Forms.TableView.RowHeightProperty.PropertyName) + UpdateRowHeight(); + else if (e.PropertyName == Xamarin.Forms.TableView.HasUnevenRowsProperty.PropertyName) + SetSource(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundView(); + } + + void SetSource() + { + var modeledView = Element; + TableView.Source = modeledView.HasUnevenRows ? new UnEvenTableViewModelRenderer(this) : new TableViewDataSource(this); + } + + void UpdateBackgroundView() + { + } + + void UpdateRowHeight() + { + var rowHeight = Element.RowHeight; + TableView.RowHeight = rowHeight <= 0 ? DefaultRowHeight : rowHeight; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs new file mode 100644 index 00000000..fc645c46 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/TimePickerRenderer.cs @@ -0,0 +1,104 @@ +using System; +using System.ComponentModel; +using AppKit; +using Foundation; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class TimePickerRenderer : ViewRenderer<TimePicker, NSDatePicker> + { + NSColor _defaultTextColor; + NSColor _defaultBackgroundColor; + bool _disposed; + + IElementController ElementController => Element; + + protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e) + { + base.OnElementChanged(e); + + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new NSDatePicker + { + DatePickerMode = NSDatePickerMode.Single, + TimeZone = new NSTimeZone("UTC"), + DatePickerStyle = NSDatePickerStyle.TextFieldAndStepper, + DatePickerElements = NSDatePickerElementFlags.HourMinuteSecond + }); + + Control.ValidateProposedDateValue += HandleValueChanged; + _defaultTextColor = Control.TextColor; + _defaultBackgroundColor = Control.BackgroundColor; + } + + UpdateTime(); + UpdateTextColor(); + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == TimePicker.TimeProperty.PropertyName || + e.PropertyName == TimePicker.FormatProperty.PropertyName) + UpdateTime(); + + if (e.PropertyName == TimePicker.TextColorProperty.PropertyName || + e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateTextColor(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + if (Control != null) + Control.ValidateProposedDateValue -= HandleValueChanged; + + _disposed = true; + } + base.Dispose(disposing); + } + + protected override void SetBackgroundColor(Color color) + { + base.SetBackgroundColor(color); + + if (Control == null) + return; + Control.BackgroundColor = color == Color.Default ? _defaultBackgroundColor : color.ToNSColor(); + } + + void HandleValueChanged(object sender, NSDatePickerValidatorEventArgs e) + { + ElementController?.SetValueFromRenderer(TimePicker.TimeProperty, + Control.DateValue.ToDateTime() - new DateTime(2001, 1, 1)); + } + + void UpdateTime() + { + if (Control == null || Element == null) + return; + var time = new DateTime(2001, 1, 1).Add(Element.Time); + var newDate = time.ToNSDate(); + if (!Equals(Control.DateValue, newDate)) + Control.DateValue = newDate; + } + + void UpdateTextColor() + { + if (Control == null || Element == null) + return; + var textColor = Element.TextColor; + + if (textColor.IsDefault || !Element.IsEnabled) + Control.TextColor = _defaultTextColor; + else + Control.TextColor = textColor.ToNSColor(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs new file mode 100644 index 00000000..8d93c2c7 --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Renderers/WebViewRenderer.cs @@ -0,0 +1,149 @@ +using System; +using System.ComponentModel; +using AppKit; +using Foundation; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Platform.MacOS +{ + public class WebViewRenderer : ViewRenderer<WebView, WebKit.WebView>, IWebViewDelegate + { + bool _disposed; + bool _ignoreSourceChanges; + WebNavigationEvent _lastBackForwardEvent; + WebNavigationEvent _lastEvent; + + IElementController ElementController => Element; + + void IWebViewDelegate.LoadHtml(string html, string baseUrl) + { + if (html != null) + Control.MainFrame.LoadHtmlString(html, + baseUrl == null ? new NSUrl(NSBundle.MainBundle.BundlePath, true) : new NSUrl(baseUrl, true)); + } + + void IWebViewDelegate.LoadUrl(string url) + { + Control.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(url))); + } + + protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) + { + base.OnElementChanged(e); + + if (e.NewElement != null) + { + if (Control == null) + { + SetNativeControl(new WebKit.WebView + { + AutoresizingMask = NSViewResizingMask.WidthSizable, + AutoresizesSubviews = true + }); + Control.OnFinishedLoading += OnNSWebViewFinishedLoad; + Control.OnFailedLoading += OnNSWebViewFailedLoadWithError; + + Element.EvalRequested += OnEvalRequested; + Element.GoBackRequested += OnGoBackRequested; + Element.GoForwardRequested += OnGoForwardRequested; + + Layer.BackgroundColor = NSColor.Clear.CGColor; + } + } + + Load(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == WebView.SourceProperty.PropertyName) + Load(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + Control.OnFinishedLoading -= OnNSWebViewFinishedLoad; + Control.OnFailedLoading -= OnNSWebViewFailedLoadWithError; + Element.EvalRequested -= OnEvalRequested; + Element.GoBackRequested -= OnGoBackRequested; + Element.GoForwardRequested -= OnGoForwardRequested; + } + base.Dispose(disposing); + } + + void Load() + { + if (_ignoreSourceChanges) + return; + + Element?.Source?.Load(this); + + UpdateCanGoBackForward(); + } + + void UpdateCanGoBackForward() + { + if (Element == null) + return; + Element.CanGoBack = Control.CanGoBack(); + Element.CanGoForward = Control.CanGoForward(); + } + + void OnEvalRequested(object sender, EvalRequested eventArg) + { + Control?.StringByEvaluatingJavaScriptFromString(eventArg?.Script); + } + + void OnGoBackRequested(object sender, EventArgs eventArgs) + { + if (Control.CanGoBack()) + { + _lastBackForwardEvent = WebNavigationEvent.Back; + Control.GoBack(); + } + + UpdateCanGoBackForward(); + } + + void OnGoForwardRequested(object sender, EventArgs eventArgs) + { + if (Control.CanGoForward()) + { + _lastBackForwardEvent = WebNavigationEvent.Forward; + Control.GoForward(); + } + + UpdateCanGoBackForward(); + } + + void OnNSWebViewFailedLoadWithError(object sender, WebKit.WebResourceErrorEventArgs e) + { + _lastEvent = _lastBackForwardEvent; + Element?.SendNavigated(new WebNavigatedEventArgs(_lastEvent, new UrlWebViewSource { Url = Control.MainFrameUrl }, + Control.MainFrameUrl, WebNavigationResult.Failure)); + + UpdateCanGoBackForward(); + } + + void OnNSWebViewFinishedLoad(object sender, WebKit.WebResourceCompletedEventArgs e) + { + if (Control.IsLoading) + return; + + _ignoreSourceChanges = true; + ElementController?.SetValueFromRenderer(WebView.SourceProperty, new UrlWebViewSource { Url = Control.MainFrameUrl }); + _ignoreSourceChanges = false; + + _lastEvent = _lastBackForwardEvent; + Element?.SendNavigated(new WebNavigatedEventArgs(_lastEvent, Element?.Source, Control.MainFrameUrl, + WebNavigationResult.Success)); + + UpdateCanGoBackForward(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj b/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj new file mode 100644 index 00000000..fe0eff2e --- /dev/null +++ b/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj @@ -0,0 +1,239 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{C0059C45-EA1E-42F3-8A0E-794BB547EC3C}</ProjectGuid> + <ProjectTypeGuids>{A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <OutputType>Library</OutputType> + <RootNamespace>Xamarin.Forms.Platform.macOS</RootNamespace> + <AssemblyName>Xamarin.Forms.Platform.macOS</AssemblyName> + <TargetFrameworkVersion>v2.0</TargetFrameworkVersion> + <TargetFrameworkIdentifier>Xamarin.Mac</TargetFrameworkIdentifier> + <MonoMacResourcePrefix>Resources</MonoMacResourcePrefix> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <ConsolePause>false</ConsolePause> + <EnableCodeSigning>false</EnableCodeSigning> + <CreatePackage>false</CreatePackage> + <EnablePackageSigning>false</EnablePackageSigning> + <IncludeMonoRuntime>false</IncludeMonoRuntime> + <UseSGen>false</UseSGen> + <HttpClientHandler> + </HttpClientHandler> + <TlsProvider> + </TlsProvider> + <LinkMode> + </LinkMode> + <XamMacArch> + </XamMacArch> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <DefineConstants> + </DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + <EnableCodeSigning>false</EnableCodeSigning> + <CreatePackage>false</CreatePackage> + <EnablePackageSigning>false</EnablePackageSigning> + <IncludeMonoRuntime>false</IncludeMonoRuntime> + <UseSGen>false</UseSGen> + <HttpClientHandler> + </HttpClientHandler> + <TlsProvider> + </TlsProvider> + <LinkMode> + </LinkMode> + <XamMacArch> + </XamMacArch> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="Xamarin.Mac" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Runtime.Serialization" /> + <Reference Include="System.Net.Http" /> + </ItemGroup> + <ItemGroup> + <Compile Include="FormsApplicationDelegate.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="PlatformRenderer.cs" /> + <Compile Include="Platform.cs" /> + <Compile Include="CADisplayLinkTicker.cs" /> + <Compile Include="Extensions\PageExtensions.cs" /> + <Compile Include="..\Xamarin.Forms.Platform.iOS\ExportCellAttribute.cs"> + <Link>ExportCellAttribute.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\ExportImageSourceHandlerAttribute.cs"> + <Link>ExportImageSourceHandlerAttribute.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Deserializer.cs"> + <Link>Deserializer.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\RendererPool.cs"> + <Link>RendererPool.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\EffectUtilities.cs"> + <Link>EffectUtilities.cs</Link> + </Compile> + <Compile Include="Renderers\PageRenderer.cs" /> + <Compile Include="Renderers\DefaultRenderer.cs" /> + <Compile Include="Extensions\AlignmentExtensions.cs" /> + <Compile Include="Renderers\ButtonRenderer.cs" /> + <Compile Include="ImageSourceHandlers.cs" /> + <Compile Include="Extensions\ButtonExtensions.cs" /> + <Compile Include="Renderers\BoxViewRenderer.cs" /> + <Compile Include="Renderers\ScrollViewRenderer.cs" /> + <Compile Include="Renderers\ActivityIndicatorRenderer.cs" /> + <Compile Include="Renderers\DatePickerRenderer.cs" /> + <Compile Include="Renderers\EntryRenderer.cs" /> + <Compile Include="Renderers\EditorRenderer.cs" /> + <Compile Include="Renderers\FrameRenderer.cs" /> + <Compile Include="Controls\FormsImageView.cs" /> + <Compile Include="Renderers\ImageRenderer.cs" /> + <Compile Include="Renderers\OpenGLViewRenderer.cs" /> + <Compile Include="Controls\MacOSOpenGLView.cs" /> + <Compile Include="Renderers\PickerRenderer.cs" /> + <Compile Include="Renderers\ProgressBarRenderer.cs" /> + <Compile Include="Renderers\SearchBarRenderer.cs" /> + <Compile Include="Renderers\SliderRenderer.cs" /> + <Compile Include="Renderers\StepperRenderer.cs" /> + <Compile Include="Renderers\SwitchRenderer.cs" /> + <Compile Include="Renderers\TimePickerRenderer.cs" /> + <Compile Include="Renderers\WebViewRenderer.cs" /> + <Compile Include="Renderers\ListViewRenderer.cs" /> + <Compile Include="Cells\CellNSView.cs" /> + <Compile Include="Cells\CellRenderer.cs" /> + <Compile Include="Cells\TextCellRenderer.cs" /> + <Compile Include="Cells\NSTableViewCellStyle.cs" /> + <Compile Include="Extensions\NSTextFieldExtensions.cs" /> + <Compile Include="Cells\ImageCellRenderer.cs" /> + <Compile Include="Cells\SwitchCellRenderer.cs" /> + <Compile Include="Cells\EntryCellRenderer.cs" /> + <Compile Include="Cells\ViewCellRenderer.cs" /> + <Compile Include="Cells\ViewCellNSView.cs" /> + <Compile Include="Renderers\ListViewDataSource.cs" /> + <Compile Include="Renderers\CustomNSTableHeaderView.cs" /> + <Compile Include="Renderers\CarouselPageRenderer.cs" /> + <Compile Include="Extensions\NSScrollViewExtensions.cs" /> + <Compile Include="Controls\ScrollViewScrollChangedEventArgs.cs" /> + <Compile Include="Renderers\MasterDetailPageRenderer.cs" /> + <Compile Include="Renderers\TabbedPageRenderer.cs" /> + <Compile Include="Renderers\NavigationPageRenderer.cs" /> + <Compile Include="Controls\FormsPageControllerDelegate.cs" /> + <Compile Include="Controls\NavigationChildPageWrapper.cs" /> + <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeViewPropertyListener.cs"> + <Link>NativeViewPropertyListener.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Core\Properties\GlobalAssemblyInfo.cs"> + <Link>Properties\GlobalAssemblyInfo.cs</Link> + </Compile> + <Compile Include="Renderers\LayoutRenderer.cs" /> + <Compile Include="Extensions\NSImageExtensions.cs" /> + <Compile Include="Renderers\NSPageContainer.cs" /> + <Compile Include="Renderers\PageControllerDelegate.cs" /> + <Compile Include="Extensions\NSViewControllerExtensions.cs" /> + <Compile Include="ModalPageTracker.cs" /> + <Compile Include="PlatformNavigation.cs" /> + <Compile Include="Renderers\TableViewRenderer.cs" /> + <Compile Include="Extensions\NSTableViewExtensions.cs" /> + <Compile Include="Renderers\TableViewDataSource.cs" /> + <Compile Include="NativeToolbarTracker.cs" /> + <Compile Include="Extensions\NSButtonExtensions.cs" /> + <Compile Include="Controls\VerticallyCenteredTextFieldCell.cs" /> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Forms.cs"> + <Link>Forms.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\DateExtensions.cs"> + <Link>Extensions\DateExtensions.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\LayoutExtensions.cs"> + <Link>Extensions\LayoutExtensions.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\ColorExtensions.cs"> + <Link>Extensions\ColorExtensions.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\UIViewExtensions.cs"> + <Link>Extensions\UIViewExtensions.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\FontExtensions.cs"> + <Link>Extensions\FontExtensions.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\FormattedStringExtensions.cs"> + <Link>Extensions\FormattedStringExtensions.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\IVisualElementRenderer.cs"> + <Link>IVisualElementRenderer.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeValueConverterService.cs"> + <Link>NativeValueConverterService.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeViewWrapper.cs"> + <Link>NativeViewWrapper.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\NativeViewWrapperRenderer.cs"> + <Link>NativeViewWrapperRenderer.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\ElementChangedEventArgs.cs"> + <Link>ElementChangedEventArgs.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\ExportRendererAttribute.cs"> + <Link>ExportRendererAttribute.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\PlatformEffect.cs"> + <Link>PlatformEffect.cs</Link> + </Compile> + <Compile Include="Controls\NSToolbarItemGroup.cs" /> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Extensions\PlatformConfigurationExtensions.cs"> + <Link>Extensions\PlatformConfigurationExtensions.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\ViewInitializedEventArgs.cs"> + <Link>ViewInitializedEventArgs.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\LabelRenderer.cs"> + <Link>Renderers\LabelRenderer.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\ResourcesProvider.cs"> + <Link>ResourcesProvider.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\VisualElementPackager.cs"> + <Link>VisualElementPackager.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\ViewRenderer.cs"> + <Link>ViewRenderer.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\VisualElementTracker.cs"> + <Link>VisualElementTracker.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\EventTracker.cs"> + <Link>EventTracker.cs</Link> + </Compile> + <Compile Include="..\Xamarin.Forms.Platform.iOS\VisualElementRenderer.cs"> + <Link>VisualElementRenderer.cs</Link> + </Compile> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Xamarin.Forms.Platform\Xamarin.Forms.Platform.csproj"> + <Project>{67F9D3A8-F71E-4428-913F-C37AE82CDB24}</Project> + <Name>Xamarin.Forms.Platform</Name> + </ProjectReference> + <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> + <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project> + <Name>Xamarin.Forms.Core</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup /> + <Import Project="$(MSBuildExtensionsPath)\Xamarin\Mac\Xamarin.Mac.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/Deserializer.cs b/Xamarin.Forms.Platform.iOS/Deserializer.cs index 86657578..dcef049c 100644 --- a/Xamarin.Forms.Platform.iOS/Deserializer.cs +++ b/Xamarin.Forms.Platform.iOS/Deserializer.cs @@ -6,7 +6,12 @@ using System.Runtime.Serialization; using System.Threading.Tasks; using System.Xml; +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { internal class Deserializer : IDeserializer { diff --git a/Xamarin.Forms.Platform.iOS/EffectUtilities.cs b/Xamarin.Forms.Platform.iOS/EffectUtilities.cs index c9523479..6b053787 100644 --- a/Xamarin.Forms.Platform.iOS/EffectUtilities.cs +++ b/Xamarin.Forms.Platform.iOS/EffectUtilities.cs @@ -1,4 +1,10 @@ + +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { internal static class EffectUtilities { diff --git a/Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs b/Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs index 560cfd1c..49cf85d9 100644 --- a/Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs +++ b/Xamarin.Forms.Platform.iOS/ElementChangedEventArgs.cs @@ -1,10 +1,16 @@ using System; +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { public class VisualElementChangedEventArgs : ElementChangedEventArgs<VisualElement> { - public VisualElementChangedEventArgs(VisualElement oldElement, VisualElement newElement) : base(oldElement, newElement) + public VisualElementChangedEventArgs(VisualElement oldElement, VisualElement newElement) + : base(oldElement, newElement) { } } diff --git a/Xamarin.Forms.Platform.iOS/EventTracker.cs b/Xamarin.Forms.Platform.iOS/EventTracker.cs index 9c6c90e9..596506a5 100644 --- a/Xamarin.Forms.Platform.iOS/EventTracker.cs +++ b/Xamarin.Forms.Platform.iOS/EventTracker.cs @@ -3,27 +3,42 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; + +#if __MOBILE__ using UIKit; +using NativeView = UIKit.UIView; +using NativeGestureRecognizer = UIKit.UIGestureRecognizer; +using NativeGestureRecognizerState = UIKit.UIGestureRecognizerState; namespace Xamarin.Forms.Platform.iOS +#else +using AppKit; +using NativeView = AppKit.NSView; +using NativeGestureRecognizer = AppKit.NSGestureRecognizer; +using NativeGestureRecognizerState = AppKit.NSGestureRecognizerState; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public class EventTracker : IDisposable { readonly NotifyCollectionChangedEventHandler _collectionChangedHandler; - readonly Dictionary<IGestureRecognizer, UIGestureRecognizer> _gestureRecognizers = new Dictionary<IGestureRecognizer, UIGestureRecognizer>(); + readonly Dictionary<IGestureRecognizer, NativeGestureRecognizer> _gestureRecognizers = new Dictionary<IGestureRecognizer, NativeGestureRecognizer>(); readonly IVisualElementRenderer _renderer; bool _disposed; - UIView _handler; + NativeView _handler; double _previousScale = 1.0; +#if __MOBILE__ UITouchEventArgs _shouldReceive; +#endif public EventTracker(IVisualElementRenderer renderer) { if (renderer == null) - throw new ArgumentNullException("renderer"); + throw new ArgumentNullException(nameof(renderer)); _collectionChangedHandler = ModelGestureRecognizersOnCollectionChanged; @@ -62,18 +77,18 @@ namespace Xamarin.Forms.Platform.iOS _handler = null; } - public void LoadEvents(UIView handler) + public void LoadEvents(NativeView handler) { if (_disposed) throw new ObjectDisposedException(null); - +#if __MOBILE__ _shouldReceive = (r, t) => t.View is IVisualElementRenderer; - +#endif _handler = handler; OnElementChanged(this, new VisualElementChangedEventArgs(null, _renderer.Element)); } - protected virtual UIGestureRecognizer GetNativeRecognizer(IGestureRecognizer recognizer) + protected virtual NativeGestureRecognizer GetNativeRecognizer(IGestureRecognizer recognizer) { if (recognizer == null) return null; @@ -84,7 +99,7 @@ namespace Xamarin.Forms.Platform.iOS var tapRecognizer = recognizer as TapGestureRecognizer; if (tapRecognizer != null) { - var uiRecognizer = CreateTapRecognizer(1, tapRecognizer.NumberOfTapsRequired, r => + var returnAction = new Action(() => { var tapGestureRecognizer = weakRecognizer.Target as TapGestureRecognizer; var eventTracker = weakEventTracker.Target as EventTracker; @@ -93,6 +108,7 @@ namespace Xamarin.Forms.Platform.iOS if (tapGestureRecognizer != null && view != null) tapGestureRecognizer.SendTapped(view); }); + var uiRecognizer = CreateTapRecognizer(tapRecognizer.NumberOfTapsRequired, returnAction); return uiRecognizer; } @@ -110,41 +126,51 @@ namespace Xamarin.Forms.Platform.iOS { var oldScale = eventTracker._previousScale; var originPoint = r.LocationInView(null); +#if __MOBILE__ originPoint = UIApplication.SharedApplication.KeyWindow.ConvertPointToView(originPoint, eventTracker._renderer.NativeView); +#else + originPoint = NSApplication.SharedApplication.KeyWindow.ContentView.ConvertPointToView(originPoint, eventTracker._renderer.NativeView); +#endif var scaledPoint = new Point(originPoint.X / view.Width, originPoint.Y / view.Height); switch (r.State) { - case UIGestureRecognizerState.Began: + case NativeGestureRecognizerState.Began: +#if __MOBILE__ if (r.NumberOfTouches < 2) return; +#endif pinchGestureRecognizer.SendPinchStarted(view, scaledPoint); startingScale = view.Scale; break; - case UIGestureRecognizerState.Changed: + case NativeGestureRecognizerState.Changed: +#if __MOBILE__ if (r.NumberOfTouches < 2 && pinchGestureRecognizer.IsPinching) { - r.State = UIGestureRecognizerState.Ended; + r.State = NativeGestureRecognizerState.Ended; pinchGestureRecognizer.SendPinchEnded(view); return; } - + var scale = r.Scale; +#else + var scale = r.Magnification; +#endif var delta = 1.0; - var dif = Math.Abs(r.Scale - oldScale) * startingScale; - if (oldScale < r.Scale) + var dif = Math.Abs(scale - oldScale) * startingScale; + if (oldScale < scale) delta = 1 + dif; - if (oldScale > r.Scale) + if (oldScale > scale) delta = 1 - dif; pinchGestureRecognizer.SendPinch(view, delta, scaledPoint); - eventTracker._previousScale = r.Scale; + eventTracker._previousScale = scale; break; - case UIGestureRecognizerState.Cancelled: - case UIGestureRecognizerState.Failed: + case NativeGestureRecognizerState.Cancelled: + case NativeGestureRecognizerState.Failed: if (pinchGestureRecognizer.IsPinching) pinchGestureRecognizer.SendPinchCanceled(view); break; - case UIGestureRecognizerState.Ended: + case NativeGestureRecognizerState.Ended: if (pinchGestureRecognizer.IsPinching) pinchGestureRecognizer.SendPinchEnded(view); eventTracker._previousScale = 1; @@ -168,33 +194,42 @@ namespace Xamarin.Forms.Platform.iOS { switch (r.State) { - case UIGestureRecognizerState.Began: + case NativeGestureRecognizerState.Began: +#if __MOBILE__ if (r.NumberOfTouches != panRecognizer.TouchPoints) return; +#endif panGestureRecognizer.SendPanStarted(view, Application.Current.PanGestureId); break; - case UIGestureRecognizerState.Changed: + case NativeGestureRecognizerState.Changed: +#if __MOBILE__ if (r.NumberOfTouches != panRecognizer.TouchPoints) { - r.State = UIGestureRecognizerState.Ended; + r.State = NativeGestureRecognizerState.Ended; panGestureRecognizer.SendPanCompleted(view, Application.Current.PanGestureId); Application.Current.PanGestureId++; return; } +#endif var translationInView = r.TranslationInView(_handler); panGestureRecognizer.SendPan(view, translationInView.X, translationInView.Y, Application.Current.PanGestureId); break; - case UIGestureRecognizerState.Cancelled: - case UIGestureRecognizerState.Failed: + case NativeGestureRecognizerState.Cancelled: + case NativeGestureRecognizerState.Failed: panGestureRecognizer.SendPanCanceled(view, Application.Current.PanGestureId); Application.Current.PanGestureId++; break; - case UIGestureRecognizerState.Ended: + case NativeGestureRecognizerState.Ended: +#if __MOBILE__ if (r.NumberOfTouches != panRecognizer.TouchPoints) { panGestureRecognizer.SendPanCompleted(view, Application.Current.PanGestureId); Application.Current.PanGestureId++; } +#else + panGestureRecognizer.SendPanCompleted(view, Application.Current.PanGestureId); + Application.Current.PanGestureId++; +#endif break; } } @@ -204,7 +239,7 @@ namespace Xamarin.Forms.Platform.iOS return null; } - +#if __MOBILE__ UIPanGestureRecognizer CreatePanRecognizer(int numTouches, Action<UIPanGestureRecognizer> action) { var result = new UIPanGestureRecognizer(action); @@ -218,14 +253,33 @@ namespace Xamarin.Forms.Platform.iOS return result; } - UITapGestureRecognizer CreateTapRecognizer(int numFingers, int numTaps, Action<UITapGestureRecognizer> action) + UITapGestureRecognizer CreateTapRecognizer(int numTaps, Action action, int numFingers = 1) { var result = new UITapGestureRecognizer(action); result.NumberOfTouchesRequired = (uint)numFingers; result.NumberOfTapsRequired = (uint)numTaps; return result; } +#else + NSPanGestureRecognizer CreatePanRecognizer(int numTouches, Action<NSPanGestureRecognizer> action) + { + var result = new NSPanGestureRecognizer(action); + return result; + } + + NSMagnificationGestureRecognizer CreatePinchRecognizer(Action<NSMagnificationGestureRecognizer> action) + { + var result = new NSMagnificationGestureRecognizer(action); + return result; + } + NSClickGestureRecognizer CreateTapRecognizer(int numTaps, Action action) + { + var result = new NSClickGestureRecognizer(action); + result.NumberOfClicksRequired = numTaps; + return result; + } +#endif void LoadRecognizers() { if (ElementGestureRecognizers == null) @@ -239,7 +293,9 @@ namespace Xamarin.Forms.Platform.iOS var nativeRecognizer = GetNativeRecognizer(recognizer); if (nativeRecognizer != null) { +#if __MOBILE__ nativeRecognizer.ShouldReceiveTouch = _shouldReceive; +#endif _handler.AddGestureRecognizer(nativeRecognizer); _gestureRecognizers[recognizer] = nativeRecognizer; diff --git a/Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs b/Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs index 50bba17f..3b3ef91b 100644 --- a/Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs +++ b/Xamarin.Forms.Platform.iOS/ExportRendererAttribute.cs @@ -1,29 +1,37 @@ using System; + +#if __MOBILE__ using UIKit; +#endif namespace Xamarin.Forms { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class ExportRendererAttribute : HandlerAttribute { +#if __MOBILE__ public ExportRendererAttribute(Type handler, Type target, UIUserInterfaceIdiom idiom) : base(handler, target) { Idiomatic = true; Idiom = idiom; } + internal UIUserInterfaceIdiom Idiom { get; } +#endif public ExportRendererAttribute(Type handler, Type target) : base(handler, target) { Idiomatic = false; } - internal UIUserInterfaceIdiom Idiom { get; } - internal bool Idiomatic { get; } public override bool ShouldRegister() { +#if __MOBILE__ return !Idiomatic || Idiom == UIDevice.CurrentDevice.UserInterfaceIdiom; +#else + return !Idiomatic; +#endif } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs index bb4342ab..2b8b9101 100644 --- a/Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Extensions/ColorExtensions.cs @@ -1,16 +1,27 @@ using System; using CoreGraphics; -using UIKit; using PointF = CoreGraphics.CGPoint; using RectangleF = CoreGraphics.CGRect; using SizeF = CoreGraphics.CGSize; - +#if __MOBILE__ +using UIKit; namespace Xamarin.Forms.Platform.iOS +#else +using AppKit; +using UIColor = AppKit.NSColor; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public static class ColorExtensions { +#if __MOBILE__ internal static readonly UIColor Black = UIColor.Black; internal static readonly UIColor SeventyPercentGrey = new UIColor(0.7f, 0.7f, 0.7f, 1); +#else + internal static readonly NSColor Black = NSColor.Black; + internal static readonly NSColor SeventyPercentGrey = NSColor.FromRgba(0.7f, 0.7f, 0.7f, 1); +#endif public static CGColor ToCGColor(this Color color) { @@ -23,10 +34,15 @@ namespace Xamarin.Forms.Platform.iOS nfloat green; nfloat blue; nfloat alpha; +#if __MOBILE__ color.GetRGBA(out red, out green, out blue, out alpha); +#else + color.GetRgba(out red, out green, out blue, out alpha); +#endif return new Color(red, green, blue, alpha); } +#if __MOBILE__ public static UIColor ToUIColor(this Color color) { return new UIColor((float)color.R, (float)color.G, (float)color.B, (float)color.A); @@ -47,6 +63,28 @@ namespace Xamarin.Forms.Platform.iOS return color.ToUIColor(); } +#else + public static NSColor ToNSColor(this Color color) + { + return NSColor.FromRgba((float)color.R, (float)color.G, (float)color.B, (float)color.A); + } + + public static NSColor ToNSColor(this Color color, Color defaultColor) + { + if (color.IsDefault) + return defaultColor.ToNSColor(); + + return color.ToNSColor(); + } + + public static NSColor ToNSColor(this Color color, NSColor defaultColor) + { + if (color.IsDefault) + return defaultColor; + + return color.ToNSColor(); + } +#endif } public static class PointExtensions @@ -55,6 +93,11 @@ namespace Xamarin.Forms.Platform.iOS { return new Point(point.X, point.Y); } + + public static PointF ToPointF(this Point point) + { + return new PointF(point.X, point.Y); + } } public static class SizeExtensions diff --git a/Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs index bb0dfff9..73ca347c 100644 --- a/Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Extensions/DateExtensions.cs @@ -1,7 +1,12 @@ using System; using Foundation; +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { public static class DateExtensions { diff --git a/Xamarin.Forms.Platform.iOS/Extensions/LayoutExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/LayoutExtensions.cs index 823c3f30..d65caf12 100644 --- a/Xamarin.Forms.Platform.iOS/Extensions/LayoutExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Extensions/LayoutExtensions.cs @@ -1,10 +1,19 @@ using System.Collections.Generic; using CoreGraphics; +#if __MOBILE__ using UIKit; - +using Xamarin.Forms.Platform.iOS; namespace Xamarin.Forms.Platform.iOS +#else +using AppKit; +using UIView = AppKit.NSView; +using Xamarin.Forms.Platform.MacOS; + +namespace Xamarin.Forms.Platform.MacOS +#endif { - public delegate SizeRequest? GetDesiredSizeDelegate(NativeViewWrapperRenderer renderer, double widthConstraint, double heightConstraint); + public delegate SizeRequest? GetDesiredSizeDelegate( + NativeViewWrapperRenderer renderer, double widthConstraint, double heightConstraint); public delegate CGSize? SizeThatFitsDelegate(CGSize size); @@ -12,13 +21,15 @@ namespace Xamarin.Forms.Platform.iOS public static class LayoutExtensions { - public static void Add(this IList<View> children, UIView view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, SizeThatFitsDelegate sizeThatFitsDelegate = null, - LayoutSubviewsDelegate layoutSubViews = null) + public static void Add(this IList<View> children, UIView view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, + SizeThatFitsDelegate sizeThatFitsDelegate = null, + LayoutSubviewsDelegate layoutSubViews = null) { children.Add(view.ToView(getDesiredSizeDelegate, sizeThatFitsDelegate, layoutSubViews)); } - public static View ToView(this UIView view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, SizeThatFitsDelegate sizeThatFitsDelegate = null, LayoutSubviewsDelegate layoutSubViews = null) + public static View ToView(this UIView view, GetDesiredSizeDelegate getDesiredSizeDelegate = null, + SizeThatFitsDelegate sizeThatFitsDelegate = null, LayoutSubviewsDelegate layoutSubViews = null) { return new NativeViewWrapper(view, getDesiredSizeDelegate, sizeThatFitsDelegate, layoutSubViews); } diff --git a/Xamarin.Forms.Platform.iOS/Extensions/PlatformConfigurationExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/PlatformConfigurationExtensions.cs index f61837f2..f2932e3a 100644 --- a/Xamarin.Forms.Platform.iOS/Extensions/PlatformConfigurationExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Extensions/PlatformConfigurationExtensions.cs @@ -1,11 +1,18 @@ +#if __MOBILE__ +using CurrentPlatform = Xamarin.Forms.PlatformConfiguration.iOS; namespace Xamarin.Forms.Platform.iOS +#else +using CurrentPlatform = Xamarin.Forms.PlatformConfiguration.macOS; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public static class PlatformConfigurationExtensions { - public static IPlatformElementConfiguration<PlatformConfiguration.iOS, T> OnThisPlatform<T>(this T element) + public static IPlatformElementConfiguration<CurrentPlatform, T> OnThisPlatform<T>(this T element) where T : Element, IElementConfiguration<T> { - return (element).On<PlatformConfiguration.iOS>(); + return (element).On<CurrentPlatform>(); } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs index 4c3eed98..8248af11 100644 --- a/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs @@ -2,10 +2,15 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; -using UIKit; using static System.String; - +#if __MOBILE__ +using UIKit; namespace Xamarin.Forms.Platform.iOS +#else +using UIView = AppKit.NSView; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public static class UIViewExtensions { @@ -16,15 +21,28 @@ namespace Xamarin.Forms.Platform.iOS return self.Subviews.Concat(self.Subviews.SelectMany(s => s.Descendants())); } - public static SizeRequest GetSizeRequest(this UIView self, double widthConstraint, double heightConstraint, double minimumWidth = -1, double minimumHeight = -1) + public static SizeRequest GetSizeRequest(this UIView self, double widthConstraint, double heightConstraint, + double minimumWidth = -1, double minimumHeight = -1) { - var s = self.SizeThatFits(new SizeF((float)widthConstraint, (float)heightConstraint)); - var request = new Size(s.Width == float.PositiveInfinity ? double.PositiveInfinity : s.Width, s.Height == float.PositiveInfinity ? double.PositiveInfinity : s.Height); - var minimum = new Size(minimumWidth < 0 ? request.Width : minimumWidth, minimumHeight < 0 ? request.Height : minimumHeight); + CoreGraphics.CGSize s; +#if __MOBILE__ + s = self.SizeThatFits(new SizeF((float)widthConstraint, (float)heightConstraint)); +#else + var control = self as AppKit.NSControl; + if (control != null) + s = control.SizeThatFits(new CoreGraphics.CGSize(widthConstraint, heightConstraint)); + else + s = self.FittingSize; +#endif + var request = new Size(s.Width == float.PositiveInfinity ? double.PositiveInfinity : s.Width, + s.Height == float.PositiveInfinity ? double.PositiveInfinity : s.Height); + var minimum = new Size(minimumWidth < 0 ? request.Width : minimumWidth, + minimumHeight < 0 ? request.Height : minimumHeight); return new SizeRequest(request, minimum); } - public static void SetBinding(this UIView view, string propertyName, BindingBase bindingBase, string updateSourceEventName = null) + public static void SetBinding(this UIView view, string propertyName, BindingBase bindingBase, + string updateSourceEventName = null) { var binding = bindingBase as Binding; //This will allow setting bindings from Xaml by reusing the MarkupExtension @@ -51,6 +69,7 @@ namespace Xamarin.Forms.Platform.iOS view.AddObserver(nativePropertyListener, key, Foundation.NSKeyValueObservingOptions.New, IntPtr.Zero); } } +#if __MOBILE__ catch (Foundation.MonoTouchException ex) { nativePropertyListener = null; @@ -61,7 +80,12 @@ namespace Xamarin.Forms.Platform.iOS } throw ex; } - +#else + catch (Exception ex) + { + throw ex; + } +#endif } NativeBindingHelpers.SetBinding(view, propertyName, bindingBase, nativePropertyListener); @@ -77,7 +101,8 @@ namespace Xamarin.Forms.Platform.iOS NativeBindingHelpers.SetValue(target, targetProperty, value); } - public static void SetBindingContext(this UIView target, object bindingContext, Func<UIView, IEnumerable<UIView>> getChildren = null) + public static void SetBindingContext(this UIView target, object bindingContext, + Func<UIView, IEnumerable<UIView>> getChildren = null) { NativeBindingHelpers.SetBindingContext(target, bindingContext, getChildren); } @@ -107,6 +132,7 @@ namespace Xamarin.Forms.Platform.iOS return null; } +#if __MOBILE__ internal static UIView FindFirstResponder(this UIView view) { if (view.IsFirstResponder) @@ -121,5 +147,6 @@ namespace Xamarin.Forms.Platform.iOS return null; } +#endif } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/Forms.cs b/Xamarin.Forms.Platform.iOS/Forms.cs index 2c8fc6f7..d6e7e07f 100644 --- a/Xamarin.Forms.Platform.iOS/Forms.cs +++ b/Xamarin.Forms.Platform.iOS/Forms.cs @@ -11,11 +11,18 @@ using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; -using CoreFoundation; +using Xamarin.Forms.Internals; using Foundation; +#if __MOBILE__ using UIKit; -using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.iOS; +using TNativeView = UIKit.UIView; +#else +using AppKit; +using Xamarin.Forms.Platform.MacOS; +using TNativeView = AppKit.NSView; + +#endif namespace Xamarin.Forms { @@ -24,7 +31,11 @@ namespace Xamarin.Forms //Preserve GetCallingAssembly static readonly bool nevertrue = false; + public static bool IsInitialized { get; private set; } + +#if __MOBILE__ static bool? s_isiOS9OrNewer; +#endif static Forms() { @@ -32,8 +43,7 @@ namespace Xamarin.Forms Assembly.GetCallingAssembly(); } - public static bool IsInitialized { get; private set; } - +#if __MOBILE__ internal static bool IsiOS9OrNewer { get @@ -43,6 +53,7 @@ namespace Xamarin.Forms return s_isiOS9OrNewer.Value; } } +#endif public static void Init() { @@ -53,19 +64,22 @@ namespace Xamarin.Forms Log.Listeners.Add(new DelegateLogListener((c, m) => Trace.WriteLine(m, c))); +#if __MOBILE__ + Device.Idiom = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad ? TargetIdiom.Tablet : TargetIdiom.Phone; +#else + Device.Idiom = TargetIdiom.Desktop; +#endif Device.PlatformServices = new IOSPlatformServices(); Device.Info = new IOSDeviceInfo(); - Registrar.RegisterAll(new[] { typeof(ExportRendererAttribute), typeof(ExportCellAttribute), typeof(ExportImageSourceHandlerAttribute) }); - - Device.Idiom = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad ? TargetIdiom.Tablet : TargetIdiom.Phone; - + Registrar.RegisterAll(new[] + { typeof(ExportRendererAttribute), typeof(ExportCellAttribute), typeof(ExportImageSourceHandlerAttribute) }); ExpressionSearch.Default = new iOSExpressionSearch(); } public static event EventHandler<ViewInitializedEventArgs> ViewInitialized; - internal static void SendViewInitialized(this VisualElement self, UIView nativeView) + internal static void SendViewInitialized(this VisualElement self, TNativeView nativeView) { ViewInitialized?.Invoke(self, new ViewInitializedEventArgs { View = self, NativeView = nativeView }); } @@ -99,16 +113,22 @@ namespace Xamarin.Forms internal class IOSDeviceInfo : DeviceInfo { +#if __MOBILE__ readonly NSObject _notification; +#endif readonly Size _scaledScreenSize; readonly double _scalingFactor; public IOSDeviceInfo() { +#if __MOBILE__ _notification = UIDevice.Notifications.ObserveOrientationDidChange((sender, args) => CurrentOrientation = UIDevice.CurrentDevice.Orientation.ToDeviceOrientation()); - _scalingFactor = UIScreen.MainScreen.Scale; _scaledScreenSize = new Size(UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height); +#else + _scalingFactor = NSScreen.MainScreen.BackingScaleFactor; + _scaledScreenSize = new Size(NSScreen.MainScreen.Frame.Width, NSScreen.MainScreen.Frame.Height); +#endif PixelScreenSize = new Size(_scaledScreenSize.Width * _scalingFactor, _scaledScreenSize.Height * _scalingFactor); } @@ -120,7 +140,9 @@ namespace Xamarin.Forms protected override void Dispose(bool disposing) { +#if __MOBILE__ _notification.Dispose(); +#endif base.Dispose(disposing); } } @@ -198,11 +220,19 @@ namespace Xamarin.Forms public bool IsInvokeRequired => !NSThread.IsMain; +#if __MOBILE__ public string RuntimePlatform => Device.iOS; +#else + public string RuntimePlatform => Device.macOS; +#endif public void OpenUriAction(Uri uri) { +#if __MOBILE__ UIApplication.SharedApplication.OpenUrl(new NSUrl(uri.AbsoluteUri)); +#else + NSWorkspace.SharedWorkspace.OpenUrl(new NSUrl(uri.AbsoluteUri)); +#endif } public void StartTimer(TimeSpan interval, Func<bool> callback) @@ -217,11 +247,11 @@ namespace Xamarin.Forms HttpClient GetHttpClient() { - var proxy = CFNetwork.GetSystemProxySettings(); + var proxy = CoreFoundation.CFNetwork.GetSystemProxySettings(); var handler = new HttpClientHandler(); if (!string.IsNullOrEmpty(proxy.HTTPProxy)) { - handler.Proxy = CFNetwork.GetDefaultProxy(); + handler.Proxy = CoreFoundation.CFNetwork.GetDefaultProxy(); handler.UseProxy = true; } return new HttpClient(handler); @@ -272,7 +302,8 @@ namespace Xamarin.Forms public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share) { - Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access, (System.IO.FileShare)share); + Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access, + (System.IO.FileShare)share); return Task.FromResult(stream); } } diff --git a/Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs b/Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs index 9a2bf580..ffb2de29 100644 --- a/Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/IVisualElementRenderer.cs @@ -1,15 +1,24 @@ using System; +#if __MOBILE__ using UIKit; +using NativeView = UIKit.UIView; +using NativeViewController = UIKit.UIViewController; namespace Xamarin.Forms.Platform.iOS +#else +using NativeView = AppKit.NSView; +using NativeViewController = AppKit.NSViewController; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public interface IVisualElementRenderer : IDisposable, IRegisterable { VisualElement Element { get; } - UIView NativeView { get; } + NativeView NativeView { get; } - UIViewController ViewController { get; } + NativeViewController ViewController { get; } event EventHandler<VisualElementChangedEventArgs> ElementChanged; diff --git a/Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs b/Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs index 9e20e045..acdd6523 100644 --- a/Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs +++ b/Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs @@ -1,16 +1,23 @@ using System; +#if __MOBILE__ using UIKit; - [assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.iOS.NativeValueConverterService))] - namespace Xamarin.Forms.Platform.iOS +#else +using UIView = AppKit.NSView; + +[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.MacOS.NativeValueConverterService))] + +namespace Xamarin.Forms.Platform.MacOS +#endif { class NativeValueConverterService : Xaml.INativeValueConverterService { public bool ConvertTo(object value, Type toType, out object nativeValue) { nativeValue = null; - if (typeof(UIView).IsInstanceOfType(value) && toType.IsAssignableFrom(typeof(View))) { + if (typeof(UIView).IsInstanceOfType(value) && toType.IsAssignableFrom(typeof(View))) + { nativeValue = ((UIView)value).ToView(); return true; } diff --git a/Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs b/Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs index 4e6601ef..e819519a 100644 --- a/Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs +++ b/Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs @@ -2,7 +2,12 @@ using System; using System.ComponentModel; using Foundation; +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { class NativeViewPropertyListener : NSObject, INotifyPropertyChanged { @@ -23,4 +28,4 @@ namespace Xamarin.Forms.Platform.iOS base.ObserveValue(keyPath, ofObject, change, context); } } -} +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs b/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs index 31cb186d..165bc0f9 100644 --- a/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs +++ b/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs @@ -1,10 +1,16 @@ -using UIKit; - +#if __MOBILE__ +using UIKit; namespace Xamarin.Forms.Platform.iOS +#else +using UIView = AppKit.NSView; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public class NativeViewWrapper : View { - public NativeViewWrapper(UIView nativeView, GetDesiredSizeDelegate getDesiredSizeDelegate = null, SizeThatFitsDelegate sizeThatFitsDelegate = null, LayoutSubviewsDelegate layoutSubViews = null) + public NativeViewWrapper(UIView nativeView, GetDesiredSizeDelegate getDesiredSizeDelegate = null, + SizeThatFitsDelegate sizeThatFitsDelegate = null, LayoutSubviewsDelegate layoutSubViews = null) { GetDesiredSizeDelegate = getDesiredSizeDelegate; SizeThatFitsDelegate = sizeThatFitsDelegate; diff --git a/Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs b/Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs index 4d1252ee..cde9fe81 100644 --- a/Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/NativeViewWrapperRenderer.cs @@ -1,8 +1,13 @@ using CoreGraphics; -using UIKit; using Xamarin.Forms.Internals; - +#if __MOBILE__ +using UIKit; namespace Xamarin.Forms.Platform.iOS +#else +using UIView = AppKit.NSView; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public class NativeViewWrapperRenderer : ViewRenderer<NativeViewWrapper, UIView> { @@ -19,6 +24,7 @@ namespace Xamarin.Forms.Platform.iOS return result ?? base.GetDesiredSize(widthConstraint, heightConstraint); } +#if __MOBILE__ public override void LayoutSubviews() { if (Element?.LayoutSubViews == null) @@ -50,6 +56,26 @@ namespace Xamarin.Forms.Platform.iOS // if it returns null, fall back to the default implementation return result ?? base.SizeThatFits(size); } +#else + public override void Layout() + { + if (Element?.LayoutSubViews == null) + { + ((IVisualElementController)Element)?.InvalidateMeasure(InvalidationTrigger.MeasureChanged); + base.Layout(); + return; + } + + // The user has specified a different implementation of LayoutSubviews + var handled = Element.LayoutSubViews(); + + if (!handled) + { + // If the delegate wasn't able to handle the request, fall back to the default implementation + base.Layout(); + } + } +#endif protected override void OnElementChanged(ElementChangedEventArgs<NativeViewWrapper> e) { diff --git a/Xamarin.Forms.Platform.iOS/PlatformEffect.cs b/Xamarin.Forms.Platform.iOS/PlatformEffect.cs index 8fb4f975..6a1ca494 100644 --- a/Xamarin.Forms.Platform.iOS/PlatformEffect.cs +++ b/Xamarin.Forms.Platform.iOS/PlatformEffect.cs @@ -1,6 +1,11 @@ -using UIKit; - +#if __MOBILE__ +using UIKit; namespace Xamarin.Forms.Platform.iOS +#else +using UIView = AppKit.NSView; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public abstract class PlatformEffect : PlatformEffect<UIView, UIView> { diff --git a/Xamarin.Forms.Platform.iOS/RendererPool.cs b/Xamarin.Forms.Platform.iOS/RendererPool.cs index babfa7bc..38fe3c02 100644 --- a/Xamarin.Forms.Platform.iOS/RendererPool.cs +++ b/Xamarin.Forms.Platform.iOS/RendererPool.cs @@ -1,11 +1,17 @@ using System; using System.Collections.Generic; +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { public sealed class RendererPool { - readonly Dictionary<Type, Stack<IVisualElementRenderer>> _freeRenderers = new Dictionary<Type, Stack<IVisualElementRenderer>>(); + readonly Dictionary<Type, Stack<IVisualElementRenderer>> _freeRenderers = + new Dictionary<Type, Stack<IVisualElementRenderer>>(); readonly VisualElement _oldElement; diff --git a/Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs b/Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs index cbc2e625..ba8acbe5 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/FontExtensions.cs @@ -1,15 +1,26 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Xamarin.Forms; +#if __MOBILE__ using UIKit; - namespace Xamarin.Forms.Platform.iOS +#else +using AppKit; +using UIFont = AppKit.NSFont; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public static class FontExtensions { static readonly Dictionary<ToUIFontKey, UIFont> ToUiFont = new Dictionary<ToUIFontKey, UIFont>(); - +#if __MOBILE__ public static UIFont ToUIFont(this Font self) + +#else + public static UIFont ToNSFont(this Font self) +#endif { var size = (float)self.FontSize; if (self.UseNamedSize) @@ -41,6 +52,7 @@ namespace Xamarin.Forms.Platform.iOS { try { +#if __MOBILE__ if (UIFont.FamilyNames.Contains(self.FontFamily)) { var descriptor = new UIFontDescriptor().CreateWithFamily(self.FontFamily); @@ -59,6 +71,25 @@ namespace Xamarin.Forms.Platform.iOS } return UIFont.FromName(self.FontFamily, size); + +#else + + var descriptor = new NSFontDescriptor().FontDescriptorWithFamily(self.FontFamily); + + if (bold || italic) + { + var traits = (NSFontSymbolicTraits)0; + if (bold) + traits = traits | NSFontSymbolicTraits.BoldTrait; + if (italic) + traits = traits | NSFontSymbolicTraits.ItalicTrait; + + descriptor = descriptor.FontDescriptorWithSymbolicTraits(traits); + return NSFont.FromDescription(descriptor, size); + } + + return NSFont.FromFontName(self.FontFamily, size); +#endif } catch { @@ -68,28 +99,54 @@ namespace Xamarin.Forms.Platform.iOS if (bold && italic) { var defaultFont = UIFont.SystemFontOfSize(size); +#if __MOBILE__ var descriptor = defaultFont.FontDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold | UIFontDescriptorSymbolicTraits.Italic); return UIFont.FromDescriptor(descriptor, 0); } - if (bold) - return UIFont.BoldSystemFontOfSize(size); if (italic) return UIFont.ItalicSystemFontOfSize(size); +#else + var descriptor = defaultFont.FontDescriptor.FontDescriptorWithSymbolicTraits( + NSFontSymbolicTraits.BoldTrait | + NSFontSymbolicTraits.ItalicTrait); + + return NSFont.FromDescription(descriptor, 0); + } + if (italic) + { + Debug.WriteLine("Italic font requested, passing regular one"); + return NSFont.UserFontOfSize(size); + } +#endif + + if (bold) + return UIFont.BoldSystemFontOfSize(size); + return UIFont.SystemFontOfSize(size); } internal static bool IsDefault(this Span self) { - return self.FontFamily == null && self.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Label), true) && self.FontAttributes == FontAttributes.None; + return self.FontFamily == null && self.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Label), true) && + self.FontAttributes == FontAttributes.None; } +#if __MOBILE__ internal static UIFont ToUIFont(this Label label) +#else + internal static UIFont ToNSFont(this Label label) +#endif { var values = label.GetValues(Label.FontFamilyProperty, Label.FontSizeProperty, Label.FontAttributesProperty); - return ToUIFont((string)values[0], (float)(double)values[1], (FontAttributes)values[2]) ?? UIFont.SystemFontOfSize(UIFont.LabelFontSize); + return ToUIFont((string)values[0], (float)(double)values[1], (FontAttributes)values[2]) ?? + UIFont.SystemFontOfSize(UIFont.LabelFontSize); } +#if __MOBILE__ internal static UIFont ToUIFont(this IFontElement element) +#else + internal static NSFont ToNSFont(this IFontElement element) +#endif { return ToUIFont(element.FontFamily, (float)element.FontSize, element.FontAttributes); } @@ -104,6 +161,7 @@ namespace Xamarin.Forms.Platform.iOS try { UIFont result; +#if __MOBILE__ if (UIFont.FamilyNames.Contains(family)) { var descriptor = new UIFontDescriptor().CreateWithFamily(family); @@ -124,6 +182,26 @@ namespace Xamarin.Forms.Platform.iOS } result = UIFont.FromName(family, size); +#else + + var descriptor = new NSFontDescriptor().FontDescriptorWithFamily(family); + + if (bold || italic) + { + var traits = (NSFontSymbolicTraits)0; + if (bold) + traits = traits | NSFontSymbolicTraits.BoldTrait; + if (italic) + traits = traits | NSFontSymbolicTraits.ItalicTrait; + + descriptor = descriptor.FontDescriptorWithSymbolicTraits(traits); + result = NSFont.FromDescription(descriptor, size); + if (result != null) + return result; + } + + result = NSFont.FromFontName(family, size); +#endif if (result != null) return result; } @@ -137,13 +215,27 @@ namespace Xamarin.Forms.Platform.iOS { var defaultFont = UIFont.SystemFontOfSize(size); +#if __MOBILE__ var descriptor = defaultFont.FontDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold | UIFontDescriptorSymbolicTraits.Italic); return UIFont.FromDescriptor(descriptor, 0); } - if (bold) - return UIFont.BoldSystemFontOfSize(size); if (italic) return UIFont.ItalicSystemFontOfSize(size); +#else + var descriptor = defaultFont.FontDescriptor.FontDescriptorWithSymbolicTraits( + NSFontSymbolicTraits.BoldTrait | + NSFontSymbolicTraits.ItalicTrait); + + return NSFont.FromDescription(descriptor, 0); + } + if (italic) + { + Debug.WriteLine("Italic font requested, passing regular one"); + return NSFont.UserFontOfSize(size); + } +#endif + if (bold) + return UIFont.BoldSystemFontOfSize(size); return UIFont.SystemFontOfSize(size); } diff --git a/Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs b/Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs index dce86b98..825a20bd 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs @@ -1,7 +1,13 @@ using Foundation; +#if __MOBILE__ using UIKit; - namespace Xamarin.Forms.Platform.iOS +#else +using AppKit; +using UIColor = AppKit.NSColor; + +namespace Xamarin.Forms.Platform.MacOS +#endif { public static class FormattedStringExtensions { @@ -19,10 +25,16 @@ namespace Xamarin.Forms.Platform.iOS if (fgcolor.IsDefault) fgcolor = Color.Black; // as defined by apple docs +#if __MOBILE__ return new NSAttributedString(span.Text, font == Font.Default ? null : font.ToUIFont(), fgcolor.ToUIColor(), span.BackgroundColor.ToUIColor()); +#else + return new NSAttributedString(span.Text, font == Font.Default ? null : font.ToNSFont(), fgcolor.ToNSColor(), + span.BackgroundColor.ToNSColor()); +#endif } - public static NSAttributedString ToAttributed(this FormattedString formattedString, Font defaultFont, Color defaultForegroundColor) + public static NSAttributedString ToAttributed(this FormattedString formattedString, Font defaultFont, + Color defaultForegroundColor) { if (formattedString == null) return null; @@ -43,6 +55,7 @@ namespace Xamarin.Forms.Platform.iOS if (span == null) return null; +#if __MOBILE__ UIFont targetFont; if (span.IsDefault()) targetFont = ((IFontElement)owner).ToUIFont(); @@ -56,9 +69,25 @@ namespace Xamarin.Forms.Platform.iOS fgcolor = Color.Black; // as defined by apple docs return new NSAttributedString(span.Text, targetFont, fgcolor.ToUIColor(), span.BackgroundColor.ToUIColor()); +#else + NSFont targetFont; + if (span.IsDefault()) + targetFont = ((IFontElement)owner).ToNSFont(); + else + targetFont = span.ToNSFont(); + + var fgcolor = span.ForegroundColor; + if (fgcolor.IsDefault) + fgcolor = defaultForegroundColor; + if (fgcolor.IsDefault) + fgcolor = Color.Black; // as defined by apple docs + + return new NSAttributedString(span.Text, targetFont, fgcolor.ToNSColor(), span.BackgroundColor.ToNSColor()); +#endif } - internal static NSAttributedString ToAttributed(this FormattedString formattedString, Element owner, Color defaultForegroundColor) + internal static NSAttributedString ToAttributed(this FormattedString formattedString, Element owner, + Color defaultForegroundColor) { if (formattedString == null) return null; diff --git a/Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs index e4967c15..445abf3e 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/LabelRenderer.cs @@ -1,12 +1,21 @@ using System; using System.ComponentModel; -using UIKit; using RectangleF = CoreGraphics.CGRect; using SizeF = CoreGraphics.CGSize; +#if __MOBILE__ +using UIKit; +using NativeLabel = UIKit.UILabel; + namespace Xamarin.Forms.Platform.iOS +#else +using AppKit; +using NativeLabel = AppKit.NSTextField; + +namespace Xamarin.Forms.Platform.MacOS +#endif { - public class LabelRenderer : ViewRenderer<Label, UILabel> + public class LabelRenderer : ViewRenderer<Label, NativeLabel> { SizeRequest _perfectSize; @@ -51,9 +60,16 @@ namespace Xamarin.Forms.Platform.iOS return result; } +#if __MOBILE__ public override void LayoutSubviews() { base.LayoutSubviews(); +#else + public override void Layout() + { + base.Layout(); +#endif + if (Control == null) return; @@ -85,7 +101,12 @@ namespace Xamarin.Forms.Platform.iOS { if (Control == null) { - SetNativeControl(new UILabel(RectangleF.Empty) { BackgroundColor = UIColor.Clear }); + SetNativeControl(new NativeLabel(RectangleF.Empty)); +#if !__MOBILE__ + Control.Editable = false; + Control.Bezeled = false; + Control.DrawsBackground = false; +#endif } UpdateText(); @@ -103,7 +124,7 @@ namespace Xamarin.Forms.Platform.iOS if (e.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName) UpdateAlignment(); else if (e.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName) - LayoutSubviews(); + UpdateLayout(); else if (e.PropertyName == Label.TextColorProperty.PropertyName) UpdateText(); else if (e.PropertyName == Label.FontProperty.PropertyName) @@ -118,21 +139,33 @@ namespace Xamarin.Forms.Platform.iOS protected override void SetBackgroundColor(Color color) { +#if __MOBILE__ if (color == Color.Default) BackgroundColor = UIColor.Clear; else BackgroundColor = color.ToUIColor(); +#else + if (color == Color.Default) + Layer.BackgroundColor = NSColor.Clear.CGColor; + else + Layer.BackgroundColor = color.ToCGColor(); +#endif + } void UpdateAlignment() { +#if __MOBILE__ Control.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(); +#else + Control.Alignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(); +#endif } void UpdateLineBreakMode() { _perfectSizeValid = false; - +#if __MOBILE__ switch (Element.LineBreakMode) { case LineBreakMode.NoWrap: @@ -160,6 +193,35 @@ namespace Xamarin.Forms.Platform.iOS Control.Lines = 1; break; } +#else + switch (Element.LineBreakMode) + { + case LineBreakMode.NoWrap: + Control.LineBreakMode = NSLineBreakMode.Clipping; + Control.MaximumNumberOfLines = 1; + break; + case LineBreakMode.WordWrap: + Control.LineBreakMode = NSLineBreakMode.ByWordWrapping; + Control.MaximumNumberOfLines = 0; + break; + case LineBreakMode.CharacterWrap: + Control.LineBreakMode = NSLineBreakMode.CharWrapping; + Control.MaximumNumberOfLines = 0; + break; + case LineBreakMode.HeadTruncation: + Control.LineBreakMode = NSLineBreakMode.TruncatingHead; + Control.MaximumNumberOfLines = 1; + break; + case LineBreakMode.MiddleTruncation: + Control.LineBreakMode = NSLineBreakMode.TruncatingMiddle; + Control.MaximumNumberOfLines = 1; + break; + case LineBreakMode.TailTruncation: + Control.LineBreakMode = NSLineBreakMode.TruncatingTail; + Control.MaximumNumberOfLines = 1; + break; + } +#endif } void UpdateText() @@ -167,9 +229,12 @@ namespace Xamarin.Forms.Platform.iOS _perfectSizeValid = false; var values = Element.GetValues(Label.FormattedTextProperty, Label.TextProperty, Label.TextColorProperty); - var formatted = (FormattedString)values[0]; + var formatted = values[0] as FormattedString; if (formatted != null) + { +#if __MOBILE__ Control.AttributedText = formatted.ToAttributed(Element, (Color)values[2]); + } else { Control.Text = (string)values[1]; @@ -177,8 +242,28 @@ namespace Xamarin.Forms.Platform.iOS Control.Font = Element.ToUIFont(); Control.TextColor = ((Color)values[2]).ToUIColor(ColorExtensions.Black); } +#else + Control.AttributedStringValue = formatted.ToAttributed(Element, (Color)values[2]); + } + else + { + Control.StringValue = (string)values[1] ?? ""; + // default value of color documented to be black in iOS docs + Control.Font = Element.ToNSFont(); + Control.TextColor = ((Color)values[2]).ToNSColor(ColorExtensions.Black); + } +#endif + + UpdateLayout(); + } + void UpdateLayout() + { +#if __MOBILE__ LayoutSubviews(); +#else + Layout(); +#endif } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/ResourcesProvider.cs b/Xamarin.Forms.Platform.iOS/ResourcesProvider.cs index 3e5655c9..f02655a0 100644 --- a/Xamarin.Forms.Platform.iOS/ResourcesProvider.cs +++ b/Xamarin.Forms.Platform.iOS/ResourcesProvider.cs @@ -1,6 +1,11 @@ +#if __MOBILE__ using UIKit; namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { internal class ResourcesProvider : ISystemResourcesProvider { @@ -8,7 +13,9 @@ namespace Xamarin.Forms.Platform.iOS public ResourcesProvider() { +#if __MOBILE__ UIApplication.Notifications.ObserveContentSizeCategoryChanged((sender, args) => UpdateStyles()); +#endif } public IResourceDictionary GetSystemResources() @@ -19,6 +26,7 @@ namespace Xamarin.Forms.Platform.iOS return _dictionary; } +#if __MOBILE__ Style GenerateListItemDetailTextStyle() { var font = new UITableViewCell(UITableViewCellStyle.Subtitle, "Foobar").DetailTextLabel.Font; @@ -44,7 +52,7 @@ namespace Xamarin.Forms.Platform.iOS void UpdateStyles() { - + _dictionary[Device.Styles.TitleStyleKey] = GenerateStyle(UIFont.PreferredHeadline); _dictionary[Device.Styles.SubtitleStyleKey] = GenerateStyle(UIFont.PreferredSubheadline); _dictionary[Device.Styles.BodyStyleKey] = GenerateStyle(UIFont.PreferredBody); @@ -53,5 +61,11 @@ namespace Xamarin.Forms.Platform.iOS _dictionary[Device.Styles.ListItemTextStyleKey] = GenerateListItemTextStyle(); _dictionary[Device.Styles.ListItemDetailTextStyleKey] = GenerateListItemDetailTextStyle(); } +#else + void UpdateStyles() + { + } +#endif + } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/ViewInitializedEventArgs.cs b/Xamarin.Forms.Platform.iOS/ViewInitializedEventArgs.cs index 73041163..9ee0da88 100644 --- a/Xamarin.Forms.Platform.iOS/ViewInitializedEventArgs.cs +++ b/Xamarin.Forms.Platform.iOS/ViewInitializedEventArgs.cs @@ -1,11 +1,16 @@ using System; -using UIKit; +#if __MOBILE__ +using TNativeView = UIKit.UIView; +#else +using TNativeView = AppKit.NSView; + +#endif namespace Xamarin.Forms { public class ViewInitializedEventArgs : EventArgs { - public UIView NativeView { get; internal set; } + public TNativeView NativeView { get; internal set; } public VisualElement View { get; internal set; } } diff --git a/Xamarin.Forms.Platform.iOS/ViewRenderer.cs b/Xamarin.Forms.Platform.iOS/ViewRenderer.cs index 6ecc307b..01f8e06a 100644 --- a/Xamarin.Forms.Platform.iOS/ViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/ViewRenderer.cs @@ -1,21 +1,34 @@ using System; using System.ComponentModel; -using UIKit; + using RectangleF = CoreGraphics.CGRect; using SizeF = CoreGraphics.CGSize; +#if __MOBILE__ +using UIKit; +using NativeView = UIKit.UIView; +using NativeColor = UIKit.UIColor; +using NativeControl = UIKit.UIControl; + namespace Xamarin.Forms.Platform.iOS +#else +using NativeView = AppKit.NSView; +using NativeColor = CoreGraphics.CGColor; +using NativeControl = AppKit.NSControl; + +namespace Xamarin.Forms.Platform.MacOS +#endif { - public abstract class ViewRenderer : ViewRenderer<View, UIView> + public abstract class ViewRenderer : ViewRenderer<View, NativeView> { } - public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView> where TView : View where TNativeView : UIView + public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView> where TView : View where TNativeView : NativeView { - UIColor _defaultColor; + NativeColor _defaultColor; public TNativeView Control { get; private set; } - +#if __MOBILE__ public override void LayoutSubviews() { base.LayoutSubviews(); @@ -30,6 +43,19 @@ namespace Xamarin.Forms.Platform.iOS return Control.SizeThatFits(size); } +#else + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + return (Control ?? NativeView).GetSizeRequest(widthConstraint, heightConstraint); + } + + public override void Layout() + { + if (Control != null) + Control.Frame = new RectangleF(0, 0, (nfloat)Element.Width, (nfloat)Element.Height); + base.Layout(); + } +#endif /// <summary> /// Determines whether the native control is disposed of when this renderer is disposed @@ -100,16 +126,24 @@ namespace Xamarin.Forms.Platform.iOS { if (Control == null) return; - +#if __MOBILE__ if (color == Color.Default) Control.BackgroundColor = _defaultColor; else Control.BackgroundColor = color.ToUIColor(); +#else + Control.Layer.BackgroundColor = color == Color.Default ? _defaultColor : color.ToCGColor(); +#endif } protected void SetNativeControl(TNativeView uiview) { +#if __MOBILE__ _defaultColor = uiview.BackgroundColor; +#else + uiview.WantsLayer = true; + _defaultColor = uiview.Layer.BackgroundColor; +#endif Control = uiview; if (Element.BackgroundColor != Color.Default) @@ -120,17 +154,19 @@ namespace Xamarin.Forms.Platform.iOS AddSubview(uiview); } - internal override void SendVisualElementInitialized(VisualElement element, UIView nativeView) +#if __MOBILE__ + internal override void SendVisualElementInitialized(VisualElement element, NativeView nativeView) { base.SendVisualElementInitialized(element, Control); } +#endif void UpdateIsEnabled() { if (Element == null || Control == null) return; - var uiControl = Control as UIControl; + var uiControl = Control as NativeControl; if (uiControl == null) return; uiControl.Enabled = Element.IsEnabled; diff --git a/Xamarin.Forms.Platform.iOS/VisualElementPackager.cs b/Xamarin.Forms.Platform.iOS/VisualElementPackager.cs index 1670176d..fa91cf6a 100644 --- a/Xamarin.Forms.Platform.iOS/VisualElementPackager.cs +++ b/Xamarin.Forms.Platform.iOS/VisualElementPackager.cs @@ -1,6 +1,11 @@ using System; +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { public class VisualElementPackager : IDisposable { @@ -13,7 +18,7 @@ namespace Xamarin.Forms.Platform.iOS public VisualElementPackager(IVisualElementRenderer renderer) { if (renderer == null) - throw new ArgumentNullException("renderer"); + throw new ArgumentNullException(nameof(renderer)); Renderer = renderer; renderer.ElementChanged += OnRendererElementChanged; @@ -100,8 +105,9 @@ namespace Xamarin.Forms.Platform.iOS continue; var nativeControl = childRenderer.NativeView; - +#if __MOBILE__ Renderer.NativeView.BringSubviewToFront(nativeControl); +#endif nativeControl.Layer.ZPosition = z * 1000; } } diff --git a/Xamarin.Forms.Platform.iOS/VisualElementRenderer.cs b/Xamarin.Forms.Platform.iOS/VisualElementRenderer.cs index 155f8b60..0e7af2a5 100644 --- a/Xamarin.Forms.Platform.iOS/VisualElementRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/VisualElementRenderer.cs @@ -1,13 +1,25 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using UIKit; -using Xamarin.Forms.PlatformConfiguration.iOSSpecific; using RectangleF = CoreGraphics.CGRect; using SizeF = CoreGraphics.CGSize; +#if __MOBILE__ +using Xamarin.Forms.PlatformConfiguration.iOSSpecific; +using UIKit; +using NativeView = UIKit.UIView; +using NativeViewController = UIKit.UIViewController; +using NativeColor = UIKit.UIColor; namespace Xamarin.Forms.Platform.iOS +#else +using AppKit; +using NativeView = AppKit.NSView; +using NativeViewController = AppKit.NSViewController; +using NativeColor = AppKit.NSColor; + +namespace Xamarin.Forms.Platform.MacOS +#endif { [Flags] public enum VisualElementRendererFlags @@ -17,9 +29,9 @@ namespace Xamarin.Forms.Platform.iOS AutoPackage = 1 << 2 } - public class VisualElementRenderer<TElement> : UIView, IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement + public class VisualElementRenderer<TElement> : NativeView, IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement { - readonly UIColor _defaultColor = UIColor.Clear; + readonly NativeColor _defaultColor = NativeColor.Clear; readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>(); @@ -31,21 +43,30 @@ namespace Xamarin.Forms.Platform.iOS VisualElementPackager _packager; VisualElementTracker _tracker; +#if __MOBILE__ UIVisualEffectView _blur; BlurEffectStyle _previousBlur; +#endif protected VisualElementRenderer() : base(RectangleF.Empty) { _propertyChangedHandler = OnElementPropertyChanged; - BackgroundColor = UIColor.Clear; +#if __MOBILE__ + BackgroundColor = _defaultColor; +#else + WantsLayer = true; + Layer.BackgroundColor = _defaultColor.CGColor; +#endif } +#if __MOBILE__ // prevent possible crashes in overrides - public sealed override UIColor BackgroundColor + public sealed override NativeColor BackgroundColor { get { return base.BackgroundColor; } set { base.BackgroundColor = value; } } +#endif public TElement Element { get; private set; } @@ -73,7 +94,7 @@ namespace Xamarin.Forms.Platform.iOS } } - public static void RegisterEffect(Effect effect, UIView container, UIView control = null) + public static void RegisterEffect(Effect effect, NativeView container, NativeView control = null) { var platformEffect = effect as PlatformEffect; if (platformEffect == null) @@ -106,10 +127,7 @@ namespace Xamarin.Forms.Platform.iOS return NativeView.GetSizeRequest(widthConstraint, heightConstraint); } - public UIView NativeView - { - get { return this; } - } + public NativeView NativeView => this; void IVisualElementRenderer.SetElement(VisualElement element) { @@ -118,13 +136,10 @@ namespace Xamarin.Forms.Platform.iOS public void SetElementSize(Size size) { - Layout.LayoutChildIntoBoundingRegion(Element, new Rectangle(Element.X, Element.Y, size.Width, size.Height)); + Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(Element, new Rectangle(Element.X, Element.Y, size.Width, size.Height)); } - public virtual UIViewController ViewController - { - get { return null; } - } + public virtual NativeViewController ViewController => null; public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged; @@ -175,6 +190,7 @@ namespace Xamarin.Forms.Platform.iOS SetAutomationId(Element.AutomationId); } +#if __MOBILE__ public override SizeF SizeThatFits(SizeF size) { return new SizeF(0, 0); @@ -190,6 +206,16 @@ namespace Xamarin.Forms.Platform.iOS Superview.Add(_blur); } } +#else + public override void MouseDown(NSEvent theEvent) + { + bool inViewCell = IsOnViewCell(Element); + + if (Element.InputTransparent || inViewCell) + base.MouseDown(theEvent); + } +#endif + protected override void Dispose(bool disposing) { if ((_flags & VisualElementRendererFlags.Disposed) != 0) @@ -227,22 +253,23 @@ namespace Xamarin.Forms.Platform.iOS for (var i = 0; i < _elementChangedHandlers.Count; i++) _elementChangedHandlers[i](this, args); - var changed = ElementChanged; - if (changed != null) - changed(this, e); - + ElementChanged?.Invoke(this, e); +#if __MOBILE__ if (e.NewElement != null) SetBlur((BlurEffectStyle)e.NewElement.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty)); +#endif } protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) SetBackgroundColor(Element.BackgroundColor); - else if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName) + else if (e.PropertyName == Xamarin.Forms.Layout.IsClippedToBoundsProperty.PropertyName) UpdateClipToBounds(); +#if __MOBILE__ else if (e.PropertyName == PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty.PropertyName) SetBlur((BlurEffectStyle)Element.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty)); +#endif } protected virtual void OnRegisterEffect(PlatformEffect effect) @@ -258,11 +285,20 @@ namespace Xamarin.Forms.Platform.iOS protected virtual void SetBackgroundColor(Color color) { if (color == Color.Default) +#if __MOBILE__ + BackgroundColor = _defaultColor; else BackgroundColor = color.ToUIColor(); + +#else + Layer.BackgroundColor = _defaultColor.CGColor; + else + Layer.BackgroundColor = color.ToCGColor(); +#endif } +#if __MOBILE__ protected virtual void SetBlur(BlurEffectStyle blur) { if (_previousBlur == blur) @@ -300,21 +336,35 @@ namespace Xamarin.Forms.Platform.iOS _blur = new UIVisualEffectView(blurEffect); LayoutSubviews(); } +#endif protected virtual void UpdateNativeWidget() { } - internal virtual void SendVisualElementInitialized(VisualElement element, UIView nativeView) + internal virtual void SendVisualElementInitialized(VisualElement element, NativeView nativeView) { element.SendViewInitialized(nativeView); } void UpdateClipToBounds() { +#if __MOBILE__ var clippableLayout = Element as Layout; if (clippableLayout != null) ClipsToBounds = clippableLayout.IsClippedToBounds; +#endif + } + + static bool IsOnViewCell(Element element) + { + + if (element.Parent == null) + return false; + else if (element.Parent is ViewCell) + return true; + else + return IsOnViewCell(element.Parent); } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs index 5b41ca84..29840e2d 100644 --- a/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs +++ b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs @@ -3,8 +3,13 @@ using System.ComponentModel; using System.Drawing; using System.Threading; using CoreAnimation; +#if __MOBILE__ namespace Xamarin.Forms.Platform.iOS +#else + +namespace Xamarin.Forms.Platform.MacOS +#endif { public class VisualElementTracker : IDisposable { @@ -18,7 +23,9 @@ namespace Xamarin.Forms.Platform.iOS // Track these by hand because the calls down into iOS are too expensive bool _isInteractive; Rectangle _lastBounds; - +#if !__MOBILE__ + Rectangle _lastParentBounds; +#endif CALayer _layer; int _updateCount; @@ -107,12 +114,17 @@ namespace Xamarin.Forms.Platform.iOS var shouldInteract = !view.InputTransparent && view.IsEnabled; if (_isInteractive != shouldInteract) { +#if __MOBILE__ uiview.UserInteractionEnabled = shouldInteract; +#endif _isInteractive = shouldInteract; } var boundsChanged = _lastBounds != view.Bounds; - +#if !__MOBILE__ + var viewParent = view.RealParent as VisualElement; + var parentBoundsChanged = _lastParentBounds != (viewParent == null ? Rectangle.Zero : viewParent.Bounds); +#endif var thread = !boundsChanged && !caLayer.Frame.IsEmpty; var anchorX = (float)view.AnchorX; @@ -136,13 +148,17 @@ namespace Xamarin.Forms.Platform.iOS { if (updateTarget != _updateCount) return; - +#if __MOBILE__ var visualElement = view; +#endif var parent = view.RealParent; var shouldRelayoutSublayers = false; if (isVisible && caLayer.Hidden) { +#if !__MOBILE__ + uiview.Hidden = false; +#endif caLayer.Hidden = false; if (!caLayer.Frame.IsEmpty) shouldRelayoutSublayers = true; @@ -150,6 +166,9 @@ namespace Xamarin.Forms.Platform.iOS if (!isVisible && !caLayer.Hidden) { +#if !__MOBILE__ + uiview.Hidden = true; +#endif caLayer.Hidden = true; shouldRelayoutSublayers = true; } @@ -157,11 +176,26 @@ namespace Xamarin.Forms.Platform.iOS // ripe for optimization var transform = CATransform3D.Identity; +#if __MOBILE__ + bool shouldUpdate = (!(visualElement is Page) || visualElement is ContentPage) && width > 0 && height > 0 && parent != null && boundsChanged; +#else + // We don't care if it's a page or not since bounds of the window can change + // TODO: Find why it doesn't work to check if the parentsBounds changed and remove true; + parentBoundsChanged = true; + bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged); +#endif // Dont ever attempt to actually change the layout of a Page unless it is a ContentPage // iOS is a really big fan of you not actually modifying the View's of the UIViewControllers - if ((!(visualElement is Page) || visualElement is ContentPage) && width > 0 && height > 0 && parent != null && boundsChanged) + if (shouldUpdate) { +#if __MOBILE__ var target = new RectangleF(x, y, width, height); +#else + var visualParent = parent as VisualElement; + float newY = visualParent == null ? y : Math.Max(0, (float)(visualParent.Height - y - view.Height)); + var target = new RectangleF(x, newY, width, height); +#endif + // must reset transform prior to setting frame... caLayer.Transform = transform; uiview.Frame = target; @@ -170,11 +204,17 @@ namespace Xamarin.Forms.Platform.iOS } else if (width <= 0 || height <= 0) { + //TODO: FInd why it doesn't work +#if __MOBILE__ caLayer.Hidden = true; +#endif return; } - +#if __MOBILE__ caLayer.AnchorPoint = new PointF(anchorX, anchorY); +#else + caLayer.AnchorPoint = new PointF(anchorX - 0.5f, anchorY - 0.5f); +#endif caLayer.Opacity = opacity; const double epsilon = 0.001; @@ -209,6 +249,9 @@ namespace Xamarin.Forms.Platform.iOS update(); _lastBounds = view.Bounds; +#if !__MOBILE__ + _lastParentBounds = viewParent?.Bounds ?? Rectangle.Zero; +#endif } void SetElement(VisualElement oldElement, VisualElement newElement) @@ -239,14 +282,18 @@ namespace Xamarin.Forms.Platform.iOS if (_layer == null) { +#if !__MOBILE__ + Renderer.NativeView.WantsLayer = true; +#endif _layer = Renderer.NativeView.Layer; +#if __MOBILE__ _isInteractive = Renderer.NativeView.UserInteractionEnabled; +#endif } OnUpdateNativeControl(_layer); - if (NativeControlUpdated != null) - NativeControlUpdated(this, EventArgs.Empty); + NativeControlUpdated?.Invoke(this, EventArgs.Empty); } } }
\ No newline at end of file diff --git a/Xamarin.Forms.sln b/Xamarin.Forms.sln index 12ba3e80..8eb22627 100644 --- a/Xamarin.Forms.sln +++ b/Xamarin.Forms.sln @@ -143,10 +143,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Pages.Azure", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PagesGallery.iOS", "PagesGallery\PagesGallery.iOS\PagesGallery.iOS.csproj", "{392156B2-760A-4EE3-A822-CABD3238A21D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Platform.MacOS", "Xamarin.Forms.Platform.MacOS\Xamarin.Forms.Platform.MacOS.csproj", "{C0059C45-EA1E-42F3-8A0E-794BB547EC3C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Maps.MacOS", "Xamarin.Forms.Maps.MacOS\Xamarin.Forms.Maps.MacOS.csproj", "{C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Xamarin.Forms.Controls.Issues\Xamarin.Forms.Controls.Issues.Shared\Xamarin.Forms.Controls.Issues.Shared.projitems*{0a39a74b-6f7a-4d41-84f2-b0ccdce899df}*SharedItemsImports = 4 Xamarin.Forms.Controls.Issues\Xamarin.Forms.Controls.Issues.Shared\Xamarin.Forms.Controls.Issues.Shared.projitems*{0f0db9cc-ea65-429c-9363-38624bf8f49c}*SharedItemsImports = 13 + Xamarin.Forms.Controls.Issues\Xamarin.Forms.Controls.Issues.Shared\Xamarin.Forms.Controls.Issues.Shared.projitems*{9db0cabb-24cc-4538-88ec-6e0a0fe40248}*SharedItemsImports = 4 Xamarin.Forms.Controls.Issues\Xamarin.Forms.Controls.Issues.Shared\Xamarin.Forms.Controls.Issues.Shared.projitems*{a34ebe01-25bf-4e69-a2dc-2288dc625541}*SharedItemsImports = 4 Xamarin.Forms.Controls.Issues\Xamarin.Forms.Controls.Issues.Shared\Xamarin.Forms.Controls.Issues.Shared.projitems*{cb9c96ce-125c-4a68-b6a1-c3ff1fbf93e1}*SharedItemsImports = 4 docs\APIDocs.projitems*{dc1f3933-ac99-4887-8b09-e13c2b346d4f}*SharedItemsImports = 13 @@ -1494,6 +1499,62 @@ Global {392156B2-760A-4EE3-A822-CABD3238A21D}.Release|Templates.ActiveCfg = Release|iPhone {392156B2-760A-4EE3-A822-CABD3238A21D}.Release|x64.ActiveCfg = Release|iPhone {392156B2-760A-4EE3-A822-CABD3238A21D}.Release|x86.ActiveCfg = Release|iPhone + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|ARM.Build.0 = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|iPhone.Build.0 = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|Templates.ActiveCfg = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|Templates.Build.0 = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|x64.Build.0 = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Debug|x86.Build.0 = Debug|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|Any CPU.Build.0 = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|ARM.ActiveCfg = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|ARM.Build.0 = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|iPhone.ActiveCfg = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|iPhone.Build.0 = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|Templates.ActiveCfg = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|Templates.Build.0 = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|x64.ActiveCfg = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|x64.Build.0 = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|x86.ActiveCfg = Release|Any CPU + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C}.Release|x86.Build.0 = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|ARM.Build.0 = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|iPhone.Build.0 = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|Templates.ActiveCfg = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|Templates.Build.0 = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|x64.ActiveCfg = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|x64.Build.0 = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|x86.ActiveCfg = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Debug|x86.Build.0 = Debug|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|Any CPU.Build.0 = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|ARM.ActiveCfg = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|ARM.Build.0 = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|iPhone.ActiveCfg = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|iPhone.Build.0 = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|Templates.ActiveCfg = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|Templates.Build.0 = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|x64.ActiveCfg = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|x64.Build.0 = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|x86.ActiveCfg = Release|Any CPU + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1551,5 +1612,7 @@ Global {42DB052E-0909-45D2-8240-187F99F393FB} = {29AC50BF-B4FB-450B-9386-0C5AD4B84226} {C9696465-7657-4843-872E-3C01891C4A9B} = {9AD757F5-E57A-459D-A0A7-E0675E045B84} {392156B2-760A-4EE3-A822-CABD3238A21D} = {80BAC3FB-357A-4D05-A050-9F234DF49C97} + {C0059C45-EA1E-42F3-8A0E-794BB547EC3C} = {29AC50BF-B4FB-450B-9386-0C5AD4B84226} + {C3C24A6D-2D0C-4053-9FCC-E54FF9CA1884} = {132FB9A4-613F-44CE-95D5-758D32D231DD} EndGlobalSection EndGlobal diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration.macOSSpecific/TabbedPage.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration.macOSSpecific/TabbedPage.xml new file mode 100644 index 00000000..550ca297 --- /dev/null +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration.macOSSpecific/TabbedPage.xml @@ -0,0 +1,176 @@ +<Type Name="TabbedPage" FullName="Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage"> + <TypeSignature Language="C#" Value="public static class TabbedPage" /> + <TypeSignature Language="ILAsm" Value=".class public auto ansi abstract sealed beforefieldinit TabbedPage extends System.Object" /> + <AssemblyInfo> + <AssemblyName>Xamarin.Forms.Core</AssemblyName> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <Base> + <BaseTypeName>System.Object</BaseTypeName> + </Base> + <Interfaces /> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + <Members> + <Member MemberName="GetTabsStyle"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.TabsStyle GetTabsStyle (Xamarin.Forms.BindableObject element);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig valuetype Xamarin.Forms.TabsStyle GetTabsStyle(class Xamarin.Forms.BindableObject element) cil managed" /> + <MemberType>Method</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="element" Type="Xamarin.Forms.BindableObject" /> + </Parameters> + <Docs> + <param name="element">To be added.</param> + <summary>To be added.</summary> + <returns>To be added.</returns> + <remarks>To be added.</remarks> + </Docs> + </Member> + <Member MemberName="GetTabsStyle"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.TabsStyle GetTabsStyle (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig valuetype Xamarin.Forms.TabsStyle GetTabsStyle(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>Method</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + <returns>To be added.</returns> + <remarks>To be added.</remarks> + </Docs> + </Member> + <Member MemberName="HideTabs"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> HideTabs (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> HideTabs(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>Method</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + <returns>To be added.</returns> + <remarks>To be added.</remarks> + </Docs> + </Member> + <Member MemberName="SetShowTabs"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> SetShowTabs (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config, Xamarin.Forms.TabsStyle value);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> SetShowTabs(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config, valuetype Xamarin.Forms.TabsStyle value) cil managed" /> + <MemberType>Method</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + <Parameter Name="value" Type="Xamarin.Forms.TabsStyle" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <param name="value">To be added.</param> + <summary>To be added.</summary> + <returns>To be added.</returns> + <remarks>To be added.</remarks> + </Docs> + </Member> + <Member MemberName="SetTabsStyle"> + <MemberSignature Language="C#" Value="public static void SetTabsStyle (Xamarin.Forms.BindableObject element, Xamarin.Forms.TabsStyle value);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig void SetTabsStyle(class Xamarin.Forms.BindableObject element, valuetype Xamarin.Forms.TabsStyle value) cil managed" /> + <MemberType>Method</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>System.Void</ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="element" Type="Xamarin.Forms.BindableObject" /> + <Parameter Name="value" Type="Xamarin.Forms.TabsStyle" /> + </Parameters> + <Docs> + <param name="element">To be added.</param> + <param name="value">To be added.</param> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + </Member> + <Member MemberName="ShowTabs"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> ShowTabs (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> ShowTabs(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>Method</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + <returns>To be added.</returns> + <remarks>To be added.</remarks> + </Docs> + </Member> + <Member MemberName="ShowTabsOnNavigation"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> ShowTabsOnNavigation (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> ShowTabsOnNavigation(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>Method</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + <returns>To be added.</returns> + <remarks>To be added.</remarks> + </Docs> + </Member> + <Member MemberName="TabsStyleProperty"> + <MemberSignature Language="C#" Value="public static readonly Xamarin.Forms.BindableProperty TabsStyleProperty;" /> + <MemberSignature Language="ILAsm" Value=".field public static initonly class Xamarin.Forms.BindableProperty TabsStyleProperty" /> + <MemberType>Field</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.BindableProperty</ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + </Member> + </Members> +</Type> diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration/macOS.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration/macOS.xml new file mode 100644 index 00000000..5012c5e8 --- /dev/null +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms.PlatformConfiguration/macOS.xml @@ -0,0 +1,35 @@ +<Type Name="macOS" FullName="Xamarin.Forms.PlatformConfiguration.macOS"> + <TypeSignature Language="C#" Value="public sealed class macOS : Xamarin.Forms.IConfigPlatform" /> + <TypeSignature Language="ILAsm" Value=".class public auto ansi sealed beforefieldinit macOS extends System.Object implements class Xamarin.Forms.IConfigPlatform" /> + <AssemblyInfo> + <AssemblyName>Xamarin.Forms.Core</AssemblyName> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <Base> + <BaseTypeName>System.Object</BaseTypeName> + </Base> + <Interfaces> + <Interface> + <InterfaceName>Xamarin.Forms.IConfigPlatform</InterfaceName> + </Interface> + </Interfaces> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + <Members> + <Member MemberName=".ctor"> + <MemberSignature Language="C#" Value="public macOS ();" /> + <MemberSignature Language="ILAsm" Value=".method public hidebysig specialname rtspecialname instance void .ctor() cil managed" /> + <MemberType>Constructor</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <Parameters /> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + </Member> + </Members> +</Type> diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/Device.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/Device.xml index 16d13483..37fbad36 100644 --- a/docs/Xamarin.Forms.Core/Xamarin.Forms/Device.xml +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/Device.xml @@ -159,6 +159,21 @@ Device.BeginInvokeOnMainThread (() => { <remarks>To be added.</remarks> </Docs> </Member> + <Member MemberName="macOS"> + <MemberSignature Language="C#" Value="public const string macOS;" /> + <MemberSignature Language="ILAsm" Value=".field public static literal string macOS" /> + <MemberType>Field</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>System.String</ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + </Member> <Member MemberName="OnPlatform"> <MemberSignature Language="C#" Value="public static void OnPlatform (Action iOS = null, Action Android = null, Action WinPhone = null, Action Default = null);" /> <MemberSignature Language="ILAsm" Value=".method public static hidebysig void OnPlatform(class System.Action iOS, class System.Action Android, class System.Action WinPhone, class System.Action Default) cil managed" /> diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/TabsStyle.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/TabsStyle.xml new file mode 100644 index 00000000..7565a1f1 --- /dev/null +++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/TabsStyle.xml @@ -0,0 +1,87 @@ +<Type Name="TabsStyle" FullName="Xamarin.Forms.TabsStyle"> + <TypeSignature Language="C#" Value="public enum TabsStyle" /> + <TypeSignature Language="ILAsm" Value=".class public auto ansi sealed TabsStyle extends System.Enum" /> + <AssemblyInfo> + <AssemblyName>Xamarin.Forms.Core</AssemblyName> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <Base> + <BaseTypeName>System.Enum</BaseTypeName> + </Base> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + <Members> + <Member MemberName="Default"> + <MemberSignature Language="C#" Value="Default" /> + <MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Forms.TabsStyle Default = int32(0)" /> + <MemberType>Field</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + </Docs> + </Member> + <Member MemberName="Hidden"> + <MemberSignature Language="C#" Value="Hidden" /> + <MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Forms.TabsStyle Hidden = int32(1)" /> + <MemberType>Field</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + </Docs> + </Member> + <Member MemberName="Icons"> + <MemberSignature Language="C#" Value="Icons" /> + <MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Forms.TabsStyle Icons = int32(2)" /> + <MemberType>Field</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + </Docs> + </Member> + <Member MemberName="OnBottom"> + <MemberSignature Language="C#" Value="OnBottom" /> + <MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Forms.TabsStyle OnBottom = int32(4)" /> + <MemberType>Field</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + </Docs> + </Member> + <Member MemberName="OnNavigation"> + <MemberSignature Language="C#" Value="OnNavigation" /> + <MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Forms.TabsStyle OnNavigation = int32(3)" /> + <MemberType>Field</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + </Docs> + </Member> + </Members> +</Type> diff --git a/docs/Xamarin.Forms.Core/index.xml b/docs/Xamarin.Forms.Core/index.xml index 1b6cb745..89af75c2 100644 --- a/docs/Xamarin.Forms.Core/index.xml +++ b/docs/Xamarin.Forms.Core/index.xml @@ -108,6 +108,9 @@ <AttributeName>System.Runtime.CompilerServices.InternalsVisibleTo("Xamarin.Forms.Core.Windows.UITests")</AttributeName> </Attribute> <Attribute> + <AttributeName>System.Runtime.CompilerServices.InternalsVisibleTo("Xamarin.Forms.Core.macOS.UITests")</AttributeName> + </Attribute> + <Attribute> <AttributeName>System.Runtime.CompilerServices.InternalsVisibleTo("Xamarin.Forms.iOS.UITests")</AttributeName> </Attribute> <Attribute> @@ -396,6 +399,7 @@ <Type Name="TableSectionBase" Kind="Class" /> <Type Name="TableSectionBase`1" DisplayName="TableSectionBase<T>" Kind="Class" /> <Type Name="TableView" Kind="Class" /> + <Type Name="TabsStyle" Kind="Enumeration" /> <Type Name="TapGestureRecognizer" Kind="Class" /> <Type Name="TappedEventArgs" Kind="Class" /> <Type Name="TargetIdiom" Kind="Enumeration" /> @@ -461,6 +465,7 @@ <Namespace Name="Xamarin.Forms.PlatformConfiguration"> <Type Name="Android" Kind="Class" /> <Type Name="iOS" Kind="Class" /> + <Type Name="macOS" Kind="Class" /> <Type Name="Tizen" Kind="Class" /> <Type Name="Windows" Kind="Class" /> </Namespace> @@ -479,6 +484,9 @@ <Type Name="StatusBarTextColorMode" Kind="Enumeration" /> <Type Name="VisualElement" Kind="Class" /> </Namespace> + <Namespace Name="Xamarin.Forms.PlatformConfiguration.macOSSpecific"> + <Type Name="TabbedPage" Kind="Class" /> + </Namespace> <Namespace Name="Xamarin.Forms.PlatformConfiguration.WindowsSpecific"> <Type Name="CollapseStyle" Kind="Enumeration" /> <Type Name="MasterDetailPage" Kind="Class" /> @@ -1627,6 +1635,113 @@ <Targets> <Target Type="T:Xamarin.Forms.IPlatformElementConfiguration`2" /> </Targets> + <Member MemberName="GetTabsStyle"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.TabsStyle GetTabsStyle (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig valuetype Xamarin.Forms.TabsStyle GetTabsStyle(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>ExtensionMethod</MemberType> + <ReturnValue> + <ReturnType>Xamarin.Forms.TabsStyle</ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + </Docs> + <Link Type="Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage" Member="M:Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage.GetTabsStyle(Xamarin.Forms.IPlatformElementConfiguration{Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage})" /> + </Member> + </ExtensionMethod> + <ExtensionMethod> + <Targets> + <Target Type="T:Xamarin.Forms.IPlatformElementConfiguration`2" /> + </Targets> + <Member MemberName="HideTabs"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> HideTabs (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> HideTabs(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>ExtensionMethod</MemberType> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + </Docs> + <Link Type="Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage" Member="M:Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage.HideTabs(Xamarin.Forms.IPlatformElementConfiguration{Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage})" /> + </Member> + </ExtensionMethod> + <ExtensionMethod> + <Targets> + <Target Type="T:Xamarin.Forms.IPlatformElementConfiguration`2" /> + </Targets> + <Member MemberName="SetShowTabs"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> SetShowTabs (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config, Xamarin.Forms.TabsStyle value);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> SetShowTabs(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config, valuetype Xamarin.Forms.TabsStyle value) cil managed" /> + <MemberType>ExtensionMethod</MemberType> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + <Parameter Name="value" Type="Xamarin.Forms.TabsStyle" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <param name="value">To be added.</param> + <summary>To be added.</summary> + </Docs> + <Link Type="Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage" Member="M:Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage.SetShowTabs(Xamarin.Forms.IPlatformElementConfiguration{Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage},Xamarin.Forms.TabsStyle)" /> + </Member> + </ExtensionMethod> + <ExtensionMethod> + <Targets> + <Target Type="T:Xamarin.Forms.IPlatformElementConfiguration`2" /> + </Targets> + <Member MemberName="ShowTabs"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> ShowTabs (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> ShowTabs(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>ExtensionMethod</MemberType> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + </Docs> + <Link Type="Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage" Member="M:Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage.ShowTabs(Xamarin.Forms.IPlatformElementConfiguration{Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage})" /> + </Member> + </ExtensionMethod> + <ExtensionMethod> + <Targets> + <Target Type="T:Xamarin.Forms.IPlatformElementConfiguration`2" /> + </Targets> + <Member MemberName="ShowTabsOnNavigation"> + <MemberSignature Language="C#" Value="public static Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> ShowTabsOnNavigation (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage> config);" /> + <MemberSignature Language="ILAsm" Value=".method public static hidebysig class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> ShowTabsOnNavigation(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.macOS, class Xamarin.Forms.TabbedPage> config) cil managed" /> + <MemberType>ExtensionMethod</MemberType> + <ReturnValue> + <ReturnType>Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage></ReturnType> + </ReturnValue> + <Parameters> + <Parameter Name="config" Type="Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage>" RefType="this" /> + </Parameters> + <Docs> + <param name="config">To be added.</param> + <summary>To be added.</summary> + </Docs> + <Link Type="Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage" Member="M:Xamarin.Forms.PlatformConfiguration.macOSSpecific.TabbedPage.ShowTabsOnNavigation(Xamarin.Forms.IPlatformElementConfiguration{Xamarin.Forms.PlatformConfiguration.macOS,Xamarin.Forms.TabbedPage})" /> + </Member> + </ExtensionMethod> + <ExtensionMethod> + <Targets> + <Target Type="T:Xamarin.Forms.IPlatformElementConfiguration`2" /> + </Targets> <Member MemberName="CollapsedPaneWidth"> <MemberSignature Language="C#" Value="public static double CollapsedPaneWidth (this Xamarin.Forms.IPlatformElementConfiguration<Xamarin.Forms.PlatformConfiguration.Windows,Xamarin.Forms.MasterDetailPage> config);" /> <MemberSignature Language="ILAsm" Value=".method public static hidebysig float64 CollapsedPaneWidth(class Xamarin.Forms.IPlatformElementConfiguration`2<class Xamarin.Forms.PlatformConfiguration.Windows, class Xamarin.Forms.MasterDetailPage> config) cil managed" /> diff --git a/docs/Xamarin.Forms.Core/ns-Xamarin.Forms.PlatformConfiguration.macOSSpecific.xml b/docs/Xamarin.Forms.Core/ns-Xamarin.Forms.PlatformConfiguration.macOSSpecific.xml new file mode 100644 index 00000000..644ef854 --- /dev/null +++ b/docs/Xamarin.Forms.Core/ns-Xamarin.Forms.PlatformConfiguration.macOSSpecific.xml @@ -0,0 +1,6 @@ +<Namespace Name="Xamarin.Forms.PlatformConfiguration.macOSSpecific"> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> +</Namespace> diff --git a/docs/Xamarin.Forms.Maps/index.xml b/docs/Xamarin.Forms.Maps/index.xml index c8db2882..775e9db2 100644 --- a/docs/Xamarin.Forms.Maps/index.xml +++ b/docs/Xamarin.Forms.Maps/index.xml @@ -33,7 +33,7 @@ <AttributeName>System.Resources.NeutralResourcesLanguage("en")</AttributeName> </Attribute> <Attribute> - <AttributeName>System.Runtime.CompilerServices.CompilationRelaxations(8)</AttributeName> + <AttributeName>System.Runtime.CompilerServices.InternalsVisibleTo("Xamarin.Forms.Maps.macOS")</AttributeName> </Attribute> <Attribute> <AttributeName>System.Runtime.CompilerServices.InternalsVisibleTo("Xamarin.Forms.Maps.iOS")</AttributeName> |